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
|
|
|
/**
|
|
|
|
|
* Test factory helpers.
|
2026-04-23 18:06:37 +02:00
|
|
|
*
|
|
|
|
|
* Two flavours:
|
|
|
|
|
* 1. Async DB-inserting factories (makePort, makeClient, makeBerth, makeYacht,
|
chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged:
- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
redirects (ocr to ai, reports to dashboard, invitations to users),
docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
let-reassign), set-state-in-effect disables in CountryFlag and
UploadForSigning preview-bytes effect, unused 'confirm' destructures in
interest contract + reservation tabs, unescaped apostrophe in test-template
card copy
2026-05-23 00:52:59 +02:00
|
|
|
* makeCompany, ...) - insert a row via the app's `db` handle and return the
|
2026-04-23 18:06:37 +02:00
|
|
|
* inserted record. Use these from integration tests that need real FK /
|
|
|
|
|
* unique-index enforcement. They require DATABASE_URL to be reachable.
|
|
|
|
|
* 2. Plain-data helpers (makeAuditMeta, makeCreateClientInput, makeCreate*)
|
chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged:
- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
redirects (ocr to ai, reports to dashboard, invitations to users),
docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
let-reassign), set-state-in-effect disables in CountryFlag and
UploadForSigning preview-bytes effect, unused 'confirm' destructures in
interest contract + reservation tabs, unescaped apostrophe in test-template
card copy
2026-05-23 00:52:59 +02:00
|
|
|
* - return in-memory objects suitable for unit tests with mocked `db`.
|
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
|
|
|
*/
|
|
|
|
|
|
2026-04-24 13:19:54 +02:00
|
|
|
import { and, eq, sql } from 'drizzle-orm';
|
2026-04-23 18:06:37 +02:00
|
|
|
import { db } from '@/lib/db';
|
|
|
|
|
import { ports, type NewPort, type Port } from '@/lib/db/schema/ports';
|
|
|
|
|
import { clients, type NewClient, type Client } from '@/lib/db/schema/clients';
|
|
|
|
|
import { berths, type NewBerth, type Berth } from '@/lib/db/schema/berths';
|
|
|
|
|
import { yachts, yachtOwnershipHistory, type NewYacht, type Yacht } from '@/lib/db/schema/yachts';
|
2026-04-24 13:19:54 +02:00
|
|
|
import {
|
|
|
|
|
companies,
|
|
|
|
|
companyMemberships,
|
|
|
|
|
type NewCompany,
|
|
|
|
|
type Company,
|
|
|
|
|
} from '@/lib/db/schema/companies';
|
2026-05-25 15:09:35 +02:00
|
|
|
import { berthTenancies, type BerthTenancy } from '@/lib/db/schema/tenancies';
|
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
|
|
|
|
2026-04-23 18:06:37 +02:00
|
|
|
// ─── Port ────────────────────────────────────────────────────────────────────
|
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
|
|
|
|
2026-04-23 18:06:37 +02:00
|
|
|
export async function makePort(args?: { overrides?: Partial<NewPort> }): Promise<Port> {
|
|
|
|
|
const suffix = Math.random().toString(36).slice(2, 10);
|
|
|
|
|
const [port] = await db
|
|
|
|
|
.insert(ports)
|
|
|
|
|
.values({
|
|
|
|
|
name: args?.overrides?.name ?? `Test Port ${suffix}`,
|
|
|
|
|
slug: args?.overrides?.slug ?? `test-port-${suffix}`,
|
|
|
|
|
...args?.overrides,
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
return port!;
|
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
|
|
|
}
|
|
|
|
|
|
2026-04-23 18:06:37 +02:00
|
|
|
// ─── 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
|
|
|
|
2026-04-23 18:06:37 +02:00
|
|
|
export async function makeClient(args: {
|
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
|
|
|
portId: string;
|
2026-04-23 18:06:37 +02:00
|
|
|
overrides?: Partial<NewClient>;
|
|
|
|
|
}): Promise<Client> {
|
|
|
|
|
const [client] = await db
|
|
|
|
|
.insert(clients)
|
|
|
|
|
.values({
|
|
|
|
|
portId: args.portId,
|
|
|
|
|
fullName: args.overrides?.fullName ?? `Test Client ${Math.random().toString(36).slice(2, 8)}`,
|
|
|
|
|
...args.overrides,
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
return 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
|
|
|
}
|
|
|
|
|
|
2026-04-23 18:06:37 +02:00
|
|
|
// ─── Berth ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
export async function makeBerth(args: {
|
|
|
|
|
portId: string;
|
|
|
|
|
overrides?: Partial<NewBerth>;
|
|
|
|
|
}): Promise<Berth> {
|
|
|
|
|
const [berth] = await db
|
|
|
|
|
.insert(berths)
|
|
|
|
|
.values({
|
|
|
|
|
portId: args.portId,
|
|
|
|
|
mooringNumber: args.overrides?.mooringNumber ?? `B-${Math.random().toString(36).slice(2, 8)}`,
|
|
|
|
|
...args.overrides,
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
return berth!;
|
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
|
|
|
}
|
|
|
|
|
|
2026-04-23 18:06:37 +02:00
|
|
|
// ─── Yacht ───────────────────────────────────────────────────────────────────
|
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
|
|
|
|
2026-04-23 18:06:37 +02:00
|
|
|
export async function makeYacht(args: {
|
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
|
|
|
portId: string;
|
2026-04-23 18:06:37 +02:00
|
|
|
ownerType: 'client' | 'company';
|
|
|
|
|
ownerId: string;
|
feat(yachts): list + owner-scoped list + autocomplete
Adds `listYachts`, `listYachtsForOwner`, and `autocomplete` to the
yacht service so UIs can page/filter yachts per port, look up all
yachts tied to a given client/company, and power search-as-you-type.
`listYachts` delegates to the shared port-scoped `buildListQuery`,
supporting search over name/hullNumber/registration plus ownerType,
ownerId and status filters; `autocomplete` caps at 10 results and is
tenant-scoped; `listYachtsForOwner` returns all yachts whose current
owner matches, newest first. Extends `makeYacht` factory to accept
flat `name`, `status`, `hullNumber`, `registration` overrides.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 00:03:36 +02:00
|
|
|
name?: string;
|
|
|
|
|
status?: 'active' | 'retired' | 'sold_away';
|
|
|
|
|
hullNumber?: string;
|
|
|
|
|
registration?: string;
|
2026-04-23 18:06:37 +02:00
|
|
|
overrides?: Partial<NewYacht>;
|
|
|
|
|
}): Promise<Yacht> {
|
|
|
|
|
const [yacht] = await db
|
|
|
|
|
.insert(yachts)
|
|
|
|
|
.values({
|
|
|
|
|
portId: args.portId,
|
feat(yachts): list + owner-scoped list + autocomplete
Adds `listYachts`, `listYachtsForOwner`, and `autocomplete` to the
yacht service so UIs can page/filter yachts per port, look up all
yachts tied to a given client/company, and power search-as-you-type.
`listYachts` delegates to the shared port-scoped `buildListQuery`,
supporting search over name/hullNumber/registration plus ownerType,
ownerId and status filters; `autocomplete` caps at 10 results and is
tenant-scoped; `listYachtsForOwner` returns all yachts whose current
owner matches, newest first. Extends `makeYacht` factory to accept
flat `name`, `status`, `hullNumber`, `registration` overrides.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 00:03:36 +02:00
|
|
|
name: args.name ?? args.overrides?.name ?? `Yacht ${Math.random().toString(36).slice(2, 8)}`,
|
2026-04-23 18:06:37 +02:00
|
|
|
currentOwnerType: args.ownerType,
|
|
|
|
|
currentOwnerId: args.ownerId,
|
feat(yachts): list + owner-scoped list + autocomplete
Adds `listYachts`, `listYachtsForOwner`, and `autocomplete` to the
yacht service so UIs can page/filter yachts per port, look up all
yachts tied to a given client/company, and power search-as-you-type.
`listYachts` delegates to the shared port-scoped `buildListQuery`,
supporting search over name/hullNumber/registration plus ownerType,
ownerId and status filters; `autocomplete` caps at 10 results and is
tenant-scoped; `listYachtsForOwner` returns all yachts whose current
owner matches, newest first. Extends `makeYacht` factory to accept
flat `name`, `status`, `hullNumber`, `registration` overrides.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 00:03:36 +02:00
|
|
|
...(args.status !== undefined ? { status: args.status } : {}),
|
|
|
|
|
...(args.hullNumber !== undefined ? { hullNumber: args.hullNumber } : {}),
|
|
|
|
|
...(args.registration !== undefined ? { registration: args.registration } : {}),
|
2026-04-23 18:06:37 +02:00
|
|
|
...args.overrides,
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
await db.insert(yachtOwnershipHistory).values({
|
|
|
|
|
yachtId: yacht!.id,
|
|
|
|
|
ownerType: args.ownerType,
|
|
|
|
|
ownerId: args.ownerId,
|
|
|
|
|
startDate: new Date(),
|
|
|
|
|
endDate: null,
|
|
|
|
|
createdBy: 'test',
|
|
|
|
|
});
|
|
|
|
|
return yacht!;
|
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
|
|
|
}
|
|
|
|
|
|
2026-04-23 18:06:37 +02:00
|
|
|
// ─── Company ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
export async function makeCompany(args: {
|
|
|
|
|
portId: string;
|
|
|
|
|
overrides?: Partial<NewCompany>;
|
|
|
|
|
}): Promise<Company> {
|
|
|
|
|
const [company] = await db
|
|
|
|
|
.insert(companies)
|
|
|
|
|
.values({
|
|
|
|
|
portId: args.portId,
|
|
|
|
|
name: args.overrides?.name ?? `Company ${Math.random().toString(36).slice(2, 8)}`,
|
|
|
|
|
...args.overrides,
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
return company!;
|
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
|
|
|
}
|
|
|
|
|
|
2026-04-24 13:19:54 +02:00
|
|
|
// ─── Company Membership ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
export async function makeMembership(args: {
|
|
|
|
|
companyId: string;
|
|
|
|
|
clientId: string;
|
|
|
|
|
role?: string;
|
|
|
|
|
roleDetail?: string;
|
|
|
|
|
startDate?: Date;
|
|
|
|
|
endDate?: Date | null;
|
|
|
|
|
isPrimary?: boolean;
|
|
|
|
|
notes?: string;
|
|
|
|
|
}) {
|
|
|
|
|
const [row] = await db
|
|
|
|
|
.insert(companyMemberships)
|
|
|
|
|
.values({
|
|
|
|
|
companyId: args.companyId,
|
|
|
|
|
clientId: args.clientId,
|
|
|
|
|
role: args.role ?? 'director',
|
|
|
|
|
roleDetail: args.roleDetail ?? null,
|
|
|
|
|
startDate: args.startDate ?? new Date(),
|
|
|
|
|
endDate: args.endDate ?? null,
|
|
|
|
|
isPrimary: args.isPrimary ?? false,
|
|
|
|
|
notes: args.notes ?? null,
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
if (!row) throw new Error('Failed to create membership');
|
|
|
|
|
return row;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── Berth Reservation ───────────────────────────────────────────────────────
|
|
|
|
|
|
2026-05-25 15:09:35 +02:00
|
|
|
export async function makeTenancy(args: {
|
2026-04-24 13:19:54 +02:00
|
|
|
berthId: string;
|
|
|
|
|
portId: string;
|
|
|
|
|
clientId: string;
|
|
|
|
|
yachtId: string;
|
|
|
|
|
status: 'pending' | 'active' | 'ended' | 'cancelled';
|
|
|
|
|
startDate?: Date;
|
|
|
|
|
endDate?: Date | null;
|
|
|
|
|
tenureType?: 'permanent' | 'fixed_term' | 'seasonal';
|
|
|
|
|
interestId?: string;
|
|
|
|
|
createdBy?: string;
|
|
|
|
|
notes?: string;
|
2026-05-25 15:09:35 +02:00
|
|
|
}): Promise<BerthTenancy> {
|
2026-04-24 13:19:54 +02:00
|
|
|
const [row] = await db
|
2026-05-25 15:09:35 +02:00
|
|
|
.insert(berthTenancies)
|
2026-04-24 13:19:54 +02:00
|
|
|
.values({
|
|
|
|
|
berthId: args.berthId,
|
|
|
|
|
portId: args.portId,
|
|
|
|
|
clientId: args.clientId,
|
|
|
|
|
yachtId: args.yachtId,
|
|
|
|
|
interestId: args.interestId ?? null,
|
|
|
|
|
status: args.status,
|
|
|
|
|
startDate: args.startDate ?? new Date(),
|
|
|
|
|
endDate: args.endDate ?? null,
|
|
|
|
|
tenureType: args.tenureType ?? 'permanent',
|
|
|
|
|
contractFileId: null,
|
|
|
|
|
notes: args.notes ?? null,
|
|
|
|
|
createdBy: args.createdBy ?? 'seed-user',
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
if (!row) throw new Error('Failed to create reservation');
|
|
|
|
|
return row;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── Yacht Ownership Transfer ────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
export async function makeOwnershipTransfer(args: {
|
|
|
|
|
yachtId: string;
|
|
|
|
|
newOwner: { type: 'client' | 'company'; id: string };
|
|
|
|
|
effectiveDate?: Date;
|
|
|
|
|
transferReason?: string;
|
|
|
|
|
transferNotes?: string;
|
|
|
|
|
createdBy?: string;
|
|
|
|
|
}) {
|
|
|
|
|
const effective = args.effectiveDate ?? new Date();
|
|
|
|
|
const createdBy = args.createdBy ?? 'seed-user';
|
|
|
|
|
return await db.transaction(async (tx) => {
|
|
|
|
|
// Close current open row
|
|
|
|
|
await tx
|
|
|
|
|
.update(yachtOwnershipHistory)
|
|
|
|
|
.set({ endDate: effective })
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(yachtOwnershipHistory.yachtId, args.yachtId),
|
|
|
|
|
sql`${yachtOwnershipHistory.endDate} IS NULL`,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Insert new open row
|
|
|
|
|
const [newHistory] = await tx
|
|
|
|
|
.insert(yachtOwnershipHistory)
|
|
|
|
|
.values({
|
|
|
|
|
yachtId: args.yachtId,
|
|
|
|
|
ownerType: args.newOwner.type,
|
|
|
|
|
ownerId: args.newOwner.id,
|
|
|
|
|
startDate: effective,
|
|
|
|
|
endDate: null,
|
|
|
|
|
transferReason: args.transferReason ?? null,
|
|
|
|
|
transferNotes: args.transferNotes ?? null,
|
|
|
|
|
createdBy,
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
|
|
|
|
|
// Update yacht's denormalized current owner
|
|
|
|
|
const [updated] = await tx
|
|
|
|
|
.update(yachts)
|
|
|
|
|
.set({
|
|
|
|
|
currentOwnerType: args.newOwner.type,
|
|
|
|
|
currentOwnerId: args.newOwner.id,
|
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
})
|
|
|
|
|
.where(eq(yachts.id, args.yachtId))
|
|
|
|
|
.returning();
|
|
|
|
|
|
|
|
|
|
return { history: newHistory!, yacht: updated! };
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
// ─── Webhook ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
export interface WebhookData {
|
|
|
|
|
id: string;
|
|
|
|
|
portId: string;
|
|
|
|
|
name: string;
|
|
|
|
|
url: string;
|
|
|
|
|
secret: string | null;
|
|
|
|
|
events: string[];
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
createdBy: string;
|
|
|
|
|
createdAt: Date;
|
|
|
|
|
updatedAt: Date;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function makeWebhook(overrides?: Partial<WebhookData>): WebhookData {
|
|
|
|
|
return {
|
|
|
|
|
id: crypto.randomUUID(),
|
|
|
|
|
portId: crypto.randomUUID(),
|
|
|
|
|
name: 'Test Webhook',
|
|
|
|
|
url: 'https://example.com/webhook',
|
|
|
|
|
secret: null,
|
|
|
|
|
events: ['client.created'],
|
|
|
|
|
isActive: true,
|
|
|
|
|
createdBy: crypto.randomUUID(),
|
|
|
|
|
createdAt: new Date(),
|
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
...overrides,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── Audit Log ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
export interface AuditMeta {
|
|
|
|
|
userId: string;
|
|
|
|
|
portId: string;
|
|
|
|
|
ipAddress: string;
|
|
|
|
|
userAgent: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function makeAuditMeta(overrides?: Partial<AuditMeta>): AuditMeta {
|
|
|
|
|
return {
|
|
|
|
|
userId: crypto.randomUUID(),
|
|
|
|
|
portId: crypto.randomUUID(),
|
|
|
|
|
ipAddress: '127.0.0.1',
|
|
|
|
|
userAgent: 'vitest/1.0',
|
|
|
|
|
...overrides,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── Auth Context ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
import type { RolePermissions } from '@/lib/db/schema/users';
|
|
|
|
|
|
chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged:
- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
redirects (ocr to ai, reports to dashboard, invitations to users),
docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
let-reassign), set-state-in-effect disables in CountryFlag and
UploadForSigning preview-bytes effect, unused 'confirm' destructures in
interest contract + reservation tabs, unescaped apostrophe in test-template
card copy
2026-05-23 00:52:59 +02:00
|
|
|
/** Full permissions - every action allowed. */
|
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
|
|
|
export function makeFullPermissions(): RolePermissions {
|
|
|
|
|
return {
|
|
|
|
|
clients: { view: true, create: true, edit: true, delete: true, merge: true, export: true },
|
2026-04-23 18:06:37 +02:00
|
|
|
interests: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: true,
|
|
|
|
|
edit: true,
|
|
|
|
|
delete: true,
|
|
|
|
|
change_stage: true,
|
feat(interests): manual stage override + Residential Partner system role
Manual stage override
Sales reps need to skip canTransitionStage rules when the data was
entered out of order — e.g. recording a contract_signed deal whose
earlier stages were never tracked in the system.
- New permission flag interests.override_stage in RolePermissions.
Plumbed through the schema TS type, the role-editor UI, the seed
file's pre-built roles (super_admin/director/sales_manager get it,
sales_agent + viewer don't), and the test factories.
- changeStageSchema gains an optional `override` boolean and the
service checks it before evaluating canTransitionStage. When
override=true the reason field becomes required (min 5 chars) and
is recorded in the audit log.
- The route handler gates `override` on the new permission so a
sales_agent without it can't pass override=true and bypass.
- InterestStagePicker auto-detects when the requested transition is
blocked by the table and switches into "override mode" — shows an
amber warning, requires the reason, button label flips to
"Override stage". When the operator lacks the permission, the
warning is red and the button is disabled.
Residential Partner role
Per the smart-archive scoping conversation: external partners who
handle residential inquiries shouldn't see marina clients, yachts,
berths, or financials. The two residential_* permission groups
already exist; this commit just seeds a pre-built system role
("residential_partner") with those flags + minimal own-reminders, so
admins can invite a partner today via /admin/users without manually
building the permission set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:32:57 +02:00
|
|
|
override_stage: true,
|
2026-04-23 18:06:37 +02:00
|
|
|
generate_eoi: true,
|
|
|
|
|
export: true,
|
|
|
|
|
},
|
2026-05-20 15:56:11 +02:00
|
|
|
berths: {
|
|
|
|
|
view: true,
|
|
|
|
|
edit: true,
|
|
|
|
|
import: true,
|
|
|
|
|
manage_waiting_list: true,
|
|
|
|
|
update_prices: true,
|
|
|
|
|
},
|
2026-04-23 18:06:37 +02:00
|
|
|
documents: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: true,
|
2026-05-05 18:33:13 +02:00
|
|
|
edit: true,
|
2026-04-23 18:06:37 +02:00
|
|
|
send_for_signing: true,
|
|
|
|
|
upload_signed: true,
|
|
|
|
|
delete: true,
|
2026-05-09 19:23:22 +02:00
|
|
|
manage_folders: true,
|
2026-04-23 18:06:37 +02:00
|
|
|
},
|
|
|
|
|
expenses: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: true,
|
|
|
|
|
edit: true,
|
|
|
|
|
delete: true,
|
|
|
|
|
export: true,
|
|
|
|
|
scan_receipt: true,
|
|
|
|
|
},
|
|
|
|
|
invoices: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: true,
|
|
|
|
|
edit: true,
|
|
|
|
|
delete: true,
|
|
|
|
|
send: true,
|
|
|
|
|
record_payment: true,
|
|
|
|
|
export: true,
|
|
|
|
|
},
|
2026-05-14 03:46:01 +02:00
|
|
|
payments: { view: true, record: true, delete: true },
|
2026-05-05 18:33:13 +02:00
|
|
|
files: { view: true, upload: true, edit: true, delete: true, manage_folders: true },
|
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
|
|
|
email: { view: true, send: true, configure_account: true },
|
2026-04-23 18:06:37 +02:00
|
|
|
reminders: {
|
|
|
|
|
view_own: true,
|
|
|
|
|
view_all: true,
|
|
|
|
|
create: true,
|
|
|
|
|
edit_own: true,
|
|
|
|
|
edit_all: true,
|
|
|
|
|
assign_others: true,
|
|
|
|
|
},
|
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
|
|
|
calendar: { connect: true, view_events: true },
|
|
|
|
|
reports: { view_dashboard: true, view_analytics: true, export: true },
|
|
|
|
|
document_templates: { view: true, generate: true, manage: true },
|
2026-04-24 12:30:06 +02:00
|
|
|
yachts: { view: true, create: true, edit: true, delete: true, transfer: true },
|
|
|
|
|
companies: { view: true, create: true, edit: true, delete: true },
|
|
|
|
|
memberships: { view: true, manage: true },
|
2026-05-25 15:09:35 +02:00
|
|
|
tenancies: { view: true, manage: true, cancel: true },
|
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
|
|
|
admin: {
|
|
|
|
|
manage_users: true,
|
|
|
|
|
view_audit_log: true,
|
|
|
|
|
manage_settings: true,
|
|
|
|
|
manage_webhooks: true,
|
|
|
|
|
manage_reports: true,
|
|
|
|
|
manage_custom_fields: true,
|
|
|
|
|
manage_forms: true,
|
|
|
|
|
manage_tags: true,
|
|
|
|
|
system_backup: true,
|
feat(clients): hard-delete with email-code confirmation (single + bulk)
Permanent client deletion is now reachable from:
- archived single-client detail page (icon button, gated by new
admin.permanently_delete_clients perm)
- archived clients list bulk action
Both flows are 2-stage: request a 4-digit code (sent to operator's
account email, 10min Redis TTL), then enter both code AND a typed
confirmation (client name single, "DELETE N CLIENTS" bulk). Cascade
strategy preserves audit trails: signed documents, email threads,
files and reminders are detached but retained; addresses, contacts,
notes, portal user, GDPR records, interests and reservations are
deleted via FK cascade or explicit tx delete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:26:42 +02:00
|
|
|
permanently_delete_clients: true,
|
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
|
|
|
},
|
feat(platform): residential module + admin UI + reliability fixes
Residential platform
- New schema: residentialClients, residentialInterests (separate from
marina/yacht clients) with migration 0010
- Service layer with CRUD + audit + sockets + per-port portal toggle
- v1 + public API routes (/api/v1/residential/*, /api/public/residential-inquiries)
- List + detail pages with inline editing for clients and interests
- Per-user residentialAccess toggle on userPortRoles (migration 0011)
- Permission keys: residential_clients, residential_interests
- Sidebar nav + role form integration
- Smoke spec covering page loads, UI create flow, public endpoint
Admin & shared UI
- Admin → Forms (form templates CRUD) with validators + service
- Notification preferences page (in-app + email per type)
- Email composition + accounts list + threads view
- Branded auth shell shared across CRM + portal auth surfaces
- Inline editing extended to yacht/company/interest detail pages
- InlineTagEditor + per-entity tags endpoints (yachts, companies)
- Notes service polymorphic across clients/interests/yachts/companies
- Client list columns: yachtCount + companyCount badges
- Reservation file-download via presigned URL (replaces stale <a href>)
Route handler refactor
- Extracted yachts/companies/berths reservation handlers to sibling
handlers.ts files (Next.js 15 route.ts only allows specific exports)
Reliability fixes
- apiFetch double-stringify bug fixed across 13 components
(apiFetch already JSON.stringifies its body; passing a stringified
body produced double-encoded JSON which failed zod validation)
- SocketProvider gated behind useSyncExternalStore-based mount check
to avoid useSession() SSR crashes under React 19 + Next 15
- apiFetch falls back to URL-pathname → port-id resolution when the
Zustand store hasn't hydrated yet (fresh contexts, e2e tests)
- CRM invite flow (schema, service, route, email, dev script)
- Dashboard route → [portSlug]/dashboard/page.tsx + redirect
- Document the dev-server restart-after-migration gotcha in CLAUDE.md
Tests
- 5-case residential smoke spec
- Integration test updates for new service signatures
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:54:32 +02:00
|
|
|
residential_clients: { view: true, create: true, edit: true, delete: true },
|
|
|
|
|
residential_interests: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: true,
|
|
|
|
|
edit: true,
|
|
|
|
|
delete: true,
|
|
|
|
|
change_stage: true,
|
|
|
|
|
},
|
2026-06-17 17:59:32 +02:00
|
|
|
inquiries: { view: true, manage: true },
|
2026-06-18 22:28:20 +02:00
|
|
|
client_groups: { view: true, manage: true },
|
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
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged:
- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
redirects (ocr to ai, reports to dashboard, invitations to users),
docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
let-reassign), set-state-in-effect disables in CountryFlag and
UploadForSigning preview-bytes effect, unused 'confirm' destructures in
interest contract + reservation tabs, unescaped apostrophe in test-template
card copy
2026-05-23 00:52:59 +02:00
|
|
|
/** Read-only viewer permissions - no create/update/delete. */
|
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
|
|
|
export function makeViewerPermissions(): RolePermissions {
|
|
|
|
|
return {
|
|
|
|
|
clients: { view: true, create: false, edit: false, delete: false, merge: false, export: false },
|
2026-04-23 18:06:37 +02:00
|
|
|
interests: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: false,
|
|
|
|
|
edit: false,
|
|
|
|
|
delete: false,
|
|
|
|
|
change_stage: false,
|
feat(interests): manual stage override + Residential Partner system role
Manual stage override
Sales reps need to skip canTransitionStage rules when the data was
entered out of order — e.g. recording a contract_signed deal whose
earlier stages were never tracked in the system.
- New permission flag interests.override_stage in RolePermissions.
Plumbed through the schema TS type, the role-editor UI, the seed
file's pre-built roles (super_admin/director/sales_manager get it,
sales_agent + viewer don't), and the test factories.
- changeStageSchema gains an optional `override` boolean and the
service checks it before evaluating canTransitionStage. When
override=true the reason field becomes required (min 5 chars) and
is recorded in the audit log.
- The route handler gates `override` on the new permission so a
sales_agent without it can't pass override=true and bypass.
- InterestStagePicker auto-detects when the requested transition is
blocked by the table and switches into "override mode" — shows an
amber warning, requires the reason, button label flips to
"Override stage". When the operator lacks the permission, the
warning is red and the button is disabled.
Residential Partner role
Per the smart-archive scoping conversation: external partners who
handle residential inquiries shouldn't see marina clients, yachts,
berths, or financials. The two residential_* permission groups
already exist; this commit just seeds a pre-built system role
("residential_partner") with those flags + minimal own-reminders, so
admins can invite a partner today via /admin/users without manually
building the permission set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:32:57 +02:00
|
|
|
override_stage: false,
|
2026-04-23 18:06:37 +02:00
|
|
|
generate_eoi: false,
|
|
|
|
|
export: false,
|
|
|
|
|
},
|
2026-05-20 15:56:11 +02:00
|
|
|
berths: {
|
|
|
|
|
view: true,
|
|
|
|
|
edit: false,
|
|
|
|
|
import: false,
|
|
|
|
|
manage_waiting_list: false,
|
|
|
|
|
update_prices: false,
|
|
|
|
|
},
|
2026-04-23 18:06:37 +02:00
|
|
|
documents: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: false,
|
2026-05-05 18:33:13 +02:00
|
|
|
edit: false,
|
2026-04-23 18:06:37 +02:00
|
|
|
send_for_signing: false,
|
|
|
|
|
upload_signed: false,
|
|
|
|
|
delete: false,
|
2026-05-09 19:23:22 +02:00
|
|
|
manage_folders: false,
|
2026-04-23 18:06:37 +02:00
|
|
|
},
|
|
|
|
|
expenses: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: false,
|
|
|
|
|
edit: false,
|
|
|
|
|
delete: false,
|
|
|
|
|
export: false,
|
|
|
|
|
scan_receipt: false,
|
|
|
|
|
},
|
|
|
|
|
invoices: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: false,
|
|
|
|
|
edit: false,
|
|
|
|
|
delete: false,
|
|
|
|
|
send: false,
|
|
|
|
|
record_payment: false,
|
|
|
|
|
export: false,
|
|
|
|
|
},
|
2026-05-14 03:46:01 +02:00
|
|
|
payments: { view: false, record: false, delete: false },
|
2026-05-05 18:33:13 +02:00
|
|
|
files: { view: true, upload: false, edit: false, delete: false, manage_folders: false },
|
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
|
|
|
email: { view: true, send: false, configure_account: false },
|
2026-04-23 18:06:37 +02:00
|
|
|
reminders: {
|
|
|
|
|
view_own: true,
|
|
|
|
|
view_all: false,
|
|
|
|
|
create: false,
|
|
|
|
|
edit_own: false,
|
|
|
|
|
edit_all: false,
|
|
|
|
|
assign_others: false,
|
|
|
|
|
},
|
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
|
|
|
calendar: { connect: false, view_events: true },
|
|
|
|
|
reports: { view_dashboard: true, view_analytics: false, export: false },
|
|
|
|
|
document_templates: { view: true, generate: false, manage: false },
|
2026-04-24 12:30:06 +02:00
|
|
|
yachts: { view: true, create: false, edit: false, delete: false, transfer: false },
|
|
|
|
|
companies: { view: true, create: false, edit: false, delete: false },
|
|
|
|
|
memberships: { view: true, manage: false },
|
2026-05-25 15:09:35 +02:00
|
|
|
tenancies: { view: true, manage: false, cancel: false },
|
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
|
|
|
admin: {
|
|
|
|
|
manage_users: false,
|
|
|
|
|
view_audit_log: false,
|
|
|
|
|
manage_settings: false,
|
|
|
|
|
manage_webhooks: false,
|
|
|
|
|
manage_reports: false,
|
|
|
|
|
manage_custom_fields: false,
|
|
|
|
|
manage_forms: false,
|
|
|
|
|
manage_tags: false,
|
|
|
|
|
system_backup: false,
|
feat(clients): hard-delete with email-code confirmation (single + bulk)
Permanent client deletion is now reachable from:
- archived single-client detail page (icon button, gated by new
admin.permanently_delete_clients perm)
- archived clients list bulk action
Both flows are 2-stage: request a 4-digit code (sent to operator's
account email, 10min Redis TTL), then enter both code AND a typed
confirmation (client name single, "DELETE N CLIENTS" bulk). Cascade
strategy preserves audit trails: signed documents, email threads,
files and reminders are detached but retained; addresses, contacts,
notes, portal user, GDPR records, interests and reservations are
deleted via FK cascade or explicit tx delete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:26:42 +02:00
|
|
|
permanently_delete_clients: false,
|
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
|
|
|
},
|
feat(platform): residential module + admin UI + reliability fixes
Residential platform
- New schema: residentialClients, residentialInterests (separate from
marina/yacht clients) with migration 0010
- Service layer with CRUD + audit + sockets + per-port portal toggle
- v1 + public API routes (/api/v1/residential/*, /api/public/residential-inquiries)
- List + detail pages with inline editing for clients and interests
- Per-user residentialAccess toggle on userPortRoles (migration 0011)
- Permission keys: residential_clients, residential_interests
- Sidebar nav + role form integration
- Smoke spec covering page loads, UI create flow, public endpoint
Admin & shared UI
- Admin → Forms (form templates CRUD) with validators + service
- Notification preferences page (in-app + email per type)
- Email composition + accounts list + threads view
- Branded auth shell shared across CRM + portal auth surfaces
- Inline editing extended to yacht/company/interest detail pages
- InlineTagEditor + per-entity tags endpoints (yachts, companies)
- Notes service polymorphic across clients/interests/yachts/companies
- Client list columns: yachtCount + companyCount badges
- Reservation file-download via presigned URL (replaces stale <a href>)
Route handler refactor
- Extracted yachts/companies/berths reservation handlers to sibling
handlers.ts files (Next.js 15 route.ts only allows specific exports)
Reliability fixes
- apiFetch double-stringify bug fixed across 13 components
(apiFetch already JSON.stringifies its body; passing a stringified
body produced double-encoded JSON which failed zod validation)
- SocketProvider gated behind useSyncExternalStore-based mount check
to avoid useSession() SSR crashes under React 19 + Next 15
- apiFetch falls back to URL-pathname → port-id resolution when the
Zustand store hasn't hydrated yet (fresh contexts, e2e tests)
- CRM invite flow (schema, service, route, email, dev script)
- Dashboard route → [portSlug]/dashboard/page.tsx + redirect
- Document the dev-server restart-after-migration gotcha in CLAUDE.md
Tests
- 5-case residential smoke spec
- Integration test updates for new service signatures
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:54:32 +02:00
|
|
|
residential_clients: { view: false, create: false, edit: false, delete: false },
|
|
|
|
|
residential_interests: {
|
|
|
|
|
view: false,
|
|
|
|
|
create: false,
|
|
|
|
|
edit: false,
|
|
|
|
|
delete: false,
|
|
|
|
|
change_stage: false,
|
|
|
|
|
},
|
2026-06-17 17:59:32 +02:00
|
|
|
inquiries: { view: true, manage: false },
|
2026-06-18 22:28:20 +02:00
|
|
|
client_groups: { view: true, manage: false },
|
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
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged:
- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
redirects (ocr to ai, reports to dashboard, invitations to users),
docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
let-reassign), set-state-in-effect disables in CountryFlag and
UploadForSigning preview-bytes effect, unused 'confirm' destructures in
interest contract + reservation tabs, unescaped apostrophe in test-template
card copy
2026-05-23 00:52:59 +02:00
|
|
|
/** Sales agent permissions - own clients/interests, no admin. */
|
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
|
|
|
export function makeSalesAgentPermissions(): RolePermissions {
|
|
|
|
|
return {
|
|
|
|
|
clients: { view: true, create: true, edit: true, delete: false, merge: false, export: false },
|
2026-04-23 18:06:37 +02:00
|
|
|
interests: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: true,
|
|
|
|
|
edit: true,
|
|
|
|
|
delete: false,
|
|
|
|
|
change_stage: true,
|
feat(interests): manual stage override + Residential Partner system role
Manual stage override
Sales reps need to skip canTransitionStage rules when the data was
entered out of order — e.g. recording a contract_signed deal whose
earlier stages were never tracked in the system.
- New permission flag interests.override_stage in RolePermissions.
Plumbed through the schema TS type, the role-editor UI, the seed
file's pre-built roles (super_admin/director/sales_manager get it,
sales_agent + viewer don't), and the test factories.
- changeStageSchema gains an optional `override` boolean and the
service checks it before evaluating canTransitionStage. When
override=true the reason field becomes required (min 5 chars) and
is recorded in the audit log.
- The route handler gates `override` on the new permission so a
sales_agent without it can't pass override=true and bypass.
- InterestStagePicker auto-detects when the requested transition is
blocked by the table and switches into "override mode" — shows an
amber warning, requires the reason, button label flips to
"Override stage". When the operator lacks the permission, the
warning is red and the button is disabled.
Residential Partner role
Per the smart-archive scoping conversation: external partners who
handle residential inquiries shouldn't see marina clients, yachts,
berths, or financials. The two residential_* permission groups
already exist; this commit just seeds a pre-built system role
("residential_partner") with those flags + minimal own-reminders, so
admins can invite a partner today via /admin/users without manually
building the permission set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:32:57 +02:00
|
|
|
override_stage: true,
|
2026-04-23 18:06:37 +02:00
|
|
|
generate_eoi: true,
|
|
|
|
|
export: false,
|
|
|
|
|
},
|
2026-05-20 15:56:11 +02:00
|
|
|
berths: {
|
|
|
|
|
view: true,
|
|
|
|
|
edit: false,
|
|
|
|
|
import: false,
|
|
|
|
|
manage_waiting_list: false,
|
|
|
|
|
update_prices: false,
|
|
|
|
|
},
|
2026-04-23 18:06:37 +02:00
|
|
|
documents: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: true,
|
2026-05-05 18:33:13 +02:00
|
|
|
edit: true,
|
2026-04-23 18:06:37 +02:00
|
|
|
send_for_signing: true,
|
|
|
|
|
upload_signed: true,
|
|
|
|
|
delete: false,
|
2026-05-09 19:23:22 +02:00
|
|
|
manage_folders: false,
|
2026-04-23 18:06:37 +02:00
|
|
|
},
|
|
|
|
|
expenses: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: true,
|
|
|
|
|
edit: true,
|
|
|
|
|
delete: false,
|
|
|
|
|
export: false,
|
|
|
|
|
scan_receipt: true,
|
|
|
|
|
},
|
|
|
|
|
invoices: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: false,
|
|
|
|
|
edit: false,
|
|
|
|
|
delete: false,
|
|
|
|
|
send: false,
|
|
|
|
|
record_payment: false,
|
|
|
|
|
export: false,
|
|
|
|
|
},
|
2026-05-14 03:46:01 +02:00
|
|
|
payments: { view: false, record: false, delete: false },
|
2026-05-05 18:33:13 +02:00
|
|
|
files: { view: true, upload: true, edit: false, delete: false, manage_folders: false },
|
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
|
|
|
email: { view: true, send: true, configure_account: false },
|
2026-04-23 18:06:37 +02:00
|
|
|
reminders: {
|
|
|
|
|
view_own: true,
|
|
|
|
|
view_all: false,
|
|
|
|
|
create: true,
|
|
|
|
|
edit_own: true,
|
|
|
|
|
edit_all: false,
|
|
|
|
|
assign_others: false,
|
|
|
|
|
},
|
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
|
|
|
calendar: { connect: true, view_events: true },
|
|
|
|
|
reports: { view_dashboard: true, view_analytics: false, export: false },
|
|
|
|
|
document_templates: { view: true, generate: true, manage: false },
|
2026-04-24 12:30:06 +02:00
|
|
|
yachts: { view: true, create: true, edit: true, delete: false, transfer: false },
|
|
|
|
|
companies: { view: true, create: true, edit: false, delete: false },
|
|
|
|
|
memberships: { view: true, manage: false },
|
2026-05-25 15:09:35 +02:00
|
|
|
tenancies: { view: true, manage: true, cancel: false },
|
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
|
|
|
admin: {
|
|
|
|
|
manage_users: false,
|
|
|
|
|
view_audit_log: false,
|
|
|
|
|
manage_settings: false,
|
|
|
|
|
manage_webhooks: false,
|
|
|
|
|
manage_reports: false,
|
|
|
|
|
manage_custom_fields: false,
|
|
|
|
|
manage_forms: false,
|
|
|
|
|
manage_tags: false,
|
|
|
|
|
system_backup: false,
|
feat(clients): hard-delete with email-code confirmation (single + bulk)
Permanent client deletion is now reachable from:
- archived single-client detail page (icon button, gated by new
admin.permanently_delete_clients perm)
- archived clients list bulk action
Both flows are 2-stage: request a 4-digit code (sent to operator's
account email, 10min Redis TTL), then enter both code AND a typed
confirmation (client name single, "DELETE N CLIENTS" bulk). Cascade
strategy preserves audit trails: signed documents, email threads,
files and reminders are detached but retained; addresses, contacts,
notes, portal user, GDPR records, interests and reservations are
deleted via FK cascade or explicit tx delete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:26:42 +02:00
|
|
|
permanently_delete_clients: false,
|
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
|
|
|
},
|
feat(platform): residential module + admin UI + reliability fixes
Residential platform
- New schema: residentialClients, residentialInterests (separate from
marina/yacht clients) with migration 0010
- Service layer with CRUD + audit + sockets + per-port portal toggle
- v1 + public API routes (/api/v1/residential/*, /api/public/residential-inquiries)
- List + detail pages with inline editing for clients and interests
- Per-user residentialAccess toggle on userPortRoles (migration 0011)
- Permission keys: residential_clients, residential_interests
- Sidebar nav + role form integration
- Smoke spec covering page loads, UI create flow, public endpoint
Admin & shared UI
- Admin → Forms (form templates CRUD) with validators + service
- Notification preferences page (in-app + email per type)
- Email composition + accounts list + threads view
- Branded auth shell shared across CRM + portal auth surfaces
- Inline editing extended to yacht/company/interest detail pages
- InlineTagEditor + per-entity tags endpoints (yachts, companies)
- Notes service polymorphic across clients/interests/yachts/companies
- Client list columns: yachtCount + companyCount badges
- Reservation file-download via presigned URL (replaces stale <a href>)
Route handler refactor
- Extracted yachts/companies/berths reservation handlers to sibling
handlers.ts files (Next.js 15 route.ts only allows specific exports)
Reliability fixes
- apiFetch double-stringify bug fixed across 13 components
(apiFetch already JSON.stringifies its body; passing a stringified
body produced double-encoded JSON which failed zod validation)
- SocketProvider gated behind useSyncExternalStore-based mount check
to avoid useSession() SSR crashes under React 19 + Next 15
- apiFetch falls back to URL-pathname → port-id resolution when the
Zustand store hasn't hydrated yet (fresh contexts, e2e tests)
- CRM invite flow (schema, service, route, email, dev script)
- Dashboard route → [portSlug]/dashboard/page.tsx + redirect
- Document the dev-server restart-after-migration gotcha in CLAUDE.md
Tests
- 5-case residential smoke spec
- Integration test updates for new service signatures
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:54:32 +02:00
|
|
|
residential_clients: { view: false, create: false, edit: false, delete: false },
|
|
|
|
|
residential_interests: {
|
|
|
|
|
view: false,
|
|
|
|
|
create: false,
|
|
|
|
|
edit: false,
|
|
|
|
|
delete: false,
|
|
|
|
|
change_stage: false,
|
|
|
|
|
},
|
2026-06-17 17:59:32 +02:00
|
|
|
inquiries: { view: true, manage: true },
|
2026-06-18 22:28:20 +02:00
|
|
|
client_groups: { view: true, manage: true },
|
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
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged:
- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
redirects (ocr to ai, reports to dashboard, invitations to users),
docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
let-reassign), set-state-in-effect disables in CountryFlag and
UploadForSigning preview-bytes effect, unused 'confirm' destructures in
interest contract + reservation tabs, unescaped apostrophe in test-template
card copy
2026-05-23 00:52:59 +02:00
|
|
|
/** Sales manager - can do most things, limited admin. */
|
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
|
|
|
export function makeSalesManagerPermissions(): RolePermissions {
|
|
|
|
|
return {
|
|
|
|
|
clients: { view: true, create: true, edit: true, delete: true, merge: true, export: true },
|
2026-04-23 18:06:37 +02:00
|
|
|
interests: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: true,
|
|
|
|
|
edit: true,
|
|
|
|
|
delete: true,
|
|
|
|
|
change_stage: true,
|
feat(interests): manual stage override + Residential Partner system role
Manual stage override
Sales reps need to skip canTransitionStage rules when the data was
entered out of order — e.g. recording a contract_signed deal whose
earlier stages were never tracked in the system.
- New permission flag interests.override_stage in RolePermissions.
Plumbed through the schema TS type, the role-editor UI, the seed
file's pre-built roles (super_admin/director/sales_manager get it,
sales_agent + viewer don't), and the test factories.
- changeStageSchema gains an optional `override` boolean and the
service checks it before evaluating canTransitionStage. When
override=true the reason field becomes required (min 5 chars) and
is recorded in the audit log.
- The route handler gates `override` on the new permission so a
sales_agent without it can't pass override=true and bypass.
- InterestStagePicker auto-detects when the requested transition is
blocked by the table and switches into "override mode" — shows an
amber warning, requires the reason, button label flips to
"Override stage". When the operator lacks the permission, the
warning is red and the button is disabled.
Residential Partner role
Per the smart-archive scoping conversation: external partners who
handle residential inquiries shouldn't see marina clients, yachts,
berths, or financials. The two residential_* permission groups
already exist; this commit just seeds a pre-built system role
("residential_partner") with those flags + minimal own-reminders, so
admins can invite a partner today via /admin/users without manually
building the permission set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:32:57 +02:00
|
|
|
override_stage: true,
|
2026-04-23 18:06:37 +02:00
|
|
|
generate_eoi: true,
|
|
|
|
|
export: true,
|
|
|
|
|
},
|
2026-05-20 15:56:11 +02:00
|
|
|
berths: {
|
|
|
|
|
view: true,
|
|
|
|
|
edit: true,
|
|
|
|
|
import: false,
|
|
|
|
|
manage_waiting_list: true,
|
|
|
|
|
update_prices: true,
|
|
|
|
|
},
|
2026-04-23 18:06:37 +02:00
|
|
|
documents: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: true,
|
2026-05-05 18:33:13 +02:00
|
|
|
edit: true,
|
2026-04-23 18:06:37 +02:00
|
|
|
send_for_signing: true,
|
|
|
|
|
upload_signed: true,
|
|
|
|
|
delete: true,
|
2026-05-09 19:23:22 +02:00
|
|
|
manage_folders: true,
|
2026-04-23 18:06:37 +02:00
|
|
|
},
|
|
|
|
|
expenses: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: true,
|
|
|
|
|
edit: true,
|
|
|
|
|
delete: true,
|
|
|
|
|
export: true,
|
|
|
|
|
scan_receipt: true,
|
|
|
|
|
},
|
|
|
|
|
invoices: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: true,
|
|
|
|
|
edit: true,
|
|
|
|
|
delete: false,
|
|
|
|
|
send: true,
|
|
|
|
|
record_payment: true,
|
|
|
|
|
export: true,
|
|
|
|
|
},
|
2026-05-14 03:46:01 +02:00
|
|
|
payments: { view: true, record: true, delete: true },
|
2026-05-05 18:33:13 +02:00
|
|
|
files: { view: true, upload: true, edit: true, delete: true, manage_folders: true },
|
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
|
|
|
email: { view: true, send: true, configure_account: false },
|
2026-04-23 18:06:37 +02:00
|
|
|
reminders: {
|
|
|
|
|
view_own: true,
|
|
|
|
|
view_all: true,
|
|
|
|
|
create: true,
|
|
|
|
|
edit_own: true,
|
|
|
|
|
edit_all: true,
|
|
|
|
|
assign_others: true,
|
|
|
|
|
},
|
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
|
|
|
calendar: { connect: true, view_events: true },
|
|
|
|
|
reports: { view_dashboard: true, view_analytics: true, export: true },
|
|
|
|
|
document_templates: { view: true, generate: true, manage: false },
|
2026-04-24 12:30:06 +02:00
|
|
|
yachts: { view: true, create: true, edit: true, delete: false, transfer: true },
|
|
|
|
|
companies: { view: true, create: true, edit: true, delete: false },
|
|
|
|
|
memberships: { view: true, manage: true },
|
2026-05-25 15:09:35 +02:00
|
|
|
tenancies: { view: true, manage: true, cancel: true },
|
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
|
|
|
admin: {
|
|
|
|
|
manage_users: false,
|
|
|
|
|
view_audit_log: true,
|
|
|
|
|
manage_settings: false,
|
|
|
|
|
manage_webhooks: false,
|
|
|
|
|
manage_reports: true,
|
|
|
|
|
manage_custom_fields: false,
|
|
|
|
|
manage_forms: false,
|
|
|
|
|
manage_tags: true,
|
|
|
|
|
system_backup: false,
|
feat(clients): hard-delete with email-code confirmation (single + bulk)
Permanent client deletion is now reachable from:
- archived single-client detail page (icon button, gated by new
admin.permanently_delete_clients perm)
- archived clients list bulk action
Both flows are 2-stage: request a 4-digit code (sent to operator's
account email, 10min Redis TTL), then enter both code AND a typed
confirmation (client name single, "DELETE N CLIENTS" bulk). Cascade
strategy preserves audit trails: signed documents, email threads,
files and reminders are detached but retained; addresses, contacts,
notes, portal user, GDPR records, interests and reservations are
deleted via FK cascade or explicit tx delete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:26:42 +02:00
|
|
|
permanently_delete_clients: false,
|
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
|
|
|
},
|
feat(platform): residential module + admin UI + reliability fixes
Residential platform
- New schema: residentialClients, residentialInterests (separate from
marina/yacht clients) with migration 0010
- Service layer with CRUD + audit + sockets + per-port portal toggle
- v1 + public API routes (/api/v1/residential/*, /api/public/residential-inquiries)
- List + detail pages with inline editing for clients and interests
- Per-user residentialAccess toggle on userPortRoles (migration 0011)
- Permission keys: residential_clients, residential_interests
- Sidebar nav + role form integration
- Smoke spec covering page loads, UI create flow, public endpoint
Admin & shared UI
- Admin → Forms (form templates CRUD) with validators + service
- Notification preferences page (in-app + email per type)
- Email composition + accounts list + threads view
- Branded auth shell shared across CRM + portal auth surfaces
- Inline editing extended to yacht/company/interest detail pages
- InlineTagEditor + per-entity tags endpoints (yachts, companies)
- Notes service polymorphic across clients/interests/yachts/companies
- Client list columns: yachtCount + companyCount badges
- Reservation file-download via presigned URL (replaces stale <a href>)
Route handler refactor
- Extracted yachts/companies/berths reservation handlers to sibling
handlers.ts files (Next.js 15 route.ts only allows specific exports)
Reliability fixes
- apiFetch double-stringify bug fixed across 13 components
(apiFetch already JSON.stringifies its body; passing a stringified
body produced double-encoded JSON which failed zod validation)
- SocketProvider gated behind useSyncExternalStore-based mount check
to avoid useSession() SSR crashes under React 19 + Next 15
- apiFetch falls back to URL-pathname → port-id resolution when the
Zustand store hasn't hydrated yet (fresh contexts, e2e tests)
- CRM invite flow (schema, service, route, email, dev script)
- Dashboard route → [portSlug]/dashboard/page.tsx + redirect
- Document the dev-server restart-after-migration gotcha in CLAUDE.md
Tests
- 5-case residential smoke spec
- Integration test updates for new service signatures
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:54:32 +02:00
|
|
|
residential_clients: { view: true, create: true, edit: true, delete: true },
|
|
|
|
|
residential_interests: {
|
|
|
|
|
view: true,
|
|
|
|
|
create: true,
|
|
|
|
|
edit: true,
|
|
|
|
|
delete: true,
|
|
|
|
|
change_stage: true,
|
|
|
|
|
},
|
2026-06-17 17:59:32 +02:00
|
|
|
inquiries: { view: true, manage: true },
|
2026-06-18 22:28:20 +02:00
|
|
|
client_groups: { view: true, manage: true },
|
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
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged:
- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
redirects (ocr to ai, reports to dashboard, invitations to users),
docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
let-reassign), set-state-in-effect disables in CountryFlag and
UploadForSigning preview-bytes effect, unused 'confirm' destructures in
interest contract + reservation tabs, unescaped apostrophe in test-template
card copy
2026-05-23 00:52:59 +02:00
|
|
|
/** Director - everything except system backup. */
|
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
|
|
|
export function makeDirectorPermissions(): RolePermissions {
|
|
|
|
|
return {
|
|
|
|
|
...makeFullPermissions(),
|
|
|
|
|
admin: {
|
|
|
|
|
...makeFullPermissions().admin,
|
|
|
|
|
system_backup: false,
|
feat(clients): hard-delete with email-code confirmation (single + bulk)
Permanent client deletion is now reachable from:
- archived single-client detail page (icon button, gated by new
admin.permanently_delete_clients perm)
- archived clients list bulk action
Both flows are 2-stage: request a 4-digit code (sent to operator's
account email, 10min Redis TTL), then enter both code AND a typed
confirmation (client name single, "DELETE N CLIENTS" bulk). Cascade
strategy preserves audit trails: signed documents, email threads,
files and reminders are detached but retained; addresses, contacts,
notes, portal user, GDPR records, interests and reservations are
deleted via FK cascade or explicit tx delete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:26:42 +02:00
|
|
|
permanently_delete_clients: false,
|
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
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── Minimal valid CreateClientInput ─────────────────────────────────────────
|
|
|
|
|
/** Returns a minimal valid CreateClientInput object for use in service calls. */
|
|
|
|
|
export function makeCreateClientInput(overrides?: { fullName?: string; portId?: string }) {
|
|
|
|
|
return {
|
|
|
|
|
fullName: overrides?.fullName ?? 'Test Client',
|
|
|
|
|
contacts: [{ channel: 'email' as const, value: 'test@example.com', isPrimary: true }],
|
|
|
|
|
tagIds: [] as string[],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Returns a minimal valid CreateInterestInput object. */
|
|
|
|
|
export function makeCreateInterestInput(overrides?: {
|
|
|
|
|
clientId?: string;
|
2026-04-23 18:06:37 +02:00
|
|
|
pipelineStage?:
|
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave
Replaces the legacy 9-stage pipeline with 7 canonical stages
(enquiry → qualified → eoi → reservation → deposit_paid → contract →
nurturing) plus three doc sub-status columns (eoi_doc_status,
reservation_doc_status, contract_doc_status) that track sent/signed
within a single stage instead of branching it.
Schema (migration 0062):
- interests gains assigned_to, deposit_expected_amount/currency,
three doc-status columns, two documenso-id columns, and
date_reservation_signed.
- New tables: qualification_criteria (per-port admin-configurable),
interest_qualifications (per-interest state), payments (deposit /
balance / refund records keyed to interest + client).
- Default qualification criteria seeded for every existing port.
- Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into
the new stage + doc-status + outcome shape.
Migration 0063 adds interest_contact_log.voice_transcript and
template_used columns for v1.1-A/B (quick-template buttons + voice
transcription via Web Speech API).
v1.1 phase work bundled here:
- A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on
the contact-log compose dialog (useVoiceTranscription hook).
- C: berth-rules-engine wraps state writes in pg_advisory_xact_lock
with an idempotent re-read; emits rule_evaluated audit traces.
- D: Documenso webhook: reservation/contract sub-status stamping
moved out of the PDF-download try-block so a download failure
no longer swallows the stamp. New integration test coverage.
- E: /admin/qualification-criteria CRUD page + admin component.
- F: default_new_interest_owner exposed in System Settings.
- G: recentActivityCount + active_engagement deal-pulse signal
surfaced as a chip on interests + hot-deals card.
- H: interest_assigned notification on assignedTo change (skips
self-assign, uses a dedupe key).
Plus the supporting components: AssignedToChip, DealPulseChip,
PaymentsSection, QualificationChecklist, MultiEoiChip,
SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner,
SupplementalInfoRequestButton, UserPicker.
Tests: 1370/1370 vitest pass (added deal-health unit suite +
expanded constants/validators/pipeline-transitions coverage). tsc
clean, eslint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
|
|
|
| 'enquiry'
|
|
|
|
|
| 'qualified'
|
|
|
|
|
| 'nurturing'
|
|
|
|
|
| 'eoi'
|
|
|
|
|
| 'reservation'
|
|
|
|
|
| 'deposit_paid'
|
|
|
|
|
| 'contract';
|
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
|
|
|
}) {
|
|
|
|
|
return {
|
|
|
|
|
clientId: overrides?.clientId ?? crypto.randomUUID(),
|
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave
Replaces the legacy 9-stage pipeline with 7 canonical stages
(enquiry → qualified → eoi → reservation → deposit_paid → contract →
nurturing) plus three doc sub-status columns (eoi_doc_status,
reservation_doc_status, contract_doc_status) that track sent/signed
within a single stage instead of branching it.
Schema (migration 0062):
- interests gains assigned_to, deposit_expected_amount/currency,
three doc-status columns, two documenso-id columns, and
date_reservation_signed.
- New tables: qualification_criteria (per-port admin-configurable),
interest_qualifications (per-interest state), payments (deposit /
balance / refund records keyed to interest + client).
- Default qualification criteria seeded for every existing port.
- Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into
the new stage + doc-status + outcome shape.
Migration 0063 adds interest_contact_log.voice_transcript and
template_used columns for v1.1-A/B (quick-template buttons + voice
transcription via Web Speech API).
v1.1 phase work bundled here:
- A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on
the contact-log compose dialog (useVoiceTranscription hook).
- C: berth-rules-engine wraps state writes in pg_advisory_xact_lock
with an idempotent re-read; emits rule_evaluated audit traces.
- D: Documenso webhook: reservation/contract sub-status stamping
moved out of the PDF-download try-block so a download failure
no longer swallows the stamp. New integration test coverage.
- E: /admin/qualification-criteria CRUD page + admin component.
- F: default_new_interest_owner exposed in System Settings.
- G: recentActivityCount + active_engagement deal-pulse signal
surfaced as a chip on interests + hot-deals card.
- H: interest_assigned notification on assignedTo change (skips
self-assign, uses a dedupe key).
Plus the supporting components: AssignedToChip, DealPulseChip,
PaymentsSection, QualificationChecklist, MultiEoiChip,
SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner,
SupplementalInfoRequestButton, UserPicker.
Tests: 1370/1370 vitest pass (added deal-health unit suite +
expanded constants/validators/pipeline-transitions coverage). tsc
clean, eslint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
|
|
|
pipelineStage: overrides?.pipelineStage ?? ('enquiry' as const),
|
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
|
|
|
reminderEnabled: false,
|
|
|
|
|
tagIds: [] as string[],
|
|
|
|
|
};
|
|
|
|
|
}
|