From f55d59a0fda0088be4af51a04b618dfbc60b5d9b Mon Sep 17 00:00:00 2001 From: Wiktor Date: Tue, 14 Apr 2026 13:12:21 +0200 Subject: [PATCH 1/3] feat: add no vat property to customers --- app/delivery/web/api/restricted/customer.go | 27 +++++++++++++++++++ app/model/customer.go | 1 + app/repos/customerRepo/customerRepo.go | 5 ++++ .../customerService/customerService.go | 4 +++ bruno/api_v1/customer/Set is_no_vat.yml | 15 +++++++++++ .../20260302163122_create_tables.sql | 3 ++- 6 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 bruno/api_v1/customer/Set is_no_vat.yml diff --git a/app/delivery/web/api/restricted/customer.go b/app/delivery/web/api/restricted/customer.go index 6e1a41c..b458bdc 100644 --- a/app/delivery/web/api/restricted/customer.go +++ b/app/delivery/web/api/restricted/customer.go @@ -3,6 +3,7 @@ package restricted import ( "strconv" + "git.ma-al.com/goc_daniel/b2b/app/delivery/middleware" "git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms" "git.ma-al.com/goc_daniel/b2b/app/model" "git.ma-al.com/goc_daniel/b2b/app/service/customerService" @@ -31,6 +32,7 @@ func CustomerHandlerRoutes(r fiber.Router) fiber.Router { r.Get("", handler.customerData) r.Get("/list", handler.listCustomers) + r.Patch("/no-vat", middleware.Require(perms.UserWriteAny), handler.setCustomerNoVatStatus) return r } @@ -109,3 +111,28 @@ var columnMappingListUsers map[string]string = map[string]string{ "first_name": "users.first_name", "last_name": "users.last_name", } + +func (h *customerHandler) setCustomerNoVatStatus(fc fiber.Ctx) error { + user, ok := localeExtractor.GetCustomer(fc) + if !ok || user == nil { + return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrBadAttribute))) + } + + var req struct { + CustomerID uint `json:"customer_id"` + IsNoVat bool `json:"is_no_vat"` + } + + if err := fc.Bind().Body(&req); err != nil { + return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrJSONBody)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrJSONBody))) + } + + if err := h.service.SetCustomerNoVatStatus(req.CustomerID, req.IsNoVat); err != nil { + return fc.Status(responseErrors.GetErrorStatus(err)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, err))) + } + + return fc.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(fc, response.Message_OK))) +} diff --git a/app/model/customer.go b/app/model/customer.go index cc4b9f1..b15b145 100644 --- a/app/model/customer.go +++ b/app/model/customer.go @@ -35,6 +35,7 @@ type Customer struct { CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + IsNoVat bool `gorm:"default:false" json:"is_no_vat"` } func (u *Customer) HasPermission(permission perms.Permission) bool { diff --git a/app/repos/customerRepo/customerRepo.go b/app/repos/customerRepo/customerRepo.go index 18dea15..c45e35c 100644 --- a/app/repos/customerRepo/customerRepo.go +++ b/app/repos/customerRepo/customerRepo.go @@ -16,6 +16,7 @@ type UICustomerRepo interface { Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error) Save(customer *model.Customer) error Create(customer *model.Customer) error + SetCustomerNoVatStatus(customerID uint, isNoVat bool) error } type CustomerRepo struct{} @@ -111,6 +112,10 @@ func (repo *CustomerRepo) Create(customer *model.Customer) error { return db.DB.Create(customer).Error } +func (repo *CustomerRepo) SetCustomerNoVatStatus(customerID uint, isNoVat bool) error { + return db.DB.Model(&model.Customer{}).Where("id = ?", customerID).Update("is_no_vat", isNoVat).Error +} + // func (repo *CustomerRepo) Search( // customerId uint, // partnerCode string, diff --git a/app/service/customerService/customerService.go b/app/service/customerService/customerService.go index bce463d..be8cc9c 100644 --- a/app/service/customerService/customerService.go +++ b/app/service/customerService/customerService.go @@ -24,3 +24,7 @@ func (s *CustomerService) GetById(id uint) (*model.Customer, error) { func (s *CustomerService) Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error) { return s.repo.Find(langId, p, filt, search) } + +func (s *CustomerService) SetCustomerNoVatStatus(customerID uint, isNoVat bool) error { + return s.repo.SetCustomerNoVatStatus(customerID, isNoVat) +} diff --git a/bruno/api_v1/customer/Set is_no_vat.yml b/bruno/api_v1/customer/Set is_no_vat.yml new file mode 100644 index 0000000..c159ad3 --- /dev/null +++ b/bruno/api_v1/customer/Set is_no_vat.yml @@ -0,0 +1,15 @@ +info: + name: Set is_no_vat + type: http + seq: 4 + +http: + method: PATCH + url: "{{bas_url" + auth: inherit + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 diff --git a/i18n/migrations/20260302163122_create_tables.sql b/i18n/migrations/20260302163122_create_tables.sql index c03014a..825ad46 100644 --- a/i18n/migrations/20260302163122_create_tables.sql +++ b/i18n/migrations/20260302163122_create_tables.sql @@ -112,7 +112,8 @@ CREATE TABLE IF NOT EXISTS b2b_customers ( country_id INT NULL DEFAULT 2, created_at DATETIME(6) NULL, updated_at DATETIME(6) NULL, - deleted_at DATETIME(6) NULL + deleted_at DATETIME(6) NULL, + is_no_vat TINYINT(1) NULL DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE UNIQUE INDEX IF NOT EXISTS idx_customers_email -- 2.49.1 From 1bf706dcd06b5232d0c1130b4a9653d53b6fcf34 Mon Sep 17 00:00:00 2001 From: Wiktor Date: Wed, 15 Apr 2026 12:55:14 +0200 Subject: [PATCH 2/3] feat: add no vat customers logic --- bruno/api_v1/customer/Set is_no_vat.yml | 9 +- i18n/migrations/20260319163200_procedures.sql | 143 +++++++++--------- 2 files changed, 82 insertions(+), 70 deletions(-) diff --git a/bruno/api_v1/customer/Set is_no_vat.yml b/bruno/api_v1/customer/Set is_no_vat.yml index c159ad3..eaa70ff 100644 --- a/bruno/api_v1/customer/Set is_no_vat.yml +++ b/bruno/api_v1/customer/Set is_no_vat.yml @@ -5,7 +5,14 @@ info: http: method: PATCH - url: "{{bas_url" + url: "{{bas_url}}/restricted/customer/no-vat" + body: + type: json + data: |- + { + "customer_id":1, + "is_no_vat": false + } auth: inherit settings: diff --git a/i18n/migrations/20260319163200_procedures.sql b/i18n/migrations/20260319163200_procedures.sql index 267985a..b823a3a 100644 --- a/i18n/migrations/20260319163200_procedures.sql +++ b/i18n/migrations/20260319163200_procedures.sql @@ -16,6 +16,7 @@ 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); @@ -29,45 +30,54 @@ BEGIN DECLARE v_has_specific INT DEFAULT 0; - -- currency 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 = ( - 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 + WHERE tr.id_tax_rules_group = v_tax_group + AND tr.id_country = c.ps_id_country LIMIT 1; - -- ================= TARGET CURRENCY ================= - SELECT c.b2b_id_currency - INTO v_target_currency + 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 - LIMIT 1; - - -- latest target rate - SELECT r.conversion_rate - INTO v_target_rate - FROM b2b_currency_rates r - WHERE r.b2b_id_currency = v_target_currency ORDER BY r.created_at DESC LIMIT 1; - -- ================= BASE PRICE (RAW) ================= + -- ================= BASE PRICE ================= SELECT COALESCE(ps.price, p.price) + COALESCE(pas.price, 0) INTO v_base_raw @@ -79,8 +89,8 @@ BEGIN AND pas.id_shop = p_id_shop WHERE p.id_product = p_id_product; - -- convert base to target currency SET v_base = v_base_raw * v_target_rate; + SET v_excl = v_base; -- ================= RULE SELECTION ================= SELECT @@ -99,71 +109,67 @@ BEGIN 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 - -- intersection rules (unchanged) - AND ( - NOT EXISTS (SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id) - OR EXISTS (SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product = p_id_product) - ) + 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 ( - NOT EXISTS (SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id) - OR EXISTS (SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product_attribute = p_id_product_attribute) - ) + 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 ( - NOT EXISTS (SELECT 1 FROM b2b_specific_price_category x WHERE x.b2b_specific_price_id = bsp.id) - OR EXISTS ( - SELECT 1 FROM b2b_specific_price_category x - JOIN ps_category_product cp ON cp.id_category = x.id_category - WHERE x.b2b_specific_price_id = bsp.id AND cp.id_product = p_id_product - ) - ) + 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 ( - NOT EXISTS (SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id) - OR EXISTS (SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_customer = p_id_customer) - ) + 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 ( - NOT EXISTS (SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id) - OR EXISTS (SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_country = p_id_country) - ) + 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 - -- customer wins - (EXISTS (SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_customer = p_id_customer)) DESC, - - -- attribute - (EXISTS (SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product_attribute = p_id_product_attribute)) DESC, - - -- product - (EXISTS (SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product = p_id_product)) DESC, - - -- category - (EXISTS ( - SELECT 1 FROM b2b_specific_price_category x - JOIN ps_category_product cp ON cp.id_category = x.id_category - WHERE x.b2b_specific_price_id = bsp.id AND cp.id_product = p_id_product - )) DESC, - - -- country - (EXISTS (SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_country = p_id_country)) DESC, - + (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 ================= - SET v_excl = v_base; - IF v_has_specific = 1 THEN IF v_reduction_type = 'amount' THEN - -- convert specific price currency if needed IF v_specific_currency_id IS NOT NULL AND v_specific_currency_id != v_target_currency THEN SELECT r.conversion_rate @@ -173,7 +179,6 @@ BEGIN ORDER BY r.created_at DESC LIMIT 1; - -- normalize → then convert to target SET v_excl = (v_fixed_price / v_specific_rate) * v_target_rate; ELSE -- 2.49.1 From e9af4bf3114f08b8665651ab38c3a98f83e152ee Mon Sep 17 00:00:00 2001 From: Wiktor Date: Wed, 15 Apr 2026 12:55:14 +0200 Subject: [PATCH 3/3] feat: add no vat customers logic --- app/delivery/web/api/restricted/customer.go | 4 +- bruno/api_v1/customer/Set is_no_vat.yml | 9 +- i18n/migrations/20260319163200_procedures.sql | 143 +++++++++--------- 3 files changed, 84 insertions(+), 72 deletions(-) diff --git a/app/delivery/web/api/restricted/customer.go b/app/delivery/web/api/restricted/customer.go index 6608892..9d15c11 100644 --- a/app/delivery/web/api/restricted/customer.go +++ b/app/delivery/web/api/restricted/customer.go @@ -105,8 +105,8 @@ var columnMappingListUsers map[string]string = map[string]string{ func (h *customerHandler) setCustomerNoVatStatus(fc fiber.Ctx) error { user, ok := localeExtractor.GetCustomer(fc) if !ok || user == nil { - return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)). - JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrBadAttribute))) + return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrInvalidBody))) } var req struct { diff --git a/bruno/api_v1/customer/Set is_no_vat.yml b/bruno/api_v1/customer/Set is_no_vat.yml index c159ad3..eaa70ff 100644 --- a/bruno/api_v1/customer/Set is_no_vat.yml +++ b/bruno/api_v1/customer/Set is_no_vat.yml @@ -5,7 +5,14 @@ info: http: method: PATCH - url: "{{bas_url" + url: "{{bas_url}}/restricted/customer/no-vat" + body: + type: json + data: |- + { + "customer_id":1, + "is_no_vat": false + } auth: inherit settings: diff --git a/i18n/migrations/20260319163200_procedures.sql b/i18n/migrations/20260319163200_procedures.sql index 267985a..b823a3a 100644 --- a/i18n/migrations/20260319163200_procedures.sql +++ b/i18n/migrations/20260319163200_procedures.sql @@ -16,6 +16,7 @@ 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); @@ -29,45 +30,54 @@ BEGIN DECLARE v_has_specific INT DEFAULT 0; - -- currency 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 = ( - 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 + WHERE tr.id_tax_rules_group = v_tax_group + AND tr.id_country = c.ps_id_country LIMIT 1; - -- ================= TARGET CURRENCY ================= - SELECT c.b2b_id_currency - INTO v_target_currency + 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 - LIMIT 1; - - -- latest target rate - SELECT r.conversion_rate - INTO v_target_rate - FROM b2b_currency_rates r - WHERE r.b2b_id_currency = v_target_currency ORDER BY r.created_at DESC LIMIT 1; - -- ================= BASE PRICE (RAW) ================= + -- ================= BASE PRICE ================= SELECT COALESCE(ps.price, p.price) + COALESCE(pas.price, 0) INTO v_base_raw @@ -79,8 +89,8 @@ BEGIN AND pas.id_shop = p_id_shop WHERE p.id_product = p_id_product; - -- convert base to target currency SET v_base = v_base_raw * v_target_rate; + SET v_excl = v_base; -- ================= RULE SELECTION ================= SELECT @@ -99,71 +109,67 @@ BEGIN 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 - -- intersection rules (unchanged) - AND ( - NOT EXISTS (SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id) - OR EXISTS (SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product = p_id_product) - ) + 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 ( - NOT EXISTS (SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id) - OR EXISTS (SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product_attribute = p_id_product_attribute) - ) + 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 ( - NOT EXISTS (SELECT 1 FROM b2b_specific_price_category x WHERE x.b2b_specific_price_id = bsp.id) - OR EXISTS ( - SELECT 1 FROM b2b_specific_price_category x - JOIN ps_category_product cp ON cp.id_category = x.id_category - WHERE x.b2b_specific_price_id = bsp.id AND cp.id_product = p_id_product - ) - ) + 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 ( - NOT EXISTS (SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id) - OR EXISTS (SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_customer = p_id_customer) - ) + 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 ( - NOT EXISTS (SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id) - OR EXISTS (SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_country = p_id_country) - ) + 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 - -- customer wins - (EXISTS (SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_customer = p_id_customer)) DESC, - - -- attribute - (EXISTS (SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product_attribute = p_id_product_attribute)) DESC, - - -- product - (EXISTS (SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product = p_id_product)) DESC, - - -- category - (EXISTS ( - SELECT 1 FROM b2b_specific_price_category x - JOIN ps_category_product cp ON cp.id_category = x.id_category - WHERE x.b2b_specific_price_id = bsp.id AND cp.id_product = p_id_product - )) DESC, - - -- country - (EXISTS (SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_country = p_id_country)) DESC, - + (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 ================= - SET v_excl = v_base; - IF v_has_specific = 1 THEN IF v_reduction_type = 'amount' THEN - -- convert specific price currency if needed IF v_specific_currency_id IS NOT NULL AND v_specific_currency_id != v_target_currency THEN SELECT r.conversion_rate @@ -173,7 +179,6 @@ BEGIN ORDER BY r.created_at DESC LIMIT 1; - -- normalize → then convert to target SET v_excl = (v_fixed_price / v_specific_rate) * v_target_rate; ELSE -- 2.49.1