mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-15 15:42:51 +01:00
641 lines
17 KiB
PHP
641 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* Invoice Ninja (https://invoiceninja.com).
|
|
*
|
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
|
*
|
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
|
*
|
|
* @license https://www.elastic.co/licensing/elastic-license
|
|
*/
|
|
|
|
namespace App\Services\EDocument\Gateway\Storecove;
|
|
|
|
use App\Services\EDocument\Gateway\MutatorUtil;
|
|
use App\Services\EDocument\Standards\Peppol\RO;
|
|
use App\Services\EDocument\Gateway\MutatorInterface;
|
|
use App\Services\EDocument\Gateway\Storecove\StorecoveRouter;
|
|
|
|
class Mutator implements MutatorInterface
|
|
{
|
|
|
|
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 $invoice;
|
|
|
|
private array $storecove_meta = [];
|
|
|
|
private MutatorUtil $mutator_util;
|
|
// Constructor
|
|
public function __construct(public Storecove $storecove)
|
|
{
|
|
$this->mutator_util = new MutatorUtil($this);
|
|
}
|
|
|
|
/**
|
|
* setInvoice
|
|
*
|
|
* @param mixed $invoice
|
|
* @return self
|
|
*/
|
|
public function setInvoice($invoice): self
|
|
{
|
|
$this->invoice = $invoice;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* setPeppol
|
|
*
|
|
* @param mixed $p_invoice
|
|
* @return self
|
|
*/
|
|
public function setPeppol($p_invoice): self
|
|
{
|
|
$this->p_invoice = $p_invoice;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* getPeppol
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function getPeppol(): mixed
|
|
{
|
|
return $this->p_invoice;
|
|
}
|
|
|
|
/**
|
|
* setClientSettings
|
|
*
|
|
* @param mixed $client_settings
|
|
* @return self
|
|
*/
|
|
public function setClientSettings($client_settings): self
|
|
{
|
|
$this->_client_settings = $client_settings;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* setCompanySettings
|
|
*
|
|
* @param mixed $company_settings
|
|
* @return self
|
|
*/
|
|
public function setCompanySettings($company_settings): self
|
|
{
|
|
$this->_company_settings = $company_settings;
|
|
return $this;
|
|
}
|
|
|
|
public function getClientSettings(): mixed
|
|
{
|
|
return $this->_client_settings;
|
|
}
|
|
|
|
public function getCompanySettings(): mixed
|
|
{
|
|
return $this->_company_settings;
|
|
}
|
|
|
|
public function getInvoice(): mixed
|
|
{
|
|
return $this->invoice;
|
|
}
|
|
|
|
public function getSetting(string $property_path): mixed
|
|
{
|
|
return $this->mutator_util->getSetting($property_path);
|
|
}
|
|
/**
|
|
* senderSpecificLevelMutators
|
|
*
|
|
* Runs sender level specific requirements for the e-invoice,
|
|
*
|
|
* ie, mutations that are required by the senders country.
|
|
*
|
|
* @return self
|
|
*/
|
|
public function senderSpecificLevelMutators(): self
|
|
{
|
|
|
|
if(method_exists($this, $this->invoice->company->country()->iso_3166_2)) {
|
|
$this->{$this->invoice->company->country()->iso_3166_2}();
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* receiverSpecificLevelMutators
|
|
*
|
|
* Runs receiver level specific requirements for the e-invoice
|
|
*
|
|
* ie mutations that are required by the receiving country
|
|
* @return self
|
|
*/
|
|
public function receiverSpecificLevelMutators(): self
|
|
{
|
|
|
|
if(method_exists($this, "client_{$this->invoice->company->country()->iso_3166_2}")) {
|
|
$this->{"client_{$this->invoice->company->country()->iso_3166_2}"}();
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* DE
|
|
*
|
|
* @Completed
|
|
* @Tested
|
|
*
|
|
* @return self
|
|
*/
|
|
public function DE(): self
|
|
{
|
|
|
|
$this->mutator_util->setPaymentMeans(true);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* CH
|
|
*
|
|
* @Completed
|
|
*
|
|
* Completed - QR-Bill to be implemented at a later date.
|
|
* @return self
|
|
*/
|
|
public 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
|
|
*/
|
|
public function AT(): self
|
|
{
|
|
//special fields for sending to AT:GOV
|
|
|
|
if($this->invoice->client->classification == 'government') {
|
|
//routing "b" for production "test" for test environment
|
|
$this->setStorecoveMeta($this->buildRouting(["scheme" => 'AT:GOV', "id" => 'b']));
|
|
|
|
//for government clients this must be set.
|
|
$this->mutator_util->setCustomerAssignedAccountId(true);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public 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
|
|
*/
|
|
public 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->mutator_util->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;
|
|
}
|
|
|
|
/**
|
|
* FI
|
|
*
|
|
* @return self
|
|
*/
|
|
public 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
|
|
*/
|
|
public 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([
|
|
["scheme" => 'FR:SIRET', "id" => '11000201100044']
|
|
|
|
// ["scheme" => 'FR:SIRET', "id" => '0009:11000201100044']
|
|
]));
|
|
|
|
// The SIRET / 0009 identifier of the final recipient is to be included in the invoice.accountingCustomerParty.publicIdentifiers array.
|
|
$this->mutator_util->setCustomerAssignedAccountId(true);
|
|
|
|
}
|
|
|
|
if(strlen($this->invoice->client->id_number ?? '') == 9) {
|
|
//SIREN
|
|
$this->setStorecoveMeta($this->buildRouting([
|
|
["scheme" => 'FR:SIRET', "id" => "{$this->invoice->client->id_number}"]
|
|
|
|
// ["scheme" => 'FR:SIRET', "id" => "0002:{$this->invoice->client->id_number}"]
|
|
]));
|
|
} else {
|
|
//SIRET
|
|
$this->setStorecoveMeta($this->buildRouting([
|
|
["scheme" => 'FR:SIRET', "id" => "{$this->invoice->client->id_number}"]
|
|
|
|
// ["scheme" => 'FR:SIRET', "id" => "0009:{$this->invoice->client->id_number}"]
|
|
]));
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* IT
|
|
*
|
|
* @return self
|
|
*/
|
|
public function IT(): self
|
|
{
|
|
|
|
// IT Sender, IT Receiver, B2B/B2G
|
|
// Provide the receiver IT:VAT and the receiver IT:CUUO (codice destinatario)
|
|
if(in_array($this->invoice->client->classification, ['business','government']) && $this->invoice->company->country()->iso_3166_2 == 'IT') {
|
|
|
|
$this->setStorecoveMeta($this->buildRouting([
|
|
["scheme" => 'IT:IVA', "id" => $this->invoice->client->vat_number],
|
|
["scheme" => 'IT:CUUO', "id" => $this->invoice->client->routing_id]
|
|
]));
|
|
|
|
return $this;
|
|
}
|
|
|
|
// IT Sender, IT Receiver, B2C
|
|
// Provide the receiver IT:CF and the receiver IT:CUUO (codice destinatario)
|
|
if($this->invoice->client->classification == 'individual' && $this->invoice->company->country()->iso_3166_2 == 'IT') {
|
|
|
|
$this->setStorecoveMeta($this->buildRouting([
|
|
["scheme" => 'IT:CF', "id" => $this->invoice->client->vat_number],
|
|
// ["scheme" => 'IT:CUUO', "id" => $this->invoice->client->routing_id]
|
|
]));
|
|
|
|
$this->setEmailRouting($this->invoice->client->present()->email());
|
|
|
|
return $this;
|
|
}
|
|
|
|
// IT Sender, non-IT Receiver
|
|
// Provide the receiver tax identifier and any routing identifier applicable to the receiving country (see Receiver Identifiers).
|
|
if($this->invoice->client->country->iso_3166_2 != 'IT' && $this->invoice->company->country()->iso_3166_2 == 'IT') {
|
|
|
|
$code = $this->getClientRoutingCode();
|
|
|
|
nlog("foreign receiver");
|
|
$this->setStorecoveMeta($this->buildRouting([
|
|
["scheme" => $code, "id" => $this->invoice->client->vat_number]
|
|
]));
|
|
|
|
return $this;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* client_IT
|
|
*
|
|
* @return self
|
|
*/
|
|
public function client_IT(): self
|
|
{
|
|
|
|
// 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.
|
|
if(in_array($this->invoice->client->classification, ['individual']) && $this->invoice->company->country()->iso_3166_2 != 'IT') {
|
|
|
|
return $this;
|
|
}
|
|
|
|
// non-IT Sender, IT Receiver, B2B/B2G
|
|
// Provide the receiver IT:VAT and the receiver IT:CUUO (codice destinatario)
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
/**
|
|
* MY
|
|
*
|
|
* @return self
|
|
*/
|
|
public function MY(): self
|
|
{
|
|
//way too much to digest here, delayed.
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* NL
|
|
*
|
|
* @return self
|
|
*/
|
|
public 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;
|
|
}
|
|
|
|
/**
|
|
* NZ
|
|
*
|
|
* @return self
|
|
*/
|
|
public 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;
|
|
}
|
|
|
|
/**
|
|
* PL
|
|
*
|
|
* @return self
|
|
*/
|
|
public 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;
|
|
}
|
|
|
|
/**
|
|
* RO
|
|
*
|
|
* @return self
|
|
*/
|
|
public 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:
|
|
$meta = ["networks" => [
|
|
[
|
|
"application" => "ro-anaf",
|
|
"settings" => [
|
|
"enabled" => true
|
|
],
|
|
],
|
|
]];
|
|
|
|
$this->setStorecoveMeta($meta);
|
|
|
|
$this->setStorecoveMeta($this->buildRouting([
|
|
["scheme" => 'RO:VAT', "id" => $this->invoice->client->vat_number],
|
|
]));
|
|
|
|
$ro = new RO($this->invoice);
|
|
|
|
$client_state = $this->mutator_util->getClientSetting('Invoice.AccountingSupplierParty.Party.PostalAddress.Address.CountrySubentity');
|
|
$client_city = $this->mutator_util->getClientSetting('Invoice.AccountingCustomerParty.Party.PostalAddress.Address.CityName');
|
|
|
|
$resolved_state = $ro->getStateCode($client_state);
|
|
$resolved_city = $ro->getSectorCode($client_city);
|
|
|
|
$this->p_invoice->AccountingCustomerParty->Party->PostalAddress->CountrySubentity = $resolved_state;
|
|
$this->p_invoice->AccountingCustomerParty->Party->PostalAddress->CityName = $resolved_city;
|
|
$this->p_invoice->AccountingCustomerParty->Party->PhysicalLocation->Address->CountrySubentity = $resolved_state;
|
|
$this->p_invoice->AccountingCustomerParty->Party->PhysicalLocation->Address->CityName = $resolved_city;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* SG
|
|
*
|
|
* @return self
|
|
*/
|
|
public function SG(): self
|
|
{
|
|
//delayed - stage 2
|
|
return $this;
|
|
}
|
|
|
|
//Sweden
|
|
public 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;
|
|
}
|
|
|
|
|
|
/////////////// Storecove Helpers ///////////////
|
|
|
|
/**
|
|
* getClientRoutingCode
|
|
*
|
|
* @return string
|
|
*/
|
|
private function getClientRoutingCode(): string
|
|
{
|
|
return (new StorecoveRouter())->resolveRouting($this->invoice->client->country->iso_3166_2, $this->invoice->client->classification);
|
|
}
|
|
|
|
|
|
/**
|
|
* Builds the Routing object for StoreCove
|
|
*
|
|
* @param array $identifiers
|
|
* @return array
|
|
*/
|
|
private function buildRouting(array $identifiers): array
|
|
{
|
|
return
|
|
[
|
|
"routing" => [
|
|
"eIdentifiers" =>
|
|
$identifiers,
|
|
|
|
]
|
|
];
|
|
}
|
|
|
|
|
|
/**
|
|
* setEmailRouting
|
|
*
|
|
* @param string $email
|
|
* @return self
|
|
*/
|
|
private function setEmailRouting(string $email): self
|
|
{
|
|
|
|
$meta = $this->getStorecoveMeta();
|
|
|
|
if(isset($meta['routing']['emails'])) {
|
|
$emails = $meta['routing']['emails'];
|
|
array_push($emails, $email);
|
|
$meta['routing']['emails'] = $emails;
|
|
} else {
|
|
$meta['routing']['emails'] = [$email];
|
|
}
|
|
|
|
$this->setStorecoveMeta($meta);
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* getStorecoveMeta
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getStorecoveMeta(): array
|
|
{
|
|
return $this->storecove_meta;
|
|
}
|
|
|
|
|
|
} |