Additional validation logic to prevent duplicate invoices have a payment schedule assigned

This commit is contained in:
David Bomba 2025-08-02 11:08:45 +10:00
parent 48e8a69fda
commit f24c8cafff
3 changed files with 132 additions and 2 deletions

View File

@ -14,6 +14,7 @@ namespace App\Http\Requests\TaskScheduler;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use App\Http\ValidationRules\Scheduler\ValidClientIds; use App\Http\ValidationRules\Scheduler\ValidClientIds;
use App\Http\ValidationRules\Scheduler\InvoiceWithNoExistingSchedule;
class StoreSchedulerRequest extends Request class StoreSchedulerRequest extends Request
{ {
@ -72,7 +73,7 @@ class StoreSchedulerRequest extends Request
'parameters.status' => ['bail','sometimes', 'nullable', 'string'], 'parameters.status' => ['bail','sometimes', 'nullable', 'string'],
'parameters.include_project_tasks' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'], 'parameters.include_project_tasks' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'],
'parameters.auto_send' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'], 'parameters.auto_send' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'],
'parameters.invoice_id' => ['bail', 'string', 'required_if:template,payment_schedule'], 'parameters.invoice_id' => ['bail', 'string', 'required_if:template,payment_schedule', new InvoiceWithNoExistingSchedule()],
'parameters.auto_bill' => ['bail', 'boolean', 'required_if:template,payment_schedule'], 'parameters.auto_bill' => ['bail', 'boolean', 'required_if:template,payment_schedule'],
'parameters.schedule' => ['bail', 'array', 'required_if:template,payment_schedule', 'min:1'], 'parameters.schedule' => ['bail', 'array', 'required_if:template,payment_schedule', 'min:1'],
'parameters.schedule.*.id' => ['bail','sometimes', 'integer'], 'parameters.schedule.*.id' => ['bail','sometimes', 'integer'],

View File

@ -0,0 +1,50 @@
<?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\Scheduler;
use App\Models\Client;
use App\Utils\Traits\MakesHash;
use App\Models\Scheduler;
use Illuminate\Contracts\Validation\Rule;
/**
* Class InvoiceWithNoExistingSchedule.
*/
class InvoiceWithNoExistingSchedule implements Rule
{
use MakesHash;
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
/** @var \App\Models\User $user */
$user = auth()->user();
return Scheduler::where('company_id', $user->company()->id)
->where('template', 'payment_schedule')
->where('parameters->invoice_id', $value)
->count() == 0;
}
/**
* @return string
*/
public function message()
{
return 'Invoice already has a payment schedule';
}
}

View File

@ -64,7 +64,86 @@ class SchedulerTest extends TestCase
} }
public function testPaymentScheduleWithPercentageBasedScheduleAndFailingValidation() public function testDuplicateInvoicePaymentSchedule()
{
$invoice = Invoice::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'client_id' => $this->client->id,
'date' => now()->format('Y-m-d'),
'due_date' => now()->addDays(30)->format('Y-m-d'),
'amount' => 300.00,
'balance' => 300.00,
]);
$invoice->service()->markSent()->save();
$data = [
'name' => 'A test payment schedule scheduler',
'frequency_id' => 0,
'next_run' => now()->format('Y-m-d'),
'template' => 'payment_schedule',
'parameters' => [
'invoice_id' => $invoice->hashed_id,
'auto_bill' => true,
'schedule' => [
[
'id' => 1,
'date' => now()->format('Y-m-d'),
'amount' => 40,
'is_amount' => false,
],
[
'id' => 2,
'date' => now()->addDays(30)->format('Y-m-d'),
'amount' => 60.00,
'is_amount' => false,
]
],
],
];
// $data = [
// 'schedule' => [
// [
// 'id' => 1,
// 'date' => now()->format('Y-m-d'),
// 'amount' => 40,
// 'is_amount' => false,
// ],
// [
// 'id' => 2,
// 'date' => now()->addDays(30)->format('Y-m-d'),
// 'amount' => 60.00,
// 'is_amount' => false,
// ]
// ],
// 'auto_bill' => true,
// ];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/task_schedulers', $data);
$response->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/task_schedulers', $data);
$response->assertStatus(422);
}
public function testPaymentScheduleWithPercentageBasedScheduleAndFailingValidation()
{ {
$invoice = Invoice::factory()->create([ $invoice = Invoice::factory()->create([
'company_id' => $this->company->id, 'company_id' => $this->company->id,