Bridge global catalogue DB to per-store product catalogue

Adds a separate CatalogueDB (pgvector) connection alongside the main
nearledb, plus a new catalogue module (repository/service/controller/
routes) to browse it by brand, category, and keyword, with brand
optional so the whole ~237-product catalogue can be browsed unfiltered.

Adds the actual bridge: importing a catalogue product snapshots it into
the tenant's own products table (keyed on brand+catalogueid, since a
catalogue row's bare id is only unique within its own brand table),
then links it via the existing productlocations upsert. Re-importing
tops up stock and refreshes price instead of duplicating. Also adds an
imported-refs endpoint so the frontend can badge already-imported items
without diffing full product lists, and wires the new AWS S3 image
store used to resolve catalogue product photos.

Bumps Go/Docker to 1.24 for the AWS SDK dependency this needs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Suriya
2026-07-16 17:34:20 +05:30
parent 950064de6c
commit fab06bb33e
20 changed files with 1408 additions and 15 deletions

View File

@@ -1,6 +1,7 @@
package repositories
import (
"errors"
"fmt"
"strconv"
"strings"
@@ -36,6 +37,10 @@ type ProductRepository interface {
CreateProductLocation(input []models.Productlocations) error
CreateProductVariant(input models.Productvariant) error
DeleteProductLocation(tenantid, locationid, productid int) error
FindTenantProductByCatalogueRef(tenantid int, brand string, catalogueid int64) (*models.Products, error)
CreateProductReturningID(product models.Products) (int, error)
GetImportedCatalogueRefs(tenantid int, brand string) ([]models.ImportedCatalogueRef, error)
UpdateProductPricing(productid int, retailprice, productcost, taxpercent float64) error
}
type productRepository struct {
@@ -753,3 +758,60 @@ func (r *productRepository) DeleteProductLocation(tenantid, locationid, producti
}
return nil
}
// FindTenantProductByCatalogueRef looks up the tenant's existing snapshot of
// a catalogue product, keyed on (tenantid, brand, catalogueid) since a
// catalogue row's bare id is only unique within its own brand table.
func (r *productRepository) FindTenantProductByCatalogueRef(tenantid int, brand string, catalogueid int64) (*models.Products, error) {
var product models.Products
result := r.db.Table("products").
Where("tenantid = ? AND productbrand = ? AND catalogueid = ?", tenantid, brand, catalogueid).
First(&product)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &product, nil
}
// CreateProductReturningID inserts a new product snapshot and returns its
// generated productid. Kept separate from CreateProduct so existing callers
// of CreateProduct are unaffected.
func (r *productRepository) CreateProductReturningID(product models.Products) (int, error) {
if err := r.db.Create(&product).Error; err != nil {
return 0, err
}
return product.Productid, nil
}
// GetImportedCatalogueRefs returns the (brand, catalogueid) pairs this
// tenant has already imported, so a catalogue browse screen can mark items
// as already-imported without diffing full product lists client-side. Brand
// is optional: omitted, it covers every brand at once — needed because the
// all-brands browse view mixes products whose bare catalogueid can collide
// across brand tables, so brand must travel with every id.
func (r *productRepository) GetImportedCatalogueRefs(tenantid int, brand string) ([]models.ImportedCatalogueRef, error) {
refs := make([]models.ImportedCatalogueRef, 0)
query := r.db.Table("products").
Select("productbrand AS brand, catalogueid").
Where("tenantid = ? AND catalogueid IS NOT NULL AND catalogueid != 0", tenantid)
if brand != "" {
query = query.Where("productbrand = ?", brand)
}
err := query.Scan(&refs).Error
return refs, err
}
// UpdateProductPricing updates only the pricing fields on a product
// snapshot, used when a catalogue product is re-imported with new pricing.
func (r *productRepository) UpdateProductPricing(productid int, retailprice, productcost, taxpercent float64) error {
return r.db.Table("products").
Where("productid = ?", productid).
Updates(map[string]interface{}{
"retailprice": retailprice,
"productcost": productcost,
"taxpercent": taxpercent,
}).Error
}