-- +goose Up DELIMITER // DROP PROCEDURE IF EXISTS get_full_product // CREATE PROCEDURE get_full_product( IN p_id_product INT UNSIGNED, IN p_id_shop INT UNSIGNED, IN p_id_lang INT UNSIGNED, IN p_id_customer INT UNSIGNED, IN b2b_id_country INT UNSIGNED, IN p_quantity INT UNSIGNED ) BEGIN DECLARE v_tax_rate DECIMAL(10, 4) DEFAULT 0; DECLARE p_id_currency DECIMAL(10, 4) DEFAULT 0; SELECT COALESCE(t.rate, 0.0000) 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 ON b2b_countries.id = b2b_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 = b2b_countries.ps_id_country ORDER BY tr.id_state DESC, tr.zipcode_from != '' DESC, tr.id_tax_rule DESC LIMIT 1; SELECT b2b_currencies.ps_id_currency INTO p_id_currency FROM b2b_currencies LEFT JOIN b2b_countries ON b2b_countries.b2b_id_currency = b2b_currencies.id WHERE b2b_countries.id = b2b_id_country LIMIT 1; /* FINAL JSON */ SELECT JSON_OBJECT( /* ================= PRODUCT ================= */ 'id_product', p.id_product, 'reference', p.reference, 'name', pl.name, 'description', pl.description, 'short_description', pl.description_short, /* ================= PRICE ================= */ 'price', JSON_OBJECT( 'base', COALESCE(ps.price * r.conversion_rate, p.price * r.conversion_rate), 'final_tax_excl', ( CASE WHEN bsp.id IS NOT NULL THEN CASE /* FIXED PRICE */ WHEN bsp.reduction_type = 'amount' THEN ( CASE WHEN bsp.b2b_id_currency IS NULL THEN bsp.price ELSE bsp.price * br_bsp.conversion_rate END ) /* PERCENTAGE */ WHEN bsp.reduction_type = 'percentage' THEN COALESCE(ps.price * r.conversion_rate, p.price * r.conversion_rate) * (1 - bsp.percentage_reduction / 100) ELSE COALESCE(ps.price * r.conversion_rate, p.price * r.conversion_rate) END ELSE COALESCE(ps.price * r.conversion_rate, p.price * r.conversion_rate) END ), 'final_tax_incl', ( ( CASE WHEN bsp.id IS NOT NULL THEN CASE WHEN bsp.reduction_type = 'amount' THEN ( CASE WHEN bsp.b2b_id_currency IS NULL THEN bsp.price ELSE bsp.price * br_bsp.conversion_rate END ) WHEN bsp.reduction_type = 'percentage' THEN COALESCE(ps.price * r.conversion_rate, p.price * r.conversion_rate) * (1 - bsp.percentage_reduction / 100) ELSE COALESCE(ps.price * r.conversion_rate, p.price * r.conversion_rate) END ELSE COALESCE(ps.price * r.conversion_rate, p.price * r.conversion_rate) END ) * (1 + v_tax_rate / 100) ) ), /* ================= META ================= */ 'active', COALESCE(ps.active, p.active), 'visibility', COALESCE(ps.visibility, p.visibility), 'manufacturer', m.name, 'category', cl.name, /* ================= IMAGE ================= */ 'cover_image', JSON_OBJECT( 'id', i.id_image, 'legend', il.legend ), /* ================= FEATURES ================= */ 'features', ( SELECT JSON_ARRAYAGG( JSON_OBJECT( 'name', fl.name, 'value', fvl.value ) ) FROM ps_feature_product fp JOIN ps_feature_lang fl ON fl.id_feature = fp.id_feature AND fl.id_lang = p_id_lang JOIN ps_feature_value_lang fvl ON fvl.id_feature_value = fp.id_feature_value AND fvl.id_lang = p_id_lang WHERE fp.id_product = p.id_product ), /* ================= COMBINATIONS ================= */ 'combinations', ( SELECT JSON_ARRAYAGG( JSON_OBJECT( 'id_product_attribute', pa.id_product_attribute, 'reference', pa.reference, 'price', JSON_OBJECT( 'impact', COALESCE(pas.price, pa.price), 'final_tax_excl', ( COALESCE(ps.price, p.price) + COALESCE(pas.price, pa.price) ), 'final_tax_incl', ( ( COALESCE(ps.price, p.price) + COALESCE(pas.price, pa.price) ) * (1 + v_tax_rate / 100) ) ), 'stock', IFNULL(sa.quantity, 0), 'default_on', pas.default_on, /* ATTRIBUTES JSON */ '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 ), /* IMAGES */ 'images', ( SELECT JSON_ARRAYAGG(img.id_image) FROM ps_product_attribute_image pai JOIN ps_image img ON img.id_image = pai.id_image WHERE pai.id_product_attribute = pa.id_product_attribute ) ) ) 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 ) ) AS product_json 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_image i ON i.id_product = p.id_product AND i.cover = 1 LEFT JOIN ps_image_lang il ON il.id_image = i.id_image AND il.id_lang = p_id_lang /* SPECIFIC PRICE */ LEFT JOIN ( SELECT bsp.* FROM b2b_specific_price bsp /* RELATIONS */ 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 bsp.is_active = TRUE /* SCOPE MATCH */ AND ( /* PRODUCT */ (bsp.scope = 'product' AND bsp_p.id_product = p_id_product) /* CATEGORY */ OR ( bsp.scope = 'category' AND bsp_c.id_category IN ( SELECT cp.id_category FROM ps_category_product cp WHERE cp.id_product = p_id_product ) ) /* SHOP (GLOBAL) */ OR (bsp.scope = 'shop') ) /* CUSTOMER MATCH */ 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 MATCH */ AND ( NOT EXISTS ( SELECT 1 FROM b2b_specific_price_country ctry WHERE ctry.b2b_specific_price_id = bsp.id ) OR EXISTS ( SELECT 1 FROM b2b_specific_price_country ctry WHERE ctry.b2b_specific_price_id = bsp.id AND ctry.b2b_id_country = b2b_id_country ) ) /* 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()) ) ) ORDER BY /* 🔥 SCOPE PRIORITY */ bsp.scope = 'product' DESC, bsp.scope = 'category' DESC, bsp.scope = 'shop' DESC, /* 🔥 CUSTOMER PRIORITY */ ( 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 ) ) DESC, /* 🔥 COUNTRY PRIORITY */ ( EXISTS ( SELECT 1 FROM b2b_specific_price_country ctry WHERE ctry.b2b_specific_price_id = bsp.id AND ctry.b2b_id_country = b2b_id_country ) ) DESC, /* GLOBAL fallback (no restrictions) naturally goes last */ bsp.from_quantity DESC, bsp.id DESC LIMIT 1 ) bsp ON 1=1 LEFT JOIN b2b_currency_rates br_bsp ON br_bsp.b2b_id_currency = bsp.b2b_id_currency AND br_bsp.created_at = ( SELECT MAX(created_at) FROM b2b_currency_rates WHERE b2b_id_currency = bsp.b2b_id_currency ) LEFT JOIN b2b_countries ON b2b_countries.id = b2b_id_country LEFT JOIN b2b_currencies ON b2b_currencies.id = p_id_currency LEFT JOIN b2b_currency_rates r ON r.b2b_id_currency = b2b_currencies.id AND r.created_at = ( SELECT MAX(created_at) FROM b2b_currency_rates WHERE b2b_id_currency = b2b_currencies.id ) WHERE p.id_product = p_id_product LIMIT 1; END // DELIMITER ; -- +goose Down