Files
opensourcepos/app/Libraries/InvoiceAttachment/UblAttachment.php
jekkos 833c06c718 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 23:20:35 +02:00

69 lines
1.7 KiB
PHP

<?php
namespace App\Libraries\InvoiceAttachment;
use App\Libraries\UBLGenerator;
class UblAttachment implements InvoiceAttachment
{
public function __construct()
{
require_once ROOTPATH . 'vendor/autoload.php';
}
/**
* @inheritDoc
*/
public function generate(array $saleData, string $type): ?string
{
try {
$generator = new UBLGenerator();
$xml = $generator->generateUblInvoice($saleData);
$tempPath = tempnam(sys_get_temp_dir(), 'ospos_ubl_');
if ($tempPath === false) {
log_message('error', 'UBL attachment: failed to create temp file');
return null;
}
$filename = $tempPath . '.xml';
rename($tempPath, $filename);
if (file_put_contents($filename, $xml) === false) {
log_message('error', 'UBL attachment: failed to write content');
@unlink($filename);
return null;
}
return $filename;
} catch (\Exception $e) {
log_message('error', 'UBL attachment generation failed: ' . $e->getMessage());
return null;
}
}
/**
* @inheritDoc
*/
public function isApplicableForType(string $type, array $saleData): bool
{
return in_array($type, ['invoice', 'tax_invoice'], true)
&& !empty($saleData['invoice_number']);
}
/**
* @inheritDoc
*/
public function getFileExtension(): string
{
return 'xml';
}
/**
* @inheritDoc
*/
public function getEnabledConfigValues(): array
{
return ['ubl_only', 'both'];
}
}