mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-08 12:12:48 +01:00
Merge pull request #10226 from turbo124/v5-develop
Improve helper text for stripe payment methods
This commit is contained in:
commit
87c788563e
@ -155,7 +155,15 @@ class BaseRule implements RuleInterface
|
||||
|
||||
$this->resolveRegions();
|
||||
|
||||
|
||||
if(!$this->isTaxableRegion()) {
|
||||
$this->tax_data = null;
|
||||
$this->tax_rate1 = 0;
|
||||
$this->tax_name1 = '';
|
||||
$this->tax_rate2 = 0;
|
||||
$this->tax_name2 = '';
|
||||
$this->tax_rate3 = 0;
|
||||
$this->tax_name3 = '';
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -317,8 +325,32 @@ class BaseRule implements RuleInterface
|
||||
}
|
||||
|
||||
if(isset($this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion})) {
|
||||
$this->tax_rate1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
|
||||
$this->tax_name1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_name;
|
||||
if ($this->client_region === 'EU') {
|
||||
$company_country_code = $this->client->company->country()->iso_3166_2;
|
||||
$client_country_code = $this->client->country->iso_3166_2;
|
||||
|
||||
$is_over_threshold = isset($this->client->company->tax_data->regions->EU->has_sales_above_threshold) &&
|
||||
$this->client->company->tax_data->regions->EU->has_sales_above_threshold;
|
||||
|
||||
$is_b2c = strlen($this->client->vat_number) < 2 ||
|
||||
!($this->client->has_valid_vat_number ?? false) ||
|
||||
$this->client->classification == 'individual';
|
||||
|
||||
// Use destination VAT only for B2C transactions over threshold
|
||||
if ($is_b2c && $is_over_threshold) {
|
||||
$this->tax_rate1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
|
||||
$this->tax_name1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_name;
|
||||
}
|
||||
// Otherwise, use origin country tax rates
|
||||
elseif (in_array($company_country_code, $this->eu_country_codes)) {
|
||||
$this->tax_rate1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$company_country_code}->tax_rate;
|
||||
$this->tax_name1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$company_country_code}->tax_name;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->tax_rate1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
|
||||
$this->tax_name1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_name;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -344,10 +376,17 @@ class BaseRule implements RuleInterface
|
||||
Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced($item),
|
||||
Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override($item),
|
||||
Product::PRODUCT_TYPE_ZERO_RATED => $this->zeroRated($item),
|
||||
Product::PRODUCT_TYPE_REVERSE_TAX => $this->zeroRated($item),
|
||||
Product::PRODUCT_INTRA_COMMUNITY => $this->zeroRated($item),
|
||||
default => $this->defaultForeign(),
|
||||
};
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
$this->taxByType($item);
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ class InvoiceItemFactory
|
||||
$item->custom_value3 = '';
|
||||
$item->custom_value4 = '';
|
||||
$item->type_id = '1';
|
||||
$item->tax_id = '1';
|
||||
|
||||
return $item;
|
||||
}
|
||||
@ -58,11 +59,11 @@ class InvoiceItemFactory
|
||||
|
||||
for ($x = 0; $x < $items; $x++) {
|
||||
$item = self::create();
|
||||
$item->quantity = $faker->numberBetween(1, 10);
|
||||
$item->quantity = rand(1, 10);
|
||||
$item->cost = $faker->randomFloat(2, 1, 1000);
|
||||
$item->line_total = $item->quantity * $item->cost;
|
||||
$item->is_amount_discount = true;
|
||||
$item->discount = $faker->numberBetween(1, 10);
|
||||
$item->discount = rand(1, 10);
|
||||
$item->notes = str_replace(['"',"'"], ['',""], $faker->realText(20));
|
||||
$item->product_key = $faker->word();
|
||||
// $item->custom_value1 = $faker->realText(10);
|
||||
@ -78,11 +79,11 @@ class InvoiceItemFactory
|
||||
|
||||
|
||||
$item = self::create();
|
||||
$item->quantity = $faker->numberBetween(1, 10);
|
||||
$item->quantity = rand(1, 10);
|
||||
$item->cost = $faker->randomFloat(2, 1, 1000);
|
||||
$item->line_total = $item->quantity * $item->cost;
|
||||
$item->is_amount_discount = true;
|
||||
$item->discount = $faker->numberBetween(1, 10);
|
||||
$item->discount = rand(1, 10);
|
||||
$item->notes = str_replace(['"',"'"], ['',""], $faker->realText(20));
|
||||
$item->product_key = $faker->word();
|
||||
// $item->custom_value1 = $faker->realText(10);
|
||||
@ -112,7 +113,7 @@ class InvoiceItemFactory
|
||||
|
||||
for ($x = 0; $x < $items; $x++) {
|
||||
$item = self::create();
|
||||
$item->quantity = $faker->numberBetween(-1, -10);
|
||||
$item->quantity = rand(-1, -10);
|
||||
$item->cost = $faker->randomFloat(2, -1, -1000);
|
||||
$item->line_total = $item->quantity * $item->cost;
|
||||
$item->is_amount_discount = true;
|
||||
|
@ -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";
|
||||
|
||||
@ -296,7 +295,7 @@ class InvoiceItemSum
|
||||
$item_tax += $item_tax_rate1_total;
|
||||
|
||||
if (strlen($this->item->tax_name1) > 1) {
|
||||
$this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total);
|
||||
$this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total, $amount, $this->item->tax_id);
|
||||
}
|
||||
|
||||
$item_tax_rate2_total = $this->calcAmountLineTax($this->item->tax_rate2, $amount);
|
||||
@ -304,7 +303,7 @@ class InvoiceItemSum
|
||||
$item_tax += $item_tax_rate2_total;
|
||||
|
||||
if (strlen($this->item->tax_name2) > 1) {
|
||||
$this->groupTax($this->item->tax_name2, $this->item->tax_rate2, $item_tax_rate2_total);
|
||||
$this->groupTax($this->item->tax_name2, $this->item->tax_rate2, $item_tax_rate2_total, $amount, $this->item->tax_id);
|
||||
}
|
||||
|
||||
$item_tax_rate3_total = $this->calcAmountLineTax($this->item->tax_rate3, $amount);
|
||||
@ -312,7 +311,7 @@ class InvoiceItemSum
|
||||
$item_tax += $item_tax_rate3_total;
|
||||
|
||||
if (strlen($this->item->tax_name3) > 1) {
|
||||
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total);
|
||||
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total, $amount, $this->item->tax_id);
|
||||
}
|
||||
|
||||
$this->setTotalTaxes($this->formatValue($item_tax, $this->currency->precision));
|
||||
@ -324,13 +323,61 @@ class InvoiceItemSum
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function groupTax($tax_name, $tax_rate, $tax_total)
|
||||
|
||||
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,
|
||||
'tax_id' => $item->tax_id ?? '1',
|
||||
])
|
||||
->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);
|
||||
}
|
||||
|
||||
$amount = $this->invoice->custom_surcharge4 + $this->invoice->custom_surcharge3 + $this->invoice->custom_surcharge2 + $this->invoice->custom_surcharge1;
|
||||
|
||||
if($tax_component > 0)
|
||||
$this->groupTax($tax['name'], $tax['percentage'], $tax_component, $amount, $tax['tax_id']);
|
||||
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function groupTax($tax_name, $tax_rate, $tax_total, $amount, $tax_id = '')
|
||||
{
|
||||
$group_tax = [];
|
||||
|
||||
$key = str_replace(' ', '', $tax_name.$tax_rate);
|
||||
|
||||
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.Number::formatValueNoTrailingZeroes(floatval($tax_rate), $this->client).'%'];
|
||||
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.Number::formatValueNoTrailingZeroes(floatval($tax_rate), $this->client).'%', 'tax_id' => $tax_id, 'tax_rate' => $tax_rate, 'base_amount' => $amount];
|
||||
|
||||
$this->tax_collection->push(collect($group_tax));
|
||||
}
|
||||
@ -425,14 +472,12 @@ class InvoiceItemSum
|
||||
$amount = $this->item->line_total;
|
||||
}
|
||||
|
||||
//$amount = ($this->sub_total > 0) ? $this->item->line_total - ($this->invoice->discount * ($this->item->line_total / $this->sub_total)) : 0;
|
||||
|
||||
$item_tax_rate1_total = $this->calcAmountLineTax($this->item->tax_rate1, $amount);
|
||||
|
||||
$item_tax += $item_tax_rate1_total;
|
||||
|
||||
if ($item_tax_rate1_total != 0) {
|
||||
$this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total);
|
||||
$this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total, $amount, $this->item->tax_id);
|
||||
}
|
||||
|
||||
$item_tax_rate2_total = $this->calcAmountLineTax($this->item->tax_rate2, $amount);
|
||||
@ -440,7 +485,7 @@ class InvoiceItemSum
|
||||
$item_tax += $item_tax_rate2_total;
|
||||
|
||||
if ($item_tax_rate2_total != 0) {
|
||||
$this->groupTax($this->item->tax_name2, $this->item->tax_rate2, $item_tax_rate2_total);
|
||||
$this->groupTax($this->item->tax_name2, $this->item->tax_rate2, $item_tax_rate2_total, $amount, $this->item->tax_id);
|
||||
}
|
||||
|
||||
$item_tax_rate3_total = $this->calcAmountLineTax($this->item->tax_rate3, $amount);
|
||||
@ -448,7 +493,7 @@ class InvoiceItemSum
|
||||
$item_tax += $item_tax_rate3_total;
|
||||
|
||||
if ($item_tax_rate3_total != 0) {
|
||||
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total);
|
||||
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total, $amount, $this->item->tax_id);
|
||||
}
|
||||
|
||||
$this->item->gross_line_total = $this->getLineTotal() + $item_tax;
|
||||
|
@ -244,7 +244,7 @@ class InvoiceItemSumInclusive
|
||||
$item_tax += $this->formatValue($item_tax_rate1_total, $this->currency->precision);
|
||||
|
||||
if (strlen($this->item->tax_name1) > 1) {
|
||||
$this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total);
|
||||
$this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total, $amount, $this->item->tax_id);
|
||||
}
|
||||
|
||||
$item_tax_rate2_total = $this->calcInclusiveLineTax($this->item->tax_rate2, $amount);
|
||||
@ -252,7 +252,7 @@ class InvoiceItemSumInclusive
|
||||
$item_tax += $this->formatValue($item_tax_rate2_total, $this->currency->precision);
|
||||
|
||||
if (strlen($this->item->tax_name2) > 1) {
|
||||
$this->groupTax($this->item->tax_name2, $this->item->tax_rate2, $item_tax_rate2_total);
|
||||
$this->groupTax($this->item->tax_name2, $this->item->tax_rate2, $item_tax_rate2_total, $amount, $this->item->tax_id);
|
||||
}
|
||||
|
||||
$item_tax_rate3_total = $this->calcInclusiveLineTax($this->item->tax_rate3, $amount);
|
||||
@ -260,7 +260,7 @@ class InvoiceItemSumInclusive
|
||||
$item_tax += $this->formatValue($item_tax_rate3_total, $this->currency->precision);
|
||||
|
||||
if (strlen($this->item->tax_name3) > 1) {
|
||||
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total);
|
||||
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total, $amount, $this->item->tax_id);
|
||||
}
|
||||
|
||||
$this->item->tax_amount = $this->formatValue($item_tax, $this->currency->precision);
|
||||
@ -270,13 +270,13 @@ class InvoiceItemSumInclusive
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function groupTax($tax_name, $tax_rate, $tax_total)
|
||||
private function groupTax($tax_name, $tax_rate, $tax_total, $amount, $tax_id = '')
|
||||
{
|
||||
$group_tax = [];
|
||||
|
||||
$key = str_replace(' ', '', $tax_name.$tax_rate);
|
||||
|
||||
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.Number::formatValueNoTrailingZeroes(floatval($tax_rate), $this->client).'%'];
|
||||
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.Number::formatValueNoTrailingZeroes(floatval($tax_rate), $this->client).'%', 'tax_id' => $tax_id, 'base_amount' => $amount];
|
||||
|
||||
$this->tax_collection->push(collect($group_tax));
|
||||
}
|
||||
@ -376,7 +376,7 @@ class InvoiceItemSumInclusive
|
||||
$item_tax += $item_tax_rate1_total;
|
||||
|
||||
if ($item_tax_rate1_total != 0) {
|
||||
$this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total);
|
||||
$this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total, $amount, $this->item->tax_id);
|
||||
}
|
||||
|
||||
$item_tax_rate2_total = $this->calcInclusiveLineTax($this->item->tax_rate2, $amount);
|
||||
@ -384,7 +384,7 @@ class InvoiceItemSumInclusive
|
||||
$item_tax += $item_tax_rate2_total;
|
||||
|
||||
if ($item_tax_rate2_total != 0) {
|
||||
$this->groupTax($this->item->tax_name2, $this->item->tax_rate2, $item_tax_rate2_total);
|
||||
$this->groupTax($this->item->tax_name2, $this->item->tax_rate2, $item_tax_rate2_total, $amount, $this->item->tax_id);
|
||||
}
|
||||
|
||||
$item_tax_rate3_total = $this->calcInclusiveLineTax($this->item->tax_rate3, $amount);
|
||||
@ -392,7 +392,7 @@ class InvoiceItemSumInclusive
|
||||
$item_tax += $item_tax_rate3_total;
|
||||
|
||||
if ($item_tax_rate3_total != 0) {
|
||||
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total);
|
||||
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total, $amount, $this->item->tax_id);
|
||||
}
|
||||
|
||||
$this->setTotalTaxes($this->getTotalTaxes() + $item_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;
|
||||
}
|
||||
|
||||
@ -352,12 +348,26 @@ class InvoiceSum
|
||||
$tax_name = $values->filter(function ($value, $k) use ($key) {
|
||||
return $value['key'] == $key;
|
||||
})->pluck('tax_name')->first();
|
||||
|
||||
$tax_rate = $values->filter(function ($value, $k) use ($key) {
|
||||
return $value['key'] == $key;
|
||||
})->pluck('tax_rate')->first();
|
||||
|
||||
$tax_id = $values->filter(function ($value, $k) use ($key) {
|
||||
return $value['key'] == $key;
|
||||
})->pluck('tax_id')->first();
|
||||
|
||||
$total_line_tax = $values->filter(function ($value, $k) use ($key) {
|
||||
return $value['key'] == $key;
|
||||
})->sum('total');
|
||||
|
||||
$base_amount = $values->filter(function ($value, $k) use ($key) {
|
||||
return $value['key'] == $key;
|
||||
})->sum('base_amount');
|
||||
|
||||
$this->tax_map[] = ['name' => $tax_name, 'total' => $total_line_tax];
|
||||
$tax_id = $values->first()['tax_id'] ?? '';
|
||||
|
||||
$this->tax_map[] = ['name' => $tax_name, 'total' => $total_line_tax, 'tax_id' => $tax_id, 'tax_rate' => $tax_rate, 'base_amount' => round($base_amount,2)];
|
||||
|
||||
$this->total_taxes += $total_line_tax;
|
||||
}
|
||||
@ -426,4 +436,14 @@ class InvoiceSum
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNetSubtotal()
|
||||
{
|
||||
return $this->getSubTotal() - $this->getTotalDiscount();
|
||||
}
|
||||
|
||||
public function getSubtotalWithSurcharges()
|
||||
{
|
||||
return $this->getSubTotal() + $this->getTotalSurcharges();
|
||||
}
|
||||
}
|
||||
|
@ -368,19 +368,33 @@ class InvoiceSumInclusive
|
||||
$values = $this->invoice_items->getGroupedTaxes();
|
||||
|
||||
foreach ($keys as $key) {
|
||||
|
||||
$tax_name = $values->filter(function ($value, $k) use ($key) {
|
||||
return $value['key'] == $key;
|
||||
})->pluck('tax_name')->first();
|
||||
|
||||
$tax_rate = $values->filter(function ($value, $k) use ($key) {
|
||||
return $value['key'] == $key;
|
||||
})->pluck('tax_rate')->first();
|
||||
|
||||
$tax_id = $values->filter(function ($value, $k) use ($key) {
|
||||
return $value['key'] == $key;
|
||||
})->pluck('tax_id')->first();
|
||||
|
||||
$total_line_tax = $values->filter(function ($value, $k) use ($key) {
|
||||
return $value['key'] == $key;
|
||||
})->sum('total');
|
||||
|
||||
$base_amount = $values->filter(function ($value, $k) use ($key) {
|
||||
return $value['key'] == $key;
|
||||
})->sum('base_amount');
|
||||
|
||||
//$total_line_tax -= $this->discount($total_line_tax);
|
||||
$tax_id = $values->first()['tax_id'] ?? '';
|
||||
|
||||
$this->tax_map[] = ['name' => $tax_name, 'total' => $total_line_tax];
|
||||
$this->tax_map[] = ['name' => $tax_name, 'total' => $total_line_tax, 'tax_id' => $tax_id, 'tax_rate' => $tax_rate, 'base_amount' => round($base_amount,2)];
|
||||
|
||||
$this->total_taxes += $total_line_tax;
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -401,6 +415,11 @@ class InvoiceSumInclusive
|
||||
return $this->getTotalTaxes();
|
||||
}
|
||||
|
||||
public function getNetSubtotal()
|
||||
{
|
||||
return $this->getSubTotal() - $this->getTotalDiscount();
|
||||
}
|
||||
|
||||
public function purgeTaxes()
|
||||
{
|
||||
return $this;
|
||||
|
@ -283,6 +283,18 @@ class InvitationController extends Controller
|
||||
|
||||
$invoice = $invitation->invoice->service()->removeUnpaidGatewayFees()->save();
|
||||
|
||||
if (! $invitation->viewed_date) {
|
||||
$invitation->markViewed();
|
||||
|
||||
if (!session()->get('is_silent')) {
|
||||
event(new InvitationWasViewed($invitation->invoice, $invitation, $invitation->invoice->company, Ninja::eventVars()));
|
||||
}
|
||||
|
||||
if (!session()->get('is_silent')) {
|
||||
$this->fireEntityViewedEvent($invitation, $invoice);
|
||||
}
|
||||
}
|
||||
|
||||
if ($invoice->partial > 0) {
|
||||
$amount = round($invoice->partial, (int)$invoice->client->currency()->precision);
|
||||
} else {
|
||||
|
@ -11,10 +11,12 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\EInvoice\ShowQuotaRequest;
|
||||
use App\Http\Requests\EInvoice\ValidateEInvoiceRequest;
|
||||
use App\Http\Requests\EInvoice\UpdateEInvoiceConfiguration;
|
||||
use App\Services\EDocument\Standards\Validation\Peppol\EntityLevel;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\BranchType\FinancialInstitutionBranch;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\FinancialInstitutionType\FinancialInstitution;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\FinancialAccountType\PayeeFinancialAccount;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\PaymentMeans;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\CardAccountType\CardAccount;
|
||||
@ -41,55 +43,91 @@ class EInvoiceController extends BaseController
|
||||
default => $data['passes'] = false,
|
||||
};
|
||||
|
||||
nlog($data);
|
||||
|
||||
return response()->json($data, $data['passes'] ? 200 : 400);
|
||||
|
||||
}
|
||||
|
||||
public function configurations(UpdateEInvoiceConfiguration $request)
|
||||
{
|
||||
|
||||
$einvoice = new \InvoiceNinja\EInvoice\Models\Peppol\Invoice();
|
||||
$pm = new PaymentMeans();
|
||||
|
||||
$pmc = new PaymentMeansCode();
|
||||
$pmc->value = $request->input('payment_means.code', null);
|
||||
|
||||
if($this->input('payment_means.code') == '48')
|
||||
foreach($request->input('payment_means', []) as $payment_means)
|
||||
{
|
||||
$ctc = new CardTypeCode();
|
||||
$ctc->value = $request->input('payment_means.card_type', null);
|
||||
$card_account = new CardAccount();
|
||||
$card_account->HolderName = $request->input('payment_means.cardholder_name', '');
|
||||
$card_account->CardTypeCode = $ctc;
|
||||
$pm->CardAccount = $card_account;
|
||||
}
|
||||
$pm = new PaymentMeans();
|
||||
|
||||
if($this->input('payment_means.iban'))
|
||||
{
|
||||
$fib = new FinancialInstitutionBranch();
|
||||
$bic_id = new ID();
|
||||
$bic_id->value = $request->input('payment_means.bic', null);
|
||||
$fib->ID = $bic_id;
|
||||
$pfa = new PayeeFinancialAccount();
|
||||
$iban_id = new ID();
|
||||
$iban_id->value = $request->input('payment_means.iban', null);
|
||||
$pfa->ID = $iban_id;
|
||||
$pfa->Name = $request->input('payment_means.account_name', null);
|
||||
$pfa->FinancialInstitutionBranch = $fib;
|
||||
|
||||
$pm->PayeeFinancialAccount = $pfa;
|
||||
$pmc = new PaymentMeansCode();
|
||||
$pmc->value = $payment_means['code'];
|
||||
$pm->PaymentMeansCode = $pmc;
|
||||
|
||||
if(in_array($payment_means['code'], ['54,55']))
|
||||
{
|
||||
$ctc = new CardTypeCode();
|
||||
$ctc->value = $payment_means['card_type'];
|
||||
$card_account = new CardAccount();
|
||||
$card_account->HolderName = $payment_means['card_holder'];
|
||||
$card_account->CardTypeCode = $ctc;
|
||||
$pm->CardAccount = $card_account;
|
||||
}
|
||||
|
||||
if(isset($payment_means['iban']))
|
||||
{
|
||||
$fib = new FinancialInstitutionBranch();
|
||||
$fi = new FinancialInstitution();
|
||||
$bic_id = new ID();
|
||||
$bic_id->value = $payment_means['bic_swift'];
|
||||
$fi->ID = $bic_id;
|
||||
$fib->FinancialInstitution = $fi;
|
||||
$pfa = new PayeeFinancialAccount();
|
||||
$iban_id = new ID();
|
||||
$iban_id->value = $payment_means['iban'];
|
||||
$pfa->ID = $iban_id;
|
||||
$pfa->Name = $payment_means['payer_bank_account'];
|
||||
$pfa->FinancialInstitutionBranch = $fib;
|
||||
|
||||
$pm->PayeeFinancialAccount = $pfa;
|
||||
|
||||
}
|
||||
|
||||
if(isset($payment_means['information']))
|
||||
$pm->InstructionNote = $payment_means['information'];
|
||||
|
||||
$einvoice->PaymentMeans[] = $pm;
|
||||
}
|
||||
|
||||
$pm->InstructionNote = $request->input('payment_means.information', '');
|
||||
|
||||
$einvoice->PaymentMeans[] = $pm;
|
||||
|
||||
|
||||
$stub = new \stdClass();
|
||||
$stub->Invoice = $einvoice;
|
||||
|
||||
$company = auth()->user()->company();
|
||||
$company->e_invoice = $stub;
|
||||
$company->save();
|
||||
}
|
||||
|
||||
public function quota(ShowQuotaRequest $request): \Illuminate\Http\Response
|
||||
{
|
||||
/**
|
||||
* @var \App\Models\Company
|
||||
*/
|
||||
$company = auth()->user()->company();
|
||||
|
||||
$response = \Illuminate\Support\Facades\Http::baseUrl(config('ninja.hosted_ninja_url'))
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
])
|
||||
->post('/api/einvoice/quota', data: [
|
||||
'license_key' => config('ninja.license_key'),
|
||||
'e_invoicing_token' => $company->account->e_invoicing_token,
|
||||
'account_key' => $company->account->key,
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
return response($response->body());
|
||||
}
|
||||
|
||||
if ($response->getStatusCode() === 400) {
|
||||
return response($response->body(), 400);
|
||||
}
|
||||
|
||||
return response()->noContent(500);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Http;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Http\Requests\EInvoice\Peppol\StoreEntityRequest;
|
||||
use App\Services\EDocument\Gateway\Storecove\Storecove;
|
||||
@ -20,7 +22,7 @@ use App\Http\Requests\EInvoice\Peppol\ShowEntityRequest;
|
||||
use App\Http\Requests\EInvoice\Peppol\UpdateEntityRequest;
|
||||
|
||||
class EInvoicePeppolController extends BaseController
|
||||
{
|
||||
{
|
||||
/**
|
||||
* Returns the legal entity ID
|
||||
*
|
||||
@ -71,16 +73,23 @@ class EInvoicePeppolController extends BaseController
|
||||
*
|
||||
*
|
||||
* @param ShowEntityRequest $request
|
||||
* @param Storecove $storecove
|
||||
* @return mixed
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function show(ShowEntityRequest $request, Storecove $storecove)
|
||||
public function show(ShowEntityRequest $request): JsonResponse
|
||||
{
|
||||
$company = auth()->user()->company();
|
||||
|
||||
$response = $storecove->getLegalEntity($company->legal_entity_id);
|
||||
$response = Http::baseUrl(config('ninja.hosted_ninja_url'))
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
])
|
||||
->post('/api/einvoice/peppol/legal_entity', data: [
|
||||
'legal_entity_id' => $company->legal_entity_id,
|
||||
'e_invoicing_token' => $company->account->e_invoicing_token,
|
||||
]);
|
||||
|
||||
return response()->json($response, 200);
|
||||
return response()->json($response->json(), 200);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,18 +107,21 @@ class EInvoicePeppolController extends BaseController
|
||||
*/
|
||||
$company = auth()->user()->company();
|
||||
|
||||
$legal_entity_response = $storecove->createLegalEntity($request->validated(), $company);
|
||||
$response = Http::baseUrl(config('ninja.hosted_ninja_url'))
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
])
|
||||
->post('/api/einvoice/peppol/setup', data: [
|
||||
...$request->validated(),
|
||||
'classification' => $request->classification ?? $company->settings->classification,
|
||||
'vat_number' => $request->vat_number ?? $company->settings->vat_number,
|
||||
'id_number' => $request->id_number ?? $company->settings->id_number,
|
||||
'e_invoicing_token' => $company->account->e_invoicing_token,
|
||||
]);
|
||||
|
||||
$scheme = $storecove->router->resolveRouting($request->country, $company->settings->classification);
|
||||
|
||||
$add_identifier_response = $storecove->addIdentifier(
|
||||
legal_entity_id: $legal_entity_response['id'],
|
||||
identifier: $company->settings->vat_number,
|
||||
scheme: $scheme,
|
||||
);
|
||||
|
||||
if ($add_identifier_response) {
|
||||
$company->legal_entity_id = $legal_entity_response['id'];
|
||||
if ($response->successful()) {
|
||||
$company->legal_entity_id = $response->json('legal_entity_id');
|
||||
|
||||
$tax_data = $company->tax_data;
|
||||
|
||||
@ -117,8 +129,13 @@ class EInvoicePeppolController extends BaseController
|
||||
$tax_data->acts_as_receiver = $request->acts_as_receiver;
|
||||
|
||||
$settings = $company->settings;
|
||||
|
||||
$settings->e_invoice_type = 'PEPPOL';
|
||||
|
||||
$settings->vat_number = $request->vat_number ?? $company->settings->vat_number;
|
||||
$settings->id_number = $request->id_number ?? $company->settings->id_number;
|
||||
$settings->classification = $request->classification ?? $company->settings->classification;
|
||||
$settings->enable_e_invoice = true;
|
||||
|
||||
$company->tax_data = $tax_data;
|
||||
$company->settings = $settings;
|
||||
|
||||
@ -127,11 +144,9 @@ class EInvoicePeppolController extends BaseController
|
||||
return response()->noContent();
|
||||
}
|
||||
|
||||
// @todo: Improve with proper error.
|
||||
|
||||
return response()->noContent(status: 422);
|
||||
return response()->noContent(status: 500);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add an additional tax identifier to
|
||||
* an existing legal entity id
|
||||
@ -144,7 +159,8 @@ class EInvoicePeppolController extends BaseController
|
||||
*/
|
||||
public function addAdditionalTaxIdentifier(AddTaxIdentifierRequest $request, Storecove $storecove): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
|
||||
// @todo: check with dave, since this method has 0 references and it's not being used.
|
||||
|
||||
$company = auth()->user()->company();
|
||||
$tax_data = $company->tax_data;
|
||||
|
||||
@ -165,14 +181,29 @@ class EInvoicePeppolController extends BaseController
|
||||
return response()->json(['message' => 'ok'], 200);
|
||||
|
||||
}
|
||||
|
||||
public function updateLegalEntity(UpdateEntityRequest $request, Storecove $storecove)
|
||||
|
||||
/**
|
||||
* Update legal properties such as acting as sender or receiver.
|
||||
*
|
||||
* @param \App\Http\Requests\EInvoice\Peppol\UpdateEntityRequest $request
|
||||
* @return JsonResponse|mixed|Response
|
||||
*/
|
||||
public function updateLegalEntity(UpdateEntityRequest $request)
|
||||
{
|
||||
$company = auth()->user()->company();
|
||||
|
||||
$r = $storecove->updateLegalEntity($company->legal_entity_id, $request->validated());
|
||||
$response = Http::baseUrl(config('ninja.hosted_ninja_url'))
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
])
|
||||
->put('/api/einvoice/peppol/update', data: [
|
||||
...$request->validated(),
|
||||
'legal_entity_id' => $company->legal_entity_id,
|
||||
'e_invoicing_token' => $company->account->e_invoicing_token,
|
||||
]);
|
||||
|
||||
if ($r->successful()) {
|
||||
if ($response->successful()) {
|
||||
$tax_data = $company->tax_data;
|
||||
|
||||
$tax_data->acts_as_sender = $request->acts_as_sender;
|
||||
@ -184,7 +215,7 @@ class EInvoicePeppolController extends BaseController
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
|
||||
|
||||
return response()->json(['message' => 'Error updating identifier'], 422);
|
||||
}
|
||||
|
||||
@ -192,21 +223,27 @@ class EInvoicePeppolController extends BaseController
|
||||
* Removed the legal identity from the Peppol network
|
||||
*
|
||||
* @param DisconnectRequest $request
|
||||
* @param Storecove $storecove
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function disconnect(DisconnectRequest $request, Storecove $storecove): \Illuminate\Http\Response
|
||||
public function disconnect(DisconnectRequest $request): \Illuminate\Http\Response
|
||||
{
|
||||
/**
|
||||
* @var \App\Models\Company $company
|
||||
*/
|
||||
$company = auth()->user()->company();
|
||||
|
||||
$response = $storecove->deleteIdentifier(
|
||||
legal_entity_id: $company->legal_entity_id,
|
||||
);
|
||||
$response = Http::baseUrl(config('ninja.hosted_ninja_url'))
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
])
|
||||
->post('/api/einvoice/peppol/disconnect', data: [
|
||||
'company_key' => $company->company_key,
|
||||
'legal_entity_id' => $company->legal_entity_id,
|
||||
'e_invoicing_token' => $company->account->e_invoicing_token,
|
||||
]);
|
||||
|
||||
if ($response) {
|
||||
if ($response->successful()) {
|
||||
$company->legal_entity_id = null;
|
||||
$company->tax_data = $this->unsetVatNumbers($company->tax_data);
|
||||
|
||||
@ -218,12 +255,9 @@ class EInvoicePeppolController extends BaseController
|
||||
$company->save();
|
||||
|
||||
return response()->noContent();
|
||||
|
||||
}
|
||||
|
||||
// @todo: Improve with proper error.
|
||||
|
||||
return response()->noContent(status: 422);
|
||||
return response()->noContent(status: 500);
|
||||
}
|
||||
|
||||
private function unsetVatNumbers(mixed $taxData): mixed
|
||||
|
@ -15,17 +15,33 @@ namespace App\Http\Controllers;
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Http\Requests\EInvoice\UpdateTokenRequest;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class EInvoiceTokenController extends BaseController
|
||||
{
|
||||
public function __invoke(UpdateTokenRequest $request): Response
|
||||
{
|
||||
/** @var \App\Models\Company $company */
|
||||
$company = auth()->user()->company();
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
// $company->e_invoicing_token = $request->get('token');
|
||||
$company->save();
|
||||
$response = Http::baseUrl(config('ninja.hosted_ninja_url'))
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
])
|
||||
->post('/api/einvoice/tokens/rotate', data: [
|
||||
'license' => config('ninja.license_key'),
|
||||
'account_key' => $user->account->key,
|
||||
]);
|
||||
|
||||
return response()->noContent();
|
||||
if ($response->successful()) {
|
||||
$user->account->update([
|
||||
'e_invoicing_token' => $response->json('token'),
|
||||
]);
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
|
||||
return response()->noContent(status: 422);
|
||||
}
|
||||
}
|
||||
|
@ -11,9 +11,11 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\License\CheckRequest;
|
||||
use App\Models\Account;
|
||||
use App\Utils\CurlUtils;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use stdClass;
|
||||
@ -224,4 +226,26 @@ class LicenseController extends BaseController
|
||||
$account->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function check(CheckRequest $request): Response|JsonResponse
|
||||
{
|
||||
if (! config('ninja.license_key')) {
|
||||
return response()->json(['message' => 'License not found. Make sure to update LICENSE_KEY in .env!'], status: 422);
|
||||
}
|
||||
|
||||
$response = Http::baseUrl(config('ninja.hosted_ninja_url'))
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
])
|
||||
->post('/api/check', data: [
|
||||
'license' => config('ninja.license_key'),
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
return response()->json($response->json());
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Invalid license'], status: 422);
|
||||
}
|
||||
}
|
||||
|
@ -517,7 +517,7 @@ class QuoteController extends BaseController
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
nlog("booop");
|
||||
|
||||
$action = $request->input('action');
|
||||
|
||||
$ids = $request->input('ids');
|
||||
|
@ -81,7 +81,7 @@ class SearchController extends Controller
|
||||
],
|
||||
],
|
||||
],
|
||||
'size' => 1000,
|
||||
'size' => 100,
|
||||
],
|
||||
];
|
||||
|
||||
|
@ -273,14 +273,16 @@ class TaskController extends BaseController
|
||||
return $request->disallowUpdate();
|
||||
}
|
||||
|
||||
$old_task = json_decode(json_encode($task));
|
||||
$old_task_status_order = $task->status_order;
|
||||
// $old_task = json_decode(json_encode($task));
|
||||
|
||||
$task = $this->task_repo->save($request->all(), $task);
|
||||
|
||||
$task = $this->task_repo->triggeredActions($request, $task);
|
||||
|
||||
if ($task->status_order != $old_task->status_order) {
|
||||
$this->task_repo->sortStatuses($old_task, $task);
|
||||
if ($task->status_order != $old_task_status_order) {
|
||||
// if ($task->status_order != $old_task->status_order) {
|
||||
$this->task_repo->sortStatuses($task);
|
||||
}
|
||||
|
||||
event(new TaskWasUpdated($task, $task->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
@ -20,8 +20,7 @@ use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreEntityRequest extends FormRequest
|
||||
{
|
||||
|
||||
private array $vat_regex_patterns = [
|
||||
public static array $vat_regex_patterns = [
|
||||
'DE' => '/^DE\d{9}$/',
|
||||
'AT' => '/^ATU\d{8}$/',
|
||||
'BE' => '/^BE0\d{9}$/',
|
||||
@ -62,7 +61,7 @@ class StoreEntityRequest extends FormRequest
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->account->isPaid() && $user->isAdmin() &&
|
||||
return $user->account->isPaid() && $user->isAdmin() &&
|
||||
$user->company()->legal_entity_id === null;
|
||||
}
|
||||
|
||||
@ -76,12 +75,15 @@ class StoreEntityRequest extends FormRequest
|
||||
'line1' => ['required', 'string'],
|
||||
'line2' => ['nullable', 'string'],
|
||||
'city' => ['required', 'string'],
|
||||
'country' => ['required', 'bail', Rule::in(array_keys($this->vat_regex_patterns))],
|
||||
'country' => ['required', 'bail', Rule::in(array_keys(self::$vat_regex_patterns))],
|
||||
'zip' => ['required', 'string'],
|
||||
'county' => ['required', 'string'],
|
||||
'acts_as_receiver' => ['required', 'bool'],
|
||||
'acts_as_sender' => ['required', 'bool'],
|
||||
'tenant_id' => ['required'],
|
||||
'classification' => ['required', 'string'],
|
||||
'vat_number' => [Rule::requiredIf(fn() => $this->input('classification') !== 'individual')],
|
||||
'id_number' => [Rule::requiredIf(fn() => $this->input('classification') === 'individual')],
|
||||
];
|
||||
}
|
||||
|
||||
@ -96,7 +98,7 @@ class StoreEntityRequest extends FormRequest
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(isset($input['country'])) {
|
||||
if (isset($input['country'])) {
|
||||
$country = $this->country();
|
||||
$input['country'] = $country->iso_3166_2;
|
||||
}
|
||||
@ -104,19 +106,18 @@ class StoreEntityRequest extends FormRequest
|
||||
$input['acts_as_receiver'] = $input['acts_as_receiver'] ?? true;
|
||||
$input['acts_as_sender'] = $input['acts_as_sender'] ?? true;
|
||||
|
||||
$this->replace($input);
|
||||
$input['classification'] = $input['classification'] ?? 'individual';
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
public function country(): Country
|
||||
{
|
||||
|
||||
/** @var \Illuminate\Support\Collection<\App\Models\Country> */
|
||||
$countries = app('countries');
|
||||
|
||||
return $countries->first(function ($c){
|
||||
return $countries->first(function ($c) {
|
||||
return $this->country == $c->id;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
27
app/Http/Requests/EInvoice/ShowQuotaRequest.php
Normal file
27
app/Http/Requests/EInvoice/ShowQuotaRequest.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\EInvoice;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ShowQuotaRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
if (app()->isLocal()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return \App\Utils\Ninja::isSelfHost() && $user->account->isPaid();
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
@ -37,41 +37,72 @@ class UpdateEInvoiceConfiguration extends Request
|
||||
|
||||
public function rules()
|
||||
{
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return [
|
||||
'entity' => 'required|bail|in:invoice,client,company',
|
||||
'payment_means' => 'sometimes|bail|array',
|
||||
'payment_means.code' => ['required_with:payment_means', 'bail', Rule::in(PaymentMeans::getPaymentMeansCodelist())],
|
||||
'payment_means.bic' => ['bail',
|
||||
Rule::requiredIf(function () {
|
||||
return in_array($this->input('payment_means.code'), ['58', '59', '49', '42', '30']);
|
||||
}),
|
||||
],
|
||||
'payment_means.iban' => ['bail', 'string', 'min:8', 'max:11',
|
||||
Rule::requiredIf(function () {
|
||||
return in_array($this->input('payment_means.code'), ['58', '59', '49', '42', '30']);
|
||||
}),
|
||||
],
|
||||
'payment_means.account_name' => ['bail', 'string', 'min:15', 'max:34',
|
||||
Rule::requiredIf(function () {
|
||||
return in_array($this->input('payment_means.code'), ['58', '59', '49', '42', '30']);
|
||||
}),
|
||||
],
|
||||
'payment_means.information' => ['bail', 'sometimes', 'string'],
|
||||
'payment_means.card_type' => ['bail', 'string', 'min:4',
|
||||
Rule::requiredIf(function () {
|
||||
return in_array($this->input('payment_means.code'), ['48']);
|
||||
}),
|
||||
],
|
||||
'payment_means.cardholder_name' => ['bail','string', 'min:4',
|
||||
Rule::requiredIf(function () {
|
||||
return in_array($this->input('payment_means.code'), ['48']);
|
||||
}),
|
||||
],
|
||||
];
|
||||
'entity' => 'required|bail|in:invoice,client,company',
|
||||
'payment_means' => 'sometimes|bail|array',
|
||||
'payment_means.*.code' => ['required_with:payment_means', 'bail', Rule::in(PaymentMeans::getPaymentMeansCodelist())],
|
||||
'payment_means.*.bic_swift' => Rule::forEach(function (string|null $value, string $attribute) {
|
||||
$index = explode('.', $attribute)[1];
|
||||
$code = $this->input("payment_means.{$index}.code");
|
||||
$requirements = PaymentMeans::$payment_means_requirements_codes[$code] ?? [];
|
||||
$rules = ['bail', 'string', 'min:8', 'max:11'];
|
||||
|
||||
if (in_array('bic_swift', $requirements)) {
|
||||
return [...$rules, 'required'];
|
||||
}
|
||||
|
||||
return [...$rules, 'nullable'];
|
||||
}),
|
||||
'payment_means.*.iban' => Rule::forEach(function (string|null $value, string $attribute) {
|
||||
$index = explode('.', $attribute)[1];
|
||||
$code = $this->input("payment_means.{$index}.code");
|
||||
$requirements = PaymentMeans::$payment_means_requirements_codes[$code] ?? [];
|
||||
$rules = ['bail', 'sometimes', 'string', 'min:15', 'max:34'];
|
||||
|
||||
if (in_array('iban', $requirements)) {
|
||||
return [...$rules, 'required'];
|
||||
}
|
||||
|
||||
return [...$rules, 'nullable'];
|
||||
}),
|
||||
'payment_means.*.payer_bank_account' => Rule::forEach(function (string|null $value, string $attribute) {
|
||||
$index = explode('.', $attribute)[1];
|
||||
$code = $this->input("payment_means.{$index}.code");
|
||||
$requirements = PaymentMeans::$payment_means_requirements_codes[$code] ?? [];
|
||||
$rules = ['bail', 'sometimes', 'string', 'max:255'];
|
||||
|
||||
if (in_array('payer_bank_account', $requirements)) {
|
||||
return [...$rules, 'required'];
|
||||
}
|
||||
|
||||
return [...$rules, 'nullable'];
|
||||
}),
|
||||
'payment_means.*.information' => ['bail', 'sometimes', 'nullable', 'string'],
|
||||
'payment_means.*.card_type' => Rule::forEach(function (string|null $value, string $attribute) {
|
||||
$index = explode('.', $attribute)[1];
|
||||
$code = $this->input("payment_means.{$index}.code");
|
||||
$requirements = PaymentMeans::$payment_means_requirements_codes[$code] ?? [];
|
||||
$rules = ['bail', 'sometimes', 'nullable', 'string', 'min:4'];
|
||||
|
||||
if (in_array('card_type', $requirements)) {
|
||||
return [...$rules, 'required'];
|
||||
}
|
||||
|
||||
return [...$rules, 'nullable'];
|
||||
}),
|
||||
'payment_means.*.card_holder' => Rule::forEach(function (string|null $value, string $attribute) {
|
||||
$index = explode('.', $attribute)[1];
|
||||
$code = $this->input("payment_means.{$index}.code");
|
||||
$requirements = PaymentMeans::$payment_means_requirements_codes[$code] ?? [];
|
||||
$rules = ['bail', 'sometimes', 'nullable', 'string', 'min:4'];
|
||||
|
||||
if (in_array('card_holder', $requirements)) {
|
||||
return [...$rules, 'required'];
|
||||
}
|
||||
|
||||
return [...$rules, 'nullable'];
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
|
@ -36,9 +36,7 @@ class UpdateTokenRequest extends Request
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'token' => 'required',
|
||||
];
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function failedAuthorization(): void
|
||||
|
33
app/Http/Requests/License/CheckRequest.php
Normal file
33
app/Http/Requests/License/CheckRequest.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\License;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CheckRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Ninja::isSelfHost() && auth()->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\ValidationRules\Invoice;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Class LockedInvoiceRule.
|
||||
*/
|
||||
class InvoiceBalanceSanity implements Rule
|
||||
{
|
||||
public $invoice;
|
||||
|
||||
public $input;
|
||||
|
||||
private $message;
|
||||
|
||||
public function __construct(Invoice $invoice, $input)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
$this->input = $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
return $this->checkIfInvoiceBalanceIsSane(); //if it exists, return false!
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function message()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function checkIfInvoiceBalanceIsSane(): bool
|
||||
{
|
||||
DB::connection(config('database.default'))->beginTransaction();
|
||||
|
||||
$this->invoice = Invoice::on(config('database.default'))->withTrashed()->find($this->invoice->id);
|
||||
$this->invoice->line_items = $this->input['line_items'];
|
||||
$temp_invoice = $this->invoice->calc()->getTempEntity();
|
||||
|
||||
DB::connection(config('database.default'))->rollBack();
|
||||
|
||||
if ($temp_invoice->balance < 0) {
|
||||
$this->message = 'Invoice balance cannot go negative';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -67,8 +67,6 @@ class ValidInvoicesRules implements Rule
|
||||
/////
|
||||
$inv = $inv_collection->firstWhere('id', $invoice['invoice_id']);
|
||||
|
||||
// $inv = Invoice::withTrashed()->whereId($invoice['invoice_id'])->first();
|
||||
|
||||
if (! $inv) {
|
||||
$this->error_msg = ctrans('texts.invoice_not_found');
|
||||
|
||||
|
@ -159,10 +159,6 @@ class InvoicePay extends Component
|
||||
|
||||
$this->setContext('fields', $fields); // $this->context['fields'] = $fields;
|
||||
|
||||
if ($company_gateway->always_show_required_fields) {
|
||||
return $this->required_fields = true;
|
||||
}
|
||||
|
||||
/** @var \App\Models\ClientContact $contact */
|
||||
$contact = $this->getContext()['contact'];
|
||||
|
||||
@ -184,6 +180,10 @@ class InvoicePay extends Component
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($company_gateway->always_show_required_fields) {
|
||||
return $this->required_fields = true;
|
||||
}
|
||||
|
||||
return $this->required_fields = false;
|
||||
|
||||
|
@ -96,13 +96,16 @@ class RequiredFields extends Component
|
||||
/** @var \App\Models\ClientContact $contact */
|
||||
$rff->check($contact);
|
||||
|
||||
if ($rff->unfilled_fields === 0) {
|
||||
if ($rff->unfilled_fields === 0 && !$this->company_gateway->always_show_required_fields)
|
||||
$this->dispatch('required-fields');
|
||||
}
|
||||
|
||||
if ($rff->unfilled_fields > 0) {
|
||||
else
|
||||
$this->is_loading = false;
|
||||
}
|
||||
|
||||
// }
|
||||
|
||||
// if ($rff->unfilled_fields > 0) {
|
||||
// $this->is_loading = false;
|
||||
// }
|
||||
}
|
||||
|
||||
public function handleSubmit(array $data)
|
||||
|
@ -128,6 +128,7 @@ class Account extends BaseModel
|
||||
'platform',
|
||||
'set_react_as_default_ap',
|
||||
'inapp_transaction_id',
|
||||
'e_invoicing_token',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
27
app/Models/EInvoicingToken.php
Normal file
27
app/Models/EInvoicingToken.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class EInvoicingToken extends Model
|
||||
{
|
||||
|
||||
protected $fillable = [
|
||||
'license',
|
||||
'token',
|
||||
'account_key',
|
||||
];
|
||||
}
|
@ -154,6 +154,11 @@ class CompanyPresenter extends EntityPresenter
|
||||
}
|
||||
}
|
||||
|
||||
public function phone()
|
||||
{
|
||||
return $this->entity->settings->phone ?? ' ';
|
||||
}
|
||||
|
||||
public function address1()
|
||||
{
|
||||
return $this->entity->settings->address1;
|
||||
|
@ -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',
|
||||
|
@ -212,11 +212,10 @@ class TaskRepository extends BaseRepository
|
||||
/**
|
||||
* Sorts the task status order IF the old status has changed between requests
|
||||
*
|
||||
* @param \stdCLass $old_task The old task object
|
||||
* @param Task $new_task The new Task model
|
||||
* @return void
|
||||
*/
|
||||
public function sortStatuses($old_task, $new_task)
|
||||
public function sortStatuses($new_task)
|
||||
{
|
||||
if (! $new_task->project()->exists()) {
|
||||
return;
|
||||
|
@ -102,6 +102,365 @@ class PaymentMeans implements PaymentMeansInterface
|
||||
'ZZZ' => 'Mutually defined',
|
||||
];
|
||||
|
||||
public static array $payment_means_requirements = [
|
||||
'1' => [], // Instrument not defined
|
||||
'2' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH credit
|
||||
'3' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH debit
|
||||
'4' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH demand debit reversal
|
||||
'5' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH demand credit reversal
|
||||
'6' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH demand credit
|
||||
'7' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH demand debit
|
||||
'8' => [], // Hold
|
||||
'9' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // National or regional clearing
|
||||
'10' => [], // In cash
|
||||
'11' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH savings credit reversal
|
||||
'12' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH savings debit reversal
|
||||
'13' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH savings credit
|
||||
'14' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH savings debit
|
||||
'15' => [
|
||||
'PayeeFinancialAccount.Name',
|
||||
'PayeeFinancialAccount.ProprietaryID'
|
||||
], // Bookentry credit
|
||||
'16' => [
|
||||
'PayeeFinancialAccount.Name',
|
||||
'PayeeFinancialAccount.ProprietaryID'
|
||||
], // Bookentry debit
|
||||
'17' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH demand CCD credit
|
||||
'18' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH demand CCD debit
|
||||
'19' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH demand CTP credit
|
||||
'20' => [], // Cheque
|
||||
'21' => [], // Banker's draft
|
||||
'22' => [], // Certified banker's draft
|
||||
'23' => [], // Bank cheque
|
||||
'24' => [], // Bill of exchange awaiting acceptance
|
||||
'25' => [], // Certified cheque
|
||||
'26' => [], // Local cheque
|
||||
'27' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH demand CTP debit
|
||||
'28' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH demand CTX credit
|
||||
'29' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH demand CTX debit
|
||||
'30' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Credit transfer
|
||||
'31' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Debit transfer
|
||||
'32' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH demand CCD+ credit
|
||||
'33' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH demand CCD+ debit
|
||||
'34' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH PPD
|
||||
'35' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH savings CCD credit
|
||||
'36' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH savings CCD debit
|
||||
'37' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH savings CTP credit
|
||||
'38' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH savings CTP debit
|
||||
'39' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH savings CTX credit
|
||||
'40' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH savings CTX debit
|
||||
'41' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH savings CCD+ credit
|
||||
'42' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Payment to bank account
|
||||
'43' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // ACH savings CCD+ debit
|
||||
'44' => [], // Accepted bill of exchange
|
||||
'45' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Referenced home-banking credit transfer
|
||||
'46' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Interbank debit transfer
|
||||
'47' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Home-banking debit transfer
|
||||
'48' => [
|
||||
'PayeeFinancialAccount.NetworkID',
|
||||
'PayeeFinancialAccount.ID'
|
||||
], // Bank card
|
||||
'49' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Direct debit
|
||||
'50' => [
|
||||
'PayeeFinancialAccount.Name'
|
||||
], // Payment by postgiro
|
||||
'51' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // FR, norme 6 97-Telereglement CFONB
|
||||
'52' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Urgent commercial payment
|
||||
'53' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Urgent Treasury Payment
|
||||
'54' => [
|
||||
'CardAccount.NetworkID',
|
||||
'CardAccount.PrimaryAccountNumberID',
|
||||
'CardAccount.HolderName'
|
||||
], // Credit card
|
||||
'55' => [
|
||||
'CardAccount.NetworkID',
|
||||
'CardAccount.PrimaryAccountNumberID',
|
||||
'CardAccount.HolderName'
|
||||
], // Debit card
|
||||
'56' => [
|
||||
'PayeeFinancialAccount.Name'
|
||||
], // Bankgiro
|
||||
'57' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Standing agreement
|
||||
'58' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // SEPA credit transfer
|
||||
'59' => [
|
||||
'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // SEPA direct debit
|
||||
'60' => [], // Promissory note
|
||||
'61' => [], // Promissory note signed by debtor
|
||||
'62' => [
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Promissory note signed by debtor and endorsed by bank
|
||||
'63' => [], // Promissory note signed by debtor and endorsed
|
||||
'64' => [
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Promissory note signed by bank
|
||||
'65' => [
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Promissory note signed by bank and endorsed by another
|
||||
'66' => [], // Promissory note signed by third party
|
||||
'67' => [
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Promissory note signed by third party and endorsed by bank
|
||||
'68' => [
|
||||
'PayeeFinancialAccount.ID'
|
||||
], // Online payment service
|
||||
'69' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Transfer Advice
|
||||
'70' => [], // Bill drawn by creditor on debtor
|
||||
'74' => [
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Bill drawn by creditor on bank
|
||||
'75' => [
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Bill drawn by creditor, endorsed by bank
|
||||
'76' => [
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Bill drawn by creditor on bank and endorsed
|
||||
'77' => [], // Bill drawn by creditor on third party
|
||||
'78' => [], // Bill drawn by creditor on third party, accepted
|
||||
'91' => [], // Not transferable banker's draft
|
||||
'92' => [], // Not transferable local cheque
|
||||
'93' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Reference giro
|
||||
'94' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Urgent giro
|
||||
'95' => [
|
||||
'PayeeFinancialAccount.ID',
|
||||
'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID'
|
||||
], // Free format giro
|
||||
'96' => [], // Requested method not used
|
||||
'97' => [
|
||||
'PayeeFinancialAccount.Name'
|
||||
], // Clearing between partners
|
||||
'ZZZ' => [], // Mutually defined
|
||||
];
|
||||
|
||||
|
||||
public static array $payment_means_requirements_codes = [
|
||||
'1' => [], // Instrument not defined
|
||||
'2' => ['iban', 'bic_swift'], // ACH credit
|
||||
'3' => ['payer_bank_account', 'iban', 'bic_swift'], // ACH debit
|
||||
'4' => ['payer_bank_account', 'iban', 'bic_swift'], // ACH demand debit reversal
|
||||
'5' => ['iban', 'bic_swift'], // ACH demand credit reversal
|
||||
'6' => ['iban', 'bic_swift'], // ACH demand credit
|
||||
'7' => ['payer_bank_account', 'iban', 'bic_swift'], // ACH demand debit
|
||||
'8' => [], // Hold
|
||||
'9' => ['iban', 'bic_swift'], // National or regional clearing
|
||||
'10' => [], // In cash
|
||||
'11' => ['iban', 'bic_swift'], // ACH savings credit reversal
|
||||
'12' => ['payer_bank_account', 'iban', 'bic_swift'], // ACH savings debit reversal
|
||||
'13' => ['iban', 'bic_swift'], // ACH savings credit
|
||||
'14' => ['payer_bank_account', 'iban', 'bic_swift'], // ACH savings debit
|
||||
'15' => ['account_holder', 'bsb_sort'], // Bookentry credit
|
||||
'16' => ['account_holder', 'bsb_sort'], // Bookentry debit
|
||||
'17' => ['iban', 'bic_swift'], // ACH demand CCD credit
|
||||
'18' => ['payer_bank_account', 'iban', 'bic_swift'], // ACH demand CCD debit
|
||||
'19' => ['iban', 'bic_swift'], // ACH demand CTP credit
|
||||
'20' => [], // Cheque
|
||||
'21' => [], // Banker's draft
|
||||
'22' => [], // Certified banker's draft
|
||||
'23' => [], // Bank cheque
|
||||
'24' => [], // Bill of exchange awaiting acceptance
|
||||
'25' => [], // Certified cheque
|
||||
'26' => [], // Local cheque
|
||||
'27' => ['payer_bank_account', 'iban', 'bic_swift'], // ACH demand CTP debit
|
||||
'28' => ['iban', 'bic_swift'], // ACH demand CTX credit
|
||||
'29' => ['payer_bank_account', 'iban', 'bic_swift'], // ACH demand CTX debit
|
||||
'30' => ['iban', 'bic_swift'], // Credit transfer
|
||||
'31' => ['iban', 'bic_swift'], // Debit transfer
|
||||
'32' => ['iban', 'bic_swift'], // ACH demand CCD+ credit
|
||||
'33' => ['payer_bank_account', 'iban', 'bic_swift'], // ACH demand CCD+ debit
|
||||
'34' => ['iban', 'bic_swift'], // ACH PPD
|
||||
'35' => ['iban', 'bic_swift'], // ACH savings CCD credit
|
||||
'36' => ['payer_bank_account', 'iban', 'bic_swift'], // ACH savings CCD debit
|
||||
'37' => ['iban', 'bic_swift'], // ACH savings CTP credit
|
||||
'38' => ['payer_bank_account', 'iban', 'bic_swift'], // ACH savings CTP debit
|
||||
'39' => ['iban', 'bic_swift'], // ACH savings CTX credit
|
||||
'40' => ['payer_bank_account', 'iban', 'bic_swift'], // ACH savings CTX debit
|
||||
'41' => ['iban', 'bic_swift'], // ACH savings CCD+ credit
|
||||
'42' => ['iban', 'bic_swift'], // Payment to bank account
|
||||
'43' => ['payer_bank_account', 'iban', 'bic_swift'], // ACH savings CCD+ debit
|
||||
'44' => [], // Accepted bill of exchange
|
||||
'45' => ['iban', 'bic_swift'], // Referenced home-banking credit transfer
|
||||
'46' => ['iban', 'bic_swift'], // Interbank debit transfer
|
||||
'47' => ['iban', 'bic_swift'], // Home-banking debit transfer
|
||||
'48' => ['card_type', 'card_number'], // Bank card
|
||||
'49' => ['payer_bank_account', 'iban', 'bic_swift'], // Direct debit
|
||||
'50' => ['account_holder'], // Payment by postgiro
|
||||
'51' => ['iban', 'bic_swift'], // FR, norme 6 97-Telereglement CFONB
|
||||
'52' => ['iban', 'bic_swift'], // Urgent commercial payment
|
||||
'53' => ['iban', 'bic_swift'], // Urgent Treasury Payment
|
||||
'54' => ['card_type', 'card_number', 'card_holder'], // Credit card
|
||||
'55' => ['card_type', 'card_number', 'card_holder'], // Debit card
|
||||
'56' => ['account_holder'], // Bankgiro
|
||||
'57' => ['iban', 'bic_swift'], // Standing agreement
|
||||
'58' => ['iban', 'bic_swift'], // SEPA credit transfer
|
||||
'59' => ['payer_bank_account', 'iban', 'bic_swift'], // SEPA direct debit
|
||||
'60' => [], // Promissory note
|
||||
'61' => [], // Promissory note signed by debtor
|
||||
'62' => ['bic_swift'], // Promissory note signed by debtor and endorsed by bank
|
||||
'63' => [], // Promissory note signed by debtor and endorsed
|
||||
'64' => ['bic_swift'], // Promissory note signed by bank
|
||||
'65' => ['bic_swift'], // Promissory note signed by bank and endorsed by another
|
||||
'66' => [], // Promissory note signed by third party
|
||||
'67' => ['bic_swift'], // Promissory note signed by third party and endorsed by bank
|
||||
'68' => ['iban'], // Online payment service
|
||||
'69' => ['iban', 'bic_swift'], // Transfer Advice
|
||||
'70' => [], // Bill drawn by creditor on debtor
|
||||
'74' => ['bic_swift'], // Bill drawn by creditor on bank
|
||||
'75' => ['bic_swift'], // Bill drawn by creditor, endorsed by bank
|
||||
'76' => ['bic_swift'], // Bill drawn by creditor on bank and endorsed
|
||||
'77' => [], // Bill drawn by creditor on third party
|
||||
'78' => [], // Bill drawn by creditor on third party, accepted
|
||||
'91' => [], // Not transferable banker's draft
|
||||
'92' => [], // Not transferable local cheque
|
||||
'93' => ['iban', 'bic_swift'], // Reference giro
|
||||
'94' => ['iban', 'bic_swift'], // Urgent giro
|
||||
'95' => ['iban', 'bic_swift'], // Free format giro
|
||||
'96' => [], // Requested method not used
|
||||
'97' => ['account_holder'], // Clearing between partners
|
||||
'ZZZ' => [], // Mutually defined
|
||||
];
|
||||
|
||||
public static array $payment_means_field_map = [
|
||||
'iban' => 'PayeeFinancialAccount.ID',
|
||||
'bic_swift' => 'PayeeFinancialAccount.FinancialInstitutionBranch.FinancialInstitution.ID',
|
||||
'payer_bank_account' => 'PaymentMandate.PayerFinancialAccount.ID',
|
||||
'account_holder' => 'PayeeFinancialAccount.Name',
|
||||
'bsb_sort' => 'PayeeFinancialAccount.ProprietaryID',
|
||||
'card_type' => 'CardAccount.NetworkID',
|
||||
'card_number' => 'CardAccount.PrimaryAccountNumberID',
|
||||
'card_holder' => 'CardAccount.HolderName'
|
||||
];
|
||||
|
||||
|
||||
public string $code = '1';
|
||||
|
||||
public ?string $information = null;
|
||||
@ -197,6 +556,6 @@ class PaymentMeans implements PaymentMeansInterface
|
||||
|
||||
public static function getPaymentMeansCodelist()
|
||||
{
|
||||
return array_keys(self::$payment_means_codelist);
|
||||
return array_keys(self::$payment_means_requirements_codes);
|
||||
}
|
||||
}
|
||||
|
@ -50,4 +50,10 @@ class AccountingCustomerParty
|
||||
$this->public_identifiers = $public_identifiers;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addPublicIdentifiers($public_identifier): self
|
||||
{
|
||||
$this->public_identifiers[] = $public_identifier;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -2,25 +2,28 @@
|
||||
|
||||
namespace App\Services\EDocument\Gateway\Storecove\Models;
|
||||
|
||||
use Symfony\Component\Serializer\Attribute\Context;
|
||||
use Symfony\Component\Serializer\Attribute\SerializedName;
|
||||
use Symfony\Component\Serializer\Attribute\SerializedPath;
|
||||
|
||||
class AllowanceCharges
|
||||
{
|
||||
// #[SerializedPath('[cbc:Amount][#]')]
|
||||
public ?float $amount_excluding_vat;
|
||||
|
||||
#[Context(['input_format' => 'float'])]
|
||||
#[SerializedPath('[cbc:Amount][#]')]
|
||||
public ?string $amount_excluding_vat;
|
||||
public ?float $amount_excluding_tax;
|
||||
|
||||
#[Context(['input_format' => 'float'])]
|
||||
#[SerializedPath('[cbc:BaseAmount][#]')]
|
||||
public ?float $base_amount_excluding_tax;
|
||||
|
||||
// #[SerializedPath('[cbc:Amount][#]')]
|
||||
public ?float $amount_including_tax;
|
||||
|
||||
// #[SerializedPath('[cbc:BaseAmount][#]')]
|
||||
public ?string $amount_excluding_tax;
|
||||
|
||||
#[SerializedPath('[cbc:BaseAmount][#]')]
|
||||
public ?string $base_amount_excluding_tax;
|
||||
|
||||
#[SerializedPath('[cbc:Amount][@currencyID]')]
|
||||
public ?string $amount_including_tax;
|
||||
|
||||
#[SerializedPath('[cbc:BaseAmount][@currencyID]')]
|
||||
public ?string $base_amount_including_tax;
|
||||
public ?float $base_amount_including_tax;
|
||||
|
||||
// #[SerializedPath('[cac:TaxCategory]')]
|
||||
// public ?Tax $tax;
|
||||
@ -39,11 +42,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 +63,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 +106,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;
|
||||
|
@ -40,7 +40,7 @@ class Invoice
|
||||
|
||||
#[SerializedPath('[cac:AllowanceCharge]')]
|
||||
/** @var AllowanceCharges[] */
|
||||
public array $allowance_charges;
|
||||
public ?array $allowance_charges = [];
|
||||
|
||||
//this is an experimental prop
|
||||
// #[SerializedPath('[cac:LegalMonetaryTotal][cbc:TaxInclusiveAmount][#]')]
|
||||
@ -67,7 +67,7 @@ class Invoice
|
||||
// /** @var ?\DateTime */
|
||||
#[SerializedPath('[cbc:DueDate]')]
|
||||
// #[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
||||
public $due_date;
|
||||
public ?string $due_date;
|
||||
|
||||
//may need something custom for this
|
||||
public ?string $invoice_period;
|
||||
@ -166,7 +166,7 @@ class Invoice
|
||||
|
||||
public function __construct(
|
||||
?string $invoice_number,
|
||||
$issue_date,
|
||||
?string $issue_date,
|
||||
?AccountingCustomerParty $accounting_customer_party,
|
||||
?array $invoice_lines,
|
||||
?string $accounting_cost,
|
||||
@ -176,14 +176,14 @@ class Invoice
|
||||
?string $accounting_currency_tax_amount_currency,
|
||||
?AccountingSupplierParty $accounting_supplier_party,
|
||||
?array $allowance_charges,
|
||||
?string $amount_including_tax,
|
||||
?string $amount_including_vat,
|
||||
?float $amount_including_tax,
|
||||
?float $amount_including_vat,
|
||||
?array $attachments,
|
||||
?bool $consumer_tax_mode,
|
||||
?Delivery $delivery,
|
||||
?DeliveryTerms $delivery_terms,
|
||||
?string $document_currency_code,
|
||||
$due_date,
|
||||
?string $due_date,
|
||||
?string $invoice_period,
|
||||
?array $issue_reasons,
|
||||
?string $issue_time,
|
||||
@ -335,12 +335,12 @@ class Invoice
|
||||
return $this->allowance_charges;
|
||||
}
|
||||
|
||||
public function getAmountIncludingTax(): ?string
|
||||
public function getAmountIncludingTax(): ?float
|
||||
{
|
||||
return $this->amount_including_tax;
|
||||
}
|
||||
|
||||
public function getAmountIncludingVat(): ?string
|
||||
public function getAmountIncludingVat(): ?float
|
||||
{
|
||||
return $this->amount_including_vat;
|
||||
}
|
||||
@ -638,13 +638,13 @@ class Invoice
|
||||
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 setAmountIncludingVat(?string $amount_including_vat): self
|
||||
public function setAmountIncludingVat(?float $amount_including_vat): self
|
||||
{
|
||||
$this->amount_including_vat = $amount_including_vat;
|
||||
return $this;
|
||||
@ -683,7 +683,7 @@ class Invoice
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDueDate($due_date): self
|
||||
public function setDueDate(?string $due_date): self
|
||||
{
|
||||
$this->due_date = $due_date;
|
||||
return $this;
|
||||
|
@ -38,20 +38,20 @@ class InvoiceLines
|
||||
|
||||
#[SerializedPath('[cac:AllowanceCharge]')]
|
||||
/** @var AllowanceCharges[] */ //todo
|
||||
public ?array $charges;
|
||||
public ?array $allowance_charges;
|
||||
|
||||
#[SerializedPath('[cbc:LineExtensionAmount][#]')]
|
||||
public ?string $amount_excluding_vat;
|
||||
public ?float $amount_excluding_vat;
|
||||
|
||||
#[SerializedPath('[cbc:TaxExclusiveAmount][#]')]
|
||||
public ?string $amount_excluding_tax;
|
||||
#[SerializedPath('[cac:Price][cbc:PriceAmount][value]')]
|
||||
public ?float $amount_excluding_tax;
|
||||
|
||||
#[SerializedPath('[cbc:TaxInclusiveAmount][#]')]
|
||||
public ?string $amount_including_tax;
|
||||
public ?float $amount_including_tax;
|
||||
|
||||
#[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,10 +98,10 @@ class InvoiceLines
|
||||
?float $quantity,
|
||||
?float $base_quantity,
|
||||
?string $quantity_unit_code,
|
||||
?array $charges,
|
||||
?string $amount_excluding_vat,
|
||||
?string $amount_excluding_tax,
|
||||
?string $amount_including_tax,
|
||||
?array $allowance_charges,
|
||||
?float $amount_excluding_vat,
|
||||
?float $amount_excluding_tax,
|
||||
?float $amount_including_tax,
|
||||
?array $taxes_duties_fees,
|
||||
?string $accounting_cost,
|
||||
?array $references,
|
||||
@ -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,20 +188,20 @@ class InvoiceLines
|
||||
*/
|
||||
public function getAllowanceCharges(): ?array
|
||||
{
|
||||
return $this->charges;
|
||||
return $this->allowance_charges;
|
||||
}
|
||||
|
||||
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 getAmountIncludingTax(): ?string
|
||||
public function getAmountIncludingTax(): ?float
|
||||
{
|
||||
return $this->amount_including_tax;
|
||||
}
|
||||
@ -320,27 +320,27 @@ 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;
|
||||
}
|
||||
|
||||
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 setAmountIncludingTax(?string $amount_including_tax): self
|
||||
public function setAmountIncludingTax(?float $amount_including_tax): self
|
||||
{
|
||||
$this->amount_including_tax = $amount_including_tax;
|
||||
return $this;
|
||||
|
@ -5,15 +5,15 @@ namespace App\Services\EDocument\Gateway\Storecove\Models;
|
||||
class Tax
|
||||
{
|
||||
public ?string $country;
|
||||
public ?string $amount;
|
||||
public ?string $percentage;
|
||||
public ?float $amount;
|
||||
public ?float $percentage;
|
||||
public ?string $category;
|
||||
public ?string $type;
|
||||
|
||||
public function __construct(
|
||||
?string $country,
|
||||
?string $amount,
|
||||
?string $percentage,
|
||||
?float $amount,
|
||||
?float $percentage,
|
||||
?string $category,
|
||||
?string $type
|
||||
) {
|
||||
@ -29,12 +29,12 @@ class Tax
|
||||
return $this->country;
|
||||
}
|
||||
|
||||
public function getAmount(): ?string
|
||||
public function getAmount(): ?float
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
public function getPercentage(): ?string
|
||||
public function getPercentage(): ?float
|
||||
{
|
||||
return $this->percentage;
|
||||
}
|
||||
@ -55,13 +55,13 @@ class Tax
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAmount(?string $amount): self
|
||||
public function setAmount(?float $amount): self
|
||||
{
|
||||
$this->amount = $amount;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPercentage(?string $percentage): self
|
||||
public function setPercentage(?float $percentage): self
|
||||
{
|
||||
$this->percentage = $percentage;
|
||||
return $this;
|
||||
|
@ -9,24 +9,24 @@ class TaxSubtotals
|
||||
{
|
||||
|
||||
#[SerializedPath('[cbc:TaxAmount][#]')]
|
||||
public ?string $tax_amount;
|
||||
public ?float $tax_amount;
|
||||
|
||||
public ?string $country;
|
||||
|
||||
#[SerializedPath('[cbc:TaxableAmount][#]')]
|
||||
public ?string $taxable_amount;
|
||||
public ?float $taxable_amount;
|
||||
|
||||
#[SerializedPath('[cac:TaxCategory][cbc:Percent]')]
|
||||
public ?string $percentage;
|
||||
public ?float $percentage;
|
||||
|
||||
#[SerializedPath('[cac:TaxCategory][cbc:ID][#]')]
|
||||
public ?string $category;
|
||||
|
||||
public function __construct(
|
||||
?string $tax_amount,
|
||||
?float $tax_amount,
|
||||
?string $country,
|
||||
?string $taxable_amount,
|
||||
?string $percentage,
|
||||
?float $taxable_amount,
|
||||
?float $percentage,
|
||||
?string $category
|
||||
) {
|
||||
$this->tax_amount = $tax_amount;
|
||||
@ -36,7 +36,7 @@ class TaxSubtotals
|
||||
$this->category = $category;
|
||||
}
|
||||
|
||||
public function getTaxAmount(): ?string
|
||||
public function getTaxAmount(): ?float
|
||||
{
|
||||
return $this->tax_amount;
|
||||
}
|
||||
@ -46,12 +46,12 @@ class TaxSubtotals
|
||||
return $this->country;
|
||||
}
|
||||
|
||||
public function getTaxableAmount(): ?string
|
||||
public function getTaxableAmount(): ?float
|
||||
{
|
||||
return $this->taxable_amount;
|
||||
}
|
||||
|
||||
public function getPercentage(): ?string
|
||||
public function getPercentage(): ?float
|
||||
{
|
||||
return $this->percentage;
|
||||
}
|
||||
@ -61,7 +61,7 @@ class TaxSubtotals
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
public function setTaxAmount(?string $tax_amount): self
|
||||
public function setTaxAmount(?float $tax_amount): self
|
||||
{
|
||||
$this->tax_amount = $tax_amount;
|
||||
return $this;
|
||||
@ -73,13 +73,13 @@ class TaxSubtotals
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTaxableAmount(?string $taxable_amount): self
|
||||
public function setTaxableAmount(?float $taxable_amount): self
|
||||
{
|
||||
$this->taxable_amount = $taxable_amount;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPercentage(?string $percentage): self
|
||||
public function setPercentage(?float $percentage): self
|
||||
{
|
||||
$this->percentage = $percentage;
|
||||
return $this;
|
||||
|
@ -2,16 +2,18 @@
|
||||
|
||||
namespace App\Services\EDocument\Gateway\Storecove\Models;
|
||||
|
||||
use Symfony\Component\Serializer\Attribute\Context;
|
||||
use Symfony\Component\Serializer\Attribute\SerializedName;
|
||||
use Symfony\Component\Serializer\Attribute\SerializedPath;
|
||||
|
||||
class TaxesDutiesFees
|
||||
{
|
||||
public ?string $country; //need to run postprocessing on this
|
||||
public ?string $amount;
|
||||
public ?float $amount;
|
||||
|
||||
#[Context(['input_format' => 'float'])]
|
||||
#[SerializedName('cbc:Percent')]
|
||||
public ?string $percentage;
|
||||
public ?float $percentage = 0;
|
||||
|
||||
#[SerializedPath('[cbc:ID][#]')]
|
||||
public ?string $category;
|
||||
@ -21,8 +23,8 @@ class TaxesDutiesFees
|
||||
|
||||
public function __construct(
|
||||
?string $country,
|
||||
?string $amount,
|
||||
?string $percentage,
|
||||
?float $amount,
|
||||
?float $percentage,
|
||||
?string $category,
|
||||
?string $type
|
||||
) {
|
||||
@ -38,12 +40,12 @@ class TaxesDutiesFees
|
||||
return $this->country;
|
||||
}
|
||||
|
||||
public function getAmount(): ?string
|
||||
public function getAmount(): ?float
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
public function getPercentage(): ?string
|
||||
public function getPercentage(): ?float
|
||||
{
|
||||
return $this->percentage;
|
||||
}
|
||||
@ -64,13 +66,13 @@ class TaxesDutiesFees
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAmount(?string $amount): self
|
||||
public function setAmount(?float $amount): self
|
||||
{
|
||||
$this->amount = $amount;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPercentage(?string $percentage): self
|
||||
public function setPercentage(?float $percentage): self
|
||||
{
|
||||
$this->percentage = $percentage;
|
||||
return $this;
|
||||
|
@ -574,6 +574,25 @@ class Mutator implements MutatorInterface
|
||||
|
||||
/////////////// Storecove Helpers ///////////////
|
||||
|
||||
public function setClientRoutingCode(): self
|
||||
{
|
||||
$code = $this->getClientRoutingCode();
|
||||
|
||||
if(strlen($this->invoice->client->vat_number) < 2 || strlen($this->invoice->client->id_number) < 2)
|
||||
return $this->setEmailRouting($this->invoice->client->present()->email());
|
||||
|
||||
if($this->invoice->client->country->iso_3166_2 == 'FR')
|
||||
$vat = $this->invoice->client->id_number;
|
||||
else
|
||||
$vat = $this->invoice->client->vat_number;
|
||||
|
||||
$this->setStorecoveMeta($this->buildRouting([
|
||||
["scheme" => $code, "id" => $vat]
|
||||
]));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* getClientRoutingCode
|
||||
*
|
||||
@ -581,7 +600,7 @@ class Mutator implements MutatorInterface
|
||||
*/
|
||||
private function getClientRoutingCode(): string
|
||||
{
|
||||
return (new StorecoveRouter())->resolveRouting($this->invoice->client->country->iso_3166_2, $this->invoice->client->classification);
|
||||
return (new StorecoveRouter())->setInvoice($this->invoice)->resolveRouting($this->invoice->client->country->iso_3166_2, $this->invoice->client->classification);
|
||||
}
|
||||
|
||||
|
||||
|
@ -74,11 +74,18 @@ class Storecove
|
||||
*/
|
||||
public function build($model): mixed
|
||||
{
|
||||
return
|
||||
// return
|
||||
$this->adapter
|
||||
->transform($model)
|
||||
->decorate()
|
||||
->validate();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getResult(): array
|
||||
{
|
||||
return $this->adapter->getDocument();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -141,33 +148,23 @@ class Storecove
|
||||
/**
|
||||
* Unused as yet
|
||||
* @todo
|
||||
* @param mixed $document
|
||||
* @return string|bool
|
||||
* @param array $payload
|
||||
*/
|
||||
public function sendJsonDocument($document)
|
||||
public function sendJsonDocument(array $payload)
|
||||
{
|
||||
|
||||
$payload = [
|
||||
// "legalEntityId" => 290868,
|
||||
"idempotencyGuid" => \Illuminate\Support\Str::uuid(),
|
||||
"routing" => [
|
||||
"eIdentifiers" => [],
|
||||
"emails" => ["david@invoiceninja.com"]
|
||||
],
|
||||
"document" => [
|
||||
"documentType" => "invoice",
|
||||
"invoice" => $document,
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
$uri = "document_submissions";
|
||||
|
||||
$r = $this->httpClient($uri, (HttpVerb::POST)->value, $payload, $this->getHeaders());
|
||||
|
||||
if($r->successful()) {
|
||||
nlog("sent! GUID = {$r->json()['guid']}");
|
||||
return $r->json()['guid'];
|
||||
}
|
||||
|
||||
nlog($payload);
|
||||
nlog($r->body());
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\EDocument\Gateway\Storecove;
|
||||
|
||||
@ -19,10 +28,10 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\Invoice as PeppolInvoice;
|
||||
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
|
||||
use App\Services\EDocument\Gateway\Transformers\StorecoveTransformer;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
|
||||
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
|
||||
use App\Services\EDocument\Gateway\Storecove\PeppolToStorecoveNormalizer;
|
||||
use App\Services\EDocument\Standards\Peppol;
|
||||
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
|
||||
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
|
||||
|
||||
@ -41,66 +50,8 @@ class StorecoveAdapter
|
||||
|
||||
private string $nexus;
|
||||
|
||||
/**
|
||||
* transform
|
||||
*
|
||||
* @param \App\Models\Invoice $invoice
|
||||
* @return self
|
||||
*/
|
||||
public function transform($invoice): self
|
||||
{
|
||||
$this->ninja_invoice = $invoice;
|
||||
|
||||
$this->buildNexus();
|
||||
|
||||
$context = [
|
||||
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d',
|
||||
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
|
||||
];
|
||||
|
||||
$serializer = $this->getSerializer();
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$this->storecove_invoice = $serializer->deserialize($invoice->e_invoice, Invoice::class, 'json', $context);
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
public function decorate(): self
|
||||
{
|
||||
//set all taxmap countries - resolve the taxing country
|
||||
$lines = $this->storecove_invoice->getInvoiceLines();
|
||||
|
||||
foreach($lines as $line)
|
||||
{
|
||||
foreach($line->taxes_duties_fees as &$tax)
|
||||
{
|
||||
$tax->country = $this->nexus;
|
||||
}
|
||||
unset($tax);
|
||||
}
|
||||
|
||||
$this->storecove_invoice->setInvoiceLines($lines);
|
||||
|
||||
$tax_subtotals = $this->storecove_invoice->getTaxSubtotals();
|
||||
|
||||
foreach($tax_subtotals as &$tax)
|
||||
{
|
||||
$tax->country = $this->nexus;
|
||||
}
|
||||
unset($tax);
|
||||
|
||||
$this->storecove_invoice->setTaxSubtotals($tax_subtotals);
|
||||
//configure identifiers
|
||||
|
||||
//set additional identifier if required (ie de => FR with FR vat)
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function validate(): self
|
||||
{
|
||||
// $this->valid_document
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -113,10 +64,203 @@ class StorecoveAdapter
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public function deserialize($storecove_object)
|
||||
{
|
||||
|
||||
$context = [
|
||||
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d',
|
||||
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
|
||||
];
|
||||
|
||||
$serializer = $this->getSerializer();
|
||||
|
||||
$obj['Invoice'] = $storecove_object['document']['invoice'];
|
||||
|
||||
$storecove_object = $serializer->normalize($obj, null, [\Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::SKIP_NULL_VALUES => true]);
|
||||
|
||||
// return $storecove_object;
|
||||
// $storecove_object = $serializer->encode($storecove_object, 'json', $context);
|
||||
// return $storecove_object;
|
||||
// return $data;
|
||||
// $object = $serializer->denormalize(json_encode($storecove_object['document']['invoice']), \App\Services\EDocument\Gateway\Storecove\Models\Invoice::class, 'json', [\Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::SKIP_NULL_VALUES => true]);
|
||||
|
||||
// return $storecove_object;
|
||||
|
||||
return $serializer->deserialize(json_encode($storecove_object), \App\Services\EDocument\Gateway\Storecove\Models\Invoice::class, 'json', $context);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* transform
|
||||
*
|
||||
* @param \App\Models\Invoice $invoice
|
||||
* @return self
|
||||
*/
|
||||
public function transform($invoice): self
|
||||
{
|
||||
$this->ninja_invoice = $invoice;
|
||||
|
||||
$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();
|
||||
|
||||
$context = [
|
||||
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d',
|
||||
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
|
||||
];
|
||||
|
||||
$e = new \InvoiceNinja\EInvoice\EInvoice();
|
||||
$peppolInvoice = $e->decode('Peppol', $p, 'xml');
|
||||
|
||||
$parent = \App\Services\EDocument\Gateway\Storecove\Models\Invoice::class;
|
||||
$peppolInvoice = $e->encode($peppolInvoice, 'json');
|
||||
$this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'json', $context);
|
||||
|
||||
$this->buildNexus();
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
public function getNexus(): string
|
||||
{
|
||||
return $this->nexus;
|
||||
}
|
||||
|
||||
public function decorate(): self
|
||||
{
|
||||
//set all taxmap countries - resolve the taxing country
|
||||
$lines = $this->storecove_invoice->getInvoiceLines();
|
||||
|
||||
foreach($lines as &$line)
|
||||
{
|
||||
if(isset($line->taxes_duties_fees))
|
||||
{
|
||||
foreach($line->taxes_duties_fees as &$tax)
|
||||
{
|
||||
$tax->country = $this->nexus;
|
||||
$tax->percentage = $tax->percentage ?? 0;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
$this->storecove_invoice->setInvoiceLines($lines);
|
||||
|
||||
$tax_subtotals = $this->storecove_invoice->getTaxSubtotals();
|
||||
|
||||
foreach($tax_subtotals as &$tax)
|
||||
{
|
||||
$tax->country = $this->nexus;
|
||||
$tax->percentage = $tax->percentage ?? 0;
|
||||
|
||||
if (property_exists($tax, 'category'))
|
||||
$tax->category = $this->tranformTaxCode($tax->category);
|
||||
|
||||
}
|
||||
unset($tax);
|
||||
|
||||
$this->storecove_invoice->setTaxSubtotals($tax_subtotals);
|
||||
//configure identifiers
|
||||
|
||||
//update payment means codes to storecove equivalents
|
||||
$payment_means = $this->storecove_invoice->getPaymentMeansArray();
|
||||
|
||||
foreach($payment_means as &$pm)
|
||||
{
|
||||
$pm->code = $this->transformPaymentMeansCode($pm->code);
|
||||
}
|
||||
|
||||
$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;
|
||||
$tax->percentage = $tax->percentage ?? 0;
|
||||
|
||||
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');
|
||||
|
||||
//resolve and set the public identifier for the customer
|
||||
$accounting_customer_party = $this->storecove_invoice->getAccountingCustomerParty();
|
||||
|
||||
if(strlen($this->ninja_invoice->client->vat_number) > 2)
|
||||
{
|
||||
// $id = str_ireplace("fr","", $this->ninja_invoice->client->vat_number);
|
||||
$id = $this->ninja_invoice->client->vat_number;
|
||||
$scheme = $this->storecove->router->setInvoice($this->ninja_invoice)->resolveTaxScheme($this->ninja_invoice->client->country->iso_3166_2, $this->ninja_invoice->client->classification ?? 'individual');
|
||||
$pi = new \App\Services\EDocument\Gateway\Storecove\Models\PublicIdentifiers($scheme, $id);
|
||||
$accounting_customer_party->addPublicIdentifiers($pi);
|
||||
$this->storecove_invoice->setAccountingCustomerParty($accounting_customer_party);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -125,11 +269,8 @@ class StorecoveAdapter
|
||||
|
||||
$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,
|
||||
@ -150,7 +291,42 @@ class StorecoveAdapter
|
||||
|
||||
return $serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the document and appends an errors prop
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDocument(): mixed
|
||||
{
|
||||
$serializer = $this->getSerializer();
|
||||
|
||||
$context = [
|
||||
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d',
|
||||
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
|
||||
];
|
||||
|
||||
$s_invoice = $serializer->encode($this->storecove_invoice, 'json', $context);
|
||||
|
||||
$s_invoice = json_decode($s_invoice, true);
|
||||
|
||||
$s_invoice = $this->removeEmptyValues($s_invoice);
|
||||
|
||||
$data = [
|
||||
'errors' => $this->getErrors(),
|
||||
'document' => $s_invoice,
|
||||
];
|
||||
|
||||
return $data;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* RemoveEmptyValues
|
||||
*
|
||||
* @param array $array
|
||||
* @return array
|
||||
*/
|
||||
private function removeEmptyValues(array $array): array
|
||||
{
|
||||
foreach ($array as $key => $value) {
|
||||
@ -169,7 +345,7 @@ class StorecoveAdapter
|
||||
|
||||
private function buildNexus(): self
|
||||
{
|
||||
|
||||
nlog("building nexus");
|
||||
//Calculate nexus
|
||||
$company_country_code = $this->ninja_invoice->company->country()->iso_3166_2;
|
||||
$client_country_code = $this->ninja_invoice->client->country->iso_3166_2;
|
||||
@ -177,27 +353,128 @@ class StorecoveAdapter
|
||||
$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)) {
|
||||
} elseif (in_array($client_country_code, $eu_countries)) {
|
||||
|
||||
//EU Sale
|
||||
|
||||
// Invalid VAT number = seller country nexus
|
||||
if(!$this->ninja_invoice->client->has_valid_vat_number)
|
||||
//EU Sale where Company country != Client Country
|
||||
|
||||
// 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';
|
||||
|
||||
|
||||
// B2C, under threshold, no Company VAT Registerd - must charge origin country VAT
|
||||
if ($is_b2c && !$is_over_threshold && strlen($this->ninja_invoice->company->settings->vat_number) < 2) {
|
||||
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->nexus = $client_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;
|
||||
}
|
||||
nlog("B2C");
|
||||
$this->nexus = $client_country_code;
|
||||
$this->setupDestinationVAT($client_country_code);
|
||||
} else {
|
||||
nlog("under threshold origin 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;
|
||||
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;
|
||||
}
|
||||
|
||||
//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->setInvoice($this->ninja_invoice)->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;
|
||||
}
|
||||
|
||||
private function tranformTaxCode(string $code): ?string
|
||||
{
|
||||
return match($code){
|
||||
'S' => 'standard',
|
||||
'Z' => 'zero_rated',
|
||||
'E' => 'exempt',
|
||||
'AE' => 'reverse_charge',
|
||||
'K' => 'intra_community',
|
||||
'G' => 'export',
|
||||
'O' => 'outside_scope',
|
||||
'L' => 'cgst',
|
||||
'I' => 'igst',
|
||||
'SS' => 'sgst',
|
||||
'B' => 'deemed_supply',
|
||||
'SR' => 'srca_s',
|
||||
'SC' => 'srca_c',
|
||||
'NR' => 'not_registered',
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
private function transformPaymentMeansCode(?string $code): string
|
||||
{
|
||||
return match($code){
|
||||
'30' => 'credit_transfer',
|
||||
'58' => 'sepa_credit_transfer',
|
||||
'31' => 'debit_transfer',
|
||||
'49' => 'direct_debit',
|
||||
'59' => 'sepa_direct_debit',
|
||||
'48' => 'card', // Generic card payment
|
||||
'54' => 'bank_card',
|
||||
'55' => 'credit_card',
|
||||
'57' => 'standing_agreement',
|
||||
'10' => 'cash',
|
||||
'20' => 'bank_cheque',
|
||||
'21' => 'cashiers_cheque',
|
||||
'97' => 'aunz_npp',
|
||||
'98' => 'aunz_npp_payid',
|
||||
'99' => 'aunz_npp_payto',
|
||||
'71' => 'aunz_bpay',
|
||||
'72' => 'aunz_postbillpay',
|
||||
'73' => 'aunz_uri',
|
||||
'50' => 'se_bankgiro',
|
||||
'51' => 'se_plusgiro',
|
||||
'74' => 'sg_giro',
|
||||
'75' => 'sg_card',
|
||||
'76' => 'sg_paynow',
|
||||
'77' => 'it_mav',
|
||||
'78' => 'it_pagopa',
|
||||
'42' => 'nl_ga_beneficiary',
|
||||
'43' => 'nl_ga_gaccount',
|
||||
'1' => 'undefined', // Instrument not defined
|
||||
default => 'undefined',
|
||||
};
|
||||
|
||||
}
|
||||
}
|
@ -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"],
|
||||
@ -96,6 +96,8 @@ class StorecoveRouter
|
||||
"Other" => ["B","DUNS, GLN, LEI",false,"DUNS, GLN, LEI"],
|
||||
];
|
||||
|
||||
private $invoice;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
@ -110,11 +112,7 @@ class StorecoveRouter
|
||||
public function resolveRouting(string $country, ?string $classification = 'business'): string
|
||||
{
|
||||
$rules = $this->routing_rules[$country];
|
||||
|
||||
if(is_array($rules) && !is_array($rules[0])) {
|
||||
return $rules[3];
|
||||
}
|
||||
|
||||
|
||||
$code = 'B';
|
||||
|
||||
match($classification) {
|
||||
@ -123,6 +121,22 @@ class StorecoveRouter
|
||||
"individual" => $code = "C",
|
||||
default => $code = "B",
|
||||
};
|
||||
|
||||
if ($this->invoice && $country == 'FR') {
|
||||
|
||||
if ($code == 'B' && strlen($this->invoice->client->id_number) == 9) {
|
||||
return 'FR:SIRENE';
|
||||
} elseif ($code == 'B' && strlen($this->invoice->client->id_number) == 14) {
|
||||
return 'FR:SIRET';
|
||||
} elseif ($code == 'G') {
|
||||
return '0009:11000201100044';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (is_array($rules) && !is_array($rules[0])) {
|
||||
return $rules[3];
|
||||
}
|
||||
|
||||
foreach($rules as $rule) {
|
||||
if(stripos($rule[0], $code) !== false) {
|
||||
@ -133,6 +147,11 @@ class StorecoveRouter
|
||||
return $rules[0][3];
|
||||
}
|
||||
|
||||
public function setInvoice($invoice):self
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* resolveTaxScheme
|
||||
*
|
||||
@ -154,6 +173,17 @@ class StorecoveRouter
|
||||
default => $code = "B",
|
||||
};
|
||||
|
||||
// if($this->invoice && $country == 'FR'){
|
||||
|
||||
// if($code == 'B' && strlen(trim(str_ireplace("fr", "", $this->invoice->client->vat_number))) == 9)
|
||||
// return 'FR:SIRENE';
|
||||
// elseif($code == 'B' && strlen(trim(str_ireplace("fr", "", $this->invoice->client->vat_number))) == 14)
|
||||
// return 'FR:SIRET';
|
||||
// elseif($code == 'G')
|
||||
// return 'FR:SIRET'; //@todo need to add customerAssignedAccountIdValue
|
||||
|
||||
// }
|
||||
|
||||
//single array
|
||||
if(is_array($rules) && !is_array($rules[0])) {
|
||||
return $rules[2];
|
||||
|
@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\EDocument\Gateway\Transformers;
|
||||
|
||||
use App\Helpers\Invoice\Taxer;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\Tax;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\Party;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\Address;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\Contact;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\References;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\InvoiceLines;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\PaymentMeans;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\TaxSubtotals;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\AllowanceCharges;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\AccountingCustomerParty;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\AccountingSupplierParty;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\Invoice as StorecoveInvoice;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class StorecoveNinjaTransformer implements TransformerInterface
|
||||
{
|
||||
public function transform(mixed $invoice)
|
||||
{
|
||||
$document = data_get($invoice, 'document.invoice');
|
||||
}
|
||||
|
||||
public function getInvoice()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function toJson()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\EDocument\Gateway\Transformers;
|
||||
|
||||
use App\Helpers\Invoice\Taxer;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\Tax;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\Party;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\Address;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\Contact;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\References;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\InvoiceLines;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\PaymentMeans;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\TaxSubtotals;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\AllowanceCharges;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\AccountingCustomerParty;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\AccountingSupplierParty;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\Invoice as StorecoveInvoice;
|
||||
use App\Services\EDocument\Gateway\Storecove\Storecove;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class StorecoveTransformer implements TransformerInterface
|
||||
{
|
||||
use Taxer;
|
||||
use NumberFormatter;
|
||||
|
||||
private StorecoveInvoice $s_invoice;
|
||||
|
||||
private array $tax_map = [];
|
||||
|
||||
public function setInvoice($s_invoice): self
|
||||
{
|
||||
$this->s_invoice = $s_invoice;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getInvoice(): StorecoveInvoice
|
||||
{
|
||||
return $this->s_invoice;
|
||||
}
|
||||
|
||||
public function createNewStorecoveInvoice(): self
|
||||
{
|
||||
$this->s_invoice = (new \ReflectionClass(StorecoveInvoice::class))->newInstanceWithoutConstructor();
|
||||
return $this;
|
||||
}
|
||||
|
||||
//$invoice = inbound peppol
|
||||
public function transform(mixed $invoice)
|
||||
{
|
||||
|
||||
$this->s_invoice->setTaxPointDate($invoice->IssueDate->format('Y-m-d'));
|
||||
|
||||
// Only use this if we are billing for services between a period.
|
||||
if (isset($invoice->InvoicePeriod[0]) &&
|
||||
isset($invoice->InvoicePeriod[0]->StartDate) &&
|
||||
isset($invoice->InvoicePeriod[0]->EndDate)) {
|
||||
$this->s_invoice->setInvoicePeriod("{$invoice->InvoicePeriod[0]->StartDate->format('Y-m-d')} - {$invoice->InvoicePeriod[0]->EndDate->format('Y-m-d')}");
|
||||
}
|
||||
|
||||
$lines = [];
|
||||
|
||||
foreach($invoice->InvoiceLine as $peppolLine)
|
||||
{
|
||||
|
||||
// Tax handling
|
||||
if(isset($peppolLine->Item->ClassifiedTaxCategory) && is_array($peppolLine->Item->ClassifiedTaxCategory)){
|
||||
foreach($peppolLine->Item->ClassifiedTaxCategory as $ctc)
|
||||
{
|
||||
$this->setTaxMap($ctc, $peppolLine, $invoice);
|
||||
}
|
||||
}
|
||||
|
||||
// //discounts
|
||||
// if(isset($peppolLine->Price->AllowanceCharge) && is_array($peppolLine->Price->AllowanceCharge)){
|
||||
|
||||
// foreach($peppolLine->Price->AllowanceCharge as $allowance)
|
||||
// {
|
||||
// $reason = isset($allowance->ChargeIndicator) ? ctrans('texts.discount') : ctrans('texts.fee');
|
||||
// $amount = $allowance->Amount->amount;
|
||||
|
||||
// $ac = new AllowanceCharges(reason: $reason, amountExcludingTax: $amount);
|
||||
// $line->addAllowanceCharge($ac);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// $lines[] = $line;
|
||||
|
||||
// }
|
||||
|
||||
// $this->s_invoice->invoiceLines = $lines;
|
||||
|
||||
|
||||
}
|
||||
|
||||
$sub_taxes = collect($this->tax_map)
|
||||
->groupBy('percentage')
|
||||
->map(function ($group) {
|
||||
|
||||
return new TaxSubtotals(
|
||||
taxable_amount: $group->sum('taxableAmount'),
|
||||
tax_amount: $group->sum('taxAmount'),
|
||||
percentage: $group->first()['percentage'],
|
||||
country: $group->first()['country'],
|
||||
category: null
|
||||
);
|
||||
|
||||
})->toArray();
|
||||
|
||||
|
||||
$this->s_invoice->setTaxSubtotals($sub_taxes);
|
||||
|
||||
|
||||
// $this->s_invoice->setAmountIncludingVat($invoice->LegalMonetaryTotal->TaxInclusiveAmount->amount);
|
||||
// $this->s_invoice->setPrepaidAmount(0);
|
||||
|
||||
return $this->s_invoice;
|
||||
|
||||
}
|
||||
|
||||
private function setTaxMap($ctc, $peppolLine, $invoice): self
|
||||
{
|
||||
$taxAmount = 0;
|
||||
$taxableAmount = 0;
|
||||
|
||||
foreach($peppolLine->Item as $item)
|
||||
{
|
||||
|
||||
$_taxAmount = $this->calcAmountLineTax($ctc->Percent, $peppolLine->LineExtensionAmount->amount);
|
||||
|
||||
$taxAmount += $_taxAmount;
|
||||
$taxableAmount += $peppolLine->LineExtensionAmount->amount;
|
||||
|
||||
}
|
||||
|
||||
$this->tax_map[] = [
|
||||
'percentage' => $ctc->Percent,
|
||||
'country' => $this->resolveJurisdication($ctc, $invoice),
|
||||
'taxAmount' => $taxAmount,
|
||||
'taxableAmount' => $taxableAmount,
|
||||
];
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
private function resolveJurisdication($ctc, $invoice): string
|
||||
{
|
||||
if(isset($ctc->TaxTotal[0]->JurisdictionRegionAddress->Country->IdentificationCode->value))
|
||||
return $ctc->TaxTotal[0]->JurisdictionRegionAddress->Country->IdentificationCode->value;
|
||||
|
||||
return $invoice->AccountingSupplierParty->Party->PostalAddress->Country->IdentificationCode->value;
|
||||
}
|
||||
|
||||
public function buildDocument(): mixed
|
||||
{
|
||||
$doc = new \stdClass;
|
||||
$doc->document->documentType = "invoice";
|
||||
$doc->document->invoice = $this->getInvoice();
|
||||
$doc->attachments = [];
|
||||
$doc->legalEntityId = '';
|
||||
$doc->idempotencyGuid = Str::uuid();
|
||||
$doc->routing->eIdentifiers = [];
|
||||
$doc->emails = [];
|
||||
|
||||
return $doc;
|
||||
}
|
||||
|
||||
public function toJson(): string
|
||||
{
|
||||
return json_encode($this->s_invoice, JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
|
||||
namespace App\Services\EDocument\Gateway\Transformers;
|
||||
|
||||
interface TransformerInterface
|
||||
{
|
||||
public function transform(mixed $invoice);
|
||||
|
||||
public function getInvoice();
|
||||
|
||||
public function toJson();
|
||||
}
|
@ -48,32 +48,58 @@ 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();
|
||||
|
||||
$result = $storecove->build($model)->getResult();
|
||||
|
||||
if (count($result['errors']) > 0) {
|
||||
nlog($result);
|
||||
return $result['errors'];
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'legal_entity_id' => $model->company->legal_entity_id,
|
||||
"idempotencyGuid" => \Illuminate\Support\Str::uuid(),
|
||||
'document' => [
|
||||
'document_type' => 'invoice',
|
||||
'invoice' => $result['document'],
|
||||
],
|
||||
'tenant_id' => $model->company->company_key,
|
||||
'routing' => $identifiers['routing'],
|
||||
|
||||
//
|
||||
'account_key' => $model->company->account->key,
|
||||
'e_invoicing_token' => $model->company->account->e_invoicing_token,
|
||||
'identifiers' => $identifiers,
|
||||
];
|
||||
|
||||
/** Concrete implementation current linked to Storecove only */
|
||||
|
||||
//@testing only
|
||||
$sc = new \App\Services\EDocument\Gateway\Storecove\Storecove();
|
||||
$r = $sc->sendJsonDocument($payload);
|
||||
|
||||
if (is_string($r)) {
|
||||
return $this->writeActivity($model, $r);
|
||||
}
|
||||
else {
|
||||
// nlog($r->body());
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
|
||||
if(Ninja::isSelfHost() && ($model instanceof Invoice) && $model->company->legal_entity_id)
|
||||
{
|
||||
|
||||
$p = new Peppol($model);
|
||||
$p->run();
|
||||
$identifiers = $p->getStorecoveMeta();
|
||||
|
||||
$result = $storecove->build($model);
|
||||
|
||||
/**************************** Legacy */
|
||||
$xml = $p->toXml();
|
||||
|
||||
$payload = [
|
||||
'legal_entity_id' => $model->company->legal_entity_id,
|
||||
'document' => base64_encode($xml),
|
||||
'tenant_id' => $model->company->company_key,
|
||||
'identifiers' => $identifiers,
|
||||
// 'e_invoicing_token' => $model->company->e_invoicing_token,
|
||||
// include whitelabel key.
|
||||
];
|
||||
|
||||
/**************************** Legacy */
|
||||
|
||||
$r = Http::withHeaders($this->getHeaders())
|
||||
->post(config('ninja.hosted_ninja_url')."/api/einvoice/submission", $payload);
|
||||
|
||||
@ -95,15 +121,9 @@ class SendEDocument implements ShouldQueue
|
||||
|
||||
if(Ninja::isHosted() && ($model instanceof Invoice) && $model->company->legal_entity_id)
|
||||
{
|
||||
//hosted sender
|
||||
$p = new Peppol($model);
|
||||
|
||||
$p->run();
|
||||
$xml = $p->toXml();
|
||||
$identifiers = $p->getStorecoveMeta();
|
||||
|
||||
$sc = new \App\Services\EDocument\Gateway\Storecove\Storecove();
|
||||
$r = $sc->sendDocument($xml, $model->company->legal_entity_id, $identifiers);
|
||||
$r = $sc->sendJsonDocument($payload);
|
||||
|
||||
if(is_string($r))
|
||||
return $this->writeActivity($model, $r);
|
||||
|
@ -64,6 +64,8 @@ class Peppol extends AbstractService
|
||||
* Exclusive Taxes
|
||||
*
|
||||
*/
|
||||
|
||||
private ?string $override_vat_number;
|
||||
|
||||
/** @var array $InvoiceTypeCodes */
|
||||
private array $InvoiceTypeCodes = [
|
||||
@ -147,6 +149,12 @@ class Peppol extends AbstractService
|
||||
|
||||
private array $tax_map = [];
|
||||
|
||||
private float $allowance_total = 0;
|
||||
|
||||
private $globalTaxCategories;
|
||||
|
||||
private string $tax_category_id;
|
||||
|
||||
public function __construct(public Invoice $invoice)
|
||||
{
|
||||
$this->company = $invoice->company;
|
||||
@ -163,6 +171,9 @@ class Peppol extends AbstractService
|
||||
*/
|
||||
public function run(): self
|
||||
{
|
||||
$this->getJurisdiction(); //Sets the nexus object into the Peppol document.
|
||||
$this->getAllUsedTaxes(); //Maps all used line item taxes
|
||||
|
||||
/** Invoice Level Props */
|
||||
$id = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\CustomizationID();
|
||||
$id->value = $this->customizationID;
|
||||
@ -187,7 +198,7 @@ class Peppol extends AbstractService
|
||||
$ip = new InvoicePeriod();
|
||||
$ip->StartDate = new \DateTime($this->invoice->date);
|
||||
$ip->EndDate = new \DateTime($this->invoice->due_date);
|
||||
$this->p_invoice->InvoicePeriod[] = $ip;
|
||||
$this->p_invoice->InvoicePeriod = [$ip];
|
||||
}
|
||||
|
||||
if ($this->invoice->project_id) {
|
||||
@ -195,7 +206,7 @@ class Peppol extends AbstractService
|
||||
$id = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\ID();
|
||||
$id->value = $this->invoice->project->number;
|
||||
$pr->ID = $id;
|
||||
$this->p_invoice->ProjectReference[] = $pr;
|
||||
$this->p_invoice->ProjectReference = [$pr];
|
||||
}
|
||||
|
||||
/** Auto switch between Invoice / Credit based on the amount value */
|
||||
@ -204,8 +215,9 @@ 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->p_invoice->Delivery = $this->getDelivery();
|
||||
|
||||
$this->setOrderReference()->setTaxBreakdown();
|
||||
|
||||
@ -217,11 +229,11 @@ class Peppol extends AbstractService
|
||||
->getPeppol();
|
||||
|
||||
//** @todo double check this logic, this will only ever write the doc once */
|
||||
if(strlen($this->invoice->backup ?? '') == 0)
|
||||
{
|
||||
$this->invoice->e_invoice = $this->toObject();
|
||||
$this->invoice->save();
|
||||
}
|
||||
// if(is_null($this->invoice->backup))
|
||||
// {
|
||||
// $this->invoice->e_invoice = $this->toObject();
|
||||
// $this->invoice->save();
|
||||
// }
|
||||
|
||||
return $this;
|
||||
|
||||
@ -426,18 +438,27 @@ 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
|
||||
}
|
||||
|
||||
$tc = clone $this->globalTaxCategories[0];
|
||||
// $tc->Percent = '0';
|
||||
unset($tc->TaxExemptionReasonCode);
|
||||
unset($tc->TaxExemptionReason);
|
||||
|
||||
$allowanceCharge->TaxCategory[] = $tc;
|
||||
$allowanceCharge->AllowanceChargeReason = ctrans('texts.discount');
|
||||
$allowances[] = $allowanceCharge;
|
||||
}
|
||||
|
||||
@ -446,14 +467,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;
|
||||
|
||||
}
|
||||
@ -462,14 +486,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;
|
||||
|
||||
}
|
||||
@ -478,14 +506,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;
|
||||
|
||||
}
|
||||
@ -494,14 +525,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;
|
||||
|
||||
}
|
||||
@ -523,12 +557,13 @@ 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();
|
||||
$tea->currencyID = $this->invoice->client->currency()->code;
|
||||
$tea->amount = $this->invoice->uses_inclusive_taxes ? round($this->invoice->amount - $this->invoice->total_taxes, 2) : $taxable;
|
||||
|
||||
$tea->amount = round($this->invoice->amount - $this->invoice->total_taxes, 2);
|
||||
$lmt->TaxExclusiveAmount = $tea;
|
||||
|
||||
$tia = new TaxInclusiveAmount();
|
||||
@ -541,6 +576,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;
|
||||
}
|
||||
|
||||
@ -572,6 +612,8 @@ class Peppol extends AbstractService
|
||||
break;
|
||||
case Product::PRODUCT_TYPE_REVERSE_TAX:
|
||||
$tax_type = 'AE';
|
||||
case Product::PRODUCT_INTRA_COMMUNITY:
|
||||
$tax_type = 'K';
|
||||
break;
|
||||
}
|
||||
|
||||
@ -579,7 +621,7 @@ class Peppol extends AbstractService
|
||||
|
||||
if (empty($tax_type)) {
|
||||
if ((in_array($this->company->country()->iso_3166_2, $eu_states) && in_array($this->invoice->client->country->iso_3166_2, $eu_states)) && $this->invoice->company->country()->iso_3166_2 != $this->invoice->client->country->iso_3166_2) {
|
||||
$tax_type = 'K'; //EEA Exempt
|
||||
$tax_type = 'K'; // EEA Exempt
|
||||
} elseif (!in_array($this->invoice->client->country->iso_3166_2, $eu_states)) {
|
||||
$tax_type = 'G'; //Free export item, VAT not charged
|
||||
} else {
|
||||
@ -600,7 +642,83 @@ class Peppol extends AbstractService
|
||||
return $tax_type;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
private function resolveTaxExemptReason($item, $ctc = null): mixed
|
||||
{
|
||||
|
||||
$eu_states = ["AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "EL", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "ES-CE", "ES-ML", "ES-CN", "SE", "IS", "LI", "NO", "CH"];
|
||||
|
||||
if($item->tax_id == '9') {
|
||||
$tax_type = 'AE'; // EEA Exempt
|
||||
$reason_code = 'vatex-eu-ae';
|
||||
$reason = 'Reverse charge';
|
||||
} elseif ((in_array($this->company->country()->iso_3166_2, $eu_states) &&
|
||||
in_array($this->invoice->client->country->iso_3166_2, $eu_states)) &&
|
||||
$this->invoice->company->country()->iso_3166_2 != $this->invoice->client->country->iso_3166_2) {
|
||||
$tax_type = 'K'; // EEA Exempt
|
||||
$reason_code = 'vatex-eu-ic';
|
||||
$reason = 'Intra-Community supply';
|
||||
|
||||
} elseif (!in_array($this->invoice->client->country->iso_3166_2, $eu_states)) {
|
||||
$tax_type = 'G'; //Free export item, VAT not charged
|
||||
$reason_code = 'vatex-eu-g';
|
||||
$reason = 'Export outside the EU';
|
||||
} else {
|
||||
$tax_type = 'O'; //Standard rate
|
||||
$reason_code = "vatex-eu-o";
|
||||
$reason = 'Services outside scope of tax';
|
||||
}
|
||||
|
||||
$this->tax_category_id = $tax_type;
|
||||
|
||||
//no vat, build a single tax category for tax exemption
|
||||
|
||||
$taxCategory = new \InvoiceNinja\EInvoice\Models\Peppol\TaxCategoryType\TaxCategory();
|
||||
$taxCategory->ID = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\ID();
|
||||
$taxCategory->ID->value = $tax_type;
|
||||
|
||||
if($this->tax_category_id != 'O')
|
||||
$taxCategory->Percent = '0';
|
||||
|
||||
$taxScheme = new \InvoiceNinja\EInvoice\Models\Peppol\TaxSchemeType\TaxScheme();
|
||||
$taxScheme->ID = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\ID();
|
||||
$taxScheme->ID->value = $this->standardizeTaxSchemeId('vat');
|
||||
$taxCategory->TaxScheme = $taxScheme;
|
||||
$terc = new \InvoiceNinja\EInvoice\Models\Peppol\CodeType\TaxExemptionReasonCode();
|
||||
$terc->value = $reason_code;
|
||||
$taxCategory->TaxExemptionReasonCode = $terc;
|
||||
$taxCategory->TaxExemptionReason = $reason;
|
||||
|
||||
$this->globalTaxCategories = [$taxCategory];
|
||||
|
||||
|
||||
if($this->tax_category_id == 'O' && isset($this->p_invoice->AccountingSupplierParty->Party->PartyTaxScheme))
|
||||
unset($this->p_invoice->AccountingSupplierParty->Party->PartyTaxScheme);
|
||||
|
||||
|
||||
if ($this->tax_category_id == 'O' && isset($this->p_invoice->AccountingCustomerParty->Party->PartyTaxScheme)) {
|
||||
unset($this->p_invoice->AccountingCustomerParty->Party->PartyTaxScheme);
|
||||
}
|
||||
|
||||
if($ctc) {
|
||||
$ctc->ID->value = $tax_type;
|
||||
|
||||
if($this->tax_category_id != 'O')
|
||||
$ctc->Percent = '0';
|
||||
// $terc = new \InvoiceNinja\EInvoice\Models\Peppol\CodeType\TaxExemptionReasonCode();
|
||||
// $terc->value = $reason_code;
|
||||
// $ctc->TaxExemptionReasonCode = $terc;
|
||||
// $ctc->TaxExemptionReason = $reason;
|
||||
|
||||
return $ctc;
|
||||
}
|
||||
|
||||
return $tax_type;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* getInvoiceLines
|
||||
*
|
||||
* Compiles the invoice line items of the document
|
||||
@ -612,32 +730,42 @@ class Peppol extends AbstractService
|
||||
$lines = [];
|
||||
|
||||
foreach($this->invoice->line_items as $key => $item) {
|
||||
|
||||
|
||||
$_item = new Item();
|
||||
$_item->Name = $item->product_key;
|
||||
$_item->Description = $item->notes;
|
||||
|
||||
|
||||
$ctc = new ClassifiedTaxCategory();
|
||||
$ctc->ID = new ID();
|
||||
$ctc->ID->value = $this->getTaxType($item->tax_id);
|
||||
|
||||
if($item->tax_rate1 > 0)
|
||||
$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 = $this->resolveTaxExemptReason($item, $ctc);
|
||||
|
||||
$ts = new TaxScheme();
|
||||
$id = new ID();
|
||||
$id->value = $this->standardizeTaxSchemeId($item->tax_name1);
|
||||
$ts->ID = $id;
|
||||
$ctc->TaxScheme = $ts;
|
||||
|
||||
$_item->ClassifiedTaxCategory[] = $ctc;
|
||||
if($this->tax_category_id == 'O')
|
||||
unset($ctc->Percent);
|
||||
|
||||
}
|
||||
|
||||
$_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();
|
||||
@ -652,7 +780,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();
|
||||
@ -681,15 +809,16 @@ class Peppol extends AbstractService
|
||||
$line->Item = $_item;
|
||||
|
||||
/** Builds the tax map for the document */
|
||||
$this->getItemTaxes($item);
|
||||
// $this->getItemTaxes($item);
|
||||
|
||||
// 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
|
||||
@ -697,29 +826,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;
|
||||
}
|
||||
@ -730,207 +865,88 @@ class Peppol extends AbstractService
|
||||
return $lines;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* calculateDiscountAmount
|
||||
*
|
||||
* Helper method to determine the discount amount to be used.
|
||||
*
|
||||
* @param mixed $item
|
||||
* @return float
|
||||
*/
|
||||
private function calculateDiscountAmount($item): float
|
||||
private function calculateTotalItemDiscountAmount($item):float
|
||||
{
|
||||
|
||||
if ($item->is_amount_discount) {
|
||||
return $item->discount / $item->quantity; // Per unit discount amount
|
||||
return $item->discount;
|
||||
}
|
||||
|
||||
return ($item->cost / $item->quantity) * ($item->discount / 100);
|
||||
|
||||
return ($item->cost * $item->quantity) * ($item->discount / 100);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* costWithDiscount
|
||||
* calculateTaxMap
|
||||
*
|
||||
* Helper method to determine the cost INCLUDING discount
|
||||
* Generates a standard tax_map entry for a given $amount
|
||||
*
|
||||
* @param mixed $item
|
||||
* @return float
|
||||
* Iterates through all of the globalTaxCategories found in the document
|
||||
*
|
||||
* @param float $amount
|
||||
* @return self
|
||||
*/
|
||||
private function costWithDiscount($item): float
|
||||
private function calculateTaxMap($amount): self
|
||||
{
|
||||
$cost = $item->cost;
|
||||
|
||||
if ($item->discount != 0) {
|
||||
if ($this->invoice->is_amount_discount) {
|
||||
$cost -= $item->discount / $item->quantity;
|
||||
} else {
|
||||
$cost -= $cost * $item->discount / 100;
|
||||
}
|
||||
foreach($this->globalTaxCategories as $tc)
|
||||
{
|
||||
|
||||
$this->tax_map[] = [
|
||||
'taxableAmount' => $amount,
|
||||
'taxAmount' => $amount * ($tc->Percent/100),
|
||||
'percentage' => $tc->Percent,
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
return $cost;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* getItemTaxes
|
||||
* getAllUsedTaxes
|
||||
*
|
||||
* Builds a tax map for later use when
|
||||
* collating taxes
|
||||
* Build a full tax category property based on all
|
||||
* of the item taxes that have been applied to the invoice.
|
||||
*
|
||||
* @param object $item
|
||||
* @return array
|
||||
* @return self
|
||||
*/
|
||||
private function getItemTaxes(object $item): array
|
||||
private function getAllUsedTaxes(): self
|
||||
{
|
||||
$item_taxes = [];
|
||||
$this->globalTaxCategories = [];
|
||||
|
||||
if(strlen($item->tax_name1 ?? '') > 1) {
|
||||
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;
|
||||
|
||||
$tax_amount = new TaxAmount();
|
||||
$tax_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiveLineTax($item->tax_rate1, $item->line_total) : $this->calcAmountLineTax($item->tax_rate1, $item->line_total);
|
||||
$tax_subtotal = new TaxSubtotal();
|
||||
$tax_subtotal->TaxAmount = $tax_amount;
|
||||
$this->globalTaxCategories[] = $taxCategory;
|
||||
|
||||
});
|
||||
|
||||
$taxable_amount = new TaxableAmount();
|
||||
$taxable_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$taxable_amount->amount = $this->invoice->uses_inclusive_taxes ? $item->line_total - $tax_amount->amount : $item->line_total;
|
||||
$tax_subtotal->TaxableAmount = $taxable_amount;
|
||||
|
||||
$tc = new TaxCategory();
|
||||
|
||||
$id = new ID();
|
||||
$id->value = $this->getTaxType($item->tax_id);
|
||||
|
||||
$tc->ID = $id;
|
||||
$tc->Percent = $item->tax_rate1;
|
||||
$ts = new TaxScheme();
|
||||
return $this;
|
||||
|
||||
$id = new ID();
|
||||
$id->value = $this->standardizeTaxSchemeId($item->tax_name1);
|
||||
|
||||
$jurisdiction = $this->getJurisdiction();
|
||||
$ts->JurisdictionRegionAddress[] = $jurisdiction;
|
||||
|
||||
$ts->ID = $id;
|
||||
$tc->TaxScheme = $ts;
|
||||
$tax_subtotal->TaxCategory = $tc;
|
||||
|
||||
$tax_total = new TaxTotal();
|
||||
$tax_total->TaxAmount = $tax_amount;
|
||||
$tax_total->TaxSubtotal[] = $tax_subtotal;
|
||||
|
||||
$this->tax_map[] = [
|
||||
'taxableAmount' => $taxable_amount->amount,
|
||||
'taxAmount' => $tax_amount->amount,
|
||||
'percentage' => $item->tax_rate1,
|
||||
];
|
||||
|
||||
$item_taxes[] = $tax_total;
|
||||
|
||||
}
|
||||
|
||||
|
||||
if(strlen($item->tax_name2 ?? '') > 1) {
|
||||
|
||||
$tax_amount = new TaxAmount();
|
||||
$tax_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiveLineTax($item->tax_rate2, $item->line_total) : $this->calcAmountLineTax($item->tax_rate2, $item->line_total);
|
||||
$tax_subtotal = new TaxSubtotal();
|
||||
$tax_subtotal->TaxAmount = $tax_amount;
|
||||
|
||||
$taxable_amount = new TaxableAmount();
|
||||
$taxable_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$taxable_amount->amount = $item->line_total;
|
||||
$tax_subtotal->TaxableAmount = $taxable_amount;
|
||||
|
||||
$tc = new TaxCategory();
|
||||
|
||||
$id = new ID();
|
||||
$id->value = $this->getTaxType($item->tax_id);
|
||||
|
||||
$tc->ID = $id;
|
||||
$tc->Percent = $item->tax_rate2;
|
||||
$ts = new TaxScheme();
|
||||
|
||||
$id = new ID();
|
||||
$id->value = $this->standardizeTaxSchemeId($item->tax_name2);
|
||||
|
||||
$jurisdiction = $this->getJurisdiction();
|
||||
$ts->JurisdictionRegionAddress[] = $jurisdiction;
|
||||
|
||||
$ts->ID = $id;
|
||||
$tc->TaxScheme = $ts;
|
||||
$tax_subtotal->TaxCategory = $tc;
|
||||
|
||||
$tax_total = new TaxTotal();
|
||||
$tax_total->TaxAmount = $tax_amount;
|
||||
$tax_total->TaxSubtotal[] = $tax_subtotal;
|
||||
|
||||
$this->tax_map[] = [
|
||||
'taxableAmount' => $taxable_amount->amount,
|
||||
'taxAmount' => $tax_amount->amount,
|
||||
'percentage' => $item->tax_rate2,
|
||||
];
|
||||
|
||||
$item_taxes[] = $tax_total;
|
||||
|
||||
}
|
||||
|
||||
|
||||
if(strlen($item->tax_name3 ?? '') > 1) {
|
||||
|
||||
$tax_amount = new TaxAmount();
|
||||
$tax_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
|
||||
$tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiveLineTax($item->tax_rate3, $item->line_total) : $this->calcAmountLineTax($item->tax_rate3, $item->line_total);
|
||||
|
||||
$tax_subtotal = new TaxSubtotal();
|
||||
$tax_subtotal->TaxAmount = $tax_amount;
|
||||
|
||||
$taxable_amount = new TaxableAmount();
|
||||
$taxable_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$taxable_amount->amount = $item->line_total;
|
||||
$tax_subtotal->TaxableAmount = $taxable_amount;
|
||||
|
||||
$tc = new TaxCategory();
|
||||
|
||||
$id = new ID();
|
||||
$id->value = $this->getTaxType($item->tax_id);
|
||||
|
||||
$tc->ID = $id;
|
||||
$tc->Percent = $item->tax_rate3;
|
||||
$ts = new TaxScheme();
|
||||
|
||||
$id = new ID();
|
||||
$id->value = $this->standardizeTaxSchemeId($item->tax_name3);
|
||||
|
||||
$jurisdiction = $this->getJurisdiction();
|
||||
$ts->JurisdictionRegionAddress[] = $jurisdiction;
|
||||
|
||||
$ts->ID = $id;
|
||||
$tc->TaxScheme = $ts;
|
||||
$tax_subtotal->TaxCategory = $tc;
|
||||
|
||||
$tax_total = new TaxTotal();
|
||||
$tax_total->TaxAmount = $tax_amount;
|
||||
$tax_total->TaxSubtotal[] = $tax_subtotal;
|
||||
|
||||
|
||||
$this->tax_map[] = [
|
||||
'taxableAmount' => $taxable_amount->amount,
|
||||
'taxAmount' => $tax_amount->amount,
|
||||
'percentage' => $item->tax_rate3,
|
||||
];
|
||||
|
||||
$item_taxes[] = $tax_total;
|
||||
|
||||
}
|
||||
|
||||
return $item_taxes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* getAccountingSupplierParty
|
||||
*
|
||||
@ -951,18 +967,20 @@ class Peppol extends AbstractService
|
||||
$pi = new PartyIdentification();
|
||||
$vatID = new ID();
|
||||
$vatID->schemeID = $this->resolveScheme();
|
||||
$vatID->value = $this->company->settings->vat_number; //todo if we are cross border - switch to the supplier local vat number
|
||||
$vatID->value = $this->override_vat_number ?? $this->company->settings->vat_number; //todo if we are cross border - switch to the supplier local vat number
|
||||
|
||||
$pi->ID = $vatID;
|
||||
$party->PartyIdentification[] = $pi;
|
||||
$pts = new \InvoiceNinja\EInvoice\Models\Peppol\PartyTaxSchemeType\PartyTaxScheme();
|
||||
|
||||
$companyID = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\CompanyID();
|
||||
$companyID->value = $this->invoice->company->settings->vat_number;
|
||||
$companyID->value = $this->override_vat_number ?? $this->company->settings->vat_number;
|
||||
$pts->CompanyID = $companyID;
|
||||
|
||||
$ts = new TaxScheme();
|
||||
$ts->ID = $vatID;
|
||||
$id = new ID();
|
||||
$id->value = $this->standardizeTaxSchemeId('vat');
|
||||
$ts->ID = $id;
|
||||
$pts->TaxScheme = $ts;
|
||||
|
||||
//@todo if we have an exact GLN/routing number we should update this, otherwise Storecove will proxy and update on transit
|
||||
@ -1033,6 +1051,21 @@ class Peppol extends AbstractService
|
||||
|
||||
$party->PartyIdentification[] = $pi;
|
||||
|
||||
$pts = new \InvoiceNinja\EInvoice\Models\Peppol\PartyTaxSchemeType\PartyTaxScheme();
|
||||
|
||||
$companyID = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\CompanyID();
|
||||
$companyID->value = $this->invoice->client->vat_number;
|
||||
$pts->CompanyID = $companyID;
|
||||
|
||||
$ts = new TaxScheme();
|
||||
$id = new ID();
|
||||
$id->value = $this->standardizeTaxSchemeId('vat');
|
||||
$ts->ID = $id;
|
||||
$pts->TaxScheme = $ts;
|
||||
|
||||
$party->PartyTaxScheme[] = $pts;
|
||||
|
||||
|
||||
}
|
||||
|
||||
$party_name = new PartyName();
|
||||
@ -1086,6 +1119,37 @@ class Peppol extends AbstractService
|
||||
return $acp;
|
||||
}
|
||||
|
||||
private function getDelivery(): array
|
||||
{
|
||||
$delivery = new \InvoiceNinja\EInvoice\Models\Peppol\DeliveryType\Delivery();
|
||||
$location = new \InvoiceNinja\EInvoice\Models\Peppol\LocationType\DeliveryLocation();
|
||||
|
||||
$address = new Address();
|
||||
// $address->CityName = $this->invoice->client->city;
|
||||
// $address->StreetName = $this->invoice->client->address1;
|
||||
|
||||
// if (strlen($this->invoice->client->address2 ?? '') > 1) {
|
||||
// $address->AdditionalStreetName = $this->invoice->client->address2;
|
||||
// }
|
||||
|
||||
// $address->PostalZone = $this->invoice->client->postal_code;
|
||||
// $address->CountrySubentity = $this->invoice->client->state;
|
||||
|
||||
$country = new Country();
|
||||
|
||||
$ic = new IdentificationCode();
|
||||
$shipping = $this->invoice->client->shipping_country ? $this->invoice->client->shipping_country->iso_3166_2 : $this->invoice->client->country->iso_3166_2;
|
||||
$ic->value = $shipping;
|
||||
|
||||
$country->IdentificationCode = $ic;
|
||||
$address->Country = $country;
|
||||
$location->Address = $address;
|
||||
$delivery->DeliveryLocation = $location;
|
||||
|
||||
return [$delivery];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* getTaxable
|
||||
*
|
||||
@ -1109,12 +1173,14 @@ class Peppol extends AbstractService
|
||||
$total += $line_total;
|
||||
}
|
||||
|
||||
$total = round($total, 2);
|
||||
|
||||
if ($this->invoice->discount > 0) {
|
||||
if ($this->invoice->is_amount_discount) {
|
||||
$total -= $this->invoice->discount;
|
||||
} else {
|
||||
$total *= (100 - $this->invoice->discount) / 100;
|
||||
$total = round($total, 2);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1134,7 +1200,7 @@ class Peppol extends AbstractService
|
||||
$total += $this->invoice->custom_surcharge4;
|
||||
}
|
||||
|
||||
return $total;
|
||||
return round($total,2);
|
||||
}
|
||||
|
||||
///////////////// Helper Methods /////////////////////////
|
||||
@ -1198,41 +1264,29 @@ class Peppol extends AbstractService
|
||||
{
|
||||
|
||||
$tax_total = new TaxTotal();
|
||||
$taxes = $this->calc->getTaxMap();
|
||||
|
||||
$taxes = collect($this->tax_map)
|
||||
->groupBy('percentage')
|
||||
->map(function ($group) {
|
||||
|
||||
return [
|
||||
'taxableAmount' => $group->sum('taxableAmount'),
|
||||
'taxAmount' => $group->sum('taxAmount'),
|
||||
'percentage' => $group->first()['percentage'],
|
||||
];
|
||||
|
||||
|
||||
});
|
||||
|
||||
foreach($taxes as $grouped_tax)
|
||||
if(count($taxes) < 1)
|
||||
{
|
||||
// Required: TaxAmount (BT-110)
|
||||
|
||||
$tax_amount = new TaxAmount();
|
||||
$tax_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$tax_amount->amount = (string)$grouped_tax['taxAmount'];
|
||||
$tax_amount->amount = (string)0;
|
||||
$tax_total->TaxAmount = $tax_amount;
|
||||
|
||||
// Required: TaxSubtotal (BG-23)
|
||||
$tax_subtotal = new TaxSubtotal();
|
||||
|
||||
// Required: TaxableAmount (BT-116)
|
||||
$taxable_amount = new TaxableAmount();
|
||||
$taxable_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$taxable_amount->amount = (string)$grouped_tax['taxableAmount'];
|
||||
$taxable_amount->amount = (string)round($this->invoice->amount,2);
|
||||
|
||||
$tax_subtotal->TaxableAmount = $taxable_amount;
|
||||
|
||||
// Required: TaxAmount (BT-117)
|
||||
$subtotal_tax_amount = new TaxAmount();
|
||||
$subtotal_tax_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$subtotal_tax_amount->amount = (string)$grouped_tax['taxAmount'];
|
||||
|
||||
$subtotal_tax_amount->amount = (string)0;
|
||||
|
||||
$tax_subtotal->TaxAmount = $subtotal_tax_amount;
|
||||
|
||||
@ -1241,11 +1295,9 @@ class Peppol extends AbstractService
|
||||
|
||||
// Required: TaxCategory ID (BT-118)
|
||||
$category_id = new ID();
|
||||
$category_id->value = 'S'; // Standard rate
|
||||
$tax_category->ID = $category_id;
|
||||
$category_id->value = $this->tax_category_id; // Exempt
|
||||
|
||||
// Required: TaxCategory Rate (BT-119)
|
||||
$tax_category->Percent = (string)$grouped_tax['percentage'];
|
||||
$tax_category->ID = $category_id;
|
||||
|
||||
// Required: TaxScheme (BG-23)
|
||||
$tax_scheme = new TaxScheme();
|
||||
@ -1254,7 +1306,67 @@ class Peppol extends AbstractService
|
||||
$tax_scheme->ID = $scheme_id;
|
||||
$tax_category->TaxScheme = $tax_scheme;
|
||||
|
||||
$tax_subtotal->TaxCategory = $tax_category;
|
||||
$tax_subtotal->TaxCategory = $this->globalTaxCategories[0];
|
||||
|
||||
$tax_total->TaxSubtotal[] = $tax_subtotal;
|
||||
|
||||
$this->p_invoice->TaxTotal[] = $tax_total;
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
foreach($taxes as $key => $grouped_tax)
|
||||
{
|
||||
// Required: TaxAmount (BT-110)
|
||||
$tax_amount = new TaxAmount();
|
||||
$tax_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$tax_amount->amount = (string)$grouped_tax['total'];
|
||||
$tax_total->TaxAmount = $tax_amount;
|
||||
|
||||
// Required: TaxSubtotal (BG-23)
|
||||
$tax_subtotal = new TaxSubtotal();
|
||||
|
||||
// Required: TaxableAmount (BT-116)
|
||||
$taxable_amount = new TaxableAmount();
|
||||
$taxable_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
|
||||
if(floatval($grouped_tax['total']) === 0.0)
|
||||
$taxable_amount->amount = (string)round($this->invoice->amount, 2);
|
||||
else
|
||||
$taxable_amount->amount = (string)round($grouped_tax['base_amount'],2);
|
||||
$tax_subtotal->TaxableAmount = $taxable_amount;
|
||||
|
||||
// Required: TaxAmount (BT-117)
|
||||
$subtotal_tax_amount = new TaxAmount();
|
||||
$subtotal_tax_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
|
||||
$subtotal_tax_amount->amount = (string)round($grouped_tax['total'],2);
|
||||
|
||||
$tax_subtotal->TaxAmount = $subtotal_tax_amount;
|
||||
|
||||
// Required: TaxCategory (BG-23)
|
||||
$tax_category = new TaxCategory();
|
||||
|
||||
// Required: TaxCategory ID (BT-118)
|
||||
$category_id = new ID();
|
||||
$category_id->value = $this->getTaxType($grouped_tax['tax_id']); // Standard rate
|
||||
|
||||
$tax_category->ID = $category_id;
|
||||
|
||||
// Required: TaxCategory Rate (BT-119)
|
||||
if($grouped_tax['tax_rate'] > 0)
|
||||
$tax_category->Percent = (string)$grouped_tax['tax_rate'];
|
||||
|
||||
// Required: TaxScheme (BG-23)
|
||||
$tax_scheme = new TaxScheme();
|
||||
$scheme_id = new ID();
|
||||
$scheme_id->value = $this->standardizeTaxSchemeId("taxname");
|
||||
$tax_scheme->ID = $scheme_id;
|
||||
$tax_category->TaxScheme = $tax_scheme;
|
||||
|
||||
$tax_subtotal->TaxCategory = $this->globalTaxCategories[0];
|
||||
|
||||
$tax_total->TaxSubtotal[] = $tax_subtotal;
|
||||
|
||||
$this->p_invoice->TaxTotal[] = $tax_total;
|
||||
@ -1268,7 +1380,7 @@ class Peppol extends AbstractService
|
||||
|
||||
//calculate nexus
|
||||
$country_code = $this->company->country()->iso_3166_2;
|
||||
$br = new BaseRule();
|
||||
$br = new \App\DataMapper\Tax\BaseRule();
|
||||
$eu_countries = $br->eu_country_codes;
|
||||
|
||||
if($this->invoice->client->country->iso_3166_2 == $this->company->country()->iso_3166_2){
|
||||
@ -1282,6 +1394,9 @@ class Peppol extends AbstractService
|
||||
//EU Sale
|
||||
if($this->company->tax_data->regions->EU->has_sales_above_threshold || !$this->invoice->client->has_valid_vat_number){ //over threshold - tax in buyer country
|
||||
$country_code = $this->invoice->client->country->iso_3166_2;
|
||||
|
||||
if(isset($this->ninja_invoice->company->tax_data->regions->EU->subregions->{$country_code}->vat_number))
|
||||
$this->override_vat_number = $this->ninja_invoice->company->tax_data->regions->EU->subregions->{$country_code}->vat_number;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,7 @@ class EntityLevel
|
||||
}
|
||||
catch(PeppolValidationException $e) {
|
||||
|
||||
$this->errors['invoice'] = ['field' => $e->getInvalidField()];
|
||||
$this->errors['invoice'] = ['field' => $e->getInvalidField(), 'label' => $e->getInvalidField()];
|
||||
|
||||
};
|
||||
|
||||
@ -126,13 +126,13 @@ class EntityLevel
|
||||
if($field == 'country_id' && $client->country_id >=1)
|
||||
continue;
|
||||
|
||||
$errors[] = ['field' => ctrans("texts.{$field}")];
|
||||
$errors[] = ['field' => $field, 'label' => ctrans("texts.{$field}")];
|
||||
|
||||
}
|
||||
|
||||
//If not an individual, you MUST have a VAT number
|
||||
if ($client->classification != 'individual' && !$this->validString($client->vat_number)) {
|
||||
$errors[] = ['field' => ctrans("texts.vat_number")];
|
||||
$errors[] = ['field' => 'vat_number', 'label' => ctrans("texts.vat_number")];
|
||||
}
|
||||
|
||||
return $errors;
|
||||
@ -180,7 +180,7 @@ class EntityLevel
|
||||
if($this->validString($settings_object->getSetting($field)))
|
||||
continue;
|
||||
|
||||
$errors[] = ['field' => ctrans("texts.{$field}")];
|
||||
$errors[] = ['field' => $field, 'label' => ctrans("texts.{$field}")];
|
||||
|
||||
}
|
||||
|
||||
@ -191,8 +191,12 @@ class EntityLevel
|
||||
//If not an individual, you MUST have a VAT number
|
||||
if($company->getSetting('classification') != 'individual' && !$this->validString($company->getSetting('vat_number')))
|
||||
{
|
||||
$errors[] = ['field' => ctrans("texts.vat_number")];
|
||||
$errors[] = ['field' => 'vat_number', 'label' => ctrans("texts.vat_number")];
|
||||
}
|
||||
elseif ($company->getSetting('classification') == 'individual' && !$this->validString($company->getSetting('id_number'))) {
|
||||
$errors[] = ['field' => 'id_number', 'label' => ctrans("texts.id_number")];
|
||||
}
|
||||
|
||||
|
||||
// foreach($this->company_fields as $field)
|
||||
// {
|
||||
|
@ -65,11 +65,13 @@ class ZugferdEDokument extends AbstractService
|
||||
|
||||
$this->xdocument = ZugferdDocumentBuilder::CreateNew($profile);
|
||||
|
||||
$user_or_company_phone = strlen($this->document->user->present()->phone()) > 3 ? $this->document->user->present()->phone() : $company->present()->phone;
|
||||
|
||||
$this->xdocument
|
||||
->setDocumentSupplyChainEvent(date_create($this->document->date ?? now()->format('Y-m-d')))
|
||||
->setDocumentSeller($company->getSetting('name'))
|
||||
->setDocumentSellerAddress($company->getSetting("address1"), $company->getSetting("address2"), "", $company->getSetting("postal_code"), $company->getSetting("city"), $company->country()->iso_3166_2, $company->getSetting("state"))
|
||||
->setDocumentSellerContact($this->document->user->present()->getFullName(), "", $this->document->user->present()->phone(), "", $this->document->user->email)
|
||||
->setDocumentSellerContact($this->document->user->present()->getFullName(), "", $user_or_company_phone, "", $this->document->user->email)
|
||||
->setDocumentSellerCommunication("EM", $this->document->user->email)
|
||||
->setDocumentBuyer($client->present()->name(), $client->number)
|
||||
->setDocumentBuyerAddress($client->address1, "", "", $client->postal_code, $client->city, $client->country->iso_3166_2, $client->state)
|
||||
@ -121,7 +123,7 @@ class ZugferdEDokument extends AbstractService
|
||||
if (isset($client->shipping_address1) && $client->shipping_country) {
|
||||
$this->xdocument->setDocumentShipToAddress($client->shipping_address1, $client->shipping_address2, "", $client->shipping_postal_code, $client->shipping_city, $client->shipping_country->iso_3166_2, $client->shipping_state);
|
||||
}
|
||||
$custom_value1=$company->settings->custom_value1;
|
||||
$custom_value1 = $company->settings->custom_value1;
|
||||
//BR-DE-23 - If „Payment means type code“ (BT-81) contains a code for credit transfer (30, 58), „CREDIT TRANSFER“ (BG-17) shall be provided.
|
||||
//Payment Means - Switcher
|
||||
if(isset($custom_value1) && !empty($custom_value1) && ($custom_value1 == '30'|| $custom_value1=='58')) {
|
||||
|
@ -138,6 +138,7 @@ class TemplateAction implements ShouldQueue
|
||||
|
||||
if($this->send_email) {
|
||||
$this->sendEmail($pdf, $template);
|
||||
return;
|
||||
} else {
|
||||
$filename = "templates/{$this->hash}.pdf";
|
||||
Storage::disk(config('filesystems.default'))->put($filename, $pdf);
|
||||
|
@ -94,6 +94,7 @@ class AccountTransformer extends EntityTransformer
|
||||
'tax_api_enabled' => (bool) config('services.tax.zip_tax.key') ? true : false,
|
||||
'nordigen_enabled' => (bool) (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')) ? true : false,
|
||||
'upload_extensions' => (string) "png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx,webp,xml,zip,csv,ods,odt,odp,".config('ninja.upload_extensions'),
|
||||
'e_invoice_quota' => (int) $account->e_invoice_quota,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -223,7 +223,6 @@ class CompanyTransformer extends EntityTransformer
|
||||
'has_quickbooks_token' => $company->quickbooks ? true : false,
|
||||
'is_quickbooks_token_active' => $company->quickbooks?->accessTokenKey ?? false,
|
||||
'legal_entity_id' => $company->legal_entity_id ?? null,
|
||||
'e_invoicing_token' => $company->e_invoicing_token ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -831,7 +831,7 @@ class HtmlEngine
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->entity->company->tax_data->regions->EU->has_sales_above_threshold ?? false){ //@phpstan-ignore-line
|
||||
if (isset($this->entity->company->tax_data->regions->EU->has_sales_above_threshold) && !$this->entity->company->tax_data->regions->EU->has_sales_above_threshold){
|
||||
$tax_label .= ctrans('text.small_company_info') ."<br>";
|
||||
}
|
||||
|
||||
|
@ -294,151 +294,160 @@ class TemplateEngine
|
||||
$this->entity = 'payment';
|
||||
}
|
||||
|
||||
DB::connection(config('database.default'))->beginTransaction();
|
||||
try {
|
||||
DB::connection(config('database.default'))->beginTransaction();
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$vendor = false;
|
||||
/** @var \App\Models\Client $client */
|
||||
$client = Client::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
]);
|
||||
$vendor = false;
|
||||
/** @var \App\Models\Client $client */
|
||||
$client = Client::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
]);
|
||||
|
||||
/** @var \App\Models\ClientContact $contact */
|
||||
$contact = ClientContact::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'client_id' => $client->id,
|
||||
'is_primary' => 1,
|
||||
'send_email' => true,
|
||||
]);
|
||||
|
||||
if ($this->entity == 'payment') {
|
||||
/** @var \App\Models\Payment $payment */
|
||||
$payment = Payment::factory()->create([
|
||||
/** @var \App\Models\ClientContact $contact */
|
||||
$contact = ClientContact::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'client_id' => $client->id,
|
||||
'amount' => 10,
|
||||
'applied' => 10,
|
||||
'refunded' => 5,
|
||||
]);
|
||||
|
||||
$this->entity_obj = $payment;
|
||||
|
||||
/** @var \App\Models\Invoice $invoice */
|
||||
$invoice = Invoice::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'client_id' => $client->id,
|
||||
'amount' => 10,
|
||||
'balance' => 10,
|
||||
'number' => rand(1, 10000)
|
||||
]);
|
||||
|
||||
/** @var \App\Models\InvoiceInvitation $invitation */
|
||||
$invitation = InvoiceInvitation::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'invoice_id' => $invoice->id,
|
||||
'client_contact_id' => $contact->id,
|
||||
]);
|
||||
|
||||
/** @var \App\Models\Invoice $invoice */
|
||||
$this->entity_obj->invoices()->attach($invoice->id, [
|
||||
'amount' => 10,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$this->entity || $this->entity == 'invoice') {
|
||||
/** @var \App\Models\Invoice $invoice */
|
||||
$invoice = Invoice::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'client_id' => $client->id,
|
||||
'amount' => '10',
|
||||
'balance' => '10',
|
||||
]);
|
||||
|
||||
$this->entity_obj = $invoice;
|
||||
|
||||
$invitation = InvoiceInvitation::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'invoice_id' => $this->entity_obj->id,
|
||||
'client_contact_id' => $contact->id,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->entity == 'quote') {
|
||||
/** @var \App\Models\Quote $quote */
|
||||
$quote = Quote::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'client_id' => $client->id,
|
||||
]);
|
||||
|
||||
$this->entity_obj = $quote;
|
||||
|
||||
$invitation = QuoteInvitation::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'quote_id' => $this->entity_obj->id,
|
||||
'client_contact_id' => $contact->id,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->entity == 'purchaseOrder') {
|
||||
/** @var \App\Models\Vendor $vendor **/
|
||||
$vendor = Vendor::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
]);
|
||||
|
||||
/** @var \App\Models\VendorContact $contact **/
|
||||
$contact = VendorContact::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'vendor_id' => $vendor->id,
|
||||
'is_primary' => 1,
|
||||
'send_email' => true,
|
||||
]);
|
||||
|
||||
/** @var \App\Models\PurchaseOrder $purchase_order **/
|
||||
$purchase_order = PurchaseOrder::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'vendor_id' => $vendor->id,
|
||||
]);
|
||||
if ($this->entity == 'payment') {
|
||||
/** @var \App\Models\Payment $payment */
|
||||
$payment = Payment::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'client_id' => $client->id,
|
||||
'amount' => 10,
|
||||
'applied' => 10,
|
||||
'refunded' => 5,
|
||||
]);
|
||||
|
||||
$this->entity_obj = $purchase_order;
|
||||
$this->entity_obj = $payment;
|
||||
|
||||
/** @var \App\Models\Invoice $invoice */
|
||||
$invoice = Invoice::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'client_id' => $client->id,
|
||||
'amount' => 10,
|
||||
'balance' => 10,
|
||||
'number' => rand(1, 10000)
|
||||
]);
|
||||
|
||||
/** @var \App\Models\InvoiceInvitation $invitation */
|
||||
$invitation = InvoiceInvitation::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'invoice_id' => $invoice->id,
|
||||
'client_contact_id' => $contact->id,
|
||||
]);
|
||||
|
||||
/** @var \App\Models\Invoice $invoice */
|
||||
$this->entity_obj->invoices()->attach($invoice->id, [
|
||||
'amount' => 10,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$this->entity || $this->entity == 'invoice') {
|
||||
/** @var \App\Models\Invoice $invoice */
|
||||
$invoice = Invoice::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'client_id' => $client->id,
|
||||
'amount' => '10',
|
||||
'balance' => '10',
|
||||
]);
|
||||
|
||||
$this->entity_obj = $invoice;
|
||||
|
||||
$invitation = InvoiceInvitation::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'invoice_id' => $this->entity_obj->id,
|
||||
'client_contact_id' => $contact->id,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->entity == 'quote') {
|
||||
/** @var \App\Models\Quote $quote */
|
||||
$quote = Quote::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'client_id' => $client->id,
|
||||
]);
|
||||
|
||||
$this->entity_obj = $quote;
|
||||
|
||||
$invitation = QuoteInvitation::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'quote_id' => $this->entity_obj->id,
|
||||
'client_contact_id' => $contact->id,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->entity == 'purchaseOrder') {
|
||||
/** @var \App\Models\Vendor $vendor **/
|
||||
$vendor = Vendor::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
]);
|
||||
|
||||
/** @var \App\Models\VendorContact $contact **/
|
||||
$contact = VendorContact::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'vendor_id' => $vendor->id,
|
||||
'is_primary' => 1,
|
||||
'send_email' => true,
|
||||
]);
|
||||
|
||||
/** @var \App\Models\PurchaseOrder $purchase_order **/
|
||||
$purchase_order = PurchaseOrder::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'vendor_id' => $vendor->id,
|
||||
]);
|
||||
|
||||
$this->entity_obj = $purchase_order;
|
||||
|
||||
/** @var \App\Models\PurchaseOrderInvitation $invitation **/
|
||||
$invitation = PurchaseOrderInvitation::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'purchase_order_id' => $this->entity_obj->id,
|
||||
'vendor_contact_id' => $contact->id,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($vendor) {
|
||||
$this->entity_obj->setRelation('invitations', $invitation);
|
||||
$this->entity_obj->setRelation('vendor', $vendor);
|
||||
$this->entity_obj->setRelation('company', $user->company());
|
||||
$this->entity_obj->load('vendor');
|
||||
$vendor->setRelation('company', $user->company());
|
||||
$vendor->load('company');
|
||||
} else {
|
||||
$this->entity_obj->setRelation('invitations', $invitation);
|
||||
$this->entity_obj->setRelation('client', $client);
|
||||
$this->entity_obj->setRelation('company', $user->company());
|
||||
$this->entity_obj->load('client');
|
||||
$client->setRelation('company', $user->company());
|
||||
$client->load('company');
|
||||
}
|
||||
|
||||
}catch(\Throwable $th){
|
||||
nlog("Throwable:: transaction:: TemplateEngine MockEntity");
|
||||
|
||||
DB::connection(config('database.default'))->rollBack();
|
||||
|
||||
/** @var \App\Models\PurchaseOrderInvitation $invitation **/
|
||||
$invitation = PurchaseOrderInvitation::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company()->id,
|
||||
'purchase_order_id' => $this->entity_obj->id,
|
||||
'vendor_contact_id' => $contact->id,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($vendor) {
|
||||
$this->entity_obj->setRelation('invitations', $invitation);
|
||||
$this->entity_obj->setRelation('vendor', $vendor);
|
||||
$this->entity_obj->setRelation('company', $user->company());
|
||||
$this->entity_obj->load('vendor');
|
||||
$vendor->setRelation('company', $user->company());
|
||||
$vendor->load('company');
|
||||
} else {
|
||||
$this->entity_obj->setRelation('invitations', $invitation);
|
||||
$this->entity_obj->setRelation('client', $client);
|
||||
$this->entity_obj->setRelation('company', $user->company());
|
||||
$this->entity_obj->load('client');
|
||||
$client->setRelation('company', $user->company());
|
||||
$client->load('company');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function tearDown()
|
||||
|
@ -24,17 +24,21 @@ trait WithSecureContext
|
||||
*/
|
||||
public function getContext(): mixed
|
||||
{
|
||||
return session()->get('secureContext.invoice-pay');
|
||||
|
||||
return \Illuminate\Support\Facades\Cache::get(session()->getId()) ?? [];
|
||||
// return session()->get('secureContext.invoice-pay');
|
||||
}
|
||||
|
||||
public function setContext(string $property, $value): array
|
||||
{
|
||||
$clone = session()->pull('secureContext.invoice-pay', default: []);
|
||||
$clone = $this->getContext();
|
||||
// $clone = session()->pull('secureContext.invoice-pay', default: []);
|
||||
|
||||
data_set($clone, $property, $value);
|
||||
|
||||
session()->put('secureContext.invoice-pay', $clone);
|
||||
// session()->put('secureContext.invoice-pay', $clone);
|
||||
|
||||
\Illuminate\Support\Facades\Cache::put(session()->getId(), $clone, now()->addHour());
|
||||
$this->dispatch(self::CONTEXT_UPDATE);
|
||||
|
||||
return $clone;
|
||||
@ -42,6 +46,7 @@ trait WithSecureContext
|
||||
|
||||
public function resetContext(): void
|
||||
{
|
||||
\Illuminate\Support\Facades\Cache::forget(session()->getId());
|
||||
session()->forget('secureContext.invoice-pay');
|
||||
}
|
||||
}
|
||||
|
@ -226,4 +226,4 @@
|
||||
],
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
}
|
379
composer.lock
generated
379
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -55,7 +55,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'after_commit' => false,
|
||||
'after_commit' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -27,7 +27,7 @@ class InvoiceFactory extends Factory
|
||||
return [
|
||||
'status_id' => Invoice::STATUS_SENT,
|
||||
'number' => $this->faker->ean13(),
|
||||
'discount' => $this->faker->numberBetween(1, 10),
|
||||
'discount' => rand(1,10),
|
||||
'is_amount_discount' => (bool) random_int(0, 1),
|
||||
'tax_name1' => 'GST',
|
||||
'tax_rate1' => 10,
|
||||
|
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('e_invoicing_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('license',64);
|
||||
$table->uuid('token')->unique()->index();
|
||||
$table->string('account_key',64);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
if (Ninja::isSelfHost()) {
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('companies', 'e_invoicing_token')) {
|
||||
$table->dropColumn('e_invoicing_token');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('accounts', 'e_invoicing_token')) {
|
||||
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
$table->string('e_invoicing_token')->nullable();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
|
||||
}
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@ -8,8 +9,23 @@ return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// Schema::table('companies', function (Blueprint $table) {
|
||||
// $table->string('e_invoicing_token')->nullable();
|
||||
// });
|
||||
if(Ninja::isSelfHost())
|
||||
{
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('companies', 'e_invoicing_token')) {
|
||||
$table->dropColumn('e_invoicing_token');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('accounts', 'e_invoicing_token')) {
|
||||
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
|
||||
$table->string('e_invoicing_token')->nullable();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
@ -5405,6 +5405,47 @@ $lang = array(
|
||||
'invalid_vat_number' => "The VAT number is not valid for the selected country. Format should be Country Code followed by number only ie, DE123456789",
|
||||
'acts_as_sender' => 'Acts as Sender',
|
||||
'acts_as_receiver' => 'Acts as Receiver',
|
||||
'peppol_token_generated' => 'PEPPOL token successfully generated.',
|
||||
'peppol_token_description' => 'Token is used as another step to make sure invoices are sent securely. Unlike white-label licenses, token can be rotated at any point without need to wait on Invoice Ninja support.',
|
||||
'peppol_token_warning' => 'You need to generate a token to continue.',
|
||||
'generate_token' => 'Generate Token',
|
||||
'total_credits_amount' => 'Amount of Credits',
|
||||
'sales_above_threshold' => 'Sales above threshold',
|
||||
'changing_vat_and_id_number_note' => 'You can\'t change your VAT number or ID number once PEPPOL is set up.',
|
||||
'iban_help' => 'The full IBAN number',
|
||||
'bic_swift' => 'BIC/Swift code',
|
||||
'bic_swift_help' => 'The Bank identifer',
|
||||
'payer_bank_account' => 'Payer Bank Account Number',
|
||||
'payer_bank_account_help' => 'The bank account number of the payer',
|
||||
'bsb_sort' => 'BSB / Sort Code',
|
||||
'bsb_sort_help' => 'Bank Branch Code',
|
||||
'card_type' => 'Card Type',
|
||||
'card_type_help' => 'ie. VISA, AMEX',
|
||||
'card_number_help' => 'last 4 digits only',
|
||||
'card_holder' => 'Card Holder Name',
|
||||
'tokenize' => 'Tokenize',
|
||||
'tokenize_help' => 'Tokenize payment method for future use.',
|
||||
'credit_card_stripe_help' => 'Accept credit card payments using Stripe.',
|
||||
'bank_transfer_stripe_help' => 'ACH direct debit. USD payments, instant verification available.',
|
||||
'alipay_stripe_help' => 'Alipay allows users in China to pay securely using their mobile wallets.',
|
||||
'sofort_stripe_help' => 'Sofort is a popular European payment method that enables bank transfers in real-time, primarily used in Germany and Austria.',
|
||||
'apple_pay_stripe_help' => 'Apple/Google Pay for users with Apple/Android devices, using saved card information for easy checkout.',
|
||||
'sepa_stripe_help' => 'SEPA Direct Debit (Single Euro Payments Area).',
|
||||
'bancontact_stripe_help' => 'Bancontact is a widely used payment method in Belgium.',
|
||||
'ideal_stripe_help' => 'iDEAL is the most popular payment method in the Netherlands.',
|
||||
'giropay_stripe_help' => 'Giropay is a German payment method that facilitates secure and immediate online bank transfers.',
|
||||
'przelewy24_stripe_help' => 'Przelewy24 is a common payment method in Poland.',
|
||||
'direct_debit_stripe_help' => 'Stripe Bank Transfers using Stripes virtual bank accounts, available in Japan, UK, USA, Europe and Mexico. Ensure this is enabled in Stripe!',
|
||||
'eps_stripe_help' => 'EPS is an Austrian online payment system.',
|
||||
'acss_stripe_help' => 'ACSS (Automated Clearing Settlement System) Direct Debit for Canadian bank accounts.',
|
||||
'becs_stripe_help' => 'BECS Direct Debit for Australian bank accounts.',
|
||||
'klarna_stripe_help' => 'Klarna buy now and pay later in installments or on a set schedule.',
|
||||
'bacs_stripe_help' => 'BACS Direct Debit for UK bank accounts, commonly used for subscription billing.',
|
||||
'fpx_stripe_help' => 'FPX is a popular online payment method in Malaysia.',
|
||||
'payment_means' => 'Payment Means',
|
||||
'act_as_sender' => 'Send E-Invoice',
|
||||
'act_as_receiver' => 'Receive E-Invoice',
|
||||
'saved_einvoice_details' => 'Saved E-Invoice Settings',
|
||||
'sales_above_threshold' => 'Sales above threshold',
|
||||
);
|
||||
|
||||
|
@ -5403,6 +5403,7 @@ Développe automatiquement la section des notes dans le tableau de produits pour
|
||||
'invalid_vat_number' => "Le numéro de TVA n'est pas valide pour le pays sélectionné. Le format doit être le code du pays suivi uniquement du numéro, par exemple DE123456789.",
|
||||
'acts_as_sender' => 'Agit en tant qu\'expéditeur',
|
||||
'acts_as_receiver' => 'Agit en tant que destinataire',
|
||||
'sales_above_threshold' => 'Ventes dépassant le seuil',
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
@ -5405,6 +5405,7 @@ $lang = array(
|
||||
'invalid_vat_number' => "Mã số VAT không hợp lệ đối với quốc gia đã chọn. Định dạng phải là Mã quốc gia theo sau là số, ví dụ: DE123456789",
|
||||
'acts_as_sender' => 'Hoạt động như Người gửi',
|
||||
'acts_as_receiver' => 'Hoạt động như Người nhận',
|
||||
'sales_above_threshold' => 'Doanh số vượt ngưỡng',
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
109
public/build/assets/app-2353b88b.js
vendored
109
public/build/assets/app-2353b88b.js
vendored
File diff suppressed because one or more lines are too long
109
public/build/assets/app-780c4e5a.js
vendored
Normal file
109
public/build/assets/app-780c4e5a.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -12,7 +12,7 @@
|
||||
"file": "assets/wait-8f4ae121.js"
|
||||
},
|
||||
"resources/js/app.js": {
|
||||
"file": "assets/app-2353b88b.js",
|
||||
"file": "assets/app-780c4e5a.js",
|
||||
"imports": [
|
||||
"_index-08e160a7.js",
|
||||
"__commonjsHelpers-725317a4.js"
|
||||
|
240
public/vendor/livewire/livewire.esm.js
vendored
240
public/vendor/livewire/livewire.esm.js
vendored
@ -17,9 +17,9 @@ var __copyProps = (to, from, except, desc) => {
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod));
|
||||
|
||||
// node_modules/alpinejs/dist/module.cjs.js
|
||||
// ../alpine/packages/alpinejs/dist/module.cjs.js
|
||||
var require_module_cjs = __commonJS({
|
||||
"node_modules/alpinejs/dist/module.cjs.js"(exports, module) {
|
||||
"../alpine/packages/alpinejs/dist/module.cjs.js"(exports, module) {
|
||||
var __create2 = Object.create;
|
||||
var __defProp2 = Object.defineProperty;
|
||||
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
||||
@ -1432,10 +1432,10 @@ var require_module_cjs = __commonJS({
|
||||
});
|
||||
}
|
||||
function cleanupElement(el) {
|
||||
if (el._x_cleanups) {
|
||||
while (el._x_cleanups.length)
|
||||
el._x_cleanups.pop()();
|
||||
}
|
||||
var _a, _b;
|
||||
(_a = el._x_effects) == null ? void 0 : _a.forEach(dequeueJob);
|
||||
while ((_b = el._x_cleanups) == null ? void 0 : _b.length)
|
||||
el._x_cleanups.pop()();
|
||||
}
|
||||
var observer = new MutationObserver(onMutate);
|
||||
var currentlyObserving = false;
|
||||
@ -1673,27 +1673,23 @@ var require_module_cjs = __commonJS({
|
||||
magics[name] = callback;
|
||||
}
|
||||
function injectMagics(obj, el) {
|
||||
let memoizedUtilities = getUtilities(el);
|
||||
Object.entries(magics).forEach(([name, callback]) => {
|
||||
let memoizedUtilities = null;
|
||||
function getUtilities() {
|
||||
if (memoizedUtilities) {
|
||||
return memoizedUtilities;
|
||||
} else {
|
||||
let [utilities, cleanup] = getElementBoundUtilities(el);
|
||||
memoizedUtilities = { interceptor, ...utilities };
|
||||
onElRemoved(el, cleanup);
|
||||
return memoizedUtilities;
|
||||
}
|
||||
}
|
||||
Object.defineProperty(obj, `$${name}`, {
|
||||
get() {
|
||||
return callback(el, getUtilities());
|
||||
return callback(el, memoizedUtilities);
|
||||
},
|
||||
enumerable: false
|
||||
});
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
function getUtilities(el) {
|
||||
let [utilities, cleanup] = getElementBoundUtilities(el);
|
||||
let utils = { interceptor, ...utilities };
|
||||
onElRemoved(el, cleanup);
|
||||
return utils;
|
||||
}
|
||||
function tryCatch(el, expression, callback, ...args) {
|
||||
try {
|
||||
return callback(...args);
|
||||
@ -2067,8 +2063,8 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
function destroyTree(root, walker = walk) {
|
||||
walker(root, (el) => {
|
||||
cleanupAttributes(el);
|
||||
cleanupElement(el);
|
||||
cleanupAttributes(el);
|
||||
});
|
||||
}
|
||||
function warnAboutMissingPlugins() {
|
||||
@ -2561,7 +2557,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
}
|
||||
function bindInputValue(el, value) {
|
||||
if (el.type === "radio") {
|
||||
if (isRadio(el)) {
|
||||
if (el.attributes.value === void 0) {
|
||||
el.value = value;
|
||||
}
|
||||
@ -2572,7 +2568,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
el.checked = checkedAttrLooseCompare(el.value, value);
|
||||
}
|
||||
}
|
||||
} else if (el.type === "checkbox") {
|
||||
} else if (isCheckbox(el)) {
|
||||
if (Number.isInteger(value)) {
|
||||
el.value = value;
|
||||
} else if (!Array.isArray(value) && typeof value !== "boolean" && ![null, void 0].includes(value)) {
|
||||
@ -2648,34 +2644,37 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
return rawValue ? Boolean(rawValue) : null;
|
||||
}
|
||||
var booleanAttributes = /* @__PURE__ */ new Set([
|
||||
"allowfullscreen",
|
||||
"async",
|
||||
"autofocus",
|
||||
"autoplay",
|
||||
"checked",
|
||||
"controls",
|
||||
"default",
|
||||
"defer",
|
||||
"disabled",
|
||||
"formnovalidate",
|
||||
"inert",
|
||||
"ismap",
|
||||
"itemscope",
|
||||
"loop",
|
||||
"multiple",
|
||||
"muted",
|
||||
"nomodule",
|
||||
"novalidate",
|
||||
"open",
|
||||
"playsinline",
|
||||
"readonly",
|
||||
"required",
|
||||
"reversed",
|
||||
"selected",
|
||||
"shadowrootclonable",
|
||||
"shadowrootdelegatesfocus",
|
||||
"shadowrootserializable"
|
||||
]);
|
||||
function isBooleanAttr(attrName) {
|
||||
const booleanAttributes = [
|
||||
"disabled",
|
||||
"checked",
|
||||
"required",
|
||||
"readonly",
|
||||
"open",
|
||||
"selected",
|
||||
"autofocus",
|
||||
"itemscope",
|
||||
"multiple",
|
||||
"novalidate",
|
||||
"allowfullscreen",
|
||||
"allowpaymentrequest",
|
||||
"formnovalidate",
|
||||
"autoplay",
|
||||
"controls",
|
||||
"loop",
|
||||
"muted",
|
||||
"playsinline",
|
||||
"default",
|
||||
"ismap",
|
||||
"reversed",
|
||||
"async",
|
||||
"defer",
|
||||
"nomodule"
|
||||
];
|
||||
return booleanAttributes.includes(attrName);
|
||||
return booleanAttributes.has(attrName);
|
||||
}
|
||||
function attributeShouldntBePreservedIfFalsy(name) {
|
||||
return !["aria-pressed", "aria-checked", "aria-expanded", "aria-selected"].includes(name);
|
||||
@ -2708,6 +2707,12 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
function isCheckbox(el) {
|
||||
return el.type === "checkbox" || el.localName === "ui-checkbox" || el.localName === "ui-switch";
|
||||
}
|
||||
function isRadio(el) {
|
||||
return el.type === "radio" || el.localName === "ui-radio";
|
||||
}
|
||||
function debounce2(func, wait) {
|
||||
var timeout;
|
||||
return function() {
|
||||
@ -2776,10 +2781,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
return stores[name];
|
||||
}
|
||||
stores[name] = value;
|
||||
initInterceptors(stores[name]);
|
||||
if (typeof value === "object" && value !== null && value.hasOwnProperty("init") && typeof value.init === "function") {
|
||||
stores[name].init();
|
||||
}
|
||||
initInterceptors(stores[name]);
|
||||
}
|
||||
function getStores() {
|
||||
return stores;
|
||||
@ -2861,7 +2866,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
get raw() {
|
||||
return raw;
|
||||
},
|
||||
version: "3.14.1",
|
||||
version: "3.14.3",
|
||||
flushAndStopDeferringMutations,
|
||||
dontAutoEvaluateFunctions,
|
||||
disableEffectScheduling,
|
||||
@ -3070,7 +3075,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
placeInDom(el._x_teleport, target2, modifiers);
|
||||
});
|
||||
};
|
||||
cleanup(() => clone2.remove());
|
||||
cleanup(() => mutateDom(() => {
|
||||
clone2.remove();
|
||||
destroyTree(clone2);
|
||||
}));
|
||||
});
|
||||
var teleportContainerDuringClone = document.createElement("div");
|
||||
function getTarget(expression) {
|
||||
@ -3294,7 +3302,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
setValue(getInputValue(el, modifiers, e, getValue()));
|
||||
});
|
||||
if (modifiers.includes("fill")) {
|
||||
if ([void 0, null, ""].includes(getValue()) || el.type === "checkbox" && Array.isArray(getValue()) || el.tagName.toLowerCase() === "select" && el.multiple) {
|
||||
if ([void 0, null, ""].includes(getValue()) || isCheckbox(el) && Array.isArray(getValue()) || el.tagName.toLowerCase() === "select" && el.multiple) {
|
||||
setValue(getInputValue(el, modifiers, { target: el }, getValue()));
|
||||
}
|
||||
}
|
||||
@ -3334,7 +3342,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
return mutateDom(() => {
|
||||
if (event instanceof CustomEvent && event.detail !== void 0)
|
||||
return event.detail !== null && event.detail !== void 0 ? event.detail : event.target.value;
|
||||
else if (el.type === "checkbox") {
|
||||
else if (isCheckbox(el)) {
|
||||
if (Array.isArray(currentValue)) {
|
||||
let newValue = null;
|
||||
if (modifiers.includes("number")) {
|
||||
@ -3365,7 +3373,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
});
|
||||
} else {
|
||||
let newValue;
|
||||
if (el.type === "radio") {
|
||||
if (isRadio(el)) {
|
||||
if (event.target.checked) {
|
||||
newValue = event.target.value;
|
||||
} else {
|
||||
@ -3558,7 +3566,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
el._x_lookup = {};
|
||||
effect3(() => loop(el, iteratorNames, evaluateItems, evaluateKey));
|
||||
cleanup(() => {
|
||||
Object.values(el._x_lookup).forEach((el2) => el2.remove());
|
||||
Object.values(el._x_lookup).forEach((el2) => mutateDom(() => {
|
||||
destroyTree(el2);
|
||||
el2.remove();
|
||||
}));
|
||||
delete el._x_prevKeys;
|
||||
delete el._x_lookup;
|
||||
});
|
||||
@ -3627,11 +3638,12 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
for (let i = 0; i < removes.length; i++) {
|
||||
let key = removes[i];
|
||||
if (!!lookup[key]._x_effects) {
|
||||
lookup[key]._x_effects.forEach(dequeueJob);
|
||||
}
|
||||
lookup[key].remove();
|
||||
lookup[key] = null;
|
||||
if (!(key in lookup))
|
||||
continue;
|
||||
mutateDom(() => {
|
||||
destroyTree(lookup[key]);
|
||||
lookup[key].remove();
|
||||
});
|
||||
delete lookup[key];
|
||||
}
|
||||
for (let i = 0; i < moves.length; i++) {
|
||||
@ -3752,12 +3764,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
});
|
||||
el._x_currentIfEl = clone2;
|
||||
el._x_undoIf = () => {
|
||||
walk(clone2, (node) => {
|
||||
if (!!node._x_effects) {
|
||||
node._x_effects.forEach(dequeueJob);
|
||||
}
|
||||
mutateDom(() => {
|
||||
destroyTree(clone2);
|
||||
clone2.remove();
|
||||
});
|
||||
clone2.remove();
|
||||
delete el._x_currentIfEl;
|
||||
};
|
||||
return clone2;
|
||||
@ -3812,9 +3822,9 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
});
|
||||
|
||||
// node_modules/@alpinejs/collapse/dist/module.cjs.js
|
||||
// ../../../../usr/local/lib/node_modules/@alpinejs/collapse/dist/module.cjs.js
|
||||
var require_module_cjs2 = __commonJS({
|
||||
"node_modules/@alpinejs/collapse/dist/module.cjs.js"(exports, module) {
|
||||
"../../../../usr/local/lib/node_modules/@alpinejs/collapse/dist/module.cjs.js"(exports, module) {
|
||||
var __defProp2 = Object.defineProperty;
|
||||
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
||||
@ -3887,7 +3897,7 @@ var require_module_cjs2 = __commonJS({
|
||||
start: { height: current + "px" },
|
||||
end: { height: full + "px" }
|
||||
}, () => el._x_isShown = true, () => {
|
||||
if (Math.abs(el.getBoundingClientRect().height - full) < 1) {
|
||||
if (el.getBoundingClientRect().height == full) {
|
||||
el.style.overflow = null;
|
||||
}
|
||||
});
|
||||
@ -3933,9 +3943,9 @@ var require_module_cjs2 = __commonJS({
|
||||
}
|
||||
});
|
||||
|
||||
// node_modules/@alpinejs/focus/dist/module.cjs.js
|
||||
// ../../../../usr/local/lib/node_modules/@alpinejs/focus/dist/module.cjs.js
|
||||
var require_module_cjs3 = __commonJS({
|
||||
"node_modules/@alpinejs/focus/dist/module.cjs.js"(exports, module) {
|
||||
"../../../../usr/local/lib/node_modules/@alpinejs/focus/dist/module.cjs.js"(exports, module) {
|
||||
var __create2 = Object.create;
|
||||
var __defProp2 = Object.defineProperty;
|
||||
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
||||
@ -4935,9 +4945,9 @@ var require_module_cjs3 = __commonJS({
|
||||
}
|
||||
});
|
||||
|
||||
// node_modules/@alpinejs/persist/dist/module.cjs.js
|
||||
// ../../../../usr/local/lib/node_modules/@alpinejs/persist/dist/module.cjs.js
|
||||
var require_module_cjs4 = __commonJS({
|
||||
"node_modules/@alpinejs/persist/dist/module.cjs.js"(exports, module) {
|
||||
"../../../../usr/local/lib/node_modules/@alpinejs/persist/dist/module.cjs.js"(exports, module) {
|
||||
var __defProp2 = Object.defineProperty;
|
||||
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
||||
@ -5024,9 +5034,9 @@ var require_module_cjs4 = __commonJS({
|
||||
}
|
||||
});
|
||||
|
||||
// node_modules/@alpinejs/intersect/dist/module.cjs.js
|
||||
// ../../../../usr/local/lib/node_modules/@alpinejs/intersect/dist/module.cjs.js
|
||||
var require_module_cjs5 = __commonJS({
|
||||
"node_modules/@alpinejs/intersect/dist/module.cjs.js"(exports, module) {
|
||||
"../../../../usr/local/lib/node_modules/@alpinejs/intersect/dist/module.cjs.js"(exports, module) {
|
||||
var __defProp2 = Object.defineProperty;
|
||||
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
||||
@ -5178,9 +5188,9 @@ var require_module_cjs6 = __commonJS({
|
||||
}
|
||||
});
|
||||
|
||||
// node_modules/@alpinejs/anchor/dist/module.cjs.js
|
||||
// ../alpine/packages/anchor/dist/module.cjs.js
|
||||
var require_module_cjs7 = __commonJS({
|
||||
"node_modules/@alpinejs/anchor/dist/module.cjs.js"(exports, module) {
|
||||
"../alpine/packages/anchor/dist/module.cjs.js"(exports, module) {
|
||||
var __defProp2 = Object.defineProperty;
|
||||
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
||||
@ -6716,9 +6726,9 @@ var require_nprogress = __commonJS({
|
||||
}
|
||||
});
|
||||
|
||||
// node_modules/@alpinejs/morph/dist/module.cjs.js
|
||||
// ../alpine/packages/morph/dist/module.cjs.js
|
||||
var require_module_cjs8 = __commonJS({
|
||||
"node_modules/@alpinejs/morph/dist/module.cjs.js"(exports, module) {
|
||||
"../alpine/packages/morph/dist/module.cjs.js"(exports, module) {
|
||||
var __defProp2 = Object.defineProperty;
|
||||
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
||||
@ -7078,9 +7088,9 @@ var require_module_cjs8 = __commonJS({
|
||||
}
|
||||
});
|
||||
|
||||
// node_modules/@alpinejs/mask/dist/module.cjs.js
|
||||
// ../../../../usr/local/lib/node_modules/@alpinejs/mask/dist/module.cjs.js
|
||||
var require_module_cjs9 = __commonJS({
|
||||
"node_modules/@alpinejs/mask/dist/module.cjs.js"(exports, module) {
|
||||
"../../../../usr/local/lib/node_modules/@alpinejs/mask/dist/module.cjs.js"(exports, module) {
|
||||
var __defProp2 = Object.defineProperty;
|
||||
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
||||
@ -8855,8 +8865,10 @@ function restoreScrollPositionOrScrollToTop() {
|
||||
}
|
||||
};
|
||||
queueMicrotask(() => {
|
||||
scroll(document.body);
|
||||
document.querySelectorAll(["[x-navigate\\:scroll]", "[wire\\:scroll]"]).forEach(scroll);
|
||||
queueMicrotask(() => {
|
||||
scroll(document.body);
|
||||
document.querySelectorAll(["[x-navigate\\:scroll]", "[wire\\:scroll]"]).forEach(scroll);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -9005,6 +9017,44 @@ function injectStyles() {
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// js/plugins/navigate/popover.js
|
||||
function packUpPersistedPopovers(persistedEl) {
|
||||
persistedEl.querySelectorAll(":popover-open").forEach((el) => {
|
||||
el.setAttribute("data-navigate-popover-open", "");
|
||||
let animations = el.getAnimations();
|
||||
el._pausedAnimations = animations.map((animation) => ({
|
||||
keyframes: animation.effect.getKeyframes(),
|
||||
options: {
|
||||
duration: animation.effect.getTiming().duration,
|
||||
easing: animation.effect.getTiming().easing,
|
||||
fill: animation.effect.getTiming().fill,
|
||||
iterations: animation.effect.getTiming().iterations
|
||||
},
|
||||
currentTime: animation.currentTime,
|
||||
playState: animation.playState
|
||||
}));
|
||||
animations.forEach((i) => i.pause());
|
||||
});
|
||||
}
|
||||
function unPackPersistedPopovers(persistedEl) {
|
||||
persistedEl.querySelectorAll("[data-navigate-popover-open]").forEach((el) => {
|
||||
el.removeAttribute("data-navigate-popover-open");
|
||||
queueMicrotask(() => {
|
||||
if (!el.isConnected)
|
||||
return;
|
||||
el.showPopover();
|
||||
el.getAnimations().forEach((i) => i.finish());
|
||||
if (el._pausedAnimations) {
|
||||
el._pausedAnimations.forEach(({ keyframes, options, currentTime, now, playState }) => {
|
||||
let animation = el.animate(keyframes, options);
|
||||
animation.currentTime = currentTime;
|
||||
});
|
||||
delete el._pausedAnimations;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// js/plugins/navigate/page.js
|
||||
var oldBodyScriptTagHashes = [];
|
||||
var attributesExemptFromScriptTagHashing = [
|
||||
@ -9143,7 +9193,7 @@ var autofocus = false;
|
||||
function navigate_default(Alpine19) {
|
||||
Alpine19.navigate = (url) => {
|
||||
let destination = createUrlObjectFromString(url);
|
||||
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
|
||||
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
|
||||
url: destination,
|
||||
history: false,
|
||||
cached: false
|
||||
@ -9174,7 +9224,7 @@ function navigate_default(Alpine19) {
|
||||
storeThePrefetchedHtmlForWhenALinkIsClicked(html, destination, finalDestination);
|
||||
});
|
||||
whenItIsReleased(() => {
|
||||
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
|
||||
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
|
||||
url: destination,
|
||||
history: false,
|
||||
cached: false
|
||||
@ -9188,7 +9238,7 @@ function navigate_default(Alpine19) {
|
||||
function navigateTo(destination, shouldPushToHistoryState = true) {
|
||||
showProgressBar && showAndStartProgressBar();
|
||||
fetchHtmlOrUsePrefetchedHtml(destination, (html, finalDestination) => {
|
||||
fireEventForOtherLibariesToHookInto("alpine:navigating");
|
||||
fireEventForOtherLibrariesToHookInto("alpine:navigating");
|
||||
restoreScroll && storeScrollInformationInHtmlBeforeNavigatingAway();
|
||||
showProgressBar && finishAndHideProgressBar();
|
||||
cleanupAlpineElementsOnThePageThatArentInsideAPersistedElement();
|
||||
@ -9196,6 +9246,7 @@ function navigate_default(Alpine19) {
|
||||
preventAlpineFromPickingUpDomChanges(Alpine19, (andAfterAllThis) => {
|
||||
enablePersist && storePersistantElementsForLater((persistedEl) => {
|
||||
packUpPersistedTeleports(persistedEl);
|
||||
packUpPersistedPopovers(persistedEl);
|
||||
});
|
||||
if (shouldPushToHistoryState) {
|
||||
updateUrlAndStoreLatestHtmlForFutureBackButtons(html, finalDestination);
|
||||
@ -9206,6 +9257,7 @@ function navigate_default(Alpine19) {
|
||||
removeAnyLeftOverStaleTeleportTargets(document.body);
|
||||
enablePersist && putPersistantElementsBack((persistedEl, newStub) => {
|
||||
unPackPersistedTeleports(persistedEl);
|
||||
unPackPersistedPopovers(persistedEl);
|
||||
});
|
||||
restoreScrollPositionOrScrollToTop();
|
||||
afterNewScriptsAreDoneLoading(() => {
|
||||
@ -9214,7 +9266,7 @@ function navigate_default(Alpine19) {
|
||||
autofocus && autofocusElementsWithTheAutofocusAttribute();
|
||||
});
|
||||
nowInitializeAlpineOnTheNewPage(Alpine19);
|
||||
fireEventForOtherLibariesToHookInto("alpine:navigated");
|
||||
fireEventForOtherLibrariesToHookInto("alpine:navigated");
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -9224,7 +9276,7 @@ function navigate_default(Alpine19) {
|
||||
whenTheBackOrForwardButtonIsClicked((ifThePageBeingVisitedHasntBeenCached) => {
|
||||
ifThePageBeingVisitedHasntBeenCached((url) => {
|
||||
let destination = createUrlObjectFromString(url);
|
||||
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
|
||||
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
|
||||
url: destination,
|
||||
history: true,
|
||||
cached: false
|
||||
@ -9236,7 +9288,7 @@ function navigate_default(Alpine19) {
|
||||
});
|
||||
}, (html, url, currentPageUrl, currentPageKey) => {
|
||||
let destination = createUrlObjectFromString(url);
|
||||
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
|
||||
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
|
||||
url: destination,
|
||||
history: true,
|
||||
cached: true
|
||||
@ -9244,29 +9296,31 @@ function navigate_default(Alpine19) {
|
||||
if (prevented)
|
||||
return;
|
||||
storeScrollInformationInHtmlBeforeNavigatingAway();
|
||||
fireEventForOtherLibariesToHookInto("alpine:navigating");
|
||||
fireEventForOtherLibrariesToHookInto("alpine:navigating");
|
||||
updateCurrentPageHtmlInSnapshotCacheForLaterBackButtonClicks(currentPageUrl, currentPageKey);
|
||||
preventAlpineFromPickingUpDomChanges(Alpine19, (andAfterAllThis) => {
|
||||
enablePersist && storePersistantElementsForLater((persistedEl) => {
|
||||
packUpPersistedTeleports(persistedEl);
|
||||
packUpPersistedPopovers(persistedEl);
|
||||
});
|
||||
swapCurrentPageWithNewHtml(html, () => {
|
||||
removeAnyLeftOverStaleProgressBars();
|
||||
removeAnyLeftOverStaleTeleportTargets(document.body);
|
||||
enablePersist && putPersistantElementsBack((persistedEl, newStub) => {
|
||||
unPackPersistedTeleports(persistedEl);
|
||||
unPackPersistedPopovers(persistedEl);
|
||||
});
|
||||
restoreScrollPositionOrScrollToTop();
|
||||
andAfterAllThis(() => {
|
||||
autofocus && autofocusElementsWithTheAutofocusAttribute();
|
||||
nowInitializeAlpineOnTheNewPage(Alpine19);
|
||||
fireEventForOtherLibariesToHookInto("alpine:navigated");
|
||||
fireEventForOtherLibrariesToHookInto("alpine:navigated");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
setTimeout(() => {
|
||||
fireEventForOtherLibariesToHookInto("alpine:navigated");
|
||||
fireEventForOtherLibrariesToHookInto("alpine:navigated");
|
||||
});
|
||||
}
|
||||
function fetchHtmlOrUsePrefetchedHtml(fromDestination, callback) {
|
||||
@ -9283,7 +9337,7 @@ function preventAlpineFromPickingUpDomChanges(Alpine19, callback) {
|
||||
});
|
||||
});
|
||||
}
|
||||
function fireEventForOtherLibariesToHookInto(name, detail) {
|
||||
function fireEventForOtherLibrariesToHookInto(name, detail) {
|
||||
let event = new CustomEvent(name, {
|
||||
cancelable: true,
|
||||
bubbles: true,
|
||||
@ -9799,6 +9853,7 @@ function morph2(component, el, html) {
|
||||
},
|
||||
lookahead: false
|
||||
});
|
||||
trigger("morphed", { el, component });
|
||||
}
|
||||
function isntElement(el) {
|
||||
return typeof el.hasAttribute !== "function";
|
||||
@ -10868,3 +10923,4 @@ focus-trap/dist/focus-trap.js:
|
||||
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE
|
||||
*)
|
||||
*/
|
||||
//# sourceMappingURL=livewire.esm.js.map
|
||||
|
7
public/vendor/livewire/livewire.esm.js.map
vendored
Normal file
7
public/vendor/livewire/livewire.esm.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
226
public/vendor/livewire/livewire.js
vendored
226
public/vendor/livewire/livewire.js
vendored
@ -712,7 +712,7 @@
|
||||
uploadManager.cancelUpload(name, cancelledCallback);
|
||||
}
|
||||
|
||||
// node_modules/alpinejs/dist/module.esm.js
|
||||
// ../alpine/packages/alpinejs/dist/module.esm.js
|
||||
var flushPending = false;
|
||||
var flushing = false;
|
||||
var queue = [];
|
||||
@ -851,10 +851,9 @@
|
||||
});
|
||||
}
|
||||
function cleanupElement(el) {
|
||||
if (el._x_cleanups) {
|
||||
while (el._x_cleanups.length)
|
||||
el._x_cleanups.pop()();
|
||||
}
|
||||
el._x_effects?.forEach(dequeueJob);
|
||||
while (el._x_cleanups?.length)
|
||||
el._x_cleanups.pop()();
|
||||
}
|
||||
var observer = new MutationObserver(onMutate);
|
||||
var currentlyObserving = false;
|
||||
@ -1092,27 +1091,23 @@
|
||||
magics[name] = callback;
|
||||
}
|
||||
function injectMagics(obj, el) {
|
||||
let memoizedUtilities = getUtilities(el);
|
||||
Object.entries(magics).forEach(([name, callback]) => {
|
||||
let memoizedUtilities = null;
|
||||
function getUtilities() {
|
||||
if (memoizedUtilities) {
|
||||
return memoizedUtilities;
|
||||
} else {
|
||||
let [utilities, cleanup2] = getElementBoundUtilities(el);
|
||||
memoizedUtilities = { interceptor, ...utilities };
|
||||
onElRemoved(el, cleanup2);
|
||||
return memoizedUtilities;
|
||||
}
|
||||
}
|
||||
Object.defineProperty(obj, `$${name}`, {
|
||||
get() {
|
||||
return callback(el, getUtilities());
|
||||
return callback(el, memoizedUtilities);
|
||||
},
|
||||
enumerable: false
|
||||
});
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
function getUtilities(el) {
|
||||
let [utilities, cleanup2] = getElementBoundUtilities(el);
|
||||
let utils = { interceptor, ...utilities };
|
||||
onElRemoved(el, cleanup2);
|
||||
return utils;
|
||||
}
|
||||
function tryCatch(el, expression, callback, ...args) {
|
||||
try {
|
||||
return callback(...args);
|
||||
@ -1486,8 +1481,8 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
function destroyTree(root, walker = walk) {
|
||||
walker(root, (el) => {
|
||||
cleanupAttributes(el);
|
||||
cleanupElement(el);
|
||||
cleanupAttributes(el);
|
||||
});
|
||||
}
|
||||
function warnAboutMissingPlugins() {
|
||||
@ -1980,7 +1975,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
}
|
||||
function bindInputValue(el, value) {
|
||||
if (el.type === "radio") {
|
||||
if (isRadio(el)) {
|
||||
if (el.attributes.value === void 0) {
|
||||
el.value = value;
|
||||
}
|
||||
@ -1991,7 +1986,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
el.checked = checkedAttrLooseCompare(el.value, value);
|
||||
}
|
||||
}
|
||||
} else if (el.type === "checkbox") {
|
||||
} else if (isCheckbox(el)) {
|
||||
if (Number.isInteger(value)) {
|
||||
el.value = value;
|
||||
} else if (!Array.isArray(value) && typeof value !== "boolean" && ![null, void 0].includes(value)) {
|
||||
@ -2067,34 +2062,37 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
return rawValue ? Boolean(rawValue) : null;
|
||||
}
|
||||
var booleanAttributes = /* @__PURE__ */ new Set([
|
||||
"allowfullscreen",
|
||||
"async",
|
||||
"autofocus",
|
||||
"autoplay",
|
||||
"checked",
|
||||
"controls",
|
||||
"default",
|
||||
"defer",
|
||||
"disabled",
|
||||
"formnovalidate",
|
||||
"inert",
|
||||
"ismap",
|
||||
"itemscope",
|
||||
"loop",
|
||||
"multiple",
|
||||
"muted",
|
||||
"nomodule",
|
||||
"novalidate",
|
||||
"open",
|
||||
"playsinline",
|
||||
"readonly",
|
||||
"required",
|
||||
"reversed",
|
||||
"selected",
|
||||
"shadowrootclonable",
|
||||
"shadowrootdelegatesfocus",
|
||||
"shadowrootserializable"
|
||||
]);
|
||||
function isBooleanAttr(attrName) {
|
||||
const booleanAttributes = [
|
||||
"disabled",
|
||||
"checked",
|
||||
"required",
|
||||
"readonly",
|
||||
"open",
|
||||
"selected",
|
||||
"autofocus",
|
||||
"itemscope",
|
||||
"multiple",
|
||||
"novalidate",
|
||||
"allowfullscreen",
|
||||
"allowpaymentrequest",
|
||||
"formnovalidate",
|
||||
"autoplay",
|
||||
"controls",
|
||||
"loop",
|
||||
"muted",
|
||||
"playsinline",
|
||||
"default",
|
||||
"ismap",
|
||||
"reversed",
|
||||
"async",
|
||||
"defer",
|
||||
"nomodule"
|
||||
];
|
||||
return booleanAttributes.includes(attrName);
|
||||
return booleanAttributes.has(attrName);
|
||||
}
|
||||
function attributeShouldntBePreservedIfFalsy(name) {
|
||||
return !["aria-pressed", "aria-checked", "aria-expanded", "aria-selected"].includes(name);
|
||||
@ -2127,6 +2125,12 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
function isCheckbox(el) {
|
||||
return el.type === "checkbox" || el.localName === "ui-checkbox" || el.localName === "ui-switch";
|
||||
}
|
||||
function isRadio(el) {
|
||||
return el.type === "radio" || el.localName === "ui-radio";
|
||||
}
|
||||
function debounce(func, wait) {
|
||||
var timeout;
|
||||
return function() {
|
||||
@ -2195,10 +2199,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
return stores[name];
|
||||
}
|
||||
stores[name] = value;
|
||||
initInterceptors(stores[name]);
|
||||
if (typeof value === "object" && value !== null && value.hasOwnProperty("init") && typeof value.init === "function") {
|
||||
stores[name].init();
|
||||
}
|
||||
initInterceptors(stores[name]);
|
||||
}
|
||||
function getStores() {
|
||||
return stores;
|
||||
@ -2280,7 +2284,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
get raw() {
|
||||
return raw;
|
||||
},
|
||||
version: "3.14.1",
|
||||
version: "3.14.3",
|
||||
flushAndStopDeferringMutations,
|
||||
dontAutoEvaluateFunctions,
|
||||
disableEffectScheduling,
|
||||
@ -3136,7 +3140,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
placeInDom(el._x_teleport, target2, modifiers);
|
||||
});
|
||||
};
|
||||
cleanup2(() => clone2.remove());
|
||||
cleanup2(() => mutateDom(() => {
|
||||
clone2.remove();
|
||||
destroyTree(clone2);
|
||||
}));
|
||||
});
|
||||
var teleportContainerDuringClone = document.createElement("div");
|
||||
function getTarget(expression) {
|
||||
@ -3360,7 +3367,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
setValue(getInputValue(el, modifiers, e, getValue()));
|
||||
});
|
||||
if (modifiers.includes("fill")) {
|
||||
if ([void 0, null, ""].includes(getValue()) || el.type === "checkbox" && Array.isArray(getValue()) || el.tagName.toLowerCase() === "select" && el.multiple) {
|
||||
if ([void 0, null, ""].includes(getValue()) || isCheckbox(el) && Array.isArray(getValue()) || el.tagName.toLowerCase() === "select" && el.multiple) {
|
||||
setValue(getInputValue(el, modifiers, { target: el }, getValue()));
|
||||
}
|
||||
}
|
||||
@ -3400,7 +3407,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
return mutateDom(() => {
|
||||
if (event instanceof CustomEvent && event.detail !== void 0)
|
||||
return event.detail !== null && event.detail !== void 0 ? event.detail : event.target.value;
|
||||
else if (el.type === "checkbox") {
|
||||
else if (isCheckbox(el)) {
|
||||
if (Array.isArray(currentValue)) {
|
||||
let newValue = null;
|
||||
if (modifiers.includes("number")) {
|
||||
@ -3431,7 +3438,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
});
|
||||
} else {
|
||||
let newValue;
|
||||
if (el.type === "radio") {
|
||||
if (isRadio(el)) {
|
||||
if (event.target.checked) {
|
||||
newValue = event.target.value;
|
||||
} else {
|
||||
@ -3624,7 +3631,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
el._x_lookup = {};
|
||||
effect3(() => loop(el, iteratorNames, evaluateItems, evaluateKey));
|
||||
cleanup2(() => {
|
||||
Object.values(el._x_lookup).forEach((el2) => el2.remove());
|
||||
Object.values(el._x_lookup).forEach((el2) => mutateDom(() => {
|
||||
destroyTree(el2);
|
||||
el2.remove();
|
||||
}));
|
||||
delete el._x_prevKeys;
|
||||
delete el._x_lookup;
|
||||
});
|
||||
@ -3693,11 +3703,12 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
for (let i = 0; i < removes.length; i++) {
|
||||
let key = removes[i];
|
||||
if (!!lookup[key]._x_effects) {
|
||||
lookup[key]._x_effects.forEach(dequeueJob);
|
||||
}
|
||||
lookup[key].remove();
|
||||
lookup[key] = null;
|
||||
if (!(key in lookup))
|
||||
continue;
|
||||
mutateDom(() => {
|
||||
destroyTree(lookup[key]);
|
||||
lookup[key].remove();
|
||||
});
|
||||
delete lookup[key];
|
||||
}
|
||||
for (let i = 0; i < moves.length; i++) {
|
||||
@ -3818,12 +3829,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
});
|
||||
el._x_currentIfEl = clone2;
|
||||
el._x_undoIf = () => {
|
||||
walk(clone2, (node) => {
|
||||
if (!!node._x_effects) {
|
||||
node._x_effects.forEach(dequeueJob);
|
||||
}
|
||||
mutateDom(() => {
|
||||
destroyTree(clone2);
|
||||
clone2.remove();
|
||||
});
|
||||
clone2.remove();
|
||||
delete el._x_currentIfEl;
|
||||
};
|
||||
return clone2;
|
||||
@ -4762,7 +4771,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
};
|
||||
|
||||
// node_modules/@alpinejs/collapse/dist/module.esm.js
|
||||
// ../../../../usr/local/lib/node_modules/@alpinejs/collapse/dist/module.esm.js
|
||||
function src_default2(Alpine3) {
|
||||
Alpine3.directive("collapse", collapse);
|
||||
collapse.inline = (el, { modifiers }) => {
|
||||
@ -4812,7 +4821,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
start: { height: current + "px" },
|
||||
end: { height: full + "px" }
|
||||
}, () => el._x_isShown = true, () => {
|
||||
if (Math.abs(el.getBoundingClientRect().height - full) < 1) {
|
||||
if (el.getBoundingClientRect().height == full) {
|
||||
el.style.overflow = null;
|
||||
}
|
||||
});
|
||||
@ -4856,7 +4865,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
var module_default2 = src_default2;
|
||||
|
||||
// node_modules/@alpinejs/focus/dist/module.esm.js
|
||||
// ../../../../usr/local/lib/node_modules/@alpinejs/focus/dist/module.esm.js
|
||||
var candidateSelectors = ["input", "select", "textarea", "a[href]", "button", "[tabindex]:not(slot)", "audio[controls]", "video[controls]", '[contenteditable]:not([contenteditable="false"])', "details>summary:first-of-type", "details"];
|
||||
var candidateSelector = /* @__PURE__ */ candidateSelectors.join(",");
|
||||
var NoElement = typeof Element === "undefined";
|
||||
@ -4968,11 +4977,11 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
var checked = getCheckedRadio(radioSet, node.form);
|
||||
return !checked || checked === node;
|
||||
};
|
||||
var isRadio = function isRadio2(node) {
|
||||
var isRadio2 = function isRadio22(node) {
|
||||
return isInput(node) && node.type === "radio";
|
||||
};
|
||||
var isNonTabbableRadio = function isNonTabbableRadio2(node) {
|
||||
return isRadio(node) && !isTabbableRadio(node);
|
||||
return isRadio2(node) && !isTabbableRadio(node);
|
||||
};
|
||||
var isZeroArea = function isZeroArea2(node) {
|
||||
var _node$getBoundingClie = node.getBoundingClientRect(), width = _node$getBoundingClie.width, height = _node$getBoundingClie.height;
|
||||
@ -5805,7 +5814,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
var module_default3 = src_default3;
|
||||
|
||||
// node_modules/@alpinejs/persist/dist/module.esm.js
|
||||
// ../../../../usr/local/lib/node_modules/@alpinejs/persist/dist/module.esm.js
|
||||
function src_default4(Alpine3) {
|
||||
let persist = () => {
|
||||
let alias;
|
||||
@ -5867,7 +5876,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
var module_default4 = src_default4;
|
||||
|
||||
// node_modules/@alpinejs/intersect/dist/module.esm.js
|
||||
// ../../../../usr/local/lib/node_modules/@alpinejs/intersect/dist/module.esm.js
|
||||
function src_default5(Alpine3) {
|
||||
Alpine3.directive("intersect", Alpine3.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater: evaluateLater2, cleanup: cleanup2 }) => {
|
||||
let evaluate3 = evaluateLater2(expression);
|
||||
@ -5967,7 +5976,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
var module_default6 = src_default6;
|
||||
|
||||
// node_modules/@alpinejs/anchor/dist/module.esm.js
|
||||
// ../alpine/packages/anchor/dist/module.esm.js
|
||||
var min = Math.min;
|
||||
var max = Math.max;
|
||||
var round = Math.round;
|
||||
@ -7471,8 +7480,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
};
|
||||
queueMicrotask(() => {
|
||||
scroll(document.body);
|
||||
document.querySelectorAll(["[x-navigate\\:scroll]", "[wire\\:scroll]"]).forEach(scroll);
|
||||
queueMicrotask(() => {
|
||||
scroll(document.body);
|
||||
document.querySelectorAll(["[x-navigate\\:scroll]", "[wire\\:scroll]"]).forEach(scroll);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -7620,6 +7631,44 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// js/plugins/navigate/popover.js
|
||||
function packUpPersistedPopovers(persistedEl) {
|
||||
persistedEl.querySelectorAll(":popover-open").forEach((el) => {
|
||||
el.setAttribute("data-navigate-popover-open", "");
|
||||
let animations = el.getAnimations();
|
||||
el._pausedAnimations = animations.map((animation) => ({
|
||||
keyframes: animation.effect.getKeyframes(),
|
||||
options: {
|
||||
duration: animation.effect.getTiming().duration,
|
||||
easing: animation.effect.getTiming().easing,
|
||||
fill: animation.effect.getTiming().fill,
|
||||
iterations: animation.effect.getTiming().iterations
|
||||
},
|
||||
currentTime: animation.currentTime,
|
||||
playState: animation.playState
|
||||
}));
|
||||
animations.forEach((i) => i.pause());
|
||||
});
|
||||
}
|
||||
function unPackPersistedPopovers(persistedEl) {
|
||||
persistedEl.querySelectorAll("[data-navigate-popover-open]").forEach((el) => {
|
||||
el.removeAttribute("data-navigate-popover-open");
|
||||
queueMicrotask(() => {
|
||||
if (!el.isConnected)
|
||||
return;
|
||||
el.showPopover();
|
||||
el.getAnimations().forEach((i) => i.finish());
|
||||
if (el._pausedAnimations) {
|
||||
el._pausedAnimations.forEach(({ keyframes, options, currentTime, now, playState }) => {
|
||||
let animation = el.animate(keyframes, options);
|
||||
animation.currentTime = currentTime;
|
||||
});
|
||||
delete el._pausedAnimations;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// js/plugins/navigate/page.js
|
||||
var oldBodyScriptTagHashes = [];
|
||||
var attributesExemptFromScriptTagHashing = [
|
||||
@ -7758,7 +7807,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
function navigate_default(Alpine3) {
|
||||
Alpine3.navigate = (url) => {
|
||||
let destination = createUrlObjectFromString(url);
|
||||
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
|
||||
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
|
||||
url: destination,
|
||||
history: false,
|
||||
cached: false
|
||||
@ -7789,7 +7838,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
storeThePrefetchedHtmlForWhenALinkIsClicked(html, destination, finalDestination);
|
||||
});
|
||||
whenItIsReleased(() => {
|
||||
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
|
||||
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
|
||||
url: destination,
|
||||
history: false,
|
||||
cached: false
|
||||
@ -7803,7 +7852,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
function navigateTo(destination, shouldPushToHistoryState = true) {
|
||||
showProgressBar && showAndStartProgressBar();
|
||||
fetchHtmlOrUsePrefetchedHtml(destination, (html, finalDestination) => {
|
||||
fireEventForOtherLibariesToHookInto("alpine:navigating");
|
||||
fireEventForOtherLibrariesToHookInto("alpine:navigating");
|
||||
restoreScroll && storeScrollInformationInHtmlBeforeNavigatingAway();
|
||||
showProgressBar && finishAndHideProgressBar();
|
||||
cleanupAlpineElementsOnThePageThatArentInsideAPersistedElement();
|
||||
@ -7811,6 +7860,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
preventAlpineFromPickingUpDomChanges(Alpine3, (andAfterAllThis) => {
|
||||
enablePersist && storePersistantElementsForLater((persistedEl) => {
|
||||
packUpPersistedTeleports(persistedEl);
|
||||
packUpPersistedPopovers(persistedEl);
|
||||
});
|
||||
if (shouldPushToHistoryState) {
|
||||
updateUrlAndStoreLatestHtmlForFutureBackButtons(html, finalDestination);
|
||||
@ -7821,6 +7871,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
removeAnyLeftOverStaleTeleportTargets(document.body);
|
||||
enablePersist && putPersistantElementsBack((persistedEl, newStub) => {
|
||||
unPackPersistedTeleports(persistedEl);
|
||||
unPackPersistedPopovers(persistedEl);
|
||||
});
|
||||
restoreScrollPositionOrScrollToTop();
|
||||
afterNewScriptsAreDoneLoading(() => {
|
||||
@ -7829,7 +7880,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
autofocus && autofocusElementsWithTheAutofocusAttribute();
|
||||
});
|
||||
nowInitializeAlpineOnTheNewPage(Alpine3);
|
||||
fireEventForOtherLibariesToHookInto("alpine:navigated");
|
||||
fireEventForOtherLibrariesToHookInto("alpine:navigated");
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -7839,7 +7890,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
whenTheBackOrForwardButtonIsClicked((ifThePageBeingVisitedHasntBeenCached) => {
|
||||
ifThePageBeingVisitedHasntBeenCached((url) => {
|
||||
let destination = createUrlObjectFromString(url);
|
||||
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
|
||||
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
|
||||
url: destination,
|
||||
history: true,
|
||||
cached: false
|
||||
@ -7851,7 +7902,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
});
|
||||
}, (html, url, currentPageUrl, currentPageKey) => {
|
||||
let destination = createUrlObjectFromString(url);
|
||||
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
|
||||
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
|
||||
url: destination,
|
||||
history: true,
|
||||
cached: true
|
||||
@ -7859,29 +7910,31 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
if (prevented)
|
||||
return;
|
||||
storeScrollInformationInHtmlBeforeNavigatingAway();
|
||||
fireEventForOtherLibariesToHookInto("alpine:navigating");
|
||||
fireEventForOtherLibrariesToHookInto("alpine:navigating");
|
||||
updateCurrentPageHtmlInSnapshotCacheForLaterBackButtonClicks(currentPageUrl, currentPageKey);
|
||||
preventAlpineFromPickingUpDomChanges(Alpine3, (andAfterAllThis) => {
|
||||
enablePersist && storePersistantElementsForLater((persistedEl) => {
|
||||
packUpPersistedTeleports(persistedEl);
|
||||
packUpPersistedPopovers(persistedEl);
|
||||
});
|
||||
swapCurrentPageWithNewHtml(html, () => {
|
||||
removeAnyLeftOverStaleProgressBars();
|
||||
removeAnyLeftOverStaleTeleportTargets(document.body);
|
||||
enablePersist && putPersistantElementsBack((persistedEl, newStub) => {
|
||||
unPackPersistedTeleports(persistedEl);
|
||||
unPackPersistedPopovers(persistedEl);
|
||||
});
|
||||
restoreScrollPositionOrScrollToTop();
|
||||
andAfterAllThis(() => {
|
||||
autofocus && autofocusElementsWithTheAutofocusAttribute();
|
||||
nowInitializeAlpineOnTheNewPage(Alpine3);
|
||||
fireEventForOtherLibariesToHookInto("alpine:navigated");
|
||||
fireEventForOtherLibrariesToHookInto("alpine:navigated");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
setTimeout(() => {
|
||||
fireEventForOtherLibariesToHookInto("alpine:navigated");
|
||||
fireEventForOtherLibrariesToHookInto("alpine:navigated");
|
||||
});
|
||||
}
|
||||
function fetchHtmlOrUsePrefetchedHtml(fromDestination, callback) {
|
||||
@ -7898,7 +7951,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
});
|
||||
});
|
||||
}
|
||||
function fireEventForOtherLibariesToHookInto(name, detail) {
|
||||
function fireEventForOtherLibrariesToHookInto(name, detail) {
|
||||
let event = new CustomEvent(name, {
|
||||
cancelable: true,
|
||||
bubbles: true,
|
||||
@ -8132,7 +8185,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
return data2;
|
||||
}
|
||||
|
||||
// node_modules/@alpinejs/morph/dist/module.esm.js
|
||||
// ../alpine/packages/morph/dist/module.esm.js
|
||||
function morph(from, toHtml, options) {
|
||||
monkeyPatchDomSetAttributeToAllowAtSymbols();
|
||||
let fromEl;
|
||||
@ -8467,7 +8520,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
}
|
||||
var module_default8 = src_default8;
|
||||
|
||||
// node_modules/@alpinejs/mask/dist/module.esm.js
|
||||
// ../../../../usr/local/lib/node_modules/@alpinejs/mask/dist/module.esm.js
|
||||
function src_default9(Alpine3) {
|
||||
Alpine3.directive("mask", (el, { value, expression }, { effect: effect3, evaluateLater: evaluateLater2, cleanup: cleanup2 }) => {
|
||||
let templateFn = () => expression;
|
||||
@ -8905,6 +8958,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
|
||||
},
|
||||
lookahead: false
|
||||
});
|
||||
trigger2("morphed", { el, component });
|
||||
}
|
||||
function isntElement(el) {
|
||||
return typeof el.hasAttribute !== "function";
|
||||
|
12
public/vendor/livewire/livewire.min.js
vendored
12
public/vendor/livewire/livewire.min.js
vendored
File diff suppressed because one or more lines are too long
6
public/vendor/livewire/livewire.min.js.map
vendored
6
public/vendor/livewire/livewire.min.js.map
vendored
File diff suppressed because one or more lines are too long
2
public/vendor/livewire/manifest.json
vendored
2
public/vendor/livewire/manifest.json
vendored
@ -1,2 +1,2 @@
|
||||
|
||||
{"/livewire.js":"cc800bf4"}
|
||||
{"/livewire.js":"38dc8241"}
|
||||
|
@ -78,7 +78,7 @@ $scrollIntoViewJsSnippet = ($scrollTo !== false)
|
||||
{{-- "Three Dots" Separator --}}
|
||||
@if (is_string($element))
|
||||
<span aria-disabled="true">
|
||||
<span class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 cursor-default leading-5 dark:bg-gray-800 dark:border-gray-600 dark:bg-gray-800 dark:border-gray-600">{{ $element }}</span>
|
||||
<span class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 cursor-default leading-5 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300">{{ $element }}</span>
|
||||
</span>
|
||||
@endif
|
||||
|
||||
|
@ -173,6 +173,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
|
||||
Route::post('charts/calculated_fields', [ChartController::class, 'calculatedFields'])->name('chart.calculated_fields');
|
||||
|
||||
Route::post('claim_license', [LicenseController::class, 'index'])->name('license.index');
|
||||
Route::post('check_license', [LicenseController::class, 'check'])->name('license.check');
|
||||
|
||||
Route::resource('clients', ClientController::class); // name = (clients. index / create / show / update / destroy / edit
|
||||
Route::put('clients/{client}/upload', [ClientController::class, 'upload'])->name('clients.upload');
|
||||
@ -239,7 +240,8 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
|
||||
Route::post('einvoice/peppol/disconnect', [EInvoicePeppolController::class, 'disconnect'])->name('einvoice.peppol.disconnect');
|
||||
Route::put('einvoice/peppol/update', [EInvoicePeppolController::class, 'updateLegalEntity'])->name('einvoice.peppol.update_legal_entity');
|
||||
|
||||
Route::put('einvoice/token/update', EInvoiceTokenController::class)->name('einvoice.token.update');
|
||||
Route::post('einvoice/token/update', EInvoiceTokenController::class)->name('einvoice.token.update');
|
||||
Route::get('einvoice/quota', [EInvoiceController::class, 'quota'])->name('einvoice.quota');
|
||||
|
||||
Route::post('emails', [EmailController::class, 'send'])->name('email.send')->middleware('user_verified');
|
||||
Route::post('emails/clientHistory/{client}', [EmailHistoryController::class, 'clientHistory'])->name('email.clientHistory');
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\EInvoice\RequestValidation;
|
||||
|
||||
use App\Http\Requests\EInvoice\UpdateEInvoiceConfiguration;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class UpdateEInvoiceConfigurationTest extends TestCase
|
||||
{
|
||||
protected UpdateEInvoiceConfiguration $request;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
|
||||
$this->request = new UpdateEInvoiceConfiguration();
|
||||
}
|
||||
|
||||
public function testConfigValidationFails()
|
||||
{
|
||||
$data = [
|
||||
'entddity' => 'invoice',
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
}
|
||||
|
||||
public function testConfigValidation()
|
||||
{
|
||||
$data = [
|
||||
'entity' => 'invoice',
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertTrue($validator->passes());
|
||||
}
|
||||
|
||||
public function testConfigValidationInvalidcode()
|
||||
{
|
||||
$data = [
|
||||
'entity' => 'invoice',
|
||||
'payment_means' => [[
|
||||
'code' => 'invalidcodehere'
|
||||
]]
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
}
|
||||
|
||||
public function testValidatesPaymentMeansForBankTransfer()
|
||||
{
|
||||
$data = [
|
||||
'entity' => 'invoice',
|
||||
'payment_means' => [[
|
||||
'code' => '30',
|
||||
'iban' => '123456789101112254',
|
||||
'bic_swift' => 'DEUTDEFF',
|
||||
'account_holder' => 'John Doe Company Limited'
|
||||
]]
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertTrue($validator->passes());
|
||||
}
|
||||
|
||||
public function testValidatesPaymentMeansForCardPayment()
|
||||
{
|
||||
$data = [
|
||||
'entity' => 'invoice',
|
||||
'payment_means' => [[
|
||||
'code' => '48',
|
||||
'card_type' => 'VISA',
|
||||
'iban' => '12345678'
|
||||
]]
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
}
|
||||
|
||||
public function testValidatesPaymentMeansForCreditCard()
|
||||
{
|
||||
$data = [
|
||||
'entity' => 'invoice',
|
||||
'payment_means' => [[
|
||||
'code' => '54',
|
||||
'card_type' => 'VISA',
|
||||
'card_number' => '************1234',
|
||||
'card_holder' => 'John Doe'
|
||||
]]
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertTrue($validator->passes());
|
||||
}
|
||||
|
||||
public function testFailsValidationWhenRequiredFieldsAreMissing()
|
||||
{
|
||||
$data = [
|
||||
'entity' => 'invoice',
|
||||
'payment_means' => [[
|
||||
'code' => '30',
|
||||
]]
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
$this->assertFalse($validator->passes());
|
||||
|
||||
$this->assertTrue($validator->errors()->has('payment_means.0.bic_swift'));
|
||||
|
||||
}
|
||||
|
||||
public function testFailsValidationWithInvalidPaymentMeansCode()
|
||||
{
|
||||
$data = [
|
||||
'entity' => 'invoice',
|
||||
'payment_means' => [[
|
||||
'code' => '999',
|
||||
]]
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
$this->assertTrue($validator->errors()->has('payment_means.0.code'));
|
||||
}
|
||||
|
||||
public function testValidatesPaymentMeansForDirectDebit()
|
||||
{
|
||||
$data = [
|
||||
'entity' => 'invoice',
|
||||
'payment_means' => [[
|
||||
'code' => '49',
|
||||
'payer_bank_account' => '12345678',
|
||||
'bic_swift' => 'DEUTDEFF'
|
||||
]]
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertTrue($validator->passes());
|
||||
}
|
||||
|
||||
public function testValidatesPaymentMeansForBookEntry()
|
||||
{
|
||||
$data = [
|
||||
'entity' => 'invoice',
|
||||
'payment_means' => [[
|
||||
'code' => '15',
|
||||
'account_holder' => 'John Doe Company Limited',
|
||||
'bsb_sort' => '123456'
|
||||
]]
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertTrue($validator->passes());
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ class AddTaxIdentifierRequestTest extends TestCase
|
||||
{
|
||||
protected AddTaxIdentifierRequest $request;
|
||||
|
||||
private array $vat_regex_patterns = [
|
||||
private array $vat_regex_patterns = [
|
||||
'DE' => '/^DE\d{9}$/',
|
||||
'AT' => '/^ATU\d{8}$/',
|
||||
'BE' => '/^BE0\d{9}$/',
|
||||
@ -48,20 +48,26 @@ class AddTaxIdentifierRequestTest extends TestCase
|
||||
|
||||
public function testValidInput()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
$data = [
|
||||
'country' => 'DE',
|
||||
'vat_number' => 'DE123456789',
|
||||
], $this->request->rules());
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertTrue($validator->passes());
|
||||
}
|
||||
|
||||
public function testInvalidCountry()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
$data = [
|
||||
'country' => 'US',
|
||||
'vat_number' => 'DE123456789',
|
||||
], $this->request->rules());
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
$this->assertArrayHasKey('country', $validator->errors()->toArray());
|
||||
@ -73,24 +79,9 @@ class AddTaxIdentifierRequestTest extends TestCase
|
||||
'country' => 'DE',
|
||||
'vat_number' => 'DE12345', // Too short
|
||||
];
|
||||
|
||||
$rules = [
|
||||
'country' => ['required', 'bail'],
|
||||
'vat_number' => [
|
||||
'required',
|
||||
'string',
|
||||
'bail',
|
||||
function ($attribute, $value, $fail) use ($data){
|
||||
if ( isset($this->vat_regex_patterns[$data['country']])) {
|
||||
if (!preg_match($this->vat_regex_patterns[$data['country']], $value)) {
|
||||
$fail(ctrans('texts.invalid_vat_number'));
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
$validator = Validator::make($data, $rules);
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
$this->assertArrayHasKey('vat_number', $validator->errors()->toArray());
|
||||
@ -98,9 +89,12 @@ class AddTaxIdentifierRequestTest extends TestCase
|
||||
|
||||
public function testMissingCountry()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
$data = [
|
||||
'vat_number' => 'DE123456789',
|
||||
], $this->request->rules());
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
$this->assertArrayHasKey('country', $validator->errors()->toArray());
|
||||
@ -108,9 +102,12 @@ class AddTaxIdentifierRequestTest extends TestCase
|
||||
|
||||
public function testMissingVatNumber()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
$data = [
|
||||
'country' => 'DE',
|
||||
], $this->request->rules());
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
$this->assertArrayHasKey('vat_number', $validator->errors()->toArray());
|
||||
|
@ -5,8 +5,6 @@ namespace Tests\Feature\EInvoice\Validation;
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use App\Http\Requests\EInvoice\Peppol\StoreEntityRequest;
|
||||
use App\Models\Country;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class CreateRequestTest extends TestCase
|
||||
{
|
||||
@ -20,36 +18,43 @@ class CreateRequestTest extends TestCase
|
||||
|
||||
public function testValidInput()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
$data = [
|
||||
'party_name' => 'Test Company',
|
||||
'line1' => '123 Test St',
|
||||
'city' => 'Test City',
|
||||
'country' => 'DE', // Assuming 1 is the ID for Germany
|
||||
'country' => 'DE',
|
||||
'zip' => '12345',
|
||||
'county' => 'Test County',
|
||||
'acts_as_sender' => true,
|
||||
'acts_as_receiver' => true,
|
||||
'tenant_id' => 'testcompanykey',
|
||||
'classification' => 'individual',
|
||||
'id_number' => 'xx',
|
||||
|
||||
];
|
||||
|
||||
], $this->request->rules());
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
|
||||
$this->assertTrue($validator->passes());
|
||||
}
|
||||
|
||||
public function testInvalidCountry()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
$data = [
|
||||
'party_name' => 'Test Company',
|
||||
'line1' => '123 Test St',
|
||||
'city' => 'Test City',
|
||||
'country' => 999, // Invalid country ID
|
||||
'country' => 999,
|
||||
'zip' => '12345',
|
||||
'county' => 'Test County',
|
||||
'acts_as_sender' => true,
|
||||
'acts_as_receiver' => true,
|
||||
'tenant_id' => 'testcompanykey',
|
||||
], $this->request->rules());
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
$this->assertArrayHasKey('country', $validator->errors()->toArray());
|
||||
@ -57,9 +62,12 @@ class CreateRequestTest extends TestCase
|
||||
|
||||
public function testMissingRequiredFields()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
$data = [
|
||||
'line2' => 'Optional line',
|
||||
], $this->request->rules());
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
$errors = $validator->errors()->toArray();
|
||||
@ -73,7 +81,7 @@ class CreateRequestTest extends TestCase
|
||||
|
||||
public function testOptionalLine2()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
$data = [
|
||||
'party_name' => 'Test Company',
|
||||
'line1' => '123 Test St',
|
||||
'line2' => 'Optional line',
|
||||
@ -81,20 +89,27 @@ class CreateRequestTest extends TestCase
|
||||
'country' => 'AT',
|
||||
'zip' => '12345',
|
||||
'county' => 'Test County',
|
||||
'tenant_id' => 'testcompanykey',
|
||||
'tenant_id' => 'testcompanykey',
|
||||
'acts_as_sender' => true,
|
||||
'acts_as_receiver' => true,
|
||||
], $this->request->rules());
|
||||
'classification' => 'business',
|
||||
'vat_number' => '234234',
|
||||
];
|
||||
|
||||
$this->request->initialize($data);
|
||||
$validator = Validator::make($data, $this->request->rules());
|
||||
|
||||
$this->assertTrue($validator->passes());
|
||||
}
|
||||
|
||||
public function testCountryPreparation()
|
||||
{
|
||||
$request = new StoreEntityRequest([
|
||||
'country' => '276', // Assuming 1 is the ID for Germany
|
||||
]);
|
||||
$data = [
|
||||
'country' => '276', // Numeric code for Germany
|
||||
];
|
||||
|
||||
$request = new StoreEntityRequest();
|
||||
$request->initialize($data);
|
||||
$request->prepareForValidation();
|
||||
|
||||
$this->assertEquals('DE', $request->input('country'));
|
||||
|
142
tests/Integration/Einvoice/Storecove/StorecoveIngestTest.php
Normal file
142
tests/Integration/Einvoice/Storecove/StorecoveIngestTest.php
Normal file
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace Tests\Integration\Einvoice\Storecove;
|
||||
|
||||
use Tests\TestCase;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\Country;
|
||||
use App\Models\Invoice;
|
||||
use Tests\MockAccountData;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\ClientContact;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\DataMapper\Tax\TaxModel;
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Services\EDocument\Standards\Peppol;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
use Symfony\Component\Serializer\Encoder\XmlEncoder;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\PaymentMeans;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
use App\Services\EDocument\Gateway\Storecove\Storecove;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
|
||||
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\Invoice as PeppolInvoice;
|
||||
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
|
||||
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
|
||||
use App\Services\EDocument\Gateway\Storecove\PeppolToStorecoveNormalizer;
|
||||
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\Invoice as StorecoveInvoice;
|
||||
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
|
||||
class StorecoveIngestTest extends TestCase
|
||||
{
|
||||
use MockAccountData;
|
||||
use DatabaseTransactions;
|
||||
|
||||
private int $routing_id = 0;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
if (config('ninja.testvars.travis') !== false || !config('ninja.storecove_api_key')) {
|
||||
$this->markTestSkipped("do not run in CI");
|
||||
}
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public function testIngestStorecoveDocument()
|
||||
{
|
||||
|
||||
$s = new Storecove();
|
||||
$x = $s->getDocument('3f0981f1-5105-4970-81f2-6b7482ad27d7');
|
||||
|
||||
$doc = $x['document']['invoice'];
|
||||
$x = json_encode($doc);
|
||||
|
||||
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
|
||||
|
||||
// Create a proper PropertyInfoExtractor
|
||||
$phpDocExtractor = new PhpDocExtractor();
|
||||
$reflectionExtractor = new ReflectionExtractor();
|
||||
|
||||
$propertyInfo = new PropertyInfoExtractor(
|
||||
// List of extractors for type info
|
||||
[$reflectionExtractor, $phpDocExtractor],
|
||||
// List of extractors for descriptions
|
||||
[$phpDocExtractor],
|
||||
// List of extractors for access info
|
||||
[$reflectionExtractor],
|
||||
// List of extractors for mutation info
|
||||
[$reflectionExtractor],
|
||||
// List of extractors for initialization info
|
||||
[$reflectionExtractor]
|
||||
);
|
||||
|
||||
$normalizers = [
|
||||
new DateTimeNormalizer(),
|
||||
new ArrayDenormalizer(),
|
||||
new ObjectNormalizer(
|
||||
$classMetadataFactory,
|
||||
null,
|
||||
null,
|
||||
$propertyInfo
|
||||
)
|
||||
];
|
||||
|
||||
$context = [
|
||||
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d',
|
||||
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
|
||||
AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => false,
|
||||
AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE => true, // Add this
|
||||
];
|
||||
|
||||
$encoders = [new JsonEncoder()];
|
||||
|
||||
|
||||
$serializer = new Serializer($normalizers, $encoders);
|
||||
|
||||
$context = [
|
||||
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d',
|
||||
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
|
||||
AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => false, // Enforce types
|
||||
];
|
||||
|
||||
$object = $serializer->deserialize(
|
||||
$x,
|
||||
StorecoveInvoice::class,
|
||||
'json',
|
||||
$context
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(StorecoveInvoice::class, $object);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -14,6 +14,7 @@ namespace Tests\Integration\Einvoice\Storecove;
|
||||
use Tests\TestCase;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\Country;
|
||||
use App\Models\Invoice;
|
||||
use Tests\MockAccountData;
|
||||
use Illuminate\Support\Str;
|
||||
@ -38,20 +39,19 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\Invoice as PeppolInvoice;
|
||||
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
|
||||
use App\Services\EDocument\Gateway\Transformers\StorecoveTransformer;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
|
||||
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
|
||||
use App\Services\EDocument\Gateway\Storecove\PeppolToStorecoveNormalizer;
|
||||
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
|
||||
use App\Services\EDocument\Gateway\Storecove\Models\Invoice as StorecoveInvoice;
|
||||
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
|
||||
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
class StorecoveTest extends TestCase
|
||||
{
|
||||
use MockAccountData;
|
||||
use DatabaseTransactions;
|
||||
|
||||
private int $routing_id;
|
||||
private int $routing_id = 0;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@ -62,6 +62,282 @@ class StorecoveTest extends TestCase
|
||||
if (config('ninja.testvars.travis') !== false || !config('ninja.storecove_api_key')) {
|
||||
$this->markTestSkipped("do not run in CI");
|
||||
}
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private function setupTestData(array $params = []): array
|
||||
{
|
||||
|
||||
$settings = CompanySettings::defaults();
|
||||
$settings->vat_number = $params['company_vat'] ?? 'DE123456789';
|
||||
$settings->country_id = Country::where('iso_3166_2', 'DE')->first()->id;
|
||||
$settings->email = $this->faker->safeEmail();
|
||||
$settings->currency_id = '3';
|
||||
|
||||
$tax_data = new TaxModel();
|
||||
$tax_data->regions->EU->has_sales_above_threshold = $params['over_threshold'] ?? false;
|
||||
$tax_data->regions->EU->tax_all_subregions = true;
|
||||
$tax_data->seller_subregion = $params['company_country'] ?? 'DE';
|
||||
|
||||
$einvoice = new \InvoiceNinja\EInvoice\Models\Peppol\Invoice();
|
||||
|
||||
$fib = new \InvoiceNinja\EInvoice\Models\Peppol\BranchType\FinancialInstitutionBranch();
|
||||
$fib->ID = "DEUTDEMMXXX"; //BIC
|
||||
|
||||
$pfa = new \InvoiceNinja\EInvoice\Models\Peppol\FinancialAccountType\PayeeFinancialAccount();
|
||||
$id = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\ID();
|
||||
$id->value = 'DE89370400440532013000';
|
||||
$pfa->ID = $id;
|
||||
$pfa->Name = 'PFA-NAME';
|
||||
|
||||
$pfa->FinancialInstitutionBranch = $fib;
|
||||
|
||||
$pm = new \InvoiceNinja\EInvoice\Models\Peppol\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;
|
||||
|
||||
$this->company->settings = $settings;
|
||||
$this->company->tax_data = $tax_data;
|
||||
$this->company->calculate_taxes = true;
|
||||
$this->company->legal_entity_id = 290868;
|
||||
$this->company->e_invoice = $stub;
|
||||
$this->company->save();
|
||||
$company = $this->company;
|
||||
|
||||
$client = Client::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->company->id,
|
||||
'country_id' => Country::where('iso_3166_2', $params['client_country'] ?? 'FR')->first()->id,
|
||||
'vat_number' => $params['client_vat'] ?? '',
|
||||
'classification' => $params['classification'] ?? 'individual',
|
||||
'has_valid_vat_number' => $params['has_valid_vat'] ?? false,
|
||||
'name' => 'Test Client',
|
||||
'is_tax_exempt' => $params['is_tax_exempt'] ?? false,
|
||||
'id_number' => $params['client_id_number'] ?? '',
|
||||
]);
|
||||
|
||||
$contact = ClientContact::factory()->create([
|
||||
'client_id' => $client->id,
|
||||
'company_id' =>$client->company_id,
|
||||
'user_id' => $client->user_id,
|
||||
'first_name' => $this->faker->firstName(),
|
||||
'last_name' => $this->faker->lastName(),
|
||||
'email' => $this->faker->safeEmail()
|
||||
]);
|
||||
|
||||
$invoice = \App\Models\Invoice::factory()->create([
|
||||
'client_id' => $client->id,
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'date' => now()->addDay()->format('Y-m-d'),
|
||||
'due_date' => now()->addDays(2)->format('Y-m-d'),
|
||||
'uses_inclusive_taxes' => false,
|
||||
'tax_rate1' => 0,
|
||||
'tax_name1' => '',
|
||||
'tax_rate2' => 0,
|
||||
'tax_name2' => '',
|
||||
'tax_rate3' => 0,
|
||||
'tax_name3' => '',
|
||||
]);
|
||||
|
||||
$items = $invoice->line_items;
|
||||
foreach($items as &$item)
|
||||
{
|
||||
$item->tax_name2 = '';
|
||||
$item->tax_rate2 = 0;
|
||||
$item->tax_name3 = '';
|
||||
$item->tax_rate3 = 0;
|
||||
$item->uses_inclusive_taxes = false;
|
||||
}
|
||||
unset($item);
|
||||
|
||||
$invoice->line_items = array_values($items);
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
|
||||
return compact('company', 'client', 'invoice');
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function testDeToFrClientTaxExemptSending()
|
||||
{
|
||||
$this->routing_id = 290868;
|
||||
|
||||
$scenario = [
|
||||
'company_vat' => 'DE923356489',
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'FR',
|
||||
'client_vat' => 'FRAA123456789',
|
||||
'client_id_number' => '123456789',
|
||||
'classification' => 'business',
|
||||
'has_valid_vat' => true,
|
||||
'over_threshold' => true,
|
||||
'legal_entity_id' => 290868,
|
||||
'is_tax_exempt' => false,
|
||||
];
|
||||
|
||||
$data = $this->setupTestData($scenario);
|
||||
|
||||
$invoice = $data['invoice'];
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
$company = $data['company'];
|
||||
$client = $data['client'];
|
||||
$client->save();
|
||||
|
||||
$this->assertEquals('DE', $company->country()->iso_3166_2);
|
||||
$this->assertEquals('FR', $client->country->iso_3166_2);
|
||||
|
||||
foreach($invoice->line_items as $item)
|
||||
{
|
||||
$this->assertEquals('1', $item->tax_id);
|
||||
$this->assertEquals(0, $item->tax_rate1);
|
||||
}
|
||||
|
||||
$this->assertEquals(floatval(0), floatval($invoice->total_taxes));
|
||||
$this->sendDocument($invoice);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* PtestDeToDeClientTaxExemptSending
|
||||
*
|
||||
* Disabled for now - there is an issue with internal tax exempt client in same country
|
||||
* @return void
|
||||
*/
|
||||
public function PtestDeToDeClientTaxExemptSending()
|
||||
{
|
||||
$this->routing_id = 290868;
|
||||
|
||||
$scenario = [
|
||||
'company_vat' => 'DE923356489',
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'DE',
|
||||
'client_vat' => 'DE173755434',
|
||||
'classification' => 'business',
|
||||
'has_valid_vat' => true,
|
||||
'over_threshold' => true,
|
||||
'legal_entity_id' => 290868,
|
||||
'is_tax_exempt' => true,
|
||||
];
|
||||
|
||||
$data = $this->setupTestData($scenario);
|
||||
|
||||
$invoice = $data['invoice'];
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
$company = $data['company'];
|
||||
$client = $data['client'];
|
||||
$client->save();
|
||||
|
||||
$this->assertEquals('DE', $company->country()->iso_3166_2);
|
||||
$this->assertEquals('DE', $client->country->iso_3166_2);
|
||||
|
||||
foreach($invoice->line_items as $item)
|
||||
{
|
||||
$this->assertEquals('1', $item->tax_id);
|
||||
$this->assertEquals(0, $item->tax_rate1);
|
||||
}
|
||||
|
||||
$this->assertEquals(floatval(0), floatval($invoice->total_taxes));
|
||||
$this->sendDocument($invoice);
|
||||
}
|
||||
|
||||
public function testDeToDeSending()
|
||||
{
|
||||
$this->routing_id = 290868;
|
||||
|
||||
$scenario = [
|
||||
'company_vat' => 'DE923356489',
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'DE',
|
||||
'client_vat' => '',
|
||||
'classification' => 'individual',
|
||||
'has_valid_vat' => false,
|
||||
'over_threshold' => true,
|
||||
'legal_entity_id' => 290868,
|
||||
];
|
||||
|
||||
$data = $this->setupTestData($scenario);
|
||||
|
||||
$invoice = $data['invoice'];
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
$company = $data['company'];
|
||||
$client = $data['client'];
|
||||
$tax_rate = $company->tax_data->regions->EU->subregions->DE->tax_rate;
|
||||
|
||||
$this->assertEquals('DE', $company->country()->iso_3166_2);
|
||||
$this->assertEquals('DE', $client->country->iso_3166_2);
|
||||
|
||||
foreach($invoice->line_items as $item)
|
||||
{
|
||||
$this->assertEquals('1', $item->tax_id);
|
||||
$this->assertEquals($tax_rate, $item->tax_rate1);
|
||||
}
|
||||
|
||||
$this->sendDocument($invoice);
|
||||
}
|
||||
|
||||
private function sendDocument($model)
|
||||
{
|
||||
$storecove = new Storecove();
|
||||
$p = new Peppol($model);
|
||||
$p->run();
|
||||
|
||||
try {
|
||||
$processor = new \Saxon\SaxonProcessor();
|
||||
} catch (\Throwable $e) {
|
||||
$this->markTestSkipped('saxon not installed');
|
||||
}
|
||||
|
||||
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($p->toXml());
|
||||
$validator->validate();
|
||||
|
||||
if (count($validator->getErrors()) > 0) {
|
||||
nlog($p->toXml());
|
||||
nlog($validator->getErrors());
|
||||
}
|
||||
|
||||
$this->assertCount(0, $validator->getErrors());
|
||||
|
||||
$identifiers = $p->gateway->mutator->setClientRoutingCode()->getStorecoveMeta();
|
||||
|
||||
$result = $storecove->build($model)->getResult();
|
||||
|
||||
if (count($result['errors']) > 0) {
|
||||
nlog("errors!");
|
||||
nlog($result);
|
||||
return $result['errors'];
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'legal_entity_id' => $model->company->legal_entity_id,
|
||||
"idempotencyGuid" => \Illuminate\Support\Str::uuid(),
|
||||
'document' => [
|
||||
'document_type' => 'invoice',
|
||||
'invoice' => $result['document'],
|
||||
],
|
||||
'tenant_id' => $model->company->company_key,
|
||||
'routing' => $identifiers['routing'],
|
||||
];
|
||||
/** Concrete implementation current linked to Storecove only */
|
||||
|
||||
//@testing only
|
||||
$sc = new \App\Services\EDocument\Gateway\Storecove\Storecove();
|
||||
$r = $sc->sendJsonDocument($payload);
|
||||
|
||||
nlog($r);
|
||||
}
|
||||
|
||||
public function testTransformPeppolToStorecove()
|
||||
@ -105,7 +381,6 @@ class StorecoveTest extends TestCase
|
||||
$this->assertInstanceOf(\InvoiceNinja\EInvoice\Models\Peppol\Invoice::class, $peppolInvoice);
|
||||
|
||||
$parent = \App\Services\EDocument\Gateway\Storecove\Models\Invoice::class;
|
||||
// $peppolInvoice = $e->encode($peppolInvoice, 'json');
|
||||
|
||||
$peppolInvoice = $data = $e->encode($peppolInvoice, 'json', $context);
|
||||
|
||||
@ -159,15 +434,6 @@ class StorecoveTest extends TestCase
|
||||
$p->run();
|
||||
$peppolInvoice = $p->getInvoice();
|
||||
|
||||
|
||||
$s_transformer = new StorecoveTransformer();
|
||||
$s_transformer->transform($peppolInvoice);
|
||||
|
||||
$json = $s_transformer->toJson();
|
||||
|
||||
$this->assertJson($json);
|
||||
|
||||
nlog($json);
|
||||
}
|
||||
|
||||
// public function testStorecoveTransformer()
|
||||
@ -245,17 +511,7 @@ class StorecoveTest extends TestCase
|
||||
$p->run();
|
||||
$peppolInvoice = $p->getInvoice();
|
||||
|
||||
|
||||
$s_transformer = new StorecoveTransformer();
|
||||
$s_transformer->transform($peppolInvoice);
|
||||
|
||||
$json = $s_transformer->toJson();
|
||||
|
||||
$this->assertJson($json);
|
||||
|
||||
nlog("percentage");
|
||||
nlog($json);
|
||||
|
||||
$this->assertNotNull($peppolInvoice);
|
||||
}
|
||||
|
||||
|
||||
@ -1680,4 +1936,5 @@ class StorecoveTest extends TestCase
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -524,6 +524,8 @@ class InvoiceItemTest extends TestCase
|
||||
$item_calc = new InvoiceItemSum($this->invoice, $settings);
|
||||
$item_calc->process();
|
||||
|
||||
nlog($item_calc->getGroupedTaxes());
|
||||
|
||||
$this->assertEquals($item_calc->getTotalTaxes(), 2.06);
|
||||
$this->assertEquals($item_calc->getGroupedTaxes()->count(), 2);
|
||||
}
|
||||
|
179
tests/Unit/Tax/TaxRuleConsistencyTest.php
Normal file
179
tests/Unit/Tax/TaxRuleConsistencyTest.php
Normal file
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace Tests\Unit\Tax;
|
||||
|
||||
use Tests\TestCase;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\Country;
|
||||
use App\Models\Invoice;
|
||||
use Tests\MockAccountData;
|
||||
use App\DataMapper\Tax\BaseRule;
|
||||
use App\DataMapper\Tax\TaxModel;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Models\ClientContact;
|
||||
use App\Services\Tax\StorecoveAdapter;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use App\Services\EDocument\Gateway\Storecove\Storecove;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
class TaxRuleConsistencyTest extends TestCase
|
||||
{
|
||||
use MockAccountData;
|
||||
use DatabaseTransactions;
|
||||
|
||||
protected $faker;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
|
||||
$this->withoutExceptionHandling();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
$this->faker = \Faker\Factory::create();
|
||||
|
||||
}
|
||||
|
||||
private function setupTestData(array $params = []): array
|
||||
{
|
||||
|
||||
$settings = CompanySettings::defaults();
|
||||
$settings->vat_number = $params['company_vat'] ?? 'DE123456789';
|
||||
$settings->country_id = Country::where('iso_3166_2', 'DE')->first()->id;
|
||||
$settings->email = $this->faker->safeEmail();
|
||||
|
||||
$tax_data = new TaxModel();
|
||||
$tax_data->regions->EU->has_sales_above_threshold = $params['over_threshold'] ?? false;
|
||||
$tax_data->regions->EU->tax_all_subregions = true;
|
||||
|
||||
$this->company->settings = $settings;
|
||||
$this->company->tax_data = $tax_data;
|
||||
$this->company->save();
|
||||
$company = $this->company;
|
||||
|
||||
$client = Client::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->company->id,
|
||||
'country_id' => Country::where('iso_3166_2', $params['client_country'] ?? 'FR')->first()->id,
|
||||
'vat_number' => $params['client_vat'] ?? '',
|
||||
'classification' => $params['classification'] ?? 'individual',
|
||||
'has_valid_vat_number' => $params['has_valid_vat'] ?? false,
|
||||
'name' => 'Test Client'
|
||||
]);
|
||||
|
||||
$contact = ClientContact::factory()->create([
|
||||
'client_id' => $client->id,
|
||||
'company_id' =>$client->company_id,
|
||||
'user_id' => $client->user_id,
|
||||
'first_name' => $this->faker->firstName(),
|
||||
'last_name' => $this->faker->lastName(),
|
||||
'email' => $this->faker->safeEmail()
|
||||
]);
|
||||
|
||||
$invoice = Invoice::factory()->create([
|
||||
'client_id' => $client->id,
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'discount' => rand(1,10),
|
||||
]);
|
||||
|
||||
$e_invoice = new \InvoiceNinja\EInvoice\Models\Peppol\Invoice();
|
||||
|
||||
$stub = json_decode('{"Invoice":{"Note":"Nooo","PaymentMeans":[{"ID":{"value":"afdasfasdfasdfas"},"PayeeFinancialAccount":{"Name":"PFA-NAME","ID":{"value":"DE89370400440532013000"},"AliasName":"PFA-Alias","AccountTypeCode":{"value":"CHECKING"},"AccountFormatCode":{"value":"IBAN"},"CurrencyCode":{"value":"EUR"},"FinancialInstitutionBranch":{"ID":{"value":"DEUTDEMMXXX"},"Name":"Deutsche Bank"}}}]}}');
|
||||
foreach ($stub as $key => $value) {
|
||||
$e_invoice->{$key} = $value;
|
||||
}
|
||||
|
||||
$invoice->e_invoice = $e_invoice;
|
||||
$invoice->save();
|
||||
|
||||
|
||||
$invoice->setRelation('company', $this->company);
|
||||
|
||||
return compact('company', 'client', 'invoice');
|
||||
}
|
||||
|
||||
public function testScenarios()
|
||||
{
|
||||
$scenarios = [
|
||||
'B2C Over Threshold' => [
|
||||
'params' => [
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'FR',
|
||||
'company_vat' => 'DE123456789',
|
||||
'client_vat' => '',
|
||||
'classification' => 'individual',
|
||||
'has_valid_vat' => false,
|
||||
'over_threshold' => true,
|
||||
],
|
||||
'expected_rate' => 20, // Should use French VAT
|
||||
'expected_nexus' => 'FR',
|
||||
],
|
||||
'B2C Under Threshold' => [
|
||||
'params' => [
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'FR',
|
||||
'company_vat' => 'DE123456789',
|
||||
'client_vat' => '',
|
||||
'classification' => 'individual',
|
||||
'has_valid_vat' => false,
|
||||
'over_threshold' => false,
|
||||
],
|
||||
'expected_rate' => 19, // Should use German VAT
|
||||
'expected_nexus' => 'DE',
|
||||
],
|
||||
'B2B Transaction' => [
|
||||
'params' => [
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'FR',
|
||||
'company_vat' => 'DE123456789',
|
||||
'client_vat' => 'FR123456789',
|
||||
'classification' => 'business',
|
||||
'has_valid_vat' => true,
|
||||
'over_threshold' => true,
|
||||
],
|
||||
'expected_rate' => 19, // Should use German VAT
|
||||
'expected_nexus' => 'DE',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($scenarios as $name => $scenario) {
|
||||
$data = $this->setupTestData($scenario['params']);
|
||||
|
||||
// Test BaseRule
|
||||
$baseRule = new BaseRule();
|
||||
$baseRule->setEntity($data['invoice']);
|
||||
$baseRule->defaultForeign();
|
||||
|
||||
// Test StorecoveAdapter
|
||||
$storecove = new Storecove();
|
||||
$storecove->build($data['invoice']);
|
||||
|
||||
$this->assertEquals(
|
||||
$scenario['expected_rate'],
|
||||
$baseRule->tax_rate1
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
$storecove->adapter->getNexus(),
|
||||
$scenario['expected_nexus']
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1025,6 +1025,10 @@ class UsTaxTest extends TestCase
|
||||
$tax_data->seller_subregion = 'CA';
|
||||
$tax_data->regions->US->has_sales_above_threshold = true;
|
||||
$tax_data->regions->US->tax_all_subregions = true;
|
||||
|
||||
$tax_data->regions->US->subregions->CA->tax_rate = 6;
|
||||
$tax_data->regions->US->subregions->CA->tax_name = 'Sales Tax';
|
||||
|
||||
$tax_data->regions->EU->has_sales_above_threshold = true;
|
||||
$tax_data->regions->EU->tax_all_subregions = true;
|
||||
$tax_data->regions->EU->subregions->DE->tax_rate = 21;
|
||||
@ -1045,6 +1049,7 @@ class UsTaxTest extends TestCase
|
||||
'has_valid_vat_number' => false,
|
||||
'postal_code' => 'xx',
|
||||
'tax_data' => new Response($this->mock_response),
|
||||
'is_tax_exempt' => false,
|
||||
]);
|
||||
|
||||
$invoice = Invoice::factory()->create([
|
||||
|
Loading…
Reference in New Issue
Block a user