From bb507036db30f44039f288eaddaa08cfc3604006 Mon Sep 17 00:00:00 2001 From: Daniel Goc Date: Tue, 14 Apr 2026 15:42:30 +0200 Subject: [PATCH 1/4] change add-product endpoint + remove-product --- app/delivery/web/api/restricted/carts.go | 80 ++++++++++++++++++- app/repos/cartsRepo/cartsRepo.go | 72 ++++++++++++++--- app/service/cartsService/cartsService.go | 16 +++- app/utils/const_data/consts.go | 1 + app/utils/responseErrors/responseErrors.go | 8 ++ .../carts/add-product-to-cart (1).yml | 5 +- .../b2b_daniel/carts/add-product-to-cart.yml | 5 +- 7 files changed, 172 insertions(+), 15 deletions(-) diff --git a/app/delivery/web/api/restricted/carts.go b/app/delivery/web/api/restricted/carts.go index a787620..34600be 100644 --- a/app/delivery/web/api/restricted/carts.go +++ b/app/delivery/web/api/restricted/carts.go @@ -29,10 +29,12 @@ func CartsHandlerRoutes(r fiber.Router) fiber.Router { handler := NewCartsHandler() r.Get("/add-new-cart", handler.AddNewCart) + r.Delete("/remove-cart", handler.RemoveCart) r.Get("/change-cart-name", handler.ChangeCartName) r.Get("/retrieve-carts-info", handler.RetrieveCartsInfo) r.Get("/retrieve-cart", handler.RetrieveCart) r.Get("/add-product-to-cart", handler.AddProduct) + r.Delete("/remove-product-from-cart", handler.RemoveProduct) return r } @@ -53,6 +55,29 @@ func (h *CartsHandler) AddNewCart(c fiber.Ctx) error { return c.JSON(response.Make(&new_cart, 0, i18n.T_(c, response.Message_OK))) } +func (h *CartsHandler) RemoveCart(c fiber.Ctx) error { + userID, ok := localeExtractor.GetUserID(c) + if !ok { + return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody))) + } + + cart_id_attribute := c.Query("cart_id") + cart_id, err := strconv.Atoi(cart_id_attribute) + if err != nil { + return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute))) + } + + err = h.cartsService.RemoveCart(userID, uint(cart_id)) + if err != nil { + return c.Status(responseErrors.GetErrorStatus(err)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err))) + } + + return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK))) +} + func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error { userID, ok := localeExtractor.GetUserID(c) if !ok { @@ -117,6 +142,7 @@ func (h *CartsHandler) RetrieveCart(c fiber.Ctx) error { return c.JSON(response.Make(cart, 0, i18n.T_(c, response.Message_OK))) } +// adds or sets given amount of products to the cart func (h *CartsHandler) AddProduct(c fiber.Ctx) error { userID, ok := localeExtractor.GetUserID(c) if !ok { @@ -159,7 +185,59 @@ func (h *CartsHandler) AddProduct(c fiber.Ctx) error { JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute))) } - err = h.cartsService.AddProduct(userID, uint(cart_id), uint(product_id), product_attribute_id, uint(amount)) + set_amount_attribute := c.Query("set_amount") + set_amount, err := strconv.ParseBool(set_amount_attribute) + if err != nil { + return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute))) + } + + err = h.cartsService.AddProduct(userID, uint(cart_id), uint(product_id), product_attribute_id, amount, set_amount) + if err != nil { + return c.Status(responseErrors.GetErrorStatus(err)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err))) + } + + return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK))) +} + +// removes product from the cart. +func (h *CartsHandler) RemoveProduct(c fiber.Ctx) error { + userID, ok := localeExtractor.GetUserID(c) + if !ok { + return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody))) + } + + cart_id_attribute := c.Query("cart_id") + cart_id, err := strconv.Atoi(cart_id_attribute) + if err != nil { + return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute))) + } + + product_id_attribute := c.Query("product_id") + product_id, err := strconv.Atoi(product_id_attribute) + if err != nil { + return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute))) + } + + product_attribute_id_attribute := c.Query("product_attribute_id") + var product_attribute_id *uint + if product_attribute_id_attribute == "" { + product_attribute_id = nil + } else { + val, err := strconv.Atoi(product_attribute_id_attribute) + if err != nil { + return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute))) + } + uval := uint(val) + product_attribute_id = &uval + } + + err = h.cartsService.RemoveProduct(userID, uint(cart_id), uint(product_id), product_attribute_id) if err != nil { return c.Status(responseErrors.GetErrorStatus(err)). JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err))) diff --git a/app/repos/cartsRepo/cartsRepo.go b/app/repos/cartsRepo/cartsRepo.go index 4141dcb..0f4c5ab 100644 --- a/app/repos/cartsRepo/cartsRepo.go +++ b/app/repos/cartsRepo/cartsRepo.go @@ -1,9 +1,13 @@ package cartsRepo import ( + "errors" + "git.ma-al.com/goc_daniel/b2b/app/db" "git.ma-al.com/goc_daniel/b2b/app/model" constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data" + "git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors" + "gorm.io/gorm" ) type UICartsRepo interface { @@ -15,7 +19,8 @@ type UICartsRepo interface { RetrieveCartsInfo(user_id uint) ([]model.CustomerCart, error) RetrieveCart(user_id uint, cart_id uint) (*model.CustomerCart, error) CheckProductExists(product_id uint, product_attribute_id *uint) (bool, error) - AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error + AddProduct(cart_id uint, product_id uint, product_attribute_id *uint, amount uint, set_amount bool) error + RemoveProduct(cart_id uint, product_id uint, product_attribute_id *uint) error } type CartsRepo struct{} @@ -129,14 +134,61 @@ func (repo *CartsRepo) CheckProductExists(product_id uint, product_attribute_id } } -func (repo *CartsRepo) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error { - product := model.CartProduct{ - CartID: cart_id, - ProductID: product_id, - ProductAttributeID: product_attribute_id, - Amount: amount, - } - err := db.DB.Create(&product).Error +func (repo *CartsRepo) AddProduct(cart_id uint, product_id uint, product_attribute_id *uint, amount uint, set_amount bool) error { + var product model.CartProduct - return err + err := db.DB. + Where(&model.CartProduct{ + CartID: cart_id, + ProductID: product_id, + ProductAttributeID: product_attribute_id, + }). + First(&product).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + if amount < 1 { + return responseErrors.ErrAmountMustBePositive + } else if amount > constdata.MAX_AMOUNT_OF_PRODUCT_IN_CART { + return responseErrors.ErrAmountMustBeReasonable + } + + product = model.CartProduct{ + CartID: cart_id, + ProductID: product_id, + ProductAttributeID: product_attribute_id, + Amount: amount, + } + + return db.DB.Create(&product).Error + } + + // Some other DB error + return err + } + + // Product already exists in cart + if set_amount { + product.Amount = amount + } else { + product.Amount = product.Amount + amount + } + + if product.Amount < 1 { + return responseErrors.ErrAmountMustBePositive + } else if product.Amount > constdata.MAX_AMOUNT_OF_PRODUCT_IN_CART { + return responseErrors.ErrAmountMustBeReasonable + } + + return db.DB.Save(&product).Error +} + +func (repo *CartsRepo) RemoveProduct(cart_id uint, product_id uint, product_attribute_id *uint) error { + return db.DB. + Where(&model.CartProduct{ + CartID: cart_id, + ProductID: product_id, + ProductAttributeID: product_attribute_id, + }). + Delete(&model.CartProduct{}).Error } diff --git a/app/service/cartsService/cartsService.go b/app/service/cartsService/cartsService.go index 2ed4006..6d75b3c 100644 --- a/app/service/cartsService/cartsService.go +++ b/app/service/cartsService/cartsService.go @@ -74,7 +74,7 @@ func (s *CartsService) RetrieveCart(user_id uint, cart_id uint) (*model.Customer return s.repo.RetrieveCart(user_id, cart_id) } -func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error { +func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount int, set_amount bool) error { exists, err := s.repo.UserHasCart(user_id, cart_id) if err != nil { return err @@ -91,5 +91,17 @@ func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, p return responseErrors.ErrProductOrItsVariationDoesNotExist } - return s.repo.AddProduct(user_id, cart_id, product_id, product_attribute_id, amount) + return s.repo.AddProduct(cart_id, product_id, product_attribute_id, uint(amount), set_amount) +} + +func (s *CartsService) RemoveProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint) error { + exists, err := s.repo.UserHasCart(user_id, cart_id) + if err != nil { + return err + } + if !exists { + return responseErrors.ErrUserHasNoSuchCart + } + + return s.repo.RemoveProduct(cart_id, product_id, product_attribute_id) } diff --git a/app/utils/const_data/consts.go b/app/utils/const_data/consts.go index 30390a6..5860000 100644 --- a/app/utils/const_data/consts.go +++ b/app/utils/const_data/consts.go @@ -15,6 +15,7 @@ var CATEGORY_BLACKLIST = []uint{250} const MAX_AMOUNT_OF_CARTS_PER_USER = 10 const DEFAULT_NEW_CART_NAME = "new cart" +const MAX_AMOUNT_OF_PRODUCT_IN_CART = 1024 const MAX_AMOUNT_OF_ADDRESSES_PER_USER = 10 diff --git a/app/utils/responseErrors/responseErrors.go b/app/utils/responseErrors/responseErrors.go index c0aee88..1eb735e 100644 --- a/app/utils/responseErrors/responseErrors.go +++ b/app/utils/responseErrors/responseErrors.go @@ -65,6 +65,8 @@ var ( ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached") ErrUserHasNoSuchCart = errors.New("user does not have cart with given id") ErrProductOrItsVariationDoesNotExist = errors.New("product or its variation with given ids does not exist") + ErrAmountMustBePositive = errors.New("amount must be positive") + ErrAmountMustBeReasonable = errors.New("amount must be reasonable") // Typed errors for orders handler ErrEmptyCart = errors.New("the cart is empty") @@ -205,6 +207,10 @@ func GetErrorCode(c fiber.Ctx, err error) string { return i18n.T_(c, "error.err_user_has_no_such_cart") case errors.Is(err, ErrProductOrItsVariationDoesNotExist): return i18n.T_(c, "error.err_product_or_its_variation_does_not_exist") + case errors.Is(err, ErrAmountMustBePositive): + return i18n.T_(c, "error.err_amount_must_be_positive") + case errors.Is(err, ErrAmountMustBeReasonable): + return i18n.T_(c, "error.err_amount_must_be_reasonable") case errors.Is(err, ErrEmptyCart): return i18n.T_(c, "error.err_cart_is_empty") @@ -292,6 +298,8 @@ func GetErrorStatus(err error) int { errors.Is(err, ErrMaxAmtOfCartsReached), errors.Is(err, ErrUserHasNoSuchCart), errors.Is(err, ErrProductOrItsVariationDoesNotExist), + errors.Is(err, ErrAmountMustBePositive), + errors.Is(err, ErrAmountMustBeReasonable), errors.Is(err, ErrEmptyCart), errors.Is(err, ErrUserHasNoSuchOrder), errors.Is(err, ErrInvalidReductionType), diff --git a/bruno/b2b_daniel/carts/add-product-to-cart (1).yml b/bruno/b2b_daniel/carts/add-product-to-cart (1).yml index eb7a5a1..f8b93c3 100644 --- a/bruno/b2b_daniel/carts/add-product-to-cart (1).yml +++ b/bruno/b2b_daniel/carts/add-product-to-cart (1).yml @@ -5,7 +5,7 @@ info: http: method: GET - url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&amount=1 + url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&amount=1&set_amount=false params: - name: cart_id value: "1" @@ -16,6 +16,9 @@ http: - name: amount value: "1" type: query + - name: set_amount + value: "false" + type: query auth: inherit settings: diff --git a/bruno/b2b_daniel/carts/add-product-to-cart.yml b/bruno/b2b_daniel/carts/add-product-to-cart.yml index ff780c1..9337ab5 100644 --- a/bruno/b2b_daniel/carts/add-product-to-cart.yml +++ b/bruno/b2b_daniel/carts/add-product-to-cart.yml @@ -5,7 +5,7 @@ info: http: method: GET - url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&product_attribute_id=1115&amount=1 + url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&product_attribute_id=1115&amount=1&set_amount=true params: - name: cart_id value: "1" @@ -19,6 +19,9 @@ http: - name: amount value: "1" type: query + - name: set_amount + value: "true" + type: query auth: inherit settings: From e0a86febc494c6b8a8f4268bf9c8f84a8e99e1fc Mon Sep 17 00:00:00 2001 From: Daniel Goc Date: Tue, 14 Apr 2026 15:52:45 +0200 Subject: [PATCH 2/4] add missing permission --- i18n/migrations/20260302163123_create_tables_data.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/i18n/migrations/20260302163123_create_tables_data.sql b/i18n/migrations/20260302163123_create_tables_data.sql index 27d10b0..2e9770b 100644 --- a/i18n/migrations/20260302163123_create_tables_data.sql +++ b/i18n/migrations/20260302163123_create_tables_data.sql @@ -76,6 +76,7 @@ INSERT INTO `b2b_route_roles` (`route_id`, `role_id`) VALUES (2, '1'), (2, '2'), (2, '3'), +(2, '4'), (3, '1'), (3, '2'), (3, '3'), From 2fd9472db19e0c2ca1c8f629d950a699ed997572 Mon Sep 17 00:00:00 2001 From: Daniel Goc Date: Wed, 15 Apr 2026 11:48:29 +0200 Subject: [PATCH 3/4] bugfix --- app/repos/localeSelectorRepo/localeSelectorRepo.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/repos/localeSelectorRepo/localeSelectorRepo.go b/app/repos/localeSelectorRepo/localeSelectorRepo.go index 1840ad5..8da31ae 100644 --- a/app/repos/localeSelectorRepo/localeSelectorRepo.go +++ b/app/repos/localeSelectorRepo/localeSelectorRepo.go @@ -3,6 +3,7 @@ package localeSelectorRepo 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" ) type UILocaleSelectorRepo interface { @@ -25,7 +26,9 @@ func (r *LocaleSelectorRepo) GetLanguages() ([]model.Language, error) { func (r *LocaleSelectorRepo) GetCountriesAndCurrencies() ([]model.Country, error) { var countries []model.Country err := db.Get(). - Preload("PSCurrency"). + Select("*"). + Preload("Currency"). + Joins("LEFT JOIN " + dbmodel.TableNamePsCountryLang + " AS cl ON cl." + dbmodel.PsCountryLangCols.IDCountry.Col() + " = b2b_countries.ps_id_country AND cl." + dbmodel.PsCountryLangCols.IDLang.Col() + " = 2"). Find(&countries).Error return countries, err } From 1bf706dcd06b5232d0c1130b4a9653d53b6fcf34 Mon Sep 17 00:00:00 2001 From: Wiktor Date: Wed, 15 Apr 2026 12:55:14 +0200 Subject: [PATCH 4/4] 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