This commit is contained in:
2026-08-31 16:40:07 +05:30
parent 5304d9aeb6
commit fe78c54eeb
2 changed files with 51 additions and 10 deletions

View File

@@ -52,15 +52,23 @@ export function ProductsPanel({
const [publishing, setPublishing] = useState<Product[] | null>(null);
/**
* One branch, always.
* The branch in scope, or all of them.
*
* `getlocationproducts` takes a single `locationid` and there is no
* tenant-wide product read. Under "All branches" this reads the first.
* Publishing is tenant-wide regardless, so the choice affects what is
* LISTED, not what is changed.
* `current` is undefined under "All branches", and that now means what it
* says: `getlocationproducts` answers a tenant-wide read with one row per
* product and stock summed across outlets.
*
* It used to fall back to `scoped[0]` — the FIRST branch — so "All branches"
* listed one outlet's products and called them the catalogue. A merchant with
* five outlets saw four products instead of six, and picking a single branch
* showed MORE than picking all of them.
*
* Publishing is tenant-wide regardless, so this affects what is LISTED, not
* what is changed.
*/
const branch = current ?? scoped[0];
const products = useLocationProducts(tenantid || undefined, branch?.locationid, 0);
const products = useLocationProducts(tenantid || undefined, current?.locationid, 0, {
allBranches: true,
});
const categories = useTenantCategories(tenantid || undefined);
/**
@@ -123,7 +131,12 @@ export function ProductsPanel({
{isUploading ? (
<SheetUploadDrawer
tenantid={tenantid || undefined}
locationid={branch?.locationid}
/* `current`, never a fallback to the first branch. An upload becomes
opening stock on ONE shelf, so under "All branches" there is no
answer to guess — passing nothing leaves the drawer's own branch
picker to ask, and it blocks the upload until it is answered.
Defaulting here would load a shelf nobody chose. */
locationid={current?.locationid}
onClose={onCloseUpload}
/>
) : null}

View File

@@ -166,20 +166,48 @@ export function useCatalogueProduct(brand: string | undefined, sku: string | und
});
}
/**
* A tenant's products, at one branch or across all of them.
*
* `locationid` of 0 or undefined means EVERY branch, and the backend answers it
* — one row per product, stock summed across outlets. It did not always: the
* read required a single branch, so "All branches" listed the FIRST one and a
* merchant with five outlets saw four products at RS Puram instead of the six
* they stock. Picking one branch showed more than picking all of them.
*
* So the tenant alone is enough to run this query now. Gating on `locationid`
* is what turned a missing backend feature into a silently wrong list.
*/
export function useLocationProducts(
tenantid: number | undefined,
locationid: number | undefined,
page = 0,
options: {
/**
* Treat a missing branch as "every branch" rather than as "not ready yet".
*
* Off by default, and that default is the safe one. A Store user is pinned
* to a single outlet and their branch resolves asynchronously — so during
* that window an opt-out default would answer tenant-wide and show them
* every other branch's stock as if it were their own.
*
* The admin catalogue opts in, because there "All branches" is a real
* selection a merchant made, not a value that has not arrived.
*/
allBranches?: boolean;
} = {},
) {
const wantsAll = options.allBranches === true;
return useQuery({
queryKey: queryKeys.products.byLocation(tenantid ?? 0, locationid ?? 0, page),
queryFn: () =>
productsApi.locationProducts({
tenantid: tenantid as number,
locationid: locationid as number,
// 0 is meaningful to the backend here, not a missing value.
locationid: locationid ?? 0,
pageno: page,
}),
enabled: Boolean(tenantid) && Boolean(locationid),
enabled: Boolean(tenantid) && (wantsAll || Boolean(locationid)),
...live,
});
}