Skip to content

Commit

Permalink
fix(tools): fix peppol invoice fields
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanvanherwijnen committed Dec 6, 2024
1 parent a5da5f3 commit 0b1ed98
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 20 deletions.
1 change: 1 addition & 0 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"@modular-api/fastify-oidc": "^0.6.4",
"@mollie/api-client": "^4.1.0",
"@slimfact/app": "^0.1.0",
"@slimfact/tools": "^0.1.0",
"@trpc/server": "^10.45.2",
"@vitrify/tools": "^0.2.3",
"axios": "^1.7.8",
Expand Down
4 changes: 4 additions & 0 deletions packages/tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
"./epc-qr": {
"types": "./dist/types/epc-qr/index.d.ts",
"import": "./dist/epc-qr/index.js"
},
"./peppol": {
"types": "./dist/types/peppol/index.d.ts",
"import": "./dist/peppol/index.js"
}
},
"scripts": {
Expand Down
113 changes: 95 additions & 18 deletions packages/tools/src/peppol/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
Invoice,
InvoiceDiscount,
InvoiceLine,
InvoiceSurcharge
} from '@modular-api/fastify-checkout'
Expand Down Expand Up @@ -28,7 +29,9 @@ export const createPeppolInvoice = ({
<cbc:DueDate>${invoice.dueDate}</cbc:DueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>${invoice.currency}</cbc:DocumentCurrencyCode>
<cbc:BuyerReference>${invoice.projectId}</cbc:BuyerReference>
<cac:ProjectReference>
<cbc:ID>${invoice.projectId ?? ''}</cbc:ID>
</cac:ProjectReference>
<cac:AccountingSupplierParty>
<cac:Party>
<cbc:EndpointID schemeID="0088">${supplier.endPointId}</cbc:EndpointID>
Expand All @@ -50,6 +53,10 @@ export const createPeppolInvoice = ({
<cbc:RegistrationName>${invoice.companyDetails.name}</cbc:RegistrationName>
<cbc:CompanyID>${invoice.companyDetails.cocNumber}</cbc:CompanyID>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>${invoice.companyDetails.contactPersonName ?? ''}</cbc:Name>
<cbc:ElectronicMail>${invoice.companyDetails.email}</cbc:ElectronicMail>
</cac:Contact>
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
Expand All @@ -71,10 +78,10 @@ export const createPeppolInvoice = ({
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>${invoice.clientDetails.companyName}</cbc:RegistrationName>
<cbc:CompanyID schemeID="0183">${invoice.clientDetails.cocNumber}</cbc:CompanyID>
<cbc:CompanyID schemeID="0183">${invoice.clientDetails.cocNumber ?? ''}</cbc:CompanyID>
</cac:PartyLegalEntity>
<cac:Contact>
<cbc:Name>${invoice.clientDetails.contactPersonName}</cbc:Name>
<cbc:Name>${invoice.clientDetails.contactPersonName ?? ''}</cbc:Name>
<cbc:ElectronicMail>${invoice.clientDetails.email}</cbc:ElectronicMail>
</cac:Contact>
</cac:Party>
Expand All @@ -92,24 +99,30 @@ export const createPeppolInvoice = ({
</cac:PayeeFinancialAccount>
</cac:PaymentMeans>
<cac:PaymentTerms>
<cbc:Note>${invoice.notes}</cbc:Note>
<cbc:Note>Payment before ${invoice.dueDate}.</cbc:Note>
</cac:PaymentTerms>
${formatSurcharges({ surcharges: invoice.surcharges, currency: invoice.currency })}
${formatDiscounts({ discounts: invoice.discounts, currency: invoice.currency })}
${formatInvoiceLines({ lines: invoice.lines, currency: invoice.currency })}
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="EUR">${formatAmount(invoice.totalIncludingTax)}</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="EUR">${formatAmount(invoice.totalExcludingTax)}</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="EUR">${formatAmount(invoice.taxSummary.reduce((acc, cur) => (acc += cur.tax), 0))}</cbc:TaxInclusiveAmount>
<cbc:ChargeTotalAmount currencyID="EUR">${formatAmount(invoice.surcharges?.reduce((acc, cur) => (acc += cur.listPriceIncludingTax), 0))}</cbc:ChargeTotalAmount>
<cbc:LineExtensionAmount currencyID="${invoice.currency}">${formatAmount(invoice.lines?.reduce((acc, cur) => (acc += cur.discountedLinePriceExcludingTax), 0))}</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="${invoice.currency}">${formatAmount(invoice.totalExcludingTax)}</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="${invoice.currency}">${formatAmount(invoice.totalIncludingTax)}</cbc:TaxInclusiveAmount>
<cbc:ChargeTotalAmount currencyID="${invoice.currency}">${formatAmount(invoice.surcharges?.reduce((acc, cur) => (acc += cur.listPriceExcludingTax), 0))}</cbc:ChargeTotalAmount>
<cbc:AllowanceTotalAmount currencyID="${invoice.currency}">${formatAmount(invoice.discounts?.reduce((acc, cur) => (acc += cur.listPriceExcludingTax), 0))}</cbc:AllowanceTotalAmount>
<cbc:PrepaidAmount currencyID="${invoice.currency}">${formatAmount(invoice.amountPaid)}</cbc:PrepaidAmount>
<cbc:PayableAmount currencyID="EUR">${formatAmount(invoice.amountDue)}</cbc:PayableAmount>
<cbc:PayableAmount currencyID="${invoice.currency}">${formatAmount(invoice.amountDue)}</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
<cac:TaxTotal>
<cbc:TaxAmount currencyID="EUR">${invoice.taxSummary.reduce((acc, cur) => (acc += cur.tax), 0)}</cbc:TaxAmount>
${formatTaxSubTotals({ taxSummary: invoice.taxSummary, currency: invoice.currency })}
<cac:TaxTotal>
<cbc:TaxAmount currencyID="${invoice.currency}">${formatAmount(invoice.taxSummary.reduce((acc, cur) => (acc += cur.tax), 0))}</cbc:TaxAmount>
${formatTaxSubTotals({ taxSummary: invoice.taxSummary, currency: invoice.currency, lines: invoice.lines, discounts: invoice.discounts || [], surcharges: invoice.surcharges || [] })}
</cac:TaxTotal>
<cbc:Note>${invoice.notes || ''}</cbc:Note>
</Invoice>`

const formatSurcharges = ({
Expand All @@ -125,9 +138,9 @@ const formatSurcharges = ({
<cac:AllowanceCharge>
<cbc:ChargeIndicator>true</cbc:ChargeIndicator>
<cbc:AllowanceChargeReason>${surcharge.description}</cbc:AllowanceChargeReason>
<cbc:Amount currencyID="${currency}">${formatAmount(surcharge.listPriceIncludingTax)}</cbc:Amount>
<cbc:Amount currencyID="${currency}">${formatAmount(surcharge.listPriceExcludingTax)}</cbc:Amount>
<cac:TaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:ID>${surcharge.taxRate}</cbc:ID>
<cbc:Percent>${surcharge.taxRate}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
Expand All @@ -137,6 +150,31 @@ const formatSurcharges = ({
` || ''
)

const formatDiscounts = ({
discounts,
currency
}: {
discounts: InvoiceDiscount[] | null
currency: Invoice['currency']
}) =>
discounts?.map(
(discount) =>
`
<cac:AllowanceCharge>
<cbc:ChargeIndicator>false</cbc:ChargeIndicator>
<cbc:AllowanceChargeReason>${discount.description}</cbc:AllowanceChargeReason>
<cbc:Amount currencyID="${currency}">${formatAmount(discount.listPriceExcludingTax)}</cbc:Amount>
<cac:TaxCategory>
<cbc:ID>${discount.taxRate}</cbc:ID>
<cbc:Percent>${discount.taxRate}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:AllowanceCharge>
` || ''
)

const formatInvoiceLines = ({
lines,
currency
Expand All @@ -156,7 +194,7 @@ const formatInvoiceLines = ({
<cac:Item>
<cbc:Description>${line.description}</cbc:Description>
<cac:ClassifiedTaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:ID>${line.taxRate}</cbc:ID>
<cbc:Percent>${line.taxRate}</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
Expand All @@ -177,16 +215,29 @@ const formatInvoiceLines = ({

const formatTaxSubTotals = ({
taxSummary,
currency
currency,
lines,
discounts,
surcharges
}: {
taxSummary: Invoice['taxSummary']
currency: Invoice['currency']
lines: InvoiceLine[]
surcharges: InvoiceSurcharge[]
discounts: InvoiceDiscount[]
}) =>
taxSummary?.map(
(tax) => `
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="${currency}"></cbc:TaxableAmount>
<cbc:TaxAmount currencyID="EUR">${formatAmount(tax.tax)}</cbc:TaxAmount>
<cbc:TaxableAmount currencyID="${currency}">${formatAmount(
calculateTaxableAmount({
taxRate: tax.taxRate,
lines,
discounts,
surcharges
})
)}</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="${currency}">${formatAmount(tax.tax)}</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>${tax.taxRate}</cbc:ID>
<cbc:Percent>${tax.taxRate.toFixed(1)}</cbc:Percent>
Expand All @@ -201,3 +252,29 @@ const formatAmount = (amount: number | null | undefined) => {
if (amount) return (amount / 100).toFixed(2)
return '0'
}

const calculateTaxableAmount = ({
taxRate,
lines,
surcharges,
discounts
}: {
taxRate: number
lines: InvoiceLine[]
surcharges: InvoiceSurcharge[]
discounts: InvoiceDiscount[]
}) => {
const linesTotal = lines
.filter((line) => line.taxRate === taxRate)
.reduce((acc, cur) => (acc += cur.listPriceExcludingTax), 0)

const discountsTotal = discounts
.filter((line) => line.taxRate === taxRate)
.reduce((acc, cur) => (acc += cur.listPriceExcludingTax), 0)

const surchargesTotal = surcharges
.filter((line) => line.taxRate === taxRate)
.reduce((acc, cur) => (acc += cur.listPriceExcludingTax), 0)

return linesTotal - discountsTotal + surchargesTotal
}
7 changes: 5 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 0b1ed98

Please sign in to comment.