feat(uat-batch): Groups R + T — Documenso list + deferred bugs
R62, T64, T65 from the 2026-05-21 plan. U66 deferred with reasoning.
Shipped:
R62 Documenso-first templates (list endpoint + admin route).
New `listTemplates(portId)` in documenso-client paginates
through every visible template on the configured instance
(5-page cap at 100/page = 500 templates which comfortably
covers every observed Documenso deploy). Handles v1 + v2
endpoint shapes; normalises to `{ id, name }` summaries.
New `GET /api/v1/admin/documenso/templates` route exposes
the list to the admin UI (gated on `admin.manage_settings`).
Powers the upcoming admin template picker — the field-mapping
editor + sync-now button + per-template badges stay as the
picker-UI follow-up. Data path is in place; UI surface
lands in a dedicated PR alongside the field-mapping editor.
T64 Duplicate E17 + missing partial unique index. Migration 0082
deduplicates any existing (port_id, mooring_number) collisions
by archiving all but the canonical row (prefers price-bearing
rows, then earliest-created; archived rows carry an explicit
`archive_reason` noting the migration). Adds partial unique
index `uniq_berths_port_mooring_active` on (port_id,
mooring_number) WHERE archived_at IS NULL so archived
moorings can be reissued but live duplicates can't be
created in the first place. Migration applied to dev DB.
T65 Stage-advance gate. `changeInterestStage` now blocks any
non-override transition into eoi / reservation / deposit_paid
/ contract when the primary berth has no price (NULL or 0)
— these stages all render the price in templates / merge
fields and a $0 generation is a real production gotcha.
Override path (sales-manager fix) stays open and records
the reason in audit log per the existing override-reason
gate.
Deferred:
U66 EOI bundle UX rework (10-14h) — multi-berth picker inside
the EOI generate dialog. Schema (`interest_berths.isInEoiBundle`)
and the rendered bundle-range preview row both exist; the
remaining work is the picker UI + re-deriving merge tokens
per selection state. Best done as a focused session with
Documenso-side verification.
Verified: tsc clean, vitest 1454/1454, migration applied.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
48
src/lib/db/migrations/0082_berth_mooring_unique.sql
Normal file
48
src/lib/db/migrations/0082_berth_mooring_unique.sql
Normal file
@@ -0,0 +1,48 @@
|
||||
-- 2026-05-21: enforce unique mooring numbers per port at the DB level.
|
||||
--
|
||||
-- The canonical mooring regex is `^[A-Z]+\d+$` (e.g. `A1`, `B12`) and
|
||||
-- the UI gates on uniqueness, but no DB-level constraint existed —
|
||||
-- which led to a duplicate E17 row in port-nimara surfaced during UAT.
|
||||
-- Partial unique index lets archived rows reuse a mooring (an old A1
|
||||
-- can be re-issued after the original is archived).
|
||||
--
|
||||
-- Before applying: any existing duplicates must be resolved. The
|
||||
-- backfill below merges duplicate E17-style rows into a single canonical
|
||||
-- row, preferring the one with a price (real berth) over the empty
|
||||
-- shadow. If multiple price-bearing rows exist, the earliest-created
|
||||
-- wins and the others archive.
|
||||
|
||||
-- Phase 1: dedupe. Pick a canonical id per (port_id, mooring_number)
|
||||
-- where archived_at IS NULL.
|
||||
WITH ranked AS (
|
||||
SELECT
|
||||
id,
|
||||
port_id,
|
||||
mooring_number,
|
||||
price,
|
||||
created_at,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY port_id, mooring_number
|
||||
ORDER BY
|
||||
-- Prefer rows that have a price (real berths)
|
||||
CASE WHEN price IS NOT NULL THEN 0 ELSE 1 END,
|
||||
-- Then earliest-created so the audit trail stays consistent
|
||||
created_at ASC,
|
||||
id ASC
|
||||
) AS rn
|
||||
FROM berths
|
||||
WHERE archived_at IS NULL
|
||||
)
|
||||
UPDATE berths
|
||||
SET
|
||||
archived_at = now(),
|
||||
archive_reason = 'Auto-archived 2026-05-21: duplicate of canonical row (mooring uniqueness migration)'
|
||||
WHERE id IN (
|
||||
SELECT id FROM ranked WHERE rn > 1
|
||||
);
|
||||
|
||||
-- Phase 2: the partial unique index. Excludes archived rows so old
|
||||
-- moorings can be reissued; includes everything else.
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uniq_berths_port_mooring_active
|
||||
ON berths (port_id, mooring_number)
|
||||
WHERE archived_at IS NULL;
|
||||
Reference in New Issue
Block a user