feat: add invoice lifecycle management #2

Merged
kdaniel2410 merged 2 commits from codex/invoice-lifecycle into main 2026-06-25 11:18:13 +00:00
5 changed files with 2128 additions and 1 deletions
Showing only changes of commit 3373762d97 - Show all commits
+4
View File
@@ -0,0 +1,4 @@
ALTER TABLE `invoices` ADD `status` text DEFAULT 'draft' NOT NULL;--> statement-breakpoint
ALTER TABLE `invoices` ADD `sent_at` integer;--> statement-breakpoint
ALTER TABLE `invoices` ADD `paid_at` integer;--> statement-breakpoint
ALTER TABLE `invoices` ADD `voided_at` integer;
File diff suppressed because it is too large Load Diff
+7
View File
@@ -113,6 +113,13 @@
"when": 1782338642144, "when": 1782338642144,
"tag": "0015_fuzzy_vampiro", "tag": "0015_fuzzy_vampiro",
"breakpoints": true "breakpoints": true
},
{
"idx": 16,
"version": "6",
"when": 1782383895120,
"tag": "0016_outstanding_talkback",
"breakpoints": true
} }
] ]
} }
+4
View File
@@ -1,9 +1,13 @@
import { z } from 'zod'; import { z } from 'zod';
import { moneyGbp, optionalText, requiredText } from './shared.schema'; import { moneyGbp, optionalText, requiredText } from './shared.schema';
export const invoiceStatusValues = ['draft', 'sent', 'paid', 'void'] as const;
export const invoiceStatusSchema = z.enum(invoiceStatusValues);
export const invoiceCreateSchema = z.object({ export const invoiceCreateSchema = z.object({
clientId: requiredText('Client'), clientId: requiredText('Client'),
invoiceNumber: requiredText('Invoice number', 80), invoiceNumber: requiredText('Invoice number', 80),
status: invoiceStatusSchema.default('draft'),
issueDate: requiredText('Issue date', 40), issueDate: requiredText('Issue date', 40),
dueDate: requiredText('Due date', 40), dueDate: requiredText('Due date', 40),
subtotalGbp: moneyGbp, subtotalGbp: moneyGbp,
+7 -1
View File
@@ -1,8 +1,10 @@
import { index, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; import { index, integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { clients } from './clients.schema'; import { clients } from './clients.schema';
import { organizations } from './organizations.schema'; import { organizations } from './organizations.schema';
import { timestamps } from './shared.schema'; import { timestamps } from './shared.schema';
export const invoiceStatusValues = ['draft', 'sent', 'paid', 'void'] as const;
export const invoices = sqliteTable( export const invoices = sqliteTable(
'invoices', 'invoices',
{ {
@@ -16,6 +18,10 @@ export const invoices = sqliteTable(
.notNull() .notNull()
.references(() => clients.id, { onDelete: 'restrict' }), .references(() => clients.id, { onDelete: 'restrict' }),
invoiceNumber: text('invoice_number').notNull().unique(), invoiceNumber: text('invoice_number').notNull().unique(),
status: text('status', { enum: invoiceStatusValues }).notNull().default('draft'),
sentAt: integer('sent_at', { mode: 'timestamp_ms' }),
paidAt: integer('paid_at', { mode: 'timestamp_ms' }),
voidedAt: integer('voided_at', { mode: 'timestamp_ms' }),
issueDate: text('issue_date').notNull(), issueDate: text('issue_date').notNull(),
dueDate: text('due_date').notNull(), dueDate: text('due_date').notNull(),
subtotalGbp: real('subtotal_gbp').notNull().default(0), subtotalGbp: real('subtotal_gbp').notNull().default(0),