mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 05:21:57 +00:00
549 lines
19 KiB
Go
549 lines
19 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
|
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
|
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
func (s *transferService) CreateSystemTransfer(ctx context.Context, req *SystemTransferRequest) (*entity.StockTransfer, error) {
|
|
if req == nil {
|
|
return nil, fmt.Errorf("system transfer request is required")
|
|
}
|
|
if strings.TrimSpace(req.TransferReason) == "" {
|
|
return nil, fmt.Errorf("transfer reason is required")
|
|
}
|
|
if req.TransferDate.IsZero() {
|
|
return nil, fmt.Errorf("transfer date is required")
|
|
}
|
|
if req.SourceWarehouseID == 0 || req.DestinationWarehouseID == 0 {
|
|
return nil, fmt.Errorf("source and destination warehouse are required")
|
|
}
|
|
if req.SourceWarehouseID == req.DestinationWarehouseID {
|
|
return nil, fmt.Errorf("source and destination warehouse must be different")
|
|
}
|
|
if req.ActorID == 0 {
|
|
return nil, fmt.Errorf("actor id is required")
|
|
}
|
|
|
|
if err := s.validateTransferWarehousesAndProducts(ctx, req.SourceWarehouseID, req.DestinationWarehouseID, req.Products); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var result *entity.StockTransfer
|
|
err := s.StockTransferRepo.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
movementResult, err := s.createTransferMovement(ctx, tx, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result = movementResult.Transfer
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (s *transferService) DeleteSystemTransfer(ctx context.Context, id uint, actorID uint) error {
|
|
if id == 0 {
|
|
return fmt.Errorf("transfer id is required")
|
|
}
|
|
if actorID == 0 {
|
|
return fmt.Errorf("actor id is required")
|
|
}
|
|
|
|
var deletedDetails []entity.StockTransferDetail
|
|
err := s.StockTransferRepo.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
var err error
|
|
deletedDetails, err = s.deleteTransferCore(ctx, tx, uint64(id), actorID)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(deletedDetails) > 0 && s.ExpenseBridge != nil {
|
|
if err := s.ExpenseBridge.OnItemsDeleted(ctx, uint64(id), deletedDetails); err != nil {
|
|
s.Log.Errorf("Failed to cleanup transfer expense link for transfer_id=%d: %+v", id, err)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Transfer berhasil dihapus, namun sinkronisasi expense gagal. Silakan cek modul expense")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *transferService) validateTransferWarehousesAndProducts(
|
|
ctx context.Context,
|
|
sourceWarehouseID uint,
|
|
destinationWarehouseID uint,
|
|
products []SystemTransferProduct,
|
|
) error {
|
|
if len(products) == 0 {
|
|
return fmt.Errorf("transfer products are required")
|
|
}
|
|
|
|
pwIDs := make([]uint, 0, len(products))
|
|
for _, product := range products {
|
|
if product.ProductID == 0 {
|
|
return fmt.Errorf("product id is required")
|
|
}
|
|
if product.ProductQty <= 0 {
|
|
return fmt.Errorf("product qty must be greater than 0 for product %d", product.ProductID)
|
|
}
|
|
|
|
sourcePW, err := s.ProductWarehouseRepo.GetProductWarehouseByProductAndWarehouseID(
|
|
ctx, product.ProductID, sourceWarehouseID,
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Produk dengan ID %d tidak ditemukan di gudang asal (ID: %d)", product.ProductID, sourceWarehouseID))
|
|
}
|
|
s.Log.Errorf("Failed to fetch product warehouse for product_id=%d, warehouse_id=%d: %+v", product.ProductID, sourceWarehouseID, err)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengecek stok produk")
|
|
}
|
|
if sourcePW.Quantity < product.ProductQty {
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok produk %d di gudang asal tidak mencukupi. Tersedia: %.2f, Diminta: %.2f", product.ProductID, sourcePW.Quantity, product.ProductQty))
|
|
}
|
|
pwIDs = append(pwIDs, sourcePW.Id)
|
|
}
|
|
|
|
if err := commonSvc.EnsureProjectFlockNotClosedForProductWarehouses(ctx, s.StockTransferRepo.DB(), pwIDs); err != nil {
|
|
return err
|
|
}
|
|
|
|
destPfkID, err := s.getActiveProjectFlockKandangID(ctx, destinationWarehouseID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if destPfkID == 0 {
|
|
return nil
|
|
}
|
|
|
|
projectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(ctx, destPfkID)
|
|
if err != nil {
|
|
s.Log.Errorf("Failed to fetch project flock kandang by ID %d: %+v", destPfkID, err)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data project flock")
|
|
}
|
|
if projectFlockKandang.ClosedAt != nil {
|
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Project flock untuk gudang tujuan sudah ditutup (closing) pada %s", projectFlockKandang.ClosedAt.Format("2006-01-02")))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *transferService) createTransferMovement(
|
|
ctx context.Context,
|
|
tx *gorm.DB,
|
|
req *SystemTransferRequest,
|
|
) (*transferMovementResult, error) {
|
|
if tx == nil {
|
|
return nil, fmt.Errorf("transaction is required")
|
|
}
|
|
|
|
stockTransferRepoTX := s.StockTransferRepo.WithTx(tx)
|
|
stockTransferDetailRepoTX := s.StockTransferDetailRepo.WithTx(tx)
|
|
productWarehouseRepoTX := rProductWarehouse.NewProductWarehouseRepository(tx)
|
|
stockLogsRepoTX := rStockLogs.NewStockLogRepository(tx)
|
|
|
|
movementNumber := strings.TrimSpace(req.MovementNumber)
|
|
if movementNumber == "" {
|
|
var err error
|
|
movementNumber, err = s.StockTransferRepo.GenerateMovementNumber(ctx)
|
|
if err != nil {
|
|
s.Log.Errorf("Failed to generate movement number: %+v", err)
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat nomor transfer")
|
|
}
|
|
}
|
|
|
|
entityTransfer := &entity.StockTransfer{
|
|
FromWarehouseId: uint64(req.SourceWarehouseID),
|
|
ToWarehouseId: uint64(req.DestinationWarehouseID),
|
|
Reason: req.TransferReason,
|
|
TransferDate: req.TransferDate,
|
|
MovementNumber: movementNumber,
|
|
CreatedBy: uint64(req.ActorID),
|
|
}
|
|
if err := stockTransferRepoTX.CreateOne(ctx, entityTransfer, nil); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
details := make([]*entity.StockTransferDetail, 0, len(req.Products))
|
|
detailMap := make(map[uint64]*entity.StockTransferDetail, len(req.Products))
|
|
for _, product := range req.Products {
|
|
sourcePW, err := productWarehouseRepoTX.GetProductWarehouseByProductAndWarehouseID(
|
|
ctx, product.ProductID, req.SourceWarehouseID,
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Produk %d tidak ditemukan di gudang asal (ID: %d)", product.ProductID, req.SourceWarehouseID))
|
|
}
|
|
s.Log.Errorf("Failed to fetch source product warehouse for product_id=%d, warehouse_id=%d: %+v", product.ProductID, req.SourceWarehouseID, err)
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data stok gudang asal")
|
|
}
|
|
|
|
destPW, err := productWarehouseRepoTX.GetProductWarehouseByProductAndWarehouseID(
|
|
ctx, product.ProductID, req.DestinationWarehouseID,
|
|
)
|
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
s.Log.Errorf("Failed to fetch dest product warehouse for product_id=%d, warehouse_id=%d: %+v", product.ProductID, req.DestinationWarehouseID, err)
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data stok gudang tujuan")
|
|
}
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
projectFlockKandangID, err := s.getActiveProjectFlockKandangID(ctx, req.DestinationWarehouseID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var pfkID *uint
|
|
if projectFlockKandangID > 0 {
|
|
pfkID = &projectFlockKandangID
|
|
}
|
|
|
|
destPW = &entity.ProductWarehouse{
|
|
ProductId: product.ProductID,
|
|
WarehouseId: req.DestinationWarehouseID,
|
|
Quantity: 0,
|
|
ProjectFlockKandangId: pfkID,
|
|
}
|
|
if err := productWarehouseRepoTX.CreateOne(ctx, destPW, nil); err != nil {
|
|
s.Log.Errorf("Failed to create product warehouse for product_id=%d, warehouse_id=%d: %+v", product.ProductID, req.DestinationWarehouseID, err)
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat data stok gudang tujuan")
|
|
}
|
|
}
|
|
|
|
detail := &entity.StockTransferDetail{
|
|
StockTransferId: entityTransfer.Id,
|
|
ProductId: uint64(product.ProductID),
|
|
SourceProductWarehouseID: func() *uint64 {
|
|
id := uint64(sourcePW.Id)
|
|
return &id
|
|
}(),
|
|
UsageQty: 0,
|
|
PendingQty: 0,
|
|
DestProductWarehouseID: func() *uint64 {
|
|
id := uint64(destPW.Id)
|
|
return &id
|
|
}(),
|
|
TotalQty: 0,
|
|
TotalUsed: 0,
|
|
}
|
|
details = append(details, detail)
|
|
detailMap[uint64(product.ProductID)] = detail
|
|
}
|
|
|
|
if err := stockTransferDetailRepoTX.CreateMany(ctx, details, nil); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
flagGroupByProduct := make(map[uint]string, len(req.Products))
|
|
for _, product := range req.Products {
|
|
detail := detailMap[uint64(product.ProductID)]
|
|
if detail == nil || detail.SourceProductWarehouseID == nil || detail.DestProductWarehouseID == nil {
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Data transfer detail tidak valid")
|
|
}
|
|
|
|
flagGroupCode, ok := flagGroupByProduct[product.ProductID]
|
|
if !ok {
|
|
var err error
|
|
flagGroupCode, err = s.resolveTransferFlagGroup(ctx, tx, product.ProductID)
|
|
if err != nil {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("FIFO v2 route tidak ditemukan untuk produk %d: %v", product.ProductID, err))
|
|
}
|
|
flagGroupByProduct[product.ProductID] = flagGroupCode
|
|
}
|
|
|
|
if err := tx.Model(&entity.StockTransferDetail{}).
|
|
Where("id = ?", detail.Id).
|
|
Updates(map[string]interface{}{
|
|
"usage_qty": product.ProductQty,
|
|
"pending_qty": 0,
|
|
"total_qty": product.ProductQty,
|
|
}).Error; err != nil {
|
|
s.Log.Errorf("Failed to update transfer detail seed fields for detail_id=%d, product_id=%d: %+v", detail.Id, product.ProductID, err)
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal memperbarui data tracking")
|
|
}
|
|
|
|
asOf := req.TransferDate
|
|
if _, err := s.FifoStockV2Svc.Reflow(ctx, commonSvc.FifoStockV2ReflowRequest{
|
|
FlagGroupCode: flagGroupCode,
|
|
ProductWarehouseID: uint(*detail.SourceProductWarehouseID),
|
|
AsOf: &asOf,
|
|
Tx: tx,
|
|
}); err != nil {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok tidak mencukupi untuk produk %d di gudang asal. Error: %v", product.ProductID, err))
|
|
}
|
|
if _, err := s.FifoStockV2Svc.Reflow(ctx, commonSvc.FifoStockV2ReflowRequest{
|
|
FlagGroupCode: flagGroupCode,
|
|
ProductWarehouseID: uint(*detail.DestProductWarehouseID),
|
|
AsOf: &asOf,
|
|
Tx: tx,
|
|
}); err != nil {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Gagal reflow stok tujuan untuk produk %d. Error: %v", product.ProductID, err))
|
|
}
|
|
|
|
type usageSnapshot struct {
|
|
UsageQty float64 `gorm:"column:usage_qty"`
|
|
PendingQty float64 `gorm:"column:pending_qty"`
|
|
}
|
|
var usage usageSnapshot
|
|
if err := tx.WithContext(ctx).
|
|
Table("stock_transfer_details").
|
|
Select("usage_qty, pending_qty").
|
|
Where("id = ?", detail.Id).
|
|
Take(&usage).Error; err != nil {
|
|
s.Log.Errorf("Failed to read transfer usage snapshot detail_id=%d, product_id=%d: %+v", detail.Id, product.ProductID, err)
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data tracking")
|
|
}
|
|
outUsageQty := usage.UsageQty
|
|
outPendingQty := usage.PendingQty
|
|
if outPendingQty > 1e-6 {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok tidak mencukupi untuk produk %d di gudang asal", product.ProductID))
|
|
}
|
|
|
|
stockLogDecrease := &entity.StockLog{
|
|
ProductWarehouseId: uint(*detail.SourceProductWarehouseID),
|
|
CreatedBy: req.ActorID,
|
|
Increase: 0,
|
|
Decrease: outUsageQty,
|
|
LoggableType: string(utils.StockLogTypeTransfer),
|
|
LoggableId: uint(detail.Id),
|
|
Notes: req.StockLogNotes,
|
|
}
|
|
stockLogs, err := stockLogsRepoTX.GetByProductWarehouse(ctx, uint(*detail.SourceProductWarehouseID), 1)
|
|
if err != nil {
|
|
s.Log.Errorf("Failed to get stock logs: %+v", err)
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
|
}
|
|
if len(stockLogs) > 0 {
|
|
latestStockLog := stockLogs[0]
|
|
stockLogDecrease.Stock = latestStockLog.Stock - stockLogDecrease.Decrease
|
|
} else {
|
|
stockLogDecrease.Stock -= stockLogDecrease.Decrease
|
|
}
|
|
if err := stockLogsRepoTX.CreateOne(ctx, stockLogDecrease, nil); err != nil {
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat log stok keluar")
|
|
}
|
|
|
|
stockLogIncrease := &entity.StockLog{
|
|
ProductWarehouseId: uint(*detail.DestProductWarehouseID),
|
|
CreatedBy: req.ActorID,
|
|
Increase: outUsageQty,
|
|
Decrease: 0,
|
|
LoggableType: string(utils.StockLogTypeTransfer),
|
|
LoggableId: uint(detail.Id),
|
|
Notes: req.StockLogNotes,
|
|
}
|
|
stockLogs, err = stockLogsRepoTX.GetByProductWarehouse(ctx, uint(*detail.DestProductWarehouseID), 1)
|
|
if err != nil {
|
|
s.Log.Errorf("Failed to get stock logs: %+v", err)
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
|
}
|
|
if len(stockLogs) > 0 {
|
|
latestStockLog := stockLogs[0]
|
|
stockLogIncrease.Stock = latestStockLog.Stock + stockLogIncrease.Increase
|
|
} else {
|
|
stockLogIncrease.Stock += stockLogIncrease.Increase
|
|
}
|
|
if err := stockLogsRepoTX.CreateOne(ctx, stockLogIncrease, nil); err != nil {
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat log stok masuk")
|
|
}
|
|
}
|
|
|
|
return &transferMovementResult{
|
|
Transfer: entityTransfer,
|
|
DetailByPID: detailMap,
|
|
}, nil
|
|
}
|
|
|
|
func (s *transferService) deleteTransferCore(
|
|
ctx context.Context,
|
|
tx *gorm.DB,
|
|
transferID uint64,
|
|
actorID uint,
|
|
) ([]entity.StockTransferDetail, error) {
|
|
stockLogRepoTx := rStockLogs.NewStockLogRepository(tx)
|
|
|
|
var transfer entity.StockTransfer
|
|
if err := tx.WithContext(ctx).
|
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
|
Where("id = ?", transferID).
|
|
Where("deleted_at IS NULL").
|
|
Take(&transfer).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Transfer dengan ID %d tidak ditemukan", transferID))
|
|
}
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data transfer")
|
|
}
|
|
|
|
var details []entity.StockTransferDetail
|
|
if err := tx.WithContext(ctx).
|
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
|
Where("stock_transfer_id = ?", transfer.Id).
|
|
Where("deleted_at IS NULL").
|
|
Order("id ASC").
|
|
Find(&details).Error; err != nil {
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil detail transfer")
|
|
}
|
|
if len(details) == 0 {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Transfer tidak memiliki detail produk")
|
|
}
|
|
|
|
detailIDs := make([]uint64, 0, len(details))
|
|
for _, detail := range details {
|
|
detailIDs = append(detailIDs, detail.Id)
|
|
}
|
|
if err := s.ensureDeletePolicyForDownstreamConsumption(ctx, tx, detailIDs); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
type reflowKey struct {
|
|
flagGroupCode string
|
|
productWarehouseID uint
|
|
}
|
|
destReflows := make(map[reflowKey]struct{})
|
|
|
|
for _, detail := range details {
|
|
if detail.SourceProductWarehouseID == nil || *detail.SourceProductWarehouseID == 0 {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Detail transfer %d tidak memiliki source product warehouse valid", detail.Id))
|
|
}
|
|
if detail.DestProductWarehouseID == nil || *detail.DestProductWarehouseID == 0 {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Detail transfer %d tidak memiliki destination product warehouse valid", detail.Id))
|
|
}
|
|
|
|
flagGroupCode, err := s.resolveTransferFlagGroup(ctx, tx, uint(detail.ProductId))
|
|
if err != nil {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("FIFO v2 route tidak ditemukan untuk produk %d: %v", detail.ProductId, err))
|
|
}
|
|
|
|
rollbackRes, err := s.FifoStockV2Svc.Rollback(ctx, commonSvc.FifoStockV2RollbackRequest{
|
|
ProductWarehouseID: uint(*detail.SourceProductWarehouseID),
|
|
Usable: commonSvc.FifoStockV2Ref{
|
|
ID: uint(detail.Id),
|
|
LegacyTypeKey: fifo.UsableKeyStockTransferOut.String(),
|
|
FunctionCode: "STOCK_TRANSFER_OUT",
|
|
},
|
|
Reason: fmt.Sprintf("transfer delete #%s", transfer.MovementNumber),
|
|
Tx: tx,
|
|
})
|
|
if err != nil {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Gagal rollback FIFO v2 transfer detail %d: %v", detail.Id, err))
|
|
}
|
|
|
|
releasedQty := 0.0
|
|
if rollbackRes != nil {
|
|
releasedQty = rollbackRes.ReleasedQty
|
|
}
|
|
if detail.UsageQty > 1e-6 && releasedQty < detail.UsageQty-1e-6 {
|
|
return nil, fiber.NewError(
|
|
fiber.StatusBadRequest,
|
|
fmt.Sprintf("Rollback FIFO v2 source transfer detail %d tidak lengkap. Dibutuhkan %.3f, terlepas %.3f", detail.Id, detail.UsageQty, releasedQty),
|
|
)
|
|
}
|
|
|
|
if releasedQty > 1e-6 {
|
|
if err := s.appendStockLog(
|
|
ctx,
|
|
stockLogRepoTx,
|
|
uint(*detail.SourceProductWarehouseID),
|
|
actorID,
|
|
releasedQty,
|
|
0,
|
|
uint(detail.Id),
|
|
fmt.Sprintf("TRANSFER DELETE #%s", transfer.MovementNumber),
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
destDecreaseQty := detail.TotalQty
|
|
if destDecreaseQty <= 1e-6 {
|
|
destDecreaseQty = detail.UsageQty
|
|
}
|
|
if destDecreaseQty > 1e-6 {
|
|
if err := s.appendStockLog(
|
|
ctx,
|
|
stockLogRepoTx,
|
|
uint(*detail.DestProductWarehouseID),
|
|
actorID,
|
|
0,
|
|
destDecreaseQty,
|
|
uint(detail.Id),
|
|
fmt.Sprintf("TRANSFER DELETE #%s", transfer.MovementNumber),
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
destReflows[reflowKey{
|
|
flagGroupCode: flagGroupCode,
|
|
productWarehouseID: uint(*detail.DestProductWarehouseID),
|
|
}] = struct{}{}
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
if err := tx.WithContext(ctx).
|
|
Where("stock_transfer_detail_id IN ?", detailIDs).
|
|
Delete(&entity.StockTransferDeliveryItem{}).Error; err != nil {
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal menghapus item delivery transfer")
|
|
}
|
|
if err := tx.WithContext(ctx).
|
|
Model(&entity.StockTransferDelivery{}).
|
|
Where("stock_transfer_id = ?", transfer.Id).
|
|
Where("deleted_at IS NULL").
|
|
Updates(map[string]any{
|
|
"deleted_at": now,
|
|
"updated_at": now,
|
|
}).Error; err != nil {
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal menghapus delivery transfer")
|
|
}
|
|
if err := tx.WithContext(ctx).
|
|
Model(&entity.StockTransferDetail{}).
|
|
Where("id IN ?", detailIDs).
|
|
Where("deleted_at IS NULL").
|
|
Updates(map[string]any{
|
|
"deleted_at": now,
|
|
"updated_at": now,
|
|
}).Error; err != nil {
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal menghapus detail transfer")
|
|
}
|
|
|
|
asOf := transfer.TransferDate
|
|
for key := range destReflows {
|
|
if _, err := s.FifoStockV2Svc.Reflow(ctx, commonSvc.FifoStockV2ReflowRequest{
|
|
FlagGroupCode: key.flagGroupCode,
|
|
ProductWarehouseID: key.productWarehouseID,
|
|
AsOf: &asOf,
|
|
Tx: tx,
|
|
}); err != nil {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Gagal reflow stok tujuan saat delete transfer: %v", err))
|
|
}
|
|
}
|
|
|
|
if err := tx.WithContext(ctx).
|
|
Model(&entity.StockTransfer{}).
|
|
Where("id = ?", transfer.Id).
|
|
Where("deleted_at IS NULL").
|
|
Updates(map[string]any{
|
|
"deleted_at": now,
|
|
"updated_at": now,
|
|
}).Error; err != nil {
|
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal menghapus transfer")
|
|
}
|
|
|
|
return details, nil
|
|
}
|