From f24c8cafffa75c6c98bfafced6b4d00d0e655df0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 2 Aug 2025 11:08:45 +1000 Subject: [PATCH] Additional validation logic to prevent duplicate invoices have a payment schedule assigned --- .../TaskScheduler/StoreSchedulerRequest.php | 3 +- .../InvoiceWithNoExistingSchedule.php | 50 ++++++++++++ tests/Feature/Scheduler/SchedulerTest.php | 81 ++++++++++++++++++- 3 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 app/Http/ValidationRules/Scheduler/InvoiceWithNoExistingSchedule.php diff --git a/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php b/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php index 1e2b72e07e..ac9dd2ef75 100644 --- a/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php +++ b/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php @@ -14,6 +14,7 @@ namespace App\Http\Requests\TaskScheduler; use App\Http\Requests\Request; use App\Http\ValidationRules\Scheduler\ValidClientIds; +use App\Http\ValidationRules\Scheduler\InvoiceWithNoExistingSchedule; class StoreSchedulerRequest extends Request { @@ -72,7 +73,7 @@ class StoreSchedulerRequest extends Request 'parameters.status' => ['bail','sometimes', 'nullable', 'string'], '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.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.schedule' => ['bail', 'array', 'required_if:template,payment_schedule', 'min:1'], 'parameters.schedule.*.id' => ['bail','sometimes', 'integer'], diff --git a/app/Http/ValidationRules/Scheduler/InvoiceWithNoExistingSchedule.php b/app/Http/ValidationRules/Scheduler/InvoiceWithNoExistingSchedule.php new file mode 100644 index 0000000000..e8702b300c --- /dev/null +++ b/app/Http/ValidationRules/Scheduler/InvoiceWithNoExistingSchedule.php @@ -0,0 +1,50 @@ +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'; + } +} diff --git a/tests/Feature/Scheduler/SchedulerTest.php b/tests/Feature/Scheduler/SchedulerTest.php index 7582649ce4..855dc78d9c 100644 --- a/tests/Feature/Scheduler/SchedulerTest.php +++ b/tests/Feature/Scheduler/SchedulerTest.php @@ -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([ 'company_id' => $this->company->id,