10 Commits

7 changed files with 134 additions and 70 deletions

View File

@@ -32,6 +32,7 @@ func CustomerHandlerRoutes(r fiber.Router) fiber.Router {
r.Get("", handler.customerData) r.Get("", handler.customerData)
r.Get("/list", middleware.Require(perms.UserReadAny), handler.listCustomers) r.Get("/list", middleware.Require(perms.UserReadAny), handler.listCustomers)
r.Patch("/no-vat", middleware.Require(perms.UserWriteAny), handler.setCustomerNoVatStatus)
return r return r
} }
@@ -100,3 +101,28 @@ var columnMappingListUsers map[string]string = map[string]string{
"first_name": "users.first_name", "first_name": "users.first_name",
"last_name": "users.last_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.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrInvalidBody)))
}
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)))
}

View File

@@ -35,6 +35,7 @@ type Customer struct {
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
IsNoVat bool `gorm:"default:false" json:"is_no_vat"`
} }
func (u *Customer) HasPermission(permission perms.Permission) bool { func (u *Customer) HasPermission(permission perms.Permission) bool {

View File

@@ -17,6 +17,7 @@ type UICustomerRepo interface {
Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error) Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error)
Save(customer *model.Customer) error Save(customer *model.Customer) error
Create(customer *model.Customer) error Create(customer *model.Customer) error
SetCustomerNoVatStatus(customerID uint, isNoVat bool) error
} }
type CustomerRepo struct{} type CustomerRepo struct{}
@@ -114,3 +115,7 @@ func (repo *CustomerRepo) Save(customer *model.Customer) error {
func (repo *CustomerRepo) Create(customer *model.Customer) error { func (repo *CustomerRepo) Create(customer *model.Customer) error {
return db.DB.Create(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
}

View File

@@ -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) { 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) return s.repo.Find(langId, p, filt, search)
} }
func (s *CustomerService) SetCustomerNoVatStatus(customerID uint, isNoVat bool) error {
return s.repo.SetCustomerNoVatStatus(customerID, isNoVat)
}

View File

@@ -0,0 +1,22 @@
info:
name: Set is_no_vat
type: http
seq: 4
http:
method: PATCH
url: "{{bas_url}}/restricted/customer/no-vat"
body:
type: json
data: |-
{
"customer_id":1,
"is_no_vat": false
}
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -112,7 +112,8 @@ CREATE TABLE IF NOT EXISTS b2b_customers (
country_id INT NULL DEFAULT 2, country_id INT NULL DEFAULT 2,
created_at DATETIME(6) NULL, created_at DATETIME(6) NULL,
updated_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; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE UNIQUE INDEX IF NOT EXISTS idx_customers_email CREATE UNIQUE INDEX IF NOT EXISTS idx_customers_email

View File

@@ -16,6 +16,7 @@ READS SQL DATA
BEGIN BEGIN
DECLARE v_tax_rate DECIMAL(10,4) DEFAULT 0; DECLARE v_tax_rate DECIMAL(10,4) DEFAULT 0;
DECLARE v_tax_group INT;
DECLARE v_base_raw DECIMAL(20,6); DECLARE v_base_raw DECIMAL(20,6);
DECLARE v_base DECIMAL(20,6); DECLARE v_base DECIMAL(20,6);
@@ -29,45 +30,54 @@ BEGIN
DECLARE v_has_specific INT DEFAULT 0; DECLARE v_has_specific INT DEFAULT 0;
-- currency
DECLARE v_target_currency BIGINT; DECLARE v_target_currency BIGINT;
DECLARE v_target_rate DECIMAL(13,6) DEFAULT 1; DECLARE v_target_rate DECIMAL(13,6) DEFAULT 1;
DECLARE v_specific_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); 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 ================= -- ================= TAX =================
SELECT COALESCE(t.rate, 0) SELECT COALESCE(t.rate, 0)
INTO v_tax_rate INTO v_tax_rate
FROM ps_tax_rule tr FROM ps_tax_rule tr
JOIN ps_tax t ON t.id_tax = tr.id_tax JOIN ps_tax t ON t.id_tax = tr.id_tax
LEFT JOIN b2b_countries c ON c.id = p_id_country LEFT JOIN b2b_countries c ON c.id = p_id_country
WHERE tr.id_tax_rules_group = ( WHERE tr.id_tax_rules_group = v_tax_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 AND tr.id_country = c.ps_id_country
LIMIT 1; LIMIT 1;
-- ================= TARGET CURRENCY ================= IF v_is_no_vat = 1 THEN
SELECT c.b2b_id_currency SET v_tax_rate = 0;
INTO v_target_currency END IF;
FROM b2b_countries c
WHERE c.id = p_id_country
LIMIT 1;
-- latest target rate -- ================= CURRENCY =================
SELECT r.conversion_rate SELECT c.b2b_id_currency, r.conversion_rate
INTO v_target_rate INTO v_target_currency, v_target_rate
FROM b2b_currency_rates r FROM b2b_countries c
WHERE r.b2b_id_currency = v_target_currency LEFT JOIN b2b_currency_rates r
ON r.b2b_id_currency = c.b2b_id_currency
WHERE c.id = p_id_country
ORDER BY r.created_at DESC ORDER BY r.created_at DESC
LIMIT 1; LIMIT 1;
-- ================= BASE PRICE (RAW) ================= -- ================= BASE PRICE =================
SELECT SELECT
COALESCE(ps.price, p.price) + COALESCE(pas.price, 0) COALESCE(ps.price, p.price) + COALESCE(pas.price, 0)
INTO v_base_raw INTO v_base_raw
@@ -79,8 +89,8 @@ BEGIN
AND pas.id_shop = p_id_shop AND pas.id_shop = p_id_shop
WHERE p.id_product = p_id_product; WHERE p.id_product = p_id_product;
-- convert base to target currency
SET v_base = v_base_raw * v_target_rate; SET v_base = v_base_raw * v_target_rate;
SET v_excl = v_base;
-- ================= RULE SELECTION ================= -- ================= RULE SELECTION =================
SELECT SELECT
@@ -99,71 +109,67 @@ BEGIN
FROM b2b_specific_price bsp 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 WHERE bsp.is_active = 1
AND bsp.from_quantity <= p_quantity AND bsp.from_quantity <= p_quantity
-- intersection rules (unchanged) AND (spp.id_product IS NOT NULL OR NOT EXISTS (
AND ( SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id
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 ( AND (spa.id_product_attribute IS NOT NULL OR NOT EXISTS (
NOT EXISTS (SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id) 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 ( AND (spc.b2b_id_customer IS NOT NULL OR NOT EXISTS (
NOT EXISTS (SELECT 1 FROM b2b_specific_price_category x WHERE x.b2b_specific_price_id = bsp.id) SELECT 1 FROM b2b_specific_price_customer 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 ( AND (spco.b2b_id_country IS NOT NULL OR NOT EXISTS (
NOT EXISTS (SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id) SELECT 1 FROM b2b_specific_price_country 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 ( AND (cp.id_product IS NOT NULL OR NOT EXISTS (
NOT EXISTS (SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id) SELECT 1 FROM b2b_specific_price_category 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) ))
)
ORDER BY ORDER BY
-- customer wins (spc.b2b_id_customer IS NOT NULL) DESC,
(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, (spa.id_product_attribute IS NOT NULL) DESC,
(spp.id_product IS NOT NULL) DESC,
-- attribute (cp.id_product IS NOT NULL) DESC,
(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, (spco.b2b_id_country IS NOT NULL) 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,
bsp.id DESC bsp.id DESC
LIMIT 1; LIMIT 1;
-- ================= APPLY ================= -- ================= APPLY =================
SET v_excl = v_base;
IF v_has_specific = 1 THEN IF v_has_specific = 1 THEN
IF v_reduction_type = 'amount' 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 IF v_specific_currency_id IS NOT NULL AND v_specific_currency_id != v_target_currency THEN
SELECT r.conversion_rate SELECT r.conversion_rate
@@ -173,7 +179,6 @@ BEGIN
ORDER BY r.created_at DESC ORDER BY r.created_at DESC
LIMIT 1; LIMIT 1;
-- normalize → then convert to target
SET v_excl = (v_fixed_price / v_specific_rate) * v_target_rate; SET v_excl = (v_fixed_price / v_specific_rate) * v_target_rate;
ELSE ELSE