Merge branch 'FEAT/BE/Transfer_stock' into 'development'

[FEAT][BE-transfer_Stock]L Update StockTransferDelivery and TransferDeliveryDTO to allow optional SupplierId

See merge request mbugroup/lti-api!265
This commit is contained in:
Hafizh A. Y.
2026-01-28 03:05:31 +00:00
7 changed files with 95 additions and 38 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ import "time"
type StockTransferDelivery struct {
Id uint64 `gorm:"primaryKey;autoIncrement"`
StockTransferId uint64
SupplierId uint64
SupplierId *uint64
VehiclePlate string
DriverName string
DocumentNumber string
@@ -60,7 +60,7 @@ type TransferDetailItemDTO struct {
type TransferDeliveryDTO struct {
Id uint64 `json:"id"`
Supplier SupplierSimpleDTO `json:"supplier"`
Supplier *SupplierSimpleDTO `json:"supplier,omitempty"`
VehiclePlate string `json:"vehicle_plate"`
DriverName string `json:"driver_name"`
DocumentNumber string `json:"document_number"`
@@ -115,7 +115,6 @@ func ToTransferListDTO(e entity.StockTransfer) TransferListDTO {
Quantity: d.UsageQty + d.PendingQty, // Total actual quantity allocated
}
if d.ExpenseNonstock != nil {
priceCopy := d.ExpenseNonstock.Price
detailDTO.TransportPerItem = &priceCopy
@@ -155,12 +154,17 @@ func ToTransferListDTO(e entity.StockTransfer) TransferListDTO {
}
}
deliveries = append(deliveries, TransferDeliveryDTO{
Id: del.Id,
Supplier: SupplierSimpleDTO{
var supplier *SupplierSimpleDTO
if del.Supplier != nil {
supplier = &SupplierSimpleDTO{
Id: del.Supplier.Id,
Name: del.Supplier.Name,
},
}
}
deliveries = append(deliveries, TransferDeliveryDTO{
Id: del.Id,
Supplier: supplier,
VehiclePlate: del.VehiclePlate,
DriverName: del.DriverName,
DocumentNumber: del.DocumentNumber,
@@ -201,7 +205,6 @@ func ToTransferDetailDTO(e entity.StockTransfer) TransferDetailDTO {
Quantity: d.UsageQty + d.PendingQty, // Total actual quantity allocated
}
if d.ExpenseNonstock != nil {
priceCopy := d.ExpenseNonstock.Price
detailDTO.TransportPerItem = &priceCopy
@@ -241,12 +244,17 @@ func ToTransferDetailDTO(e entity.StockTransfer) TransferDetailDTO {
}
}
deliveries = append(deliveries, TransferDeliveryDTO{
Id: del.Id,
Supplier: SupplierSimpleDTO{
var supplier *SupplierSimpleDTO
if del.Supplier != nil {
supplier = &SupplierSimpleDTO{
Id: del.Supplier.Id,
Name: del.Supplier.Name,
},
}
}
deliveries = append(deliveries, TransferDeliveryDTO{
Id: del.Id,
Supplier: supplier,
VehiclePlate: del.VehiclePlate,
DriverName: del.DriverName,
DocumentNumber: del.DocumentNumber,
@@ -196,6 +196,11 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
}
for _, delivery := range req.Deliveries {
// Skip supplier validation if SupplierID is 0 (optional)
if delivery.SupplierID == 0 {
continue
}
supplier, err := s.SupplierRepo.GetByID(c.Context(), uint(delivery.SupplierID), nil)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
@@ -310,9 +315,16 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
var deliveries []*entity.StockTransferDelivery
for _, delivery := range req.Deliveries {
supplierId := func() *uint64 {
if delivery.SupplierID > 0 {
id := uint64(delivery.SupplierID)
return &id
}
return nil
}()
deliveries = append(deliveries, &entity.StockTransferDelivery{
StockTransferId: entityTransfer.Id,
SupplierId: uint64(delivery.SupplierID),
SupplierId: supplierId,
VehiclePlate: delivery.VehiclePlate,
DriverName: delivery.DriverName,
ShippingCostItem: delivery.DeliveryCostPerItem,
@@ -458,6 +470,11 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
if len(req.Deliveries) > 0 {
for _, delivery := range req.Deliveries {
// Skip adding to expensePayloads if SupplierID is 0 (optional)
if delivery.SupplierID == 0 {
continue
}
for _, prod := range delivery.Products {
detail := detailMap[uint64(prod.ProductID)]
if detail == nil {
@@ -26,7 +26,7 @@ type TransferDelivery struct {
DocumentIndex int `json:"document_index" validate:"omitempty,min=-1" default:"-1"`
DriverName string `json:"driver_name" validate:"required"`
VehiclePlate string `json:"vehicle_plate" validate:"required"`
SupplierID uint `json:"supplier_id" validate:"required"`
SupplierID uint `json:"supplier_id" `
Products []TransferDeliveryProduct `json:"products" validate:"required,dive"`
}
@@ -650,9 +650,9 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
repoTx := s.Repository.WithTx(dbTransaction)
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
// Gunakan repo baru untuk transaction scope agar bisa akses method custom
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction)
stockLogRepoTx := rStockLogs.NewStockLogRepository(dbTransaction)
for _, approvableID := range approvableIDs {
transfer, err := repoTx.GetByID(c.Context(), approvableID, nil)
@@ -687,23 +687,28 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil targets transfer")
}
// Hitung total quantity dari targets untuk di-consume dari sources
totalTargetQty := 0.0
for _, target := range targets {
totalTargetQty += target.TotalQty
}
// Consume dari laying_transfer_sources (Usable) - akan consume dari ProjectFlockPopulation (Stockable)
totalSourceRequested := 0.0
for _, source := range sources {
totalSourceRequested += source.RequestedQty
}
for _, source := range sources {
if source.ProductWarehouseId == nil {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Source product warehouse tidak ditemukan untuk transfer %d", approvableID))
}
sourceShare := (source.RequestedQty / totalSourceRequested) * totalTargetQty
consumeResult, err := s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
UsableKey: fifo.UsableKeyTransferToLayingOut,
UsableID: source.Id,
ProductWarehouseID: *source.ProductWarehouseId,
Quantity: totalTargetQty,
Quantity: sourceShare,
AllowPending: false,
Tx: dbTransaction,
})
@@ -717,6 +722,19 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
}, nil); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Gagal update source usage qty")
}
stockLogDecrease := &entity.StockLog{
ProductWarehouseId: *source.ProductWarehouseId,
CreatedBy: actorID,
Increase: 0,
Decrease: sourceShare,
LoggableType: string(utils.StockLogTypeTransferLaying),
LoggableId: approvableID,
Notes: fmt.Sprintf("TL #%s", transfer.TransferNumber),
}
if err := stockLogRepoTx.CreateOne(c.Context(), stockLogDecrease, nil); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat log stok keluar")
}
}
for _, target := range targets {
@@ -725,7 +743,7 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
}
note := fmt.Sprintf("Transfer to Laying #%s", transfer.TransferNumber)
replenishResult, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
_, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyTransferToLayingIn,
StockableID: target.Id,
ProductWarehouseID: *target.ProductWarehouseId,
@@ -738,10 +756,23 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
}
if err := targetRepoTx.PatchOne(c.Context(), target.Id, map[string]interface{}{
"total_qty": replenishResult.AddedQuantity,
"total_qty": target.TotalQty,
}, nil); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Gagal update target total qty")
}
stockLogIncrease := &entity.StockLog{
ProductWarehouseId: *target.ProductWarehouseId,
CreatedBy: actorID,
Increase: target.TotalQty,
Decrease: 0,
LoggableType: string(utils.StockLogTypeTransferLaying),
LoggableId: approvableID,
Notes: fmt.Sprintf("TL #%s", transfer.TransferNumber),
}
if err := stockLogRepoTx.CreateOne(c.Context(), stockLogIncrease, nil); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat log stok masuk")
}
}
}
}
@@ -30,7 +30,7 @@ type Update struct {
type Query struct {
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
Limit int `query:"limit" validate:"omitempty,number,min=1,gt=0"`
Search string `query:"search" validate:"omitempty"`
StartDate string `query:"start_date" validate:"omitempty"`
EndDate string `query:"end_date" validate:"omitempty"`
+1
View File
@@ -111,6 +111,7 @@ type StockLogType string
const (
StockLogTypeAdjustment StockLogType = "ADJUSTMENT"
StockLogTypeTransfer StockLogType = "TRANSFER"
StockLogTypeTransferLaying StockLogType = "TRANSFER_LAYING"
StockLogTypeMarketing StockLogType = "MARKETING"
StockLogTypeChikin StockLogType = "CHICKIN"
StockLogTypePurchase StockLogType = "PURCHASE"