Updates for xrechung extended validation

This commit is contained in:
David Bomba 2025-08-29 11:37:26 +10:00
parent 5258f6cf3e
commit 0897d2ec11
8 changed files with 34307 additions and 18 deletions

View File

@ -246,7 +246,7 @@ class OrderXDocument extends AbstractService
} elseif (in_array($this->document->client->country->iso_3166_2, ["ES-CE", "ES-ML"])) {
$tax_type = OrderDutyTaxFeeCategories::TAX_FOR_PRODUCTION_SERVICES_AND_IMPORTATION_IN_CEUTA_AND_MELILLA;
} else {
nlog("Unkown tax case for xinvoice");
// nlog("Unkown tax case for xinvoice");
$tax_type = OrderDutyTaxFeeCategories::STANDARD_RATE;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -104,22 +104,22 @@ class ZugferdEDocument extends AbstractService
if ($this->document->custom_surcharge1 > 0) {
$surcharge = $this->document->uses_inclusive_taxes ? ($this->document->custom_surcharge1 / (1 + ($item["tax_rate"] / 100))) : $this->document->custom_surcharge1;
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"]);
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"],null,null,null,null,null,null, ctrans('texts.surcharge'));
}
if ($this->document->custom_surcharge2 > 0) {
$surcharge = $this->document->uses_inclusive_taxes ? ($this->document->custom_surcharge2 / (1 + ($item["tax_rate"] / 100))) : $this->document->custom_surcharge2;
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"]);
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"],null,null,null,null,null,null, ctrans('texts.surcharge'));
}
if ($this->document->custom_surcharge3 > 0) {
$surcharge = $this->document->uses_inclusive_taxes ? ($this->document->custom_surcharge3 / (1 + ($item["tax_rate"] / 100))) : $this->document->custom_surcharge3;
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"]);
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"],null,null,null,null,null,null, ctrans('texts.surcharge'));
}
if ($this->document->custom_surcharge4 > 0) {
$surcharge = $this->document->uses_inclusive_taxes ? ($this->document->custom_surcharge4 / (1 + ($item["tax_rate"] / 100))) : $this->document->custom_surcharge4;
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"]);
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"],null,null,null,null,null,null, ctrans('texts.surcharge'));
}
return $this;
@ -175,7 +175,8 @@ class ZugferdEDocument extends AbstractService
false,
$this->tax_code,
"VAT",
0
0,
null,null,null,null,null,null, ctrans('texts.discount')
);
}
@ -215,7 +216,8 @@ class ZugferdEDocument extends AbstractService
false,
$this->getTaxType($item["tax_id"] ?? '2'),
"VAT",
$item["tax_rate"]
$item["tax_rate"],
null,null,null,null,null,null,ctrans('texts.discount')
);
}
@ -270,7 +272,7 @@ class ZugferdEDocument extends AbstractService
$this->tax_code = ZugferdDutyTaxFeeCategories::EXEMPT_FROM_TAX;
// $this->exemption_reason_code = "VATEX-EU-NOT-TAX";
$this->exemption_reason_code = "VATEX-EU-O";
nlog("exemption_reason_code: {$this->exemption_reason_code}");
// nlog("exemption_reason_code: {$this->exemption_reason_code}");
} elseif ($item->tax_id == '9') { //reverse charge
$this->tax_code = ZugferdDutyTaxFeeCategories::VAT_REVERSE_CHARGE;
$this->exemption_reason_code = "VATEX-EU-AE";
@ -587,7 +589,7 @@ class ZugferdEDocument extends AbstractService
} elseif (in_array($this->document->client->country->iso_3166_2, ["ES-CE", "ES-ML"])) {
$tax_type = ZugferdDutyTaxFeeCategories::TAX_FOR_PRODUCTION_SERVICES_AND_IMPORTATION_IN_CEUTA_AND_MELILLA;
} else {
nlog("Unkown tax case for xinvoice");
// nlog("Unkown tax case for xinvoice");
$tax_type = ZugferdDutyTaxFeeCategories::STANDARD_RATE;
}
}

View File

@ -350,7 +350,7 @@ class ZugferdEDokument extends AbstractService
} elseif (in_array($this->document->client->country->iso_3166_2, ["ES-CE", "ES-ML"])) {
$tax_type = ZugferdDutyTaxFeeCategories::TAX_FOR_PRODUCTION_SERVICES_AND_IMPORTATION_IN_CEUTA_AND_MELILLA;
} else {
nlog("Unkown tax case for xinvoice");
// nlog("Unkown tax case for xinvoice");
$tax_type = ZugferdDutyTaxFeeCategories::STANDARD_RATE;
}
}

12
composer.lock generated
View File

@ -20947,16 +20947,16 @@
},
{
"name": "spatie/backtrace",
"version": "1.8.0",
"version": "1.8.1",
"source": {
"type": "git",
"url": "https://github.com/spatie/backtrace.git",
"reference": "1607d8870bf597fc4ad79a6945cf0b2e584c2669"
"reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/backtrace/zipball/1607d8870bf597fc4ad79a6945cf0b2e584c2669",
"reference": "1607d8870bf597fc4ad79a6945cf0b2e584c2669",
"url": "https://api.github.com/repos/spatie/backtrace/zipball/8c0f16a59ae35ec8c62d85c3c17585158f430110",
"reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110",
"shasum": ""
},
"require": {
@ -20995,7 +20995,7 @@
],
"support": {
"issues": "https://github.com/spatie/backtrace/issues",
"source": "https://github.com/spatie/backtrace/tree/1.8.0"
"source": "https://github.com/spatie/backtrace/tree/1.8.1"
},
"funding": [
{
@ -21007,7 +21007,7 @@
"type": "other"
}
],
"time": "2025-08-25T16:16:45+00:00"
"time": "2025-08-26T08:22:30+00:00"
},
{
"name": "spatie/error-solutions",

View File

@ -67,6 +67,10 @@ class ZugferdTest extends TestCase
];
private string $zug_16931 = 'Services/EDocument/Standards/Validation/Zugferd/zugferd_16931.xslt';
private string $zf_extended_wl = 'Services/EDocument/Standards/Validation/Zugferd/FACTUR-X_EXTENDED.xslt';
private string $extended_profile = 'XInvoice-Extended';
protected function setUp(): void
{
@ -96,7 +100,7 @@ class ZugferdTest extends TestCase
$settings->classification = $params['company_classification'] ?? 'business';
$settings->country_id = Country::where('iso_3166_2', 'DE')->first()->id;
$settings->email = $this->faker->safeEmail();
$settings->e_invoice_type = 'XInvoice_3_0';
$settings->e_invoice_type = $params['e_invoice_type'] ?? 'XInvoice_3_0';
$settings->currency_id = '3';
$settings->name = 'Test Company';
$settings->address1 = 'Line 1 of address of the seller';
@ -255,6 +259,66 @@ class ZugferdTest extends TestCase
$this->assertCount(0, $validator->getErrors());
}
}
public function testDeTodeTaxExemptExtendedProfile()
{
$scenario = [
'company_vat' => 'DE923356489',
'company_country' => 'DE',
'client_country' => 'DE',
'client_vat' => 'DE923356488',
'classification' => 'business',
'has_valid_vat' => true,
'over_threshold' => true,
'legal_entity_id' => 290868,
'e_invoice_type' => $this->extended_profile,
];
$data = $this->setupTestData($scenario);
$invoice = $data['invoice'];
$repo = new InvoiceRepository();
foreach($this->inclusive_scenarios as $scenario){
$invoice_data = json_decode($scenario, true);
$line_items = $invoice_data['line_items'];
foreach ($line_items as &$item) {
$item['tax_rate1'] = 0;
$item['tax_name1'] = 'VAT';
$item['tax_id'] = '5';
}
unset($item);
$invoice_data['line_items'] = array_values($line_items);
$invoice_data['uses_inclusive_taxes'] = false;
$invoice = $repo->save($invoice_data, $invoice);
$invoice = $invoice->calc()->getInvoice();
$xml = $invoice->service()->getEInvoice();
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
$validator->setStyleSheets([$this->zf_extended_wl]);
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
$validator->validate();
if (count($validator->getErrors()) > 0) {
nlog($invoice->withoutRelations()->toArray());
nlog($xml);
nlog($validator->getErrors());
}
$this->assertCount(0, $validator->getErrors());
}
}
@ -585,7 +649,8 @@ class ZugferdTest extends TestCase
{
$zug_16931 = 'Services/EDocument/Standards/Validation/Zugferd/zugferd_16931.xslt';
// $xr_cii = 'Services/EDocument/Standards/Validation/Zugferd/xrechnung_cii.xslt';
// $zug_16931 = 'Services/EDocument/Standards/Validation/Zugferd/FACTUR-X_MINIMUM.xslt';
$scenario = [
@ -621,6 +686,8 @@ class ZugferdTest extends TestCase
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
$validator->setStyleSheets([$this->zug_16931]);
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
$validator->validate();
@ -684,5 +751,399 @@ class ZugferdTest extends TestCase
}
// ============================================================================
// EXTENDED PROFILE TEST METHODS - Duplicates using extended profile and XSLT
// ============================================================================
public function testDeToNlReverseTaxExtendedProfile()
{
$scenario = [
'company_vat' => 'DE923356489',
'company_country' => 'DE',
'client_country' => 'NL',
'client_vat' => 'NL808436332B01',
'classification' => 'business',
'has_valid_vat' => true,
'over_threshold' => true,
'legal_entity_id' => 290868,
'e_invoice_type' => $this->extended_profile,
];
$data = $this->setupTestData($scenario);
$invoice = $data['invoice'];
$repo = new InvoiceRepository();
foreach($this->inclusive_scenarios as $scenario){
$invoice_data = json_decode($scenario, true);
$line_items = $invoice_data['line_items'];
foreach ($line_items as &$item) {
$item['tax_rate1'] = 0;
$item['tax_name1'] = '';
$item['tax_id'] = '9';
}
unset($item);
$invoice_data['line_items'] = array_values($line_items);
$invoice_data['uses_inclusive_taxes'] = false;
$invoice = $repo->save($invoice_data, $invoice);
$invoice = $invoice->calc()->getInvoice();
$xml = $invoice->service()->getEInvoice();
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
$validator->setStyleSheets([$this->zf_extended_wl]);
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
$validator->validate();
if (count($validator->getErrors()) > 0) {
nlog($invoice->withoutRelations()->toArray());
nlog($xml);
nlog($validator->getErrors());
}
$this->assertCount(0, $validator->getErrors());
}
}
public function testInclusiveScenariosExtendedProfile()
{
$scenario = [
'company_vat' => 'DE923356489',
'company_country' => 'DE',
'client_country' => 'DE',
'client_vat' => 'DE923356488',
'classification' => 'business',
'has_valid_vat' => true,
'over_threshold' => true,
'legal_entity_id' => 290868,
'e_invoice_type' => $this->extended_profile,
];
$data = $this->setupTestData($scenario);
$invoice = $data['invoice'];
$repo = new InvoiceRepository();
foreach($this->inclusive_scenarios as $scenario){
$invoice_data = json_decode($scenario, true);
$invoice = $repo->save($invoice_data, $invoice);
$invoice = $invoice->calc()->getInvoice();
$xml = $invoice->service()->getEInvoice();
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
$validator->setStyleSheets([$this->zf_extended_wl]);
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
$validator->validate();
if (count($validator->getErrors()) > 0) {
nlog($invoice->withoutRelations()->toArray());
nlog($xml);
nlog($validator->getErrors());
}
$this->assertCount(0, $validator->getErrors());
}
}
public function testExclusiveScenariosExtendedProfile()
{
$scenario = [
'company_vat' => 'DE923356489',
'company_country' => 'DE',
'client_country' => 'DE',
'client_vat' => 'DE923356488',
'classification' => 'business',
'has_valid_vat' => true,
'over_threshold' => true,
'legal_entity_id' => 290868,
'e_invoice_type' => $this->extended_profile,
];
$data = $this->setupTestData($scenario);
$invoice = $data['invoice'];
$repo = new InvoiceRepository();
foreach($this->inclusive_scenarios as $scenario){
$invoice_data = json_decode($scenario, true);
$invoice_data['uses_inclusive_taxes'] = false;
$invoice = $repo->save($invoice_data, $invoice);
$invoice = $invoice->calc()->getInvoice();
$xml = $invoice->service()->getEInvoice();
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
$validator->setStyleSheets([$this->zf_extended_wl]);
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
$validator->validate();
if (count($validator->getErrors()) > 0) {
nlog($invoice->withoutRelations()->toArray());
nlog($xml);
nlog($validator->getErrors());
}
$this->assertCount(0, $validator->getErrors());
}
}
public function testZugFerdValidationExtendedProfile()
{
$scenario = [
'company_vat' => 'DE923356489',
'company_country' => 'DE',
'client_country' => 'DE',
'client_vat' => 'DE923356488',
'classification' => 'business',
'has_valid_vat' => true,
'over_threshold' => true,
'legal_entity_id' => 290868,
'e_invoice_type' => $this->extended_profile,
];
$data = $this->setupTestData($scenario);
$invoice = $data['invoice'];
$xml = $invoice->service()->getEInvoice();
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
$validator->setStyleSheets([$this->zf_extended_wl]);
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
$validator->validate();
if (count($validator->getErrors()) > 0) {
nlog($xml);
nlog($validator->getErrors());
}
$this->assertCount(0, $validator->getErrors());
}
public function testZugFerdValidationWithInclusiveTaxesExtendedProfile()
{
$scenario = [
'company_vat' => 'DE923356489',
'company_country' => 'DE',
'client_country' => 'DE',
'client_vat' => 'DE923356488',
'classification' => 'business',
'has_valid_vat' => true,
'over_threshold' => true,
'legal_entity_id' => 290868,
'e_invoice_type' => $this->extended_profile,
];
$data = $this->setupTestData($scenario);
$invoice = $data['invoice'];
$invoice->uses_inclusive_taxes = true;
$invoice = $invoice->calc()->getInvoice();
$xml = $invoice->service()->getEInvoice();
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
$validator->setStyleSheets([$this->zf_extended_wl]);
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
$validator->validate();
if (count($validator->getErrors()) > 0) {
nlog($xml);
nlog($validator->getErrors());
}
$this->assertCount(0, $validator->getErrors());
}
public function testZugFerdValidationWithInclusiveTaxesAndTotalAmountDiscountExtendedProfile()
{
$scenario = [
'company_vat' => 'DE923356489',
'company_country' => 'DE',
'client_country' => 'DE',
'client_vat' => 'DE923356488',
'classification' => 'business',
'has_valid_vat' => true,
'over_threshold' => true,
'legal_entity_id' => 290868,
'e_invoice_type' => $this->extended_profile,
];
$data = $this->setupTestData($scenario);
$invoice = $data['invoice'];
$invoice->uses_inclusive_taxes = true;
$invoice = $invoice->calc()->getInvoice();
$invoice->discount=20;
$invoice->is_amount_discount = true;
$xml = $invoice->service()->getEInvoice();
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
$validator->setStyleSheets([$this->zf_extended_wl]);
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
$validator->validate();
if (count($validator->getErrors()) > 0) {
nlog($xml);
nlog($validator->getErrors());
}
$this->assertCount(0, $validator->getErrors());
}
public function testZugFerdValidationWithInclusiveTaxesAndTotalPercentDiscountExtendedProfile()
{
$scenario = [
'company_vat' => 'DE923356489',
'company_country' => 'DE',
'client_country' => 'DE',
'client_vat' => 'DE923356488',
'classification' => 'business',
'has_valid_vat' => true,
'over_threshold' => true,
'legal_entity_id' => 290868,
'e_invoice_type' => $this->extended_profile,
];
$data = $this->setupTestData($scenario);
$invoice = $data['invoice'];
$invoice->uses_inclusive_taxes = true;
$invoice = $invoice->calc()->getInvoice();
$invoice->discount=20;
$invoice->is_amount_discount = false;
$xml = $invoice->service()->getEInvoice();
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
$validator->setStyleSheets([$this->zf_extended_wl]);
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
$validator->validate();
if (count($validator->getErrors()) > 0) {
nlog($xml);
nlog($validator->getErrors());
}
$this->assertCount(0, $validator->getErrors());
}
public function testZugFerdValidationWithInclusiveTaxesAndTotalPercentDiscountOnLineItemsAlsoExtendedProfile()
{
$scenario = [
'company_vat' => 'DE923356489',
'company_country' => 'DE',
'client_country' => 'DE',
'client_vat' => 'DE923356488',
'classification' => 'business',
'has_valid_vat' => true,
'over_threshold' => true,
'legal_entity_id' => 290868,
'e_invoice_type' => $this->extended_profile,
];
$data = $this->setupTestData($scenario);
$invoice = $data['invoice'];
$invoice->uses_inclusive_taxes = true;
$invoice = $invoice->calc()->getInvoice();
$invoice->discount=20;
$invoice->is_amount_discount = false;
$items = $invoice->line_items;
foreach($items as &$item){
$item->discount=10;
$item->is_amount_discount = false;
}
unset($item);
$invoice->line_items = $items;
$xml = $invoice->service()->getEInvoice();
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
$validator->setStyleSheets([$this->zf_extended_wl]);
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
$validator->validate();
if (count($validator->getErrors()) > 0) {
nlog($xml);
nlog($validator->getErrors());
}
$this->assertCount(0, $validator->getErrors());
}
public function testZugFerdValidationWithInclusiveTaxesAndTotalAmountDiscountOnLineItemsAlsoExtendedProfile()
{
$scenario = [
'company_vat' => 'DE923356489',
'company_country' => 'DE',
'client_country' => 'DE',
'client_vat' => 'DE923356488',
'classification' => 'business',
'has_valid_vat' => true,
'over_threshold' => true,
'legal_entity_id' => 290868,
'e_invoice_type' => $this->extended_profile,
];
$data = $this->setupTestData($scenario);
$invoice = $data['invoice'];
$invoice->uses_inclusive_taxes = true;
$invoice = $invoice->calc()->getInvoice();
$invoice->discount=20;
$invoice->is_amount_discount = true;
$items = $invoice->line_items;
foreach($items as &$item){
$item->discount=5;
$item->is_amount_discount = true;
}
unset($item);
$invoice->line_items = $items;
$xml = $invoice->service()->getEInvoice();
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
$validator->setStyleSheets([$this->zf_extended_wl]);
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
$validator->validate();
if (count($validator->getErrors()) > 0) {
nlog($xml);
nlog($validator->getErrors());
}
$this->assertCount(0, $validator->getErrors());
}
}