diff --git a/cmd/consolidate-duplicate-product-warehouses/main.go b/cmd/consolidate-duplicate-product-warehouses/main.go index 135d4082..ba3f5462 100644 --- a/cmd/consolidate-duplicate-product-warehouses/main.go +++ b/cmd/consolidate-duplicate-product-warehouses/main.go @@ -30,12 +30,13 @@ type options struct { } type duplicateGroup struct { - WarehouseID uint `json:"warehouse_id"` - WarehouseName string `json:"warehouse_name"` - ProductID uint `json:"product_id"` - ProductName string `json:"product_name"` - AreaName string `json:"area_name"` - LocationName string `json:"location_name"` + WarehouseID uint `json:"warehouse_id"` + WarehouseName string `json:"warehouse_name"` + ProductID uint `json:"product_id"` + ProductName string `json:"product_name"` + AreaName string `json:"area_name"` + LocationName string `json:"location_name"` + ProjectFlockKandangID *uint `json:"project_flock_kandang_id,omitempty"` SurvivorID uint `json:"survivor_id"` SurvivorQty float64 `json:"survivor_qty"` @@ -128,11 +129,12 @@ WITH duplicates AS ( p.name AS product_name, COALESCE(a.name, 'N/A') AS area_name, COALESCE(l.name, 'N/A') AS location_name, + pw.project_flock_kandang_id, pw.id, pw.qty, - MIN(pw.id) OVER (PARTITION BY pw.warehouse_id, pw.product_id) AS survivor_id, - COUNT(*) OVER (PARTITION BY pw.warehouse_id, pw.product_id) AS duplicate_count, - SUM(pw.qty) OVER (PARTITION BY pw.warehouse_id, pw.product_id) AS total_qty + MIN(pw.id) OVER (PARTITION BY pw.warehouse_id, pw.product_id, pw.project_flock_kandang_id) AS survivor_id, + COUNT(*) OVER (PARTITION BY pw.warehouse_id, pw.product_id, pw.project_flock_kandang_id) AS duplicate_count, + SUM(pw.qty) OVER (PARTITION BY pw.warehouse_id, pw.product_id, pw.project_flock_kandang_id) AS total_qty FROM product_warehouses pw JOIN warehouses w ON w.id = pw.warehouse_id JOIN products p ON p.id = pw.product_id @@ -147,6 +149,7 @@ SELECT product_name, area_name, location_name, + project_flock_kandang_id, survivor_id, (SELECT qty FROM duplicates d2 WHERE d2.id = survivor_id LIMIT 1) AS survivor_qty, duplicate_count - 1 AS absorbed_count, @@ -154,7 +157,7 @@ SELECT STRING_AGG(id::text, ', ' ORDER BY id::text) FILTER (WHERE id <> survivor_id) AS absorbed_ids FROM duplicates WHERE duplicate_count > 1 -GROUP BY warehouse_id, warehouse_name, product_id, product_name, area_name, location_name, survivor_id, total_qty, duplicate_count +GROUP BY warehouse_id, warehouse_name, product_id, product_name, area_name, location_name, project_flock_kandang_id, survivor_id, total_qty, duplicate_count ORDER BY area_name, location_name, warehouse_name, product_name `, filters) @@ -378,15 +381,20 @@ func renderConsolidation(mode string, groups []duplicateGroup, summary consolida } w := tabwriter.NewWriter(os.Stdout, 2, 8, 2, ' ', 0) - fmt.Fprintln(w, "AREA\tLOCATION\tWAREHOUSE\tPRODUCT\tSURVIVOR_ID\tSURVIVOR_QTY\tABSORBED_COUNT\tTOTAL_MERGED_QTY\tABSORBED_IDS") + fmt.Fprintln(w, "AREA\tLOCATION\tWAREHOUSE\tPRODUCT\tPFK_ID\tSURVIVOR_ID\tSURVIVOR_QTY\tABSORBED_COUNT\tTOTAL_MERGED_QTY\tABSORBED_IDS") for _, g := range groups { + pfkID := "-" + if g.ProjectFlockKandangID != nil { + pfkID = fmt.Sprintf("%d", *g.ProjectFlockKandangID) + } fmt.Fprintf( w, - "%s\t%s\t%s\t%s\t%d\t%.3f\t%d\t%.3f\t%s\n", + "%s\t%s\t%s\t%s\t%s\t%d\t%.3f\t%d\t%.3f\t%s\n", g.AreaName, g.LocationName, g.WarehouseName, g.ProductName, + pfkID, g.SurvivorID, g.SurvivorQty, g.AbsorbedCount, diff --git a/cmd/consolidate-kandang-to-farm-stocks/main.go b/cmd/consolidate-kandang-to-farm-stocks/main.go index acf5b4ba..61e4c279 100644 --- a/cmd/consolidate-kandang-to-farm-stocks/main.go +++ b/cmd/consolidate-kandang-to-farm-stocks/main.go @@ -729,6 +729,9 @@ func reflowAndRecalculateProductWarehouse( if err != nil { return fmt.Errorf("resolve flag group for product_warehouse %d: %w", productWarehouseID, err) } + if flagGroupCode == "" { + return nil + } if _, err := fifoSvc.Reflow(ctx, commonSvc.FifoStockV2ReflowRequest{ FlagGroupCode: flagGroupCode, @@ -778,6 +781,9 @@ func resolveFlagGroupByProductWarehouse(ctx context.Context, tx *gorm.DB, produc Order("rr.id ASC"). Limit(1). Take(&selected).Error + if err == gorm.ErrRecordNotFound { + return "", nil + } if err != nil { return "", err } diff --git a/cmd/repoint-wrong-warehouse-relations/main.go b/cmd/repoint-wrong-warehouse-relations/main.go index 2e1b2484..7a534b72 100644 --- a/cmd/repoint-wrong-warehouse-relations/main.go +++ b/cmd/repoint-wrong-warehouse-relations/main.go @@ -400,8 +400,10 @@ func buildReferencePlan(ctx context.Context, db *gorm.DB) (*referencePlan, error } func runPrechecks(ctx context.Context, db *gorm.DB, rows []planRow, refs *referencePlan, opts *options) error { - if err := ensureNoBlockedWarehouseRefs(ctx, db, rows, refs.BlockedWarehouseRefs); err != nil { - return err + if opts.DeleteWrongWarehouses { + if err := ensureNoBlockedWarehouseRefs(ctx, db, rows, refs.BlockedWarehouseRefs); err != nil { + return err + } } if err := ensureNoPurchaseItemWarehouseConflicts(ctx, db, rows); err != nil { return err @@ -813,6 +815,9 @@ func reflowAndRecalculateProductWarehouse( if err != nil { return fmt.Errorf("resolve flag group for product_warehouse %d: %w", productWarehouseID, err) } + if flagGroupCode == "" { + return nil + } if _, err := fifoSvc.Reflow(ctx, commonSvc.FifoStockV2ReflowRequest{ FlagGroupCode: flagGroupCode, @@ -862,6 +867,9 @@ func resolveFlagGroupByProductWarehouse(ctx context.Context, tx *gorm.DB, produc Order("rr.id ASC"). Limit(1). Take(&selected).Error + if err == gorm.ErrRecordNotFound { + return "", nil + } if err != nil { return "", err } diff --git a/cmd/verify-stock-consolidation/main.go b/cmd/verify-stock-consolidation/main.go index 7e3f6a0b..67d57817 100644 --- a/cmd/verify-stock-consolidation/main.go +++ b/cmd/verify-stock-consolidation/main.go @@ -39,7 +39,7 @@ type sourceWarehouseCheck struct { KandangName string `json:"kandang_name"` SourceWarehouseID uint `json:"source_warehouse_id"` SourceWarehouseName string `json:"source_warehouse_name"` - Case string `json:"case"` + Case string `json:"case" gorm:"column:case_type"` DeletedAt *string `json:"deleted_at"` StockInProductWH float64 `json:"stock_in_product_wh"` ActivePurchaseItems int64 `json:"active_purchase_items"` @@ -145,7 +145,7 @@ func parseFlags() (*options, error) { } func verifySourceWarehouses(ctx context.Context, db *gorm.DB, opts *options) ([]sourceWarehouseCheck, error) { - filters := buildFilters(opts) + filters, args := buildFilters(opts) query := fmt.Sprintf(` WITH case_a_warehouses AS ( -- Case A: Kandang-level warehouses (type != 'LOKASI' or NULL) @@ -234,7 +234,7 @@ ORDER BY asw.area_name ASC, asw.kandang_location_name ASC, w.name ASC `, andClause(filters)) rows := make([]sourceWarehouseCheck, 0) - if err := db.WithContext(ctx).Raw(query).Scan(&rows).Error; err != nil { + if err := db.WithContext(ctx).Raw(query, args...).Scan(&rows).Error; err != nil { return nil, err } @@ -257,7 +257,7 @@ ORDER BY asw.area_name ASC, asw.kandang_location_name ASC, w.name ASC } func verifyDestinationWarehouses(ctx context.Context, db *gorm.DB, opts *options, sourceChecks []sourceWarehouseCheck) ([]destinationWarehouseCheck, error) { - filters := buildFilters(opts) + filters, args := buildFilters(opts) query := fmt.Sprintf(` SELECT a.name AS area_name, @@ -270,13 +270,11 @@ SELECT COALESCE(SUM(sl.stock), 0) AS stock_logs_total, COUNT(DISTINCT sl.id) AS stock_logs_count FROM warehouses fw -JOIN locations loc ON loc.id = fw.location_id -JOIN areas a ON a.id = loc.area_id -JOIN kandangs k ON k.location_id = fw.location_id AND k.deleted_at IS NULL -JOIN locations kl ON kl.id = k.location_id -JOIN products p ON true +JOIN locations kl ON kl.id = fw.location_id +JOIN areas a ON a.id = kl.area_id +JOIN product_warehouses pw ON pw.warehouse_id = fw.id +JOIN products p ON p.id = pw.product_id JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = 'products' AND UPPER(f.name) IN ('PAKAN', 'OVK') -LEFT JOIN product_warehouses pw ON pw.warehouse_id = fw.id AND pw.product_id = p.id LEFT JOIN stock_logs sl ON sl.product_warehouse_id = pw.id WHERE fw.deleted_at IS NULL AND UPPER(COALESCE(fw.type, '')) = 'LOKASI' @@ -293,7 +291,7 @@ ORDER BY a.name ASC, kl.name ASC, fw.name ASC, p.name ASC `, andClause(filters)) rows := make([]destinationWarehouseCheck, 0) - if err := db.WithContext(ctx).Raw(query).Scan(&rows).Error; err != nil { + if err := db.WithContext(ctx).Raw(query, args...).Scan(&rows).Error; err != nil { return nil, err } @@ -371,15 +369,18 @@ func verifyOrphanedReferences(ctx context.Context, db *gorm.DB, sourceChecks []s return results, nil } -func buildFilters(opts *options) []string { +func buildFilters(opts *options) ([]string, []any) { filters := make([]string, 0, 2) + args := make([]any, 0, 2) if opts.AreaName != "" { - filters = append(filters, fmt.Sprintf("a.name = '%s'", opts.AreaName)) + filters = append(filters, "a.name = ?") + args = append(args, opts.AreaName) } if opts.KandangLocationName != "" { - filters = append(filters, fmt.Sprintf("kl.name = '%s'", opts.KandangLocationName)) + filters = append(filters, "kl.name = ?") + args = append(args, opts.KandangLocationName) } - return filters + return filters, args } func andClause(filters []string) string { diff --git a/consolidate-duplicate-product-warehouses b/consolidate-duplicate-product-warehouses new file mode 100755 index 00000000..d2c3fa4a Binary files /dev/null and b/consolidate-duplicate-product-warehouses differ diff --git a/consolidate-kandang-to-farm-stocks b/consolidate-kandang-to-farm-stocks new file mode 100755 index 00000000..797bf8fa Binary files /dev/null and b/consolidate-kandang-to-farm-stocks differ diff --git a/repoint-wrong-warehouse-relations b/repoint-wrong-warehouse-relations index 5e2f81aa..6b82cabd 100755 Binary files a/repoint-wrong-warehouse-relations and b/repoint-wrong-warehouse-relations differ diff --git a/verify-stock-consolidation b/verify-stock-consolidation new file mode 100755 index 00000000..0d946252 Binary files /dev/null and b/verify-stock-consolidation differ