Initial commit: Port Nimara CRM (Layers 0-4)
Some checks failed
Build & Push Docker Images / build-and-push (push) Has been cancelled
Build & Push Docker Images / deploy (push) Has been cancelled
Build & Push Docker Images / lint (push) Has been cancelled

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>
This commit is contained in:
2026-03-26 11:52:51 +01:00
commit 67d7e6e3d5
572 changed files with 86496 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
import { z } from 'zod';
import { baseListQuerySchema } from '@/lib/api/route-helpers';
export const createInvoiceSchema = z
.object({
clientName: z.string().min(1).max(200),
billingEmail: z.string().email().optional(),
billingAddress: z.string().max(500).optional(),
dueDate: z.string().min(1),
paymentTerms: z
.enum(['immediate', 'net10', 'net15', 'net30', 'net45', 'net60'])
.default('net30'),
currency: z.string().length(3).default('USD'),
notes: z.string().max(2000).optional(),
lineItems: z
.array(
z.object({
description: z.string().min(1),
quantity: z.coerce.number().positive().default(1),
unitPrice: z.coerce.number().min(0),
}),
)
.optional(),
expenseIds: z.array(z.string()).optional(),
})
.refine(
(data) =>
(data.lineItems && data.lineItems.length > 0) ||
(data.expenseIds && data.expenseIds.length > 0),
{ message: 'Invoice must have at least one line item or linked expense' },
);
export const updateInvoiceSchema = z.object({
clientName: z.string().min(1).max(200).optional(),
billingEmail: z.string().email().optional(),
billingAddress: z.string().max(500).optional(),
dueDate: z.string().min(1).optional(),
paymentTerms: z
.enum(['immediate', 'net10', 'net15', 'net30', 'net45', 'net60'])
.optional(),
currency: z.string().length(3).optional(),
notes: z.string().max(2000).optional(),
lineItems: z
.array(
z.object({
description: z.string().min(1),
quantity: z.coerce.number().positive().default(1),
unitPrice: z.coerce.number().min(0),
}),
)
.optional(),
expenseIds: z.array(z.string()).optional(),
});
export const recordPaymentSchema = z.object({
paymentDate: z.string().min(1),
paymentMethod: z.string().optional(),
paymentReference: z.string().optional(),
});
export const listInvoicesSchema = baseListQuerySchema.extend({
status: z.string().optional(),
clientName: z.string().optional(),
dateFrom: z.string().optional(),
dateTo: z.string().optional(),
});
export type CreateInvoiceInput = z.infer<typeof createInvoiceSchema>;
export type UpdateInvoiceInput = z.infer<typeof updateInvoiceSchema>;
export type RecordPaymentInput = z.infer<typeof recordPaymentSchema>;
export type ListInvoicesInput = z.infer<typeof listInvoicesSchema>;