package processing import ( "context" "fmt" "strings" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) // SyncCompanyA1Opts controls admin Sync A1 (dump category backfill + Fix hygiene). // Default is backfill-categories + hygiene — never a full wipe/reimport. type SyncCompanyA1Opts struct { FixCompanyCatalogOpts // MySQLDumpPath is an explicit dump path; empty triggers auto-detect // (SEED_A1_MYSQL_DUMP + MySQLDumpCandidates). MySQLDumpPath string // SkipDumpBackfill skips dump scan even when a dump is found. SkipDumpBackfill bool } // SyncCompanyA1Result combines dump category backfill with FixCompanyCatalogResult. type SyncCompanyA1Result struct { FixCompanyCatalogResult DumpFound bool `json:"dump_found"` DumpPath string `json:"dump_path,omitempty"` DumpSkippedReason string `json:"dump_skipped_reason,omitempty"` DumpPairs int `json:"dump_pairs"` DumpMappedUpdated int64 `json:"dump_mapped_updated"` DumpProcessedUpdated int64 `json:"dump_processed_updated"` PurgedOtherRaw int64 `json:"purged_other_raw"` PurgedOtherPP int64 `json:"purged_other_pp"` // DumpStatus is a short label for flash UI ({dump_status}). DumpStatus string `json:"dump_status"` } // SyncCompanyA1 runs dump→category backfill when a MySQL dump is available, then // FixCompanyCatalog hygiene. Does not wipe or reimport the catalog. func SyncCompanyA1(ctx context.Context, pool *pgxpool.Pool, companyID uuid.UUID, companyName string, a1Cohort bool, opts SyncCompanyA1Opts) (SyncCompanyA1Result, error) { out := SyncCompanyA1Result{ FixCompanyCatalogResult: FixCompanyCatalogResult{ CompanyID: companyID, CompanyName: companyName, A1Cohort: a1Cohort, }, DumpStatus: "skipped", } if pool == nil { return out, fmt.Errorf("nil pool") } dumpPath := strings.TrimSpace(opts.MySQLDumpPath) if !opts.SkipDumpBackfill { resolved := ResolveMySQLDumpPath(dumpPath) if resolved != "" { out.DumpFound = true out.DumpPath = resolved dumpRes, err := BackfillCategoriesFromMySQLDump(ctx, pool, companyID, resolved) if err != nil { return out, fmt.Errorf("dump category backfill: %w", err) } out.DumpPairs = dumpRes.DumpPairs out.DumpMappedUpdated = dumpRes.MappedUpdated out.DumpProcessedUpdated = dumpRes.ProcessedUpdated out.DumpStatus = "ok" rawN, ppN, err := PurgeA1FixtureEANsFromOtherTenants(ctx, pool, companyID) if err != nil { return out, fmt.Errorf("purge fixture eans: %w", err) } out.PurgedOtherRaw = rawN out.PurgedOtherPP = ppN } else if dumpPath != "" { out.DumpSkippedReason = fmt.Sprintf("mysql dump not found at %q and no auto-detect match", dumpPath) out.DumpStatus = "missing" } else { out.DumpSkippedReason = "no MySQL dump (set SEED_A1_MYSQL_DUMP or place descrybe_new.sql in scripts/seed or ~/Downloads); continuing with DB hygiene only" out.DumpStatus = "missing" } } else { out.DumpSkippedReason = "dump backfill skipped by request" out.DumpStatus = "skipped" } fixRes, err := FixCompanyCatalog(ctx, pool, companyID, companyName, a1Cohort, opts.FixCompanyCatalogOpts) if err != nil { return out, err } out.FixCompanyCatalogResult = fixRes return out, nil }