e2e changes
This commit is contained in:
@@ -4,9 +4,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"nearle/models"
|
||||
"nearle/repositories"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ProductService interface {
|
||||
@@ -22,7 +24,7 @@ type ProductService interface {
|
||||
RemoveProductVariant(tenantid, variantid int) error
|
||||
VariantChildIDs(tenantid int) (map[int]bool, error)
|
||||
CreateProductStock(stocks []models.Productstock) error
|
||||
CreateProduct(product models.Products) error
|
||||
CreateProduct(product models.Products) (models.Products, error)
|
||||
UpdateProduct(product models.Products) error
|
||||
DeleteProduct(productID int) error
|
||||
GetStockStatement(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Productstockstatement, error)
|
||||
@@ -169,8 +171,27 @@ func (s *productService) UpdateProductStatus(productIDs []int, status string) er
|
||||
return s.repo.UpdateProductStatus(productIDs, status)
|
||||
}
|
||||
|
||||
func (s *productService) CreateProduct(product models.Products) error {
|
||||
return s.repo.CreateProduct(product)
|
||||
// CreateProduct stores one product and hands it back with its id filled in.
|
||||
//
|
||||
// It used to return only an error, and the id was lost on the way out: the
|
||||
// repository took the struct by value, GORM wrote the generated productid onto
|
||||
// that copy, and the copy was discarded — so the endpoint answered
|
||||
// `productid: 0` for a row that certainly had one.
|
||||
//
|
||||
// The caller needs it. A product is not sellable until it has been priced at an
|
||||
// outlet and stocked there, and both of those calls are keyed on productid, so
|
||||
// every importer had to create, re-read the tenant's whole catalogue, and match
|
||||
// its own rows back by SKU to carry on — which is also why creating two
|
||||
// products with the same SKU quietly attached the second one's stock to the
|
||||
// first.
|
||||
func (s *productService) CreateProduct(product models.Products) (models.Products, error) {
|
||||
id, err := s.repo.CreateProductReturningID(product)
|
||||
if err != nil {
|
||||
return models.Products{}, err
|
||||
}
|
||||
|
||||
product.Productid = id
|
||||
return product, nil
|
||||
}
|
||||
|
||||
func (s *productService) UpdateProduct(product models.Products) error {
|
||||
@@ -308,6 +329,54 @@ func (s *productService) DeleteProductLocation(tenantid, locationid, productid i
|
||||
return s.repo.DeleteProductLocation(tenantid, locationid, productid)
|
||||
}
|
||||
|
||||
// catalogueFactsOf collects the catalogue fields the product table has no
|
||||
// column for, so an import keeps them instead of leaving them behind.
|
||||
//
|
||||
// Only what the catalogue actually stated: an empty field is omitted rather
|
||||
// than written as `""` or `[]`, so a reader can tell "the catalogue did not say"
|
||||
// from "the catalogue said none". The drawer prints a row per fact and an empty
|
||||
// string would print an empty row.
|
||||
//
|
||||
// The keys are the catalogue's own wire names. They are what the console
|
||||
// already reads off a live catalogue row, so the same rendering works against
|
||||
// either source without a translation layer in between.
|
||||
func catalogueFactsOf(p *models.CatalogueProduct) map[string]any {
|
||||
facts := map[string]any{}
|
||||
if p == nil {
|
||||
return facts
|
||||
}
|
||||
|
||||
put := func(key, value string) {
|
||||
if v := strings.TrimSpace(value); v != "" {
|
||||
facts[key] = v
|
||||
}
|
||||
}
|
||||
putList := func(key string, values []string) {
|
||||
kept := make([]string, 0, len(values))
|
||||
for _, v := range values {
|
||||
if t := strings.TrimSpace(v); t != "" {
|
||||
kept = append(kept, t)
|
||||
}
|
||||
}
|
||||
if len(kept) > 0 {
|
||||
facts[key] = kept
|
||||
}
|
||||
}
|
||||
|
||||
put("title", p.Title)
|
||||
put("category", p.Category)
|
||||
put("variant_key", p.VariantKey)
|
||||
put("sku_source", p.SKUSource)
|
||||
put("price_range", p.PriceRange)
|
||||
put("fssai_license", p.FSSAILicense)
|
||||
put("search_query", p.SearchQuery)
|
||||
putList("providers", p.Providers)
|
||||
putList("highlights", p.Highlights)
|
||||
putList("nutrients", p.Nutrients)
|
||||
|
||||
return facts
|
||||
}
|
||||
|
||||
// ImportCatalogueProduct bridges a global catalogue product (CatalogueDB) into
|
||||
// a tenant's own store catalogue: it snapshots the catalogue product into the
|
||||
// tenant's `products` table on first import (keyed on brand+catalogueid so
|
||||
@@ -417,6 +486,27 @@ func (s *productService) ImportCatalogueProduct(reqs []models.ImportCataloguePro
|
||||
Taxpercent: req.Taxpercent,
|
||||
Approve: 1,
|
||||
}
|
||||
// Everything the snapshot has no column for, kept as the catalogue
|
||||
// stated it.
|
||||
//
|
||||
// Ten of the catalogue's eighteen fields used to stop here. Two of
|
||||
// them SHOULD — `category` is remapped to the platform's own
|
||||
// categoryid, and `price_range` is replaced by the price the shop
|
||||
// sets — but they are kept anyway, because what other retailers
|
||||
// charge is the most useful thing on the drawer when somebody is
|
||||
// deciding what to charge, and the catalogue's own category is how
|
||||
// a mis-filed product gets noticed.
|
||||
//
|
||||
// Encoding failure is swallowed, like the images below: the product
|
||||
// is worth creating without its facts, and refusing an import over
|
||||
// a nutrition line would be the wrong trade.
|
||||
if encoded, err := json.Marshal(catalogueFactsOf(catalogueProduct)); err == nil {
|
||||
snapshot.Cataloguefacts = string(encoded)
|
||||
} else {
|
||||
log.Printf("import: could not encode catalogue facts for %s/%d: %v",
|
||||
req.Brand, req.Catalogueid, err)
|
||||
}
|
||||
|
||||
if len(catalogueProduct.Images) > 0 {
|
||||
// The first stays where every reader already looks for it.
|
||||
snapshot.Productimage = catalogueProduct.Images[0]
|
||||
|
||||
Reference in New Issue
Block a user