| |
| |
| |
| |
|
|
| import { |
| DEFAULT_TEMPLATE_LABELS, |
| DEFAULT_TEMPLATE_SETTINGS, |
| } from "@midday/invoice"; |
|
|
| |
| |
| |
| export type InvoiceLineItem = { |
| name?: string | null; |
| quantity?: number; |
| unit?: string | null; |
| price?: number; |
| vat?: number | null; |
| tax?: number | null; |
| taxRate?: number | null; |
| productId?: string; |
| }; |
|
|
| |
| |
| |
| export interface RecurringInvoiceData { |
| teamId: string; |
| userId: string; |
| customerId: string | null; |
| customerName: string | null; |
| template: unknown; |
| templateId: string | null; |
| timezone: string; |
| currency: string | null; |
| dueDateOffset: number; |
| paymentDetails: unknown; |
| fromDetails: unknown; |
| noteDetails: unknown; |
| topBlock: unknown; |
| bottomBlock: unknown; |
| vat: number | null; |
| tax: number | null; |
| discount: number | null; |
| subtotal: number | null; |
| amount: number | null; |
| lineItems: unknown; |
| } |
|
|
| |
| |
| |
| export interface BuiltInvoiceTemplate { |
| customerLabel: string; |
| title: string; |
| fromLabel: string; |
| invoiceNoLabel: string; |
| issueDateLabel: string; |
| dueDateLabel: string; |
| descriptionLabel: string; |
| priceLabel: string; |
| quantityLabel: string; |
| totalLabel: string; |
| totalSummaryLabel: string; |
| vatLabel: string; |
| subtotalLabel: string; |
| taxLabel: string; |
| discountLabel: string; |
| timezone: string; |
| paymentLabel: string; |
| noteLabel: string; |
| logoUrl: string | null; |
| currency: string; |
| dateFormat: string; |
| includeVat: boolean; |
| includeTax: boolean; |
| includeDiscount: boolean; |
| includeDecimals: boolean; |
| includeUnits: boolean; |
| includeQr: boolean; |
| includePdf: boolean; |
| includeLineItemTax: boolean; |
| lineItemTaxLabel?: string; |
| sendCopy: boolean; |
| paymentEnabled: boolean; |
| paymentTermsDays?: number; |
| taxRate: number; |
| vatRate: number; |
| size: "a4" | "letter"; |
| deliveryType: "create_and_send"; |
| locale: string; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function buildInvoiceTemplateFromRecurring( |
| recurring: RecurringInvoiceData, |
| ): BuiltInvoiceTemplate { |
| const template = (recurring.template as Record<string, unknown>) || {}; |
|
|
| return { |
| |
| customerLabel: |
| (template.customerLabel as string) ?? |
| DEFAULT_TEMPLATE_LABELS.customerLabel, |
| title: (template.title as string) ?? DEFAULT_TEMPLATE_LABELS.title, |
| fromLabel: |
| (template.fromLabel as string) ?? DEFAULT_TEMPLATE_LABELS.fromLabel, |
| invoiceNoLabel: |
| (template.invoiceNoLabel as string) ?? |
| DEFAULT_TEMPLATE_LABELS.invoiceNoLabel, |
| issueDateLabel: |
| (template.issueDateLabel as string) ?? |
| DEFAULT_TEMPLATE_LABELS.issueDateLabel, |
| dueDateLabel: |
| (template.dueDateLabel as string) ?? DEFAULT_TEMPLATE_LABELS.dueDateLabel, |
| descriptionLabel: |
| (template.descriptionLabel as string) ?? |
| DEFAULT_TEMPLATE_LABELS.descriptionLabel, |
| priceLabel: |
| (template.priceLabel as string) ?? DEFAULT_TEMPLATE_LABELS.priceLabel, |
| quantityLabel: |
| (template.quantityLabel as string) ?? |
| DEFAULT_TEMPLATE_LABELS.quantityLabel, |
| totalLabel: |
| (template.totalLabel as string) ?? DEFAULT_TEMPLATE_LABELS.totalLabel, |
| totalSummaryLabel: |
| (template.totalSummaryLabel as string) ?? |
| DEFAULT_TEMPLATE_LABELS.totalSummaryLabel, |
| vatLabel: (template.vatLabel as string) ?? DEFAULT_TEMPLATE_LABELS.vatLabel, |
| subtotalLabel: |
| (template.subtotalLabel as string) ?? |
| DEFAULT_TEMPLATE_LABELS.subtotalLabel, |
| taxLabel: (template.taxLabel as string) ?? DEFAULT_TEMPLATE_LABELS.taxLabel, |
| discountLabel: |
| (template.discountLabel as string) ?? |
| DEFAULT_TEMPLATE_LABELS.discountLabel, |
| paymentLabel: |
| (template.paymentLabel as string) ?? DEFAULT_TEMPLATE_LABELS.paymentLabel, |
| noteLabel: |
| (template.noteLabel as string) ?? DEFAULT_TEMPLATE_LABELS.noteLabel, |
|
|
| |
| timezone: recurring.timezone, |
|
|
| |
| logoUrl: (template.logoUrl as string | null) ?? null, |
| currency: recurring.currency ?? DEFAULT_TEMPLATE_SETTINGS.currency, |
| dateFormat: |
| (template.dateFormat as string) ?? DEFAULT_TEMPLATE_SETTINGS.dateFormat, |
| locale: (template.locale as string) ?? DEFAULT_TEMPLATE_SETTINGS.locale, |
| size: (template.size as "a4" | "letter") ?? DEFAULT_TEMPLATE_SETTINGS.size, |
|
|
| |
| includeVat: |
| (template.includeVat as boolean) ?? DEFAULT_TEMPLATE_SETTINGS.includeVat, |
| includeTax: |
| (template.includeTax as boolean) ?? DEFAULT_TEMPLATE_SETTINGS.includeTax, |
| includeDiscount: |
| (template.includeDiscount as boolean) ?? |
| DEFAULT_TEMPLATE_SETTINGS.includeDiscount, |
| includeDecimals: |
| (template.includeDecimals as boolean) ?? |
| DEFAULT_TEMPLATE_SETTINGS.includeDecimals, |
| includeUnits: |
| (template.includeUnits as boolean) ?? |
| DEFAULT_TEMPLATE_SETTINGS.includeUnits, |
| includeQr: |
| (template.includeQr as boolean) ?? DEFAULT_TEMPLATE_SETTINGS.includeQr, |
| includePdf: |
| (template.includePdf as boolean) ?? DEFAULT_TEMPLATE_SETTINGS.includePdf, |
| includeLineItemTax: |
| (template.includeLineItemTax as boolean) ?? |
| DEFAULT_TEMPLATE_SETTINGS.includeLineItemTax, |
| lineItemTaxLabel: |
| (template.lineItemTaxLabel as string) ?? |
| DEFAULT_TEMPLATE_LABELS.lineItemTaxLabel, |
| sendCopy: |
| (template.sendCopy as boolean) ?? DEFAULT_TEMPLATE_SETTINGS.sendCopy, |
| paymentEnabled: |
| (template.paymentEnabled as boolean) ?? |
| DEFAULT_TEMPLATE_SETTINGS.paymentEnabled, |
| paymentTermsDays: |
| (template.paymentTermsDays as number) ?? |
| DEFAULT_TEMPLATE_SETTINGS.paymentTermsDays, |
|
|
| |
| taxRate: (template.taxRate as number) ?? DEFAULT_TEMPLATE_SETTINGS.taxRate, |
| vatRate: (template.vatRate as number) ?? DEFAULT_TEMPLATE_SETTINGS.vatRate, |
|
|
| |
| deliveryType: "create_and_send", |
| }; |
| } |
|
|
| |
| |
| |
| |
| export function stringifyJsonField(field: unknown): string | null { |
| if (field === null || field === undefined) { |
| return null; |
| } |
| return JSON.stringify(field); |
| } |
|
|
| |
| |
| |
| |
| export function parseLineItems( |
| lineItems: unknown, |
| ): InvoiceLineItem[] | undefined { |
| if (!lineItems || !Array.isArray(lineItems)) { |
| return undefined; |
| } |
| return lineItems as InvoiceLineItem[]; |
| } |
|
|
| |
| |
| |
| export interface RecurringDataValidationResult { |
| isValid: boolean; |
| errors: string[]; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function validateRecurringDataIntegrity( |
| recurring: RecurringInvoiceData, |
| ): RecurringDataValidationResult { |
| const errors: string[] = []; |
|
|
| |
| if (recurring.template !== null && typeof recurring.template !== "object") { |
| errors.push("Template data is not a valid object"); |
| } |
| if (Array.isArray(recurring.template)) { |
| errors.push("Template data is an array, expected object"); |
| } |
|
|
| |
| if (!recurring.teamId) { |
| errors.push("Missing teamId"); |
| } |
| if (!recurring.userId) { |
| errors.push("Missing userId"); |
| } |
| if (!recurring.timezone) { |
| errors.push("Missing timezone"); |
| } |
|
|
| |
| if ( |
| typeof recurring.dueDateOffset !== "number" || |
| Number.isNaN(recurring.dueDateOffset) |
| ) { |
| errors.push("Invalid dueDateOffset"); |
| } |
|
|
| |
| if (recurring.lineItems !== null && recurring.lineItems !== undefined) { |
| if (!Array.isArray(recurring.lineItems)) { |
| errors.push("lineItems is not an array"); |
| } |
| } |
|
|
| return { |
| isValid: errors.length === 0, |
| errors, |
| }; |
| } |
|
|