category
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user