diff --git a/models/product.go b/models/product.go index 5926fa6..78dff8e 100644 --- a/models/product.go +++ b/models/product.go @@ -21,11 +21,21 @@ type Productcount struct { Outofstock int `json:"outofstock"` } -// ProductCategoryUpdate is one row of a bulk re-filing: which product, and -// which category it should sit in. +// ProductCategoryUpdate is one row of a bulk re-filing: which product, which +// category it sits in, and — the field that actually decides what a shopper +// sees — which subcategory. +// +// Both are here because they do different jobs. GetProducts filters on +// categoryid (the customer app asks for 2), while GetProductsBySubcategory +// GROUPS on subcategoryid, so the aisle heading in the app comes from the +// second and the first only decides whether the product is returned at all. +// A caller re-filing products into aisles sends categoryid 2 unchanged and the +// subcategory it worked out. type ProductCategoryUpdate struct { Productid int `json:"productid"` Categoryid int `json:"categoryid"` + // Optional: 0 leaves whatever the product already has. + Subcategoryid int `json:"subcategoryid"` } type ProductCategory struct { diff --git a/repositories/productRepository.go b/repositories/productRepository.go index 9c51c18..2083b02 100644 --- a/repositories/productRepository.go +++ b/repositories/productRepository.go @@ -1429,7 +1429,7 @@ func (r *productRepository) RecategoriseProducts(tenantID int, updates []models. } placeholders := make([]string, 0, len(updates)) - args := make([]any, 0, len(updates)*2) + args := make([]any, 0, len(updates)*3) for _, u := range updates { if u.Productid == 0 || u.Categoryid == 0 { // categoryid 0 is the value the customer app rejects outright. @@ -1437,18 +1437,27 @@ func (r *productRepository) RecategoriseProducts(tenantID int, updates []models. // product from every shopper. continue } - placeholders = append(placeholders, "(?::bigint, ?::bigint)") - args = append(args, u.Productid, u.Categoryid) + placeholders = append(placeholders, "(?::bigint, ?::bigint, ?::bigint)") + args = append(args, u.Productid, u.Categoryid, u.Subcategoryid) } if len(placeholders) == 0 { return 0, nil } + // subcategoryid is written only when the caller sent one. + // + // It is the field the customer app groups by, so a 0 arriving from a caller + // that does not know about aisles would silently move a product back into + // the app's "Uncategorized" bucket. Zero therefore means "leave it alone", + // the same rule UpdateProductCategory already follows. args = append(args, tenantID) tx := r.db.Exec(` UPDATE products p - SET categoryid = v.categoryid - FROM (VALUES `+strings.Join(placeholders, ",")+`) AS v(productid, categoryid) + SET categoryid = v.categoryid, + subcategoryid = CASE WHEN v.subcategoryid > 0 + THEN v.subcategoryid + ELSE p.subcategoryid END + FROM (VALUES `+strings.Join(placeholders, ",")+`) AS v(productid, categoryid, subcategoryid) WHERE p.productid = v.productid AND p.tenantid = ? `, args...)