mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'revert-915302c4' into 'dev/fifo-v2'
Revert "Merge branch 'fix/implement-fifo-v2' into 'dev/fifo-v2'" See merge request mbugroup/lti-api!342
This commit is contained in:
@@ -10,11 +10,6 @@ type FifoStockV2Service = fifoStockV2.Service
|
||||
|
||||
type FifoStockV2Lane = fifoStockV2.Lane
|
||||
|
||||
const (
|
||||
FifoStockV2LaneStockable FifoStockV2Lane = fifoStockV2.LaneStockable
|
||||
FifoStockV2LaneUsable FifoStockV2Lane = fifoStockV2.LaneUsable
|
||||
)
|
||||
|
||||
type FifoStockV2Ref = fifoStockV2.Ref
|
||||
|
||||
type FifoStockV2GatherRequest = fifoStockV2.GatherRequest
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type FifoStockV2RouteRule struct {
|
||||
FlagGroupCode string `gorm:"column:flag_group_code"`
|
||||
Lane string `gorm:"column:lane"`
|
||||
FunctionCode string `gorm:"column:function_code"`
|
||||
SourceTable string `gorm:"column:source_table"`
|
||||
LegacyTypeKey string `gorm:"column:legacy_type_key"`
|
||||
AllowPendingDefault bool `gorm:"column:allow_pending_default"`
|
||||
}
|
||||
|
||||
func ResolveFifoStockV2RouteByProductIDAndLane(
|
||||
ctx context.Context,
|
||||
db *gorm.DB,
|
||||
productID uint,
|
||||
functionCode string,
|
||||
lane FifoStockV2Lane,
|
||||
) (*FifoStockV2RouteRule, error) {
|
||||
rows, err := resolveFifoStockV2RoutesByProductID(ctx, db, productID, functionCode, lane)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
selected := rows[0]
|
||||
return &selected, nil
|
||||
}
|
||||
|
||||
func ResolveFifoStockV2RouteByProductWarehouseIDAndLane(
|
||||
ctx context.Context,
|
||||
db *gorm.DB,
|
||||
productWarehouseID uint,
|
||||
functionCode string,
|
||||
lane FifoStockV2Lane,
|
||||
) (*FifoStockV2RouteRule, error) {
|
||||
rows, err := resolveFifoStockV2RoutesByProductWarehouseID(ctx, db, productWarehouseID, functionCode, lane)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
selected := rows[0]
|
||||
return &selected, nil
|
||||
}
|
||||
|
||||
func resolveFifoStockV2RoutesByProductID(
|
||||
ctx context.Context,
|
||||
db *gorm.DB,
|
||||
productID uint,
|
||||
functionCode string,
|
||||
lane FifoStockV2Lane,
|
||||
) ([]FifoStockV2RouteRule, error) {
|
||||
normalizedCode := strings.ToUpper(strings.TrimSpace(functionCode))
|
||||
if db == nil || productID == 0 || normalizedCode == "" {
|
||||
return []FifoStockV2RouteRule{}, nil
|
||||
}
|
||||
|
||||
query := db.WithContext(ctx).
|
||||
Table("fifo_stock_v2_route_rules rr").
|
||||
Select("rr.flag_group_code, rr.lane, rr.function_code, rr.source_table, rr.legacy_type_key, rr.allow_pending_default").
|
||||
Joins("JOIN fifo_stock_v2_flag_groups fg ON fg.code = rr.flag_group_code AND fg.is_active = TRUE").
|
||||
Where("rr.is_active = TRUE").
|
||||
Where("rr.function_code = ?", normalizedCode).
|
||||
Where(`
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM flags f
|
||||
JOIN fifo_stock_v2_flag_members fm ON fm.flag_name = f.name AND fm.is_active = TRUE
|
||||
WHERE f.flagable_type = ?
|
||||
AND f.flagable_id = ?
|
||||
AND fm.flag_group_code = rr.flag_group_code
|
||||
)
|
||||
`, entity.FlagableTypeProduct, productID)
|
||||
|
||||
if lane == FifoStockV2LaneStockable || lane == FifoStockV2LaneUsable {
|
||||
query = query.Where("rr.lane = ?", string(lane))
|
||||
}
|
||||
|
||||
var rows []FifoStockV2RouteRule
|
||||
err := query.
|
||||
Order("CASE WHEN rr.source_table = 'adjustment_stocks' THEN 0 ELSE 1 END ASC").
|
||||
Order("rr.id ASC").
|
||||
Find(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func resolveFifoStockV2RoutesByProductWarehouseID(
|
||||
ctx context.Context,
|
||||
db *gorm.DB,
|
||||
productWarehouseID uint,
|
||||
functionCode string,
|
||||
lane FifoStockV2Lane,
|
||||
) ([]FifoStockV2RouteRule, error) {
|
||||
normalizedCode := strings.ToUpper(strings.TrimSpace(functionCode))
|
||||
if db == nil || productWarehouseID == 0 || normalizedCode == "" {
|
||||
return []FifoStockV2RouteRule{}, nil
|
||||
}
|
||||
|
||||
query := db.WithContext(ctx).
|
||||
Table("fifo_stock_v2_route_rules rr").
|
||||
Select("rr.flag_group_code, rr.lane, rr.function_code, rr.source_table, rr.legacy_type_key, rr.allow_pending_default").
|
||||
Joins("JOIN fifo_stock_v2_flag_groups fg ON fg.code = rr.flag_group_code AND fg.is_active = TRUE").
|
||||
Where("rr.is_active = TRUE").
|
||||
Where("rr.function_code = ?", normalizedCode).
|
||||
Where(`
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM product_warehouses pw
|
||||
JOIN flags f ON f.flagable_type = ? AND f.flagable_id = pw.product_id
|
||||
JOIN fifo_stock_v2_flag_members fm ON fm.flag_name = f.name AND fm.is_active = TRUE
|
||||
WHERE pw.id = ?
|
||||
AND fm.flag_group_code = rr.flag_group_code
|
||||
)
|
||||
`, entity.FlagableTypeProduct, productWarehouseID)
|
||||
|
||||
if lane == FifoStockV2LaneStockable || lane == FifoStockV2LaneUsable {
|
||||
query = query.Where("rr.lane = ?", string(lane))
|
||||
}
|
||||
|
||||
var rows []FifoStockV2RouteRule
|
||||
err := query.
|
||||
Order("CASE WHEN rr.source_table = 'adjustment_stocks' THEN 0 ELSE 1 END ASC").
|
||||
Order("rr.id ASC").
|
||||
Find(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
||||
@@ -16,7 +16,6 @@ SET
|
||||
|
||||
INSERT INTO fifo_stock_v2_flag_members(flag_name, flag_group_code, priority)
|
||||
VALUES
|
||||
('AYAM', 'AYAM', 5),
|
||||
('DOC', 'AYAM', 10),
|
||||
('PULLET', 'AYAM', 20),
|
||||
('LAYER', 'AYAM', 30),
|
||||
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
UPDATE fifo_stock_v2_flag_members
|
||||
SET
|
||||
is_active = FALSE,
|
||||
updated_at = NOW()
|
||||
WHERE flag_name = 'AYAM'
|
||||
AND flag_group_code = 'AYAM';
|
||||
|
||||
INSERT INTO fifo_stock_v2_route_rules(
|
||||
flag_group_code,
|
||||
lane,
|
||||
function_code,
|
||||
source_table,
|
||||
source_id_column,
|
||||
product_warehouse_col,
|
||||
quantity_col,
|
||||
used_quantity_col,
|
||||
pending_quantity_col,
|
||||
scope_sql,
|
||||
legacy_type_key,
|
||||
allow_pending_default,
|
||||
is_active
|
||||
)
|
||||
VALUES
|
||||
('AYAM', 'USABLE', 'TRANSFER_TO_LAYING_OUT', 'laying_transfer_sources', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_usage_qty', 'deleted_at IS NULL', 'TRANSFERTOLAYING_OUT', TRUE, TRUE)
|
||||
ON CONFLICT (flag_group_code, lane, function_code, source_table) DO UPDATE
|
||||
SET
|
||||
source_id_column = EXCLUDED.source_id_column,
|
||||
product_warehouse_col = EXCLUDED.product_warehouse_col,
|
||||
quantity_col = EXCLUDED.quantity_col,
|
||||
used_quantity_col = EXCLUDED.used_quantity_col,
|
||||
pending_quantity_col = EXCLUDED.pending_quantity_col,
|
||||
scope_sql = EXCLUDED.scope_sql,
|
||||
legacy_type_key = EXCLUDED.legacy_type_key,
|
||||
allow_pending_default = EXCLUDED.allow_pending_default,
|
||||
is_active = TRUE,
|
||||
updated_at = NOW();
|
||||
|
||||
UPDATE fifo_stock_v2_overconsume_rules
|
||||
SET
|
||||
is_active = TRUE
|
||||
WHERE lane = 'USABLE'
|
||||
AND function_code = 'TRANSFER_TO_LAYING_OUT';
|
||||
|
||||
INSERT INTO fifo_stock_v2_overconsume_rules(flag_group_code, function_code, lane, allow_overconsume, priority, reason, is_active)
|
||||
SELECT NULL, 'TRANSFER_TO_LAYING_OUT', 'USABLE', FALSE, 50, 'fifo_v2_exception_transfer_laying_block', TRUE
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM fifo_stock_v2_overconsume_rules
|
||||
WHERE flag_group_code IS NULL
|
||||
AND function_code = 'TRANSFER_TO_LAYING_OUT'
|
||||
AND lane = 'USABLE'
|
||||
AND reason = 'fifo_v2_exception_transfer_laying_block'
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
-205
@@ -1,205 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
INSERT INTO fifo_stock_v2_flag_members(flag_name, flag_group_code, priority, is_active)
|
||||
VALUES
|
||||
('AYAM', 'AYAM', 5, TRUE)
|
||||
ON CONFLICT (flag_name) DO UPDATE
|
||||
SET
|
||||
flag_group_code = EXCLUDED.flag_group_code,
|
||||
priority = EXCLUDED.priority,
|
||||
is_active = TRUE,
|
||||
updated_at = NOW();
|
||||
|
||||
WITH desired_rules AS (
|
||||
SELECT * FROM (
|
||||
VALUES
|
||||
-- AYAM STOCKABLE
|
||||
('AYAM', 'STOCKABLE', 'ADJUSTMENT_IN', 'adjustment_stocks', 'id', 'product_warehouse_id', 'total_qty', 'total_used', NULL, NULL, 'ADJUSTMENT_IN', TRUE, TRUE),
|
||||
('AYAM', 'STOCKABLE', 'STOCK_TRANSFER_IN', 'stock_transfer_details', 'id', 'dest_product_warehouse_id', 'total_qty', 'total_used', NULL, 'deleted_at IS NULL', 'STOCK_TRANSFER_IN', TRUE, TRUE),
|
||||
('AYAM', 'STOCKABLE', 'PURCHASE_IN', 'purchase_items', 'id', 'product_warehouse_id', 'total_qty', 'total_used', NULL, NULL, 'PURCHASE_ITEMS', TRUE, TRUE),
|
||||
('AYAM', 'STOCKABLE', 'TRANSFER_TO_LAYING_IN', 'laying_transfer_targets', 'id', 'product_warehouse_id', 'total_qty', 'total_used', NULL, 'deleted_at IS NULL', 'TRANSFERTOLAYING_IN', TRUE, TRUE),
|
||||
|
||||
-- AYAM USABLE
|
||||
('AYAM', 'USABLE', 'ADJUSTMENT_OUT', 'adjustment_stocks', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_qty', NULL, 'ADJUSTMENT_OUT', TRUE, TRUE),
|
||||
('AYAM', 'USABLE', 'STOCK_TRANSFER_OUT', 'stock_transfer_details', 'id', 'source_product_warehouse_id', 'usage_qty', NULL, 'pending_qty', 'deleted_at IS NULL', 'STOCK_TRANSFER_OUT', TRUE, TRUE),
|
||||
('AYAM', 'USABLE', 'CHICKIN_OUT', 'project_chickins', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_usage_qty', 'deleted_at IS NULL', 'PROJECT_CHICKIN', TRUE, TRUE),
|
||||
('AYAM', 'USABLE', 'RECORDING_DEPLETION_OUT', 'recording_depletions', 'id', 'source_product_warehouse_id', 'qty', NULL, 'pending_qty', NULL, 'RECORDING_DEPLETION', TRUE, TRUE),
|
||||
('AYAM', 'USABLE', 'MARKETING_OUT', 'marketing_delivery_products', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_qty', NULL, 'MARKETING_DELIVERY', TRUE, TRUE),
|
||||
|
||||
-- AFKIR/CULLING/MATI STOCKABLE
|
||||
('AFKIR_CULLING_MATI', 'STOCKABLE', 'ADJUSTMENT_IN', 'adjustment_stocks', 'id', 'product_warehouse_id', 'total_qty', 'total_used', NULL, NULL, 'ADJUSTMENT_IN', TRUE, TRUE),
|
||||
('AFKIR_CULLING_MATI', 'STOCKABLE', 'STOCK_TRANSFER_IN', 'stock_transfer_details', 'id', 'dest_product_warehouse_id', 'total_qty', 'total_used', NULL, 'deleted_at IS NULL', 'STOCK_TRANSFER_IN', TRUE, TRUE),
|
||||
('AFKIR_CULLING_MATI', 'STOCKABLE', 'RECORDING_DEPLETION_IN', 'recording_depletions', 'id', 'product_warehouse_id', 'qty', NULL, NULL, NULL, 'RECORDING_DEPLETION', TRUE, TRUE),
|
||||
|
||||
-- AFKIR/CULLING/MATI USABLE
|
||||
('AFKIR_CULLING_MATI', 'USABLE', 'ADJUSTMENT_OUT', 'adjustment_stocks', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_qty', NULL, 'ADJUSTMENT_OUT', TRUE, TRUE),
|
||||
('AFKIR_CULLING_MATI', 'USABLE', 'STOCK_TRANSFER_OUT', 'stock_transfer_details', 'id', 'source_product_warehouse_id', 'usage_qty', NULL, 'pending_qty', 'deleted_at IS NULL', 'STOCK_TRANSFER_OUT', TRUE, TRUE),
|
||||
('AFKIR_CULLING_MATI', 'USABLE', 'MARKETING_OUT', 'marketing_delivery_products', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_qty', NULL, 'MARKETING_DELIVERY', TRUE, TRUE),
|
||||
|
||||
-- PAKAN STOCKABLE
|
||||
('PAKAN', 'STOCKABLE', 'ADJUSTMENT_IN', 'adjustment_stocks', 'id', 'product_warehouse_id', 'total_qty', 'total_used', NULL, NULL, 'ADJUSTMENT_IN', TRUE, TRUE),
|
||||
('PAKAN', 'STOCKABLE', 'STOCK_TRANSFER_IN', 'stock_transfer_details', 'id', 'dest_product_warehouse_id', 'total_qty', 'total_used', NULL, 'deleted_at IS NULL', 'STOCK_TRANSFER_IN', TRUE, TRUE),
|
||||
('PAKAN', 'STOCKABLE', 'PURCHASE_IN', 'purchase_items', 'id', 'product_warehouse_id', 'total_qty', 'total_used', NULL, NULL, 'PURCHASE_ITEMS', TRUE, TRUE),
|
||||
|
||||
-- PAKAN USABLE
|
||||
('PAKAN', 'USABLE', 'ADJUSTMENT_OUT', 'adjustment_stocks', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_qty', NULL, 'ADJUSTMENT_OUT', TRUE, TRUE),
|
||||
('PAKAN', 'USABLE', 'STOCK_TRANSFER_OUT', 'stock_transfer_details', 'id', 'source_product_warehouse_id', 'usage_qty', NULL, 'pending_qty', 'deleted_at IS NULL', 'STOCK_TRANSFER_OUT', TRUE, TRUE),
|
||||
('PAKAN', 'USABLE', 'RECORDING_STOCK_OUT', 'recording_stocks', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_qty', NULL, 'RECORDING_STOCK', TRUE, TRUE),
|
||||
('PAKAN', 'USABLE', 'MARKETING_OUT', 'marketing_delivery_products', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_qty', NULL, 'MARKETING_DELIVERY', TRUE, TRUE),
|
||||
|
||||
-- OVK STOCKABLE
|
||||
('OVK', 'STOCKABLE', 'ADJUSTMENT_IN', 'adjustment_stocks', 'id', 'product_warehouse_id', 'total_qty', 'total_used', NULL, NULL, 'ADJUSTMENT_IN', TRUE, TRUE),
|
||||
('OVK', 'STOCKABLE', 'STOCK_TRANSFER_IN', 'stock_transfer_details', 'id', 'dest_product_warehouse_id', 'total_qty', 'total_used', NULL, 'deleted_at IS NULL', 'STOCK_TRANSFER_IN', TRUE, TRUE),
|
||||
('OVK', 'STOCKABLE', 'PURCHASE_IN', 'purchase_items', 'id', 'product_warehouse_id', 'total_qty', 'total_used', NULL, NULL, 'PURCHASE_ITEMS', TRUE, TRUE),
|
||||
|
||||
-- OVK USABLE
|
||||
('OVK', 'USABLE', 'ADJUSTMENT_OUT', 'adjustment_stocks', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_qty', NULL, 'ADJUSTMENT_OUT', TRUE, TRUE),
|
||||
('OVK', 'USABLE', 'STOCK_TRANSFER_OUT', 'stock_transfer_details', 'id', 'source_product_warehouse_id', 'usage_qty', NULL, 'pending_qty', 'deleted_at IS NULL', 'STOCK_TRANSFER_OUT', TRUE, TRUE),
|
||||
('OVK', 'USABLE', 'RECORDING_STOCK_OUT', 'recording_stocks', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_qty', NULL, 'RECORDING_STOCK', TRUE, TRUE),
|
||||
('OVK', 'USABLE', 'MARKETING_OUT', 'marketing_delivery_products', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_qty', NULL, 'MARKETING_DELIVERY', TRUE, TRUE),
|
||||
|
||||
-- TELUR STOCKABLE
|
||||
('TELUR', 'STOCKABLE', 'ADJUSTMENT_IN', 'adjustment_stocks', 'id', 'product_warehouse_id', 'total_qty', 'total_used', NULL, NULL, 'ADJUSTMENT_IN', TRUE, TRUE),
|
||||
('TELUR', 'STOCKABLE', 'STOCK_TRANSFER_IN', 'stock_transfer_details', 'id', 'dest_product_warehouse_id', 'total_qty', 'total_used', NULL, 'deleted_at IS NULL', 'STOCK_TRANSFER_IN', TRUE, TRUE),
|
||||
('TELUR', 'STOCKABLE', 'RECORDING_EGG_IN', 'recording_eggs', 'id', 'product_warehouse_id', 'total_qty', 'total_used', NULL, NULL, 'RECORDING_EGG', TRUE, TRUE),
|
||||
|
||||
-- TELUR USABLE
|
||||
('TELUR', 'USABLE', 'ADJUSTMENT_OUT', 'adjustment_stocks', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_qty', NULL, 'ADJUSTMENT_OUT', TRUE, TRUE),
|
||||
('TELUR', 'USABLE', 'STOCK_TRANSFER_OUT', 'stock_transfer_details', 'id', 'source_product_warehouse_id', 'usage_qty', NULL, 'pending_qty', 'deleted_at IS NULL', 'STOCK_TRANSFER_OUT', TRUE, TRUE),
|
||||
('TELUR', 'USABLE', 'MARKETING_OUT', 'marketing_delivery_products', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_qty', NULL, 'MARKETING_DELIVERY', TRUE, TRUE),
|
||||
|
||||
-- TELUR_GRADE STOCKABLE
|
||||
('TELUR_GRADE', 'STOCKABLE', 'ADJUSTMENT_IN', 'adjustment_stocks', 'id', 'product_warehouse_id', 'total_qty', 'total_used', NULL, NULL, 'ADJUSTMENT_IN', TRUE, TRUE),
|
||||
('TELUR_GRADE', 'STOCKABLE', 'STOCK_TRANSFER_IN', 'stock_transfer_details', 'id', 'dest_product_warehouse_id', 'total_qty', 'total_used', NULL, 'deleted_at IS NULL', 'STOCK_TRANSFER_IN', TRUE, TRUE),
|
||||
('TELUR_GRADE', 'STOCKABLE', 'RECORDING_EGG_IN', 'recording_eggs', 'id', 'product_warehouse_id', 'total_qty', 'total_used', NULL, NULL, 'RECORDING_EGG', TRUE, TRUE),
|
||||
|
||||
-- TELUR_GRADE USABLE
|
||||
('TELUR_GRADE', 'USABLE', 'ADJUSTMENT_OUT', 'adjustment_stocks', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_qty', NULL, 'ADJUSTMENT_OUT', TRUE, TRUE),
|
||||
('TELUR_GRADE', 'USABLE', 'STOCK_TRANSFER_OUT', 'stock_transfer_details', 'id', 'source_product_warehouse_id', 'usage_qty', NULL, 'pending_qty', 'deleted_at IS NULL', 'STOCK_TRANSFER_OUT', TRUE, TRUE),
|
||||
('TELUR_GRADE', 'USABLE', 'MARKETING_OUT', 'marketing_delivery_products', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_qty', NULL, 'MARKETING_DELIVERY', TRUE, TRUE)
|
||||
) AS v(
|
||||
flag_group_code,
|
||||
lane,
|
||||
function_code,
|
||||
source_table,
|
||||
source_id_column,
|
||||
product_warehouse_col,
|
||||
quantity_col,
|
||||
used_quantity_col,
|
||||
pending_quantity_col,
|
||||
scope_sql,
|
||||
legacy_type_key,
|
||||
allow_pending_default,
|
||||
is_active
|
||||
)
|
||||
)
|
||||
INSERT INTO fifo_stock_v2_route_rules(
|
||||
flag_group_code,
|
||||
lane,
|
||||
function_code,
|
||||
source_table,
|
||||
source_id_column,
|
||||
product_warehouse_col,
|
||||
quantity_col,
|
||||
used_quantity_col,
|
||||
pending_quantity_col,
|
||||
scope_sql,
|
||||
legacy_type_key,
|
||||
allow_pending_default,
|
||||
is_active
|
||||
)
|
||||
SELECT
|
||||
flag_group_code,
|
||||
lane,
|
||||
function_code,
|
||||
source_table,
|
||||
source_id_column,
|
||||
product_warehouse_col,
|
||||
quantity_col,
|
||||
used_quantity_col,
|
||||
pending_quantity_col,
|
||||
scope_sql,
|
||||
legacy_type_key,
|
||||
allow_pending_default,
|
||||
is_active
|
||||
FROM desired_rules
|
||||
ON CONFLICT (flag_group_code, lane, function_code, source_table) DO UPDATE
|
||||
SET
|
||||
source_id_column = EXCLUDED.source_id_column,
|
||||
product_warehouse_col = EXCLUDED.product_warehouse_col,
|
||||
quantity_col = EXCLUDED.quantity_col,
|
||||
used_quantity_col = EXCLUDED.used_quantity_col,
|
||||
pending_quantity_col = EXCLUDED.pending_quantity_col,
|
||||
scope_sql = EXCLUDED.scope_sql,
|
||||
legacy_type_key = EXCLUDED.legacy_type_key,
|
||||
allow_pending_default = EXCLUDED.allow_pending_default,
|
||||
is_active = EXCLUDED.is_active,
|
||||
updated_at = NOW();
|
||||
|
||||
WITH desired_rules AS (
|
||||
SELECT * FROM (
|
||||
VALUES
|
||||
('AYAM', 'STOCKABLE', 'ADJUSTMENT_IN', 'adjustment_stocks'),
|
||||
('AYAM', 'STOCKABLE', 'STOCK_TRANSFER_IN', 'stock_transfer_details'),
|
||||
('AYAM', 'STOCKABLE', 'PURCHASE_IN', 'purchase_items'),
|
||||
('AYAM', 'STOCKABLE', 'TRANSFER_TO_LAYING_IN', 'laying_transfer_targets'),
|
||||
('AYAM', 'USABLE', 'ADJUSTMENT_OUT', 'adjustment_stocks'),
|
||||
('AYAM', 'USABLE', 'STOCK_TRANSFER_OUT', 'stock_transfer_details'),
|
||||
('AYAM', 'USABLE', 'CHICKIN_OUT', 'project_chickins'),
|
||||
('AYAM', 'USABLE', 'RECORDING_DEPLETION_OUT', 'recording_depletions'),
|
||||
('AYAM', 'USABLE', 'MARKETING_OUT', 'marketing_delivery_products'),
|
||||
('AFKIR_CULLING_MATI', 'STOCKABLE', 'ADJUSTMENT_IN', 'adjustment_stocks'),
|
||||
('AFKIR_CULLING_MATI', 'STOCKABLE', 'STOCK_TRANSFER_IN', 'stock_transfer_details'),
|
||||
('AFKIR_CULLING_MATI', 'STOCKABLE', 'RECORDING_DEPLETION_IN', 'recording_depletions'),
|
||||
('AFKIR_CULLING_MATI', 'USABLE', 'ADJUSTMENT_OUT', 'adjustment_stocks'),
|
||||
('AFKIR_CULLING_MATI', 'USABLE', 'STOCK_TRANSFER_OUT', 'stock_transfer_details'),
|
||||
('AFKIR_CULLING_MATI', 'USABLE', 'MARKETING_OUT', 'marketing_delivery_products'),
|
||||
('PAKAN', 'STOCKABLE', 'ADJUSTMENT_IN', 'adjustment_stocks'),
|
||||
('PAKAN', 'STOCKABLE', 'STOCK_TRANSFER_IN', 'stock_transfer_details'),
|
||||
('PAKAN', 'STOCKABLE', 'PURCHASE_IN', 'purchase_items'),
|
||||
('PAKAN', 'USABLE', 'ADJUSTMENT_OUT', 'adjustment_stocks'),
|
||||
('PAKAN', 'USABLE', 'STOCK_TRANSFER_OUT', 'stock_transfer_details'),
|
||||
('PAKAN', 'USABLE', 'RECORDING_STOCK_OUT', 'recording_stocks'),
|
||||
('PAKAN', 'USABLE', 'MARKETING_OUT', 'marketing_delivery_products'),
|
||||
('OVK', 'STOCKABLE', 'ADJUSTMENT_IN', 'adjustment_stocks'),
|
||||
('OVK', 'STOCKABLE', 'STOCK_TRANSFER_IN', 'stock_transfer_details'),
|
||||
('OVK', 'STOCKABLE', 'PURCHASE_IN', 'purchase_items'),
|
||||
('OVK', 'USABLE', 'ADJUSTMENT_OUT', 'adjustment_stocks'),
|
||||
('OVK', 'USABLE', 'STOCK_TRANSFER_OUT', 'stock_transfer_details'),
|
||||
('OVK', 'USABLE', 'RECORDING_STOCK_OUT', 'recording_stocks'),
|
||||
('OVK', 'USABLE', 'MARKETING_OUT', 'marketing_delivery_products'),
|
||||
('TELUR', 'STOCKABLE', 'ADJUSTMENT_IN', 'adjustment_stocks'),
|
||||
('TELUR', 'STOCKABLE', 'STOCK_TRANSFER_IN', 'stock_transfer_details'),
|
||||
('TELUR', 'STOCKABLE', 'RECORDING_EGG_IN', 'recording_eggs'),
|
||||
('TELUR', 'USABLE', 'ADJUSTMENT_OUT', 'adjustment_stocks'),
|
||||
('TELUR', 'USABLE', 'STOCK_TRANSFER_OUT', 'stock_transfer_details'),
|
||||
('TELUR', 'USABLE', 'MARKETING_OUT', 'marketing_delivery_products'),
|
||||
('TELUR_GRADE', 'STOCKABLE', 'ADJUSTMENT_IN', 'adjustment_stocks'),
|
||||
('TELUR_GRADE', 'STOCKABLE', 'STOCK_TRANSFER_IN', 'stock_transfer_details'),
|
||||
('TELUR_GRADE', 'STOCKABLE', 'RECORDING_EGG_IN', 'recording_eggs'),
|
||||
('TELUR_GRADE', 'USABLE', 'ADJUSTMENT_OUT', 'adjustment_stocks'),
|
||||
('TELUR_GRADE', 'USABLE', 'STOCK_TRANSFER_OUT', 'stock_transfer_details'),
|
||||
('TELUR_GRADE', 'USABLE', 'MARKETING_OUT', 'marketing_delivery_products')
|
||||
) AS v(flag_group_code, lane, function_code, source_table)
|
||||
)
|
||||
UPDATE fifo_stock_v2_route_rules rr
|
||||
SET
|
||||
is_active = FALSE,
|
||||
updated_at = NOW()
|
||||
WHERE rr.flag_group_code IN ('AYAM', 'AFKIR_CULLING_MATI', 'PAKAN', 'OVK', 'TELUR', 'TELUR_GRADE')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM desired_rules d
|
||||
WHERE d.flag_group_code = rr.flag_group_code
|
||||
AND d.lane = rr.lane
|
||||
AND d.function_code = rr.function_code
|
||||
AND d.source_table = rr.source_table
|
||||
);
|
||||
|
||||
UPDATE fifo_stock_v2_overconsume_rules
|
||||
SET
|
||||
is_active = FALSE
|
||||
WHERE lane = 'USABLE'
|
||||
AND function_code = 'TRANSFER_TO_LAYING_OUT';
|
||||
|
||||
COMMIT;
|
||||
@@ -198,7 +198,7 @@ func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories
|
||||
Category: "Day Old Chick",
|
||||
Price: 7500,
|
||||
Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"},
|
||||
Flags: []utils.FlagType{utils.FlagAyam},
|
||||
Flags: []utils.FlagType{utils.FlagDOC, utils.FlagPullet, utils.FlagLayer},
|
||||
IsVisible: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -58,17 +58,17 @@ type SapronakReportDTO struct {
|
||||
|
||||
// Simplified view for project-level sapronak response
|
||||
type SapronakCategoryRowDTO struct {
|
||||
ID int `json:"id"`
|
||||
Date string `json:"date"`
|
||||
ReferenceNumber string `json:"reference_number"`
|
||||
QtyIn float64 `json:"qty_in"`
|
||||
QtyOut float64 `json:"qty_out"`
|
||||
QtyUsed float64 `json:"qty_used"`
|
||||
Description string `json:"description"`
|
||||
ID int `json:"id"`
|
||||
Date string `json:"date"`
|
||||
ReferenceNumber string `json:"reference_number"`
|
||||
QtyIn float64 `json:"qty_in"`
|
||||
QtyOut float64 `json:"qty_out"`
|
||||
QtyUsed float64 `json:"qty_used"`
|
||||
Description string `json:"description"`
|
||||
ProductCategory []string `json:"product_category"`
|
||||
UnitPrice float64 `json:"unit_price"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
Notes string `json:"notes"`
|
||||
UnitPrice float64 `json:"unit_price"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
type SapronakCategoryTotalDTO struct {
|
||||
@@ -148,7 +148,7 @@ func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag strin
|
||||
|
||||
normalizeFlag := func(raw string) string {
|
||||
normalized := strings.ToUpper(strings.TrimSpace(raw))
|
||||
if normalized == "AYAM" || normalized == "PULLET" {
|
||||
if normalized == "PULLET" {
|
||||
return "DOC"
|
||||
}
|
||||
return normalized
|
||||
@@ -177,7 +177,6 @@ func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag strin
|
||||
}
|
||||
|
||||
flagOrder := map[string]int{
|
||||
"AYAM": 0,
|
||||
"DOC": 0,
|
||||
"PAKAN": 0,
|
||||
"OVK": 0,
|
||||
|
||||
@@ -446,7 +446,7 @@ SELECT
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('AYAM', 'DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
@@ -459,7 +459,7 @@ SELECT
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('AYAM', 'DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
@@ -495,7 +495,7 @@ SELECT
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('AYAM', 'DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
@@ -508,7 +508,7 @@ SELECT
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('AYAM', 'DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
@@ -545,7 +545,7 @@ SELECT
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('AYAM', 'DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
@@ -558,7 +558,7 @@ SELECT
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('AYAM', 'DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
@@ -595,7 +595,7 @@ SELECT
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('AYAM', 'DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
@@ -608,7 +608,7 @@ SELECT
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('AYAM', 'DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
@@ -645,7 +645,7 @@ SELECT
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('AYAM', 'DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
@@ -658,7 +658,7 @@ SELECT
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('AYAM', 'DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
@@ -685,7 +685,7 @@ WHERE pw.warehouse_id IN ?
|
||||
FROM flags f
|
||||
WHERE f.flagable_id = pw.product_id
|
||||
AND f.flagable_type = 'products'
|
||||
AND UPPER(f.name) NOT IN ('AYAM', 'DOC', 'LAYER', 'PULLET', 'AYAM-AFKIR', 'AYAM-MATI', 'AYAM-CULLING', 'TELUR-UTUH', 'TELUR-PECAH', 'TELUR-PUTIH', 'TELUR-RETAK')
|
||||
AND UPPER(f.name) NOT IN ('DOC', 'LAYER', 'PULLET', 'AYAM-AFKIR', 'AYAM-MATI', 'AYAM-CULLING', 'TELUR-UTUH', 'TELUR-PECAH', 'TELUR-PUTIH', 'TELUR-RETAK')
|
||||
)
|
||||
`
|
||||
|
||||
@@ -702,7 +702,7 @@ SELECT
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('AYAM', 'DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
@@ -715,7 +715,7 @@ SELECT
|
||||
f.name,
|
||||
' ' ORDER BY
|
||||
CASE
|
||||
WHEN UPPER(f.name) IN ('AYAM', 'DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
WHEN UPPER(f.name) IN ('DOC', 'PAKAN', 'OVK', 'PULLET') THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
f.name
|
||||
@@ -743,7 +743,7 @@ WHERE pw.project_flock_kandang_id IN ?
|
||||
FROM flags f
|
||||
WHERE f.flagable_id = pw.product_id
|
||||
AND f.flagable_type = 'products'
|
||||
AND UPPER(f.name) NOT IN ('AYAM', 'DOC', 'LAYER', 'PULLET', 'AYAM-AFKIR', 'AYAM-MATI', 'AYAM-CULLING', 'TELUR-UTUH', 'TELUR-PECAH', 'TELUR-PUTIH', 'TELUR-RETAK')
|
||||
AND UPPER(f.name) NOT IN ('DOC', 'LAYER', 'PULLET', 'AYAM-AFKIR', 'AYAM-MATI', 'AYAM-CULLING', 'TELUR-UTUH', 'TELUR-PECAH', 'TELUR-PUTIH', 'TELUR-RETAK')
|
||||
)
|
||||
`
|
||||
)
|
||||
@@ -796,9 +796,9 @@ func sapronakFlags(flags ...utils.FlagType) []string {
|
||||
}
|
||||
|
||||
var (
|
||||
sapronakFlagsAll = sapronakFlags(utils.FlagAyam, utils.FlagDOC, utils.FlagPakan, utils.FlagOVK, utils.FlagPullet)
|
||||
sapronakFlagsAll = sapronakFlags(utils.FlagDOC, utils.FlagPakan, utils.FlagOVK, utils.FlagPullet)
|
||||
sapronakFlagsUsage = sapronakFlags(utils.FlagPakan, utils.FlagOVK)
|
||||
sapronakFlagsChickin = sapronakFlags(utils.FlagAyam, utils.FlagDOC, utils.FlagPullet)
|
||||
sapronakFlagsChickin = sapronakFlags(utils.FlagDOC, utils.FlagPullet)
|
||||
)
|
||||
|
||||
func (r *ClosingRepositoryImpl) joinSapronakProductFlag(db *gorm.DB, productAlias string) *gorm.DB {
|
||||
@@ -808,8 +808,7 @@ func (r *ClosingRepositoryImpl) joinSapronakProductFlag(db *gorm.DB, productAlia
|
||||
Where("flagable_type = ?", entity.FlagableTypeProduct).
|
||||
Where("name IN ?", sapronakFlagsAll).
|
||||
Order(fmt.Sprintf(
|
||||
"flagable_id, CASE WHEN name = '%s' THEN 1 WHEN name = '%s' THEN 2 WHEN name = '%s' THEN 3 WHEN name = '%s' THEN 4 WHEN name = '%s' THEN 5 ELSE 6 END",
|
||||
utils.FlagAyam,
|
||||
"flagable_id, CASE WHEN name = '%s' THEN 1 WHEN name = '%s' THEN 2 WHEN name = '%s' THEN 3 WHEN name = '%s' THEN 4 ELSE 5 END",
|
||||
utils.FlagDOC,
|
||||
utils.FlagPullet,
|
||||
utils.FlagPakan,
|
||||
@@ -1239,7 +1238,7 @@ func (r *ClosingRepositoryImpl) FetchSapronakAdjustments(ctx context.Context, ka
|
||||
Where("sa.status = ?", entity.StockAllocationStatusActive).
|
||||
Where("w.kandang_id = ?", kandangID).
|
||||
Where("f.name IN ?", sapronakFlagsAll).
|
||||
Where("f.name NOT IN ?", sapronakFlags(utils.FlagAyam, utils.FlagDOC, utils.FlagPullet)).
|
||||
Where("f.name NOT IN ?", sapronakFlags(utils.FlagDOC, utils.FlagPullet)).
|
||||
Group("pw.product_id, p.name, f.name, pi.received_date, st.transfer_date, lt.transfer_date, pfp_po.received_date, pc.chick_in_date, ast_in.created_at, ast.created_at, po.po_number, st.movement_number, lt.transfer_number, pfp_po.po_number, pc.id, ast_in.id, ast.id, p.product_price")
|
||||
outgoingQuery = r.joinSapronakProductFlag(outgoingQuery, "p")
|
||||
outgoing, err := scanAndGroupDetails(outgoingQuery)
|
||||
|
||||
@@ -856,7 +856,7 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint
|
||||
// FeedUsedPerHead: feedUsedPerHead,
|
||||
}
|
||||
|
||||
chickenFlagNames := []string{string(utils.FlagAyam), string(utils.FlagPullet), string(utils.FlagAyamAfkir), string(utils.FlagAyamCulling), string(utils.FlagLayer)}
|
||||
chickenFlagNames := []string{string(utils.FlagPullet), string(utils.FlagAyamAfkir), string(utils.FlagAyamCulling), string(utils.FlagLayer)}
|
||||
chickenSalesWeight, chickenSalesQty, chickenSalesPrice, err := s.Repository.SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(c.Context(), projectFlockKandangIDs, chickenFlagNames)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to fetch chicken sales data for project flock %d: %+v", projectFlockID, err)
|
||||
@@ -885,7 +885,7 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint
|
||||
chickenDepletion = 0
|
||||
}
|
||||
|
||||
chickenPerformance := calculatePerformanceMetrics(chickenAverageWeight, chickenSalesWeight, feedUsed, population, chickenDepletion, age)
|
||||
chickenPerformance := calculatePerformanceMetrics(chickenAverageWeight, chickenSalesWeight, feedUsed, population, chickenDepletion, age)
|
||||
if fcrActFromRecording != nil {
|
||||
chickenPerformance.FcrAct = *fcrActFromRecording
|
||||
}
|
||||
|
||||
@@ -432,8 +432,8 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
|
||||
return true
|
||||
}
|
||||
candidate := strings.ToUpper(f)
|
||||
if filterFlag == "AYAM" || filterFlag == "DOC" || filterFlag == "PULLET" {
|
||||
return candidate == "AYAM" || candidate == "DOC" || candidate == "PULLET"
|
||||
if filterFlag == "DOC" || filterFlag == "PULLET" {
|
||||
return candidate == "DOC" || candidate == "PULLET"
|
||||
}
|
||||
return candidate == filterFlag
|
||||
}
|
||||
@@ -474,8 +474,7 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
|
||||
if !isLaying {
|
||||
filteredUsage := make([]repository.SapronakUsageRow, 0, len(chickinUsageRows))
|
||||
for _, row := range chickinUsageRows {
|
||||
flag := strings.ToUpper(row.Flag)
|
||||
if flag == "AYAM" || flag == "DOC" {
|
||||
if strings.ToUpper(row.Flag) == "DOC" {
|
||||
filteredUsage = append(filteredUsage, row)
|
||||
}
|
||||
}
|
||||
@@ -484,8 +483,7 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
|
||||
filteredDetail := make(map[uint][]repository.SapronakDetailRow, len(chickinUsageDetailsRows))
|
||||
for pid, rows := range chickinUsageDetailsRows {
|
||||
for _, d := range rows {
|
||||
flag := strings.ToUpper(d.Flag)
|
||||
if flag == "AYAM" || flag == "DOC" {
|
||||
if strings.ToUpper(d.Flag) == "DOC" {
|
||||
filteredDetail[pid] = append(filteredDetail[pid], d)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ type CountSapronakQuery struct {
|
||||
KandangID uint `query:"kandang_id" validate:"omitempty,gt=0"`
|
||||
ProjectFlockKandangID uint `query:"project_flock_kandang_id" validate:"omitempty,gt=0"`
|
||||
Status string `query:"status" validate:"omitempty,oneof=active closing all"`
|
||||
Flag string `query:"flag" validate:"omitempty,oneof=AYAM DOC OVK PAKAN PULLET ayam doc ovk pakan pullet"`
|
||||
Flag string `query:"flag" validate:"omitempty,oneof=DOC OVK PAKAN PULLET doc ovk pakan pullet"`
|
||||
}
|
||||
|
||||
@@ -26,49 +26,12 @@ func NewConstantRepository(db *gorm.DB) ConstantRepository {
|
||||
}
|
||||
|
||||
func (r *ConstantRepositoryImpl) GetConstants() (map[string]interface{}, error) {
|
||||
flagSet := make(map[string]struct{})
|
||||
for _, f := range utils.AllowedFlagTypes(utils.FlagGroupProduct) {
|
||||
flagSet[string(f)] = struct{}{}
|
||||
}
|
||||
for _, f := range utils.AllowedFlagTypes(utils.FlagGroupNonstock) {
|
||||
flagSet[string(f)] = struct{}{}
|
||||
}
|
||||
flagSet[string(utils.FlagIsActive)] = struct{}{}
|
||||
|
||||
flagList := make([]string, 0, len(flagSet))
|
||||
for f := range flagSet {
|
||||
flagList = append(flagList, f)
|
||||
flagList := make([]string, 0)
|
||||
for f := range utils.AllFlagTypes() {
|
||||
flagList = append(flagList, string(f))
|
||||
}
|
||||
sort.Strings(flagList)
|
||||
|
||||
legacyFlagAliasesRaw := utils.LegacyFlagTypeAliases()
|
||||
legacyFlagAliases := make(map[string]string, len(legacyFlagAliasesRaw))
|
||||
for legacy, canonical := range legacyFlagAliasesRaw {
|
||||
legacyFlagAliases[string(legacy)] = string(canonical)
|
||||
}
|
||||
|
||||
productFlagOptionsRaw := utils.ProductFlagOptions()
|
||||
productFlagOptions := make([]map[string]interface{}, 0, len(productFlagOptionsRaw))
|
||||
productMainFlags := make([]string, 0, len(productFlagOptionsRaw))
|
||||
productSubFlagToFlag := make(map[string]string)
|
||||
for _, option := range productFlagOptionsRaw {
|
||||
flag := string(option.Flag)
|
||||
productMainFlags = append(productMainFlags, flag)
|
||||
|
||||
subFlags := make([]string, len(option.SubFlags))
|
||||
for i, subFlag := range option.SubFlags {
|
||||
subFlagStr := string(subFlag)
|
||||
subFlags[i] = subFlagStr
|
||||
productSubFlagToFlag[subFlagStr] = flag
|
||||
}
|
||||
|
||||
productFlagOptions = append(productFlagOptions, map[string]interface{}{
|
||||
"flag": flag,
|
||||
"sub_flags": subFlags,
|
||||
"allow_without_sub_flag": option.AllowWithoutSubFlag,
|
||||
})
|
||||
}
|
||||
|
||||
type approvalStepConstant struct {
|
||||
StepNumber uint16 `json:"step_number"`
|
||||
StepName string `json:"step_name"`
|
||||
@@ -115,13 +78,7 @@ func (r *ConstantRepositoryImpl) GetConstants() (map[string]interface{}, error)
|
||||
adjustmentSubtypesByType := utils.AdjustmentTransactionSubtypesByTypeForFrontend()
|
||||
|
||||
return map[string]interface{}{
|
||||
"flags": flagList,
|
||||
"legacy_flag_aliases": legacyFlagAliases,
|
||||
"product_flag_mapping": map[string]interface{}{
|
||||
"flags": productMainFlags,
|
||||
"options": productFlagOptions,
|
||||
"sub_flag_to_flag": productSubFlagToFlag,
|
||||
},
|
||||
"flags": flagList,
|
||||
"warehouse_types": []string{
|
||||
"AREA",
|
||||
"LOKASI",
|
||||
|
||||
@@ -364,7 +364,7 @@ func (r *DashboardRepositoryImpl) SumSapronakCost(ctx context.Context, start, en
|
||||
Joins("LEFT JOIN product_warehouses AS pw ON pw.id = pi.product_warehouse_id").
|
||||
Joins("LEFT JOIN project_flock_kandangs AS pfk ON pfk.id = COALESCE(pi.project_flock_kandang_id, pw.project_flock_kandang_id)").
|
||||
Joins("LEFT JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||
Where("f.name IN ?", []utils.FlagType{utils.FlagAyam, utils.FlagDOC, utils.FlagPullet, utils.FlagPakan, utils.FlagOVK}).
|
||||
Where("f.name IN ?", []utils.FlagType{utils.FlagDOC, utils.FlagPakan, utils.FlagOVK}).
|
||||
Where("pi.received_date IS NOT NULL").
|
||||
Where("pi.received_date >= ? AND pi.received_date < ?", start, end)
|
||||
|
||||
|
||||
@@ -179,7 +179,6 @@ func calculateAgeFromChickin(projectFlockKandang *entity.ProjectFlockKandang, cu
|
||||
flag == string(utils.FlagTelurPecah) ||
|
||||
flag == string(utils.FlagTelurPutih) ||
|
||||
flag == string(utils.FlagTelurRetak) ||
|
||||
flag == string(utils.FlagAyam) ||
|
||||
flag == string(utils.FlagAyamAfkir) ||
|
||||
flag == string(utils.FlagAyamCulling) ||
|
||||
flag == string(utils.FlagAyamMati) {
|
||||
|
||||
@@ -144,7 +144,6 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
|
||||
for _, t := range marketingTypes {
|
||||
switch t {
|
||||
case string(utils.MarketingTypeAyamPullet):
|
||||
flagSet[string(utils.FlagAyam)] = struct{}{}
|
||||
flagSet[string(utils.FlagDOC)] = struct{}{}
|
||||
flagSet[string(utils.FlagPullet)] = struct{}{}
|
||||
flagSet[string(utils.FlagLayer)] = struct{}{}
|
||||
|
||||
@@ -50,16 +50,6 @@ type transferService struct {
|
||||
ExpenseBridge TransferExpenseBridge
|
||||
}
|
||||
|
||||
const (
|
||||
transferFunctionCodeIn = "STOCK_TRANSFER_IN"
|
||||
transferFunctionCodeOut = "STOCK_TRANSFER_OUT"
|
||||
)
|
||||
|
||||
type transferRoutePair struct {
|
||||
Stockable commonSvc.FifoStockV2RouteRule
|
||||
Usable commonSvc.FifoStockV2RouteRule
|
||||
}
|
||||
|
||||
func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTransfer.StockTransferRepository, stockTransferDetailRepo rStockTransfer.StockTransferDetailRepository, stockTransferDeliveryRepo rStockTransfer.StockTransferDeliveryRepository, stockTransferDeliveryItemRepo rStockTransfer.StockTransferDeliveryItemRepository, stockLogsRepo rStockLogs.StockLogRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, supplierRepo rSupplier.SupplierRepository, warehouseRepo warehouseRepo.WarehouseRepository, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository, documentSvc commonSvc.DocumentService, fifoSvc commonSvc.FifoService, fifoStockV2Svc commonSvc.FifoStockV2Service, expenseBridge TransferExpenseBridge) TransferService {
|
||||
return &transferService{
|
||||
Log: utils.Log,
|
||||
@@ -454,9 +444,9 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
||||
}
|
||||
}
|
||||
|
||||
routePairsByProductID := map[uint]transferRoutePair{}
|
||||
if len(req.Products) > 0 {
|
||||
routePairsByProductID, err = s.resolveTransferRoutes(c.Context(), tx, req.Products)
|
||||
pakanProducts := map[uint]bool{}
|
||||
if s.FifoStockV2Svc != nil && len(req.Products) > 0 {
|
||||
pakanProducts, err = s.resolvePakanProducts(c.Context(), tx, req.Products)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -464,17 +454,10 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
||||
|
||||
for _, product := range req.Products {
|
||||
detail := detailMap[uint64(product.ProductID)]
|
||||
routePair, ok := routePairsByProductID[uint(product.ProductID)]
|
||||
if !ok {
|
||||
return fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Konfigurasi FIFO v2 transfer tidak ditemukan untuk produk %d", product.ProductID),
|
||||
)
|
||||
}
|
||||
|
||||
outUsageQty := 0.0
|
||||
outPendingQty := 0.0
|
||||
useFifoV2 := s.FifoStockV2Svc != nil
|
||||
useFifoV2 := s.FifoStockV2Svc != nil && pakanProducts[uint(product.ProductID)]
|
||||
if useFifoV2 {
|
||||
s.Log.Infof(
|
||||
"[fifo-v2][transfer] use reflow movement=%s detail_id=%d product_id=%d source_pw=%d qty=%.3f",
|
||||
@@ -485,12 +468,12 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
||||
product.ProductQty,
|
||||
)
|
||||
reflowResult, err := s.FifoStockV2Svc.Reflow(c.Context(), commonSvc.FifoStockV2ReflowRequest{
|
||||
FlagGroupCode: routePair.Usable.FlagGroupCode,
|
||||
FlagGroupCode: "PAKAN",
|
||||
ProductWarehouseID: uint(*detail.SourceProductWarehouseID),
|
||||
Usable: commonSvc.FifoStockV2Ref{
|
||||
ID: uint(detail.Id),
|
||||
LegacyTypeKey: routePair.Usable.LegacyTypeKey,
|
||||
FunctionCode: routePair.Usable.FunctionCode,
|
||||
LegacyTypeKey: fifo.UsableKeyStockTransferOut.String(),
|
||||
FunctionCode: "STOCK_TRANSFER_OUT",
|
||||
},
|
||||
DesiredQty: product.ProductQty,
|
||||
Tx: tx,
|
||||
@@ -508,12 +491,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
||||
outPendingQty,
|
||||
)
|
||||
} else {
|
||||
usableKey := fifo.UsableKey(strings.TrimSpace(routePair.Usable.LegacyTypeKey))
|
||||
if usableKey == "" {
|
||||
usableKey = fifo.UsableKeyStockTransferOut
|
||||
}
|
||||
consumeResult, err := s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
|
||||
UsableKey: usableKey,
|
||||
UsableKey: fifo.UsableKeyStockTransferOut,
|
||||
UsableID: uint(detail.Id),
|
||||
ProductWarehouseID: uint(*detail.SourceProductWarehouseID),
|
||||
Quantity: product.ProductQty,
|
||||
@@ -574,12 +553,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
||||
product.ProductQty,
|
||||
)
|
||||
}
|
||||
stockableKey := fifo.StockableKey(strings.TrimSpace(routePair.Stockable.LegacyTypeKey))
|
||||
if stockableKey == "" {
|
||||
stockableKey = fifo.StockableKeyStockTransferIn
|
||||
}
|
||||
replenishResult, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
|
||||
StockableKey: stockableKey,
|
||||
StockableKey: fifo.StockableKeyStockTransferIn,
|
||||
StockableID: uint(detail.Id),
|
||||
ProductWarehouseID: uint(*detail.DestProductWarehouseID),
|
||||
Quantity: product.ProductQty,
|
||||
@@ -682,72 +657,50 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *transferService) resolveTransferRoutes(
|
||||
func (s *transferService) resolvePakanProducts(
|
||||
ctx context.Context,
|
||||
tx *gorm.DB,
|
||||
products []validation.TransferProduct,
|
||||
) (map[uint]transferRoutePair, error) {
|
||||
out := make(map[uint]transferRoutePair, len(products))
|
||||
) (map[uint]bool, error) {
|
||||
out := make(map[uint]bool, len(products))
|
||||
if len(products) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
productIDs := make(map[uint]struct{}, len(products))
|
||||
productIDs := make([]uint, 0, len(products))
|
||||
seen := make(map[uint]struct{}, len(products))
|
||||
for _, product := range products {
|
||||
if product.ProductID == 0 {
|
||||
continue
|
||||
}
|
||||
productIDs[product.ProductID] = struct{}{}
|
||||
if _, ok := seen[product.ProductID]; ok {
|
||||
continue
|
||||
}
|
||||
seen[product.ProductID] = struct{}{}
|
||||
productIDs = append(productIDs, product.ProductID)
|
||||
}
|
||||
if len(productIDs) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
for productID := range productIDs {
|
||||
usableRoute, err := commonSvc.ResolveFifoStockV2RouteByProductIDAndLane(
|
||||
ctx,
|
||||
tx,
|
||||
productID,
|
||||
transferFunctionCodeOut,
|
||||
commonSvc.FifoStockV2LaneUsable,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if usableRoute == nil {
|
||||
return nil, fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Produk %d tidak mendukung transaksi Transfer Stock (OUT) pada matrix FIFO v2", productID),
|
||||
)
|
||||
}
|
||||
|
||||
stockableRoute, err := commonSvc.ResolveFifoStockV2RouteByProductIDAndLane(
|
||||
ctx,
|
||||
tx,
|
||||
productID,
|
||||
transferFunctionCodeIn,
|
||||
commonSvc.FifoStockV2LaneStockable,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if stockableRoute == nil {
|
||||
return nil, fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Produk %d tidak mendukung transaksi Transfer Stock (IN) pada matrix FIFO v2", productID),
|
||||
)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(usableRoute.FlagGroupCode) != strings.TrimSpace(stockableRoute.FlagGroupCode) {
|
||||
return nil, fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Konfigurasi matrix FIFO v2 transfer tidak konsisten untuk produk %d", productID),
|
||||
)
|
||||
}
|
||||
|
||||
out[productID] = transferRoutePair{
|
||||
Stockable: *stockableRoute,
|
||||
Usable: *usableRoute,
|
||||
}
|
||||
type row struct {
|
||||
ProductID uint `gorm:"column:product_id"`
|
||||
}
|
||||
var rows []row
|
||||
err := tx.WithContext(ctx).
|
||||
Table("flags f").
|
||||
Select("DISTINCT f.flagable_id AS product_id").
|
||||
Where("f.flagable_type = ?", entity.FlagableTypeProduct).
|
||||
Where("f.name IN ?", []string{"PAKAN", "PRE-STARTER", "STARTER", "FINISHER"}).
|
||||
Where("f.flagable_id IN ?", productIDs).
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
out[row.ProductID] = true
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,6 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetClosingPenjualanForAgeChickD
|
||||
Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = product_warehouses.project_flock_kandang_id").
|
||||
Where("project_flock_kandangs.project_flock_id = ?", projectFlockID).
|
||||
Where("flags.name IN (?)", []string{
|
||||
string(utils.FlagAyam),
|
||||
string(utils.FlagAyamAfkir),
|
||||
string(utils.FlagAyamCulling),
|
||||
string(utils.FlagPullet),
|
||||
@@ -159,12 +158,9 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetClosingPenjualanByCategory(c
|
||||
string(utils.FlagTelurPecah),
|
||||
string(utils.FlagTelurPutih),
|
||||
string(utils.FlagTelurRetak),
|
||||
string(utils.FlagTelurPapacal),
|
||||
string(utils.FlagTelurJumbo),
|
||||
})
|
||||
} else {
|
||||
db = db.Where("flags.name IN (?)", []string{
|
||||
string(utils.FlagAyam),
|
||||
string(utils.FlagDOC),
|
||||
string(utils.FlagPullet),
|
||||
string(utils.FlagLayer),
|
||||
@@ -331,12 +327,12 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
|
||||
switch filters.MarketingType {
|
||||
case "ayam":
|
||||
db = db.Where("flags.name IN (?)", []string{
|
||||
string(utils.FlagAyam), string(utils.FlagDOC), string(utils.FlagPullet), string(utils.FlagLayer),
|
||||
string(utils.FlagDOC), string(utils.FlagPullet), string(utils.FlagLayer),
|
||||
})
|
||||
case "telur":
|
||||
db = db.Where("flags.name IN (?)", []string{
|
||||
string(utils.FlagTelur), string(utils.FlagTelurUtuh), string(utils.FlagTelurPecah),
|
||||
string(utils.FlagTelurPutih), string(utils.FlagTelurRetak), string(utils.FlagTelurPapacal), string(utils.FlagTelurJumbo),
|
||||
string(utils.FlagTelurPutih), string(utils.FlagTelurRetak),
|
||||
})
|
||||
case "trading":
|
||||
db = db.Where("flags.name IN (?)", []string{
|
||||
|
||||
@@ -549,29 +549,8 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Delivery product not found")
|
||||
}
|
||||
|
||||
route, err := commonSvc.ResolveFifoStockV2RouteByProductWarehouseIDAndLane(
|
||||
ctx,
|
||||
tx,
|
||||
marketingProduct.ProductWarehouseId,
|
||||
"MARKETING_OUT",
|
||||
commonSvc.FifoStockV2LaneUsable,
|
||||
)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve FIFO v2 marketing route")
|
||||
}
|
||||
if route == nil {
|
||||
return fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Product warehouse %d tidak mendukung transaksi Marketing pada matrix FIFO v2", marketingProduct.ProductWarehouseId),
|
||||
)
|
||||
}
|
||||
usableKey := fifo.UsableKey(route.LegacyTypeKey)
|
||||
if route.LegacyTypeKey == "" {
|
||||
usableKey = fifo.UsableKeyMarketingDelivery
|
||||
}
|
||||
|
||||
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
||||
UsableKey: usableKey,
|
||||
UsableKey: fifo.UsableKeyMarketingDelivery,
|
||||
UsableID: deliveryProduct.Id,
|
||||
ProductWarehouseID: marketingProduct.ProductWarehouseId,
|
||||
Quantity: requestedQty,
|
||||
@@ -624,27 +603,6 @@ func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gor
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Product warehouse not found")
|
||||
}
|
||||
|
||||
route, err := commonSvc.ResolveFifoStockV2RouteByProductWarehouseIDAndLane(
|
||||
ctx,
|
||||
tx,
|
||||
marketingProduct.ProductWarehouseId,
|
||||
"MARKETING_OUT",
|
||||
commonSvc.FifoStockV2LaneUsable,
|
||||
)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve FIFO v2 marketing route")
|
||||
}
|
||||
if route == nil {
|
||||
return fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Product warehouse %d tidak mendukung transaksi Marketing pada matrix FIFO v2", marketingProduct.ProductWarehouseId),
|
||||
)
|
||||
}
|
||||
usableKey := fifo.UsableKey(route.LegacyTypeKey)
|
||||
if route.LegacyTypeKey == "" {
|
||||
usableKey = fifo.UsableKeyMarketingDelivery
|
||||
}
|
||||
|
||||
deliveryProductRepo := marketingRepo.NewMarketingDeliveryProductRepository(tx)
|
||||
currentUsage, err := deliveryProductRepo.GetUsageQty(ctx, deliveryProduct.Id)
|
||||
if err != nil {
|
||||
@@ -656,7 +614,7 @@ func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gor
|
||||
}
|
||||
|
||||
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
||||
UsableKey: usableKey,
|
||||
UsableKey: fifo.UsableKeyMarketingDelivery,
|
||||
UsableID: deliveryProduct.Id,
|
||||
Tx: tx,
|
||||
}); err != nil {
|
||||
|
||||
@@ -48,8 +48,6 @@ type salesOrdersService struct {
|
||||
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
||||
}
|
||||
|
||||
const marketingFunctionCodeOut = "MARKETING_OUT"
|
||||
|
||||
func NewSalesOrdersService(marketingRepo repository.MarketingRepository, customerRepo customerRepo.CustomerRepository, productWarehouseRepo productWarehouseRepo.ProductWarehouseRepository, userRepo userRepo.UserRepository, approvalSvc commonSvc.ApprovalService, fifoSvc commonSvc.FifoService, warehouseRepo warehouseRepo.WarehouseRepository,
|
||||
projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository, validate *validator.Validate) SalesOrdersService {
|
||||
return &salesOrdersService{
|
||||
@@ -76,36 +74,6 @@ func (s salesOrdersService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
Preload("Products.ProductWarehouse.Warehouse")
|
||||
}
|
||||
|
||||
func (s salesOrdersService) resolveMarketingUsableKey(ctx context.Context, tx *gorm.DB, productWarehouseID uint) (fifo.UsableKey, error) {
|
||||
if productWarehouseID == 0 {
|
||||
return "", fiber.NewError(fiber.StatusBadRequest, "Product warehouse tidak valid untuk transaksi Marketing")
|
||||
}
|
||||
|
||||
route, err := commonSvc.ResolveFifoStockV2RouteByProductWarehouseIDAndLane(
|
||||
ctx,
|
||||
tx,
|
||||
productWarehouseID,
|
||||
marketingFunctionCodeOut,
|
||||
commonSvc.FifoStockV2LaneUsable,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve FIFO v2 marketing route")
|
||||
}
|
||||
if route == nil {
|
||||
return "", fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Product warehouse %d tidak mendukung transaksi Marketing pada matrix FIFO v2", productWarehouseID),
|
||||
)
|
||||
}
|
||||
|
||||
usableKey := fifo.UsableKey(strings.TrimSpace(route.LegacyTypeKey))
|
||||
if usableKey == "" {
|
||||
usableKey = fifo.UsableKeyMarketingDelivery
|
||||
}
|
||||
|
||||
return usableKey, nil
|
||||
}
|
||||
|
||||
func (s salesOrdersService) getOne(c *fiber.Ctx, id uint) (*entity.Marketing, error) {
|
||||
if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); err != nil {
|
||||
return nil, err
|
||||
@@ -408,12 +376,8 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
||||
if qtyDiff < 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Cannot decrease quantity after stock has been allocated. Please delete and create new product.")
|
||||
} else if qtyDiff > 0 {
|
||||
usableKey, err := s.resolveMarketingUsableKey(c.Context(), dbTransaction, rp.ProductWarehouseId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
|
||||
UsableKey: usableKey,
|
||||
_, err := s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
|
||||
UsableKey: fifo.UsableKeyMarketingDelivery,
|
||||
UsableID: deliveryProduct.Id,
|
||||
ProductWarehouseID: rp.ProductWarehouseId,
|
||||
Quantity: qtyDiff,
|
||||
@@ -474,13 +438,9 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
||||
if deliveryProduct.DeliveryDate != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Cannot delete marketing product %d because it has been delivered", old.Id))
|
||||
}
|
||||
usableKey, err := s.resolveMarketingUsableKey(c.Context(), dbTransaction, old.ProductWarehouseId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.FifoSvc.ReleaseUsage(c.Context(), commonSvc.StockReleaseRequest{
|
||||
UsableKey: usableKey,
|
||||
UsableKey: fifo.UsableKeyMarketingDelivery,
|
||||
UsableID: deliveryProduct.Id,
|
||||
Tx: dbTransaction,
|
||||
}); err != nil {
|
||||
@@ -562,22 +522,9 @@ func (s salesOrdersService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
if len(marketing.Products) > 0 {
|
||||
deliveryProducts, err := marketingDeliveryProductRepoTx.GetByMarketingId(c.Context(), marketing.Id)
|
||||
if err == nil && len(deliveryProducts) > 0 {
|
||||
deliveryUsableKeyByProductID := make(map[uint]fifo.UsableKey, len(marketing.Products))
|
||||
for _, product := range marketing.Products {
|
||||
usableKey, err := s.resolveMarketingUsableKey(c.Context(), dbTransaction, product.ProductWarehouseId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deliveryUsableKeyByProductID[product.Id] = usableKey
|
||||
}
|
||||
|
||||
for _, dp := range deliveryProducts {
|
||||
usableKey, ok := deliveryUsableKeyByProductID[dp.MarketingProductId]
|
||||
if !ok {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product untuk delivery %d tidak ditemukan", dp.Id))
|
||||
}
|
||||
if err := s.FifoSvc.ReleaseUsage(c.Context(), commonSvc.StockReleaseRequest{
|
||||
UsableKey: usableKey,
|
||||
UsableKey: fifo.UsableKeyMarketingDelivery,
|
||||
UsableID: dp.Id,
|
||||
Tx: dbTransaction,
|
||||
}); err != nil {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
productCategoryDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories/dto"
|
||||
uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto"
|
||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||
utils "gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
)
|
||||
|
||||
// === DTO Structs ===
|
||||
@@ -18,9 +17,6 @@ type ProductRelationDTO struct {
|
||||
ProductPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||
SellingPrice *float64 `gorm:"type:numeric(15,3)"`
|
||||
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
||||
Flag *string `json:"flag,omitempty"`
|
||||
SubFlag *string `json:"sub_flag,omitempty"`
|
||||
SubFlags *[]string `json:"sub_flags,omitempty"`
|
||||
Flags *[]string `json:"flags,omitempty"`
|
||||
ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"`
|
||||
Suppliers []ProductSupplierDTO `json:"suppliers"`
|
||||
@@ -35,9 +31,6 @@ type ProductListDTO struct {
|
||||
SellingPrice *float64 `json:"selling_price,omitempty"`
|
||||
Tax *float64 `json:"tax,omitempty"`
|
||||
ExpiryPeriod *int `json:"expiry_period,omitempty"`
|
||||
Flag *string `json:"flag,omitempty"`
|
||||
SubFlag *string `json:"sub_flag,omitempty"`
|
||||
SubFlags []string `json:"sub_flags,omitempty"`
|
||||
Flags []string `json:"flags"`
|
||||
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
||||
ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"`
|
||||
@@ -66,13 +59,6 @@ func ToProductRelationDTO(e entity.Product) ProductRelationDTO {
|
||||
for i, f := range e.Flags {
|
||||
flags[i] = f.Name
|
||||
}
|
||||
flag, subFlag, subFlags := resolveProductFlagAndSubFlags(flags)
|
||||
var subFlagsRef *[]string
|
||||
if len(subFlags) > 0 {
|
||||
values := make([]string, len(subFlags))
|
||||
copy(values, subFlags)
|
||||
subFlagsRef = &values
|
||||
}
|
||||
|
||||
var uomRef *uomDTO.UomRelationDTO
|
||||
if e.Uom.Id != 0 {
|
||||
@@ -91,9 +77,6 @@ func ToProductRelationDTO(e entity.Product) ProductRelationDTO {
|
||||
Name: e.Name,
|
||||
ProductPrice: e.ProductPrice,
|
||||
SellingPrice: e.SellingPrice,
|
||||
Flag: flag,
|
||||
SubFlag: subFlag,
|
||||
SubFlags: subFlagsRef,
|
||||
Flags: &flags,
|
||||
Uom: uomRef,
|
||||
ProductCategory: categoryRef,
|
||||
@@ -118,7 +101,6 @@ func ToProductListDTO(e entity.Product) ProductListDTO {
|
||||
for i, f := range e.Flags {
|
||||
flags[i] = f.Name
|
||||
}
|
||||
flag, subFlag, subFlags := resolveProductFlagAndSubFlags(flags)
|
||||
|
||||
var uomRef *uomDTO.UomRelationDTO
|
||||
if e.Uom.Id != 0 {
|
||||
@@ -129,9 +111,6 @@ func ToProductListDTO(e entity.Product) ProductListDTO {
|
||||
return ProductListDTO{
|
||||
Id: e.Id,
|
||||
Name: e.Name,
|
||||
Flag: flag,
|
||||
SubFlag: subFlag,
|
||||
SubFlags: subFlags,
|
||||
Flags: flags,
|
||||
Uom: uomRef,
|
||||
Brand: e.Brand,
|
||||
@@ -162,58 +141,6 @@ func ToProductDetailDTO(e entity.Product) ProductDetailDTO {
|
||||
}
|
||||
}
|
||||
|
||||
func resolveProductFlagAndSubFlags(flags []string) (*string, *string, []string) {
|
||||
normalized := utils.NormalizeFlagTypes(flags)
|
||||
if len(normalized) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
available := make(map[utils.FlagType]struct{}, len(normalized))
|
||||
for _, flag := range normalized {
|
||||
available[flag] = struct{}{}
|
||||
}
|
||||
|
||||
var selectedFlag utils.FlagType
|
||||
for _, mainFlag := range utils.ProductMainFlags() {
|
||||
if _, ok := available[mainFlag]; ok {
|
||||
selectedFlag = mainFlag
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if selectedFlag == "" {
|
||||
subToMain := utils.ProductSubFlagToFlag()
|
||||
for _, flag := range normalized {
|
||||
if parent, ok := subToMain[flag]; ok {
|
||||
selectedFlag = parent
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if selectedFlag == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
flag := string(selectedFlag)
|
||||
|
||||
var subFlag *string
|
||||
subFlagValues := make([]string, 0)
|
||||
subFlagsByMain := utils.ProductSubFlagsByFlag()
|
||||
for _, sub := range subFlagsByMain[selectedFlag] {
|
||||
if _, ok := available[sub]; ok {
|
||||
subFlagValues = append(subFlagValues, string(sub))
|
||||
}
|
||||
}
|
||||
|
||||
if len(subFlagValues) > 0 {
|
||||
first := subFlagValues[0]
|
||||
subFlag = &first
|
||||
}
|
||||
|
||||
return &flag, subFlag, subFlagValues
|
||||
}
|
||||
|
||||
func toProductSupplierDTOs(relations []entity.ProductSupplier) []ProductSupplierDTO {
|
||||
if len(relations) == 0 {
|
||||
return make([]ProductSupplierDTO, 0)
|
||||
|
||||
@@ -41,151 +41,6 @@ func normalizeProductFlags(raw []string) ([]string, error) {
|
||||
return utils.FlagTypesToStrings(normalized), nil
|
||||
}
|
||||
|
||||
func productMainFlagOptionsString() []string {
|
||||
mainFlags := utils.ProductMainFlags()
|
||||
result := make([]string, len(mainFlags))
|
||||
for i, flag := range mainFlags {
|
||||
result[i] = string(flag)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func productSubFlagOptionsString(flag utils.FlagType) []string {
|
||||
subFlagsByFlag := utils.ProductSubFlagsByFlag()
|
||||
subFlags := subFlagsByFlag[flag]
|
||||
result := make([]string, len(subFlags))
|
||||
for i, subFlag := range subFlags {
|
||||
result[i] = string(subFlag)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func normalizeStructuredSubFlagsInput(subFlagRaw *string, subFlagsRaw []string, hasSubFlagsField bool) ([]utils.FlagType, error) {
|
||||
values := make([]string, 0, len(subFlagsRaw)+1)
|
||||
|
||||
if subFlagRaw != nil {
|
||||
single := strings.TrimSpace(*subFlagRaw)
|
||||
if single == "" {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "sub_flag cannot be empty")
|
||||
}
|
||||
values = append(values, single)
|
||||
}
|
||||
|
||||
if hasSubFlagsField {
|
||||
for _, raw := range subFlagsRaw {
|
||||
item := strings.TrimSpace(raw)
|
||||
if item == "" {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "sub_flags cannot contain empty value")
|
||||
}
|
||||
values = append(values, item)
|
||||
}
|
||||
}
|
||||
|
||||
if len(values) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return utils.NormalizeFlagTypes(values), nil
|
||||
}
|
||||
|
||||
func resolveProductFlagsFromFlagInput(flagRaw *string, subFlagRaw *string, subFlagsRaw []string, hasSubFlagsField bool) ([]string, bool, error) {
|
||||
if flagRaw == nil && subFlagRaw == nil && !hasSubFlagsField {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
if flagRaw == nil && (subFlagRaw != nil || hasSubFlagsField) {
|
||||
return nil, false, fiber.NewError(fiber.StatusBadRequest, "flag is required when sub_flag/sub_flags is provided")
|
||||
}
|
||||
|
||||
flagText := strings.TrimSpace(*flagRaw)
|
||||
if flagText == "" {
|
||||
return nil, false, fiber.NewError(fiber.StatusBadRequest, "flag cannot be empty")
|
||||
}
|
||||
|
||||
flag := utils.CanonicalFlagType(flagText)
|
||||
if !utils.IsProductMainFlag(flag) {
|
||||
return nil, false, fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Invalid product flag: %s. Allowed flags: %s", flagText, strings.Join(productMainFlagOptionsString(), ", ")),
|
||||
)
|
||||
}
|
||||
|
||||
out := []string{string(flag)}
|
||||
|
||||
normalizedSubFlags, err := normalizeStructuredSubFlagsInput(subFlagRaw, subFlagsRaw, hasSubFlagsField)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if len(normalizedSubFlags) == 0 {
|
||||
if !utils.ProductFlagAllowWithoutSubFlag(flag) {
|
||||
return nil, false, fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("sub_flag/sub_flags is required for flag %s", string(flag)),
|
||||
)
|
||||
}
|
||||
return out, true, nil
|
||||
}
|
||||
|
||||
invalidSubFlags := make([]string, 0)
|
||||
for _, subFlag := range normalizedSubFlags {
|
||||
if !utils.IsValidProductSubFlag(flag, subFlag) {
|
||||
invalidSubFlags = append(invalidSubFlags, string(subFlag))
|
||||
}
|
||||
}
|
||||
if len(invalidSubFlags) > 0 {
|
||||
return nil, false, fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Invalid sub_flags %s for flag %s. Allowed sub_flags: %s", strings.Join(invalidSubFlags, ", "), string(flag), strings.Join(productSubFlagOptionsString(flag), ", ")),
|
||||
)
|
||||
}
|
||||
|
||||
out = append(out, utils.FlagTypesToStrings(normalizedSubFlags)...)
|
||||
return out, true, nil
|
||||
}
|
||||
|
||||
func resolveCreateProductFlags(req *validation.Create) ([]string, error) {
|
||||
hasStructuredInput := req.Flag != nil || req.SubFlag != nil || req.SubFlags != nil
|
||||
if len(req.Flags) > 0 && hasStructuredInput {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Use either flags or flag/sub_flag/sub_flags, not both")
|
||||
}
|
||||
|
||||
if len(req.Flags) > 0 {
|
||||
return normalizeProductFlags(req.Flags)
|
||||
}
|
||||
|
||||
flags, _, err := resolveProductFlagsFromFlagInput(req.Flag, req.SubFlag, req.SubFlags, req.SubFlags != nil)
|
||||
return flags, err
|
||||
}
|
||||
|
||||
func resolveUpdateProductFlags(req *validation.Update) (bool, []string, error) {
|
||||
hasStructuredInput := req.Flag != nil || req.SubFlag != nil || req.SubFlags != nil
|
||||
|
||||
if req.Flags != nil {
|
||||
if hasStructuredInput {
|
||||
if len(*req.Flags) > 0 {
|
||||
return false, nil, fiber.NewError(fiber.StatusBadRequest, "Use either flags or flag/sub_flag/sub_flags, not both")
|
||||
}
|
||||
} else {
|
||||
flags, err := normalizeProductFlags(*req.Flags)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return true, flags, nil
|
||||
}
|
||||
}
|
||||
|
||||
subFlagsRaw := make([]string, 0)
|
||||
if req.SubFlags != nil {
|
||||
subFlagsRaw = *req.SubFlags
|
||||
}
|
||||
flags, provided, err := resolveProductFlagsFromFlagInput(req.Flag, req.SubFlag, subFlagsRaw, req.SubFlags != nil)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return provided, flags, nil
|
||||
}
|
||||
|
||||
func NewProductService(repo repository.ProductRepository, validate *validator.Validate) ProductService {
|
||||
return &productService{
|
||||
Log: utils.Log,
|
||||
@@ -322,7 +177,7 @@ func (s *productService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
||||
}
|
||||
}
|
||||
|
||||
productFlags, flagErr := resolveCreateProductFlags(req)
|
||||
productFlags, flagErr := normalizeProductFlags(req.Flags)
|
||||
if flagErr != nil {
|
||||
return nil, flagErr
|
||||
}
|
||||
@@ -482,10 +337,13 @@ func (s productService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
||||
flagUpdate bool
|
||||
flagValues []string
|
||||
)
|
||||
var flagErr error
|
||||
flagUpdate, flagValues, flagErr = resolveUpdateProductFlags(req)
|
||||
if flagErr != nil {
|
||||
return nil, flagErr
|
||||
if req.Flags != nil {
|
||||
flagUpdate = true
|
||||
var flagErr error
|
||||
flagValues, flagErr = normalizeProductFlags(*req.Flags)
|
||||
if flagErr != nil {
|
||||
return nil, flagErr
|
||||
}
|
||||
}
|
||||
|
||||
if len(updateBody) == 0 && !supplierUpdate && !flagUpdate {
|
||||
|
||||
@@ -6,37 +6,31 @@ type SupplierPrice struct {
|
||||
}
|
||||
|
||||
type Create struct {
|
||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
||||
Brand string `json:"brand" validate:"required_strict,min=2,max=50"`
|
||||
Sku *string `json:"sku,omitempty" validate:"omitempty,max=100"`
|
||||
UomID uint `json:"uom_id" validate:"required,gt=0"`
|
||||
ProductCategoryID uint `json:"product_category_id" validate:"required,gt=0"`
|
||||
ProductPrice float64 `json:"product_price" validate:"required"`
|
||||
SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"`
|
||||
Tax *float64 `json:"tax,omitempty" validate:"omitempty"`
|
||||
ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"`
|
||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
||||
Brand string `json:"brand" validate:"required_strict,min=2,max=50"`
|
||||
Sku *string `json:"sku,omitempty" validate:"omitempty,max=100"`
|
||||
UomID uint `json:"uom_id" validate:"required,gt=0"`
|
||||
ProductCategoryID uint `json:"product_category_id" validate:"required,gt=0"`
|
||||
ProductPrice float64 `json:"product_price" validate:"required"`
|
||||
SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"`
|
||||
Tax *float64 `json:"tax,omitempty" validate:"omitempty"`
|
||||
ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"`
|
||||
Suppliers []SupplierPrice `json:"suppliers,omitempty" validate:"omitempty,dive"`
|
||||
Flag *string `json:"flag,omitempty" validate:"omitempty,max=50"`
|
||||
SubFlag *string `json:"sub_flag,omitempty" validate:"omitempty,max=50"`
|
||||
SubFlags []string `json:"sub_flags,omitempty" validate:"omitempty,dive,max=50"`
|
||||
Flags []string `json:"flags,omitempty" validate:"omitempty,dive"`
|
||||
Flags []string `json:"flags,omitempty" validate:"omitempty,dive"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=3"`
|
||||
Brand *string `json:"brand,omitempty" validate:"omitempty,min=2"`
|
||||
Sku *string `json:"sku,omitempty" validate:"omitempty"`
|
||||
UomID *uint `json:"uom_id,omitempty" validate:"omitempty,gt=0"`
|
||||
ProductCategoryID *uint `json:"product_category_id,omitempty" validate:"omitempty,gt=0"`
|
||||
ProductPrice *float64 `json:"product_price,omitempty" validate:"omitempty"`
|
||||
SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"`
|
||||
Tax *float64 `json:"tax,omitempty" validate:"omitempty"`
|
||||
ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"`
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=3"`
|
||||
Brand *string `json:"brand,omitempty" validate:"omitempty,min=2"`
|
||||
Sku *string `json:"sku,omitempty" validate:"omitempty"`
|
||||
UomID *uint `json:"uom_id,omitempty" validate:"omitempty,gt=0"`
|
||||
ProductCategoryID *uint `json:"product_category_id,omitempty" validate:"omitempty,gt=0"`
|
||||
ProductPrice *float64 `json:"product_price,omitempty" validate:"omitempty"`
|
||||
SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"`
|
||||
Tax *float64 `json:"tax,omitempty" validate:"omitempty"`
|
||||
ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"`
|
||||
Suppliers *[]SupplierPrice `json:"suppliers,omitempty" validate:"omitempty,dive"`
|
||||
Flag *string `json:"flag,omitempty" validate:"omitempty,max=50"`
|
||||
SubFlag *string `json:"sub_flag,omitempty" validate:"omitempty,max=50"`
|
||||
SubFlags *[]string `json:"sub_flags,omitempty" validate:"omitempty,dive,max=50"`
|
||||
Flags *[]string `json:"flags,omitempty" validate:"omitempty,dive"`
|
||||
Flags *[]string `json:"flags,omitempty" validate:"omitempty,dive"`
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
|
||||
@@ -29,8 +29,6 @@ import (
|
||||
|
||||
var chickinUsableKey = fifo.UsableKeyProjectChickin
|
||||
|
||||
const chickinFunctionCodeOut = "CHICKIN_OUT"
|
||||
|
||||
type ChickinService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectChickin, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectChickin, error)
|
||||
@@ -164,32 +162,25 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
||||
|
||||
if productWarehouse.Product.Id != 0 {
|
||||
|
||||
requiredFlags := make([]utils.FlagType, 0, 2)
|
||||
var requiredFlag utils.FlagType
|
||||
if projectFlockKandang.ProjectFlock.Category == string(utils.ProjectFlockCategoryGrowing) {
|
||||
requiredFlags = append(requiredFlags, utils.FlagAyam, utils.FlagDOC)
|
||||
requiredFlag = utils.FlagDOC
|
||||
} else if projectFlockKandang.ProjectFlock.Category == string(utils.ProjectFlockCategoryLaying) {
|
||||
requiredFlags = append(requiredFlags, utils.FlagAyam, utils.FlagPullet)
|
||||
requiredFlag = utils.FlagPullet
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid flock category for chickin")
|
||||
}
|
||||
|
||||
hasRequiredFlag := false
|
||||
for _, flag := range productWarehouse.Product.Flags {
|
||||
currentFlag := utils.FlagType(flag.Name)
|
||||
for _, requiredFlag := range requiredFlags {
|
||||
if currentFlag == requiredFlag {
|
||||
hasRequiredFlag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasRequiredFlag {
|
||||
if utils.FlagType(flag.Name) == requiredFlag {
|
||||
hasRequiredFlag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasRequiredFlag {
|
||||
requiredText := strings.Join(utils.FlagTypesToStrings(requiredFlags), " or ")
|
||||
return nil, fmt.Errorf("product warehouse %d cannot be used for %s chickin. Product must have %s flag (product ID: %d, warehouse ID: %d)", chickinReq.ProductWarehouseId, projectFlockKandang.ProjectFlock.Category, requiredText, productWarehouse.Product.Id, productWarehouse.Id)
|
||||
return nil, fmt.Errorf("product warehouse %d cannot be used for %s chickin. Product must have %s flag (product ID: %d, warehouse ID: %d)", chickinReq.ProductWarehouseId, projectFlockKandang.ProjectFlock.Category, requiredFlag, productWarehouse.Product.Id, productWarehouse.Id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,9 +483,9 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
|
||||
var targetFlag utils.FlagType
|
||||
if category == string(utils.ProjectFlockCategoryGrowing) {
|
||||
targetFlag = utils.FlagAyam
|
||||
targetFlag = utils.FlagPullet
|
||||
} else if category == string(utils.ProjectFlockCategoryLaying) {
|
||||
targetFlag = utils.FlagAyam
|
||||
targetFlag = utils.FlagLayer
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
@@ -630,29 +621,8 @@ func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB,
|
||||
return nil
|
||||
}
|
||||
|
||||
route, err := commonSvc.ResolveFifoStockV2RouteByProductWarehouseIDAndLane(
|
||||
ctx,
|
||||
tx,
|
||||
chickin.ProductWarehouseId,
|
||||
chickinFunctionCodeOut,
|
||||
commonSvc.FifoStockV2LaneUsable,
|
||||
)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve FIFO v2 chickin route")
|
||||
}
|
||||
if route == nil {
|
||||
return fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Product warehouse %d tidak mendukung transaksi Chickin pada matrix FIFO v2", chickin.ProductWarehouseId),
|
||||
)
|
||||
}
|
||||
usableKey := fifo.UsableKey(strings.TrimSpace(route.LegacyTypeKey))
|
||||
if usableKey == "" {
|
||||
usableKey = chickinUsableKey
|
||||
}
|
||||
|
||||
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
||||
UsableKey: usableKey,
|
||||
UsableKey: chickinUsableKey,
|
||||
UsableID: chickin.Id,
|
||||
ProductWarehouseID: chickin.ProductWarehouseId,
|
||||
Quantity: desiredQty,
|
||||
@@ -718,34 +688,13 @@ func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB,
|
||||
return nil
|
||||
}
|
||||
|
||||
route, err := commonSvc.ResolveFifoStockV2RouteByProductWarehouseIDAndLane(
|
||||
ctx,
|
||||
tx,
|
||||
chickin.ProductWarehouseId,
|
||||
chickinFunctionCodeOut,
|
||||
commonSvc.FifoStockV2LaneUsable,
|
||||
)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve FIFO v2 chickin route")
|
||||
}
|
||||
if route == nil {
|
||||
return fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Product warehouse %d tidak mendukung transaksi Chickin pada matrix FIFO v2", chickin.ProductWarehouseId),
|
||||
)
|
||||
}
|
||||
usableKey := fifo.UsableKey(strings.TrimSpace(route.LegacyTypeKey))
|
||||
if usableKey == "" {
|
||||
usableKey = chickinUsableKey
|
||||
}
|
||||
|
||||
var currentUsage float64
|
||||
if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Select("usage_qty").Scan(¤tUsage).Error; err != nil {
|
||||
|
||||
}
|
||||
|
||||
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
||||
UsableKey: usableKey,
|
||||
UsableKey: chickinUsableKey,
|
||||
UsableID: chickin.Id,
|
||||
Tx: tx,
|
||||
}); err != nil {
|
||||
|
||||
@@ -312,7 +312,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
||||
return nil, err
|
||||
}
|
||||
feedIDs := recordingutil.CollectWarehouseIDs(req.Stocks, func(st validation.Stock) uint { return st.ProductWarehouseId })
|
||||
if err := s.ensureProductWarehousesByFlags(ctx, feedIDs, []string{"PAKAN", "PRE-STARTER", "STARTER", "FINISHER", "OVK", "OBAT", "VITAMIN", "KIMIA"}, "feed"); err != nil {
|
||||
if err := s.ensureProductWarehousesByFlags(ctx, feedIDs, []string{"PAKAN", "OVK"}, "feed"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
depletionIDs := recordingutil.CollectWarehouseIDs(req.Depletions, func(d validation.Depletion) uint { return d.ProductWarehouseId })
|
||||
@@ -320,7 +320,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
||||
return nil, err
|
||||
}
|
||||
eggIDs := recordingutil.CollectWarehouseIDs(req.Eggs, func(e validation.Egg) uint { return e.ProductWarehouseId })
|
||||
if err := s.ensureProductWarehousesByFlags(ctx, eggIDs, []string{"TELUR", "TELUR-UTUH", "TELUR-PUTIH", "TELUR-RETAK", "TELUR-PECAH", "TELUR-PAPACAL", "TELUR-JUMBO"}, "egg"); err != nil {
|
||||
if err := s.ensureProductWarehousesByFlags(ctx, eggIDs, []string{"TELUR-UTUH", "TELUR-PECAH", "TELUR-PUTIH", "TELUR-RETAK", "TELUR"}, "egg"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
actorID, err := m.ActorIDFromContext(c)
|
||||
@@ -512,7 +512,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
||||
return err
|
||||
}
|
||||
feedIDs := recordingutil.CollectWarehouseIDs(req.Stocks, func(st validation.Stock) uint { return st.ProductWarehouseId })
|
||||
if err := s.ensureProductWarehousesByFlags(ctx, feedIDs, []string{"PAKAN", "PRE-STARTER", "STARTER", "FINISHER", "OVK", "OBAT", "VITAMIN", "KIMIA"}, "feed"); err != nil {
|
||||
if err := s.ensureProductWarehousesByFlags(ctx, feedIDs, []string{"PAKAN", "OVK"}, "feed"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.syncRecordingStocks(ctx, tx, recordingEntity.Id, existingStocks, req.Stocks, note, actorID); err != nil {
|
||||
@@ -613,7 +613,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
||||
return err
|
||||
}
|
||||
eggIDs := recordingutil.CollectWarehouseIDs(req.Eggs, func(e validation.Egg) uint { return e.ProductWarehouseId })
|
||||
if err := s.ensureProductWarehousesByFlags(ctx, eggIDs, []string{"TELUR", "TELUR-UTUH", "TELUR-PUTIH", "TELUR-RETAK", "TELUR-PECAH", "TELUR-PAPACAL", "TELUR-JUMBO"}, "egg"); err != nil {
|
||||
if err := s.ensureProductWarehousesByFlags(ctx, eggIDs, []string{"TELUR-UTUH", "TELUR-PECAH", "TELUR-PUTIH", "TELUR-RETAK", "TELUR"}, "egg"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureRecordingEggsUnused(existingEggs); err != nil {
|
||||
|
||||
@@ -21,93 +21,7 @@ import (
|
||||
var recordingStockUsableKey = fifo.UsableKeyRecordingStock
|
||||
var recordingDepletionUsableKey = fifo.UsableKeyRecordingDepletion
|
||||
|
||||
const (
|
||||
depletionUsageTolerance = 0.000001
|
||||
recordingFunctionCodeStockOut = "RECORDING_STOCK_OUT"
|
||||
recordingFunctionCodeDepletionOut = "RECORDING_DEPLETION_OUT"
|
||||
recordingFunctionCodeDepletionIn = "RECORDING_DEPLETION_IN"
|
||||
recordingFunctionCodeRecordingEggIn = "RECORDING_EGG_IN"
|
||||
)
|
||||
|
||||
func (s *recordingService) resolveRecordingUsableKey(
|
||||
ctx context.Context,
|
||||
tx *gorm.DB,
|
||||
productWarehouseID uint,
|
||||
functionCode string,
|
||||
fallback fifo.UsableKey,
|
||||
actionLabel string,
|
||||
) (fifo.UsableKey, error) {
|
||||
if productWarehouseID == 0 {
|
||||
return "", fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product warehouse tidak valid untuk transaksi %s", actionLabel))
|
||||
}
|
||||
|
||||
route, err := commonSvc.ResolveFifoStockV2RouteByProductWarehouseIDAndLane(
|
||||
ctx,
|
||||
tx,
|
||||
productWarehouseID,
|
||||
functionCode,
|
||||
commonSvc.FifoStockV2LaneUsable,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve FIFO v2 recording route")
|
||||
}
|
||||
if route == nil {
|
||||
return "", fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Product warehouse %d tidak mendukung transaksi %s pada matrix FIFO v2", productWarehouseID, actionLabel),
|
||||
)
|
||||
}
|
||||
|
||||
usableKey := fifo.UsableKey(strings.TrimSpace(route.LegacyTypeKey))
|
||||
if usableKey == "" {
|
||||
usableKey = fallback
|
||||
}
|
||||
if usableKey == "" {
|
||||
return "", fiber.NewError(fiber.StatusInternalServerError, "FIFO v2 recording route misconfiguration")
|
||||
}
|
||||
|
||||
return usableKey, nil
|
||||
}
|
||||
|
||||
func (s *recordingService) resolveRecordingStockableKey(
|
||||
ctx context.Context,
|
||||
tx *gorm.DB,
|
||||
productWarehouseID uint,
|
||||
functionCode string,
|
||||
fallback fifo.StockableKey,
|
||||
actionLabel string,
|
||||
) (fifo.StockableKey, error) {
|
||||
if productWarehouseID == 0 {
|
||||
return "", fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product warehouse tidak valid untuk transaksi %s", actionLabel))
|
||||
}
|
||||
|
||||
route, err := commonSvc.ResolveFifoStockV2RouteByProductWarehouseIDAndLane(
|
||||
ctx,
|
||||
tx,
|
||||
productWarehouseID,
|
||||
functionCode,
|
||||
commonSvc.FifoStockV2LaneStockable,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve FIFO v2 recording route")
|
||||
}
|
||||
if route == nil {
|
||||
return "", fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Product warehouse %d tidak mendukung transaksi %s pada matrix FIFO v2", productWarehouseID, actionLabel),
|
||||
)
|
||||
}
|
||||
|
||||
stockableKey := fifo.StockableKey(strings.TrimSpace(route.LegacyTypeKey))
|
||||
if stockableKey == "" {
|
||||
stockableKey = fallback
|
||||
}
|
||||
if stockableKey == "" {
|
||||
return "", fiber.NewError(fiber.StatusInternalServerError, "FIFO v2 recording route misconfiguration")
|
||||
}
|
||||
|
||||
return stockableKey, nil
|
||||
}
|
||||
const depletionUsageTolerance = 0.000001
|
||||
|
||||
func (s *recordingService) logStockTrace(action string, stock entity.RecordingStock, extra string) {
|
||||
if s == nil || s.Log == nil {
|
||||
@@ -211,20 +125,8 @@ func (s *recordingService) consumeRecordingStocks(
|
||||
}
|
||||
desiredTotal := desired + pending
|
||||
|
||||
usableKey, err := s.resolveRecordingUsableKey(
|
||||
ctx,
|
||||
tx,
|
||||
stock.ProductWarehouseId,
|
||||
recordingFunctionCodeStockOut,
|
||||
recordingStockUsableKey,
|
||||
"Recording (Stock)",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
||||
UsableKey: usableKey,
|
||||
UsableKey: recordingStockUsableKey,
|
||||
UsableID: stock.Id,
|
||||
ProductWarehouseID: stock.ProductWarehouseId,
|
||||
Quantity: desiredTotal,
|
||||
@@ -307,21 +209,9 @@ func (s *recordingService) consumeRecordingDepletions(
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Source product warehouse tidak ditemukan untuk depletion")
|
||||
}
|
||||
|
||||
usableKey, err := s.resolveRecordingUsableKey(
|
||||
ctx,
|
||||
tx,
|
||||
sourceWarehouseID,
|
||||
recordingFunctionCodeDepletionOut,
|
||||
recordingDepletionUsableKey,
|
||||
"Recording Depletion (Source)",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
desired := depletion.Qty + depletion.PendingQty
|
||||
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
||||
UsableKey: usableKey,
|
||||
UsableKey: recordingDepletionUsableKey,
|
||||
UsableID: depletion.Id,
|
||||
ProductWarehouseID: sourceWarehouseID,
|
||||
Quantity: desired,
|
||||
@@ -424,20 +314,8 @@ func (s *recordingService) releaseRecordingStocks(
|
||||
if stock.Id == 0 {
|
||||
continue
|
||||
}
|
||||
usableKey, err := s.resolveRecordingUsableKey(
|
||||
ctx,
|
||||
tx,
|
||||
stock.ProductWarehouseId,
|
||||
recordingFunctionCodeStockOut,
|
||||
recordingStockUsableKey,
|
||||
"Recording (Stock)",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if stock.UsageQty != nil && *stock.UsageQty > 0 {
|
||||
activeCount, err := s.countActiveAllocations(ctx, tx, usableKey, stock.Id)
|
||||
activeCount, err := s.countActiveAllocations(ctx, tx, fifo.UsableKeyRecordingStock, stock.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -448,16 +326,16 @@ func (s *recordingService) releaseRecordingStocks(
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := s.resyncStockableUsageFromAllocations(ctx, tx, usableKey, stock.Id); err != nil {
|
||||
if err := s.resyncStockableUsageFromAllocations(ctx, tx, fifo.UsableKeyRecordingStock, stock.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.ensureActiveAllocations(ctx, tx, usableKey, stock.Id); err != nil {
|
||||
if err := s.ensureActiveAllocations(ctx, tx, fifo.UsableKeyRecordingStock, stock.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.logStockTrace("release:start", stock, "")
|
||||
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
||||
UsableKey: usableKey,
|
||||
UsableKey: recordingStockUsableKey,
|
||||
UsableID: stock.Id,
|
||||
Tx: tx,
|
||||
}); err != nil {
|
||||
@@ -522,28 +400,8 @@ func (s *recordingService) releaseRecordingDepletions(
|
||||
if depletion.Id == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
sourceWarehouseID := uint(0)
|
||||
if depletion.SourceProductWarehouseId != nil {
|
||||
sourceWarehouseID = *depletion.SourceProductWarehouseId
|
||||
}
|
||||
if sourceWarehouseID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Source product warehouse tidak ditemukan untuk depletion")
|
||||
}
|
||||
usableKey, err := s.resolveRecordingUsableKey(
|
||||
ctx,
|
||||
tx,
|
||||
sourceWarehouseID,
|
||||
recordingFunctionCodeDepletionOut,
|
||||
recordingDepletionUsableKey,
|
||||
"Recording Depletion (Source)",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if depletion.UsageQty > 0 {
|
||||
activeCount, err := s.countActiveAllocations(ctx, tx, usableKey, depletion.Id)
|
||||
activeCount, err := s.countActiveAllocations(ctx, tx, fifo.UsableKeyRecordingDepletion, depletion.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -560,10 +418,10 @@ func (s *recordingService) releaseRecordingDepletions(
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := s.resyncStockableUsageFromAllocations(ctx, tx, usableKey, depletion.Id); err != nil {
|
||||
if err := s.resyncStockableUsageFromAllocations(ctx, tx, fifo.UsableKeyRecordingDepletion, depletion.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.ensureActiveAllocations(ctx, tx, usableKey, depletion.Id); err != nil {
|
||||
if err := s.ensureActiveAllocations(ctx, tx, fifo.UsableKeyRecordingDepletion, depletion.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -573,8 +431,15 @@ func (s *recordingService) releaseRecordingDepletions(
|
||||
return err
|
||||
}
|
||||
|
||||
sourceWarehouseID := uint(0)
|
||||
if depletion.SourceProductWarehouseId != nil {
|
||||
sourceWarehouseID = *depletion.SourceProductWarehouseId
|
||||
}
|
||||
if sourceWarehouseID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Source product warehouse tidak ditemukan untuk depletion")
|
||||
}
|
||||
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
||||
UsableKey: usableKey,
|
||||
UsableKey: recordingDepletionUsableKey,
|
||||
UsableID: depletion.Id,
|
||||
Tx: tx,
|
||||
}); err != nil {
|
||||
@@ -765,20 +630,9 @@ func (s *recordingService) replenishRecordingEggs(
|
||||
if egg.Id == 0 || egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
|
||||
continue
|
||||
}
|
||||
stockableKey, err := s.resolveRecordingStockableKey(
|
||||
ctx,
|
||||
tx,
|
||||
egg.ProductWarehouseId,
|
||||
recordingFunctionCodeRecordingEggIn,
|
||||
fifo.StockableKeyRecordingEgg,
|
||||
"Recording Egg",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.logEggTrace("replenish:start", egg, "")
|
||||
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
||||
StockableKey: stockableKey,
|
||||
StockableKey: fifo.StockableKeyRecordingEgg,
|
||||
StockableID: egg.Id,
|
||||
ProductWarehouseID: egg.ProductWarehouseId,
|
||||
Quantity: float64(egg.Qty),
|
||||
@@ -836,20 +690,9 @@ func (s *recordingService) replenishRecordingDepletions(
|
||||
if depletion.Id == 0 || depletion.ProductWarehouseId == 0 || depletion.Qty <= 0 {
|
||||
continue
|
||||
}
|
||||
stockableKey, err := s.resolveRecordingStockableKey(
|
||||
ctx,
|
||||
tx,
|
||||
depletion.ProductWarehouseId,
|
||||
recordingFunctionCodeDepletionIn,
|
||||
fifo.StockableKeyRecordingDepletion,
|
||||
"Recording Depletion (Destination)",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.logDepletionTrace("replenish:start", depletion, "")
|
||||
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
||||
StockableKey: stockableKey,
|
||||
StockableKey: fifo.StockableKeyRecordingDepletion,
|
||||
StockableID: depletion.Id,
|
||||
ProductWarehouseID: depletion.ProductWarehouseId,
|
||||
Quantity: depletion.Qty,
|
||||
@@ -881,20 +724,9 @@ func (s *recordingService) reduceRecordingDepletions(
|
||||
if depletion.Id == 0 || depletion.ProductWarehouseId == 0 || depletion.Qty <= 0 {
|
||||
continue
|
||||
}
|
||||
stockableKey, err := s.resolveRecordingStockableKey(
|
||||
ctx,
|
||||
tx,
|
||||
depletion.ProductWarehouseId,
|
||||
recordingFunctionCodeDepletionIn,
|
||||
fifo.StockableKeyRecordingDepletion,
|
||||
"Recording Depletion (Destination)",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.logDepletionTrace("reduce:start", depletion, "")
|
||||
if err := s.FifoSvc.AdjustStockableQuantity(ctx, commonSvc.StockAdjustRequest{
|
||||
StockableKey: stockableKey,
|
||||
StockableKey: fifo.StockableKeyRecordingDepletion,
|
||||
StockableID: depletion.Id,
|
||||
ProductWarehouseID: depletion.ProductWarehouseId,
|
||||
Quantity: -depletion.Qty,
|
||||
@@ -926,20 +758,9 @@ func (s *recordingService) reduceRecordingEggs(
|
||||
if egg.Id == 0 || egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
|
||||
continue
|
||||
}
|
||||
stockableKey, err := s.resolveRecordingStockableKey(
|
||||
ctx,
|
||||
tx,
|
||||
egg.ProductWarehouseId,
|
||||
recordingFunctionCodeRecordingEggIn,
|
||||
fifo.StockableKeyRecordingEgg,
|
||||
"Recording Egg",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.logEggTrace("reduce:start", egg, "")
|
||||
if err := s.FifoSvc.AdjustStockableQuantity(ctx, commonSvc.StockAdjustRequest{
|
||||
StockableKey: stockableKey,
|
||||
StockableKey: fifo.StockableKeyRecordingEgg,
|
||||
StockableID: egg.Id,
|
||||
ProductWarehouseID: egg.ProductWarehouseId,
|
||||
Quantity: -float64(egg.Qty),
|
||||
|
||||
@@ -43,8 +43,7 @@ type PurchaseService interface {
|
||||
}
|
||||
|
||||
const (
|
||||
priceTolerance = 0.0001
|
||||
purchaseFunctionCodeIn = "PURCHASE_IN"
|
||||
priceTolerance = 0.0001
|
||||
)
|
||||
|
||||
type purchaseService struct {
|
||||
@@ -406,14 +405,6 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase
|
||||
indexMap[key] = len(aggregated) - 1
|
||||
}
|
||||
|
||||
routeValidationProducts := make([]uint, 0, len(aggregated))
|
||||
for _, item := range aggregated {
|
||||
routeValidationProducts = append(routeValidationProducts, item.productId)
|
||||
}
|
||||
if err := s.ensurePurchaseRouteSupport(c.Context(), s.PurchaseRepo.DB(), routeValidationProducts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dueDate *time.Time
|
||||
now := time.Now().UTC()
|
||||
d := now.AddDate(0, 0, req.CreditTerm)
|
||||
@@ -996,17 +987,6 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
return nil, utils.BadRequest("Receiving data must be provided for all purchase items")
|
||||
}
|
||||
|
||||
receivingProductIDs := make([]uint, 0, len(prepared))
|
||||
for _, prep := range prepared {
|
||||
if prep.item == nil || prep.item.ProductId == 0 {
|
||||
continue
|
||||
}
|
||||
receivingProductIDs = append(receivingProductIDs, prep.item.ProductId)
|
||||
}
|
||||
if err := s.ensurePurchaseRouteSupport(c.Context(), s.PurchaseRepo.DB(), receivingProductIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
receivingAction := action
|
||||
completedAction := entity.ApprovalActionApproved
|
||||
approvalSvc := commonSvc.NewApprovalService(
|
||||
@@ -1854,15 +1834,6 @@ func (s *purchaseService) buildStaffAdjustmentPayload(
|
||||
productSupplierCache := make(map[uint]bool)
|
||||
newItems := make([]*entity.PurchaseItem, 0, len(newPayloads))
|
||||
emptyVehicle := ""
|
||||
newProductIDs := make([]uint, 0, len(newPayloads))
|
||||
for _, payload := range newPayloads {
|
||||
if payload.ProductID > 0 {
|
||||
newProductIDs = append(newProductIDs, payload.ProductID)
|
||||
}
|
||||
}
|
||||
if err := s.ensurePurchaseRouteSupport(ctx, s.PurchaseRepo.DB(), newProductIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, payload := range newPayloads {
|
||||
if payload.ProductID == 0 || payload.WarehouseID == 0 {
|
||||
@@ -1947,48 +1918,6 @@ func calculateTotalPrice(quantity float64, price float64, provided *float64, ref
|
||||
return *provided, nil
|
||||
}
|
||||
|
||||
func (s *purchaseService) ensurePurchaseRouteSupport(ctx context.Context, db *gorm.DB, productIDs []uint) error {
|
||||
if len(productIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
seen := make(map[uint]struct{}, len(productIDs))
|
||||
for _, productID := range productIDs {
|
||||
if productID == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[productID]; ok {
|
||||
continue
|
||||
}
|
||||
seen[productID] = struct{}{}
|
||||
|
||||
route, err := commonSvc.ResolveFifoStockV2RouteByProductIDAndLane(
|
||||
ctx,
|
||||
db,
|
||||
productID,
|
||||
purchaseFunctionCodeIn,
|
||||
commonSvc.FifoStockV2LaneStockable,
|
||||
)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to resolve FIFO v2 PURCHASE_IN route for product %d: %+v", productID, err)
|
||||
return utils.Internal("Failed to resolve FIFO v2 purchase route")
|
||||
}
|
||||
if route == nil {
|
||||
return utils.BadRequest(fmt.Sprintf("Product %d tidak mendukung transaksi Purchase pada matrix FIFO v2", productID))
|
||||
}
|
||||
if !strings.EqualFold(strings.TrimSpace(route.SourceTable), "purchase_items") {
|
||||
s.Log.Errorf(
|
||||
"Invalid FIFO v2 PURCHASE_IN route source table for product %d: expected purchase_items got %s",
|
||||
productID,
|
||||
route.SourceTable,
|
||||
)
|
||||
return utils.Internal("FIFO v2 purchase route misconfiguration")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *purchaseService) attachLatestApproval(ctx context.Context, item *entity.Purchase) error {
|
||||
if item == nil || item.Id == 0 || s.ApprovalSvc == nil {
|
||||
return nil
|
||||
|
||||
@@ -92,7 +92,7 @@ func ToMarketingReportItems(mdps []entity.MarketingDeliveryProduct, hppMap map[u
|
||||
for _, flag := range mdp.MarketingProduct.ProductWarehouse.Product.Flags {
|
||||
ft := utils.FlagType(flag.Name)
|
||||
|
||||
if ft == utils.FlagAyam || ft == utils.FlagAyamAfkir || ft == utils.FlagAyamCulling || ft == utils.FlagAyamMati ||
|
||||
if ft == utils.FlagAyamAfkir || ft == utils.FlagAyamCulling || ft == utils.FlagAyamMati ||
|
||||
ft == utils.FlagDOC || ft == utils.FlagPullet || ft == utils.FlagLayer {
|
||||
hasAyam = true
|
||||
}
|
||||
|
||||
+9
-185
@@ -14,18 +14,9 @@ type FlagType string
|
||||
|
||||
type FlagGroup string
|
||||
|
||||
type ProductFlagOption struct {
|
||||
Flag FlagType `json:"flag"`
|
||||
SubFlags []FlagType `json:"sub_flags"`
|
||||
AllowWithoutSubFlag bool `json:"allow_without_sub_flag"`
|
||||
}
|
||||
|
||||
const (
|
||||
FlagIsActive FlagType = "IS_ACTIVE"
|
||||
|
||||
FlagAyam FlagType = "AYAM"
|
||||
|
||||
// Legacy AYAM flags kept for backward compatibility with existing production data.
|
||||
FlagDOC FlagType = "DOC"
|
||||
FlagPullet FlagType = "PULLET"
|
||||
FlagLayer FlagType = "LAYER"
|
||||
@@ -45,13 +36,11 @@ const (
|
||||
FlagAyamMati FlagType = "AYAM-MATI"
|
||||
|
||||
//flag telur
|
||||
FlagTelur FlagType = "TELUR"
|
||||
FlagTelurUtuh FlagType = "TELUR-UTUH"
|
||||
FlagTelurPecah FlagType = "TELUR-PECAH"
|
||||
FlagTelurPutih FlagType = "TELUR-PUTIH"
|
||||
FlagTelurRetak FlagType = "TELUR-RETAK"
|
||||
FlagTelurPapacal FlagType = "TELUR-PAPACAL"
|
||||
FlagTelurJumbo FlagType = "TELUR-JUMBO"
|
||||
FlagTelur FlagType = "TELUR"
|
||||
FlagTelurUtuh FlagType = "TELUR-UTUH"
|
||||
FlagTelurPecah FlagType = "TELUR-PECAH"
|
||||
FlagTelurPutih FlagType = "TELUR-PUTIH"
|
||||
FlagTelurRetak FlagType = "TELUR-RETAK"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -61,10 +50,9 @@ const (
|
||||
|
||||
var flagGroupOptions = map[FlagGroup][]FlagType{
|
||||
FlagGroupProduct: {
|
||||
FlagAyam,
|
||||
FlagAyamAfkir,
|
||||
FlagAyamCulling,
|
||||
FlagAyamMati,
|
||||
FlagDOC,
|
||||
FlagPullet,
|
||||
FlagLayer,
|
||||
FlagPakan,
|
||||
FlagPreStarter,
|
||||
FlagStarter,
|
||||
@@ -73,82 +61,12 @@ var flagGroupOptions = map[FlagGroup][]FlagType{
|
||||
FlagObat,
|
||||
FlagVitamin,
|
||||
FlagKimia,
|
||||
FlagTelur,
|
||||
FlagTelurUtuh,
|
||||
FlagTelurPutih,
|
||||
FlagTelurRetak,
|
||||
FlagTelurPecah,
|
||||
FlagTelurPapacal,
|
||||
FlagTelurJumbo,
|
||||
},
|
||||
FlagGroupNonstock: {
|
||||
FlagEkspedisi,
|
||||
},
|
||||
}
|
||||
|
||||
var legacyFlagTypeAliases = map[FlagType]FlagType{
|
||||
FlagDOC: FlagAyam,
|
||||
FlagPullet: FlagAyam,
|
||||
FlagLayer: FlagAyam,
|
||||
}
|
||||
|
||||
var productMainFlags = []FlagType{
|
||||
FlagAyam,
|
||||
FlagPakan,
|
||||
FlagOVK,
|
||||
FlagTelur,
|
||||
}
|
||||
|
||||
var productSubFlagsByFlag = map[FlagType][]FlagType{
|
||||
FlagAyam: {
|
||||
FlagAyamAfkir,
|
||||
FlagAyamCulling,
|
||||
FlagAyamMati,
|
||||
},
|
||||
FlagPakan: {
|
||||
FlagPreStarter,
|
||||
FlagStarter,
|
||||
FlagFinisher,
|
||||
},
|
||||
FlagOVK: {
|
||||
FlagObat,
|
||||
FlagVitamin,
|
||||
FlagKimia,
|
||||
},
|
||||
FlagTelur: {
|
||||
FlagTelurUtuh,
|
||||
FlagTelurPutih,
|
||||
FlagTelurRetak,
|
||||
FlagTelurPecah,
|
||||
FlagTelurPapacal,
|
||||
FlagTelurJumbo,
|
||||
},
|
||||
}
|
||||
|
||||
var productSubFlagToFlag = func() map[FlagType]FlagType {
|
||||
out := make(map[FlagType]FlagType)
|
||||
for flag, subFlags := range productSubFlagsByFlag {
|
||||
for _, subFlag := range subFlags {
|
||||
out[subFlag] = flag
|
||||
}
|
||||
}
|
||||
return out
|
||||
}()
|
||||
|
||||
var productAllowWithoutSubFlagByFlag = map[FlagType]bool{
|
||||
FlagAyam: true,
|
||||
FlagPakan: false,
|
||||
FlagOVK: false,
|
||||
FlagTelur: false,
|
||||
}
|
||||
|
||||
func canonicalizeFlagType(flag FlagType) FlagType {
|
||||
if canonical, ok := legacyFlagTypeAliases[flag]; ok {
|
||||
return canonical
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
||||
var allFlagTypes = func() map[FlagType]struct{} {
|
||||
m := map[FlagType]struct{}{
|
||||
FlagIsActive: {},
|
||||
@@ -165,95 +83,6 @@ func AllFlagTypes() map[FlagType]struct{} {
|
||||
return allFlagTypes
|
||||
}
|
||||
|
||||
func CanonicalFlagType(v string) FlagType {
|
||||
normalized := FlagType(strings.ToUpper(strings.TrimSpace(v)))
|
||||
if normalized == "" {
|
||||
return ""
|
||||
}
|
||||
return canonicalizeFlagType(normalized)
|
||||
}
|
||||
|
||||
func LegacyFlagTypeAliases() map[FlagType]FlagType {
|
||||
out := make(map[FlagType]FlagType, len(legacyFlagTypeAliases))
|
||||
for legacy, canonical := range legacyFlagTypeAliases {
|
||||
out[legacy] = canonical
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func ProductMainFlags() []FlagType {
|
||||
out := make([]FlagType, len(productMainFlags))
|
||||
copy(out, productMainFlags)
|
||||
return out
|
||||
}
|
||||
|
||||
func ProductSubFlagsByFlag() map[FlagType][]FlagType {
|
||||
out := make(map[FlagType][]FlagType, len(productSubFlagsByFlag))
|
||||
for flag, subFlags := range productSubFlagsByFlag {
|
||||
dup := make([]FlagType, len(subFlags))
|
||||
copy(dup, subFlags)
|
||||
out[flag] = dup
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func ProductSubFlagToFlag() map[FlagType]FlagType {
|
||||
out := make(map[FlagType]FlagType, len(productSubFlagToFlag))
|
||||
for subFlag, flag := range productSubFlagToFlag {
|
||||
out[subFlag] = flag
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func ProductFlagOptions() []ProductFlagOption {
|
||||
result := make([]ProductFlagOption, 0, len(productMainFlags))
|
||||
for _, flag := range productMainFlags {
|
||||
subFlags := productSubFlagsByFlag[flag]
|
||||
dup := make([]FlagType, len(subFlags))
|
||||
copy(dup, subFlags)
|
||||
result = append(result, ProductFlagOption{
|
||||
Flag: flag,
|
||||
SubFlags: dup,
|
||||
AllowWithoutSubFlag: productAllowWithoutSubFlagByFlag[flag],
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ProductFlagAllowWithoutSubFlag(flag FlagType) bool {
|
||||
canonical := canonicalizeFlagType(flag)
|
||||
allow, ok := productAllowWithoutSubFlagByFlag[canonical]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return allow
|
||||
}
|
||||
|
||||
func IsProductMainFlag(flag FlagType) bool {
|
||||
canonical := canonicalizeFlagType(flag)
|
||||
for _, f := range productMainFlags {
|
||||
if f == canonical {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsValidProductSubFlag(flag FlagType, subFlag FlagType) bool {
|
||||
canonicalFlag := canonicalizeFlagType(flag)
|
||||
canonicalSubFlag := canonicalizeFlagType(subFlag)
|
||||
allowedSubFlags, ok := productSubFlagsByFlag[canonicalFlag]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
for _, allowed := range allowedSubFlags {
|
||||
if allowed == canonicalSubFlag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// WarehouseType
|
||||
// -------------------------------------------------------------------
|
||||
@@ -792,11 +621,7 @@ const (
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func IsValidFlagType(v string) bool {
|
||||
flag := FlagType(strings.ToUpper(strings.TrimSpace(v)))
|
||||
if _, ok := allFlagTypes[flag]; ok {
|
||||
return true
|
||||
}
|
||||
_, ok := legacyFlagTypeAliases[flag]
|
||||
_, ok := allFlagTypes[FlagType(strings.ToUpper(strings.TrimSpace(v)))]
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -842,7 +667,6 @@ func NormalizeFlagTypes(flags []string) []FlagType {
|
||||
if normalized == "" {
|
||||
continue
|
||||
}
|
||||
normalized = canonicalizeFlagType(normalized)
|
||||
if _, exists := seen[normalized]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user