wms-be/modules/product/service/product_service.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
}