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
|
type FifoStockV2Lane = fifoStockV2.Lane
|
||||||
|
|
||||||
const (
|
|
||||||
FifoStockV2LaneStockable FifoStockV2Lane = fifoStockV2.LaneStockable
|
|
||||||
FifoStockV2LaneUsable FifoStockV2Lane = fifoStockV2.LaneUsable
|
|
||||||
)
|
|
||||||
|
|
||||||
type FifoStockV2Ref = fifoStockV2.Ref
|
type FifoStockV2Ref = fifoStockV2.Ref
|
||||||
|
|
||||||
type FifoStockV2GatherRequest = fifoStockV2.GatherRequest
|
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)
|
INSERT INTO fifo_stock_v2_flag_members(flag_name, flag_group_code, priority)
|
||||||
VALUES
|
VALUES
|
||||||
('AYAM', 'AYAM', 5),
|
|
||||||
('DOC', 'AYAM', 10),
|
('DOC', 'AYAM', 10),
|
||||||
('PULLET', 'AYAM', 20),
|
('PULLET', 'AYAM', 20),
|
||||||
('LAYER', 'AYAM', 30),
|
('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",
|
Category: "Day Old Chick",
|
||||||
Price: 7500,
|
Price: 7500,
|
||||||
Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"},
|
Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"},
|
||||||
Flags: []utils.FlagType{utils.FlagAyam},
|
Flags: []utils.FlagType{utils.FlagDOC, utils.FlagPullet, utils.FlagLayer},
|
||||||
IsVisible: true,
|
IsVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -58,17 +58,17 @@ type SapronakReportDTO struct {
|
|||||||
|
|
||||||
// Simplified view for project-level sapronak response
|
// Simplified view for project-level sapronak response
|
||||||
type SapronakCategoryRowDTO struct {
|
type SapronakCategoryRowDTO struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Date string `json:"date"`
|
Date string `json:"date"`
|
||||||
ReferenceNumber string `json:"reference_number"`
|
ReferenceNumber string `json:"reference_number"`
|
||||||
QtyIn float64 `json:"qty_in"`
|
QtyIn float64 `json:"qty_in"`
|
||||||
QtyOut float64 `json:"qty_out"`
|
QtyOut float64 `json:"qty_out"`
|
||||||
QtyUsed float64 `json:"qty_used"`
|
QtyUsed float64 `json:"qty_used"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
ProductCategory []string `json:"product_category"`
|
ProductCategory []string `json:"product_category"`
|
||||||
UnitPrice float64 `json:"unit_price"`
|
UnitPrice float64 `json:"unit_price"`
|
||||||
TotalAmount float64 `json:"total_amount"`
|
TotalAmount float64 `json:"total_amount"`
|
||||||
Notes string `json:"notes"`
|
Notes string `json:"notes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SapronakCategoryTotalDTO struct {
|
type SapronakCategoryTotalDTO struct {
|
||||||
@@ -148,7 +148,7 @@ func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag strin
|
|||||||
|
|
||||||
normalizeFlag := func(raw string) string {
|
normalizeFlag := func(raw string) string {
|
||||||
normalized := strings.ToUpper(strings.TrimSpace(raw))
|
normalized := strings.ToUpper(strings.TrimSpace(raw))
|
||||||
if normalized == "AYAM" || normalized == "PULLET" {
|
if normalized == "PULLET" {
|
||||||
return "DOC"
|
return "DOC"
|
||||||
}
|
}
|
||||||
return normalized
|
return normalized
|
||||||
@@ -177,7 +177,6 @@ func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
flagOrder := map[string]int{
|
flagOrder := map[string]int{
|
||||||
"AYAM": 0,
|
|
||||||
"DOC": 0,
|
"DOC": 0,
|
||||||
"PAKAN": 0,
|
"PAKAN": 0,
|
||||||
"OVK": 0,
|
"OVK": 0,
|
||||||
|
|||||||
@@ -446,7 +446,7 @@ SELECT
|
|||||||
f.name,
|
f.name,
|
||||||
' ' ORDER BY
|
' ' ORDER BY
|
||||||
CASE
|
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
|
ELSE 1
|
||||||
END,
|
END,
|
||||||
f.name
|
f.name
|
||||||
@@ -459,7 +459,7 @@ SELECT
|
|||||||
f.name,
|
f.name,
|
||||||
' ' ORDER BY
|
' ' ORDER BY
|
||||||
CASE
|
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
|
ELSE 1
|
||||||
END,
|
END,
|
||||||
f.name
|
f.name
|
||||||
@@ -495,7 +495,7 @@ SELECT
|
|||||||
f.name,
|
f.name,
|
||||||
' ' ORDER BY
|
' ' ORDER BY
|
||||||
CASE
|
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
|
ELSE 1
|
||||||
END,
|
END,
|
||||||
f.name
|
f.name
|
||||||
@@ -508,7 +508,7 @@ SELECT
|
|||||||
f.name,
|
f.name,
|
||||||
' ' ORDER BY
|
' ' ORDER BY
|
||||||
CASE
|
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
|
ELSE 1
|
||||||
END,
|
END,
|
||||||
f.name
|
f.name
|
||||||
@@ -545,7 +545,7 @@ SELECT
|
|||||||
f.name,
|
f.name,
|
||||||
' ' ORDER BY
|
' ' ORDER BY
|
||||||
CASE
|
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
|
ELSE 1
|
||||||
END,
|
END,
|
||||||
f.name
|
f.name
|
||||||
@@ -558,7 +558,7 @@ SELECT
|
|||||||
f.name,
|
f.name,
|
||||||
' ' ORDER BY
|
' ' ORDER BY
|
||||||
CASE
|
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
|
ELSE 1
|
||||||
END,
|
END,
|
||||||
f.name
|
f.name
|
||||||
@@ -595,7 +595,7 @@ SELECT
|
|||||||
f.name,
|
f.name,
|
||||||
' ' ORDER BY
|
' ' ORDER BY
|
||||||
CASE
|
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
|
ELSE 1
|
||||||
END,
|
END,
|
||||||
f.name
|
f.name
|
||||||
@@ -608,7 +608,7 @@ SELECT
|
|||||||
f.name,
|
f.name,
|
||||||
' ' ORDER BY
|
' ' ORDER BY
|
||||||
CASE
|
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
|
ELSE 1
|
||||||
END,
|
END,
|
||||||
f.name
|
f.name
|
||||||
@@ -645,7 +645,7 @@ SELECT
|
|||||||
f.name,
|
f.name,
|
||||||
' ' ORDER BY
|
' ' ORDER BY
|
||||||
CASE
|
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
|
ELSE 1
|
||||||
END,
|
END,
|
||||||
f.name
|
f.name
|
||||||
@@ -658,7 +658,7 @@ SELECT
|
|||||||
f.name,
|
f.name,
|
||||||
' ' ORDER BY
|
' ' ORDER BY
|
||||||
CASE
|
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
|
ELSE 1
|
||||||
END,
|
END,
|
||||||
f.name
|
f.name
|
||||||
@@ -685,7 +685,7 @@ WHERE pw.warehouse_id IN ?
|
|||||||
FROM flags f
|
FROM flags f
|
||||||
WHERE f.flagable_id = pw.product_id
|
WHERE f.flagable_id = pw.product_id
|
||||||
AND f.flagable_type = 'products'
|
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,
|
f.name,
|
||||||
' ' ORDER BY
|
' ' ORDER BY
|
||||||
CASE
|
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
|
ELSE 1
|
||||||
END,
|
END,
|
||||||
f.name
|
f.name
|
||||||
@@ -715,7 +715,7 @@ SELECT
|
|||||||
f.name,
|
f.name,
|
||||||
' ' ORDER BY
|
' ' ORDER BY
|
||||||
CASE
|
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
|
ELSE 1
|
||||||
END,
|
END,
|
||||||
f.name
|
f.name
|
||||||
@@ -743,7 +743,7 @@ WHERE pw.project_flock_kandang_id IN ?
|
|||||||
FROM flags f
|
FROM flags f
|
||||||
WHERE f.flagable_id = pw.product_id
|
WHERE f.flagable_id = pw.product_id
|
||||||
AND f.flagable_type = 'products'
|
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 (
|
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)
|
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 {
|
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("flagable_type = ?", entity.FlagableTypeProduct).
|
||||||
Where("name IN ?", sapronakFlagsAll).
|
Where("name IN ?", sapronakFlagsAll).
|
||||||
Order(fmt.Sprintf(
|
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",
|
"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.FlagAyam,
|
|
||||||
utils.FlagDOC,
|
utils.FlagDOC,
|
||||||
utils.FlagPullet,
|
utils.FlagPullet,
|
||||||
utils.FlagPakan,
|
utils.FlagPakan,
|
||||||
@@ -1239,7 +1238,7 @@ func (r *ClosingRepositoryImpl) FetchSapronakAdjustments(ctx context.Context, ka
|
|||||||
Where("sa.status = ?", entity.StockAllocationStatusActive).
|
Where("sa.status = ?", entity.StockAllocationStatusActive).
|
||||||
Where("w.kandang_id = ?", kandangID).
|
Where("w.kandang_id = ?", kandangID).
|
||||||
Where("f.name IN ?", sapronakFlagsAll).
|
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")
|
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")
|
outgoingQuery = r.joinSapronakProductFlag(outgoingQuery, "p")
|
||||||
outgoing, err := scanAndGroupDetails(outgoingQuery)
|
outgoing, err := scanAndGroupDetails(outgoingQuery)
|
||||||
|
|||||||
@@ -856,7 +856,7 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint
|
|||||||
// FeedUsedPerHead: feedUsedPerHead,
|
// 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)
|
chickenSalesWeight, chickenSalesQty, chickenSalesPrice, err := s.Repository.SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(c.Context(), projectFlockKandangIDs, chickenFlagNames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to fetch chicken sales data for project flock %d: %+v", projectFlockID, err)
|
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
|
chickenDepletion = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
chickenPerformance := calculatePerformanceMetrics(chickenAverageWeight, chickenSalesWeight, feedUsed, population, chickenDepletion, age)
|
chickenPerformance := calculatePerformanceMetrics(chickenAverageWeight, chickenSalesWeight, feedUsed, population, chickenDepletion, age)
|
||||||
if fcrActFromRecording != nil {
|
if fcrActFromRecording != nil {
|
||||||
chickenPerformance.FcrAct = *fcrActFromRecording
|
chickenPerformance.FcrAct = *fcrActFromRecording
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,8 +432,8 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
candidate := strings.ToUpper(f)
|
candidate := strings.ToUpper(f)
|
||||||
if filterFlag == "AYAM" || filterFlag == "DOC" || filterFlag == "PULLET" {
|
if filterFlag == "DOC" || filterFlag == "PULLET" {
|
||||||
return candidate == "AYAM" || candidate == "DOC" || candidate == "PULLET"
|
return candidate == "DOC" || candidate == "PULLET"
|
||||||
}
|
}
|
||||||
return candidate == filterFlag
|
return candidate == filterFlag
|
||||||
}
|
}
|
||||||
@@ -474,8 +474,7 @@ func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.Proj
|
|||||||
if !isLaying {
|
if !isLaying {
|
||||||
filteredUsage := make([]repository.SapronakUsageRow, 0, len(chickinUsageRows))
|
filteredUsage := make([]repository.SapronakUsageRow, 0, len(chickinUsageRows))
|
||||||
for _, row := range chickinUsageRows {
|
for _, row := range chickinUsageRows {
|
||||||
flag := strings.ToUpper(row.Flag)
|
if strings.ToUpper(row.Flag) == "DOC" {
|
||||||
if flag == "AYAM" || flag == "DOC" {
|
|
||||||
filteredUsage = append(filteredUsage, row)
|
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))
|
filteredDetail := make(map[uint][]repository.SapronakDetailRow, len(chickinUsageDetailsRows))
|
||||||
for pid, rows := range chickinUsageDetailsRows {
|
for pid, rows := range chickinUsageDetailsRows {
|
||||||
for _, d := range rows {
|
for _, d := range rows {
|
||||||
flag := strings.ToUpper(d.Flag)
|
if strings.ToUpper(d.Flag) == "DOC" {
|
||||||
if flag == "AYAM" || flag == "DOC" {
|
|
||||||
filteredDetail[pid] = append(filteredDetail[pid], d)
|
filteredDetail[pid] = append(filteredDetail[pid], d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ type CountSapronakQuery struct {
|
|||||||
KandangID uint `query:"kandang_id" validate:"omitempty,gt=0"`
|
KandangID uint `query:"kandang_id" validate:"omitempty,gt=0"`
|
||||||
ProjectFlockKandangID uint `query:"project_flock_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"`
|
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) {
|
func (r *ConstantRepositoryImpl) GetConstants() (map[string]interface{}, error) {
|
||||||
flagSet := make(map[string]struct{})
|
flagList := make([]string, 0)
|
||||||
for _, f := range utils.AllowedFlagTypes(utils.FlagGroupProduct) {
|
for f := range utils.AllFlagTypes() {
|
||||||
flagSet[string(f)] = struct{}{}
|
flagList = append(flagList, string(f))
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
sort.Strings(flagList)
|
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 {
|
type approvalStepConstant struct {
|
||||||
StepNumber uint16 `json:"step_number"`
|
StepNumber uint16 `json:"step_number"`
|
||||||
StepName string `json:"step_name"`
|
StepName string `json:"step_name"`
|
||||||
@@ -115,13 +78,7 @@ func (r *ConstantRepositoryImpl) GetConstants() (map[string]interface{}, error)
|
|||||||
adjustmentSubtypesByType := utils.AdjustmentTransactionSubtypesByTypeForFrontend()
|
adjustmentSubtypesByType := utils.AdjustmentTransactionSubtypesByTypeForFrontend()
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"flags": flagList,
|
"flags": flagList,
|
||||||
"legacy_flag_aliases": legacyFlagAliases,
|
|
||||||
"product_flag_mapping": map[string]interface{}{
|
|
||||||
"flags": productMainFlags,
|
|
||||||
"options": productFlagOptions,
|
|
||||||
"sub_flag_to_flag": productSubFlagToFlag,
|
|
||||||
},
|
|
||||||
"warehouse_types": []string{
|
"warehouse_types": []string{
|
||||||
"AREA",
|
"AREA",
|
||||||
"LOKASI",
|
"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 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 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").
|
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 IS NOT NULL").
|
||||||
Where("pi.received_date >= ? AND pi.received_date < ?", start, end)
|
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.FlagTelurPecah) ||
|
||||||
flag == string(utils.FlagTelurPutih) ||
|
flag == string(utils.FlagTelurPutih) ||
|
||||||
flag == string(utils.FlagTelurRetak) ||
|
flag == string(utils.FlagTelurRetak) ||
|
||||||
flag == string(utils.FlagAyam) ||
|
|
||||||
flag == string(utils.FlagAyamAfkir) ||
|
flag == string(utils.FlagAyamAfkir) ||
|
||||||
flag == string(utils.FlagAyamCulling) ||
|
flag == string(utils.FlagAyamCulling) ||
|
||||||
flag == string(utils.FlagAyamMati) {
|
flag == string(utils.FlagAyamMati) {
|
||||||
|
|||||||
@@ -144,7 +144,6 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
|
|||||||
for _, t := range marketingTypes {
|
for _, t := range marketingTypes {
|
||||||
switch t {
|
switch t {
|
||||||
case string(utils.MarketingTypeAyamPullet):
|
case string(utils.MarketingTypeAyamPullet):
|
||||||
flagSet[string(utils.FlagAyam)] = struct{}{}
|
|
||||||
flagSet[string(utils.FlagDOC)] = struct{}{}
|
flagSet[string(utils.FlagDOC)] = struct{}{}
|
||||||
flagSet[string(utils.FlagPullet)] = struct{}{}
|
flagSet[string(utils.FlagPullet)] = struct{}{}
|
||||||
flagSet[string(utils.FlagLayer)] = struct{}{}
|
flagSet[string(utils.FlagLayer)] = struct{}{}
|
||||||
|
|||||||
@@ -50,16 +50,6 @@ type transferService struct {
|
|||||||
ExpenseBridge TransferExpenseBridge
|
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 {
|
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{
|
return &transferService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
@@ -454,9 +444,9 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
routePairsByProductID := map[uint]transferRoutePair{}
|
pakanProducts := map[uint]bool{}
|
||||||
if len(req.Products) > 0 {
|
if s.FifoStockV2Svc != nil && len(req.Products) > 0 {
|
||||||
routePairsByProductID, err = s.resolveTransferRoutes(c.Context(), tx, req.Products)
|
pakanProducts, err = s.resolvePakanProducts(c.Context(), tx, req.Products)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -464,17 +454,10 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
|
|
||||||
for _, product := range req.Products {
|
for _, product := range req.Products {
|
||||||
detail := detailMap[uint64(product.ProductID)]
|
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
|
outUsageQty := 0.0
|
||||||
outPendingQty := 0.0
|
outPendingQty := 0.0
|
||||||
useFifoV2 := s.FifoStockV2Svc != nil
|
useFifoV2 := s.FifoStockV2Svc != nil && pakanProducts[uint(product.ProductID)]
|
||||||
if useFifoV2 {
|
if useFifoV2 {
|
||||||
s.Log.Infof(
|
s.Log.Infof(
|
||||||
"[fifo-v2][transfer] use reflow movement=%s detail_id=%d product_id=%d source_pw=%d qty=%.3f",
|
"[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,
|
product.ProductQty,
|
||||||
)
|
)
|
||||||
reflowResult, err := s.FifoStockV2Svc.Reflow(c.Context(), commonSvc.FifoStockV2ReflowRequest{
|
reflowResult, err := s.FifoStockV2Svc.Reflow(c.Context(), commonSvc.FifoStockV2ReflowRequest{
|
||||||
FlagGroupCode: routePair.Usable.FlagGroupCode,
|
FlagGroupCode: "PAKAN",
|
||||||
ProductWarehouseID: uint(*detail.SourceProductWarehouseID),
|
ProductWarehouseID: uint(*detail.SourceProductWarehouseID),
|
||||||
Usable: commonSvc.FifoStockV2Ref{
|
Usable: commonSvc.FifoStockV2Ref{
|
||||||
ID: uint(detail.Id),
|
ID: uint(detail.Id),
|
||||||
LegacyTypeKey: routePair.Usable.LegacyTypeKey,
|
LegacyTypeKey: fifo.UsableKeyStockTransferOut.String(),
|
||||||
FunctionCode: routePair.Usable.FunctionCode,
|
FunctionCode: "STOCK_TRANSFER_OUT",
|
||||||
},
|
},
|
||||||
DesiredQty: product.ProductQty,
|
DesiredQty: product.ProductQty,
|
||||||
Tx: tx,
|
Tx: tx,
|
||||||
@@ -508,12 +491,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
outPendingQty,
|
outPendingQty,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
usableKey := fifo.UsableKey(strings.TrimSpace(routePair.Usable.LegacyTypeKey))
|
|
||||||
if usableKey == "" {
|
|
||||||
usableKey = fifo.UsableKeyStockTransferOut
|
|
||||||
}
|
|
||||||
consumeResult, err := s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
|
consumeResult, err := s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
|
||||||
UsableKey: usableKey,
|
UsableKey: fifo.UsableKeyStockTransferOut,
|
||||||
UsableID: uint(detail.Id),
|
UsableID: uint(detail.Id),
|
||||||
ProductWarehouseID: uint(*detail.SourceProductWarehouseID),
|
ProductWarehouseID: uint(*detail.SourceProductWarehouseID),
|
||||||
Quantity: product.ProductQty,
|
Quantity: product.ProductQty,
|
||||||
@@ -574,12 +553,8 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
product.ProductQty,
|
product.ProductQty,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
stockableKey := fifo.StockableKey(strings.TrimSpace(routePair.Stockable.LegacyTypeKey))
|
|
||||||
if stockableKey == "" {
|
|
||||||
stockableKey = fifo.StockableKeyStockTransferIn
|
|
||||||
}
|
|
||||||
replenishResult, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
|
replenishResult, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
|
||||||
StockableKey: stockableKey,
|
StockableKey: fifo.StockableKeyStockTransferIn,
|
||||||
StockableID: uint(detail.Id),
|
StockableID: uint(detail.Id),
|
||||||
ProductWarehouseID: uint(*detail.DestProductWarehouseID),
|
ProductWarehouseID: uint(*detail.DestProductWarehouseID),
|
||||||
Quantity: product.ProductQty,
|
Quantity: product.ProductQty,
|
||||||
@@ -682,72 +657,50 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *transferService) resolveTransferRoutes(
|
func (s *transferService) resolvePakanProducts(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
tx *gorm.DB,
|
tx *gorm.DB,
|
||||||
products []validation.TransferProduct,
|
products []validation.TransferProduct,
|
||||||
) (map[uint]transferRoutePair, error) {
|
) (map[uint]bool, error) {
|
||||||
out := make(map[uint]transferRoutePair, len(products))
|
out := make(map[uint]bool, len(products))
|
||||||
if len(products) == 0 {
|
if len(products) == 0 {
|
||||||
return out, nil
|
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 {
|
for _, product := range products {
|
||||||
if product.ProductID == 0 {
|
if product.ProductID == 0 {
|
||||||
continue
|
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 {
|
type row struct {
|
||||||
usableRoute, err := commonSvc.ResolveFifoStockV2RouteByProductIDAndLane(
|
ProductID uint `gorm:"column:product_id"`
|
||||||
ctx,
|
}
|
||||||
tx,
|
var rows []row
|
||||||
productID,
|
err := tx.WithContext(ctx).
|
||||||
transferFunctionCodeOut,
|
Table("flags f").
|
||||||
commonSvc.FifoStockV2LaneUsable,
|
Select("DISTINCT f.flagable_id AS product_id").
|
||||||
)
|
Where("f.flagable_type = ?", entity.FlagableTypeProduct).
|
||||||
if err != nil {
|
Where("f.name IN ?", []string{"PAKAN", "PRE-STARTER", "STARTER", "FINISHER"}).
|
||||||
return nil, err
|
Where("f.flagable_id IN ?", productIDs).
|
||||||
}
|
Scan(&rows).Error
|
||||||
if usableRoute == nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(
|
return nil, err
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, row := range rows {
|
||||||
|
out[row.ProductID] = true
|
||||||
|
}
|
||||||
return out, nil
|
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").
|
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("project_flock_kandangs.project_flock_id = ?", projectFlockID).
|
||||||
Where("flags.name IN (?)", []string{
|
Where("flags.name IN (?)", []string{
|
||||||
string(utils.FlagAyam),
|
|
||||||
string(utils.FlagAyamAfkir),
|
string(utils.FlagAyamAfkir),
|
||||||
string(utils.FlagAyamCulling),
|
string(utils.FlagAyamCulling),
|
||||||
string(utils.FlagPullet),
|
string(utils.FlagPullet),
|
||||||
@@ -159,12 +158,9 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetClosingPenjualanByCategory(c
|
|||||||
string(utils.FlagTelurPecah),
|
string(utils.FlagTelurPecah),
|
||||||
string(utils.FlagTelurPutih),
|
string(utils.FlagTelurPutih),
|
||||||
string(utils.FlagTelurRetak),
|
string(utils.FlagTelurRetak),
|
||||||
string(utils.FlagTelurPapacal),
|
|
||||||
string(utils.FlagTelurJumbo),
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
db = db.Where("flags.name IN (?)", []string{
|
db = db.Where("flags.name IN (?)", []string{
|
||||||
string(utils.FlagAyam),
|
|
||||||
string(utils.FlagDOC),
|
string(utils.FlagDOC),
|
||||||
string(utils.FlagPullet),
|
string(utils.FlagPullet),
|
||||||
string(utils.FlagLayer),
|
string(utils.FlagLayer),
|
||||||
@@ -331,12 +327,12 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
|
|||||||
switch filters.MarketingType {
|
switch filters.MarketingType {
|
||||||
case "ayam":
|
case "ayam":
|
||||||
db = db.Where("flags.name IN (?)", []string{
|
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":
|
case "telur":
|
||||||
db = db.Where("flags.name IN (?)", []string{
|
db = db.Where("flags.name IN (?)", []string{
|
||||||
string(utils.FlagTelur), string(utils.FlagTelurUtuh), string(utils.FlagTelurPecah),
|
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":
|
case "trading":
|
||||||
db = db.Where("flags.name IN (?)", []string{
|
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")
|
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{
|
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
||||||
UsableKey: usableKey,
|
UsableKey: fifo.UsableKeyMarketingDelivery,
|
||||||
UsableID: deliveryProduct.Id,
|
UsableID: deliveryProduct.Id,
|
||||||
ProductWarehouseID: marketingProduct.ProductWarehouseId,
|
ProductWarehouseID: marketingProduct.ProductWarehouseId,
|
||||||
Quantity: requestedQty,
|
Quantity: requestedQty,
|
||||||
@@ -624,27 +603,6 @@ func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gor
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, "Product warehouse not found")
|
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)
|
deliveryProductRepo := marketingRepo.NewMarketingDeliveryProductRepository(tx)
|
||||||
currentUsage, err := deliveryProductRepo.GetUsageQty(ctx, deliveryProduct.Id)
|
currentUsage, err := deliveryProductRepo.GetUsageQty(ctx, deliveryProduct.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -656,7 +614,7 @@ func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gor
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
||||||
UsableKey: usableKey,
|
UsableKey: fifo.UsableKeyMarketingDelivery,
|
||||||
UsableID: deliveryProduct.Id,
|
UsableID: deliveryProduct.Id,
|
||||||
Tx: tx,
|
Tx: tx,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|||||||
@@ -48,8 +48,6 @@ type salesOrdersService struct {
|
|||||||
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
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,
|
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 {
|
projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository, validate *validator.Validate) SalesOrdersService {
|
||||||
return &salesOrdersService{
|
return &salesOrdersService{
|
||||||
@@ -76,36 +74,6 @@ func (s salesOrdersService) withRelations(db *gorm.DB) *gorm.DB {
|
|||||||
Preload("Products.ProductWarehouse.Warehouse")
|
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) {
|
func (s salesOrdersService) getOne(c *fiber.Ctx, id uint) (*entity.Marketing, error) {
|
||||||
if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); err != nil {
|
if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -408,12 +376,8 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
|||||||
if qtyDiff < 0 {
|
if qtyDiff < 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Cannot decrease quantity after stock has been allocated. Please delete and create new product.")
|
return fiber.NewError(fiber.StatusBadRequest, "Cannot decrease quantity after stock has been allocated. Please delete and create new product.")
|
||||||
} else if qtyDiff > 0 {
|
} else if qtyDiff > 0 {
|
||||||
usableKey, err := s.resolveMarketingUsableKey(c.Context(), dbTransaction, rp.ProductWarehouseId)
|
_, err := s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
|
||||||
if err != nil {
|
UsableKey: fifo.UsableKeyMarketingDelivery,
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
|
|
||||||
UsableKey: usableKey,
|
|
||||||
UsableID: deliveryProduct.Id,
|
UsableID: deliveryProduct.Id,
|
||||||
ProductWarehouseID: rp.ProductWarehouseId,
|
ProductWarehouseID: rp.ProductWarehouseId,
|
||||||
Quantity: qtyDiff,
|
Quantity: qtyDiff,
|
||||||
@@ -474,13 +438,9 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
|||||||
if deliveryProduct.DeliveryDate != nil {
|
if deliveryProduct.DeliveryDate != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Cannot delete marketing product %d because it has been delivered", old.Id))
|
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{
|
if err := s.FifoSvc.ReleaseUsage(c.Context(), commonSvc.StockReleaseRequest{
|
||||||
UsableKey: usableKey,
|
UsableKey: fifo.UsableKeyMarketingDelivery,
|
||||||
UsableID: deliveryProduct.Id,
|
UsableID: deliveryProduct.Id,
|
||||||
Tx: dbTransaction,
|
Tx: dbTransaction,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -562,22 +522,9 @@ func (s salesOrdersService) DeleteOne(c *fiber.Ctx, id uint) error {
|
|||||||
if len(marketing.Products) > 0 {
|
if len(marketing.Products) > 0 {
|
||||||
deliveryProducts, err := marketingDeliveryProductRepoTx.GetByMarketingId(c.Context(), marketing.Id)
|
deliveryProducts, err := marketingDeliveryProductRepoTx.GetByMarketingId(c.Context(), marketing.Id)
|
||||||
if err == nil && len(deliveryProducts) > 0 {
|
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 {
|
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{
|
if err := s.FifoSvc.ReleaseUsage(c.Context(), commonSvc.StockReleaseRequest{
|
||||||
UsableKey: usableKey,
|
UsableKey: fifo.UsableKeyMarketingDelivery,
|
||||||
UsableID: dp.Id,
|
UsableID: dp.Id,
|
||||||
Tx: dbTransaction,
|
Tx: dbTransaction,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
productCategoryDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories/dto"
|
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"
|
uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto"
|
||||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
utils "gitlab.com/mbugroup/lti-api.git/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
@@ -18,9 +17,6 @@ type ProductRelationDTO struct {
|
|||||||
ProductPrice float64 `gorm:"type:numeric(15,3);not null"`
|
ProductPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
SellingPrice *float64 `gorm:"type:numeric(15,3)"`
|
SellingPrice *float64 `gorm:"type:numeric(15,3)"`
|
||||||
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
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"`
|
Flags *[]string `json:"flags,omitempty"`
|
||||||
ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"`
|
ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"`
|
||||||
Suppliers []ProductSupplierDTO `json:"suppliers"`
|
Suppliers []ProductSupplierDTO `json:"suppliers"`
|
||||||
@@ -35,9 +31,6 @@ type ProductListDTO struct {
|
|||||||
SellingPrice *float64 `json:"selling_price,omitempty"`
|
SellingPrice *float64 `json:"selling_price,omitempty"`
|
||||||
Tax *float64 `json:"tax,omitempty"`
|
Tax *float64 `json:"tax,omitempty"`
|
||||||
ExpiryPeriod *int `json:"expiry_period,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"`
|
Flags []string `json:"flags"`
|
||||||
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
||||||
ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"`
|
ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"`
|
||||||
@@ -66,13 +59,6 @@ func ToProductRelationDTO(e entity.Product) ProductRelationDTO {
|
|||||||
for i, f := range e.Flags {
|
for i, f := range e.Flags {
|
||||||
flags[i] = f.Name
|
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
|
var uomRef *uomDTO.UomRelationDTO
|
||||||
if e.Uom.Id != 0 {
|
if e.Uom.Id != 0 {
|
||||||
@@ -91,9 +77,6 @@ func ToProductRelationDTO(e entity.Product) ProductRelationDTO {
|
|||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
ProductPrice: e.ProductPrice,
|
ProductPrice: e.ProductPrice,
|
||||||
SellingPrice: e.SellingPrice,
|
SellingPrice: e.SellingPrice,
|
||||||
Flag: flag,
|
|
||||||
SubFlag: subFlag,
|
|
||||||
SubFlags: subFlagsRef,
|
|
||||||
Flags: &flags,
|
Flags: &flags,
|
||||||
Uom: uomRef,
|
Uom: uomRef,
|
||||||
ProductCategory: categoryRef,
|
ProductCategory: categoryRef,
|
||||||
@@ -118,7 +101,6 @@ func ToProductListDTO(e entity.Product) ProductListDTO {
|
|||||||
for i, f := range e.Flags {
|
for i, f := range e.Flags {
|
||||||
flags[i] = f.Name
|
flags[i] = f.Name
|
||||||
}
|
}
|
||||||
flag, subFlag, subFlags := resolveProductFlagAndSubFlags(flags)
|
|
||||||
|
|
||||||
var uomRef *uomDTO.UomRelationDTO
|
var uomRef *uomDTO.UomRelationDTO
|
||||||
if e.Uom.Id != 0 {
|
if e.Uom.Id != 0 {
|
||||||
@@ -129,9 +111,6 @@ func ToProductListDTO(e entity.Product) ProductListDTO {
|
|||||||
return ProductListDTO{
|
return ProductListDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
Flag: flag,
|
|
||||||
SubFlag: subFlag,
|
|
||||||
SubFlags: subFlags,
|
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
Uom: uomRef,
|
Uom: uomRef,
|
||||||
Brand: e.Brand,
|
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 {
|
func toProductSupplierDTOs(relations []entity.ProductSupplier) []ProductSupplierDTO {
|
||||||
if len(relations) == 0 {
|
if len(relations) == 0 {
|
||||||
return make([]ProductSupplierDTO, 0)
|
return make([]ProductSupplierDTO, 0)
|
||||||
|
|||||||
@@ -41,151 +41,6 @@ func normalizeProductFlags(raw []string) ([]string, error) {
|
|||||||
return utils.FlagTypesToStrings(normalized), nil
|
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 {
|
func NewProductService(repo repository.ProductRepository, validate *validator.Validate) ProductService {
|
||||||
return &productService{
|
return &productService{
|
||||||
Log: utils.Log,
|
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 {
|
if flagErr != nil {
|
||||||
return nil, flagErr
|
return nil, flagErr
|
||||||
}
|
}
|
||||||
@@ -482,10 +337,13 @@ func (s productService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
flagUpdate bool
|
flagUpdate bool
|
||||||
flagValues []string
|
flagValues []string
|
||||||
)
|
)
|
||||||
var flagErr error
|
if req.Flags != nil {
|
||||||
flagUpdate, flagValues, flagErr = resolveUpdateProductFlags(req)
|
flagUpdate = true
|
||||||
if flagErr != nil {
|
var flagErr error
|
||||||
return nil, flagErr
|
flagValues, flagErr = normalizeProductFlags(*req.Flags)
|
||||||
|
if flagErr != nil {
|
||||||
|
return nil, flagErr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(updateBody) == 0 && !supplierUpdate && !flagUpdate {
|
if len(updateBody) == 0 && !supplierUpdate && !flagUpdate {
|
||||||
|
|||||||
@@ -6,37 +6,31 @@ type SupplierPrice struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
||||||
Brand string `json:"brand" validate:"required_strict,min=2,max=50"`
|
Brand string `json:"brand" validate:"required_strict,min=2,max=50"`
|
||||||
Sku *string `json:"sku,omitempty" validate:"omitempty,max=100"`
|
Sku *string `json:"sku,omitempty" validate:"omitempty,max=100"`
|
||||||
UomID uint `json:"uom_id" validate:"required,gt=0"`
|
UomID uint `json:"uom_id" validate:"required,gt=0"`
|
||||||
ProductCategoryID uint `json:"product_category_id" validate:"required,gt=0"`
|
ProductCategoryID uint `json:"product_category_id" validate:"required,gt=0"`
|
||||||
ProductPrice float64 `json:"product_price" validate:"required"`
|
ProductPrice float64 `json:"product_price" validate:"required"`
|
||||||
SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"`
|
SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"`
|
||||||
Tax *float64 `json:"tax,omitempty" validate:"omitempty"`
|
Tax *float64 `json:"tax,omitempty" validate:"omitempty"`
|
||||||
ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"`
|
ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"`
|
||||||
Suppliers []SupplierPrice `json:"suppliers,omitempty" validate:"omitempty,dive"`
|
Suppliers []SupplierPrice `json:"suppliers,omitempty" validate:"omitempty,dive"`
|
||||||
Flag *string `json:"flag,omitempty" validate:"omitempty,max=50"`
|
Flags []string `json:"flags,omitempty" validate:"omitempty,dive"`
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty,min=3"`
|
Name *string `json:"name,omitempty" validate:"omitempty,min=3"`
|
||||||
Brand *string `json:"brand,omitempty" validate:"omitempty,min=2"`
|
Brand *string `json:"brand,omitempty" validate:"omitempty,min=2"`
|
||||||
Sku *string `json:"sku,omitempty" validate:"omitempty"`
|
Sku *string `json:"sku,omitempty" validate:"omitempty"`
|
||||||
UomID *uint `json:"uom_id,omitempty" validate:"omitempty,gt=0"`
|
UomID *uint `json:"uom_id,omitempty" validate:"omitempty,gt=0"`
|
||||||
ProductCategoryID *uint `json:"product_category_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"`
|
ProductPrice *float64 `json:"product_price,omitempty" validate:"omitempty"`
|
||||||
SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"`
|
SellingPrice *float64 `json:"selling_price,omitempty" validate:"omitempty"`
|
||||||
Tax *float64 `json:"tax,omitempty" validate:"omitempty"`
|
Tax *float64 `json:"tax,omitempty" validate:"omitempty"`
|
||||||
ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"`
|
ExpiryPeriod *int `json:"expiry_period,omitempty" validate:"omitempty,gt=0"`
|
||||||
Suppliers *[]SupplierPrice `json:"suppliers,omitempty" validate:"omitempty,dive"`
|
Suppliers *[]SupplierPrice `json:"suppliers,omitempty" validate:"omitempty,dive"`
|
||||||
Flag *string `json:"flag,omitempty" validate:"omitempty,max=50"`
|
Flags *[]string `json:"flags,omitempty" validate:"omitempty,dive"`
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ import (
|
|||||||
|
|
||||||
var chickinUsableKey = fifo.UsableKeyProjectChickin
|
var chickinUsableKey = fifo.UsableKeyProjectChickin
|
||||||
|
|
||||||
const chickinFunctionCodeOut = "CHICKIN_OUT"
|
|
||||||
|
|
||||||
type ChickinService interface {
|
type ChickinService interface {
|
||||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectChickin, int64, error)
|
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectChickin, int64, error)
|
||||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectChickin, 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 {
|
if productWarehouse.Product.Id != 0 {
|
||||||
|
|
||||||
requiredFlags := make([]utils.FlagType, 0, 2)
|
var requiredFlag utils.FlagType
|
||||||
if projectFlockKandang.ProjectFlock.Category == string(utils.ProjectFlockCategoryGrowing) {
|
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) {
|
} else if projectFlockKandang.ProjectFlock.Category == string(utils.ProjectFlockCategoryLaying) {
|
||||||
requiredFlags = append(requiredFlags, utils.FlagAyam, utils.FlagPullet)
|
requiredFlag = utils.FlagPullet
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("invalid flock category for chickin")
|
return nil, fmt.Errorf("invalid flock category for chickin")
|
||||||
}
|
}
|
||||||
|
|
||||||
hasRequiredFlag := false
|
hasRequiredFlag := false
|
||||||
for _, flag := range productWarehouse.Product.Flags {
|
for _, flag := range productWarehouse.Product.Flags {
|
||||||
currentFlag := utils.FlagType(flag.Name)
|
if utils.FlagType(flag.Name) == requiredFlag {
|
||||||
for _, requiredFlag := range requiredFlags {
|
hasRequiredFlag = true
|
||||||
if currentFlag == requiredFlag {
|
|
||||||
hasRequiredFlag = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hasRequiredFlag {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasRequiredFlag {
|
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, requiredFlag, 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, requiredText, productWarehouse.Product.Id, productWarehouse.Id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,9 +483,9 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
|||||||
|
|
||||||
var targetFlag utils.FlagType
|
var targetFlag utils.FlagType
|
||||||
if category == string(utils.ProjectFlockCategoryGrowing) {
|
if category == string(utils.ProjectFlockCategoryGrowing) {
|
||||||
targetFlag = utils.FlagAyam
|
targetFlag = utils.FlagPullet
|
||||||
} else if category == string(utils.ProjectFlockCategoryLaying) {
|
} else if category == string(utils.ProjectFlockCategoryLaying) {
|
||||||
targetFlag = utils.FlagAyam
|
targetFlag = utils.FlagLayer
|
||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -630,29 +621,8 @@ func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB,
|
|||||||
return nil
|
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{
|
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
||||||
UsableKey: usableKey,
|
UsableKey: chickinUsableKey,
|
||||||
UsableID: chickin.Id,
|
UsableID: chickin.Id,
|
||||||
ProductWarehouseID: chickin.ProductWarehouseId,
|
ProductWarehouseID: chickin.ProductWarehouseId,
|
||||||
Quantity: desiredQty,
|
Quantity: desiredQty,
|
||||||
@@ -718,34 +688,13 @@ func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB,
|
|||||||
return nil
|
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
|
var currentUsage float64
|
||||||
if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Select("usage_qty").Scan(¤tUsage).Error; err != nil {
|
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{
|
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
||||||
UsableKey: usableKey,
|
UsableKey: chickinUsableKey,
|
||||||
UsableID: chickin.Id,
|
UsableID: chickin.Id,
|
||||||
Tx: tx,
|
Tx: tx,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
feedIDs := recordingutil.CollectWarehouseIDs(req.Stocks, func(st validation.Stock) uint { return st.ProductWarehouseId })
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
depletionIDs := recordingutil.CollectWarehouseIDs(req.Depletions, func(d validation.Depletion) uint { return d.ProductWarehouseId })
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
eggIDs := recordingutil.CollectWarehouseIDs(req.Eggs, func(e validation.Egg) uint { return e.ProductWarehouseId })
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
actorID, err := m.ActorIDFromContext(c)
|
||||||
@@ -512,7 +512,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
feedIDs := recordingutil.CollectWarehouseIDs(req.Stocks, func(st validation.Stock) uint { return st.ProductWarehouseId })
|
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
|
return err
|
||||||
}
|
}
|
||||||
if err := s.syncRecordingStocks(ctx, tx, recordingEntity.Id, existingStocks, req.Stocks, note, actorID); err != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
eggIDs := recordingutil.CollectWarehouseIDs(req.Eggs, func(e validation.Egg) uint { return e.ProductWarehouseId })
|
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
|
return err
|
||||||
}
|
}
|
||||||
if err := ensureRecordingEggsUnused(existingEggs); err != nil {
|
if err := ensureRecordingEggsUnused(existingEggs); err != nil {
|
||||||
|
|||||||
@@ -21,93 +21,7 @@ import (
|
|||||||
var recordingStockUsableKey = fifo.UsableKeyRecordingStock
|
var recordingStockUsableKey = fifo.UsableKeyRecordingStock
|
||||||
var recordingDepletionUsableKey = fifo.UsableKeyRecordingDepletion
|
var recordingDepletionUsableKey = fifo.UsableKeyRecordingDepletion
|
||||||
|
|
||||||
const (
|
const depletionUsageTolerance = 0.000001
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *recordingService) logStockTrace(action string, stock entity.RecordingStock, extra string) {
|
func (s *recordingService) logStockTrace(action string, stock entity.RecordingStock, extra string) {
|
||||||
if s == nil || s.Log == nil {
|
if s == nil || s.Log == nil {
|
||||||
@@ -211,20 +125,8 @@ func (s *recordingService) consumeRecordingStocks(
|
|||||||
}
|
}
|
||||||
desiredTotal := desired + pending
|
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{
|
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
||||||
UsableKey: usableKey,
|
UsableKey: recordingStockUsableKey,
|
||||||
UsableID: stock.Id,
|
UsableID: stock.Id,
|
||||||
ProductWarehouseID: stock.ProductWarehouseId,
|
ProductWarehouseID: stock.ProductWarehouseId,
|
||||||
Quantity: desiredTotal,
|
Quantity: desiredTotal,
|
||||||
@@ -307,21 +209,9 @@ func (s *recordingService) consumeRecordingDepletions(
|
|||||||
return fiber.NewError(fiber.StatusBadRequest, "Source product warehouse tidak ditemukan untuk depletion")
|
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
|
desired := depletion.Qty + depletion.PendingQty
|
||||||
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
||||||
UsableKey: usableKey,
|
UsableKey: recordingDepletionUsableKey,
|
||||||
UsableID: depletion.Id,
|
UsableID: depletion.Id,
|
||||||
ProductWarehouseID: sourceWarehouseID,
|
ProductWarehouseID: sourceWarehouseID,
|
||||||
Quantity: desired,
|
Quantity: desired,
|
||||||
@@ -424,20 +314,8 @@ func (s *recordingService) releaseRecordingStocks(
|
|||||||
if stock.Id == 0 {
|
if stock.Id == 0 {
|
||||||
continue
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -448,16 +326,16 @@ func (s *recordingService) releaseRecordingStocks(
|
|||||||
}
|
}
|
||||||
continue
|
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
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.logStockTrace("release:start", stock, "")
|
s.logStockTrace("release:start", stock, "")
|
||||||
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
||||||
UsableKey: usableKey,
|
UsableKey: recordingStockUsableKey,
|
||||||
UsableID: stock.Id,
|
UsableID: stock.Id,
|
||||||
Tx: tx,
|
Tx: tx,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -522,28 +400,8 @@ func (s *recordingService) releaseRecordingDepletions(
|
|||||||
if depletion.Id == 0 {
|
if depletion.Id == 0 {
|
||||||
continue
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -560,10 +418,10 @@ func (s *recordingService) releaseRecordingDepletions(
|
|||||||
}
|
}
|
||||||
continue
|
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
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -573,8 +431,15 @@ func (s *recordingService) releaseRecordingDepletions(
|
|||||||
return err
|
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{
|
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
||||||
UsableKey: usableKey,
|
UsableKey: recordingDepletionUsableKey,
|
||||||
UsableID: depletion.Id,
|
UsableID: depletion.Id,
|
||||||
Tx: tx,
|
Tx: tx,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -765,20 +630,9 @@ func (s *recordingService) replenishRecordingEggs(
|
|||||||
if egg.Id == 0 || egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
|
if egg.Id == 0 || egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stockableKey, err := s.resolveRecordingStockableKey(
|
|
||||||
ctx,
|
|
||||||
tx,
|
|
||||||
egg.ProductWarehouseId,
|
|
||||||
recordingFunctionCodeRecordingEggIn,
|
|
||||||
fifo.StockableKeyRecordingEgg,
|
|
||||||
"Recording Egg",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.logEggTrace("replenish:start", egg, "")
|
s.logEggTrace("replenish:start", egg, "")
|
||||||
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
||||||
StockableKey: stockableKey,
|
StockableKey: fifo.StockableKeyRecordingEgg,
|
||||||
StockableID: egg.Id,
|
StockableID: egg.Id,
|
||||||
ProductWarehouseID: egg.ProductWarehouseId,
|
ProductWarehouseID: egg.ProductWarehouseId,
|
||||||
Quantity: float64(egg.Qty),
|
Quantity: float64(egg.Qty),
|
||||||
@@ -836,20 +690,9 @@ func (s *recordingService) replenishRecordingDepletions(
|
|||||||
if depletion.Id == 0 || depletion.ProductWarehouseId == 0 || depletion.Qty <= 0 {
|
if depletion.Id == 0 || depletion.ProductWarehouseId == 0 || depletion.Qty <= 0 {
|
||||||
continue
|
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, "")
|
s.logDepletionTrace("replenish:start", depletion, "")
|
||||||
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
||||||
StockableKey: stockableKey,
|
StockableKey: fifo.StockableKeyRecordingDepletion,
|
||||||
StockableID: depletion.Id,
|
StockableID: depletion.Id,
|
||||||
ProductWarehouseID: depletion.ProductWarehouseId,
|
ProductWarehouseID: depletion.ProductWarehouseId,
|
||||||
Quantity: depletion.Qty,
|
Quantity: depletion.Qty,
|
||||||
@@ -881,20 +724,9 @@ func (s *recordingService) reduceRecordingDepletions(
|
|||||||
if depletion.Id == 0 || depletion.ProductWarehouseId == 0 || depletion.Qty <= 0 {
|
if depletion.Id == 0 || depletion.ProductWarehouseId == 0 || depletion.Qty <= 0 {
|
||||||
continue
|
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, "")
|
s.logDepletionTrace("reduce:start", depletion, "")
|
||||||
if err := s.FifoSvc.AdjustStockableQuantity(ctx, commonSvc.StockAdjustRequest{
|
if err := s.FifoSvc.AdjustStockableQuantity(ctx, commonSvc.StockAdjustRequest{
|
||||||
StockableKey: stockableKey,
|
StockableKey: fifo.StockableKeyRecordingDepletion,
|
||||||
StockableID: depletion.Id,
|
StockableID: depletion.Id,
|
||||||
ProductWarehouseID: depletion.ProductWarehouseId,
|
ProductWarehouseID: depletion.ProductWarehouseId,
|
||||||
Quantity: -depletion.Qty,
|
Quantity: -depletion.Qty,
|
||||||
@@ -926,20 +758,9 @@ func (s *recordingService) reduceRecordingEggs(
|
|||||||
if egg.Id == 0 || egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
|
if egg.Id == 0 || egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stockableKey, err := s.resolveRecordingStockableKey(
|
|
||||||
ctx,
|
|
||||||
tx,
|
|
||||||
egg.ProductWarehouseId,
|
|
||||||
recordingFunctionCodeRecordingEggIn,
|
|
||||||
fifo.StockableKeyRecordingEgg,
|
|
||||||
"Recording Egg",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.logEggTrace("reduce:start", egg, "")
|
s.logEggTrace("reduce:start", egg, "")
|
||||||
if err := s.FifoSvc.AdjustStockableQuantity(ctx, commonSvc.StockAdjustRequest{
|
if err := s.FifoSvc.AdjustStockableQuantity(ctx, commonSvc.StockAdjustRequest{
|
||||||
StockableKey: stockableKey,
|
StockableKey: fifo.StockableKeyRecordingEgg,
|
||||||
StockableID: egg.Id,
|
StockableID: egg.Id,
|
||||||
ProductWarehouseID: egg.ProductWarehouseId,
|
ProductWarehouseID: egg.ProductWarehouseId,
|
||||||
Quantity: -float64(egg.Qty),
|
Quantity: -float64(egg.Qty),
|
||||||
|
|||||||
@@ -43,8 +43,7 @@ type PurchaseService interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
priceTolerance = 0.0001
|
priceTolerance = 0.0001
|
||||||
purchaseFunctionCodeIn = "PURCHASE_IN"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type purchaseService struct {
|
type purchaseService struct {
|
||||||
@@ -406,14 +405,6 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase
|
|||||||
indexMap[key] = len(aggregated) - 1
|
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
|
var dueDate *time.Time
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
d := now.AddDate(0, 0, req.CreditTerm)
|
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")
|
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
|
receivingAction := action
|
||||||
completedAction := entity.ApprovalActionApproved
|
completedAction := entity.ApprovalActionApproved
|
||||||
approvalSvc := commonSvc.NewApprovalService(
|
approvalSvc := commonSvc.NewApprovalService(
|
||||||
@@ -1854,15 +1834,6 @@ func (s *purchaseService) buildStaffAdjustmentPayload(
|
|||||||
productSupplierCache := make(map[uint]bool)
|
productSupplierCache := make(map[uint]bool)
|
||||||
newItems := make([]*entity.PurchaseItem, 0, len(newPayloads))
|
newItems := make([]*entity.PurchaseItem, 0, len(newPayloads))
|
||||||
emptyVehicle := ""
|
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 {
|
for _, payload := range newPayloads {
|
||||||
if payload.ProductID == 0 || payload.WarehouseID == 0 {
|
if payload.ProductID == 0 || payload.WarehouseID == 0 {
|
||||||
@@ -1947,48 +1918,6 @@ func calculateTotalPrice(quantity float64, price float64, provided *float64, ref
|
|||||||
return *provided, nil
|
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 {
|
func (s *purchaseService) attachLatestApproval(ctx context.Context, item *entity.Purchase) error {
|
||||||
if item == nil || item.Id == 0 || s.ApprovalSvc == nil {
|
if item == nil || item.Id == 0 || s.ApprovalSvc == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func ToMarketingReportItems(mdps []entity.MarketingDeliveryProduct, hppMap map[u
|
|||||||
for _, flag := range mdp.MarketingProduct.ProductWarehouse.Product.Flags {
|
for _, flag := range mdp.MarketingProduct.ProductWarehouse.Product.Flags {
|
||||||
ft := utils.FlagType(flag.Name)
|
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 {
|
ft == utils.FlagDOC || ft == utils.FlagPullet || ft == utils.FlagLayer {
|
||||||
hasAyam = true
|
hasAyam = true
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-185
@@ -14,18 +14,9 @@ type FlagType string
|
|||||||
|
|
||||||
type FlagGroup string
|
type FlagGroup string
|
||||||
|
|
||||||
type ProductFlagOption struct {
|
|
||||||
Flag FlagType `json:"flag"`
|
|
||||||
SubFlags []FlagType `json:"sub_flags"`
|
|
||||||
AllowWithoutSubFlag bool `json:"allow_without_sub_flag"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FlagIsActive FlagType = "IS_ACTIVE"
|
FlagIsActive FlagType = "IS_ACTIVE"
|
||||||
|
|
||||||
FlagAyam FlagType = "AYAM"
|
|
||||||
|
|
||||||
// Legacy AYAM flags kept for backward compatibility with existing production data.
|
|
||||||
FlagDOC FlagType = "DOC"
|
FlagDOC FlagType = "DOC"
|
||||||
FlagPullet FlagType = "PULLET"
|
FlagPullet FlagType = "PULLET"
|
||||||
FlagLayer FlagType = "LAYER"
|
FlagLayer FlagType = "LAYER"
|
||||||
@@ -45,13 +36,11 @@ const (
|
|||||||
FlagAyamMati FlagType = "AYAM-MATI"
|
FlagAyamMati FlagType = "AYAM-MATI"
|
||||||
|
|
||||||
//flag telur
|
//flag telur
|
||||||
FlagTelur FlagType = "TELUR"
|
FlagTelur FlagType = "TELUR"
|
||||||
FlagTelurUtuh FlagType = "TELUR-UTUH"
|
FlagTelurUtuh FlagType = "TELUR-UTUH"
|
||||||
FlagTelurPecah FlagType = "TELUR-PECAH"
|
FlagTelurPecah FlagType = "TELUR-PECAH"
|
||||||
FlagTelurPutih FlagType = "TELUR-PUTIH"
|
FlagTelurPutih FlagType = "TELUR-PUTIH"
|
||||||
FlagTelurRetak FlagType = "TELUR-RETAK"
|
FlagTelurRetak FlagType = "TELUR-RETAK"
|
||||||
FlagTelurPapacal FlagType = "TELUR-PAPACAL"
|
|
||||||
FlagTelurJumbo FlagType = "TELUR-JUMBO"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -61,10 +50,9 @@ const (
|
|||||||
|
|
||||||
var flagGroupOptions = map[FlagGroup][]FlagType{
|
var flagGroupOptions = map[FlagGroup][]FlagType{
|
||||||
FlagGroupProduct: {
|
FlagGroupProduct: {
|
||||||
FlagAyam,
|
FlagDOC,
|
||||||
FlagAyamAfkir,
|
FlagPullet,
|
||||||
FlagAyamCulling,
|
FlagLayer,
|
||||||
FlagAyamMati,
|
|
||||||
FlagPakan,
|
FlagPakan,
|
||||||
FlagPreStarter,
|
FlagPreStarter,
|
||||||
FlagStarter,
|
FlagStarter,
|
||||||
@@ -73,82 +61,12 @@ var flagGroupOptions = map[FlagGroup][]FlagType{
|
|||||||
FlagObat,
|
FlagObat,
|
||||||
FlagVitamin,
|
FlagVitamin,
|
||||||
FlagKimia,
|
FlagKimia,
|
||||||
FlagTelur,
|
|
||||||
FlagTelurUtuh,
|
|
||||||
FlagTelurPutih,
|
|
||||||
FlagTelurRetak,
|
|
||||||
FlagTelurPecah,
|
|
||||||
FlagTelurPapacal,
|
|
||||||
FlagTelurJumbo,
|
|
||||||
},
|
},
|
||||||
FlagGroupNonstock: {
|
FlagGroupNonstock: {
|
||||||
FlagEkspedisi,
|
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{} {
|
var allFlagTypes = func() map[FlagType]struct{} {
|
||||||
m := map[FlagType]struct{}{
|
m := map[FlagType]struct{}{
|
||||||
FlagIsActive: {},
|
FlagIsActive: {},
|
||||||
@@ -165,95 +83,6 @@ func AllFlagTypes() map[FlagType]struct{} {
|
|||||||
return allFlagTypes
|
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
|
// WarehouseType
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
@@ -792,11 +621,7 @@ const (
|
|||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
func IsValidFlagType(v string) bool {
|
func IsValidFlagType(v string) bool {
|
||||||
flag := FlagType(strings.ToUpper(strings.TrimSpace(v)))
|
_, ok := allFlagTypes[FlagType(strings.ToUpper(strings.TrimSpace(v)))]
|
||||||
if _, ok := allFlagTypes[flag]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
_, ok := legacyFlagTypeAliases[flag]
|
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -842,7 +667,6 @@ func NormalizeFlagTypes(flags []string) []FlagType {
|
|||||||
if normalized == "" {
|
if normalized == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
normalized = canonicalizeFlagType(normalized)
|
|
||||||
if _, exists := seen[normalized]; exists {
|
if _, exists := seen[normalized]; exists {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user