|
|
|
@@ -4,6 +4,7 @@ import (
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"mime/multipart"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/go-playground/validator/v10"
|
|
|
|
@@ -27,7 +28,7 @@ import (
|
|
|
|
|
type TransferService interface {
|
|
|
|
|
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.StockTransfer, int64, error)
|
|
|
|
|
GetOne(ctx *fiber.Ctx, id uint) (*entity.StockTransfer, error)
|
|
|
|
|
CreateOne(ctx *fiber.Ctx, req *validation.TransferRequest) (*entity.StockTransfer, error)
|
|
|
|
|
CreateOne(ctx *fiber.Ctx, req *validation.TransferRequest, files []*multipart.FileHeader) (*entity.StockTransfer, error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type transferService struct {
|
|
|
|
@@ -42,9 +43,10 @@ type transferService struct {
|
|
|
|
|
SupplierRepo rSupplier.SupplierRepository
|
|
|
|
|
WarehouseRepo warehouseRepo.WarehouseRepository
|
|
|
|
|
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
|
|
|
|
DocumentSvc commonSvc.DocumentService
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTransfer.StockTransferRepository, stockTransferDetailRepo rStockTransfer.StockTransferDetailRepository, stockTransferDeliveryRepo rStockTransfer.StockTransferDeliveryRepository, stockTransferDeliveryItemRepo rStockTransfer.StockTransferDeliveryItemRepository, stockLogsRepo rStockLogs.StockLogRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, supplierRepo rSupplier.SupplierRepository, warehouseRepo warehouseRepo.WarehouseRepository, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository) TransferService {
|
|
|
|
|
func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTransfer.StockTransferRepository, stockTransferDetailRepo rStockTransfer.StockTransferDetailRepository, stockTransferDeliveryRepo rStockTransfer.StockTransferDeliveryRepository, stockTransferDeliveryItemRepo rStockTransfer.StockTransferDeliveryItemRepository, stockLogsRepo rStockLogs.StockLogRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, supplierRepo rSupplier.SupplierRepository, warehouseRepo warehouseRepo.WarehouseRepository, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository, documentSvc commonSvc.DocumentService) TransferService {
|
|
|
|
|
return &transferService{
|
|
|
|
|
Log: utils.Log,
|
|
|
|
|
Validate: validate,
|
|
|
|
@@ -57,6 +59,7 @@ func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTr
|
|
|
|
|
SupplierRepo: supplierRepo,
|
|
|
|
|
WarehouseRepo: warehouseRepo,
|
|
|
|
|
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
|
|
|
|
DocumentSvc: documentSvc,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -72,7 +75,10 @@ func (s transferService) withRelations(db *gorm.DB) *gorm.DB {
|
|
|
|
|
Preload("Details").
|
|
|
|
|
Preload("Details.Product").
|
|
|
|
|
Preload("Deliveries.Items").
|
|
|
|
|
Preload("Deliveries.Supplier")
|
|
|
|
|
Preload("Deliveries.Supplier").
|
|
|
|
|
Preload("Documents", func(db *gorm.DB) *gorm.DB {
|
|
|
|
|
return db.Where("documentable_type = ?", "STOCK_TRANSFER")
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s transferService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.StockTransfer, int64, error) {
|
|
|
|
@@ -94,31 +100,31 @@ func (s transferService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entit
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.Log.Infof("Retrieved %d transfers", len(transfers))
|
|
|
|
|
|
|
|
|
|
return transfers, total, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s transferService) GetOne(c *fiber.Ctx, id uint) (*entity.StockTransfer, error) {
|
|
|
|
|
var transfer entity.StockTransfer
|
|
|
|
|
s.Log.Infof("Attempting to get StockTransfer with ID: %d", id)
|
|
|
|
|
|
|
|
|
|
transferPtr, err := s.StockTransferRepo.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
|
|
|
|
return s.withRelations(db)
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.Log.Errorf("Error getting StockTransfer ID %d: %+v", id, err)
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
return nil, fiber.NewError(fiber.StatusNotFound, "Transfer not found")
|
|
|
|
|
}
|
|
|
|
|
s.Log.Errorf("Failed to get transfer by ID: %+v", err)
|
|
|
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.Log.Infof("Retrieved transfer: %+v", transfer)
|
|
|
|
|
if transferPtr != nil {
|
|
|
|
|
s.Log.Infof("StockTransfer %d has %d documents", transferPtr.Id, len(transferPtr.Documents))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return transferPtr, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferRequest) (*entity.StockTransfer, error) {
|
|
|
|
|
func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferRequest, files []*multipart.FileHeader) (*entity.StockTransfer, error) {
|
|
|
|
|
|
|
|
|
|
pwIDs := make([]uint, 0, len(req.Products))
|
|
|
|
|
|
|
|
|
@@ -180,7 +186,6 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|
|
|
|
|
|
|
|
|
seqNum, err := s.StockTransferRepo.GetNextMovementNumber(c.Context())
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.Log.Errorf("Failed to get next movement number: %+v", err)
|
|
|
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to generate movement number")
|
|
|
|
|
}
|
|
|
|
|
movementNumber := fmt.Sprintf("PND-MBU-%05d", seqNum)
|
|
|
|
@@ -198,10 +203,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|
|
|
|
err = s.StockTransferRepo.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
|
|
|
|
|
|
|
|
|
if err := s.StockTransferRepo.WithTx(tx).CreateOne(c.Context(), entityTransfer, nil); err != nil {
|
|
|
|
|
s.Log.Errorf("Failed to create stock transfer: %+v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
s.Log.Infof("Stock transfer created: %+v", entityTransfer.Id)
|
|
|
|
|
|
|
|
|
|
var details []*entity.StockTransferDetail
|
|
|
|
|
for _, product := range req.Products {
|
|
|
|
@@ -212,10 +215,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
if err := s.StockTransferDetailRepo.WithTx(tx).CreateMany(c.Context(), details, nil); err != nil {
|
|
|
|
|
s.Log.Errorf("Failed to create stock transfer details: %+v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
s.Log.Infof("Stock transfer details created for transfer ID: %+v", entityTransfer.Id)
|
|
|
|
|
|
|
|
|
|
var deliveries []*entity.StockTransferDelivery
|
|
|
|
|
for _, delivery := range req.Deliveries {
|
|
|
|
@@ -224,13 +225,11 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|
|
|
|
SupplierId: uint64(delivery.SupplierID),
|
|
|
|
|
VehiclePlate: delivery.VehiclePlate,
|
|
|
|
|
DriverName: delivery.DriverName,
|
|
|
|
|
DocumentPath: "https://tourism.gov.in/sites/default/files/2019-04/dummy-pdf_2.pdf",
|
|
|
|
|
ShippingCostItem: delivery.DeliveryCostPerItem,
|
|
|
|
|
ShippingCostTotal: delivery.DeliveryCost,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
if err := s.StockTransferDeliveryRepo.WithTx(tx).CreateMany(c.Context(), deliveries, nil); err != nil {
|
|
|
|
|
s.Log.Errorf("Failed to create stock transfer deliveries: %+v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -256,27 +255,46 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err := s.StockTransferDeliveryItemRepo.WithTx(tx).CreateMany(c.Context(), deliveryItems, nil); err != nil {
|
|
|
|
|
s.Log.Errorf("Failed to create stock transfer delivery items: %+v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
s.Log.Infof("Stock transfer delivery items created for transfer ID: %+v", entityTransfer.Id)
|
|
|
|
|
|
|
|
|
|
actorIDCopy := actorID
|
|
|
|
|
if s.DocumentSvc != nil && len(files) > 0 {
|
|
|
|
|
s.Log.Infof("Starting document upload for %d files", len(files))
|
|
|
|
|
documentFiles := make([]commonSvc.DocumentFile, 0, len(files))
|
|
|
|
|
for idx, file := range files {
|
|
|
|
|
docIndex := idx
|
|
|
|
|
documentFiles = append(documentFiles, commonSvc.DocumentFile{
|
|
|
|
|
File: file,
|
|
|
|
|
Type: "STOCK_TRANSFER_DOCUMENT",
|
|
|
|
|
Index: &docIndex,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
_, err := s.DocumentSvc.UploadDocuments(c.Context(), commonSvc.DocumentUploadRequest{
|
|
|
|
|
DocumentableType: "STOCK_TRANSFER",
|
|
|
|
|
DocumentableID: entityTransfer.Id,
|
|
|
|
|
CreatedBy: &actorIDCopy,
|
|
|
|
|
Files: documentFiles,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.Log.Errorf("Failed to upload documents for transfer %d: %+v", entityTransfer.Id, err)
|
|
|
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to upload transfer documents")
|
|
|
|
|
}
|
|
|
|
|
s.Log.Infof("Successfully uploaded documents for transfer ID %d", entityTransfer.Id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, product := range req.Products {
|
|
|
|
|
sourcePW, err := s.ProductWarehouseRepo.GetProductWarehouseByProductAndWarehouseID(c.Context(), uint(product.ProductID), uint(req.SourceWarehouseID))
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.Log.Errorf("Failed to get source product warehouse: %+v", err)
|
|
|
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get source product warehouse")
|
|
|
|
|
}
|
|
|
|
|
if sourcePW.Quantity < product.ProductQty {
|
|
|
|
|
s.Log.Errorf("Insufficient stock in source warehouse for product ID: %+v", product.ProductID)
|
|
|
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient stock in source warehouse for product ID: %d", product.ProductID))
|
|
|
|
|
}
|
|
|
|
|
sourcePW.Quantity -= product.ProductQty
|
|
|
|
|
if err := s.ProductWarehouseRepo.WithTx(tx).UpdateOne(c.Context(), sourcePW.Id, sourcePW, nil); err != nil {
|
|
|
|
|
s.Log.Errorf("Failed to update source product warehouse: %+v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
s.Log.Infof("Source product warehouse updated: %+v", sourcePW.Id)
|
|
|
|
|
|
|
|
|
|
decreaseLog := &entity.StockLog{
|
|
|
|
|
Decrease: product.ProductQty,
|
|
|
|
@@ -287,7 +305,6 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|
|
|
|
CreatedBy: actorID,
|
|
|
|
|
}
|
|
|
|
|
if err := s.StockLogsRepository.WithTx(tx).CreateOne(c.Context(), decreaseLog, nil); err != nil {
|
|
|
|
|
s.Log.Errorf("Failed to create stock log decrease: %+v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -295,7 +312,6 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|
|
|
|
c.Context(), uint(product.ProductID), uint(req.DestinationWarehouseID),
|
|
|
|
|
)
|
|
|
|
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
s.Log.Errorf("Failed to get destination product warehouse: %+v", err)
|
|
|
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get destination product warehouse")
|
|
|
|
|
}
|
|
|
|
|
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
@@ -311,18 +327,14 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|
|
|
|
ProjectFlockKandangId: &projectFlockKandangID,
|
|
|
|
|
}
|
|
|
|
|
if err := s.ProductWarehouseRepo.WithTx(tx).CreateOne(c.Context(), destPW, nil); err != nil {
|
|
|
|
|
s.Log.Errorf("Failed to create destination product warehouse: %+v", err)
|
|
|
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create destination product warehouse")
|
|
|
|
|
}
|
|
|
|
|
s.Log.Infof("Destination product warehouse created: %+v", destPW.Id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
destPW.Quantity += product.ProductQty
|
|
|
|
|
if err := s.ProductWarehouseRepo.WithTx(tx).UpdateOne(c.Context(), destPW.Id, destPW, nil); err != nil {
|
|
|
|
|
s.Log.Errorf("Failed to update destination product warehouse: %+v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
s.Log.Infof("Destination product warehouse updated: %+v", destPW.Id)
|
|
|
|
|
|
|
|
|
|
increaseLog := &entity.StockLog{
|
|
|
|
|
Increase: product.ProductQty,
|
|
|
|
@@ -333,7 +345,6 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|
|
|
|
CreatedBy: actorID,
|
|
|
|
|
}
|
|
|
|
|
if err := s.StockLogsRepository.WithTx(tx).CreateOne(c.Context(), increaseLog, nil); err != nil {
|
|
|
|
|
s.Log.Errorf("Failed to create stock log increase: %+v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -343,7 +354,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.Log.Errorf("Transaction failed in CreateOne: %+v", err)
|
|
|
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to process transfer transaction")
|
|
|
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to process transfer transaction: %v", err))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result, err := s.GetOne(c, uint(entityTransfer.Id))
|
|
|
|
@@ -359,7 +370,6 @@ func (s *transferService) getActiveProjectFlockKandangID(ctx context.Context, wa
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
return 0, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Gudang dengan ID %d tidak ditemukan", warehouseID))
|
|
|
|
|
}
|
|
|
|
|
s.Log.Errorf("Failed to get warehouse %d: %+v", warehouseID, err)
|
|
|
|
|
return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data gudang")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -372,7 +382,6 @@ func (s *transferService) getActiveProjectFlockKandangID(ctx context.Context, wa
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
return 0, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Kandang %d belum memiliki project flock aktif", *warehouse.KandangId))
|
|
|
|
|
}
|
|
|
|
|
s.Log.Errorf("Failed to get active project flock for kandang %d: %+v", *warehouse.KandangId, err)
|
|
|
|
|
return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil project flock kandang")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|