This commit is contained in:
2026-09-07 17:27:27 +05:30
parent c31696ce41
commit 954dfb8c00
6 changed files with 671 additions and 24 deletions

View File

@@ -141,6 +141,17 @@ export const productsApi = {
{ tenantid },
),
/**
* Exchanges category NAMES for this tenant's category ids, creating any that
* do not exist yet.
*
* POST because it writes: a sheet naming an aisle this shop has never stocked
* opens the aisle rather than failing. Keyed on the lowercased, trimmed name,
* so a caller looks up whatever casing its own sheet used.
*/
resolveCategories: (tenantid: number, names: string[]) =>
api.post<Record<string, number>>(`${WEB}/products/resolvecategories`, { tenantid, names }),
/** Unlinks from the store. The product row and its order history survive. */
removeFromStore: (body: { tenantid: number; locationid: number; productid: number }) =>
api.del<unknown>(`${WEB}/products/deleteproductlocation`, body),
@@ -154,6 +165,13 @@ export const productsApi = {
export interface SheetProductRow {
productname: string;
productsku: string;
/**
* The category NAME, from the ladder in `productCategory.ts`.
*
* The name is what the pipeline and the catalogue both speak; the id is
* per-tenant and is resolved from this at import time.
*/
category?: string;
categoryid: number;
subcategoryid: number;
retailprice: number;
@@ -209,13 +227,39 @@ export async function importSheetProducts(
const failures: SheetImportResult['failures'] = [];
const createdSkus: string[] = [];
/*
Categories first, in one call, before a single product is created.
Every row carries a category NAME worked out by the ladder in
`productCategory.ts`, and the customer app browses by categoryid and rejects
0 — so a product created before its aisle exists is a product no shopper can
find. Resolving up front also means one round trip for the whole sheet rather
than one per row.
A failure here is not fatal. The rows still carry whatever categoryid the
operator picked for the upload, which is exactly the behaviour this had
before, so a category service that is down costs the new filing and not the
import.
*/
const wanted = [...new Set(rows.map((row) => row.category ?? '').filter(Boolean))];
let resolved: Record<string, number> = {};
if (wanted.length > 0) {
try {
resolved = (await productsApi.resolveCategories(tenantid, wanted)) ?? {};
} catch {
resolved = {};
}
}
const categoryIdFor = (row: SheetProductRow): number =>
resolved[(row.category ?? '').trim().toLowerCase()] ?? row.categoryid;
for (const [index, row] of rows.entries()) {
try {
await productsApi.createProduct({
tenantid,
productname: row.productname,
productsku: row.productsku,
categoryid: row.categoryid,
categoryid: categoryIdFor(row),
subcategoryid: row.subcategoryid,
retailprice: row.retailprice,
productcost: row.productcost,