Improved validation for recurring ivnoice numbers

This commit is contained in:
David Bomba 2025-01-13 18:06:01 +11:00
parent 7304f01de6
commit 9a7478e175
5 changed files with 32 additions and 138 deletions

View File

@ -13,7 +13,6 @@ namespace App\Http\Requests\RecurringInvoice;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Project\ValidProjectForClient;
use App\Http\ValidationRules\Recurring\UniqueRecurringInvoiceNumberRule;
use App\Models\Client;
use App\Models\RecurringInvoice;
use App\Utils\Traits\CleanLineItems;
@ -68,7 +67,7 @@ class StoreRecurringInvoiceRequest extends Request
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
$rules['number'] = new UniqueRecurringInvoiceNumberRule($this->all());
$rules['number'] = ['bail', 'nullable', \Illuminate\Validation\Rule::unique('recurring_invoices')->where('company_id', $user->company()->id)];
$rules['tax_rate1'] = 'bail|sometimes|numeric';
$rules['tax_rate2'] = 'bail|sometimes|numeric';

View File

@ -12,7 +12,6 @@
namespace App\Http\Requests\RecurringQuote;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Recurring\UniqueRecurringQuoteNumberRule;
use App\Models\Client;
use App\Models\RecurringQuote;
use App\Utils\Traits\CleanLineItems;
@ -63,7 +62,7 @@ class StoreRecurringQuoteRequest extends Request
$rules['frequency_id'] = 'required|integer|digits_between:1,12';
$rules['number'] = new UniqueRecurringQuoteNumberRule($this->all());
$rules['number'] = ['bail', 'nullable', \Illuminate\Validation\Rule::unique('recurring_quotes')->where('company_id', $user->company()->id)];
return $rules;
}

View File

@ -1,67 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\ValidationRules\Recurring;
use App\Models\RecurringInvoice;
use Illuminate\Contracts\Validation\Rule;
/**
* Class UniqueRecurringInvoiceNumberRule.
*/
class UniqueRecurringInvoiceNumberRule implements Rule
{
public $input;
public function __construct($input)
{
$this->input = $input;
}
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return $this->checkIfInvoiceNumberUnique(); //if it exists, return false!
}
/**
* @return string
*/
public function message()
{
return ctrans('texts.recurring_invoice_number_taken', ['number' => $this->input['number']]);
}
/**
* @return bool
*/
private function checkIfInvoiceNumberUnique(): bool
{
if (empty($this->input['number'])) {
return true;
}
$invoice = RecurringInvoice::query()->where('client_id', $this->input['client_id'])
->where('number', $this->input['number'])
->withTrashed()
->exists();
if ($invoice) {
return false;
}
return true;
}
}

View File

@ -1,67 +0,0 @@
<?php
/**
* Quote Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Quote Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\ValidationRules\Recurring;
use App\Models\RecurringQuote;
use Illuminate\Contracts\Validation\Rule;
/**
* Class UniqueRecurringQuoteNumberRule.
*/
class UniqueRecurringQuoteNumberRule implements Rule
{
public $input;
public function __construct($input)
{
$this->input = $input;
}
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return $this->checkIfQuoteNumberUnique(); //if it exists, return false!
}
/**
* @return string
*/
public function message()
{
return ctrans('texts.recurring_quote_number_taken', ['number' => $this->input['number']]);
}
/**
* @return bool
*/
private function checkIfQuoteNumberUnique(): bool
{
if (empty($this->input['number'])) {
return true;
}
$invoice = RecurringQuote::query()->where('client_id', $this->input['client_id'])
->where('number', $this->input['number'])
->withTrashed()
->exists();
if ($invoice) {
return false;
}
return true;
}
}

View File

@ -61,6 +61,36 @@ class RecurringInvoiceTest extends TestCase
$this->makeTestData();
}
public function testUniqueNumber()
{
$data = [
'client_id' => $this->client->hashed_id,
'frequency_id' => 5,
'next_send_date' => now()->addMonth()->format('Y-m-d'),
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/recurring_invoices', $data);
$response->assertStatus(200);
$arr = $response->json();
$this->assertNotNull($arr['data']['number']);
$data['number'] = $arr['data']['number'];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/recurring_invoices', $data)
->assertStatus(422);
}
public function testBulkUpdatesTaxes()
{
RecurringInvoice::factory(5)->create([