Files
opensourcepos/app/Libraries/InvoiceAttachment/InvoiceAttachmentGenerator.php
jekkos e6b288c291 Add Peppol (UBL) invoice support for Phase 1
Implementation of UBL 2.1 invoice generation to comply with Belgium's 2026 Peppol mandate.

Key changes:
- Add num-num/ubl-invoice dependency via composer.json
- Create Ubl_generator library to convert OSPOS sale data to UBL format
- Create country_helper.php to map country names to ISO 3166-1 alpha-2 codes
- Extend Email_lib to support multiple attachments for PDF+UBL emails
- Add getUblInvoice() method in Sales controller for UBL download
- Modify getSendPdf() to optionally attach UBL based on invoice_format config
- Add database migration for invoice_format configuration (pdf_only/ubl_only/both)
- Add UBL download button to invoice view
- Add UBL download link to sales manage table
- Add language keys for UBL-related UI elements

Data mapping:
- Company name/address -> Supplier Party
- account_number -> Company VAT number
- Customer address/country -> Customer Party with ISO country code
- Customer tax_id -> Customer VAT number
- Cart items -> InvoiceLines
- Taxes -> TaxCategory and TaxTotal
- Totals -> LegalMonetaryTotal

Features:
- Generate valid UBL 2.1 XML invoices
- Download UBL from invoice view and manage table
- Email with PDF, UBL, or both based on configuration
- Support for multiple customer countries with ISO code mapping
- Graceful handling of missing optional customer fields
2026-04-02 19:57:20 +02:00

75 lines
1.9 KiB
PHP

<?php
namespace App\Libraries\InvoiceAttachment;
class InvoiceAttachmentGenerator
{
/** @var InvoiceAttachment[] */
private array $attachments = [];
/**
* Register an attachment generator.
*/
public function register(InvoiceAttachment $attachment): self
{
$this->attachments[] = $attachment;
return $this;
}
/**
* Create generator with attachments based on config.
* Factory method that instantiates the right attachments.
*
* @param string $invoiceFormat Config value: 'pdf_only', 'ubl_only', or 'both'
* @return self
*/
public static function createFromConfig(string $invoiceFormat): self
{
$generator = new self();
if (in_array($invoiceFormat, ['pdf_only', 'both'], true)) {
$generator->register(new PdfAttachment());
}
if (in_array($invoiceFormat, ['ubl_only', 'both'], true)) {
$generator->register(new UblAttachment());
}
return $generator;
}
/**
* Generate all applicable attachments for a sale.
*
* @param array $saleData The sale data
* @param string $type The document type
* @return string[] Array of file paths to generated attachments
*/
public function generateAttachments(array $saleData, string $type): array
{
$files = [];
foreach ($this->attachments as $attachment) {
if ($attachment->isApplicableForType($type, $saleData)) {
$filepath = $attachment->generate($saleData, $type);
if ($filepath !== null) {
$files[] = $filepath;
}
}
}
return $files;
}
/**
* Clean up temporary attachment files.
*
* @param string[] $files
*/
public static function cleanup(array $files): void
{
foreach ($files as $file) {
@unlink($file);
}
}
}