Tests around handling cancellations in verifactu
This commit is contained in:
parent
f7961ecb61
commit
a447b6a20b
|
|
@ -785,7 +785,7 @@ class InvoiceController extends BaseController
|
|||
}
|
||||
break;
|
||||
case 'cancel':
|
||||
$invoice = $invoice->service()->handleCancellation()->save();
|
||||
$invoice = $invoice->service()->handleCancellation(request()->input('reason'))->save();
|
||||
if (! $bulk) {
|
||||
$this->itemResponse($invoice);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class RestoreDisabledRule implements ValidationRule
|
|||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
|
||||
if (empty($value) || $value != 'restore') {
|
||||
if (empty($value) ||!in_array($value, ['delete', 'restore'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -36,9 +36,13 @@ class RestoreDisabledRule implements ValidationRule
|
|||
$company = $user->company();
|
||||
|
||||
/** For verifactu, we do not allow restores of deleted invoices */
|
||||
if($company->verifactuEnabled() && Invoice::withTrashed()->whereIn('id', $this->transformKeys(request()->ids))->where('company_id', $company->id)->where('is_deleted', true)->exists()) {
|
||||
if($company->verifactuEnabled() && $value == 'restore' &&Invoice::withTrashed()->whereIn('id', $this->transformKeys(request()->ids))->where('company_id', $company->id)->where('is_deleted', true)->exists()) {
|
||||
$fail(ctrans('texts.restore_disabled_verifactu'));
|
||||
}
|
||||
|
||||
if ($company->verifactuEnabled() && $value == 'delete' && Invoice::withTrashed()->whereIn('id', $this->transformKeys(request()->ids))->where('company_id', $company->id)->where('status_id', Invoice::STATUS_CANCELLED)->exists()) {
|
||||
$fail(ctrans('texts.delete_disabled_verifactu'));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace App\Services\Invoice;
|
|||
|
||||
use App\Events\Invoice\InvoiceWasCancelled;
|
||||
use App\Models\Invoice;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Services\AbstractService;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
|
|
@ -33,6 +34,10 @@ class HandleCancellation extends AbstractService
|
|||
return $this->invoice;
|
||||
}
|
||||
|
||||
if($this->invoice->company->verifactuEnabled()) {
|
||||
return $this->verifactuCancellation();
|
||||
}
|
||||
|
||||
$adjustment = ($this->invoice->balance < 0) ? abs($this->invoice->balance) : $this->invoice->balance * -1;
|
||||
|
||||
$this->backupCancellation($adjustment);
|
||||
|
|
@ -55,6 +60,59 @@ class HandleCancellation extends AbstractService
|
|||
return $this->invoice;
|
||||
}
|
||||
|
||||
|
||||
private function verifactuCancellation(): Invoice
|
||||
{
|
||||
|
||||
$replicated_invoice = $this->invoice->replicate();
|
||||
|
||||
$this->invoice = $this->invoice->service()->setStatus(Invoice::STATUS_CANCELLED)->save();
|
||||
$this->invoice->service()->workFlow()->save();
|
||||
|
||||
$replicated_invoice->status_id = Invoice::STATUS_DRAFT;
|
||||
$replicated_invoice->date = now()->format('Y-m-d');
|
||||
$replicated_invoice->due_date = null;
|
||||
$replicated_invoice->partial = 0;
|
||||
$replicated_invoice->partial_due_date = null;
|
||||
$replicated_invoice->number = null;
|
||||
$replicated_invoice->amount = 0;
|
||||
$replicated_invoice->balance = 0;
|
||||
$replicated_invoice->paid_to_date = 0;
|
||||
|
||||
$items = $replicated_invoice->line_items;
|
||||
|
||||
foreach($items as &$item) {
|
||||
$item->quantity = $item->quantity * -1;
|
||||
}
|
||||
|
||||
$replicated_invoice->line_items = $items;
|
||||
|
||||
$backup = new \stdClass();
|
||||
$backup->cancelled_invoice_id = $this->invoice->hashed_id;
|
||||
$backup->cancelled_invoice_number = $this->invoice->number;
|
||||
$backup->cancellation_reason = $this->reason ?? 'R3';
|
||||
|
||||
$replicated_invoice->backup = $backup;
|
||||
|
||||
$invoice_repository = new InvoiceRepository();
|
||||
$replicated_invoice = $invoice_repository->save([], $replicated_invoice);
|
||||
$replicated_invoice->service()->markSent()->sendVerifactu()->save();
|
||||
|
||||
$old_backup = new \stdClass();
|
||||
$old_backup->credit_invoice_id = $replicated_invoice->hashed_id;
|
||||
$old_backup->credit_invoice_number = $replicated_invoice->number;
|
||||
$old_backup->cancellation_reason = $this->reason ?? 'R3';
|
||||
|
||||
$this->invoice->backup = $old_backup;
|
||||
$this->invoice->saveQuietly();
|
||||
$this->invoice->fresh();
|
||||
|
||||
event(new InvoiceWasCancelled($this->invoice, $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
event('eloquent.updated: App\Models\Invoice', $this->invoice);
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
public function reverse()
|
||||
{
|
||||
/* The stored cancelled object - contains the adjustment and status*/
|
||||
|
|
|
|||
|
|
@ -674,6 +674,15 @@ class InvoiceService
|
|||
|
||||
}
|
||||
|
||||
//@todo - verifactu
|
||||
public function sendVerifactu()
|
||||
{
|
||||
// if($this->invoice->company->verifactuEnabled()) {
|
||||
// (new SendVerifactu($this->invoice))->handle();
|
||||
// }
|
||||
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Saves the invoice.
|
||||
* @return Invoice object
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ trait ActionsInvoice
|
|||
{
|
||||
public function invoiceDeletable($invoice): bool
|
||||
{
|
||||
//Cancelled invoices are not deletable if verifactu is enabled
|
||||
if($invoice->company->verifactuEnabled() && $invoice->status_id == Invoice::STATUS_CANCELLED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($invoice->status_id <= Invoice::STATUS_SENT &&
|
||||
$invoice->is_deleted == false &&
|
||||
$invoice->deleted_at == null &&
|
||||
|
|
|
|||
|
|
@ -5574,6 +5574,7 @@ $lang = array(
|
|||
'selected_products' => 'Selected Products',
|
||||
'create_company_error_unauthorized' => 'You are not authorized to create a company. Only the account owner can create a company.',
|
||||
'restore_disabled_verifactu' => 'You cannot restore an invoice once it has been deleted',
|
||||
'delete_disabled_verifactu' => 'You cannot delete an invoice once it has been cancelled',
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
namespace Tests\Feature\EInvoice\Verifactu;
|
||||
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use Tests\TestCase;
|
||||
use App\Models\Client;
|
||||
use App\Models\Invoice;
|
||||
|
|
@ -45,6 +46,92 @@ class VerifactuApiTest extends TestCase
|
|||
$this->makeTestData();
|
||||
}
|
||||
|
||||
public function test_cancel_invoice_response()
|
||||
{
|
||||
|
||||
$item = new InvoiceItem();
|
||||
$item->quantity = 1;
|
||||
$item->product_key = 'product_1';
|
||||
$item->notes = 'Product 1';
|
||||
$item->cost = 100;
|
||||
$item->discount = 0;
|
||||
$item->tax_rate1 = 21;
|
||||
$item->tax_name1 = 'IVA';
|
||||
|
||||
$invoice = Invoice::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'client_id' => $this->client->id,
|
||||
'user_id' => $this->user->id,
|
||||
'number' => 'INV-0001',
|
||||
'date' => now()->format('Y-m-d'),
|
||||
'due_date' => now()->addDays(100)->format('Y-m-d'),
|
||||
'status_id' => Invoice::STATUS_DRAFT,
|
||||
'is_deleted' => false,
|
||||
'tax_rate1' => 0,
|
||||
'tax_name1' => '',
|
||||
'tax_rate2' => 0,
|
||||
'tax_name2' => '',
|
||||
'tax_rate3' => 0,
|
||||
'tax_name3' => '',
|
||||
'line_items' => [$item],
|
||||
'discount' => 0,
|
||||
'uses_inclusive_taxes' => false,
|
||||
'exchange_rate' => 1,
|
||||
'partial' => 0,
|
||||
'partial_due_date' => null,
|
||||
'footer' => '',
|
||||
]);
|
||||
|
||||
$repo = new InvoiceRepository();
|
||||
$invoice = $repo->save([], $invoice);
|
||||
|
||||
$invoice->service()->markSent()->save();
|
||||
|
||||
$this->assertEquals($invoice->status_id, Invoice::STATUS_SENT);
|
||||
$this->assertEquals($invoice->balance, 121);
|
||||
$this->assertEquals($invoice->amount, 121);
|
||||
|
||||
$settings = $this->company->settings;
|
||||
$settings->e_invoice_type = 'verifactu';
|
||||
|
||||
$this->company->settings = $settings;
|
||||
$this->company->save();
|
||||
|
||||
|
||||
$data = [
|
||||
'action' => 'cancel',
|
||||
'ids' => [$invoice->hashed_id],
|
||||
'reason' => 'R3'
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/invoices/bulk', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals($arr['data'][0]['status_id'], Invoice::STATUS_CANCELLED);
|
||||
$this->assertEquals($arr['data'][0]['balance'], 121);
|
||||
$this->assertEquals($arr['data'][0]['amount'], 121);
|
||||
$this->assertNotNull($arr['data'][0]['backup']['credit_invoice_id']);
|
||||
$this->assertNotNull($arr['data'][0]['backup']['credit_invoice_number']);
|
||||
$this->assertEquals($arr['data'][0]['backup']['cancellation_reason'], 'R3');
|
||||
|
||||
$credit_invoice = Invoice::find($this->decodePrimaryKey($arr['data'][0]['backup']['credit_invoice_id']));
|
||||
|
||||
nlog($credit_invoice->toArray());
|
||||
$this->assertNotNull($credit_invoice);
|
||||
$this->assertEquals($credit_invoice->status_id, Invoice::STATUS_SENT);
|
||||
$this->assertEquals($credit_invoice->balance, -121);
|
||||
$this->assertEquals($credit_invoice->amount, -121);
|
||||
$this->assertEquals($credit_invoice->backup->cancelled_invoice_id, $invoice->hashed_id);
|
||||
$this->assertEquals($credit_invoice->backup->cancelled_invoice_number, $invoice->number);
|
||||
$this->assertEquals($credit_invoice->backup->cancellation_reason, 'R3');
|
||||
}
|
||||
|
||||
public function test_restore_invoice_validation()
|
||||
{
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue