Refactoring storecove
This commit is contained in:
parent
de1c7fdc0e
commit
59ec545c1c
|
|
@ -237,38 +237,64 @@ class Rule extends BaseRule implements RuleInterface
|
||||||
*/
|
*/
|
||||||
public function calculateRates(): self
|
public function calculateRates(): self
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// Tax exempt clients always get zero tax
|
||||||
if ($this->client->is_tax_exempt) {
|
if ($this->client->is_tax_exempt) {
|
||||||
// nlog("tax exempt");
|
|
||||||
$this->tax_rate = 0;
|
$this->tax_rate = 0;
|
||||||
$this->reduced_tax_rate = 0;
|
$this->reduced_tax_rate = 0;
|
||||||
} elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->vat_number && $this->client->has_valid_vat_number && $this->eu_business_tax_exempt) {
|
return $this;
|
||||||
// nlog("euro zone and tax exempt");
|
}
|
||||||
|
|
||||||
|
// B2B within EU with valid VAT
|
||||||
|
if ($this->client_subregion != $this->client->company->tax_data->seller_subregion &&
|
||||||
|
in_array($this->client_subregion, $this->eu_country_codes) &&
|
||||||
|
$this->client->vat_number &&
|
||||||
|
$this->client->has_valid_vat_number &&
|
||||||
|
$this->eu_business_tax_exempt) {
|
||||||
$this->tax_rate = 0;
|
$this->tax_rate = 0;
|
||||||
$this->reduced_tax_rate = 0;
|
$this->reduced_tax_rate = 0;
|
||||||
} elseif(!in_array($this->client_subregion, $this->eu_country_codes) && ($this->foreign_consumer_tax_exempt || $this->foreign_business_tax_exempt)) { //foreign + tax exempt
|
return $this;
|
||||||
// nlog("foreign and tax exempt");
|
}
|
||||||
$this->tax_rate = 0;
|
|
||||||
$this->reduced_tax_rate = 0;
|
// Non-EU transactions
|
||||||
} elseif(!in_array($this->client_subregion, $this->eu_country_codes)) {
|
if (!in_array($this->client_subregion, $this->eu_country_codes)) {
|
||||||
$this->defaultForeign();
|
if ($this->foreign_consumer_tax_exempt || $this->foreign_business_tax_exempt) {
|
||||||
} elseif(in_array($this->client_subregion, $this->eu_country_codes) && ((strlen($this->client->vat_number ?? '') == 1) || !$this->client->has_valid_vat_number)) { //eu country / no valid vat
|
$this->tax_rate = 0;
|
||||||
if($this->client->company->tax_data->seller_subregion != $this->client_subregion) {
|
$this->reduced_tax_rate = 0;
|
||||||
// nlog("eu zone with sales above threshold");
|
} else {
|
||||||
|
$this->defaultForeign();
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// B2C or invalid VAT within EU
|
||||||
|
$is_b2c = strlen($this->client->vat_number ?? '') <= 1 ||
|
||||||
|
!$this->client->has_valid_vat_number ||
|
||||||
|
$this->client->classification == 'individual';
|
||||||
|
|
||||||
|
if ($is_b2c) {
|
||||||
|
$is_over_threshold = $this->client->company->tax_data->regions->EU->has_sales_above_threshold ?? false;
|
||||||
|
|
||||||
|
if ($is_over_threshold && $this->client->company->tax_data->seller_subregion != $this->client_subregion) {
|
||||||
|
// Over threshold - use destination country rates
|
||||||
|
$this->tax_name = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->tax_name;
|
||||||
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->tax_rate ?? 0;
|
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->tax_rate ?? 0;
|
||||||
$this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_tax_rate ?? 0;
|
$this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_tax_rate ?? 0;
|
||||||
} else {
|
} else {
|
||||||
// nlog("EU with intra-community supply ie DE to DE");
|
// Under threshold or domestic - use origin country rates
|
||||||
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->tax_rate ?? 0;
|
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->tax_rate ?? 0;
|
||||||
$this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->reduced_tax_rate ?? 0;
|
$this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->reduced_tax_rate ?? 0;
|
||||||
}
|
}
|
||||||
} else {
|
return $this;
|
||||||
// nlog("default tax");
|
|
||||||
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->tax_rate ?? 0;
|
|
||||||
$this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->reduced_tax_rate ?? 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default case (B2B without valid VAT)
|
||||||
|
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->tax_rate ?? 0;
|
||||||
|
$this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->reduced_tax_rate ?? 0;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,8 +141,8 @@ class InvoiceItemSum
|
||||||
$this->invoice = $invoice;
|
$this->invoice = $invoice;
|
||||||
$this->client = $invoice->client ?? $invoice->vendor;
|
$this->client = $invoice->client ?? $invoice->vendor;
|
||||||
|
|
||||||
if ($this->invoice->client) {
|
if ($this->client) {
|
||||||
$this->currency = $this->invoice->client->currency();
|
$this->currency = $this->client->currency();
|
||||||
$this->shouldCalculateTax();
|
$this->shouldCalculateTax();
|
||||||
} else {
|
} else {
|
||||||
$this->currency = $this->invoice->vendor->currency();
|
$this->currency = $this->invoice->vendor->currency();
|
||||||
|
|
@ -158,7 +158,7 @@ class InvoiceItemSum
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->calcLineItems();
|
$this->calcLineItems()->getPeppolSurchargeTaxes();
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +186,6 @@ class InvoiceItemSum
|
||||||
|
|
||||||
if (in_array($this->client->company->country()->iso_3166_2, $this->tax_jurisdictions)) { //only calculate for supported tax jurisdictions
|
if (in_array($this->client->company->country()->iso_3166_2, $this->tax_jurisdictions)) { //only calculate for supported tax jurisdictions
|
||||||
|
|
||||||
|
|
||||||
/** @var \App\DataMapper\Tax\BaseRule $class */
|
/** @var \App\DataMapper\Tax\BaseRule $class */
|
||||||
$class = "App\DataMapper\Tax\\".str_replace("-","_",$this->client->company->country()->iso_3166_2)."\\Rule";
|
$class = "App\DataMapper\Tax\\".str_replace("-","_",$this->client->company->country()->iso_3166_2)."\\Rule";
|
||||||
|
|
||||||
|
|
@ -208,6 +207,46 @@ class InvoiceItemSum
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function calculateNexus()
|
||||||
|
{
|
||||||
|
|
||||||
|
$company_country_code = $this->invoice->company->country()->iso_3166_2;
|
||||||
|
$client_country_code = $this->client->country->iso_3166_2;
|
||||||
|
$base_rule = new \App\DataMapper\Tax\BaseRule();
|
||||||
|
$eu_countries = $base_rule->eu_country_codes;
|
||||||
|
$nexus_rule = $company_country_code;
|
||||||
|
|
||||||
|
if ($client_country_code == $company_country_code) {
|
||||||
|
//Domestic Sales
|
||||||
|
$nexus_rule = $company_country_code;
|
||||||
|
} elseif (in_array($company_country_code, $eu_countries) && !in_array($client_country_code, $eu_countries)) {
|
||||||
|
//NON-EU Sale
|
||||||
|
$nexus_rule = $company_country_code;
|
||||||
|
} elseif (in_array($company_country_code, $eu_countries) && in_array($client_country_code, $eu_countries)) {
|
||||||
|
|
||||||
|
//EU Sale
|
||||||
|
// Invalid VAT number = seller country nexus
|
||||||
|
if(isset($this->client->has_valid_vat_number) && !$this->client->has_valid_vat_number){
|
||||||
|
$nexus_rule = $company_country_code;
|
||||||
|
}
|
||||||
|
elseif (isset($this->invoice->company->tax_data->regions->EU->has_sales_above_threshold) && $this->invoice->company->tax_data->regions->EU->has_sales_above_threshold) { //over threshold - tax in buyer country
|
||||||
|
$nexus_rule = $client_country_code;
|
||||||
|
} elseif (isset($this->invoice->company->tax_data->regions->EU->has_sales_above_threshold) && !$this->invoice->company->tax_data->regions->EU->has_sales_above_threshold) {
|
||||||
|
$nexus_rule = $company_country_code;
|
||||||
|
} elseif ($this->client->classification != 'individual' && (isset($this->client->has_valid_vat_number) && !$this->client->has_valid_vat_number)){
|
||||||
|
$nexus_rule = $company_country_code;
|
||||||
|
} else {
|
||||||
|
$nexus_rule = $company_country_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
nlog($nexus_rule);
|
||||||
|
|
||||||
|
$class = "App\DataMapper\Tax\\".str_replace("-", "_", $nexus_rule)."\\Rule";
|
||||||
|
return $class;
|
||||||
|
}
|
||||||
|
|
||||||
private function push(): self
|
private function push(): self
|
||||||
{
|
{
|
||||||
$this->sub_total += round($this->getLineTotal(), $this->currency->precision);
|
$this->sub_total += round($this->getLineTotal(), $this->currency->precision);
|
||||||
|
|
@ -324,6 +363,50 @@ class InvoiceItemSum
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function getPeppolSurchargeTaxes(): self
|
||||||
|
{
|
||||||
|
if(!$this->client->getSetting('e_invoice_type') == 'PEPPOL')
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
collect($this->invoice->line_items)
|
||||||
|
->flatMap(function ($item) {
|
||||||
|
return collect([1, 2, 3])
|
||||||
|
->map(fn ($i) => [
|
||||||
|
'name' => $item->{"tax_name{$i}"} ?? '',
|
||||||
|
'percentage' => $item->{"tax_rate{$i}"} ?? 0,
|
||||||
|
])
|
||||||
|
->filter(fn ($tax) => strlen($tax['name']) > 1);
|
||||||
|
})
|
||||||
|
->unique(fn ($tax) => $tax['percentage'] . '_' . $tax['name'])
|
||||||
|
->values()
|
||||||
|
->each(function ($tax){
|
||||||
|
|
||||||
|
$tax_component = 0;
|
||||||
|
|
||||||
|
if ($this->invoice->custom_surcharge1) {
|
||||||
|
$tax_component += round($this->invoice->custom_surcharge1 * ($tax['percentage'] / 100), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->invoice->custom_surcharge2) {
|
||||||
|
$tax_component += round($this->invoice->custom_surcharge2 * ($tax['percentage'] / 100), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->invoice->custom_surcharge3) {
|
||||||
|
$tax_component += round($this->invoice->custom_surcharge3 * ($tax['percentage'] / 100), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->invoice->custom_surcharge4) {
|
||||||
|
$tax_component += round($this->invoice->custom_surcharge4 * ($tax['percentage'] / 100), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->groupTax($tax['name'], $tax['percentage'], $tax_component);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
private function groupTax($tax_name, $tax_rate, $tax_total)
|
private function groupTax($tax_name, $tax_rate, $tax_total)
|
||||||
{
|
{
|
||||||
$group_tax = [];
|
$group_tax = [];
|
||||||
|
|
|
||||||
|
|
@ -240,15 +240,11 @@ class InvoiceSum
|
||||||
|
|
||||||
public function getRecurringInvoice()
|
public function getRecurringInvoice()
|
||||||
{
|
{
|
||||||
// $this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
|
|
||||||
// $this->invoice->total_taxes = $this->getTotalTaxes();
|
|
||||||
|
|
||||||
$this->setCalculatedAttributes();
|
$this->setCalculatedAttributes();
|
||||||
$this->invoice->balance = $this->invoice->amount;
|
$this->invoice->balance = $this->invoice->amount;
|
||||||
$this->invoice->saveQuietly();
|
$this->invoice->saveQuietly();
|
||||||
|
|
||||||
// $this->invoice->saveQuietly();
|
|
||||||
|
|
||||||
return $this->invoice;
|
return $this->invoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -426,4 +422,14 @@ class InvoiceSum
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getNetSubtotal()
|
||||||
|
{
|
||||||
|
return $this->getSubTotal() - $this->getTotalDiscount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubtotalWithSurcharges()
|
||||||
|
{
|
||||||
|
return $this->getSubTotal() + $this->getTotalSurcharges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -401,6 +401,11 @@ class InvoiceSumInclusive
|
||||||
return $this->getTotalTaxes();
|
return $this->getTotalTaxes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getNetSubtotal()
|
||||||
|
{
|
||||||
|
return $this->getSubTotal() - $this->getTotalDiscount();
|
||||||
|
}
|
||||||
|
|
||||||
public function purgeTaxes()
|
public function purgeTaxes()
|
||||||
{
|
{
|
||||||
return $this;
|
return $this;
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ class Product extends BaseModel
|
||||||
public const PRODUCT_TYPE_OVERRIDE_TAX = 7;
|
public const PRODUCT_TYPE_OVERRIDE_TAX = 7;
|
||||||
public const PRODUCT_TYPE_ZERO_RATED = 8;
|
public const PRODUCT_TYPE_ZERO_RATED = 8;
|
||||||
public const PRODUCT_TYPE_REVERSE_TAX = 9;
|
public const PRODUCT_TYPE_REVERSE_TAX = 9;
|
||||||
|
public const PRODUCT_INTRA_COMMUNITY = 10;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'custom_value1',
|
'custom_value1',
|
||||||
|
|
|
||||||
|
|
@ -51,4 +51,10 @@ class AccountingSupplierParty
|
||||||
$this->public_identifiers = $public_identifiers;
|
$this->public_identifiers = $public_identifiers;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function addPublicIdentifiers($public_identifier): self
|
||||||
|
{
|
||||||
|
$this->public_identifiers[] = $public_identifier;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,20 @@ use Symfony\Component\Serializer\Attribute\SerializedPath;
|
||||||
|
|
||||||
class AllowanceCharges
|
class AllowanceCharges
|
||||||
{
|
{
|
||||||
#[SerializedPath('[cbc:Amount][#]')]
|
// #[SerializedPath('[cbc:Amount][#]')]
|
||||||
public ?string $amount_excluding_vat;
|
public ?float $amount_excluding_vat;
|
||||||
|
|
||||||
// #[SerializedPath('[cbc:BaseAmount][#]')]
|
#[SerializedPath('[cbc:Amount][#]')]
|
||||||
public ?string $amount_excluding_tax;
|
public ?float $amount_excluding_tax;
|
||||||
|
|
||||||
#[SerializedPath('[cbc:BaseAmount][#]')]
|
#[SerializedPath('[cbc:BaseAmount][#]')]
|
||||||
public ?string $base_amount_excluding_tax;
|
public ?float $base_amount_excluding_tax;
|
||||||
|
|
||||||
#[SerializedPath('[cbc:Amount][@currencyID]')]
|
// #[SerializedPath('[cbc:Amount][#]')]
|
||||||
public ?string $amount_including_tax;
|
public ?float $amount_including_tax;
|
||||||
|
|
||||||
#[SerializedPath('[cbc:BaseAmount][@currencyID]')]
|
// #[SerializedPath('[cbc:BaseAmount][#]')]
|
||||||
public ?string $base_amount_including_tax;
|
public ?float $base_amount_including_tax;
|
||||||
|
|
||||||
// #[SerializedPath('[cac:TaxCategory]')]
|
// #[SerializedPath('[cac:TaxCategory]')]
|
||||||
// public ?Tax $tax;
|
// public ?Tax $tax;
|
||||||
|
|
@ -39,11 +39,11 @@ class AllowanceCharges
|
||||||
* @param TaxesDutiesFees[] $taxes_duties_fees
|
* @param TaxesDutiesFees[] $taxes_duties_fees
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
?string $amount_excluding_vat,
|
?float $amount_excluding_vat,
|
||||||
?string $amount_excluding_tax,
|
?float $amount_excluding_tax,
|
||||||
?string $base_amount_excluding_tax,
|
?float $base_amount_excluding_tax,
|
||||||
?string $amount_including_tax,
|
?float $amount_including_tax,
|
||||||
?string $base_amount_including_tax,
|
?float $base_amount_including_tax,
|
||||||
// ?Tax $tax,
|
// ?Tax $tax,
|
||||||
?array $taxes_duties_fees,
|
?array $taxes_duties_fees,
|
||||||
?string $reason,
|
?string $reason,
|
||||||
|
|
@ -60,27 +60,27 @@ class AllowanceCharges
|
||||||
$this->reason_code = $reason_code;
|
$this->reason_code = $reason_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAmountExcludingVat(): ?string
|
public function getAmountExcludingVat(): ?float
|
||||||
{
|
{
|
||||||
return $this->amount_excluding_vat;
|
return $this->amount_excluding_vat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAmountExcludingTax(): ?string
|
public function getAmountExcludingTax(): ?float
|
||||||
{
|
{
|
||||||
return $this->amount_excluding_tax;
|
return $this->amount_excluding_tax;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBaseAmountExcludingTax(): ?string
|
public function getBaseAmountExcludingTax(): ?float
|
||||||
{
|
{
|
||||||
return $this->base_amount_excluding_tax;
|
return $this->base_amount_excluding_tax;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAmountIncludingTax(): ?string
|
public function getAmountIncludingTax(): ?float
|
||||||
{
|
{
|
||||||
return $this->amount_including_tax;
|
return $this->amount_including_tax;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBaseAmountIncludingTax(): ?string
|
public function getBaseAmountIncludingTax(): ?float
|
||||||
{
|
{
|
||||||
return $this->base_amount_including_tax;
|
return $this->base_amount_including_tax;
|
||||||
}
|
}
|
||||||
|
|
@ -103,31 +103,31 @@ class AllowanceCharges
|
||||||
return $this->reason_code;
|
return $this->reason_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAmountExcludingVat(?string $amount_excluding_vat): self
|
public function setAmountExcludingVat(?float $amount_excluding_vat): self
|
||||||
{
|
{
|
||||||
$this->amount_excluding_vat = $amount_excluding_vat;
|
$this->amount_excluding_vat = $amount_excluding_vat;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAmountExcludingTax(?string $amount_excluding_tax): self
|
public function setAmountExcludingTax(?float $amount_excluding_tax): self
|
||||||
{
|
{
|
||||||
$this->amount_excluding_tax = $amount_excluding_tax;
|
$this->amount_excluding_tax = $amount_excluding_tax;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setBaseAmountExcludingTax(?string $base_amount_excluding_tax): self
|
public function setBaseAmountExcludingTax(?float $base_amount_excluding_tax): self
|
||||||
{
|
{
|
||||||
$this->base_amount_excluding_tax = $base_amount_excluding_tax;
|
$this->base_amount_excluding_tax = $base_amount_excluding_tax;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAmountIncludingTax(?string $amount_including_tax): self
|
public function setAmountIncludingTax(?float $amount_including_tax): self
|
||||||
{
|
{
|
||||||
$this->amount_including_tax = $amount_including_tax;
|
$this->amount_including_tax = $amount_including_tax;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setBaseAmountIncludingTax(?string $base_amount_including_tax): self
|
public function setBaseAmountIncludingTax(?float $base_amount_including_tax): self
|
||||||
{
|
{
|
||||||
$this->base_amount_including_tax = $base_amount_including_tax;
|
$this->base_amount_including_tax = $base_amount_including_tax;
|
||||||
return $this;
|
return $this;
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,12 @@ class InvoiceLines
|
||||||
|
|
||||||
#[SerializedPath('[cac:AllowanceCharge]')]
|
#[SerializedPath('[cac:AllowanceCharge]')]
|
||||||
/** @var AllowanceCharges[] */ //todo
|
/** @var AllowanceCharges[] */ //todo
|
||||||
public ?array $charges;
|
public ?array $allowance_charges;
|
||||||
|
|
||||||
#[SerializedPath('[cbc:LineExtensionAmount][#]')]
|
#[SerializedPath('[cbc:LineExtensionAmount][#]')]
|
||||||
public ?float $amount_excluding_vat;
|
public ?float $amount_excluding_vat;
|
||||||
|
|
||||||
#[SerializedPath('[cbc:TaxExclusiveAmount][#]')]
|
#[SerializedPath('[cac:Price][cbc:PriceAmount][value]')]
|
||||||
public ?float $amount_excluding_tax;
|
public ?float $amount_excluding_tax;
|
||||||
|
|
||||||
#[SerializedPath('[cbc:TaxInclusiveAmount][#]')]
|
#[SerializedPath('[cbc:TaxInclusiveAmount][#]')]
|
||||||
|
|
@ -51,7 +51,7 @@ class InvoiceLines
|
||||||
|
|
||||||
#[SerializedPath('[cac:Item][cac:ClassifiedTaxCategory]')]
|
#[SerializedPath('[cac:Item][cac:ClassifiedTaxCategory]')]
|
||||||
/** @var TaxesDutiesFees[] */
|
/** @var TaxesDutiesFees[] */
|
||||||
public array $taxes_duties_fees;
|
public ?array $taxes_duties_fees = [];
|
||||||
|
|
||||||
#[SerializedPath('[cbc:AccountingCost]')]
|
#[SerializedPath('[cbc:AccountingCost]')]
|
||||||
public ?string $accounting_cost;
|
public ?string $accounting_cost;
|
||||||
|
|
@ -83,7 +83,7 @@ class InvoiceLines
|
||||||
public ?string $note;
|
public ?string $note;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param AllowanceCharges[] $charges
|
* @param AllowanceCharges[] $allowance_charges
|
||||||
* @param TaxesDutiesFees[] $taxes_duties_fees
|
* @param TaxesDutiesFees[] $taxes_duties_fees
|
||||||
* @param References[] $references
|
* @param References[] $references
|
||||||
* @param AdditionalItemProperties[] $additional_item_properties
|
* @param AdditionalItemProperties[] $additional_item_properties
|
||||||
|
|
@ -98,7 +98,7 @@ class InvoiceLines
|
||||||
?float $quantity,
|
?float $quantity,
|
||||||
?float $base_quantity,
|
?float $base_quantity,
|
||||||
?string $quantity_unit_code,
|
?string $quantity_unit_code,
|
||||||
?array $charges,
|
?array $allowance_charges,
|
||||||
?float $amount_excluding_vat,
|
?float $amount_excluding_vat,
|
||||||
?float $amount_excluding_tax,
|
?float $amount_excluding_tax,
|
||||||
?float $amount_including_tax,
|
?float $amount_including_tax,
|
||||||
|
|
@ -122,7 +122,7 @@ class InvoiceLines
|
||||||
$this->quantity = $quantity;
|
$this->quantity = $quantity;
|
||||||
$this->base_quantity = $base_quantity;
|
$this->base_quantity = $base_quantity;
|
||||||
$this->quantity_unit_code = $quantity_unit_code;
|
$this->quantity_unit_code = $quantity_unit_code;
|
||||||
$this->charges = $charges;
|
$this->allowance_charges = $allowance_charges;
|
||||||
$this->amount_excluding_vat = $amount_excluding_vat;
|
$this->amount_excluding_vat = $amount_excluding_vat;
|
||||||
$this->amount_excluding_tax = $amount_excluding_tax;
|
$this->amount_excluding_tax = $amount_excluding_tax;
|
||||||
$this->amount_including_tax = $amount_including_tax;
|
$this->amount_including_tax = $amount_including_tax;
|
||||||
|
|
@ -188,7 +188,7 @@ class InvoiceLines
|
||||||
*/
|
*/
|
||||||
public function getAllowanceCharges(): ?array
|
public function getAllowanceCharges(): ?array
|
||||||
{
|
{
|
||||||
return $this->charges;
|
return $this->allowance_charges;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAmountExcludingVat(): ?float
|
public function getAmountExcludingVat(): ?float
|
||||||
|
|
@ -320,11 +320,11 @@ class InvoiceLines
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param AllowanceCharges[] $charges
|
* @param AllowanceCharges[] $allowance_charges
|
||||||
*/
|
*/
|
||||||
public function setAllowanceCharges(?array $charges): self
|
public function setAllowanceCharges(?array $allowance_charges): self
|
||||||
{
|
{
|
||||||
$this->charges = $charges;
|
$this->allowance_charges = $allowance_charges;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,36 @@ class StorecoveAdapter
|
||||||
|
|
||||||
private string $nexus;
|
private string $nexus;
|
||||||
|
|
||||||
|
public function validate(): self
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInvoice(): Invoice
|
||||||
|
{
|
||||||
|
return $this->storecove_invoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getErrors(): array
|
||||||
|
{
|
||||||
|
return $this->errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* addError
|
||||||
|
*
|
||||||
|
* Adds an error to the errors array.
|
||||||
|
*
|
||||||
|
* @param string $error
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
private function addError(string $error): self
|
||||||
|
{
|
||||||
|
$this->errors[] = $error;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* transform
|
* transform
|
||||||
*
|
*
|
||||||
|
|
@ -54,32 +84,30 @@ class StorecoveAdapter
|
||||||
|
|
||||||
$serializer = $this->getSerializer();
|
$serializer = $this->getSerializer();
|
||||||
|
|
||||||
|
|
||||||
|
/** Currently - due to class structures, the serialization process goes like this:
|
||||||
|
*
|
||||||
|
* e-invoice => Peppol -> XML -> Peppol Decoded -> encode to Peppol -> deserialize to Storecove
|
||||||
|
*/
|
||||||
$p = (new Peppol($invoice))->run()->toXml();
|
$p = (new Peppol($invoice))->run()->toXml();
|
||||||
|
|
||||||
nlog($p);
|
$context = [
|
||||||
|
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d',
|
||||||
|
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
|
||||||
|
];
|
||||||
|
|
||||||
$context = [
|
$e = new \InvoiceNinja\EInvoice\EInvoice();
|
||||||
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d',
|
$peppolInvoice = $e->decode('Peppol', $p, 'xml');
|
||||||
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
|
nlog($peppolInvoice);
|
||||||
];
|
|
||||||
|
|
||||||
|
$parent = \App\Services\EDocument\Gateway\Storecove\Models\Invoice::class;
|
||||||
|
$peppolInvoice = $e->encode($peppolInvoice, 'json');
|
||||||
|
$this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'json', $context);
|
||||||
|
|
||||||
$e = new \InvoiceNinja\EInvoice\EInvoice();
|
nlog($this->storecove_invoice);
|
||||||
$peppolInvoice = $e->decode('Peppol', $p, 'xml');
|
|
||||||
|
|
||||||
$parent = \App\Services\EDocument\Gateway\Storecove\Models\Invoice::class;
|
|
||||||
$peppolInvoice = $data = $e->encode($peppolInvoice, 'json');
|
|
||||||
$this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'json', $context);
|
|
||||||
// $s_invoice = $serializer->encode($invoice, 'json', $context);
|
|
||||||
// $arr = json_decode($s_invoice, true);
|
|
||||||
// $data = $this->removeEmptyValues($arr);
|
|
||||||
|
|
||||||
|
|
||||||
$this->buildNexus();
|
$this->buildNexus();
|
||||||
|
|
||||||
// @phpstan-ignore-next-line
|
|
||||||
// $this->storecove_invoice = $serializer->deserialize($data, Invoice::class, 'json', $context);
|
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -89,16 +117,39 @@ $this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'js
|
||||||
//set all taxmap countries - resolve the taxing country
|
//set all taxmap countries - resolve the taxing country
|
||||||
$lines = $this->storecove_invoice->getInvoiceLines();
|
$lines = $this->storecove_invoice->getInvoiceLines();
|
||||||
|
|
||||||
foreach($lines as $line)
|
foreach($lines as &$line)
|
||||||
{
|
{
|
||||||
foreach($line->taxes_duties_fees as &$tax)
|
if(isset($line->taxes_duties_fees))
|
||||||
{
|
{
|
||||||
$tax->country = $this->nexus;
|
foreach($line->taxes_duties_fees as &$tax)
|
||||||
|
{
|
||||||
if(property_exists($tax,'category'))
|
$tax->country = $this->nexus;
|
||||||
$tax->category = $this->tranformTaxCode($tax->category);
|
|
||||||
|
if(property_exists($tax,'category'))
|
||||||
|
$tax->category = $this->tranformTaxCode($tax->category);
|
||||||
|
}
|
||||||
|
unset($tax);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($line->allowance_charges))
|
||||||
|
{
|
||||||
|
foreach($line->allowance_charges as &$allowance)
|
||||||
|
{
|
||||||
|
if($allowance->reason == ctrans('texts.discount'))
|
||||||
|
$allowance->amount_excluding_tax = $allowance->amount_excluding_tax * -1;
|
||||||
|
|
||||||
|
foreach($allowance->getTaxesDutiesFees as &$tax)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (property_exists($tax, 'category')) {
|
||||||
|
$tax->category = $this->tranformTaxCode($tax->category);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
unset($tax);
|
||||||
|
}
|
||||||
|
unset($allowance);
|
||||||
}
|
}
|
||||||
unset($tax);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->storecove_invoice->setInvoiceLines($lines);
|
$this->storecove_invoice->setInvoiceLines($lines);
|
||||||
|
|
@ -128,43 +179,46 @@ $this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'js
|
||||||
|
|
||||||
$this->storecove_invoice->setPaymentMeansArray($payment_means);
|
$this->storecove_invoice->setPaymentMeansArray($payment_means);
|
||||||
|
|
||||||
|
$allowances = $this->storecove_invoice->getAllowanceCharges() ?? [];
|
||||||
|
|
||||||
|
foreach($allowances as &$allowance)
|
||||||
|
{
|
||||||
|
$taxes = $allowance->getTaxesDutiesFees() ?? [];
|
||||||
|
|
||||||
|
foreach($taxes as &$tax)
|
||||||
|
{
|
||||||
|
$tax->country = $this->nexus;
|
||||||
|
|
||||||
|
if (property_exists($tax, 'category')) {
|
||||||
|
$tax->category = $this->tranformTaxCode($tax->category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($tax);
|
||||||
|
|
||||||
|
|
||||||
|
if ($allowance->reason == ctrans('texts.discount')) {
|
||||||
|
$allowance->amount_excluding_tax = $allowance->amount_excluding_tax * -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowance->setTaxesDutiesFees($taxes);
|
||||||
|
|
||||||
|
}
|
||||||
|
unset($allowance);
|
||||||
|
|
||||||
|
$this->storecove_invoice->setAllowanceCharges($allowances);
|
||||||
|
|
||||||
$this->storecove_invoice->setTaxSystem('tax_line_percentages');
|
$this->storecove_invoice->setTaxSystem('tax_line_percentages');
|
||||||
//set additional identifier if required (ie de => FR with FR vat)
|
//set additional identifier if required (ie de => FR with FR vat)
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validate(): self
|
|
||||||
{
|
|
||||||
// $this->valid_document
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getInvoice(): Invoice
|
|
||||||
{
|
|
||||||
return $this->storecove_invoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getErrors(): array
|
|
||||||
{
|
|
||||||
return $this->errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addError(string $error): self
|
|
||||||
{
|
|
||||||
$this->errors[] = $error;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getSerializer()
|
private function getSerializer()
|
||||||
{
|
{
|
||||||
|
|
||||||
$phpDocExtractor = new PhpDocExtractor();
|
$phpDocExtractor = new PhpDocExtractor();
|
||||||
$reflectionExtractor = new ReflectionExtractor();
|
$reflectionExtractor = new ReflectionExtractor();
|
||||||
// list of PropertyListExtractorInterface (any iterable)
|
|
||||||
$typeExtractors = [$reflectionExtractor,$phpDocExtractor];
|
$typeExtractors = [$reflectionExtractor,$phpDocExtractor];
|
||||||
// list of PropertyDescriptionExtractorInterface (any iterable)
|
|
||||||
$descriptionExtractors = [$phpDocExtractor];
|
$descriptionExtractors = [$phpDocExtractor];
|
||||||
// list of PropertyAccessExtractorInterface (any iterable)
|
|
||||||
$propertyInitializableExtractors = [$reflectionExtractor];
|
$propertyInitializableExtractors = [$reflectionExtractor];
|
||||||
$propertyInfo = new PropertyInfoExtractor(
|
$propertyInfo = new PropertyInfoExtractor(
|
||||||
$propertyInitializableExtractors,
|
$propertyInitializableExtractors,
|
||||||
|
|
@ -185,7 +239,12 @@ $this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'js
|
||||||
|
|
||||||
return $serializer;
|
return $serializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the document and appends an errors prop
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
public function getDocument(): mixed
|
public function getDocument(): mixed
|
||||||
{
|
{
|
||||||
$serializer = $this->getSerializer();
|
$serializer = $this->getSerializer();
|
||||||
|
|
@ -209,7 +268,13 @@ $this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'js
|
||||||
return $data;
|
return $data;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RemoveEmptyValues
|
||||||
|
*
|
||||||
|
* @param array $array
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
private function removeEmptyValues(array $array): array
|
private function removeEmptyValues(array $array): array
|
||||||
{
|
{
|
||||||
foreach ($array as $key => $value) {
|
foreach ($array as $key => $value) {
|
||||||
|
|
@ -236,27 +301,74 @@ $this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'js
|
||||||
$eu_countries = $br->eu_country_codes;
|
$eu_countries = $br->eu_country_codes;
|
||||||
|
|
||||||
if ($client_country_code == $company_country_code) {
|
if ($client_country_code == $company_country_code) {
|
||||||
//Domestic Sales
|
//Domestic Sales
|
||||||
|
nlog("domestic sales");
|
||||||
$this->nexus = $company_country_code;
|
$this->nexus = $company_country_code;
|
||||||
} elseif (in_array($company_country_code, $eu_countries) && !in_array($client_country_code, $eu_countries)) {
|
} elseif (in_array($company_country_code, $eu_countries) && !in_array($client_country_code, $eu_countries)) {
|
||||||
//NON-EU Sale
|
//NON-EU Sale
|
||||||
|
nlog("non eu");
|
||||||
$this->nexus = $company_country_code;
|
$this->nexus = $company_country_code;
|
||||||
} elseif (in_array($company_country_code, $eu_countries) && in_array($client_country_code, $eu_countries)) {
|
} elseif (in_array($company_country_code, $eu_countries) && in_array($client_country_code, $eu_countries)) {
|
||||||
|
|
||||||
//EU Sale
|
//EU Sale
|
||||||
|
|
||||||
// Invalid VAT number = seller country nexus
|
// First, determine if we're over threshold
|
||||||
if(!$this->ninja_invoice->client->has_valid_vat_number)
|
$is_over_threshold = isset($this->ninja_invoice->company->tax_data->regions->EU->has_sales_above_threshold) &&
|
||||||
$this->nexus = $company_country_code;
|
$this->ninja_invoice->company->tax_data->regions->EU->has_sales_above_threshold;
|
||||||
else if ($this->ninja_invoice->company->tax_data->regions->EU->has_sales_above_threshold && isset($this->ninja_invoice->company->tax_data->regions->EU->subregions->{$client_country_code}->vat_number)) { //over threshold - tax in buyer country
|
|
||||||
$this->nexus = $client_country_code;
|
// Is this B2B or B2C?
|
||||||
}
|
$is_b2c = strlen($this->ninja_invoice->client->vat_number) < 2 ||
|
||||||
|
!($this->ninja_invoice->client->has_valid_vat_number ?? false) ||
|
||||||
|
$this->ninja_invoice->client->classification == 'individual';
|
||||||
|
|
||||||
|
if (strlen($this->ninja_invoice->company->settings->vat_number) < 2) {
|
||||||
|
// No VAT registration at all - must charge origin country VAT
|
||||||
|
nlog("no company vat");
|
||||||
|
$this->nexus = $company_country_code;
|
||||||
|
} elseif ($is_b2c) {
|
||||||
|
if ($is_over_threshold) {
|
||||||
|
// B2C over threshold - need destination VAT number
|
||||||
|
if (!isset($this->ninja_invoice->company->tax_data->regions->EU->subregions->{$client_country_code}->vat_number)) {
|
||||||
|
$this->addError("Tax Nexus is client country ({$client_country_code}) - however VAT number not present for this region. Document not sent!");
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
nlog("B2C");
|
||||||
|
$this->nexus = $client_country_code;
|
||||||
|
$this->setupDestinationVAT($client_country_code);
|
||||||
|
} else {
|
||||||
|
nlog("under threshold origina country");
|
||||||
|
// B2C under threshold - origin country VAT
|
||||||
|
$this->nexus = $company_country_code;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nlog("B2B with valid vat");
|
||||||
|
// B2B with valid VAT - origin country
|
||||||
|
$this->nexus = $company_country_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlog("nexus = {$this->nexus}");
|
||||||
|
nlog($this->ninja_invoice->company->tax_data->regions->EU->has_sales_above_threshold);
|
||||||
|
nlog("is b2c {$is_b2c}");
|
||||||
|
nlog("is over threshold {$is_over_threshold}");
|
||||||
|
|
||||||
//If we reach here? We are in an invalid state!
|
|
||||||
$this->nexus = $company_country_code;
|
|
||||||
$this->addError("Tax Nexus is client country ({$client_country_code}) - however VAT number not present for this region. Document not sent!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setupDestinationVAT($client_country_code):self
|
||||||
|
{
|
||||||
|
nlog("configuring destination tax");
|
||||||
|
$this->storecove_invoice->setConsumerTaxMode(true);
|
||||||
|
$id = $this->ninja_invoice->company->tax_data->regions->EU->subregions->{$client_country_code}->vat_number;
|
||||||
|
$scheme = $this->storecove->router->resolveTaxScheme($client_country_code, $this->ninja_invoice->client->classification ?? 'individual');
|
||||||
|
|
||||||
|
$pi = new \App\Services\EDocument\Gateway\Storecove\Models\PublicIdentifiers($scheme, $id);
|
||||||
|
$asp = $this->storecove_invoice->getAccountingSupplierParty();
|
||||||
|
$asp->addPublicIdentifiers($pi);
|
||||||
|
$this->storecove_invoice->setAccountingSupplierParty($asp);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ class StorecoveRouter
|
||||||
"MK" => ["B+G","","MK:VAT","MK:VAT"],
|
"MK" => ["B+G","","MK:VAT","MK:VAT"],
|
||||||
"MT" => ["B+G","","MT:VAT","MT:VAT"],
|
"MT" => ["B+G","","MT:VAT","MT:VAT"],
|
||||||
"NL" => ["G","NL:OINO",false,"NL:OINO"],
|
"NL" => ["G","NL:OINO",false,"NL:OINO"],
|
||||||
"NL" => ["B","NL:KVK","NL:VAT","NL:KVK or NL:VAT"],
|
"NL" => ["B","NL:KVK","NL:VAT","NL:VAT"],
|
||||||
"PL" => ["G+B","","PL:VAT","PL:VAT"],
|
"PL" => ["G+B","","PL:VAT","PL:VAT"],
|
||||||
"PT" => ["G+B","","PT:VAT","PT:VAT"],
|
"PT" => ["G+B","","PT:VAT","PT:VAT"],
|
||||||
"RO" => ["G+B","","RO:VAT","RO:VAT"],
|
"RO" => ["G+B","","RO:VAT","RO:VAT"],
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,12 @@ class SendEDocument implements ShouldQueue
|
||||||
public function handle(Storecove $storecove)
|
public function handle(Storecove $storecove)
|
||||||
{
|
{
|
||||||
MultiDB::setDB($this->db);
|
MultiDB::setDB($this->db);
|
||||||
|
|
||||||
|
nlog("trying");
|
||||||
|
|
||||||
$model = $this->entity::find($this->id);
|
$model = $this->entity::find($this->id);
|
||||||
|
|
||||||
|
/** Concrete implementation current linked to Storecove only */
|
||||||
$p = new Peppol($model);
|
$p = new Peppol($model);
|
||||||
$p->run();
|
$p->run();
|
||||||
$identifiers = $p->gateway->mutator->setClientRoutingCode()->getStorecoveMeta();
|
$identifiers = $p->gateway->mutator->setClientRoutingCode()->getStorecoveMeta();
|
||||||
|
|
@ -58,6 +61,7 @@ class SendEDocument implements ShouldQueue
|
||||||
$result = $storecove->build($model);
|
$result = $storecove->build($model);
|
||||||
|
|
||||||
if (count($result['errors']) > 0) {
|
if (count($result['errors']) > 0) {
|
||||||
|
nlog($result);
|
||||||
return $result['errors'];
|
return $result['errors'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,12 +74,10 @@ class SendEDocument implements ShouldQueue
|
||||||
],
|
],
|
||||||
'tenant_id' => $model->company->company_key,
|
'tenant_id' => $model->company->company_key,
|
||||||
'routing' => $identifiers['routing'],
|
'routing' => $identifiers['routing'],
|
||||||
// 'e_invoicing_token' => $model->company->e_invoicing_token,
|
|
||||||
// include whitelabel key.
|
|
||||||
];
|
];
|
||||||
|
/** Concrete implementation current linked to Storecove only */
|
||||||
|
|
||||||
//temp
|
//@testing only
|
||||||
|
|
||||||
$sc = new \App\Services\EDocument\Gateway\Storecove\Storecove();
|
$sc = new \App\Services\EDocument\Gateway\Storecove\Storecove();
|
||||||
$r = $sc->sendJsonDocument($payload);
|
$r = $sc->sendJsonDocument($payload);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,10 @@ class Peppol extends AbstractService
|
||||||
|
|
||||||
private array $tax_map = [];
|
private array $tax_map = [];
|
||||||
|
|
||||||
|
private float $allowance_total = 0;
|
||||||
|
|
||||||
|
private $globalTaxCategories;
|
||||||
|
|
||||||
public function __construct(public Invoice $invoice)
|
public function __construct(public Invoice $invoice)
|
||||||
{
|
{
|
||||||
$this->company = $invoice->company;
|
$this->company = $invoice->company;
|
||||||
|
|
@ -166,6 +170,7 @@ class Peppol extends AbstractService
|
||||||
public function run(): self
|
public function run(): self
|
||||||
{
|
{
|
||||||
$this->getJurisdiction();
|
$this->getJurisdiction();
|
||||||
|
$this->getAllUsedTaxes();
|
||||||
|
|
||||||
/** Invoice Level Props */
|
/** Invoice Level Props */
|
||||||
$id = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\CustomizationID();
|
$id = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\CustomizationID();
|
||||||
|
|
@ -208,8 +213,8 @@ class Peppol extends AbstractService
|
||||||
$this->p_invoice->AccountingSupplierParty = $this->getAccountingSupplierParty();
|
$this->p_invoice->AccountingSupplierParty = $this->getAccountingSupplierParty();
|
||||||
$this->p_invoice->AccountingCustomerParty = $this->getAccountingCustomerParty();
|
$this->p_invoice->AccountingCustomerParty = $this->getAccountingCustomerParty();
|
||||||
$this->p_invoice->InvoiceLine = $this->getInvoiceLines();
|
$this->p_invoice->InvoiceLine = $this->getInvoiceLines();
|
||||||
$this->p_invoice->LegalMonetaryTotal = $this->getLegalMonetaryTotal();
|
|
||||||
$this->p_invoice->AllowanceCharge = $this->getAllowanceCharges();
|
$this->p_invoice->AllowanceCharge = $this->getAllowanceCharges();
|
||||||
|
$this->p_invoice->LegalMonetaryTotal = $this->getLegalMonetaryTotal();
|
||||||
|
|
||||||
$this->setOrderReference()->setTaxBreakdown();
|
$this->setOrderReference()->setTaxBreakdown();
|
||||||
|
|
||||||
|
|
@ -430,18 +435,22 @@ class Peppol extends AbstractService
|
||||||
$allowanceCharge->ChargeIndicator = 'false'; // false = discount
|
$allowanceCharge->ChargeIndicator = 'false'; // false = discount
|
||||||
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
||||||
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
||||||
$allowanceCharge->Amount->amount = (string)$this->calc->getTotalDiscount();
|
$allowanceCharge->Amount->amount = (string)number_format($this->calc->getTotalDiscount(),2);
|
||||||
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
|
||||||
$allowanceCharge->BaseAmount->currencyID = $this->invoice->client->currency()->code;
|
|
||||||
$allowanceCharge->BaseAmount->amount = (string) $this->calc->getSubTotal();
|
|
||||||
|
|
||||||
// Add percentage if available
|
// Add percentage if available
|
||||||
if ($this->invoice->discount > 0 && !$this->invoice->is_amount_discount) {
|
if ($this->invoice->discount > 0 && !$this->invoice->is_amount_discount) {
|
||||||
|
|
||||||
|
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
||||||
|
$allowanceCharge->BaseAmount->currencyID = $this->invoice->client->currency()->code;
|
||||||
|
$allowanceCharge->BaseAmount->amount = (string) number_format($this->calc->getSubtotalWithSurcharges(), 2);
|
||||||
|
|
||||||
$mfn = new \InvoiceNinja\EInvoice\Models\Peppol\NumericType\MultiplierFactorNumeric();
|
$mfn = new \InvoiceNinja\EInvoice\Models\Peppol\NumericType\MultiplierFactorNumeric();
|
||||||
$mfn->value = (string) ($this->invoice->discount / 100);
|
$mfn->value = (string)number_format(round(($this->invoice->discount), 2), 2); // Format to always show 2 decimals
|
||||||
$allowanceCharge->MultiplierFactorNumeric = $mfn; // Convert percentage to decimal
|
$allowanceCharge->MultiplierFactorNumeric = $mfn; // Convert percentage to decimal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$allowanceCharge->TaxCategory = $this->globalTaxCategories;
|
||||||
|
$allowanceCharge->AllowanceChargeReason = ctrans('texts.discount');
|
||||||
$allowances[] = $allowanceCharge;
|
$allowances[] = $allowanceCharge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -450,14 +459,17 @@ class Peppol extends AbstractService
|
||||||
|
|
||||||
// Add Allowance Charge to Price
|
// Add Allowance Charge to Price
|
||||||
$allowanceCharge = new \InvoiceNinja\EInvoice\Models\Peppol\AllowanceChargeType\AllowanceCharge();
|
$allowanceCharge = new \InvoiceNinja\EInvoice\Models\Peppol\AllowanceChargeType\AllowanceCharge();
|
||||||
// $allowanceCharge->ChargeIndicator = true; // false = discount
|
|
||||||
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
||||||
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
||||||
$allowanceCharge->Amount->amount = (string)$this->invoice->custom_surcharge1;
|
$allowanceCharge->Amount->amount = (string)$this->invoice->custom_surcharge1;
|
||||||
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
||||||
$allowanceCharge->BaseAmount->currencyID = $this->invoice->client->currency()->code;
|
$allowanceCharge->BaseAmount->currencyID = $this->invoice->client->currency()->code;
|
||||||
$allowanceCharge->BaseAmount->amount = (string) $this->calc->getSubTotal();
|
$allowanceCharge->BaseAmount->amount = (string) $this->calc->getSubtotalWithSurcharges();
|
||||||
|
|
||||||
|
$this->calculateTaxMap($this->invoice->custom_surcharge1);
|
||||||
|
|
||||||
|
$allowanceCharge->TaxCategory = $this->globalTaxCategories;
|
||||||
|
$allowanceCharge->AllowanceChargeReason = ctrans('texts.surcharge');
|
||||||
$allowances[] = $allowanceCharge;
|
$allowances[] = $allowanceCharge;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -466,14 +478,18 @@ class Peppol extends AbstractService
|
||||||
|
|
||||||
// Add Allowance Charge to Price
|
// Add Allowance Charge to Price
|
||||||
$allowanceCharge = new \InvoiceNinja\EInvoice\Models\Peppol\AllowanceChargeType\AllowanceCharge();
|
$allowanceCharge = new \InvoiceNinja\EInvoice\Models\Peppol\AllowanceChargeType\AllowanceCharge();
|
||||||
// $allowanceCharge->ChargeIndicator = true; // false = discount
|
|
||||||
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
||||||
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
||||||
$allowanceCharge->Amount->amount = (string)$this->invoice->custom_surcharge2;
|
$allowanceCharge->Amount->amount = (string)$this->invoice->custom_surcharge2;
|
||||||
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
||||||
$allowanceCharge->BaseAmount->currencyID = $this->invoice->client->currency()->code;
|
$allowanceCharge->BaseAmount->currencyID = $this->invoice->client->currency()->code;
|
||||||
$allowanceCharge->BaseAmount->amount = (string) $this->calc->getSubTotal();
|
$allowanceCharge->BaseAmount->amount = (string) $this->calc->getSubtotalWithSurcharges();
|
||||||
|
|
||||||
|
|
||||||
|
$this->calculateTaxMap($this->invoice->custom_surcharge2);
|
||||||
|
|
||||||
|
$allowanceCharge->TaxCategory = $this->globalTaxCategories;
|
||||||
|
$allowanceCharge->AllowanceChargeReason = ctrans('texts.surcharge');
|
||||||
$allowances[] = $allowanceCharge;
|
$allowances[] = $allowanceCharge;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -482,14 +498,17 @@ class Peppol extends AbstractService
|
||||||
|
|
||||||
// Add Allowance Charge to Price
|
// Add Allowance Charge to Price
|
||||||
$allowanceCharge = new \InvoiceNinja\EInvoice\Models\Peppol\AllowanceChargeType\AllowanceCharge();
|
$allowanceCharge = new \InvoiceNinja\EInvoice\Models\Peppol\AllowanceChargeType\AllowanceCharge();
|
||||||
// $allowanceCharge->ChargeIndicator = true; // false = discount
|
|
||||||
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
||||||
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
||||||
$allowanceCharge->Amount->amount = (string)$this->invoice->custom_surcharge3;
|
$allowanceCharge->Amount->amount = (string)$this->invoice->custom_surcharge3;
|
||||||
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
||||||
$allowanceCharge->BaseAmount->currencyID = $this->invoice->client->currency()->code;
|
$allowanceCharge->BaseAmount->currencyID = $this->invoice->client->currency()->code;
|
||||||
$allowanceCharge->BaseAmount->amount = (string) $this->calc->getSubTotal();
|
$allowanceCharge->BaseAmount->amount = (string) $this->calc->getSubtotalWithSurcharges();
|
||||||
|
|
||||||
|
$this->calculateTaxMap($this->invoice->custom_surcharge3);
|
||||||
|
|
||||||
|
$allowanceCharge->TaxCategory = $this->globalTaxCategories;
|
||||||
|
$allowanceCharge->AllowanceChargeReason = ctrans('texts.surcharge');
|
||||||
$allowances[] = $allowanceCharge;
|
$allowances[] = $allowanceCharge;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -498,14 +517,17 @@ class Peppol extends AbstractService
|
||||||
|
|
||||||
// Add Allowance Charge to Price
|
// Add Allowance Charge to Price
|
||||||
$allowanceCharge = new \InvoiceNinja\EInvoice\Models\Peppol\AllowanceChargeType\AllowanceCharge();
|
$allowanceCharge = new \InvoiceNinja\EInvoice\Models\Peppol\AllowanceChargeType\AllowanceCharge();
|
||||||
// $allowanceCharge->ChargeIndicator = true; // false = discount
|
|
||||||
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
||||||
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
||||||
$allowanceCharge->Amount->amount = (string)$this->invoice->custom_surcharge4;
|
$allowanceCharge->Amount->amount = (string)$this->invoice->custom_surcharge4;
|
||||||
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
||||||
$allowanceCharge->BaseAmount->currencyID = $this->invoice->client->currency()->code;
|
$allowanceCharge->BaseAmount->currencyID = $this->invoice->client->currency()->code;
|
||||||
$allowanceCharge->BaseAmount->amount = (string) $this->calc->getSubTotal();
|
$allowanceCharge->BaseAmount->amount = (string) $this->calc->getSubtotalWithSurcharges();
|
||||||
|
|
||||||
|
$this->calculateTaxMap($this->invoice->custom_surcharge4);
|
||||||
|
|
||||||
|
$allowanceCharge->TaxCategory = $this->globalTaxCategories;
|
||||||
|
$allowanceCharge->AllowanceChargeReason = ctrans('texts.surcharge');
|
||||||
$allowances[] = $allowanceCharge;
|
$allowances[] = $allowanceCharge;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -527,7 +549,7 @@ class Peppol extends AbstractService
|
||||||
|
|
||||||
$lea = new LineExtensionAmount();
|
$lea = new LineExtensionAmount();
|
||||||
$lea->currencyID = $this->invoice->client->currency()->code;
|
$lea->currencyID = $this->invoice->client->currency()->code;
|
||||||
$lea->amount = $this->invoice->uses_inclusive_taxes ? round($this->invoice->amount - $this->invoice->total_taxes, 2) : $taxable;
|
$lea->amount = $this->invoice->uses_inclusive_taxes ? round($this->invoice->amount - $this->invoice->total_taxes, 2) : $this->calc->getSubTotal();
|
||||||
$lmt->LineExtensionAmount = $lea;
|
$lmt->LineExtensionAmount = $lea;
|
||||||
|
|
||||||
$tea = new TaxExclusiveAmount();
|
$tea = new TaxExclusiveAmount();
|
||||||
|
|
@ -545,6 +567,11 @@ class Peppol extends AbstractService
|
||||||
$pa->amount = $this->invoice->amount;
|
$pa->amount = $this->invoice->amount;
|
||||||
$lmt->PayableAmount = $pa;
|
$lmt->PayableAmount = $pa;
|
||||||
|
|
||||||
|
$am = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\AllowanceTotalAmount();
|
||||||
|
$am->currencyID = $this->invoice->client->currency()->code;
|
||||||
|
$am->amount = (string)$this->calc->getTotalDiscount();
|
||||||
|
$lmt->AllowanceTotalAmount = $am;
|
||||||
|
|
||||||
return $lmt;
|
return $lmt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -576,6 +603,8 @@ class Peppol extends AbstractService
|
||||||
break;
|
break;
|
||||||
case Product::PRODUCT_TYPE_REVERSE_TAX:
|
case Product::PRODUCT_TYPE_REVERSE_TAX:
|
||||||
$tax_type = 'AE';
|
$tax_type = 'AE';
|
||||||
|
case Product::PRODUCT_INTRA_COMMUNITY:
|
||||||
|
$tax_type = 'K';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -616,32 +645,43 @@ class Peppol extends AbstractService
|
||||||
$lines = [];
|
$lines = [];
|
||||||
|
|
||||||
foreach($this->invoice->line_items as $key => $item) {
|
foreach($this->invoice->line_items as $key => $item) {
|
||||||
|
|
||||||
|
$base_price_amount = (string)$this->calculateAdjustedBaseAmount($item);
|
||||||
|
|
||||||
$_item = new Item();
|
$_item = new Item();
|
||||||
$_item->Name = $item->product_key;
|
$_item->Name = $item->product_key;
|
||||||
$_item->Description = $item->notes;
|
$_item->Description = $item->notes;
|
||||||
|
|
||||||
if($item->tax_rate1 > 0)
|
|
||||||
|
$ctc = new ClassifiedTaxCategory();
|
||||||
|
$ctc->ID = new ID();
|
||||||
|
$ctc->ID->value = $this->getTaxType($item->tax_id);
|
||||||
|
$ctc->Percent = (string)$item->tax_rate1;
|
||||||
|
|
||||||
|
$ts = new TaxScheme();
|
||||||
|
$id = new ID();
|
||||||
|
$id->value = $this->standardizeTaxSchemeId($item->tax_name1);
|
||||||
|
$ts->ID = $id;
|
||||||
|
$ctc->TaxScheme = $ts;
|
||||||
|
|
||||||
|
if(floatval($item->tax_rate1) === 0.0)
|
||||||
{
|
{
|
||||||
$ctc = new ClassifiedTaxCategory();
|
$ctc->ID->value = 'K';
|
||||||
$ctc->ID = new ID();
|
|
||||||
$ctc->ID->value = $this->getTaxType($item->tax_id);
|
|
||||||
$ctc->Percent = $item->tax_rate1;
|
|
||||||
|
|
||||||
$ts = new TaxScheme();
|
$terc = new \InvoiceNinja\EInvoice\Models\Peppol\CodeType\TaxExemptionReasonCode();
|
||||||
$id = new ID();
|
$terc->value = 'VATEX-EU-IC';
|
||||||
$id->value = $this->standardizeTaxSchemeId($item->tax_name1);
|
$ctc->TaxExemptionReasonCode = $terc;
|
||||||
$ts->ID = $id;
|
$ctc->TaxExemptionReason = 'Intra-Community supply';
|
||||||
$ctc->TaxScheme = $ts;
|
|
||||||
|
|
||||||
$_item->ClassifiedTaxCategory[] = $ctc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$_item->ClassifiedTaxCategory[] = $ctc;
|
||||||
|
|
||||||
|
|
||||||
if ($item->tax_rate2 > 0) {
|
if ($item->tax_rate2 > 0) {
|
||||||
$ctc = new ClassifiedTaxCategory();
|
$ctc = new ClassifiedTaxCategory();
|
||||||
$ctc->ID = new ID();
|
$ctc->ID = new ID();
|
||||||
$ctc->ID->value = $this->getTaxType($item->tax_id);
|
$ctc->ID->value = $this->getTaxType($item->tax_id);
|
||||||
$ctc->Percent = $item->tax_rate2;
|
$ctc->Percent = (string)$item->tax_rate2;
|
||||||
|
|
||||||
$ts = new TaxScheme();
|
$ts = new TaxScheme();
|
||||||
$id = new ID();
|
$id = new ID();
|
||||||
|
|
@ -656,7 +696,7 @@ class Peppol extends AbstractService
|
||||||
$ctc = new ClassifiedTaxCategory();
|
$ctc = new ClassifiedTaxCategory();
|
||||||
$ctc->ID = new ID();
|
$ctc->ID = new ID();
|
||||||
$ctc->ID->value = $this->getTaxType($item->tax_id);
|
$ctc->ID->value = $this->getTaxType($item->tax_id);
|
||||||
$ctc->Percent = $item->tax_rate3;
|
$ctc->Percent = (string)$item->tax_rate3;
|
||||||
|
|
||||||
$ts = new TaxScheme();
|
$ts = new TaxScheme();
|
||||||
$id = new ID();
|
$id = new ID();
|
||||||
|
|
@ -689,11 +729,12 @@ class Peppol extends AbstractService
|
||||||
|
|
||||||
// Handle Price and Discounts
|
// Handle Price and Discounts
|
||||||
if ($item->discount > 0) {
|
if ($item->discount > 0) {
|
||||||
|
|
||||||
// Base Price (before discount)
|
// Base Price (before discount)
|
||||||
$basePrice = new Price();
|
$basePrice = new Price();
|
||||||
$basePriceAmount = new PriceAmount();
|
$basePriceAmount = new PriceAmount();
|
||||||
$basePriceAmount->currencyID = $this->invoice->client->currency()->code;
|
$basePriceAmount->currencyID = $this->invoice->client->currency()->code;
|
||||||
$basePriceAmount->amount = (string)($item->cost - $this->calculateDiscountAmount($item));
|
$basePriceAmount->amount = (string)$item->cost;
|
||||||
$basePrice->PriceAmount = $basePriceAmount;
|
$basePrice->PriceAmount = $basePriceAmount;
|
||||||
|
|
||||||
// Add Allowance Charge to Price
|
// Add Allowance Charge to Price
|
||||||
|
|
@ -701,29 +742,35 @@ class Peppol extends AbstractService
|
||||||
$allowanceCharge->ChargeIndicator = 'false'; // false = discount
|
$allowanceCharge->ChargeIndicator = 'false'; // false = discount
|
||||||
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
||||||
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
||||||
$allowanceCharge->Amount->amount = (string)$this->calculateDiscountAmount($item);
|
$allowanceCharge->Amount->amount = (string)number_format($this->calculateTotalItemDiscountAmount($item),2);
|
||||||
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
$this->allowance_total += $this->calculateTotalItemDiscountAmount($item);
|
||||||
$allowanceCharge->BaseAmount->currencyID = $this->invoice->client->currency()->code;
|
|
||||||
$allowanceCharge->BaseAmount->amount = (string)$item->cost;
|
|
||||||
|
|
||||||
// Add percentage if available
|
// Add percentage if available
|
||||||
if ($item->discount > 0 && !$item->is_amount_discount) {
|
if ($item->discount > 0 && !$item->is_amount_discount) {
|
||||||
|
|
||||||
|
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
||||||
|
$allowanceCharge->BaseAmount->currencyID = $this->invoice->client->currency()->code;
|
||||||
|
$allowanceCharge->BaseAmount->amount = (string)round(($item->cost * $item->quantity),2);
|
||||||
|
|
||||||
$mfn = new \InvoiceNinja\EInvoice\Models\Peppol\NumericType\MultiplierFactorNumeric();
|
$mfn = new \InvoiceNinja\EInvoice\Models\Peppol\NumericType\MultiplierFactorNumeric();
|
||||||
$mfn->value = (string) ($item->discount / 100);
|
$mfn->value = (string) round($item->discount,2);
|
||||||
$allowanceCharge->MultiplierFactorNumeric = $mfn; // Convert percentage to decimal
|
$allowanceCharge->MultiplierFactorNumeric = $mfn; // Convert percentage to decimal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// }
|
||||||
|
// Required reason
|
||||||
|
$allowanceCharge->AllowanceChargeReason = ctrans('texts.discount');
|
||||||
|
|
||||||
$basePrice->AllowanceCharge[] = $allowanceCharge;
|
|
||||||
$line->Price = $basePrice;
|
$line->Price = $basePrice;
|
||||||
|
$line->AllowanceCharge[] = $allowanceCharge;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// No discount case
|
// No discount case
|
||||||
$price = new Price();
|
$price = new Price();
|
||||||
$pa = new PriceAmount();
|
$pa = new PriceAmount();
|
||||||
$pa->currencyID = $this->invoice->client->currency()->code;
|
$pa->currencyID = $this->invoice->client->currency()->code;
|
||||||
$pa->amount = (string) ($this->costWithDiscount($item) - ($this->invoice->uses_inclusive_taxes
|
$pa->amount = (string)$item->cost;
|
||||||
? ($this->calcInclusiveLineTax($item->tax_rate1, $item->line_total) / $item->quantity)
|
|
||||||
: 0));
|
|
||||||
$price->PriceAmount = $pa;
|
$price->PriceAmount = $pa;
|
||||||
$line->Price = $price;
|
$line->Price = $price;
|
||||||
}
|
}
|
||||||
|
|
@ -735,47 +782,127 @@ class Peppol extends AbstractService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* calculateDiscountAmount
|
// * calculateDiscountAmount
|
||||||
*
|
// *
|
||||||
* Helper method to determine the discount amount to be used.
|
// * Helper method to determine the discount amount to be used.
|
||||||
*
|
// *
|
||||||
* @param mixed $item
|
// * @param mixed $item
|
||||||
* @return float
|
// * @return float
|
||||||
*/
|
// */
|
||||||
private function calculateDiscountAmount($item): float
|
// private function calculateDiscountAmount($item): float
|
||||||
{
|
// {
|
||||||
if ($item->is_amount_discount) {
|
// if ($item->is_amount_discount) {
|
||||||
return $item->discount / $item->quantity; // Per unit discount amount
|
// return $item->discount / $item->quantity; // Per unit discount amount
|
||||||
}
|
// }
|
||||||
|
|
||||||
return ($item->cost / $item->quantity) * ($item->discount / 100);
|
// return ($item->cost / $item->quantity) * ($item->discount / 100);
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
private function calculateTotalItemDiscountAmount($item):float
|
||||||
/**
|
|
||||||
* costWithDiscount
|
|
||||||
*
|
|
||||||
* Helper method to determine the cost INCLUDING discount
|
|
||||||
*
|
|
||||||
* @param mixed $item
|
|
||||||
* @return float
|
|
||||||
*/
|
|
||||||
private function costWithDiscount($item): float
|
|
||||||
{
|
{
|
||||||
$cost = $item->cost;
|
|
||||||
|
if ($item->is_amount_discount) {
|
||||||
if ($item->discount != 0) {
|
return $item->discount;
|
||||||
if ($this->invoice->is_amount_discount) {
|
|
||||||
$cost -= $item->discount / $item->quantity;
|
|
||||||
} else {
|
|
||||||
$cost -= $cost * $item->discount / 100;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $cost;
|
return ($item->cost) * ($item->discount / 100);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * costWithDiscount
|
||||||
|
// *
|
||||||
|
// * Helper method to determine the cost INCLUDING discount
|
||||||
|
// *
|
||||||
|
// * @param mixed $item
|
||||||
|
// * @return float
|
||||||
|
// */
|
||||||
|
// private function costWithDiscount($item): float
|
||||||
|
// {
|
||||||
|
// $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;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* calculateTaxMap
|
||||||
|
*
|
||||||
|
* Generates a standard tax_map entry for a given $amount
|
||||||
|
*
|
||||||
|
* Iterates through all of the globalTaxCategories found in the document
|
||||||
|
*
|
||||||
|
* @param float $amount
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
private function calculateTaxMap($amount): self
|
||||||
|
{
|
||||||
|
|
||||||
|
foreach($this->globalTaxCategories as $tc)
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->tax_map[] = [
|
||||||
|
'taxableAmount' => $amount,
|
||||||
|
'taxAmount' => $amount * ($tc->Percent/100),
|
||||||
|
'percentage' => $tc->Percent,
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getAllUsedTaxes
|
||||||
|
*
|
||||||
|
* Build a full tax category property based on all
|
||||||
|
* of the item taxes that have been applied to the invoice.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
private function getAllUsedTaxes(): self
|
||||||
|
{
|
||||||
|
$this->globalTaxCategories = [];
|
||||||
|
|
||||||
|
collect($this->invoice->line_items)
|
||||||
|
->flatMap(function ($item) {
|
||||||
|
return collect([1, 2, 3])
|
||||||
|
->map(fn ($i) => [
|
||||||
|
'name' => $item->{"tax_name{$i}"} ?? '',
|
||||||
|
'percentage' => $item->{"tax_rate{$i}"} ?? 0,
|
||||||
|
'scheme' => $this->getTaxType($item->tax_id),
|
||||||
|
])
|
||||||
|
->filter(fn ($tax) => strlen($tax['name']) > 1);
|
||||||
|
})
|
||||||
|
->unique(fn ($tax) => $tax['percentage'] . '_' . $tax['name'])
|
||||||
|
->values()
|
||||||
|
->each(function ($tax){
|
||||||
|
|
||||||
|
$taxCategory = new \InvoiceNinja\EInvoice\Models\Peppol\TaxCategoryType\TaxCategory();
|
||||||
|
$taxCategory->ID = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\ID();
|
||||||
|
$taxCategory->ID->value = $tax['scheme'];
|
||||||
|
$taxCategory->Percent = (string)$tax['percentage'];
|
||||||
|
$taxScheme = new \InvoiceNinja\EInvoice\Models\Peppol\TaxSchemeType\TaxScheme();
|
||||||
|
$taxScheme->ID = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\ID();
|
||||||
|
$taxScheme->ID->value = $this->standardizeTaxSchemeId($tax['name']);
|
||||||
|
$taxCategory->TaxScheme = $taxScheme;
|
||||||
|
|
||||||
|
$this->globalTaxCategories[] = $taxCategory;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* getItemTaxes
|
* getItemTaxes
|
||||||
*
|
*
|
||||||
|
|
@ -788,18 +915,20 @@ class Peppol extends AbstractService
|
||||||
private function getItemTaxes(object $item): array
|
private function getItemTaxes(object $item): array
|
||||||
{
|
{
|
||||||
$item_taxes = [];
|
$item_taxes = [];
|
||||||
|
|
||||||
|
$adjusted_base_amount = $this->calculateAdjustedBaseAmount($item);
|
||||||
|
|
||||||
if(strlen($item->tax_name1 ?? '') > 1) {
|
// if(strlen($item->tax_name1 ?? '') > 1) {
|
||||||
|
|
||||||
$tax_amount = new TaxAmount();
|
$tax_amount = new TaxAmount();
|
||||||
$tax_amount->currencyID = $this->invoice->client->currency()->code;
|
$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_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiveLineTax($item->tax_rate1, $adjusted_base_amount) : $this->calcAmountLineTax($item->tax_rate1, $adjusted_base_amount);
|
||||||
$tax_subtotal = new TaxSubtotal();
|
$tax_subtotal = new TaxSubtotal();
|
||||||
$tax_subtotal->TaxAmount = $tax_amount;
|
$tax_subtotal->TaxAmount = $tax_amount;
|
||||||
|
|
||||||
$taxable_amount = new TaxableAmount();
|
$taxable_amount = new TaxableAmount();
|
||||||
$taxable_amount->currencyID = $this->invoice->client->currency()->code;
|
$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;
|
$taxable_amount->amount = $this->invoice->uses_inclusive_taxes ? $adjusted_base_amount - $tax_amount->amount : $adjusted_base_amount;
|
||||||
$tax_subtotal->TaxableAmount = $taxable_amount;
|
$tax_subtotal->TaxableAmount = $taxable_amount;
|
||||||
|
|
||||||
$tc = new TaxCategory();
|
$tc = new TaxCategory();
|
||||||
|
|
@ -807,8 +936,11 @@ class Peppol extends AbstractService
|
||||||
$id = new ID();
|
$id = new ID();
|
||||||
$id->value = $this->getTaxType($item->tax_id);
|
$id->value = $this->getTaxType($item->tax_id);
|
||||||
|
|
||||||
|
if(floatval($item->tax_rate1) === 0.0)
|
||||||
|
$id->value = 'K';
|
||||||
|
|
||||||
$tc->ID = $id;
|
$tc->ID = $id;
|
||||||
$tc->Percent = $item->tax_rate1;
|
$tc->Percent = (string)$item->tax_rate1;
|
||||||
$ts = new TaxScheme();
|
$ts = new TaxScheme();
|
||||||
|
|
||||||
$id = new ID();
|
$id = new ID();
|
||||||
|
|
@ -833,7 +965,7 @@ class Peppol extends AbstractService
|
||||||
|
|
||||||
$item_taxes[] = $tax_total;
|
$item_taxes[] = $tax_total;
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
if(strlen($item->tax_name2 ?? '') > 1) {
|
if(strlen($item->tax_name2 ?? '') > 1) {
|
||||||
|
|
@ -855,7 +987,7 @@ class Peppol extends AbstractService
|
||||||
$id->value = $this->getTaxType($item->tax_id);
|
$id->value = $this->getTaxType($item->tax_id);
|
||||||
|
|
||||||
$tc->ID = $id;
|
$tc->ID = $id;
|
||||||
$tc->Percent = $item->tax_rate2;
|
$tc->Percent = (string)$item->tax_rate2;
|
||||||
$ts = new TaxScheme();
|
$ts = new TaxScheme();
|
||||||
|
|
||||||
$id = new ID();
|
$id = new ID();
|
||||||
|
|
@ -904,7 +1036,7 @@ class Peppol extends AbstractService
|
||||||
$id->value = $this->getTaxType($item->tax_id);
|
$id->value = $this->getTaxType($item->tax_id);
|
||||||
|
|
||||||
$tc->ID = $id;
|
$tc->ID = $id;
|
||||||
$tc->Percent = $item->tax_rate3;
|
$tc->Percent = (string)$item->tax_rate3;
|
||||||
$ts = new TaxScheme();
|
$ts = new TaxScheme();
|
||||||
|
|
||||||
$id = new ID();
|
$id = new ID();
|
||||||
|
|
@ -1246,6 +1378,11 @@ class Peppol extends AbstractService
|
||||||
// Required: TaxCategory ID (BT-118)
|
// Required: TaxCategory ID (BT-118)
|
||||||
$category_id = new ID();
|
$category_id = new ID();
|
||||||
$category_id->value = 'S'; // Standard rate
|
$category_id->value = 'S'; // Standard rate
|
||||||
|
|
||||||
|
if(floatval($grouped_tax['taxAmount']) === 0.0)
|
||||||
|
$category_id->value = 'K'; // Exempt
|
||||||
|
|
||||||
|
|
||||||
$tax_category->ID = $category_id;
|
$tax_category->ID = $category_id;
|
||||||
|
|
||||||
// Required: TaxCategory Rate (BT-119)
|
// Required: TaxCategory Rate (BT-119)
|
||||||
|
|
@ -1342,4 +1479,72 @@ class Peppol extends AbstractService
|
||||||
return '0037';
|
return '0037';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* calculateAdjustedBaseAmount
|
||||||
|
*
|
||||||
|
* Calculates the adjusted base amount for a line item considering invoice-level discounts
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private function calculateAdjustedBaseAmount(
|
||||||
|
object $line_item,
|
||||||
|
// float $invoice_discount,
|
||||||
|
// bool $is_percentage,
|
||||||
|
// array $all_line_items,
|
||||||
|
// float $allowance_charges
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// 1. Calculate total invoice amount before invoice-level discount
|
||||||
|
$total_amount = 0;
|
||||||
|
foreach ($this->invoice->line_items as $item) {
|
||||||
|
$line_total = $item->quantity * $item->cost;
|
||||||
|
|
||||||
|
// Apply line-level discount if exists
|
||||||
|
if ($item->discount > 0) {
|
||||||
|
if ($item->is_amount_discount) {
|
||||||
|
$line_total -= $item->discount;
|
||||||
|
} else {
|
||||||
|
$line_total -= ($line_total * ($item->discount / 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$total_amount += $line_total;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Add any additional charges or subtract additional allowances
|
||||||
|
$total_amount += ($this->invoice->custom_surcharge1 + $this->invoice->custom_surcharge2 +$this->invoice->custom_surcharge3 + $this->invoice->custom_surcharge4);
|
||||||
|
|
||||||
|
// 3. Calculate this line item's proportion of total
|
||||||
|
$line_total = $line_item->quantity * $line_item->cost;
|
||||||
|
|
||||||
|
// Apply line-level discount if exists
|
||||||
|
if ($line_item->discount > 0) {
|
||||||
|
if ($line_item->is_amount_discount) {
|
||||||
|
$line_total -= $line_item->discount;
|
||||||
|
} else {
|
||||||
|
$line_total -= ($line_total * ($line_item->discount / 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$proportion = $line_total / $total_amount;
|
||||||
|
|
||||||
|
// 4. Calculate invoice-level discount amount for this line
|
||||||
|
$line_discount = 0;
|
||||||
|
if (!$this->invoice->is_amount_discount) {
|
||||||
|
$line_discount = $line_total * ($this->invoice->discount / 100);
|
||||||
|
} else {
|
||||||
|
$line_discount = $this->invoice->discount * $proportion;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Return adjusted base amount
|
||||||
|
return (string)round($line_total - $line_discount, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,258 @@ class PeppolTest extends TestCase
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDeInvoiceIntraCommunitySupply()
|
||||||
|
{
|
||||||
|
|
||||||
|
$settings = CompanySettings::defaults();
|
||||||
|
$settings->address1 = 'Dudweilerstr. 34b';
|
||||||
|
$settings->city = 'Ost Alessa';
|
||||||
|
$settings->state = 'Bayern';
|
||||||
|
$settings->postal_code = '98060';
|
||||||
|
$settings->vat_number = 'DE923356489';
|
||||||
|
$settings->country_id = '276';
|
||||||
|
$settings->currency_id = '3';
|
||||||
|
|
||||||
|
$einvoice = new \InvoiceNinja\EInvoice\Models\Peppol\Invoice();
|
||||||
|
|
||||||
|
$fib = new FinancialInstitutionBranch();
|
||||||
|
$fib->ID = "DEUTDEMMXXX"; //BIC
|
||||||
|
// $fib->Name = 'Deutsche Bank';
|
||||||
|
|
||||||
|
$pfa = new PayeeFinancialAccount();
|
||||||
|
$pfa->ID = 'DE89370400440532013000';
|
||||||
|
$pfa->Name = 'PFA-NAME';
|
||||||
|
// $pfa->AliasName = 'PFA-Alias';
|
||||||
|
$pfa->AccountTypeCode = 'CHECKING';
|
||||||
|
$pfa->AccountFormatCode = 'IBAN';
|
||||||
|
$pfa->CurrencyCode = 'EUR';
|
||||||
|
$pfa->FinancialInstitutionBranch = $fib;
|
||||||
|
|
||||||
|
$pm = new PaymentMeans();
|
||||||
|
$pm->PayeeFinancialAccount = $pfa;
|
||||||
|
$einvoice->PaymentMeans[] = $pm;
|
||||||
|
|
||||||
|
$stub = new \stdClass();
|
||||||
|
$stub->Invoice = $einvoice;
|
||||||
|
|
||||||
|
$company = Company::factory()->create([
|
||||||
|
'account_id' => $this->account->id,
|
||||||
|
'settings' => $settings,
|
||||||
|
'e_invoice' => $stub,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cu = CompanyUserFactory::create($this->user->id, $company->id, $this->account->id);
|
||||||
|
$cu->is_owner = true;
|
||||||
|
$cu->is_admin = true;
|
||||||
|
$cu->is_locked = false;
|
||||||
|
$cu->save();
|
||||||
|
|
||||||
|
$client_settings = ClientSettings::defaults();
|
||||||
|
$client_settings->currency_id = '3';
|
||||||
|
|
||||||
|
$client = Client::factory()->create([
|
||||||
|
'company_id' => $company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'name' => 'German Client Name',
|
||||||
|
'address1' => 'Kinderhausen 96b',
|
||||||
|
'address2' => 'Apt. 842',
|
||||||
|
'city' => 'Süd Jessestadt',
|
||||||
|
'state' => 'Bayern',
|
||||||
|
'postal_code' => '33323',
|
||||||
|
'country_id' => 276,
|
||||||
|
'routing_id' => 'ABC1234',
|
||||||
|
'settings' => $client_settings,
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
$item = new InvoiceItem();
|
||||||
|
$item->product_key = "Product Key";
|
||||||
|
$item->notes = "Product Description";
|
||||||
|
$item->cost = 10;
|
||||||
|
$item->quantity = 10;
|
||||||
|
$item->discount = 0;
|
||||||
|
$item->is_amount_discount = false;
|
||||||
|
$item->tax_rate1 = 0;
|
||||||
|
$item->tax_name1 = '';
|
||||||
|
|
||||||
|
$invoice = Invoice::factory()->create([
|
||||||
|
'company_id' => $company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'discount' => 0,
|
||||||
|
'uses_inclusive_taxes' => false,
|
||||||
|
'status_id' => 1,
|
||||||
|
'tax_rate1' => 0,
|
||||||
|
'tax_name1' => '',
|
||||||
|
'tax_rate2' => 0,
|
||||||
|
'tax_rate3' => 0,
|
||||||
|
'tax_name2' => '',
|
||||||
|
'tax_name3' => '',
|
||||||
|
'line_items' => [$item],
|
||||||
|
'number' => 'DE-'.rand(1000, 100000),
|
||||||
|
'date' => now()->format('Y-m-d'),
|
||||||
|
'is_amount_discount' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$invoice = $invoice->calc()->getInvoice();
|
||||||
|
$invoice->service()->markSent()->save();
|
||||||
|
|
||||||
|
$this->assertEquals(100, $invoice->amount);
|
||||||
|
|
||||||
|
$peppol = new Peppol($invoice);
|
||||||
|
$peppol->setInvoiceDefaults();
|
||||||
|
$peppol->run();
|
||||||
|
|
||||||
|
|
||||||
|
nlog($peppol->toXml());
|
||||||
|
|
||||||
|
// nlog($peppol->toObject());
|
||||||
|
|
||||||
|
$de_invoice = $peppol->getInvoice();
|
||||||
|
|
||||||
|
$this->assertNotNull($de_invoice);
|
||||||
|
|
||||||
|
$e = new EInvoice();
|
||||||
|
$xml = $e->encode($de_invoice, 'xml');
|
||||||
|
$this->assertNotNull($xml);
|
||||||
|
|
||||||
|
|
||||||
|
$errors = $e->validate($de_invoice);
|
||||||
|
|
||||||
|
if(count($errors) > 0) {
|
||||||
|
nlog($errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertCount(0, $errors);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeInvoiceSingleInvoiceSurcharge()
|
||||||
|
{
|
||||||
|
|
||||||
|
$settings = CompanySettings::defaults();
|
||||||
|
$settings->address1 = 'Dudweilerstr. 34b';
|
||||||
|
$settings->city = 'Ost Alessa';
|
||||||
|
$settings->state = 'Bayern';
|
||||||
|
$settings->postal_code = '98060';
|
||||||
|
$settings->vat_number = 'DE923356489';
|
||||||
|
$settings->country_id = '276';
|
||||||
|
$settings->currency_id = '3';
|
||||||
|
|
||||||
|
$einvoice = new \InvoiceNinja\EInvoice\Models\Peppol\Invoice();
|
||||||
|
|
||||||
|
$fib = new FinancialInstitutionBranch();
|
||||||
|
$fib->ID = "DEUTDEMMXXX"; //BIC
|
||||||
|
// $fib->Name = 'Deutsche Bank';
|
||||||
|
|
||||||
|
$pfa = new PayeeFinancialAccount();
|
||||||
|
$pfa->ID = 'DE89370400440532013000';
|
||||||
|
$pfa->Name = 'PFA-NAME';
|
||||||
|
// $pfa->AliasName = 'PFA-Alias';
|
||||||
|
$pfa->AccountTypeCode = 'CHECKING';
|
||||||
|
$pfa->AccountFormatCode = 'IBAN';
|
||||||
|
$pfa->CurrencyCode = 'EUR';
|
||||||
|
$pfa->FinancialInstitutionBranch = $fib;
|
||||||
|
|
||||||
|
$pm = new PaymentMeans();
|
||||||
|
$pm->PayeeFinancialAccount = $pfa;
|
||||||
|
$einvoice->PaymentMeans[] = $pm;
|
||||||
|
|
||||||
|
$stub = new \stdClass();
|
||||||
|
$stub->Invoice = $einvoice;
|
||||||
|
|
||||||
|
$company = Company::factory()->create([
|
||||||
|
'account_id' => $this->account->id,
|
||||||
|
'settings' => $settings,
|
||||||
|
'e_invoice' => $stub,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cu = CompanyUserFactory::create($this->user->id, $company->id, $this->account->id);
|
||||||
|
$cu->is_owner = true;
|
||||||
|
$cu->is_admin = true;
|
||||||
|
$cu->is_locked = false;
|
||||||
|
$cu->save();
|
||||||
|
|
||||||
|
$client_settings = ClientSettings::defaults();
|
||||||
|
$client_settings->currency_id = '3';
|
||||||
|
|
||||||
|
$client = Client::factory()->create([
|
||||||
|
'company_id' => $company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'name' => 'German Client Name',
|
||||||
|
'address1' => 'Kinderhausen 96b',
|
||||||
|
'address2' => 'Apt. 842',
|
||||||
|
'city' => 'Süd Jessestadt',
|
||||||
|
'state' => 'Bayern',
|
||||||
|
'postal_code' => '33323',
|
||||||
|
'country_id' => 276,
|
||||||
|
'routing_id' => 'ABC1234',
|
||||||
|
'settings' => $client_settings,
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
$item = new InvoiceItem();
|
||||||
|
$item->product_key = "Product Key";
|
||||||
|
$item->notes = "Product Description";
|
||||||
|
$item->cost = 10;
|
||||||
|
$item->quantity = 10;
|
||||||
|
$item->discount = 0;
|
||||||
|
$item->is_amount_discount = false;
|
||||||
|
$item->tax_rate1 = 19;
|
||||||
|
$item->tax_name1 = 'mwst';
|
||||||
|
|
||||||
|
$invoice = Invoice::factory()->create([
|
||||||
|
'company_id' => $company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'discount' => 0,
|
||||||
|
'uses_inclusive_taxes' => false,
|
||||||
|
'status_id' => 1,
|
||||||
|
'tax_rate1' => 0,
|
||||||
|
'tax_name1' => '',
|
||||||
|
'tax_rate2' => 0,
|
||||||
|
'tax_rate3' => 0,
|
||||||
|
'tax_name2' => '',
|
||||||
|
'tax_name3' => '',
|
||||||
|
'line_items' => [$item],
|
||||||
|
'number' => 'DE-'.rand(1000, 100000),
|
||||||
|
'date' => now()->format('Y-m-d'),
|
||||||
|
'is_amount_discount' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$invoice->custom_surcharge1 = 10;
|
||||||
|
$invoice = $invoice->calc()->getInvoice();
|
||||||
|
$invoice->service()->markSent()->save();
|
||||||
|
|
||||||
|
$this->assertEquals(130.90, $invoice->amount);
|
||||||
|
|
||||||
|
$peppol = new Peppol($invoice);
|
||||||
|
$peppol->setInvoiceDefaults();
|
||||||
|
$peppol->run();
|
||||||
|
|
||||||
|
|
||||||
|
// $peppol->toJson()->toXml();
|
||||||
|
|
||||||
|
// nlog($peppol->toObject());
|
||||||
|
|
||||||
|
$de_invoice = $peppol->getInvoice();
|
||||||
|
|
||||||
|
$this->assertNotNull($de_invoice);
|
||||||
|
|
||||||
|
$e = new EInvoice();
|
||||||
|
$xml = $e->encode($de_invoice, 'xml');
|
||||||
|
$this->assertNotNull($xml);
|
||||||
|
|
||||||
|
|
||||||
|
$errors = $e->validate($de_invoice);
|
||||||
|
|
||||||
|
if(count($errors) > 0) {
|
||||||
|
nlog($errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertCount(0, $errors);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public function testDeInvoicePercentDiscounts()
|
public function testDeInvoicePercentDiscounts()
|
||||||
{
|
{
|
||||||
|
|
@ -159,6 +411,7 @@ class PeppolTest extends TestCase
|
||||||
$peppol->setInvoiceDefaults();
|
$peppol->setInvoiceDefaults();
|
||||||
$peppol->run();
|
$peppol->run();
|
||||||
|
|
||||||
|
|
||||||
// $peppol->toJson()->toXml();
|
// $peppol->toJson()->toXml();
|
||||||
|
|
||||||
// nlog($peppol->toObject());
|
// nlog($peppol->toObject());
|
||||||
|
|
@ -182,6 +435,455 @@ class PeppolTest extends TestCase
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDeInvoiceLevelAndItemLevelPercentageDiscount()
|
||||||
|
{
|
||||||
|
|
||||||
|
$settings = CompanySettings::defaults();
|
||||||
|
$settings->address1 = 'Dudweilerstr. 34b';
|
||||||
|
$settings->city = 'Ost Alessa';
|
||||||
|
$settings->state = 'Bayern';
|
||||||
|
$settings->postal_code = '98060';
|
||||||
|
$settings->vat_number = 'DE923356489';
|
||||||
|
$settings->id_number = '991-00110-12';
|
||||||
|
$settings->country_id = '276';
|
||||||
|
$settings->currency_id = '3';
|
||||||
|
|
||||||
|
$einvoice = new \InvoiceNinja\EInvoice\Models\Peppol\Invoice();
|
||||||
|
|
||||||
|
$fib = new FinancialInstitutionBranch();
|
||||||
|
$fib->ID = "DEUTDEMMXXX"; //BIC
|
||||||
|
// $fib->Name = 'Deutsche Bank';
|
||||||
|
|
||||||
|
$pfa = new PayeeFinancialAccount();
|
||||||
|
$id = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\ID();
|
||||||
|
$id->value = 'DE89370400440532013000';
|
||||||
|
$pfa->ID = $id;
|
||||||
|
$pfa->Name = 'PFA-NAME';
|
||||||
|
|
||||||
|
$pfa->FinancialInstitutionBranch = $fib;
|
||||||
|
|
||||||
|
$pm = new PaymentMeans();
|
||||||
|
$pm->PayeeFinancialAccount = $pfa;
|
||||||
|
|
||||||
|
$pmc = new \InvoiceNinja\EInvoice\Models\Peppol\CodeType\PaymentMeansCode();
|
||||||
|
$pmc->value = '30';
|
||||||
|
|
||||||
|
$pm->PaymentMeansCode = $pmc;
|
||||||
|
|
||||||
|
$einvoice->PaymentMeans[] = $pm;
|
||||||
|
|
||||||
|
$stub = new \stdClass();
|
||||||
|
$stub->Invoice = $einvoice;
|
||||||
|
|
||||||
|
$company = Company::factory()->create([
|
||||||
|
'account_id' => $this->account->id,
|
||||||
|
'settings' => $settings,
|
||||||
|
'e_invoice' => $stub,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cu = CompanyUserFactory::create($this->user->id, $company->id, $this->account->id);
|
||||||
|
$cu->is_owner = true;
|
||||||
|
$cu->is_admin = true;
|
||||||
|
$cu->is_locked = false;
|
||||||
|
$cu->save();
|
||||||
|
|
||||||
|
$client_settings = ClientSettings::defaults();
|
||||||
|
$client_settings->currency_id = '3';
|
||||||
|
|
||||||
|
$client = Client::factory()->create([
|
||||||
|
'company_id' => $company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'name' => 'German Client Name',
|
||||||
|
'address1' => 'Kinderhausen 96b',
|
||||||
|
'address2' => 'Apt. 842',
|
||||||
|
'city' => 'Süd Jessestadt',
|
||||||
|
'state' => 'Bayern',
|
||||||
|
'postal_code' => '33323',
|
||||||
|
'country_id' => 276,
|
||||||
|
'routing_id' => 'ABC1234',
|
||||||
|
'settings' => $client_settings,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$item = new InvoiceItem();
|
||||||
|
$item->product_key = "Product Key";
|
||||||
|
$item->notes = "Product Description";
|
||||||
|
$item->cost = 100;
|
||||||
|
$item->quantity = 1;
|
||||||
|
$item->discount = 10;
|
||||||
|
$item->is_amount_discount = false;
|
||||||
|
$item->tax_rate1 = 19;
|
||||||
|
$item->tax_name1 = 'mwst';
|
||||||
|
|
||||||
|
$invoice = Invoice::factory()->create([
|
||||||
|
'company_id' => $company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'discount' => 10,
|
||||||
|
'uses_inclusive_taxes' => false,
|
||||||
|
'status_id' => 1,
|
||||||
|
'tax_rate1' => 0,
|
||||||
|
'tax_name1' => '',
|
||||||
|
'tax_rate2' => 0,
|
||||||
|
'tax_rate3' => 0,
|
||||||
|
'tax_name2' => '',
|
||||||
|
'tax_name3' => '',
|
||||||
|
'line_items' => [$item],
|
||||||
|
'number' => 'DE-'.rand(1000, 100000),
|
||||||
|
'date' => now()->format('Y-m-d'),
|
||||||
|
'due_date' => now()->addDays(30)->format('Y-m-d'),
|
||||||
|
'is_amount_discount' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$invoice = $invoice->calc()->getInvoice();
|
||||||
|
$invoice->service()->markSent()->save();
|
||||||
|
|
||||||
|
$this->assertEquals(96.39, $invoice->amount);
|
||||||
|
|
||||||
|
$peppol = new Peppol($invoice);
|
||||||
|
$peppol->setInvoiceDefaults();
|
||||||
|
$peppol->run();
|
||||||
|
|
||||||
|
// nlog($peppol->toXml());
|
||||||
|
|
||||||
|
$de_invoice = $peppol->getInvoice();
|
||||||
|
|
||||||
|
$this->assertNotNull($de_invoice);
|
||||||
|
|
||||||
|
$e = new EInvoice();
|
||||||
|
$xml = $e->encode($de_invoice, 'xml');
|
||||||
|
|
||||||
|
|
||||||
|
$this->assertNotNull($xml);
|
||||||
|
|
||||||
|
$errors = $e->validate($de_invoice);
|
||||||
|
|
||||||
|
if(count($errors) > 0) {
|
||||||
|
nlog($xml);
|
||||||
|
nlog($errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertCount(0, $errors);
|
||||||
|
|
||||||
|
$xml = $peppol->toXml();
|
||||||
|
|
||||||
|
try{
|
||||||
|
$processor = new \Saxon\SaxonProcessor();
|
||||||
|
}
|
||||||
|
catch(\Throwable $e){
|
||||||
|
$this->markTestSkipped('saxon not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = new XsltDocumentValidator($xml);
|
||||||
|
$validator->validate();
|
||||||
|
|
||||||
|
if(count($validator->getErrors()) >0){
|
||||||
|
nlog($xml);
|
||||||
|
nlog($validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertCount(0, $validator->getErrors());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function testDeInvoiceLevelPercentageDiscount()
|
||||||
|
{
|
||||||
|
|
||||||
|
$settings = CompanySettings::defaults();
|
||||||
|
$settings->address1 = 'Dudweilerstr. 34b';
|
||||||
|
$settings->city = 'Ost Alessa';
|
||||||
|
$settings->state = 'Bayern';
|
||||||
|
$settings->postal_code = '98060';
|
||||||
|
$settings->vat_number = 'DE923356489';
|
||||||
|
$settings->id_number = '991-00110-12';
|
||||||
|
$settings->country_id = '276';
|
||||||
|
$settings->currency_id = '3';
|
||||||
|
|
||||||
|
$einvoice = new \InvoiceNinja\EInvoice\Models\Peppol\Invoice();
|
||||||
|
|
||||||
|
$fib = new FinancialInstitutionBranch();
|
||||||
|
$fib->ID = "DEUTDEMMXXX"; //BIC
|
||||||
|
// $fib->Name = 'Deutsche Bank';
|
||||||
|
|
||||||
|
$pfa = new PayeeFinancialAccount();
|
||||||
|
$id = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\ID();
|
||||||
|
$id->value = 'DE89370400440532013000';
|
||||||
|
$pfa->ID = $id;
|
||||||
|
$pfa->Name = 'PFA-NAME';
|
||||||
|
|
||||||
|
$pfa->FinancialInstitutionBranch = $fib;
|
||||||
|
|
||||||
|
$pm = new PaymentMeans();
|
||||||
|
$pm->PayeeFinancialAccount = $pfa;
|
||||||
|
|
||||||
|
$pmc = new \InvoiceNinja\EInvoice\Models\Peppol\CodeType\PaymentMeansCode();
|
||||||
|
$pmc->value = '30';
|
||||||
|
|
||||||
|
$pm->PaymentMeansCode = $pmc;
|
||||||
|
|
||||||
|
$einvoice->PaymentMeans[] = $pm;
|
||||||
|
|
||||||
|
$stub = new \stdClass();
|
||||||
|
$stub->Invoice = $einvoice;
|
||||||
|
|
||||||
|
$company = Company::factory()->create([
|
||||||
|
'account_id' => $this->account->id,
|
||||||
|
'settings' => $settings,
|
||||||
|
'e_invoice' => $stub,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cu = CompanyUserFactory::create($this->user->id, $company->id, $this->account->id);
|
||||||
|
$cu->is_owner = true;
|
||||||
|
$cu->is_admin = true;
|
||||||
|
$cu->is_locked = false;
|
||||||
|
$cu->save();
|
||||||
|
|
||||||
|
$client_settings = ClientSettings::defaults();
|
||||||
|
$client_settings->currency_id = '3';
|
||||||
|
|
||||||
|
$client = Client::factory()->create([
|
||||||
|
'company_id' => $company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'name' => 'German Client Name',
|
||||||
|
'address1' => 'Kinderhausen 96b',
|
||||||
|
'address2' => 'Apt. 842',
|
||||||
|
'city' => 'Süd Jessestadt',
|
||||||
|
'state' => 'Bayern',
|
||||||
|
'postal_code' => '33323',
|
||||||
|
'country_id' => 276,
|
||||||
|
'routing_id' => 'ABC1234',
|
||||||
|
'settings' => $client_settings,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$item = new InvoiceItem();
|
||||||
|
$item->product_key = "Product Key";
|
||||||
|
$item->notes = "Product Description";
|
||||||
|
$item->cost = 100;
|
||||||
|
$item->quantity = 1;
|
||||||
|
$item->discount = 0;
|
||||||
|
$item->is_amount_discount = false;
|
||||||
|
$item->tax_rate1 = 19;
|
||||||
|
$item->tax_name1 = 'mwst';
|
||||||
|
|
||||||
|
$invoice = Invoice::factory()->create([
|
||||||
|
'company_id' => $company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'discount' => 10,
|
||||||
|
'uses_inclusive_taxes' => false,
|
||||||
|
'status_id' => 1,
|
||||||
|
'tax_rate1' => 0,
|
||||||
|
'tax_name1' => '',
|
||||||
|
'tax_rate2' => 0,
|
||||||
|
'tax_rate3' => 0,
|
||||||
|
'tax_name2' => '',
|
||||||
|
'tax_name3' => '',
|
||||||
|
'line_items' => [$item],
|
||||||
|
'number' => 'DE-'.rand(1000, 100000),
|
||||||
|
'date' => now()->format('Y-m-d'),
|
||||||
|
'due_date' => now()->addDays(30)->format('Y-m-d'),
|
||||||
|
'is_amount_discount' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$invoice = $invoice->calc()->getInvoice();
|
||||||
|
$invoice->service()->markSent()->save();
|
||||||
|
|
||||||
|
$this->assertEquals(107.10, $invoice->amount);
|
||||||
|
|
||||||
|
$peppol = new Peppol($invoice);
|
||||||
|
$peppol->setInvoiceDefaults();
|
||||||
|
$peppol->run();
|
||||||
|
|
||||||
|
// nlog($peppol->toXml());
|
||||||
|
|
||||||
|
$de_invoice = $peppol->getInvoice();
|
||||||
|
|
||||||
|
$this->assertNotNull($de_invoice);
|
||||||
|
|
||||||
|
$e = new EInvoice();
|
||||||
|
$xml = $e->encode($de_invoice, 'xml');
|
||||||
|
|
||||||
|
|
||||||
|
$this->assertNotNull($xml);
|
||||||
|
|
||||||
|
$errors = $e->validate($de_invoice);
|
||||||
|
|
||||||
|
if(count($errors) > 0) {
|
||||||
|
nlog($xml);
|
||||||
|
nlog($errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertCount(0, $errors);
|
||||||
|
|
||||||
|
$xml = $peppol->toXml();
|
||||||
|
|
||||||
|
try{
|
||||||
|
$processor = new \Saxon\SaxonProcessor();
|
||||||
|
}
|
||||||
|
catch(\Throwable $e){
|
||||||
|
$this->markTestSkipped('saxon not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = new XsltDocumentValidator($xml);
|
||||||
|
$validator->validate();
|
||||||
|
|
||||||
|
if(count($validator->getErrors()) >0){
|
||||||
|
nlog($xml);
|
||||||
|
nlog($validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertCount(0, $validator->getErrors());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeInvoiceAmountAndItemAmountDiscounts()
|
||||||
|
{
|
||||||
|
|
||||||
|
$settings = CompanySettings::defaults();
|
||||||
|
$settings->address1 = 'Dudweilerstr. 34b';
|
||||||
|
$settings->city = 'Ost Alessa';
|
||||||
|
$settings->state = 'Bayern';
|
||||||
|
$settings->postal_code = '98060';
|
||||||
|
$settings->vat_number = 'DE923356489';
|
||||||
|
$settings->id_number = '991-00110-12';
|
||||||
|
$settings->country_id = '276';
|
||||||
|
$settings->currency_id = '3';
|
||||||
|
|
||||||
|
$einvoice = new \InvoiceNinja\EInvoice\Models\Peppol\Invoice();
|
||||||
|
|
||||||
|
$fib = new FinancialInstitutionBranch();
|
||||||
|
$fib->ID = "DEUTDEMMXXX"; //BIC
|
||||||
|
// $fib->Name = 'Deutsche Bank';
|
||||||
|
|
||||||
|
$pfa = new PayeeFinancialAccount();
|
||||||
|
$id = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\ID();
|
||||||
|
$id->value = 'DE89370400440532013000';
|
||||||
|
$pfa->ID = $id;
|
||||||
|
$pfa->Name = 'PFA-NAME';
|
||||||
|
|
||||||
|
$pfa->FinancialInstitutionBranch = $fib;
|
||||||
|
|
||||||
|
$pm = new PaymentMeans();
|
||||||
|
$pm->PayeeFinancialAccount = $pfa;
|
||||||
|
|
||||||
|
$pmc = new \InvoiceNinja\EInvoice\Models\Peppol\CodeType\PaymentMeansCode();
|
||||||
|
$pmc->value = '30';
|
||||||
|
|
||||||
|
$pm->PaymentMeansCode = $pmc;
|
||||||
|
|
||||||
|
$einvoice->PaymentMeans[] = $pm;
|
||||||
|
|
||||||
|
$stub = new \stdClass();
|
||||||
|
$stub->Invoice = $einvoice;
|
||||||
|
|
||||||
|
$company = Company::factory()->create([
|
||||||
|
'account_id' => $this->account->id,
|
||||||
|
'settings' => $settings,
|
||||||
|
'e_invoice' => $stub,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cu = CompanyUserFactory::create($this->user->id, $company->id, $this->account->id);
|
||||||
|
$cu->is_owner = true;
|
||||||
|
$cu->is_admin = true;
|
||||||
|
$cu->is_locked = false;
|
||||||
|
$cu->save();
|
||||||
|
|
||||||
|
$client_settings = ClientSettings::defaults();
|
||||||
|
$client_settings->currency_id = '3';
|
||||||
|
|
||||||
|
$client = Client::factory()->create([
|
||||||
|
'company_id' => $company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'name' => 'German Client Name',
|
||||||
|
'address1' => 'Kinderhausen 96b',
|
||||||
|
'address2' => 'Apt. 842',
|
||||||
|
'city' => 'Süd Jessestadt',
|
||||||
|
'state' => 'Bayern',
|
||||||
|
'postal_code' => '33323',
|
||||||
|
'country_id' => 276,
|
||||||
|
'routing_id' => 'ABC1234',
|
||||||
|
'settings' => $client_settings,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$item = new InvoiceItem();
|
||||||
|
$item->product_key = "Product Key";
|
||||||
|
$item->notes = "Product Description";
|
||||||
|
$item->cost = 10;
|
||||||
|
$item->quantity = 10;
|
||||||
|
$item->discount = 5;
|
||||||
|
$item->is_amount_discount = true;
|
||||||
|
$item->tax_rate1 = 19;
|
||||||
|
$item->tax_name1 = 'mwst';
|
||||||
|
|
||||||
|
$invoice = Invoice::factory()->create([
|
||||||
|
'company_id' => $company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'discount' => 5,
|
||||||
|
'uses_inclusive_taxes' => false,
|
||||||
|
'status_id' => 1,
|
||||||
|
'tax_rate1' => 0,
|
||||||
|
'tax_name1' => '',
|
||||||
|
'tax_rate2' => 0,
|
||||||
|
'tax_rate3' => 0,
|
||||||
|
'tax_name2' => '',
|
||||||
|
'tax_name3' => '',
|
||||||
|
'line_items' => [$item],
|
||||||
|
'number' => 'DE-'.rand(1000, 100000),
|
||||||
|
'date' => now()->format('Y-m-d'),
|
||||||
|
'due_date' => now()->addDays(30)->format('Y-m-d'),
|
||||||
|
'is_amount_discount' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$invoice = $invoice->calc()->getInvoice();
|
||||||
|
$invoice->service()->markSent()->save();
|
||||||
|
|
||||||
|
$this->assertEquals(107.1, $invoice->amount);
|
||||||
|
|
||||||
|
$peppol = new Peppol($invoice);
|
||||||
|
$peppol->setInvoiceDefaults();
|
||||||
|
$peppol->run();
|
||||||
|
|
||||||
|
$de_invoice = $peppol->getInvoice();
|
||||||
|
|
||||||
|
$this->assertNotNull($de_invoice);
|
||||||
|
|
||||||
|
$e = new EInvoice();
|
||||||
|
$xml = $e->encode($de_invoice, 'xml');
|
||||||
|
$this->assertNotNull($xml);
|
||||||
|
|
||||||
|
$errors = $e->validate($de_invoice);
|
||||||
|
|
||||||
|
if(count($errors) > 0) {
|
||||||
|
nlog($errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertCount(0, $errors);
|
||||||
|
|
||||||
|
$xml = $peppol->toXml();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
try{
|
||||||
|
$processor = new \Saxon\SaxonProcessor();
|
||||||
|
}
|
||||||
|
catch(\Throwable $e){
|
||||||
|
$this->markTestSkipped('saxon not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = new XsltDocumentValidator($xml);
|
||||||
|
$validator->validate();
|
||||||
|
|
||||||
|
if(count($validator->getErrors()) > 0)
|
||||||
|
{
|
||||||
|
nlog($xml);
|
||||||
|
nlog($validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertCount(0, $validator->getErrors());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public function testDeInvoiceAmountDiscounts()
|
public function testDeInvoiceAmountDiscounts()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -207,18 +909,6 @@ class PeppolTest extends TestCase
|
||||||
$pfa->ID = $id;
|
$pfa->ID = $id;
|
||||||
$pfa->Name = 'PFA-NAME';
|
$pfa->Name = 'PFA-NAME';
|
||||||
|
|
||||||
// $code = new \InvoiceNinja\EInvoice\Models\Peppol\CodeType\AccountTypeCode();
|
|
||||||
// $code->value = 'CHECKING';
|
|
||||||
// $pfa->AccountTypeCode = $code;
|
|
||||||
|
|
||||||
// $code = new \InvoiceNinja\EInvoice\Models\Peppol\CodeType\AccountFormatCode();
|
|
||||||
// $code->value = 'IBAN';
|
|
||||||
// $pfa->AccountFormatCode = $code;
|
|
||||||
|
|
||||||
// $code = new \InvoiceNinja\EInvoice\Models\Peppol\CodeType\CurrencyCode();
|
|
||||||
// $code->value = 'EUR';
|
|
||||||
// $pfa->CurrencyCode = $code;
|
|
||||||
|
|
||||||
$pfa->FinancialInstitutionBranch = $fib;
|
$pfa->FinancialInstitutionBranch = $fib;
|
||||||
|
|
||||||
$pm = new PaymentMeans();
|
$pm = new PaymentMeans();
|
||||||
|
|
@ -320,6 +1010,8 @@ class PeppolTest extends TestCase
|
||||||
|
|
||||||
$xml = $peppol->toXml();
|
$xml = $peppol->toXml();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
$processor = new \Saxon\SaxonProcessor();
|
$processor = new \Saxon\SaxonProcessor();
|
||||||
}
|
}
|
||||||
|
|
@ -330,7 +1022,11 @@ class PeppolTest extends TestCase
|
||||||
$validator = new XsltDocumentValidator($xml);
|
$validator = new XsltDocumentValidator($xml);
|
||||||
$validator->validate();
|
$validator->validate();
|
||||||
|
|
||||||
nlog($validator->getErrors());
|
if(count($validator->getErrors()) > 0)
|
||||||
|
{
|
||||||
|
nlog($xml);
|
||||||
|
nlog($validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
$this->assertCount(0, $validator->getErrors());
|
$this->assertCount(0, $validator->getErrors());
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue