Tests around the handling of verifactu credit amounts

This commit is contained in:
David Bomba 2025-08-12 13:52:05 +10:00
parent c7e79fe673
commit 1a3badf748
2 changed files with 180 additions and 4 deletions

View File

@ -0,0 +1,109 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\ValidationRules\Invoice;
use Closure;
use App\Models\Invoice;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\Validation\ValidationRule;
/**
* Class VerifactuAmountCheck.
*/
class VerifactuAmountCheck implements ValidationRule
{
use MakesHash;
public function __construct(private array $input){}
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (empty($value)) {
return;
}
$user = auth()->user();
$company = $user->company();
$start = microtime(true);
/** For verifactu, we do not allow restores of deleted invoices */
if ($company->verifactuEnabled()) {
$invoice = false;
$child_invoices = false;
$child_invoice_totals = 0;
$child_invoice_count = 0;
if(isset($this->input['modified_invoice_id'])) {
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($this->input['modified_invoice_id']));
$child_invoices = Invoice::withTrashed()
->whereIn('id', $this->transformKeys($invoice->backup->child_invoice_ids->toArray()))
->get();
$child_invoice_totals = round($child_invoices->sum('amount'), 2);
$child_invoice_count = $child_invoices->count();
// if($child_invoice_totals + $invoice->amount < 0) {
// $fail("Negative invoices can only be linked to existing invoices");
// }
}
$items = collect($this->input['line_items'])->map(function ($item) use($company){
$discount = $item['discount'] ?? 0;
$is_amount_discount = $this->input['is_amount_discount'] ?? true;
if(!$is_amount_discount && $discount > 0) {
$discount = $item['quantity'] * $item['cost'] * ($discount / 100);
}
$line_total = ($item['quantity'] * $item['cost']) - $discount;
if(!$company->settings->inclusive_taxes) {
$tax = ($item['tax_rate1'] ?? 0) + ($item['tax_rate2'] ?? 0) + ($item['tax_rate3'] ?? 0);
$tax_amount = $line_total * ($tax / 100);
$line_total += $tax_amount;
}
return $line_total;
});
$total_discount = $this->input['discount'] ?? 0;
$is_amount_discount = $this->input['is_amount_discount'] ?? true;
if(!$is_amount_discount) {
$total_discount = $items->sum() * ($total_discount / 100);
}
$total = $items->sum() - $total_discount;
nlog("total " . $total);
nlog(!$invoice);
nlog($total);
nlog($child_invoice_totals);
nlog($invoice->amount ?? 0);
if($total < 0 && !$invoice) {
$fail("Negative invoices {$total} can only be linked to existing invoices");
}
elseif($invoice && ($total + $child_invoice_totals + $invoice->amount) < 0) {
$total_adjustments = $total + $child_invoice_totals;
$fail("Total Adjustment {$total_adjustments} cannot exceed the original invoice amount {$invoice->amount}");
}
}
nlog(microtime(true) - $start);
}
}

View File

@ -91,6 +91,73 @@ class VerifactuApiTest extends TestCase
} }
public function test_credits_never_exceed_original_invoice9()
{
$settings = $this->company->settings;
$settings->e_invoice_type = 'verifactu';
$this->company->settings = $settings;
$this->company->save();
$invoice = $this->buildData();
$invoice->service()->markSent()->save();
$this->assertEquals(121, $invoice->amount);
$data = $invoice->toArray();
unset($data['client']);
unset($data['invitations']);
$data['client_id'] = $this->client->hashed_id;
$data['verifactu_modified'] = true;
$data['modified_invoice_id'] = $invoice->hashed_id;
$data['number'] = null;
$data['line_items'] = [];
$data['discount'] = 122;
$data['is_amount_discount'] = true;
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/invoices', $data);
$response->assertStatus(422);
}
public function test_credits_never_exceed_original_invoice8()
{
$settings = $this->company->settings;
$settings->e_invoice_type = 'verifactu';
$this->company->settings = $settings;
$this->company->save();
$invoice = $this->buildData();
$invoice->service()->markSent()->save();
$this->assertEquals(121, $invoice->amount);
$data = $invoice->toArray();
unset($data['client']);
unset($data['invitations']);
$data['client_id'] = $this->client->hashed_id;
$data['verifactu_modified'] = true;
$data['modified_invoice_id'] = $invoice->hashed_id;
$data['number'] = null;
$data['discount'] = 121;
$data['is_amount_discount'] = true;
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/invoices', $data);
$response->assertStatus(200);
}
public function test_credits_never_exceed_original_invoice7() public function test_credits_never_exceed_original_invoice7()
{ {
@ -285,7 +352,7 @@ class VerifactuApiTest extends TestCase
$invoice->line_items = []; $invoice->line_items = [];
$invoice->discount = 500; $invoice->discount = 500;
$invoice->is_amount_discount = false; $invoice->is_amount_discount = true;
$data = $invoice->toArray(); $data = $invoice->toArray();
$data['client_id'] = $this->client->hashed_id; $data['client_id'] = $this->client->hashed_id;
@ -316,7 +383,7 @@ class VerifactuApiTest extends TestCase
$invoice->line_items = []; $invoice->line_items = [];
$invoice->discount = 500; $invoice->discount = 500;
$invoice->is_amount_discount = false; $invoice->is_amount_discount = true;
$data = $invoice->toArray(); $data = $invoice->toArray();
$data['client_id'] = $this->client->hashed_id; $data['client_id'] = $this->client->hashed_id;
@ -348,7 +415,7 @@ class VerifactuApiTest extends TestCase
// $invoice->line_items = []; // $invoice->line_items = [];
$invoice->discount = 5; $invoice->discount = 5;
$invoice->is_amount_discount = false; $invoice->is_amount_discount = true;
$data = $invoice->toArray(); $data = $invoice->toArray();
$data['client_id'] = $this->client->hashed_id; $data['client_id'] = $this->client->hashed_id;
@ -376,7 +443,7 @@ class VerifactuApiTest extends TestCase
$invoice = $this->buildData(); $invoice = $this->buildData();
$invoice->line_items = []; $invoice->line_items = [];
$invoice->discount = 500; $invoice->discount = 500;
$invoice->is_amount_discount = false; $invoice->is_amount_discount = true;
$data = $invoice->toArray(); $data = $invoice->toArray();
$data['client_id'] = $this->client->hashed_id; $data['client_id'] = $this->client->hashed_id;