From f55d59a0fda0088be4af51a04b618dfbc60b5d9b Mon Sep 17 00:00:00 2001 From: Wiktor Date: Tue, 14 Apr 2026 13:12:21 +0200 Subject: [PATCH 1/4] 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 From 1bf706dcd06b5232d0c1130b4a9653d53b6fcf34 Mon Sep 17 00:00:00 2001 From: Wiktor Date: Wed, 15 Apr 2026 12:55:14 +0200 Subject: [PATCH 2/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 From e9af4bf3114f08b8665651ab38c3a98f83e152ee Mon Sep 17 00:00:00 2001 From: Wiktor Date: Wed, 15 Apr 2026 12:55:14 +0200 Subject: [PATCH 3/4] 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 From 2ca07f03cea50b85b700d1286d8cbb38c15b39ab Mon Sep 17 00:00:00 2001 From: Daniel Goc Date: Wed, 15 Apr 2026 13:19:28 +0200 Subject: [PATCH 4/4] expanded by is_oem --- app/delivery/web/api/restricted/carts.go | 3 +- app/delivery/web/api/restricted/product.go | 1 + app/model/product.go | 3 +- app/repos/cartsRepo/cartsRepo.go | 8 +-- app/repos/productsRepo/productsRepo.go | 19 +++++- app/service/cartsService/cartsService.go | 8 ++- app/service/menuService/menuService.go | 59 +++++++++++++++---- app/utils/const_data/consts.go | 1 + app/view/product.go | 2 + bruno/api_v1/product/Products List.yml | 7 ++- bruno/b2b_daniel/addresses/folder.yml | 2 +- bruno/b2b_daniel/carts/add-new-cart.yml | 6 +- bruno/b2b_daniel/list/folder.yml | 7 --- bruno/b2b_daniel/list/list-products.yml | 24 -------- bruno/b2b_daniel/list/list-users.yml | 21 ------- bruno/b2b_daniel/orders/folder.yml | 2 +- .../b2b_daniel/product-translation/folder.yml | 2 +- bruno/b2b_daniel/storage-old/folder.yml | 2 +- .../b2b_daniel/storage-restricted/folder.yml | 2 +- .../20260302163122_create_tables.sql | 10 ++++ i18n/migrations/20260319163200_procedures.sql | 13 +++- 21 files changed, 116 insertions(+), 86 deletions(-) delete mode 100644 bruno/b2b_daniel/list/folder.yml delete mode 100644 bruno/b2b_daniel/list/list-products.yml delete mode 100644 bruno/b2b_daniel/list/list-users.yml diff --git a/app/delivery/web/api/restricted/carts.go b/app/delivery/web/api/restricted/carts.go index a787620..27ec676 100644 --- a/app/delivery/web/api/restricted/carts.go +++ b/app/delivery/web/api/restricted/carts.go @@ -44,7 +44,8 @@ func (h *CartsHandler) AddNewCart(c fiber.Ctx) error { JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody))) } - new_cart, err := h.cartsService.CreateNewCart(userID) + name := c.Query("name") + new_cart, err := h.cartsService.CreateNewCart(userID, name) if err != nil { return c.Status(responseErrors.GetErrorStatus(err)). JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err))) diff --git a/app/delivery/web/api/restricted/product.go b/app/delivery/web/api/restricted/product.go index ba6b99d..9e58550 100644 --- a/app/delivery/web/api/restricted/product.go +++ b/app/delivery/web/api/restricted/product.go @@ -112,6 +112,7 @@ var columnMappingListProducts map[string]string = map[string]string{ "quantity": "bp.quantity", "is_favorite": "bp.is_favorite", "is_new": "bp.is_new", + "is_oem": "bp.is_oem", } func (h *ProductsHandler) AddToFavorites(c fiber.Ctx) error { diff --git a/app/model/product.go b/app/model/product.go index 6595f79..1432f1e 100644 --- a/app/model/product.go +++ b/app/model/product.go @@ -12,7 +12,8 @@ type ProductInList struct { PriceTaxExcl float64 `gorm:"column:price_tax_excl" json:"price_tax_excl"` PriceTaxIncl float64 `gorm:"column:price_tax_incl" json:"price_tax_incl"` IsFavorite bool `gorm:"column:is_favorite" json:"is_favorite"` - IsNew uint `gorm:"column:is_new" json:"is_new"` + IsNew bool `gorm:"column:is_new" json:"is_new"` + IsOEM bool `gorm:"column:is_oem" json:"is_oem"` } type ProductFilters struct { diff --git a/app/repos/cartsRepo/cartsRepo.go b/app/repos/cartsRepo/cartsRepo.go index 4141dcb..78e3a79 100644 --- a/app/repos/cartsRepo/cartsRepo.go +++ b/app/repos/cartsRepo/cartsRepo.go @@ -3,12 +3,11 @@ package cartsRepo import ( "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" ) type UICartsRepo interface { CartsAmount(user_id uint) (uint, error) - CreateNewCart(user_id uint) (model.CustomerCart, error) + CreateNewCart(user_id uint, name string) (model.CustomerCart, error) RemoveCart(user_id uint, cart_id uint) error UserHasCart(user_id uint, cart_id uint) (bool, error) UpdateCartName(user_id uint, cart_id uint, new_name string) error @@ -37,10 +36,7 @@ func (repo *CartsRepo) CartsAmount(user_id uint) (uint, error) { return amt, err } -func (repo *CartsRepo) CreateNewCart(user_id uint) (model.CustomerCart, error) { - var name string - name = constdata.DEFAULT_NEW_CART_NAME - +func (repo *CartsRepo) CreateNewCart(user_id uint, name string) (model.CustomerCart, error) { cart := model.CustomerCart{ UserID: user_id, Name: &name, diff --git a/app/repos/productsRepo/productsRepo.go b/app/repos/productsRepo/productsRepo.go index a6d850d..fb139a5 100644 --- a/app/repos/productsRepo/productsRepo.go +++ b/app/repos/productsRepo/productsRepo.go @@ -122,6 +122,19 @@ func (repo *ProductsRepo) Find(langID uint, userID uint, p find.Paging, filt *fi Group("product_id"), }, }, + { + Name: "oems", + Subquery: exclause.Subquery{ + DB: db.DB. + Table("b2b_oems"). + Select(` + product_id AS product_id, + COUNT(*) > 0 AS is_customers_oem + `). + Where("user_id = ?", userID). + Group("product_id"), + }, + }, { Name: "new_product_days", Subquery: exclause.Subquery{ @@ -150,6 +163,7 @@ func (repo *ProductsRepo) Find(langID uint, userID uint, p find.Paging, filt *fi pl.name AS name, ps.id_category_default AS category_id, p.reference AS reference, + p.is_oem AS is_oem, sa.quantity AS quantity, COALESCE(f.is_favorite, 0) AS is_favorite, CASE @@ -166,7 +180,9 @@ func (repo *ProductsRepo) Find(langID uint, userID uint, p find.Paging, filt *fi Joins("LEFT JOIN favorites f ON f.product_id = ps.id_product"). Joins("LEFT JOIN ps_stock_available sa ON sa.id_product = ps.id_product AND sa.id_product_attribute = 0"). Joins("LEFT JOIN new_product_days npd ON 1 = 1"). + Joins("LEFT JOIN oems ON oems.product_id = ps.id_product"). Where("ps.active = ?", 1). + Where("(p.is_oem = 0 OR oems.is_customers_oem > 0)"). Group("ps.id_product"), }, }, @@ -182,7 +198,8 @@ func (repo *ProductsRepo) Find(langID uint, userID uint, p find.Paging, filt *fi COALESCE(v.variants_number, 0) AS variants_number, bp.quantity AS quantity, bp.is_favorite AS is_favorite, - bp.is_new AS is_new + bp.is_new AS is_new, + bp.is_oem AS is_oem `, config.Get().Image.ImagePrefix). Joins("JOIN ps_product_lang pl ON pl.id_product = bp.product_id AND pl.id_lang = ?", langID). Joins("JOIN ps_image_shop ims ON ims.id_product = bp.product_id AND ims.cover = 1"). diff --git a/app/service/cartsService/cartsService.go b/app/service/cartsService/cartsService.go index 2ed4006..27116ef 100644 --- a/app/service/cartsService/cartsService.go +++ b/app/service/cartsService/cartsService.go @@ -17,7 +17,7 @@ func New() *CartsService { } } -func (s *CartsService) CreateNewCart(user_id uint) (model.CustomerCart, error) { +func (s *CartsService) CreateNewCart(user_id uint, name string) (model.CustomerCart, error) { var cart model.CustomerCart customers_carts_amount, err := s.repo.CartsAmount(user_id) @@ -28,8 +28,12 @@ func (s *CartsService) CreateNewCart(user_id uint) (model.CustomerCart, error) { return cart, responseErrors.ErrMaxAmtOfCartsReached } + if name == "" { + name = constdata.DEFAULT_NEW_CART_NAME + } + // create new cart for customer - cart, err = s.repo.CreateNewCart(user_id) + cart, err = s.repo.CreateNewCart(user_id, name) return cart, nil } diff --git a/app/service/menuService/menuService.go b/app/service/menuService/menuService.go index cc98450..595303a 100644 --- a/app/service/menuService/menuService.go +++ b/app/service/menuService/menuService.go @@ -231,21 +231,54 @@ func (s *MenuService) GetTopMenu(languageId uint, roleId uint) ([]*model.B2BTopM func (s *MenuService) appendAdditional(all_categories *[]model.ScannedCategory, id_lang uint, iso_code string) { for i := 0; i < len(*all_categories); i++ { - (*all_categories)[i].Filter = "category_id_in=" + strconv.Itoa(int((*all_categories)[i].CategoryID)) + (*all_categories)[i].Filter = "category_id_eq=" + strconv.Itoa(int((*all_categories)[i].CategoryID)) } - var additional model.ScannedCategory - additional.CategoryID = 10001 - additional.Name = "New Products" - additional.Active = 1 - additional.Position = 10 - additional.ParentID = 2 - additional.IsRoot = 0 - additional.LinkRewrite = i18n.T___(id_lang, "category.new_products") - additional.IsoCode = iso_code + // the new products category + var new_products_category model.ScannedCategory + new_products_category.CategoryID = constdata.ADDITIONAL_CATEGORIES_INDEX + 1 + new_products_category.Name = "New Products" + new_products_category.Active = 1 + new_products_category.Position = 10 + new_products_category.ParentID = 2 + new_products_category.IsRoot = 0 + new_products_category.LinkRewrite = i18n.T___(id_lang, "category.new_products") + new_products_category.IsoCode = iso_code - additional.Visited = false - additional.Filter = "is_new_in=true" + new_products_category.Visited = false + new_products_category.Filter = "is_new_eq=true" - *all_categories = append(*all_categories, additional) + *all_categories = append(*all_categories, new_products_category) + + // the oem products category + var oem_products_category model.ScannedCategory + oem_products_category.CategoryID = constdata.ADDITIONAL_CATEGORIES_INDEX + 2 + oem_products_category.Name = "OEM Products" + oem_products_category.Active = 1 + oem_products_category.Position = 11 + oem_products_category.ParentID = 2 + oem_products_category.IsRoot = 0 + oem_products_category.LinkRewrite = i18n.T___(id_lang, "category.oem_products") + oem_products_category.IsoCode = iso_code + + oem_products_category.Visited = false + oem_products_category.Filter = "is_oem_eq=true" + + *all_categories = append(*all_categories, oem_products_category) + + // the favorite products category + var favorite_products_category model.ScannedCategory + favorite_products_category.CategoryID = constdata.ADDITIONAL_CATEGORIES_INDEX + 3 + favorite_products_category.Name = "Favourite Products" // British English version. + favorite_products_category.Active = 1 + favorite_products_category.Position = 12 + favorite_products_category.ParentID = 2 + favorite_products_category.IsRoot = 0 + favorite_products_category.LinkRewrite = i18n.T___(id_lang, "category.favorite_products") + favorite_products_category.IsoCode = iso_code + + favorite_products_category.Visited = false + favorite_products_category.Filter = "is_favorite_eq=true" + + *all_categories = append(*all_categories, favorite_products_category) } diff --git a/app/utils/const_data/consts.go b/app/utils/const_data/consts.go index 30390a6..f090c2c 100644 --- a/app/utils/const_data/consts.go +++ b/app/utils/const_data/consts.go @@ -9,6 +9,7 @@ const ADMIN_NOTIFICATION_LANGUAGE = 2 // CATEGORY_TREE_ROOT_ID corresponds to id_category in ps_category which has is_root_category=1 const CATEGORY_TREE_ROOT_ID = 2 +const ADDITIONAL_CATEGORIES_INDEX = 10000 // since arrays can not be const var CATEGORY_BLACKLIST = []uint{250} diff --git a/app/view/product.go b/app/view/product.go index 9dba8d1..a505477 100644 --- a/app/view/product.go +++ b/app/view/product.go @@ -95,4 +95,6 @@ type Product struct { Category string `gorm:"column:category" json:"category"` IsFavorite bool `gorm:"column:is_favorite" json:"is_favorite"` + IsOEM bool `gorm:"column:is_oem" json:"is_oem"` + IsNew bool `gorm:"column:is_new" json:"is_new"` } diff --git a/bruno/api_v1/product/Products List.yml b/bruno/api_v1/product/Products List.yml index c099b28..31a415f 100644 --- a/bruno/api_v1/product/Products List.yml +++ b/bruno/api_v1/product/Products List.yml @@ -5,7 +5,7 @@ info: http: method: GET - url: "{{bas_url}}/restricted/product/list?p=1&elems=30&reference=~NC100" + url: "{{bas_url}}/restricted/product/list?p=1&elems=30&reference=~NC100&is_new_eq=0&is_favorite_eq=false&is_oem_eq=FALSE" params: - name: p value: "1" @@ -27,11 +27,12 @@ http: - name: is_new_eq value: "0" type: query - disabled: true - name: is_favorite_eq value: "false" type: query - disabled: true + - name: is_oem_eq + value: "FALSE" + type: query body: type: json data: "" diff --git a/bruno/b2b_daniel/addresses/folder.yml b/bruno/b2b_daniel/addresses/folder.yml index aaa37e8..bc8e1c4 100644 --- a/bruno/b2b_daniel/addresses/folder.yml +++ b/bruno/b2b_daniel/addresses/folder.yml @@ -1,7 +1,7 @@ info: name: addresses type: folder - seq: 10 + seq: 9 request: auth: inherit diff --git a/bruno/b2b_daniel/carts/add-new-cart.yml b/bruno/b2b_daniel/carts/add-new-cart.yml index 20199cf..de2bba7 100644 --- a/bruno/b2b_daniel/carts/add-new-cart.yml +++ b/bruno/b2b_daniel/carts/add-new-cart.yml @@ -5,7 +5,11 @@ info: http: method: GET - url: http://localhost:3000/api/v1/restricted/carts/add-new-cart + url: http://localhost:3000/api/v1/restricted/carts/add-new-cart?name=carttt + params: + - name: name + value: carttt + type: query auth: inherit settings: diff --git a/bruno/b2b_daniel/list/folder.yml b/bruno/b2b_daniel/list/folder.yml deleted file mode 100644 index 52fa517..0000000 --- a/bruno/b2b_daniel/list/folder.yml +++ /dev/null @@ -1,7 +0,0 @@ -info: - name: list - type: folder - seq: 3 - -request: - auth: inherit diff --git a/bruno/b2b_daniel/list/list-products.yml b/bruno/b2b_daniel/list/list-products.yml deleted file mode 100644 index 20e6cac..0000000 --- a/bruno/b2b_daniel/list/list-products.yml +++ /dev/null @@ -1,24 +0,0 @@ -info: - name: list-products - type: http - seq: 1 - -http: - method: GET - url: http://localhost:3000/api/v1/restricted/list/list-products?p=1&elems=10&target_user_id=2 - params: - - name: p - value: "1" - type: query - - name: elems - value: "10" - type: query - - name: target_user_id - value: "2" - type: query - -settings: - encodeUrl: true - timeout: 0 - followRedirects: true - maxRedirects: 5 diff --git a/bruno/b2b_daniel/list/list-users.yml b/bruno/b2b_daniel/list/list-users.yml deleted file mode 100644 index 85d70fa..0000000 --- a/bruno/b2b_daniel/list/list-users.yml +++ /dev/null @@ -1,21 +0,0 @@ -info: - name: list-users - type: http - seq: 1 - -http: - method: GET - url: http://localhost:3000/api/v1/restricted/list/list-users?p=1&elems=10 - params: - - name: p - value: "1" - type: query - - name: elems - value: "10" - type: query - -settings: - encodeUrl: true - timeout: 0 - followRedirects: true - maxRedirects: 5 diff --git a/bruno/b2b_daniel/orders/folder.yml b/bruno/b2b_daniel/orders/folder.yml index f542e59..262ee49 100644 --- a/bruno/b2b_daniel/orders/folder.yml +++ b/bruno/b2b_daniel/orders/folder.yml @@ -1,7 +1,7 @@ info: name: orders type: folder - seq: 11 + seq: 10 request: auth: inherit diff --git a/bruno/b2b_daniel/product-translation/folder.yml b/bruno/b2b_daniel/product-translation/folder.yml index cda7116..71c5004 100644 --- a/bruno/b2b_daniel/product-translation/folder.yml +++ b/bruno/b2b_daniel/product-translation/folder.yml @@ -1,7 +1,7 @@ info: name: product-translation type: folder - seq: 2 + seq: 3 request: auth: inherit diff --git a/bruno/b2b_daniel/storage-old/folder.yml b/bruno/b2b_daniel/storage-old/folder.yml index 852efec..9dc0684 100644 --- a/bruno/b2b_daniel/storage-old/folder.yml +++ b/bruno/b2b_daniel/storage-old/folder.yml @@ -1,7 +1,7 @@ info: name: storage-old type: folder - seq: 1 + seq: 2 request: auth: inherit diff --git a/bruno/b2b_daniel/storage-restricted/folder.yml b/bruno/b2b_daniel/storage-restricted/folder.yml index ec9eca7..940bde2 100644 --- a/bruno/b2b_daniel/storage-restricted/folder.yml +++ b/bruno/b2b_daniel/storage-restricted/folder.yml @@ -1,7 +1,7 @@ info: name: storage-restricted type: folder - seq: 9 + seq: 8 request: auth: inherit diff --git a/i18n/migrations/20260302163122_create_tables.sql b/i18n/migrations/20260302163122_create_tables.sql index e25205d..10617a0 100644 --- a/i18n/migrations/20260302163122_create_tables.sql +++ b/i18n/migrations/20260302163122_create_tables.sql @@ -161,6 +161,16 @@ CREATE TABLE IF NOT EXISTS b2b_favorites ( ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; +-- oems +CREATE TABLE IF NOT EXISTS b2b_oems ( + user_id BIGINT UNSIGNED NOT NULL, + product_id INT UNSIGNED NOT NULL, + PRIMARY KEY (user_id, product_id), + CONSTRAINT fk_oems_customer FOREIGN KEY (user_id) REFERENCES b2b_customers(id) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT fk_oems_product FOREIGN KEY (product_id) REFERENCES ps_product(id_product) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; + + -- refresh_tokens CREATE TABLE IF NOT EXISTS b2b_refresh_tokens ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, diff --git a/i18n/migrations/20260319163200_procedures.sql b/i18n/migrations/20260319163200_procedures.sql index 267985a..4c11b94 100644 --- a/i18n/migrations/20260319163200_procedures.sql +++ b/i18n/migrations/20260319163200_procedures.sql @@ -379,10 +379,19 @@ BEGIN 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 + ) 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 @@ -400,6 +409,8 @@ BEGIN 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;