expand orders #75

Merged
goc_daniel merged 3 commits from expand_orders into main 2026-04-16 14:25:59 +00:00
10 changed files with 74 additions and 10 deletions

View File

@@ -73,6 +73,11 @@ var columnMappingListOrders map[string]string = map[string]string{
"name": "b2b_customer_orders.name", "name": "b2b_customer_orders.name",
"country_id": "b2b_customer_orders.country_id", "country_id": "b2b_customer_orders.country_id",
"status": "b2b_customer_orders.status", "status": "b2b_customer_orders.status",
"base_price": "b2b_customer_orders.base_price",
"tax_incl": "b2b_customer_orders.tax_incl",
"tax_excl": "b2b_customer_orders.tax_excl",
"created_at": "b2b_customer_orders.created_at",
"updated_at": "b2b_customer_orders.updated_at",
} }
func (h *OrdersHandler) PlaceNewOrder(c fiber.Ctx) error { func (h *OrdersHandler) PlaceNewOrder(c fiber.Ctx) error {

View File

@@ -1,5 +1,7 @@
package model package model
import "time"
type CustomerOrder struct { type CustomerOrder struct {
OrderID uint `gorm:"column:order_id;primaryKey;autoIncrement" json:"order_id"` OrderID uint `gorm:"column:order_id;primaryKey;autoIncrement" json:"order_id"`
UserID uint `gorm:"column:user_id;not null;index" json:"user_id"` UserID uint `gorm:"column:user_id;not null;index" json:"user_id"`
@@ -8,6 +10,11 @@ type CustomerOrder struct {
AddressString string `gorm:"column:address_string;not null" json:"address_string"` AddressString string `gorm:"column:address_string;not null" json:"address_string"`
AddressUnparsed *AddressUnparsed `gorm:"-" json:"address_unparsed"` AddressUnparsed *AddressUnparsed `gorm:"-" json:"address_unparsed"`
Status string `gorm:"column:status;size:50;not null" json:"status"` Status string `gorm:"column:status;size:50;not null" json:"status"`
BasePrice float64 `gorm:"column:base_price;type:decimal(10,2);not null" json:"base_price"`
TaxIncl float64 `gorm:"column:tax_incl;type:decimal(10,2);not null" json:"tax_incl"`
TaxExcl float64 `gorm:"column:tax_excl;type:decimal(10,2);not null" json:"tax_excl"`
CreatedAt time.Time `gorm:"column:created_at;not null" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;not null" json:"updated_at"`
Products []OrderProduct `gorm:"foreignKey:OrderID;references:OrderID" json:"products"` Products []OrderProduct `gorm:"foreignKey:OrderID;references:OrderID" json:"products"`
} }

View File

@@ -1,6 +1,8 @@
package ordersRepo package ordersRepo
import ( import (
"time"
"git.ma-al.com/goc_daniel/b2b/app/db" "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"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data" constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
@@ -11,7 +13,7 @@ import (
type UIOrdersRepo interface { type UIOrdersRepo interface {
UserHasOrder(user_id uint, order_id uint) (bool, error) UserHasOrder(user_id uint, order_id uint) (bool, error)
Find(user_id uint, p find.Paging, filt *filters.FiltersList) (*find.Found[model.CustomerOrder], error) Find(user_id uint, p find.Paging, filt *filters.FiltersList) (*find.Found[model.CustomerOrder], error)
PlaceNewOrder(cart *model.CustomerCart, name string, country_id uint, address_info string) error PlaceNewOrder(cart *model.CustomerCart, name string, country_id uint, address_info string, base_price float64, tax_incl float64, tax_excl float64) error
ChangeOrderAddress(order_id uint, country_id uint, address_info string) error ChangeOrderAddress(order_id uint, country_id uint, address_info string) error
ChangeOrderStatus(order_id uint, status string) error ChangeOrderStatus(order_id uint, status string) error
} }
@@ -69,7 +71,7 @@ func (repo *OrdersRepo) Find(user_id uint, p find.Paging, filt *filters.FiltersL
}, nil }, nil
} }
func (repo *OrdersRepo) PlaceNewOrder(cart *model.CustomerCart, name string, country_id uint, address_info string) error { func (repo *OrdersRepo) PlaceNewOrder(cart *model.CustomerCart, name string, country_id uint, address_info string, base_price float64, tax_incl float64, tax_excl float64) error {
order := model.CustomerOrder{ order := model.CustomerOrder{
UserID: cart.UserID, UserID: cart.UserID,
Name: name, Name: name,
@@ -87,6 +89,12 @@ func (repo *OrdersRepo) PlaceNewOrder(cart *model.CustomerCart, name string, cou
}) })
} }
order.CreatedAt = time.Now()
order.UpdatedAt = time.Now()
order.BasePrice = base_price
order.TaxIncl = tax_incl
order.TaxExcl = tax_excl
return db.DB.Create(&order).Error return db.DB.Create(&order).Error
} }
@@ -97,6 +105,7 @@ func (repo *OrdersRepo) ChangeOrderAddress(order_id uint, country_id uint, addre
Updates(map[string]interface{}{ Updates(map[string]interface{}{
"country_id": country_id, "country_id": country_id,
"address_string": address_info, "address_string": address_info,
"updated_at": time.Now(),
}). }).
Error Error
} }
@@ -105,6 +114,9 @@ func (repo *OrdersRepo) ChangeOrderStatus(order_id uint, status string) error {
return db.DB. return db.DB.
Table("b2b_customer_orders"). Table("b2b_customer_orders").
Where("order_id = ?", order_id). Where("order_id = ?", order_id).
Update("status", status). Updates(map[string]interface{}{
goc_daniel marked this conversation as resolved
Review

this code is fine but just letting you know that when you use DB.Model() instead of DB.Table the updated_at columns is being updated by gorm automatically

this code is fine but just letting you know that when you use DB.Model() instead of DB.Table the updated_at columns is being updated by gorm automatically
"status": status,
"updated_at": time.Now(),
}).
Error Error
} }

View File

@@ -130,6 +130,7 @@ func (s *AuthService) Login(req *model.LoginRequest) (*model.AuthResponse, strin
user.LangID = *req.LangID user.LangID = *req.LangID
} }
user.Country = nil
goc_daniel marked this conversation as resolved
Review

db shouldn't be called in the service and updating logic should be in single place in repo. that way we don't have to rely on remembering to nullify associated models like country.

Best approach would be to build a Customer model out of a DTO for this operation for the sake of safety.
But the customer struct is small enough that nullifying Country and Role would be sufficient for now.

db shouldn't be called in the service and updating logic should be in single place in repo. that way we don't have to rely on remembering to nullify associated models like country. Best approach would be to build a Customer model out of a DTO for this operation for the sake of safety. But the customer struct is small enough that nullifying Country and Role would be sufficient for now.
Review

yeah, it's old code, and it's coauthored by me and Marek. Let's leave things as they are (and hopefully not change Customer struct anymore)

yeah, it's old code, and it's coauthored by me and Marek. Let's leave things as they are (and hopefully not change Customer struct anymore)
s.db.Save(&user) s.db.Save(&user)
// Generate access token (JWT) // Generate access token (JWT)
@@ -259,6 +260,7 @@ func (s *AuthService) CompleteRegistration(req *model.CompleteRegistrationReques
user.EmailVerificationToken = "" user.EmailVerificationToken = ""
user.EmailVerificationExpires = nil user.EmailVerificationExpires = nil
user.Country = nil
if err := s.db.Save(&user).Error; err != nil { if err := s.db.Save(&user).Error; err != nil {
return nil, "", fmt.Errorf("failed to update user: %w", err) return nil, "", fmt.Errorf("failed to update user: %w", err)
} }
@@ -327,6 +329,7 @@ func (s *AuthService) RequestPasswordReset(emailAddr string) error {
user.PasswordResetToken = token user.PasswordResetToken = token
user.PasswordResetExpires = &expiresAt user.PasswordResetExpires = &expiresAt
user.LastPasswordResetRequest = &now user.LastPasswordResetRequest = &now
user.Country = nil
if err := s.db.Save(&user).Error; err != nil { if err := s.db.Save(&user).Error; err != nil {
return fmt.Errorf("failed to save reset token: %w", err) return fmt.Errorf("failed to save reset token: %w", err)
} }
@@ -381,6 +384,7 @@ func (s *AuthService) ResetPassword(token, newPassword string) error {
user.PasswordResetToken = "" user.PasswordResetToken = ""
user.PasswordResetExpires = nil user.PasswordResetExpires = nil
user.Country = nil
if err := s.db.Save(&user).Error; err != nil { if err := s.db.Save(&user).Error; err != nil {
logger.Error("password reset failed - database error", logger.Error("password reset failed - database error",
"service", "AuthService.ResetPassword", "service", "AuthService.ResetPassword",
@@ -596,6 +600,7 @@ func (s *AuthService) UpdateJWTToken(user *model.Customer) (string, error) {
} }
// Save the updated user // Save the updated user
user.Country = nil
if err := s.db.Save(user).Error; err != nil { if err := s.db.Save(user).Error; err != nil {
return "", fmt.Errorf("database error: %w", err) return "", fmt.Errorf("database error: %w", err)
} }

View File

@@ -92,6 +92,7 @@ func (s *AuthService) HandleGoogleCallback(code string) (*model.AuthResponse, st
// Update last login // Update last login
now := time.Now() now := time.Now()
user.LastLoginAt = &now user.LastLoginAt = &now
user.Country = nil
s.db.Save(user) s.db.Save(user)
// Generate access token (JWT) // Generate access token (JWT)

View File

@@ -7,8 +7,10 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/model" "git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/repos/cartsRepo" "git.ma-al.com/goc_daniel/b2b/app/repos/cartsRepo"
"git.ma-al.com/goc_daniel/b2b/app/repos/ordersRepo" "git.ma-al.com/goc_daniel/b2b/app/repos/ordersRepo"
"git.ma-al.com/goc_daniel/b2b/app/repos/productsRepo"
"git.ma-al.com/goc_daniel/b2b/app/service/addressesService" "git.ma-al.com/goc_daniel/b2b/app/service/addressesService"
"git.ma-al.com/goc_daniel/b2b/app/service/emailService" "git.ma-al.com/goc_daniel/b2b/app/service/emailService"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
"git.ma-al.com/goc_daniel/b2b/app/utils/logger" "git.ma-al.com/goc_daniel/b2b/app/utils/logger"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters" "git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find" "git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
@@ -18,6 +20,7 @@ import (
type OrderService struct { type OrderService struct {
ordersRepo ordersRepo.UIOrdersRepo ordersRepo ordersRepo.UIOrdersRepo
cartsRepo cartsRepo.UICartsRepo cartsRepo cartsRepo.UICartsRepo
productsRepo productsRepo.UIProductsRepo
addressesService *addressesService.AddressesService addressesService *addressesService.AddressesService
emailService *emailService.EmailService emailService *emailService.EmailService
} }
@@ -26,6 +29,7 @@ func New() *OrderService {
return &OrderService{ return &OrderService{
ordersRepo: ordersRepo.New(), ordersRepo: ordersRepo.New(),
cartsRepo: cartsRepo.New(), cartsRepo: cartsRepo.New(),
productsRepo: productsRepo.New(),
addressesService: addressesService.New(), addressesService: addressesService.New(),
emailService: emailService.NewEmailService(), emailService: emailService.NewEmailService(),
} }
@@ -85,8 +89,10 @@ func (s *OrderService) PlaceNewOrder(user_id uint, cart_id uint, name string, co
name = *cart.Name name = *cart.Name
} }
base_price, tax_incl, tax_excl, err := s.getOrderTotalPrice(user_id, cart_id, country_id)
// all checks passed // all checks passed
err = s.ordersRepo.PlaceNewOrder(cart, name, country_id, address_info) err = s.ordersRepo.PlaceNewOrder(cart, name, country_id, address_info, base_price, tax_incl, tax_excl)
if err != nil { if err != nil {
return err return err
} }
@@ -153,3 +159,27 @@ func (s *OrderService) ChangeOrderStatus(user *model.Customer, order_id uint, st
return s.ordersRepo.ChangeOrderStatus(order_id, status) return s.ordersRepo.ChangeOrderStatus(order_id, status)
} }
func (s *OrderService) getOrderTotalPrice(user_id uint, cart_id uint, country_id uint) (float64, float64, float64, error) {
cart, err := s.cartsRepo.RetrieveCart(user_id, cart_id)
if err != nil {
return 0.0, 0.0, 0.0, err
}
base_price := 0.0
tax_incl := 0.0
tax_excl := 0.0
for _, product := range cart.Products {
prices, err := s.productsRepo.GetPrice(product.ProductID, product.ProductAttributeID, constdata.SHOP_ID, user_id, country_id, product.Amount)
if err != nil {
return 0.0, 0.0, 0.0, err
}
base_price += prices.Base
tax_incl += prices.FinalTaxIncl
tax_excl += prices.FinalTaxExcl
}
return base_price, tax_incl, tax_excl, nil
}

View File

@@ -5,15 +5,14 @@ info:
http: http:
method: POST method: POST
url: http://localhost:3000/api/v1/public/auth/update-choice?lang_id=0&country_id=1 url: http://localhost:3000/api/v1/public/auth/update-choice?lang_id=1&country_id=1
params: params:
- name: lang_id - name: lang_id
value: "0" value: "1"
type: query type: query
- name: country_id - name: country_id
value: "1" value: "1"
type: query type: query
auth: inherit
settings: settings:
encodeUrl: true encodeUrl: true

View File

@@ -5,7 +5,7 @@ info:
http: http:
method: GET 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&set_amount=true url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&product_attribute_id=1115&amount=2&set_amount=true
params: params:
- name: cart_id - name: cart_id
value: "1" value: "1"
@@ -17,7 +17,7 @@ http:
value: "1115" value: "1115"
type: query type: query
- name: amount - name: amount
value: "1" value: "2"
type: query type: query
- name: set_amount - name: set_amount
value: "true" value: "true"

View File

@@ -251,6 +251,11 @@ CREATE TABLE IF NOT EXISTS b2b_customer_orders (
country_id BIGINT UNSIGNED NOT NULL, country_id BIGINT UNSIGNED NOT NULL,
address_string TEXT NOT NULL, address_string TEXT NOT NULL,
status VARCHAR(50) NOT NULL, status VARCHAR(50) NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
base_price DECIMAL(10, 2) NOT NULL,
tax_incl DECIMAL(10, 2) NOT NULL,
tax_excl DECIMAL(10, 2) NOT NULL,
CONSTRAINT fk_customer_orders_customers FOREIGN KEY (user_id) REFERENCES b2b_customers(id) ON DELETE NO ACTION ON UPDATE CASCADE, CONSTRAINT fk_customer_orders_customers FOREIGN KEY (user_id) REFERENCES b2b_customers(id) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT fk_customer_orders_countries FOREIGN KEY (country_id) REFERENCES b2b_countries(id) ON DELETE NO ACTION ON UPDATE CASCADE CONSTRAINT fk_customer_orders_countries FOREIGN KEY (country_id) REFERENCES b2b_countries(id) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

View File

@@ -415,7 +415,7 @@ BEGIN
LEFT JOIN ps_manufacturer m LEFT JOIN ps_manufacturer m
ON m.id_manufacturer = p.id_manufacturer ON m.id_manufacturer = p.id_manufacturer
LEFT JOIN ps_configuration LEFT JOIN ps_configuration
ON ps_configuration.name = PS_NB_DAYS_NEW_PRODUCT ON ps_configuration.name = 'PS_NB_DAYS_NEW_PRODUCT'
WHERE p.id_product = p_id_product WHERE p.id_product = p_id_product
LIMIT 1; LIMIT 1;