feat: product_attribute list with prices

This commit is contained in:
2026-04-09 09:51:06 +02:00
parent d56650ae5d
commit 75af44b0df
10 changed files with 736 additions and 45 deletions

View File

@@ -377,4 +377,537 @@ LIMIT
END //
DELIMITER ;
-----------------------------------
DELIMITER //
DROP PROCEDURE IF EXISTS get_product_attributes_with_price //
CREATE PROCEDURE get_product_attributes_with_price(
IN p_id_lang INT UNSIGNED,
IN p_id_product INT UNSIGNED,
IN p_id_shop INT UNSIGNED,
IN p_id_customer INT UNSIGNED,
IN p_id_country INT UNSIGNED,
IN p_quantity INT UNSIGNED
)
BEGIN
DECLARE v_tax_rate DECIMAL(10,4) DEFAULT 0;
DECLARE v_currency_rate DECIMAL(10,4) DEFAULT 1;
-- =========================================================
-- TAX
-- =========================================================
SELECT COALESCE(t.rate, 0)
INTO v_tax_rate
FROM ps_tax_rule tr
INNER JOIN ps_tax t ON t.id_tax = tr.id_tax
LEFT JOIN b2b_countries c ON c.id = p_id_country
WHERE tr.id_tax_rules_group = (
SELECT ps.id_tax_rules_group
FROM ps_product_shop ps
WHERE ps.id_product = p_id_product
AND ps.id_shop = p_id_shop
LIMIT 1
)
AND tr.id_country = c.ps_id_country
LIMIT 1;
-- =========================================================
-- CURRENCY
-- =========================================================
SELECT COALESCE(r.conversion_rate, 1)
INTO v_currency_rate
FROM b2b_countries c
LEFT JOIN b2b_currencies cur ON cur.id = c.b2b_id_currency
LEFT JOIN b2b_currency_rates r ON r.b2b_id_currency = cur.id
WHERE c.id = p_id_country
ORDER BY r.created_at DESC
LIMIT 1;
-- =========================================================
-- MAIN RESULT
-- =========================================================
SELECT
pa.id_product_attribute,
pa.reference,
-- =====================================================
-- BASE PRICE (product + attribute impact)
-- =====================================================
(
(COALESCE(ps.price, p.price) + COALESCE(pas.price, 0))
* v_currency_rate
) AS base_price,
-- =====================================================
-- FINAL PRICE EXCL (FULL RULE ENGINE)
-- =====================================================
COALESCE(sp.price_tax_excl,
(COALESCE(ps.price, p.price) + COALESCE(pas.price, 0)) * v_currency_rate
) AS price_tax_excl,
-- =====================================================
-- FINAL PRICE INCL
-- =====================================================
(
COALESCE(sp.price_tax_excl,
(COALESCE(ps.price, p.price) + COALESCE(pas.price, 0)) * v_currency_rate
)
) * (1 + v_tax_rate / 100) AS price_tax_incl,
-- =====================================================
-- STOCK
-- =====================================================
IFNULL(sa.quantity, 0) AS quantity,
-- =====================================================
-- ATTRIBUTES
-- =====================================================
(
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'group', agl.name,
'attribute', al.name
)
)
FROM ps_product_attribute_combination pac
JOIN ps_attribute a ON a.id_attribute = pac.id_attribute
JOIN ps_attribute_lang al
ON al.id_attribute = a.id_attribute AND al.id_lang = p_id_lang
JOIN ps_attribute_group_lang agl
ON agl.id_attribute_group = a.id_attribute_group AND agl.id_lang = p_id_lang
WHERE pac.id_product_attribute = pa.id_product_attribute
) AS attributes
FROM ps_product_attribute pa
JOIN ps_product_attribute_shop pas
ON pas.id_product_attribute = pa.id_product_attribute
AND pas.id_shop = p_id_shop
JOIN ps_product p
ON p.id_product = pa.id_product
LEFT JOIN ps_product_shop ps
ON ps.id_product = p.id_product
AND ps.id_shop = p_id_shop
LEFT JOIN ps_stock_available sa
ON sa.id_product = pa.id_product
AND sa.id_product_attribute = pa.id_product_attribute
AND sa.id_shop = p_id_shop
-- =====================================================
-- FULL SPECIFIC PRICE ENGINE (IDENTICAL RULES TO MAIN)
-- =====================================================
LEFT JOIN (
SELECT *
FROM (
SELECT
pa.id_product_attribute,
-- FINAL PRICE
CASE
WHEN bsp.reduction_type = 'amount' THEN
CASE
WHEN bsp.b2b_id_currency IS NULL THEN bsp.price
ELSE bsp.price
END
WHEN bsp.reduction_type = 'percentage' THEN
(
(COALESCE(ps.price, p.price) + COALESCE(pas.price, 0))
* v_currency_rate
) * (1 - bsp.percentage_reduction / 100)
ELSE
(
(COALESCE(ps.price, p.price) + COALESCE(pas.price, 0))
* v_currency_rate
)
END AS price_tax_excl,
ROW_NUMBER() OVER (
PARTITION BY pa.id_product_attribute
ORDER BY
bsp.scope = 'product' DESC,
bsp.scope = 'category' DESC,
bsp.scope = 'shop' DESC,
bsp.from_quantity DESC,
bsp.id DESC
) AS rn
FROM ps_product_attribute pa
JOIN ps_product p ON p.id_product = pa.id_product
LEFT JOIN ps_product_shop ps
ON ps.id_product = p.id_product
AND ps.id_shop = p_id_shop
LEFT JOIN ps_product_attribute_shop pas
ON pas.id_product_attribute = pa.id_product_attribute
JOIN b2b_specific_price bsp ON bsp.is_active = TRUE
LEFT JOIN b2b_specific_price_product bsp_p
ON bsp_p.b2b_specific_price_id = bsp.id
LEFT JOIN b2b_specific_price_category bsp_c
ON bsp_c.b2b_specific_price_id = bsp.id
WHERE pa.id_product = p_id_product
-- =========================
-- SCOPE
-- =========================
AND (
(bsp.scope = 'product' AND bsp_p.id_product = p_id_product)
OR (
bsp.scope = 'category'
AND bsp_c.id_category IN (
SELECT id_category
FROM ps_category_product
WHERE id_product = p_id_product
)
)
OR (bsp.scope = 'shop')
)
-- =========================
-- CUSTOMER
-- =========================
AND (
NOT EXISTS (
SELECT 1 FROM b2b_specific_price_customer c
WHERE c.b2b_specific_price_id = bsp.id
)
OR EXISTS (
SELECT 1 FROM b2b_specific_price_customer c
WHERE c.b2b_specific_price_id = bsp.id
AND c.b2b_id_customer = p_id_customer
)
)
-- =========================
-- COUNTRY
-- =========================
AND (
NOT EXISTS (
SELECT 1 FROM b2b_specific_price_country c
WHERE c.b2b_specific_price_id = bsp.id
)
OR EXISTS (
SELECT 1 FROM b2b_specific_price_country c
WHERE c.b2b_specific_price_id = bsp.id
AND c.b2b_id_country = p_id_country
)
)
-- =========================
-- ATTRIBUTE
-- =========================
AND (
NOT EXISTS (
SELECT 1 FROM b2b_specific_price_product_attribute a
WHERE a.b2b_specific_price_id = bsp.id
)
OR EXISTS (
SELECT 1 FROM b2b_specific_price_product_attribute a
WHERE a.b2b_specific_price_id = bsp.id
AND a.id_product_attribute = pa.id_product_attribute
)
)
-- =========================
-- QUANTITY
-- =========================
AND bsp.from_quantity <= p_quantity
-- =========================
-- DATE
-- =========================
AND (
bsp.has_expiration_date = FALSE
OR (
(bsp.valid_from IS NULL OR bsp.valid_from <= NOW())
AND (bsp.valid_till IS NULL OR bsp.valid_till >= NOW())
)
)
) ranked
WHERE ranked.rn = 1
) sp ON sp.id_product_attribute = pa.id_product_attribute
WHERE pa.id_product = p_id_product;
END //
DELIMITER ;
--------------------------------
DELIMITER //
DROP PROCEDURE IF EXISTS get_product_price //
CREATE PROCEDURE get_product_price(
IN p_id_product INT UNSIGNED,
IN p_id_shop INT UNSIGNED,
IN p_id_customer INT UNSIGNED,
IN p_id_country INT UNSIGNED,
IN p_quantity INT UNSIGNED
)
BEGIN
DECLARE v_tax_rate DECIMAL(10,4) DEFAULT 0;
DECLARE v_currency_rate DECIMAL(10,4) DEFAULT 1;
DECLARE v_base_price DECIMAL(20,6) DEFAULT 0;
DECLARE v_final_excl DECIMAL(20,6) DEFAULT 0;
DECLARE v_final_incl DECIMAL(20,6) DEFAULT 0;
DECLARE v_has_specific INT DEFAULT 0;
DECLARE v_reduction_type VARCHAR(20);
DECLARE v_percentage DECIMAL(10,4);
DECLARE v_fixed_price DECIMAL(20,6);
DECLARE v_specific_currency_id INT;
-- =========================
-- 1. TAX RATE
-- =========================
SELECT COALESCE(t.rate, 0)
INTO v_tax_rate
FROM ps_tax_rule tr
INNER JOIN ps_tax t ON t.id_tax = tr.id_tax
LEFT JOIN b2b_countries c ON c.id = p_id_country
WHERE tr.id_tax_rules_group = (
SELECT ps.id_tax_rules_group
FROM ps_product_shop ps
WHERE ps.id_product = p_id_product
AND ps.id_shop = p_id_shop
LIMIT 1
)
AND tr.id_country = c.ps_id_country
ORDER BY tr.id_state DESC, tr.zipcode_from DESC, tr.id_tax_rule DESC
LIMIT 1;
-- =========================
-- 2. CURRENCY RATE
-- =========================
SELECT COALESCE(r.conversion_rate, 1)
INTO v_currency_rate
FROM b2b_countries c
LEFT JOIN b2b_currencies cur ON cur.id = c.b2b_id_currency
LEFT JOIN b2b_currency_rates r ON r.b2b_id_currency = cur.id
WHERE c.id = p_id_country
ORDER BY r.created_at DESC
LIMIT 1;
-- =========================
-- 3. BASE PRICE
-- =========================
SELECT COALESCE(ps.price, p.price) * v_currency_rate
INTO v_base_price
FROM ps_product p
LEFT JOIN ps_product_shop ps
ON ps.id_product = p.id_product
AND ps.id_shop = p_id_shop
WHERE p.id_product = p_id_product
LIMIT 1;
-- =========================
-- 4. SPECIFIC PRICE (correct wildcard-aware match)
-- =========================
SELECT
1,
bsp.reduction_type,
bsp.percentage_reduction,
bsp.price,
bsp.b2b_id_currency
INTO
v_has_specific,
v_reduction_type,
v_percentage,
v_fixed_price,
v_specific_currency_id
FROM b2b_specific_price bsp
LEFT JOIN b2b_specific_price_product bsp_p
ON bsp_p.b2b_specific_price_id = bsp.id
LEFT JOIN b2b_specific_price_category bsp_c
ON bsp_c.b2b_specific_price_id = bsp.id
LEFT JOIN b2b_specific_price_customer bsp_u
ON bsp_u.b2b_specific_price_id = bsp.id
LEFT JOIN b2b_specific_price_country bsp_ct
ON bsp_ct.b2b_specific_price_id = bsp.id
LEFT JOIN b2b_specific_price_product_attribute bsp_pa
ON bsp_pa.b2b_specific_price_id = bsp.id
WHERE bsp.is_active = TRUE
-- =========================
-- PRODUCT / CATEGORY / SHOP SCOPE
-- =========================
AND (
(bsp.scope = 'product' AND bsp_p.id_product = p_id_product)
OR (
bsp.scope = 'category'
AND bsp_c.id_category IN (
SELECT id_category
FROM ps_category_product
WHERE id_product = p_id_product
)
)
OR (bsp.scope = 'shop')
)
-- =========================
-- CUSTOMER RULE (wildcard-aware)
-- =========================
AND (
NOT EXISTS (
SELECT 1
FROM b2b_specific_price_customer c
WHERE c.b2b_specific_price_id = bsp.id
)
OR EXISTS (
SELECT 1
FROM b2b_specific_price_customer c
WHERE c.b2b_specific_price_id = bsp.id
AND c.b2b_id_customer = p_id_customer
)
)
-- =========================
-- COUNTRY RULE (wildcard-aware)
-- =========================
AND (
NOT EXISTS (
SELECT 1
FROM b2b_specific_price_country c
WHERE c.b2b_specific_price_id = bsp.id
)
OR EXISTS (
SELECT 1
FROM b2b_specific_price_country c
WHERE c.b2b_specific_price_id = bsp.id
AND c.b2b_id_country = p_id_country
)
)
-- =========================
-- PRODUCT ATTRIBUTE RULE (wildcard-aware)
-- =========================
AND (
NOT EXISTS (
SELECT 1
FROM b2b_specific_price_product_attribute a
WHERE a.b2b_specific_price_id = bsp.id
)
OR EXISTS (
SELECT 1
FROM b2b_specific_price_product_attribute a
WHERE a.b2b_specific_price_id = bsp.id
AND a.id_product_attribute = 0
)
)
-- =========================
-- QUANTITY RULE
-- =========================
AND bsp.from_quantity <= p_quantity
-- =========================
-- DATE RULE (FIXED precedence bug)
-- =========================
AND (
bsp.has_expiration_date = FALSE
OR (
(bsp.valid_from IS NULL OR bsp.valid_from <= NOW())
AND (bsp.valid_till IS NULL OR bsp.valid_till >= NOW())
)
)
-- =========================
-- PRIORITY ORDERING (IMPROVED)
-- =========================
ORDER BY
-- strongest match wins
(EXISTS (
SELECT 1 FROM b2b_specific_price_customer c
WHERE c.b2b_specific_price_id = bsp.id
)) DESC,
(EXISTS (
SELECT 1 FROM b2b_specific_price_country c
WHERE c.b2b_specific_price_id = bsp.id
)) DESC,
bsp.scope = 'product' DESC,
bsp.scope = 'category' DESC,
bsp.scope = 'shop' DESC,
bsp.from_quantity DESC,
bsp.id DESC
LIMIT 1;
-- =========================
-- 5. APPLY SPECIFIC PRICE
-- =========================
SET v_final_excl = v_base_price;
IF v_has_specific = 1 THEN
IF v_reduction_type = 'amount' THEN
IF v_specific_currency_id IS NULL THEN
SET v_final_excl = v_fixed_price;
ELSE
SET v_final_excl = v_fixed_price; -- assume already converted or pre-handled
END IF;
ELSEIF v_reduction_type = 'percentage' THEN
SET v_final_excl = v_base_price * (1 - v_percentage / 100);
END IF;
END IF;
-- =========================
-- 6. TAX
-- =========================
SET v_final_incl = v_final_excl * (1 + v_tax_rate / 100);
-- =========================
-- 7. RETURN RESULT
-- =========================
SELECT
p_id_product AS id_product,
v_base_price AS price_base,
v_final_excl AS price_tax_excl,
v_final_incl AS price_tax_incl,
v_tax_rate AS tax_rate;
END //
DELIMITER ;
-- +goose Down