'EIN', 'US' => 'SSN', 'NZ' => 'GST', 'CH' => 'VAT', // VAT number = CHE - 999999999 - MWST|IVA|VAT 'IS' => 'VAT', 'LI' => 'VAT', 'NO' => 'VAT', 'AD' => 'VAT', 'AL' => 'VAT', 'AT' => 'VAT', 'BA' => 'VAT', 'BE' => 'VAT', 'BG' => 'VAT', 'AU' => 'ABN', //Australia 'CA' => 'CBN', //Canada 'MX' => 'RFC', //Mexico 'NZ' => 'GST', //Nuuu zulund 'GB' => 'VAT', //Great Britain 'SA' => 'TIN', //South Africa 'CY' => 'VAT', 'CZ' => 'VAT', 'DE' => 'VAT', //tested - requires Payment Means to be defined. 'DK' => 'ERST', 'EE' => 'VAT', 'ES' => 'VAT', //tested - B2G pending 'FI' => 'VAT', 'FR' => 'VAT', 'GR' => 'VAT', 'HR' => 'VAT', 'HU' => 'VAT', 'IE' => 'VAT', 'IT' => 'IVA', //tested - Requires a Customer Party Identification (VAT number) 'IT' => 'CF', //tested - Requires a Customer Party Identification (VAT number) 'LT' => 'VAT', 'LU' => 'VAT', 'LV' => 'VAT', 'MC' => 'VAT', 'ME' => 'VAT', 'MK' => 'VAT', 'MT' => 'VAT', 'NL' => 'VAT', 'PL' => 'VAT', 'PT' => 'VAT', 'RO' => 'VAT', 'RS' => 'VAT', 'SE' => 'VAT', 'SI' => 'VAT', 'SK' => 'VAT', 'SM' => 'VAT', 'TR' => 'VAT', 'VA' => 'VAT', 'IN' => 'GSTIN', 'JP' => 'IIN', 'MY' => 'TIN', 'SG' => 'GST', 'GB' => 'VAT', 'SA' => 'TIN', ]; private array $InvoiceTypeCodes = [ "380" => "Commercial invoice", "381" => "Credit note", "383" => "Corrected invoice", "384" => "Prepayment invoice", "386" => "Proforma invoice", "875" => "Self-billed invoice", "976" => "Factored invoice", "84" => "Invoice for cross border services", "82" => "Simplified invoice", "80" => "Debit note", "875" => "Self-billed credit note", "896" => "Debit note related to self-billed invoice" ]; private Company $company; private InvoiceSum | InvoiceSumInclusive $calc; private \InvoiceNinja\EInvoice\Models\Peppol\Invoice $p_invoice; private ?\InvoiceNinja\EInvoice\Models\Peppol\Invoice $_client_settings; private ?\InvoiceNinja\EInvoice\Models\Peppol\Invoice $_company_settings; private EInvoice $e; private array $storecove_meta = []; /** * @param Invoice $invoice */ public function __construct(public Invoice $invoice) { $this->company = $invoice->company; $this->calc = $this->invoice->calc(); $this->e = new EInvoice(); $this->setSettings()->setInvoice(); } /** * Rehydrates an existing e invoice - or - scaffolds a new one * * @return self */ private function setInvoice(): self { if($this->invoice->e_invoice){ $this->p_invoice = $this->e->decode('Peppol', json_encode($this->invoice->e_invoice->Invoice), 'json'); return $this; } $this->p_invoice = new \InvoiceNinja\EInvoice\Models\Peppol\Invoice(); $this->setInvoiceDefaults(); return $this; } /** * Transforms the settings props into usable models we can merge. * * @return self */ private function setSettings(): self { $this->_client_settings = isset($this->invoice->client->e_invoice->Invoice) ? $this->e->decode('Peppol', json_encode($this->invoice->client->e_invoice->Invoice), 'json') : null; $this->_company_settings = isset($this->invoice->company->e_invoice->Invoice) ? $this->e->decode('Peppol', json_encode($this->invoice->company->e_invoice->Invoice), 'json') : null; return $this; } public function getInvoice(): \InvoiceNinja\EInvoice\Models\Peppol\Invoice { //@todo - need to process this and remove null values return $this->p_invoice; } public function toXml(): string { $e = new EInvoice(); $xml = $e->encode($this->p_invoice, 'xml'); $prefix = ' '; return str_ireplace(['\n',''], ['', $prefix], $xml); } public function toJson(): string { $e = new EInvoice(); $json = $e->encode($this->p_invoice, 'json'); return $json; } public function toArray(): array { return json_decode($this->toJson(), true); } public function run() { $this->p_invoice->ID = $this->invoice->number; $this->p_invoice->IssueDate = new \DateTime($this->invoice->date); if($this->invoice->due_date) $this->p_invoice->DueDate = new \DateTime($this->invoice->due_date); $this->p_invoice->InvoiceTypeCode = 380; // $this->p_invoice->AccountingSupplierParty = $this->getAccountingSupplierParty(); $this->p_invoice->AccountingCustomerParty = $this->getAccountingCustomerParty(); $this->p_invoice->InvoiceLine = $this->getInvoiceLines(); // $this->p_invoice->TaxTotal = $this->getTotalTaxes(); it only wants the aggregate here!! $this->p_invoice->LegalMonetaryTotal = $this->getLegalMonetaryTotal(); $this->countryLevelMutators(); return $this; } private function getLegalMonetaryTotal(): LegalMonetaryTotal { $taxable = $this->getTaxable(); $lmt = new LegalMonetaryTotal(); $lea = new LineExtensionAmount(); $lea->currencyID = $this->invoice->client->currency()->code; $lea->amount = $this->invoice->uses_inclusive_taxes ? round($this->invoice->amount - $this->invoice->total_taxes, 2) : $taxable; $lmt->LineExtensionAmount = $lea; $tea = new TaxExclusiveAmount(); $tea->currencyID = $this->invoice->client->currency()->code; $tea->amount = $this->invoice->uses_inclusive_taxes ? round($this->invoice->amount - $this->invoice->total_taxes,2) : $taxable; $lmt->TaxExclusiveAmount = $tea; $tia = new TaxInclusiveAmount(); $tia->currencyID = $this->invoice->client->currency()->code; $tia->amount = $this->invoice->amount; $lmt->TaxInclusiveAmount = $tia; $pa = new PayableAmount(); $pa->currencyID = $this->invoice->client->currency()->code; $pa->amount = $this->invoice->amount; $lmt->PayableAmount = $pa; return $lmt; } private function getTotalTaxAmount(): float { if(!$this->invoice->total_taxes) return 0; elseif($this->invoice->uses_inclusive_taxes) return $this->invoice->total_taxes; return $this->calcAmountLineTax($this->invoice->tax_rate1, $this->invoice->amount) ?? 0; } private function getTotalTaxes(): array { $taxes = []; $type_id = $this->invoice->line_items[0]->type_id; // if(strlen($this->invoice->tax_name1 ?? '') > 1) { $tax_amount = new TaxAmount(); $tax_amount->currencyID = $this->invoice->client->currency()->code; $tax_amount->amount = $this->getTotalTaxAmount(); $tax_subtotal = new TaxSubtotal(); $tax_subtotal->TaxAmount = $tax_amount; $taxable_amount = new TaxableAmount(); $taxable_amount->currencyID = $this->invoice->client->currency()->code; $taxable_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->invoice->amount - $this->invoice->total_taxes : $this->invoice->amount; $tax_subtotal->TaxableAmount = $taxable_amount; $tc = new TaxCategory(); $tc->ID = $type_id == '2' ? 'HUR' : 'C62'; $tc->Percent = $this->invoice->tax_rate1; $ts = new PeppolTaxScheme(); $ts->ID = strlen($this->invoice->tax_name1 ?? '') > 1 ? $this->invoice->tax_name1 : '0'; $tc->TaxScheme = $ts; $tax_subtotal->TaxCategory = $tc; $tax_total = new TaxTotal(); $tax_total->TaxAmount = $tax_amount; $tax_total->TaxSubtotal[] = $tax_subtotal; $taxes[] = $tax_total; // } if(strlen($this->invoice->tax_name2 ?? '') > 1) { $tax_amount = new TaxAmount(); $tax_amount->currencyID = $this->invoice->client->currency()->code; $tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiveLineTax($this->invoice->tax_rate2, $this->invoice->amount) : $this->calcAmountLineTax($this->invoice->tax_rate2, $this->invoice->amount); $tax_subtotal = new TaxSubtotal(); $tax_subtotal->TaxAmount = $tax_amount; $taxable_amount = new TaxableAmount(); $taxable_amount->currencyID = $this->invoice->client->currency()->code; $taxable_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->invoice->amount- $this->invoice->total_taxes : $this->invoice->amount; $tax_subtotal->TaxableAmount = $taxable_amount; $tc = new TaxCategory(); $tc->ID = $type_id == '2' ? 'HUR' : 'C62'; $tc->Percent = $this->invoice->tax_rate2; $ts = new PeppolTaxScheme(); $ts->ID = $this->invoice->tax_name2; $tc->TaxScheme = $ts; $tax_subtotal->TaxCategory = $tc; $tax_total = new TaxTotal(); $tax_total->TaxAmount = $tax_amount; $tax_total->TaxSubtotal[] = $tax_subtotal; $taxes[] = $tax_total; } if(strlen($this->invoice->tax_name3 ?? '') > 1) { $tax_amount = new TaxAmount(); $tax_amount->currencyID = $this->invoice->client->currency()->code; $tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiveLineTax($this->invoice->tax_rate3, $this->invoice->amount) : $this->calcAmountLineTax($this->invoice->tax_rate3, $this->invoice->amount); $tax_subtotal = new TaxSubtotal(); $tax_subtotal->TaxAmount = $tax_amount; $taxable_amount = new TaxableAmount(); $taxable_amount->currencyID = $this->invoice->client->currency()->code; $taxable_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->invoice->amount - $this->invoice->total_taxes : $this->invoice->amount; $tax_subtotal->TaxableAmount = $taxable_amount; $tc = new TaxCategory(); $tc->ID = $type_id == '2' ? 'HUR' : 'C62'; $tc->Percent = $this->invoice->tax_rate3; $ts = new PeppolTaxScheme(); $ts->ID = $this->invoice->tax_name3; $tc->TaxScheme = $ts; $tax_subtotal->TaxCategory = $tc; $tax_total = new TaxTotal(); $tax_total->TaxAmount = $tax_amount; $tax_total->TaxSubtotal[] = $tax_subtotal; $taxes[] = $tax_total; } return $taxes; } private function getInvoiceLines(): array { $lines = []; foreach($this->invoice->line_items as $key => $item) { $_item = new Item(); $_item->Name = $item->product_key; $_item->Description = $item->notes; $line = new InvoiceLine(); $line->ID = $key + 1; $line->InvoicedQuantity = $item->quantity; $lea = new LineExtensionAmount(); $lea->currencyID = $this->invoice->client->currency()->code; // $lea->amount = $item->line_total; $lea->amount = $this->invoice->uses_inclusive_taxes ? $item->line_total - $this->calcInclusiveLineTax($item->tax_rate1, $item->line_total) : $item->line_total; $line->LineExtensionAmount = $lea; $line->Item = $_item; $item_taxes = $this->getItemTaxes($item); if(count($item_taxes) > 0) { $line->TaxTotal = $item_taxes; } // else { // $line->TaxTotal = $this->zeroTaxAmount(); // } $price = new Price(); $pa = new PriceAmount(); $pa->currencyID = $this->invoice->client->currency()->code; $pa->amount = $this->costWithDiscount($item) - ( $this->invoice->uses_inclusive_taxes ? ($this->calcInclusiveLineTax($item->tax_rate1, $item->line_total)/$item->quantity) : 0); $price->PriceAmount = $pa; $line->Price = $price; $lines[] = $line; } return $lines; } private function costWithDiscount($item) { $cost = $item->cost; if ($item->discount != 0) { if ($this->invoice->is_amount_discount) { $cost -= $item->discount / $item->quantity; } else { $cost -= $cost * $item->discount / 100; } } return $cost; } private function zeroTaxAmount(): array { $blank_tax = []; $tax_amount = new TaxAmount(); $tax_amount->currencyID = $this->invoice->client->currency()->code; $tax_amount->amount = '0'; $tax_subtotal = new TaxSubtotal(); $tax_subtotal->TaxAmount = $tax_amount; $taxable_amount = new TaxableAmount(); $taxable_amount->currencyID = $this->invoice->client->currency()->code; $taxable_amount->amount = '0'; $tax_subtotal->TaxableAmount = $taxable_amount; $tc = new TaxCategory(); $tc->ID = 'Z'; $tc->Percent = 0; $ts = new PeppolTaxScheme(); $ts->ID = '0'; $tc->TaxScheme = $ts; $tax_subtotal->TaxCategory = $tc; $tax_total = new TaxTotal(); $tax_total->TaxAmount = $tax_amount; $tax_total->TaxSubtotal[] = $tax_subtotal; $blank_tax[] = $tax_total; return $blank_tax; } private function getItemTaxes(object $item): array { $item_taxes = []; if(strlen($item->tax_name1 ?? '') > 1) { $tax_amount = new TaxAmount(); $tax_amount->currencyID = $this->invoice->client->currency()->code; $tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiveLineTax($item->tax_rate1, $item->line_total) : $this->calcAmountLineTax($item->tax_rate1, $item->line_total); $tax_subtotal = new TaxSubtotal(); $tax_subtotal->TaxAmount = $tax_amount; $taxable_amount = new TaxableAmount(); $taxable_amount->currencyID = $this->invoice->client->currency()->code; $taxable_amount->amount = $this->invoice->uses_inclusive_taxes ? $item->line_total - $tax_amount->amount : $item->line_total; $tax_subtotal->TaxableAmount = $taxable_amount; $tc = new TaxCategory(); $tc->ID = $item->type_id == '2' ? 'HUR' : 'C62'; $tc->Percent = $item->tax_rate1; $ts = new PeppolTaxScheme(); $ts->ID = $item->tax_name1; $tc->TaxScheme = $ts; $tax_subtotal->TaxCategory = $tc; $tax_total = new TaxTotal(); $tax_total->TaxAmount = $tax_amount; $tax_total->TaxSubtotal[] = $tax_subtotal; $item_taxes[] = $tax_total; } if(strlen($item->tax_name2 ?? '') > 1) { $tax_amount = new TaxAmount(); $tax_amount->currencyID = $this->invoice->client->currency()->code; $tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiveLineTax($item->tax_rate2, $item->line_total) : $this->calcAmountLineTax($item->tax_rate2, $item->line_total); $tax_subtotal = new TaxSubtotal(); $tax_subtotal->TaxAmount = $tax_amount; $taxable_amount = new TaxableAmount(); $taxable_amount->currencyID = $this->invoice->client->currency()->code; $taxable_amount->amount = $item->line_total; $tax_subtotal->TaxableAmount = $taxable_amount; $tc = new TaxCategory(); $tc->ID = $item->type_id == '2' ? 'HUR' : 'C62'; $tc->Percent = $item->tax_rate2; $ts = new PeppolTaxScheme(); $ts->ID = $item->tax_name2; $tc->TaxScheme = $ts; $tax_subtotal->TaxCategory = $tc; $tax_total = new TaxTotal(); $tax_total->TaxAmount = $tax_amount; $tax_total->TaxSubtotal[] = $tax_subtotal; $item_taxes[] = $tax_total; } if(strlen($item->tax_name3 ?? '') > 1) { $tax_amount = new TaxAmount(); $tax_amount->currencyID = $this->invoice->client->currency()->code; $tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiveLineTax($item->tax_rate3, $item->line_total) : $this->calcAmountLineTax($item->tax_rate3, $item->line_total); $tax_subtotal = new TaxSubtotal(); $tax_subtotal->TaxAmount = $tax_amount; $taxable_amount = new TaxableAmount(); $taxable_amount->currencyID = $this->invoice->client->currency()->code; $taxable_amount->amount = $item->line_total; $tax_subtotal->TaxableAmount = $taxable_amount; $tc = new TaxCategory(); $tc->ID = $item->type_id == '2' ? 'HUR' : 'C62'; $tc->Percent = $item->tax_rate3; $ts = new PeppolTaxScheme(); $ts->ID = $item->tax_name3; $tc->TaxScheme = $ts; $tax_subtotal->TaxCategory = $tc; $tax_total = new TaxTotal(); $tax_total->TaxAmount = $tax_amount; $tax_total->TaxSubtotal[] = $tax_subtotal; $item_taxes[] = $tax_total; } return $item_taxes; } private function getAccountingSupplierParty(): AccountingSupplierParty { $asp = new AccountingSupplierParty(); $party = new Party(); $party_name = new PartyName(); $party_name->Name = $this->invoice->company->present()->name(); $party->PartyName[] = $party_name; $address = new Address(); $address->CityName = $this->invoice->company->settings->city; $address->StreetName = $this->invoice->company->settings->address1; // $address->BuildingName = $this->invoice->company->settings->address2; $address->PostalZone = $this->invoice->company->settings->postal_code; $address->CountrySubentity = $this->invoice->company->settings->state; // $address->CountrySubentityCode = $this->invoice->company->settings->state; $country = new Country(); $country->IdentificationCode = $this->invoice->company->country()->iso_3166_2; $address->Country = $country; $party->PostalAddress = $address; $party->PhysicalLocation = $address; $contact = new Contact(); $contact->ElectronicMail = $this->getSetting('Invoice.AccountingSupplierParty.Party.Contact') ?? $this->invoice->company->owner()->present()->email(); $contact->Telephone = $this->getSetting('Invoice.AccountingSupplierParty.Party.Telephone') ?? $this->invoice->company->getSetting('phone'); $contact->Name = $this->getSetting('Invoice.AccountingSupplierParty.Party.Name') ?? $this->invoice->company->owner()->present()->name(); $party->Contact = $contact; $asp->Party = $party; return $asp; } private function getAccountingCustomerParty(): AccountingCustomerParty { $acp = new AccountingCustomerParty(); $party = new Party(); if(strlen($this->invoice->client->vat_number ?? '') > 1) { $pi = new PartyIdentification; $vatID = new ID; $vatID->schemeID = 'CH:MWST'; $vatID->value = $this->invoice->client->vat_number; $pi->ID = $vatID; $party->PartyIdentification[] = $pi; } $party_name = new PartyName(); $party_name->Name = $this->invoice->client->present()->name(); $party->PartyName[] = $party_name; $address = new Address(); $address->CityName = $this->invoice->client->city; $address->StreetName = $this->invoice->client->address1; // $address->BuildingName = $this->invoice->client->address2; $address->PostalZone = $this->invoice->client->postal_code; $address->CountrySubentity = $this->invoice->client->state; // $address->CountrySubentityCode = $this->invoice->client->state; $country = new Country(); $country->IdentificationCode = $this->invoice->client->country->iso_3166_2; $address->Country = $country; $party->PostalAddress = $address; $party->PhysicalLocation = $address; $contact = new Contact(); $contact->ElectronicMail = $this->invoice->client->present()->email(); $party->Contact = $contact; $acp->Party = $party; return $acp; } private function getTaxable(): float { $total = 0; foreach ($this->invoice->line_items as $item) { $line_total = $item->quantity * $item->cost; if ($item->discount != 0) { if ($this->invoice->is_amount_discount) { $line_total -= $item->discount; } else { $line_total -= $line_total * $item->discount / 100; } } $total += $line_total; } if ($this->invoice->discount > 0) { if ($this->invoice->is_amount_discount) { $total -= $this->invoice->discount; } else { $total *= (100 - $this->invoice->discount) / 100; $total = round($total, 2); } } if ($this->invoice->custom_surcharge1 && $this->invoice->custom_surcharge_tax1) { $total += $this->invoice->custom_surcharge1; } if ($this->invoice->custom_surcharge2 && $this->invoice->custom_surcharge_tax2) { $total += $this->invoice->custom_surcharge2; } if ($this->invoice->custom_surcharge3 && $this->invoice->custom_surcharge_tax3) { $total += $this->invoice->custom_surcharge3; } if ($this->invoice->custom_surcharge4 && $this->invoice->custom_surcharge_tax4) { $total += $this->invoice->custom_surcharge4; } return $total; } ///////////////// Helper Methods ///////////////////////// /** * setInvoiceDefaults * * Stubs a default einvoice * @return self */ public function setInvoiceDefaults(): self { $settings = [ 'AccountingCostCode' => 7, 'AccountingCost' => 7, 'BuyerReference' => 6, 'AccountingSupplierParty' => 1, 'AccountingCustomerParty' => 2, 'PayeeParty' => 1, 'BuyerCustomerParty' => 2, 'SellerSupplierParty' => 1, 'TaxRepresentativeParty' => 1, 'Delivery' => 1, 'DeliveryTerms' => 7, 'PaymentMeans' => 7, 'PaymentTerms' => 7, ]; //only scans for top level props foreach($settings as $prop => $visibility){ if($prop_value = $this->getSetting($prop)) $this->p_invoice->{$prop} = $prop_value; } return $this; } /** * getSetting * * Attempts to harvest and return a preconfigured prop from company / client / invoice settings * * @param string $property_path * @return mixed */ public function getSetting(string $property_path): mixed { if($prop_value = PropertyResolver::resolve($this->p_invoice, $property_path)) { return $prop_value; }elseif($prop_value = PropertyResolver::resolve($this->_client_settings, $property_path)) { return $prop_value; }elseif($prop_value = PropertyResolver::resolve($this->_company_settings, $property_path)) { return $prop_value; } return null; } /** * countryLevelMutators * * Runs country level specific requirements for the e-invoice * @return self */ private function countryLevelMutators():self { if(method_exists($this, $this->invoice->company->country()->iso_3166_2)) $this->{$this->invoice->company->country()->iso_3166_2}(); return $this; } /** * setPaymentMeans * * Sets the payment means - if it exists * @param bool $required * @return self */ private function setPaymentMeans(bool $required = false): self { if(isset($this->p_invoice->PaymentMeans)) return $this; elseif($paymentMeans = $this->getSetting('Invoice.PaymentMeans')){ $this->p_invoice->PaymentMeans = is_array($paymentMeans) ? $paymentMeans : [$paymentMeans]; return $this; } return $this->checkRequired($required, "Payment Means"); } /** * setOrderReference * * sets the order reference - if it exists (Never rely on settings for this) * * @param bool $required * @return self */ private function setOrderReference(bool $required = false): self { $this->p_invoice->BuyerReference = $this->invoice->po_number ?? ''; if(strlen($this->invoice->po_number ?? '') > 1) { $order_reference = new OrderReference(); $id = new ID(); $id->value = $this->invoice->po_number; $order_reference->ID = $id; $this->p_invoice->OrderReference = $order_reference; $this->setStorecoveMeta(["document" => [ "invoice" => [ "references" => [ "documentType" => "purchase_order", "documentId" => $this->invoice->po_number, ], ], ] ]); return $this; } return $this->checkRequired($required, 'Order Reference'); } /** * setCustomerAssignedAccountId * * Sets the client id_number CAN rely on settings * * @param bool $required * @return self */ private function setCustomerAssignedAccountId(bool $required = false): self { //@phpstan-ignore-next-line if(isset($this->p_invoice->AccountingCustomerParty->CustomerAssignedAccountID)){ return $this; } elseif($customer_assigned_account_id = $this->getSetting('Invoice.AccountingCustomerParty.CustomerAssignedAccountID')){ $this->p_invoice->AccountingCustomerParty->CustomerAssignedAccountID = $customer_assigned_account_id; return $this; } elseif(strlen($this->invoice->client->id_number ?? '') > 1){ $customer_assigned_account_id = new CustomerAssignedAccountID(); $customer_assigned_account_id->value = $this->invoice->client->id_number; $this->p_invoice->AccountingCustomerParty->CustomerAssignedAccountID = $customer_assigned_account_id; return $this; } //@phpstan-ignore-next-line return $this->checkRequired($required, 'Client ID Number'); } /** * Check Required * * Throws if a required field is missing. * * @param bool $required * @param string $section * @return self */ private function checkRequired(bool $required, string $section): self { return $required ? throw new \Exception("e-invoice generation halted:: {$section} required") : $this; } /** * Builds the Routing object for StoreCove * * @param string $schemeId * @param string $id * @return array */ private function buildRouting(string $schemeId, string $id): array { return [ "routing" => [ "publicIdentifiers" => [ [ "scheme" => $schemeId, "id" => $id ] ] ] ]; } ////////////////////////// Country level mutators ///////////////////////////////////// /** * DE * * @Completed * @Tested * * @return self */ private function DE(): self { $this->setPaymentMeans(true); return $this; } /** * setStorecoveMeta * * updates the storecove payload for sending documents * * @param array $meta * @return self */ private function setStorecoveMeta(array $meta): self { $this->storecove_meta = array_merge($this->storecove_meta, $meta); return $this; } /** * CH * * @Completed * * Completed - QR-Bill to be implemented at a later date. * @return self */ private function CH(): self { return $this; } /** * AT * * @Pending * * Need to ensure when sending to government entities that we route appropriately * Also need to ensure customerAssignedAccountIdValue is set so that the sender can be resolved. * * Need a way to define if the client is a government entity. * * @return self */ private function AT(): self { //special fields for sending to AT:GOV return $this; } private function AU(): self { //if payment means are included, they must be the same `type` return $this; } /** * ES * * @Pending * B2G configuration * B2G Testing * * testing. // routing identifier - 293098 * * @return self */ private function ES(): self { if(!isset($this->invoice->due_date)) $this->p_invoice->DueDate = new \DateTime($this->invoice->date); if($this->invoice->client->classification == 'business' && $this->invoice->company->getSetting('classification') == 'business') { //must have a paymentmeans as credit_transfer $this->setPaymentMeans(true); } // For B2G, provide three ES:FACE identifiers in the routing object, // as well as the ES:VAT tax identifier in the accountingCustomerParty.publicIdentifiers. // The invoice will then be routed through the FACe network. The three required ES:FACE identifiers are as follows: // "routing": { // "eIdentifiers":[ // { // "scheme": "ES:FACE", // "id": "L01234567", // "role": "ES-01-FISCAL" // }, // { // "scheme": "ES:FACE", // "id": "L01234567", // "role": "ES-02-RECEPTOR" // }, // { // "scheme": "ES:FACE", // "id": "L01234567", // "role": "ES-03-PAGADOR" // } // ] // } return $this; } private function FI(): self { // For Finvoice, provide an FI:OPID routing identifier and an FI:OVT legal identifier. // An FI:VAT is recommended. In many cases (depending on the sender/receiver country and the type of service/goods) // an FI:VAT is required. So we recommend always including this. return $this; } /** * FR * @Pending - clarification on codes needed * * @return self */ private function FR(): self { // When sending invoices to the French government (Chorus Pro): // All invoices have to be routed to SIRET 0009:11000201100044. There is no test environment for sending to public entities. // The SIRET / 0009 identifier of the final recipient is to be included in the invoice.accountingCustomerParty.publicIdentifiers array. if($this->invoice->client->classification == 'government'){ //route to SIRET 0009:11000201100044 $this->setStorecoveMeta($this->buildRouting('FR:SIRET', "0009:11000201100044")); // The SIRET / 0009 identifier of the final recipient is to be included in the invoice.accountingCustomerParty.publicIdentifiers array. $this->setCustomerAssignedAccountId(true); } if(strlen($this->invoice->client->id_number ?? '') == 9) { //SIREN $this->setStorecoveMeta($this->buildRouting('FR:SIREN', "0002:{$this->invoice->client->id_number}")); } else { //SIRET $this->setStorecoveMeta($this->buildRouting('FR:SIRET', "0009:{$this->invoice->client->id_number}")); } // ??????????????????????? //@TODO // The service code must be sent in invoice.buyerReference (deprecated) or the invoice.references array (documentType buyer_reference) if(strlen($this->invoice->po_number ?? '') >1) { $this->setOrderReference(false); } return $this; } private function IT(): self { // IT Sender, IT Receiver, B2B/B2G // Provide the receiver IT:VAT and the receiver IT:CUUO (codice destinatario) // IT Sender, IT Receiver, B2C // Provide the receiver IT:CF and the receiver IT:CUUO (codice destinatario) // IT Sender, non-IT Receiver // Provide the receiver tax identifier and any routing identifier applicable to the receiving country (see Receiver Identifiers). // non-IT Sender, IT Receiver, B2B/B2G // Provide the receiver IT:VAT and the receiver IT:CUUO (codice destinatario) // non-IT Sender, IT Receiver, B2C // Provide the receiver IT:CF and an optional email. The invoice will be eReported and sent via email. Note that this cannot be a PEC email address. return $this; } private function MY(): self { //way too much to digest here, delayed. return $this; } private function NL(): self { // When sending to public entities, the invoice.accountingSupplierParty.party.contact.email is mandatory. // Dutch senders and receivers require a legal identifier. For companies, this is NL:KVK, for public entities this is NL:OINO. return $this; } private function NZ(): self { // New Zealand uses a GLN to identify businesses. In addition, when sending invoices to a New Zealand customer, make sure you include the pseudo identifier NZ:GST as their tax identifier. return $this; } private function PL(): self { // Because using this network is not yet mandatory, the default workflow is to not use this network. Therefore, you have to force its use, as follows: // "routing": { // "eIdentifiers": [ // { // "scheme": "PL:VAT", // "id": "PL0101010101" // } // ], // "networks": [ // { // "application": "pl-ksef", // "settings": { // "enabled": true // } // } // ] // } // Note this will only work if your LegalEntity has been setup for this network. return $this; } private function RO(): self { // Because using this network is not yet mandatory, the default workflow is to not use this network. Therefore, you have to force its use, as follows: // "routing": { // "eIdentifiers": [ // { // "scheme": "RO:VAT", // "id": "RO010101010" // } // ], // "networks": [ // { // "application": "ro-anaf", // "settings": { // "enabled": true // } // } // ] // } // Note this will only work if your LegalEntity has been setup for this network. // The county field for a Romania address must use the ISO3166-2:RO codes, e.g. "RO-AB, RO-AR". Don’t omit the country prefix! // The city field for county RO-B must be SECTOR1 - SECTOR6. return $this; } private function SG(): self { //delayed - stage 2 return $this; } //Sweden private function SE(): self { // Deliver invoices to the "Svefaktura" co-operation of local Swedish service providers. // Routing is through the SE:ORGNR together with a network specification: // "routing": { // "eIdentifiers": [ // { // "scheme": "SE:ORGNR", // "id": "0012345678" // } // ], // "networks": [ // { // "application": "svefaktura", // "settings": { // "enabled": true // } // } // ] // } // Use of the "Svefaktura" co-operation can also be induced by specifying an operator id, as follows: // "routing": { // "eIdentifiers": [ // { // "scheme": "SE:ORGNR", // "id": "0012345678" // }, // { // "scheme": "SE:OPID", // "id": "1234567890" // } // ] // } return $this; } }