| 1 | import z from 'zod' |
| 2 | |
| 3 | // Member rates are effective-dated, so an entry must be priced with the rate in |
| 4 | // effect on the day the work was done — not "now". Keying the rate map by |
| 5 | // member + work date lets a member span a rate change within one invoice. |
| 6 | export function memberRateKey(memberId |
| 7 | const iso = |
| 8 | return `${memberId} |
| 9 | } |
| 10 | |
| 11 | // Items carry optional metadata so the editor (and the server) can reason |
| 12 | // about origin without parsing the description. `source` records what spawned |
| 13 | // the line; `sourceCurrency` + `rateUsed` make the conversion auditable — |
| 14 | // these are the rates the server snapshots into `invoice_conversion_rates`. |
| 15 | const invoiceItemSourceSchema = z |
| 16 | z.object({ |
| 17 | z.object({ |
| 18 | z.object({ |
| 19 | kind: z.literal |
| 20 | memberId: z.string |
| 21 | workDate: z.string |
| 22 | }), |
| 23 | ]) |
| 24 | |
| 25 | const invoiceItemSchema = z |
| 26 | description: z.string |
| 27 | quantity: z.string |
| 28 | unitPrice: z.string |
| 29 | amount: z.string |
| 30 | source: invoiceItemSourceSchema.optional |
| 31 | sourceCurrency: z.string |
| 32 | rateUsed: z.number |
| 33 | }) |
| 34 | |
| 35 | const customFieldSchema = z |
| 36 | label: z.string |
| 37 | value: z.string |
| 38 | }) |
| 39 | |
| 40 | const invoiceDetailsBase = z |
| 41 | clientMemberIds: z.array |
| 42 | invoiceNumber: z.string |
| 43 | issueDate: z.coerce |
| 44 | dueDate: z.coerce |
| 45 | notes: z.string |
| 46 | currency: z.string |
| 47 | items: z.array |
| 48 | requirementIds: z.array |
| 49 | expenseIds: z.array |
| 50 | // Sender (From) details |
| 51 | senderLogo: z.string |
| 52 | senderSignature: z.string |
| 53 | senderName: z.string |
| 54 | senderAddress: z.string |
| 55 | senderCustomFields: z.array |
| 56 | // Client (To) details |
| 57 | clientName: z.string |
| 58 | clientAddress: z.string |
| 59 | clientCustomFields: z.array |
| 60 | // Additional details |
| 61 | paymentTerms: z.string |
| 62 | terms: z.string |
| 63 | // Discount |
| 64 | discountLabel: z.string |
| 65 | discountAmount: z.string |
| 66 | }) |
| 67 | |
| 68 | export const createInvoiceSchema = |
| 69 | projectId: z.string |
| 70 | recipientType: z.enum |
| 71 | }) |
| 72 | |
| 73 | export const updateInvoiceSchema = |
| 74 | invoiceId: z.string |
| 75 | }) |
| 76 | |
| 77 | export const sendInvoiceSchema = |
| 78 | invoiceId: z.string |
| 79 | clientMemberIds: z.array |
| 80 | }) |
| 81 | |
| 82 | export const markInvoicePaidSchema = |
| 83 | invoiceId: z.string |
| 84 | }) |
| 85 | |
| 86 | export const deleteInvoiceSchema = |
| 87 | invoiceId: z.string |
| 88 | }) |
| 89 | |
| 90 | export const extendInvoiceSchema = |
| 91 | invoiceId: z.string |
| 92 | }) |
| 93 | |
| 94 | export const createInvoiceThreadSchema = |
| 95 | invoiceId: z.string |
| 96 | title: z.string |
| 97 | body: z.string |
| 98 | }) |
| 99 | |
| 100 | export const replyToThreadSchema = |
| 101 | threadId: z.string |
| 102 | body: z.string |
| 103 | }) |
| 104 | |
| 105 | export const resolveThreadSchema = |
| 106 | threadId: z.string |
| 107 | }) |
| 108 | |
| 109 | export const changeInvoiceStatusSchema = |
| 110 | invoiceId: z.string |
| 111 | status: z.enum |
| 112 | }) |
| 113 | |
| 114 | export const invoiceFormSchema = |
| 115 | invoiceNumber: z.string |
| 116 | currency: z.string |
| 117 | issueDate: z.string |
| 118 | dueDate: z.string |
| 119 | senderLogo: z.string |
| 120 | senderSignature: z.string |
| 121 | senderName: z.string |
| 122 | senderAddress: z.string |
| 123 | senderCustomFields: z.array |
| 124 | clientName: z.string |
| 125 | clientAddress: z.string |
| 126 | clientCustomFields: z.array |
| 127 | clientMemberIds: z.array |
| 128 | items: z.array |
| 129 | expenseIds: z.array |
| 130 | selectedRequirementIds: z.array |
| 131 | paymentTerms: z.string |
| 132 | notes: z.string |
| 133 | terms: z.string |
| 134 | discountLabel: z.string |
| 135 | discountAmount: z.string |
| 136 | }) |