mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
FIX[BE]: fixing transfer to laying and implement correct fifo stock
This commit is contained in:
+79
@@ -0,0 +1,79 @@
|
|||||||
|
-- Rollback: Revert FIFO fields back to laying_transfers from detail tables
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- PART 1: Remove FIFO columns from detail tables
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Add back old qty column first
|
||||||
|
ALTER TABLE laying_transfer_sources
|
||||||
|
ADD COLUMN IF NOT EXISTS qty NUMERIC(15, 3) NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE laying_transfer_targets
|
||||||
|
ADD COLUMN IF NOT EXISTS qty NUMERIC(15, 3) NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Now drop FIFO columns
|
||||||
|
ALTER TABLE laying_transfer_sources
|
||||||
|
DROP COLUMN IF EXISTS usage_qty,
|
||||||
|
DROP COLUMN IF EXISTS pending_usage_qty;
|
||||||
|
|
||||||
|
ALTER TABLE laying_transfer_targets
|
||||||
|
DROP COLUMN IF EXISTS total_qty,
|
||||||
|
DROP COLUMN IF EXISTS total_used;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- PART 2: Add back FIFO columns to laying_transfers table
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Add columns back for USABLE role (source warehouse)
|
||||||
|
ALTER TABLE laying_transfers
|
||||||
|
ADD COLUMN product_warehouse_id BIGINT,
|
||||||
|
ADD COLUMN pending_usage_qty NUMERIC(15, 3),
|
||||||
|
ADD COLUMN usage_qty NUMERIC(15, 3);
|
||||||
|
|
||||||
|
-- Add columns back for STOCKABLE role (destination warehouse)
|
||||||
|
ALTER TABLE laying_transfers
|
||||||
|
ADD COLUMN dest_product_warehouse_id BIGINT,
|
||||||
|
ADD COLUMN total_qty NUMERIC(15, 3) DEFAULT 0 NOT NULL,
|
||||||
|
ADD COLUMN total_used NUMERIC(15, 3) DEFAULT 0 NOT NULL;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- PART 3: Recreate foreign key constraints
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'product_warehouses') THEN
|
||||||
|
-- Add source product warehouse FK
|
||||||
|
ALTER TABLE laying_transfers
|
||||||
|
ADD CONSTRAINT fk_laying_transfers_product_warehouse_id
|
||||||
|
FOREIGN KEY (product_warehouse_id)
|
||||||
|
REFERENCES product_warehouses(id)
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
|
||||||
|
-- Add destination product warehouse FK
|
||||||
|
ALTER TABLE laying_transfers
|
||||||
|
ADD CONSTRAINT fk_laying_transfers_dest_product_warehouse_id
|
||||||
|
FOREIGN KEY (dest_product_warehouse_id)
|
||||||
|
REFERENCES product_warehouses(id)
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- PART 4: Recreate indexes for performance
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
CREATE INDEX idx_laying_transfers_product_warehouse_id
|
||||||
|
ON laying_transfers(product_warehouse_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_laying_transfers_dest_product_warehouse_id
|
||||||
|
ON laying_transfers(dest_product_warehouse_id);
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- PART 5: Recreate comments for documentation
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
COMMENT ON COLUMN laying_transfers.product_warehouse_id IS 'Product warehouse at source (Growing flock) - for USABLE role';
|
||||||
|
COMMENT ON COLUMN laying_transfers.dest_product_warehouse_id IS 'Product warehouse at destination (Laying flock) - for STOCKABLE role';
|
||||||
|
COMMENT ON COLUMN laying_transfers.total_qty IS 'Total lot quantity introduced to destination warehouse - for STOCKABLE role';
|
||||||
|
COMMENT ON COLUMN laying_transfers.total_used IS 'Quantity already consumed from this lot at destination - for FIFO STOCKABLE role';
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
-- Move FIFO fields from laying_transfers to detail tables (sources & targets)
|
||||||
|
-- This enables proper FIFO integration for transfer laying with multiple sources and targets
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- PART 1: Remove FIFO-related columns from laying_transfers table
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Drop foreign key constraints first
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
-- Drop source product warehouse FK
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint
|
||||||
|
WHERE conname = 'fk_laying_transfers_product_warehouse_id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE laying_transfers
|
||||||
|
DROP CONSTRAINT fk_laying_transfers_product_warehouse_id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Drop destination product warehouse FK
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint
|
||||||
|
WHERE conname = 'fk_laying_transfers_dest_product_warehouse_id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE laying_transfers
|
||||||
|
DROP CONSTRAINT fk_laying_transfers_dest_product_warehouse_id;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Drop indexes
|
||||||
|
DROP INDEX IF EXISTS idx_laying_transfers_product_warehouse_id;
|
||||||
|
DROP INDEX IF EXISTS idx_laying_transfers_dest_product_warehouse_id;
|
||||||
|
|
||||||
|
-- Remove columns from laying_transfers
|
||||||
|
ALTER TABLE laying_transfers
|
||||||
|
DROP COLUMN IF EXISTS product_warehouse_id,
|
||||||
|
DROP COLUMN IF EXISTS dest_product_warehouse_id,
|
||||||
|
DROP COLUMN IF EXISTS pending_usage_qty,
|
||||||
|
DROP COLUMN IF EXISTS usage_qty,
|
||||||
|
DROP COLUMN IF EXISTS total_qty,
|
||||||
|
DROP COLUMN IF EXISTS total_used;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- PART 2: Add FIFO columns to laying_transfer_sources (USABLE role)
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
ALTER TABLE laying_transfer_sources
|
||||||
|
ADD COLUMN usage_qty NUMERIC(15, 3) DEFAULT 0 NOT NULL,
|
||||||
|
ADD COLUMN pending_usage_qty NUMERIC(15, 3) DEFAULT 0 NOT NULL;
|
||||||
|
|
||||||
|
-- Add comments for documentation
|
||||||
|
COMMENT ON COLUMN laying_transfer_sources.usage_qty IS 'Quantity consumed from this source - for FIFO USABLE role';
|
||||||
|
COMMENT ON COLUMN laying_transfer_sources.pending_usage_qty IS 'Quantity pending to consume from this source - for FIFO USABLE role';
|
||||||
|
|
||||||
|
-- Drop old qty column as it's replaced by usage_qty
|
||||||
|
ALTER TABLE laying_transfer_sources
|
||||||
|
DROP COLUMN IF EXISTS qty;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- PART 3: Add FIFO columns to laying_transfer_targets (STOCKABLE role)
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
ALTER TABLE laying_transfer_targets
|
||||||
|
ADD COLUMN total_qty NUMERIC(15, 3) DEFAULT 0 NOT NULL,
|
||||||
|
ADD COLUMN total_used NUMERIC(15, 3) DEFAULT 0 NOT NULL;
|
||||||
|
|
||||||
|
-- Add comments for documentation
|
||||||
|
COMMENT ON COLUMN laying_transfer_targets.total_qty IS 'Total lot quantity introduced to this target warehouse - for FIFO STOCKABLE role';
|
||||||
|
COMMENT ON COLUMN laying_transfer_targets.total_used IS 'Quantity already consumed from this lot at target warehouse - for FIFO STOCKABLE role';
|
||||||
|
|
||||||
|
-- Drop old qty column as it's replaced by total_qty
|
||||||
|
ALTER TABLE laying_transfer_targets
|
||||||
|
DROP COLUMN IF EXISTS qty;
|
||||||
@@ -12,17 +12,6 @@ type LayingTransfer struct {
|
|||||||
FromProjectFlockId uint `gorm:"not null"`
|
FromProjectFlockId uint `gorm:"not null"`
|
||||||
ToProjectFlockId uint `gorm:"not null"`
|
ToProjectFlockId uint `gorm:"not null"`
|
||||||
TransferDate time.Time `gorm:"type:date;not null"`
|
TransferDate time.Time `gorm:"type:date;not null"`
|
||||||
|
|
||||||
|
|
||||||
PendingUsageQty *float64 `gorm:"type:numeric(15,3)"`
|
|
||||||
UsageQty *float64 `gorm:"type:numeric(15,3)"`
|
|
||||||
ProductWarehouseId *uint `gorm:"type:bigint"` // Source PW (PULLET)
|
|
||||||
|
|
||||||
|
|
||||||
DestProductWarehouseID *uint `gorm:"column:dest_product_warehouse_id;type:bigint"` // Destination PW (LAYER)
|
|
||||||
TotalQty float64 `gorm:"column:total_qty;type:numeric(15,3);default:0"` // Total lot introduced to destination
|
|
||||||
TotalUsed float64 `gorm:"column:total_used;type:numeric(15,3);default:0"` // Already consumed from this lot
|
|
||||||
|
|
||||||
Notes string `gorm:"type:text"`
|
Notes string `gorm:"type:text"`
|
||||||
CreatedBy uint `gorm:"not null"`
|
CreatedBy uint `gorm:"not null"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
@@ -31,8 +20,6 @@ type LayingTransfer struct {
|
|||||||
|
|
||||||
FromProjectFlock *ProjectFlock `gorm:"foreignKey:FromProjectFlockId;references:Id"`
|
FromProjectFlock *ProjectFlock `gorm:"foreignKey:FromProjectFlockId;references:Id"`
|
||||||
ToProjectFlock *ProjectFlock `gorm:"foreignKey:ToProjectFlockId;references:Id"`
|
ToProjectFlock *ProjectFlock `gorm:"foreignKey:ToProjectFlockId;references:Id"`
|
||||||
ProductWarehouse *ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"` // Source PW
|
|
||||||
DestProductWarehouse *ProductWarehouse `gorm:"foreignKey:DestProductWarehouseID;references:Id"` // Destination PW
|
|
||||||
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
Sources []LayingTransferSource `gorm:"foreignKey:LayingTransferId;constraint:OnDelete:CASCADE"`
|
Sources []LayingTransferSource `gorm:"foreignKey:LayingTransferId;constraint:OnDelete:CASCADE"`
|
||||||
Targets []LayingTransferTarget `gorm:"foreignKey:LayingTransferId;constraint:OnDelete:CASCADE"`
|
Targets []LayingTransferTarget `gorm:"foreignKey:LayingTransferId;constraint:OnDelete:CASCADE"`
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ type LayingTransferSource struct {
|
|||||||
LayingTransferId uint `gorm:"index;not null"`
|
LayingTransferId uint `gorm:"index;not null"`
|
||||||
SourceProjectFlockKandangId uint `gorm:"not null"`
|
SourceProjectFlockKandangId uint `gorm:"not null"`
|
||||||
ProductWarehouseId *uint `gorm:""`
|
ProductWarehouseId *uint `gorm:""`
|
||||||
Qty float64 `gorm:"type:numeric(15,3);not null"`
|
UsageQty float64 `gorm:"type:numeric(15,3);default:0;not null"` // FIFO USABLE field
|
||||||
|
PendingUsageQty float64 `gorm:"type:numeric(15,3);default:0;not null"` // FIFO USABLE field
|
||||||
Note string `gorm:"type:text"`
|
Note string `gorm:"type:text"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ type LayingTransferTarget struct {
|
|||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
LayingTransferId uint `gorm:"index;not null"`
|
LayingTransferId uint `gorm:"index;not null"`
|
||||||
TargetProjectFlockKandangId uint `gorm:"not null"`
|
TargetProjectFlockKandangId uint `gorm:"not null"`
|
||||||
Qty float64 `gorm:"type:numeric(15,3);not null"`
|
TotalQty float64 `gorm:"type:numeric(15,3);default:0;not null"` // FIFO STOCKABLE field
|
||||||
|
TotalUsed float64 `gorm:"type:numeric(15,3);default:0;not null"` // FIFO STOCKABLE field
|
||||||
ProductWarehouseId *uint `gorm:""`
|
ProductWarehouseId *uint `gorm:""`
|
||||||
Note string `gorm:"type:text"`
|
Note string `gorm:"type:text"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|||||||
+3
-3
@@ -96,9 +96,9 @@ func (r *projectFlockPopulationRepositoryImpl) GetTotalQtyByProjectFlockKandangI
|
|||||||
var total float64
|
var total float64
|
||||||
err := r.DB().WithContext(ctx).
|
err := r.DB().WithContext(ctx).
|
||||||
Table("project_flock_populations").
|
Table("project_flock_populations").
|
||||||
Select("COALESCE(SUM(total_qty), 0) AS total_qty").
|
Select("COALESCE(SUM(total_qty - total_used_qty), 0) AS available_qty").
|
||||||
Joins("JOIN project_chickins ON project_chickins.id = project_flock_populations.project_chickin_id").
|
Joins("JOIN product_warehouses pw ON project_flock_populations.product_warehouse_id = pw.id").
|
||||||
Where("project_chickins.project_flock_kandang_id = ?", projectFlockKandangID).
|
Where("pw.project_flock_kandang_id = ?", projectFlockKandangID).
|
||||||
Scan(&total).Error
|
Scan(&total).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|||||||
+2
-2
@@ -84,7 +84,7 @@ func (u *TransferLayingController) CreateOne(c *fiber.Ctx) error {
|
|||||||
req := new(validation.Create)
|
req := new(validation.Create)
|
||||||
|
|
||||||
if err := c.BodyParser(req); err != nil {
|
if err := c.BodyParser(req); err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
return fiber.NewError(fiber.StatusBadRequest, "Format permintaan tidak valid")
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := u.TransferLayingService.CreateOne(c, req)
|
result, err := u.TransferLayingService.CreateOne(c, req)
|
||||||
@@ -96,7 +96,7 @@ func (u *TransferLayingController) CreateOne(c *fiber.Ctx) error {
|
|||||||
JSON(response.Success{
|
JSON(response.Success{
|
||||||
Code: fiber.StatusCreated,
|
Code: fiber.StatusCreated,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Create transferLaying successfully",
|
Message: "Berhasil membuat transfer laying",
|
||||||
Data: dto.ToTransferLayingListDTO(*result),
|
Data: dto.ToTransferLayingListDTO(*result),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,8 +67,6 @@ type TransferLayingListDTO struct {
|
|||||||
TransferLayingRelationDTO
|
TransferLayingRelationDTO
|
||||||
FromProjectFlock *ProjectFlockSummaryDTO `json:"from_project_flock,omitempty"`
|
FromProjectFlock *ProjectFlockSummaryDTO `json:"from_project_flock,omitempty"`
|
||||||
ToProjectFlock *ProjectFlockSummaryDTO `json:"to_project_flock,omitempty"`
|
ToProjectFlock *ProjectFlockSummaryDTO `json:"to_project_flock,omitempty"`
|
||||||
PendingUsageQty *float64 `json:"pending_usage_qty"`
|
|
||||||
UsageQty *float64 `json:"usage_qty"`
|
|
||||||
CreatedBy uint `json:"created_by"`
|
CreatedBy uint `json:"created_by"`
|
||||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
@@ -166,7 +164,7 @@ func ToProductWarehouseSummaryDTO(pw *entity.ProductWarehouse) *ProductWarehouse
|
|||||||
func ToLayingTransferSourceDTO(source entity.LayingTransferSource) LayingTransferSourceDTO {
|
func ToLayingTransferSourceDTO(source entity.LayingTransferSource) LayingTransferSourceDTO {
|
||||||
return LayingTransferSourceDTO{
|
return LayingTransferSourceDTO{
|
||||||
SourceProjectFlockKandang: ToProjectFlockKandangSummaryDTO(source.SourceProjectFlockKandang),
|
SourceProjectFlockKandang: ToProjectFlockKandangSummaryDTO(source.SourceProjectFlockKandang),
|
||||||
Qty: source.Qty,
|
Qty: source.UsageQty, // Ambil dari UsageQty (FIFO consumed quantity)
|
||||||
ProductWarehouse: ToProductWarehouseSummaryDTO(source.ProductWarehouse),
|
ProductWarehouse: ToProductWarehouseSummaryDTO(source.ProductWarehouse),
|
||||||
Note: source.Note,
|
Note: source.Note,
|
||||||
}
|
}
|
||||||
@@ -186,7 +184,7 @@ func ToLayingTransferSourceDTOs(sources []entity.LayingTransferSource) []LayingT
|
|||||||
func ToLayingTransferTargetDTO(target entity.LayingTransferTarget) LayingTransferTargetDTO {
|
func ToLayingTransferTargetDTO(target entity.LayingTransferTarget) LayingTransferTargetDTO {
|
||||||
return LayingTransferTargetDTO{
|
return LayingTransferTargetDTO{
|
||||||
TargetProjectFlockKandang: ToProjectFlockKandangSummaryDTO(target.TargetProjectFlockKandang),
|
TargetProjectFlockKandang: ToProjectFlockKandangSummaryDTO(target.TargetProjectFlockKandang),
|
||||||
Qty: target.Qty,
|
Qty: target.TotalQty, // Ambil dari TotalQty (FIFO replenished quantity)
|
||||||
ProductWarehouse: ToProductWarehouseSummaryDTO(target.ProductWarehouse),
|
ProductWarehouse: ToProductWarehouseSummaryDTO(target.ProductWarehouse),
|
||||||
Note: target.Note,
|
Note: target.Note,
|
||||||
}
|
}
|
||||||
@@ -223,8 +221,6 @@ func ToTransferLayingListDTO(e entity.LayingTransfer) TransferLayingListDTO {
|
|||||||
TransferLayingRelationDTO: ToTransferLayingRelationDTO(e),
|
TransferLayingRelationDTO: ToTransferLayingRelationDTO(e),
|
||||||
FromProjectFlock: ToProjectFlockSummaryDTO(e.FromProjectFlock),
|
FromProjectFlock: ToProjectFlockSummaryDTO(e.FromProjectFlock),
|
||||||
ToProjectFlock: ToProjectFlockSummaryDTO(e.ToProjectFlock),
|
ToProjectFlock: ToProjectFlockSummaryDTO(e.ToProjectFlock),
|
||||||
PendingUsageQty: e.PendingUsageQty,
|
|
||||||
UsageQty: e.UsageQty,
|
|
||||||
CreatedBy: e.CreatedBy,
|
CreatedBy: e.CreatedBy,
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
|
|||||||
@@ -36,30 +36,13 @@ func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, val
|
|||||||
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
|
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
|
||||||
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
|
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
|
||||||
|
|
||||||
|
// daftarin jadi stockable
|
||||||
if err := fifoService.RegisterUsable(fifo.UsableConfig{
|
|
||||||
Key: fifo.UsableKeyTransferToLaying,
|
|
||||||
Table: "laying_transfers",
|
|
||||||
Columns: fifo.UsableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "product_warehouse_id",
|
|
||||||
UsageQuantity: "usage_qty",
|
|
||||||
PendingQuantity: "pending_usage_qty",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
}); err != nil {
|
|
||||||
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
|
||||||
panic(fmt.Sprintf("failed to register transfer to laying usable workflow: %v", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if err := fifoService.RegisterStockable(fifo.StockableConfig{
|
if err := fifoService.RegisterStockable(fifo.StockableConfig{
|
||||||
Key: fifo.StockableKeyTransferToLaying,
|
Key: fifo.StockableKeyTransferToLayingIn,
|
||||||
Table: "laying_transfers",
|
Table: "laying_transfer_targets",
|
||||||
Columns: fifo.StockableColumns{
|
Columns: fifo.StockableColumns{
|
||||||
ID: "id",
|
ID: "id",
|
||||||
ProductWarehouseID: "dest_product_warehouse_id",
|
ProductWarehouseID: "product_warehouse_id",
|
||||||
TotalQuantity: "total_qty",
|
TotalQuantity: "total_qty",
|
||||||
TotalUsedQuantity: "total_used",
|
TotalUsedQuantity: "total_used",
|
||||||
CreatedAt: "created_at",
|
CreatedAt: "created_at",
|
||||||
@@ -71,6 +54,24 @@ func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, val
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// daftarin jadi usable
|
||||||
|
if err := fifoService.RegisterUsable(fifo.UsableConfig{
|
||||||
|
Key: fifo.UsableKeyTransferToLayingOut,
|
||||||
|
Table: "laying_transfer_sources",
|
||||||
|
Columns: fifo.UsableColumns{
|
||||||
|
ID: "id",
|
||||||
|
ProductWarehouseID: "product_warehouse_id",
|
||||||
|
UsageQuantity: "usage_qty",
|
||||||
|
PendingQuantity: "pending_usage_qty",
|
||||||
|
CreatedAt: "created_at",
|
||||||
|
},
|
||||||
|
OrderBy: []string{"created_at ASC", "id ASC"},
|
||||||
|
}); err != nil {
|
||||||
|
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
||||||
|
panic(fmt.Sprintf("failed to register transfer to laying usable workflow: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||||
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
||||||
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowTransferToLaying, utils.TransferToLayingApprovalSteps); err != nil {
|
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowTransferToLaying, utils.TransferToLayingApprovalSteps); err != nil {
|
||||||
|
|||||||
+208
-287
@@ -16,6 +16,7 @@ import (
|
|||||||
ProjectFlockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
ProjectFlockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/validations"
|
||||||
|
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"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||||
|
|
||||||
@@ -45,6 +46,7 @@ type transferLayingService struct {
|
|||||||
ProjectFlockPopulationRepo ProjectFlockRepository.ProjectFlockPopulationRepository
|
ProjectFlockPopulationRepo ProjectFlockRepository.ProjectFlockPopulationRepository
|
||||||
ProductWarehouseRepo rInventory.ProductWarehouseRepository
|
ProductWarehouseRepo rInventory.ProductWarehouseRepository
|
||||||
WarehouseRepo rWarehouse.WarehouseRepository
|
WarehouseRepo rWarehouse.WarehouseRepository
|
||||||
|
StockLogRepo rStockLogs.StockLogRepository
|
||||||
ApprovalService commonSvc.ApprovalService
|
ApprovalService commonSvc.ApprovalService
|
||||||
FifoSvc commonSvc.FifoService
|
FifoSvc commonSvc.FifoService
|
||||||
}
|
}
|
||||||
@@ -69,6 +71,7 @@ func NewTransferLayingService(
|
|||||||
ProjectFlockPopulationRepo: projectFlockPopulationRepo,
|
ProjectFlockPopulationRepo: projectFlockPopulationRepo,
|
||||||
ProductWarehouseRepo: productWarehouseRepo,
|
ProductWarehouseRepo: productWarehouseRepo,
|
||||||
WarehouseRepo: warehouseRepo,
|
WarehouseRepo: warehouseRepo,
|
||||||
|
StockLogRepo: rStockLogs.NewStockLogRepository(repo.DB()),
|
||||||
ApprovalService: approvalService,
|
ApprovalService: approvalService,
|
||||||
FifoSvc: fifoSvc,
|
FifoSvc: fifoSvc,
|
||||||
}
|
}
|
||||||
@@ -164,55 +167,42 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := s.ProjectFlockRepo.GetByID(c.Context(), req.SourceProjectFlockId, nil); err != nil {
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
commonSvc.RelationCheck{Name: "Source Project Flock", ID: &req.SourceProjectFlockId, Exists: s.ProjectFlockRepo.IdExists},
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Source Project Flock not found")
|
commonSvc.RelationCheck{Name: "Target Project Flock", ID: &req.TargetProjectFlockId, Exists: s.ProjectFlockRepo.IdExists},
|
||||||
}
|
); err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate source project flock")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := s.ProjectFlockRepo.GetByID(c.Context(), req.TargetProjectFlockId, nil); err != nil {
|
sourceKandangIDs := make([]uint, len(req.SourceKandangs))
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
for i, detail := range req.SourceKandangs {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Target Project Flock not found")
|
sourceKandangIDs[i] = detail.ProjectFlockKandangId
|
||||||
}
|
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate target project flock")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, detail := range req.SourceKandangs {
|
if err := s.validateKandangOwnership(
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
c.Context(),
|
||||||
commonSvc.RelationCheck{Name: "Source Project Flock Kandang", ID: &detail.ProjectFlockKandangId, Exists: s.ProjectFlockKandangRepo.IdExists},
|
req.SourceProjectFlockId,
|
||||||
); err != nil {
|
sourceKandangIDs,
|
||||||
return nil, err
|
); err != nil {
|
||||||
}
|
return nil, err
|
||||||
|
|
||||||
pfk, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), detail.ProjectFlockKandangId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get source project flock kandang")
|
|
||||||
}
|
|
||||||
if pfk.ProjectFlockId != req.SourceProjectFlockId {
|
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d does not belong to source project flock %d", detail.ProjectFlockKandangId, req.SourceProjectFlockId))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, detail := range req.TargetKandangs {
|
targetKandangIDs := make([]uint, len(req.TargetKandangs))
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
for i, detail := range req.TargetKandangs {
|
||||||
commonSvc.RelationCheck{Name: "Target Project Flock Kandang", ID: &detail.ProjectFlockKandangId, Exists: s.ProjectFlockKandangRepo.IdExists},
|
targetKandangIDs[i] = detail.ProjectFlockKandangId
|
||||||
); err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pfk, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), detail.ProjectFlockKandangId)
|
if err := s.validateKandangOwnership(
|
||||||
if err != nil {
|
c.Context(),
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get target project flock kandang")
|
req.TargetProjectFlockId,
|
||||||
}
|
targetKandangIDs,
|
||||||
if pfk.ProjectFlockId != req.TargetProjectFlockId {
|
); err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Target kandang %d does not belong to target project flock %d", detail.ProjectFlockKandangId, req.TargetProjectFlockId))
|
return nil, err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transferDate, err := utils.ParseDateString(req.TransferDate)
|
transferDate, err := utils.ParseDateString(req.TransferDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transfer date format")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Format tanggal transfer tidak valid")
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalSourceQty, totalTargetQty float64
|
var totalSourceQty, totalTargetQty float64
|
||||||
@@ -220,7 +210,7 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
|
|
||||||
for _, sourceDetail := range req.SourceKandangs {
|
for _, sourceDetail := range req.SourceKandangs {
|
||||||
if sourceDetail.Quantity <= 0 {
|
if sourceDetail.Quantity <= 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Source kandang quantity must be greater than 0")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Jumlah kandang sumber harus lebih dari 0")
|
||||||
}
|
}
|
||||||
totalSourceQty += sourceDetail.Quantity
|
totalSourceQty += sourceDetail.Quantity
|
||||||
|
|
||||||
@@ -239,11 +229,11 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if totalPopulation == 0 {
|
if totalPopulation == 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d has no population available for transfer", sourceDetail.ProjectFlockKandangId))
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Kandang sumber %d tidak memiliki populasi untuk ditransfer", sourceDetail.ProjectFlockKandangId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if totalPopulation < sourceDetail.Quantity {
|
if totalPopulation < sourceDetail.Quantity {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d has insufficient quantity. Available: %.0f, Requested: %.0f", sourceDetail.ProjectFlockKandangId, totalPopulation, sourceDetail.Quantity))
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Kandang sumber %d jumlah tidak mencukupi. Tersedia: %.0f, Diminta: %.0f", sourceDetail.ProjectFlockKandangId, totalPopulation, sourceDetail.Quantity))
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceWarehouseMap[sourceDetail.ProjectFlockKandangId] = productWarehouseId
|
sourceWarehouseMap[sourceDetail.ProjectFlockKandangId] = productWarehouseId
|
||||||
@@ -251,13 +241,13 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
|
|
||||||
for _, targetDetail := range req.TargetKandangs {
|
for _, targetDetail := range req.TargetKandangs {
|
||||||
if targetDetail.Quantity <= 0 {
|
if targetDetail.Quantity <= 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Target kandang quantity must be greater than 0")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Jumlah kandang tujuan harus lebih dari 0")
|
||||||
}
|
}
|
||||||
totalTargetQty += targetDetail.Quantity
|
totalTargetQty += targetDetail.Quantity
|
||||||
}
|
}
|
||||||
|
|
||||||
if totalSourceQty != totalTargetQty {
|
if totalSourceQty != totalTargetQty {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Total source quantity (%f) must equal total target quantity (%f)", totalSourceQty, totalTargetQty))
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Jumlah total sumber (%.0f) harus sama dengan jumlah total tujuan (%.0f)", totalSourceQty, totalTargetQty))
|
||||||
}
|
}
|
||||||
|
|
||||||
transferNumber := fmt.Sprintf("TL-%d", time.Now().UnixNano())
|
transferNumber := fmt.Sprintf("TL-%d", time.Now().UnixNano())
|
||||||
@@ -268,22 +258,14 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
FromProjectFlockId: req.SourceProjectFlockId,
|
FromProjectFlockId: req.SourceProjectFlockId,
|
||||||
ToProjectFlockId: req.TargetProjectFlockId,
|
ToProjectFlockId: req.TargetProjectFlockId,
|
||||||
TransferDate: transferDate,
|
TransferDate: transferDate,
|
||||||
PendingUsageQty: &totalSourceQty,
|
|
||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sourceWarehouseMap) > 0 {
|
|
||||||
for _, pwID := range sourceWarehouseMap {
|
|
||||||
createBody.ProductWarehouseId = &pwID
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
repoTx := s.Repository.WithTx(dbTransaction)
|
repoTx := s.Repository.WithTx(dbTransaction)
|
||||||
|
|
||||||
if err := repoTx.CreateOne(c.Context(), createBody, nil); err != nil {
|
if err := repoTx.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer laying record")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat record transfer laying")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sourceDetail := range req.SourceKandangs {
|
for _, sourceDetail := range req.SourceKandangs {
|
||||||
@@ -292,78 +274,91 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
source := entity.LayingTransferSource{
|
source := entity.LayingTransferSource{
|
||||||
LayingTransferId: createBody.Id,
|
LayingTransferId: createBody.Id,
|
||||||
SourceProjectFlockKandangId: sourceDetail.ProjectFlockKandangId,
|
SourceProjectFlockKandangId: sourceDetail.ProjectFlockKandangId,
|
||||||
Qty: sourceDetail.Quantity,
|
UsageQty: 0,
|
||||||
|
PendingUsageQty: 0, // Di-set 0, biarkan FIFO Consume yang handle saat Approval
|
||||||
ProductWarehouseId: &productWarehouseId,
|
ProductWarehouseId: &productWarehouseId,
|
||||||
}
|
}
|
||||||
if err := dbTransaction.Create(&source).Error; err != nil {
|
if err := dbTransaction.Create(&source).Error; err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer source")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat sumber transfer")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstTargetProductWarehouseID *uint
|
for _, targetDetail := range req.TargetKandangs {
|
||||||
|
|
||||||
for i, targetDetail := range req.TargetKandangs {
|
targetprojectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId)
|
||||||
|
|
||||||
targetPFK, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target project flock kandang")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mendapatkan project flock kandang tujuan")
|
||||||
}
|
}
|
||||||
|
|
||||||
targetWarehouse, err := s.WarehouseRepo.GetLatestByKandangID(c.Context(), targetPFK.KandangId)
|
targetWarehouse, err := s.WarehouseRepo.GetLatestByKandangID(c.Context(), targetprojectFlockKandang.KandangId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No warehouse found for target kandang %d", targetDetail.ProjectFlockKandangId))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Warehouse tidak ditemukan untuk kandang tujuan %d", targetDetail.ProjectFlockKandangId))
|
||||||
}
|
}
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target warehouse")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mendapatkan warehouse tujuan")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ambil product ID dari salah satu source warehouse (harusnya semua sources product-nya sama)
|
||||||
|
var sourceProductID uint
|
||||||
|
for _, sourceDetail := range req.SourceKandangs {
|
||||||
|
if pwID, ok := sourceWarehouseMap[sourceDetail.ProjectFlockKandangId]; ok {
|
||||||
|
// Get product warehouse untuk ambil product ID
|
||||||
|
var sourcePW entity.ProductWarehouse
|
||||||
|
if err := dbTransaction.First(&sourcePW, pwID).Error; err == nil {
|
||||||
|
sourceProductID = sourcePW.ProductId
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceProductID == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mendapatkan product dari source warehouse")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cari product warehouse di target berdasarkan: warehouse + project_flock_kandang + PRODUCT
|
||||||
var targetPW entity.ProductWarehouse
|
var targetPW entity.ProductWarehouse
|
||||||
err = dbTransaction.Where("warehouse_id = ? AND project_flock_kandang_id = ?", targetWarehouse.Id, targetDetail.ProjectFlockKandangId).
|
err = dbTransaction.Where("warehouse_id = ? AND project_flock_kandang_id = ? AND product_id = ?",
|
||||||
|
targetWarehouse.Id, targetDetail.ProjectFlockKandangId, sourceProductID).
|
||||||
First(&targetPW).Error
|
First(&targetPW).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No product warehouse found for target kandang %d in warehouse %d", targetDetail.ProjectFlockKandangId, targetWarehouse.Id))
|
// Create baru dengan product yang sama dengan source
|
||||||
|
targetPW = entity.ProductWarehouse{
|
||||||
|
ProductId: sourceProductID,
|
||||||
|
WarehouseId: targetWarehouse.Id,
|
||||||
|
ProjectFlockKandangId: &targetDetail.ProjectFlockKandangId,
|
||||||
|
Quantity: 0,
|
||||||
|
}
|
||||||
|
if err := dbTransaction.Create(&targetPW).Error; err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal membuat product warehouse untuk kandang tujuan %d: %v", targetDetail.ProjectFlockKandangId, err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mendapatkan product warehouse untuk kandang tujuan %d: %v", targetDetail.ProjectFlockKandangId, err))
|
||||||
}
|
}
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to get product warehouse for target kandang %d: %v", targetDetail.ProjectFlockKandangId, err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target := entity.LayingTransferTarget{
|
target := entity.LayingTransferTarget{
|
||||||
LayingTransferId: createBody.Id,
|
LayingTransferId: createBody.Id,
|
||||||
TargetProjectFlockKandangId: targetDetail.ProjectFlockKandangId,
|
TargetProjectFlockKandangId: targetDetail.ProjectFlockKandangId,
|
||||||
Qty: targetDetail.Quantity,
|
TotalQty: targetDetail.Quantity,
|
||||||
|
TotalUsed: 0,
|
||||||
ProductWarehouseId: &targetPW.Id,
|
ProductWarehouseId: &targetPW.Id,
|
||||||
}
|
}
|
||||||
if err := dbTransaction.Create(&target).Error; err != nil {
|
if err := dbTransaction.Create(&target).Error; err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer target")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat target transfer")
|
||||||
}
|
|
||||||
|
|
||||||
if i == 0 {
|
|
||||||
firstTargetProductWarehouseID = &targetPW.Id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set DestProductWarehouseID untuk STOCKABLE role (ambil dari target pertama)
|
|
||||||
if firstTargetProductWarehouseID != nil {
|
|
||||||
createBody.DestProductWarehouseID = firstTargetProductWarehouseID
|
|
||||||
|
|
||||||
// Update DestProductWarehouseID ke database
|
|
||||||
if err := dbTransaction.Model(&entity.LayingTransfer{}).
|
|
||||||
Where("id = ?", createBody.Id).
|
|
||||||
Update("dest_product_warehouse_id", *firstTargetProductWarehouseID).Error; err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update DestProductWarehouseID")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := createApprovalTransferLaying(c.Context(), dbTransaction, createBody.Id, createBody.CreatedBy); err != nil {
|
if err := createApprovalTransferLaying(c.Context(), dbTransaction, createBody.Id, createBody.CreatedBy); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer approval")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat approval transfer")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer laying")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat transfer laying")
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.GetOne(c, createBody.Id)
|
return s.GetOne(c, createBody.Id)
|
||||||
@@ -412,24 +407,8 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update,
|
|||||||
|
|
||||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
repoTx := s.Repository.WithTx(dbTransaction)
|
repoTx := s.Repository.WithTx(dbTransaction)
|
||||||
projectFlockPopulationRepoTx := s.ProjectFlockPopulationRepo.WithTx(dbTransaction)
|
|
||||||
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
|
||||||
|
|
||||||
for _, oldSource := range existingTransfer.Sources {
|
|
||||||
if oldSource.ProductWarehouseId != nil && oldSource.Qty > 0 {
|
|
||||||
|
|
||||||
if err := productWarehouseRepoTx.PatchOne(c.Context(), *oldSource.ProductWarehouseId, map[string]any{
|
|
||||||
"qty": gorm.Expr("qty + ?", oldSource.Qty),
|
|
||||||
}, nil); err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to restore warehouse quantity")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.restoreProjectFlockPopulation(c.Context(), projectFlockPopulationRepoTx, oldSource.SourceProjectFlockKandangId, oldSource.Qty); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Hapus old sources dan targets
|
||||||
for _, oldSource := range existingTransfer.Sources {
|
for _, oldSource := range existingTransfer.Sources {
|
||||||
if err := dbTransaction.Delete(&oldSource).Error; err != nil {
|
if err := dbTransaction.Delete(&oldSource).Error; err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete old source")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete old source")
|
||||||
@@ -442,23 +421,16 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
totalSourceQty := 0.0
|
|
||||||
for _, source := range req.SourceKandangs {
|
|
||||||
totalSourceQty += source.Quantity
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := repoTx.PatchOne(c.Context(), id, map[string]any{
|
if err := repoTx.PatchOne(c.Context(), id, map[string]any{
|
||||||
"transfer_date": transferDate,
|
"transfer_date": transferDate,
|
||||||
"notes": req.Reason,
|
"notes": req.Reason,
|
||||||
"pending_usage_qty": &totalSourceQty,
|
|
||||||
}, nil); err != nil {
|
}, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transfer header")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transfer header")
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceWarehouseMap := make(map[uint]uint)
|
// Create new sources dengan pending quantity
|
||||||
for _, sourceDetail := range req.SourceKandangs {
|
for _, sourceDetail := range req.SourceKandangs {
|
||||||
|
populations, err := s.ProjectFlockPopulationRepo.GetByProjectFlockKandangID(c.Context(), sourceDetail.ProjectFlockKandangId)
|
||||||
populations, err := projectFlockPopulationRepoTx.GetByProjectFlockKandangID(c.Context(), sourceDetail.ProjectFlockKandangId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get populations")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get populations")
|
||||||
}
|
}
|
||||||
@@ -467,48 +439,37 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update,
|
|||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d has no population available", sourceDetail.ProjectFlockKandangId))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d has no population available", sourceDetail.ProjectFlockKandangId))
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalPopulation float64
|
|
||||||
var productWarehouseId uint
|
var productWarehouseId uint
|
||||||
|
|
||||||
for _, pop := range populations {
|
for _, pop := range populations {
|
||||||
totalPopulation += pop.TotalQty
|
|
||||||
if pop.ProductWarehouseId > 0 {
|
if pop.ProductWarehouseId > 0 {
|
||||||
productWarehouseId = pop.ProductWarehouseId
|
productWarehouseId = pop.ProductWarehouseId
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if totalPopulation < sourceDetail.Quantity {
|
if productWarehouseId == 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d has insufficient quantity. Available: %.0f, Requested: %.0f", sourceDetail.ProjectFlockKandangId, totalPopulation, sourceDetail.Quantity))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d has no product warehouse", sourceDetail.ProjectFlockKandangId))
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceWarehouseMap[sourceDetail.ProjectFlockKandangId] = productWarehouseId
|
|
||||||
|
|
||||||
source := entity.LayingTransferSource{
|
source := entity.LayingTransferSource{
|
||||||
LayingTransferId: id,
|
LayingTransferId: id,
|
||||||
SourceProjectFlockKandangId: sourceDetail.ProjectFlockKandangId,
|
SourceProjectFlockKandangId: sourceDetail.ProjectFlockKandangId,
|
||||||
Qty: sourceDetail.Quantity,
|
UsageQty: 0,
|
||||||
|
PendingUsageQty: sourceDetail.Quantity,
|
||||||
ProductWarehouseId: &productWarehouseId,
|
ProductWarehouseId: &productWarehouseId,
|
||||||
}
|
}
|
||||||
if err := dbTransaction.Create(&source).Error; err != nil {
|
if err := dbTransaction.Create(&source).Error; err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer source")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer source")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.reduceProjectFlockPopulation(c.Context(), projectFlockPopulationRepoTx, sourceDetail.ProjectFlockKandangId, sourceDetail.Quantity); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := productWarehouseRepoTx.PatchOne(c.Context(), productWarehouseId, map[string]any{"qty": gorm.Expr("qty - ?", sourceDetail.Quantity)}, nil); err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update source warehouse quantity")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, targetDetail := range req.TargetKandangs {
|
for _, targetDetail := range req.TargetKandangs {
|
||||||
targetPFK, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId)
|
targetprojectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target project flock kandang")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target project flock kandang")
|
||||||
}
|
}
|
||||||
|
|
||||||
targetWarehouse, err := s.WarehouseRepo.GetLatestByKandangID(c.Context(), targetPFK.KandangId)
|
targetWarehouse, err := s.WarehouseRepo.GetLatestByKandangID(c.Context(), targetprojectFlockKandang.KandangId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No warehouse found for target kandang %d", targetDetail.ProjectFlockKandangId))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No warehouse found for target kandang %d", targetDetail.ProjectFlockKandangId))
|
||||||
@@ -516,20 +477,50 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update,
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target warehouse")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target warehouse")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ambil product ID dari source yang pertama (semua sources seharusnya product-nya sama)
|
||||||
|
var sourceProductID uint
|
||||||
|
if len(req.SourceKandangs) > 0 {
|
||||||
|
firstSourceKandangID := req.SourceKandangs[0].ProjectFlockKandangId
|
||||||
|
populations, err := s.ProjectFlockPopulationRepo.GetByProjectFlockKandangID(c.Context(), firstSourceKandangID)
|
||||||
|
if err == nil && len(populations) > 0 && populations[0].ProductWarehouseId > 0 {
|
||||||
|
var sourcePW entity.ProductWarehouse
|
||||||
|
if err := dbTransaction.First(&sourcePW, populations[0].ProductWarehouseId).Error; err == nil {
|
||||||
|
sourceProductID = sourcePW.ProductId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceProductID == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get product from source warehouse")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cari product warehouse di target berdasarkan: warehouse + project_flock_kandang + PRODUCT
|
||||||
var targetPW entity.ProductWarehouse
|
var targetPW entity.ProductWarehouse
|
||||||
err = dbTransaction.Where("warehouse_id = ? AND project_flock_kandang_id = ?", targetWarehouse.Id, targetDetail.ProjectFlockKandangId).
|
err = dbTransaction.Where("warehouse_id = ? AND project_flock_kandang_id = ? AND product_id = ?",
|
||||||
|
targetWarehouse.Id, targetDetail.ProjectFlockKandangId, sourceProductID).
|
||||||
First(&targetPW).Error
|
First(&targetPW).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No product warehouse found for target kandang %d in warehouse %d", targetDetail.ProjectFlockKandangId, targetWarehouse.Id))
|
// Create baru dengan product yang sama dengan source
|
||||||
|
targetPW = entity.ProductWarehouse{
|
||||||
|
ProductId: sourceProductID,
|
||||||
|
WarehouseId: targetWarehouse.Id,
|
||||||
|
ProjectFlockKandangId: &targetDetail.ProjectFlockKandangId,
|
||||||
|
Quantity: 0,
|
||||||
|
}
|
||||||
|
if err := dbTransaction.Create(&targetPW).Error; err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to create product warehouse for target kandang %d: %v", targetDetail.ProjectFlockKandangId, err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to get product warehouse for target kandang %d: %v", targetDetail.ProjectFlockKandangId, err))
|
||||||
}
|
}
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to get product warehouse for target kandang %d: %v", targetDetail.ProjectFlockKandangId, err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target := entity.LayingTransferTarget{
|
target := entity.LayingTransferTarget{
|
||||||
LayingTransferId: id,
|
LayingTransferId: id,
|
||||||
TargetProjectFlockKandangId: targetDetail.ProjectFlockKandangId,
|
TargetProjectFlockKandangId: targetDetail.ProjectFlockKandangId,
|
||||||
Qty: targetDetail.Quantity,
|
TotalQty: targetDetail.Quantity,
|
||||||
|
TotalUsed: 0,
|
||||||
ProductWarehouseId: &targetPW.Id,
|
ProductWarehouseId: &targetPW.Id,
|
||||||
}
|
}
|
||||||
if err := dbTransaction.Create(&target).Error; err != nil {
|
if err := dbTransaction.Create(&target).Error; err != nil {
|
||||||
@@ -573,49 +564,9 @@ func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
|||||||
}
|
}
|
||||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
repoTx := s.Repository.WithTx(dbTransaction)
|
repoTx := s.Repository.WithTx(dbTransaction)
|
||||||
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
|
||||||
projectFlockPopulationRepoTx := s.ProjectFlockPopulationRepo.WithTx(dbTransaction)
|
|
||||||
|
|
||||||
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
|
|
||||||
sources, err := sourceRepoTx.GetByLayingTransferId(c.Context(), id)
|
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer sources")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, source := range sources {
|
|
||||||
if source.ProductWarehouseId != nil && source.Qty > 0 {
|
|
||||||
|
|
||||||
if err := productWarehouseRepoTx.PatchOne(c.Context(), *source.ProductWarehouseId, map[string]any{
|
|
||||||
"qty": gorm.Expr("qty + ?", source.Qty),
|
|
||||||
}, nil); err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to restore source warehouse quantity")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, source := range sources {
|
|
||||||
populations, err := projectFlockPopulationRepoTx.GetByProjectFlockKandangID(c.Context(), source.SourceProjectFlockKandangId)
|
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get populations for restoration")
|
|
||||||
}
|
|
||||||
|
|
||||||
remainingToRestore := source.Qty
|
|
||||||
for i := len(populations) - 1; i >= 0 && remainingToRestore > 0; i-- {
|
|
||||||
pop := populations[i]
|
|
||||||
restoreAmount := remainingToRestore
|
|
||||||
if pop.TotalQty < remainingToRestore {
|
|
||||||
restoreAmount = pop.TotalQty
|
|
||||||
}
|
|
||||||
|
|
||||||
newQty := pop.TotalQty + restoreAmount
|
|
||||||
if err := projectFlockPopulationRepoTx.PatchOne(c.Context(), pop.Id, map[string]any{"total_qty": newQty}, nil); err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to restore population quantity")
|
|
||||||
}
|
|
||||||
|
|
||||||
remainingToRestore -= restoreAmount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Delete transfer - cascade akan menghapus sources dan targets
|
||||||
|
// FIFO akan menangani stock allocation cleanup via foreign key constraints
|
||||||
if err := repoTx.DeleteOne(c.Context(), id); err != nil {
|
if err := repoTx.DeleteOne(c.Context(), id); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete transfer laying")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete transfer laying")
|
||||||
}
|
}
|
||||||
@@ -667,7 +618,6 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
|
|||||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
repoTx := s.Repository.WithTx(dbTransaction)
|
repoTx := s.Repository.WithTx(dbTransaction)
|
||||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
|
|
||||||
targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction)
|
targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction)
|
||||||
|
|
||||||
for _, approvableID := range approvableIDs {
|
for _, approvableID := range approvableIDs {
|
||||||
@@ -691,70 +641,77 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to record approval")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to record approval")
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == entity.ApprovalActionApproved && transfer.PendingUsageQty != nil && *transfer.PendingUsageQty > 0 {
|
if action == entity.ApprovalActionApproved {
|
||||||
|
|
||||||
sources, err := sourceRepoTx.GetByLayingTransferId(c.Context(), approvableID)
|
sources, err := repository.NewLayingTransferSourceRepository(dbTransaction).GetByLayingTransferId(c.Context(), approvableID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer sources")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil sources transfer")
|
||||||
}
|
}
|
||||||
|
|
||||||
targets, err := targetRepoTx.GetByLayingTransferId(c.Context(), approvableID)
|
targets, err := targetRepoTx.GetByLayingTransferId(c.Context(), approvableID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer targets")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil targets transfer")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sources) > 0 && len(targets) > 0 {
|
// Hitung total quantity dari targets untuk di-consume dari sources
|
||||||
|
totalTargetQty := 0.0
|
||||||
|
for _, target := range targets {
|
||||||
|
totalTargetQty += target.TotalQty
|
||||||
|
}
|
||||||
|
|
||||||
for _, source := range sources {
|
// Consume dari laying_transfer_sources (Usable) - akan consume dari ProjectFlockPopulation (Stockable)
|
||||||
if source.ProductWarehouseId == nil {
|
for _, source := range sources {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Source product warehouse not found for transfer %d", approvableID))
|
if source.ProductWarehouseId == nil {
|
||||||
}
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Source product warehouse tidak ditemukan untuk transfer %d", approvableID))
|
||||||
|
|
||||||
_, err := s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
|
|
||||||
UsableKey: fifo.UsableKeyTransferToLaying,
|
|
||||||
UsableID: approvableID,
|
|
||||||
ProductWarehouseID: *source.ProductWarehouseId,
|
|
||||||
Quantity: source.Qty,
|
|
||||||
AllowPending: false,
|
|
||||||
Tx: dbTransaction,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to consume FIFO stock for source %d: %v", source.ProductWarehouseId, err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if transfer.DestProductWarehouseID != nil {
|
consumeResult, err := s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
|
||||||
note := fmt.Sprintf("Transfer to Laying #%s", transfer.TransferNumber)
|
UsableKey: fifo.UsableKeyTransferToLayingOut,
|
||||||
replenishResult, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
|
UsableID: source.Id,
|
||||||
StockableKey: fifo.StockableKeyTransferToLaying,
|
ProductWarehouseID: *source.ProductWarehouseId,
|
||||||
StockableID: approvableID,
|
Quantity: totalTargetQty,
|
||||||
ProductWarehouseID: *transfer.DestProductWarehouseID,
|
AllowPending: false,
|
||||||
Quantity: *transfer.PendingUsageQty,
|
Tx: dbTransaction,
|
||||||
Note: ¬e,
|
})
|
||||||
Tx: dbTransaction,
|
if err != nil {
|
||||||
})
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal consume FIFO stock: %v", err))
|
||||||
if err != nil {
|
}
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to replenish stock to destination warehouse: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dbTransaction.Model(&entity.LayingTransfer{}).
|
// Update source usage tracking
|
||||||
Where("id = ?", approvableID).
|
if err := dbTransaction.Model(&entity.LayingTransferSource{}).
|
||||||
Updates(map[string]interface{}{
|
Where("id = ?", source.Id).
|
||||||
"total_qty": replenishResult.AddedQuantity,
|
Updates(map[string]interface{}{
|
||||||
}).Error; err != nil {
|
"usage_qty": source.UsageQty + consumeResult.UsageQuantity,
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update total quantity for transfer")
|
"pending_usage_qty": consumeResult.PendingQuantity,
|
||||||
}
|
}).Error; err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal update source usage qty")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usageQty := *transfer.PendingUsageQty
|
// Replenish ke target warehouse
|
||||||
updateData := map[string]any{
|
for _, target := range targets {
|
||||||
"usage_qty": usageQty,
|
if target.ProductWarehouseId == nil {
|
||||||
"total_qty": usageQty, // Same as usage_qty for initial transfer
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Target product warehouse tidak ditemukan untuk transfer %d", approvableID))
|
||||||
"pending_usage_qty": nil,
|
}
|
||||||
}
|
|
||||||
if err := repoTx.PatchOne(c.Context(), approvableID, updateData, nil); err != nil {
|
note := fmt.Sprintf("Transfer to Laying #%s - Target Kandang", transfer.TransferNumber)
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transfer laying status")
|
replenishResult, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
|
||||||
|
StockableKey: fifo.StockableKeyTransferToLayingIn,
|
||||||
|
StockableID: target.Id,
|
||||||
|
ProductWarehouseID: *target.ProductWarehouseId,
|
||||||
|
Quantity: target.TotalQty,
|
||||||
|
Note: ¬e,
|
||||||
|
Tx: dbTransaction,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal replenish stock ke target warehouse: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dbTransaction.Model(&entity.LayingTransferTarget{}).
|
||||||
|
Where("id = ?", target.Id).
|
||||||
|
Update("total_qty", replenishResult.AddedQuantity).Error; err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal update target total qty")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -832,66 +789,6 @@ func (s *transferLayingService) getOrCreateProductWarehouse(ctx context.Context,
|
|||||||
return newWarehouse, nil
|
return newWarehouse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *transferLayingService) reduceProjectFlockPopulation(ctx context.Context, populationRepo ProjectFlockRepository.ProjectFlockPopulationRepository, projectFlockKandangID uint, quantityToReduce float64) error {
|
|
||||||
|
|
||||||
populations, err := populationRepo.GetByProjectFlockKandangID(ctx, projectFlockKandangID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(populations) == 0 {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "No populations found for reduction")
|
|
||||||
}
|
|
||||||
|
|
||||||
remainingToReduce := quantityToReduce
|
|
||||||
|
|
||||||
for i := len(populations) - 1; i >= 0; i-- {
|
|
||||||
if remainingToReduce <= 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
pop := populations[i]
|
|
||||||
reductionAmount := remainingToReduce
|
|
||||||
if pop.TotalQty < remainingToReduce {
|
|
||||||
reductionAmount = pop.TotalQty
|
|
||||||
}
|
|
||||||
|
|
||||||
newQty := pop.TotalQty - reductionAmount
|
|
||||||
if err := populationRepo.PatchOne(ctx, pop.Id, map[string]any{"total_qty": newQty}, nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
remainingToReduce -= reductionAmount
|
|
||||||
}
|
|
||||||
|
|
||||||
if remainingToReduce > 0 {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient population to reduce. Still need to reduce: %.0f", remainingToReduce))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *transferLayingService) restoreProjectFlockPopulation(ctx context.Context, populationRepo ProjectFlockRepository.ProjectFlockPopulationRepository, projectFlockKandangID uint, quantityToRestore float64) error {
|
|
||||||
populations, err := populationRepo.GetByProjectFlockKandangID(ctx, projectFlockKandangID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(populations) == 0 {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "No populations found for restoration")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(populations) > 0 {
|
|
||||||
lastPop := populations[len(populations)-1]
|
|
||||||
newQty := lastPop.TotalQty + quantityToRestore
|
|
||||||
if err := populationRepo.PatchOne(ctx, lastPop.Id, map[string]any{"total_qty": newQty}, nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s transferLayingService) GetAvailableQtyPerKandang(ctx *fiber.Ctx, projectFlockID uint) (*entity.ProjectFlock, map[uint]float64, error) {
|
func (s transferLayingService) GetAvailableQtyPerKandang(ctx *fiber.Ctx, projectFlockID uint) (*entity.ProjectFlock, map[uint]float64, error) {
|
||||||
|
|
||||||
pf, err := s.ProjectFlockRepo.GetByID(ctx.Context(), projectFlockID, func(db *gorm.DB) *gorm.DB {
|
pf, err := s.ProjectFlockRepo.GetByID(ctx.Context(), projectFlockID, func(db *gorm.DB) *gorm.DB {
|
||||||
@@ -925,3 +822,27 @@ func (s transferLayingService) GetAvailableQtyPerKandang(ctx *fiber.Ctx, project
|
|||||||
|
|
||||||
return pf, kandangAvailableQty, nil
|
return pf, kandangAvailableQty, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *transferLayingService) validateKandangOwnership(
|
||||||
|
ctx context.Context,
|
||||||
|
projectFlockID uint,
|
||||||
|
kandangIDs []uint,
|
||||||
|
) error {
|
||||||
|
|
||||||
|
for _, kandangID := range kandangIDs {
|
||||||
|
// validasi terlebih dahulu apakah kandangnya itu ada atau gak
|
||||||
|
projectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(ctx, kandangID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Kandang %d tidak ditemukan", kandangID))
|
||||||
|
}
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get project flock kandang")
|
||||||
|
}
|
||||||
|
|
||||||
|
if projectFlockKandang.ProjectFlockId != projectFlockID {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Kandang %d tidak terhubung ke project flock %d", kandangID, projectFlockID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ package fifo
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Usable Keys
|
// Usable Keys
|
||||||
UsableKeyRecordingStock UsableKey = "RECORDING_STOCK"
|
UsableKeyRecordingStock UsableKey = "RECORDING_STOCK"
|
||||||
UsableKeyProjectChickin UsableKey = "PROJECT_CHICKIN"
|
UsableKeyProjectChickin UsableKey = "PROJECT_CHICKIN"
|
||||||
UsableKeyMarketingDelivery UsableKey = "MARKETING_DELIVERY"
|
UsableKeyMarketingDelivery UsableKey = "MARKETING_DELIVERY"
|
||||||
UsableKeyTransferToLaying UsableKey = "TRANSFER_TO_LAYING"
|
UsableKeyTransferToLayingOut UsableKey = "TRANSFERTOLAYING_OUT"
|
||||||
UsableKeyStockTransferOut UsableKey = "STOCK_TRANSFER_OUT"
|
UsableKeyStockTransferOut UsableKey = "STOCK_TRANSFER_OUT"
|
||||||
UsableKeyAdjustmentOut UsableKey = "ADJUSTMENT_OUT"
|
UsableKeyAdjustmentOut UsableKey = "ADJUSTMENT_OUT"
|
||||||
|
|
||||||
// Stockable Keys
|
// Stockable Keys
|
||||||
StockableKeyTransferToLaying StockableKey = "TRANSFER_TO_LAYING"
|
StockableKeyTransferToLayingIn StockableKey = "TRANSFERTOLAYING_IN"
|
||||||
StockableKeyStockTransferIn StockableKey = "STOCK_TRANSFER_IN"
|
StockableKeyStockTransferIn StockableKey = "STOCK_TRANSFER_IN"
|
||||||
StockableKeyAdjustmentIn StockableKey = "ADJUSTMENT_IN"
|
StockableKeyAdjustmentIn StockableKey = "ADJUSTMENT_IN"
|
||||||
StockableKeyPurchaseItems StockableKey = "PURCHASE_ITEMS"
|
StockableKeyPurchaseItems StockableKey = "PURCHASE_ITEMS"
|
||||||
StockableKeyProjectFlockPopulation StockableKey = "PROJECT_FLOCK_POPULATION"
|
StockableKeyProjectFlockPopulation StockableKey = "PROJECT_FLOCK_POPULATION"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user