Commit Graph

3 Commits

Author SHA1 Message Date
Matt Ciaccio
d62822c284 fix(migration): NocoDB import safety + dedup helpers + lead-source backfill
migration-apply: residential client + interest inserts now wrap in
db.transaction so a partial failure can't leave an orphan client
row without its interest (or vice versa).

migration-transform: buildPlannedDocument returns null when there
are no signers so the apply pass doesn't try to send a Documenso
envelope without recipients. mapDocumentStatus gets an explicit
"Awaiting Further Details" branch that no longer auto-promotes via
stale sign-time fields. parseFlexibleDate handles ISO and DD-MM-YYYY
inputs uniformly.

backfill-legacy-lead-source: chunk UPDATE WHERE clause now
isNull(source) on top of the inArray match, so a re-run can't
overwrite a more accurate source written between batches.

Adds 235 lines of vitest coverage on migration-transform.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 22:56:18 +02:00
Matt Ciaccio
c612bbdfd9 fix(migration): legacy bare-mooring lookup + port-nimara berth backfill
Some checks failed
Build & Push Docker Images / lint (push) Failing after 1m12s
Build & Push Docker Images / build-and-push (push) Has been skipped
Two issues surfaced when applying the migration to dev:

1. Mooring number format mismatch
   The legacy NocoDB Interests table writes bare mooring strings
   ("D32", "B16", "A4"), but the new berths table (mirroring the
   NocoDB Berths snapshot) uses zero-padded dashed form ("D-32",
   "B-16", "A-04"). The interest→berth lookup missed every reference.

   migration-apply.ts now tries the literal value first, then falls
   back to a normalized form via `normalizeLegacyMooring(raw)`:
     "D32" -> "D-32"
     "A4"  -> "A-04"
     "E18" -> "E-18"
   Multi-mooring strings ("A3, D30") are left as-is so they surface in
   the warnings list for human review rather than silently picking one.

2. port-nimara only had the 12 hand-rolled seed berths, not the 117-
   berth NocoDB snapshot
   The mobile-foundation seed only places those 12 in port-nimara; the
   117-berth snapshot was added later but only seeded into Marina
   Azzurra (the secondary test port). Migrated interests reference
   moorings well beyond A-01..D-03, so most lookups failed.

   New scripts/load-berths-to-port-nimara.ts: idempotently loads any
   missing snapshot berths into port-nimara without disturbing the
   existing 12 (skips moorings that already exist). Run once;
   subsequent runs no-op.

Result of full migration run on dev:
  237 clients inserted (out of 245 total — 8 from prior seed)
  406 contacts, 52 addresses, 38 yachts, 252 interests
  27 interest→berth links resolved (only 13 source rows had a Berth
  field set in NocoDB to begin with — most legacy interests are early
  inquiries with no berth assignment)
  1 unresolved warning: source=277 has multi-mooring "A3, D30"

Verified in UI:
  /port-nimara/clients shows real names (John-michael Seelye, Reza
  Amjad, Etiennette Clamouze, …)
  /port-nimara/clients/<id> renders contacts (gmail.com addresses,
  E.164 phones), tab counts (Interests N, Yachts N), pipeline summary
  Dashboard: 245 clients, 266 active interests, $46.5M pipeline value
  Pipeline funnel chart now shows real distribution (180 Open, 45
  EOI Signed, dropoff through stages)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:05:11 +02:00
Matt Ciaccio
c45aac551d feat(dedup): wire --apply path for NocoDB migration
Some checks failed
Build & Push Docker Images / lint (push) Successful in 1m12s
Build & Push Docker Images / build-and-push (push) Failing after 3m41s
Completes the migration script's apply phase, which was stubbed at
the P3 ship to defer until after the runtime surfaces (P2) and the
comms safety net were in place. Both prerequisites just landed on
main, so this unblocks the actual data import.

src/lib/dedup/migration-apply.ts (new):
  Idempotent apply driver. Walks the MigrationPlan, inserting clients,
  contacts, addresses, yacht stubs, and interests, threading every
  insert through the migration_source_links ledger so re-runs against
  the same data are safe. Per-entity transactions (not one giant
  transaction) so partial-failure resumption is just "run again."

  Per-entity behavior:
    - clients: idempotent on (source_system, source_id, target_type=client)
      across the entire dedup cluster — if any source row already maps
      to a client, reuse that record.
    - contacts: bulk insert, primary email + primary phone independent.
    - addresses: bulk insert, port_id required (schema enforces it),
      first address marked primary when multiple.
    - yachts: minimal stub when the legacy interest had a yachtName,
      currentOwnerType=client + currentOwnerId=migrated client. Linked
      via migration_source_links target_type=yacht.
    - interests: looks up berthId via mooring number, yachtId via the
      stub above. Carries Documenso ID forward when present.

  surnameToken from PlannedClient is dropped on insert (it's a dedup
  blocking-index artifact; runtime dedup re-derives from fullName).

scripts/migrate-from-nocodb.ts:
  - Removes the "not yet implemented" guard for --apply.
  - Adds EMAIL_REDIRECT_TO precondition gate: --apply errors out unless
    the env var is set, OR --unsafe-skip-redirect-check is also passed
    (production cutover only). Refers to docs/operations/outbound-comms-safety.md.
  - Re-fetches NocoDB at apply time (rather than reading a saved report
    dir) so the data is always fresh. Re-running is safe via the
    idempotency ledger.
  - Resolves target port via --port-slug (or first port if omitted).
  - Generates a UUID applyId tagged on every link, which pairs with a
    future --rollback flag.
  - Apply summary prints inserted/skipped counts per entity type plus
    the first 20 warnings.

Verification: 0 tsc errors, 926/926 vitest passing, lint clean.
The actual end-to-end run requires NOCODB_URL + NOCODB_TOKEN in .env
which aren't configured in this checkout; that's the operator's next
step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:53:04 +02:00