443 lines
13 KiB
Go
443 lines
13 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
|
|
categoryrepo "github.com/Caknoooo/go-gin-clean-starter/modules/category/repository"
|
|
invstoragerepo "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_storage/repository"
|
|
invtransactionrepo "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_transaction/repository"
|
|
"github.com/Caknoooo/go-gin-clean-starter/modules/product/dto"
|
|
"github.com/Caknoooo/go-gin-clean-starter/modules/product/query"
|
|
"github.com/Caknoooo/go-gin-clean-starter/modules/product/repository"
|
|
sequenceservice "github.com/Caknoooo/go-gin-clean-starter/modules/sequence/service"
|
|
"github.com/Caknoooo/go-gin-clean-starter/pkg/constants"
|
|
pkgdto "github.com/Caknoooo/go-gin-clean-starter/pkg/dto"
|
|
"github.com/Caknoooo/go-gin-clean-starter/pkg/utils"
|
|
"github.com/google/uuid"
|
|
"github.com/sirupsen/logrus"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type ProductService interface {
|
|
Create(ctx context.Context, req dto.ProductCreateRequest) (dto.ProductResponse, error)
|
|
GetById(ctx context.Context, productId string) (dto.ProductResponse, error)
|
|
GetAll(ctx context.Context, filter query.ProductFilter) ([]dto.ProductResponse, int64, error)
|
|
Update(ctx context.Context, req dto.ProductUpdateRequest, productId string) (dto.ProductResponse, error)
|
|
Delete(ctx context.Context, productId string) error
|
|
AssignCrossReference(ctx context.Context, productId string, vendorIds []string) error
|
|
RemoveCrossReference(ctx context.Context, productId string, vendorIds []string) error
|
|
GetInvTransactionsByProductAndClient(ctx context.Context, productId string, clientId string) ([]dto.ProductInventoryTransactionResponse, error)
|
|
GetInvStoragesByProductAndClient(ctx context.Context, productId string, clientId string) ([]dto.ProductInventoryStorageResponse, error)
|
|
GetCrossReferencesByProduct(ctx context.Context, productId string) ([]dto.ProductVendorResponse, error)
|
|
}
|
|
|
|
type productService struct {
|
|
db *gorm.DB
|
|
productRepo repository.ProductRepository
|
|
inventoryTransactionRepo invtransactionrepo.InventoryTransactionRepository
|
|
inventoryStorageRepo invstoragerepo.InventoryStorageRepository
|
|
categoryRepo categoryrepo.CategoryRepository
|
|
sequenceService sequenceservice.SequenceService
|
|
log *logrus.Logger
|
|
}
|
|
|
|
// GetCrossReferencesByProductAndClient implements ProductService.
|
|
func (s *productService) GetCrossReferencesByProduct(ctx context.Context, productId string) ([]dto.ProductVendorResponse, error) {
|
|
crossReferences, err := s.productRepo.GetCrossReferencesByProduct(ctx, nil, productId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var responses []dto.ProductVendorResponse
|
|
for _, cr := range crossReferences {
|
|
responses = append(responses, dto.MapCrossReferenceToProductVendorResponse(cr))
|
|
}
|
|
if len(responses) == 0 {
|
|
responses = []dto.ProductVendorResponse{} // pastikan slice kosong, bukan nil
|
|
}
|
|
return responses, nil
|
|
}
|
|
|
|
// GetInvStoragesByProductAndClient implements ProductService.
|
|
func (s *productService) GetInvStoragesByProductAndClient(ctx context.Context, productId string, clientId string) ([]dto.ProductInventoryStorageResponse, error) {
|
|
invStorages, err := s.inventoryStorageRepo.GetStoragesByProductAndClient(ctx, nil, productId, clientId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var responses []dto.ProductInventoryStorageResponse
|
|
for _, invStorage := range invStorages {
|
|
responses = append(responses, dto.MapInventoryStorageToProductInventoryStorageResponse(invStorage))
|
|
}
|
|
return responses, nil
|
|
}
|
|
|
|
// GetInvTransactionsByProductAndClient implements ProductService.
|
|
func (s *productService) GetInvTransactionsByProductAndClient(ctx context.Context, productId string, clientId string) ([]dto.ProductInventoryTransactionResponse, error) {
|
|
invTransactions, err := s.inventoryTransactionRepo.GetByProductAndClient(ctx, nil, productId, clientId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
responses := dto.MapInventoryTransactionsToResponses(invTransactions)
|
|
return responses, nil
|
|
}
|
|
|
|
// AssignCrossReference implements ProductService.
|
|
func (s *productService) AssignCrossReference(ctx context.Context, productId string, vendorIds []string) error {
|
|
tx := s.db.Begin()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
tx.Rollback()
|
|
}
|
|
}()
|
|
err := s.productRepo.AssignCrossReference(ctx, tx, productId, vendorIds)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
if err := tx.Commit().Error; err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveCrossReference implements ProductService.
|
|
func (s *productService) RemoveCrossReference(ctx context.Context, productId string, vendorIds []string) error {
|
|
tx := s.db.Begin()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
tx.Rollback()
|
|
}
|
|
}()
|
|
err := s.productRepo.RemoveCrossReference(ctx, tx, productId, vendorIds)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
if err := tx.Commit().Error; err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func NewProductService(productRepo repository.ProductRepository, db *gorm.DB, inventoryTransactionRepo invtransactionrepo.InventoryTransactionRepository,
|
|
inventoryStorageRepo invstoragerepo.InventoryStorageRepository, categoryRepo categoryrepo.CategoryRepository, sequenceService sequenceservice.SequenceService, log *logrus.Logger) ProductService {
|
|
return &productService{
|
|
productRepo: productRepo,
|
|
db: db,
|
|
inventoryTransactionRepo: inventoryTransactionRepo,
|
|
inventoryStorageRepo: inventoryStorageRepo,
|
|
categoryRepo: categoryRepo,
|
|
sequenceService: sequenceService,
|
|
log: log,
|
|
}
|
|
}
|
|
|
|
func (s *productService) Create(ctx context.Context, req dto.ProductCreateRequest) (dto.ProductResponse, error) {
|
|
tx := s.db.Begin()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
// UUID fields
|
|
clientUUID, err := uuid.Parse(req.ClientID)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return dto.ProductResponse{}, err
|
|
}
|
|
|
|
uomUUID, err := uuid.Parse(*req.UomID)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return dto.ProductResponse{}, err
|
|
}
|
|
|
|
categoryUUID, err := uuid.Parse(*req.CategoryID)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return dto.ProductResponse{}, err
|
|
}
|
|
|
|
category, err := s.categoryRepo.GetById(ctx, tx, categoryUUID.String())
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return dto.ProductResponse{}, err
|
|
}
|
|
|
|
// Gunakan sequenceService untuk generate nomor referensi
|
|
seqConfig := pkgdto.SequenceConfig{
|
|
EntityType: "product",
|
|
Prefix: "PRD",
|
|
Period: "",
|
|
}
|
|
|
|
refNumber, err := s.sequenceService.GenerateNumberWithPeriod(ctx, req.ClientID, seqConfig, category.SearchKey)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return dto.ProductResponse{}, err
|
|
}
|
|
product := entities.MProductEntity{
|
|
Name: req.Name,
|
|
RefNumber: refNumber,
|
|
SKU: req.SKU,
|
|
Description: req.Description,
|
|
Status: req.Status,
|
|
IsReturnable: req.IsReturnable,
|
|
CategoryID: &categoryUUID,
|
|
UomID: &uomUUID,
|
|
ClientID: clientUUID,
|
|
FullAuditTrail: utils.FillAuditTrail(ctx, constants.CREATE),
|
|
|
|
// DimLength: req.DimLength,
|
|
// DimWidth: req.DimWidth,
|
|
// DimHeight: req.DimHeight,
|
|
// Weight: req.Weight,
|
|
// Volume: req.Volume,
|
|
// MaxStackHeight: req.MaxStackHeight,
|
|
// Temperature: req.Temperature,
|
|
// IsHazardous: req.IsHazardous,
|
|
// MinStock: req.MinStock,
|
|
// MaxStock: req.MaxStock,
|
|
// ReplenishType: req.ReplenishType,
|
|
// CycleCount: req.CycleCount,
|
|
// LotRules: req.LotRules,
|
|
// LeadTime: req.LeadTime,
|
|
// MultiplyRate: req.MultiplyRate,
|
|
// DivideRate: req.DivideRate,
|
|
}
|
|
|
|
// product.DimUomID = parseUUID(req.DimUomID)
|
|
// product.WeightUomID = parseUUID(req.WeightUomID)
|
|
// product.VolumeUomID = parseUUID(req.VolumeUomID)
|
|
// product.MinStockUomID = parseUUID(req.MinStockUomID)
|
|
// product.MaxStockUomID = parseUUID(req.MaxStockUomID)
|
|
// product.LeadTimeUomID = parseUUID(req.LeadTimeUomID)
|
|
// product.UomToUomID = parseUUID(req.UomToUomID)
|
|
|
|
created, err := s.productRepo.Create(ctx, tx, product)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return dto.ProductResponse{}, err
|
|
}
|
|
err = tx.Commit().Error
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return dto.ProductResponse{}, err
|
|
}
|
|
s.log.WithFields(logrus.Fields{
|
|
"user_id": utils.GetUserID(ctx),
|
|
"action": "create",
|
|
"entity": "product",
|
|
"entity_id": created.ID.String(),
|
|
}).Info("Product created")
|
|
return s.GetById(ctx, created.ID.String())
|
|
}
|
|
|
|
func (s *productService) GetById(ctx context.Context, productId string) (dto.ProductResponse, error) {
|
|
product, err := s.productRepo.GetById(ctx, nil, productId)
|
|
if err != nil {
|
|
return dto.ProductResponse{}, err
|
|
}
|
|
return dto.MapProductToResponse(product), nil
|
|
}
|
|
|
|
func (s *productService) GetAll(ctx context.Context, filter query.ProductFilter) ([]dto.ProductResponse, int64, error) {
|
|
products, total, err := s.productRepo.GetAll(ctx, filter)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
var responses []dto.ProductResponse
|
|
for _, p := range products {
|
|
responses = append(responses, dto.MapProductToResponse(p))
|
|
}
|
|
if len(responses) == 0 {
|
|
responses = []dto.ProductResponse{} // <-- pastikan slice kosong, bukan nil
|
|
}
|
|
|
|
return responses, total, nil
|
|
}
|
|
|
|
func (s *productService) Update(ctx context.Context, req dto.ProductUpdateRequest, productId string) (dto.ProductResponse, error) {
|
|
tx := s.db.Begin()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
tx.Rollback()
|
|
}
|
|
}()
|
|
product, err := s.productRepo.GetById(ctx, tx, productId)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return dto.ProductResponse{}, err
|
|
}
|
|
if req.Name != nil {
|
|
product.Name = *req.Name
|
|
}
|
|
if req.SKU != nil {
|
|
product.SKU = *req.SKU
|
|
}
|
|
if req.Description != nil {
|
|
product.Description = *req.Description
|
|
}
|
|
if req.Status != nil {
|
|
product.Status = *req.Status
|
|
}
|
|
if req.IsReturnable != nil {
|
|
product.IsReturnable = *req.IsReturnable
|
|
}
|
|
if req.DimLength != nil {
|
|
product.DimLength = *req.DimLength
|
|
}
|
|
if req.DimWidth != nil {
|
|
product.DimWidth = *req.DimWidth
|
|
}
|
|
if req.DimHeight != nil {
|
|
product.DimHeight = *req.DimHeight
|
|
}
|
|
if req.Weight != nil {
|
|
product.Weight = *req.Weight
|
|
}
|
|
if req.Volume != nil {
|
|
product.Volume = *req.Volume
|
|
}
|
|
if req.MaxStackHeight != nil {
|
|
product.MaxStackHeight = *req.MaxStackHeight
|
|
}
|
|
if req.Temperature != nil {
|
|
product.Temperature = *req.Temperature
|
|
}
|
|
if req.IsHazardous != nil {
|
|
product.IsHazardous = *req.IsHazardous
|
|
}
|
|
if req.MinStock != nil {
|
|
product.MinStock = *req.MinStock
|
|
}
|
|
if req.MaxStock != nil {
|
|
product.MaxStock = *req.MaxStock
|
|
}
|
|
if req.ReplenishType != nil {
|
|
product.ReplenishType = *req.ReplenishType
|
|
}
|
|
if req.CycleCount != nil {
|
|
product.CycleCount = *req.CycleCount
|
|
}
|
|
if req.LotRules != nil {
|
|
product.LotRules = *req.LotRules
|
|
}
|
|
if req.LeadTime != nil {
|
|
product.LeadTime = *req.LeadTime
|
|
}
|
|
if req.MultiplyRate != nil {
|
|
product.MultiplyRate = *req.MultiplyRate
|
|
}
|
|
if req.DivideRate != nil {
|
|
product.DivideRate = *req.DivideRate
|
|
}
|
|
if req.ClientID != nil {
|
|
product.ClientID = parseUUID(*req.ClientID)
|
|
}
|
|
if req.CategoryID != nil {
|
|
id := parseUUID(*req.CategoryID)
|
|
product.CategoryID = &id
|
|
}
|
|
if req.UomID != nil {
|
|
id := parseUUID(*req.UomID)
|
|
product.UomID = &id
|
|
}
|
|
if req.DimUomID != nil {
|
|
id := parseUUID(*req.DimUomID)
|
|
product.DimUomID = &id
|
|
}
|
|
if req.WeightUomID != nil {
|
|
id := parseUUID(*req.WeightUomID)
|
|
product.WeightUomID = &id
|
|
}
|
|
if req.VolumeUomID != nil {
|
|
id := parseUUID(*req.VolumeUomID)
|
|
product.VolumeUomID = &id
|
|
}
|
|
if req.MinStockUomID != nil {
|
|
id := parseUUID(*req.MinStockUomID)
|
|
product.MinStockUomID = &id
|
|
}
|
|
if req.MaxStockUomID != nil {
|
|
id := parseUUID(*req.MaxStockUomID)
|
|
product.MaxStockUomID = &id
|
|
}
|
|
if req.LeadTimeUomID != nil {
|
|
id := parseUUID(*req.LeadTimeUomID)
|
|
product.LeadTimeUomID = &id
|
|
}
|
|
if req.UomToUomID != nil {
|
|
id := parseUUID(*req.UomToUomID)
|
|
product.UomToUomID = &id
|
|
}
|
|
|
|
product.FullAuditTrail = utils.FillAuditTrail(ctx, constants.UPDATE)
|
|
updated, err := s.productRepo.Update(ctx, tx, product)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return dto.ProductResponse{}, err
|
|
}
|
|
err = tx.Commit().Error
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return dto.ProductResponse{}, err
|
|
}
|
|
s.log.WithFields(logrus.Fields{
|
|
"user_id": utils.GetUserID(ctx),
|
|
"action": "delete",
|
|
"entity": "product",
|
|
"entity_id": productId,
|
|
}).Info("Product Updated")
|
|
return s.GetById(ctx, updated.ID.String())
|
|
}
|
|
|
|
func (s *productService) Delete(ctx context.Context, productId string) error {
|
|
tx := s.db.Begin()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
product, err := s.productRepo.GetById(ctx, tx, productId)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
product.FullAuditTrail = utils.FillAuditTrail(ctx, constants.DELETE)
|
|
if _, err := s.productRepo.Update(ctx, tx, product); err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
err = s.productRepo.Delete(ctx, tx, productId)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
err = tx.Commit().Error
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
s.log.WithFields(logrus.Fields{
|
|
"user_id": utils.GetUserID(ctx),
|
|
"action": "delete",
|
|
"entity": "product",
|
|
"entity_id": productId,
|
|
}).Info("Product Deleted")
|
|
return nil
|
|
}
|
|
|
|
// Helper
|
|
func parseUUID(id string) uuid.UUID {
|
|
u, err := uuid.Parse(id)
|
|
if err != nil {
|
|
return uuid.Nil
|
|
}
|
|
return u
|
|
}
|