Fixes for zugferd standard
This commit is contained in:
parent
168cd7add0
commit
bdb3676577
|
|
@ -333,7 +333,7 @@ class BaseRule implements RuleInterface
|
|||
}
|
||||
|
||||
public function defaultForeign(): self
|
||||
{
|
||||
{nlog("default foreign");
|
||||
if ($this->invoice->client->is_tax_exempt) {
|
||||
|
||||
$this->tax_rate1 = 0;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ class InvoiceItemFactory
|
|||
$item->tax_name1 = 'GST';
|
||||
$item->tax_rate1 = 10.00;
|
||||
$item->type_id = '1';
|
||||
$item->tax_id = '1';
|
||||
|
||||
$data[] = $item;
|
||||
}
|
||||
|
|
@ -93,6 +94,7 @@ class InvoiceItemFactory
|
|||
$item->tax_name1 = 'GST';
|
||||
$item->tax_rate1 = 10.00;
|
||||
$item->type_id = '2';
|
||||
$item->tax_id = '2';
|
||||
|
||||
$data[] = $item;
|
||||
|
||||
|
|
@ -127,7 +129,7 @@ class InvoiceItemFactory
|
|||
$item->tax_name1 = '';
|
||||
$item->tax_rate1 = 0;
|
||||
$item->type_id = '1';
|
||||
|
||||
$item->tax_id = '1';
|
||||
$data[] = $item;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,12 +88,7 @@ class CreateEDocument implements ShouldQueue
|
|||
case "XInvoice-Basic":
|
||||
|
||||
//New implementation now the default 2025-02-04 - requires zugferd_version_two=false to disable
|
||||
if(config('ninja.zugferd_version_two')){
|
||||
$zugferd = (new ZugferdEDocument($this->document))->run();
|
||||
}
|
||||
else {
|
||||
$zugferd = (new ZugferdEDokument($this->document))->run();
|
||||
}
|
||||
|
||||
return $this->returnObject ? $zugferd->xdocument : $zugferd->getXml();
|
||||
case "Facturae_3.2":
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ class ZugferdEDocument extends AbstractService
|
|||
private Client $client;
|
||||
|
||||
private InvoiceSum | InvoiceSumInclusive $calc;
|
||||
|
||||
private ?string $tax_code = null;
|
||||
|
||||
private ?string $exemption_reason_code = null;
|
||||
|
||||
/**
|
||||
* __construct
|
||||
*
|
||||
|
|
@ -93,44 +98,87 @@ class ZugferdEDocument extends AbstractService
|
|||
|
||||
private function setCustomSurcharges(): self
|
||||
{
|
||||
$item = $this->calc->getTaxMap()->first();
|
||||
$item = $this->calc->getTaxMap()->first() ?: ['tax_rate' => 0, 'tax_id' => null];
|
||||
|
||||
$tax_code = $item['tax_id'] ? $this->getTaxType($item["tax_id"] ?? '2') : $this->tax_code;
|
||||
|
||||
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, $this->getTaxType($item["tax_id"] ?? '2'), "VAT", $item["tax_rate"]);
|
||||
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"]);
|
||||
}
|
||||
|
||||
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, $this->getTaxType($item["tax_id"] ?? '2'), "VAT", $item["tax_rate"]);
|
||||
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"]);
|
||||
}
|
||||
|
||||
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, $this->getTaxType($item["tax_id"] ?? '2'), "VAT", $item["tax_rate"]);
|
||||
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"]);
|
||||
}
|
||||
|
||||
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, $this->getTaxType($item["tax_id"] ?? '2'), "VAT", $item["tax_rate"]);
|
||||
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* setDocumentTaxes
|
||||
*
|
||||
* VATEX-EU-143 - Article 143 - Exemptions on importation
|
||||
* VATEX-EU-146 - Article 146 - Exemptions on exportation
|
||||
* VATEX-EU-148 - Article 148 - Exemptions for international transport
|
||||
* VATEX-EU-151 - Article 151 - Exemptions for certain transactions
|
||||
* VATEX-EU-169 - Article 169 - Right of deduction
|
||||
* VATEX-EU-AE - Reverse charge - VAT to be paid by the recipient
|
||||
* VATEX-EU-D - Triangulation rule - Intra-EU supply
|
||||
* VATEX-EU-F - Free export item, tax not charged
|
||||
* VATEX-EU-G - Export outside the EU
|
||||
* VATEX-EU-IC - Intra-Community supply
|
||||
* VATEX-EU-O - Outside scope of tax
|
||||
* VATEX-EU-IC-SC - Intra-Community supply of services to customer in another member state
|
||||
* VATEX-EU-AE-SC - Services to customer outside the EU
|
||||
* VATEX-EU-NOT-TAX - Not subject to VAT
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function setDocumentTaxes(): self
|
||||
{
|
||||
if ($this->document->total_taxes == 0) {
|
||||
|
||||
$base_amount = 0;
|
||||
$tax_amount = 0;
|
||||
$tax_rate = 0;
|
||||
|
||||
if($this->tax_code == ZugferdDutyTaxFeeCategories::VAT_REVERSE_CHARGE){ //reverse charge
|
||||
$base_amount = $this->document->amount;
|
||||
}
|
||||
|
||||
$this->xdocument->addDocumentTax(
|
||||
ZugferdDutyTaxFeeCategories::EXEMPT_FROM_TAX,
|
||||
$this->tax_code,
|
||||
"VAT",
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
ctrans('texts.vat_not_registered'),
|
||||
"VATNOTREG"
|
||||
$base_amount,
|
||||
$tax_amount,
|
||||
$tax_rate,
|
||||
null,
|
||||
$this->exemption_reason_code
|
||||
);
|
||||
|
||||
|
||||
if ($this->calc->getTotalDiscount() > 0) {
|
||||
|
||||
$this->xdocument->addDocumentAllowanceCharge(
|
||||
$this->calc->getTotalDiscount(),
|
||||
false,
|
||||
$this->tax_code,
|
||||
"VAT",
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +213,7 @@ class ZugferdEDocument extends AbstractService
|
|||
$this->xdocument->addDocumentAllowanceCharge(
|
||||
round($this->calc->getTotalDiscount() * $ratio, 2),
|
||||
false,
|
||||
$tax_type,
|
||||
$this->getTaxType($item["tax_id"] ?? '2'),
|
||||
"VAT",
|
||||
$item["tax_rate"]
|
||||
);
|
||||
|
|
@ -206,7 +254,27 @@ class ZugferdEDocument extends AbstractService
|
|||
|
||||
$this->calc = $this->document->calc();
|
||||
|
||||
$br = new \App\DataMapper\Tax\BaseRule();
|
||||
$eu_states = $br->eu_country_codes;
|
||||
|
||||
$item = $this->document->line_items[0];
|
||||
|
||||
if (!in_array($this->document->client->country->iso_3166_2, $eu_states)) {
|
||||
$this->tax_code = ZugferdDutyTaxFeeCategories::FREE_EXPORT_ITEM_TAX_NOT_CHARGED;
|
||||
$exemption_reason_code = "VATEX-EU-G";
|
||||
} elseif ($this->client->is_tax_exempt || $item->tax_id == '5' || $item->tax_id == '8') {
|
||||
$this->tax_code = ZugferdDutyTaxFeeCategories::EXEMPT_FROM_TAX;
|
||||
$this->exemption_reason_code = "VATEX-EU-NOT-TAX";
|
||||
} elseif ($item->tax_id == '9') { //reverse charge
|
||||
$this->tax_code = ZugferdDutyTaxFeeCategories::VAT_REVERSE_CHARGE;
|
||||
$this->exemption_reason_code = "VATEX-EU-AE";
|
||||
} elseif ($item->tax_id == '10') { //intra-community
|
||||
$this->tax_code = ZugferdDutyTaxFeeCategories::VAT_EXEMPT_FOR_EEA_INTRACOMMUNITY_SUPPLY_OF_GOODS_AND_SERVICES;
|
||||
$this->exemption_reason_code = "VATEX-EU-IC";
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
private function setDocumentSummation(): self
|
||||
|
|
@ -470,10 +538,7 @@ class ZugferdEDocument extends AbstractService
|
|||
|
||||
private function getDocumentLevelTaxRegistration(): string
|
||||
{
|
||||
$items = $this->document->line_items;
|
||||
$tax_id = $items[0]->tax_id ?? '1';
|
||||
return $this->getTaxType($tax_id);
|
||||
|
||||
return strlen($this->client->vat_number ?? '') > 1 ? "VA" : "FC";
|
||||
}
|
||||
|
||||
private function getTaxType(string $tax_id): string
|
||||
|
|
@ -513,7 +578,7 @@ class ZugferdEDocument extends AbstractService
|
|||
if ((in_array($this->company->country()->iso_3166_2, $eu_states) && in_array($this->client->country->iso_3166_2, $eu_states)) && $this->company->country()->iso_3166_2 != $this->client->country->iso_3166_2) {
|
||||
$tax_type = ZugferdDutyTaxFeeCategories::VAT_EXEMPT_FOR_EEA_INTRACOMMUNITY_SUPPLY_OF_GOODS_AND_SERVICES;
|
||||
} elseif (!in_array($this->document->client->country->iso_3166_2, $eu_states)) {
|
||||
$tax_type = ZugferdDutyTaxFeeCategories::SERVICE_OUTSIDE_SCOPE_OF_TAX;
|
||||
$tax_type = ZugferdDutyTaxFeeCategories::FREE_EXPORT_ITEM_TAX_NOT_CHARGED;
|
||||
} elseif ($this->document->client->country->iso_3166_2 == "ES-CN") {
|
||||
$tax_type = ZugferdDutyTaxFeeCategories::CANARY_ISLANDS_GENERAL_INDIRECT_TAX;
|
||||
} elseif (in_array($this->document->client->country->iso_3166_2, ["ES-CE", "ES-ML"])) {
|
||||
|
|
|
|||
|
|
@ -198,6 +198,69 @@ class ZugferdTest extends TestCase
|
|||
return compact('company', 'client', 'invoice');
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function testDeToNlReverseTax()
|
||||
{
|
||||
|
||||
$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,
|
||||
];
|
||||
|
||||
$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->zug_16931]);
|
||||
$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 testInclusiveScenarios()
|
||||
{
|
||||
|
||||
|
|
@ -232,7 +295,7 @@ class ZugferdTest extends TestCase
|
|||
|
||||
if (count($validator->getErrors()) > 0) {
|
||||
|
||||
nlog($invoice->toArray());
|
||||
nlog($invoice->withoutRelations()->toArray());
|
||||
nlog($xml);
|
||||
nlog($validator->getErrors());
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue