product-procedures #52
@@ -63,7 +63,7 @@ func AuthMiddleware() fiber.Handler {
|
|||||||
// Set user in context
|
// Set user in context
|
||||||
c.Locals(constdata.USER_LOCALES_NAME, user.ToSession())
|
c.Locals(constdata.USER_LOCALES_NAME, user.ToSession())
|
||||||
c.Locals(constdata.USER_LOCALES_ID, user.ID)
|
c.Locals(constdata.USER_LOCALES_ID, user.ID)
|
||||||
|
c.Locals(constdata.LANG_LOCALES_ID, user.LangID)
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ func RequireAdmin() fiber.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if userSession.Role != model.RoleAdmin {
|
if model.CustomerRole(userSession.RoleName) != model.RoleAdmin {
|
||||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||||
"error": "admin access required",
|
"error": "admin access required",
|
||||||
})
|
})
|
||||||
|
|||||||
28
app/delivery/middleware/permissions.go
Normal file
28
app/delivery/middleware/permissions.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Require(p perms.Permission) fiber.Handler {
|
||||||
|
return func(c fiber.Ctx) error {
|
||||||
|
u := c.Locals("user")
|
||||||
|
if u == nil {
|
||||||
|
return c.SendStatus(fiber.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, ok := u.(*model.UserSession)
|
||||||
|
if !ok {
|
||||||
|
return c.SendStatus(fiber.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, perm := range user.Permissions {
|
||||||
|
if perm == p {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.SendStatus(fiber.StatusForbidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
11
app/delivery/middleware/perms/permissions.go
Normal file
11
app/delivery/middleware/perms/permissions.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package perms
|
||||||
|
|
||||||
|
type Permission string
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserRead Permission = "user.read"
|
||||||
|
UserWrite Permission = "user.write"
|
||||||
|
UserReadAny Permission = "user.read.any"
|
||||||
|
UserWriteAny Permission = "user.write.any"
|
||||||
|
UserDeleteAny Permission = "user.delete.any"
|
||||||
|
)
|
||||||
@@ -360,7 +360,7 @@ func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
|
|||||||
user := model.Customer{
|
user := model.Customer{
|
||||||
ID: userLocals.UserID,
|
ID: userLocals.UserID,
|
||||||
Email: userLocals.Email,
|
Email: userLocals.Email,
|
||||||
Role: userLocals.Role,
|
Role: model.Role{ID: userLocals.RoleID, Name: userLocals.RoleName},
|
||||||
LangID: userLocals.LangID,
|
LangID: userLocals.LangID,
|
||||||
CountryID: userLocals.CountryID,
|
CountryID: userLocals.CountryID,
|
||||||
IsActive: userLocals.IsActive,
|
IsActive: userLocals.IsActive,
|
||||||
|
|||||||
70
app/delivery/web/api/restricted/customer.go
Normal file
70
app/delivery/web/api/restricted/customer.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package restricted
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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/service/customerService"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
||||||
|
"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 customerHandler struct {
|
||||||
|
service *customerService.CustomerService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCustomerHandler() *customerHandler {
|
||||||
|
customerService := customerService.New()
|
||||||
|
return &customerHandler{
|
||||||
|
service: customerService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CustomerHandlerRoutes(r fiber.Router) fiber.Router {
|
||||||
|
handler := NewCustomerHandler()
|
||||||
|
|
||||||
|
r.Get("", handler.customerData)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *customerHandler) customerData(fc fiber.Ctx) error {
|
||||||
|
var customerId uint
|
||||||
|
customerIdStr := fc.Query("id")
|
||||||
|
if customerIdStr != "" {
|
||||||
|
user, ok := fc.Locals("user").(*model.UserSession)
|
||||||
|
if !ok {
|
||||||
|
return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
id, err := strconv.ParseUint(customerIdStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.ErrBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.UserID != uint(id) && !user.HasPermission(perms.UserReadAny) {
|
||||||
|
return fc.Status(fiber.StatusForbidden).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrForbidden)))
|
||||||
|
}
|
||||||
|
|
||||||
|
customerId = uint(id)
|
||||||
|
} else {
|
||||||
|
id, ok := fc.Locals("userID").(uint)
|
||||||
|
if !ok {
|
||||||
|
return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
customerId = id
|
||||||
|
}
|
||||||
|
|
||||||
|
customer, err := h.service.GetById(customerId)
|
||||||
|
if err != nil {
|
||||||
|
return fc.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fc.JSON(response.Make(&customer, 0, i18n.T_(fc, response.Message_OK)))
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package restricted
|
package restricted
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/menuService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/menuService"
|
||||||
"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/nullable"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
||||||
@@ -45,12 +46,12 @@ func (h *MenuHandler) GetMenu(c fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
|
func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
|
||||||
lang_id, ok := c.Locals("langID").(uint)
|
session, ok := c.Locals("user").(*model.UserSession)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
}
|
}
|
||||||
menu, err := h.menuService.GetTopMenu(lang_id)
|
menu, err := h.menuService.GetTopMenu(session.LangID, session.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)))
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package restricted
|
package restricted
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"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/service/productService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/productService"
|
||||||
|
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/nullable"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||||
@@ -30,7 +32,7 @@ func ProductsHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
handler := NewProductsHandler()
|
handler := NewProductsHandler()
|
||||||
|
|
||||||
//TODO: WIP doesn't work yet
|
//TODO: WIP doesn't work yet
|
||||||
r.Get("/product/:id/:country_id/:quantity", handler.GetProductJson)
|
r.Get("/:id/:country_id/:quantity", handler.GetProductJson)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@@ -60,19 +62,18 @@ func (h *ProductsHandler) GetProductJson(c fiber.Ctx) error {
|
|||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
id_lang, ok := c.Locals("lang_id").(int)
|
p_id_customer, ok := c.Locals(constdata.USER_LOCALES_ID).(uint)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
}
|
}
|
||||||
|
fmt.Printf("p_id_customer: %v\n", p_id_customer)
|
||||||
p_id_customer, ok := c.Locals("user_id").(int)
|
id_lang, ok := c.Locals(constdata.LANG_LOCALES_ID).(uint)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
}
|
}
|
||||||
|
productJson, err := h.productService.GetJSON(p_id_product, int(id_lang), int(p_id_customer), b2b_id_country, p_quantity)
|
||||||
productJson, err := h.productService.GetJSON(p_id_product, id_lang, p_id_customer, b2b_id_country, p_quantity)
|
|
||||||
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)))
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ func (s *Server) Setup() error {
|
|||||||
menuRouting := s.public.Group("/menu")
|
menuRouting := s.public.Group("/menu")
|
||||||
public.RoutingHandlerRoutes(menuRouting)
|
public.RoutingHandlerRoutes(menuRouting)
|
||||||
|
|
||||||
|
pCustomer := s.restricted.Group("/customer")
|
||||||
|
restricted.CustomerHandlerRoutes(pCustomer)
|
||||||
|
|
||||||
// product translation routes (restricted)
|
// product translation routes (restricted)
|
||||||
productTranslation := s.restricted.Group("/product-translation")
|
productTranslation := s.restricted.Group("/product-translation")
|
||||||
restricted.ProductTranslationHandlerRoutes(productTranslation)
|
restricted.ProductTranslationHandlerRoutes(productTranslation)
|
||||||
@@ -98,7 +101,8 @@ func (s *Server) Setup() error {
|
|||||||
list := s.restricted.Group("/list")
|
list := s.restricted.Group("/list")
|
||||||
restricted.ListHandlerRoutes(list)
|
restricted.ListHandlerRoutes(list)
|
||||||
|
|
||||||
restricted.ProductsHandlerRoutes(s.restricted)
|
product := s.restricted.Group("/product")
|
||||||
|
restricted.ProductsHandlerRoutes(product)
|
||||||
|
|
||||||
// locale selector (restricted)
|
// locale selector (restricted)
|
||||||
// this is basically for changing user's selected language and country
|
// this is basically for changing user's selected language and country
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package model
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,7 +14,8 @@ type Customer struct {
|
|||||||
Password string `gorm:"size:255" json:"-"` // Hashed password, not exposed in JSON
|
Password string `gorm:"size:255" json:"-"` // Hashed password, not exposed in JSON
|
||||||
FirstName string `gorm:"size:100" json:"first_name"`
|
FirstName string `gorm:"size:100" json:"first_name"`
|
||||||
LastName string `gorm:"size:100" json:"last_name"`
|
LastName string `gorm:"size:100" json:"last_name"`
|
||||||
Role CustomerRole `gorm:"type:varchar(20);default:'user'" json:"role"`
|
RoleID uint `gorm:"column:role_id;not null;default:1" json:"-"`
|
||||||
|
Role Role `gorm:"foreignKey:RoleID" json:"role"`
|
||||||
Provider AuthProvider `gorm:"type:varchar(20);default:'local'" json:"provider"`
|
Provider AuthProvider `gorm:"type:varchar(20);default:'local'" json:"provider"`
|
||||||
ProviderID string `gorm:"size:255" json:"provider_id,omitempty"` // ID from OAuth provider
|
ProviderID string `gorm:"size:255" json:"provider_id,omitempty"` // ID from OAuth provider
|
||||||
AvatarURL string `gorm:"size:500" json:"avatar_url,omitempty"`
|
AvatarURL string `gorm:"size:500" json:"avatar_url,omitempty"`
|
||||||
@@ -32,14 +34,6 @@ type Customer struct {
|
|||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomerRole represents the role of a user
|
|
||||||
type CustomerRole string
|
|
||||||
|
|
||||||
const (
|
|
||||||
RoleUser CustomerRole = "user"
|
|
||||||
RoleAdmin CustomerRole = "admin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AuthProvider represents the authentication provider
|
// AuthProvider represents the authentication provider
|
||||||
type AuthProvider string
|
type AuthProvider string
|
||||||
|
|
||||||
@@ -53,16 +47,6 @@ func (Customer) TableName() string {
|
|||||||
return "b2b_customers"
|
return "b2b_customers"
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAdmin checks if the user has admin role
|
|
||||||
func (u *Customer) IsAdmin() bool {
|
|
||||||
return u.Role == RoleAdmin
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanManageUsers checks if the user can manage other users
|
|
||||||
func (u *Customer) CanManageUsers() bool {
|
|
||||||
return u.Role == RoleAdmin
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullName returns the user's full name
|
// FullName returns the user's full name
|
||||||
func (u *Customer) FullName() string {
|
func (u *Customer) FullName() string {
|
||||||
if u.FirstName == "" && u.LastName == "" {
|
if u.FirstName == "" && u.LastName == "" {
|
||||||
@@ -73,27 +57,51 @@ func (u *Customer) FullName() string {
|
|||||||
|
|
||||||
// UserSession represents a user session for JWT claims
|
// UserSession represents a user session for JWT claims
|
||||||
type UserSession struct {
|
type UserSession struct {
|
||||||
UserID uint `json:"user_id"`
|
UserID uint `json:"user_id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Role CustomerRole `json:"role"`
|
RoleID uint `json:"role_id"`
|
||||||
LangID uint `json:"lang_id"`
|
RoleName string `json:"role_name"`
|
||||||
CountryID uint `json:"country_id"`
|
LangID uint `json:"lang_id"`
|
||||||
IsActive bool `json:"is_active"`
|
CountryID uint `json:"country_id"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
Permissions []perms.Permission `json:"permissions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us *UserSession) HasPermission(permission perms.Permission) bool {
|
||||||
|
for _, p := range us.Permissions {
|
||||||
|
if p == permission {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToSession converts User to UserSession
|
// ToSession converts User to UserSession
|
||||||
func (u *Customer) ToSession() *UserSession {
|
func (u *Customer) ToSession() *UserSession {
|
||||||
|
|
||||||
return &UserSession{
|
return &UserSession{
|
||||||
UserID: u.ID,
|
UserID: u.ID,
|
||||||
Email: u.Email,
|
Email: u.Email,
|
||||||
Role: u.Role,
|
RoleID: u.Role.ID,
|
||||||
LangID: u.LangID,
|
RoleName: u.Role.Name,
|
||||||
CountryID: u.CountryID,
|
Permissions: BuildPermissionSlice(u),
|
||||||
IsActive: u.IsActive,
|
LangID: u.LangID,
|
||||||
|
CountryID: u.CountryID,
|
||||||
|
IsActive: u.IsActive,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BuildPermissionSlice(user *Customer) []perms.Permission {
|
||||||
|
var perms []perms.Permission
|
||||||
|
|
||||||
|
for _, p := range user.Role.Permissions {
|
||||||
|
perms = append(perms, p.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return perms
|
||||||
|
}
|
||||||
|
|
||||||
// LoginRequest represents the login form data
|
// LoginRequest represents the login form data
|
||||||
type LoginRequest struct {
|
type LoginRequest struct {
|
||||||
Email string `json:"email" form:"email"`
|
Email string `json:"email" form:"email"`
|
||||||
|
|||||||
12
app/model/permission.go
Normal file
12
app/model/permission.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
|
||||||
|
|
||||||
|
type Permission struct {
|
||||||
|
ID uint
|
||||||
|
Name perms.Permission
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Permission) TableName() string {
|
||||||
|
return "b2b_permissions"
|
||||||
|
}
|
||||||
19
app/model/role.go
Normal file
19
app/model/role.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type Role struct {
|
||||||
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
|
Name string `gorm:"size:64" json:"name"`
|
||||||
|
Permissions []Permission `gorm:"many2many:b2b_role_permissions;" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Role) TableName() string {
|
||||||
|
return "b2b_roles"
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomerRole string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoleUser CustomerRole = "user"
|
||||||
|
RoleAdmin CustomerRole = "admin"
|
||||||
|
RoleSuperAdmin CustomerRole = "super_admin"
|
||||||
|
)
|
||||||
@@ -19,7 +19,7 @@ func New() UICurrencyRepo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (repo *CurrencyRepo) CreateConversionRate(currencyRate *model.CurrencyRate) error {
|
func (repo *CurrencyRepo) CreateConversionRate(currencyRate *model.CurrencyRate) error {
|
||||||
return db.DB.Debug().Create(currencyRate).Error
|
return db.DB.Create(currencyRate).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *CurrencyRepo) Get(id uint) (*model.Currency, error) {
|
func (repo *CurrencyRepo) Get(id uint) (*model.Currency, error) {
|
||||||
|
|||||||
27
app/repos/customerRepo/customerRepo.go
Normal file
27
app/repos/customerRepo/customerRepo.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package customerRepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UICustomerRepo interface {
|
||||||
|
Get(id uint) (*model.Customer, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomerRepo struct{}
|
||||||
|
|
||||||
|
func New() UICustomerRepo {
|
||||||
|
return &CustomerRepo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *CustomerRepo) Get(id uint) (*model.Customer, error) {
|
||||||
|
var customer model.Customer
|
||||||
|
|
||||||
|
err := db.DB.
|
||||||
|
Preload("Role").
|
||||||
|
First(&customer, id).
|
||||||
|
Error
|
||||||
|
|
||||||
|
return &customer, err
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
type UIRoutesRepo interface {
|
type UIRoutesRepo interface {
|
||||||
GetRoutes(langId uint) ([]model.Route, error)
|
GetRoutes(langId uint) ([]model.Route, error)
|
||||||
GetTopMenu(id uint) ([]model.B2BTopMenu, error)
|
GetTopMenu(id uint, roleId uint) ([]model.B2BTopMenu, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoutesRepo struct{}
|
type RoutesRepo struct{}
|
||||||
@@ -26,12 +26,16 @@ func (p *RoutesRepo) GetRoutes(langId uint) ([]model.Route, error) {
|
|||||||
return routes, nil
|
return routes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RoutesRepo) GetTopMenu(id uint) ([]model.B2BTopMenu, error) {
|
func (p *RoutesRepo) GetTopMenu(langId uint, roleId uint) ([]model.B2BTopMenu, error) {
|
||||||
var menus []model.B2BTopMenu
|
var menus []model.B2BTopMenu
|
||||||
|
|
||||||
err := db.Get().
|
err := db.
|
||||||
Where("active = ?", 1).
|
Get().
|
||||||
Order("parent_id ASC, position ASC").
|
Model(model.B2BTopMenu{}).
|
||||||
|
Joins("JOIN b2b_top_menu_roles tmr ON tmr.top_menu_id = b2b_top_menu.menu_id").
|
||||||
|
Where(model.B2BTopMenu{Active: 1}).
|
||||||
|
Where("tmr.role_id = ?", roleId).
|
||||||
|
Order("b2b_top_menu.parent_id ASC, b2b_top_menu.position ASC").
|
||||||
Find(&menus).Error
|
Find(&menus).Error
|
||||||
|
|
||||||
return menus, err
|
return menus, err
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ import (
|
|||||||
|
|
||||||
// JWTClaims represents the JWT claims
|
// JWTClaims represents the JWT claims
|
||||||
type JWTClaims struct {
|
type JWTClaims struct {
|
||||||
UserID uint `json:"user_id"`
|
UserID uint `json:"user_id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Role model.CustomerRole `json:"customer_role"`
|
Role string `json:"customer_role"`
|
||||||
CartsIDs []uint `json:"carts_ids"`
|
CartsIDs []uint `json:"carts_ids"`
|
||||||
LangID uint `json:"lang_id"`
|
LangID uint `json:"lang_id"`
|
||||||
CountryID uint `json:"country_id"`
|
CountryID uint `json:"country_id"`
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ func (s *AuthService) Login(req *model.LoginRequest) (*model.AuthResponse, strin
|
|||||||
var user model.Customer
|
var user model.Customer
|
||||||
|
|
||||||
// Find user by email
|
// Find user by email
|
||||||
if err := s.db.Where("email = ?", req.Email).First(&user).Error; err != nil {
|
if err := s.db.Preload("Role.Permissions").Where("email = ?", req.Email).First(&user).Error; err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, "", responseErrors.ErrInvalidCredentials
|
return nil, "", responseErrors.ErrInvalidCredentials
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,7 @@ func (s *AuthService) Register(req *model.RegisterRequest) error {
|
|||||||
Password: string(hashedPassword),
|
Password: string(hashedPassword),
|
||||||
FirstName: req.FirstName,
|
FirstName: req.FirstName,
|
||||||
LastName: req.LastName,
|
LastName: req.LastName,
|
||||||
Role: model.RoleUser,
|
Role: model.Role{},
|
||||||
Provider: model.ProviderLocal,
|
Provider: model.ProviderLocal,
|
||||||
IsActive: false,
|
IsActive: false,
|
||||||
EmailVerified: false,
|
EmailVerified: false,
|
||||||
@@ -422,7 +422,7 @@ func (s *AuthService) RevokeAllRefreshTokens(userID uint) {
|
|||||||
// GetUserByID retrieves a user by ID
|
// GetUserByID retrieves a user by ID
|
||||||
func (s *AuthService) GetUserByID(userID uint) (*model.Customer, error) {
|
func (s *AuthService) GetUserByID(userID uint) (*model.Customer, error) {
|
||||||
var user model.Customer
|
var user model.Customer
|
||||||
if err := s.db.First(&user, userID).Error; err != nil {
|
if err := s.db.Preload("Role.Permissions").First(&user, userID).Error; err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, responseErrors.ErrUserNotFound
|
return nil, responseErrors.ErrUserNotFound
|
||||||
}
|
}
|
||||||
@@ -489,7 +489,7 @@ func (s *AuthService) generateAccessToken(user *model.Customer) (string, error)
|
|||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Username: user.Email,
|
Username: user.Email,
|
||||||
Role: user.Role,
|
Role: user.Role.Name,
|
||||||
CartsIDs: []uint{},
|
CartsIDs: []uint{},
|
||||||
LangID: user.LangID,
|
LangID: user.LangID,
|
||||||
CountryID: user.CountryID,
|
CountryID: user.CountryID,
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ func (s *AuthService) findOrCreateGoogleUser(info *view.GoogleUserInfo) (*model.
|
|||||||
Provider: model.ProviderGoogle,
|
Provider: model.ProviderGoogle,
|
||||||
ProviderID: info.ID,
|
ProviderID: info.ID,
|
||||||
AvatarURL: info.Picture,
|
AvatarURL: info.Picture,
|
||||||
Role: model.RoleUser,
|
Role: model.Role{},
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
EmailVerified: true,
|
EmailVerified: true,
|
||||||
LangID: 2, // default is english
|
LangID: 2, // default is english
|
||||||
|
|||||||
20
app/service/customerService/customerService.go
Normal file
20
app/service/customerService/customerService.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package customerService
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/repos/customerRepo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomerService struct {
|
||||||
|
repo customerRepo.UICustomerRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *CustomerService {
|
||||||
|
return &CustomerService{
|
||||||
|
repo: customerRepo.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CustomerService) GetById(id uint) (*model.Customer, error) {
|
||||||
|
return s.repo.Get(id)
|
||||||
|
}
|
||||||
@@ -98,8 +98,8 @@ func (a ByPosition) Len() int { return len(a) }
|
|||||||
func (a ByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
func (a ByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
func (a ByPosition) Less(i, j int) bool { return a[i].Position < a[j].Position }
|
func (a ByPosition) Less(i, j int) bool { return a[i].Position < a[j].Position }
|
||||||
|
|
||||||
func (s *MenuService) GetTopMenu(id uint) ([]*model.B2BTopMenu, error) {
|
func (s *MenuService) GetTopMenu(languageId uint, roleId uint) ([]*model.B2BTopMenu, error) {
|
||||||
items, err := s.routesRepo.GetTopMenu(id)
|
items, err := s.routesRepo.GetTopMenu(languageId, roleId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ const DEFAULT_NEW_CART_NAME = "new cart"
|
|||||||
|
|
||||||
const USER_LOCALES_NAME = "user"
|
const USER_LOCALES_NAME = "user"
|
||||||
const USER_LOCALES_ID = "userID"
|
const USER_LOCALES_ID = "userID"
|
||||||
|
const LANG_LOCALES_ID = "langID"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// Typed errors for request validation and authentication
|
// Typed errors for request validation and authentication
|
||||||
|
ErrForbidden = errors.New("forbidden")
|
||||||
ErrInvalidBody = errors.New("invalid request body")
|
ErrInvalidBody = errors.New("invalid request body")
|
||||||
ErrNotAuthenticated = errors.New("not authenticated")
|
ErrNotAuthenticated = errors.New("not authenticated")
|
||||||
ErrUserNotFound = errors.New("user not found")
|
ErrUserNotFound = errors.New("user not found")
|
||||||
@@ -83,6 +84,8 @@ func NewError(err error, status int) *Error {
|
|||||||
// GetErrorCode returns the error code string for HTTP response mapping
|
// GetErrorCode returns the error code string for HTTP response mapping
|
||||||
func GetErrorCode(c fiber.Ctx, err error) string {
|
func GetErrorCode(c fiber.Ctx, err error) string {
|
||||||
switch {
|
switch {
|
||||||
|
case errors.Is(err, ErrForbidden):
|
||||||
|
return i18n.T_(c, "error.err_forbidden")
|
||||||
case errors.Is(err, ErrInvalidBody):
|
case errors.Is(err, ErrInvalidBody):
|
||||||
return i18n.T_(c, "error.err_invalid_body")
|
return i18n.T_(c, "error.err_invalid_body")
|
||||||
case errors.Is(err, ErrInvalidCredentials):
|
case errors.Is(err, ErrInvalidCredentials):
|
||||||
@@ -167,6 +170,8 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
|||||||
// GetErrorStatus returns the HTTP status code for the given error
|
// GetErrorStatus returns the HTTP status code for the given error
|
||||||
func GetErrorStatus(err error) int {
|
func GetErrorStatus(err error) int {
|
||||||
switch {
|
switch {
|
||||||
|
case errors.Is(err, ErrForbidden):
|
||||||
|
return fiber.StatusForbidden
|
||||||
case errors.Is(err, ErrInvalidCredentials),
|
case errors.Is(err, ErrInvalidCredentials),
|
||||||
errors.Is(err, ErrNotAuthenticated),
|
errors.Is(err, ErrNotAuthenticated),
|
||||||
errors.Is(err, ErrInvalidToken),
|
errors.Is(err, ErrInvalidToken),
|
||||||
|
|||||||
@@ -43,7 +43,42 @@ CREATE TABLE IF NOT EXISTS b2b_translations (
|
|||||||
CONSTRAINT fk_translations_component FOREIGN KEY (component_id) REFERENCES b2b_components(id) ON DELETE RESTRICT ON UPDATE RESTRICT
|
CONSTRAINT fk_translations_component FOREIGN KEY (component_id) REFERENCES b2b_components(id) ON DELETE RESTRICT ON UPDATE RESTRICT
|
||||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE `b2b_roles` (
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`name` VARCHAR(63) NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX `IX_b2b_roles_id`
|
||||||
|
ON `b2b_roles` (
|
||||||
|
`id` ASC
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE b2b_permissions (
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`name` VARCHAR(255) NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE b2b_role_permissions (
|
||||||
|
role_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
permission_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (role_id, permission_id),
|
||||||
|
|
||||||
|
CONSTRAINT fk_role_permissions_role
|
||||||
|
FOREIGN KEY (role_id)
|
||||||
|
REFERENCES b2b_roles(id)
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
|
||||||
|
CONSTRAINT fk_role_permissions_permission
|
||||||
|
FOREIGN KEY (permission_id)
|
||||||
|
REFERENCES b2b_permissions(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE `b2b_top_menu_roles` (
|
||||||
|
`top_menu_id` BIGINT UNSIGNED NOT NULL,
|
||||||
|
`role_id` BIGINT UNSIGNED NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
-- customers
|
-- customers
|
||||||
CREATE TABLE IF NOT EXISTS b2b_customers (
|
CREATE TABLE IF NOT EXISTS b2b_customers (
|
||||||
@@ -52,7 +87,7 @@ CREATE TABLE IF NOT EXISTS b2b_customers (
|
|||||||
password VARCHAR(255) NULL,
|
password VARCHAR(255) NULL,
|
||||||
first_name VARCHAR(100) NULL,
|
first_name VARCHAR(100) NULL,
|
||||||
last_name VARCHAR(100) NULL,
|
last_name VARCHAR(100) NULL,
|
||||||
role VARCHAR(20) NULL DEFAULT 'user',
|
role_id BIGINT UNSIGNED NOT NULL DEFAULT 1,
|
||||||
provider VARCHAR(20) NULL DEFAULT 'local',
|
provider VARCHAR(20) NULL DEFAULT 'local',
|
||||||
provider_id VARCHAR(255) NULL,
|
provider_id VARCHAR(255) NULL,
|
||||||
avatar_url VARCHAR(500) NULL,
|
avatar_url VARCHAR(500) NULL,
|
||||||
@@ -77,6 +112,9 @@ ON b2b_customers (email);
|
|||||||
CREATE INDEX IF NOT EXISTS idx_customers_deleted_at
|
CREATE INDEX IF NOT EXISTS idx_customers_deleted_at
|
||||||
ON b2b_customers (deleted_at);
|
ON b2b_customers (deleted_at);
|
||||||
|
|
||||||
|
ALTER TABLE b2b_customers
|
||||||
|
ADD CONSTRAINT fk_customer_role
|
||||||
|
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 (
|
||||||
@@ -175,16 +213,7 @@ CREATE TABLE b2b_specific_price (
|
|||||||
b2b_id_currency BIGINT UNSIGNED NULL, -- specifies which currency is used for the price
|
b2b_id_currency BIGINT UNSIGNED NULL, -- specifies which currency is used for the price
|
||||||
percentage_reduction DECIMAL(5, 2) NULL,
|
percentage_reduction DECIMAL(5, 2) NULL,
|
||||||
from_quantity INT UNSIGNED DEFAULT 1,
|
from_quantity INT UNSIGNED DEFAULT 1,
|
||||||
is_active BOOLEAN DEFAULT TRUE,
|
is_active BOOLEAN DEFAULT TRUE
|
||||||
CONSTRAINT fk_b2b_specific_price_country FOREIGN KEY (b2b_id_country) REFERENCES b2b_countries(id) ON DELETE
|
|
||||||
SET
|
|
||||||
NULL ON UPDATE CASCADE,
|
|
||||||
CONSTRAINT fk_b2b_specific_price_customer FOREIGN KEY (b2b_id_customer) REFERENCES b2b_customers(id) ON DELETE
|
|
||||||
SET
|
|
||||||
NULL ON UPDATE CASCADE,
|
|
||||||
CONSTRAINT fk_b2b_specific_price_currency FOREIGN KEY (b2b_id_currency) REFERENCES b2b_currencies(id) ON DELETE
|
|
||||||
SET
|
|
||||||
NULL ON UPDATE CASCADE
|
|
||||||
) ENGINE = InnoDB;
|
) ENGINE = InnoDB;
|
||||||
CREATE INDEX idx_b2b_scope ON b2b_specific_price(scope);
|
CREATE INDEX idx_b2b_scope ON b2b_specific_price(scope);
|
||||||
CREATE INDEX idx_b2b_customer ON b2b_specific_price(b2b_id_customer);
|
CREATE INDEX idx_b2b_customer ON b2b_specific_price(b2b_id_customer);
|
||||||
@@ -344,6 +373,60 @@ END$$
|
|||||||
|
|
||||||
DELIMITER ;
|
DELIMITER ;
|
||||||
|
|
||||||
|
CREATE TABLE b2b_specific_price_product (
|
||||||
|
b2b_specific_price_id BIGINT UNSIGNED,
|
||||||
|
id_product INT UNSIGNED,
|
||||||
|
PRIMARY KEY (b2b_specific_price_id, id_product),
|
||||||
|
FOREIGN KEY (b2b_specific_price_id) REFERENCES b2b_specific_price(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (id_product) REFERENCES ps_product(id_product) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE b2b_specific_price_category (
|
||||||
|
b2b_specific_price_id BIGINT UNSIGNED,
|
||||||
|
id_category INT UNSIGNED,
|
||||||
|
PRIMARY KEY (b2b_specific_price_id, id_category),
|
||||||
|
FOREIGN KEY (b2b_specific_price_id) REFERENCES b2b_specific_price(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (id_category) REFERENCES ps_category(id_category) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE b2b_specific_price_product_attribute (
|
||||||
|
b2b_specific_price_id BIGINT UNSIGNED,
|
||||||
|
id_product_attribute INT UNSIGNED,
|
||||||
|
PRIMARY KEY (b2b_specific_price_id, id_product_attribute),
|
||||||
|
FOREIGN KEY (b2b_specific_price_id) REFERENCES b2b_specific_price(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (id_product_attribute) REFERENCES ps_product_attribute(id_product_attribute) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE b2b_specific_price_customer (
|
||||||
|
b2b_specific_price_id BIGINT UNSIGNED,
|
||||||
|
b2b_id_customer BIGINT UNSIGNED,
|
||||||
|
PRIMARY KEY (b2b_specific_price_id, b2b_id_customer),
|
||||||
|
FOREIGN KEY (b2b_specific_price_id) REFERENCES b2b_specific_price(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (b2b_id_customer) REFERENCES b2b_customers(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE b2b_specific_price_country (
|
||||||
|
b2b_specific_price_id BIGINT UNSIGNED,
|
||||||
|
b2b_id_country BIGINT UNSIGNED,
|
||||||
|
PRIMARY KEY (b2b_specific_price_id, b2b_id_country),
|
||||||
|
FOREIGN KEY (b2b_specific_price_id) REFERENCES b2b_specific_price(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (b2b_id_country) REFERENCES b2b_countries(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_b2b_product_rel
|
||||||
|
ON b2b_specific_price_product (id_product);
|
||||||
|
|
||||||
|
CREATE INDEX idx_b2b_category_rel
|
||||||
|
ON b2b_specific_price_category (id_category);
|
||||||
|
|
||||||
|
CREATE INDEX idx_b2b_product_attribute_rel
|
||||||
|
ON b2b_specific_price_product_attribute (id_product_attribute);
|
||||||
|
|
||||||
|
CREATE INDEX idx_bsp_customer
|
||||||
|
ON b2b_specific_price_customer (b2b_specific_price_id, b2b_id_customer);
|
||||||
|
|
||||||
|
CREATE INDEX idx_bsp_country
|
||||||
|
ON b2b_specific_price_country (b2b_specific_price_id, b2b_id_country);
|
||||||
-- +goose Down
|
-- +goose Down
|
||||||
|
|
||||||
DROP TABLE IF EXISTS b2b_countries;
|
DROP TABLE IF EXISTS b2b_countries;
|
||||||
|
|||||||
@@ -321,19 +321,36 @@ AND (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
ORDER BY
|
ORDER BY
|
||||||
/* 🔥 STRICT PRIORITY */
|
/* 🔥 SCOPE PRIORITY */
|
||||||
bsp.scope = 'product' DESC,
|
bsp.scope = 'product' DESC,
|
||||||
bsp.scope = 'category' DESC,
|
bsp.scope = 'category' DESC,
|
||||||
bsp.scope = 'shop' DESC,
|
bsp.scope = 'shop' DESC,
|
||||||
|
|
||||||
bsp.b2b_id_customer DESC,
|
/* 🔥 CUSTOMER PRIORITY */
|
||||||
bsp.b2b_id_country DESC,
|
(
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM b2b_specific_price_customer c
|
||||||
|
WHERE c.b2b_specific_price_id = bsp.id
|
||||||
|
AND c.b2b_id_customer = p_id_customer
|
||||||
|
)
|
||||||
|
) DESC,
|
||||||
|
|
||||||
|
/* 🔥 COUNTRY PRIORITY */
|
||||||
|
(
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM b2b_specific_price_country ctry
|
||||||
|
WHERE ctry.b2b_specific_price_id = bsp.id
|
||||||
|
AND ctry.b2b_id_country = b2b_id_country
|
||||||
|
)
|
||||||
|
) DESC,
|
||||||
|
|
||||||
|
/* GLOBAL fallback (no restrictions) naturally goes last */
|
||||||
|
|
||||||
bsp.from_quantity DESC,
|
bsp.from_quantity DESC,
|
||||||
bsp.id DESC
|
bsp.id DESC
|
||||||
|
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
) bsp ON 1=1
|
) bsp ON 1=1
|
||||||
LEFT JOIN b2b_currency_rates br_bsp
|
LEFT JOIN b2b_currency_rates br_bsp
|
||||||
ON br_bsp.b2b_id_currency = bsp.b2b_id_currency
|
ON br_bsp.b2b_id_currency = bsp.b2b_id_currency
|
||||||
|
|||||||
Reference in New Issue
Block a user