diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 5927c8536523..669d5f306aea 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -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', diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 8d71e9ba0015..2779526c0eb5 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -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); diff --git a/app/Factory/CompanyFactory.php b/app/Factory/CompanyFactory.php index f6d744ba2c0e..a2b789e9d60c 100644 --- a/app/Factory/CompanyFactory.php +++ b/app/Factory/CompanyFactory.php @@ -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; diff --git a/app/Factory/CompanyUserFactory.php b/app/Factory/CompanyUserFactory.php new file mode 100644 index 000000000000..c80e1aa61a47 --- /dev/null +++ b/app/Factory/CompanyUserFactory.php @@ -0,0 +1,30 @@ +user_id = $user_id; + $company_user->company_id = $company_id; + $company_user->account_id = $account_id; + + return $company_user; + + } +} \ No newline at end of file diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 975f3d6e75a2..0e0f89cc1f01 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -139,6 +139,7 @@ class AccountController extends BaseController $account = CreateAccount::dispatchNow($request->all()); $ct = CompanyUser::whereUserId(auth()->user()->id); + return $this->listResponse($ct); } diff --git a/app/Http/Controllers/ClientPortal/InvitationController.php b/app/Http/Controllers/ClientPortal/InvitationController.php index 5ab7783297e6..393df9b290fa 100644 --- a/app/Http/Controllers/ClientPortal/InvitationController.php +++ b/app/Http/Controllers/ClientPortal/InvitationController.php @@ -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})]); diff --git a/app/Http/Controllers/CompanyController.php b/app/Http/Controllers/CompanyController.php index 43af60b5c726..50a50ef05af5 100644 --- a/app/Http/Controllers/CompanyController.php +++ b/app/Http/Controllers/CompanyController.php @@ -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; diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index f8b59b58308e..9edf1a6aab52 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -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))); } diff --git a/app/Http/Controllers/OpenAPI/CompanySchema.php b/app/Http/Controllers/OpenAPI/CompanySchema.php index 0dcd2821d095..90870421c215 100644 --- a/app/Http/Controllers/OpenAPI/CompanySchema.php +++ b/app/Http/Controllers/OpenAPI/CompanySchema.php @@ -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"), diff --git a/app/Http/Controllers/OpenAPI/CompanySettingsSchema.php b/app/Http/Controllers/OpenAPI/CompanySettingsSchema.php index 1af5ac1684d7..dfeeb2ea535b 100644 --- a/app/Http/Controllers/OpenAPI/CompanySettingsSchema.php +++ b/app/Http/Controllers/OpenAPI/CompanySettingsSchema.php @@ -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"), diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index cbf7cd7e6dc8..b2b4cbdacd71 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -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))); } diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index 16cfa9556449..f0ba485a070a 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -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))); } } diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index 19259f4e3346..ea2d8286de0b 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -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))); } diff --git a/app/Http/Controllers/RecurringInvoiceController.php b/app/Http/Controllers/RecurringInvoiceController.php index 5b27fbef3853..1edac32e2bec 100644 --- a/app/Http/Controllers/RecurringInvoiceController.php +++ b/app/Http/Controllers/RecurringInvoiceController.php @@ -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': diff --git a/app/Http/Controllers/RecurringQuoteController.php b/app/Http/Controllers/RecurringQuoteController.php index 7fda52848ae4..4dccabe7b496 100644 --- a/app/Http/Controllers/RecurringQuoteController.php +++ b/app/Http/Controllers/RecurringQuoteController.php @@ -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))); } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index bf02e3eec736..a84e1dda4010 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -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))); } diff --git a/app/Http/Requests/User/StoreUserRequest.php b/app/Http/Requests/User/StoreUserRequest.php index 890a508602aa..515f32488f7b 100644 --- a/app/Http/Requests/User/StoreUserRequest.php +++ b/app/Http/Requests/User/StoreUserRequest.php @@ -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', + ] } diff --git a/app/Jobs/Account/CreateAccount.php b/app/Jobs/Account/CreateAccount.php index 8cf0a1878f74..c5a439e33bf3 100644 --- a/app/Jobs/Account/CreateAccount.php +++ b/app/Jobs/Account/CreateAccount.php @@ -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 */ diff --git a/app/Jobs/Company/CreateCompanyToken.php b/app/Jobs/Company/CreateCompanyToken.php index 87b8b48e28f9..236c6810323d 100644 --- a/app/Jobs/Company/CreateCompanyToken.php +++ b/app/Jobs/Company/CreateCompanyToken.php @@ -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; diff --git a/app/Jobs/User/CreateUser.php b/app/Jobs/User/CreateUser.php index 764a56af1fbc..803e3cf30a82 100644 --- a/app/Jobs/User/CreateUser.php +++ b/app/Jobs/User/CreateUser.php @@ -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()), ]); diff --git a/app/Libraries/MultiDB.php b/app/Libraries/MultiDB.php index 9be708240961..cd445e018f6d 100644 --- a/app/Libraries/MultiDB.php +++ b/app/Libraries/MultiDB.php @@ -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; diff --git a/app/Models/Client.php b/app/Models/Client.php index dcf1414887a0..5a63f53d6520 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -59,6 +59,7 @@ class Client extends BaseModel ]; protected $fillable = [ + 'currency_id', 'name', 'website', 'private_notes', diff --git a/app/Models/Company.php b/app/Models/Company.php index d5b6a56baf91..cb6b111fa875 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -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 = [ diff --git a/app/Models/CompanyUser.php b/app/Models/CompanyUser.php index c8a525edeaae..4edc088b314e 100644 --- a/app/Models/CompanyUser.php +++ b/app/Models/CompanyUser.php @@ -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); diff --git a/app/Repositories/ClientRepository.php b/app/Repositories/ClientRepository.php index 443db89e9b10..d0a8b76612c3 100644 --- a/app/Repositories/ClientRepository.php +++ b/app/Repositories/ClientRepository.php @@ -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(); diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index 128617296710..f4068d811e34 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -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; } diff --git a/app/Transformers/CompanyTokenTransformer.php b/app/Transformers/CompanyTokenTransformer.php index e68494b9a0a2..93af56c99159 100644 --- a/app/Transformers/CompanyTokenTransformer.php +++ b/app/Transformers/CompanyTokenTransformer.php @@ -44,6 +44,7 @@ class CompanyTokenTransformer extends EntityTransformer return [ 'token' => $company_token->token, 'name' => $company_token->name ?: '', + 'user_agent' => $company_token->user_agent ?: 'Unidentified', ]; } diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 3d4bc4744b3d..8003e9abdb72 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -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 ?: '', diff --git a/app/Utils/Traits/GeneratesCounter.php b/app/Utils/Traits/GeneratesCounter.php index e3f75dc01496..b427092dfcce 100644 --- a/app/Utils/Traits/GeneratesCounter.php +++ b/app/Utils/Traits/GeneratesCounter.php @@ -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(); diff --git a/app/Utils/Traits/Inviteable.php b/app/Utils/Traits/Inviteable.php index 9c21dc5cb5c8..c512a9d049d2 100644 --- a/app/Utils/Traits/Inviteable.php +++ b/app/Utils/Traits/Inviteable.php @@ -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; } diff --git a/database/factories/CompanyFactory.php b/database/factories/CompanyFactory.php index 5e6e137e1600..b4117903042d 100644 --- a/database/factories/CompanyFactory.php +++ b/database/factories/CompanyFactory.php @@ -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, diff --git a/database/migrations/2014_10_13_000000_create_users_table.php b/database/migrations/2014_10_13_000000_create_users_table.php index 091bec5ab96e..191c27bf5ec6 100644 --- a/database/migrations/2014_10_13_000000_create_users_table.php +++ b/database/migrations/2014_10_13_000000_create_users_table.php @@ -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'); diff --git a/database/seeds/PaymentLibrariesSeeder.php b/database/seeds/PaymentLibrariesSeeder.php index a46c138a6f9a..bb924cc45761 100644 --- a/database/seeds/PaymentLibrariesSeeder.php +++ b/database/seeds/PaymentLibrariesSeeder.php @@ -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) { diff --git a/database/seeds/RandomDataSeeder.php b/database/seeds/RandomDataSeeder.php index 7493d2755be9..6dec2e8e1ec8 100644 --- a/database/seeds/RandomDataSeeder.php +++ b/database/seeds/RandomDataSeeder.php @@ -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)]); diff --git a/tests/Feature/PaymentTest.php b/tests/Feature/PaymentTest.php index 6fd91d3e7b6a..bef2fbf0e6be 100644 --- a/tests/Feature/PaymentTest.php +++ b/tests/Feature/PaymentTest.php @@ -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); } diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index c44fbaa24c6f..b79d39e812ad 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -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(); diff --git a/tests/Unit/GeneratesCounterTest.php b/tests/Unit/GeneratesCounterTest.php index 31591780fe6c..a20869742872 100644 --- a/tests/Unit/GeneratesCounterTest.php +++ b/tests/Unit/GeneratesCounterTest.php @@ -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();