FIX[BE]: fixing bug transfer to laying, delet biaya, nominal expesen e, chickin

This commit is contained in:
aguhh18
2026-01-07 09:27:39 +07:00
parent a08466a28e
commit 0a84e427c1
21 changed files with 432 additions and 126 deletions
@@ -0,0 +1,9 @@
-- Drop foreign key and column
ALTER TABLE laying_transfers
DROP CONSTRAINT IF EXISTS fk_laying_transfers_product_warehouse_id;
ALTER TABLE laying_transfers
DROP COLUMN IF EXISTS product_warehouse_id;
-- Drop index
DROP INDEX IF EXISTS idx_laying_transfers_product_warehouse_id;
@@ -0,0 +1,19 @@
-- Add product_warehouse_id to laying_transfers for FIFO support
ALTER TABLE laying_transfers
ADD COLUMN product_warehouse_id BIGINT;
-- Add foreign key
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'product_warehouses') THEN
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;
END IF;
END $$;
-- Add index
CREATE INDEX idx_laying_transfers_product_warehouse_id
ON laying_transfers(product_warehouse_id);
@@ -0,0 +1,14 @@
-- Rollback: Remove STOCKABLE fields from laying_transfers
-- Drop index
DROP INDEX IF EXISTS idx_laying_transfers_dest_product_warehouse_id;
-- Drop foreign key constraint
ALTER TABLE laying_transfers
DROP CONSTRAINT IF EXISTS fk_laying_transfers_dest_product_warehouse_id;
-- Drop columns
ALTER TABLE laying_transfers
DROP COLUMN IF EXISTS dest_product_warehouse_id,
DROP COLUMN IF EXISTS total_qty,
DROP COLUMN IF EXISTS total_used;
@@ -0,0 +1,30 @@
-- Add STOCKABLE fields to laying_transfers for destination warehouse
-- This enables Transfer to Laying to work as DUAL ROLE (Stockable + Usable)
-- Add columns 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,
ADD COLUMN total_used NUMERIC(15, 3) DEFAULT 0;
-- Add foreign key constraint
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'product_warehouses') THEN
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 $$;
-- Add index for performance
CREATE INDEX idx_laying_transfers_dest_product_warehouse_id
ON laying_transfers(dest_product_warehouse_id);
-- Add comment 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 STOCKABLE role';
+17 -6
View File
@@ -12,18 +12,29 @@ type LayingTransfer struct {
FromProjectFlockId uint `gorm:"not null"`
ToProjectFlockId uint `gorm:"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"`
CreatedBy uint `gorm:"not null"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index"`
FromProjectFlock *ProjectFlock `gorm:"foreignKey:FromProjectFlockId;references:Id"`
ToProjectFlock *ProjectFlock `gorm:"foreignKey:ToProjectFlockId;references:Id"`
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
Sources []LayingTransferSource `gorm:"foreignKey:LayingTransferId;constraint:OnDelete:CASCADE"`
Targets []LayingTransferTarget `gorm:"foreignKey:LayingTransferId;constraint:OnDelete:CASCADE"`
LatestApproval *Approval `gorm:"-" json:"-"`
FromProjectFlock *ProjectFlock `gorm:"foreignKey:FromProjectFlockId;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"`
Sources []LayingTransferSource `gorm:"foreignKey:LayingTransferId;constraint:OnDelete:CASCADE"`
Targets []LayingTransferTarget `gorm:"foreignKey:LayingTransferId;constraint:OnDelete:CASCADE"`
LatestApproval *Approval `gorm:"-" json:"-"`
}
@@ -36,7 +36,7 @@ func (AdjustmentModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validat
fifoService := commonSvc.NewFifoService(db, stockAllocRepo, productWarehouseRepo, utils.Log)
err := fifoService.RegisterStockable(fifo.StockableConfig{
Key: fifo.StockableKey("ADJUSTMENT_IN"),
Key: fifo.StockableKeyAdjustmentIn,
Table: "adjustment_stocks",
Columns: fifo.StockableColumns{
ID: "id",
@@ -52,7 +52,7 @@ func (AdjustmentModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validat
}
err = fifoService.RegisterUsable(fifo.UsableConfig{
Key: fifo.UsableKey("ADJUSTMENT_OUT"),
Key: fifo.UsableKeyAdjustmentOut,
Table: "adjustment_stocks",
Columns: fifo.UsableColumns{
ID: "id",
@@ -20,6 +20,7 @@ import (
projectFlockKandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
stockLogsRepo "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"
)
@@ -123,15 +124,9 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
projectFlockKandangID = &pfkID
}
pw, err := s.ProductWarehouseRepo.FindByProductWarehouseAndPfk(
ctx,
uint(req.ProductID),
uint(req.WarehouseID),
projectFlockKandangID,
)
pw, err := s.ProductWarehouseRepo.FindByProductWarehouseAndPfk(ctx, uint(req.ProductID), uint(req.WarehouseID), projectFlockKandangID)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
s.Log.Errorf("Failed to find product warehouse: %+v", err)
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get product warehouse")
}
@@ -143,7 +138,6 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
}
if err := s.ProductWarehouseRepo.CreateOne(ctx, newPW, nil); err != nil {
s.Log.Errorf("Failed to create product warehouse: %+v", err)
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create product warehouse")
}
pw = newPW
@@ -163,7 +157,6 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get product warehouse")
}
// Create StockLog for history tracking
afterQuantity := productWarehouse.Quantity
newLog := &entity.StockLog{
LoggableType: string(utils.StockLogTypeAdjustment),
@@ -189,7 +182,6 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
return err
}
// Create AdjustmentStock record for FIFO tracking
adjustmentStock := &entity.AdjustmentStock{
StockLogId: newLog.Id,
ProductWarehouseId: productWarehouse.Id,
@@ -200,10 +192,10 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
}
if transactionType == string(utils.StockLogTransactionTypeIncrease) {
// Adjustment INCREASE → Replenish stock (Stockable)
note := fmt.Sprintf("Stock Adjustment IN #%d", newLog.Id)
_, err := s.FifoSvc.Replenish(ctx, common.StockReplenishRequest{
StockableKey: "ADJUSTMENT_IN",
StockableKey: fifo.StockableKeyAdjustmentIn,
StockableID: adjustmentStock.Id,
ProductWarehouseID: uint(productWarehouse.Id),
Quantity: req.Quantity,
@@ -215,9 +207,8 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
}
} else {
// Adjustment DECREASE → Consume stock (Usable)
_, err := s.FifoSvc.Consume(ctx, common.StockConsumeRequest{
UsableKey: "ADJUSTMENT_OUT",
UsableKey: fifo.UsableKeyAdjustmentOut,
UsableID: adjustmentStock.Id,
ProductWarehouseID: uint(productWarehouse.Id),
Quantity: req.Quantity,
@@ -230,6 +221,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
}
// Update ProductWarehouse quantity (for backward compatibility/reporting)
productWarehouse.Quantity = afterQuantity
if err := s.ProductWarehouseRepo.WithTx(tx).UpdateOne(ctx, productWarehouse.Id, productWarehouse, nil); err != nil {
s.Log.Errorf("Failed to update product warehouse quantity: %+v", err)
@@ -43,12 +43,10 @@ func (TransferModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
panic(err)
}
// Initialize FIFO Service
fifoService := commonSvc.NewFifoService(db, stockAllocRepo, productWarehouseRepo, utils.Log)
// Register Transfer as Stockable (adds stock to destination warehouse)
err = fifoService.RegisterStockable(fifo.StockableConfig{
Key: fifo.StockableKey("STOCK_TRANSFER_IN"),
Key: fifo.StockableKeyStockTransferIn,
Table: "stock_transfer_details",
Columns: fifo.StockableColumns{
ID: "id",
@@ -63,9 +61,8 @@ func (TransferModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
panic(err)
}
// Register Transfer as Usable (consumes stock from source warehouse)
err = fifoService.RegisterUsable(fifo.UsableConfig{
Key: fifo.UsableKey("STOCK_TRANSFER_OUT"),
Key: fifo.UsableKeyStockTransferOut,
Table: "stock_transfer_details",
Columns: fifo.UsableColumns{
ID: "id",
@@ -21,6 +21,7 @@ import (
projectFlockKandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/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"
)
@@ -337,24 +338,21 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
}
}
// Execute FIFO operations for each product
for _, product := range req.Products {
detail := detailMap[uint64(product.ProductID)]
// Step 1: Consume stock from source warehouse (STOCK_TRANSFER_OUT)
consumeResult, err := s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
UsableKey: "STOCK_TRANSFER_OUT",
UsableKey: fifo.UsableKeyStockTransferOut,
UsableID: uint(detail.Id),
ProductWarehouseID: uint(*detail.SourceProductWarehouseID),
Quantity: product.ProductQty,
AllowPending: false, // Don't allow pending, must have actual stock
AllowPending: false,
Tx: tx,
})
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok tidak cukup di gudang asal untuk produk %d: %v", product.ProductID, err))
}
// Update usage tracking fields for source warehouse
if err := tx.Model(&entity.StockTransferDetail{}).
Where("id = ?", detail.Id).
Updates(map[string]interface{}{
@@ -367,7 +365,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
// Step 2: Replenish stock to destination warehouse (STOCK_TRANSFER_IN)
note := fmt.Sprintf("Transfer #%s", entityTransfer.MovementNumber)
replenishResult, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
StockableKey: "STOCK_TRANSFER_IN",
StockableKey: fifo.StockableKeyStockTransferIn,
StockableID: uint(detail.Id),
ProductWarehouseID: uint(*detail.DestProductWarehouseID),
Quantity: product.ProductQty,
@@ -378,7 +376,6 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal menambah stok di gudang tujuan untuk produk %d: %v", product.ProductID, err))
}
// Update total tracking fields for destination warehouse
if err := tx.Model(&entity.StockTransferDetail{}).
Where("id = ?", detail.Id).
Updates(map[string]interface{}{
@@ -58,6 +58,24 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
}
}
if err := fifoService.RegisterStockable(fifo.StockableConfig{
Key: fifo.StockableKeyProjectFlockPopulation,
Table: "project_flock_populations",
Columns: fifo.StockableColumns{
ID: "id",
ProductWarehouseID: "product_warehouse_id",
TotalQuantity: "total_qty",
TotalUsedQuantity: "total_used_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 project flock population stockable workflow: %v", err))
}
}
approvalRepo := commonRepo.NewApprovalRepository(db)
approvalService := commonSvc.NewApprovalService(approvalRepo)
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowChickin, utils.ChickinApprovalSteps); err != nil {
@@ -17,6 +17,7 @@ type ProjectChickinRepository interface {
GetTotalPendingUsageQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
GetTotalChickinQtyByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error)
GetByProjectFlockKandangIDForUpdate(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error)
UpdateUsageFields(ctx context.Context, tx *gorm.DB, chickinID uint, usageQty, pendingUsageQty float64) error
}
type ChickinRepositoryImpl struct {
@@ -123,3 +124,13 @@ func (r *ChickinRepositoryImpl) GetTotalChickinQtyByProjectFlockID(ctx context.C
Scan(&result).Error
return result, err
}
func (r *ChickinRepositoryImpl) UpdateUsageFields(ctx context.Context, tx *gorm.DB, chickinID uint, usageQty, pendingUsageQty float64) error {
return tx.WithContext(ctx).
Model(&entity.ProjectChickin{}).
Where("id = ?", chickinID).
Updates(map[string]interface{}{
"usage_qty": usageQty,
"pending_usage_qty": pendingUsageQty,
}).Error
}
@@ -214,9 +214,6 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
pendingQtyMap[existingChickin.ProductWarehouseId] += existingChickin.PendingUsageQty
}
}
// CRITICAL: Validate chickins sequentially to prevent over-allocation within the same request
// pendingQtyMap is accumulated as we validate each chickin to ensure total pending doesn't exceed available stock
for idx, chickin := range newChikins {
pendingQty := pendingQtyMap[chickin.ProductWarehouseId]
desiredQty := chickinQtyMap[uint(idx)]
@@ -232,8 +229,6 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
chickinQtyMap[uint(idx)] = availableQty
// ACCUMULATE pending for this product warehouse so NEXT chickin in same request sees it
// This prevents multiple chickins in same request from over-allocating the same stock
pendingQtyMap[chickin.ProductWarehouseId] += availableQty
}
@@ -358,12 +353,15 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
}
if chickin.UsageQty > 0 {
currentUsageQty := chickin.UsageQty
if err := s.ReleaseChickinStocks(c.Context(), s.Repository.DB(), chickin, actorID); err != nil {
return err
}
warehouseDeltas := make(map[uint]float64)
warehouseDeltas[chickin.ProductWarehouseId] += chickin.UsageQty
warehouseDeltas[chickin.ProductWarehouseId] += currentUsageQty
if err := s.adjustProductWarehouseQuantities(c.Context(), s.Repository.DB(), warehouseDeltas); err != nil {
s.Log.Errorf("Failed to adjust product warehouses for deleted chickin %d: %+v", chickin.Id, err)
return err
@@ -618,7 +616,7 @@ func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []enti
population := &entity.ProjectFlockPopulation{
ProjectChickinId: chickin.Id,
ProductWarehouseId: targetPW.Id,
TotalQty: quantityToConvert,
TotalQty: 0, // Will be set by FIFO Replenish
TotalUsedQty: 0,
Notes: chickin.Notes,
CreatedBy: actorID,
@@ -634,15 +632,22 @@ func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []enti
return fmt.Errorf("failed to reset pending usage qty for chickin %d: %w", chickin.Id, err)
}
// Replenish stock to target ProductWarehouse based on source flag
// StockableKey is PROJECT_CHICKIN but StockableID refers to Population ID
if err := s.ReplenishChickinStocks(ctx.Context(), dbTransaction, &chickin, targetPW, population, actorID); err != nil {
s.Log.Errorf("Failed to replenish stock for chickin %d: %+v", chickin.Id, err)
return err
}
totalQuantityAdded += quantityToConvert
}
// NOTE: Tidak menambah target ProductWarehouse quantity karena:
// 1. Ayam sudah dipakai (masuk population)
// 2. ProductWarehouse source sudah berkurang saat create chickin (ConsumeChickinStocks)
// 3. Menambah quantity disini akan menyebabkan double count
//
// PULLET/LAYER untuk flock ini akan di-add lewat mekanisme lain (misal: purchase, transfer, dll)
// NOTE: ProductWarehouse target sudah ditambah melalui ReplenishChickinStocks
// yang dipanggil di atas untuk setiap chickin berdasarkan flag source:
// - DOC → replenish ke PULLET
// - PULLET → replenish ke LAYER
// - LAYER → tidak perlu replenish (sudah final)
// - DOC+PULLET+LAYER → replenish ke dirinya sendiri
return nil
}
@@ -671,10 +676,7 @@ func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB,
s.Log.Infof("ConsumeChickinStocks result: usage_qty=%.3f, pending_qty=%.3f, allocated_allocations=%d",
result.UsageQuantity, result.PendingQuantity, len(result.AddedAllocations))
if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Updates(map[string]interface{}{
"usage_qty": result.UsageQuantity,
"pending_usage_qty": result.PendingQuantity,
}).Error; err != nil {
if err := s.Repository.UpdateUsageFields(ctx, tx, chickin.Id, result.UsageQuantity, result.PendingQuantity); err != nil {
return err
}
@@ -696,6 +698,101 @@ func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB,
return nil
}
func (s *chickinService) ReplenishChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, targetPW *entity.ProductWarehouse, population *entity.ProjectFlockPopulation, actorID uint) error {
if chickin == nil || targetPW == nil || population == nil || s.FifoSvc == nil {
return nil
}
sourcePW, err := s.ProductWarehouseRepo.GetByID(ctx, chickin.ProductWarehouseId, func(db *gorm.DB) *gorm.DB {
return db.Preload("Product.Flags")
})
if err != nil {
return err
}
if sourcePW == nil || sourcePW.Product.Id == 0 {
return fmt.Errorf("source product warehouse or product not found for chickin %d", chickin.Id)
}
sourceFlags := sourcePW.Product.Flags
if len(sourceFlags) == 0 {
s.Log.Warnf("Source product %d has no flags, skipping replenish for chickin %d", sourcePW.Product.Id, chickin.Id)
return nil
}
hasDoc := false
hasPullet := false
hasLayer := false
for _, flag := range sourceFlags {
flagName := utils.FlagType(flag.Name)
if flagName == utils.FlagDOC {
hasDoc = true
} else if flagName == utils.FlagPullet {
hasPullet = true
} else if flagName == utils.FlagLayer {
hasLayer = true
}
}
if hasDoc && hasPullet && hasLayer {
s.Log.Infof("Chickin %d has mixed flags (DOC+PULLET+LAYER), replenishing to source PW %d", chickin.Id, sourcePW.Id)
_, err = s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyProjectFlockPopulation,
StockableID: population.Id,
ProductWarehouseID: sourcePW.Id,
Quantity: chickin.UsageQty,
Tx: tx,
})
if err != nil {
s.Log.Errorf("Failed to replenish stock to source PW for chickin %d: %+v", chickin.Id, err)
return err
}
return nil
}
// LAYER only - no replenish needed
if hasLayer && !hasDoc && !hasPullet {
s.Log.Infof("Chickin %d has LAYER flag only, skipping replenish", chickin.Id)
return nil
}
if hasDoc && !hasPullet && !hasLayer {
s.Log.Infof("Chickin %d has DOC flag, replenishing to PULLET PW %d", chickin.Id, targetPW.Id)
_, err = s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyProjectFlockPopulation,
StockableID: population.Id,
ProductWarehouseID: targetPW.Id,
Quantity: chickin.UsageQty,
Tx: tx,
})
if err != nil {
s.Log.Errorf("Failed to replenish stock to PULLET PW for chickin %d: %+v", chickin.Id, err)
return err
}
return nil
}
if hasPullet && !hasDoc && !hasLayer {
s.Log.Infof("Chickin %d has PULLET flag, replenishing to LAYER PW %d", chickin.Id, targetPW.Id)
_, err = s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyProjectFlockPopulation,
StockableID: population.Id,
ProductWarehouseID: targetPW.Id,
Quantity: chickin.UsageQty,
Tx: tx,
})
if err != nil {
s.Log.Errorf("Failed to replenish stock to LAYER PW for chickin %d: %+v", chickin.Id, err)
return err
}
return nil
}
// Other combinations (e.g., DOC + PULLET without LAYER) - skip for now
s.Log.Warnf("Chickin %d has unsupported flag combination, skipping replenish", chickin.Id)
return nil
}
func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, actorID uint) error {
if chickin == nil || s.FifoSvc == nil {
return nil
@@ -703,8 +800,7 @@ func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB,
var currentUsage float64
if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Select("usage_qty").Scan(&currentUsage).Error; err != nil {
s.Log.Warnf("Failed to get current usage for chickin %d: %+v", chickin.Id, err)
currentUsage = 0
}
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
@@ -716,14 +812,10 @@ func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB,
return err
}
if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Updates(map[string]interface{}{
"usage_qty": 0,
"pending_usage_qty": 0,
}).Error; err != nil {
if err := s.Repository.UpdateUsageFields(ctx, tx, chickin.Id, 0, 0); err != nil {
return err
}
// Create stock log for the restoration
if currentUsage > 0 {
increaseLog := &entity.StockLog{
Increase: currentUsage,
@@ -734,8 +826,7 @@ func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB,
Notes: fmt.Sprintf("Chickin #%d - Stock released", chickin.Id),
}
if err := s.StockLogRepo.CreateOne(ctx, increaseLog, nil); err != nil {
s.Log.Errorf("Failed to create stock log for chickin %d: %+v", chickin.Id, err)
// Don't return error here, stock already released
s.Log.Errorf("Failed to create stock log for released chickin %d: %+v", chickin.Id, err)
}
}
@@ -552,6 +552,7 @@ func (s projectFlockKandangService) Closing(c *fiber.Ctx, id uint, req *validati
}
func (s projectFlockKandangService) calculateAvailableQuantityForProductWarehouse(c *fiber.Ctx, projectFlockKandang *entity.ProjectFlockKandang, productWarehouse *entity.ProductWarehouse) (float64, error) {
availableQty := productWarehouse.Quantity
if projectFlockKandang.ProjectFlock.Category == string(utils.ProjectFlockCategoryGrowing) {
@@ -564,7 +565,17 @@ func (s projectFlockKandangService) calculateAvailableQuantityForProductWarehous
}
}
availableQty = productWarehouse.Quantity - totalPendingQty
totalPopulationQty := 0.0
if s.PopulationRepo != nil {
popQty, err := s.PopulationRepo.GetTotalQtyByProductWarehouseID(c.Context(), productWarehouse.Id)
if err != nil {
s.Log.Errorf("Failed to get population qty for PW %d: %+v", productWarehouse.Id, err)
} else {
totalPopulationQty = popQty
}
}
availableQty = productWarehouse.Quantity - totalPendingQty - totalPopulationQty
if availableQty < 0 {
availableQty = 0
}
@@ -578,7 +589,17 @@ func (s projectFlockKandangService) calculateAvailableQuantityForProductWarehous
}
}
availableQty = productWarehouse.Quantity - totalPendingQty
totalPopulationQty := 0.0
if s.PopulationRepo != nil {
popQty, err := s.PopulationRepo.GetTotalQtyByProductWarehouseID(c.Context(), productWarehouse.Id)
if err != nil {
s.Log.Errorf("Failed to get population qty for PW %d: %+v", productWarehouse.Id, err)
} else {
totalPopulationQty = popQty
}
}
availableQty = productWarehouse.Quantity - totalPendingQty - totalPopulationQty
if availableQty < 0 {
availableQty = 0
}
@@ -9,19 +9,17 @@ import (
)
type ProjectFlockPopulationRepository interface {
// domain-specific
GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectFlockPopulation, error)
ExistsByProjectChickinID(ctx context.Context, projectChickinID uint) (bool, error)
GetByProjectChickinIDAndProductWarehouseID(ctx context.Context, projectChickinID uint, productWarehouseID uint) ([]entity.ProjectFlockPopulation, error)
GetByProjectFlockKandangIDAndProductWarehouseID(ctx context.Context, projectFlockKandangID uint, productWarehouseID uint) ([]entity.ProjectFlockPopulation, error)
GetTotalQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
GetTotalQtyByProductWarehouseID(ctx context.Context, productWarehouseID uint) (float64, error)
GetAvailableQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
// subset of base repository methods used by services
CreateOne(ctx context.Context, entity *entity.ProjectFlockPopulation, modifier func(*gorm.DB) *gorm.DB) error
PatchOne(ctx context.Context, id uint, updates map[string]any, modifier func(*gorm.DB) *gorm.DB) error
// transaction helpers
WithTx(tx *gorm.DB) ProjectFlockPopulationRepository
DB() *gorm.DB
}
@@ -108,6 +106,19 @@ func (r *projectFlockPopulationRepositoryImpl) GetTotalQtyByProjectFlockKandangI
return total, nil
}
func (r *projectFlockPopulationRepositoryImpl) GetTotalQtyByProductWarehouseID(ctx context.Context, productWarehouseID uint) (float64, error) {
var total float64
err := r.DB().WithContext(ctx).
Model(&entity.ProjectFlockPopulation{}).
Where("product_warehouse_id = ?", productWarehouseID).
Select("COALESCE(SUM(total_qty), 0)").
Scan(&total).Error
if err != nil {
return 0, err
}
return total, nil
}
func (r *projectFlockPopulationRepositoryImpl) GetAvailableQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error) {
var total float64
err := r.DB().WithContext(ctx).
@@ -28,6 +28,7 @@ type ProjectFlockKandangRepository interface {
ProjectPeriodsByProjectIDs(ctx context.Context, projectIDs []uint) (map[uint]int, error)
HasOpenNewerPeriod(ctx context.Context, kandangID uint, currentPeriod int, excludeID *uint) (bool, error)
ListIDsByProjectAndKandang(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]uint, error)
GetTotalPendingChickinQtyByProductWarehouseID(ctx context.Context, productWarehouseID uint) (float64, error)
WithTx(tx *gorm.DB) ProjectFlockKandangRepository
DB() *gorm.DB
IdExists(ctx context.Context, id uint) (bool, error)
@@ -206,6 +207,19 @@ func (r *projectFlockKandangRepositoryImpl) IdExists(ctx context.Context, id uin
return repository.Exists[entity.ProjectFlockKandang](ctx, r.db, id)
}
func (r *projectFlockKandangRepositoryImpl) GetTotalPendingChickinQtyByProductWarehouseID(ctx context.Context, productWarehouseID uint) (float64, error) {
var total float64
err := r.db.WithContext(ctx).
Table("project_chickins").
Select("COALESCE(SUM(pending_usage_qty), 0)").
Where("product_warehouse_id = ?", productWarehouseID).
Scan(&total).Error
if err != nil {
return 0, err
}
return total, nil
}
func (r *projectFlockKandangRepositoryImpl) GetByID(ctx context.Context, id uint) (*entity.ProjectFlockKandang, error) {
record := new(entity.ProjectFlockKandang)
if err := r.db.WithContext(ctx).
@@ -2,6 +2,7 @@ package transfer_layings
import (
"fmt"
"strings"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
@@ -13,6 +14,7 @@ import (
rTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/repositories"
sTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/services"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
rInventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
@@ -31,6 +33,44 @@ func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, val
productWarehouseRepo := rInventory.NewProductWarehouseRepository(db)
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
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{
Key: fifo.StockableKeyTransferToLaying,
Table: "laying_transfers",
Columns: fifo.StockableColumns{
ID: "id",
ProductWarehouseID: "dest_product_warehouse_id",
TotalQuantity: "total_qty",
TotalUsedQuantity: "total_used",
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 stockable workflow: %v", err))
}
}
approvalRepo := commonRepo.NewApprovalRepository(db)
approvalService := commonSvc.NewApprovalService(approvalRepo)
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowTransferToLaying, utils.TransferToLayingApprovalSteps); err != nil {
@@ -45,6 +85,7 @@ func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, val
productWarehouseRepo,
warehouseRepo,
approvalService,
fifoService,
validate,
)
userService := sUser.NewUserService(userRepo, validate)
@@ -17,6 +17,7 @@ import (
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"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
@@ -45,6 +46,7 @@ type transferLayingService struct {
ProductWarehouseRepo rInventory.ProductWarehouseRepository
WarehouseRepo rWarehouse.WarehouseRepository
ApprovalService commonSvc.ApprovalService
FifoSvc commonSvc.FifoService
}
func NewTransferLayingService(
@@ -55,6 +57,7 @@ func NewTransferLayingService(
productWarehouseRepo rInventory.ProductWarehouseRepository,
warehouseRepo rWarehouse.WarehouseRepository,
approvalService commonSvc.ApprovalService,
fifoSvc commonSvc.FifoService,
validate *validator.Validate,
) TransferLayingService {
return &transferLayingService{
@@ -67,6 +70,7 @@ func NewTransferLayingService(
ProductWarehouseRepo: productWarehouseRepo,
WarehouseRepo: warehouseRepo,
ApprovalService: approvalService,
FifoSvc: fifoSvc,
}
}
@@ -268,15 +272,20 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
CreatedBy: actorID,
}
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
if len(sourceWarehouseMap) > 0 {
for _, pwID := range sourceWarehouseMap {
createBody.ProductWarehouseId = &pwID
break
}
}
if err := s.Repository.WithTx(dbTransaction).CreateOne(c.Context(), createBody, nil); err != nil {
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
repoTx := s.Repository.WithTx(dbTransaction)
if err := repoTx.CreateOne(c.Context(), createBody, nil); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer laying record")
}
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
projectFlockPopulationRepoTx := s.ProjectFlockPopulationRepo.WithTx(dbTransaction)
for _, sourceDetail := range req.SourceKandangs {
productWarehouseId := sourceWarehouseMap[sourceDetail.ProjectFlockKandangId]
@@ -290,13 +299,6 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer source")
}
if err := s.reduceProjectFlockPopulation(c.Context(), projectFlockPopulationRepoTx, sourceDetail.ProjectFlockKandangId, sourceDetail.Quantity); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reduce project flock population")
}
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 {
@@ -325,6 +327,22 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
}
}
// Set DestProductWarehouseID untuk STOCKABLE role (ambil dari target pertama)
if len(req.TargetKandangs) > 0 {
firstTargetPWID := req.TargetKandangs[0].ProjectFlockKandangId
// Cari ProductWarehouse untuk target kandang
targetWarehouse, _ := s.WarehouseRepo.GetLatestByKandangID(c.Context(), firstTargetPWID)
if targetWarehouse != nil {
// Query ProductWarehouse by warehouse and kandang
var targetPW entity.ProductWarehouse
err := dbTransaction.Where("warehouse_id = ? AND project_flock_kandang_id = ?", targetWarehouse.Id, firstTargetPWID).
First(&targetPW).Error
if err == nil {
createBody.DestProductWarehouseID = &targetPW.Id
}
}
}
if err := createApprovalTransferLaying(c.Context(), dbTransaction, createBody.Id, createBody.CreatedBy); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transfer approval")
}
@@ -339,7 +357,7 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
return s.GetOne(c, createBody.Id)
}
func (s transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.LayingTransfer, error) {
func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.LayingTransfer, error) {
if err := s.Validate.Struct(req); err != nil {
return nil, err
}
@@ -381,6 +399,7 @@ func (s transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, i
}
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
repoTx := s.Repository.WithTx(dbTransaction)
projectFlockPopulationRepoTx := s.ProjectFlockPopulationRepo.WithTx(dbTransaction)
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
@@ -416,7 +435,7 @@ func (s transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, i
totalSourceQty += source.Quantity
}
if err := s.Repository.WithTx(dbTransaction).PatchOne(c.Context(), id, map[string]any{
if err := repoTx.PatchOne(c.Context(), id, map[string]any{
"transfer_date": transferDate,
"notes": req.Reason,
"pending_usage_qty": &totalSourceQty,
@@ -531,8 +550,9 @@ func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error {
}
}
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
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)
@@ -551,7 +571,6 @@ func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error {
}
}
projectFlockPopulationRepoTx := s.ProjectFlockPopulationRepo.WithTx(dbTransaction)
for _, source := range sources {
populations, err := projectFlockPopulationRepoTx.GetByProjectFlockKandangID(c.Context(), source.SourceProjectFlockKandangId)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
@@ -575,7 +594,7 @@ func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error {
}
}
if err := s.Repository.WithTx(dbTransaction).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")
}
@@ -624,14 +643,13 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
}
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
repoTx := s.Repository.WithTx(dbTransaction)
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction)
productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
for _, approvableID := range approvableIDs {
transfer, err := s.Repository.GetByID(c.Context(), approvableID, nil)
transfer, err := repoTx.GetByID(c.Context(), approvableID, nil)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("TransferLaying %d not found", approvableID))
@@ -664,44 +682,45 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
}
if len(sources) > 0 && len(targets) > 0 {
firstSource := sources[0]
if firstSource.ProductWarehouseId == nil {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Source product warehouse not found for transfer %d", approvableID))
}
sourceWarehouse, err := productWarehouseRepoTx.GetByID(c.Context(), *firstSource.ProductWarehouseId, nil)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get source warehouse")
}
for _, target := range targets {
targetPFK, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), target.TargetProjectFlockKandangId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
continue
}
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target project flock kandang")
for _, source := range sources {
if source.ProductWarehouseId == nil {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Source product warehouse not found for transfer %d", approvableID))
}
targetWarehouse, err := s.WarehouseRepo.GetLatestByKandangID(c.Context(), targetPFK.KandangId)
_, 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 {
if errors.Is(err, gorm.ErrRecordNotFound) {
continue
}
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target warehouse")
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to consume FIFO stock for source %d: %v", source.ProductWarehouseId, err))
}
}
if transfer.DestProductWarehouseID != nil {
note := fmt.Sprintf("Transfer to Laying #%s", transfer.TransferNumber)
replenishResult, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyTransferToLaying,
StockableID: approvableID,
ProductWarehouseID: *transfer.DestProductWarehouseID,
Quantity: *transfer.PendingUsageQty,
Note: &note,
Tx: dbTransaction,
})
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to replenish stock to destination warehouse: %v", err))
}
if _, err := s.getOrCreateProductWarehouse(
c.Context(),
dbTransaction,
sourceWarehouse.ProductId,
targetWarehouse.Id,
target.Qty,
actorID,
&target.TargetProjectFlockKandangId, // Set flock ID agar bisa di-chickin di target flock
); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create or update product warehouse")
if err := dbTransaction.Model(&entity.LayingTransfer{}).
Where("id = ?", approvableID).
Updates(map[string]interface{}{
"total_qty": replenishResult.AddedQuantity,
}).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update total quantity for transfer")
}
}
}
@@ -709,9 +728,10 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
usageQty := *transfer.PendingUsageQty
updateData := map[string]any{
"usage_qty": usageQty,
"total_qty": usageQty, // Same as usage_qty for initial transfer
"pending_usage_qty": nil,
}
if err := s.Repository.WithTx(dbTransaction).PatchOne(c.Context(), approvableID, updateData, nil); err != nil {
if err := repoTx.PatchOne(c.Context(), approvableID, updateData, nil); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transfer laying status")
}
}
+1 -1
View File
@@ -75,7 +75,7 @@ func (PurchaseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
fifoService := commonSvc.NewFifoService(db, stockAllocRepo, productWarehouseRepo, utils.Log)
_ = fifoService.RegisterStockable(fifo.StockableConfig{
Key: fifo.StockableKey("PURCHASE_ITEMS"),
Key: fifo.StockableKeyPurchaseItems,
Table: "purchase_items",
Columns: fifo.StockableColumns{
ID: "id",
@@ -42,8 +42,7 @@ type PurchaseService interface {
}
const (
priceTolerance = 0.0001
purchaseStockableKey = fifo.StockableKey("PURCHASE_ITEMS")
priceTolerance = 0.0001
)
type purchaseService struct {
@@ -924,7 +923,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
continue
}
if _, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
StockableKey: purchaseStockableKey,
StockableKey: fifo.StockableKeyPurchaseItems,
StockableID: adj.itemID,
ProductWarehouseID: adj.pwID,
Quantity: adj.qty,
@@ -106,7 +106,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
Where("r.deleted_at IS NULL")
recordingPfk = applyLocationFilters(recordingPfk, areaIDs, locationIDs, kandangIDs)
purchaseStockableKey := fifo.StockableKey("PURCHASE_ITEMS").String()
purchaseStockableKey := fifo.StockableKeyPurchaseItems.String()
transferStockableKey := fifo.StockableKey("STOCK_TRANSFER_DETAILS").String()
query := r.db.WithContext(ctx).
+14 -3
View File
@@ -1,7 +1,18 @@
package fifo
const (
UsableKeyRecordingStock UsableKey = "RECORDING_STOCK"
UsableKeyProjectChickin UsableKey = "PROJECT_CHICKIN"
UsableKeyMarketingDelivery UsableKey = "MARKETING_DELIVERY"
// Usable Keys
UsableKeyRecordingStock UsableKey = "RECORDING_STOCK"
UsableKeyProjectChickin UsableKey = "PROJECT_CHICKIN"
UsableKeyMarketingDelivery UsableKey = "MARKETING_DELIVERY"
UsableKeyTransferToLaying UsableKey = "TRANSFER_TO_LAYING"
UsableKeyStockTransferOut UsableKey = "STOCK_TRANSFER_OUT"
UsableKeyAdjustmentOut UsableKey = "ADJUSTMENT_OUT"
// Stockable Keys
StockableKeyTransferToLaying StockableKey = "TRANSFER_TO_LAYING"
StockableKeyStockTransferIn StockableKey = "STOCK_TRANSFER_IN"
StockableKeyAdjustmentIn StockableKey = "ADJUSTMENT_IN"
StockableKeyPurchaseItems StockableKey = "PURCHASE_ITEMS"
StockableKeyProjectFlockPopulation StockableKey = "PROJECT_FLOCK_POPULATION"
)