mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat: konfigurasi sistem toggle pemakaian pakan ovk negatif
This commit is contained in:
@@ -27,6 +27,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"
|
||||
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
||||
rSystemSettings "gitlab.com/mbugroup/lti-api.git/internal/modules/system-settings/repositories"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||
|
||||
@@ -159,6 +160,8 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
||||
validate,
|
||||
)
|
||||
|
||||
systemSettingRepo := rSystemSettings.NewSystemSettingRepository(db)
|
||||
|
||||
recordingService := sRecording.NewRecordingService(
|
||||
recordingRepo,
|
||||
projectFlockKandangRepo,
|
||||
@@ -174,6 +177,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
||||
transferLayingRepo,
|
||||
transferLayingService,
|
||||
validate,
|
||||
systemSettingRepo,
|
||||
)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
|
||||
@@ -24,6 +24,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"
|
||||
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
||||
rSystemSettings "gitlab.com/mbugroup/lti-api.git/internal/modules/system-settings/repositories"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
||||
fifo "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||
@@ -63,6 +64,7 @@ type recordingService struct {
|
||||
TransferLayingSvc sTransferLaying.TransferLayingService
|
||||
FifoStockV2Svc commonSvc.FifoStockV2Service
|
||||
StockLogRepo rStockLogs.StockLogRepository
|
||||
SystemSettingRepo rSystemSettings.SystemSettingRepository
|
||||
}
|
||||
|
||||
func NewRecordingService(
|
||||
@@ -80,6 +82,7 @@ func NewRecordingService(
|
||||
transferLayingRepo rTransferLaying.TransferLayingRepository,
|
||||
transferLayingSvc sTransferLaying.TransferLayingService,
|
||||
validate *validator.Validate,
|
||||
systemSettingRepo rSystemSettings.SystemSettingRepository,
|
||||
) RecordingService {
|
||||
return &recordingService{
|
||||
Log: utils.Log,
|
||||
@@ -97,6 +100,7 @@ func NewRecordingService(
|
||||
TransferLayingSvc: transferLayingSvc,
|
||||
FifoStockV2Svc: fifoStockV2Svc,
|
||||
StockLogRepo: stockLogRepo,
|
||||
SystemSettingRepo: systemSettingRepo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,6 +394,11 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Stocks, err = s.resolveStocksForMigrationMode(ctx, req.Stocks, pfk, actorID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.ensureProductWarehousesExist(c, req.Stocks, req.Depletions, req.Eggs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -635,6 +644,10 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
||||
if match {
|
||||
hasStockChanges = false
|
||||
} else {
|
||||
req.Stocks, err = s.resolveStocksForMigrationMode(ctx, req.Stocks, pfkForRoute, actorID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.ensureProductWarehousesExist(c, req.Stocks, nil, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2205,6 +2218,104 @@ func (s *recordingService) validateWarehouseIDs(
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveMigrationWarehouseID returns the warehouse ID to use when auto-creating product_warehouse
|
||||
// entries in migration mode. Prefers the farm-level (LOKASI) warehouse of the kandang's location;
|
||||
// falls back to the kandang-level (KANDANG) warehouse if no LOKASI warehouse exists.
|
||||
func (s *recordingService) resolveMigrationWarehouseID(ctx context.Context, kandangID uint) (uint, error) {
|
||||
type row struct {
|
||||
ID uint `gorm:"column:id"`
|
||||
LocationID *uint `gorm:"column:location_id"`
|
||||
}
|
||||
|
||||
db := s.ProductWarehouseRepo.DB().WithContext(ctx)
|
||||
|
||||
// Step 1: get the kandang's location_id
|
||||
var kandang row
|
||||
if err := db.Table("kandangs").Select("id, location_id").
|
||||
Where("id = ? AND deleted_at IS NULL", kandangID).
|
||||
Limit(1).Take(&kandang).Error; err != nil {
|
||||
return 0, fmt.Errorf("kandang %d tidak ditemukan: %w", kandangID, err)
|
||||
}
|
||||
|
||||
// Step 2: prefer a LOKASI-type warehouse at the kandang's location (farm-level)
|
||||
if kandang.LocationID != nil && *kandang.LocationID != 0 {
|
||||
var lokasi row
|
||||
err := db.Table("warehouses").Select("id").
|
||||
Where("type = 'LOKASI' AND location_id = ? AND deleted_at IS NULL", *kandang.LocationID).
|
||||
Limit(1).Take(&lokasi).Error
|
||||
if err == nil {
|
||||
return lokasi.ID, nil
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, fmt.Errorf("gagal mencari warehouse LOKASI untuk location %d: %w", *kandang.LocationID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: fall back to the KANDANG-type warehouse
|
||||
var kandangWH row
|
||||
if err := db.Table("warehouses").Select("id").
|
||||
Where("type = 'KANDANG' AND kandang_id = ? AND deleted_at IS NULL", kandangID).
|
||||
Limit(1).Take(&kandangWH).Error; err != nil {
|
||||
return 0, fmt.Errorf("warehouse tidak ditemukan untuk kandang %d: %w", kandangID, err)
|
||||
}
|
||||
return kandangWH.ID, nil
|
||||
}
|
||||
|
||||
// resolveStocksForMigrationMode handles stocks that use product_id (instead of product_warehouse_id)
|
||||
// when migration mode (allow_negative_pakan_ovk) is enabled. It finds or creates product_warehouse
|
||||
// entries in the farm-level (LOKASI) warehouse, falling back to the kandang warehouse, then sets
|
||||
// the resolved product_warehouse_id on each stock item.
|
||||
func (s *recordingService) resolveStocksForMigrationMode(
|
||||
ctx context.Context,
|
||||
stocks []validation.Stock,
|
||||
pfk *entity.ProjectFlockKandang,
|
||||
actorID uint,
|
||||
) ([]validation.Stock, error) {
|
||||
if s.SystemSettingRepo == nil {
|
||||
return stocks, nil
|
||||
}
|
||||
allowed, err := s.SystemSettingRepo.GetAllowNegativePakanOVK(ctx)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to read allow_negative_pakan_ovk setting: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal membaca konfigurasi sistem")
|
||||
}
|
||||
if !allowed {
|
||||
return stocks, nil
|
||||
}
|
||||
|
||||
var warehouseID uint
|
||||
warehouseResolved := false
|
||||
|
||||
result := make([]validation.Stock, len(stocks))
|
||||
copy(result, stocks)
|
||||
|
||||
for i := range result {
|
||||
stock := &result[i]
|
||||
if stock.ProductId == nil || stock.ProductWarehouseId != 0 {
|
||||
continue
|
||||
}
|
||||
// Resolve target warehouse lazily on first need (same for all stocks in one request)
|
||||
if !warehouseResolved {
|
||||
if pfk == nil || pfk.KandangId == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Kandang tidak ditemukan untuk mode migrasi")
|
||||
}
|
||||
warehouseID, err = s.resolveMigrationWarehouseID(ctx, pfk.KandangId)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to resolve migration warehouse for kandang %d: %+v", pfk.KandangId, err)
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Warehouse tidak ditemukan untuk mode migrasi")
|
||||
}
|
||||
warehouseResolved = true
|
||||
}
|
||||
pwID, err := s.ProductWarehouseRepo.EnsureProductWarehouse(ctx, *stock.ProductId, warehouseID, nil, actorID)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to ensure product warehouse for product %d in warehouse %d: %+v", *stock.ProductId, warehouseID, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal menyiapkan product warehouse untuk produk %d", *stock.ProductId))
|
||||
}
|
||||
stock.ProductWarehouseId = pwID
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *recordingService) computeRecordingDay(ctx context.Context, projectFlockKandangID uint, recordTime time.Time) (int, error) {
|
||||
if projectFlockKandangID == 0 {
|
||||
return 0, fiber.NewError(fiber.StatusBadRequest, "Project flock kandang tidak valid")
|
||||
|
||||
@@ -4,7 +4,8 @@ import "time"
|
||||
|
||||
type (
|
||||
Stock struct {
|
||||
ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,number,min=1"`
|
||||
ProductWarehouseId uint `json:"product_warehouse_id" validate:"omitempty,number,min=1"`
|
||||
ProductId *uint `json:"product_id,omitempty" validate:"omitempty,number,min=1"`
|
||||
Qty float64 `json:"qty" validate:"required,gte=0"`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user