Files
pn-new-crm/src/app/api/v1/clients/[id]/field-history/route.ts

36 lines
1.1 KiB
TypeScript
Raw Normal View History

feat(uat-batch): M43 — form-template bindings + inline field history Closes plan item 43 (Form-template fields bind to Interest/Client data — autofill, override-preservation history, dual-surface audit trail). Phase 1 — Editor: - New bindable-fields catalog (src/lib/templates/bindable-fields.ts): client/yacht/interest paths, each tagged with the entity, column, and default input type. Source of truth for what can bind + what interest_field_history.field_path strings the writers should use. - formFieldSchema gains optional bindTo, validated against the catalog as an allow-list (no arbitrary paths sneak through). - form-template-form admin sheet: per-field "Bind to" dropdown grouped by entity, auto-derives label/key/type when a binding is picked, shows "Autofills from + writes back to {label} . {path}" badge. Phase 2 — Runtime + history writes: - supplemental-forms.service.applySubmission already wrote interest_field_history rows for client name/email/address from the earlier 0081 migration session. Extended to also capture phone + yacht (name, length, width, draft) diffs that were silently going to the entity without an audit row, and to push insert-path overrides for the no-existing-address case. - Field paths aligned with the bindable-fields catalog so detail-page lookups work via exact-match WHERE field_path = ?. Phase 3 — Inline history surface: - New /api/v1/clients/[id]/field-history (mirror of the existing interests endpoint). - shared/field-history: FieldHistoryProvider wraps a detail tab and fires a single keyed GET; FieldHistoryIcon consumes the context and renders a small clock affordance only when at least one override exists, opening a popover with the reverse-chrono diff list. - Client + Interest detail Overview tabs wrapped in the provider; EditableRow gains an optional historyPath prop; ContactsEditor renders the icon next to the canonical primary email/phone. 1454/1454 vitest, tsc clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:51:39 +02:00
import { NextResponse } from 'next/server';
import { and, desc, eq } from 'drizzle-orm';
import { withAuth, withPermission } from '@/lib/api/helpers';
import { db } from '@/lib/db';
import { interestFieldHistory } from '@/lib/db/schema';
import { errorResponse } from '@/lib/errors';
/**
* GET /api/v1/clients/[id]/field-history
*
* Returns every supplemental-form override that touched the client (rolling
* up across all of their interests), newest first. Powers the inline clock
* icon + popover on Client detail. Mirrors /interests/[id]/field-history.
*/
export const GET = withAuth(
withPermission('clients', 'view', async (_req, ctx, params) => {
try {
const rows = await db
.select()
.from(interestFieldHistory)
.where(
and(
eq(interestFieldHistory.portId, ctx.portId),
eq(interestFieldHistory.clientId, params.id!),
),
)
.orderBy(desc(interestFieldHistory.createdAt))
.limit(100);
return NextResponse.json({ data: rows });
} catch (error) {
return errorResponse(error);
}
}),
);