31 Commits

Author SHA1 Message Date
9a90de3f11 Merge pull request 'no-vat-customers' (#71) from no-vat-customers into main
Reviewed-on: #71
2026-04-15 11:32:13 +00:00
6efb39edf7 Merge branch 'no-vat-customers' of ssh://git.ma-al.com:8822/goc_daniel/b2b into no-vat-customers 2026-04-15 12:58:37 +02:00
e9af4bf311 feat: add no vat customers logic 2026-04-15 12:58:23 +02:00
cc570cc6a8 Merge branch 'main' into no-vat-customers 2026-04-15 10:57:16 +00:00
1bf706dcd0 feat: add no vat customers logic 2026-04-15 12:55:14 +02:00
84b4c70ffb Merge pull request 'bugfix' (#70) from countries_bugfix into main
Reviewed-on: #70
Reviewed-by: Wiktor Dudzic <dudzic_wiktor@ma-al.com>
2026-04-15 10:00:41 +00:00
Daniel Goc
2fd9472db1 bugfix 2026-04-15 11:48:29 +02:00
66df535317 Merge pull request 'expand_carts' (#69) from expand_carts into main
Reviewed-on: #69
Reviewed-by: Wiktor Dudzic <dudzic_wiktor@ma-al.com>
2026-04-15 09:19:16 +00:00
e31ecda582 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into no-vat-customers 2026-04-15 11:14:40 +02:00
Daniel Goc
e0a86febc4 add missing permission 2026-04-14 15:52:45 +02:00
Daniel Goc
40154ec861 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into expand_carts 2026-04-14 15:43:44 +02:00
Daniel Goc
bb507036db change add-product endpoint + remove-product 2026-04-14 15:42:30 +02:00
80a1314dc0 Merge pull request 'some small fixes' (#68) from small_fixes into main
Reviewed-on: #68
2026-04-14 13:16:17 +00:00
Daniel Goc
100a9f57d4 some small fixes 2026-04-14 14:08:57 +02:00
773e7d3c20 Merge pull request 'feat: lookup by id in customer search' (#61) from cust-search into main
Reviewed-on: #61
2026-04-14 11:42:56 +00:00
8e063978a8 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into no-vat-customers 2026-04-14 13:40:37 +02:00
03a0e5ea64 Merge branch 'main' into cust-search 2026-04-14 11:39:18 +00:00
ce8c19f715 Merge pull request 'feat: make routing per role, add unlogged role' (#67) from routing-per-role into main
Reviewed-on: #67
Reviewed-by: goc_daniel <goc_daniel@ma-al.com>
2026-04-14 11:39:13 +00:00
31a2744131 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into no-vat-customers 2026-04-14 13:36:11 +02:00
4edcb0a852 Merge branch 'main' into cust-search 2026-04-14 11:22:00 +00:00
a4120dafa2 Merge branch 'main' into routing-per-role 2026-04-14 11:21:53 +00:00
5e1a8e898c Merge pull request 'orders' (#58) from orders into main
Reviewed-on: #58
Reviewed-by: Wiktor Dudzic <dudzic_wiktor@ma-al.com>
2026-04-14 11:20:05 +00:00
8e3e41d6fe Merge branch 'main' into cust-search 2026-04-14 11:16:42 +00:00
b33da9d072 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into routing-per-role 2026-04-14 13:15:51 +02:00
f55d59a0fd feat: add no vat property to customers 2026-04-14 13:12:21 +02:00
ab783b599d chore: add favorite field to base product query 2026-04-14 11:07:55 +02:00
d173af29fe fix: actually add the unlogged role to migration 2026-04-14 10:18:12 +02:00
f14d60d67b chore: swap permission string in handler to consts 2026-04-14 10:17:05 +02:00
967b101f9b feat: make routing per role, add unlogged role 2026-04-14 09:54:37 +02:00
97ca510b99 Merge branch 'main' into cust-search 2026-04-14 06:26:47 +00:00
83b7cd49dd feat: lookup by id in customer search 2026-04-13 14:43:18 +02:00
29 changed files with 465 additions and 234 deletions

View File

@@ -7,17 +7,18 @@ import (
"time" "time"
"git.ma-al.com/goc_daniel/b2b/app/config" "git.ma-al.com/goc_daniel/b2b/app/config"
"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/model"
"git.ma-al.com/goc_daniel/b2b/app/service/authService" "git.ma-al.com/goc_daniel/b2b/app/service/authService"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data" constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
) )
// AuthMiddleware creates authentication middleware // AuthMiddleware creates authentication middleware
func AuthMiddleware() fiber.Handler { func Authenticate() fiber.Handler {
authService := authService.NewAuthService() authService := authService.NewAuthService()
return func(c fiber.Ctx) error { return func(c fiber.Ctx) error {
// Get token from Authorization header // Get token from Authorization header
authHeader := c.Get("Authorization") authHeader := c.Get("Authorization")
@@ -25,17 +26,13 @@ func AuthMiddleware() fiber.Handler {
// Try to get from cookie // Try to get from cookie
authHeader = c.Cookies("access_token") authHeader = c.Cookies("access_token")
if authHeader == "" { if authHeader == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ return c.Next()
"error": "authorization token required",
})
} }
} else { } else {
// Extract token from "Bearer <token>" // Extract token from "Bearer <token>"
parts := strings.Split(authHeader, " ") parts := strings.Split(authHeader, " ")
if len(parts) != 2 || parts[0] != "Bearer" { if len(parts) != 2 || parts[0] != "Bearer" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ return c.Next()
"error": "invalid authorization header format",
})
} }
authHeader = parts[1] authHeader = parts[1]
} }
@@ -43,24 +40,18 @@ func AuthMiddleware() fiber.Handler {
// Validate token // Validate token
claims, err := authService.ValidateToken(authHeader) claims, err := authService.ValidateToken(authHeader)
if err != nil { if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ return c.Next()
"error": "invalid or expired token",
})
} }
// Get user from database // Get user from database
user, err := authService.GetUserByID(claims.UserID) user, err := authService.GetUserByID(claims.UserID)
if err != nil { if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ return c.Next()
"error": "user not found",
})
} }
// Check if user is active // Check if user is active
if !user.IsActive { if !user.IsActive {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ return c.Next()
"error": "user account is inactive",
})
} }
// Create locale. LangID is overwritten by auth Token // Create locale. LangID is overwritten by auth Token
@@ -78,10 +69,8 @@ func AuthMiddleware() fiber.Handler {
} }
// We now populate the target user // We now populate the target user
if model.CustomerRole(user.Role.Name) != model.RoleAdmin { if !userLocale.OriginalUser.HasPermission(perms.Teleport) {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ return c.Next()
"error": "admin access required",
})
} }
targetUserID, err := strconv.Atoi(targetUserIDAttribute) targetUserID, err := strconv.Atoi(targetUserIDAttribute)
@@ -114,6 +103,18 @@ func AuthMiddleware() fiber.Handler {
} }
} }
func Authorize() fiber.Handler {
return func(c fiber.Ctx) error {
_, ok := localeExtractor.GetUserID(c)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "not authenticated",
})
}
return c.Next()
}
}
// Webdav // Webdav
func Webdav() fiber.Handler { func Webdav() fiber.Handler {
authService := authService.NewAuthService() authService := authService.NewAuthService()

View File

@@ -14,4 +14,5 @@ const (
SearchCreateIndex Permission = "search.create_index" SearchCreateIndex Permission = "search.create_index"
OrdersViewAll Permission = "orders.view_all" OrdersViewAll Permission = "orders.view_all"
OrdersModifyAll Permission = "orders.modify_all" OrdersModifyAll Permission = "orders.modify_all"
Teleport Permission = "teleport"
) )

View File

@@ -49,7 +49,7 @@ func AuthHandlerRoutes(r fiber.Router) fiber.Router {
r.Get("/google", handler.GoogleLogin) r.Get("/google", handler.GoogleLogin)
r.Get("/google/callback", handler.GoogleCallback) r.Get("/google/callback", handler.GoogleCallback)
authProtected := r.Group("", middleware.AuthMiddleware()) authProtected := r.Group("", middleware.Authorize())
authProtected.Get("/me", handler.Me) authProtected.Get("/me", handler.Me)
authProtected.Post("/update-choice", handler.UpdateJWTToken) authProtected.Post("/update-choice", handler.UpdateJWTToken)

View File

@@ -2,6 +2,7 @@ package public
import ( import (
"git.ma-al.com/goc_daniel/b2b/app/service/menuService" "git.ma-al.com/goc_daniel/b2b/app/service/menuService"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n" "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/localeExtractor"
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable" "git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
@@ -31,12 +32,21 @@ func RoutingHandlerRoutes(r fiber.Router) fiber.Router {
} }
func (h *RoutingHandler) GetRouting(c fiber.Ctx) error { func (h *RoutingHandler) GetRouting(c fiber.Ctx) error {
lang_id, ok := localeExtractor.GetLangID(c) langId, ok := localeExtractor.GetLangID(c)
if !ok { if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)). return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute))) JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
} }
menu, err := h.menuService.GetRoutes(lang_id)
var roleId uint
customer, ok := localeExtractor.GetCustomer(c)
if !ok {
roleId = constdata.UNLOGGED_USER_ROLE_ID
} else {
roleId = customer.RoleID
}
menu, err := h.menuService.GetRoutes(langId, roleId)
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)))

View File

@@ -29,10 +29,12 @@ func CartsHandlerRoutes(r fiber.Router) fiber.Router {
handler := NewCartsHandler() handler := NewCartsHandler()
r.Get("/add-new-cart", handler.AddNewCart) r.Get("/add-new-cart", handler.AddNewCart)
r.Delete("/remove-cart", handler.RemoveCart)
r.Get("/change-cart-name", handler.ChangeCartName) r.Get("/change-cart-name", handler.ChangeCartName)
r.Get("/retrieve-carts-info", handler.RetrieveCartsInfo) r.Get("/retrieve-carts-info", handler.RetrieveCartsInfo)
r.Get("/retrieve-cart", handler.RetrieveCart) r.Get("/retrieve-cart", handler.RetrieveCart)
r.Get("/add-product-to-cart", handler.AddProduct) r.Get("/add-product-to-cart", handler.AddProduct)
r.Delete("/remove-product-from-cart", handler.RemoveProduct)
return r return r
} }
@@ -53,6 +55,29 @@ func (h *CartsHandler) AddNewCart(c fiber.Ctx) error {
return c.JSON(response.Make(&new_cart, 0, i18n.T_(c, response.Message_OK))) return c.JSON(response.Make(&new_cart, 0, i18n.T_(c, response.Message_OK)))
} }
func (h *CartsHandler) RemoveCart(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)))
}
err = h.cartsService.RemoveCart(userID, uint(cart_id))
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)))
}
func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error { func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c) userID, ok := localeExtractor.GetUserID(c)
if !ok { if !ok {
@@ -117,6 +142,7 @@ func (h *CartsHandler) RetrieveCart(c fiber.Ctx) error {
return c.JSON(response.Make(cart, 0, i18n.T_(c, response.Message_OK))) return c.JSON(response.Make(cart, 0, i18n.T_(c, response.Message_OK)))
} }
// adds or sets given amount of products to the cart
func (h *CartsHandler) AddProduct(c fiber.Ctx) error { func (h *CartsHandler) AddProduct(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c) userID, ok := localeExtractor.GetUserID(c)
if !ok { if !ok {
@@ -159,7 +185,59 @@ func (h *CartsHandler) AddProduct(c fiber.Ctx) error {
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute))) JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
} }
err = h.cartsService.AddProduct(userID, uint(cart_id), uint(product_id), product_attribute_id, uint(amount)) set_amount_attribute := c.Query("set_amount")
set_amount, err := strconv.ParseBool(set_amount_attribute)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
err = h.cartsService.AddProduct(userID, uint(cart_id), uint(product_id), product_attribute_id, amount, set_amount)
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)))
}
// removes product from the cart.
func (h *CartsHandler) RemoveProduct(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)))
}
product_id_attribute := c.Query("product_id")
product_id, err := strconv.Atoi(product_id_attribute)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
product_attribute_id_attribute := c.Query("product_attribute_id")
var product_attribute_id *uint
if product_attribute_id_attribute == "" {
product_attribute_id = nil
} else {
val, err := strconv.Atoi(product_attribute_id_attribute)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
uval := uint(val)
product_attribute_id = &uval
}
err = h.cartsService.RemoveProduct(userID, uint(cart_id), uint(product_id), product_attribute_id)
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)))

View File

@@ -3,6 +3,7 @@ package restricted
import ( import (
"strconv" "strconv"
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware"
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms" "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/model"
"git.ma-al.com/goc_daniel/b2b/app/service/customerService" "git.ma-al.com/goc_daniel/b2b/app/service/customerService"
@@ -30,7 +31,8 @@ func CustomerHandlerRoutes(r fiber.Router) fiber.Router {
handler := NewCustomerHandler() handler := NewCustomerHandler()
r.Get("", handler.customerData) r.Get("", handler.customerData)
r.Get("/list", handler.listCustomers) r.Get("/list", middleware.Require(perms.UserReadAny), handler.listCustomers)
r.Patch("/no-vat", middleware.Require(perms.UserWriteAny), handler.setCustomerNoVatStatus)
return r return r
} }
@@ -75,10 +77,6 @@ func (h *customerHandler) listCustomers(fc fiber.Ctx) error {
return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)). return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrBadAttribute))) JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrBadAttribute)))
} }
if !user.HasPermission(perms.UserReadAny) {
return fc.Status(fiber.StatusForbidden).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrForbidden)))
}
p, filt, err := query_params.ParseFilters[model.Customer](fc, columnMappingListUsers) p, filt, err := query_params.ParseFilters[model.Customer](fc, columnMappingListUsers)
if err != nil { if err != nil {
@@ -87,12 +85,6 @@ func (h *customerHandler) listCustomers(fc fiber.Ctx) error {
} }
search := fc.Query("search") search := fc.Query("search")
if search != "" {
if !user.HasPermission(perms.UserReadAny) {
return fc.Status(fiber.StatusForbidden).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrForbidden)))
}
}
customer, err := h.service.Find(user.LangID, p, filt, search) customer, err := h.service.Find(user.LangID, p, filt, search)
if err != nil { if err != nil {
@@ -109,3 +101,28 @@ var columnMappingListUsers map[string]string = map[string]string{
"first_name": "users.first_name", "first_name": "users.first_name",
"last_name": "users.last_name", "last_name": "users.last_name",
} }
func (h *customerHandler) setCustomerNoVatStatus(fc fiber.Ctx) error {
user, ok := localeExtractor.GetCustomer(fc)
if !ok || user == nil {
return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrInvalidBody)))
}
var req struct {
CustomerID uint `json:"customer_id"`
IsNoVat bool `json:"is_no_vat"`
}
if err := fc.Bind().Body(&req); err != nil {
return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrJSONBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrJSONBody)))
}
if err := h.service.SetCustomerNoVatStatus(req.CustomerID, req.IsNoVat); err != nil {
return fc.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, err)))
}
return fc.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(fc, response.Message_OK)))
}

View File

@@ -86,9 +86,10 @@ func (s *Server) Setup() error {
// API routes // API routes
s.api = s.app.Group("/api/v1") s.api = s.app.Group("/api/v1")
s.api.Use(middleware.Authenticate())
s.public = s.api.Group("/public") s.public = s.api.Group("/public")
s.restricted = s.api.Group("/restricted") s.restricted = s.api.Group("/restricted")
s.restricted.Use(middleware.AuthMiddleware()) s.restricted.Use(middleware.Authorize())
s.webdav = s.api.Group("/webdav") s.webdav = s.api.Group("/webdav")
s.webdav.Use(middleware.Webdav()) s.webdav.Use(middleware.Webdav())

View File

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

View File

@@ -7,7 +7,6 @@ type Route struct {
Component string `gorm:"type:varchar(255);not null;comment:path to component file" json:"component"` Component string `gorm:"type:varchar(255);not null;comment:path to component file" json:"component"`
Meta *string `gorm:"type:longtext;default:'{}'" json:"meta,omitempty"` Meta *string `gorm:"type:longtext;default:'{}'" json:"meta,omitempty"`
Active *bool `gorm:"type:tinyint;default:1" json:"active,omitempty"` Active *bool `gorm:"type:tinyint;default:1" json:"active,omitempty"`
SortOrder *int `gorm:"type:int;default:0" json:"sort_order,omitempty"`
} }
func (Route) TableName() string { func (Route) TableName() string {

View File

@@ -1,9 +1,13 @@
package cartsRepo package cartsRepo
import ( import (
"errors"
"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"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
"gorm.io/gorm"
) )
type UICartsRepo interface { type UICartsRepo interface {
@@ -15,7 +19,8 @@ type UICartsRepo interface {
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) (bool, 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(cart_id uint, product_id uint, product_attribute_id *uint, amount uint, set_amount bool) error
RemoveProduct(cart_id uint, product_id uint, product_attribute_id *uint) error
} }
type CartsRepo struct{} type CartsRepo struct{}
@@ -129,14 +134,61 @@ func (repo *CartsRepo) CheckProductExists(product_id uint, product_attribute_id
} }
} }
func (repo *CartsRepo) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error { func (repo *CartsRepo) AddProduct(cart_id uint, product_id uint, product_attribute_id *uint, amount uint, set_amount bool) error {
product := model.CartProduct{ var product model.CartProduct
CartID: cart_id,
ProductID: product_id,
ProductAttributeID: product_attribute_id,
Amount: amount,
}
err := db.DB.Create(&product).Error
return err err := db.DB.
Where(&model.CartProduct{
CartID: cart_id,
ProductID: product_id,
ProductAttributeID: product_attribute_id,
}).
First(&product).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
if amount < 1 {
return responseErrors.ErrAmountMustBePositive
} else if amount > constdata.MAX_AMOUNT_OF_PRODUCT_IN_CART {
return responseErrors.ErrAmountMustBeReasonable
}
product = model.CartProduct{
CartID: cart_id,
ProductID: product_id,
ProductAttributeID: product_attribute_id,
Amount: amount,
}
return db.DB.Create(&product).Error
}
// Some other DB error
return err
}
// Product already exists in cart
if set_amount {
product.Amount = amount
} else {
product.Amount = product.Amount + amount
}
if product.Amount < 1 {
return responseErrors.ErrAmountMustBePositive
} else if product.Amount > constdata.MAX_AMOUNT_OF_PRODUCT_IN_CART {
return responseErrors.ErrAmountMustBeReasonable
}
return db.DB.Save(&product).Error
}
func (repo *CartsRepo) RemoveProduct(cart_id uint, product_id uint, product_attribute_id *uint) error {
return db.DB.
Where(&model.CartProduct{
CartID: cart_id,
ProductID: product_id,
ProductAttributeID: product_attribute_id,
}).
Delete(&model.CartProduct{}).Error
} }

View File

@@ -1,6 +1,7 @@
package customerRepo package customerRepo
import ( import (
"fmt"
"strings" "strings"
"git.ma-al.com/goc_daniel/b2b/app/db" "git.ma-al.com/goc_daniel/b2b/app/db"
@@ -16,6 +17,7 @@ type UICustomerRepo interface {
Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error) Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error)
Save(customer *model.Customer) error Save(customer *model.Customer) error
Create(customer *model.Customer) error Create(customer *model.Customer) error
SetCustomerNoVatStatus(customerID uint, isNoVat bool) error
} }
type CustomerRepo struct{} type CustomerRepo struct{}
@@ -80,13 +82,16 @@ func (repo *CustomerRepo) Find(langId uint, p find.Paging, filt *filters.Filters
for _, word := range words { for _, word := range words {
conditions = append(conditions, ` conditions = append(conditions, `
(LOWER(first_name) LIKE ? OR (
id = ? OR
LOWER(first_name) LIKE ? OR
LOWER(last_name) LIKE ? OR LOWER(last_name) LIKE ? OR
LOWER(email) LIKE ?) LOWER(email) LIKE ?)
`) `)
args = append(args, strings.ToLower(word))
for range 3 { for range 3 {
args = append(args, "%"+strings.ToLower(word)+"%") args = append(args, fmt.Sprintf("%%%s%%", strings.ToLower(word)))
} }
} }
@@ -111,87 +116,6 @@ func (repo *CustomerRepo) Create(customer *model.Customer) error {
return db.DB.Create(customer).Error return db.DB.Create(customer).Error
} }
// func (repo *CustomerRepo) Search( func (repo *CustomerRepo) SetCustomerNoVatStatus(customerID uint, isNoVat bool) error {
// customerId uint, return db.DB.Model(&model.Customer{}).Where("id = ?", customerID).Update("is_no_vat", isNoVat).Error
// partnerCode string, }
// p find.Paging,
// filt *filters.FiltersList,
// search string,
// ) (found find.Found[model.UserInList], err error) {
// words := strings.Fields(search)
// if len(words) > 5 {
// words = words[:5]
// }
// query := ctx.DB().
// Model(&model.Customer{}).
// Select("customer.id AS id, customer.first_name as first_name, customer.last_name as last_name, customer.phone_number AS phone_number, customer.email AS email, count(distinct investment_plan_contract.id) as iiplan_purchases, count(distinct `order`.id) as single_purchases, entity.name as entity_name").
// Where("customer.id <> ?", customerId).
// Where("(customer.id IN (SELECT id FROM customer WHERE partner_code IN (WITH RECURSIVE partners AS (SELECT code AS dst FROM partner WHERE code = ? UNION SELECT code FROM partner JOIN partners ON partners.dst = partner.superior_code) SELECT dst FROM partners)) OR customer.recommender_code = ?)", partnerCode, partnerCode).
// Scopes(view.CustomerListQuery())
// var conditions []string
// var args []interface{}
// for _, word := range words {
// conditions = append(conditions, `
// (LOWER(first_name) LIKE ? OR
// LOWER(last_name) LIKE ? OR
// phone_number LIKE ? OR
// LOWER(email) LIKE ?)
// `)
// for i := 0; i < 4; i++ {
// args = append(args, "%"+strings.ToLower(word)+"%")
// }
// }
// finalQuery := strings.Join(conditions, " AND ")
// query = query.Where(finalQuery, args...).
// Scopes(filt.All()...)
// found, err = find.Paginate[V](ctx, p, query)
// return found, errs.Recorded(span, err)
// }
// func (repo *ListRepo) ListUsers(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.UserInList], error) {
// var list []model.UserInList
// var total int64
// query := db.Get().
// Table("b2b_customers AS users").
// Select(`
// users.id AS id,
// users.email AS email,
// users.first_name AS first_name,
// users.last_name AS last_name,
// users.role AS role
// `)
// // 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.UserInList]{}, err
// }
// err = query.
// Order("users.id DESC").
// Limit(p.Limit()).
// Offset(p.Offset()).
// Find(&list).Error
// if err != nil {
// return find.Found[model.UserInList]{}, err
// }
// return find.Found[model.UserInList]{
// Items: list,
// Count: uint(total),
// }, nil
// }

View File

@@ -3,6 +3,7 @@ package localeSelectorRepo
import ( import (
"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"
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
) )
type UILocaleSelectorRepo interface { type UILocaleSelectorRepo interface {
@@ -25,7 +26,9 @@ func (r *LocaleSelectorRepo) GetLanguages() ([]model.Language, error) {
func (r *LocaleSelectorRepo) GetCountriesAndCurrencies() ([]model.Country, error) { func (r *LocaleSelectorRepo) GetCountriesAndCurrencies() ([]model.Country, error) {
var countries []model.Country var countries []model.Country
err := db.Get(). err := db.Get().
Preload("PSCurrency"). Select("*").
Preload("Currency").
Joins("LEFT JOIN " + dbmodel.TableNamePsCountryLang + " AS cl ON cl." + dbmodel.PsCountryLangCols.IDCountry.Col() + " = b2b_countries.ps_id_country AND cl." + dbmodel.PsCountryLangCols.IDLang.Col() + " = 2").
Find(&countries).Error Find(&countries).Error
return countries, err return countries, err
} }

View File

@@ -18,7 +18,7 @@ type UIProductsRepo interface {
// GetJSON(p_id_product, p_id_shop, p_id_lang, p_id_customer, b2b_id_country, p_quantity int) (*json.RawMessage, error) // GetJSON(p_id_product, p_id_shop, p_id_lang, p_id_customer, b2b_id_country, p_quantity int) (*json.RawMessage, error)
Find(id_lang uint, userID uint, p find.Paging, filt *filters.FiltersList) (*find.Found[model.ProductInList], error) Find(id_lang uint, userID uint, p find.Paging, filt *filters.FiltersList) (*find.Found[model.ProductInList], error)
GetProductVariants(langID uint, productID uint, shopID uint, customerID uint, countryID uint, quantity uint) ([]view.ProductAttribute, error) GetProductVariants(langID uint, productID uint, shopID uint, customerID uint, countryID uint, quantity uint) ([]view.ProductAttribute, error)
GetBase(p_id_product, p_id_shop, p_id_lang uint) (view.Product, error) GetBase(p_id_product, p_id_shop, p_id_lang, p_id_customer uint) (view.Product, error)
GetPrice(p_id_product uint, productAttributeID *uint, p_id_shop uint, p_id_customer uint, p_id_country uint, p_quantity uint) (view.Price, error) GetPrice(p_id_product uint, productAttributeID *uint, p_id_shop uint, p_id_customer uint, p_id_country uint, p_quantity uint) (view.Price, error)
GetVariants(p_id_product, p_id_shop, p_id_lang, p_id_customer, p_id_country, p_quantity uint) ([]view.ProductAttribute, error) GetVariants(p_id_product, p_id_shop, p_id_lang, p_id_customer, p_id_country, p_quantity uint) ([]view.ProductAttribute, error)
AddToFavorites(userID uint, productID uint) error AddToFavorites(userID uint, productID uint) error
@@ -33,11 +33,11 @@ func New() UIProductsRepo {
return &ProductsRepo{} return &ProductsRepo{}
} }
func (repo *ProductsRepo) GetBase(p_id_product, p_id_shop, p_id_lang uint) (view.Product, error) { func (repo *ProductsRepo) GetBase(p_id_product, p_id_shop, p_id_lang, p_id_customer uint) (view.Product, error) {
var result view.Product var result view.Product
err := db.DB.Raw(`CALL get_product_base(?,?,?)`, err := db.DB.Raw(`CALL get_product_base(?,?,?,?)`,
p_id_product, p_id_shop, p_id_lang). p_id_product, p_id_shop, p_id_lang, p_id_customer).
Scan(&result).Error Scan(&result).Error
return result, err return result, err

View File

@@ -7,7 +7,7 @@ import (
) )
type UIRoutesRepo interface { type UIRoutesRepo interface {
GetRoutes(langId uint) ([]model.Route, error) GetRoutes(langId uint, roleId uint) ([]model.Route, error)
GetTopMenu(id uint, roleId uint) ([]model.B2BTopMenu, error) GetTopMenu(id uint, roleId uint) ([]model.B2BTopMenu, error)
} }
@@ -17,13 +17,18 @@ func New() UIRoutesRepo {
return &RoutesRepo{} return &RoutesRepo{}
} }
func (p *RoutesRepo) GetRoutes(langId uint) ([]model.Route, error) { func (p *RoutesRepo) GetRoutes(langId uint, roleId uint) ([]model.Route, error) {
routes := []model.Route{} routes := []model.Route{}
err := db.DB.Find(&routes, model.Route{Active: nullable.GetNil(true)}).Error
if err != nil { err := db.
return nil, err Get().
} Model(model.Route{}).
return routes, nil Joins("JOIN b2b_route_roles rr ON rr.route_id = b2b_routes.id").
Where(model.Route{Active: nullable.GetNil(true)}).
Where("rr.role_id = ?", roleId).
Find(&routes).Error
return routes, err
} }
func (p *RoutesRepo) GetTopMenu(langId uint, roleId uint) ([]model.B2BTopMenu, error) { func (p *RoutesRepo) GetTopMenu(langId uint, roleId uint) ([]model.B2BTopMenu, error) {

View File

@@ -74,7 +74,7 @@ func (s *CartsService) RetrieveCart(user_id uint, cart_id uint) (*model.Customer
return s.repo.RetrieveCart(user_id, cart_id) return s.repo.RetrieveCart(user_id, cart_id)
} }
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 int, set_amount bool) error {
exists, 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
@@ -91,5 +91,17 @@ func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, p
return responseErrors.ErrProductOrItsVariationDoesNotExist return responseErrors.ErrProductOrItsVariationDoesNotExist
} }
return s.repo.AddProduct(user_id, cart_id, product_id, product_attribute_id, amount) return s.repo.AddProduct(cart_id, product_id, product_attribute_id, uint(amount), set_amount)
}
func (s *CartsService) RemoveProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint) error {
exists, err := s.repo.UserHasCart(user_id, cart_id)
if err != nil {
return err
}
if !exists {
return responseErrors.ErrUserHasNoSuchCart
}
return s.repo.RemoveProduct(cart_id, product_id, product_attribute_id)
} }

View File

@@ -24,3 +24,7 @@ func (s *CustomerService) GetById(id uint) (*model.Customer, error) {
func (s *CustomerService) Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error) { func (s *CustomerService) Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error) {
return s.repo.Find(langId, p, filt, search) return s.repo.Find(langId, p, filt, search)
} }
func (s *CustomerService) SetCustomerNoVatStatus(customerID uint, isNoVat bool) error {
return s.repo.SetCustomerNoVatStatus(customerID, isNoVat)
}

View File

@@ -102,8 +102,8 @@ func (s *MenuService) createTree(index int, all_categories *([]model.ScannedCate
return node, true return node, true
} }
func (s *MenuService) GetRoutes(id_lang uint) ([]model.Route, error) { func (s *MenuService) GetRoutes(id_lang, roleId uint) ([]model.Route, error) {
return s.routesRepo.GetRoutes(id_lang) return s.routesRepo.GetRoutes(id_lang, roleId)
} }
func (s *MenuService) scannedToNormalCategory(scanned model.ScannedCategory) model.Category { func (s *MenuService) scannedToNormalCategory(scanned model.ScannedCategory) model.Category {

View File

@@ -27,7 +27,7 @@ func (s *ProductService) Get(
p_id_product, p_id_lang, p_id_customer, b2b_id_country, p_quantity uint, p_id_product, p_id_lang, p_id_customer, b2b_id_country, p_quantity uint,
) (*json.RawMessage, error) { ) (*json.RawMessage, error) {
product, err := s.productsRepo.GetBase(p_id_product, constdata.SHOP_ID, p_id_lang) product, err := s.productsRepo.GetBase(p_id_product, constdata.SHOP_ID, p_id_lang, p_id_customer)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -15,6 +15,7 @@ var CATEGORY_BLACKLIST = []uint{250}
const MAX_AMOUNT_OF_CARTS_PER_USER = 10 const MAX_AMOUNT_OF_CARTS_PER_USER = 10
const DEFAULT_NEW_CART_NAME = "new cart" const DEFAULT_NEW_CART_NAME = "new cart"
const MAX_AMOUNT_OF_PRODUCT_IN_CART = 1024
const MAX_AMOUNT_OF_ADDRESSES_PER_USER = 10 const MAX_AMOUNT_OF_ADDRESSES_PER_USER = 10
@@ -32,3 +33,5 @@ const WEBDAV_TRIMMED_ROOT = "localhost:3000/api/v1/webdav/storage"
const NON_ALNUM_REGEX = `[^a-z0-9]+` const NON_ALNUM_REGEX = `[^a-z0-9]+`
const MULTI_DASH_REGEX = `-+` const MULTI_DASH_REGEX = `-+`
const SLUG_REGEX = `^[a-z0-9]+(?:-[a-z0-9]+)*$` const SLUG_REGEX = `^[a-z0-9]+(?:-[a-z0-9]+)*$`
const UNLOGGED_USER_ROLE_ID = 4

View File

@@ -65,6 +65,8 @@ var (
ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached") ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached")
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")
ErrAmountMustBePositive = errors.New("amount must be positive")
ErrAmountMustBeReasonable = errors.New("amount must be reasonable")
// Typed errors for orders handler // Typed errors for orders handler
ErrEmptyCart = errors.New("the cart is empty") ErrEmptyCart = errors.New("the cart is empty")
@@ -205,6 +207,10 @@ func GetErrorCode(c fiber.Ctx, err error) string {
return i18n.T_(c, "error.err_user_has_no_such_cart") return i18n.T_(c, "error.err_user_has_no_such_cart")
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, ErrAmountMustBePositive):
return i18n.T_(c, "error.err_amount_must_be_positive")
case errors.Is(err, ErrAmountMustBeReasonable):
return i18n.T_(c, "error.err_amount_must_be_reasonable")
case errors.Is(err, ErrEmptyCart): case errors.Is(err, ErrEmptyCart):
return i18n.T_(c, "error.err_cart_is_empty") return i18n.T_(c, "error.err_cart_is_empty")
@@ -292,6 +298,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, ErrAmountMustBePositive),
errors.Is(err, ErrAmountMustBeReasonable),
errors.Is(err, ErrEmptyCart), errors.Is(err, ErrEmptyCart),
errors.Is(err, ErrUserHasNoSuchOrder), errors.Is(err, ErrUserHasNoSuchOrder),
errors.Is(err, ErrInvalidReductionType), errors.Is(err, ErrInvalidReductionType),

View File

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

View File

@@ -0,0 +1,15 @@
info:
name: Routes
type: http
seq: 1
http:
method: GET
url: ""
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -0,0 +1,7 @@
info:
name: routes
type: folder
seq: 10
request:
auth: inherit

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&amount=1 url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&amount=1&set_amount=false
params: params:
- name: cart_id - name: cart_id
value: "1" value: "1"
@@ -16,6 +16,9 @@ http:
- name: amount - name: amount
value: "1" value: "1"
type: query type: query
- name: set_amount
value: "false"
type: query
auth: inherit auth: inherit
settings: settings:

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 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
params: params:
- name: cart_id - name: cart_id
value: "1" value: "1"
@@ -19,6 +19,9 @@ http:
- name: amount - name: amount
value: "1" value: "1"
type: query type: query
- name: set_amount
value: "true"
type: query
auth: inherit auth: inherit
settings: settings:

View File

@@ -43,7 +43,6 @@ INSERT IGNORE INTO `b2b_top_menu` (`menu_id`, `label`, `parent_id`, `params`, `a
(9, JSON_COMPACT('{"name":"carts","trans":{"pl":{"label":"Koszyki"},"en":{"label":"Carts"},"de":{"label":"Warenkörbe"}}}'),3,JSON_COMPACT('{"route": {"name": "home", "params":{"locale": ""}}}'),1,1); (9, JSON_COMPACT('{"name":"carts","trans":{"pl":{"label":"Koszyki"},"en":{"label":"Carts"},"de":{"label":"Warenkörbe"}}}'),3,JSON_COMPACT('{"route": {"name": "home", "params":{"locale": ""}}}'),1,1);
-- +goose Down -- +goose Down
DROP TABLE IF EXISTS b2b_routes; DROP TABLE IF EXISTS b2b_routes;

View File

@@ -112,7 +112,8 @@ CREATE TABLE IF NOT EXISTS b2b_customers (
country_id INT NULL DEFAULT 2, country_id INT NULL DEFAULT 2,
created_at DATETIME(6) NULL, created_at DATETIME(6) NULL,
updated_at DATETIME(6) NULL, updated_at DATETIME(6) NULL,
deleted_at DATETIME(6) NULL deleted_at DATETIME(6) NULL,
is_no_vat TINYINT(1) NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE UNIQUE INDEX IF NOT EXISTS idx_customers_email CREATE UNIQUE INDEX IF NOT EXISTS idx_customers_email
@@ -338,6 +339,24 @@ ON b2b_specific_price_customer (b2b_id_customer);
CREATE INDEX idx_bsp_country_rel CREATE INDEX idx_bsp_country_rel
ON b2b_specific_price_country (b2b_id_country); ON b2b_specific_price_country (b2b_id_country);
CREATE TABLE b2b_route_roles (
route_id INT NOT NULL,
role_id BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (route_id, role_id),
INDEX idx_role_id (role_id),
INDEX idx_route_id (route_id),
CONSTRAINT FK_b2b_route_roles_route_id
FOREIGN KEY (route_id)
REFERENCES b2b_routes (id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_b2b_route_roles_role_id
FOREIGN KEY (role_id)
REFERENCES b2b_roles (id)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB;
DELIMITER // DELIMITER //
CREATE FUNCTION IF NOT EXISTS slugify_eu(input TEXT) CREATE FUNCTION IF NOT EXISTS slugify_eu(input TEXT)
@@ -438,6 +457,7 @@ DROP TABLE IF EXISTS b2b_customer_carts;
DROP TABLE IF EXISTS b2b_specific_price_country; DROP TABLE IF EXISTS b2b_specific_price_country;
DROP TABLE IF EXISTS b2b_specific_price_customer; DROP TABLE IF EXISTS b2b_specific_price_customer;
DROP TABLE IF EXISTS b2b_specific_price_product_attribute; DROP TABLE IF EXISTS b2b_specific_price_product_attribute;
DROP TABLE IF EXISTS b2b_route_roles;
DROP TABLE IF EXISTS b2b_specific_price_category; DROP TABLE IF EXISTS b2b_specific_price_category;
DROP TABLE IF EXISTS b2b_specific_price_product; DROP TABLE IF EXISTS b2b_specific_price_product;
DROP TABLE IF EXISTS b2b_specific_price; DROP TABLE IF EXISTS b2b_specific_price;

View File

@@ -10,6 +10,7 @@ VALUES
INSERT INTO `b2b_roles` (`name`, `id`) VALUES ('user','1'); INSERT INTO `b2b_roles` (`name`, `id`) VALUES ('user','1');
INSERT INTO `b2b_roles` (`name`, `id`) VALUES ('admin','2'); INSERT INTO `b2b_roles` (`name`, `id`) VALUES ('admin','2');
INSERT INTO `b2b_roles` (`name`, `id`) VALUES ('super_admin','3'); INSERT INTO `b2b_roles` (`name`, `id`) VALUES ('super_admin','3');
INSERT INTO `b2b_roles` (`name`, `id`) VALUES ('unlogged','4');
-- insert sample admin user admin@ma-al.com/Maal12345678 -- insert sample admin user admin@ma-al.com/Maal12345678
@@ -39,6 +40,9 @@ INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('6', 'webdav.create_token')
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('7', 'product_translation.save'); INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('7', 'product_translation.save');
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('8', 'product_translation.translate'); INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('8', 'product_translation.translate');
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('9', 'search.create_index'); INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('9', 'search.create_index');
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('10', 'orders.view_all');
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('11', 'orders.modify_all');
INSERT INTO `b2b_permissions` (`id`, `name`) VALUES ('12', 'teleport');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '1'); INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '1');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '2'); INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '2');
@@ -49,6 +53,9 @@ INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '6'
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '7'); INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '7');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '8'); INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '8');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '9'); INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '9');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '10');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '11');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('2', '12');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '1'); INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '1');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '2'); INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '2');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '3'); INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '3');
@@ -58,4 +65,36 @@ INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '6'
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '7'); INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '7');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '8'); INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '8');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '9'); INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '9');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '10');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '11');
INSERT INTO `b2b_role_permissions` (`role_id`, `permission_id`) VALUES ('3', '12');
INSERT INTO `b2b_route_roles` (`route_id`, `role_id`) VALUES
(1, '1'),
(1, '2'),
(1, '3'),
(2, '1'),
(2, '2'),
(2, '3'),
(2, '4'),
(3, '1'),
(3, '2'),
(3, '3'),
(3, '4'),
(4, '1'),
(4, '2'),
(4, '3'),
(4, '4'),
(5, '1'),
(5, '2'),
(5, '3'),
(5, '4'),
(6, '1'),
(6, '2'),
(6, '3'),
(6, '4'),
(7, '1'),
(7, '2'),
(7, '3'),
(7, '4');
-- +goose Down -- +goose Down

View File

@@ -16,6 +16,7 @@ READS SQL DATA
BEGIN BEGIN
DECLARE v_tax_rate DECIMAL(10,4) DEFAULT 0; DECLARE v_tax_rate DECIMAL(10,4) DEFAULT 0;
DECLARE v_tax_group INT;
DECLARE v_base_raw DECIMAL(20,6); DECLARE v_base_raw DECIMAL(20,6);
DECLARE v_base DECIMAL(20,6); DECLARE v_base DECIMAL(20,6);
@@ -29,45 +30,54 @@ BEGIN
DECLARE v_has_specific INT DEFAULT 0; DECLARE v_has_specific INT DEFAULT 0;
-- currency
DECLARE v_target_currency BIGINT; DECLARE v_target_currency BIGINT;
DECLARE v_target_rate DECIMAL(13,6) DEFAULT 1; DECLARE v_target_rate DECIMAL(13,6) DEFAULT 1;
DECLARE v_specific_rate DECIMAL(13,6) DEFAULT 1; DECLARE v_specific_rate DECIMAL(13,6) DEFAULT 1;
DECLARE v_is_no_vat TINYINT DEFAULT 0;
SET p_id_product_attribute = NULLIF(p_id_product_attribute, 0); SET p_id_product_attribute = NULLIF(p_id_product_attribute, 0);
-- ================= CUSTOMER VAT =================
SELECT COALESCE(c.is_no_vat, 0)
INTO v_is_no_vat
FROM b2b_customers c
WHERE c.id = p_id_customer
LIMIT 1;
-- ================= TAX GROUP =================
SELECT ps.id_tax_rules_group
INTO v_tax_group
FROM ps_product_shop ps
WHERE ps.id_product = p_id_product
AND ps.id_shop = p_id_shop
LIMIT 1;
-- ================= TAX ================= -- ================= TAX =================
SELECT COALESCE(t.rate, 0) SELECT COALESCE(t.rate, 0)
INTO v_tax_rate INTO v_tax_rate
FROM ps_tax_rule tr FROM ps_tax_rule tr
JOIN ps_tax t ON t.id_tax = tr.id_tax JOIN ps_tax t ON t.id_tax = tr.id_tax
LEFT JOIN b2b_countries c ON c.id = p_id_country LEFT JOIN b2b_countries c ON c.id = p_id_country
WHERE tr.id_tax_rules_group = ( WHERE tr.id_tax_rules_group = v_tax_group
SELECT ps.id_tax_rules_group AND tr.id_country = c.ps_id_country
FROM ps_product_shop ps
WHERE ps.id_product = p_id_product
AND ps.id_shop = p_id_shop
LIMIT 1
)
AND tr.id_country = c.ps_id_country
LIMIT 1; LIMIT 1;
-- ================= TARGET CURRENCY ================= IF v_is_no_vat = 1 THEN
SELECT c.b2b_id_currency SET v_tax_rate = 0;
INTO v_target_currency END IF;
-- ================= CURRENCY =================
SELECT c.b2b_id_currency, r.conversion_rate
INTO v_target_currency, v_target_rate
FROM b2b_countries c FROM b2b_countries c
LEFT JOIN b2b_currency_rates r
ON r.b2b_id_currency = c.b2b_id_currency
WHERE c.id = p_id_country WHERE c.id = p_id_country
LIMIT 1;
-- latest target rate
SELECT r.conversion_rate
INTO v_target_rate
FROM b2b_currency_rates r
WHERE r.b2b_id_currency = v_target_currency
ORDER BY r.created_at DESC ORDER BY r.created_at DESC
LIMIT 1; LIMIT 1;
-- ================= BASE PRICE (RAW) ================= -- ================= BASE PRICE =================
SELECT SELECT
COALESCE(ps.price, p.price) + COALESCE(pas.price, 0) COALESCE(ps.price, p.price) + COALESCE(pas.price, 0)
INTO v_base_raw INTO v_base_raw
@@ -79,8 +89,8 @@ BEGIN
AND pas.id_shop = p_id_shop AND pas.id_shop = p_id_shop
WHERE p.id_product = p_id_product; WHERE p.id_product = p_id_product;
-- convert base to target currency
SET v_base = v_base_raw * v_target_rate; SET v_base = v_base_raw * v_target_rate;
SET v_excl = v_base;
-- ================= RULE SELECTION ================= -- ================= RULE SELECTION =================
SELECT SELECT
@@ -99,71 +109,67 @@ BEGIN
FROM b2b_specific_price bsp FROM b2b_specific_price bsp
LEFT JOIN b2b_specific_price_product spp
ON spp.b2b_specific_price_id = bsp.id
AND spp.id_product = p_id_product
LEFT JOIN b2b_specific_price_product_attribute spa
ON spa.b2b_specific_price_id = bsp.id
AND spa.id_product_attribute = p_id_product_attribute
LEFT JOIN b2b_specific_price_customer spc
ON spc.b2b_specific_price_id = bsp.id
AND spc.b2b_id_customer = p_id_customer
LEFT JOIN b2b_specific_price_country spco
ON spco.b2b_specific_price_id = bsp.id
AND spco.b2b_id_country = p_id_country
LEFT JOIN b2b_specific_price_category spcat
ON spcat.b2b_specific_price_id = bsp.id
LEFT JOIN ps_category_product cp
ON cp.id_category = spcat.id_category
AND cp.id_product = p_id_product
WHERE bsp.is_active = 1 WHERE bsp.is_active = 1
AND bsp.from_quantity <= p_quantity AND bsp.from_quantity <= p_quantity
-- intersection rules (unchanged) AND (spp.id_product IS NOT NULL OR NOT EXISTS (
AND ( SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id
NOT EXISTS (SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id) ))
OR EXISTS (SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product = p_id_product)
)
AND ( AND (spa.id_product_attribute IS NOT NULL OR NOT EXISTS (
NOT EXISTS (SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id) SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id
OR EXISTS (SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product_attribute = p_id_product_attribute) ))
)
AND ( AND (spc.b2b_id_customer IS NOT NULL OR NOT EXISTS (
NOT EXISTS (SELECT 1 FROM b2b_specific_price_category x WHERE x.b2b_specific_price_id = bsp.id) SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id
OR EXISTS ( ))
SELECT 1 FROM b2b_specific_price_category x
JOIN ps_category_product cp ON cp.id_category = x.id_category
WHERE x.b2b_specific_price_id = bsp.id AND cp.id_product = p_id_product
)
)
AND ( AND (spco.b2b_id_country IS NOT NULL OR NOT EXISTS (
NOT EXISTS (SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id) SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id
OR EXISTS (SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_customer = p_id_customer) ))
)
AND ( AND (cp.id_product IS NOT NULL OR NOT EXISTS (
NOT EXISTS (SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id) SELECT 1 FROM b2b_specific_price_category x WHERE x.b2b_specific_price_id = bsp.id
OR EXISTS (SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_country = p_id_country) ))
)
ORDER BY ORDER BY
-- customer wins (spc.b2b_id_customer IS NOT NULL) DESC,
(EXISTS (SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_customer = p_id_customer)) DESC, (spa.id_product_attribute IS NOT NULL) DESC,
(spp.id_product IS NOT NULL) DESC,
-- attribute (cp.id_product IS NOT NULL) DESC,
(EXISTS (SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product_attribute = p_id_product_attribute)) DESC, (spco.b2b_id_country IS NOT NULL) DESC,
-- product
(EXISTS (SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product = p_id_product)) DESC,
-- category
(EXISTS (
SELECT 1 FROM b2b_specific_price_category x
JOIN ps_category_product cp ON cp.id_category = x.id_category
WHERE x.b2b_specific_price_id = bsp.id AND cp.id_product = p_id_product
)) DESC,
-- country
(EXISTS (SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_country = p_id_country)) DESC,
bsp.id DESC bsp.id DESC
LIMIT 1; LIMIT 1;
-- ================= APPLY ================= -- ================= APPLY =================
SET v_excl = v_base;
IF v_has_specific = 1 THEN IF v_has_specific = 1 THEN
IF v_reduction_type = 'amount' THEN IF v_reduction_type = 'amount' THEN
-- convert specific price currency if needed
IF v_specific_currency_id IS NOT NULL AND v_specific_currency_id != v_target_currency THEN IF v_specific_currency_id IS NOT NULL AND v_specific_currency_id != v_target_currency THEN
SELECT r.conversion_rate SELECT r.conversion_rate
@@ -173,7 +179,6 @@ BEGIN
ORDER BY r.created_at DESC ORDER BY r.created_at DESC
LIMIT 1; LIMIT 1;
-- normalize → then convert to target
SET v_excl = (v_fixed_price / v_specific_rate) * v_target_rate; SET v_excl = (v_fixed_price / v_specific_rate) * v_target_rate;
ELSE ELSE
@@ -319,7 +324,8 @@ DROP PROCEDURE IF EXISTS get_product_base //
CREATE PROCEDURE get_product_base( CREATE PROCEDURE get_product_base(
IN p_id_product INT, IN p_id_product INT,
IN p_id_shop INT, IN p_id_shop INT,
IN p_id_lang INT IN p_id_lang INT,
IN p_id_customer INT
) )
BEGIN BEGIN
SELECT SELECT
@@ -376,14 +382,12 @@ BEGIN
-- Relations -- Relations
m.name AS manufacturer, m.name AS manufacturer,
cl.name AS category cl.name AS category,
-- This doesn't fit to base product, I'll add proper is_favorite to product later EXISTS(
SELECT 1 FROM b2b_favorites f
-- EXISTS( WHERE f.user_id = p_id_customer AND f.product_id = p_id_product
-- SELECT 1 FROM b2b_favorites f ) AS is_favorite
-- WHERE f.user_id = p_id_customer AND f.product_id = p_id_product
-- ) AS is_favorite