mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
cmd: check validate and fix bug risks in commands
This commit is contained in:
@@ -36,6 +36,7 @@ type duplicateGroup struct {
|
|||||||
ProductName string `json:"product_name"`
|
ProductName string `json:"product_name"`
|
||||||
AreaName string `json:"area_name"`
|
AreaName string `json:"area_name"`
|
||||||
LocationName string `json:"location_name"`
|
LocationName string `json:"location_name"`
|
||||||
|
ProjectFlockKandangID *uint `json:"project_flock_kandang_id,omitempty"`
|
||||||
|
|
||||||
SurvivorID uint `json:"survivor_id"`
|
SurvivorID uint `json:"survivor_id"`
|
||||||
SurvivorQty float64 `json:"survivor_qty"`
|
SurvivorQty float64 `json:"survivor_qty"`
|
||||||
@@ -128,11 +129,12 @@ WITH duplicates AS (
|
|||||||
p.name AS product_name,
|
p.name AS product_name,
|
||||||
COALESCE(a.name, 'N/A') AS area_name,
|
COALESCE(a.name, 'N/A') AS area_name,
|
||||||
COALESCE(l.name, 'N/A') AS location_name,
|
COALESCE(l.name, 'N/A') AS location_name,
|
||||||
|
pw.project_flock_kandang_id,
|
||||||
pw.id,
|
pw.id,
|
||||||
pw.qty,
|
pw.qty,
|
||||||
MIN(pw.id) OVER (PARTITION BY pw.warehouse_id, pw.product_id) AS survivor_id,
|
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) AS duplicate_count,
|
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) AS total_qty
|
SUM(pw.qty) OVER (PARTITION BY pw.warehouse_id, pw.product_id, pw.project_flock_kandang_id) AS total_qty
|
||||||
FROM product_warehouses pw
|
FROM product_warehouses pw
|
||||||
JOIN warehouses w ON w.id = pw.warehouse_id
|
JOIN warehouses w ON w.id = pw.warehouse_id
|
||||||
JOIN products p ON p.id = pw.product_id
|
JOIN products p ON p.id = pw.product_id
|
||||||
@@ -147,6 +149,7 @@ SELECT
|
|||||||
product_name,
|
product_name,
|
||||||
area_name,
|
area_name,
|
||||||
location_name,
|
location_name,
|
||||||
|
project_flock_kandang_id,
|
||||||
survivor_id,
|
survivor_id,
|
||||||
(SELECT qty FROM duplicates d2 WHERE d2.id = survivor_id LIMIT 1) AS survivor_qty,
|
(SELECT qty FROM duplicates d2 WHERE d2.id = survivor_id LIMIT 1) AS survivor_qty,
|
||||||
duplicate_count - 1 AS absorbed_count,
|
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
|
STRING_AGG(id::text, ', ' ORDER BY id::text) FILTER (WHERE id <> survivor_id) AS absorbed_ids
|
||||||
FROM duplicates
|
FROM duplicates
|
||||||
WHERE duplicate_count > 1
|
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
|
ORDER BY area_name, location_name, warehouse_name, product_name
|
||||||
`, filters)
|
`, filters)
|
||||||
|
|
||||||
@@ -378,15 +381,20 @@ func renderConsolidation(mode string, groups []duplicateGroup, summary consolida
|
|||||||
}
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 2, 8, 2, ' ', 0)
|
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 {
|
for _, g := range groups {
|
||||||
|
pfkID := "-"
|
||||||
|
if g.ProjectFlockKandangID != nil {
|
||||||
|
pfkID = fmt.Sprintf("%d", *g.ProjectFlockKandangID)
|
||||||
|
}
|
||||||
fmt.Fprintf(
|
fmt.Fprintf(
|
||||||
w,
|
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.AreaName,
|
||||||
g.LocationName,
|
g.LocationName,
|
||||||
g.WarehouseName,
|
g.WarehouseName,
|
||||||
g.ProductName,
|
g.ProductName,
|
||||||
|
pfkID,
|
||||||
g.SurvivorID,
|
g.SurvivorID,
|
||||||
g.SurvivorQty,
|
g.SurvivorQty,
|
||||||
g.AbsorbedCount,
|
g.AbsorbedCount,
|
||||||
|
|||||||
@@ -729,6 +729,9 @@ func reflowAndRecalculateProductWarehouse(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("resolve flag group for product_warehouse %d: %w", productWarehouseID, err)
|
return fmt.Errorf("resolve flag group for product_warehouse %d: %w", productWarehouseID, err)
|
||||||
}
|
}
|
||||||
|
if flagGroupCode == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := fifoSvc.Reflow(ctx, commonSvc.FifoStockV2ReflowRequest{
|
if _, err := fifoSvc.Reflow(ctx, commonSvc.FifoStockV2ReflowRequest{
|
||||||
FlagGroupCode: flagGroupCode,
|
FlagGroupCode: flagGroupCode,
|
||||||
@@ -778,6 +781,9 @@ func resolveFlagGroupByProductWarehouse(ctx context.Context, tx *gorm.DB, produc
|
|||||||
Order("rr.id ASC").
|
Order("rr.id ASC").
|
||||||
Limit(1).
|
Limit(1).
|
||||||
Take(&selected).Error
|
Take(&selected).Error
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -400,9 +400,11 @@ 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 {
|
func runPrechecks(ctx context.Context, db *gorm.DB, rows []planRow, refs *referencePlan, opts *options) error {
|
||||||
|
if opts.DeleteWrongWarehouses {
|
||||||
if err := ensureNoBlockedWarehouseRefs(ctx, db, rows, refs.BlockedWarehouseRefs); err != nil {
|
if err := ensureNoBlockedWarehouseRefs(ctx, db, rows, refs.BlockedWarehouseRefs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if err := ensureNoPurchaseItemWarehouseConflicts(ctx, db, rows); err != nil {
|
if err := ensureNoPurchaseItemWarehouseConflicts(ctx, db, rows); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -813,6 +815,9 @@ func reflowAndRecalculateProductWarehouse(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("resolve flag group for product_warehouse %d: %w", productWarehouseID, err)
|
return fmt.Errorf("resolve flag group for product_warehouse %d: %w", productWarehouseID, err)
|
||||||
}
|
}
|
||||||
|
if flagGroupCode == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := fifoSvc.Reflow(ctx, commonSvc.FifoStockV2ReflowRequest{
|
if _, err := fifoSvc.Reflow(ctx, commonSvc.FifoStockV2ReflowRequest{
|
||||||
FlagGroupCode: flagGroupCode,
|
FlagGroupCode: flagGroupCode,
|
||||||
@@ -862,6 +867,9 @@ func resolveFlagGroupByProductWarehouse(ctx context.Context, tx *gorm.DB, produc
|
|||||||
Order("rr.id ASC").
|
Order("rr.id ASC").
|
||||||
Limit(1).
|
Limit(1).
|
||||||
Take(&selected).Error
|
Take(&selected).Error
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ type sourceWarehouseCheck struct {
|
|||||||
KandangName string `json:"kandang_name"`
|
KandangName string `json:"kandang_name"`
|
||||||
SourceWarehouseID uint `json:"source_warehouse_id"`
|
SourceWarehouseID uint `json:"source_warehouse_id"`
|
||||||
SourceWarehouseName string `json:"source_warehouse_name"`
|
SourceWarehouseName string `json:"source_warehouse_name"`
|
||||||
Case string `json:"case"`
|
Case string `json:"case" gorm:"column:case_type"`
|
||||||
DeletedAt *string `json:"deleted_at"`
|
DeletedAt *string `json:"deleted_at"`
|
||||||
StockInProductWH float64 `json:"stock_in_product_wh"`
|
StockInProductWH float64 `json:"stock_in_product_wh"`
|
||||||
ActivePurchaseItems int64 `json:"active_purchase_items"`
|
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) {
|
func verifySourceWarehouses(ctx context.Context, db *gorm.DB, opts *options) ([]sourceWarehouseCheck, error) {
|
||||||
filters := buildFilters(opts)
|
filters, args := buildFilters(opts)
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
WITH case_a_warehouses AS (
|
WITH case_a_warehouses AS (
|
||||||
-- Case A: Kandang-level warehouses (type != 'LOKASI' or NULL)
|
-- 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))
|
`, andClause(filters))
|
||||||
|
|
||||||
rows := make([]sourceWarehouseCheck, 0)
|
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
|
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) {
|
func verifyDestinationWarehouses(ctx context.Context, db *gorm.DB, opts *options, sourceChecks []sourceWarehouseCheck) ([]destinationWarehouseCheck, error) {
|
||||||
filters := buildFilters(opts)
|
filters, args := buildFilters(opts)
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
SELECT
|
SELECT
|
||||||
a.name AS area_name,
|
a.name AS area_name,
|
||||||
@@ -270,13 +270,11 @@ SELECT
|
|||||||
COALESCE(SUM(sl.stock), 0) AS stock_logs_total,
|
COALESCE(SUM(sl.stock), 0) AS stock_logs_total,
|
||||||
COUNT(DISTINCT sl.id) AS stock_logs_count
|
COUNT(DISTINCT sl.id) AS stock_logs_count
|
||||||
FROM warehouses fw
|
FROM warehouses fw
|
||||||
JOIN locations loc ON loc.id = fw.location_id
|
JOIN locations kl ON kl.id = fw.location_id
|
||||||
JOIN areas a ON a.id = loc.area_id
|
JOIN areas a ON a.id = kl.area_id
|
||||||
JOIN kandangs k ON k.location_id = fw.location_id AND k.deleted_at IS NULL
|
JOIN product_warehouses pw ON pw.warehouse_id = fw.id
|
||||||
JOIN locations kl ON kl.id = k.location_id
|
JOIN products p ON p.id = pw.product_id
|
||||||
JOIN products p ON true
|
|
||||||
JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = 'products' AND UPPER(f.name) IN ('PAKAN', 'OVK')
|
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
|
LEFT JOIN stock_logs sl ON sl.product_warehouse_id = pw.id
|
||||||
WHERE fw.deleted_at IS NULL
|
WHERE fw.deleted_at IS NULL
|
||||||
AND UPPER(COALESCE(fw.type, '')) = 'LOKASI'
|
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))
|
`, andClause(filters))
|
||||||
|
|
||||||
rows := make([]destinationWarehouseCheck, 0)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,15 +369,18 @@ func verifyOrphanedReferences(ctx context.Context, db *gorm.DB, sourceChecks []s
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildFilters(opts *options) []string {
|
func buildFilters(opts *options) ([]string, []any) {
|
||||||
filters := make([]string, 0, 2)
|
filters := make([]string, 0, 2)
|
||||||
|
args := make([]any, 0, 2)
|
||||||
if opts.AreaName != "" {
|
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 != "" {
|
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 {
|
func andClause(filters []string) string {
|
||||||
|
|||||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
Reference in New Issue
Block a user