mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat(BE): price-product-supplier
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
-- Rollback: remove price from supplier relations
|
||||||
|
ALTER TABLE product_suppliers
|
||||||
|
DROP COLUMN IF EXISTS price;
|
||||||
|
|
||||||
|
ALTER TABLE nonstock_suppliers
|
||||||
|
DROP COLUMN IF EXISTS price;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-- Migration: add price to supplier relations
|
||||||
|
ALTER TABLE product_suppliers
|
||||||
|
ADD COLUMN IF NOT EXISTS price NUMERIC(15, 3) NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE nonstock_suppliers
|
||||||
|
ADD COLUMN IF NOT EXISTS price NUMERIC(15, 3) NOT NULL DEFAULT 0;
|
||||||
@@ -5,6 +5,7 @@ import "time"
|
|||||||
type NonstockSupplier struct {
|
type NonstockSupplier struct {
|
||||||
NonstockId uint `gorm:"not null"`
|
NonstockId uint `gorm:"not null"`
|
||||||
SupplierId uint `gorm:"not null"`
|
SupplierId uint `gorm:"not null"`
|
||||||
|
Price float64 `gorm:"type:numeric(15,3);not null;default:0"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|
||||||
Nonstock Nonstock `gorm:"foreignKey:NonstockId;references:Id"`
|
Nonstock Nonstock `gorm:"foreignKey:NonstockId;references:Id"`
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import "time"
|
|||||||
type ProductSupplier struct {
|
type ProductSupplier struct {
|
||||||
ProductId uint `gorm:"not null"`
|
ProductId uint `gorm:"not null"`
|
||||||
SupplierId uint `gorm:"not null"`
|
SupplierId uint `gorm:"not null"`
|
||||||
|
Price float64 `gorm:"type:numeric(15,3);not null;default:0"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|
||||||
Product Product `gorm:"foreignKey:ProductId;references:Id"`
|
Product Product `gorm:"foreignKey:ProductId;references:Id"`
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto"
|
|
||||||
uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto"
|
uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto"
|
||||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
)
|
)
|
||||||
@@ -23,7 +22,7 @@ type NonstockListDTO struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Flags []string `json:"flags"`
|
Flags []string `json:"flags"`
|
||||||
Uom *uomDTO.UomRelationDTO `json:"uom"`
|
Uom *uomDTO.UomRelationDTO `json:"uom"`
|
||||||
Suppliers []supplierDTO.SupplierRelationDTO `json:"suppliers"`
|
Suppliers []NonstockSupplierDTO `json:"suppliers"`
|
||||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
@@ -33,6 +32,14 @@ type NonstockDetailDTO struct {
|
|||||||
NonstockListDTO
|
NonstockListDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NonstockSupplierDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Price float64 `json:"price"`
|
||||||
|
}
|
||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToNonstockRelationDTO(e entity.Nonstock) NonstockRelationDTO {
|
func ToNonstockRelationDTO(e entity.Nonstock) NonstockRelationDTO {
|
||||||
@@ -99,21 +106,27 @@ func ToNonstockDetailDTO(e entity.Nonstock) NonstockDetailDTO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toNonstockSupplierDTOs(relations []entity.NonstockSupplier) []supplierDTO.SupplierRelationDTO {
|
func toNonstockSupplierDTOs(relations []entity.NonstockSupplier) []NonstockSupplierDTO {
|
||||||
if len(relations) == 0 {
|
if len(relations) == 0 {
|
||||||
return make([]supplierDTO.SupplierRelationDTO, 0)
|
return make([]NonstockSupplierDTO, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]supplierDTO.SupplierRelationDTO, 0, len(relations))
|
result := make([]NonstockSupplierDTO, 0, len(relations))
|
||||||
for _, relation := range relations {
|
for _, relation := range relations {
|
||||||
if relation.Supplier.Id == 0 {
|
if relation.Supplier.Id == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result = append(result, supplierDTO.ToSupplierRelationDTO(relation.Supplier))
|
result = append(result, NonstockSupplierDTO{
|
||||||
|
Id: relation.Supplier.Id,
|
||||||
|
Name: relation.Supplier.Name,
|
||||||
|
Alias: relation.Supplier.Alias,
|
||||||
|
Category: relation.Supplier.Category,
|
||||||
|
Price: relation.Price,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result) == 0 {
|
if len(result) == 0 {
|
||||||
return make([]supplierDTO.SupplierRelationDTO, 0)
|
return make([]NonstockSupplierDTO, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
type NonstockRepository interface {
|
type NonstockRepository interface {
|
||||||
repository.BaseRepository[entity.Nonstock]
|
repository.BaseRepository[entity.Nonstock]
|
||||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||||
SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, nonstockID uint, supplierIDs []uint) error
|
SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, nonstockID uint, suppliers []entity.NonstockSupplier) error
|
||||||
UomExists(ctx context.Context, uomID uint) (bool, error)
|
UomExists(ctx context.Context, uomID uint) (bool, error)
|
||||||
GetSuppliersByIDs(ctx context.Context, supplierIDs []uint) ([]entity.Supplier, error)
|
GetSuppliersByIDs(ctx context.Context, supplierIDs []uint) ([]entity.Supplier, error)
|
||||||
SyncFlags(ctx context.Context, tx *gorm.DB, nonstockID uint, flags []string) error
|
SyncFlags(ctx context.Context, tx *gorm.DB, nonstockID uint, flags []string) error
|
||||||
@@ -40,13 +40,13 @@ func (r *NonstockRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, e
|
|||||||
return repository.Exists[entity.Nonstock](ctx, r.DB(), id)
|
return repository.Exists[entity.Nonstock](ctx, r.DB(), id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *NonstockRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, nonstockID uint, supplierIDs []uint) error {
|
func (r *NonstockRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, nonstockID uint, suppliers []entity.NonstockSupplier) error {
|
||||||
db := tx
|
db := tx
|
||||||
if db == nil {
|
if db == nil {
|
||||||
db = r.DB()
|
db = r.DB()
|
||||||
}
|
}
|
||||||
|
|
||||||
if supplierIDs == nil {
|
if suppliers == nil {
|
||||||
return db.WithContext(ctx).
|
return db.WithContext(ctx).
|
||||||
Where("nonstock_id = ?", nonstockID).
|
Where("nonstock_id = ?", nonstockID).
|
||||||
Delete(&entity.NonstockSupplier{}).
|
Delete(&entity.NonstockSupplier{}).
|
||||||
@@ -61,18 +61,31 @@ func (r *NonstockRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
existingMap := make(map[uint]struct{}, len(existing))
|
existingMap := make(map[uint]entity.NonstockSupplier, len(existing))
|
||||||
for _, rel := range existing {
|
for _, rel := range existing {
|
||||||
existingMap[rel.SupplierId] = struct{}{}
|
existingMap[rel.SupplierId] = rel
|
||||||
}
|
}
|
||||||
|
|
||||||
incomingMap := make(map[uint]struct{}, len(supplierIDs))
|
incomingMap := make(map[uint]struct{}, len(suppliers))
|
||||||
for _, id := range supplierIDs {
|
for _, rel := range suppliers {
|
||||||
incomingMap[id] = struct{}{}
|
incomingMap[rel.SupplierId] = struct{}{}
|
||||||
if _, exists := existingMap[id]; exists {
|
if existingRel, exists := existingMap[rel.SupplierId]; exists {
|
||||||
|
if existingRel.Price != rel.Price {
|
||||||
|
if err := db.WithContext(ctx).
|
||||||
|
Model(&entity.NonstockSupplier{}).
|
||||||
|
Where("nonstock_id = ? AND supplier_id = ?", nonstockID, rel.SupplierId).
|
||||||
|
Update("price", rel.Price).
|
||||||
|
Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
record := entity.NonstockSupplier{NonstockId: nonstockID, SupplierId: id}
|
record := entity.NonstockSupplier{
|
||||||
|
NonstockId: nonstockID,
|
||||||
|
SupplierId: rel.SupplierId,
|
||||||
|
Price: rel.Price,
|
||||||
|
}
|
||||||
if err := db.WithContext(ctx).Create(&record).Error; err != nil {
|
if err := db.WithContext(ctx).Create(&record).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,8 +111,25 @@ func (s *nonstockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
supplierIDs := utils.UniqueUintSlice(req.SupplierIDs)
|
var (
|
||||||
if len(supplierIDs) > 0 {
|
supplierLinks []entity.NonstockSupplier
|
||||||
|
supplierIDs []uint
|
||||||
|
)
|
||||||
|
if len(req.Suppliers) > 0 {
|
||||||
|
seen := make(map[uint]struct{}, len(req.Suppliers))
|
||||||
|
supplierLinks = make([]entity.NonstockSupplier, 0, len(req.Suppliers))
|
||||||
|
supplierIDs = make([]uint, 0, len(req.Suppliers))
|
||||||
|
for _, supplier := range req.Suppliers {
|
||||||
|
if _, exists := seen[supplier.SupplierID]; exists {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Duplicate supplier_id %d", supplier.SupplierID))
|
||||||
|
}
|
||||||
|
seen[supplier.SupplierID] = struct{}{}
|
||||||
|
supplierIDs = append(supplierIDs, supplier.SupplierID)
|
||||||
|
supplierLinks = append(supplierLinks, entity.NonstockSupplier{
|
||||||
|
SupplierId: supplier.SupplierID,
|
||||||
|
Price: supplier.Price,
|
||||||
|
})
|
||||||
|
}
|
||||||
supplierList, supplierErr := s.Repository.GetSuppliersByIDs(ctx, supplierIDs)
|
supplierList, supplierErr := s.Repository.GetSuppliersByIDs(ctx, supplierIDs)
|
||||||
if supplierErr != nil {
|
if supplierErr != nil {
|
||||||
s.Log.Errorf("Failed to validate suppliers: %+v", supplierErr)
|
s.Log.Errorf("Failed to validate suppliers: %+v", supplierErr)
|
||||||
@@ -155,7 +172,7 @@ func (s *nonstockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.Repository.SyncSuppliersDiff(ctx, tx, createBody.Id, supplierIDs)
|
return s.Repository.SyncSuppliersDiff(ctx, tx, createBody.Id, supplierLinks)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -193,15 +210,27 @@ func (s nonstockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint
|
|||||||
updateBody["uom_id"] = *req.UomID
|
updateBody["uom_id"] = *req.UomID
|
||||||
}
|
}
|
||||||
|
|
||||||
var supplierIDs []uint
|
var supplierLinks []entity.NonstockSupplier
|
||||||
var supplierUpdate bool
|
var supplierUpdate bool
|
||||||
if req.SupplierIDs != nil {
|
if req.Suppliers != nil {
|
||||||
supplierUpdate = true
|
supplierUpdate = true
|
||||||
supplierIDs = utils.UniqueUintSlice(*req.SupplierIDs)
|
if len(*req.Suppliers) > 0 {
|
||||||
if len(supplierIDs) > 0 {
|
seen := make(map[uint]struct{}, len(*req.Suppliers))
|
||||||
var supplierList []entity.Supplier
|
supplierLinks = make([]entity.NonstockSupplier, 0, len(*req.Suppliers))
|
||||||
var supplierErr error
|
supplierIDs := make([]uint, 0, len(*req.Suppliers))
|
||||||
supplierList, supplierErr = s.Repository.GetSuppliersByIDs(ctx, supplierIDs)
|
for _, supplier := range *req.Suppliers {
|
||||||
|
if _, exists := seen[supplier.SupplierID]; exists {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Duplicate supplier_id %d", supplier.SupplierID))
|
||||||
|
}
|
||||||
|
seen[supplier.SupplierID] = struct{}{}
|
||||||
|
supplierIDs = append(supplierIDs, supplier.SupplierID)
|
||||||
|
supplierLinks = append(supplierLinks, entity.NonstockSupplier{
|
||||||
|
SupplierId: supplier.SupplierID,
|
||||||
|
Price: supplier.Price,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
supplierList, supplierErr := s.Repository.GetSuppliersByIDs(ctx, supplierIDs)
|
||||||
if supplierErr != nil {
|
if supplierErr != nil {
|
||||||
s.Log.Errorf("Failed to validate suppliers: %+v", supplierErr)
|
s.Log.Errorf("Failed to validate suppliers: %+v", supplierErr)
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate suppliers")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate suppliers")
|
||||||
@@ -253,11 +282,7 @@ func (s nonstockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint
|
|||||||
}
|
}
|
||||||
|
|
||||||
if supplierUpdate {
|
if supplierUpdate {
|
||||||
var ids []uint
|
if err := s.Repository.SyncSuppliersDiff(ctx, tx, id, supplierLinks); err != nil {
|
||||||
if len(supplierIDs) > 0 {
|
|
||||||
ids = supplierIDs
|
|
||||||
}
|
|
||||||
if err := s.Repository.SyncSuppliersDiff(ctx, tx, id, ids); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
|
type SupplierPrice struct {
|
||||||
|
SupplierID uint `json:"supplier_id" validate:"required,gt=0"`
|
||||||
|
Price float64 `json:"price" validate:"required,gte=0"`
|
||||||
|
}
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
||||||
UomID uint `json:"uom_id" validate:"required,gt=0"`
|
UomID uint `json:"uom_id" validate:"required,gt=0"`
|
||||||
SupplierIDs []uint `json:"supplier_ids" validate:"dive,gt=0"`
|
Suppliers []SupplierPrice `json:"suppliers,omitempty" validate:"omitempty,dive"`
|
||||||
Flags []string `json:"flags" validate:"dive,max=50"`
|
Flags []string `json:"flags" validate:"dive,max=50"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty,min=3,max=50"`
|
Name *string `json:"name,omitempty" validate:"omitempty,min=3,max=50"`
|
||||||
UomID *uint `json:"uom_id,omitempty" validate:"omitempty,gt=0"`
|
UomID *uint `json:"uom_id,omitempty" validate:"omitempty,gt=0"`
|
||||||
SupplierIDs *[]uint `json:"supplier_ids,omitempty" validate:"omitempty,dive,gt=0"`
|
Suppliers *[]SupplierPrice `json:"suppliers,omitempty" validate:"omitempty,dive"`
|
||||||
Flags *[]string `json:"flags,omitempty" validate:"omitempty,dive,max=50"`
|
Flags *[]string `json:"flags,omitempty" validate:"omitempty,dive,max=50"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
productCategoryDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories/dto"
|
productCategoryDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories/dto"
|
||||||
supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto"
|
|
||||||
uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto"
|
uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto"
|
||||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
)
|
)
|
||||||
@@ -20,7 +19,7 @@ type ProductRelationDTO struct {
|
|||||||
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
||||||
Flags *[]string `json:"flags,omitempty"`
|
Flags *[]string `json:"flags,omitempty"`
|
||||||
ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"`
|
ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"`
|
||||||
Suppliers []supplierDTO.SupplierRelationDTO `json:"suppliers"`
|
Suppliers []ProductSupplierDTO `json:"suppliers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductListDTO struct {
|
type ProductListDTO struct {
|
||||||
@@ -35,7 +34,7 @@ type ProductListDTO struct {
|
|||||||
Flags []string `json:"flags"`
|
Flags []string `json:"flags"`
|
||||||
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
||||||
ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"`
|
ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"`
|
||||||
Suppliers []supplierDTO.SupplierRelationDTO `json:"suppliers"`
|
Suppliers []ProductSupplierDTO `json:"suppliers"`
|
||||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
@@ -45,6 +44,14 @@ type ProductDetailDTO struct {
|
|||||||
ProductListDTO
|
ProductListDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProductSupplierDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Price float64 `json:"price"`
|
||||||
|
}
|
||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToProductRelationDTO(e entity.Product) ProductRelationDTO {
|
func ToProductRelationDTO(e entity.Product) ProductRelationDTO {
|
||||||
@@ -134,21 +141,27 @@ func ToProductDetailDTO(e entity.Product) ProductDetailDTO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toProductSupplierDTOs(relations []entity.ProductSupplier) []supplierDTO.SupplierRelationDTO {
|
func toProductSupplierDTOs(relations []entity.ProductSupplier) []ProductSupplierDTO {
|
||||||
if len(relations) == 0 {
|
if len(relations) == 0 {
|
||||||
return make([]supplierDTO.SupplierRelationDTO, 0)
|
return make([]ProductSupplierDTO, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]supplierDTO.SupplierRelationDTO, 0, len(relations))
|
result := make([]ProductSupplierDTO, 0, len(relations))
|
||||||
for _, relation := range relations {
|
for _, relation := range relations {
|
||||||
if relation.Supplier.Id == 0 {
|
if relation.Supplier.Id == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result = append(result, supplierDTO.ToSupplierRelationDTO(relation.Supplier))
|
result = append(result, ProductSupplierDTO{
|
||||||
|
Id: relation.Supplier.Id,
|
||||||
|
Name: relation.Supplier.Name,
|
||||||
|
Alias: relation.Supplier.Alias,
|
||||||
|
Category: relation.Supplier.Category,
|
||||||
|
Price: relation.Price,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result) == 0 {
|
if len(result) == 0 {
|
||||||
return make([]supplierDTO.SupplierRelationDTO, 0)
|
return make([]ProductSupplierDTO, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type ProductRepository interface {
|
|||||||
CategoryExists(ctx context.Context, categoryID uint) (bool, error)
|
CategoryExists(ctx context.Context, categoryID uint) (bool, error)
|
||||||
GetSuppliersByIDs(ctx context.Context, supplierIDs []uint) ([]entity.Supplier, error)
|
GetSuppliersByIDs(ctx context.Context, supplierIDs []uint) ([]entity.Supplier, error)
|
||||||
IsLinkedToSupplier(ctx context.Context, productID, supplierID uint) (bool, error)
|
IsLinkedToSupplier(ctx context.Context, productID, supplierID uint) (bool, error)
|
||||||
SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, productID uint, supplierIDs []uint) error
|
SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, productID uint, suppliers []entity.ProductSupplier) error
|
||||||
SyncFlags(ctx context.Context, tx *gorm.DB, productID uint, flags []string) error
|
SyncFlags(ctx context.Context, tx *gorm.DB, productID uint, flags []string) error
|
||||||
DeleteFlags(ctx context.Context, tx *gorm.DB, productID uint) error
|
DeleteFlags(ctx context.Context, tx *gorm.DB, productID uint) error
|
||||||
GetFlags(ctx context.Context, productID uint) ([]entity.Flag, error)
|
GetFlags(ctx context.Context, productID uint) ([]entity.Flag, error)
|
||||||
@@ -102,13 +102,13 @@ func (r *ProductRepositoryImpl) IsLinkedToSupplier(ctx context.Context, productI
|
|||||||
return count > 0, nil
|
return count > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProductRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, productID uint, supplierIds []uint) error {
|
func (r *ProductRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, productID uint, suppliers []entity.ProductSupplier) error {
|
||||||
db := tx
|
db := tx
|
||||||
if db == nil {
|
if db == nil {
|
||||||
db = r.DB()
|
db = r.DB()
|
||||||
}
|
}
|
||||||
|
|
||||||
if supplierIds == nil {
|
if suppliers == nil {
|
||||||
return db.WithContext(ctx).
|
return db.WithContext(ctx).
|
||||||
Where("product_id = ?", productID).
|
Where("product_id = ?", productID).
|
||||||
Delete(&entity.ProductSupplier{}).
|
Delete(&entity.ProductSupplier{}).
|
||||||
@@ -123,18 +123,31 @@ func (r *ProductRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
existingMap := make(map[uint]struct{}, len(existing))
|
existingMap := make(map[uint]entity.ProductSupplier, len(existing))
|
||||||
for _, rel := range existing {
|
for _, rel := range existing {
|
||||||
existingMap[rel.SupplierId] = struct{}{}
|
existingMap[rel.SupplierId] = rel
|
||||||
}
|
}
|
||||||
|
|
||||||
incomingMap := make(map[uint]struct{}, len(supplierIds))
|
incomingMap := make(map[uint]struct{}, len(suppliers))
|
||||||
for _, id := range supplierIds {
|
for _, rel := range suppliers {
|
||||||
incomingMap[id] = struct{}{}
|
incomingMap[rel.SupplierId] = struct{}{}
|
||||||
if _, exists := existingMap[id]; exists {
|
if existingRel, exists := existingMap[rel.SupplierId]; exists {
|
||||||
|
if existingRel.Price != rel.Price {
|
||||||
|
if err := db.WithContext(ctx).
|
||||||
|
Model(&entity.ProductSupplier{}).
|
||||||
|
Where("product_id = ? AND supplier_id = ?", productID, rel.SupplierId).
|
||||||
|
Update("price", rel.Price).
|
||||||
|
Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
record := entity.ProductSupplier{ProductId: productID, SupplierId: id}
|
record := entity.ProductSupplier{
|
||||||
|
ProductId: productID,
|
||||||
|
SupplierId: rel.SupplierId,
|
||||||
|
Price: rel.Price,
|
||||||
|
}
|
||||||
if err := db.WithContext(ctx).Create(&record).Error; err != nil {
|
if err := db.WithContext(ctx).Create(&record).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,9 +138,25 @@ func (s *productService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
supplierIDs := utils.UniqueUintSlice(req.SupplierIDs)
|
var (
|
||||||
var err error
|
supplierLinks []entity.ProductSupplier
|
||||||
if len(supplierIDs) > 0 {
|
supplierIDs []uint
|
||||||
|
)
|
||||||
|
if len(req.Suppliers) > 0 {
|
||||||
|
seen := make(map[uint]struct{}, len(req.Suppliers))
|
||||||
|
supplierLinks = make([]entity.ProductSupplier, 0, len(req.Suppliers))
|
||||||
|
supplierIDs = make([]uint, 0, len(req.Suppliers))
|
||||||
|
for _, supplier := range req.Suppliers {
|
||||||
|
if _, exists := seen[supplier.SupplierID]; exists {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Duplicate supplier_id %d", supplier.SupplierID))
|
||||||
|
}
|
||||||
|
seen[supplier.SupplierID] = struct{}{}
|
||||||
|
supplierIDs = append(supplierIDs, supplier.SupplierID)
|
||||||
|
supplierLinks = append(supplierLinks, entity.ProductSupplier{
|
||||||
|
SupplierId: supplier.SupplierID,
|
||||||
|
Price: supplier.Price,
|
||||||
|
})
|
||||||
|
}
|
||||||
suppliers, err := s.Repository.GetSuppliersByIDs(ctx, supplierIDs)
|
suppliers, err := s.Repository.GetSuppliersByIDs(ctx, supplierIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to validate suppliers: %+v", err)
|
s.Log.Errorf("Failed to validate suppliers: %+v", err)
|
||||||
@@ -180,7 +196,7 @@ func (s *productService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
CreatedBy: 1,
|
CreatedBy: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
err := s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
repoTx := s.Repository.WithTx(tx)
|
repoTx := s.Repository.WithTx(tx)
|
||||||
|
|
||||||
if err := repoTx.CreateOne(ctx, createBody, nil); err != nil {
|
if err := repoTx.CreateOne(ctx, createBody, nil); err != nil {
|
||||||
@@ -191,7 +207,7 @@ func (s *productService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.Repository.SyncSuppliersDiff(ctx, tx, createBody.Id, supplierIDs)
|
return s.Repository.SyncSuppliersDiff(ctx, tx, createBody.Id, supplierLinks)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -276,15 +292,27 @@ func (s productService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
|
|
||||||
ctx := c.Context()
|
ctx := c.Context()
|
||||||
|
|
||||||
var suppliers []entity.Supplier
|
var supplierLinks []entity.ProductSupplier
|
||||||
var supplierIDs []uint
|
|
||||||
var supplierUpdate bool
|
var supplierUpdate bool
|
||||||
if req.SupplierIDs != nil {
|
if req.Suppliers != nil {
|
||||||
supplierUpdate = true
|
supplierUpdate = true
|
||||||
supplierIDs = utils.UniqueUintSlice(*req.SupplierIDs)
|
if len(*req.Suppliers) > 0 {
|
||||||
if len(supplierIDs) > 0 {
|
seen := make(map[uint]struct{}, len(*req.Suppliers))
|
||||||
var err error
|
supplierLinks = make([]entity.ProductSupplier, 0, len(*req.Suppliers))
|
||||||
suppliers, err = s.Repository.GetSuppliersByIDs(ctx, supplierIDs)
|
supplierIDs := make([]uint, 0, len(*req.Suppliers))
|
||||||
|
for _, supplier := range *req.Suppliers {
|
||||||
|
if _, exists := seen[supplier.SupplierID]; exists {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Duplicate supplier_id %d", supplier.SupplierID))
|
||||||
|
}
|
||||||
|
seen[supplier.SupplierID] = struct{}{}
|
||||||
|
supplierIDs = append(supplierIDs, supplier.SupplierID)
|
||||||
|
supplierLinks = append(supplierLinks, entity.ProductSupplier{
|
||||||
|
SupplierId: supplier.SupplierID,
|
||||||
|
Price: supplier.Price,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suppliers, err := s.Repository.GetSuppliersByIDs(ctx, supplierIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to validate suppliers: %+v", err)
|
s.Log.Errorf("Failed to validate suppliers: %+v", err)
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate suppliers")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate suppliers")
|
||||||
@@ -336,11 +364,7 @@ func (s productService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if supplierUpdate {
|
if supplierUpdate {
|
||||||
var ids []uint
|
if err := s.Repository.SyncSuppliersDiff(ctx, tx, id, supplierLinks); err != nil {
|
||||||
if len(supplierIDs) > 0 {
|
|
||||||
ids = supplierIDs
|
|
||||||
}
|
|
||||||
if err := s.Repository.SyncSuppliersDiff(ctx, tx, id, ids); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
|
type SupplierPrice struct {
|
||||||
|
SupplierID uint `json:"supplier_id" validate:"required,gt=0"`
|
||||||
|
Price float64 `json:"price" validate:"required,gte=0"`
|
||||||
|
}
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
||||||
Brand string `json:"brand" validate:"required_strict,min=2,max=50"`
|
Brand string `json:"brand" validate:"required_strict,min=2,max=50"`
|
||||||
@@ -10,7 +15,7 @@ type Create struct {
|
|||||||
SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"`
|
SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"`
|
||||||
Tax *float64 `json:"tax,omitempty" validate:"omitempty"`
|
Tax *float64 `json:"tax,omitempty" validate:"omitempty"`
|
||||||
ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"`
|
ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"`
|
||||||
SupplierIDs []uint `json:"supplier_ids,omitempty" validate:"omitempty,dive,gt=0"`
|
Suppliers []SupplierPrice `json:"suppliers,omitempty" validate:"omitempty,dive"`
|
||||||
Flags []string `json:"flags,omitempty" validate:"omitempty,dive"`
|
Flags []string `json:"flags,omitempty" validate:"omitempty,dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +29,7 @@ type Update struct {
|
|||||||
SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"`
|
SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"`
|
||||||
Tax *float64 `json:"tax,omitempty" validate:"omitempty"`
|
Tax *float64 `json:"tax,omitempty" validate:"omitempty"`
|
||||||
ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"`
|
ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"`
|
||||||
SupplierIDs *[]uint `json:"supplier_ids,omitempty" validate:"omitempty,dive,gt=0"`
|
Suppliers *[]SupplierPrice `json:"suppliers,omitempty" validate:"omitempty,dive"`
|
||||||
Flags *[]string `json:"flags,omitempty" validate:"omitempty,dive"`
|
Flags *[]string `json:"flags,omitempty" validate:"omitempty,dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
type SupplierNonstockDTO struct {
|
type SupplierNonstockDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Price float64 `json:"price"`
|
||||||
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
||||||
Flags []string `json:"flags"`
|
Flags []string `json:"flags"`
|
||||||
}
|
}
|
||||||
@@ -42,6 +43,7 @@ func toSupplierNonstockDTOs(relations []entity.NonstockSupplier) []SupplierNonst
|
|||||||
result = append(result, SupplierNonstockDTO{
|
result = append(result, SupplierNonstockDTO{
|
||||||
Id: Nonstock.Id,
|
Id: Nonstock.Id,
|
||||||
Name: Nonstock.Name,
|
Name: Nonstock.Name,
|
||||||
|
Price: relation.Price,
|
||||||
Uom: uomRef,
|
Uom: uomRef,
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ import (
|
|||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type SupplierProductDTO struct {
|
type SupplierProductDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
ProductPrice float64 `gorm:"type:numeric(15,3);not null"`
|
ProductPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
SellingPrice *float64 `gorm:"type:numeric(15,3)"`
|
SellingPrice *float64 `gorm:"type:numeric(15,3)"`
|
||||||
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
SupplierPrice float64 `json:"supplier_price"`
|
||||||
Flags []string `json:"flags"`
|
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
||||||
|
Flags []string `json:"flags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
@@ -42,12 +43,13 @@ func toSupplierProductDTOs(relations []entity.ProductSupplier) []SupplierProduct
|
|||||||
}
|
}
|
||||||
|
|
||||||
result = append(result, SupplierProductDTO{
|
result = append(result, SupplierProductDTO{
|
||||||
Id: product.Id,
|
Id: product.Id,
|
||||||
Name: product.Name,
|
Name: product.Name,
|
||||||
ProductPrice: product.ProductPrice,
|
ProductPrice: product.ProductPrice,
|
||||||
SellingPrice: product.SellingPrice,
|
SellingPrice: product.SellingPrice,
|
||||||
Uom: uomRef,
|
SupplierPrice: relation.Price,
|
||||||
Flags: flags,
|
Uom: uomRef,
|
||||||
|
Flags: flags,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|||||||
Reference in New Issue
Block a user