orders
This commit is contained in:
@@ -51,3 +51,37 @@ func InStockOnly(products []models.Products, locationID int) []models.Products {
|
||||
}
|
||||
return inStock
|
||||
}
|
||||
|
||||
// InStockOnlyGrouped is InStockOnly for the grouped shape `getallproducts`
|
||||
// returns: a tenant with its products, rather than a flat list.
|
||||
//
|
||||
// ── Why this one does NOT skip when locationID is 0 ──────────────────────────
|
||||
//
|
||||
// InStockOnly above returns early without an outlet, because the query behind
|
||||
// it computes stock scoped to that outlet and answers 0 for everything when
|
||||
// there is none — filtering that would empty the catalogue rather than filter
|
||||
// it. FetchFilteredProducts is built differently: its stock subquery reads
|
||||
// `(? = 0 OR locationid = ?)`, so with no outlet it aggregates the ledger across
|
||||
// the tenant's outlets and still returns a real number. Measured 2026-09-02 on
|
||||
// R mart: 24 products unscoped, 20 with a live balance and 4 at zero.
|
||||
//
|
||||
// So the number is trustworthy either way, and the rule can be applied either
|
||||
// way: a shop that holds none of something anywhere is not offering it.
|
||||
//
|
||||
// A tenant left with no sellable products keeps its group, with an empty list.
|
||||
// Dropping the group would tell the app the shop does not exist, which is a
|
||||
// different and wrong statement — the shop is open, it has nothing on the shelf.
|
||||
func InStockOnlyGrouped(groups []models.Tenantproducts) []models.Tenantproducts {
|
||||
out := make([]models.Tenantproducts, 0, len(groups))
|
||||
for _, group := range groups {
|
||||
kept := make([]models.Products, 0, len(group.Products))
|
||||
for _, product := range group.Products {
|
||||
if product.Productstock > 0 {
|
||||
kept = append(kept, product)
|
||||
}
|
||||
}
|
||||
group.Products = kept
|
||||
out = append(out, group)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -606,3 +606,64 @@ func TestImportPublishesAfterTheRowsExist(t *testing.T) {
|
||||
t.Fatalf("want CreateProductLocation before PublishPricedLocations, got %v", repo.calls)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The grouped shape, which is what `getallproducts` answers with.
|
||||
|
||||
Measured 2026-09-02 on R mart: the app was offering four products with a zero
|
||||
balance — Cadbury Bournvita 500g, Amul 90g, Amul 150g and a Dabur chewing gum.
|
||||
A shopper could pick any of them and only find out at checkout, where the stock
|
||||
check answers 409.
|
||||
*/
|
||||
|
||||
func groupOf(stocks ...int) []models.Tenantproducts {
|
||||
products := make([]models.Products, 0, len(stocks))
|
||||
for i, s := range stocks {
|
||||
products = append(products, models.Products{Productid: 100 + i, Productstock: s})
|
||||
}
|
||||
return []models.Tenantproducts{{Products: products}}
|
||||
}
|
||||
|
||||
func TestAnEmptyShelfIsNotOfferedToShoppers(t *testing.T) {
|
||||
got := InStockOnlyGrouped(groupOf(5, 0, 3, 0))
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("expected the tenant group to survive, got %d groups", len(got))
|
||||
}
|
||||
if len(got[0].Products) != 2 {
|
||||
t.Fatalf("expected 2 sellable products, got %d", len(got[0].Products))
|
||||
}
|
||||
for _, p := range got[0].Products {
|
||||
if p.Productstock <= 0 {
|
||||
t.Errorf("product %d has no stock and was still offered", p.Productid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAShopWithNothingOnTheShelfStillExists(t *testing.T) {
|
||||
// The group is kept with an empty list. Dropping it would tell the app the
|
||||
// shop is not there, which is a different and wrong statement: it is open,
|
||||
// it just has nothing to sell today.
|
||||
got := InStockOnlyGrouped(groupOf(0, 0))
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("the shop disappeared: got %d groups", len(got))
|
||||
}
|
||||
if len(got[0].Products) != 0 {
|
||||
t.Errorf("expected nothing sellable, got %d", len(got[0].Products))
|
||||
}
|
||||
}
|
||||
|
||||
func TestANegativeBalanceIsNotStockWhenGrouped(t *testing.T) {
|
||||
// A ledger can go negative through correction. Negative is not "some".
|
||||
got := InStockOnlyGrouped(groupOf(-4))
|
||||
if len(got[0].Products) != 0 {
|
||||
t.Errorf("a negative balance was treated as stock")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilteringGroupsDoesNotDisturbTheCallersSlice(t *testing.T) {
|
||||
original := groupOf(1, 0)
|
||||
_ = InStockOnlyGrouped(original)
|
||||
if len(original[0].Products) != 2 {
|
||||
t.Errorf("the caller's slice was modified: %d products left", len(original[0].Products))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user