-- +goose Up DELIMITER // DROP FUNCTION IF EXISTS fn_product_price // CREATE FUNCTION fn_product_price( p_id_product INT UNSIGNED, p_id_shop INT UNSIGNED, p_id_customer INT UNSIGNED, p_id_country INT UNSIGNED, p_quantity INT UNSIGNED, p_id_product_attribute INT UNSIGNED ) RETURNS JSON DETERMINISTIC READS SQL DATA BEGIN DECLARE v_tax_rate DECIMAL(10,4) DEFAULT 0; DECLARE v_tax_group INT; DECLARE v_base_raw DECIMAL(20,6); DECLARE v_base DECIMAL(20,6); DECLARE v_excl DECIMAL(20,6); DECLARE v_incl DECIMAL(20,6); DECLARE v_reduction_type VARCHAR(20); DECLARE v_percentage DECIMAL(10,4); DECLARE v_fixed_price DECIMAL(20,6); DECLARE v_specific_currency_id BIGINT; DECLARE v_has_specific INT DEFAULT 0; DECLARE v_target_currency BIGINT; DECLARE v_target_rate DECIMAL(13,6) DEFAULT 1; DECLARE v_specific_rate DECIMAL(13,6) DEFAULT 1; DECLARE v_is_no_vat TINYINT DEFAULT 0; SET p_id_product_attribute = NULLIF(p_id_product_attribute, 0); -- ================= CUSTOMER VAT ================= SELECT COALESCE(c.is_no_vat, 0) INTO v_is_no_vat FROM b2b_customers c WHERE c.id = p_id_customer LIMIT 1; -- ================= TAX GROUP ================= SELECT ps.id_tax_rules_group INTO v_tax_group FROM ps_product_shop ps WHERE ps.id_product = p_id_product AND ps.id_shop = p_id_shop LIMIT 1; -- ================= TAX ================= SELECT COALESCE(t.rate, 0) INTO v_tax_rate FROM ps_tax_rule tr 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 = v_tax_group AND tr.id_country = c.ps_id_country LIMIT 1; IF v_is_no_vat = 1 THEN SET v_tax_rate = 0; END IF; -- ================= CURRENCY ================= SELECT c.b2b_id_currency, r.conversion_rate INTO v_target_currency, v_target_rate FROM b2b_countries c LEFT JOIN b2b_currency_rates r ON r.b2b_id_currency = c.b2b_id_currency WHERE c.id = p_id_country ORDER BY r.created_at DESC LIMIT 1; -- ================= BASE PRICE ================= SELECT COALESCE(ps.price, p.price) + COALESCE(pas.price, 0) INTO v_base_raw FROM ps_product p 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 = p_id_product_attribute AND pas.id_shop = p_id_shop WHERE p.id_product = p_id_product; SET v_base = v_base_raw * v_target_rate; SET v_excl = v_base; -- ================= RULE SELECTION ================= 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 spp ON spp.b2b_specific_price_id = bsp.id AND spp.id_product = p_id_product LEFT JOIN b2b_specific_price_product_attribute spa ON spa.b2b_specific_price_id = bsp.id AND spa.id_product_attribute = p_id_product_attribute LEFT JOIN b2b_specific_price_customer spc ON spc.b2b_specific_price_id = bsp.id AND spc.b2b_id_customer = p_id_customer LEFT JOIN b2b_specific_price_country spco ON spco.b2b_specific_price_id = bsp.id AND spco.b2b_id_country = p_id_country LEFT JOIN b2b_specific_price_category spcat ON spcat.b2b_specific_price_id = bsp.id LEFT JOIN ps_category_product cp ON cp.id_category = spcat.id_category AND cp.id_product = p_id_product WHERE bsp.is_active = 1 AND bsp.from_quantity <= p_quantity AND (spp.id_product IS NOT NULL OR NOT EXISTS ( SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id )) AND (spa.id_product_attribute IS NOT NULL OR NOT EXISTS ( SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id )) AND (spc.b2b_id_customer IS NOT NULL OR NOT EXISTS ( SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id )) AND (spco.b2b_id_country IS NOT NULL OR NOT EXISTS ( SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id )) AND (cp.id_product IS NOT NULL OR NOT EXISTS ( SELECT 1 FROM b2b_specific_price_category x WHERE x.b2b_specific_price_id = bsp.id )) ORDER BY (spc.b2b_id_customer IS NOT NULL) DESC, (spa.id_product_attribute IS NOT NULL) DESC, (spp.id_product IS NOT NULL) DESC, (cp.id_product IS NOT NULL) DESC, (spco.b2b_id_country IS NOT NULL) DESC, bsp.id DESC LIMIT 1; -- ================= APPLY ================= IF v_has_specific = 1 THEN IF v_reduction_type = 'amount' THEN IF v_specific_currency_id IS NOT NULL AND v_specific_currency_id != v_target_currency THEN SELECT r.conversion_rate INTO v_specific_rate FROM b2b_currency_rates r WHERE r.b2b_id_currency = v_specific_currency_id ORDER BY r.created_at DESC LIMIT 1; SET v_excl = (v_fixed_price / v_specific_rate) * v_target_rate; ELSE SET v_excl = v_fixed_price; END IF; ELSEIF v_reduction_type = 'percentage' THEN SET v_excl = v_base * (1 - v_percentage / 100); END IF; END IF; SET v_incl = v_excl * (1 + v_tax_rate / 100); RETURN JSON_OBJECT( 'base', v_base, 'final_tax_excl', v_excl, 'final_tax_incl', v_incl, 'tax_rate', v_tax_rate, 'rate', v_target_rate ); END // DELIMITER ; DELIMITER // DROP PROCEDURE IF EXISTS get_product_variants // CREATE PROCEDURE get_product_variants( IN p_id_product INT, IN p_id_shop INT, IN p_id_lang INT, IN p_id_customer INT, IN p_id_country INT, IN p_quantity INT ) BEGIN SELECT pa.id_product_attribute, pa.reference, -- PRICE (computed once per row via correlated subquery) CAST(JSON_UNQUOTE(JSON_EXTRACT( fn_product_price( p_id_product, p_id_shop, p_id_customer, p_id_country, p_quantity, pa.id_product_attribute ), '$.base' )) AS DECIMAL(20,6)) AS base_price, CAST(JSON_UNQUOTE(JSON_EXTRACT( fn_product_price( p_id_product, p_id_shop, p_id_customer, p_id_country, p_quantity, pa.id_product_attribute ), '$.final_tax_excl' )) AS DECIMAL(20,6)) AS price_tax_excl, CAST(JSON_UNQUOTE(JSON_EXTRACT( fn_product_price( p_id_product, p_id_shop, p_id_customer, p_id_country, p_quantity, pa.id_product_attribute ), '$.final_tax_incl' )) AS DECIMAL(20,6)) AS price_tax_incl, IFNULL(sa.quantity, 0) AS quantity, ( 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 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 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, IN p_id_shop INT, IN p_id_customer INT, IN p_id_country INT, IN p_quantity INT ) BEGIN SELECT fn_product_price( p_id_product, p_id_shop, p_id_customer, p_id_country, p_quantity, NULL ) AS price; END // DELIMITER ; DELIMITER // DROP PROCEDURE IF EXISTS get_product_base // CREATE PROCEDURE get_product_base( IN p_id_product INT, IN p_id_shop INT, IN p_id_lang INT, IN p_id_customer INT ) BEGIN SELECT p.id_product AS id, -- matches view.Product.ID p.reference, p.supplier_reference, p.ean13, p.upc, p.isbn, -- Price related (basic) p.price AS base_price, p.wholesale_price, p.unity, p.unit_price_ratio, -- Stock & Availability p.quantity, p.minimal_quantity, p.available_for_order, p.available_date, p.out_of_stock AS out_of_stock_behavior, -- 0=deny, 1=allow, 2=default -- Flags COALESCE(ps.on_sale, 0) AS on_sale, COALESCE(ps.show_price, 1) AS show_price, p.condition, p.is_virtual, -- Physical p.weight, p.width, p.height, p.depth, p.additional_shipping_cost, -- Delivery p.additional_delivery_times AS delivery_days, -- you can adjust if needed -- Status COALESCE(ps.active, p.active) AS active, COALESCE(ps.visibility, p.visibility) AS visibility, p.indexed, -- Other useful p.date_add, p.date_upd, -- Language data pl.name, pl.description, pl.description_short, -- Relations m.name AS manufacturer, cl.name AS category, p.is_oem, EXISTS( SELECT 1 FROM b2b_favorites f WHERE f.user_id = p_id_customer AND f.product_id = p_id_product ) AS is_favorite, CASE WHEN ps.date_add >= DATE_SUB( NOW(), INTERVAL COALESCE(CAST(ps_configuration.value AS SIGNED), 20) DAY ) AND ps.active = 1 THEN 1 ELSE 0 END AS is_new FROM ps_product p LEFT JOIN ps_product_shop ps ON ps.id_product = p.id_product AND ps.id_shop = p_id_shop LEFT JOIN ps_product_lang pl ON pl.id_product = p.id_product AND pl.id_lang = p_id_lang AND pl.id_shop = p_id_shop LEFT JOIN ps_category_lang cl ON cl.id_category = COALESCE(ps.id_category_default, p.id_category_default) AND cl.id_lang = p_id_lang AND cl.id_shop = p_id_shop LEFT JOIN ps_manufacturer m ON m.id_manufacturer = p.id_manufacturer LEFT JOIN ps_configuration ON ps_configuration.name = PS_NB_DAYS_NEW_PRODUCT WHERE p.id_product = p_id_product LIMIT 1; END // DELIMITER ; -- +goose Down