feat: Implement PEPPOL and XRechnung validators for compliance with e-invoice specifications
- Added PeppolValidator class to validate PEPPOL BIS 3.0 invoices, including checks for endpoint IDs, document type IDs, process IDs, party identification, and business rules. - Implemented validation for GLN check digits, document types, and transport protocols specific to PEPPOL. - Added XRechnungValidator class to validate XRechnung 3.0 invoices, focusing on German-specific requirements such as Leitweg-ID, payment details, seller contact, and tax registration. - Included validation for IBAN and BIC formats, ensuring compliance with SEPA regulations. - Established methods for checking B2G invoice indicators and validating mandatory fields for both validators.
This commit is contained in:
596
ts/formats/semantic/semantic.adapter.ts
Normal file
596
ts/formats/semantic/semantic.adapter.ts
Normal file
@@ -0,0 +1,596 @@
|
||||
/**
|
||||
* Adapter for converting between EInvoice and EN16931 Semantic Model
|
||||
* Provides bidirectional conversion capabilities
|
||||
*/
|
||||
|
||||
import { EInvoice } from '../../einvoice.js';
|
||||
import type {
|
||||
EN16931SemanticModel,
|
||||
Seller,
|
||||
Buyer,
|
||||
PostalAddress,
|
||||
Contact,
|
||||
InvoiceLine,
|
||||
VATBreakdown,
|
||||
DocumentTotals,
|
||||
PaymentInstructions,
|
||||
Allowance,
|
||||
Charge,
|
||||
Period,
|
||||
DeliveryInformation,
|
||||
PriceDetails,
|
||||
VATInformation,
|
||||
ItemInformation
|
||||
} from './bt-bg.model.js';
|
||||
|
||||
/**
|
||||
* Adapter for converting between EInvoice and EN16931 Semantic Model
|
||||
*/
|
||||
export class SemanticModelAdapter {
|
||||
/**
|
||||
* Convert EInvoice to EN16931 Semantic Model
|
||||
*/
|
||||
public toSemanticModel(invoice: EInvoice): EN16931SemanticModel {
|
||||
return {
|
||||
// Core document information
|
||||
documentInformation: {
|
||||
invoiceNumber: invoice.accountingDocId,
|
||||
issueDate: invoice.issueDate,
|
||||
typeCode: this.mapInvoiceType(invoice.accountingDocType),
|
||||
currencyCode: invoice.currency,
|
||||
notes: invoice.notes ? this.mapNotes(invoice.notes) : undefined
|
||||
},
|
||||
|
||||
// Process metadata
|
||||
processControl: invoice.metadata?.profileId ? {
|
||||
businessProcessType: invoice.metadata.businessProcessId,
|
||||
specificationIdentifier: invoice.metadata.profileId
|
||||
} : undefined,
|
||||
|
||||
// References
|
||||
references: {
|
||||
buyerReference: invoice.metadata?.buyerReference,
|
||||
projectReference: invoice.projectReference,
|
||||
contractReference: invoice.metadata?.contractReference,
|
||||
purchaseOrderReference: invoice.metadata?.extensions?.purchaseOrderReference,
|
||||
salesOrderReference: invoice.metadata?.extensions?.salesOrderReference,
|
||||
precedingInvoices: invoice.metadata?.extensions?.precedingInvoices
|
||||
},
|
||||
|
||||
// Seller
|
||||
seller: {
|
||||
...this.mapSeller(invoice.from),
|
||||
postalAddress: this.mapAddress(invoice.from),
|
||||
contact: this.mapContact(invoice.from)
|
||||
},
|
||||
|
||||
// Buyer
|
||||
buyer: {
|
||||
...this.mapBuyer(invoice.to),
|
||||
postalAddress: this.mapAddress(invoice.to),
|
||||
contact: this.mapContact(invoice.to)
|
||||
},
|
||||
|
||||
// Payee (if different from seller)
|
||||
payee: invoice.metadata?.extensions?.payee,
|
||||
|
||||
// Tax representative
|
||||
taxRepresentative: invoice.metadata?.extensions?.taxRepresentative,
|
||||
|
||||
// Delivery
|
||||
delivery: this.mapDelivery(invoice),
|
||||
|
||||
// Invoice period
|
||||
invoicingPeriod: invoice.metadata?.invoicingPeriod ? {
|
||||
startDate: invoice.metadata.invoicingPeriod.startDate,
|
||||
endDate: invoice.metadata.invoicingPeriod.endDate,
|
||||
descriptionCode: invoice.metadata.invoicingPeriod.descriptionCode
|
||||
} : undefined,
|
||||
|
||||
// Payment instructions
|
||||
paymentInstructions: this.mapPaymentInstructions(invoice),
|
||||
|
||||
// Payment card info
|
||||
paymentCardInfo: invoice.metadata?.extensions?.paymentCard,
|
||||
|
||||
// Direct debit
|
||||
directDebit: invoice.metadata?.extensions?.directDebit,
|
||||
|
||||
// Payment terms
|
||||
paymentTerms: invoice.dueInDays !== undefined ? {
|
||||
note: `Payment due in ${invoice.dueInDays} days`
|
||||
} : undefined,
|
||||
|
||||
// Document level allowances and charges
|
||||
documentLevelAllowances: invoice.metadata?.extensions?.documentAllowances,
|
||||
documentLevelCharges: invoice.metadata?.extensions?.documentCharges,
|
||||
|
||||
// Document totals
|
||||
documentTotals: this.mapDocumentTotals(invoice),
|
||||
|
||||
// VAT breakdown
|
||||
vatBreakdown: this.mapVATBreakdown(invoice),
|
||||
|
||||
// Additional documents
|
||||
additionalDocuments: invoice.metadata?.extensions?.supportingDocuments,
|
||||
|
||||
// Invoice lines
|
||||
invoiceLines: this.mapInvoiceLines(invoice.items || [])
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert EN16931 Semantic Model to EInvoice
|
||||
*/
|
||||
public fromSemanticModel(model: EN16931SemanticModel): EInvoice {
|
||||
const invoice = new EInvoice();
|
||||
invoice.accountingDocId = model.documentInformation.invoiceNumber;
|
||||
invoice.issueDate = model.documentInformation.issueDate;
|
||||
invoice.accountingDocType = this.reverseMapInvoiceType(model.documentInformation.typeCode);
|
||||
invoice.currency = model.documentInformation.currencyCode;
|
||||
invoice.from = this.reverseMapSeller(model.seller);
|
||||
invoice.to = this.reverseMapBuyer(model.buyer);
|
||||
invoice.items = this.reverseMapInvoiceLines(model.invoiceLines);
|
||||
|
||||
// Set metadata
|
||||
if (model.processControl) {
|
||||
invoice.metadata = {
|
||||
...invoice.metadata,
|
||||
profileId: model.processControl.specificationIdentifier,
|
||||
businessProcessId: model.processControl.businessProcessType
|
||||
};
|
||||
}
|
||||
|
||||
// Set references
|
||||
if (model.references) {
|
||||
invoice.metadata = {
|
||||
...invoice.metadata,
|
||||
buyerReference: model.references.buyerReference,
|
||||
contractReference: model.references.contractReference,
|
||||
extensions: {
|
||||
...invoice.metadata?.extensions,
|
||||
purchaseOrderReference: model.references.purchaseOrderReference,
|
||||
salesOrderReference: model.references.salesOrderReference,
|
||||
precedingInvoices: model.references.precedingInvoices
|
||||
}
|
||||
};
|
||||
invoice.projectReference = model.references.projectReference;
|
||||
}
|
||||
|
||||
// Set payment terms
|
||||
if (model.paymentTerms?.note) {
|
||||
const daysMatch = model.paymentTerms.note.match(/(\d+) days/);
|
||||
if (daysMatch) {
|
||||
invoice.dueInDays = parseInt(daysMatch[1], 10);
|
||||
}
|
||||
}
|
||||
|
||||
// Set payment options
|
||||
if (model.paymentInstructions.paymentAccountIdentifier) {
|
||||
invoice.paymentOptions = {
|
||||
sepa: {
|
||||
iban: model.paymentInstructions.paymentAccountIdentifier,
|
||||
bic: model.paymentInstructions.paymentServiceProviderIdentifier
|
||||
},
|
||||
bankInfo: {
|
||||
accountHolder: model.paymentInstructions.paymentAccountName || '',
|
||||
institutionName: model.paymentInstructions.paymentServiceProviderIdentifier || ''
|
||||
}
|
||||
} as any;
|
||||
}
|
||||
|
||||
// Set extensions
|
||||
if (model.payee || model.taxRepresentative || model.documentLevelAllowances) {
|
||||
invoice.metadata = {
|
||||
...invoice.metadata,
|
||||
extensions: {
|
||||
...invoice.metadata?.extensions,
|
||||
payee: model.payee,
|
||||
taxRepresentative: model.taxRepresentative,
|
||||
documentAllowances: model.documentLevelAllowances,
|
||||
documentCharges: model.documentLevelCharges,
|
||||
supportingDocuments: model.additionalDocuments,
|
||||
paymentCard: model.paymentCardInfo,
|
||||
directDebit: model.directDebit,
|
||||
taxDetails: model.vatBreakdown
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return invoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map invoice type code
|
||||
*/
|
||||
private mapInvoiceType(type: string): string {
|
||||
const typeMap: Record<string, string> = {
|
||||
'invoice': '380',
|
||||
'creditNote': '381',
|
||||
'debitNote': '383',
|
||||
'correctedInvoice': '384',
|
||||
'prepaymentInvoice': '386',
|
||||
'selfBilledInvoice': '389',
|
||||
'invoice_380': '380',
|
||||
'credit_note_381': '381'
|
||||
};
|
||||
return typeMap[type] || '380';
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse map invoice type code
|
||||
*/
|
||||
private reverseMapInvoiceType(code: string): string {
|
||||
const typeMap: Record<string, string> = {
|
||||
'380': 'invoice',
|
||||
'381': 'creditNote',
|
||||
'383': 'debitNote',
|
||||
'384': 'correctedInvoice',
|
||||
'386': 'prepaymentInvoice',
|
||||
'389': 'selfBilledInvoice'
|
||||
};
|
||||
return typeMap[code] || 'invoice';
|
||||
}
|
||||
|
||||
/**
|
||||
* Map notes
|
||||
*/
|
||||
private mapNotes(notes: string | string[]): Array<{ noteContent: string }> {
|
||||
const notesArray = Array.isArray(notes) ? notes : [notes];
|
||||
return notesArray.map(note => ({ noteContent: note }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Map seller information
|
||||
*/
|
||||
private mapSeller(from: EInvoice['from']): Seller {
|
||||
const contact = from as any;
|
||||
if (contact.type === 'company') {
|
||||
return {
|
||||
name: contact.name || '',
|
||||
tradingName: contact.tradingName,
|
||||
identifier: contact.registrationDetails?.registrationId,
|
||||
legalRegistrationIdentifier: contact.registrationDetails?.registrationId,
|
||||
vatIdentifier: contact.registrationDetails?.vatId || contact.vatNumber,
|
||||
taxRegistrationIdentifier: contact.taxId,
|
||||
additionalLegalInfo: contact.description,
|
||||
electronicAddress: contact.email || contact.contact?.email
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
name: contact.name || `${contact.firstName || ''} ${contact.lastName || ''}`.trim(),
|
||||
identifier: contact.registrationDetails?.registrationId,
|
||||
vatIdentifier: contact.registrationDetails?.vatId || contact.vatNumber,
|
||||
electronicAddress: contact.email
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map buyer information
|
||||
*/
|
||||
private mapBuyer(to: EInvoice['to']): Buyer {
|
||||
const contact = to as any;
|
||||
if (contact.type === 'company') {
|
||||
return {
|
||||
name: contact.name || '',
|
||||
tradingName: contact.tradingName,
|
||||
identifier: contact.registrationDetails?.registrationId,
|
||||
legalRegistrationIdentifier: contact.registrationDetails?.registrationId,
|
||||
vatIdentifier: contact.registrationDetails?.vatId || contact.vatNumber,
|
||||
electronicAddress: contact.email || contact.contact?.email
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
name: contact.name || `${contact.firstName || ''} ${contact.lastName || ''}`.trim(),
|
||||
identifier: contact.registrationDetails?.registrationId,
|
||||
vatIdentifier: contact.registrationDetails?.vatId || contact.vatNumber,
|
||||
electronicAddress: contact.email
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map address
|
||||
*/
|
||||
private mapAddress(party: EInvoice['from'] | EInvoice['to']): PostalAddress {
|
||||
const contact = party as any;
|
||||
const address: PostalAddress = {
|
||||
countryCode: contact.address?.country || contact.country || ''
|
||||
};
|
||||
|
||||
if (contact.address) {
|
||||
if (typeof contact.address === 'string') {
|
||||
const addressParts = contact.address.split(',').map((s: string) => s.trim());
|
||||
address.addressLine1 = addressParts[0];
|
||||
if (addressParts.length > 1) address.addressLine2 = addressParts[1];
|
||||
} else if (typeof contact.address === 'object') {
|
||||
address.addressLine1 = [contact.address.streetName, contact.address.houseNumber].filter(Boolean).join(' ');
|
||||
address.city = contact.address.city;
|
||||
address.postCode = contact.address.postalCode;
|
||||
address.countryCode = contact.address.country || address.countryCode;
|
||||
}
|
||||
}
|
||||
|
||||
// Support both nested and flat structures
|
||||
if (!address.city) address.city = contact.city;
|
||||
if (!address.postCode) address.postCode = contact.postalCode;
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map contact information
|
||||
*/
|
||||
private mapContact(party: EInvoice['from'] | EInvoice['to']): Contact | undefined {
|
||||
const contact = party as any;
|
||||
if (contact.type === 'company' && contact.contact) {
|
||||
return {
|
||||
contactPoint: contact.contact.name,
|
||||
telephoneNumber: contact.contact.phone,
|
||||
emailAddress: contact.contact.email
|
||||
};
|
||||
} else if (contact.type === 'person') {
|
||||
return {
|
||||
contactPoint: contact.name || `${contact.firstName || ''} ${contact.lastName || ''}`.trim(),
|
||||
telephoneNumber: contact.phone,
|
||||
emailAddress: contact.email
|
||||
};
|
||||
} else if (contact.email || contact.phone) {
|
||||
// Fallback for any contact with email or phone
|
||||
return {
|
||||
contactPoint: contact.name,
|
||||
telephoneNumber: contact.phone,
|
||||
emailAddress: contact.email
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map delivery information
|
||||
*/
|
||||
private mapDelivery(invoice: EInvoice): DeliveryInformation | undefined {
|
||||
const delivery = invoice.metadata?.extensions?.delivery;
|
||||
if (!delivery) return undefined;
|
||||
|
||||
return {
|
||||
name: delivery.name,
|
||||
locationIdentifier: delivery.locationId,
|
||||
actualDeliveryDate: delivery.actualDate,
|
||||
deliveryAddress: delivery.address ? {
|
||||
addressLine1: delivery.address.line1,
|
||||
addressLine2: delivery.address.line2,
|
||||
city: delivery.address.city,
|
||||
postCode: delivery.address.postCode,
|
||||
countryCode: delivery.address.countryCode
|
||||
} : undefined
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map payment instructions
|
||||
*/
|
||||
private mapPaymentInstructions(invoice: EInvoice): PaymentInstructions {
|
||||
const paymentMeans = invoice.metadata?.extensions?.paymentMeans;
|
||||
|
||||
return {
|
||||
paymentMeansTypeCode: paymentMeans?.paymentMeansCode || '30', // Default to credit transfer
|
||||
paymentMeansText: paymentMeans?.paymentMeansText,
|
||||
remittanceInformation: paymentMeans?.remittanceInformation,
|
||||
paymentAccountIdentifier: invoice.paymentAccount?.iban,
|
||||
paymentAccountName: invoice.paymentAccount?.accountName,
|
||||
paymentServiceProviderIdentifier: invoice.paymentAccount?.bic || invoice.paymentAccount?.institutionName
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map document totals
|
||||
*/
|
||||
private mapDocumentTotals(invoice: EInvoice): DocumentTotals {
|
||||
return {
|
||||
lineExtensionAmount: invoice.totalNet,
|
||||
taxExclusiveAmount: invoice.totalNet,
|
||||
taxInclusiveAmount: invoice.totalGross,
|
||||
allowanceTotalAmount: invoice.metadata?.extensions?.documentAllowances?.reduce(
|
||||
(sum, a) => sum + a.amount, 0
|
||||
),
|
||||
chargeTotalAmount: invoice.metadata?.extensions?.documentCharges?.reduce(
|
||||
(sum, c) => sum + c.amount, 0
|
||||
),
|
||||
prepaidAmount: invoice.metadata?.extensions?.prepaidAmount,
|
||||
roundingAmount: invoice.metadata?.extensions?.roundingAmount,
|
||||
payableAmount: invoice.totalGross
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map VAT breakdown
|
||||
*/
|
||||
private mapVATBreakdown(invoice: EInvoice): VATBreakdown[] | undefined {
|
||||
const taxDetails = invoice.metadata?.extensions?.taxDetails;
|
||||
if (!taxDetails) {
|
||||
// Create default VAT breakdown from invoice totals
|
||||
if (invoice.totalVat > 0) {
|
||||
return [{
|
||||
vatCategoryTaxableAmount: invoice.totalNet,
|
||||
vatCategoryTaxAmount: invoice.totalVat,
|
||||
vatCategoryCode: 'S', // Standard rate
|
||||
vatCategoryRate: (invoice.totalVat / invoice.totalNet) * 100
|
||||
}];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return taxDetails as VATBreakdown[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Map invoice lines
|
||||
*/
|
||||
private mapInvoiceLines(items: EInvoice['items']): InvoiceLine[] {
|
||||
if (!items) return [];
|
||||
|
||||
return items.map((item, index) => ({
|
||||
identifier: (index + 1).toString(),
|
||||
note: (item as any).description || (item as any).text || '',
|
||||
invoicedQuantity: item.unitQuantity,
|
||||
invoicedQuantityUnitOfMeasureCode: item.unitType || 'C62',
|
||||
lineExtensionAmount: item.unitNetPrice * item.unitQuantity,
|
||||
purchaseOrderLineReference: (item as any).purchaseOrderLineRef,
|
||||
buyerAccountingReference: (item as any).buyerAccountingRef,
|
||||
period: (item as any).period,
|
||||
allowances: (item as any).allowances,
|
||||
charges: (item as any).charges,
|
||||
priceDetails: {
|
||||
itemNetPrice: item.unitNetPrice,
|
||||
itemPriceDiscount: (item as any).priceDiscount,
|
||||
itemGrossPrice: (item as any).grossPrice,
|
||||
itemPriceBaseQuantity: (item as any).priceBaseQuantity || 1
|
||||
},
|
||||
vatInformation: {
|
||||
categoryCode: this.mapVATCategory(item.vatPercentage),
|
||||
rate: item.vatPercentage
|
||||
},
|
||||
itemInformation: {
|
||||
name: item.name,
|
||||
description: (item as any).description || (item as any).text || '',
|
||||
sellersIdentifier: item.articleNumber,
|
||||
buyersIdentifier: (item as any).buyersItemId,
|
||||
standardIdentifier: (item as any).gtin || (item as any).ean,
|
||||
classificationIdentifier: (item as any).unspsc,
|
||||
originCountryCode: (item as any).originCountry,
|
||||
attributes: (item as any).attributes
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Map VAT category from percentage
|
||||
*/
|
||||
private mapVATCategory(percentage?: number): string {
|
||||
if (percentage === undefined || percentage === null) return 'S';
|
||||
if (percentage === 0) return 'Z';
|
||||
if (percentage > 0) return 'S';
|
||||
return 'E'; // Exempt
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse map seller
|
||||
*/
|
||||
private reverseMapSeller(seller: Seller & { postalAddress: PostalAddress }): EInvoice['from'] {
|
||||
const isCompany = seller.legalRegistrationIdentifier || seller.tradingName;
|
||||
|
||||
return {
|
||||
type: isCompany ? 'company' : 'person',
|
||||
name: seller.name,
|
||||
description: seller.additionalLegalInfo || '',
|
||||
address: {
|
||||
streetName: seller.postalAddress.addressLine1 || '',
|
||||
houseNumber: '',
|
||||
city: seller.postalAddress.city || '',
|
||||
postalCode: seller.postalAddress.postCode || '',
|
||||
country: seller.postalAddress.countryCode || ''
|
||||
},
|
||||
registrationDetails: {
|
||||
vatId: seller.vatIdentifier || '',
|
||||
registrationId: seller.identifier || seller.legalRegistrationIdentifier || '',
|
||||
registrationName: seller.name
|
||||
},
|
||||
status: 'active',
|
||||
foundedDate: {
|
||||
year: new Date().getFullYear(),
|
||||
month: new Date().getMonth() + 1,
|
||||
day: new Date().getDate()
|
||||
}
|
||||
} as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse map buyer
|
||||
*/
|
||||
private reverseMapBuyer(buyer: Buyer & { postalAddress: PostalAddress }): EInvoice['to'] {
|
||||
const isCompany = buyer.legalRegistrationIdentifier || buyer.tradingName;
|
||||
|
||||
return {
|
||||
type: isCompany ? 'company' : 'person',
|
||||
name: buyer.name,
|
||||
description: '',
|
||||
address: {
|
||||
streetName: buyer.postalAddress.addressLine1 || '',
|
||||
houseNumber: '',
|
||||
city: buyer.postalAddress.city || '',
|
||||
postalCode: buyer.postalAddress.postCode || '',
|
||||
country: buyer.postalAddress.countryCode || ''
|
||||
},
|
||||
registrationDetails: {
|
||||
vatId: buyer.vatIdentifier || '',
|
||||
registrationId: buyer.identifier || buyer.legalRegistrationIdentifier || '',
|
||||
registrationName: buyer.name
|
||||
},
|
||||
status: 'active',
|
||||
foundedDate: {
|
||||
year: new Date().getFullYear(),
|
||||
month: new Date().getMonth() + 1,
|
||||
day: new Date().getDate()
|
||||
}
|
||||
} as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse map invoice lines
|
||||
*/
|
||||
private reverseMapInvoiceLines(lines: InvoiceLine[]): EInvoice['items'] {
|
||||
return lines.map((line, index) => ({
|
||||
position: index + 1,
|
||||
name: line.itemInformation.name,
|
||||
description: line.itemInformation.description || '',
|
||||
unitQuantity: line.invoicedQuantity,
|
||||
unitType: line.invoicedQuantityUnitOfMeasureCode,
|
||||
unitNetPrice: line.priceDetails.itemNetPrice,
|
||||
vatPercentage: line.vatInformation.rate || 0,
|
||||
articleNumber: line.itemInformation.sellersIdentifier || ''
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate semantic model completeness
|
||||
*/
|
||||
public validateSemanticModel(model: EN16931SemanticModel): string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
// Check mandatory fields
|
||||
if (!model.documentInformation.invoiceNumber) {
|
||||
errors.push('BT-1: Invoice number is mandatory');
|
||||
}
|
||||
if (!model.documentInformation.issueDate) {
|
||||
errors.push('BT-2: Invoice issue date is mandatory');
|
||||
}
|
||||
if (!model.documentInformation.typeCode) {
|
||||
errors.push('BT-3: Invoice type code is mandatory');
|
||||
}
|
||||
if (!model.documentInformation.currencyCode) {
|
||||
errors.push('BT-5: Invoice currency code is mandatory');
|
||||
}
|
||||
if (!model.seller?.name) {
|
||||
errors.push('BT-27: Seller name is mandatory');
|
||||
}
|
||||
if (!model.seller?.postalAddress?.countryCode) {
|
||||
errors.push('BT-40: Seller country code is mandatory');
|
||||
}
|
||||
if (!model.buyer?.name) {
|
||||
errors.push('BT-44: Buyer name is mandatory');
|
||||
}
|
||||
if (!model.buyer?.postalAddress?.countryCode) {
|
||||
errors.push('BT-55: Buyer country code is mandatory');
|
||||
}
|
||||
if (!model.documentTotals) {
|
||||
errors.push('BG-22: Document totals are mandatory');
|
||||
}
|
||||
if (!model.invoiceLines || model.invoiceLines.length === 0) {
|
||||
errors.push('BG-25: At least one invoice line is mandatory');
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user