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