mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
9b016dc30a
- Extend DB schema for stock transfers - Build stock transfer API (create,)
241 lines
10 KiB
Go
241 lines
10 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
|
rStockTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories"
|
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/validations"
|
|
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/stock-logs/repositories"
|
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
|
|
|
"github.com/go-playground/validator/v10"
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/sirupsen/logrus"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
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)
|
|
}
|
|
|
|
type transferService struct {
|
|
Log *logrus.Logger
|
|
Validate *validator.Validate
|
|
StockTransferRepo rStockTransfer.StockTransferRepository
|
|
StockTransferDetailRepo rStockTransfer.StockTransferDetailRepository
|
|
StockTransferDeliveryRepo rStockTransfer.StockTransferDeliveryRepository
|
|
StockTransferDeliveryItemRepo rStockTransfer.StockTransferDeliveryItemRepository
|
|
StockLogsRepository rStockLogs.StockLogRepository
|
|
ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository
|
|
}
|
|
|
|
func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTransfer.StockTransferRepository, stockTransferDetailRepo rStockTransfer.StockTransferDetailRepository, stockTransferDeliveryRepo rStockTransfer.StockTransferDeliveryRepository, stockTransferDeliveryItemRepo rStockTransfer.StockTransferDeliveryItemRepository, stockLogsRepo rStockLogs.StockLogRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository) TransferService {
|
|
return &transferService{
|
|
Log: utils.Log,
|
|
Validate: validate,
|
|
StockTransferRepo: stockTransferRepo,
|
|
StockTransferDetailRepo: stockTransferDetailRepo,
|
|
StockTransferDeliveryRepo: stockTransferDeliveryRepo,
|
|
StockTransferDeliveryItemRepo: stockTransferDeliveryItemRepo,
|
|
StockLogsRepository: stockLogsRepo,
|
|
ProductWarehouseRepo: productWarehouseRepo,
|
|
}
|
|
}
|
|
|
|
func (s transferService) withRelations(db *gorm.DB) *gorm.DB {
|
|
return db.Preload("CreatedUser")
|
|
}
|
|
|
|
func (s transferService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.StockTransfer, int64, error) {
|
|
if err := s.Validate.Struct(params); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
// offset := (params.Page - 1) * params.Limit
|
|
|
|
// transfers, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
|
// db = s.withRelations(db)
|
|
// if params.Search != "" {
|
|
// return db.Where("name LIKE ?", "%"+params.Search+"%")
|
|
// }
|
|
// return db.Order("created_at DESC").Order("updated_at DESC")
|
|
// })
|
|
|
|
// if err != nil {
|
|
// s.Log.Errorf("Failed to get transfers: %+v", err)
|
|
// return nil, 0, err
|
|
// }
|
|
return nil, 0, nil
|
|
}
|
|
|
|
func (s transferService) GetOne(c *fiber.Ctx, id uint) (*entity.StockTransfer, error) {
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferRequest) (*entity.StockTransfer, error) {
|
|
if err := s.Validate.Struct(req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Validasi stok di gudang asal
|
|
for _, product := range req.Products {
|
|
sourcePW, err := s.ProductWarehouseRepo.GetProductWarehouseByProductAndWarehouseID(
|
|
c.Context(), uint(product.ProductID), uint(req.SourceWarehouseID),
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok produk %d tidak tersedia di gudang asal", product.ProductID))
|
|
}
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal cek stok produk di gudang asal")
|
|
}
|
|
if sourcePW.Quantity < product.ProductQty {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok produk %d di gudang asal tidak cukup", product.ProductID))
|
|
}
|
|
}
|
|
|
|
// Generate movement number
|
|
// Format: PND-MBU-00001
|
|
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)
|
|
transferDate, _ := utils.ParseDateString(req.TransferDate)
|
|
|
|
entityTransfer := &entity.StockTransfer{
|
|
FromWarehouseId: uint64(req.SourceWarehouseID),
|
|
ToWarehouseId: uint64(req.DestinationWarehouseID),
|
|
Reason: req.TransferReason,
|
|
TransferDate: transferDate,
|
|
MovementNumber: movementNumber,
|
|
CreatedBy: 1, //todo: get from token
|
|
}
|
|
|
|
// Save the transfer entity to the database
|
|
err = s.StockTransferRepo.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
|
|
|
// Insert header
|
|
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)
|
|
|
|
// insert ke details
|
|
var details []*entity.StockTransferDetail
|
|
for _, product := range req.Products {
|
|
details = append(details, &entity.StockTransferDetail{
|
|
StockTransferId: entityTransfer.Id,
|
|
ProductId: uint64(product.ProductID),
|
|
Quantity: product.ProductQty,
|
|
})
|
|
}
|
|
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)
|
|
|
|
// Tambahkan proses insert delivery
|
|
var deliveries []*entity.StockTransferDelivery
|
|
for _, delivery := range req.Deliveries {
|
|
deliveries = append(deliveries, &entity.StockTransferDelivery{
|
|
StockTransferId: entityTransfer.Id,
|
|
SupplierId: uint64(delivery.SupplierID),
|
|
VehiclePlate: delivery.VehiclePlate,
|
|
DriverName: delivery.DriverName,
|
|
DocumentPath: delivery.Document,
|
|
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
|
|
}
|
|
|
|
// tambahkan insert ke delivery items sebagai fivot
|
|
var deliveryItems []*entity.StockTransferDeliveryItem
|
|
for i, delivery := range req.Deliveries {
|
|
for _, item := range delivery.Products {
|
|
deliveryItems = append(deliveryItems, &entity.StockTransferDeliveryItem{
|
|
StockTransferDeliveryId: deliveries[i].Id,
|
|
StockTransferDetailId: uint64(item.ProductID),
|
|
Quantity: item.ProductQty,
|
|
})
|
|
}
|
|
}
|
|
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)
|
|
|
|
// Proses pengurangan stok di gudang asal dan penambahan stok di gudang tujuan
|
|
for _, product := range req.Products {
|
|
// Kurangi stok di gudang asal
|
|
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)
|
|
|
|
// Tambah stok di gudang tujuan
|
|
destPW, err := s.ProductWarehouseRepo.GetProductWarehouseByProductAndWarehouseID(
|
|
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) {
|
|
// Jika belum ada record untuk produk di gudang tujuan, buat baru
|
|
destPW = &entity.ProductWarehouse{
|
|
ProductId: uint(product.ProductID),
|
|
WarehouseId: uint(req.DestinationWarehouseID),
|
|
Quantity: 0,
|
|
CreatedBy: 1, // TODO: should Get from auth middleware
|
|
}
|
|
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)
|
|
}
|
|
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
s.Log.Errorf("Transaction failed in CreateOne: %+v", err)
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to process transfer transaction")
|
|
}
|
|
|
|
return entityTransfer, nil
|
|
}
|