package main import ( "context" "flag" "fmt" "log" "math" "os" "gitlab.com/mbugroup/lti-api.git/internal/config" "gitlab.com/mbugroup/lti-api.git/internal/database" "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo" "gorm.io/gorm" ) type mismatchRow struct { ChickinID uint `gorm:"column:chickin_id"` ProjectFlockKandang uint `gorm:"column:project_flock_kandang_id"` ProductWarehouseID uint `gorm:"column:product_warehouse_id"` UsageQty float64 `gorm:"column:usage_qty"` TraceQty float64 `gorm:"column:trace_qty"` } func main() { var projectFlockKandangID uint flag.UintVar(&projectFlockKandangID, "project-flock-kandang-id", 0, "Optional project flock kandang scope") flag.Parse() ctx := context.Background() db := database.Connect(config.DBHost, config.DBName) rows, err := loadTraceMismatches(ctx, db, projectFlockKandangID) if err != nil { log.Fatalf("failed to load trace mismatches: %v", err) } activeConsumeRows, err := countActiveConsumeProjectChickin(ctx, db, projectFlockKandangID) if err != nil { log.Fatalf("failed to count active consume rows: %v", err) } fmt.Printf("Scope project_flock_kandang_id=%d\n", projectFlockKandangID) fmt.Printf("Mismatched chickin trace rows: %d\n", len(rows)) fmt.Printf("Active PROJECT_CHICKIN consume rows: %d\n", activeConsumeRows) if len(rows) > 0 { for _, row := range rows { fmt.Printf( "MISMATCH chickin_id=%d pfk=%d pw=%d usage=%.3f trace=%.3f diff=%.3f\n", row.ChickinID, row.ProjectFlockKandang, row.ProductWarehouseID, row.UsageQty, row.TraceQty, row.TraceQty-row.UsageQty, ) } } if len(rows) > 0 || activeConsumeRows > 0 { os.Exit(1) } } func loadTraceMismatches(ctx context.Context, db *gorm.DB, projectFlockKandangID uint) ([]mismatchRow, error) { query := db.WithContext(ctx). Table("project_chickins pc"). Select(` pc.id AS chickin_id, pc.project_flock_kandang_id, pc.product_warehouse_id, COALESCE(pc.usage_qty, 0) AS usage_qty, COALESCE(SUM(sa.qty), 0) AS trace_qty `). Joins(` LEFT JOIN stock_allocations sa ON sa.usable_type = ? AND sa.usable_id = pc.id AND sa.status = 'ACTIVE' AND sa.allocation_purpose = 'TRACE_CHICKIN' `, fifo.UsableKeyProjectChickin.String()). Where("pc.deleted_at IS NULL"). Where("COALESCE(pc.usage_qty,0) > 0"). Group("pc.id, pc.project_flock_kandang_id, pc.product_warehouse_id, pc.usage_qty") if projectFlockKandangID > 0 { query = query.Where("pc.project_flock_kandang_id = ?", projectFlockKandangID) } rows := make([]mismatchRow, 0) if err := query.Scan(&rows).Error; err != nil { return nil, err } out := make([]mismatchRow, 0, len(rows)) for _, row := range rows { if math.Abs(row.TraceQty-row.UsageQty) > 1e-3 { out = append(out, row) } } return out, nil } func countActiveConsumeProjectChickin(ctx context.Context, db *gorm.DB, projectFlockKandangID uint) (int64, error) { q := db.WithContext(ctx). Table("stock_allocations sa"). Joins("JOIN project_chickins pc ON pc.id = sa.usable_id"). Where("sa.usable_type = ?", fifo.UsableKeyProjectChickin.String()). Where("sa.status = 'ACTIVE'"). Where("sa.allocation_purpose = 'CONSUME'") if projectFlockKandangID > 0 { q = q.Where("pc.project_flock_kandang_id = ?", projectFlockKandangID) } var count int64 if err := q.Count(&count).Error; err != nil { return 0, err } return count, nil }