orders #58
@@ -12,4 +12,6 @@ const (
|
|||||||
ProductTranslationSave Permission = "product_translation.save"
|
ProductTranslationSave Permission = "product_translation.save"
|
||||||
ProductTranslationTranslate Permission = "product_translation.translate"
|
ProductTranslationTranslate Permission = "product_translation.translate"
|
||||||
SearchCreateIndex Permission = "search.create_index"
|
SearchCreateIndex Permission = "search.create_index"
|
||||||
|
OrdersViewAll Permission = "orders.view_all"
|
||||||
|
OrdersModifyAll Permission = "orders.modify_all"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -124,13 +124,13 @@ func (h *AddressesHandler) RetrieveAddressesInfo(c fiber.Ctx) error {
|
|||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
}
|
}
|
||||||
|
|
||||||
addresses_info, err := h.addressesService.RetrieveAddressesInfo(userID)
|
addresses, err := h.addressesService.RetrieveAddresses(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(response.Make(&addresses_info, 0, i18n.T_(c, response.Message_OK)))
|
return c.JSON(response.Make(addresses, 0, i18n.T_(c, response.Message_OK)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *AddressesHandler) DeleteAddress(c fiber.Ctx) error {
|
func (h *AddressesHandler) DeleteAddress(c fiber.Ctx) error {
|
||||||
|
|||||||
171
app/delivery/web/api/restricted/orders.go
Normal file
171
app/delivery/web/api/restricted/orders.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package restricted
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/service/orderService"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/query_params"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrdersHandler struct {
|
||||||
|
ordersService *orderService.OrderService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOrdersHandler() *OrdersHandler {
|
||||||
|
ordersService := orderService.New()
|
||||||
|
return &OrdersHandler{
|
||||||
|
ordersService: ordersService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func OrdersHandlerRoutes(r fiber.Router) fiber.Router {
|
||||||
|
handler := NewOrdersHandler()
|
||||||
|
|
||||||
|
r.Get("/list", handler.ListOrders)
|
||||||
|
r.Post("/place-new-order", handler.PlaceNewOrder)
|
||||||
|
r.Post("/change-order-address", handler.ChangeOrderAddress)
|
||||||
|
r.Get("/change-order-status", handler.ChangeOrderStatus)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// when a user (not admin) wants to list orders, we automatically append filter to only view his orders.
|
||||||
|
// we base permissions and user based on target user only.
|
||||||
|
func (h *OrdersHandler) ListOrders(c fiber.Ctx) error {
|
||||||
|
user, ok := localeExtractor.GetCustomer(c)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
paging, filters, err := query_params.ParseFilters[model.CustomerOrder](c, columnMappingListOrders)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := h.ordersService.Find(user, paging, filters)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&list.Items, int(list.Count), i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
var columnMappingListOrders map[string]string = map[string]string{
|
||||||
|
"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",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OrdersHandler) PlaceNewOrder(c fiber.Ctx) error {
|
||||||
|
userID, ok := localeExtractor.GetUserID(c)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
cart_id_attribute := c.Query("cart_id")
|
||||||
|
cart_id, err := strconv.Atoi(cart_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
country_id_attribute := c.Query("country_id")
|
||||||
|
country_id, err := strconv.Atoi(country_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
address_info := string(c.Body())
|
||||||
|
if address_info == "" {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
name := c.Query("name")
|
||||||
|
|
||||||
|
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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// we base permissions and user based on target user only.
|
||||||
|
func (h *OrdersHandler) ChangeOrderAddress(c fiber.Ctx) error {
|
||||||
|
user, ok := localeExtractor.GetCustomer(c)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
order_id_attribute := c.Query("order_id")
|
||||||
|
order_id, err := strconv.Atoi(order_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
country_id_attribute := c.Query("country_id")
|
||||||
|
country_id, err := strconv.Atoi(country_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
address_info := string(c.Body())
|
||||||
|
if address_info == "" {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.ordersService.ChangeOrderAddress(user, uint(order_id), uint(country_id), address_info)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// we base permissions and user based on target user only.
|
||||||
|
// TODO: well, permissions and all that.
|
||||||
|
func (h *OrdersHandler) ChangeOrderStatus(c fiber.Ctx) error {
|
||||||
|
user, ok := localeExtractor.GetCustomer(c)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
order_id_attribute := c.Query("order_id")
|
||||||
|
order_id, err := strconv.Atoi(order_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
status := c.Query("status")
|
||||||
|
|
||||||
|
err = h.ordersService.ChangeOrderStatus(user, uint(order_id), status)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
@@ -132,8 +132,13 @@ func (s *Server) Setup() error {
|
|||||||
carts := s.restricted.Group("/carts")
|
carts := s.restricted.Group("/carts")
|
||||||
restricted.CartsHandlerRoutes(carts)
|
restricted.CartsHandlerRoutes(carts)
|
||||||
|
|
||||||
|
// orders (restricted)
|
||||||
|
orders := s.restricted.Group("/orders")
|
||||||
|
restricted.OrdersHandlerRoutes(orders)
|
||||||
|
|
||||||
specificPrice := s.restricted.Group("/specific-price")
|
specificPrice := s.restricted.Group("/specific-price")
|
||||||
restricted.SpecificPriceHandlerRoutes(specificPrice)
|
restricted.SpecificPriceHandlerRoutes(specificPrice)
|
||||||
|
|
||||||
// addresses (restricted)
|
// addresses (restricted)
|
||||||
addresses := s.restricted.Group("/addresses")
|
addresses := s.restricted.Group("/addresses")
|
||||||
restricted.AddressesHandlerRoutes(addresses)
|
restricted.AddressesHandlerRoutes(addresses)
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ package model
|
|||||||
type Address struct {
|
type Address struct {
|
||||||
ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
|
ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
|
||||||
CustomerID uint `gorm:"column:b2b_customer_id;not null;index" json:"customer_id"`
|
CustomerID uint `gorm:"column:b2b_customer_id;not null;index" json:"customer_id"`
|
||||||
AddressInfo string `gorm:"column:address_info;not null" json:"address_info"`
|
AddressString string `gorm:"column:address_string;not null" json:"address_string"`
|
||||||
|
AddressUnparsed *AddressUnparsed `gorm:"-" json:"address_unparsed"`
|
||||||
CountryID uint `gorm:"column:b2b_country_id;not null" json:"country_id"`
|
CountryID uint `gorm:"column:b2b_country_id;not null" json:"country_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,15 +12,7 @@ func (Address) TableName() string {
|
|||||||
return "b2b_addresses"
|
return "b2b_addresses"
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddressUnparsed struct {
|
type AddressUnparsed interface{}
|
||||||
ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
|
|
||||||
CustomerID uint `gorm:"column:b2b_customer_id;not null;index" json:"customer_id"`
|
|
||||||
AddressInfo AddressField `gorm:"column:address_info;not null" json:"address_info"`
|
|
||||||
CountryID uint `gorm:"column:b2b_country_id;not null" json:"country_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AddressField interface {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Address template in Poland
|
// Address template in Poland
|
||||||
type AddressPL struct {
|
type AddressPL struct {
|
||||||
|
|||||||
27
app/model/order.go
Normal file
27
app/model/order.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type CustomerOrder struct {
|
||||||
|
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"`
|
||||||
|
AddressString string `gorm:"column:address_string;not null" json:"address_string"`
|
||||||
|
AddressUnparsed *AddressUnparsed `gorm:"-" json:"address_unparsed"`
|
||||||
|
Status string `gorm:"column:status;size:50;not null" json:"status"`
|
||||||
|
Products []OrderProduct `gorm:"foreignKey:OrderID;references:OrderID" json:"products"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (CustomerOrder) TableName() string {
|
||||||
|
return "b2b_customer_orders"
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderProduct struct {
|
||||||
|
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"`
|
||||||
|
Amount uint `gorm:"column:amount;not null" json:"amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OrderProduct) TableName() string {
|
||||||
|
return "b2b_orders_products"
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ func (repo *AddressesRepo) UserAddressesAmt(user_id uint) (uint, error) {
|
|||||||
func (repo *AddressesRepo) AddNewAddress(user_id uint, address_info string, country_id uint) error {
|
func (repo *AddressesRepo) AddNewAddress(user_id uint, address_info string, country_id uint) error {
|
||||||
address := model.Address{
|
address := model.Address{
|
||||||
CustomerID: user_id,
|
CustomerID: user_id,
|
||||||
AddressInfo: address_info,
|
AddressString: address_info,
|
||||||
CountryID: country_id,
|
CountryID: country_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ func (repo *AddressesRepo) UpdateAddress(user_id uint, address_id uint, address_
|
|||||||
address := model.Address{
|
address := model.Address{
|
||||||
ID: address_id,
|
ID: address_id,
|
||||||
CustomerID: user_id,
|
CustomerID: user_id,
|
||||||
AddressInfo: address_info,
|
AddressString: address_info,
|
||||||
CountryID: country_id,
|
CountryID: country_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ import (
|
|||||||
type UICartsRepo interface {
|
type UICartsRepo interface {
|
||||||
CartsAmount(user_id uint) (uint, error)
|
CartsAmount(user_id uint) (uint, error)
|
||||||
CreateNewCart(user_id uint) (model.CustomerCart, error)
|
CreateNewCart(user_id uint) (model.CustomerCart, error)
|
||||||
UserHasCart(user_id uint, cart_id uint) (uint, 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
|
UpdateCartName(user_id uint, cart_id uint, new_name string) error
|
||||||
RetrieveCartsInfo(user_id uint) ([]model.CustomerCart, error)
|
RetrieveCartsInfo(user_id uint) ([]model.CustomerCart, error)
|
||||||
RetrieveCart(user_id uint, cart_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
|
AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +50,15 @@ func (repo *CartsRepo) CreateNewCart(user_id uint) (model.CustomerCart, error) {
|
|||||||
return cart, err
|
return cart, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *CartsRepo) UserHasCart(user_id uint, cart_id uint) (uint, error) {
|
func (repo *CartsRepo) RemoveCart(user_id uint, cart_id uint) error {
|
||||||
|
return db.DB.
|
||||||
|
Table("b2b_customer_carts").
|
||||||
|
Where("cart_id = ? AND user_id = ?", cart_id, user_id).
|
||||||
|
Delete(nil).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *CartsRepo) UserHasCart(user_id uint, cart_id uint) (bool, error) {
|
||||||
var amt uint
|
var amt uint
|
||||||
|
|
||||||
err := db.DB.
|
err := db.DB.
|
||||||
@@ -59,7 +68,7 @@ func (repo *CartsRepo) UserHasCart(user_id uint, cart_id uint) (uint, error) {
|
|||||||
Scan(&amt).
|
Scan(&amt).
|
||||||
Error
|
Error
|
||||||
|
|
||||||
return amt, err
|
return amt >= 1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *CartsRepo) UpdateCartName(user_id uint, cart_id uint, new_name string) error {
|
func (repo *CartsRepo) UpdateCartName(user_id uint, cart_id uint, new_name string) error {
|
||||||
@@ -96,7 +105,7 @@ func (repo *CartsRepo) RetrieveCart(user_id uint, cart_id uint) (*model.Customer
|
|||||||
return &cart, err
|
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
|
var amt uint
|
||||||
|
|
||||||
if product_attribute_id == nil {
|
if product_attribute_id == nil {
|
||||||
@@ -106,7 +115,7 @@ func (repo *CartsRepo) CheckProductExists(product_id uint, product_attribute_id
|
|||||||
Where("id_product = ?", product_id).
|
Where("id_product = ?", product_id).
|
||||||
Scan(&amt).
|
Scan(&amt).
|
||||||
Error
|
Error
|
||||||
return amt, err
|
return amt >= 1, err
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
err := db.DB.
|
err := db.DB.
|
||||||
@@ -116,7 +125,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).
|
Where("ps.id_product = ? AND pas.id_product_attribute = ?", product_id, *product_attribute_id).
|
||||||
Scan(&amt).
|
Scan(&amt).
|
||||||
Error
|
Error
|
||||||
return amt, err
|
return amt >= 1, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
110
app/repos/ordersRepo/ordersRepo.go
Normal file
110
app/repos/ordersRepo/ordersRepo.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
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(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{}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
query := db.Get().
|
||||||
|
Model(&model.CustomerOrder{}).
|
||||||
|
Preload("Products").
|
||||||
|
Order("b2b_customer_orders.order_id DESC")
|
||||||
|
|
||||||
|
// Apply all filters
|
||||||
|
if filt != nil {
|
||||||
|
filt.ApplyAll(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run counter first as query is without limit and offset
|
||||||
|
err := query.Count(&total).Error
|
||||||
|
if err != nil {
|
||||||
|
return &find.Found[model.CustomerOrder]{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = query.
|
||||||
|
Limit(p.Limit()).
|
||||||
|
Offset(p.Offset()).
|
||||||
|
Find(&list).Error
|
||||||
|
if err != nil {
|
||||||
|
return &find.Found[model.CustomerOrder]{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &find.Found[model.CustomerOrder]{
|
||||||
|
Items: list,
|
||||||
|
Count: uint(total),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
AddressString: address_info,
|
||||||
|
Status: constdata.NEW_ORDER_STATUS,
|
||||||
|
Products: make([]model.OrderProduct, 0, len(cart.Products)),
|
||||||
|
}
|
||||||
|
|
||||||
|
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(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_string": address_info,
|
||||||
|
}).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ func New() *AddressesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AddressesService) GetTemplate(country_id uint) (model.AddressField, error) {
|
func (s *AddressesService) GetTemplate(country_id uint) (model.AddressUnparsed, error) {
|
||||||
switch country_id {
|
switch country_id {
|
||||||
|
|
||||||
case 1: // Poland
|
case 1: // Poland
|
||||||
@@ -49,7 +49,7 @@ func (s *AddressesService) AddNewAddress(user_id uint, address_info string, coun
|
|||||||
return responseErrors.ErrMaxAmtOfAddressesReached
|
return responseErrors.ErrMaxAmtOfAddressesReached
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.validateAddressJson(address_info, country_id)
|
_, err = s.ValidateAddressJson(address_info, country_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ func (s *AddressesService) ModifyAddress(user_id uint, address_id uint, address_
|
|||||||
return responseErrors.ErrUserHasNoSuchAddress
|
return responseErrors.ErrUserHasNoSuchAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.validateAddressJson(address_info, country_id)
|
_, err = s.ValidateAddressJson(address_info, country_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -74,30 +74,23 @@ func (s *AddressesService) ModifyAddress(user_id uint, address_id uint, address_
|
|||||||
return s.repo.UpdateAddress(user_id, address_id, address_info, country_id)
|
return s.repo.UpdateAddress(user_id, address_id, address_info, country_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AddressesService) RetrieveAddressesInfo(user_id uint) (*[]model.AddressUnparsed, error) {
|
func (s *AddressesService) RetrieveAddresses(user_id uint) (*[]model.Address, error) {
|
||||||
parsed_addresses, err := s.repo.RetrieveAddresses(user_id)
|
addresses, err := s.repo.RetrieveAddresses(user_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var unparsed_addresses []model.AddressUnparsed
|
for i := 0; i < len(*addresses); i++ {
|
||||||
|
address_unparsed, err := s.ValidateAddressJson((*addresses)[i].AddressString, (*addresses)[i].CountryID)
|
||||||
for i := 0; i < len(*parsed_addresses); i++ {
|
|
||||||
var next_address model.AddressUnparsed
|
|
||||||
next_address.ID = (*parsed_addresses)[i].ID
|
|
||||||
next_address.CustomerID = (*parsed_addresses)[i].CustomerID
|
|
||||||
next_address.CountryID = (*parsed_addresses)[i].CountryID
|
|
||||||
|
|
||||||
next_address.AddressInfo, err = s.validateAddressJson((*parsed_addresses)[i].AddressInfo, next_address.CountryID)
|
|
||||||
// log such errors
|
// log such errors
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("err: %v\n", err)
|
fmt.Printf("err: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
unparsed_addresses = append(unparsed_addresses, next_address)
|
(*addresses)[i].AddressUnparsed = &address_unparsed
|
||||||
}
|
}
|
||||||
|
|
||||||
return &unparsed_addresses, nil
|
return addresses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AddressesService) DeleteAddress(user_id uint, address_id uint) error {
|
func (s *AddressesService) DeleteAddress(user_id uint, address_id uint) error {
|
||||||
@@ -112,7 +105,7 @@ func (s *AddressesService) DeleteAddress(user_id uint, address_id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validateAddressJson makes sure that the info string represents a valid json of address in given country
|
// validateAddressJson makes sure that the info string represents a valid json of address in given country
|
||||||
func (s *AddressesService) validateAddressJson(info string, country_id uint) (model.AddressField, error) {
|
func (s *AddressesService) ValidateAddressJson(info string, country_id uint) (model.AddressUnparsed, error) {
|
||||||
dec := json.NewDecoder(strings.NewReader(info))
|
dec := json.NewDecoder(strings.NewReader(info))
|
||||||
dec.DisallowUnknownFields()
|
dec.DisallowUnknownFields()
|
||||||
|
|
||||||
|
|||||||
@@ -34,12 +34,24 @@ func (s *CartsService) CreateNewCart(user_id uint) (model.CustomerCart, error) {
|
|||||||
return cart, nil
|
return cart, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CartsService) UpdateCartName(user_id uint, cart_id uint, new_name string) error {
|
func (s *CartsService) RemoveCart(user_id uint, cart_id uint) error {
|
||||||
amt, err := s.repo.UserHasCart(user_id, cart_id)
|
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if amt != 1 {
|
if !exists {
|
||||||
|
return responseErrors.ErrUserHasNoSuchCart
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.RemoveCart(user_id, cart_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CartsService) UpdateCartName(user_id uint, cart_id uint, new_name string) error {
|
||||||
|
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
return responseErrors.ErrUserHasNoSuchCart
|
return responseErrors.ErrUserHasNoSuchCart
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,11 +63,11 @@ func (s *CartsService) RetrieveCartsInfo(user_id uint) ([]model.CustomerCart, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CartsService) RetrieveCart(user_id uint, cart_id uint) (*model.CustomerCart, error) {
|
func (s *CartsService) RetrieveCart(user_id uint, cart_id uint) (*model.CustomerCart, error) {
|
||||||
amt, err := s.repo.UserHasCart(user_id, cart_id)
|
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if amt != 1 {
|
if !exists {
|
||||||
return nil, responseErrors.ErrUserHasNoSuchCart
|
return nil, responseErrors.ErrUserHasNoSuchCart
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,19 +75,19 @@ func (s *CartsService) RetrieveCart(user_id uint, cart_id uint) (*model.Customer
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error {
|
func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error {
|
||||||
amt, err := s.repo.UserHasCart(user_id, cart_id)
|
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if amt != 1 {
|
if !exists {
|
||||||
return responseErrors.ErrUserHasNoSuchCart
|
return responseErrors.ErrUserHasNoSuchCart
|
||||||
}
|
}
|
||||||
|
|
||||||
amt, err = s.repo.CheckProductExists(product_id, product_attribute_id)
|
exists, err = s.repo.CheckProductExists(product_id, product_attribute_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if amt != 1 {
|
if !exists {
|
||||||
return responseErrors.ErrProductOrItsVariationDoesNotExist
|
return responseErrors.ErrProductOrItsVariationDoesNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,18 @@ func (s *EmailService) SendNewUserAdminNotification(userEmail, userName, baseURL
|
|||||||
return s.SendEmail(s.config.AdminEmail, subject, body)
|
return s.SendEmail(s.config.AdminEmail, subject, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendNewOrderPlacedNotification sends an email to admin when new order is placed
|
||||||
|
func (s *EmailService) SendNewOrderPlacedNotification(userID uint) error {
|
||||||
|
if s.config.AdminEmail == "" {
|
||||||
|
return nil // No admin email configured
|
||||||
|
}
|
||||||
|
|
||||||
|
subject := "New Order Created"
|
||||||
|
body := s.newOrderPlacedTemplate(userID)
|
||||||
|
|
||||||
|
return s.SendEmail(s.config.AdminEmail, subject, body)
|
||||||
|
}
|
||||||
|
|
||||||
// verificationEmailTemplate returns the HTML template for email verification
|
// verificationEmailTemplate returns the HTML template for email verification
|
||||||
func (s *EmailService) verificationEmailTemplate(name, verificationURL string, langID uint) string {
|
func (s *EmailService) verificationEmailTemplate(name, verificationURL string, langID uint) string {
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
@@ -137,3 +149,10 @@ func (s *EmailService) newUserAdminNotificationTemplate(userEmail, userName, bas
|
|||||||
emails.EmailAdminNotificationWrapper(view.EmailLayout[view.EmailAdminNotificationData]{LangID: constdata.ADMIN_NOTIFICATION_LANGUAGE, Data: view.EmailAdminNotificationData{UserEmail: userEmail, UserName: userName, BaseURL: baseURL}}).Render(context.Background(), &buf)
|
emails.EmailAdminNotificationWrapper(view.EmailLayout[view.EmailAdminNotificationData]{LangID: constdata.ADMIN_NOTIFICATION_LANGUAGE, Data: view.EmailAdminNotificationData{UserEmail: userEmail, UserName: userName, BaseURL: baseURL}}).Render(context.Background(), &buf)
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newUserAdminNotificationTemplate returns the HTML template for admin notification
|
||||||
|
func (s *EmailService) newOrderPlacedTemplate(userID uint) string {
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
emails.EmailNewOrderPlacedWrapper(view.EmailLayout[view.EmailNewOrderPlacedData]{LangID: constdata.ADMIN_NOTIFICATION_LANGUAGE, Data: view.EmailNewOrderPlacedData{UserID: userID}}).Render(context.Background(), &buf)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|||||||
145
app/service/orderService/orderService.go
Normal file
145
app/service/orderService/orderService.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package orderService
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"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/repos/cartsRepo"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/repos/ordersRepo"
|
||||||
|
"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/utils/query/filters"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderService struct {
|
||||||
|
ordersRepo ordersRepo.UIOrdersRepo
|
||||||
|
cartsRepo cartsRepo.UICartsRepo
|
||||||
|
addressesService *addressesService.AddressesService
|
||||||
|
emailService *emailService.EmailService
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *OrderService {
|
||||||
|
return &OrderService{
|
||||||
|
ordersRepo: ordersRepo.New(),
|
||||||
|
cartsRepo: cartsRepo.New(),
|
||||||
|
addressesService: addressesService.New(),
|
||||||
|
emailService: emailService.NewEmailService(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderService) Find(user *model.Customer, p find.Paging, filt *filters.FiltersList) (*find.Found[model.CustomerOrder], error) {
|
||||||
|
if !user.HasPermission(perms.OrdersViewAll) {
|
||||||
|
// append filter to view only this user's orders
|
||||||
|
idStr := strconv.FormatUint(uint64(user.ID), 10)
|
||||||
|
filt.Append(filters.Where("b2b_customer_orders.user_id = " + idStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := s.ordersRepo.Find(user.ID, p, filt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(list.Items); i++ {
|
||||||
|
address_unparsed, err := s.addressesService.ValidateAddressJson(list.Items[i].AddressString, list.Items[i].CountryID)
|
||||||
|
// log such errors
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("err: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Items[i].AddressUnparsed = &address_unparsed
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := s.cartsRepo.UserHasCart(user_id, cart_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return responseErrors.ErrUserHasNoSuchCart
|
||||||
|
}
|
||||||
|
|
||||||
|
cart, err := s.cartsRepo.RetrieveCart(user_id, cart_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(cart.Products) == 0 {
|
||||||
|
return responseErrors.ErrEmptyCart
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "" && cart.Name != nil {
|
||||||
|
name = *cart.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// all checks passed
|
||||||
|
err = s.ordersRepo.PlaceNewOrder(cart, name, country_id, address_info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// from this point onward we do not cancel this order.
|
||||||
|
goc_daniel marked this conversation as resolved
Outdated
|
|||||||
|
|
||||||
|
// if no error is returned, remove the cart. This should be smooth
|
||||||
|
goc_daniel marked this conversation as resolved
Outdated
dudzic_wiktor
commented
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
|
|||||||
|
err = s.cartsRepo.RemoveCart(user_id, cart_id)
|
||||||
|
if err != nil {
|
||||||
|
// Log error but don't fail placing order
|
||||||
|
_ = err
|
||||||
|
}
|
||||||
|
|
||||||
|
// send email to admin
|
||||||
|
go func(user_id uint) {
|
||||||
|
err := s.emailService.SendNewOrderPlacedNotification(user_id)
|
||||||
|
if err != nil {
|
||||||
|
// Log error but don't fail placing order
|
||||||
|
_ = err
|
||||||
|
}
|
||||||
|
}(user_id)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderService) ChangeOrderAddress(user *model.Customer, order_id uint, country_id uint, address_info string) error {
|
||||||
|
_, err := s.addressesService.ValidateAddressJson(address_info, country_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.HasPermission(perms.OrdersModifyAll) {
|
||||||
|
exists, err := s.ordersRepo.UserHasOrder(user.ID, order_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
goc_daniel marked this conversation as resolved
dudzic_wiktor
commented
standard user never changes it's own order status, only admin has privilege to do that. endpoint should forbid access at the handler level standard user never changes it's own order status, only admin has privilege to do that. endpoint should forbid access at the handler level
|
|||||||
|
if !exists {
|
||||||
|
return responseErrors.ErrUserHasNoSuchOrder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.ordersRepo.ChangeOrderAddress(order_id, country_id, address_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is obiously just an initial version of this function
|
||||||
|
func (s *OrderService) ChangeOrderStatus(user *model.Customer, order_id uint, status string) error {
|
||||||
|
if !user.HasPermission(perms.OrdersModifyAll) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
26
app/templ/emails/emailNewOrderPlacedNotification.templ
Normal file
26
app/templ/emails/emailNewOrderPlacedNotification.templ
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package emails
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/templ/layout"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/view"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
templ EmailNewOrderPlacedWrapper(data view.EmailLayout[view.EmailNewOrderPlacedData]) {
|
||||||
|
@layout.Base( i18n.T___(data.LangID, "email.email_new_order_placed_notification_title")) {
|
||||||
|
<div class="container">
|
||||||
|
<div class="email-wrapper">
|
||||||
|
<div class="email-header">
|
||||||
|
<h1>New Order Placed</h1>
|
||||||
|
</div>
|
||||||
|
<div class="email-body">
|
||||||
|
<p>Hello Administrator,</p>
|
||||||
|
<p>User with id { data.Data.UserID } has placed a new order. </p>
|
||||||
|
</div>
|
||||||
|
<div class="email-footer">
|
||||||
|
<p>© 2024 Gitea Manager. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,9 @@ const MAX_AMOUNT_OF_ADDRESSES_PER_USER = 10
|
|||||||
|
|
||||||
const USER_LOCALE = "user"
|
const USER_LOCALE = "user"
|
||||||
|
|
||||||
|
// ORDERS
|
||||||
|
const NEW_ORDER_STATUS = "PENDING"
|
||||||
|
|
||||||
// WEBDAV
|
// WEBDAV
|
||||||
const NBYTES_IN_WEBDAV_TOKEN = 32
|
const NBYTES_IN_WEBDAV_TOKEN = 32
|
||||||
const WEBDAV_HREF_ROOT = "http://localhost:3000/api/v1/webdav/storage"
|
const WEBDAV_HREF_ROOT = "http://localhost:3000/api/v1/webdav/storage"
|
||||||
|
|||||||
@@ -66,11 +66,16 @@ var (
|
|||||||
ErrUserHasNoSuchCart = errors.New("user does not have cart with given id")
|
ErrUserHasNoSuchCart = errors.New("user does not have cart with given id")
|
||||||
ErrProductOrItsVariationDoesNotExist = errors.New("product or its variation with given ids does not exist")
|
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 price reduction handler
|
// Typed errors for price reduction handler
|
||||||
ErrInvalidReductionType = errors.New("invalid reduction type: must be 'amount' or 'percentage'")
|
ErrInvalidReductionType = errors.New("invalid reduction type: must be 'amount' or 'percentage'")
|
||||||
ErrPercentageRequired = errors.New("percentage_reduction required when reduction_type is percentage")
|
ErrPercentageRequired = errors.New("percentage_reduction required when reduction_type is percentage")
|
||||||
ErrPriceRequired = errors.New("price required when reduction_type is amount")
|
ErrPriceRequired = errors.New("price required when reduction_type is amount")
|
||||||
ErrSpecificPriceNotFound = errors.New("price reduction not found")
|
ErrSpecificPriceNotFound = errors.New("price reduction not found")
|
||||||
|
|
||||||
// Typed errors for storage
|
// Typed errors for storage
|
||||||
ErrAccessDenied = errors.New("access denied!")
|
ErrAccessDenied = errors.New("access denied!")
|
||||||
ErrFolderDoesNotExist = errors.New("folder does not exist")
|
ErrFolderDoesNotExist = errors.New("folder does not exist")
|
||||||
@@ -201,6 +206,11 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
|||||||
case errors.Is(err, ErrProductOrItsVariationDoesNotExist):
|
case errors.Is(err, ErrProductOrItsVariationDoesNotExist):
|
||||||
return i18n.T_(c, "error.err_product_or_its_variation_does_not_exist")
|
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):
|
case errors.Is(err, ErrAccessDenied):
|
||||||
return i18n.T_(c, "error.err_access_denied")
|
return i18n.T_(c, "error.err_access_denied")
|
||||||
case errors.Is(err, ErrFolderDoesNotExist):
|
case errors.Is(err, ErrFolderDoesNotExist):
|
||||||
@@ -282,6 +292,8 @@ func GetErrorStatus(err error) int {
|
|||||||
errors.Is(err, ErrMaxAmtOfCartsReached),
|
errors.Is(err, ErrMaxAmtOfCartsReached),
|
||||||
errors.Is(err, ErrUserHasNoSuchCart),
|
errors.Is(err, ErrUserHasNoSuchCart),
|
||||||
errors.Is(err, ErrProductOrItsVariationDoesNotExist),
|
errors.Is(err, ErrProductOrItsVariationDoesNotExist),
|
||||||
|
errors.Is(err, ErrEmptyCart),
|
||||||
|
errors.Is(err, ErrUserHasNoSuchOrder),
|
||||||
errors.Is(err, ErrInvalidReductionType),
|
errors.Is(err, ErrInvalidReductionType),
|
||||||
errors.Is(err, ErrPercentageRequired),
|
errors.Is(err, ErrPercentageRequired),
|
||||||
errors.Is(err, ErrPriceRequired),
|
errors.Is(err, ErrPriceRequired),
|
||||||
|
|||||||
@@ -18,3 +18,7 @@ type EmailAdminNotificationData struct {
|
|||||||
type EmailPasswordResetData struct {
|
type EmailPasswordResetData struct {
|
||||||
ResetURL string
|
ResetURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EmailNewOrderPlacedData struct {
|
||||||
|
UserID uint
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ info:
|
|||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
url: http://localhost:3000/api/v1/restricted/carts/retrieve-cart?cart_id=3
|
url: http://localhost:3000/api/v1/restricted/carts/retrieve-cart?cart_id=1
|
||||||
params:
|
params:
|
||||||
- name: cart_id
|
- name: cart_id
|
||||||
value: "3"
|
value: "1"
|
||||||
type: query
|
type: query
|
||||||
auth: inherit
|
auth: inherit
|
||||||
|
|
||||||
|
|||||||
33
bruno/b2b_daniel/orders/change-order-address.yml
Normal file
33
bruno/b2b_daniel/orders/change-order-address.yml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
info:
|
||||||
|
name: change-order-address
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/orders/change-order-address?order_id=1&country_id=1
|
||||||
|
params:
|
||||||
|
- name: order_id
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
- name: country_id
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
body:
|
||||||
|
type: json
|
||||||
|
data: |-
|
||||||
|
{
|
||||||
|
"postal_code": "31-154",
|
||||||
|
"city": "Kraków",
|
||||||
|
"voivodeship": "śląskie",
|
||||||
|
"street": "Długa",
|
||||||
|
"building_no": "5",
|
||||||
|
"recipient": "Adam Adamowicz"
|
||||||
|
}
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
22
bruno/b2b_daniel/orders/change-order-status.yml
Normal file
22
bruno/b2b_daniel/orders/change-order-status.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
info:
|
||||||
|
name: change-order-status
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/orders/change-order-status?order_id=1&status=PAID
|
||||||
|
params:
|
||||||
|
- name: order_id
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
- name: status
|
||||||
|
value: PAID
|
||||||
|
type: query
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
7
bruno/b2b_daniel/orders/folder.yml
Normal file
7
bruno/b2b_daniel/orders/folder.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
info:
|
||||||
|
name: orders
|
||||||
|
type: folder
|
||||||
|
seq: 11
|
||||||
|
|
||||||
|
request:
|
||||||
|
auth: inherit
|
||||||
31
bruno/b2b_daniel/orders/list.yml
Normal file
31
bruno/b2b_daniel/orders/list.yml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
info:
|
||||||
|
name: list
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/orders/list?p=1&elems=30&sort=product_id,asc&user_id=2&name=~sdj
|
||||||
|
params:
|
||||||
|
- name: p
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
- name: elems
|
||||||
|
value: "30"
|
||||||
|
type: query
|
||||||
|
- name: sort
|
||||||
|
value: product_id,asc
|
||||||
|
type: query
|
||||||
|
- name: user_id
|
||||||
|
value: "2"
|
||||||
|
type: query
|
||||||
|
- name: name
|
||||||
|
value: ~sdj
|
||||||
|
type: query
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
37
bruno/b2b_daniel/orders/place-new-order.yml
Normal file
37
bruno/b2b_daniel/orders/place-new-order.yml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
info:
|
||||||
|
name: place-new-order
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: POST
|
||||||
|
url: http://localhost:3000/api/v1/restricted/orders/place-new-order?cart_id=1&name=sdjalksd&country_id=1
|
||||||
|
params:
|
||||||
|
- name: cart_id
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
- name: name
|
||||||
|
value: sdjalksd
|
||||||
|
type: query
|
||||||
|
- name: country_id
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
body:
|
||||||
|
type: json
|
||||||
|
data: |-
|
||||||
|
{
|
||||||
|
"postal_code": "31-154",
|
||||||
|
"city": "Kraków",
|
||||||
|
"voivodeship": "małopolskie",
|
||||||
|
"street": "Długa",
|
||||||
|
"building_no": "5",
|
||||||
|
"apartment_no": "7",
|
||||||
|
"recipient": "Jan Kowalski"
|
||||||
|
}
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
@@ -130,7 +130,7 @@ FOREIGN KEY (role_id) REFERENCES b2b_roles(id);
|
|||||||
|
|
||||||
-- customer_carts
|
-- customer_carts
|
||||||
CREATE TABLE IF NOT EXISTS b2b_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,
|
user_id BIGINT UNSIGNED NOT NULL,
|
||||||
name VARCHAR(255) NULL,
|
name VARCHAR(255) NULL,
|
||||||
CONSTRAINT fk_customer_carts_customers FOREIGN KEY (user_id) REFERENCES b2b_customers(id) ON DELETE CASCADE ON UPDATE CASCADE
|
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
|
-- carts_products
|
||||||
CREATE TABLE IF NOT EXISTS b2b_carts_products (
|
CREATE TABLE IF NOT EXISTS b2b_carts_products (
|
||||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
cart_id INT UNSIGNED NOT NULL,
|
cart_id BIGINT UNSIGNED NOT NULL,
|
||||||
product_id INT UNSIGNED NOT NULL,
|
product_id INT UNSIGNED NOT NULL,
|
||||||
product_attribute_id INT NULL,
|
product_attribute_id INT NULL,
|
||||||
amount INT UNSIGNED NOT NULL,
|
amount INT UNSIGNED NOT NULL,
|
||||||
@@ -224,7 +224,7 @@ ON `b2b_countries` (
|
|||||||
CREATE TABLE IF NOT EXISTS b2b_addresses (
|
CREATE TABLE IF NOT EXISTS b2b_addresses (
|
||||||
id BIGINT UNSIGNED AUTO_INCREMENT NOT NULL,
|
id BIGINT UNSIGNED AUTO_INCREMENT NOT NULL,
|
||||||
b2b_customer_id BIGINT UNSIGNED NOT NULL,
|
b2b_customer_id BIGINT UNSIGNED NOT NULL,
|
||||||
address_info TEXT NOT NULL,
|
address_string TEXT NOT NULL,
|
||||||
b2b_country_id BIGINT UNSIGNED NOT NULL,
|
b2b_country_id BIGINT UNSIGNED NOT NULL,
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
CONSTRAINT fk_b2b_addresses_b2b_customers FOREIGN KEY (b2b_customer_id) REFERENCES b2b_customers (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
CONSTRAINT fk_b2b_addresses_b2b_customers FOREIGN KEY (b2b_customer_id) REFERENCES b2b_customers (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
@@ -232,6 +232,34 @@ CREATE TABLE IF NOT EXISTS b2b_addresses (
|
|||||||
) ENGINE = InnoDB;
|
) ENGINE = InnoDB;
|
||||||
|
|
||||||
|
|
||||||
|
-- customer_orders
|
||||||
|
CREATE TABLE IF NOT EXISTS b2b_customer_orders (
|
||||||
|
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_string 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;
|
||||||
|
CREATE INDEX idx_customer_orders_user_id ON b2b_customer_orders (user_id);
|
||||||
|
CREATE INDEX idx_customer_orders_country_id ON b2b_customer_orders (country_id);
|
||||||
|
|
||||||
|
|
||||||
|
-- orders_products
|
||||||
|
CREATE TABLE IF NOT EXISTS b2b_orders_products (
|
||||||
|
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,
|
||||||
|
amount INT UNSIGNED NOT NULL,
|
||||||
|
CONSTRAINT fk_orders_products_customer_orders FOREIGN KEY (order_id) REFERENCES b2b_customer_orders (order_id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_orders_products_product FOREIGN KEY (product_id) REFERENCES ps_product (id_product) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_orders_products_order_id ON b2b_orders_products (order_id);
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE b2b_specific_price (
|
CREATE TABLE b2b_specific_price (
|
||||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
|
|||||||
Reference in New Issue
Block a user
after successful order placement we should either delete or deactivate the cart that is leftover