From ff72af9a8a8baefd55f11c2a55c11d352c55ae64 Mon Sep 17 00:00:00 2001 From: abhishek Date: Tue, 11 Aug 2026 17:10:21 +0530 Subject: [PATCH] changes fixed in the catalogue --- controllers/productController.go | 44 ++++++++++++++++++++++-- repositories/productPublishRepository.go | 13 +++++-- repositories/productRepository.go | 16 ++++++--- services/productService.go | 6 ++-- 4 files changed, 65 insertions(+), 14 deletions(-) diff --git a/controllers/productController.go b/controllers/productController.go index d5451ca..34233a7 100644 --- a/controllers/productController.go +++ b/controllers/productController.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "strconv" + "strings" "nearle/models" "nearle/services" @@ -573,10 +574,43 @@ func (ctl *ProductController) ImportCatalogueProduct(c *fiber.Ctx) error { } for _, req := range data { - if req.Tenantid == 0 || req.Locationid == 0 || req.Brand == "" || req.Catalogueid == 0 || req.Categoryid == 0 { + // `categoryid` is deliberately NOT required. + // + // Importing adds a product to the admin catalogue and nothing else — it + // reaches no shop and cannot be sold — so there is nothing yet that + // depends on it being classified. The console matches the tenant's own + // category against the catalogue's where one lines up and sends 0 where + // none does, and the admin corrects it there. + // + // Requiring it forced the console to send *something*, and what it sent + // was the tenant's first category regardless of the product. That is + // worse than uncategorised: an unclassified product is visibly + // unfinished, while a wrongly classified one looks done and is only + // found by someone browsing the wrong aisle. + // Named individually rather than as one list of five. + // + // The old message recited every required field whichever one was + // actually missing, so a request rejected for `categoryid: 0` read as + // "catalogueid is required" and sent somebody looking at the wrong + // field entirely. An error that does not say what is wrong costs more + // than the branch it saves. + var missing []string + if req.Tenantid == 0 { + missing = append(missing, "tenantid") + } + if req.Locationid == 0 { + missing = append(missing, "locationid") + } + if req.Brand == "" { + missing = append(missing, "brand") + } + if req.Catalogueid == 0 { + missing = append(missing, "catalogueid") + } + if len(missing) > 0 { return c.Status(http.StatusBadRequest).JSON(fiber.Map{ "code": http.StatusBadRequest, - "message": "tenantid, locationid, brand, catalogueid, and categoryid are required", + "message": fmt.Sprintf("missing required field(s): %s", strings.Join(missing, ", ")), "status": false, }) } @@ -703,6 +737,10 @@ func (ctl *ProductController) PublishProduct(c *fiber.Ctx) error { Tenantid int `json:"tenantid"` Productid int `json:"productid"` Price float64 `json:"price"` + // Optional. Omitted leaves the product at 0%, which is a real rate for + // staples rather than a missing value — so it is written as given + // rather than skipped when zero. + Taxpercent float64 `json:"taxpercent"` } if err := c.BodyParser(&input); err != nil { @@ -711,7 +749,7 @@ func (ctl *ProductController) PublishProduct(c *fiber.Ctx) error { }) } - outlets, err := ctl.productService.PublishProduct(input.Tenantid, input.Productid, input.Price) + outlets, err := ctl.productService.PublishProduct(input.Tenantid, input.Productid, input.Price, input.Taxpercent) if err != nil { // 400 rather than 500: every failure here is something the caller can // act on — no price, no outlets, or a product that is not theirs. diff --git a/repositories/productPublishRepository.go b/repositories/productPublishRepository.go index d945318..1de3354 100644 --- a/repositories/productPublishRepository.go +++ b/repositories/productPublishRepository.go @@ -30,13 +30,20 @@ import ( // Idempotent: publishing an already-published product re-prices it and leaves // the original `publishedat` alone, so "when did this go live" survives a // second click. -func (r *productRepository) PublishProduct(tenantID, productID int, price float64) (int, error) { +func (r *productRepository) PublishProduct(tenantID, productID int, price, taxPercent float64) (int, error) { if tenantID <= 0 || productID <= 0 { return 0, fmt.Errorf("tenantid and productid are required") } if price <= 0 { return 0, fmt.Errorf("set a price before publishing — a product with no price cannot be sold at a till") } + // Zero is a legitimate rate — plenty of staples are zero-rated — so it + // cannot double as "not specified". Negative is refused rather than stored: + // three products already carry taxpercent -1, which yields negative tax + // under either convention. + if taxPercent < 0 { + return 0, fmt.Errorf("tax percentage cannot be negative") + } var affected int err := r.db.Transaction(func(tx *gorm.DB) error { @@ -93,9 +100,9 @@ func (r *productRepository) PublishProduct(tenantID, productID int, price float6 // The master price follows, so an outlet that has no row of its own // still reads the right figure through the COALESCE fallback in // GetProducts. - return tx.Exec(`UPDATE products SET retailprice = ?, updated = NOW() + return tx.Exec(`UPDATE products SET retailprice = ?, taxpercent = ?, updated = NOW() WHERE productid = ? AND tenantid = ?`, - price, productID, tenantID).Error + price, taxPercent, productID, tenantID).Error }) return affected, err diff --git a/repositories/productRepository.go b/repositories/productRepository.go index 23b99ca..508b371 100644 --- a/repositories/productRepository.go +++ b/repositories/productRepository.go @@ -42,7 +42,7 @@ type ProductRepository interface { // Releasing a product from the admin catalogue to every outlet, and pulling // it back. See productPublishRepository.go. - PublishProduct(tenantID, productID int, price float64) (int, error) + PublishProduct(tenantID, productID int, price, taxPercent float64) (int, error) UnpublishProduct(tenantID, productID int) (int, error) FindTenantProductByCatalogueRef(tenantid int, brand string, catalogueid int64) (*models.Products, error) CreateProductReturningID(product models.Products) (int, error) @@ -400,7 +400,7 @@ func (r *productRepository) GetStockStatement(tenantID, locationID, subcategoryI // arithmetically identical to closing — the Inventory ledger then showed // the same number in both columns and looked like stock never moved, even // on days with sales. - query := `SELECT a.productid,a.productname,a.productimage,a.categoryid,a.subcategoryid,a.productunit,a.unitvalue,a.productcost,a.taxpercent,a.taxamount,a.retailprice,b.tenantid,b.locationid,b.publishedat, + query := `SELECT a.productid,a.productname,a.productimage,a.categoryid,a.subcategoryid,a.productunit,a.unitvalue,a.productcost,a.taxpercent,a.taxamount,a.retailprice,b.tenantid,b.locationid, COALESCE( SUM(CASE WHEN UPPER(c.stocktype) = 'IN' AND c.stockdate::date < CURRENT_DATE THEN c.quantity ELSE 0 END) - SUM(CASE WHEN UPPER(c.stocktype) = 'OUT' AND c.stockdate::date < CURRENT_DATE THEN c.quantity ELSE 0 END),0 ) AS opening, @@ -435,7 +435,7 @@ func (r *productRepository) GetStockStatement(tenantID, locationID, subcategoryI a.productid, a.productname, a.productimage, a.categoryid, a.subcategoryid, a.productunit, a.productcost, a.taxpercent, a.taxamount, - a.retailprice, b.tenantid, b.locationid, b.publishedat + a.retailprice, b.tenantid, b.locationid ORDER BY a.productid DESC LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) if err := r.db.Raw(query, params...).Scan(&data).Error; err != nil { @@ -475,7 +475,13 @@ func (r *productRepository) GetLocationProducts(tenantID, locationID, subcategor // retailprice when the store hasn't set its own. Returning a bare b.price // reported 0 for any product priced only at tenant level, which the store // catalogue then rendered as "—". - query := `SELECT a.*, b.productlocationid, b.status, + // b.publishedat decides whether a store user may see this row at all, so it + // has to be selected explicitly for the same reason b.price does: `a.*` + // covers products, and publication is a per-outlet fact on + // productlocations. Without it the column came back null for every row — + // including ones that had just been published — and the store catalogue, + // which filters on it, showed nothing. + query := `SELECT a.*, b.productlocationid, b.status, b.publishedat, COALESCE(NULLIF(b.price, 0), a.retailprice, 0) AS price, COALESCE(SUM(CASE WHEN UPPER(c.stocktype) = 'IN' THEN c.quantity ELSE 0 END), 0) AS total_in, COALESCE(SUM(CASE WHEN UPPER(c.stocktype) = 'OUT' THEN c.quantity ELSE 0 END), 0) AS total_out, @@ -500,7 +506,7 @@ func (r *productRepository) GetLocationProducts(tenantID, locationID, subcategor query += ` GROUP BY a.productid, a.productname, a.productimage, a.categoryid, a.subcategoryid, a.productunit, a.productcost, a.taxpercent, a.taxamount, a.retailprice, - b.tenantid, b.locationid, b.productlocationid, b.status, b.price + b.tenantid, b.locationid, b.productlocationid, b.status, b.price, b.publishedat ORDER BY a.productid DESC LIMIT ? OFFSET ?` params = append(params, pagesize, offset) diff --git a/services/productService.go b/services/productService.go index c4286a0..4d87615 100644 --- a/services/productService.go +++ b/services/productService.go @@ -35,7 +35,7 @@ type ProductService interface { ImportCatalogueProduct(reqs []models.ImportCatalogueProductRequest) error GetImportedCatalogueRefs(tenantid int, brand string) ([]models.ImportedCatalogueRef, error) GetTenantCategories(tenantid int) ([]models.TenantCategory, error) - PublishProduct(tenantID, productID int, price float64) (int, error) + PublishProduct(tenantID, productID int, price, taxPercent float64) (int, error) UnpublishProduct(tenantID, productID int) (int, error) } type productService struct { @@ -352,8 +352,8 @@ func (s *productService) GetTenantCategories(tenantid int) ([]models.TenantCateg // // The price check lives in the repository rather than here, so it holds for any // caller — the console form is a convenience, not the rule. -func (s *productService) PublishProduct(tenantID, productID int, price float64) (int, error) { - return s.repo.PublishProduct(tenantID, productID, price) +func (s *productService) PublishProduct(tenantID, productID int, price, taxPercent float64) (int, error) { + return s.repo.PublishProduct(tenantID, productID, price, taxPercent) } func (s *productService) UnpublishProduct(tenantID, productID int) (int, error) {