fix products listing

This commit is contained in:
2026-03-27 23:17:21 +01:00
parent ec05101037
commit 9ec329b1d6
429 changed files with 9816 additions and 3774 deletions

View File

@@ -5,14 +5,16 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/db"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
"github.com/WinterYukky/gorm-extra-clause-plugin/exclause"
)
type UIProductDescriptionRepo interface {
GetProductDescription(productID uint, productid_lang uint) (*model.ProductDescription, error)
CreateIfDoesNotExist(productID uint, productid_lang uint) error
UpdateFields(productID uint, productid_lang uint, updates map[string]string) error
GetMeiliProducts(id_lang uint) ([]model.MeiliSearchProduct, error)
GetMeiliProducts(id_lang uint, offset, limit int) ([]model.MeiliSearchProduct, error)
}
type ProductDescriptionRepo struct{}
@@ -25,9 +27,12 @@ func New() UIProductDescriptionRepo {
func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid_lang uint) (*model.ProductDescription, error) {
var ProductDescription model.ProductDescription
err := db.DB.
Table("ps_product_lang").
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, constdata.SHOP_ID, productid_lang).
err := db.Get().
Where(&dbmodel.PsProductLang{
IDProduct: int32(productID),
IDShop: int32(constdata.SHOP_ID),
IDLang: int32(productid_lang),
}).
First(&ProductDescription).Error
if err != nil {
return nil, fmt.Errorf("database error: %w", err)
@@ -44,9 +49,12 @@ func (r *ProductDescriptionRepo) CreateIfDoesNotExist(productID uint, productid_
LangID: productid_lang,
}
err := db.DB.
Table("ps_product_lang").
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, constdata.SHOP_ID, productid_lang).
err := db.Get().
Where(&dbmodel.PsProductLang{
IDProduct: int32(productID),
IDShop: int32(constdata.SHOP_ID),
IDLang: int32(productid_lang),
}).
FirstOrCreate(&record).Error
if err != nil {
return fmt.Errorf("database error: %w", err)
@@ -64,9 +72,13 @@ func (r *ProductDescriptionRepo) UpdateFields(productID uint, productid_lang uin
updatesIface[k] = v
}
err := db.DB.
Table("ps_product_lang").
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, constdata.SHOP_ID, productid_lang).
err := db.Get().
Model(&dbmodel.PsProductLang{}).
Where(&dbmodel.PsProductLang{
IDProduct: int32(productID),
IDShop: int32(constdata.SHOP_ID),
IDLang: int32(productid_lang),
}).
Updates(updatesIface).Error
if err != nil {
return fmt.Errorf("database error: %w", err)
@@ -75,127 +87,107 @@ func (r *ProductDescriptionRepo) UpdateFields(productID uint, productid_lang uin
return nil
}
// We assume that any user has access to all product descriptions
func (r *ProductDescriptionRepo) GetMeiliProducts(id_lang uint) ([]model.MeiliSearchProduct, error) {
// GetMeiliProductsBatchedScanned returns a batch of products with LIMIT/OFFSET pagination
// The scanning is done inside the repo to keep the service layer cleaner
func (r *ProductDescriptionRepo) GetMeiliProducts(id_lang uint, offset, limit int) ([]model.MeiliSearchProduct, error) {
var products []model.MeiliSearchProduct
query := db.Get().Debug().Raw(`
WITH products_page AS (
SELECT ps.id_product, ps.price
FROM ps_product_shop ps
WHERE ps.id_shop = ? AND ps.active = 1
),
variation_attributes AS (
SELECT pas.id_product, pagl.public_name AS attribute_name,
JSON_ARRAYAGG(DISTINCT pal.name) AS attribute_values
FROM ps_product_attribute_shop pas
JOIN ps_product_attribute_combination ppac
ON ppac.id_product_attribute = pas.id_product_attribute
JOIN ps_attribute_lang pal
ON pal.id_attribute = ppac.id_attribute AND pal.id_lang = ?
JOIN ps_attribute pa
ON pa.id_attribute = ppac.id_attribute
JOIN ps_attribute_group pag
ON pag.id_attribute_group = pa.id_attribute_group
JOIN ps_attribute_group_lang pagl
ON pagl.id_attribute_group = pag.id_attribute_group AND pagl.id_lang = ?
WHERE pas.id_shop = ?
GROUP BY pas.id_product, pagl.public_name
),
variations AS (
SELECT id_product, JSON_OBJECTAGG(attribute_name, attribute_values) AS attributes
FROM variation_attributes
GROUP BY id_product
),
variation_attribute_filters AS (
SELECT pas.id_product,
JSON_ARRAYAGG(
DISTINCT CONCAT(
LOWER(REPLACE(CAST(pagl.public_name AS CHAR) COLLATE utf8mb4_unicode_ci, ' ', '_')),
':',
LOWER(REPLACE(CAST(pal.name AS CHAR) COLLATE utf8mb4_unicode_ci, ' ', '_'))
)
) AS attribute_filters
FROM ps_product_attribute_shop pas
JOIN ps_product_attribute_combination ppac
ON ppac.id_product_attribute = pas.id_product_attribute
JOIN ps_attribute_lang pal
ON pal.id_attribute = ppac.id_attribute AND pal.id_lang = ?
JOIN ps_attribute pa
ON pa.id_attribute = ppac.id_attribute
JOIN ps_attribute_group pag
ON pag.id_attribute_group = pa.id_attribute_group
JOIN ps_attribute_group_lang pagl
ON pagl.id_attribute_group = pag.id_attribute_group AND pagl.id_lang = ?
WHERE pas.id_shop = ?
GROUP BY pas.id_product
),
features AS (
SELECT pfp.id_product, JSON_OBJECTAGG(pfl.name, pfvl.value) AS features
FROM ps_feature_product pfp
JOIN ps_feature_lang pfl
ON pfl.id_feature = pfp.id_feature AND pfl.id_lang = ?
JOIN ps_feature_value_lang pfvl
ON pfvl.id_feature_value = pfp.id_feature_value AND pfvl.id_lang = ?
GROUP BY pfp.id_product
),
images AS (
SELECT id_product, id_image
FROM ps_image_shop
WHERE id_shop = ? AND cover = 1
),
categories AS (
SELECT id_product, JSON_ARRAYAGG(id_category) AS category_ids
FROM ps_category_product
GROUP BY id_product
)
SELECT pp.id_product,
pl.name,
TRIM(REGEXP_REPLACE(REGEXP_REPLACE(pl.description, '<[^>]*>', ' '), '[[:space:]]+', ' ')) AS description,
TRIM(REGEXP_REPLACE(REGEXP_REPLACE(pl.description_short, '<[^>]*>', ' '), '[[:space:]]+', ' ')) AS description_short,
TRIM(REGEXP_REPLACE(REGEXP_REPLACE(pl.usage, '<[^>]*>', ' '), '[[:space:]]+', ' ')) AS used_for,
p.ean13,
p.reference,
pp.price,
ps.id_category_default AS id_category,
cl.name AS category_name,
cl.link_rewrite,
COALESCE(vary.attributes, JSON_OBJECT()) AS attributes,
COALESCE(vaf.attribute_filters, JSON_ARRAY()) AS attribute_filters,
COALESCE(feat.features, JSON_OBJECT()) AS features,
img.id_image,
cat.category_ids,
(SELECT COUNT(*) FROM ps_product_attribute_shop pas2 WHERE pas2.id_product = pp.id_product AND pas2.id_shop = ?) AS variations
FROM products_page pp
JOIN ps_product_shop ps ON ps.id_product = pp.id_product
JOIN ps_product_lang pl
ON pl.id_product = ps.id_product AND pl.id_shop = ? AND pl.id_lang = ?
JOIN ps_product p ON p.id_product = ps.id_product
JOIN ps_category_lang cl
ON cl.id_category = ps.id_category_default AND cl.id_shop = ? AND cl.id_lang = ?
LEFT JOIN variations vary ON vary.id_product = ps.id_product
LEFT JOIN variation_attribute_filters vaf ON vaf.id_product = ps.id_product
LEFT JOIN features feat ON feat.id_product = ps.id_product
LEFT JOIN images img ON img.id_product = ps.id_product
LEFT JOIN categories cat ON cat.id_product = ps.id_product
ORDER BY ps.id_product
`,
constdata.SHOP_ID, // products_page
id_lang, id_lang, // variation_attributes pal.id_lang, pagl.id_lang
constdata.SHOP_ID, // variation_attributes pas.id_shop
id_lang, id_lang, // variation_attribute_filters pal.id_lang, pagl.id_lang
constdata.SHOP_ID, // variation_attribute_filters pas.id_shop
id_lang, id_lang, // features pfl.id_lang, pfvl.id_lang
constdata.SHOP_ID, // images id_shop
constdata.SHOP_ID, // variation count subquery
constdata.SHOP_ID, // ps_product_lang pl.id_shop
id_lang, // ps_product_lang pl.id_lang
constdata.SHOP_ID, // ps_category_lang cl.id_shop
id_lang, // ps_category_lang cl.id_lang
)
if err := query.Scan(&products).Error; err != nil {
return products, fmt.Errorf("database error: %w", err)
}
err := db.Get().
Table("ps_product_shop ps").
Select(`
ps.id_product AS id_product,
pl.name AS name,
TRIM(REGEXP_REPLACE(REGEXP_REPLACE(pl.description_short, '<[^>]*>', ' '), '[[:space:]]+', ' ')) AS description,
p.ean13,
p.reference,
ps.price,
ps.id_category_default AS id_category,
cl.name AS cat_name,
cl.link_rewrite AS l_rew,
COALESCE(vary.attributes, JSON_OBJECT()) AS attr,
COALESCE(feat.features, JSON_OBJECT()) AS feat,
img.id_image,
cat.category_ids,
(SELECT COUNT(*) FROM ps_product_attribute_shop pas2 WHERE pas2.id_product = ps.id_product AND pas2.id_shop = ?) AS variations
`, constdata.SHOP_ID).
Joins("JOIN ps_product p ON p.id_product = ps.id_product").
Joins("JOIN ps_product_lang pl ON pl.id_product = ps.id_product AND pl.id_shop = ? AND pl.id_lang = ?", constdata.SHOP_ID, id_lang).
Joins("JOIN ps_category_lang cl ON cl.id_category = ps.id_category_default AND cl.id_shop = ? AND cl.id_lang = ?", constdata.SHOP_ID, id_lang).
Joins("LEFT JOIN variations vary ON vary.id_product = ps.id_product").
Joins("LEFT JOIN features feat ON feat.id_product = ps.id_product").
Joins("LEFT JOIN images img ON img.id_product = ps.id_product").
Joins("LEFT JOIN categories cat ON cat.id_product = ps.id_product").
Joins("JOIN products_page pp ON pp.id_product = ps.id_product").
Where("ps.active = ?", 1).
Order("ps.id_product").
Clauses(exclause.With{CTEs: []exclause.CTE{
{
Name: "products_page",
Subquery: exclause.Subquery{
DB: db.Get().
Model(&dbmodel.PsProductShop{}).
Select("id_product, price").
Where("id_shop = ? AND active = 1", constdata.SHOP_ID).
Order("id_product").
Limit(limit).
Offset(offset),
},
},
{
Name: "variation_attributes",
Subquery: exclause.Subquery{
DB: db.Get().
Table("ps_product_attribute_shop pas"). // <- explicit alias here
Select(`
pas.id_product,
pag.id_attribute_group AS attribute_name,
JSON_ARRAYAGG(DISTINCT pa.id_attribute) AS attribute_values
`).
Joins("JOIN ps_product_attribute_combination ppac ON ppac.id_product_attribute = pas.id_product_attribute").
Joins("JOIN ps_attribute pa ON pa.id_attribute = ppac.id_attribute").
Joins("JOIN ps_attribute_group pag ON pag.id_attribute_group = pa.id_attribute_group").
Where("pas.id_shop = ?", constdata.SHOP_ID).
Group("pas.id_product, pag.id_attribute_group"),
},
},
{
Name: "variations",
Subquery: exclause.Subquery{
DB: db.Get().
Table("variation_attributes").
Select("id_product, JSON_OBJECTAGG(attribute_name, attribute_values) AS attributes").
Group("id_product"),
},
},
{
Name: "features",
Subquery: exclause.Subquery{
DB: db.Get().
Table("ps_feature_product pfp"). // <- explicit alias
Select("pfp.id_product, JSON_OBJECTAGG(pfp.id_feature, pfp.id_feature_value) AS features").
Group("pfp.id_product"),
},
},
{
Name: "images",
Subquery: exclause.Subquery{
DB: db.Get().
Model(&dbmodel.PsImageShop{}).
Select("id_product, id_image").
Where("id_shop = ? AND cover = 1", constdata.SHOP_ID),
},
},
{
Name: "categories",
Subquery: exclause.Subquery{
DB: db.Get().
Model(&dbmodel.PsCategoryProduct{}).
Select("id_product, JSON_ARRAYAGG(id_category) AS category_ids").
Group("id_product"),
},
},
}}).Find(&products).Error
return products, nil
return products, err
}