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>
This commit is contained in:
Matt Ciaccio
2026-05-04 22:56:18 +02:00
parent 089f4a67a4
commit d62822c284
9 changed files with 938 additions and 47 deletions

View File

@@ -7,7 +7,7 @@
*
* Auth: `xc-token` header per NocoDB v2 API.
*
* The shape returned is a verbatim record of the row's fields caller
* The shape returned is a verbatim record of the row's fields - caller
* is responsible for mapping to the new schema via `nocodb-transform.ts`.
*/
@@ -34,7 +34,7 @@ export function loadNocoDbConfig(env: NodeJS.ProcessEnv = process.env): NocoDbCo
// ─── Table identifiers ──────────────────────────────────────────────────────
//
// These IDs are stable per the NocoDB base they were captured during the
// These IDs are stable per the NocoDB base - they were captured during the
// 2026-05-03 audit and won't change unless the base is rebuilt. If the
// base is reset, regenerate them from `getTablesList`.
export const NOCO_TABLES = {
@@ -67,7 +67,7 @@ export type NocoDbRow = Record<string, unknown> & { Id: number };
/**
* Fetch all rows from a NocoDB table. Auto-paginates until the API
* reports `isLastPage`. The legacy base is small (252 Interests rows
* being the largest table) so we keep this simple no streaming.
* being the largest table) so we keep this simple - no streaming.
*/
export async function fetchAllRows(
tableId: string,
@@ -95,7 +95,7 @@ export async function fetchAllRows(
if (!res.ok) {
throw new Error(
`NocoDB fetch failed: ${res.status} ${res.statusText} table ${tableId} page ${page}`,
`NocoDB fetch failed: ${res.status} ${res.statusText} - table ${tableId} page ${page}`,
);
}
@@ -110,7 +110,7 @@ export async function fetchAllRows(
}
/**
* Convenience snapshot pulls every table the migration cares about
* Convenience snapshot - pulls every table the migration cares about
* in parallel. Returned shape is the input the transform layer expects.
*/
export interface NocoDbSnapshot {