Files
accounted/lib/providers/dto.ts
T
Mattsson 00ae3540db feat(customers): carry contact person and invoice copy recipients through migration (#1392)
* feat(customers): carry contact person and invoice copy recipients through migration

Extends the arcim-migration entity mapper, Fortnox provider mapper, canonical
DTOs, customer APIs (web + v1) and invoice send flows so contact person and
customer-level invoice CC/BCC addresses survive provider migrations. NULL
means unconfigured and empty means an explicit clear, so re-syncs enrich
legacy gaps without resurrecting deliberately removed values. Fortnox fixed
assets are split into a dedicated follow-up issue.

Fixes #1345

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(db): bump customer metadata migration past pack-slug version

Main already contains 20260803230000; keep new versions strictly newest so
Supabase branching applies them in order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(customers): complete Customer type consumers and make enrichment payload resolvable

The preview-pdf mock customer and the makeCustomer fixture now carry the
three new metadata fields, fixing the type-check failure in Build (zero
extensions) and Vercel.

The enrichment update in the migration orchestrator now spells its payload
as an object literal typed CustomerMetadataEnrichment (absent keys drop at
serialization), so the phantom-column guard resolves the columns instead of
counting another unresolvable dynamic payload past its ceiling. The cc/bcc
guards also verify element types instead of casting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 10:00:03 +02:00

451 lines
11 KiB
TypeScript

// Canonical DTO types for provider data normalization
// ============================================
// Resource Type
// ============================================
export const ResourceType = {
SalesInvoices: 'salesinvoices',
SupplierInvoices: 'supplierinvoices',
Customers: 'customers',
Suppliers: 'suppliers',
Journals: 'journals',
AccountingAccounts: 'accountingaccounts',
CompanyInformation: 'companyinformation',
AccountingPeriods: 'accountingperiods',
FinancialDimensions: 'financialdimensions',
BalanceSheet: 'balancesheet',
IncomeStatement: 'incomestatement',
TrialBalances: 'trialbalances',
Payments: 'payments',
Attachments: 'attachments',
} as const;
export type ResourceType = (typeof ResourceType)[keyof typeof ResourceType];
// ============================================
// Common
// ============================================
export interface AmountType {
value: number;
currencyCode: string;
}
export interface PostalAddress {
streetName?: string;
additionalStreetName?: string;
buildingNumber?: string;
cityName?: string;
postalZone?: string;
countrySubentity?: string;
countryCode?: string;
}
export interface Contact {
name?: string;
telephone?: string;
email?: string;
website?: string;
}
export interface PartyIdentification {
id: string;
schemeId?: string;
}
export interface PartyLegalEntity {
registrationName: string;
companyId?: string;
companyIdSchemeId?: string;
}
export interface PartyDto {
name: string;
identifications: PartyIdentification[];
postalAddress?: PostalAddress;
legalEntity?: PartyLegalEntity;
contact?: Contact;
}
export interface FinancialDimensionRef {
dimensionId: string;
dimensionValueId: string;
name?: string;
}
export interface AllowanceChargeDto {
chargeIndicator: boolean;
reason?: string;
amount: AmountType;
taxPercent?: number;
}
export interface TaxTotalDto {
taxAmount: AmountType;
taxSubtotals?: TaxSubtotalDto[];
}
export interface TaxSubtotalDto {
taxableAmount: AmountType;
taxAmount: AmountType;
taxCategory?: string;
percent?: number;
}
export interface PaginatedResponse<T> {
data: T[];
page: number;
pageSize: number;
totalCount: number;
hasMore: boolean;
}
// ============================================
// Sales Invoice
// ============================================
export type InvoiceStatusCode = 'draft' | 'sent' | 'booked' | 'paid' | 'overdue' | 'cancelled' | 'credited';
export interface LegalMonetaryTotalDto {
lineExtensionAmount: AmountType;
taxExclusiveAmount?: AmountType;
taxInclusiveAmount?: AmountType;
allowanceTotalAmount?: AmountType;
chargeTotalAmount?: AmountType;
payableRoundingAmount?: AmountType;
payableAmount: AmountType;
}
export interface PaymentStatusDto {
paid: boolean;
balance: AmountType;
lastPaymentDate?: string;
}
export interface SalesInvoiceLineDto {
id: string;
description?: string;
quantity?: number;
unitCode?: string;
unitPrice?: AmountType;
lineExtensionAmount: AmountType;
taxPercent?: number;
taxAmount?: AmountType;
accountNumber?: string;
itemName?: string;
articleNumber?: string;
financialDimensions?: FinancialDimensionRef[];
}
export interface SalesInvoiceDto {
id: string;
invoiceNumber: string;
issueDate: string;
dueDate?: string;
deliveryDate?: string;
invoiceTypeCode?: string;
currencyCode: string;
status: InvoiceStatusCode;
supplier: PartyDto;
customer: PartyDto;
lines: SalesInvoiceLineDto[];
allowanceCharges?: AllowanceChargeDto[];
taxTotal?: TaxTotalDto;
legalMonetaryTotal: LegalMonetaryTotalDto;
paymentStatus: PaymentStatusDto;
paymentTerms?: string;
note?: string;
buyerReference?: string;
orderReference?: string;
financialDimensions?: FinancialDimensionRef[];
createdAt?: string;
updatedAt?: string;
_raw?: Record<string, unknown>;
}
// ============================================
// Supplier Invoice
// ============================================
export interface SupplierInvoiceLineDto {
id: string;
description?: string;
quantity?: number;
unitCode?: string;
unitPrice?: AmountType;
lineExtensionAmount: AmountType;
taxPercent?: number;
taxAmount?: AmountType;
accountNumber?: string;
itemName?: string;
articleNumber?: string;
financialDimensions?: FinancialDimensionRef[];
}
export interface SupplierInvoiceDto {
id: string;
invoiceNumber: string;
issueDate: string;
dueDate?: string;
deliveryDate?: string;
invoiceTypeCode?: string;
currencyCode: string;
status: InvoiceStatusCode;
supplier: PartyDto;
buyer: PartyDto;
lines: SupplierInvoiceLineDto[];
allowanceCharges?: AllowanceChargeDto[];
taxTotal?: TaxTotalDto;
legalMonetaryTotal: LegalMonetaryTotalDto;
paymentStatus: PaymentStatusDto;
paymentTerms?: string;
note?: string;
ocrNumber?: string;
financialDimensions?: FinancialDimensionRef[];
createdAt?: string;
updatedAt?: string;
_raw?: Record<string, unknown>;
}
// ============================================
// Customer
// ============================================
export type CustomerType = 'company' | 'private';
export interface CustomerDto {
id: string;
customerNumber: string;
type?: CustomerType;
party: PartyDto;
invoiceEmailCcAddresses?: string[];
invoiceEmailBccAddresses?: string[];
deliveryAddresses?: PostalAddress[];
financialDimensions?: FinancialDimensionRef[];
active: boolean;
vatNumber?: string;
defaultPaymentTermsDays?: number;
note?: string;
createdAt?: string;
updatedAt?: string;
_raw?: Record<string, unknown>;
}
// ============================================
// Supplier
// ============================================
export interface SupplierDto {
id: string;
supplierNumber: string;
party: PartyDto;
deliveryAddresses?: PostalAddress[];
financialDimensions?: FinancialDimensionRef[];
active: boolean;
vatNumber?: string;
bankAccount?: string;
bankGiro?: string;
plusGiro?: string;
defaultPaymentTermsDays?: number;
note?: string;
createdAt?: string;
updatedAt?: string;
_raw?: Record<string, unknown>;
}
// ============================================
// Journal
// ============================================
export interface AccountingEntryDto {
accountNumber: string;
accountName?: string;
debit: number;
credit: number;
transactionDate?: string;
description?: string;
financialDimensions?: FinancialDimensionRef[];
}
export interface AccountingSeriesDto {
id: string;
description?: string;
}
export interface JournalDto {
id: string;
journalNumber: string;
series?: AccountingSeriesDto;
description?: string;
registrationDate: string;
fiscalYear?: number;
entries: AccountingEntryDto[];
totalDebit?: AmountType;
totalCredit?: AmountType;
createdAt?: string;
updatedAt?: string;
_raw?: Record<string, unknown>;
}
// ============================================
// Accounting Account
// ============================================
export type AccountType = 'asset' | 'liability' | 'equity' | 'revenue' | 'expense' | 'other';
export interface AccountingAccountDto {
accountNumber: string;
name: string;
description?: string;
type?: AccountType;
vatCode?: string;
active: boolean;
balanceBroughtForward?: number;
balanceCarriedForward?: number;
sruCode?: string;
createdAt?: string;
updatedAt?: string;
_raw?: Record<string, unknown>;
}
// ============================================
// Company Information
// ============================================
export interface CompanyInformationDto {
companyName: string;
organizationNumber?: string;
legalEntity?: PartyLegalEntity;
address?: PostalAddress;
contact?: Contact;
vatNumber?: string;
fiscalYearStart?: string;
baseCurrency?: string;
_raw?: Record<string, unknown>;
}
// ============================================
// Payment
// ============================================
export type PaymentMethodCode = 'bank_transfer' | 'card' | 'cash' | 'autogiro' | 'bankgiro' | 'plusgiro' | 'swish' | 'other';
export interface PaymentDto {
id: string;
paymentNumber?: string;
invoiceId: string;
paymentDate: string;
amount: AmountType;
paymentMethod?: PaymentMethodCode;
reference?: string;
note?: string;
createdAt?: string;
updatedAt?: string;
_raw?: Record<string, unknown>;
}
// ============================================
// Accounting Period
// ============================================
export type PeriodStatus = 'open' | 'closed' | 'locked';
export interface AccountingPeriodDto {
id: string;
fiscalYear: number;
fromDate: string;
toDate: string;
status?: PeriodStatus;
description?: string;
_raw?: Record<string, unknown>;
}
// ============================================
// Financial Dimension
// ============================================
export interface FinancialDimensionValueDto {
id: string;
code: string;
name: string;
active: boolean;
}
export interface FinancialDimensionDto {
id: string;
name: string;
description?: string;
values: FinancialDimensionValueDto[];
_raw?: Record<string, unknown>;
}
// ============================================
// Reports
// ============================================
export interface FinancialReportCategoryDto {
name: string;
amount: AmountType;
children?: FinancialReportCategoryDto[];
accounts?: { accountNumber: string; name?: string; amount: AmountType }[];
}
export interface BalanceSheetDto {
fiscalYear: number;
periodEnd: string;
baseCurrency: string;
assets: FinancialReportCategoryDto;
liabilities: FinancialReportCategoryDto;
equity: FinancialReportCategoryDto;
_raw?: Record<string, unknown>;
}
export interface IncomeStatementDto {
fiscalYear: number;
periodStart: string;
periodEnd: string;
baseCurrency: string;
revenue: FinancialReportCategoryDto;
expenses: FinancialReportCategoryDto;
netIncome: AmountType;
_raw?: Record<string, unknown>;
}
export interface TrialBalanceEntryDto {
accountNumber: string;
accountName?: string;
openingDebit: number;
openingCredit: number;
periodDebit: number;
periodCredit: number;
closingDebit: number;
closingCredit: number;
}
export interface TrialBalanceDto {
fiscalYear: number;
periodStart: string;
periodEnd: string;
baseCurrency: string;
entries: TrialBalanceEntryDto[];
_raw?: Record<string, unknown>;
}
// ============================================
// Attachment
// ============================================
export type AttachmentType = 'pdf' | 'image' | 'xml' | 'other';
export interface AttachmentDto {
id: string;
fileName: string;
mimeType?: string;
type?: AttachmentType;
size?: number;
downloadUrl?: string;
createdAt?: string;
_raw?: Record<string, unknown>;
}