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
|
||||
{
|
||||
|
||||
// Tax exempt clients always get zero tax
|
||||
if ($this->client->is_tax_exempt) {
|
||||
// nlog("tax exempt");
|
||||
$this->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) {
|
||||
// nlog("euro zone and tax exempt");
|
||||
return $this;
|
||||
}
|
||||
|
||||
// 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->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
|
||||
// nlog("foreign and tax exempt");
|
||||
$this->tax_rate = 0;
|
||||
$this->reduced_tax_rate = 0;
|
||||
} elseif(!in_array($this->client_subregion, $this->eu_country_codes)) {
|
||||
$this->defaultForeign();
|
||||
} 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
|
||||
if($this->client->company->tax_data->seller_subregion != $this->client_subregion) {
|
||||
// nlog("eu zone with sales above threshold");
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Non-EU transactions
|
||||
if (!in_array($this->client_subregion, $this->eu_country_codes)) {
|
||||
if ($this->foreign_consumer_tax_exempt || $this->foreign_business_tax_exempt) {
|
||||
$this->tax_rate = 0;
|
||||
$this->reduced_tax_rate = 0;
|
||||
} 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->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_tax_rate ?? 0;
|
||||
} 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->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->reduced_tax_rate ?? 0;
|
||||
}
|
||||
} else {
|
||||
// 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;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,8 +141,8 @@ class InvoiceItemSum
|
|||
$this->invoice = $invoice;
|
||||
$this->client = $invoice->client ?? $invoice->vendor;
|
||||
|
||||
if ($this->invoice->client) {
|
||||
$this->currency = $this->invoice->client->currency();
|
||||
if ($this->client) {
|
||||
$this->currency = $this->client->currency();
|
||||
$this->shouldCalculateTax();
|
||||
} else {
|
||||
$this->currency = $this->invoice->vendor->currency();
|
||||
|
|
@ -158,7 +158,7 @@ class InvoiceItemSum
|
|||
return $this;
|
||||
}
|
||||
|
||||
$this->calcLineItems();
|
||||
$this->calcLineItems()->getPeppolSurchargeTaxes();
|
||||
|
||||
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
|
||||
|
||||
|
||||
/** @var \App\DataMapper\Tax\BaseRule $class */
|
||||
$class = "App\DataMapper\Tax\\".str_replace("-","_",$this->client->company->country()->iso_3166_2)."\\Rule";
|
||||
|
||||
|
|
@ -208,6 +207,46 @@ class InvoiceItemSum
|
|||
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
|
||||
{
|
||||
$this->sub_total += round($this->getLineTotal(), $this->currency->precision);
|
||||
|
|
@ -324,6 +363,50 @@ class InvoiceItemSum
|
|||
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)
|
||||
{
|
||||
$group_tax = [];
|
||||
|
|
|
|||
|
|
@ -240,15 +240,11 @@ class InvoiceSum
|
|||
|
||||
public function getRecurringInvoice()
|
||||
{
|
||||
// $this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
|
||||
// $this->invoice->total_taxes = $this->getTotalTaxes();
|
||||
|
||||
$this->setCalculatedAttributes();
|
||||
$this->invoice->balance = $this->invoice->amount;
|
||||
$this->invoice->saveQuietly();
|
||||
|
||||
// $this->invoice->saveQuietly();
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
|
|
@ -426,4 +422,14 @@ class InvoiceSum
|
|||
|
||||
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();
|
||||
}
|
||||
|
||||
public function getNetSubtotal()
|
||||
{
|
||||
return $this->getSubTotal() - $this->getTotalDiscount();
|
||||
}
|
||||
|
||||
public function purgeTaxes()
|
||||
{
|
||||
return $this;
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ class Product extends BaseModel
|
|||
public const PRODUCT_TYPE_OVERRIDE_TAX = 7;
|
||||
public const PRODUCT_TYPE_ZERO_RATED = 8;
|
||||
public const PRODUCT_TYPE_REVERSE_TAX = 9;
|
||||
public const PRODUCT_INTRA_COMMUNITY = 10;
|
||||
|
||||
protected $fillable = [
|
||||
'custom_value1',
|
||||
|
|
|
|||
|
|
@ -51,4 +51,10 @@ class AccountingSupplierParty
|
|||
$this->public_identifiers = $public_identifiers;
|
||||
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
|
||||
{
|
||||
#[SerializedPath('[cbc:Amount][#]')]
|
||||
public ?string $amount_excluding_vat;
|
||||
// #[SerializedPath('[cbc:Amount][#]')]
|
||||
public ?float $amount_excluding_vat;
|
||||
|
||||
// #[SerializedPath('[cbc:BaseAmount][#]')]
|
||||
public ?string $amount_excluding_tax;
|
||||
#[SerializedPath('[cbc:Amount][#]')]
|
||||
public ?float $amount_excluding_tax;
|
||||
|
||||
#[SerializedPath('[cbc:BaseAmount][#]')]
|
||||
public ?string $base_amount_excluding_tax;
|
||||
public ?float $base_amount_excluding_tax;
|
||||
|
||||
#[SerializedPath('[cbc:Amount][@currencyID]')]
|
||||
public ?string $amount_including_tax;
|
||||
// #[SerializedPath('[cbc:Amount][#]')]
|
||||
public ?float $amount_including_tax;
|
||||
|
||||
#[SerializedPath('[cbc:BaseAmount][@currencyID]')]
|
||||
public ?string $base_amount_including_tax;
|
||||
// #[SerializedPath('[cbc:BaseAmount][#]')]
|
||||
public ?float $base_amount_including_tax;
|
||||
|
||||
// #[SerializedPath('[cac:TaxCategory]')]
|
||||
// public ?Tax $tax;
|
||||
|
|
@ -39,11 +39,11 @@ class AllowanceCharges
|
|||
* @param TaxesDutiesFees[] $taxes_duties_fees
|
||||
*/
|
||||
public function __construct(
|
||||
?string $amount_excluding_vat,
|
||||
?string $amount_excluding_tax,
|
||||
?string $base_amount_excluding_tax,
|
||||
?string $amount_including_tax,
|
||||
?string $base_amount_including_tax,
|
||||
?float $amount_excluding_vat,
|
||||
?float $amount_excluding_tax,
|
||||
?float $base_amount_excluding_tax,
|
||||
?float $amount_including_tax,
|
||||
?float $base_amount_including_tax,
|
||||
// ?Tax $tax,
|
||||
?array $taxes_duties_fees,
|
||||
?string $reason,
|
||||
|
|
@ -60,27 +60,27 @@ class AllowanceCharges
|
|||
$this->reason_code = $reason_code;
|
||||
}
|
||||
|
||||
public function getAmountExcludingVat(): ?string
|
||||
public function getAmountExcludingVat(): ?float
|
||||
{
|
||||
return $this->amount_excluding_vat;
|
||||
}
|
||||
|
||||
public function getAmountExcludingTax(): ?string
|
||||
public function getAmountExcludingTax(): ?float
|
||||
{
|
||||
return $this->amount_excluding_tax;
|
||||
}
|
||||
|
||||
public function getBaseAmountExcludingTax(): ?string
|
||||
public function getBaseAmountExcludingTax(): ?float
|
||||
{
|
||||
return $this->base_amount_excluding_tax;
|
||||
}
|
||||
|
||||
public function getAmountIncludingTax(): ?string
|
||||
public function getAmountIncludingTax(): ?float
|
||||
{
|
||||
return $this->amount_including_tax;
|
||||
}
|
||||
|
||||
public function getBaseAmountIncludingTax(): ?string
|
||||
public function getBaseAmountIncludingTax(): ?float
|
||||
{
|
||||
return $this->base_amount_including_tax;
|
||||
}
|
||||
|
|
@ -103,31 +103,31 @@ class AllowanceCharges
|
|||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
return $this;
|
||||
|
|
|
|||
|
|
@ -38,12 +38,12 @@ class InvoiceLines
|
|||
|
||||
#[SerializedPath('[cac:AllowanceCharge]')]
|
||||
/** @var AllowanceCharges[] */ //todo
|
||||
public ?array $charges;
|
||||
public ?array $allowance_charges;
|
||||
|
||||
#[SerializedPath('[cbc:LineExtensionAmount][#]')]
|
||||
public ?float $amount_excluding_vat;
|
||||
|
||||
#[SerializedPath('[cbc:TaxExclusiveAmount][#]')]
|
||||
#[SerializedPath('[cac:Price][cbc:PriceAmount][value]')]
|
||||
public ?float $amount_excluding_tax;
|
||||
|
||||
#[SerializedPath('[cbc:TaxInclusiveAmount][#]')]
|
||||
|
|
@ -51,7 +51,7 @@ class InvoiceLines
|
|||
|
||||
#[SerializedPath('[cac:Item][cac:ClassifiedTaxCategory]')]
|
||||
/** @var TaxesDutiesFees[] */
|
||||
public array $taxes_duties_fees;
|
||||
public ?array $taxes_duties_fees = [];
|
||||
|
||||
#[SerializedPath('[cbc:AccountingCost]')]
|
||||
public ?string $accounting_cost;
|
||||
|
|
@ -83,7 +83,7 @@ class InvoiceLines
|
|||
public ?string $note;
|
||||
|
||||
/**
|
||||
* @param AllowanceCharges[] $charges
|
||||
* @param AllowanceCharges[] $allowance_charges
|
||||
* @param TaxesDutiesFees[] $taxes_duties_fees
|
||||
* @param References[] $references
|
||||
* @param AdditionalItemProperties[] $additional_item_properties
|
||||
|
|
@ -98,7 +98,7 @@ class InvoiceLines
|
|||
?float $quantity,
|
||||
?float $base_quantity,
|
||||
?string $quantity_unit_code,
|
||||
?array $charges,
|
||||
?array $allowance_charges,
|
||||
?float $amount_excluding_vat,
|
||||
?float $amount_excluding_tax,
|
||||
?float $amount_including_tax,
|
||||
|
|
@ -122,7 +122,7 @@ class InvoiceLines
|
|||
$this->quantity = $quantity;
|
||||
$this->base_quantity = $base_quantity;
|
||||
$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_tax = $amount_excluding_tax;
|
||||
$this->amount_including_tax = $amount_including_tax;
|
||||
|
|
@ -188,7 +188,7 @@ class InvoiceLines
|
|||
*/
|
||||
public function getAllowanceCharges(): ?array
|
||||
{
|
||||
return $this->charges;
|
||||
return $this->allowance_charges;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,36 @@ class StorecoveAdapter
|
|||
|
||||
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
|
||||
*
|
||||
|
|
@ -54,32 +84,30 @@ class StorecoveAdapter
|
|||
|
||||
$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();
|
||||
|
||||
nlog($p);
|
||||
$context = [
|
||||
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d',
|
||||
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
|
||||
];
|
||||
|
||||
$context = [
|
||||
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d',
|
||||
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
|
||||
];
|
||||
$e = new \InvoiceNinja\EInvoice\EInvoice();
|
||||
$peppolInvoice = $e->decode('Peppol', $p, 'xml');
|
||||
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();
|
||||
$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);
|
||||
|
||||
nlog($this->storecove_invoice);
|
||||
|
||||
$this->buildNexus();
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
// $this->storecove_invoice = $serializer->deserialize($data, Invoice::class, 'json', $context);
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
|
@ -89,16 +117,39 @@ $this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'js
|
|||
//set all taxmap countries - resolve the taxing country
|
||||
$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;
|
||||
|
||||
if(property_exists($tax,'category'))
|
||||
$tax->category = $this->tranformTaxCode($tax->category);
|
||||
foreach($line->taxes_duties_fees as &$tax)
|
||||
{
|
||||
$tax->country = $this->nexus;
|
||||
|
||||
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);
|
||||
|
|
@ -128,43 +179,46 @@ $this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'js
|
|||
|
||||
$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');
|
||||
//set additional identifier if required (ie de => FR with FR vat)
|
||||
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()
|
||||
{
|
||||
|
||||
$phpDocExtractor = new PhpDocExtractor();
|
||||
$reflectionExtractor = new ReflectionExtractor();
|
||||
// list of PropertyListExtractorInterface (any iterable)
|
||||
$typeExtractors = [$reflectionExtractor,$phpDocExtractor];
|
||||
// list of PropertyDescriptionExtractorInterface (any iterable)
|
||||
$descriptionExtractors = [$phpDocExtractor];
|
||||
// list of PropertyAccessExtractorInterface (any iterable)
|
||||
$propertyInitializableExtractors = [$reflectionExtractor];
|
||||
$propertyInfo = new PropertyInfoExtractor(
|
||||
$propertyInitializableExtractors,
|
||||
|
|
@ -185,7 +239,12 @@ $this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'js
|
|||
|
||||
return $serializer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds the document and appends an errors prop
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDocument(): mixed
|
||||
{
|
||||
$serializer = $this->getSerializer();
|
||||
|
|
@ -209,7 +268,13 @@ $this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'js
|
|||
return $data;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* RemoveEmptyValues
|
||||
*
|
||||
* @param array $array
|
||||
* @return array
|
||||
*/
|
||||
private function removeEmptyValues(array $array): array
|
||||
{
|
||||
foreach ($array as $key => $value) {
|
||||
|
|
@ -236,27 +301,74 @@ $this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'js
|
|||
$eu_countries = $br->eu_country_codes;
|
||||
|
||||
if ($client_country_code == $company_country_code) {
|
||||
//Domestic Sales
|
||||
//Domestic Sales
|
||||
nlog("domestic sales");
|
||||
$this->nexus = $company_country_code;
|
||||
} elseif (in_array($company_country_code, $eu_countries) && !in_array($client_country_code, $eu_countries)) {
|
||||
//NON-EU Sale
|
||||
nlog("non eu");
|
||||
$this->nexus = $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(!$this->ninja_invoice->client->has_valid_vat_number)
|
||||
$this->nexus = $company_country_code;
|
||||
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;
|
||||
}
|
||||
|
||||
// First, determine if we're over threshold
|
||||
$is_over_threshold = isset($this->ninja_invoice->company->tax_data->regions->EU->has_sales_above_threshold) &&
|
||||
$this->ninja_invoice->company->tax_data->regions->EU->has_sales_above_threshold;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class StorecoveRouter
|
|||
"MK" => ["B+G","","MK:VAT","MK:VAT"],
|
||||
"MT" => ["B+G","","MT:VAT","MT:VAT"],
|
||||
"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"],
|
||||
"PT" => ["G+B","","PT:VAT","PT:VAT"],
|
||||
"RO" => ["G+B","","RO:VAT","RO:VAT"],
|
||||
|
|
|
|||
|
|
@ -48,9 +48,12 @@ class SendEDocument implements ShouldQueue
|
|||
public function handle(Storecove $storecove)
|
||||
{
|
||||
MultiDB::setDB($this->db);
|
||||
|
||||
nlog("trying");
|
||||
|
||||
$model = $this->entity::find($this->id);
|
||||
|
||||
/** Concrete implementation current linked to Storecove only */
|
||||
$p = new Peppol($model);
|
||||
$p->run();
|
||||
$identifiers = $p->gateway->mutator->setClientRoutingCode()->getStorecoveMeta();
|
||||
|
|
@ -58,6 +61,7 @@ class SendEDocument implements ShouldQueue
|
|||
$result = $storecove->build($model);
|
||||
|
||||
if (count($result['errors']) > 0) {
|
||||
nlog($result);
|
||||
return $result['errors'];
|
||||
}
|
||||
|
||||
|
|
@ -70,12 +74,10 @@ class SendEDocument implements ShouldQueue
|
|||
],
|
||||
'tenant_id' => $model->company->company_key,
|
||||
'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();
|
||||
$r = $sc->sendJsonDocument($payload);
|
||||
|
||||
|
|
|
|||
|
|
@ -149,6 +149,10 @@ class Peppol extends AbstractService
|
|||
|
||||
private array $tax_map = [];
|
||||
|
||||
private float $allowance_total = 0;
|
||||
|
||||
private $globalTaxCategories;
|
||||
|
||||
public function __construct(public Invoice $invoice)
|
||||
{
|
||||
$this->company = $invoice->company;
|
||||
|
|
@ -166,6 +170,7 @@ class Peppol extends AbstractService
|
|||
public function run(): self
|
||||
{
|
||||
$this->getJurisdiction();
|
||||
$this->getAllUsedTaxes();
|
||||
|
||||
/** Invoice Level Props */
|
||||
$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->AccountingCustomerParty = $this->getAccountingCustomerParty();
|
||||
$this->p_invoice->InvoiceLine = $this->getInvoiceLines();
|
||||
$this->p_invoice->LegalMonetaryTotal = $this->getLegalMonetaryTotal();
|
||||
$this->p_invoice->AllowanceCharge = $this->getAllowanceCharges();
|
||||
$this->p_invoice->LegalMonetaryTotal = $this->getLegalMonetaryTotal();
|
||||
|
||||
$this->setOrderReference()->setTaxBreakdown();
|
||||
|
||||
|
|
@ -430,18 +435,22 @@ class Peppol extends AbstractService
|
|||
$allowanceCharge->ChargeIndicator = 'false'; // false = discount
|
||||
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
||||
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$allowanceCharge->Amount->amount = (string)$this->calc->getTotalDiscount();
|
||||
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
||||
$allowanceCharge->BaseAmount->currencyID = $this->invoice->client->currency()->code;
|
||||
$allowanceCharge->BaseAmount->amount = (string) $this->calc->getSubTotal();
|
||||
$allowanceCharge->Amount->amount = (string)number_format($this->calc->getTotalDiscount(),2);
|
||||
|
||||
// Add percentage if available
|
||||
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->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->TaxCategory = $this->globalTaxCategories;
|
||||
$allowanceCharge->AllowanceChargeReason = ctrans('texts.discount');
|
||||
$allowances[] = $allowanceCharge;
|
||||
}
|
||||
|
||||
|
|
@ -450,14 +459,17 @@ class Peppol extends AbstractService
|
|||
|
||||
// Add Allowance Charge to Price
|
||||
$allowanceCharge = new \InvoiceNinja\EInvoice\Models\Peppol\AllowanceChargeType\AllowanceCharge();
|
||||
// $allowanceCharge->ChargeIndicator = true; // false = discount
|
||||
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
||||
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$allowanceCharge->Amount->amount = (string)$this->invoice->custom_surcharge1;
|
||||
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
||||
$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;
|
||||
|
||||
}
|
||||
|
|
@ -466,14 +478,18 @@ class Peppol extends AbstractService
|
|||
|
||||
// Add Allowance Charge to Price
|
||||
$allowanceCharge = new \InvoiceNinja\EInvoice\Models\Peppol\AllowanceChargeType\AllowanceCharge();
|
||||
// $allowanceCharge->ChargeIndicator = true; // false = discount
|
||||
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
||||
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$allowanceCharge->Amount->amount = (string)$this->invoice->custom_surcharge2;
|
||||
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
||||
$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;
|
||||
|
||||
}
|
||||
|
|
@ -482,14 +498,17 @@ class Peppol extends AbstractService
|
|||
|
||||
// Add Allowance Charge to Price
|
||||
$allowanceCharge = new \InvoiceNinja\EInvoice\Models\Peppol\AllowanceChargeType\AllowanceCharge();
|
||||
// $allowanceCharge->ChargeIndicator = true; // false = discount
|
||||
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
||||
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$allowanceCharge->Amount->amount = (string)$this->invoice->custom_surcharge3;
|
||||
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
||||
$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;
|
||||
|
||||
}
|
||||
|
|
@ -498,14 +517,17 @@ class Peppol extends AbstractService
|
|||
|
||||
// Add Allowance Charge to Price
|
||||
$allowanceCharge = new \InvoiceNinja\EInvoice\Models\Peppol\AllowanceChargeType\AllowanceCharge();
|
||||
// $allowanceCharge->ChargeIndicator = true; // false = discount
|
||||
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
||||
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$allowanceCharge->Amount->amount = (string)$this->invoice->custom_surcharge4;
|
||||
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
||||
$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;
|
||||
|
||||
}
|
||||
|
|
@ -527,7 +549,7 @@ class Peppol extends AbstractService
|
|||
|
||||
$lea = new LineExtensionAmount();
|
||||
$lea->currencyID = $this->invoice->client->currency()->code;
|
||||
$lea->amount = $this->invoice->uses_inclusive_taxes ? round($this->invoice->amount - $this->invoice->total_taxes, 2) : $taxable;
|
||||
$lea->amount = $this->invoice->uses_inclusive_taxes ? round($this->invoice->amount - $this->invoice->total_taxes, 2) : $this->calc->getSubTotal();
|
||||
$lmt->LineExtensionAmount = $lea;
|
||||
|
||||
$tea = new TaxExclusiveAmount();
|
||||
|
|
@ -545,6 +567,11 @@ class Peppol extends AbstractService
|
|||
$pa->amount = $this->invoice->amount;
|
||||
$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;
|
||||
}
|
||||
|
||||
|
|
@ -576,6 +603,8 @@ class Peppol extends AbstractService
|
|||
break;
|
||||
case Product::PRODUCT_TYPE_REVERSE_TAX:
|
||||
$tax_type = 'AE';
|
||||
case Product::PRODUCT_INTRA_COMMUNITY:
|
||||
$tax_type = 'K';
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -616,32 +645,43 @@ class Peppol extends AbstractService
|
|||
$lines = [];
|
||||
|
||||
foreach($this->invoice->line_items as $key => $item) {
|
||||
|
||||
$base_price_amount = (string)$this->calculateAdjustedBaseAmount($item);
|
||||
|
||||
$_item = new Item();
|
||||
$_item->Name = $item->product_key;
|
||||
$_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 = new ID();
|
||||
$ctc->ID->value = $this->getTaxType($item->tax_id);
|
||||
$ctc->Percent = $item->tax_rate1;
|
||||
$ctc->ID->value = 'K';
|
||||
|
||||
$ts = new TaxScheme();
|
||||
$id = new ID();
|
||||
$id->value = $this->standardizeTaxSchemeId($item->tax_name1);
|
||||
$ts->ID = $id;
|
||||
$ctc->TaxScheme = $ts;
|
||||
|
||||
$_item->ClassifiedTaxCategory[] = $ctc;
|
||||
$terc = new \InvoiceNinja\EInvoice\Models\Peppol\CodeType\TaxExemptionReasonCode();
|
||||
$terc->value = 'VATEX-EU-IC';
|
||||
$ctc->TaxExemptionReasonCode = $terc;
|
||||
$ctc->TaxExemptionReason = 'Intra-Community supply';
|
||||
}
|
||||
|
||||
$_item->ClassifiedTaxCategory[] = $ctc;
|
||||
|
||||
|
||||
if ($item->tax_rate2 > 0) {
|
||||
$ctc = new ClassifiedTaxCategory();
|
||||
$ctc->ID = new ID();
|
||||
$ctc->ID->value = $this->getTaxType($item->tax_id);
|
||||
$ctc->Percent = $item->tax_rate2;
|
||||
$ctc->Percent = (string)$item->tax_rate2;
|
||||
|
||||
$ts = new TaxScheme();
|
||||
$id = new ID();
|
||||
|
|
@ -656,7 +696,7 @@ class Peppol extends AbstractService
|
|||
$ctc = new ClassifiedTaxCategory();
|
||||
$ctc->ID = new ID();
|
||||
$ctc->ID->value = $this->getTaxType($item->tax_id);
|
||||
$ctc->Percent = $item->tax_rate3;
|
||||
$ctc->Percent = (string)$item->tax_rate3;
|
||||
|
||||
$ts = new TaxScheme();
|
||||
$id = new ID();
|
||||
|
|
@ -689,11 +729,12 @@ class Peppol extends AbstractService
|
|||
|
||||
// Handle Price and Discounts
|
||||
if ($item->discount > 0) {
|
||||
|
||||
// Base Price (before discount)
|
||||
$basePrice = new Price();
|
||||
$basePriceAmount = new PriceAmount();
|
||||
$basePriceAmount->currencyID = $this->invoice->client->currency()->code;
|
||||
$basePriceAmount->amount = (string)($item->cost - $this->calculateDiscountAmount($item));
|
||||
$basePriceAmount->amount = (string)$item->cost;
|
||||
$basePrice->PriceAmount = $basePriceAmount;
|
||||
|
||||
// Add Allowance Charge to Price
|
||||
|
|
@ -701,29 +742,35 @@ class Peppol extends AbstractService
|
|||
$allowanceCharge->ChargeIndicator = 'false'; // false = discount
|
||||
$allowanceCharge->Amount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\Amount();
|
||||
$allowanceCharge->Amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$allowanceCharge->Amount->amount = (string)$this->calculateDiscountAmount($item);
|
||||
$allowanceCharge->BaseAmount = new \InvoiceNinja\EInvoice\Models\Peppol\AmountType\BaseAmount();
|
||||
$allowanceCharge->BaseAmount->currencyID = $this->invoice->client->currency()->code;
|
||||
$allowanceCharge->BaseAmount->amount = (string)$item->cost;
|
||||
$allowanceCharge->Amount->amount = (string)number_format($this->calculateTotalItemDiscountAmount($item),2);
|
||||
$this->allowance_total += $this->calculateTotalItemDiscountAmount($item);
|
||||
|
||||
|
||||
// Add percentage if available
|
||||
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->value = (string) ($item->discount / 100);
|
||||
$mfn->value = (string) round($item->discount,2);
|
||||
$allowanceCharge->MultiplierFactorNumeric = $mfn; // Convert percentage to decimal
|
||||
}
|
||||
|
||||
// }
|
||||
// Required reason
|
||||
$allowanceCharge->AllowanceChargeReason = ctrans('texts.discount');
|
||||
|
||||
$basePrice->AllowanceCharge[] = $allowanceCharge;
|
||||
$line->Price = $basePrice;
|
||||
$line->AllowanceCharge[] = $allowanceCharge;
|
||||
|
||||
} else {
|
||||
// No discount case
|
||||
$price = new Price();
|
||||
$pa = new PriceAmount();
|
||||
$pa->currencyID = $this->invoice->client->currency()->code;
|
||||
$pa->amount = (string) ($this->costWithDiscount($item) - ($this->invoice->uses_inclusive_taxes
|
||||
? ($this->calcInclusiveLineTax($item->tax_rate1, $item->line_total) / $item->quantity)
|
||||
: 0));
|
||||
$pa->amount = (string)$item->cost;
|
||||
$price->PriceAmount = $pa;
|
||||
$line->Price = $price;
|
||||
}
|
||||
|
|
@ -735,47 +782,127 @@ class Peppol extends AbstractService
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* calculateDiscountAmount
|
||||
*
|
||||
* Helper method to determine the discount amount to be used.
|
||||
*
|
||||
* @param mixed $item
|
||||
* @return float
|
||||
*/
|
||||
private function calculateDiscountAmount($item): float
|
||||
{
|
||||
if ($item->is_amount_discount) {
|
||||
return $item->discount / $item->quantity; // Per unit discount amount
|
||||
}
|
||||
// /**
|
||||
// * calculateDiscountAmount
|
||||
// *
|
||||
// * Helper method to determine the discount amount to be used.
|
||||
// *
|
||||
// * @param mixed $item
|
||||
// * @return float
|
||||
// */
|
||||
// private function calculateDiscountAmount($item): float
|
||||
// {
|
||||
// if ($item->is_amount_discount) {
|
||||
// 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);
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* costWithDiscount
|
||||
*
|
||||
* Helper method to determine the cost INCLUDING discount
|
||||
*
|
||||
* @param mixed $item
|
||||
* @return float
|
||||
*/
|
||||
private function costWithDiscount($item): float
|
||||
private function calculateTotalItemDiscountAmount($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;
|
||||
}
|
||||
|
||||
if ($item->is_amount_discount) {
|
||||
return $item->discount;
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
|
|
@ -788,18 +915,20 @@ class Peppol extends AbstractService
|
|||
private function getItemTaxes(object $item): array
|
||||
{
|
||||
$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->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->TaxAmount = $tax_amount;
|
||||
|
||||
$taxable_amount = new TaxableAmount();
|
||||
$taxable_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$taxable_amount->amount = $this->invoice->uses_inclusive_taxes ? $item->line_total - $tax_amount->amount : $item->line_total;
|
||||
$taxable_amount->amount = $this->invoice->uses_inclusive_taxes ? $adjusted_base_amount - $tax_amount->amount : $adjusted_base_amount;
|
||||
$tax_subtotal->TaxableAmount = $taxable_amount;
|
||||
|
||||
$tc = new TaxCategory();
|
||||
|
|
@ -807,8 +936,11 @@ class Peppol extends AbstractService
|
|||
$id = new ID();
|
||||
$id->value = $this->getTaxType($item->tax_id);
|
||||
|
||||
if(floatval($item->tax_rate1) === 0.0)
|
||||
$id->value = 'K';
|
||||
|
||||
$tc->ID = $id;
|
||||
$tc->Percent = $item->tax_rate1;
|
||||
$tc->Percent = (string)$item->tax_rate1;
|
||||
$ts = new TaxScheme();
|
||||
|
||||
$id = new ID();
|
||||
|
|
@ -833,7 +965,7 @@ class Peppol extends AbstractService
|
|||
|
||||
$item_taxes[] = $tax_total;
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
|
||||
if(strlen($item->tax_name2 ?? '') > 1) {
|
||||
|
|
@ -855,7 +987,7 @@ class Peppol extends AbstractService
|
|||
$id->value = $this->getTaxType($item->tax_id);
|
||||
|
||||
$tc->ID = $id;
|
||||
$tc->Percent = $item->tax_rate2;
|
||||
$tc->Percent = (string)$item->tax_rate2;
|
||||
$ts = new TaxScheme();
|
||||
|
||||
$id = new ID();
|
||||
|
|
@ -904,7 +1036,7 @@ class Peppol extends AbstractService
|
|||
$id->value = $this->getTaxType($item->tax_id);
|
||||
|
||||
$tc->ID = $id;
|
||||
$tc->Percent = $item->tax_rate3;
|
||||
$tc->Percent = (string)$item->tax_rate3;
|
||||
$ts = new TaxScheme();
|
||||
|
||||
$id = new ID();
|
||||
|
|
@ -1246,6 +1378,11 @@ class Peppol extends AbstractService
|
|||
// Required: TaxCategory ID (BT-118)
|
||||
$category_id = new ID();
|
||||
$category_id->value = 'S'; // Standard rate
|
||||
|
||||
if(floatval($grouped_tax['taxAmount']) === 0.0)
|
||||
$category_id->value = 'K'; // Exempt
|
||||
|
||||
|
||||
$tax_category->ID = $category_id;
|
||||
|
||||
// Required: TaxCategory Rate (BT-119)
|
||||
|
|
@ -1342,4 +1479,72 @@ class Peppol extends AbstractService
|
|||
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()
|
||||
{
|
||||
|
|
@ -159,6 +411,7 @@ class PeppolTest extends TestCase
|
|||
$peppol->setInvoiceDefaults();
|
||||
$peppol->run();
|
||||
|
||||
|
||||
// $peppol->toJson()->toXml();
|
||||
|
||||
// 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()
|
||||
{
|
||||
|
||||
|
|
@ -207,18 +909,6 @@ class PeppolTest extends TestCase
|
|||
$pfa->ID = $id;
|
||||
$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;
|
||||
|
||||
$pm = new PaymentMeans();
|
||||
|
|
@ -320,6 +1010,8 @@ class PeppolTest extends TestCase
|
|||
|
||||
$xml = $peppol->toXml();
|
||||
|
||||
|
||||
|
||||
try{
|
||||
$processor = new \Saxon\SaxonProcessor();
|
||||
}
|
||||
|
|
@ -330,7 +1022,11 @@ class PeppolTest extends TestCase
|
|||
$validator = new XsltDocumentValidator($xml);
|
||||
$validator->validate();
|
||||
|
||||
nlog($validator->getErrors());
|
||||
if(count($validator->getErrors()) > 0)
|
||||
{
|
||||
nlog($xml);
|
||||
nlog($validator->getErrors());
|
||||
}
|
||||
|
||||
$this->assertCount(0, $validator->getErrors());
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue