diff --git a/app/delivery/middleware/perms/permissions.go b/app/delivery/middleware/perms/permissions.go index 7fa5458..40c10d4 100644 --- a/app/delivery/middleware/perms/permissions.go +++ b/app/delivery/middleware/perms/permissions.go @@ -3,9 +3,10 @@ package perms type Permission string const ( - UserReadAny Permission = "user.read.any" - UserWriteAny Permission = "user.write.any" - UserDeleteAny Permission = "user.delete.any" - CurrencyWrite Permission = "currency.write" - ViewAllOrders Permission = "orders.view" + UserReadAny Permission = "user.read.any" + UserWriteAny Permission = "user.write.any" + UserDeleteAny Permission = "user.delete.any" + CurrencyWrite Permission = "currency.write" + ViewAllOrders Permission = "orders.view" + ModifyAllOrders Permission = "orders.modify" ) diff --git a/app/delivery/web/api/restricted/orders.go b/app/delivery/web/api/restricted/orders.go index 28e8266..0d0ef51 100644 --- a/app/delivery/web/api/restricted/orders.go +++ b/app/delivery/web/api/restricted/orders.go @@ -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))) diff --git a/app/model/order.go b/app/model/order.go index e01a9e0..8fcb9b6 100644 --- a/app/model/order.go +++ b/app/model/order.go @@ -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"` diff --git a/app/repos/cartsRepo/cartsRepo.go b/app/repos/cartsRepo/cartsRepo.go index b15700c..dab8bee 100644 --- a/app/repos/cartsRepo/cartsRepo.go +++ b/app/repos/cartsRepo/cartsRepo.go @@ -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 } } diff --git a/app/repos/ordersRepo/ordersRepo.go b/app/repos/ordersRepo/ordersRepo.go index d299976..a530293 100644 --- a/app/repos/ordersRepo/ordersRepo.go +++ b/app/repos/ordersRepo/ordersRepo.go @@ -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 } diff --git a/app/service/orderService/orderService.go b/app/service/orderService/orderService.go index ccd46e0..397f226 100644 --- a/app/service/orderService/orderService.go +++ b/app/service/orderService/orderService.go @@ -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 { - return nil + 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.ChangeOrderStatus(order_id, status) } diff --git a/app/utils/const_data/consts.go b/app/utils/const_data/consts.go index aa62f27..547698c 100644 --- a/app/utils/const_data/consts.go +++ b/app/utils/const_data/consts.go @@ -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" diff --git a/app/utils/responseErrors/responseErrors.go b/app/utils/responseErrors/responseErrors.go index 752e532..4d14a50 100644 --- a/app/utils/responseErrors/responseErrors.go +++ b/app/utils/responseErrors/responseErrors.go @@ -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") - ErrEmptyCart = errors.New("the cart is empty") + + // 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), diff --git a/i18n/migrations/20260302163122_create_tables.sql b/i18n/migrations/20260302163122_create_tables.sql index 6040571..8c1cf30 100644 --- a/i18n/migrations/20260302163122_create_tables.sql +++ b/i18n/migrations/20260302163122_create_tables.sql @@ -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,