mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-04-03 06:44:27 -04:00
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
121 lines
4.7 KiB
PHP
121 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Libraries\InvoiceAttachment;
|
|
|
|
use CodeIgniter\Test\CIUnitTestCase;
|
|
use App\Libraries\InvoiceAttachment\InvoiceAttachmentGenerator;
|
|
use App\Libraries\InvoiceAttachment\InvoiceAttachment;
|
|
use App\Libraries\InvoiceAttachment\PdfAttachment;
|
|
use App\Libraries\InvoiceAttachment\UblAttachment;
|
|
|
|
class InvoiceAttachmentGeneratorTest extends CIUnitTestCase
|
|
{
|
|
public function testCreateFromConfigPdfOnly(): void
|
|
{
|
|
$generator = InvoiceAttachmentGenerator::createFromConfig('pdf_only');
|
|
$this->assertInstanceOf(InvoiceAttachmentGenerator::class, $generator);
|
|
}
|
|
|
|
public function testCreateFromConfigUblOnly(): void
|
|
{
|
|
$generator = InvoiceAttachmentGenerator::createFromConfig('ubl_only');
|
|
$this->assertInstanceOf(InvoiceAttachmentGenerator::class, $generator);
|
|
}
|
|
|
|
public function testCreateFromConfigBoth(): void
|
|
{
|
|
$generator = InvoiceAttachmentGenerator::createFromConfig('both');
|
|
$this->assertInstanceOf(InvoiceAttachmentGenerator::class, $generator);
|
|
}
|
|
|
|
public function testCreateFromConfigPdfOnlyRegistersPdfAttachment(): void
|
|
{
|
|
$generator = InvoiceAttachmentGenerator::createFromConfig('pdf_only');
|
|
$attachments = $this->getPrivateProperty($generator, 'attachments');
|
|
|
|
$this->assertCount(1, $attachments);
|
|
$this->assertInstanceOf(PdfAttachment::class, $attachments[0]);
|
|
}
|
|
|
|
public function testCreateFromConfigUblOnlyRegistersUblAttachment(): void
|
|
{
|
|
$generator = InvoiceAttachmentGenerator::createFromConfig('ubl_only');
|
|
$attachments = $this->getPrivateProperty($generator, 'attachments');
|
|
|
|
$this->assertCount(1, $attachments);
|
|
$this->assertInstanceOf(UblAttachment::class, $attachments[0]);
|
|
}
|
|
|
|
public function testCreateFromConfigBothRegistersBothAttachments(): void
|
|
{
|
|
$generator = InvoiceAttachmentGenerator::createFromConfig('both');
|
|
$attachments = $this->getPrivateProperty($generator, 'attachments');
|
|
|
|
$this->assertCount(2, $attachments);
|
|
$this->assertInstanceOf(PdfAttachment::class, $attachments[0]);
|
|
$this->assertInstanceOf(UblAttachment::class, $attachments[1]);
|
|
}
|
|
|
|
public function testRegisterAddsAttachment(): void
|
|
{
|
|
$generator = new InvoiceAttachmentGenerator();
|
|
$mockAttachment = new class implements InvoiceAttachment {
|
|
public function generate(array $saleData, string $type): ?string { return null; }
|
|
public function isApplicableForType(string $type, array $saleData): bool { return true; }
|
|
public function getFileExtension(): string { return 'test'; }
|
|
public function getEnabledConfigValues(): array { return ['test']; }
|
|
};
|
|
|
|
$result = $generator->register($mockAttachment);
|
|
|
|
$this->assertSame($generator, $result);
|
|
$attachments = $this->getPrivateProperty($generator, 'attachments');
|
|
$this->assertCount(1, $attachments);
|
|
}
|
|
|
|
public function testRegisterIsChainable(): void
|
|
{
|
|
$generator = new InvoiceAttachmentGenerator();
|
|
$mockAttachment = new class implements InvoiceAttachment {
|
|
public function generate(array $saleData, string $type): ?string { return null; }
|
|
public function isApplicableForType(string $type, array $saleData): bool { return true; }
|
|
public function getFileExtension(): string { return 'test'; }
|
|
public function getEnabledConfigValues(): array { return ['test']; }
|
|
};
|
|
|
|
$result = $generator->register($mockAttachment)->register($mockAttachment);
|
|
|
|
$attachments = $this->getPrivateProperty($result, 'attachments');
|
|
$this->assertCount(2, $attachments);
|
|
}
|
|
|
|
public function testGenerateAttachmentsReturnsEmptyArrayWhenNoAttachmentsRegistered(): void
|
|
{
|
|
$generator = new InvoiceAttachmentGenerator();
|
|
$result = $generator->generateAttachments([], 'invoice');
|
|
|
|
$this->assertIsArray($result);
|
|
$this->assertEmpty($result);
|
|
}
|
|
|
|
public function testCleanupRemovesFiles(): void
|
|
{
|
|
$tempFile1 = tempnam(sys_get_temp_dir(), 'test_');
|
|
$tempFile2 = tempnam(sys_get_temp_dir(), 'test_');
|
|
|
|
$this->assertFileExists($tempFile1);
|
|
$this->assertFileExists($tempFile2);
|
|
|
|
InvoiceAttachmentGenerator::cleanup([$tempFile1, $tempFile2]);
|
|
|
|
$this->assertFileDoesNotExist($tempFile1);
|
|
$this->assertFileDoesNotExist($tempFile2);
|
|
}
|
|
|
|
public function testCleanupHandlesNonExistentFiles(): void
|
|
{
|
|
// Should not throw an exception
|
|
InvoiceAttachmentGenerator::cleanup(['/non/existent/file1', '/non/existent/file2']);
|
|
$this->assertTrue(true);
|
|
}
|
|
} |