Refactor to use generate parent/child ids

This commit is contained in:
David Bomba 2025-08-12 13:41:11 +10:00
parent 8d23ba14d4
commit c7e79fe673
3 changed files with 316 additions and 6 deletions

View File

@ -18,6 +18,7 @@ use Illuminate\Validation\Rule;
use App\Utils\Traits\CleanLineItems; use App\Utils\Traits\CleanLineItems;
use App\Http\ValidationRules\Project\ValidProjectForClient; use App\Http\ValidationRules\Project\ValidProjectForClient;
use App\Http\ValidationRules\Invoice\CanGenerateModificationInvoice; use App\Http\ValidationRules\Invoice\CanGenerateModificationInvoice;
use App\Http\ValidationRules\Invoice\VerifactuAmountCheck;
class StoreInvoiceRequest extends Request class StoreInvoiceRequest extends Request
{ {
@ -45,7 +46,7 @@ class StoreInvoiceRequest extends Request
$rules = []; $rules = [];
$rules['client_id'] = ['required', 'bail', Rule::exists('clients', 'id')->where('company_id', $user->company()->id)->where('is_deleted', 0)]; $rules['client_id'] = ['required', 'bail', new VerifactuAmountCheck($this->all()) , Rule::exists('clients', 'id')->where('company_id', $user->company()->id)->where('is_deleted', 0)];
if ($this->file('documents') && is_array($this->file('documents'))) { if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->fileValidation(); $rules['documents.*'] = $this->fileValidation();
@ -72,7 +73,7 @@ class StoreInvoiceRequest extends Request
$rules['date'] = 'bail|sometimes|date:Y-m-d'; $rules['date'] = 'bail|sometimes|date:Y-m-d';
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date ?? '') > 1), 'date']; $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date ?? '') > 1), 'date'];
$rules['line_items'] = 'array'; $rules['line_items'] = ['bail', 'array'];
$rules['discount'] = 'sometimes|numeric|max:99999999999999'; $rules['discount'] = 'sometimes|numeric|max:99999999999999';
$rules['tax_rate1'] = 'bail|sometimes|numeric'; $rules['tax_rate1'] = 'bail|sometimes|numeric';
$rules['tax_rate2'] = 'bail|sometimes|numeric'; $rules['tax_rate2'] = 'bail|sometimes|numeric';

View File

@ -61,6 +61,18 @@ class HandleCancellation extends AbstractService
} }
/**
* verifactuCancellation
* @todo we must ensure that if there have been previous credit notes attached to the invoice,
* that the credit notes are not exceeded by the cancellation amount.
* This is because the credit notes are not linked to the invoice, but are linked to the
* invoice's backup.
* So we need to check the backup for the credit notes and ensure that the cancellation amount
* does not exceed the credit notes.
* If it does, we need to create a new credit note with the remaining amount.
* This is because the credit notes are not linked to the invoice, but are linked to the
* @return Invoice
*/
private function verifactuCancellation(): Invoice private function verifactuCancellation(): Invoice
{ {

View File

@ -91,6 +91,305 @@ class VerifactuApiTest extends TestCase
} }
public function test_credits_never_exceed_original_invoice7()
{
$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'] = 120;
$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_invoice6()
{
$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);
$invoice->line_items = [[
'quantity' => -1,
'cost' => 10,
'discount' => 0,
'tax_rate1' => 21,
'tax_name1' => 'IVA',
]];
$invoice->discount = 0;
$invoice->is_amount_discount = false;
$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;
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/invoices', $data);
$response->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/invoices', $data);
$response->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/invoices', $data);
$response->assertStatus(200);
$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_invoice5()
{
$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);
$invoice->line_items = [[
'quantity' => -5,
'cost' => 100,
'discount' => 0,
'tax_rate1' => 21,
'tax_name1' => 'IVA',
]];
$invoice->discount = 0;
$invoice->is_amount_discount = false;
$data = $invoice->toArray();
unset($data['client']);
$data['client_id'] = $this->client->hashed_id;
$data['verifactu_modified'] = true;
$data['modified_invoice_id'] = $invoice->hashed_id;
$data['number'] = null;
$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_invoice4()
{
$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['company']);
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'] = [[
'quantity' => -1,
'cost' => 100,
'discount' => 0,
'tax_rate1' => 21,
'tax_name1' => 'IVA',
]];
$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_invoice3()
{
$settings = $this->company->settings;
$settings->e_invoice_type = 'verifactu';
$this->company->settings = $settings;
$this->company->save();
$invoice = $this->buildData();
$invoice->service()->markSent()->save();
$invoice->line_items = [];
$invoice->discount = 500;
$invoice->is_amount_discount = false;
$data = $invoice->toArray();
$data['client_id'] = $this->client->hashed_id;
$data['verifactu_modified'] = true;
$data['modified_invoice_id'] = $invoice->hashed_id;
$data['number'] = null;
$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_invoice2()
{
$settings = $this->company->settings;
$settings->e_invoice_type = 'verifactu';
$this->company->settings = $settings;
$this->company->save();
$invoice = $this->buildData();
$invoice->service()->markSent()->save();
$invoice->line_items = [];
$invoice->discount = 500;
$invoice->is_amount_discount = false;
$data = $invoice->toArray();
$data['client_id'] = $this->client->hashed_id;
$data['verifactu_modified'] = true;
$data['modified_invoice_id'] = $invoice->hashed_id;
$data['number'] = null;
$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_invoice()
{
$settings = $this->company->settings;
$settings->e_invoice_type = 'verifactu';
$this->company->settings = $settings;
$this->company->save();
$invoice = $this->buildData();
$invoice->service()->markSent()->save();
// $invoice->line_items = [];
$invoice->discount = 5;
$invoice->is_amount_discount = false;
$data = $invoice->toArray();
$data['client_id'] = $this->client->hashed_id;
$data['verifactu_modified'] = true;
$data['modified_invoice_id'] = $invoice->hashed_id;
$data['number'] = null;
$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_verifactu_amount_check()
{
$settings = $this->company->settings;
$settings->e_invoice_type = 'verifactu';
$this->company->settings = $settings;
$this->company->save();
$invoice = $this->buildData();
$invoice->line_items = [];
$invoice->discount = 500;
$invoice->is_amount_discount = false;
$data = $invoice->toArray();
$data['client_id'] = $this->client->hashed_id;
$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_create_modification_invoice() public function test_create_modification_invoice()
{ {
@ -281,14 +580,12 @@ class VerifactuApiTest extends TestCase
$response->assertStatus(200); $response->assertStatus(200);
$arr = $response->json(); $arr = $response->json();
// nlog($arr);
$this->assertEquals($arr['data'][0]['status_id'], Invoice::STATUS_CANCELLED); $this->assertEquals($arr['data'][0]['status_id'], Invoice::STATUS_CANCELLED);
$this->assertEquals($arr['data'][0]['balance'], 121); $this->assertEquals($arr['data'][0]['balance'], 121);
$this->assertEquals($arr['data'][0]['amount'], 121); $this->assertEquals($arr['data'][0]['amount'], 121);
$this->assertNotNull($arr['data'][0]['backup']['child_invoice_ids'][0]); $this->assertNotNull($arr['data'][0]['backup']['child_invoice_ids'][0]);
$credit_invoice = Invoice::find($this->decodePrimaryKey($arr['data'][0]['backup']['child_invoice_ids'][0])); $credit_invoice = Invoice::find($this->decodePrimaryKey($arr['data'][0]['backup']['child_invoice_ids'][0]));
$this->assertNotNull($credit_invoice); $this->assertNotNull($credit_invoice);