470 lines
13 KiB
Go
470 lines
13 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
|
|
"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"
|
|
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
|
|
}
|
|
|
|
type productService struct {
|
|
db *gorm.DB
|
|
productRepo repository.ProductRepository
|
|
}
|
|
|
|
// 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) ProductService {
|
|
return &productService{
|
|
productRepo: productRepo,
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
}()
|
|
refNumber, err := entities.GenerateRefNumberProduct(tx, req.ClientID, *req.CategoryID)
|
|
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,
|
|
// 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,
|
|
}
|
|
// UUID fields
|
|
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
|
|
}
|
|
// 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
|
|
}
|
|
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 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, 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
|
|
}
|
|
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
|
|
}
|
|
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()
|
|
}
|
|
}()
|
|
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
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Helper
|
|
func parseUUID(id string) uuid.UUID {
|
|
u, err := uuid.Parse(id)
|
|
if err != nil {
|
|
return uuid.Nil
|
|
}
|
|
return u
|
|
}
|
|
|
|
func mapProductToResponse(product entities.MProductEntity) dto.ProductResponse {
|
|
crossRefs := make([]dto.ProductVendorResponse, 0, len(product.CrossReferences))
|
|
for _, v := range product.CrossReferences {
|
|
crossRefs = append(crossRefs, dto.ProductVendorResponse{
|
|
ID: v.Vendor.ID.String(),
|
|
Name: v.Vendor.Name,
|
|
Address: v.Vendor.Address,
|
|
ContactPerson: v.Vendor.ContactPerson,
|
|
SearchKey: v.Vendor.SearchKey,
|
|
})
|
|
}
|
|
|
|
logrus.Infof("Inventory Transactions Count: %d", len(product.InventoryTransactions))
|
|
invTransactions := make([]dto.ProductInventoryTransactionResponse, 0, len(product.InventoryTransactions))
|
|
for _, it := range product.InventoryTransactions {
|
|
var transactionQuantity float64
|
|
var lot, locater, invReceiptRef, invIssueRef, invMoveRef string
|
|
var transactionDate string
|
|
|
|
// Receipt
|
|
if it.InvReceipt.ID != uuid.Nil {
|
|
invReceiptRef = it.InvReceipt.ReferenceNumber
|
|
transactionDate = utils.DateTimeToString(it.TransactionDate)
|
|
// Cari line yang sesuai product
|
|
for _, line := range it.InvReceipt.ReceiptLines {
|
|
if line.ProductID == it.ProductID {
|
|
transactionQuantity = line.Quantity
|
|
lot = line.BatchNumber
|
|
// Jika ada field lokasi, isi di sini
|
|
// locater = line.Locater
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Issue
|
|
if it.InvIssue.ID != uuid.Nil {
|
|
invIssueRef = it.InvIssue.DocumentNumber
|
|
transactionDate = utils.DateTimeToString(it.TransactionDate)
|
|
for _, line := range it.InvIssue.IssueLines {
|
|
if line.ProductID == it.ProductID {
|
|
transactionQuantity = line.IssuedQuantity
|
|
// lot = line.BatchNumber
|
|
// locater = line.Locater
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Move
|
|
if it.InvMove.ID != uuid.Nil {
|
|
invMoveRef = it.InvMove.MovementNumber
|
|
transactionDate = utils.DateTimeToString(it.TransactionDate)
|
|
for _, line := range it.InvMove.MovementLines {
|
|
if line.ProductID == it.ProductID {
|
|
transactionQuantity = line.MovedQuantity
|
|
// lot = line.BatchNumber
|
|
// locater = line.Locater
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
invTransactions = append(invTransactions, dto.ProductInventoryTransactionResponse{
|
|
ID: it.ID.String(),
|
|
TransactionDate: transactionDate,
|
|
TransactionType: it.TransactionType,
|
|
TransactionQuantity: transactionQuantity,
|
|
Lot: lot,
|
|
Locater: locater,
|
|
InvReceiptRef: invReceiptRef,
|
|
InvIssueRef: invIssueRef,
|
|
InvMoveRef: invMoveRef,
|
|
})
|
|
}
|
|
|
|
return dto.ProductResponse{
|
|
ID: product.ID.String(),
|
|
Name: product.Name,
|
|
RefNumber: product.RefNumber,
|
|
SKU: product.SKU,
|
|
Description: product.Description,
|
|
Status: product.Status,
|
|
IsReturnable: product.IsReturnable,
|
|
DimLength: product.DimLength,
|
|
DimWidth: product.DimWidth,
|
|
DimHeight: product.DimHeight,
|
|
Weight: product.Weight,
|
|
Volume: product.Volume,
|
|
MaxStackHeight: product.MaxStackHeight,
|
|
Temperature: product.Temperature,
|
|
IsHazardous: product.IsHazardous,
|
|
MinStock: product.MinStock,
|
|
MaxStock: product.MaxStock,
|
|
ReplenishType: product.ReplenishType,
|
|
CycleCount: product.CycleCount,
|
|
LotRules: product.LotRules,
|
|
LeadTime: product.LeadTime,
|
|
MultiplyRate: product.MultiplyRate,
|
|
DivideRate: product.DivideRate,
|
|
Client: pkgdto.IdNameResponse{
|
|
ID: product.Client.ID.String(),
|
|
Name: product.Client.Name,
|
|
},
|
|
Category: pkgdto.IdNameResponse{
|
|
ID: product.Category.ID.String(),
|
|
Name: product.Category.Name,
|
|
},
|
|
Uom: pkgdto.IdNameResponse{
|
|
ID: product.Uom.ID.String(),
|
|
Name: product.Uom.Name,
|
|
},
|
|
DimUom: pkgdto.IdNameResponse{
|
|
ID: product.DimUom.ID.String(),
|
|
Name: product.DimUom.Name,
|
|
},
|
|
WeightUom: pkgdto.IdNameResponse{
|
|
ID: product.WeightUom.ID.String(),
|
|
Name: product.WeightUom.Name,
|
|
},
|
|
VolumeUom: pkgdto.IdNameResponse{
|
|
ID: product.VolumeUom.ID.String(),
|
|
Name: product.VolumeUom.Name,
|
|
},
|
|
MinStockUom: pkgdto.IdNameResponse{
|
|
ID: product.MinStockUom.ID.String(),
|
|
Name: product.MinStockUom.Name,
|
|
},
|
|
MaxStockUom: pkgdto.IdNameResponse{
|
|
ID: product.MaxStockUom.ID.String(),
|
|
Name: product.MaxStockUom.Name,
|
|
},
|
|
LeadTimeUom: pkgdto.IdNameResponse{
|
|
ID: product.LeadTimeUom.ID.String(),
|
|
Name: product.LeadTimeUom.Name,
|
|
},
|
|
UomToUom: pkgdto.IdNameResponse{
|
|
ID: product.UomToUom.ID.String(),
|
|
Name: product.UomToUom.Name,
|
|
},
|
|
CrossReferences: crossRefs,
|
|
InvTransactions: invTransactions,
|
|
}
|
|
}
|