console categories

This commit is contained in:
2026-09-07 17:53:35 +05:30
parent c699a400c3
commit e87f09c4f2
5 changed files with 107 additions and 0 deletions

View File

@@ -18,6 +18,7 @@ type ProductRepository interface {
GetProductCount(tenantID, categoryID, subcategoryID int, approve string) ([]models.Productcount, error)
GetProductCategory() ([]models.ProductCategory, error)
EnsureTenantCategories(tenantID int, names []string) (map[string]int, error)
RecategoriseProducts(tenantID int, updates []models.ProductCategoryUpdate) (int64, error)
GetProductVariants(tenantID, subcategoryID int) ([]models.Productvariant, error)
GetCatalougeProducts(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Products, error)
GetProductStocks(tenantID, locationID string) ([]models.Productstocks, error)
@@ -1404,6 +1405,56 @@ func (r *productRepository) GetImportedCatalogueRefs(tenantid int, brand string)
// row despite being in real use). This is deliberately not the global
// productcategories list: that list can omit categoryids tenants actually
// have products in, which would make the import category picker unusable.
// RecategoriseProducts moves products into different categories, in bulk.
//
// Written for the backfill that re-files everything imported before categories
// existed: those rows all carry whichever single category the tenant had, while
// the catalogue has known all along what each product actually is.
//
// ── Two things this deliberately does not do ────────────────────────────────
//
// It does not touch any column but categoryid. UpdateProduct next door runs
// GORM's Updates over a whole struct, which is why it can only be trusted with
// a hand-built partial — a bulk caller sending full rows would write back every
// field it happened to have read, including a price it never meant to change.
// One column, named explicitly, cannot do that.
//
// It is scoped by tenantid in the WHERE clause, not just trusted from the
// caller. A productid is global, so a mistyped id in a list would otherwise
// re-file another merchant's product into a category that does not exist for
// them — invisible to both shops and to the app.
func (r *productRepository) RecategoriseProducts(tenantID int, updates []models.ProductCategoryUpdate) (int64, error) {
if tenantID == 0 || len(updates) == 0 {
return 0, nil
}
placeholders := make([]string, 0, len(updates))
args := make([]any, 0, len(updates)*2)
for _, u := range updates {
if u.Productid == 0 || u.Categoryid == 0 {
// categoryid 0 is the value the customer app rejects outright.
// Refusing it here means a bad row is skipped rather than hiding a
// product from every shopper.
continue
}
placeholders = append(placeholders, "(?::bigint, ?::bigint)")
args = append(args, u.Productid, u.Categoryid)
}
if len(placeholders) == 0 {
return 0, nil
}
args = append(args, tenantID)
tx := r.db.Exec(`
UPDATE products p
SET categoryid = v.categoryid
FROM (VALUES `+strings.Join(placeholders, ",")+`) AS v(productid, categoryid)
WHERE p.productid = v.productid
AND p.tenantid = ?
`, args...)
return tx.RowsAffected, tx.Error
}
// EnsureTenantCategories maps category NAMES to this tenant's category ids,
// creating any that do not exist yet, and is safe to call repeatedly.
//