Merge branch 'cmd/consolidate-and-repoint-stocks' into 'development'

cmd: resolve active allocation repointment

See merge request mbugroup/lti-api!449
This commit is contained in:
Adnan Zahir
2026-04-23 23:33:12 +07:00
+102 -6
View File
@@ -26,12 +26,13 @@ const (
)
type options struct {
Apply bool
Output string
AreaName string
KandangLocationName string
DBSSLMode string
DeleteWrongWarehouses bool
Apply bool
Output string
AreaName string
KandangLocationName string
DBSSLMode string
DeleteWrongWarehouses bool
AllowMovingAllocatedStocks bool
}
type planRow struct {
@@ -123,6 +124,16 @@ func main() {
log.Fatalf("failed to load plan rows: %v", err)
}
if opts.AllowMovingAllocatedStocks {
allocatedRows, err := loadPlanRowsWithAllocations(ctx, db, opts)
if err != nil {
log.Fatalf("failed to load allocated plan rows: %v", err)
}
rows = append(rows, allocatedRows...)
// Remove duplicates
rows = deduplicatePlanRows(rows)
}
if len(rows) == 0 {
fmt.Println("No misplaced PAKAN/OVK stocks found in wrong-location warehouses")
return
@@ -158,6 +169,7 @@ func parseFlags() (*options, error) {
flag.StringVar(&opts.KandangLocationName, "kandang-location-name", "", "Optional exact canonical kandang location filter")
flag.StringVar(&opts.DBSSLMode, "db-sslmode", "", "Optional database sslmode override, for example: require")
flag.BoolVar(&opts.DeleteWrongWarehouses, "delete-wrong-warehouses", true, "Soft delete wrong warehouse rows after all references have been moved")
flag.BoolVar(&opts.AllowMovingAllocatedStocks, "allow-moving-allocated-stocks", false, "Allow moving stocks that have active allocations (use with caution - for old recordings with completed allocations)")
flag.Parse()
opts.Output = strings.ToLower(strings.TrimSpace(opts.Output))
@@ -175,6 +187,90 @@ func parseFlags() (*options, error) {
return &opts, nil
}
func deduplicatePlanRows(rows []planRow) []planRow {
seen := make(map[uint]struct{})
result := make([]planRow, 0, len(rows))
for _, row := range rows {
if _, ok := seen[row.SurvivorPWID]; !ok {
seen[row.SurvivorPWID] = struct{}{}
result = append(result, row)
}
}
return result
}
func loadPlanRowsWithAllocations(ctx context.Context, db *gorm.DB, opts *options) ([]planRow, error) {
filters := make([]string, 0, 2)
args := make([]any, 0, 2)
if opts.AreaName != "" {
filters = append(filters, "a.name = ?")
args = append(args, opts.AreaName)
}
if opts.KandangLocationName != "" {
filters = append(filters, "kl.name = ?")
args = append(args, opts.KandangLocationName)
}
query := fmt.Sprintf(`
SELECT
a.name AS area_name,
kl.name AS kandang_location_name,
k.id AS kandang_id,
k.name AS kandang_name,
w.id AS wrong_warehouse_id,
w.name AS wrong_warehouse_name,
correct_w.id AS correct_warehouse_id,
correct_w.name AS correct_warehouse_name,
p.id AS product_id,
p.name AS product_name,
wp.project_flock_kandang_id,
wp.id AS survivor_pw_id,
COALESCE(wp.qty, 0) AS survivor_current_qty,
cpw.id AS absorbed_pw_id,
cpw.qty AS absorbed_current_qty
FROM warehouses w
JOIN kandangs k
ON k.id = w.kandang_id
AND k.deleted_at IS NULL
JOIN locations kl
ON kl.id = k.location_id
JOIN areas a
ON a.id = kl.area_id
JOIN LATERAL (
SELECT w2.id, w2.name
FROM warehouses w2
WHERE w2.location_id = k.location_id
AND UPPER(COALESCE(w2.type, '')) = 'LOKASI'
AND w2.deleted_at IS NULL
ORDER BY w2.id ASC
LIMIT 1
) AS correct_w ON TRUE
JOIN product_warehouses wp
ON wp.warehouse_id = w.id
JOIN products p
ON p.id = wp.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 cpw
ON cpw.product_id = wp.product_id
AND cpw.warehouse_id = correct_w.id
AND cpw.project_flock_kandang_id IS NOT DISTINCT FROM wp.project_flock_kandang_id
WHERE w.deleted_at IS NULL
AND w.kandang_id IS NOT NULL
AND w.location_id IS DISTINCT FROM k.location_id
%s
ORDER BY a.name ASC, kl.name ASC, k.name ASC, wp.id ASC
`, andClause(filters))
rows := make([]planRow, 0)
if err := db.WithContext(ctx).Raw(query, args...).Scan(&rows).Error; err != nil {
return nil, err
}
return rows, nil
}
func loadPlanRows(ctx context.Context, db *gorm.DB, opts *options) ([]planRow, error) {
filters := make([]string, 0, 2)
args := make([]any, 0, 2)