offline sales upload
This commit is contained in:
@@ -1287,6 +1287,118 @@ export async function getSalesSummary(opts: {
|
||||
return res.details;
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// OFFLINE (IN-STORE) SALES
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export interface SaleTemplateRow {
|
||||
productid: number;
|
||||
productname: string;
|
||||
productunit: string;
|
||||
unitvalue: string;
|
||||
categoryname: string;
|
||||
currentstock: number;
|
||||
price: number;
|
||||
taxpercent: number;
|
||||
}
|
||||
|
||||
export interface SaleTemplate {
|
||||
tenantid: number;
|
||||
locationid: number;
|
||||
locationname: string;
|
||||
products: SaleTemplateRow[];
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /products/getsaletemplate — every product stocked at one outlet, with its
|
||||
* live ledger balance and price.
|
||||
*
|
||||
* This is what the offline-sales spreadsheet is built from, and the reason it
|
||||
* has to be generated rather than hand-written: `productid` is the only usable
|
||||
* key for a product. Across the live catalogue 6,245 products share just 93
|
||||
* distinct `productsku` values (one tenant has 463 products all carrying sku
|
||||
* "1"), so a store cannot identify a product by SKU, and product names are not
|
||||
* unique enough either. Pre-filling productid removes the problem entirely.
|
||||
*/
|
||||
export async function getSaleTemplate(opts: {
|
||||
tenantid: number;
|
||||
locationid: number;
|
||||
}): Promise<SaleTemplate> {
|
||||
const res = await fiestaGet<{ details: SaleTemplate | null }>('products/getsaletemplate', {
|
||||
tenantid: opts.tenantid,
|
||||
locationid: opts.locationid,
|
||||
});
|
||||
if (!res?.details) throw new Error('No products are stocked at this outlet yet.');
|
||||
return res.details;
|
||||
}
|
||||
|
||||
export interface OfflineSaleItemInput {
|
||||
productid: number;
|
||||
productname?: string;
|
||||
qtysold: number;
|
||||
unitprice?: number;
|
||||
discountamount?: number;
|
||||
taxpercent?: number;
|
||||
}
|
||||
|
||||
export interface OfflineSaleBillInput {
|
||||
billno?: string;
|
||||
saledate?: string;
|
||||
paymentmode?: string;
|
||||
customername?: string;
|
||||
customermobile?: string;
|
||||
remarks?: string;
|
||||
items: OfflineSaleItemInput[];
|
||||
}
|
||||
|
||||
export interface OfflineSaleResult {
|
||||
billno: string;
|
||||
status: 'imported' | 'duplicate' | 'failed';
|
||||
orderid: string;
|
||||
orderheaderid: number;
|
||||
itemcount: number;
|
||||
amount: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface OfflineSalesUploadResponse {
|
||||
imported: number;
|
||||
duplicate: number;
|
||||
failed: number;
|
||||
totalamount: number;
|
||||
results: OfflineSaleResult[];
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /orders/uploadofflinesales — import counter sales as real orders.
|
||||
*
|
||||
* Each bill is committed independently, so the response reports a per-bill
|
||||
* outcome and a partially-good spreadsheet still imports its good bills. A
|
||||
* thrown error therefore means nothing at all was attempted (bad outlet, empty
|
||||
* batch); individual rejections come back inside `results`.
|
||||
*
|
||||
* Re-uploading the same file is safe: the backend records each bill number and
|
||||
* refuses one it has already imported rather than deducting the stock twice.
|
||||
*/
|
||||
export async function uploadOfflineSales(input: {
|
||||
tenantid: number;
|
||||
locationid: number;
|
||||
userid?: number;
|
||||
bills: OfflineSaleBillInput[];
|
||||
}): Promise<OfflineSalesUploadResponse> {
|
||||
const res = await fiestaSend<{ details: OfflineSalesUploadResponse }>(
|
||||
'orders/uploadofflinesales',
|
||||
'POST',
|
||||
{
|
||||
tenantid: input.tenantid,
|
||||
locationid: input.locationid,
|
||||
userid: input.userid ?? 0,
|
||||
bills: input.bills,
|
||||
},
|
||||
);
|
||||
return res.details;
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// GLOBAL CATALOGUE
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user