Merge pull request #4173 from turbo124/v5-develop

Validation rules for projects / clients.
This commit is contained in:
David Bomba 2020-10-16 20:17:30 +11:00 committed by GitHub
commit 2e233a6437
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 199 additions and 13 deletions

View File

@ -154,10 +154,12 @@ class CompanySettings extends BaseSettings
public $email_style_custom = ''; //the template itself public $email_style_custom = ''; //the template itself
public $email_subject_invoice = ''; public $email_subject_invoice = '';
public $email_subject_quote = ''; public $email_subject_quote = '';
public $email_subject_credit = '';
public $email_subject_payment = ''; public $email_subject_payment = '';
public $email_subject_payment_partial = ''; public $email_subject_payment_partial = '';
public $email_subject_statement = ''; public $email_subject_statement = '';
public $email_template_invoice = ''; public $email_template_invoice = '';
public $email_template_credit = '';
public $email_template_quote = ''; public $email_template_quote = '';
public $email_template_payment = ''; public $email_template_payment = '';
public $email_template_payment_partial = ''; public $email_template_payment_partial = '';
@ -350,10 +352,12 @@ class CompanySettings extends BaseSettings
'email_signature' => 'string', 'email_signature' => 'string',
'email_subject_invoice' => 'string', 'email_subject_invoice' => 'string',
'email_subject_quote' => 'string', 'email_subject_quote' => 'string',
'email_subject_credit' => 'string',
'email_subject_payment' => 'string', 'email_subject_payment' => 'string',
'email_subject_payment_partial' => 'string', 'email_subject_payment_partial' => 'string',
'email_template_invoice' => 'string', 'email_template_invoice' => 'string',
'email_template_quote' => 'string', 'email_template_quote' => 'string',
'email_template_credit' => 'string',
'email_template_payment' => 'string', 'email_template_payment' => 'string',
'email_template_payment_partial' => 'string', 'email_template_payment_partial' => 'string',
'email_subject_reminder1' => 'string', 'email_subject_reminder1' => 'string',

View File

@ -30,6 +30,9 @@ class EmailTemplateDefaults
case 'email_template_quote': case 'email_template_quote':
return self::emailQuoteTemplate(); return self::emailQuoteTemplate();
break; break;
case 'email_template_credit':
return self::emailCreditTemplate();
break;
case 'email_template_payment': case 'email_template_payment':
return self::emailPaymentTemplate(); return self::emailPaymentTemplate();
break; break;
@ -69,6 +72,9 @@ class EmailTemplateDefaults
case 'email_subject_quote': case 'email_subject_quote':
return self::emailQuoteSubject(); return self::emailQuoteSubject();
break; break;
case 'email_subject_credit':
return self::emailCreditSubject();
break;
case 'email_subject_payment': case 'email_subject_payment':
return self::emailPaymentSubject(); return self::emailPaymentSubject();
break; break;
@ -109,7 +115,11 @@ class EmailTemplateDefaults
public static function emailInvoiceSubject() public static function emailInvoiceSubject()
{ {
return ctrans('texts.invoice_subject', ['number'=>'$number', 'account'=>'$company.name']); return ctrans('texts.invoice_subject', ['number'=>'$number', 'account'=>'$company.name']);
//return Parsedown::instance()->line(self::transformText('invoice_subject')); }
public static function emailCreditSubject()
{
return ctrans('texts.credit_subject', ['number'=>'$number', 'account'=>'$company.name']);
} }
public static function emailInvoiceTemplate() public static function emailInvoiceTemplate()
@ -122,14 +132,11 @@ class EmailTemplateDefaults
$invoice_message = '<p>'.self::transformText('invoice_message').'</p><br><br><p>$view_link</p>'; $invoice_message = '<p>'.self::transformText('invoice_message').'</p><br><br><p>$view_link</p>';
return $invoice_message; return $invoice_message;
//return $converter->convertToHtml($invoice_message);
} }
public static function emailQuoteSubject() public static function emailQuoteSubject()
{ {
return ctrans('texts.quote_subject', ['number'=>'$number', 'account'=>'$company.name']); return ctrans('texts.quote_subject', ['number'=>'$number', 'account'=>'$company.name']);
//return Parsedown::instance()->line(self::transformText('quote_subject'));
} }
public static function emailQuoteTemplate() public static function emailQuoteTemplate()
@ -158,6 +165,17 @@ class EmailTemplateDefaults
} }
public static function emailCreditTemplate()
{
$converter = new CommonMarkConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);
return $converter->convertToHtml(self::transformText('credit_message'));
}
public static function emailPaymentPartialTemplate() public static function emailPaymentPartialTemplate()
{ {
$converter = new CommonMarkConverter([ $converter = new CommonMarkConverter([

View File

@ -17,6 +17,7 @@ use App\Events\Misc\InvitationWasViewed;
use App\Events\Quote\QuoteWasViewed; use App\Events\Quote\QuoteWasViewed;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\InvoiceInvitation; use App\Models\InvoiceInvitation;
use App\Models\RecurringInvoiceInvitation;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;

View File

@ -13,6 +13,7 @@ namespace App\Http\Requests\Invoice;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use App\Http\ValidationRules\Invoice\UniqueInvoiceNumberRule; use App\Http\ValidationRules\Invoice\UniqueInvoiceNumberRule;
use App\Http\ValidationRules\Project\ValidProjectForClient;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\Invoice; use App\Models\Invoice;
use App\Utils\Traits\CleanLineItems; use App\Utils\Traits\CleanLineItems;
@ -47,12 +48,14 @@ class StoreInvoiceRequest extends Request
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
$rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id; $rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id;
$rules['invitations.*.client_contact_id'] = 'distinct'; $rules['invitations.*.client_contact_id'] = 'distinct';
$rules['number'] = new UniqueInvoiceNumberRule($this->all()); $rules['number'] = new UniqueInvoiceNumberRule($this->all());
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
return $rules; return $rules;
} }
@ -68,6 +71,10 @@ class StoreInvoiceRequest extends Request
$input['client_id'] = $this->decodePrimaryKey($input['client_id']); $input['client_id'] = $this->decodePrimaryKey($input['client_id']);
} }
if (array_key_exists('project_id', $input) && is_string($input['project_id'])) {
$input['project_id'] = $this->decodePrimaryKey($input['project_id']);
}
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) { if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']); $input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
} }

View File

@ -33,7 +33,8 @@ class StoreProjectRequest extends Request
{ {
$rules = []; $rules = [];
$rules['name'] ='required|unique:projects,name,null,null,company_id,'.auth()->user()->companyId(); //$rules['name'] ='required|unique:projects,name,null,null,company_id,'.auth()->user()->companyId();
$rules['name'] = 'required';
$rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id; $rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id;
return $rules; return $rules;

View File

@ -12,6 +12,7 @@
namespace App\Http\Requests\RecurringInvoice; namespace App\Http\Requests\RecurringInvoice;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use App\Http\ValidationRules\Recurring\UniqueRecurringInvoiceNumberRule;
use App\Models\Client; use App\Models\Client;
use App\Models\RecurringInvoice; use App\Models\RecurringInvoice;
use App\Utils\Traits\CleanLineItems; use App\Utils\Traits\CleanLineItems;
@ -52,6 +53,8 @@ class StoreRecurringInvoiceRequest extends Request
$rules['frequency_id'] = 'required|integer'; $rules['frequency_id'] = 'required|integer';
$rules['number'] = new UniqueRecurringInvoiceNumberRule($this->all());
return $rules; return $rules;
} }

View File

@ -48,6 +48,10 @@ class UpdateRecurringInvoiceRequest extends Request
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
if ($this->input('number')) {
$rules['number'] = 'unique:recurring_invoices,number,'.$this->id.',id,company_id,'.$this->recurring_invoice->company_id;
}
return $rules; return $rules;
} }

View File

@ -48,10 +48,13 @@ class StoreVendorRequest extends Request
protected function prepareForValidation() protected function prepareForValidation()
{ {
// $input = $this->all(); $input = $this->all();
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
}
// $this->replace($input); $this->replace($input);
} }
public function messages() public function messages()

View File

@ -69,6 +69,10 @@ class UpdateVendorRequest extends Request
{ {
$input = $this->all(); $input = $this->all();
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
}
$this->replace($input); $this->replace($input);
} }
} }

View File

@ -0,0 +1,55 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\ValidationRules\Project;
use App\Models\Project;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\Validation\Rule;
/**
* Class ValidProjectForClient.
*/
class ValidProjectForClient implements Rule
{
use MakesHash;
public $input;
public function __construct($input)
{
$this->input = $input;
}
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
if(is_string($this->input['project_id']))
$this->input['project_id'] = $this->decodePrimaryKey($this->input['project_id']);
$project = Project::findOrFail($this->input['project_id']);
return $project->client_id == $this->input['client_id'];
}
/**
* @return string
*/
public function message()
{
return "Project client does not match entity client";
}
}

View File

@ -0,0 +1,73 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\ValidationRules\Recurring;
use App\Libraries\MultiDB;
use App\Models\Invoice;
use App\Models\RecurringInvoice;
use App\Models\User;
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 "Recurring Invoice number {$this->input['number']} already taken";
}
/**
* @param $email
*
* //off,when_sent,when_paid
*
* @return bool
*/
private function checkIfInvoiceNumberUnique() : bool
{
if(empty($this->input['number']))
return true;
$invoice = RecurringInvoice::where('client_id', $this->input['client_id'])
->where('number', $this->input['number'])
->withTrashed()
->exists();
if ($invoice) {
return false;
}
return true;
}
}

View File

@ -50,7 +50,7 @@ class RecurringInvoiceInvitation extends BaseModel
*/ */
public function contact() public function contact()
{ {
return $this->belongsTo(ClientContact::class)->withTrashed(); return $this->belongsTo(ClientContact::class, 'client_contact_id', 'id')->withTrashed();
} }
/** /**

View File

@ -57,6 +57,7 @@ class VendorRepository extends BaseRepository
*/ */
public function save(array $data, Vendor $vendor) : ?Vendor public function save(array $data, Vendor $vendor) : ?Vendor
{ {
$vendor->fill($data); $vendor->fill($data);
$vendor->save(); $vendor->save();

View File

@ -28,7 +28,7 @@ class CompanyLedgerTransformer extends EntityTransformer
*/ */
public function transform(CompanyLedger $company_ledger) public function transform(CompanyLedger $company_ledger)
{ {
$entity_name = lcfirst(class_basename($company_ledger->company_ledgerable_type)).'_id'; $entity_name = lcfirst(rtrim(class_basename($company_ledger->company_ledgerable_type), 's')).'_id';
return [ return [
$entity_name => (string) $this->encodePrimaryKey($company_ledger->company_ledgerable_id), $entity_name => (string) $this->encodePrimaryKey($company_ledger->company_ledgerable_id),

View File

@ -104,6 +104,10 @@ trait AppSetup
'subject' => EmailTemplateDefaults::emailStatementSubject(), 'subject' => EmailTemplateDefaults::emailStatementSubject(),
'body' => EmailTemplateDefaults::emailStatementTemplate(), 'body' => EmailTemplateDefaults::emailStatementTemplate(),
], ],
'credit' => [
'subject' => EmailTemplateDefaults::emailCreditSubject(),
'body' => EmailTemplateDefaults::emailCreditTemplate(),
],
]; ];
Cache::forever($name, $data); Cache::forever($name, $data);

View File

@ -11,6 +11,8 @@
namespace App\Utils\Traits; namespace App\Utils\Traits;
use Illuminate\Support\Str;
/** /**
* Class Inviteable. * Class Inviteable.
*/ */
@ -42,7 +44,9 @@ trait Inviteable
public function getLink() :string public function getLink() :string
{ {
$entity_type = strtolower(class_basename($this->entityType())); //$entity_type = strtolower(class_basename($this->entityType()));
$entity_type = Str::snake(class_basename($this->entityType()));
//$this->with('company','contact',$this->entity_type); //$this->with('company','contact',$this->entity_type);
//$this->with('company'); //$this->with('company');

View File

@ -3283,5 +3283,7 @@ return [
'saved_at' => 'Saved at :time', 'saved_at' => 'Saved at :time',
'credit_payment' => 'Credit applied to Invoice :invoice_number', 'credit_payment' => 'Credit applied to Invoice :invoice_number',
'credit_subject' => 'New credit :number from :account',
'credit_message' => 'To view your credit for :amount, click the link below.',
]; ];

View File

@ -31,7 +31,7 @@ Route::group(['middleware' => ['auth:contact', 'locale'], 'prefix' => 'client',
Route::get('invoices/{invoice_invitation}', 'ClientPortal\InvoiceController@show')->name('invoice.show_invitation'); Route::get('invoices/{invoice_invitation}', 'ClientPortal\InvoiceController@show')->name('invoice.show_invitation');
Route::get('recurring_invoices', 'ClientPortal\RecurringInvoiceController@index')->name('recurring_invoices.index')->middleware('portal_enabled'); Route::get('recurring_invoices', 'ClientPortal\RecurringInvoiceController@index')->name('recurring_invoices.index')->middleware('portal_enabled');
Route::get('recurring_invoices/{recurring_invoice}', 'ClientPortal\RecurringInvoiceController@show')->name('recurring_invoices.show'); Route::get('recurring_invoices/{recurring_invoice}', 'ClientPortal\RecurringInvoiceController@show')->name('recurring_invoice.show');
Route::get('recurring_invoices/{recurring_invoice}/request_cancellation', 'ClientPortal\RecurringInvoiceController@requestCancellation')->name('recurring_invoices.request_cancellation'); Route::get('recurring_invoices/{recurring_invoice}/request_cancellation', 'ClientPortal\RecurringInvoiceController@requestCancellation')->name('recurring_invoices.request_cancellation');
Route::post('payments/process', 'ClientPortal\PaymentController@process')->name('payments.process'); Route::post('payments/process', 'ClientPortal\PaymentController@process')->name('payments.process');

View File

@ -99,6 +99,8 @@ class ShopInvoiceTest extends TestCase
$this->company->enable_shop_api = true; $this->company->enable_shop_api = true;
$this->company->save(); $this->company->save();
Product::truncate();
$product = Product::factory()->create([ $product = Product::factory()->create([
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'company_id' => $this->company->id, 'company_id' => $this->company->id,