Enhancements to API (#3088)

* working on email throttling

* Fixes for invitaiton links

* pass custom fields as object

* Add user agent to company token

* Update company token transformer

* Remove prefix setting from CompanySettings

* Implement user agent on company token & provide better error handling for undefined relationships includes

* Fix bulk actions

* Working on updating/creating a company user

* Fixes for tests
This commit is contained in:
David Bomba 2019-11-21 19:38:57 +11:00 committed by GitHub
parent 170340cdfa
commit 69efd4d574
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 186 additions and 121 deletions

View File

@ -71,37 +71,28 @@ class CompanySettings extends BaseSettings
/* Counters */
public $invoice_number_pattern = '';
public $invoice_number_counter = 1;
public $invoice_number_prefix = '';
public $quote_number_prefix = '';
public $quote_number_pattern = '';
public $quote_number_counter = 1;
public $client_number_prefix = '';
public $client_number_pattern = '';
public $client_number_counter = 1;
public $credit_number_prefix = '';
public $credit_number_pattern = '';
public $credit_number_counter = 1;
public $task_number_prefix = '';
public $task_number_pattern = '';
public $task_number_counter = 1;
public $expense_number_prefix = '';
public $expense_number_pattern = '';
public $expense_number_counter = 1;
public $vendor_number_prefix = '';
public $vendor_number_pattern = '';
public $vendor_number_counter = 1;
public $ticket_number_prefix = '';
public $ticket_number_pattern = '';
public $ticket_number_counter = 1;
public $payment_number_prefix = '';
public $payment_number_pattern = '';
public $payment_number_counter = 1;
@ -260,19 +251,14 @@ class CompanySettings extends BaseSettings
'embed_documents' => 'bool',
'all_pages_header' => 'bool',
'all_pages_footer' => 'bool',
'task_number_prefix' => 'string',
'task_number_pattern' => 'string',
'task_number_counter' => 'int',
'expense_number_prefix' => 'string',
'expense_number_pattern' => 'string',
'expense_number_counter' => 'int',
'vendor_number_prefix' => 'string',
'vendor_number_pattern' => 'string',
'vendor_number_counter' => 'int',
'ticket_number_prefix' => 'string',
'ticket_number_pattern' => 'string',
'ticket_number_counter' => 'int',
'payment_number_prefix' => 'string',
'payment_number_pattern' => 'string',
'payment_number_counter' => 'int',
'reply_to_email' => 'string',
@ -287,10 +273,8 @@ class CompanySettings extends BaseSettings
'city' => 'string',
'company_logo' => 'string',
'country_id' => 'string',
'client_number_prefix' => 'string',
'client_number_pattern' => 'string',
'client_number_counter' => 'integer',
'credit_number_prefix' => 'string',
'credit_number_pattern' => 'string',
'credit_number_counter' => 'integer',
'currency_id' => 'string',
@ -320,7 +304,6 @@ class CompanySettings extends BaseSettings
'email_template_reminder_endless' => 'string',
'enable_client_portal_password' => 'bool',
'inclusive_taxes' => 'bool',
'invoice_number_prefix' => 'string',
'invoice_number_pattern' => 'string',
'invoice_number_counter' => 'integer',
'invoice_design_id' => 'string',
@ -336,7 +319,6 @@ class CompanySettings extends BaseSettings
'phone' => 'string',
'postal_code' => 'string',
'quote_design_id' => 'string',
'quote_number_prefix' => 'string',
'quote_number_pattern' => 'string',
'quote_number_counter' => 'integer',
'quote_terms' => 'string',

View File

@ -22,6 +22,7 @@ use Illuminate\Validation\ValidationException;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Illuminate\Database\Eloquent\RelationNotFoundException;
class Handler extends ExceptionHandler
{
@ -70,15 +71,15 @@ class Handler extends ExceptionHandler
public function render($request, Exception $exception)
{
if ($exception instanceof ModelNotFoundException)
if ($exception instanceof ModelNotFoundException && $request->expectsJson())
{
return response()->json(['message'=>'Record not found'],400);
}
else if($exception instanceof ThrottleRequestsException)
else if($exception instanceof ThrottleRequestsException && $request->expectsJson())
{
return response()->json(['message'=>'Too many requests'],429);
}
else if($exception instanceof FatalThrowableError)
else if($exception instanceof FatalThrowableError && $request->expectsJson())
{
return response()->json(['message'=>'Fatal error'], 500);
}
@ -95,15 +96,18 @@ class Handler extends ExceptionHandler
'message' => ctrans('texts.token_expired'),
'message-type' => 'danger']);
}
else if ($exception instanceof NotFoundHttpException) {
else if ($exception instanceof NotFoundHttpException && $request->expectsJson()) {
return response()->json(['message'=>'Route does not exist'],404);
}
else if($exception instanceof MethodNotAllowedHttpException){
else if($exception instanceof MethodNotAllowedHttpException && $request->expectsJson()){
return response()->json(['message'=>'Method not support for this route'],404);
}
else if ($exception instanceof ValidationException && $request->expectsJson()) {
return response()->json(['message' => 'The given data was invalid.', 'errors' => $exception->validator->getMessageBag()], 422);
}
else if ($exception instanceof RelationNotFoundException && $request->expectsJson()) {
return response()->json(['message' => $exception->getMessage()], 400);
}
return parent::render($request, $exception);

View File

@ -28,6 +28,7 @@ class CompanyFactory
$company->company_key = $this->createHash();
$company->settings = CompanySettings::defaults();
$company->db = config('database.default');
$company->custom_fields = (object) ['custom1' => '1', 'custom2' => '2', 'custom3'=>3];
$company->domain = '';
return $company;

View File

@ -0,0 +1,30 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Factory;
use App\Models\CompanyUser;
class CompanyUserFactory
{
public static function create($user_id, $company_id, $account_id) :CompanyUser
{
$company_user = new CompanyUser;
$company_user->user_id = $user_id;
$company_user->company_id = $company_id;
$company_user->account_id = $account_id;
return $company_user;
}
}

View File

@ -139,6 +139,7 @@ class AccountController extends BaseController
$account = CreateAccount::dispatchNow($request->all());
$ct = CompanyUser::whereUserId(auth()->user()->id);
return $this->listResponse($ct);
}

View File

@ -32,7 +32,7 @@ class InvitationController extends Controller
public function router(string $entity, string $invitation_key)
{
$key = $entity.'_id';
$entity_obj = ucfirst($entity).'Invitation';
$entity_obj = 'App\Models\\'.ucfirst($entity).'Invitation';
$invitation = $entity_obj::whereRaw("BINARY `key`= ?", [$invitation_key])->first();
@ -40,7 +40,9 @@ class InvitationController extends Controller
if((bool)$invitation->contact->client->getSetting('enable_client_portal_password') !== false)
$this->middleware('auth:contact');
else
auth()->guard('contact')->login($invitation->contact, false);
$invitation->markViewed();
return redirect()->route('client.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->{$key})]);

View File

@ -228,7 +228,7 @@ class CompanyController extends BaseController
/*
* Create token
*/
$company_token = CreateCompanyToken::dispatchNow($company, auth()->user());
$company_token = CreateCompanyToken::dispatchNow($company, auth()->user(), request()->server('HTTP_USER_AGENT'));
$this->entity_transformer = CompanyUserTransformer::class;
$this->entity_type = CompanyUser::class;

View File

@ -519,7 +519,7 @@ class InvoiceController extends BaseController
$ids = request()->input('ids');
$invoices = Invoice::withTrashed()->find($ids);
$invoices = Invoice::withTrashed()->find($this->transformKeys($ids));
$invoices->each(function ($invoice, $key) use($action){
@ -528,8 +528,7 @@ class InvoiceController extends BaseController
});
//todo need to return the updated dataset
return $this->listResponse(Invoice::withTrashed()->whereIn('id', $ids));
return $this->listResponse(Invoice::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}

View File

@ -7,9 +7,11 @@
* @OA\Property(property="size_id", type="string", example="1", description="The company size ID"),
* @OA\Property(property="industry_id", type="string", example="1", description="The company industry ID"),
* @OA\Property(property="portal_mode", type="string", example="subdomain", description="Determines the client facing urls ie: subdomain,domain,iframe"),
* @OA\Property(property="domain", type="string", example="http://acmeco.invoicing.co", description="Determines the client facing url "),
* @OA\Property(property="portal_domain", type="string", example="https://subdomain.invoicing.co", description="The fully qualified domain for client facing URLS"),
* @OA\Property(property="enabled_tax_rates", type="integer", example="1", description="Number of taxes rates used per entity"),
* @OA\Property(property="fill_products", type="boolean", example=true, description="Toggles filling a product description based on product key"),
* @OA\Property(property="enable_invoice_quantity", type="boolean", example=true, description="Toggles filling a product description based on product key"),
* @OA\Property(property="convert_products", type="boolean", example=true, description="___________"),
* @OA\Property(property="update_products", type="boolean", example=true, description="Toggles updating a product description which description changes"),
* @OA\Property(property="custom_fields", type="object", description="Custom fields map"),

View File

@ -35,32 +35,23 @@
* @OA\Property(property="auto_convert_quote", type="boolean", example=true, description="____________"),
* @OA\Property(property="inclusive_taxes", type="boolean", example=true, description="____________"),
* @OA\Property(property="translations", type="object", example="", description="JSON payload of customized translations"),
* @OA\Property(property="task_number_prefix", type="string", example="R", description="This string is prepended to the task number"),
* @OA\Property(property="task_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the task number pattern"),
* @OA\Property(property="task_number_counter", type="integer", example="1", description="____________"),
* @OA\Property(property="expense_number_prefix", type="string", example="R", description="This string is prepended to the expense number"),
* @OA\Property(property="expense_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the expense number pattern"),
* @OA\Property(property="expense_number_counter", type="integer", example="1", description="____________"),
* @OA\Property(property="vendor_number_prefix", type="string", example="R", description="This string is prepended to the vendor number"),
* @OA\Property(property="vendor_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the vendor number pattern"),
* @OA\Property(property="vendor_number_counter", type="integer", example="1", description="____________"),
* @OA\Property(property="ticket_number_prefix", type="string", example="R", description="This string is prepended to the ticket number"),
* @OA\Property(property="ticket_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the ticket number pattern"),
* @OA\Property(property="ticket_number_counter", type="integer", example="1", description="____________"),
*
* @OA\Property(property="payment_number_prefix", type="string", example="R", description="This string is prepended to the payment number"),
* @OA\Property(property="payment_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the payment number pattern"),
* @OA\Property(property="payment_number_counter", type="integer", example="1", description="____________"),
* @OA\Property(property="invoice_number_prefix", type="string", example="R", description="This string is prepended to the invoice number"),
* @OA\Property(property="invoice_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the invoice number pattern"),
* @OA\Property(property="invoice_number_counter", type="integer", example="1", description="____________"),
* @OA\Property(property="quote_number_prefix", type="string", example="R", description="This string is prepended to the quote number"),
* @OA\Property(property="quote_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the quote number pattern"),
* @OA\Property(property="quote_number_counter", type="integer", example="1", description="____________"),
* @OA\Property(property="client_number_prefix", type="string", example="R", description="This string is prepended to the client number"),
* @OA\Property(property="client_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the client number pattern"),
* @OA\Property(property="client_number_counter", type="integer", example="1", description="____________"),
* @OA\Property(property="credit_number_prefix", type="string", example="R", description="This string is prepended to the credit number"),
* @OA\Property(property="credit_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the credit number pattern"),
* @OA\Property(property="credit_number_counter", type="integer", example="1", description="____________"),
* @OA\Property(property="recurring_invoice_number_prefix", type="string", example="R", description="This string is prepended to the recurring invoice number"),

View File

@ -554,7 +554,7 @@ class PaymentController extends BaseController
$ids = request()->input('ids');
$payments = Payment::withTrashed()->find($ids);
$payments = Payment::withTrashed()->find($this->transformKeys($ids));
$payments->each(function ($payment, $key) use($action){
@ -563,8 +563,7 @@ class PaymentController extends BaseController
});
//todo need to return the updated dataset
return $this->listResponse(Payment::withTrashed()->whereIn('id', $ids));
return $this->listResponse(Payment::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}

View File

@ -470,17 +470,16 @@ class ProductController extends BaseController
$ids = request()->input('ids');
$products = Product::withTrashed()->find($ids);
$products = Product::withTrashed()->find($this->transformKeys($ids));
$products->each(function ($product, $key) use($action){
if(auth()->user()->can('edit', $product))
ActionEntity::dispatchNow($product, $action);
$this->product_repo->{$action}($product);
});
//todo need to return the updated dataset
return response()->json([], 200);
return $this->listResponse(Product::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}
}

View File

@ -507,17 +507,16 @@ class QuoteController extends BaseController
$ids = request()->input('ids');
$quotes = Quote::withTrashed()->find($ids);
$quotes = Quote::withTrashed()->find($this->transformKeys($ids));
$quotes->each(function ($quote, $key) use($action){
if(auth()->user()->can('edit', $quote))
$this->quote_repo->{$action}($quote);
$this->product_repo->{$action}($quote);
});
//todo need to return the updated dataset
return $this->listResponse(Quote::withTrashed()->whereIn('id', $ids));
return $this->listResponse(Quote::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}

View File

@ -517,7 +517,7 @@ class RecurringInvoiceController extends BaseController
$ids = request()->input('ids');
$recurring_invoices = RecurringInvoice::withTrashed()->find($ids);
$recurring_invoices = RecurringInvoice::withTrashed()->find($this->transformKeys($ids));
$recurring_invoices->each(function ($recurring_invoice, $key) use($action){
@ -526,8 +526,7 @@ class RecurringInvoiceController extends BaseController
});
//todo need to return the updated dataset
return $this->listResponse(RecurringInvoice::withTrashed()->whereIn('id', $ids));
return $this->listResponse(RecurringInvoice::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}
@ -610,7 +609,7 @@ class RecurringInvoiceController extends BaseController
// return $this->itemResponse($recurring_invoice);
break;
case 'clone_to_quote':
// $quote = CloneRecurringInvoiceToQuoteFactory::create($recurring_invoice, auth()->user()->id);
// $recurring_invoice = CloneRecurringInvoiceToQuoteFactory::create($recurring_invoice, auth()->user()->id);
// todo build the quote transformer and return response here
break;
case 'history':

View File

@ -515,7 +515,7 @@ class RecurringQuoteController extends BaseController
$ids = request()->input('ids');
$recurring_quotes = RecurringQuote::withTrashed()->find($ids);
$recurring_quotes = RecurringQuote::withTrashed()->find($this->transformKeys($ids));
$recurring_quotes->each(function ($recurring_quote, $key) use($action){
@ -524,8 +524,7 @@ class RecurringQuoteController extends BaseController
});
//todo need to return the updated dataset
return $this->listResponse(RecurringQuote::withTrashed()->whereIn('id', $ids));
return $this->listResponse(RecurringQuote::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}

View File

@ -213,7 +213,7 @@ class UserController extends BaseController
'settings' => $request->input('settings'),
]);
CreateCompanyToken::dispatchNow($company, $user);
CreateCompanyToken::dispatchNow($company, $user, request()->server('HTTP_USER_AGENT'));
$user->load('companies');
@ -514,9 +514,7 @@ class UserController extends BaseController
$ids = request()->input('ids');
$ids = $this->transformKeys($ids);
$users = User::withTrashed()->find($ids);
$users = User::withTrashed()->find($this->transformKeys($ids));
$users->each(function ($user, $key) use($action){
@ -525,8 +523,7 @@ class UserController extends BaseController
});
//todo need to return the updated dataset
return $this->listResponse(User::withTrashed()->whereIn('id', $ids));
return $this->listResponse(User::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}

View File

@ -27,7 +27,7 @@ class StoreUserRequest extends Request
public function authorize() : bool
{
return auth()->user()->can('create', User::class);
return auth()->user()->isAdmin();
}
@ -40,7 +40,6 @@ class StoreUserRequest extends Request
'first_name' => 'required|string|max:100',
'last_name' => 'required|string:max:100',
'email' => new NewUniqueUserRule(),
'is_admin' => 'required',
];
}
@ -49,16 +48,34 @@ class StoreUserRequest extends Request
{
$input = $this->all();
if(!isset($input['is_admin']))
$input['is_admin'] = null;
if(isset($input['company_user']))
{
if(!isset($input['permissions']))
$input['permissions'] = json_encode([]);
if(!isset($input['company_user']['permissions']))
$input['company_user']['permissions'] = '';
if(!isset($input['settings']))
$input['settings'] = json_encode(DefaultSettings::userSettings());
if(!isset($input['company_user']['settings']))
$input['company_user']['settings'] = json_encode(DefaultSettings::userSettings());
}
else{
$input['company_user'] = [
'settings' => json_encode(DefaultSettings::userSettings()),
'permissions' => '',
];
}
$this->replace($input);
return $this->all();
}
public function messages()
{
return [
'company_user' => 'T',
]
}

View File

@ -80,7 +80,7 @@ class CreateAccount
/*
* Create token
*/
$company_token = CreateCompanyToken::dispatchNow($company, $user);
$company_token = CreateCompanyToken::dispatchNow($company, $user, $this->request['user_agent']);
/*
* Login user
*/

View File

@ -29,16 +29,19 @@ class CreateCompanyToken implements ShouldQueue
protected $user;
protected $user_agent;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Company $company, User $user)
public function __construct(Company $company, User $user, string $user_agent)
{
$this->company = $company;
$this->user = $user;
$this->user_agent = $user_agent;
}
/**
@ -55,6 +58,7 @@ class CreateCompanyToken implements ShouldQueue
'token' => Str::random(64),
'name' => $this->user->first_name. ' '. $this->user->last_name,
'company_id' => $this->company->id,
'user_agent' => $this->user_agent,
]);
return $ct;

View File

@ -70,7 +70,7 @@ class CreateUser
'is_owner' => $this->company_owner,
'is_admin' => 1,
'is_locked' => 0,
'permissions' => json_encode([]),
'permissions' => '',
'settings' => json_encode(DefaultSettings::userSettings()),
]);

View File

@ -178,11 +178,12 @@ class MultiDB
public static function findAndSetDbByInvitation($entity, $invitation_key)
{
$entity.'Invitation';
$class = 'App\Models\\'.ucfirst($entity).'Invitation';
foreach (self::$dbs as $db)
{
if($invite = $entity::on($db)->whereKey($invitation_key)->first())
if($invite = $class::on($db)->whereRaw("BINARY `key`= ?",[$invitation_key])->first())
{
self::setDb($db);
return true;

View File

@ -59,6 +59,7 @@ class Client extends BaseModel
];
protected $fillable = [
'currency_id',
'name',
'website',
'private_notes',

View File

@ -53,6 +53,17 @@ class Company extends BaseModel
'enable_product_cost',
'enable_product_quantity',
'default_quantity',
'enable_invoice_quantity',
'enabled_tax_rates',
'portal_mode',
'portal_domain',
'convert_products',
'update_products',
'custom_surcharge_taxes1',
'custom_surcharge_taxes2',
'custom_surcharge_taxes3',
'custom_surcharge_taxes4',
];
protected $hidden = [

View File

@ -23,12 +23,20 @@ class CompanyUser extends Pivot
* @var array
*/
protected $casts = [
'permissions' => 'object',
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
];
protected $fillable = [
'account_id',
'permissions',
'settings',
'is_admin',
'is_owner',
'is_locked'
];
public function account()
{
return $this->belongsTo(Account::class);

View File

@ -73,7 +73,7 @@ class ClientRepository extends BaseRepository
$contacts = $this->contact_repo->save($data['contacts'], $client);
if($data['name'] == '')
if(empty($data['name']))
$data['name'] = $client->present()->name();

View File

@ -12,6 +12,8 @@
namespace App\Repositories;
use App\Models\User;
use App\Models\CompanyUser;
use App\Factory\CompanyUserFactory;
use Illuminate\Http\Request;
/**
@ -50,9 +52,24 @@ class UserRepository extends BaseRepository
{
$user->fill($data);
$user->save();
if($data['company_user'])
{
$company = auth()->user()->company();
$account_id = $company->account->id;
$cu = CompanyUser::whereUserId($user->id)->whereCompanyId($company->id)->first();
if(!$cu)
$cu = CompanyUserFactory::create($user->id, $company->id, $account_id);
$cu->fill($data['company_user']);
$cu->save();
}
return $user;
}

View File

@ -44,6 +44,7 @@ class CompanyTokenTransformer extends EntityTransformer
return [
'token' => $company_token->token,
'name' => $company_token->name ?: '',
'user_agent' => $company_token->user_agent ?: 'Unidentified',
];
}

View File

@ -68,6 +68,8 @@ class CompanyTransformer extends EntityTransformer
*/
public function transform(Company $company)
{
$std = new \stdClass;
return [
'id' => (string)$this->encodePrimaryKey($company->id),
'company_key' => (string)$company->company_key ?: '',
@ -81,7 +83,7 @@ class CompanyTransformer extends EntityTransformer
'enable_product_cost' => (bool)$company->enable_product_cost,
'enable_product_quantity' => (bool)$company->enable_product_quantity,
'default_quantity' => (bool)$company->default_quantity,
'custom_fields' => (string) $company->custom_fields,
'custom_fields' => $company->custom_fields ?: $std,
'size_id' => (string) $company->size_id ?: '',
'industry_id' => (string) $company->industry_id ?: '',
'first_month_of_year' => (string) $company->first_month_of_year ?: '',

View File

@ -61,10 +61,9 @@ trait GeneratesCounter
//Return a valid counter
$pattern = $client->getSetting('invoice_number_pattern');
$prefix = $client->getSetting('invoice_number_prefix');
$padding = $client->getSetting('counter_padding');
$invoice_number = $this->checkEntityNumber(Invoice::class, $client, $counter, $padding, $prefix, $pattern);
$invoice_number = $this->checkEntityNumber(Invoice::class, $client, $counter, $padding, $pattern);
$this->incrementCounter($counter_entity, 'invoice_number_counter');
@ -126,9 +125,9 @@ trait GeneratesCounter
//Return a valid counter
$pattern = '';
$prefix = $client->company->settings->recurring_invoice_number_prefix;
$padding = $client->company->settings->counter_padding;
$invoice_number = $this->checkEntityNumber(Invoice::class, $client, $counter, $padding, $prefix, $pattern);
$padding = $client->getSetting('counter_padding');
$invoice_number = $this->checkEntityNumber(Invoice::class, $client, $counter, $padding, $pattern);
$invoice_number = $this->prefixCounter($invoice_number, $client->getSetting('recurring_number_prefix'));
//increment the correct invoice_number Counter (company vs client)
if($is_client_counter)
@ -155,7 +154,7 @@ trait GeneratesCounter
$counter = $client->getSetting('client_number_counter' );
$setting_entity = $client->getSettingEntity('client_number_counter');
$client_number = $this->checkEntityNumber(Client::class, $client, $counter, $client->getSetting('counter_padding'), $client->getSetting('client_number_prefix'), $client->getSetting('client_number_pattern'));
$client_number = $this->checkEntityNumber(Client::class, $client, $counter, $client->getSetting('counter_padding'), $client->getSetting('client_number_pattern'));
$this->incrementCounter($setting_entity, 'client_number_counter');
@ -183,11 +182,10 @@ trait GeneratesCounter
* @param Collection $entity The entity ie App\Models\Client, Invoice, Quote etc
* @param integer $counter The counter
* @param integer $padding The padding
* @param string $prefix The prefix
*
*
* @return string The padded and prefixed invoice number
*/
private function checkEntityNumber($class, $client, $counter, $padding, $prefix, $pattern)
private function checkEntityNumber($class, $client, $counter, $padding, $pattern)
{
$check = false;
@ -195,10 +193,7 @@ trait GeneratesCounter
$number = $this->padCounter($counter, $padding);
if(isset($prefix) && strlen($prefix) >= 1)
$number = $this->prefixCounter($number, $prefix);
else
$number = $this->applyNumberPattern($client, $number, $pattern);
$number = $this->applyNumberPattern($client, $number, $pattern);
if($class == Invoice::class || $class == RecurringInvoice::class)
$check = $class::whereCompanyId($client->company_id)->whereInvoiceNumber($number)->withTrashed()->first();

View File

@ -52,14 +52,14 @@ trait Inviteable
switch ($this->company->portal_mode) {
case 'subdomain':
return $domain . $entity_type .'/'. $this->key;
return $domain .'client/'. $entity_type .'/'. $this->key;
break;
case 'iframe':
return $domain . $entity_type .'/'. $this->key;
return $domain .'client/'. $entity_type .'/'. $this->key;
//return $domain . $entity_type .'/'. $this->contact->client->client_hash .'/'. $this->key;
break;
case 'domain':
return $domain . $entity_type .'/'. $this->key;
return $domain .'client/'. $entity_type .'/'. $this->key;
break;
}

View File

@ -10,6 +10,8 @@ $factory->define(App\Models\Company::class, function (Faker $faker) {
'ip' => $faker->ipv4,
'db' => config('database.default'),
'settings' => CompanySettings::defaults(),
'custom_fields' => (object) ['custom1' => '1', 'custom2' => '2', 'custom3'=>3],
// 'address1' => $faker->secondaryAddress,
// 'address2' => $faker->address,
// 'city' => $faker->city,

View File

@ -152,6 +152,7 @@ class CreateUsersTable extends Migration
$table->boolean('custom_surcharge_taxes2')->default(false);
$table->boolean('custom_surcharge_taxes3')->default(false);
$table->boolean('custom_surcharge_taxes4')->default(false);
$table->boolean('enable_invoice_quantity')->default(true);
$table->boolean('show_product_cost')->default(false);
$table->unsignedInteger('enabled_tax_rates')->default(1);
@ -195,6 +196,7 @@ class CreateUsersTable extends Migration
$table->boolean('is_owner')->default(false);
$table->boolean('is_admin')->default(false);
$table->boolean('is_locked')->default(false); // locks user out of account
$table->timestamps(6);
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
@ -271,7 +273,8 @@ class CreateUsersTable extends Migration
$table->unsignedInteger('user_id');
$table->string('token')->nullable();
$table->string('name')->nullable();
$table->string('user_agent')->nullable();
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');

View File

@ -66,7 +66,7 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'GoCardless', 'provider' => 'GoCardlessV2\Redirect', 'sort_order' => 9, 'is_offsite' => true, 'key' => 'b9886f9257f0c6ee7c302f1c74475f6c', 'fields' => '{"accessToken":"","webhookSecret":"","testMode":true}'],
['name' => 'PagSeguro', 'provider' => 'PagSeguro', 'key' => 'ef498756b54db63c143af0ec433da803', 'fields' => '{"email":"","token":"","sandbox":false}'],
['name' => 'PAYMILL', 'provider' => 'Paymill', 'key' => 'ca52f618a39367a4c944098ebf977e1c', 'fields' => '{"apiKey":""}'],
['name' => 'Custom', 'provider' => 'Custom2', 'is_offsite' => true, 'sort_order' => 21, 'key' => '54faab2ab6e3223dbe848b1686490baa', 'fields' => '{"text":"","name":""}'],
['name' => 'Custom', 'provider' => 'Custom', 'is_offsite' => true, 'sort_order' => 21, 'key' => '54faab2ab6e3223dbe848b1686490baa', 'fields' => '{"name":"","text":""}'],
];
foreach ($gateways as $gateway) {

View File

@ -93,7 +93,7 @@ class RandomDataSeeder extends Seeder
]);
factory(\App\Models\Client::class, 20)->create(['user_id' => $user->id, 'company_id' => $company->id])->each(function ($c) use ($user, $company){
factory(\App\Models\Client::class, 10)->create(['user_id' => $user->id, 'company_id' => $company->id])->each(function ($c) use ($user, $company){
factory(\App\Models\ClientContact::class,1)->create([
'user_id' => $user->id,
@ -102,7 +102,7 @@ class RandomDataSeeder extends Seeder
'is_primary' => 1
]);
factory(\App\Models\ClientContact::class,10)->create([
factory(\App\Models\ClientContact::class,5)->create([
'user_id' => $user->id,
'client_id' => $c->id,
'company_id' => $company->id
@ -111,10 +111,10 @@ class RandomDataSeeder extends Seeder
});
/** Product Factory */
factory(\App\Models\Product::class,50)->create(['user_id' => $user->id, 'company_id' => $company->id]);
factory(\App\Models\Product::class,20)->create(['user_id' => $user->id, 'company_id' => $company->id]);
/** Invoice Factory */
factory(\App\Models\Invoice::class,50)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id]);
factory(\App\Models\Invoice::class,20)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id]);
$invoices = Invoice::cursor();
$invoice_repo = new InvoiceRepository();
@ -162,7 +162,7 @@ class RandomDataSeeder extends Seeder
});
/** Recurring Invoice Factory */
factory(\App\Models\RecurringInvoice::class,20)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id]);
factory(\App\Models\RecurringInvoice::class,10)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id]);
// factory(\App\Models\Payment::class,20)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id, 'settings' => ClientSettings::buildClientSettings($company->settings, $client->settings)]);

View File

@ -239,9 +239,7 @@ class PaymentTest extends TestCase
}
catch(ValidationException $e) {
\Log::error('in the validator');
$message = json_decode($e->validator->getMessageBag(),1);
\Log::error($message);
$this->assertNotNull($message);
}

View File

@ -15,6 +15,7 @@ use App\DataMapper\ClientSettings;
use App\DataMapper\CompanySettings;
use App\DataMapper\DefaultSettings;
use App\Factory\ClientFactory;
use App\Factory\CompanyUserFactory;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceItemFactory;
use App\Factory\InvoiceToRecurringInvoiceFactory;
@ -103,6 +104,11 @@ trait MockAccountData
]);
}
$cu = CompanyUserFactory::create($this->user->id, $this->company->id, $this->account->id);
$cu->is_owner = true;
$cu->is_admin = true;
$cu->save();
$this->token = \Illuminate\Support\Str::random(64);
$company_token = CompanyToken::create([
@ -113,14 +119,14 @@ trait MockAccountData
'token' => $this->token,
]);
$this->user->companies()->attach($this->company->id, [
'account_id' => $this->account->id,
'is_owner' => 1,
'is_admin' => 1,
'is_locked' => 0,
'permissions' => json_encode([]),
'settings' => json_encode(DefaultSettings::userSettings()),
]);
// $this->user->companies()->attach($this->company->id, [
// 'account_id' => $this->account->id,
// 'is_owner' => 1,
// 'is_admin' => 1,
// 'is_locked' => 0,
// 'permissions' => '',
// 'settings' => json_encode(DefaultSettings::userSettings()),
// ]);
$this->client = ClientFactory::create($this->company->id, $this->user->id);
$this->client->save();

View File

@ -66,7 +66,6 @@ class GeneratesCounterTest extends TestCase
public function testInvoiceNumberPattern()
{
$settings = $this->client->company->settings;
$settings->invoice_number_prefix = '';
$settings->invoice_number_counter = 1;
$settings->invoice_number_pattern = '{$year}-{$counter}';
@ -89,7 +88,6 @@ class GeneratesCounterTest extends TestCase
public function testInvoiceClientNumberPattern()
{
$settings = $this->company->settings;
$settings->client_number_prefix = '';
$settings->client_number_pattern = '{$year}-{$clientCounter}';
$settings->client_number_counter = 10;
@ -155,7 +153,6 @@ class GeneratesCounterTest extends TestCase
public function testInvoicePrefix()
{
$settings = $this->company->settings;
$settings->invoice_number_prefix = 'X';
$this->company->settings = $settings;
$this->company->save();
@ -165,11 +162,11 @@ class GeneratesCounterTest extends TestCase
$invoice_number = $this->getNextInvoiceNumber($cliz);
$this->assertEquals($invoice_number, 'X0001');
$this->assertEquals($invoice_number, '0007');
$invoice_number = $this->getNextInvoiceNumber($cliz);
$this->assertEquals($invoice_number, 'X0002');
$this->assertEquals($invoice_number, '0008');
}
@ -190,7 +187,6 @@ class GeneratesCounterTest extends TestCase
public function testClientNumberPrefix()
{
$settings = $this->company->settings;
$settings->client_number_prefix = 'C';
$this->company->settings = $settings;
$this->company->save();
@ -200,11 +196,11 @@ class GeneratesCounterTest extends TestCase
$client_number = $this->getNextClientNumber($cliz);
$this->assertEquals($client_number, 'C0001');
$this->assertEquals($client_number, '0001');
$client_number = $this->getNextClientNumber($cliz);
$this->assertEquals($client_number, 'C0002');
$this->assertEquals($client_number, '0002');
}
@ -212,7 +208,6 @@ class GeneratesCounterTest extends TestCase
public function testClientNumberPattern()
{
$settings = $this->company->settings;
$settings->client_number_prefix = '';
$settings->client_number_pattern = '{$year}-{$user_id}-{$counter}';
$this->company->settings = $settings;
$this->company->save();