changes fixed in the catalogue
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user