category changes
This commit is contained in:
@@ -29,6 +29,8 @@ import type {
|
||||
ProductStockRequest,
|
||||
ProductSubCategory,
|
||||
} from './types';
|
||||
import { APP_BROWSE_CATEGORY } from '@/features/catalogue/tenantCategories';
|
||||
import { aisleIdForCategory, aisleIdsFrom } from '@/features/store-admin/appAisle';
|
||||
|
||||
export interface LocationProductQuery {
|
||||
tenantid: number;
|
||||
@@ -153,13 +155,20 @@ export const productsApi = {
|
||||
api.post<Record<string, number>>(`${WEB}/products/resolvecategories`, { tenantid, names }),
|
||||
|
||||
/**
|
||||
* Re-files products into different categories, in bulk.
|
||||
* Re-files products into different aisles, in bulk.
|
||||
*
|
||||
* `categoryid` should be 2 on every row — the customer app FILTERS on it and
|
||||
* anything else removes the product from its browse. `subcategoryid` is the
|
||||
* one that decides the heading a shopper reads; 0 leaves whatever the product
|
||||
* already has, so a caller that does not know the aisle cannot erase one.
|
||||
*
|
||||
* Scoped by tenant on the server as well as here — a productid is global, so
|
||||
* a wrong id in the list would otherwise move another merchant's product.
|
||||
*/
|
||||
recategorise: (tenantid: number, updates: { productid: number; categoryid: number }[]) =>
|
||||
api.put<{ moved: number }>(`${WEB}/products/recategorise`, { tenantid, updates }),
|
||||
recategorise: (
|
||||
tenantid: number,
|
||||
updates: { productid: number; categoryid: number; subcategoryid?: number }[],
|
||||
) => api.put<{ moved: number }>(`${WEB}/products/recategorise`, { tenantid, updates }),
|
||||
|
||||
/** Unlinks from the store. The product row and its order history survive. */
|
||||
removeFromStore: (body: { tenantid: number; locationid: number; productid: number }) =>
|
||||
@@ -237,30 +246,26 @@ export async function importSheetProducts(
|
||||
const createdSkus: string[] = [];
|
||||
|
||||
/*
|
||||
Categories first, in one call, before a single product is created.
|
||||
The aisles 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.
|
||||
`productCategory.ts`. What the customer app groups by is not that category
|
||||
but `products.subcategoryid` — one of ten platform rows under category 2 —
|
||||
so the name is folded into an aisle and the aisle looked up by name here. See
|
||||
`appAisle.ts` for the endpoint that is measured against.
|
||||
|
||||
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.
|
||||
A failure here is not fatal: the lookup falls back to the ids last read from
|
||||
the platform, and a row that still cannot be placed is created with
|
||||
subcategoryid 0, which the app lists under "Uncategorized".
|
||||
*/
|
||||
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 = {};
|
||||
}
|
||||
let aisleIds: ReadonlyMap<string, number>;
|
||||
try {
|
||||
aisleIds = aisleIdsFrom(await productsApi.subCategories(tenantid, APP_BROWSE_CATEGORY));
|
||||
} catch {
|
||||
aisleIds = aisleIdsFrom(undefined);
|
||||
}
|
||||
const categoryIdFor = (row: SheetProductRow): number =>
|
||||
resolved[(row.category ?? '').trim().toLowerCase()] ?? row.categoryid;
|
||||
const subcategoryIdFor = (row: SheetProductRow): number =>
|
||||
aisleIdForCategory(row.category, aisleIds) || Number(row.subcategoryid) || 0;
|
||||
|
||||
for (const [index, row] of rows.entries()) {
|
||||
try {
|
||||
@@ -268,8 +273,10 @@ export async function importSheetProducts(
|
||||
tenantid,
|
||||
productname: row.productname,
|
||||
productsku: row.productsku,
|
||||
categoryid: categoryIdFor(row),
|
||||
subcategoryid: row.subcategoryid,
|
||||
// ALWAYS 2 — the app filters on it and would drop anything else. The
|
||||
// aisle a shopper reads is the subcategory.
|
||||
categoryid: APP_BROWSE_CATEGORY,
|
||||
subcategoryid: subcategoryIdFor(row),
|
||||
retailprice: row.retailprice,
|
||||
productcost: row.productcost,
|
||||
taxpercent: row.taxpercent,
|
||||
|
||||
Reference in New Issue
Block a user