Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
import type { Template } from '@pdfme/common';
|
|
|
|
|
|
|
|
|
|
export const interestSummaryTemplate: Template = {
|
2026-03-26 12:06:18 +01:00
|
|
|
basePdf: 'BLANK_PDF' as unknown as string,
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
schemas: [
|
|
|
|
|
[
|
refactor(clients): drop deprecated yacht/company/proxy columns
PR 13: now that all reads are migrated to the dedicated yacht / company
/ membership entities, drop the columns that mirrored them on `clients`:
companyName, isProxy, proxyType, actualOwnerName, relationshipNotes,
yachtName, yachtLength{Ft,M}, yachtWidth{Ft,M}, yachtDraft{Ft,M},
berthSizeDesired.
Migration `0008_loud_ikaris.sql` issues the destructive ALTER TABLE
DROP COLUMN statements. Run `pnpm db:push` (or the migration runner) to
apply.
Caller cleanup (zero behavioral change to remaining flows):
- Drops the legacy `generateEoi` flow entirely (route, service function,
pdfme template, validator schema). The dual-path generate-and-sign
service from PR 11 has fully replaced it; the route was no longer
wired to the UI.
- `clients.service`: company-name search column / WHERE / audit value
removed; search now ranks by full name only.
- `interests.service`: `resolveLeadCategory` reads dimensions from
`yachts` via `interest.yachtId` instead of the dropped
`client.yachtLength{Ft,M}`.
- `record-export`: client-summary now lists yachts via owner-side
lookup (direct + active company memberships); interest-summary fetches
yacht via `interest.yachtId`. Both PDF templates updated to read
yacht details from the new entity.
- `client-detail-header`, `client-picker`, `command-search`,
`search-result-item`, `use-search` hook, `types/domain.ts`,
`search.service` — drop the companyName badge / sub-label / typed
field everywhere it was rendered or fetched.
- `ai.ts` worker: drop the company / yacht context lines from the
prompt (will be re-added later sourced from the new entities).
- `validators/interests.ts`: remove the deprecated public-form flat
yacht/company fields. The route already ignores them.
- `factories.ts`: drop the `isProxy: false` default.
Tests: 652/652 green; type-check clean. The
`security-sensitive-data` tests use `companyName` / `isProxy` as
arbitrary record keys for a generic util — left unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 13:57:54 +02:00
|
|
|
{
|
|
|
|
|
name: 'portName',
|
|
|
|
|
type: 'text',
|
|
|
|
|
position: { x: 20, y: 15 },
|
|
|
|
|
width: 100,
|
|
|
|
|
height: 10,
|
|
|
|
|
fontSize: 16,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'title',
|
|
|
|
|
type: 'text',
|
|
|
|
|
position: { x: 20, y: 30 },
|
|
|
|
|
width: 170,
|
|
|
|
|
height: 8,
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'clientInfo',
|
|
|
|
|
type: 'text',
|
|
|
|
|
position: { x: 20, y: 45 },
|
|
|
|
|
width: 80,
|
|
|
|
|
height: 30,
|
|
|
|
|
fontSize: 9,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'berthInfo',
|
|
|
|
|
type: 'text',
|
|
|
|
|
position: { x: 110, y: 45 },
|
|
|
|
|
width: 80,
|
|
|
|
|
height: 30,
|
|
|
|
|
fontSize: 9,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'stageAndCategory',
|
|
|
|
|
type: 'text',
|
|
|
|
|
position: { x: 20, y: 80 },
|
|
|
|
|
width: 170,
|
|
|
|
|
height: 15,
|
|
|
|
|
fontSize: 9,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'milestones',
|
|
|
|
|
type: 'text',
|
|
|
|
|
position: { x: 20, y: 100 },
|
|
|
|
|
width: 170,
|
|
|
|
|
height: 40,
|
|
|
|
|
fontSize: 8,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'notes',
|
|
|
|
|
type: 'text',
|
|
|
|
|
position: { x: 20, y: 145 },
|
|
|
|
|
width: 170,
|
|
|
|
|
height: 30,
|
|
|
|
|
fontSize: 9,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'recentTimeline',
|
|
|
|
|
type: 'text',
|
|
|
|
|
position: { x: 20, y: 180 },
|
|
|
|
|
width: 170,
|
|
|
|
|
height: 85,
|
|
|
|
|
fontSize: 8,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'generatedAt',
|
|
|
|
|
type: 'text',
|
|
|
|
|
position: { x: 20, y: 275 },
|
|
|
|
|
width: 170,
|
|
|
|
|
height: 6,
|
|
|
|
|
fontSize: 7,
|
|
|
|
|
},
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function formatDate(d: Date | string | null | undefined): string {
|
|
|
|
|
if (!d) return 'N/A';
|
|
|
|
|
return new Date(d).toLocaleDateString('en-GB');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function buildInterestSummaryInputs(
|
2026-03-26 12:06:18 +01:00
|
|
|
interest: Record<string, unknown>,
|
|
|
|
|
client: Record<string, unknown>,
|
refactor(clients): drop deprecated yacht/company/proxy columns
PR 13: now that all reads are migrated to the dedicated yacht / company
/ membership entities, drop the columns that mirrored them on `clients`:
companyName, isProxy, proxyType, actualOwnerName, relationshipNotes,
yachtName, yachtLength{Ft,M}, yachtWidth{Ft,M}, yachtDraft{Ft,M},
berthSizeDesired.
Migration `0008_loud_ikaris.sql` issues the destructive ALTER TABLE
DROP COLUMN statements. Run `pnpm db:push` (or the migration runner) to
apply.
Caller cleanup (zero behavioral change to remaining flows):
- Drops the legacy `generateEoi` flow entirely (route, service function,
pdfme template, validator schema). The dual-path generate-and-sign
service from PR 11 has fully replaced it; the route was no longer
wired to the UI.
- `clients.service`: company-name search column / WHERE / audit value
removed; search now ranks by full name only.
- `interests.service`: `resolveLeadCategory` reads dimensions from
`yachts` via `interest.yachtId` instead of the dropped
`client.yachtLength{Ft,M}`.
- `record-export`: client-summary now lists yachts via owner-side
lookup (direct + active company memberships); interest-summary fetches
yacht via `interest.yachtId`. Both PDF templates updated to read
yacht details from the new entity.
- `client-detail-header`, `client-picker`, `command-search`,
`search-result-item`, `use-search` hook, `types/domain.ts`,
`search.service` — drop the companyName badge / sub-label / typed
field everywhere it was rendered or fetched.
- `ai.ts` worker: drop the company / yacht context lines from the
prompt (will be re-added later sourced from the new entities).
- `validators/interests.ts`: remove the deprecated public-form flat
yacht/company fields. The route already ignores them.
- `factories.ts`: drop the `isProxy: false` default.
Tests: 652/652 green; type-check clean. The
`security-sensitive-data` tests use `companyName` / `isProxy` as
arbitrary record keys for a generic util — left unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 13:57:54 +02:00
|
|
|
yacht: Record<string, unknown> | null,
|
2026-03-26 12:06:18 +01:00
|
|
|
berth: Record<string, unknown> | null,
|
|
|
|
|
timeline: Record<string, unknown>[],
|
|
|
|
|
port: Record<string, unknown>,
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
): Record<string, string> {
|
|
|
|
|
const clientInfo = [
|
|
|
|
|
`Name: ${client?.fullName ?? 'N/A'}`,
|
refactor(clients): drop deprecated yacht/company/proxy columns
PR 13: now that all reads are migrated to the dedicated yacht / company
/ membership entities, drop the columns that mirrored them on `clients`:
companyName, isProxy, proxyType, actualOwnerName, relationshipNotes,
yachtName, yachtLength{Ft,M}, yachtWidth{Ft,M}, yachtDraft{Ft,M},
berthSizeDesired.
Migration `0008_loud_ikaris.sql` issues the destructive ALTER TABLE
DROP COLUMN statements. Run `pnpm db:push` (or the migration runner) to
apply.
Caller cleanup (zero behavioral change to remaining flows):
- Drops the legacy `generateEoi` flow entirely (route, service function,
pdfme template, validator schema). The dual-path generate-and-sign
service from PR 11 has fully replaced it; the route was no longer
wired to the UI.
- `clients.service`: company-name search column / WHERE / audit value
removed; search now ranks by full name only.
- `interests.service`: `resolveLeadCategory` reads dimensions from
`yachts` via `interest.yachtId` instead of the dropped
`client.yachtLength{Ft,M}`.
- `record-export`: client-summary now lists yachts via owner-side
lookup (direct + active company memberships); interest-summary fetches
yacht via `interest.yachtId`. Both PDF templates updated to read
yacht details from the new entity.
- `client-detail-header`, `client-picker`, `command-search`,
`search-result-item`, `use-search` hook, `types/domain.ts`,
`search.service` — drop the companyName badge / sub-label / typed
field everywhere it was rendered or fetched.
- `ai.ts` worker: drop the company / yacht context lines from the
prompt (will be re-added later sourced from the new entities).
- `validators/interests.ts`: remove the deprecated public-form flat
yacht/company fields. The route already ignores them.
- `factories.ts`: drop the `isProxy: false` default.
Tests: 652/652 green; type-check clean. The
`security-sensitive-data` tests use `companyName` / `isProxy` as
arbitrary record keys for a generic util — left unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 13:57:54 +02:00
|
|
|
yacht?.name ? `Yacht: ${yacht.name}` : null,
|
|
|
|
|
yacht?.lengthFt
|
|
|
|
|
? `Length: ${yacht.lengthFt}ft${yacht.lengthM ? ` / ${yacht.lengthM}m` : ''}`
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
: null,
|
|
|
|
|
]
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.join('\n');
|
|
|
|
|
|
|
|
|
|
const berthInfo = berth
|
|
|
|
|
? [
|
|
|
|
|
`Mooring: ${berth.mooringNumber}`,
|
|
|
|
|
berth.area ? `Area: ${berth.area}` : null,
|
|
|
|
|
berth.lengthFt ? `Length: ${berth.lengthFt}ft` : null,
|
refactor(clients): drop deprecated yacht/company/proxy columns
PR 13: now that all reads are migrated to the dedicated yacht / company
/ membership entities, drop the columns that mirrored them on `clients`:
companyName, isProxy, proxyType, actualOwnerName, relationshipNotes,
yachtName, yachtLength{Ft,M}, yachtWidth{Ft,M}, yachtDraft{Ft,M},
berthSizeDesired.
Migration `0008_loud_ikaris.sql` issues the destructive ALTER TABLE
DROP COLUMN statements. Run `pnpm db:push` (or the migration runner) to
apply.
Caller cleanup (zero behavioral change to remaining flows):
- Drops the legacy `generateEoi` flow entirely (route, service function,
pdfme template, validator schema). The dual-path generate-and-sign
service from PR 11 has fully replaced it; the route was no longer
wired to the UI.
- `clients.service`: company-name search column / WHERE / audit value
removed; search now ranks by full name only.
- `interests.service`: `resolveLeadCategory` reads dimensions from
`yachts` via `interest.yachtId` instead of the dropped
`client.yachtLength{Ft,M}`.
- `record-export`: client-summary now lists yachts via owner-side
lookup (direct + active company memberships); interest-summary fetches
yacht via `interest.yachtId`. Both PDF templates updated to read
yacht details from the new entity.
- `client-detail-header`, `client-picker`, `command-search`,
`search-result-item`, `use-search` hook, `types/domain.ts`,
`search.service` — drop the companyName badge / sub-label / typed
field everywhere it was rendered or fetched.
- `ai.ts` worker: drop the company / yacht context lines from the
prompt (will be re-added later sourced from the new entities).
- `validators/interests.ts`: remove the deprecated public-form flat
yacht/company fields. The route already ignores them.
- `factories.ts`: drop the `isProxy: false` default.
Tests: 652/652 green; type-check clean. The
`security-sensitive-data` tests use `companyName` / `isProxy` as
arbitrary record keys for a generic util — left unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 13:57:54 +02:00
|
|
|
berth.price
|
|
|
|
|
? `Price: ${berth.priceCurrency ?? 'USD'} ${Number(berth.price).toLocaleString()}`
|
|
|
|
|
: null,
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
`Status: ${berth.status ?? 'available'}`,
|
|
|
|
|
]
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.join('\n')
|
|
|
|
|
: 'No berth linked';
|
|
|
|
|
|
|
|
|
|
const stageAndCategory = [
|
|
|
|
|
`Stage: ${interest.pipelineStage ?? 'open'}`,
|
|
|
|
|
interest.leadCategory ? `Category: ${interest.leadCategory}` : null,
|
|
|
|
|
interest.source ? `Source: ${interest.source}` : null,
|
|
|
|
|
interest.eoiStatus ? `EOI status: ${interest.eoiStatus}` : null,
|
|
|
|
|
interest.contractStatus ? `Contract: ${interest.contractStatus}` : null,
|
|
|
|
|
interest.depositStatus ? `Deposit: ${interest.depositStatus}` : null,
|
|
|
|
|
]
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.join(' | ');
|
|
|
|
|
|
|
|
|
|
const milestones = [
|
2026-03-26 12:29:55 +01:00
|
|
|
`First contact: ${formatDate(interest.dateFirstContact as Date | string | null | undefined)}`,
|
|
|
|
|
`Last contact: ${formatDate(interest.dateLastContact as Date | string | null | undefined)}`,
|
|
|
|
|
`EOI sent: ${formatDate(interest.dateEoiSent as Date | string | null | undefined)}`,
|
|
|
|
|
`EOI signed: ${formatDate(interest.dateEoiSigned as Date | string | null | undefined)}`,
|
|
|
|
|
`Contract sent: ${formatDate(interest.dateContractSent as Date | string | null | undefined)}`,
|
|
|
|
|
`Contract signed: ${formatDate(interest.dateContractSigned as Date | string | null | undefined)}`,
|
|
|
|
|
`Deposit received: ${formatDate(interest.dateDepositReceived as Date | string | null | undefined)}`,
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
].join('\n');
|
|
|
|
|
|
refactor(clients): drop deprecated yacht/company/proxy columns
PR 13: now that all reads are migrated to the dedicated yacht / company
/ membership entities, drop the columns that mirrored them on `clients`:
companyName, isProxy, proxyType, actualOwnerName, relationshipNotes,
yachtName, yachtLength{Ft,M}, yachtWidth{Ft,M}, yachtDraft{Ft,M},
berthSizeDesired.
Migration `0008_loud_ikaris.sql` issues the destructive ALTER TABLE
DROP COLUMN statements. Run `pnpm db:push` (or the migration runner) to
apply.
Caller cleanup (zero behavioral change to remaining flows):
- Drops the legacy `generateEoi` flow entirely (route, service function,
pdfme template, validator schema). The dual-path generate-and-sign
service from PR 11 has fully replaced it; the route was no longer
wired to the UI.
- `clients.service`: company-name search column / WHERE / audit value
removed; search now ranks by full name only.
- `interests.service`: `resolveLeadCategory` reads dimensions from
`yachts` via `interest.yachtId` instead of the dropped
`client.yachtLength{Ft,M}`.
- `record-export`: client-summary now lists yachts via owner-side
lookup (direct + active company memberships); interest-summary fetches
yacht via `interest.yachtId`. Both PDF templates updated to read
yacht details from the new entity.
- `client-detail-header`, `client-picker`, `command-search`,
`search-result-item`, `use-search` hook, `types/domain.ts`,
`search.service` — drop the companyName badge / sub-label / typed
field everywhere it was rendered or fetched.
- `ai.ts` worker: drop the company / yacht context lines from the
prompt (will be re-added later sourced from the new entities).
- `validators/interests.ts`: remove the deprecated public-form flat
yacht/company fields. The route already ignores them.
- `factories.ts`: drop the `isProxy: false` default.
Tests: 652/652 green; type-check clean. The
`security-sensitive-data` tests use `companyName` / `isProxy` as
arbitrary record keys for a generic util — left unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 13:57:54 +02:00
|
|
|
const notesText = interest.notes ? `Notes:\n${interest.notes}` : 'No notes';
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
|
|
|
|
|
const timelineText =
|
|
|
|
|
timeline.length > 0
|
|
|
|
|
? timeline
|
|
|
|
|
.map(
|
|
|
|
|
(e) =>
|
2026-03-26 12:29:55 +01:00
|
|
|
`${formatDate(e.createdAt as Date | string | null | undefined)} ${e.action ?? e.eventType ?? 'event'} ${e.entityType ?? e.type ?? ''}${e.fieldChanged ? ` [${e.fieldChanged}]` : ''}`,
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
)
|
|
|
|
|
.join('\n')
|
|
|
|
|
: 'No timeline events';
|
|
|
|
|
|
|
|
|
|
return {
|
2026-03-26 12:29:55 +01:00
|
|
|
portName: (port?.name as string) ?? 'Port Nimara',
|
2026-05-04 22:57:01 +02:00
|
|
|
title: `Interest Summary - ${client?.fullName ?? 'Unknown Client'}`,
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
clientInfo,
|
|
|
|
|
berthInfo,
|
|
|
|
|
stageAndCategory,
|
|
|
|
|
milestones: `Milestones:\n${milestones}`,
|
|
|
|
|
notes: notesText,
|
|
|
|
|
recentTimeline: `Recent Timeline (last ${timeline.length}):\n${timelineText}`,
|
|
|
|
|
generatedAt: `Generated: ${new Date().toLocaleString('en-GB')}`,
|
|
|
|
|
};
|
|
|
|
|
}
|