orders #58

Merged
goc_daniel merged 13 commits from orders into main 2026-04-14 11:20:06 +00:00
9 changed files with 131 additions and 44 deletions
Showing only changes of commit 134bc4ea53 - Show all commits

View File

@@ -8,4 +8,5 @@ const (
UserDeleteAny Permission = "user.delete.any"
CurrencyWrite Permission = "currency.write"
ViewAllOrders Permission = "orders.view"
ModifyAllOrders Permission = "orders.modify"
)

View File

@@ -61,8 +61,9 @@ func (h *OrdersHandler) ListOrders(c fiber.Ctx) error {
}
var columnMappingListOrders map[string]string = map[string]string{
"order_id": "b2b_customer_orders.id",
"order_id": "b2b_customer_orders.order_id",
"user_id": "b2b_customer_orders.user_id",
"name": "b2b_customer_orders.name",
"country_id": "b2b_customer_orders.country_id",
"status": "b2b_customer_orders.status",
}
@@ -81,6 +82,7 @@ func (h *OrdersHandler) PlaceNewOrder(c fiber.Ctx) error {
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
address_info := c.Query("address_info")
country_id_attribute := c.Query("country_id")
country_id, err := strconv.Atoi(country_id_attribute)
if err != nil {
@@ -88,9 +90,9 @@ func (h *OrdersHandler) PlaceNewOrder(c fiber.Ctx) error {
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
address_info := c.Query("address_info")
name := c.Query("name")
err = h.ordersService.PlaceNewOrder(userID, uint(cart_id), uint(country_id), address_info)
err = h.ordersService.PlaceNewOrder(userID, uint(cart_id), name, uint(country_id), address_info)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))

View File

@@ -1,12 +1,13 @@
package model
type CustomerOrder struct {
ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
UserID uint `gorm:"column:user_id;not null;index" json:"-"`
OrderID uint `gorm:"column:order_id;primaryKey;autoIncrement" json:"order_id"`
UserID uint `gorm:"column:user_id;not null;index" json:"user_id"`
Name string `gorm:"column:name;not null" json:"name"`
CountryID uint `gorm:"column:country_id;not null" json:"country_id"`
AddressJSON *string `gorm:"column:address_json" json:"address_json,omitempty"`
Status *string `gorm:"column:status;size:50" json:"status,omitempty"`
Products []OrderProduct `gorm:"foreignKey:OrderID;references:ID" json:"products,omitempty"`
AddressJSON string `gorm:"column:address_json;not null" json:"address_json"`
Status string `gorm:"column:status;size:50;not null" json:"status"`
Products []OrderProduct `gorm:"foreignKey:OrderID;references:ID" json:"products"`
}
func (CustomerOrder) TableName() string {
@@ -14,7 +15,6 @@ func (CustomerOrder) TableName() string {
}
type OrderProduct struct {
ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"-"`
OrderID uint `gorm:"column:order_id;not null;index" json:"-"`
ProductID uint `gorm:"column:product_id;not null" json:"product_id"`
ProductAttributeID *uint `gorm:"column:product_attribute_id" json:"product_attribute_id,omitempty"`

View File

@@ -9,11 +9,11 @@ import (
type UICartsRepo interface {
CartsAmount(user_id uint) (uint, error)
CreateNewCart(user_id uint) (model.CustomerCart, error)
UserHasCart(user_id uint, cart_id uint) (uint, error)
UserHasCart(user_id uint, cart_id uint) (bool, error)
UpdateCartName(user_id uint, cart_id uint, new_name string) error
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) (uint, 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
}
@@ -49,7 +49,7 @@ func (repo *CartsRepo) CreateNewCart(user_id uint) (model.CustomerCart, error) {
return cart, err
}
func (repo *CartsRepo) UserHasCart(user_id uint, cart_id uint) (uint, error) {
func (repo *CartsRepo) UserHasCart(user_id uint, cart_id uint) (bool, error) {
var amt uint
err := db.DB.
@@ -59,7 +59,7 @@ func (repo *CartsRepo) UserHasCart(user_id uint, cart_id uint) (uint, error) {
Scan(&amt).
Error
return amt, err
return amt >= 1, err
}
func (repo *CartsRepo) UpdateCartName(user_id uint, cart_id uint, new_name string) error {
@@ -96,7 +96,7 @@ func (repo *CartsRepo) RetrieveCart(user_id uint, cart_id uint) (*model.Customer
return &cart, err
}
func (repo *CartsRepo) CheckProductExists(product_id uint, product_attribute_id *uint) (uint, error) {
func (repo *CartsRepo) CheckProductExists(product_id uint, product_attribute_id *uint) (bool, error) {
var amt uint
if product_attribute_id == nil {
@@ -106,7 +106,7 @@ func (repo *CartsRepo) CheckProductExists(product_id uint, product_attribute_id
Where("id_product = ?", product_id).
Scan(&amt).
Error
return amt, err
return amt >= 1, err
} else {
err := db.DB.
@@ -116,7 +116,7 @@ func (repo *CartsRepo) CheckProductExists(product_id uint, product_attribute_id
Where("ps.id_product = ? AND pas.id_product_attribute = ?", product_id, *product_attribute_id).
Scan(&amt).
Error
return amt, err
return amt >= 1, err
}
}

View File

@@ -3,15 +3,17 @@ package ordersRepo
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"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
)
type UIOrdersRepo interface {
UserHasOrder(user_id uint, order_id uint) (bool, error)
Find(user_id uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.CustomerOrder], error)
PlaceNewOrder(user_id uint, cart_id uint, country_id uint, address_info string) error
ChangeOrderAddress(user_id uint, order_id uint, country_id uint, address_info string) error
ChangeOrderStatus(user_id uint, order_id uint, status string) error
PlaceNewOrder(cart *model.CustomerCart, name string, 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
}
type OrdersRepo struct{}
@@ -20,6 +22,19 @@ func New() UIOrdersRepo {
return &OrdersRepo{}
}
func (repo *OrdersRepo) UserHasOrder(user_id uint, order_id uint) (bool, error) {
var amt uint
err := db.DB.
Table("b2b_customer_orders").
Select("COUNT(*) AS amt").
Where("user_id = ? AND order_id = ?", user_id, order_id).
Scan(&amt).
Error
return amt >= 1, err
}
func (repo *OrdersRepo) Find(user_id uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.CustomerOrder], error) {
var list []model.CustomerOrder
var total int64
@@ -54,15 +69,42 @@ func (repo *OrdersRepo) Find(user_id uint, p find.Paging, filt *filters.FiltersL
}, nil
}
func (repo *OrdersRepo) PlaceNewOrder(user_id uint, cart_id uint, country_id uint, address_info string) error {
func (repo *OrdersRepo) PlaceNewOrder(cart *model.CustomerCart, name string, country_id uint, address_info string) error {
order := model.CustomerOrder{
UserID: cart.UserID,
Name: name,
CountryID: country_id,
AddressJSON: address_info,
Status: constdata.NEW_ORDER_STATUS,
Products: make([]model.OrderProduct, 0, len(cart.Products)),
}
return nil
for _, product := range cart.Products {
order.Products = append(order.Products, model.OrderProduct{
ProductID: product.ProductID,
ProductAttributeID: product.ProductAttributeID,
Amount: product.Amount,
})
}
return db.DB.Create(&order).Error
}
func (repo *OrdersRepo) ChangeOrderAddress(user_id uint, order_id uint, country_id uint, address_info string) error {
return nil
func (repo *OrdersRepo) ChangeOrderAddress(order_id uint, country_id uint, address_info string) error {
return db.DB.
Table("b2b_customer_orders").
Where("order_id = ?", order_id).
Updates(map[string]interface{}{
"country_id": country_id,
"address_info": address_info,
}).
Error
}
func (repo *OrdersRepo) ChangeOrderStatus(user_id uint, order_id uint, status string) error {
return nil
func (repo *OrdersRepo) ChangeOrderStatus(order_id uint, status string) error {
return db.DB.
Table("b2b_customer_orders").
Where("order_id = ?", order_id).
Update("status", status).
Error
}

View File

@@ -37,17 +37,17 @@ func (s *OrderService) Find(user *model.Customer, p find.Paging, filt *filters.F
return s.ordersRepo.Find(user.ID, p, filt)
}
func (s *OrderService) PlaceNewOrder(user_id uint, cart_id uint, country_id uint, address_info string) error {
func (s *OrderService) PlaceNewOrder(user_id uint, cart_id uint, name string, country_id uint, address_info string) error {
_, err := s.addressesService.ValidateAddressJson(address_info, country_id)
if err != nil {
return err
}
amt, err := s.cartsRepo.UserHasCart(user_id, cart_id)
exists, err := s.cartsRepo.UserHasCart(user_id, cart_id)
if err != nil {
return err
}
if amt <= 0 {
if !exists {
return responseErrors.ErrUserHasNoSuchCart
}
@@ -59,14 +59,45 @@ func (s *OrderService) PlaceNewOrder(user_id uint, cart_id uint, country_id uint
return responseErrors.ErrEmptyCart
}
if name == "" && cart.Name != nil {
name = *cart.Name
}
// all checks passed
return s.ordersRepo.PlaceNewOrder(user_id, cart_id, country_id, address_info)
return s.ordersRepo.PlaceNewOrder(cart, name, country_id, address_info)
}
func (s *OrderService) ChangeOrderAddress(user *model.Customer, order_id uint, country_id uint, address_info string) error {
return nil
_, err := s.addressesService.ValidateAddressJson(address_info, country_id)
if err != nil {
return err
}
if !user.HasPermission(perms.ModifyAllOrders) {
exists, err := s.ordersRepo.UserHasOrder(user.ID, order_id)
if err != nil {
return err
}
if !exists {
return responseErrors.ErrUserHasNoSuchOrder
}
}
return s.ordersRepo.ChangeOrderAddress(order_id, country_id, address_info)
}
func (s *OrderService) ChangeOrderStatus(user *model.Customer, order_id uint, status string) error {
goc_daniel marked this conversation as resolved Outdated

after successful order placement we should either delete or deactivate the cart that is leftover

after successful order placement we should either delete or deactivate the cart that is leftover
return nil
if !user.HasPermission(perms.ModifyAllOrders) {
exists, err := s.ordersRepo.UserHasOrder(user.ID, order_id)
goc_daniel marked this conversation as resolved Outdated

should be a goroutine to not stall the user from getting response from the server

should be a goroutine to not stall the user from getting response from the server
if err != nil {
return err
}
if !exists {
return responseErrors.ErrUserHasNoSuchOrder
}
}
return s.ordersRepo.ChangeOrderStatus(order_id, status)
}

View File

@@ -16,6 +16,9 @@ const MAX_AMOUNT_OF_ADDRESSES_PER_USER = 10
const USER_LOCALE = "user"
// ORDERS
const NEW_ORDER_STATUS = "PENDING"
// WEBDAV
const NBYTES_IN_WEBDAV_TOKEN = 32
const WEBDAV_HREF_ROOT = "http://localhost:3000/api/v1/webdav/storage"

View File

@@ -65,7 +65,10 @@ 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")
// Typed errors for orders handler
ErrEmptyCart = errors.New("the cart is empty")
ErrUserHasNoSuchOrder = errors.New("user does not have order with given id")
// Typed errors for storage
ErrAccessDenied = errors.New("access denied!")
@@ -196,8 +199,11 @@ 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, ErrEmptyCart):
return i18n.T_(c, "error.err_cart_is_empty")
case errors.Is(err, ErrUserHasNoSuchOrder):
return i18n.T_(c, "error.err_user_has_no_such_order")
case errors.Is(err, ErrAccessDenied):
return i18n.T_(c, "error.err_access_denied")
@@ -272,6 +278,7 @@ func GetErrorStatus(err error) int {
errors.Is(err, ErrUserHasNoSuchCart),
errors.Is(err, ErrProductOrItsVariationDoesNotExist),
errors.Is(err, ErrEmptyCart),
errors.Is(err, ErrUserHasNoSuchOrder),
errors.Is(err, ErrAccessDenied),
errors.Is(err, ErrFolderDoesNotExist),
errors.Is(err, ErrFileDoesNotExist),

View File

@@ -130,7 +130,7 @@ FOREIGN KEY (role_id) REFERENCES b2b_roles(id);
-- customer_carts
CREATE TABLE IF NOT EXISTS b2b_customer_carts (
cart_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
cart_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
name VARCHAR(255) NULL,
CONSTRAINT fk_customer_carts_customers FOREIGN KEY (user_id) REFERENCES b2b_customers(id) ON DELETE CASCADE ON UPDATE CASCADE
@@ -140,8 +140,8 @@ CREATE INDEX IF NOT EXISTS idx_customer_carts_user_id ON b2b_customer_carts (use
-- carts_products
CREATE TABLE IF NOT EXISTS b2b_carts_products (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
cart_id INT UNSIGNED NOT NULL,
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
cart_id BIGINT UNSIGNED NOT NULL,
product_id INT UNSIGNED NOT NULL,
product_attribute_id INT NULL,
amount INT UNSIGNED NOT NULL,
@@ -153,11 +153,12 @@ CREATE INDEX IF NOT EXISTS idx_carts_products_cart_id ON b2b_carts_products (car
-- customer_orders
CREATE TABLE IF NOT EXISTS b2b_customer_orders (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
name TEXT NOT NULL,
country_id BIGINT UNSIGNED NOT NULL,
address_json TEXT NULL,
status VARCHAR(50) NULL,
address_json TEXT NOT NULL,
status VARCHAR(50) 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_countries FOREIGN KEY (country_id) REFERENCES b2b_countries(id) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
@@ -167,7 +168,7 @@ CREATE INDEX idx_customer_orders_country_id ON b2b_customer_orders (country_id);
-- orders_products
CREATE TABLE IF NOT EXISTS b2b_orders_products (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT UNSIGNED NOT NULL,
product_id INT UNSIGNED NOT NULL,
product_attribute_id INT NULL,