wms-be/modules/inventory_receipt/service/inventory_receipt_service.go

677 lines
22 KiB
Go

package service
import (
"context"
"errors"
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
dtodomain "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_receipt/dto"
"github.com/Caknoooo/go-gin-clean-starter/modules/inventory_receipt/query"
"github.com/Caknoooo/go-gin-clean-starter/modules/inventory_receipt/repository"
invstoragerepository "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_storage/repository"
productrepository "github.com/Caknoooo/go-gin-clean-starter/modules/product/repository"
sequenceservice "github.com/Caknoooo/go-gin-clean-starter/modules/sequence/service"
uomrepository "github.com/Caknoooo/go-gin-clean-starter/modules/uom/repository"
"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 InventoryReceiptService interface {
Create(ctx context.Context, req dtodomain.InventoryReceiptCreateRequest) (dtodomain.InventoryReceiptResponse, error)
GetById(ctx context.Context, id string) (dtodomain.InventoryReceiptResponse, error)
GetAll(ctx context.Context, filter query.InventoryReceiptFilter) ([]dtodomain.InventoryReceiptResponse, int64, error)
Update(ctx context.Context, req dtodomain.InventoryReceiptUpdateRequest, id string) (dtodomain.InventoryReceiptResponse, error)
Delete(ctx context.Context, id string) error
GetLinesByReceiptId(ctx context.Context, id string) ([]dtodomain.InventoryReceiptLineResponse, error)
GetLineById(ctx context.Context, lineId string) (dtodomain.InventoryReceiptLineResponse, error)
CreateLine(ctx context.Context, receiptId string, req dtodomain.InventoryReceiptLineCreateRequest) (dtodomain.InventoryReceiptLineResponse, error)
UpdateLine(ctx context.Context, lineId string, req dtodomain.InventoryReceiptLineUpdateRequest) (dtodomain.InventoryReceiptLineResponse, error)
DeleteLine(ctx context.Context, lineId string) error
OnComplete(ctx context.Context, id string) (dtodomain.InventoryReceiptResponse, error)
}
type inventoryReceiptService struct {
db *gorm.DB
receiptRepo repository.InventoryReceiptRepository
receiptLineRepo repository.InventoryReceiptLineRepository
productRepo productrepository.ProductRepository
uomRepo uomrepository.UomRepository
invStorageRepository invstoragerepository.InventoryStorageRepository
sequenceService sequenceservice.SequenceService
log *logrus.Logger
}
// GetLineById implements InventoryReceiptService.
func (s *inventoryReceiptService) GetLineById(ctx context.Context, lineId string) (dtodomain.InventoryReceiptLineResponse, error) {
line, err := s.receiptLineRepo.GetById(ctx, nil, lineId)
if err != nil {
return dtodomain.InventoryReceiptLineResponse{}, err
}
return dtodomain.ToInventoryReceiptLineResponse(line), nil
}
func (s *inventoryReceiptService) GetLinesByReceiptId(ctx context.Context, id string) ([]dtodomain.InventoryReceiptLineResponse, error) {
lines, err := s.receiptLineRepo.GetAllByReceiptId(ctx, id)
if err != nil {
return nil, err
}
var responses []dtodomain.InventoryReceiptLineResponse
for _, line := range lines {
responses = append(responses, dtodomain.ToInventoryReceiptLineResponse(line))
}
return responses, nil
}
func (s *inventoryReceiptService) OnComplete(ctx context.Context, id string) (dtodomain.InventoryReceiptResponse, error) {
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
receipt, err := s.receiptRepo.GetById(ctx, tx, id)
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
receipt.Status = constants.COMPLETED
for _, line := range receipt.ReceiptLines {
product, err := s.productRepo.GetById(ctx, tx, line.ProductID.String())
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
existing, err := s.invStorageRepository.GetLatestByProductAndClient(ctx, tx, product.ID.String(), receipt.ClientID.String())
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
if existing.ID != uuid.Nil {
existing.OnHandQuantity += line.Quantity
existing.AvailableQuantity += line.Quantity
_, err = s.invStorageRepository.Update(ctx, tx, existing)
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
} else {
newStorage := entities.InventoryStorageEntity{
ProductID: line.ProductID,
UomID: *product.UomID,
OnHandQuantity: line.Quantity,
AvailableQuantity: line.Quantity,
InvReceiptID: receipt.ID,
ClientID: receipt.ClientID,
}
_, err = s.invStorageRepository.Create(ctx, tx, newStorage)
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
}
}
if _, err := s.receiptRepo.Update(ctx, tx, receipt); err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
tx.Commit()
return toInventoryReceiptResponse(receipt), nil
}
// DeleteLine implements InventoryReceiptService.
func (s *inventoryReceiptService) DeleteLine(ctx context.Context, lineId string) error {
line, err := s.receiptLineRepo.GetById(ctx, nil, lineId)
if err != nil {
return err
}
line.FullAuditTrail = utils.FillAuditTrail(ctx, constants.DELETE)
_, err = s.receiptLineRepo.Update(ctx, nil, line)
if err != nil {
return err
}
result := s.receiptLineRepo.Delete(ctx, nil, lineId)
if result != nil {
return result
}
s.log.WithFields(logrus.Fields{
"user_id": utils.GetUserID(ctx),
"action": "delete",
"entity": "inventory_receipt_line",
"entity_id": lineId,
}).Info("Inventory Receipt Line deleted")
return nil
}
// UpdateLine implements InventoryReceiptService.
func (s *inventoryReceiptService) UpdateLine(ctx context.Context, lineId string, req dtodomain.InventoryReceiptLineUpdateRequest) (dtodomain.InventoryReceiptLineResponse, error) {
line, err := s.receiptLineRepo.GetById(ctx, nil, lineId)
if err != nil {
return dtodomain.InventoryReceiptLineResponse{}, err
}
before := line
if req.Quantity != nil {
line.Quantity = *req.Quantity
}
if req.BatchNumber != nil {
line.BatchNumber = *req.BatchNumber
}
if req.RepackingSuggestion != nil {
line.RepackingSuggestion = *req.RepackingSuggestion
}
if req.RepackUomID != nil {
if *req.RepackUomID != "" {
tmp, err := uuid.Parse(*req.RepackUomID)
if err != nil {
return dtodomain.InventoryReceiptLineResponse{}, err
}
line.RepackUomID = &tmp
} else {
line.RepackUomID = nil
}
}
if req.ProductID != nil {
if *req.ProductID != "" {
tmp, err := uuid.Parse(*req.ProductID)
if err != nil {
return dtodomain.InventoryReceiptLineResponse{}, err
}
line.ProductID = tmp
} else {
line.ProductID = uuid.Nil
}
}
line.FullAuditTrail = utils.FillAuditTrail(ctx, constants.UPDATE)
updated, err := s.receiptLineRepo.Update(ctx, nil, line)
if err != nil {
return dtodomain.InventoryReceiptLineResponse{}, err
}
changes := utils.GetChangedFields(before, updated)
s.log.WithFields(logrus.Fields{
"user_id": utils.GetUserID(ctx),
"action": "update",
"entity": "inventory_receipt_line",
"entity_id": lineId,
"changes": changes,
}).Info("Inventory Receipt Line updated")
var repackUomID *string
if updated.RepackUomID != nil {
tmp := updated.RepackUomID.String()
repackUomID = &tmp
}
product := dtodomain.InventoryReceiptLineProductResponse{}
if updated.Product.ID != uuid.Nil {
product = dtodomain.InventoryReceiptLineProductResponse{
ID: updated.Product.ID.String(),
Name: updated.Product.Name,
}
}
return dtodomain.InventoryReceiptLineResponse{
ID: updated.ID.String(),
Quantity: updated.Quantity,
BatchNumber: updated.BatchNumber,
RepackingSuggestion: updated.RepackingSuggestion,
RepackUomID: repackUomID,
Product: product,
ClientID: updated.ClientID.String(),
}, nil
}
func toAssignmentResponse(e entities.TAssignmentEntity) dtodomain.AssignmentResponse {
// client := pkgdto.IdNameResponse{}
// if e.Client.ID != uuid.Nil {
// client = pkgdto.IdNameResponse{
// ID: e.Client.ID.String(),
// Name: e.Client.Name,
// }
// }
users := make([]dtodomain.AssignmentUserResponse, 0)
for _, user := range e.AssignmentUsers {
userResp := dtodomain.AssignmentUserResponse{
ID: user.ID.String(),
TaskType: user.TaskType,
User: pkgdto.IdNameResponse{ID: user.User.ID.String(), Name: user.User.Name},
Role: pkgdto.IdNameResponse{ID: user.Role.ID.String(), Name: user.Role.Name},
// Client: pkgdto.IdNameResponse{ID: user.Client.ID.String(), Name: user.Client.Name},
}
users = append(users, userResp)
}
return dtodomain.AssignmentResponse{
ID: e.ID.String(),
DocumentType: e.DocumentType,
DocumentID: e.DocumentID.String(),
// Client: client,
AssignmentUsers: users,
}
}
func toInventoryReceiptResponse(e entities.TInventoryReceiptEntity) dtodomain.InventoryReceiptResponse {
client := pkgdto.IdNameResponse{}
if e.Client.ID != uuid.Nil {
client = pkgdto.IdNameResponse{
ID: e.Client.ID.String(),
Name: e.Client.Name,
}
}
// lines := make([]dtodomain.InventoryReceiptLineResponse, 0)
// for _, line := range e.ReceiptLines {
// product := dtodomain.InventoryReceiptLineProductResponse{}
// if line.Product.ID != uuid.Nil {
// product = dtodomain.InventoryReceiptLineProductResponse{
// ID: line.Product.ID.String(),
// Name: line.Product.Name,
// RefNumber: line.Product.RefNumber,
// Uom: pkgdto.IdNameResponse{
// ID: line.Product.Uom.ID.String(),
// Name: line.Product.Uom.Name,
// },
// DimLength: line.Product.DimLength,
// DimWidth: line.Product.DimWidth,
// DimHeight: line.Product.DimHeight,
// DimUom: pkgdto.IdNameResponse{
// ID: line.Product.DimUom.ID.String(),
// Name: line.Product.DimUom.Name,
// },
// }
// }
// var repackUomID *string
// if line.RepackUomID != nil {
// tmp := line.RepackUomID.String()
// repackUomID = &tmp
// } else {
// repackUomID = nil
// }
// lines = append(lines, dtodomain.InventoryReceiptLineResponse{
// ID: line.ID.String(),
// Quantity: line.Quantity,
// BatchNumber: line.BatchNumber,
// RepackingSuggestion: line.RepackingSuggestion,
// RepackUomID: repackUomID,
// Product: product,
// ClientID: line.ClientID.String(),
// })
// }
var assignment *dtodomain.AssignmentResponse
if e.Assignment.ID != uuid.Nil {
assignmentObj := toAssignmentResponse(e.Assignment)
assignment = &assignmentObj
} else {
assignment = nil
}
return dtodomain.InventoryReceiptResponse{
ID: e.ID.String(),
ReferenceNumber: e.ReferenceNumber,
DocumentNumber: e.DocumentNumber,
Status: e.Status,
DocumentDate: utils.DateTimeToString(e.DocumentDate),
Source: e.Source,
QrCodeFile: e.QrCodeFile,
// ClientID: e.ClientID.String(),
Client: client,
// LineCount: len(lines),
// ReceiptLines: lines,
Assignment: assignment,
}
}
func (s *inventoryReceiptService) Create(ctx context.Context, req dtodomain.InventoryReceiptCreateRequest) (dtodomain.InventoryReceiptResponse, error) {
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
clientUUID, err := uuid.Parse(req.ClientID)
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
docNum, err := s.sequenceService.GenerateDocumentNumber(ctx, req.ClientID, "RCPT", pkgdto.SequenceConfig{
Prefix: "RCPT",
EntityType: "INV_RECEIPT",
Period: "",
})
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
receipt := entities.TInventoryReceiptEntity{
ReferenceNumber: req.ReferenceNumber,
DocumentNumber: docNum,
DocumentDate: utils.StringToDateTime(req.DocumentDate),
Source: req.Source,
QrCodeFile: req.QrCodeFile,
ClientID: clientUUID,
Status: req.Status,
FullAuditTrail: utils.FillAuditTrail(ctx, constants.CREATE),
}
created, err := s.receiptRepo.Create(ctx, tx, receipt)
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
// Bulk create lines
var lines []entities.TInventoryReceiptLineEntity
// var invStorages []entities.InventoryStorageEntity
for _, lineReq := range req.ReceiptLines {
var productUUID uuid.UUID
if lineReq.ProductID != "" {
productUUID, err = uuid.Parse(lineReq.ProductID)
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
} else if lineReq.ProductCode != "" {
product, err := s.productRepo.GetByCode(ctx, tx, lineReq.ProductCode, req.ClientID)
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
productUUID = product.ID
} else {
productUUID = uuid.Nil
}
var repackUomUUID *uuid.UUID
if lineReq.RepackUomID != "" {
tmp, err := uuid.Parse(lineReq.RepackUomID)
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
repackUomUUID = &tmp
} else if lineReq.RepackUomCode != "" {
uom, err := s.uomRepo.GetByCode(ctx, tx, lineReq.RepackUomCode, req.ClientID)
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
repackUomUUID = &uom.ID
} else {
repackUomUUID = nil
}
clientLineUUID, err := uuid.Parse(lineReq.ClientID)
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
lines = append(lines, entities.TInventoryReceiptLineEntity{
Quantity: lineReq.Quantity,
BatchNumber: lineReq.BatchNumber,
RepackingSuggestion: lineReq.RepackingSuggestion,
RepackUomID: repackUomUUID,
InvReceiptID: created.ID,
ProductID: productUUID,
ClientID: clientLineUUID,
})
// // Prepare inventory storage entity
// product, err := s.productRepo.GetById(ctx, tx, productUUID.String())
// if err != nil {
// tx.Rollback()
// return dtodomain.InventoryReceiptResponse{}, err
// }
// invStorages = append(invStorages, entities.InventoryStorageEntity{
// ProductID: productUUID,
// UomID: *product.UomID,
// OnHandQuantity: 0,
// AvailableQuantity: 0,
// InvReceiptID: created.ID,
// ClientID: clientUUID,
// })
}
if len(lines) > 0 {
err = s.receiptLineRepo.BulkCreate(ctx, tx, lines)
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
}
// if len(invStorages) > 0 {
// err = s.invStorageRepository.BulkCreate(ctx, tx, invStorages)
// if err != nil {
// tx.Rollback()
// return dtodomain.InventoryReceiptResponse{}, err
// }
// }
tx.Commit()
s.log.WithFields(logrus.Fields{
"user_id": utils.GetUserID(ctx),
"action": "create",
"entity": "inventory_receipt",
"entity_id": created.ID.String(),
}).Info("Inventory Receipt created")
result, err := s.receiptRepo.GetById(ctx, nil, created.ID.String())
if err != nil {
return dtodomain.InventoryReceiptResponse{}, err
}
return toInventoryReceiptResponse(result), nil
}
func (s *inventoryReceiptService) GetById(ctx context.Context, id string) (dtodomain.InventoryReceiptResponse, error) {
receipt, err := s.receiptRepo.GetById(ctx, nil, id)
if err != nil {
return dtodomain.InventoryReceiptResponse{}, err
}
return toInventoryReceiptResponse(receipt), nil
}
func (s *inventoryReceiptService) GetAll(ctx context.Context, filter query.InventoryReceiptFilter) ([]dtodomain.InventoryReceiptResponse, int64, error) {
receipts, total, err := s.receiptRepo.GetAll(ctx, filter)
if err != nil {
return nil, 0, err
}
var responses []dtodomain.InventoryReceiptResponse
for _, e := range receipts {
responses = append(responses, toInventoryReceiptResponse(e))
}
if responses == nil {
responses = make([]dtodomain.InventoryReceiptResponse, 0)
}
return responses, total, nil
}
func (s *inventoryReceiptService) Update(ctx context.Context, req dtodomain.InventoryReceiptUpdateRequest, id string) (dtodomain.InventoryReceiptResponse, error) {
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
receipt, err := s.receiptRepo.GetById(ctx, tx, id)
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
before := receipt
if req.ReferenceNumber != "" {
receipt.ReferenceNumber = req.ReferenceNumber
}
receipt.DocumentDate = utils.StringToDateTime(req.DocumentDate)
receipt.Source = req.Source
receipt.QrCodeFile = req.QrCodeFile
receipt.Status = req.Status
receipt.FullAuditTrail = utils.FillAuditTrail(ctx, constants.UPDATE)
updated, err := s.receiptRepo.Update(ctx, tx, receipt)
if err != nil {
tx.Rollback()
return dtodomain.InventoryReceiptResponse{}, err
}
tx.Commit()
result, err := s.receiptRepo.GetById(ctx, nil, updated.ID.String())
if err != nil {
return dtodomain.InventoryReceiptResponse{}, err
}
changes := utils.GetChangedFields(before, result)
s.log.WithFields(logrus.Fields{
"user_id": utils.GetUserID(ctx),
"action": "update",
"entity": "inventory_receipt",
"entity_id": id,
"changes": changes,
}).Info("Inventory Receipt updated")
return toInventoryReceiptResponse(result), nil
}
func (s *inventoryReceiptService) Delete(ctx context.Context, id string) error {
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
receipt, err := s.receiptRepo.GetById(ctx, tx, id)
if err != nil {
tx.Rollback()
return err
}
receipt.FullAuditTrail = utils.FillAuditTrail(ctx, constants.DELETE)
_, err = s.receiptRepo.Update(ctx, tx, receipt)
if err != nil {
tx.Rollback()
return err
}
if err := s.receiptRepo.Delete(ctx, tx, id); err != nil {
tx.Rollback()
return err
}
tx.Commit()
s.log.WithFields(logrus.Fields{
"user_id": utils.GetUserID(ctx),
"action": "delete",
"entity": "inventory_receipt",
"entity_id": id,
}).Info("Inventory Receipt deleted")
return nil
}
func (s *inventoryReceiptService) CreateLine(ctx context.Context, receiptId string, req dtodomain.InventoryReceiptLineCreateRequest) (dtodomain.InventoryReceiptLineResponse, error) {
receiptUUID, err := uuid.Parse(receiptId)
if err != nil {
return dtodomain.InventoryReceiptLineResponse{}, err
}
var productUUID uuid.UUID
if req.ProductID != "" {
productUUID, err = uuid.Parse(req.ProductID)
if err != nil {
return dtodomain.InventoryReceiptLineResponse{}, err
}
} else if req.ProductCode != "" {
product, err := s.productRepo.GetByCode(ctx, nil, req.ProductCode, req.ClientID)
if err != nil {
return dtodomain.InventoryReceiptLineResponse{}, err
}
productUUID = product.ID
} else {
productUUID = uuid.Nil
}
var repackUomUUID *uuid.UUID
if req.RepackUomID != "" {
tmp, err := uuid.Parse(req.RepackUomID)
if err != nil {
return dtodomain.InventoryReceiptLineResponse{}, err
}
repackUomUUID = &tmp
} else if req.RepackUomCode != "" {
uom, err := s.uomRepo.GetByCode(ctx, nil, req.RepackUomCode, req.ClientID)
if err != nil {
return dtodomain.InventoryReceiptLineResponse{}, err
}
repackUomUUID = &uom.ID
} else {
repackUomUUID = nil
}
clientLineUUID, err := uuid.Parse(req.ClientID)
if err != nil {
return dtodomain.InventoryReceiptLineResponse{}, err
}
line := entities.TInventoryReceiptLineEntity{
Quantity: req.Quantity,
BatchNumber: req.BatchNumber,
RepackingSuggestion: req.RepackingSuggestion,
RepackUomID: repackUomUUID,
InvReceiptID: receiptUUID,
ProductID: productUUID,
ClientID: clientLineUUID,
FullAuditTrail: utils.FillAuditTrail(ctx, constants.CREATE),
}
created, err := s.receiptLineRepo.Create(ctx, nil, line)
if err != nil {
return dtodomain.InventoryReceiptLineResponse{}, err
}
s.log.WithFields(logrus.Fields{
"user_id": utils.GetUserID(ctx),
"action": "create",
"entity": "inventory_receipt_line",
"entity_id": created.ID.String(),
}).Info("Inventory Receipt Line created")
var repackUomID *string
if created.RepackUomID != nil {
tmp := created.RepackUomID.String()
repackUomID = &tmp
}
product := dtodomain.InventoryReceiptLineProductResponse{}
if created.Product.ID != uuid.Nil {
product = dtodomain.InventoryReceiptLineProductResponse{
ID: created.Product.ID.String(),
Name: created.Product.Name,
}
}
return dtodomain.InventoryReceiptLineResponse{
ID: created.ID.String(),
Quantity: created.Quantity,
BatchNumber: created.BatchNumber,
RepackingSuggestion: created.RepackingSuggestion,
RepackUomID: repackUomID,
Product: product,
ClientID: created.ClientID.String(),
}, nil
}
func NewInventoryReceiptService(db *gorm.DB,
receiptRepo repository.InventoryReceiptRepository,
receiptLineRepo repository.InventoryReceiptLineRepository,
productRepo productrepository.ProductRepository,
uomRepo uomrepository.UomRepository,
invStorageRepository invstoragerepository.InventoryStorageRepository,
sequenceService sequenceservice.SequenceService, log *logrus.Logger) InventoryReceiptService {
return &inventoryReceiptService{
db: db,
receiptRepo: receiptRepo,
receiptLineRepo: receiptLineRepo,
productRepo: productRepo,
uomRepo: uomRepo,
invStorageRepository: invStorageRepository,
sequenceService: sequenceService,
log: log,
}
}