sales & grn fixers
This commit is contained in:
@@ -10,6 +10,9 @@ export interface ListCustomersParams {
|
||||
q?: string
|
||||
status?: EntityStatus
|
||||
customerType?: CustomerType
|
||||
/** The system cash customer (walk-in / one-time buyers) is left out by default — pass true only
|
||||
* where the list is used to resolve names for existing documents. */
|
||||
includeCashCustomer?: boolean
|
||||
}
|
||||
|
||||
export const customersApi = {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// GRN configuration. Singleton - no id. A saved `defaultWarehouseId` pins every GRN to that
|
||||
// warehouse (the server forces it too); with none saved, New GRN shows a warehouse picker.
|
||||
import { apiRequestWithETag } from "@/lib/api-client"
|
||||
import { ApiResult } from "@/types/common"
|
||||
import { GrnConfig, UpdateGrnConfigRequest } from "@/types/grn"
|
||||
|
||||
export const grnConfigApi = {
|
||||
get(): Promise<ApiResult<GrnConfig>> {
|
||||
return apiRequestWithETag<GrnConfig>("/grn-config")
|
||||
},
|
||||
|
||||
update(request: UpdateGrnConfigRequest, ifMatch: string): Promise<ApiResult<GrnConfig>> {
|
||||
return apiRequestWithETag<GrnConfig>("/grn-config", { method: "PUT", body: request, ifMatch })
|
||||
},
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { apiRequest, apiRequestWithETag, buildQuery } from "@/lib/api-client"
|
||||
import { ApiResult, PagedResponse } from "@/types/common"
|
||||
import {
|
||||
CreateBulkSalesInvoicePaymentRequest,
|
||||
CreateSalesInvoicePaymentRequest,
|
||||
CreateSalesInvoiceRequest,
|
||||
CreateSalesSlipRequest,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
SalesSlipStatus,
|
||||
SalesSlipPostingCheck,
|
||||
SalesSlipSummary,
|
||||
SalesItemStock,
|
||||
SalesStockAvailability,
|
||||
SalesSuggestedPrice,
|
||||
UpdateSalesInvoiceRequest,
|
||||
@@ -58,6 +60,11 @@ export const salesApi = {
|
||||
return apiRequest<SalesStockAvailability>(`/sales-invoices/stock-availability${buildQuery({ itemId, warehouseId })}`)
|
||||
},
|
||||
|
||||
/** Issuable stock for every item at a warehouse — New Sale's "Add item" dropdown. */
|
||||
getStockSummary(warehouseId: number): Promise<SalesItemStock[]> {
|
||||
return apiRequest<SalesItemStock[]>(`/sales-invoices/stock-summary${buildQuery({ warehouseId })}`)
|
||||
},
|
||||
|
||||
getInvoice(salesInvoiceId: number): Promise<ApiResult<SalesInvoice>> {
|
||||
return apiRequestWithETag<SalesInvoice>(`/sales-invoices/${salesInvoiceId}`)
|
||||
},
|
||||
@@ -87,6 +94,11 @@ export const salesApi = {
|
||||
return apiRequest<SalesInvoicePayment>(`/sales-invoices/${salesInvoiceId}/payments`, { method: "POST", body: request })
|
||||
},
|
||||
|
||||
/** Pay several invoices of one customer in one go — one GL journal; returns a payment per invoice/account split. */
|
||||
payInvoicesBulk(request: CreateBulkSalesInvoicePaymentRequest): Promise<SalesInvoicePayment[]> {
|
||||
return apiRequest<SalesInvoicePayment[]>("/sales-invoices/payments/bulk", { method: "POST", body: request })
|
||||
},
|
||||
|
||||
/** Payment history for an invoice, newest first. */
|
||||
listInvoicePayments(salesInvoiceId: number): Promise<SalesInvoicePayment[]> {
|
||||
return apiRequest<SalesInvoicePayment[]>(`/sales-invoices/${salesInvoiceId}/payments`)
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
import { ItemListItem } from "@/types/master-data"
|
||||
|
||||
/** Rounds a sales unit price to 2dp, half away from zero (matches the server's SalesPricingService).
|
||||
* Shifts via the exponent string rather than `* 100` so binary-float cases like 1.005 round up. */
|
||||
export function roundPrice(value: number): number {
|
||||
const shifted = Number(`${Math.round(Number(`${Math.abs(value)}e2`))}e-2`)
|
||||
// Values JS prints in exponent form (e.g. 1e-7) can't take the string shift — those are ~0 anyway.
|
||||
const rounded = Number.isFinite(shifted) ? shifted : Math.round(Math.abs(value) * 100) / 100
|
||||
return value < 0 ? -rounded : rounded
|
||||
}
|
||||
|
||||
/** A unit price input's value as a 2dp price, or null when cleared. */
|
||||
export function parsePriceInput(value: string): number | null {
|
||||
if (value === "") return null
|
||||
const n = Number(value)
|
||||
return Number.isFinite(n) ? roundPrice(n) : null
|
||||
}
|
||||
|
||||
export function getSuggestedUnitPrice(items: ItemListItem[], itemId: number | null | undefined): number | null {
|
||||
if (!itemId) return null
|
||||
return items.find((item) => item.itemId === itemId)?.sellingFlatPrice ?? null
|
||||
const price = items.find((item) => item.itemId === itemId)?.sellingFlatPrice
|
||||
return price === null || price === undefined ? null : roundPrice(price)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user