From 2b07442260c5af8551195695eae93b988fd761a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Duli=C3=A7i?= Date: Fri, 16 Apr 2021 00:59:43 +0300 Subject: [PATCH] improved tenant identification --- app/Abstracts/Commands/Module.php | 6 +- app/Abstracts/Export.php | 21 +- app/Abstracts/Factory.php | 3 +- app/Abstracts/Http/Controller.php | 69 ++- app/Abstracts/Http/FormRequest.php | 2 +- app/Abstracts/Import.php | 48 +- app/Abstracts/Job.php | 8 +- app/Abstracts/JobShouldQueue.php | 49 ++ app/Abstracts/Notification.php | 24 +- .../View/Components/DocumentShow.php | 8 +- .../View/Components/DocumentTemplate.php | 2 +- app/BulkActions/Common/Companies.php | 6 +- app/Console/Commands/BillReminder.php | 12 +- app/Console/Commands/DownloadModule.php | 3 +- app/Console/Commands/FinishUpdate.php | 3 +- app/Console/Commands/InvoiceReminder.php | 12 +- app/Console/Commands/RecurringCheck.php | 13 +- app/Console/Commands/Update.php | 3 +- .../Stubs/Modules/listeners/install.stub | 2 +- .../Common/CompanyForgettingCurrent.php | 20 + app/Events/Common/CompanyForgotCurrent.php | 20 + app/Events/Common/CompanyMadeCurrent.php | 20 + app/Events/Common/CompanyMakingCurrent.php | 20 + app/Http/Controllers/Api/Common/Companies.php | 8 +- app/Http/Controllers/Auth/Login.php | 59 ++- app/Http/Controllers/Banking/Transactions.php | 10 +- app/Http/Controllers/Banking/Transfers.php | 10 +- app/Http/Controllers/Common/Companies.php | 34 +- app/Http/Controllers/Common/Dashboards.php | 9 +- app/Http/Controllers/Common/Import.php | 2 +- app/Http/Controllers/Common/Items.php | 10 +- app/Http/Controllers/Common/Uploads.php | 2 +- app/Http/Controllers/Install/Updates.php | 4 +- .../Modals/DocumentItemColumns.php | 2 +- .../Controllers/Modals/InvoiceTemplates.php | 2 +- app/Http/Controllers/Modals/Items.php | 2 +- app/Http/Controllers/Modules/Item.php | 12 +- app/Http/Controllers/Modules/My.php | 2 +- app/Http/Controllers/Portal/Invoices.php | 6 +- app/Http/Controllers/Purchases/Bills.php | 10 +- app/Http/Controllers/Purchases/Payments.php | 10 +- app/Http/Controllers/Purchases/Vendors.php | 10 +- app/Http/Controllers/Sales/Customers.php | 10 +- app/Http/Controllers/Sales/Invoices.php | 10 +- app/Http/Controllers/Sales/Revenues.php | 10 +- app/Http/Controllers/Settings/Categories.php | 10 +- app/Http/Controllers/Settings/Modules.php | 2 +- app/Http/Controllers/Settings/Settings.php | 2 +- app/Http/Controllers/Wizard/Companies.php | 4 +- app/Http/Kernel.php | 26 +- app/Http/Middleware/ApiCompany.php | 41 -- app/Http/Middleware/Authenticate.php | 4 +- app/Http/Middleware/IdentifyCompany.php | 59 +++ app/Http/Middleware/LoadCurrencies.php | 29 -- app/Http/Middleware/LoadSettings.php | 29 -- .../Middleware/RedirectIfAuthenticated.php | 12 +- .../{CanApiKey.php => RedirectIfNoApiKey.php} | 20 +- .../Middleware/RedirectIfNotInstalled.php | 5 +- .../RedirectIfWizardNotCompleted.php | 5 +- .../RedirectSignedIfAuthenticated.php | 6 +- app/Http/Middleware/SignedCompany.php | 33 -- app/Http/ViewComposers/DocumentType.php | 16 +- app/Http/ViewComposers/InvoiceText.php | 4 +- app/Http/ViewComposers/Logo.php | 2 +- app/Http/ViewComposers/Notifications.php | 2 + app/Http/ViewComposers/Suggestions.php | 2 + app/Imports/Banking/Transfers.php | 4 +- app/Jobs/Auth/NotifyUser.php | 36 ++ app/Jobs/Common/CreateCompany.php | 11 +- app/Jobs/Common/CreateMediableForExport.php | 55 +++ app/Jobs/Common/DeleteCompany.php | 12 +- app/Jobs/Common/DeleteDashboard.php | 2 +- app/Jobs/Common/UpdateCompany.php | 20 +- app/Jobs/Common/UpdateDashboard.php | 2 +- app/Listeners/Auth/Login.php | 15 - app/Listeners/Menu/AddAdminItems.php | 6 +- app/Listeners/Module/UpdateExtraModules.php | 2 +- app/Listeners/Update/V20/Version200.php | 19 +- app/Listeners/Update/V20/Version203.php | 15 +- app/Listeners/Update/V21/Version210.php | 19 +- app/Listeners/Update/V21/Version213.php | 19 +- app/Models/Auth/User.php | 29 +- app/Models/Common/Company.php | 83 ++++ app/Notifications/Common/ExportCompleted.php | 52 +++ app/Notifications/Common/ExportFailed.php | 57 +++ app/Notifications/Common/ImportCompleted.php | 48 ++ app/Notifications/Common/ImportFailed.php | 62 +++ app/Notifications/Portal/PaymentReceived.php | 5 +- app/Notifications/Purchase/Bill.php | 3 +- app/Notifications/Sale/Invoice.php | 5 +- app/Providers/Macro.php | 73 ++- app/Providers/Observer.php | 22 +- app/Providers/Queue.php | 102 +++++ app/Providers/Route.php | 12 +- app/Scopes/Company.php | 2 +- app/Traits/Import.php | 16 +- app/Traits/Jobs.php | 38 +- app/Traits/Modules.php | 2 +- app/Traits/Scopes.php | 2 +- app/Traits/SiteApi.php | 2 +- app/Traits/Uploads.php | 8 +- app/Traits/Users.php | 57 ++- app/Utilities/Modules.php | 2 +- app/Utilities/Overrider.php | 9 +- app/Utilities/Widgets.php | 2 +- app/Utilities/helpers.php | 50 ++- .../Components/Documents/Form/Company.php | 2 +- composer.json | 2 + composer.lock | 424 +++--------------- config/app.php | 1 + config/excel.php | 89 +++- config/mediable.php | 5 +- database/seeds/TestCompany.php | 13 +- overrides/maatwebsite/excel/QueuedWriter.php | 215 +++++++++ public/files/import/bills.xlsx | Bin 11876 -> 13820 bytes public/files/import/invoices.xlsx | Bin 11885 -> 13833 bytes resources/lang/en-GB/messages.php | 4 +- resources/lang/en-GB/notifications.php | 27 ++ .../views/common/companies/index.blade.php | 4 +- .../documents/form/company.blade.php | 2 +- resources/views/errors/403.blade.php | 2 +- resources/views/errors/404.blade.php | 2 +- resources/views/errors/500.blade.php | 2 +- resources/views/partials/admin/head.blade.php | 2 +- routes/guest.php | 4 + tests/Feature/FeatureTestCase.php | 17 +- 126 files changed, 1719 insertions(+), 999 deletions(-) create mode 100644 app/Abstracts/JobShouldQueue.php create mode 100644 app/Events/Common/CompanyForgettingCurrent.php create mode 100644 app/Events/Common/CompanyForgotCurrent.php create mode 100644 app/Events/Common/CompanyMadeCurrent.php create mode 100644 app/Events/Common/CompanyMakingCurrent.php delete mode 100644 app/Http/Middleware/ApiCompany.php create mode 100644 app/Http/Middleware/IdentifyCompany.php delete mode 100644 app/Http/Middleware/LoadCurrencies.php delete mode 100644 app/Http/Middleware/LoadSettings.php rename app/Http/Middleware/{CanApiKey.php => RedirectIfNoApiKey.php} (54%) delete mode 100644 app/Http/Middleware/SignedCompany.php create mode 100644 app/Jobs/Auth/NotifyUser.php create mode 100644 app/Jobs/Common/CreateMediableForExport.php create mode 100644 app/Notifications/Common/ExportCompleted.php create mode 100644 app/Notifications/Common/ExportFailed.php create mode 100644 app/Notifications/Common/ImportCompleted.php create mode 100644 app/Notifications/Common/ImportFailed.php create mode 100644 app/Providers/Queue.php create mode 100644 overrides/maatwebsite/excel/QueuedWriter.php diff --git a/app/Abstracts/Commands/Module.php b/app/Abstracts/Commands/Module.php index 35f4d49b7..b41e7b79a 100644 --- a/app/Abstracts/Commands/Module.php +++ b/app/Abstracts/Commands/Module.php @@ -21,9 +21,9 @@ abstract class Module extends Command protected function changeRuntime() { - $this->old_company_id = session('company_id'); + $this->old_company_id = company_id(); - session(['company_id' => $this->company_id]); + company($this->company_id)->makeCurrent(); app()->setLocale($this->locale); @@ -36,7 +36,7 @@ abstract class Module extends Command session()->forget('company_id'); if (!empty($this->old_company_id)) { - session(['company_id' => $this->old_company_id]); + company($this->old_company_id)->makeCurrent(); } } diff --git a/app/Abstracts/Export.php b/app/Abstracts/Export.php index 1b4a3ec26..5508ebc87 100644 --- a/app/Abstracts/Export.php +++ b/app/Abstracts/Export.php @@ -4,8 +4,12 @@ namespace App\Abstracts; use App\Events\Export\HeadingsPreparing; use App\Events\Export\RowsPreparing; +use App\Notifications\Common\ExportFailed; use App\Utilities\Date; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Contracts\Translation\HasLocalePreference; use Illuminate\Support\Str; +use Maatwebsite\Excel\Concerns\Exportable; use Maatwebsite\Excel\Concerns\FromCollection; use Maatwebsite\Excel\Concerns\ShouldAutoSize; use Maatwebsite\Excel\Concerns\WithHeadings; @@ -13,16 +17,21 @@ use Maatwebsite\Excel\Concerns\WithMapping; use Maatwebsite\Excel\Concerns\WithTitle; use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate; -abstract class Export implements FromCollection, ShouldAutoSize, WithHeadings, WithMapping, WithTitle +abstract class Export implements FromCollection, HasLocalePreference, ShouldAutoSize, ShouldQueue, WithHeadings, WithMapping, WithTitle { + use Exportable; + public $ids; public $fields; + public $user; + public function __construct($ids = null) { $this->ids = $ids; $this->fields = $this->fields(); + $this->user = user(); } public function title(): string @@ -74,4 +83,14 @@ abstract class Export implements FromCollection, ShouldAutoSize, WithHeadings, W return $rows; } + + public function preferredLocale() + { + return $this->user->locale; + } + + public function failed(\Throwable $exception): void + { + $this->user->notify(new ExportFailed($exception)); + } } diff --git a/app/Abstracts/Factory.php b/app/Abstracts/Factory.php index 4cef11fe8..0c61d7d41 100644 --- a/app/Abstracts/Factory.php +++ b/app/Abstracts/Factory.php @@ -29,8 +29,7 @@ abstract class Factory extends BaseFactory $this->user = User::first(); $this->company = $this->user->companies()->first(); - session(['company_id' => $this->company->id]); - setting()->setExtraColumns(['company_id' => $this->company->id]); + company($this->company->id)->makeCurrent(); } public function getCompanyUsers() diff --git a/app/Abstracts/Http/Controller.php b/app/Abstracts/Http/Controller.php index 371a635fa..2bfe8931b 100644 --- a/app/Abstracts/Http/Controller.php +++ b/app/Abstracts/Http/Controller.php @@ -3,11 +3,12 @@ namespace App\Abstracts\Http; use App\Abstracts\Http\Response; +use App\Jobs\Auth\NotifyUser; +use App\Jobs\Common\CreateMediableForExport; +use App\Notifications\Common\ImportCompleted; use App\Traits\Jobs; use App\Traits\Permissions; use App\Traits\Relationships; -use Exception; -use ErrorException; use Illuminate\Database\Eloquent\Collection; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Validation\ValidatesRequests; @@ -15,9 +16,6 @@ use Illuminate\Pagination\Paginator; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Routing\Controller as BaseController; use Illuminate\Support\Str; -use Maatwebsite\Excel\Exceptions\SheetNotFoundException; -use Maatwebsite\Excel\Facades\Excel; -use Throwable; abstract class Controller extends BaseController { @@ -78,23 +76,49 @@ abstract class Controller extends BaseController * * @param $class * @param $request - * @param $url + * @param $translation * * @return mixed */ - public function importExcel($class, $request) + public function importExcel($class, $request, $translation) { try { - Excel::import($class, $request->file('import')); + $file = $request->file('import'); + + if (should_queue()) { + $class->queue($file)->onQueue('imports')->chain([ + new NotifyUser(user(), new ImportCompleted), + ]); + + $message = trans('messages.success.import_queued', ['type' => $translation]); + } else { + $class->import($file); + + $message = trans('messages.success.imported', ['type' => $translation]); + } $response = [ 'success' => true, 'error' => false, 'data' => null, - 'message' => '', + 'message' => $message, ]; - } catch (SheetNotFoundException | ErrorException | Exception | Throwable $e) { - $message = $e->getMessage(); + } catch (\Throwable $e) { + if ($e instanceof \Maatwebsite\Excel\Validators\ValidationException) { + foreach ($e->failures() as $failure) { + $message = trans('messages.error.import_column', [ + 'message' => collect($failure->errors())->first(), + 'column' => $failure->attribute(), + 'line' => $failure->row(), + ]); + + flash($message)->error()->important(); + } + + $message = ''; + } else { + $message = $e->getMessage(); + } $response = [ 'success' => false, @@ -111,15 +135,30 @@ abstract class Controller extends BaseController * Export the excel file or catch errors * * @param $class - * @param $file_name + * @param $translation + * @param $extension * * @return mixed */ - public function exportExcel($class, $file_name, $extension = 'xlsx') + public function exportExcel($class, $translation, $extension = 'xlsx') { try { - return Excel::download($class, Str::filename($file_name) . '.' . $extension); - } catch (ErrorException | Exception | Throwable $e) { + $file_name = Str::filename($translation) . '.' . $extension; + + if (should_queue()) { + $class->queue($file_name)->onQueue('exports')->chain([ + new CreateMediableForExport(user(), $file_name), + ]); + + $message = trans('messages.success.export_queued', ['type' => $translation]); + + flash($message)->success(); + + return back(); + } else { + return $class->download($file_name); + } + } catch (\Throwable $e) { flash($e->getMessage())->error()->important(); return back(); diff --git a/app/Abstracts/Http/FormRequest.php b/app/Abstracts/Http/FormRequest.php index 1628789b1..2097492f5 100644 --- a/app/Abstracts/Http/FormRequest.php +++ b/app/Abstracts/Http/FormRequest.php @@ -15,7 +15,7 @@ abstract class FormRequest extends BaseFormRequest protected function prepareForValidation() { $this->merge([ - 'company_id' => session('company_id'), + 'company_id' => company_id(), ]); } diff --git a/app/Abstracts/Import.php b/app/Abstracts/Import.php index 3cdd14e97..1bf5e3c74 100644 --- a/app/Abstracts/Import.php +++ b/app/Abstracts/Import.php @@ -5,30 +5,34 @@ namespace App\Abstracts; use App\Traits\Import as ImportHelper; use App\Utilities\Date; use Carbon\Exceptions\InvalidFormatException; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Contracts\Translation\HasLocalePreference; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Validator; -use Illuminate\Support\Str; use Maatwebsite\Excel\Concerns\Importable; -use Maatwebsite\Excel\Concerns\SkipsOnError; -use Maatwebsite\Excel\Concerns\SkipsOnFailure; +use Maatwebsite\Excel\Concerns\SkipsEmptyRows; use Maatwebsite\Excel\Concerns\ToModel; use Maatwebsite\Excel\Concerns\WithChunkReading; use Maatwebsite\Excel\Concerns\WithHeadingRow; use Maatwebsite\Excel\Concerns\WithMapping; use Maatwebsite\Excel\Concerns\WithValidation; -use Maatwebsite\Excel\Validators\Failure; use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate; -abstract class Import implements ToModel, SkipsOnError, SkipsOnFailure, WithChunkReading, WithHeadingRow, WithMapping, WithValidation +abstract class Import implements HasLocalePreference, ShouldQueue, SkipsEmptyRows, WithChunkReading, WithHeadingRow, WithMapping, WithValidation, ToModel { use Importable, ImportHelper; - public $empty_field = 'empty---'; + public $user; + + public function __construct() + { + $this->user = user(); + } public function map($row): array { - $row['company_id'] = session('company_id'); + $row['company_id'] = company_id(); // Make enabled field integer if (isset($row['enabled'])) { @@ -67,31 +71,6 @@ abstract class Import implements ToModel, SkipsOnError, SkipsOnFailure, WithChun return 100; } - public function onFailure(Failure ...$failures) - { - $sheet = Str::snake((new \ReflectionClass($this))->getShortName()); - - foreach ($failures as $failure) { - // @todo remove after 3.2 release https://github.com/Maatwebsite/Laravel-Excel/issues/1834#issuecomment-474340743 - if (collect($failure->values())->first() == $this->empty_field) { - continue; - } - - $message = trans('messages.error.import_column', [ - 'message' => collect($failure->errors())->first(), - 'sheet' => $sheet, - 'line' => $failure->row(), - ]); - - flash($message)->error()->important(); - } - } - - public function onError(\Throwable $e) - { - flash($e->getMessage())->error()->important(); - } - public function isNotValid($row) { return Validator::make($row, $this->rules())->fails(); @@ -111,4 +90,9 @@ abstract class Import implements ToModel, SkipsOnError, SkipsOnFailure, WithChun return false; } + + public function preferredLocale() + { + return $this->user->locale; + } } diff --git a/app/Abstracts/Job.php b/app/Abstracts/Job.php index 2bd701a6d..2d3c41de4 100644 --- a/app/Abstracts/Job.php +++ b/app/Abstracts/Job.php @@ -6,14 +6,10 @@ use App\Abstracts\Http\FormRequest; use App\Traits\Jobs; use App\Traits\Relationships; use App\Traits\Uploads; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; -abstract class Job implements ShouldQueue +abstract class Job { - use InteractsWithQueue, Jobs, Queueable, Relationships, SerializesModels, Uploads; + use Jobs, Relationships, Uploads; public function getRequestInstance($request) { diff --git a/app/Abstracts/JobShouldQueue.php b/app/Abstracts/JobShouldQueue.php new file mode 100644 index 000000000..04adaa208 --- /dev/null +++ b/app/Abstracts/JobShouldQueue.php @@ -0,0 +1,49 @@ +getRequestAsCollection($request); + } + + /** + * Covert the request to collection. + * + * @param mixed $request + * @return \Illuminate\Support\Collection + */ + public function getRequestAsCollection($request) + { + if (is_array($request)) { + $data = $request; + + $request = new class() extends FormRequest {}; + + $request->merge($data); + } + + return collect($request->all()); + } +} diff --git a/app/Abstracts/Notification.php b/app/Abstracts/Notification.php index 6843920ac..36ff85072 100644 --- a/app/Abstracts/Notification.php +++ b/app/Abstracts/Notification.php @@ -2,19 +2,21 @@ namespace App\Abstracts; -use App\Models\Common\EmailTemplate; +use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification as BaseNotification; -abstract class Notification extends BaseNotification +abstract class Notification extends BaseNotification implements ShouldQueue { + use Queueable; + /** * Create a notification instance. */ public function __construct() { - $this->queue = 'high'; - $this->delay = config('queue.connections.database.delay'); + $this->onQueue('notifications'); } /** @@ -35,24 +37,24 @@ abstract class Notification extends BaseNotification */ public function initMessage() { - $template = EmailTemplate::alias($this->template)->first(); + app('url')->defaults(['company_id' => company_id()]); $message = (new MailMessage) ->from(config('mail.from.address'), config('mail.from.name')) - ->subject($this->getSubject($template)) - ->view('partials.email.body', ['body' => $this->getBody($template)]); + ->subject($this->getSubject()) + ->view('partials.email.body', ['body' => $this->getBody()]); return $message; } - public function getSubject($template) + public function getSubject() { - return $this->replaceTags($template->subject); + return $this->replaceTags($this->template->subject); } - public function getBody($template) + public function getBody() { - return $this->replaceTags($template->body); + return $this->replaceTags($this->template->body); } public function replaceTags($content) diff --git a/app/Abstracts/View/Components/DocumentShow.php b/app/Abstracts/View/Components/DocumentShow.php index 13702130c..f0d313a26 100644 --- a/app/Abstracts/View/Components/DocumentShow.php +++ b/app/Abstracts/View/Components/DocumentShow.php @@ -611,7 +611,7 @@ abstract class DocumentShow extends Base $image->make($path)->resize($width, $height)->encode(); }); } catch (NotReadableException | \Exception $e) { - Log::info('Company ID: ' . session('company_id') . ' components/documentshow.php exception.'); + Log::info('Company ID: ' . company_id() . ' components/documentshow.php exception.'); Log::info($e->getMessage()); $path = base_path('public/img/company.png'); @@ -651,11 +651,11 @@ abstract class DocumentShow extends Base $route .= 'signed.' . $page . '.show'; try { - route($route, [$this->document->id, 'company_id' => session('company_id')]); + route($route, [$this->document->id, 'company_id' => company_id()]); - $signedUrl = URL::signedRoute($route, [$this->document->id, 'company_id' => session('company_id')]); + $signedUrl = URL::signedRoute($route, [$this->document->id]); } catch (\Exception $e) { - $signedUrl = URL::signedRoute('signed.invoices.show', [$this->document->id, 'company_id' => session('company_id')]); + $signedUrl = URL::signedRoute('signed.invoices.show', [$this->document->id]); } return $signedUrl; diff --git a/app/Abstracts/View/Components/DocumentTemplate.php b/app/Abstracts/View/Components/DocumentTemplate.php index b7f4d7405..add41f9da 100644 --- a/app/Abstracts/View/Components/DocumentTemplate.php +++ b/app/Abstracts/View/Components/DocumentTemplate.php @@ -213,7 +213,7 @@ abstract class DocumentTemplate extends Base $image->make($path)->resize($width, $height)->encode(); }); } catch (NotReadableException | \Exception $e) { - Log::info('Company ID: ' . session('company_id') . ' components/documentshow.php exception.'); + Log::info('Company ID: ' . company_id() . ' components/documentshow.php exception.'); Log::info($e->getMessage()); $path = base_path('public/img/company.png'); diff --git a/app/BulkActions/Common/Companies.php b/app/BulkActions/Common/Companies.php index f4c120a10..7ffce5e22 100644 --- a/app/BulkActions/Common/Companies.php +++ b/app/BulkActions/Common/Companies.php @@ -35,7 +35,7 @@ class Companies extends BulkAction foreach ($companies as $company) { try { - $this->dispatch(new UpdateCompany($company, $request->merge(['enabled' => 1]), session('company_id'))); + $this->dispatch(new UpdateCompany($company, $request->merge(['enabled' => 1]))); } catch (\Exception $e) { flash($e->getMessage())->error()->important(); } @@ -48,7 +48,7 @@ class Companies extends BulkAction foreach ($companies as $company) { try { - $this->dispatch(new UpdateCompany($company, $request->merge(['enabled' => 0]), session('company_id'))); + $this->dispatch(new UpdateCompany($company, $request->merge(['enabled' => 0]))); } catch (\Exception $e) { flash($e->getMessage())->error()->important(); } @@ -61,7 +61,7 @@ class Companies extends BulkAction foreach ($companies as $company) { try { - $this->dispatch(new DeleteCompany($company, session('company_id'))); + $this->dispatch(new DeleteCompany($company)); } catch (\Exception $e) { flash($e->getMessage())->error()->important(); } diff --git a/app/Console/Commands/BillReminder.php b/app/Console/Commands/BillReminder.php index 5062daf11..a921a3311 100644 --- a/app/Console/Commands/BillReminder.php +++ b/app/Console/Commands/BillReminder.php @@ -47,12 +47,8 @@ class BillReminder extends Command $this->info('Sending bill reminders for ' . $company->name . ' company.'); - // Set company id - session(['company_id' => $company->id]); - - // Override settings and currencies - Overrider::load('settings'); - Overrider::load('currencies'); + // Set company + $company->makeCurrent(); // Don't send reminders if disabled if (!setting('schedule.send_bill_reminder')) { @@ -70,9 +66,7 @@ class BillReminder extends Command } } - // Unset company_id - session()->forget('company_id'); - setting()->forgetAll(); + Company::forgetCurrent(); } protected function remind($day) diff --git a/app/Console/Commands/DownloadModule.php b/app/Console/Commands/DownloadModule.php index 463a1b6f5..75e55163c 100644 --- a/app/Console/Commands/DownloadModule.php +++ b/app/Console/Commands/DownloadModule.php @@ -47,8 +47,7 @@ class DownloadModule extends Command $this->alias = $this->argument('alias'); $this->company = $this->argument('company'); - session(['company_id' => $this->company]); - setting()->setExtraColumns(['company_id' => $this->company]); + company($this->company)->makeCurrent(); if (!$path = $this->download()) { return self::CMD_ERROR; diff --git a/app/Console/Commands/FinishUpdate.php b/app/Console/Commands/FinishUpdate.php index 86e810157..15e6f6b4c 100644 --- a/app/Console/Commands/FinishUpdate.php +++ b/app/Console/Commands/FinishUpdate.php @@ -57,8 +57,7 @@ class FinishUpdate extends Command } } - session(['company_id' => $company_id]); - setting()->setExtraColumns(['company_id' => $company_id]); + company($company_id)->makeCurrent(); // Disable model cache during update config(['laravel-model-caching.enabled' => false]); diff --git a/app/Console/Commands/InvoiceReminder.php b/app/Console/Commands/InvoiceReminder.php index 7874e5bdb..25caccc53 100644 --- a/app/Console/Commands/InvoiceReminder.php +++ b/app/Console/Commands/InvoiceReminder.php @@ -47,12 +47,8 @@ class InvoiceReminder extends Command $this->info('Sending invoice reminders for ' . $company->name . ' company.'); - // Set company id - session(['company_id' => $company->id]); - - // Override settings and currencies - Overrider::load('settings'); - Overrider::load('currencies'); + // Set company + $company->makeCurrent(); // Don't send reminders if disabled if (!setting('schedule.send_invoice_reminder')) { @@ -70,9 +66,7 @@ class InvoiceReminder extends Command } } - // Unset company_id - session()->forget('company_id'); - setting()->forgetAll(); + Company::forgetCurrent(); } protected function remind($day) diff --git a/app/Console/Commands/RecurringCheck.php b/app/Console/Commands/RecurringCheck.php index c50475905..16f6e83fe 100644 --- a/app/Console/Commands/RecurringCheck.php +++ b/app/Console/Commands/RecurringCheck.php @@ -7,10 +7,10 @@ use App\Events\Banking\TransactionRecurring; use App\Events\Document\DocumentCreated; use App\Events\Document\DocumentRecurring; use App\Models\Banking\Transaction; +use App\Models\Common\Company; use App\Models\Common\Recurring; use App\Models\Document\Document; use App\Utilities\Date; -use App\Utilities\Overrider; use Illuminate\Console\Command; class RecurringCheck extends Command @@ -83,12 +83,7 @@ class RecurringCheck extends Command continue; } - // Set company id - session(['company_id' => $recur->company_id]); - - // Override settings and currencies - Overrider::load('settings'); - Overrider::load('currencies'); + company($recur->company_id)->makeCurrent(); $today = Date::today(); @@ -121,9 +116,7 @@ class RecurringCheck extends Command } } - // Unset company_id - session()->forget('company_id'); - setting()->forgetAll(); + Company::forgetCurrent(); } protected function recur($model, $type, $schedule_date) diff --git a/app/Console/Commands/Update.php b/app/Console/Commands/Update.php index 89ca9853b..dfdf4da9a 100644 --- a/app/Console/Commands/Update.php +++ b/app/Console/Commands/Update.php @@ -68,8 +68,7 @@ class Update extends Command $this->old = $this->getOldVersion(); - session(['company_id' => $this->company]); - setting()->setExtraColumns(['company_id' => $this->company]); + company($this->company)->makeCurrent(); if (!$path = $this->download()) { return self::CMD_ERROR; diff --git a/app/Console/Stubs/Modules/listeners/install.stub b/app/Console/Stubs/Modules/listeners/install.stub index 4210e4b10..1c8760457 100644 --- a/app/Console/Stubs/Modules/listeners/install.stub +++ b/app/Console/Stubs/Modules/listeners/install.stub @@ -40,7 +40,7 @@ class FinishInstallation protected function callSeeds() { Artisan::call('company:seed', [ - 'company' => session('company_id'), + 'company' => company_id(), '--class' => 'Modules\$STUDLY_NAME$\Database\Seeds\$STUDLY_NAME$DatabaseSeeder', ]); } diff --git a/app/Events/Common/CompanyForgettingCurrent.php b/app/Events/Common/CompanyForgettingCurrent.php new file mode 100644 index 000000000..dfd793765 --- /dev/null +++ b/app/Events/Common/CompanyForgettingCurrent.php @@ -0,0 +1,20 @@ +company = $company; + } +} diff --git a/app/Events/Common/CompanyForgotCurrent.php b/app/Events/Common/CompanyForgotCurrent.php new file mode 100644 index 000000000..15beb2b18 --- /dev/null +++ b/app/Events/Common/CompanyForgotCurrent.php @@ -0,0 +1,20 @@ +company = $company; + } +} diff --git a/app/Events/Common/CompanyMadeCurrent.php b/app/Events/Common/CompanyMadeCurrent.php new file mode 100644 index 000000000..828d643c0 --- /dev/null +++ b/app/Events/Common/CompanyMadeCurrent.php @@ -0,0 +1,20 @@ +company = $company; + } +} diff --git a/app/Events/Common/CompanyMakingCurrent.php b/app/Events/Common/CompanyMakingCurrent.php new file mode 100644 index 000000000..7787922b6 --- /dev/null +++ b/app/Events/Common/CompanyMakingCurrent.php @@ -0,0 +1,20 @@ +company = $company; + } +} diff --git a/app/Http/Controllers/Api/Common/Companies.php b/app/Http/Controllers/Api/Common/Companies.php index bbdb8270c..4f5b8a5a3 100644 --- a/app/Http/Controllers/Api/Common/Companies.php +++ b/app/Http/Controllers/Api/Common/Companies.php @@ -69,7 +69,7 @@ class Companies extends ApiController public function update(Company $company, Request $request) { try { - $company = $this->dispatch(new UpdateCompany($company, $request, session('company_id'))); + $company = $this->dispatch(new UpdateCompany($company, $request)); return $this->item($company->fresh(), new Transformer()); } catch (\Exception $e) { @@ -86,7 +86,7 @@ class Companies extends ApiController public function enable(Company $company) { try { - $company = $this->dispatch(new UpdateCompany($company, request()->merge(['enabled' => 1]), session('company_id'))); + $company = $this->dispatch(new UpdateCompany($company, request()->merge(['enabled' => 1]))); return $this->item($company->fresh(), new Transformer()); } catch (\Exception $e) { @@ -103,7 +103,7 @@ class Companies extends ApiController public function disable(Company $company) { try { - $company = $this->dispatch(new UpdateCompany($company, request()->merge(['enabled' => 0]), session('company_id'))); + $company = $this->dispatch(new UpdateCompany($company, request()->merge(['enabled' => 0]))); return $this->item($company->fresh(), new Transformer()); } catch (\Exception $e) { @@ -120,7 +120,7 @@ class Companies extends ApiController public function destroy(Company $company) { try { - $this->dispatch(new DeleteCompany($company, session('company_id'))); + $this->dispatch(new DeleteCompany($company)); return $this->response->noContent(); } catch (\Exception $e) { diff --git a/app/Http/Controllers/Auth/Login.php b/app/Http/Controllers/Auth/Login.php index 9b45ec5f4..5d8c3a035 100644 --- a/app/Http/Controllers/Auth/Login.php +++ b/app/Http/Controllers/Auth/Login.php @@ -37,16 +37,14 @@ class Login extends Controller { // Attempt to login if (!auth()->attempt($request->only('email', 'password'), $request->get('remember', false))) { - $response = [ + return response()->json([ 'status' => null, 'success' => false, 'error' => true, 'message' => trans('auth.failed'), 'data' => null, 'redirect' => null, - ]; - - return response()->json($response); + ]); } // Get user object @@ -56,49 +54,64 @@ class Login extends Controller if (!$user->enabled) { $this->logout(); - $response = [ + return response()->json([ 'status' => null, 'success' => false, 'error' => true, 'message' => trans('auth.disabled'), 'data' => null, 'redirect' => null, - ]; - - return response()->json($response); + ]); } - // Check if is customer - if ($user->can('read-client-portal')) { - $path = session('url.intended', 'portal'); + $company = $user->withoutEvents(function () use ($user) { + return $user->companies()->enabled()->first(); + }); - // Path must start with 'portal' prefix - if (!Str::startsWith($path, 'portal')) { - $path = 'portal'; + // Logout if no company assigned + if (!$company) { + $this->logout(); + + return response()->json([ + 'status' => null, + 'success' => false, + 'error' => true, + 'message' => trans('auth.error.no_company'), + 'data' => null, + 'redirect' => null, + ]); + } + + // Redirect to portal if is customer + if ($user->can('read-client-portal')) { + $path = session('url.intended', ''); + + // Path must start with company id and 'portal' prefix + if (!Str::startsWith($path, $company->id . '/portal')) { + $path = route('portal.dashboard', ['company_id' => $company->id]); } - $response = [ + return response()->json([ 'status' => null, 'success' => true, 'error' => false, 'message' => null, 'data' => null, 'redirect' => url($path), - ]; - - return response()->json($response); + ]); } - $response = [ + // Redirect to landing page if is user + $url = route($user->landing_page, ['company_id' => $company->id]); + + return response()->json([ 'status' => null, 'success' => true, 'error' => false, 'message' => null, 'data' => null, - 'redirect' => redirect()->intended(route($user->landing_page))->getTargetUrl(), - ]; - - return response()->json($response); + 'redirect' => redirect()->intended($url)->getTargetUrl(), + ]); } public function destroy() diff --git a/app/Http/Controllers/Banking/Transactions.php b/app/Http/Controllers/Banking/Transactions.php index 8ed90812b..9641dc223 100644 --- a/app/Http/Controllers/Banking/Transactions.php +++ b/app/Http/Controllers/Banking/Transactions.php @@ -42,20 +42,16 @@ class Transactions extends Controller */ public function import(ImportRequest $request) { - $response = $this->importExcel(new Import, $request); + $response = $this->importExcel(new Import, $request, trans_choice('general.transactions', 2)); if ($response['success']) { $response['redirect'] = route('transactions.index'); - $message = trans('messages.success.imported', ['type' => trans_choice('general.transactions', 2)]); - - flash($message)->success(); + flash($response['message'])->success(); } else { $response['redirect'] = route('import.create', ['banking', 'transactions']); - $message = $response['message']; - - flash($message)->error()->important(); + flash($response['message'])->error()->important(); } return response()->json($response); diff --git a/app/Http/Controllers/Banking/Transfers.php b/app/Http/Controllers/Banking/Transfers.php index fd4a77ea3..35a72318b 100644 --- a/app/Http/Controllers/Banking/Transfers.php +++ b/app/Http/Controllers/Banking/Transfers.php @@ -97,20 +97,16 @@ class Transfers extends Controller */ public function import(ImportRequest $request) { - $response = $this->importExcel(new Import, $request); + $response = $this->importExcel(new Import, $request, trans_choice('general.transfers', 2)); if ($response['success']) { $response['redirect'] = route('transfers.index'); - $message = trans('messages.success.imported', ['type' => trans_choice('general.transfers', 2)]); - - flash($message)->success(); + flash($response['message'])->success(); } else { $response['redirect'] = route('import.create', ['banking', 'transfers']); - $message = $response['message']; - - flash($message)->error()->important(); + flash($response['message'])->error()->important(); } return response()->json($response); diff --git a/app/Http/Controllers/Common/Companies.php b/app/Http/Controllers/Common/Companies.php index d72b7b0d6..4a1a49738 100644 --- a/app/Http/Controllers/Common/Companies.php +++ b/app/Http/Controllers/Common/Companies.php @@ -11,7 +11,6 @@ use App\Models\Common\Company; use App\Models\Setting\Currency; use App\Traits\Uploads; use App\Traits\Users; -use App\Utilities\Overrider; class Companies extends Controller { @@ -60,7 +59,7 @@ class Companies extends Controller */ public function store(Request $request) { - $company_id = session('company_id'); + $current_company_id = company_id(); $response = $this->ajaxDispatch(new CreateCompany($request)); @@ -78,9 +77,7 @@ class Companies extends Controller flash($message)->error()->important(); } - session(['company_id' => $company_id]); - - Overrider::load('settings'); + company($current_company_id)->makeCurrent(); return response()->json($response); } @@ -94,7 +91,7 @@ class Companies extends Controller */ public function edit(Company $company) { - if (!$this->isUserCompany($company->id)) { + if ($this->isNotUserCompany($company->id)) { return redirect()->route('companies.index'); } @@ -113,9 +110,9 @@ class Companies extends Controller */ public function update(Company $company, Request $request) { - $company_id = session('company_id'); + $current_company_id = company_id(); - $response = $this->ajaxDispatch(new UpdateCompany($company, $request, session('company_id'))); + $response = $this->ajaxDispatch(new UpdateCompany($company, $request, company_id())); if ($response['success']) { $response['redirect'] = route('companies.index'); @@ -131,9 +128,7 @@ class Companies extends Controller flash($message)->error()->important(); } - session(['company_id' => $company_id]); - - Overrider::load('settings'); + company($current_company_id)->makeCurrent(); return response()->json($response); } @@ -147,7 +142,7 @@ class Companies extends Controller */ public function enable(Company $company) { - $response = $this->ajaxDispatch(new UpdateCompany($company, request()->merge(['enabled' => 1]), session('company_id'))); + $response = $this->ajaxDispatch(new UpdateCompany($company, request()->merge(['enabled' => 1]))); if ($response['success']) { $response['message'] = trans('messages.success.enabled', ['type' => trans_choice('general.companies', 1)]); @@ -165,7 +160,7 @@ class Companies extends Controller */ public function disable(Company $company) { - $response = $this->ajaxDispatch(new UpdateCompany($company, request()->merge(['enabled' => 0]), session('company_id'))); + $response = $this->ajaxDispatch(new UpdateCompany($company, request()->merge(['enabled' => 0]))); if ($response['success']) { $response['message'] = trans('messages.success.disabled', ['type' => trans_choice('general.companies', 1)]); @@ -183,7 +178,7 @@ class Companies extends Controller */ public function destroy(Company $company) { - $response = $this->ajaxDispatch(new DeleteCompany($company, session('company_id'))); + $response = $this->ajaxDispatch(new DeleteCompany($company)); $response['redirect'] = route('companies.index'); @@ -210,22 +205,21 @@ class Companies extends Controller public function switch(Company $company) { if ($this->isUserCompany($company->id)) { - $old_company_id = session('company_id'); + $old_company_id = company_id(); + + $company->makeCurrent(); - session(['company_id' => $company->id]); session(['dashboard_id' => user()->dashboards()->enabled()->pluck('id')->first()]); - Overrider::load('settings'); - event(new \App\Events\Common\CompanySwitched($company, $old_company_id)); // Check wizard if (!setting('wizard.completed', false)) { - return redirect()->route('wizard.edit'); + return redirect()->route('wizard.edit', ['company_id' => $company->id]); } } - return redirect()->route('dashboard'); + return redirect()->route('dashboard', ['company_id' => $company->id]); } public function autocomplete() diff --git a/app/Http/Controllers/Common/Dashboards.php b/app/Http/Controllers/Common/Dashboards.php index 1180d24fa..22f7418dc 100644 --- a/app/Http/Controllers/Common/Dashboards.php +++ b/app/Http/Controllers/Common/Dashboards.php @@ -7,7 +7,6 @@ use App\Http\Requests\Common\Dashboard as Request; use App\Jobs\Common\CreateDashboard; use App\Jobs\Common\DeleteDashboard; use App\Jobs\Common\UpdateDashboard; -use App\Models\Common\Company; use App\Models\Common\Dashboard; use App\Models\Common\Widget; use App\Traits\DateTime; @@ -60,7 +59,7 @@ class Dashboards extends Controller if (empty($dashboard)) { $dashboard = $this->dispatch(new CreateDashboard([ - 'company_id' => session('company_id'), + 'company_id' => company_id(), 'name' => trans_choice('general.dashboards', 1), 'default_widgets' => 'core', ])); @@ -89,7 +88,7 @@ class Dashboards extends Controller */ public function create() { - $users = Company::find(session('company_id'))->users()->get()->sortBy('name'); + $users = company()->users()->get()->sortBy('name'); return view('common.dashboards.create', compact('users')); } @@ -130,11 +129,11 @@ class Dashboards extends Controller */ public function edit(Dashboard $dashboard) { - if (!$this->isUserDashboard($dashboard->id)) { + if ($this->isNotUserDashboard($dashboard->id)) { return redirect()->route('dashboards.index'); } - $users = Company::find(session('company_id'))->users()->get()->sortBy('name'); + $users = company()->users()->get()->sortBy('name'); return view('common.dashboards.edit', compact('dashboard', 'users')); } diff --git a/app/Http/Controllers/Common/Import.php b/app/Http/Controllers/Common/Import.php index c24a5b843..a5f1f51cc 100644 --- a/app/Http/Controllers/Common/Import.php +++ b/app/Http/Controllers/Common/Import.php @@ -16,7 +16,7 @@ class Import extends Controller */ public function create($group, $type, $route = null) { - $path = $group . '/' . $type; + $path = company_id() . '/' . $group . '/' . $type; if (module($group) instanceof \Akaunting\Module\Module) { $namespace = $group . '::'; diff --git a/app/Http/Controllers/Common/Items.php b/app/Http/Controllers/Common/Items.php index 5d1d002a8..cd8067d44 100644 --- a/app/Http/Controllers/Common/Items.php +++ b/app/Http/Controllers/Common/Items.php @@ -111,20 +111,16 @@ class Items extends Controller */ public function import(ImportRequest $request) { - $response = $this->importExcel(new Import, $request); + $response = $this->importExcel(new Import, $request, trans_choice('general.items', 2)); if ($response['success']) { $response['redirect'] = route('items.index'); - $message = trans('messages.success.imported', ['type' => trans_choice('general.items', 2)]); - - flash($message)->success(); + flash($response['message'])->success(); } else { $response['redirect'] = route('import.create', ['common', 'items']); - $message = $response['message']; - - flash($message)->error()->important(); + flash($response['message'])->error()->important(); } return response()->json($response); diff --git a/app/Http/Controllers/Common/Uploads.php b/app/Http/Controllers/Common/Uploads.php index 7b4871693..71eefcd39 100644 --- a/app/Http/Controllers/Common/Uploads.php +++ b/app/Http/Controllers/Common/Uploads.php @@ -182,7 +182,7 @@ class Uploads extends Controller $folders = explode('/', $media->directory); // Check if company can access media - if ($folders[0] != session('company_id')) { + if ($folders[0] != company_id()) { return false; } diff --git a/app/Http/Controllers/Install/Updates.php b/app/Http/Controllers/Install/Updates.php index 7c82f02cb..22b1e70a9 100644 --- a/app/Http/Controllers/Install/Updates.php +++ b/app/Http/Controllers/Install/Updates.php @@ -73,7 +73,7 @@ class Updates extends Controller Cache::forget('updates'); Cache::forget('versions'); - event(new UpdateCacheCleared(session('company_id'))); + event(new UpdateCacheCleared(company_id())); return redirect()->back(); } @@ -274,7 +274,7 @@ class Updates extends Controller set_time_limit(900); // 15 minutes try { - $this->dispatch(new FinishUpdate($request['alias'], $request['version'], $request['installed'], session('company_id'))); + $this->dispatch(new FinishUpdate($request['alias'], $request['version'], $request['installed'], company_id())); $json = [ 'success' => true, diff --git a/app/Http/Controllers/Modals/DocumentItemColumns.php b/app/Http/Controllers/Modals/DocumentItemColumns.php index e28901b70..7281ce258 100644 --- a/app/Http/Controllers/Modals/DocumentItemColumns.php +++ b/app/Http/Controllers/Modals/DocumentItemColumns.php @@ -110,7 +110,7 @@ class DocumentItemColumns extends Controller $company_id = $request->get('company_id'); if (empty($company_id)) { - $company_id = session('company_id'); + $company_id = company_id(); } foreach ($fields as $key => $value) { diff --git a/app/Http/Controllers/Modals/InvoiceTemplates.php b/app/Http/Controllers/Modals/InvoiceTemplates.php index d7e47c138..873b2186d 100644 --- a/app/Http/Controllers/Modals/InvoiceTemplates.php +++ b/app/Http/Controllers/Modals/InvoiceTemplates.php @@ -32,7 +32,7 @@ class InvoiceTemplates extends Controller $company_id = $request->get('company_id'); if (empty($company_id)) { - $company_id = session('company_id'); + $company_id = company_id(); } foreach ($fields as $key => $value) { diff --git a/app/Http/Controllers/Modals/Items.php b/app/Http/Controllers/Modals/Items.php index 23cac8104..4449295b5 100644 --- a/app/Http/Controllers/Modals/Items.php +++ b/app/Http/Controllers/Modals/Items.php @@ -56,7 +56,7 @@ class Items extends Controller { if ($request->get('type', false) == 'inline') { $data = [ - 'company_id' => session('company_id'), + 'company_id' => company_id(), 'name' => '', 'sale_price' => 0, 'purchase_price' => 0, diff --git a/app/Http/Controllers/Modules/Item.php b/app/Http/Controllers/Modules/Item.php index 2aa6ed8bc..9d05d4515 100644 --- a/app/Http/Controllers/Modules/Item.php +++ b/app/Http/Controllers/Modules/Item.php @@ -204,7 +204,7 @@ class Item extends Controller try { $this->dispatch(new CopyFiles($request['alias'], $request['path'])); - event(new \App\Events\Module\Copied($request['alias'], session('company_id'))); + event(new \App\Events\Module\Copied($request['alias'], company_id())); $json = [ 'success' => true, @@ -236,9 +236,9 @@ class Item extends Controller public function install(Request $request) { try { - event(new \App\Events\Module\Installing($request['alias'], session('company_id'))); + event(new \App\Events\Module\Installing($request['alias'], company_id())); - $this->dispatch(new InstallModule($request['alias'], session('company_id'))); + $this->dispatch(new InstallModule($request['alias'], company_id())); $name = module($request['alias'])->getName(); @@ -277,7 +277,7 @@ class Item extends Controller try { $name = module($alias)->getName(); - $this->dispatch(new UninstallModule($alias, session('company_id'))); + $this->dispatch(new UninstallModule($alias, company_id())); $message = trans('modules.uninstalled', ['module' => $name]); @@ -296,7 +296,7 @@ class Item extends Controller try { $name = module($alias)->getName(); - $this->dispatch(new EnableModule($alias, session('company_id'))); + $this->dispatch(new EnableModule($alias, company_id())); $message = trans('modules.enabled', ['module' => $name]); @@ -315,7 +315,7 @@ class Item extends Controller try { $name = module($alias)->getName(); - $this->dispatch(new DisableModule($alias, session('company_id'))); + $this->dispatch(new DisableModule($alias, company_id())); $message = trans('modules.disabled', ['module' => $name]); diff --git a/app/Http/Controllers/Modules/My.php b/app/Http/Controllers/Modules/My.php index ded71f548..4cb009d81 100644 --- a/app/Http/Controllers/Modules/My.php +++ b/app/Http/Controllers/Modules/My.php @@ -19,7 +19,7 @@ class My extends Controller { $purchased = $this->getMyModules(); $modules = $this->getInstalledModules(); - $installed = Module::where('company_id', '=', session('company_id'))->pluck('enabled', 'alias')->toArray(); + $installed = Module::where('company_id', '=', company_id())->pluck('enabled', 'alias')->toArray(); return $this->response('modules.my.index', compact('purchased', 'modules', 'installed')); } diff --git a/app/Http/Controllers/Portal/Invoices.php b/app/Http/Controllers/Portal/Invoices.php index 3104cf9fb..261bb7c7b 100644 --- a/app/Http/Controllers/Portal/Invoices.php +++ b/app/Http/Controllers/Portal/Invoices.php @@ -119,12 +119,12 @@ class Invoices extends Controller $codes = explode('.', $payment_method_key); if (!isset($payment_actions[$codes[0]])) { - $payment_actions[$codes[0]] = URL::signedRoute('signed.invoices.' . $codes[0] . '.show', [$invoice->id, 'company_id' => session('company_id')]); + $payment_actions[$codes[0]] = URL::signedRoute('signed.invoices.' . $codes[0] . '.show', [$invoice->id]); } } - $print_action = URL::signedRoute('signed.invoices.print', [$invoice->id, 'company_id' => session('company_id')]); - $pdf_action = URL::signedRoute('signed.invoices.pdf', [$invoice->id, 'company_id' => session('company_id')]); + $print_action = URL::signedRoute('signed.invoices.print', [$invoice->id]); + $pdf_action = URL::signedRoute('signed.invoices.pdf', [$invoice->id]); event(new \App\Events\Document\DocumentViewed($invoice)); diff --git a/app/Http/Controllers/Purchases/Bills.php b/app/Http/Controllers/Purchases/Bills.php index ac7606e2b..aee66508a 100644 --- a/app/Http/Controllers/Purchases/Bills.php +++ b/app/Http/Controllers/Purchases/Bills.php @@ -128,20 +128,16 @@ class Bills extends Controller */ public function import(ImportRequest $request) { - $response = $this->importExcel(new Import, $request); + $response = $this->importExcel(new Import, $request, trans_choice('general.bills', 2)); if ($response['success']) { $response['redirect'] = route('bills.index'); - $message = trans('messages.success.imported', ['type' => trans_choice('general.bills', 1)]); - - flash($message)->success(); + flash($response['message'])->success(); } else { $response['redirect'] = route('import.create', ['purchases', 'bills']); - $message = $response['message']; - - flash($message)->error()->important(); + flash($response['message'])->error()->important(); } return response()->json($response); diff --git a/app/Http/Controllers/Purchases/Payments.php b/app/Http/Controllers/Purchases/Payments.php index 8d9899ac3..c46880d32 100644 --- a/app/Http/Controllers/Purchases/Payments.php +++ b/app/Http/Controllers/Purchases/Payments.php @@ -134,20 +134,16 @@ class Payments extends Controller */ public function import(ImportRequest $request) { - $response = $this->importExcel(new Import, $request); + $response = $this->importExcel(new Import, $request, trans_choice('general.payments', 2)); if ($response['success']) { $response['redirect'] = route('payments.index'); - $message = trans('messages.success.imported', ['type' => trans_choice('general.payments', 2)]); - - flash($message)->success(); + flash($response['message'])->success(); } else { $response['redirect'] = route('import.create', ['purchases', 'payments']); - $message = $response['message']; - - flash($message)->error()->important(); + flash($response['message'])->error()->important(); } return response()->json($response); diff --git a/app/Http/Controllers/Purchases/Vendors.php b/app/Http/Controllers/Purchases/Vendors.php index 1af867166..9e73c41c3 100644 --- a/app/Http/Controllers/Purchases/Vendors.php +++ b/app/Http/Controllers/Purchases/Vendors.php @@ -161,20 +161,16 @@ class Vendors extends Controller */ public function import(ImportRequest $request) { - $response = $this->importExcel(new Import, $request); + $response = $this->importExcel(new Import, $request, trans_choice('general.vendors', 2)); if ($response['success']) { $response['redirect'] = route('vendors.index'); - $message = trans('messages.success.imported', ['type' => trans_choice('general.vendors', 2)]); - - flash($message)->success(); + flash($response['message'])->success(); } else { $response['redirect'] = route('import.create', ['purchases', 'vendors']); - $message = $response['message']; - - flash($message)->error()->important(); + flash($response['message'])->error()->important(); } return response()->json($response); diff --git a/app/Http/Controllers/Sales/Customers.php b/app/Http/Controllers/Sales/Customers.php index d2a02b9ba..70ee6f8ef 100644 --- a/app/Http/Controllers/Sales/Customers.php +++ b/app/Http/Controllers/Sales/Customers.php @@ -159,20 +159,16 @@ class Customers extends Controller */ public function import(ImportRequest $request) { - $response = $this->importExcel(new Import, $request); + $response = $this->importExcel(new Import, $request, trans_choice('general.customers', 2)); if ($response['success']) { $response['redirect'] = route('customers.index'); - $message = trans('messages.success.imported', ['type' => trans_choice('general.customers', 1)]); - - flash($message)->success(); + flash($response['message'])->success(); } else { $response['redirect'] = route('import.create', ['sales', 'customers']); - $message = $response['message']; - - flash($message)->error()->important(); + flash($response['message'])->error()->important(); } return response()->json($response); diff --git a/app/Http/Controllers/Sales/Invoices.php b/app/Http/Controllers/Sales/Invoices.php index 1742e25cc..52422b2dd 100644 --- a/app/Http/Controllers/Sales/Invoices.php +++ b/app/Http/Controllers/Sales/Invoices.php @@ -127,20 +127,16 @@ class Invoices extends Controller */ public function import(ImportRequest $request) { - $response = $this->importExcel(new Import, $request); + $response = $this->importExcel(new Import, $request, trans_choice('general.invoices', 2)); if ($response['success']) { $response['redirect'] = route('invoices.index'); - $message = trans('messages.success.imported', ['type' => trans_choice('general.invoices', 2)]); - - flash($message)->success(); + flash($response['message'])->success(); } else { $response['redirect'] = route('import.create', ['sales', 'invoices']); - $message = $response['message']; - - flash($message)->error()->important(); + flash($response['message'])->error()->important(); } return response()->json($response); diff --git a/app/Http/Controllers/Sales/Revenues.php b/app/Http/Controllers/Sales/Revenues.php index 842d88d0d..10ec8abd0 100644 --- a/app/Http/Controllers/Sales/Revenues.php +++ b/app/Http/Controllers/Sales/Revenues.php @@ -134,20 +134,16 @@ class Revenues extends Controller */ public function import(ImportRequest $request) { - $response = $this->importExcel(new Import, $request); + $response = $this->importExcel(new Import, $request, trans_choice('general.revenues', 2)); if ($response['success']) { $response['redirect'] = route('revenues.index'); - $message = trans('messages.success.imported', ['type' => trans_choice('general.revenues', 2)]); - - flash($message)->success(); + flash($response['message'])->success(); } else { $response['redirect'] = route('import.create', ['sales', 'revenues']); - $message = $response['message']; - - flash($message)->error()->important(); + flash($response['message'])->error()->important(); } return response()->json($response); diff --git a/app/Http/Controllers/Settings/Categories.php b/app/Http/Controllers/Settings/Categories.php index 6bdc533e5..3bdceaff1 100644 --- a/app/Http/Controllers/Settings/Categories.php +++ b/app/Http/Controllers/Settings/Categories.php @@ -92,20 +92,16 @@ class Categories extends Controller */ public function import(ImportRequest $request) { - $response = $this->importExcel(new Import, $request); + $response = $this->importExcel(new Import, $request, trans_choice('general.categories', 2)); if ($response['success']) { $response['redirect'] = route('categories.index'); - $message = trans('messages.success.imported', ['type' => trans_choice('general.categories', 2)]); - - flash($message)->success(); + flash($response['message'])->success(); } else { $response['redirect'] = route('import.create', ['settings', 'categories']); - $message = $response['message']; - - flash($message)->error()->important(); + flash($response['message'])->error()->important(); } return response()->json($response); diff --git a/app/Http/Controllers/Settings/Modules.php b/app/Http/Controllers/Settings/Modules.php index c6001e951..a9e61fb59 100644 --- a/app/Http/Controllers/Settings/Modules.php +++ b/app/Http/Controllers/Settings/Modules.php @@ -16,7 +16,7 @@ class Modules extends Controller */ public function __construct() { - $alias = request()->segment(1); + $alias = request()->segment(2); // Add CRUD permission check $this->middleware('permission:create-' . $alias . '-settings')->only('create', 'store', 'duplicate', 'import'); diff --git a/app/Http/Controllers/Settings/Settings.php b/app/Http/Controllers/Settings/Settings.php index 90364505e..5fdf4cb09 100644 --- a/app/Http/Controllers/Settings/Settings.php +++ b/app/Http/Controllers/Settings/Settings.php @@ -80,7 +80,7 @@ class Settings extends Controller $company_id = $request->get('company_id'); if (empty($company_id)) { - $company_id = session('company_id'); + $company_id = company_id(); } $company = Company::find($company_id); diff --git a/app/Http/Controllers/Wizard/Companies.php b/app/Http/Controllers/Wizard/Companies.php index e712499a9..d28d95afc 100644 --- a/app/Http/Controllers/Wizard/Companies.php +++ b/app/Http/Controllers/Wizard/Companies.php @@ -31,7 +31,7 @@ class Companies extends Controller */ public function edit() { - $company = Company::find(session('company_id')); + $company = Company::find(company_id()); return view('wizard.companies.edit', compact('company')); } @@ -46,7 +46,7 @@ class Companies extends Controller public function update(Request $request) { // Company - $company = Company::find(session('company_id')); + $company = Company::find(company_id()); $fields = $request->all(); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index cba2297bc..95a62f472 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -36,11 +36,8 @@ class Kernel extends HttpKernel // 'session.auth', 'session.errors', 'csrf', - 'bindings', 'install.redirect', 'header.x', - 'company.settings', - 'company.currencies', 'language', 'firewall.all', ], @@ -55,16 +52,16 @@ class Kernel extends HttpKernel 'auth.disabled', 'throttle:api', 'permission:read-api', - 'api.company', + 'company.identify', 'bindings', - 'company.settings', - 'company.currencies', 'language', 'firewall.all', ], 'common' => [ 'web', + 'company.identify', + 'bindings', 'wizard.redirect', ], @@ -77,6 +74,8 @@ class Kernel extends HttpKernel 'web', 'auth', 'auth.disabled', + 'company.identify', + 'bindings', 'wizard.redirect', 'menu.admin', 'permission:read-admin-panel', @@ -86,6 +85,8 @@ class Kernel extends HttpKernel 'web', 'auth', 'auth.disabled', + 'company.identify', + 'bindings', 'permission:read-admin-panel', ], @@ -93,6 +94,8 @@ class Kernel extends HttpKernel 'web', 'auth', 'auth.disabled', + 'company.identify', + 'bindings', 'menu.portal', 'permission:read-client-portal', ], @@ -105,11 +108,9 @@ class Kernel extends HttpKernel 'csrf', 'signature', 'signed.redirect', - 'company.signed', + 'company.identify', 'bindings', 'header.x', - 'company.settings', - 'company.currencies', 'language', 'firewall.all', ], @@ -141,13 +142,10 @@ class Kernel extends HttpKernel 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, // Akaunting - 'api.company' => \App\Http\Middleware\ApiCompany::class, - 'api.key' => \App\Http\Middleware\CanApiKey::class, + 'api.key' => \App\Http\Middleware\RedirectIfNoApiKey::class, 'auth.disabled' => \App\Http\Middleware\LogoutIfUserDisabled::class, 'auth.redirect' => \App\Http\Middleware\RedirectIfAuthenticated::class, - 'company.currencies' => \App\Http\Middleware\LoadCurrencies::class, - 'company.settings' => \App\Http\Middleware\LoadSettings::class, - 'company.signed' => \App\Http\Middleware\SignedCompany::class, + 'company.identify' => \App\Http\Middleware\IdentifyCompany::class, 'dropzone' => \App\Http\Middleware\Dropzone::class, 'header.x' => \App\Http\Middleware\AddXHeader::class, 'menu.admin' => \App\Http\Middleware\AdminMenu::class, diff --git a/app/Http/Middleware/ApiCompany.php b/app/Http/Middleware/ApiCompany.php deleted file mode 100644 index 1504a48ca..000000000 --- a/app/Http/Middleware/ApiCompany.php +++ /dev/null @@ -1,41 +0,0 @@ -get('company_id'); - - if (empty($company_id)) { - return $next($request); - } - - // Check if user can access company - if (!$this->isUserCompany($company_id)) { - return $next($request); - } - - // Set company id - session(['company_id' => $company_id]); - - // Set the company settings - setting()->setExtraColumns(['company_id' => $company_id]); - setting()->load(true); - - return $next($request); - } -} diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 323a8297f..de3dbd461 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -10,7 +10,7 @@ class Authenticate extends Middleware * Get the path the user should be redirected to when they are not authenticated. * * @param \Illuminate\Http\Request $request - * @return string + * @return string|null */ protected function redirectTo($request) { @@ -18,4 +18,4 @@ class Authenticate extends Middleware return route('login'); } } -} \ No newline at end of file +} diff --git a/app/Http/Middleware/IdentifyCompany.php b/app/Http/Middleware/IdentifyCompany.php new file mode 100644 index 000000000..4ef15be58 --- /dev/null +++ b/app/Http/Middleware/IdentifyCompany.php @@ -0,0 +1,59 @@ +isApi() + ? $this->getCompanyIdFromApi($request) + : $this->getCompanyIdFromWeb($request); + + if (empty($company_id)) { + abort(500, 'Missing company'); + } + + // Check if user can access company + if ($request->isNotSigned($company_id) && $this->isNotUserCompany($company_id)) { + throw new AuthenticationException('Unauthenticated.', $guards); + } + + // Set company as current + company($company_id)->makeCurrent(); + + // Fix routes + app('url')->defaults(['company_id' => $company_id]); + $request->route()->forgetParameter('company_id'); + + return $next($request); + } + + protected function getCompanyIdFromWeb($request) + { + return (int) $request->route('company_id'); + } + + protected function getCompanyIdFromApi($request) + { + $company_id = $request->get('company_id', $request->header('X-Company')); + + return $company_id ?: optional($this->getFirstCompanyOfUser())->id; + } +} diff --git a/app/Http/Middleware/LoadCurrencies.php b/app/Http/Middleware/LoadCurrencies.php deleted file mode 100644 index b71a6b48b..000000000 --- a/app/Http/Middleware/LoadCurrencies.php +++ /dev/null @@ -1,29 +0,0 @@ -guard($guard)->check()) { - $user = user(); - - if ($user->contact) { - return redirect()->route('portal.dashboard'); - } - - return redirect()->route($user->landing_page); + if (!auth()->guard($guard)->check()) { + continue; } + + return redirect(user()->getLandingPageOfUser()); } return $next($request); diff --git a/app/Http/Middleware/CanApiKey.php b/app/Http/Middleware/RedirectIfNoApiKey.php similarity index 54% rename from app/Http/Middleware/CanApiKey.php rename to app/Http/Middleware/RedirectIfNoApiKey.php index 0680c40b4..046cff251 100644 --- a/app/Http/Middleware/CanApiKey.php +++ b/app/Http/Middleware/RedirectIfNoApiKey.php @@ -4,7 +4,7 @@ namespace App\Http\Middleware; use Closure; -class CanApiKey +class RedirectIfNoApiKey { /** * Handle an incoming request. @@ -14,15 +14,15 @@ class CanApiKey * @return mixed */ public function handle($request, Closure $next) - { - if ($request['alias'] != 'core') { - if (setting('apps.api_key')) { - return $next($request); - } else { - redirect('apps/api-key/create')->send(); - } - } else { + { + if ($request->get('alias') == 'core') { return $next($request); } + + if (setting('apps.api_key')) { + return $next($request); + } + + return redirect()->route('apps.api-key.create'); } -} \ No newline at end of file +} diff --git a/app/Http/Middleware/RedirectIfNotInstalled.php b/app/Http/Middleware/RedirectIfNotInstalled.php index f05d1e6ed..44f24dfe5 100644 --- a/app/Http/Middleware/RedirectIfNotInstalled.php +++ b/app/Http/Middleware/RedirectIfNotInstalled.php @@ -3,7 +3,6 @@ namespace App\Http\Middleware; use Closure; -use Illuminate\Support\Str; class RedirectIfNotInstalled { @@ -22,11 +21,11 @@ class RedirectIfNotInstalled } // Already in the installation wizard - if (Str::startsWith($request->getPathInfo(), '/install')) { + if ($request->isInstall()) { return $next($request); } // Not installed, redirect to installation wizard - redirect()->route('install.requirements')->send(); + return redirect()->route('install.requirements'); } } diff --git a/app/Http/Middleware/RedirectIfWizardNotCompleted.php b/app/Http/Middleware/RedirectIfWizardNotCompleted.php index 351842557..f78a3f999 100644 --- a/app/Http/Middleware/RedirectIfWizardNotCompleted.php +++ b/app/Http/Middleware/RedirectIfWizardNotCompleted.php @@ -3,7 +3,6 @@ namespace App\Http\Middleware; use Closure; -use Illuminate\Support\Str; class RedirectIfWizardNotCompleted { @@ -22,11 +21,11 @@ class RedirectIfWizardNotCompleted } // Check url - if (Str::startsWith($request->getPathInfo(), '/wizard') || Str::startsWith($request->getPathInfo(), '/settings')) { + if ($request->isWizard(company_id()) || $request->is(company_id() . '/settings/*')) { return $next($request); } // Redirect to wizard - redirect()->route('wizard.edit')->send(); + return redirect()->route('wizard.edit'); } } diff --git a/app/Http/Middleware/RedirectSignedIfAuthenticated.php b/app/Http/Middleware/RedirectSignedIfAuthenticated.php index 15a80396d..841c879cf 100644 --- a/app/Http/Middleware/RedirectSignedIfAuthenticated.php +++ b/app/Http/Middleware/RedirectSignedIfAuthenticated.php @@ -24,14 +24,14 @@ class RedirectSignedIfAuthenticated $page = 'dashboard'; $params = []; - if ($request->segment(2) == 'invoices') { + if ($request->segment(3) == 'invoices') { $page = 'invoices.show'; - $invoice = Document::find($request->segment(3)); + $invoice = Document::find($request->segment(4)); $params = [$invoice->id]; } - redirect()->route($prefix . $page, $params)->send(); + return redirect()->route($prefix . $page, $params); } } diff --git a/app/Http/Middleware/SignedCompany.php b/app/Http/Middleware/SignedCompany.php deleted file mode 100644 index ef8a08ec8..000000000 --- a/app/Http/Middleware/SignedCompany.php +++ /dev/null @@ -1,33 +0,0 @@ -get('company_id'); - - if (empty($company_id)) { - return $next($request); - } - - // Set company id - session(['company_id' => $company_id]); - - // Set the company settings - setting()->setExtraColumns(['company_id' => $company_id]); - setting()->load(true); - - return $next($request); - } -} diff --git a/app/Http/ViewComposers/DocumentType.php b/app/Http/ViewComposers/DocumentType.php index f5234faf3..75cbf6656 100644 --- a/app/Http/ViewComposers/DocumentType.php +++ b/app/Http/ViewComposers/DocumentType.php @@ -9,7 +9,6 @@ use Illuminate\View\View; class DocumentType { - /** * Bind data to the view. * @@ -18,14 +17,15 @@ class DocumentType */ public function compose(View $view) { - if (!empty(request()->route())) { - $route = request()->route(); + $route = request()->route(); - /** @var Invoices|Bills|PortalInvoices $controller */ - $controller = $route->getController(); - - $view->with(['type' => $controller->type ?? '']); + if (empty($route)) { + return; } - } + /** @var Invoices|Bills|PortalInvoices $controller */ + $controller = $route->getController(); + + $view->with(['type' => $controller->type ?? '']); + } } diff --git a/app/Http/ViewComposers/InvoiceText.php b/app/Http/ViewComposers/InvoiceText.php index e0e2711e4..6826ac531 100644 --- a/app/Http/ViewComposers/InvoiceText.php +++ b/app/Http/ViewComposers/InvoiceText.php @@ -6,7 +6,6 @@ use Illuminate\View\View; class InvoiceText { - /** * Bind data to the view. * @@ -41,5 +40,4 @@ class InvoiceText $view->with(['text_override' => $text_override]); } - -} \ No newline at end of file +} diff --git a/app/Http/ViewComposers/Logo.php b/app/Http/ViewComposers/Logo.php index 53a759131..c72680de7 100644 --- a/app/Http/ViewComposers/Logo.php +++ b/app/Http/ViewComposers/Logo.php @@ -42,7 +42,7 @@ class Logo $image->make($path)->resize($width, $height)->encode(); }); } catch (NotReadableException | \Exception $e) { - Log::info('Company ID: ' . session('company_id') . ' viewcomposer/logo.php exception.'); + Log::info('Company ID: ' . company_id() . ' viewcomposer/logo.php exception.'); Log::info($e->getMessage()); $path = base_path('public/img/company.png'); diff --git a/app/Http/ViewComposers/Notifications.php b/app/Http/ViewComposers/Notifications.php index a80dbc67e..41beec734 100644 --- a/app/Http/ViewComposers/Notifications.php +++ b/app/Http/ViewComposers/Notifications.php @@ -27,6 +27,8 @@ class Notifications return; } + $path = str_replace('{company_id}/', '', $path); + if (!$notifications = $this->getNotifications($path)) { return; } diff --git a/app/Http/ViewComposers/Suggestions.php b/app/Http/ViewComposers/Suggestions.php index 0992fa70f..e8a9c21df 100644 --- a/app/Http/ViewComposers/Suggestions.php +++ b/app/Http/ViewComposers/Suggestions.php @@ -31,6 +31,8 @@ class Suggestions return; } + $path = str_replace('{company_id}/', '', $path); + if (!$suggestions = $this->getSuggestions($path)) { return; } diff --git a/app/Imports/Banking/Transfers.php b/app/Imports/Banking/Transfers.php index 4a6b13b9f..351924cfa 100644 --- a/app/Imports/Banking/Transfers.php +++ b/app/Imports/Banking/Transfers.php @@ -49,7 +49,7 @@ class Transfers extends Import private function getExpenseTransactionId($row) { $expense_transaction = Transaction::create([ - 'company_id' => session('company_id'), + 'company_id' => company_id(), 'type' => 'expense', 'account_id' => $row['from_account_id'], 'paid_at' => $row['transferred_at'], @@ -81,7 +81,7 @@ class Transfers extends Import } $income_transaction = Transaction::create([ - 'company_id' => session('company_id'), + 'company_id' => company_id(), 'type' => 'income', 'account_id' => $row['to_account_id'], 'paid_at' => $row['transferred_at'], diff --git a/app/Jobs/Auth/NotifyUser.php b/app/Jobs/Auth/NotifyUser.php new file mode 100644 index 000000000..c27f2c488 --- /dev/null +++ b/app/Jobs/Auth/NotifyUser.php @@ -0,0 +1,36 @@ +user = $user; + $this->notification = $notification; + + $this->onQueue('jobs'); + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $this->user->notify($this->notification); + } +} diff --git a/app/Jobs/Common/CreateCompany.php b/app/Jobs/Common/CreateCompany.php index d20adf8d2..a16548fb1 100644 --- a/app/Jobs/Common/CreateCompany.php +++ b/app/Jobs/Common/CreateCompany.php @@ -31,14 +31,14 @@ class CreateCompany extends Job */ public function handle() { + $current_company_id = company_id(); + event(new CompanyCreating($this->request)); \DB::transaction(function () { $this->company = Company::create($this->request->all()); - // Clear settings - setting()->setExtraColumns(['company_id' => $this->company->id]); - setting()->forgetAll(); + $this->company->makeCurrent(); $this->callSeeds(); @@ -47,6 +47,10 @@ class CreateCompany extends Job event(new CompanyCreated($this->company)); + if (!empty($current_company_id)) { + company($current_company_id)->makeCurrent(); + } + return $this->company; } @@ -106,6 +110,5 @@ class CreateCompany extends Job } setting()->save(); - setting()->forgetAll(); } } diff --git a/app/Jobs/Common/CreateMediableForExport.php b/app/Jobs/Common/CreateMediableForExport.php new file mode 100644 index 000000000..4f176340a --- /dev/null +++ b/app/Jobs/Common/CreateMediableForExport.php @@ -0,0 +1,55 @@ +user = $user; + $this->file_name = $file_name; + + $this->onQueue('jobs'); + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $source = storage_path('app/uploads/' . $this->file_name); + $destination = storage_path('app/uploads/' . company_id() . '/exports/'); + + // Create exports directory + if (!File::isDirectory($destination)) { + File::makeDirectory($destination); + } + + File::move($source, $destination . $this->file_name); + + // Create the media record + $media = $this->importMedia($this->file_name, 'exports'); + + $this->user->attachMedia($media, 'export'); + + $download_url = route('uploads.download', ['id' => $media->id, 'company_id' => company_id()]); + + $this->user->notify(new ExportCompleted($download_url)); + } +} diff --git a/app/Jobs/Common/DeleteCompany.php b/app/Jobs/Common/DeleteCompany.php index 53bb22d85..bde4121fe 100644 --- a/app/Jobs/Common/DeleteCompany.php +++ b/app/Jobs/Common/DeleteCompany.php @@ -11,17 +11,17 @@ class DeleteCompany extends Job protected $company; - protected $active_company_id; + protected $current_company_id; /** * Create a new job instance. * - * @param $request + * @param $company */ - public function __construct($company, $active_company_id) + public function __construct($company) { $this->company = $company; - $this->active_company_id = $active_company_id; + $this->current_company_id = company_id(); } /** @@ -56,14 +56,14 @@ class DeleteCompany extends Job public function authorize() { // Can't delete active company - if ($this->company->id == $this->active_company_id) { + if ($this->company->id == $this->current_company_id) { $message = trans('companies.error.delete_active'); throw new \Exception($message); } // Check if user can access company - if (!$this->isUserCompany($this->company->id)) { + if ($this->isNotUserCompany($this->company->id)) { $message = trans('companies.error.not_user_company'); throw new \Exception($message); diff --git a/app/Jobs/Common/DeleteDashboard.php b/app/Jobs/Common/DeleteDashboard.php index 69704dabc..95020aab1 100644 --- a/app/Jobs/Common/DeleteDashboard.php +++ b/app/Jobs/Common/DeleteDashboard.php @@ -60,7 +60,7 @@ class DeleteDashboard extends Job } // Check if user can access dashboard - if (!$this->isUserDashboard($this->dashboard->id)) { + if ($this->isNotUserDashboard($this->dashboard->id)) { $message = trans('dashboards.error.not_user_dashboard'); throw new \Exception($message); diff --git a/app/Jobs/Common/UpdateCompany.php b/app/Jobs/Common/UpdateCompany.php index 901c1036c..2ae8d6d03 100644 --- a/app/Jobs/Common/UpdateCompany.php +++ b/app/Jobs/Common/UpdateCompany.php @@ -16,7 +16,7 @@ class UpdateCompany extends Job protected $request; - protected $active_company_id; + protected $current_company_id; /** * Create a new job instance. @@ -24,11 +24,11 @@ class UpdateCompany extends Job * @param $company * @param $request */ - public function __construct($company, $request, $active_company_id) + public function __construct($company, $request) { $this->company = $company; $this->request = $this->getRequestInstance($request); - $this->active_company_id = $active_company_id; + $this->current_company_id = company_id(); } /** @@ -45,10 +45,7 @@ class UpdateCompany extends Job \DB::transaction(function () { $this->company->update($this->request->all()); - // Clear current and load given company settings - setting()->setExtraColumns(['company_id' => $this->company->id]); - setting()->forgetAll(); - setting()->load(true); + $this->company->makeCurrent(); if ($this->request->has('name')) { setting()->set('company.name', $this->request->get('name')); @@ -89,11 +86,14 @@ class UpdateCompany extends Job } setting()->save(); - setting()->forgetAll(); }); event(new CompanyUpdated($this->company, $this->request)); + if (!empty($this->current_company_id)) { + company($this->current_company_id)->makeCurrent(); + } + return $this->company; } @@ -105,14 +105,14 @@ class UpdateCompany extends Job public function authorize() { // Can't disable active company - if (($this->request->get('enabled', 1) == 0) && ($this->company->id == $this->active_company_id)) { + if (($this->request->get('enabled', 1) == 0) && ($this->company->id == $this->current_company_id)) { $message = trans('companies.error.disable_active'); throw new \Exception($message); } // Check if user can access company - if (!$this->isUserCompany($this->company->id)) { + if ($this->isNotUserCompany($this->company->id)) { $message = trans('companies.error.not_user_company'); throw new \Exception($message); diff --git a/app/Jobs/Common/UpdateDashboard.php b/app/Jobs/Common/UpdateDashboard.php index 736c3763a..6fef36ac7 100644 --- a/app/Jobs/Common/UpdateDashboard.php +++ b/app/Jobs/Common/UpdateDashboard.php @@ -77,7 +77,7 @@ class UpdateDashboard extends Job } // Check if user can access dashboard - if (!$this->isUserDashboard($this->dashboard->id)) { + if ($this->isNotUserDashboard($this->dashboard->id)) { $message = trans('dashboards.error.not_user_dashboard'); throw new \Exception($message); diff --git a/app/Listeners/Auth/Login.php b/app/Listeners/Auth/Login.php index 99bdfc01a..7032b0b2d 100644 --- a/app/Listeners/Auth/Login.php +++ b/app/Listeners/Auth/Login.php @@ -15,21 +15,6 @@ class Login */ public function handle(Event $event) { - // Get first company - $company = $event->user->companies()->enabled()->first(); - - // Logout if no company assigned - if (!$company) { - app('App\Http\Controllers\Auth\Login')->logout(); - - flash(trans('auth.error.no_company'))->error()->important(); - - return; - } - - // Set company id - session(['company_id' => $company->id]); - // Save user login time $event->user->last_logged_in_at = Date::now(); diff --git a/app/Listeners/Menu/AddAdminItems.php b/app/Listeners/Menu/AddAdminItems.php index 5d656ba51..b423c1934 100644 --- a/app/Listeners/Menu/AddAdminItems.php +++ b/app/Listeners/Menu/AddAdminItems.php @@ -29,17 +29,17 @@ class AddAdminItems if (session('dashboard_id') != $dashboard->id) { $sub->route('dashboards.switch', $dashboard->name, ['dashboard' => $dashboard->id], $key, $attr); } else { - $sub->url('/', $dashboard->name, $key, $attr); + $sub->url('/' . company_id(), $dashboard->name, $key, $attr); } } }, 10, [ - 'url' => '/', + 'url' => '/' . company_id(), 'title' => trans_choice('general.dashboards', 2), 'icon' => 'fa fa-tachometer-alt', ]); } else { $menu->add([ - 'url' => '/', + 'url' => '/' . company_id(), 'title' => trans_choice('general.dashboards', 1), 'icon' => 'fa fa-tachometer-alt', 'order' => 10, diff --git a/app/Listeners/Module/UpdateExtraModules.php b/app/Listeners/Module/UpdateExtraModules.php index 93c38ad1b..8e4a9fa81 100644 --- a/app/Listeners/Module/UpdateExtraModules.php +++ b/app/Listeners/Module/UpdateExtraModules.php @@ -51,7 +51,7 @@ class UpdateExtraModules continue; } - $company_id = session('company_id'); + $company_id = company_id(); $command = "update {$alias} {$company_id} {$latest_version}"; diff --git a/app/Listeners/Update/V20/Version200.php b/app/Listeners/Update/V20/Version200.php index 11fed20a6..3bed33012 100644 --- a/app/Listeners/Update/V20/Version200.php +++ b/app/Listeners/Update/V20/Version200.php @@ -70,34 +70,25 @@ class Version200 extends Listener protected function updateCompanies() { - $company_id = session('company_id'); + $company_id = company_id(); $companies = Company::cursor(); foreach ($companies as $company) { - session(['company_id' => $company->id]); + $company->makeCurrent(); - $this->updateSettings($company); + $this->updateSettings(); $this->createEmailTemplates($company); $this->createReports($company); } - setting()->forgetAll(); - - session(['company_id' => $company_id]); - - Overrider::load('settings'); + company($company_id)->makeCurrent(); } - public function updateSettings($company) + public function updateSettings() { - // Set the active company settings - setting()->setExtraColumns(['company_id' => $company->id]); - setting()->forgetAll(); - setting()->load(true); - // Override settings config(['app.url' => route('dashboard')]); config(['app.timezone' => setting('general.timezone', 'UTC')]); diff --git a/app/Listeners/Update/V20/Version203.php b/app/Listeners/Update/V20/Version203.php index bb46e49ee..aa4682279 100644 --- a/app/Listeners/Update/V20/Version203.php +++ b/app/Listeners/Update/V20/Version203.php @@ -30,30 +30,21 @@ class Version203 extends Listener protected function updateCompanies() { - $company_id = session('company_id'); + $company_id = company_id(); $companies = Company::cursor(); foreach ($companies as $company) { - session(['company_id' => $company->id]); + $company->makeCurrent(); $this->updateSettings($company); } - setting()->forgetAll(); - - session(['company_id' => $company_id]); - - Overrider::load('settings'); + company($company_id)->makeCurrent(); } public function updateSettings($company) { - // Set the active company settings - setting()->setExtraColumns(['company_id' => $company->id]); - setting()->forgetAll(); - setting()->load(true); - setting()->set(['invoice.payment_terms' => setting('invoice.payment_terms', 0)]); setting()->save(); diff --git a/app/Listeners/Update/V21/Version210.php b/app/Listeners/Update/V21/Version210.php index a66fce154..4008ac825 100644 --- a/app/Listeners/Update/V21/Version210.php +++ b/app/Listeners/Update/V21/Version210.php @@ -1031,24 +1031,20 @@ class Version210 extends Listener protected function updateCompanies() { - $company_id = session('company_id'); + $company_id = company_id(); $companies = Company::cursor(); foreach ($companies as $company) { - session(['company_id' => $company->id]); + $company->makeCurrent(); - $this->updateSettings($company); + $this->updateSettings(); } - setting()->forgetAll(); - - session(['company_id' => $company_id]); - - Overrider::load('settings'); + company($company_id)->makeCurrent(); } - public function updateSettings($company) + public function updateSettings() { $income_category = Category::income()->enabled()->first(); $expense_category = Category::expense()->enabled()->first(); @@ -1057,11 +1053,6 @@ class Version210 extends Listener return; } - // Set the active company settings - setting()->setExtraColumns(['company_id' => $company->id]); - setting()->forgetAll(); - setting()->load(true); - setting()->set(['default.income_category' => setting('default.income_category', $income_category->id)]); setting()->set(['default.expense_category' => setting('default.expense_category', $expense_category->id)]); diff --git a/app/Listeners/Update/V21/Version213.php b/app/Listeners/Update/V21/Version213.php index 003e847d5..67504389b 100644 --- a/app/Listeners/Update/V21/Version213.php +++ b/app/Listeners/Update/V21/Version213.php @@ -30,30 +30,21 @@ class Version213 extends Listener protected function updateCompanies() { - $company_id = session('company_id'); + $company_id = company_id(); $companies = Company::cursor(); foreach ($companies as $company) { - session(['company_id' => $company->id]); + $company->makeCurrent(); - $this->updateSettings($company); + $this->updateSettings(); } - setting()->forgetAll(); - - session(['company_id' => $company_id]); - - Overrider::load('settings'); + company($company_id)->makeCurrent(); } - public function updateSettings($company) + public function updateSettings() { - // Set the active company settings - setting()->setExtraColumns(['company_id' => $company->id]); - setting()->forgetAll(); - setting()->load(true); - $company_logo = setting('company.logo'); if (is_array($company_logo)) { diff --git a/app/Models/Auth/User.php b/app/Models/Auth/User.php index d7d1338af..a06692a74 100644 --- a/app/Models/Auth/User.php +++ b/app/Models/Auth/User.php @@ -5,7 +5,9 @@ namespace App\Models\Auth; use App\Traits\Tenants; use App\Notifications\Auth\Reset; use App\Traits\Media; +use App\Traits\Users; use Date; +use Illuminate\Contracts\Translation\HasLocalePreference; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; @@ -14,9 +16,9 @@ use Kyslik\ColumnSortable\Sortable; use Laratrust\Traits\LaratrustUserTrait; use Lorisleiva\LaravelSearchString\Concerns\SearchString; -class User extends Authenticatable +class User extends Authenticatable implements HasLocalePreference { - use HasFactory, LaratrustUserTrait, Notifiable, SearchString, SoftDeletes, Sortable, Media, Tenants; + use HasFactory, LaratrustUserTrait, Notifiable, SearchString, SoftDeletes, Sortable, Media, Tenants, Users; protected $table = 'users'; @@ -193,20 +195,39 @@ class User extends Authenticatable } /** - * Convert tax to Array. + * Attach company_ids attribute to model. * * @return void */ public function setCompanyIds() { - $this->setAttribute('company_ids', $this->companies->pluck('id')->toArray()); + $company_ids = $this->withoutEvents(function () { + return $this->companies->pluck('id')->toArray(); + }); + + $this->setAttribute('company_ids', $company_ids); } + /** + * Detach company_ids attribute from model. + * + * @return void + */ public function unsetCompanyIds() { $this->offsetUnset('company_ids'); } + /** + * Get the user's preferred locale. + * + * @return string + */ + public function preferredLocale() + { + return $this->locale; + } + /** * Create a new factory instance for the model. * diff --git a/app/Models/Common/Company.php b/app/Models/Common/Company.php index 7143b57f3..5b4c94826 100644 --- a/app/Models/Common/Company.php +++ b/app/Models/Common/Company.php @@ -2,11 +2,16 @@ namespace App\Models\Common; +use App\Events\Common\CompanyForgettingCurrent; +use App\Events\Common\CompanyForgotCurrent; +use App\Events\Common\CompanyMadeCurrent; +use App\Events\Common\CompanyMakingCurrent; use App\Models\Document\Document; use App\Traits\Contacts; use App\Traits\Media; use App\Traits\Tenants; use App\Traits\Transactions; +use App\Utilities\Overrider; use Illuminate\Database\Eloquent\Model as Eloquent; use Illuminate\Database\Eloquent\SoftDeletes; use Kyslik\ColumnSortable\Sortable; @@ -435,4 +440,82 @@ class Company extends Eloquent { return $this->getMedia('company_logo')->last(); } + + public function makeCurrent() + { + if ($this->isCurrent()) { + return $this; + } + + static::forgetCurrent(); + + event(new CompanyMakingCurrent($this)); + + // Bind to container + app()->instance(static::class, $this); + + // Set session for backward compatibility @deprecated + //session(['company_id' => $this->id]); + + // Load settings + setting()->setExtraColumns(['company_id' => $this->id]); + setting()->forgetAll(); + setting()->load(true); + + // Override settings and currencies + Overrider::load('settings'); + Overrider::load('currencies'); + + event(new CompanyMadeCurrent($this)); + + return $this; + } + + public function isCurrent() + { + return optional(static::getCurrent())->id === $this->id; + } + + public function isNotCurrent() + { + return !$this->isCurrent(); + } + + public static function getCurrent() + { + if (!app()->has(static::class)) { + return null; + } + + return app(static::class); + } + + public static function forgetCurrent() + { + $current = static::getCurrent(); + + if (is_null($current)) { + return null; + } + + event(new CompanyForgettingCurrent($current)); + + // Remove from container + app()->forgetInstance(static::class); + + // Unset session for backward compatibility @deprecated + //session()->forget('company_id'); + + // Remove settings + setting()->forgetAll(); + + event(new CompanyForgotCurrent($current)); + + return $current; + } + + public static function hasCurrent() + { + return static::getCurrent() !== null; + } } diff --git a/app/Notifications/Common/ExportCompleted.php b/app/Notifications/Common/ExportCompleted.php new file mode 100644 index 000000000..effeb4a21 --- /dev/null +++ b/app/Notifications/Common/ExportCompleted.php @@ -0,0 +1,52 @@ +download_url = $download_url; + + $this->onQueue('notifications'); + } + + /** + * Get the notification's channels. + * + * @param mixed $notifiable + * @return array|string + */ + public function via($notifiable) + { + return ['mail']; + } + + /** + * Build the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + return (new MailMessage) + ->subject(trans('notifications.export.completed.subject')) + ->line(trans('notifications.export.completed.description')) + ->action(trans('general.download'), $this->download_url); + } +} diff --git a/app/Notifications/Common/ExportFailed.php b/app/Notifications/Common/ExportFailed.php new file mode 100644 index 000000000..b44b366db --- /dev/null +++ b/app/Notifications/Common/ExportFailed.php @@ -0,0 +1,57 @@ +exception = $exception; + + $this->onQueue('notifications'); + } + + /** + * Get the notification's channels. + * + * @param mixed $notifiable + * @return array|string + */ + public function via($notifiable) + { + return ['mail']; + } + + /** + * Build the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + return (new MailMessage) + ->subject(trans('notifications.export.failed.subject')) + ->line(trans('notifications.export.failed.description')) + ->line($this->exception->getMessage()); + } +} diff --git a/app/Notifications/Common/ImportCompleted.php b/app/Notifications/Common/ImportCompleted.php new file mode 100644 index 000000000..512b583b7 --- /dev/null +++ b/app/Notifications/Common/ImportCompleted.php @@ -0,0 +1,48 @@ +onQueue('notifications'); + } + + /** + * Get the notification's channels. + * + * @param mixed $notifiable + * @return array|string + */ + public function via($notifiable) + { + return ['mail']; + } + + /** + * Build the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + $dashboard_url = route('dashboard', ['company_id' => company_id()]); + + return (new MailMessage) + ->subject(trans('notifications.import.completed.subject')) + ->line(trans('notifications.import.completed.description')) + ->action(trans_choice('general.dashboards', 1), $dashboard_url); + } +} diff --git a/app/Notifications/Common/ImportFailed.php b/app/Notifications/Common/ImportFailed.php new file mode 100644 index 000000000..7eda94032 --- /dev/null +++ b/app/Notifications/Common/ImportFailed.php @@ -0,0 +1,62 @@ +errors = $errors; + + $this->onQueue('notifications'); + } + + /** + * Get the notification's channels. + * + * @param mixed $notifiable + * @return array|string + */ + public function via($notifiable) + { + return ['mail']; + } + + /** + * Build the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + $message = (new MailMessage) + ->subject(trans('notifications.import.failed.subject')) + ->line(trans('notifications.import.failed.description')); + + foreach ($this->errors as $error) { + $message->line($error); + } + + return $message; + } +} diff --git a/app/Notifications/Portal/PaymentReceived.php b/app/Notifications/Portal/PaymentReceived.php index 45f8d5c55..daa522502 100644 --- a/app/Notifications/Portal/PaymentReceived.php +++ b/app/Notifications/Portal/PaymentReceived.php @@ -3,6 +3,7 @@ namespace App\Notifications\Portal; use App\Abstracts\Notification; +use App\Models\Common\EmailTemplate; use Illuminate\Support\Facades\URL; class PaymentReceived extends Notification @@ -39,7 +40,7 @@ class PaymentReceived extends Notification $this->invoice = $invoice; $this->transaction = $transaction; - $this->template = $template; + $this->template = EmailTemplate::alias($template)->first(); } /** @@ -105,7 +106,7 @@ class PaymentReceived extends Notification money($this->invoice->amount, $this->invoice->currency_code, true), company_date($this->invoice->due_at), trans('documents.statuses.' . $this->invoice->status), - URL::signedRoute('signed.invoices.show', [$this->invoice->id, 'company_id' => $this->invoice->company_id]), + URL::signedRoute('signed.invoices.show', [$this->invoice->id]), route('invoices.show', $this->invoice->id), route('portal.invoices.show', $this->invoice->id), money($this->transaction->amount, $this->transaction->currency_code, true), diff --git a/app/Notifications/Purchase/Bill.php b/app/Notifications/Purchase/Bill.php index fe99f7423..a13317183 100644 --- a/app/Notifications/Purchase/Bill.php +++ b/app/Notifications/Purchase/Bill.php @@ -3,6 +3,7 @@ namespace App\Notifications\Purchase; use App\Abstracts\Notification; +use App\Models\Common\EmailTemplate; class Bill extends Notification { @@ -31,7 +32,7 @@ class Bill extends Notification parent::__construct(); $this->bill = $bill; - $this->template = $template; + $this->template = EmailTemplate::alias($template)->first(); } /** diff --git a/app/Notifications/Sale/Invoice.php b/app/Notifications/Sale/Invoice.php index 0dfb3e1d2..7c30a3521 100644 --- a/app/Notifications/Sale/Invoice.php +++ b/app/Notifications/Sale/Invoice.php @@ -3,6 +3,7 @@ namespace App\Notifications\Sale; use App\Abstracts\Notification; +use App\Models\Common\EmailTemplate; use Illuminate\Support\Facades\URL; class Invoice extends Notification @@ -32,7 +33,7 @@ class Invoice extends Notification parent::__construct(); $this->invoice = $invoice; - $this->template = $template; + $this->template = EmailTemplate::alias($template)->first(); } /** @@ -95,7 +96,7 @@ class Invoice extends Notification money($this->invoice->amount, $this->invoice->currency_code, true), money($this->invoice->amount_due, $this->invoice->currency_code, true), company_date($this->invoice->due_at), - URL::signedRoute('signed.invoices.show', [$this->invoice->id, 'company_id' => $this->invoice->company_id]), + URL::signedRoute('signed.invoices.show', [$this->invoice->id]), route('invoices.show', $this->invoice->id), route('portal.invoices.show', $this->invoice->id), $this->invoice->contact_name, diff --git a/app/Providers/Macro.php b/app/Providers/Macro.php index 6da03db44..c045b1627 100644 --- a/app/Providers/Macro.php +++ b/app/Providers/Macro.php @@ -9,6 +9,16 @@ use Illuminate\View\Factory as ViewFactory; class Macro extends ServiceProvider { + /** + * Register any application services. + * + * @return void + */ + public function register() + { + // + } + /** * Bootstrap any application services. * @@ -24,6 +34,59 @@ class Macro extends ServiceProvider return !$this->isApi(); }); + Request::macro('isAuth', function () { + return $this->is('auth/*'); + }); + + Request::macro('isNotAuth', function () { + return !$this->isAuth(); + }); + + Request::macro('isInstall', function () { + return $this->is('install/*'); + }); + + Request::macro('isNotInstall', function () { + return !$this->isInstall(); + }); + + Request::macro('isSigned', function ($company_id) { + return $this->is($company_id . '/signed/*'); + }); + + Request::macro('isNotSigned', function ($company_id) { + return !$this->isSigned($company_id); + }); + + Request::macro('isPortal', function ($company_id) { + return $this->is($company_id . '/portal') || $this->is($company_id . '/portal/*'); + }); + + Request::macro('isNotPortal', function ($company_id) { + return !$this->isPortal($company_id); + }); + + Request::macro('isWizard', function ($company_id) { + return $this->is($company_id . '/wizard') || $this->is($company_id . '/wizard/*'); + }); + + Request::macro('isNotWizard', function ($company_id) { + return !$this->isWizard($company_id); + }); + + Request::macro('isAdmin', function ($company_id) { + return $this->isNotApi() + && $this->isNotAuth() + && $this->isNotInstall() + && $this->isNotSigned($company_id) + && $this->isNotPortal($company_id) + && $this->isNotWizard($company_id); + }); + + Request::macro('isNotAdmin', function ($company_id) { + return !$this->isAdmin($company_id); + }); + Str::macro('filename', function ($string, $separator = '-') { // Replace @ with the word 'at' $string = str_replace('@', $separator.'at'.$separator, $string); @@ -47,14 +110,4 @@ class Macro extends ServiceProvider return false; }); } - - /** - * Register any application services. - * - * @return void - */ - public function register() - { - // - } } diff --git a/app/Providers/Observer.php b/app/Providers/Observer.php index 655ef52ee..27af5bdce 100644 --- a/app/Providers/Observer.php +++ b/app/Providers/Observer.php @@ -8,17 +8,7 @@ use Illuminate\Support\ServiceProvider as Provider; class Observer extends Provider { /** - * Register bindings in the container. - * - * @return void - */ - public function boot() - { - Transaction::observe('App\Observers\Transaction'); - } - - /** - * Register the service provider. + * Register any application services. * * @return void */ @@ -26,4 +16,14 @@ class Observer extends Provider { // } + + /** + * Bootstrap any application services. + * + * @return void + */ + public function boot() + { + Transaction::observe('App\Observers\Transaction'); + } } diff --git a/app/Providers/Queue.php b/app/Providers/Queue.php new file mode 100644 index 000000000..87b02ab5c --- /dev/null +++ b/app/Providers/Queue.php @@ -0,0 +1,102 @@ +createPayloadUsing(function ($connection, $queue, $payload) { + $company_id = company_id(); + + if (empty($company_id)) { + return []; + } + + return ['company_id' => $company_id]; + }); + + app('events')->listen(JobProcessing::class, function ($event) { + $payload = $event->job->payload(); + + if (!array_key_exists('company_id', $payload)) { + return; + } + + $company = company($payload['company_id']); + + if (empty($company)) { + $event->job->delete(); + } + + $company->makeCurrent(); + }); + + app('events')->listen(JobFailed::class, function ($event) { + if (!$event->exception instanceof \Maatwebsite\Excel\Validators\ValidationException) { + return; + } + + $body = $event->job->getRawBody(); + if (empty($body) || !is_string($body)) { + return; + } + + $payload = json_decode($body); + if (empty($payload) || empty($payload->data) || empty($payload->data->command)) { + return; + } + + $excel_job = unserialize($payload->data->command); + if (!$excel_job instanceof \Maatwebsite\Excel\Jobs\ReadChunk) { + return; + } + + $ref = new \ReflectionProperty($excel_job, 'import'); + $ref->setAccessible(true); + + // Get import class + $class = $ref->getValue($excel_job); + + if (!$class instanceof \App\Abstracts\Import) { + return; + } + + $errors = []; + + foreach ($event->exception->failures() as $failure) { + $message = trans('messages.error.import_column', [ + 'message' => collect($failure->errors())->first(), + 'column' => $failure->attribute(), + 'line' => $failure->row(), + ]); + + $errors[] = $message; + } + + if (!empty($errors)) { + $class->user->notify(new ImportFailed($errors)); + } + }); + } +} diff --git a/app/Providers/Route.php b/app/Providers/Route.php index 20dcefa37..f1234a578 100644 --- a/app/Providers/Route.php +++ b/app/Providers/Route.php @@ -92,7 +92,8 @@ class Route extends Provider */ protected function mapCommonRoutes() { - Facade::middleware('common') + Facade::prefix('{company_id}') + ->middleware('common') ->namespace($this->namespace) ->group(base_path('routes/common.php')); } @@ -120,7 +121,7 @@ class Route extends Provider */ protected function mapWizardRoutes() { - Facade::prefix('wizard') + Facade::prefix('{company_id}/wizard') ->middleware('wizard') ->namespace($this->namespace) ->group(base_path('routes/wizard.php')); @@ -135,7 +136,8 @@ class Route extends Provider */ protected function mapAdminRoutes() { - Facade::middleware('admin') + Facade::prefix('{company_id}') + ->middleware('admin') ->namespace($this->namespace) ->group(base_path('routes/admin.php')); } @@ -149,7 +151,7 @@ class Route extends Provider */ protected function mapPortalRoutes() { - Facade::prefix('portal') + Facade::prefix('{company_id}/portal') ->middleware('portal') ->namespace($this->namespace) ->group(base_path('routes/portal.php')); @@ -164,7 +166,7 @@ class Route extends Provider */ protected function mapSignedRoutes() { - Facade::prefix('signed') + Facade::prefix('{company_id}/signed') ->middleware('signed') ->namespace($this->namespace) ->group(base_path('routes/signed.php')); diff --git a/app/Scopes/Company.php b/app/Scopes/Company.php index 9105f0554..9fc85ea79 100644 --- a/app/Scopes/Company.php +++ b/app/Scopes/Company.php @@ -42,6 +42,6 @@ class Company implements Scope } // Apply company scope - $builder->where($table . '.company_id', '=', session('company_id')); + $builder->where($table . '.company_id', '=', company_id()); } } diff --git a/app/Traits/Import.php b/app/Traits/Import.php index d4a59d0f7..e851add97 100644 --- a/app/Traits/Import.php +++ b/app/Traits/Import.php @@ -116,9 +116,9 @@ trait Import public function getAccountIdFromCurrency($row) { return Account::firstOrCreate([ + 'company_id' => company_id(), 'currency_code' => $row['currency_code'], ], [ - 'company_id' => session('company_id'), 'name' => !empty($row['account_name']) ? $row['account_name'] : $row['currency_code'], 'number' => !empty($row['account_number']) ? $row['account_number'] : rand(1, 10000), 'opening_balance' => !empty($row['opening_balance']) ? $row['opening_balance'] : 0, @@ -129,9 +129,9 @@ trait Import public function getAccountIdFromName($row) { return Account::firstOrCreate([ + 'company_id' => company_id(), 'name' => $row['account_name'], ], [ - 'company_id' => session('company_id'), 'number' => !empty($row['account_number']) ? $row['account_number'] : rand(1, 10000), 'currency_code' => !empty($row['currency_code']) ? $row['currency_code'] : setting('default.currency'), 'opening_balance' => !empty($row['opening_balance']) ? $row['opening_balance'] : 0, @@ -142,9 +142,9 @@ trait Import public function getAccountIdFromNumber($row) { return Account::firstOrCreate([ + 'company_id' => company_id(), 'number' => $row['account_number'], ], [ - 'company_id' => session('company_id'), 'name' => !empty($row['account_name']) ? $row['account_name'] : $row['account_number'], 'currency_code' => !empty($row['currency_code']) ? $row['currency_code'] : setting('default.currency'), 'opening_balance' => !empty($row['opening_balance']) ? $row['opening_balance'] : 0, @@ -155,9 +155,9 @@ trait Import public function getCategoryIdFromName($row, $type) { return Category::firstOrCreate([ + 'company_id' => company_id(), 'name' => $row['category_name'], ], [ - 'company_id' => session('company_id'), 'type' => $type, 'color' => !empty($row['category_color']) ? $row['category_color'] : '#' . dechex(rand(0x000000, 0xFFFFFF)), 'enabled' => 1, @@ -167,9 +167,9 @@ trait Import public function getContactIdFromEmail($row, $type) { return Contact::firstOrCreate([ + 'company_id' => company_id(), 'email' => $row['contact_email'], ], [ - 'company_id' => session('company_id'), 'type' => $type, 'name' => !empty($row['contact_name']) ? $row['contact_name'] : $row['contact_email'], 'currency_code' => !empty($row['contact_currency']) ? $row['contact_currency'] : setting('default.currency'), @@ -180,9 +180,9 @@ trait Import public function getContactIdFromName($row, $type) { return Contact::firstOrCreate([ + 'company_id' => company_id(), 'name' => $row['contact_name'], ], [ - 'company_id' => session('company_id'), 'type' => $type, 'currency_code' => !empty($row['contact_currency']) ? $row['contact_currency'] : setting('default.currency'), 'enabled' => 1, @@ -192,9 +192,9 @@ trait Import public function getItemIdFromName($row) { return Item::firstOrCreate([ + 'company_id' => company_id(), 'name' => $row['item_name'], ], [ - 'company_id' => session('company_id'), 'sale_price' => !empty($row['sale_price']) ? $row['sale_price'] : (!empty($row['price']) ? $row['price'] : 0), 'purchase_price' => !empty($row['purchase_price']) ? $row['purchase_price'] : (!empty($row['price']) ? $row['price'] : 0), 'enabled' => 1, @@ -204,9 +204,9 @@ trait Import public function getTaxIdFromRate($row, $type = 'normal') { return Tax::firstOrCreate([ + 'company_id' => company_id(), 'rate' => $row['tax_rate'], ], [ - 'company_id' => session('company_id'), 'type' => $type, 'name' => !empty($row['tax_name']) ? $row['tax_name'] : $row['tax_rate'], 'enabled' => 1, diff --git a/app/Traits/Jobs.php b/app/Traits/Jobs.php index 731467584..783414a55 100644 --- a/app/Traits/Jobs.php +++ b/app/Traits/Jobs.php @@ -3,6 +3,7 @@ namespace App\Traits; use Exception; +use Illuminate\Bus\Dispatcher; use Throwable; trait Jobs @@ -17,7 +18,32 @@ trait Jobs { $function = $this->getDispatchFunction(); - return $function($job); + return $this->$function($job); + } + + /** + * Dispatch a job to its appropriate handler. + * + * @param mixed $command + * @return mixed + */ + public function dispatchQueue($job) + { + return app(Dispatcher::class)->dispatch($job); + } + + /** + * Dispatch a command to its appropriate handler in the current process. + * + * Queuable jobs will be dispatched to the "sync" queue. + * + * @param mixed $command + * @param mixed $handler + * @return mixed + */ + public function dispatchSync($job, $handler = null) + { + return app(Dispatcher::class)->dispatchSync($job, $handler); } /** @@ -26,12 +52,12 @@ trait Jobs * @param mixed $job * @param mixed $handler * @return mixed + * + * @deprecated Will be removed in a future Laravel version. */ public function dispatchNow($job, $handler = null) { - $result = dispatch_now($job, $handler); - - return $result; + return app(Dispatcher::class)->dispatchNow($job, $handler); } /** @@ -65,8 +91,6 @@ trait Jobs public function getDispatchFunction() { - $config = config('queue.default'); - - return ($config == 'sync') ? 'dispatch_now' : 'dispatch'; + return should_queue() ? 'dispatchQueue' : 'dispatchSync'; } } diff --git a/app/Traits/Modules.php b/app/Traits/Modules.php index 5a86f0ed5..f6fdfbceb 100644 --- a/app/Traits/Modules.php +++ b/app/Traits/Modules.php @@ -164,7 +164,7 @@ trait Modules public function getInstalledModules() { - $key = 'apps.installed.' . session('company_id'); + $key = 'apps.installed.' . company_id(); if ($installed = Cache::get($key)) { return $installed; diff --git a/app/Traits/Scopes.php b/app/Traits/Scopes.php index b679bc6a9..67bbb0d9e 100644 --- a/app/Traits/Scopes.php +++ b/app/Traits/Scopes.php @@ -83,7 +83,7 @@ trait Scopes return $type; } - $type = $request->get('type') ?: Str::singular((string) $request->segment(2)); + $type = $request->get('type') ?: Str::singular((string) $request->segment(3)); if ($type == 'revenue') { $type = 'income'; diff --git a/app/Traits/SiteApi.php b/app/Traits/SiteApi.php index 20e29a789..5d73e0f04 100644 --- a/app/Traits/SiteApi.php +++ b/app/Traits/SiteApi.php @@ -19,7 +19,7 @@ trait SiteApi $headers['headers'] = [ 'Authorization' => 'Bearer ' . setting('apps.api_key'), 'Accept' => 'application/json', - 'Referer' => app()->runningInConsole() ? config('app.url') : route('dashboard'), + 'Referer' => app()->runningInConsole() ? config('app.url') : url('/'), 'Akaunting' => version('short'), 'Language' => language()->getShortCode(), 'Information' => json_encode(Info::all()), diff --git a/app/Traits/Uploads.php b/app/Traits/Uploads.php index 1576ab5d1..aafd2dba4 100644 --- a/app/Traits/Uploads.php +++ b/app/Traits/Uploads.php @@ -2,8 +2,8 @@ namespace App\Traits; -use MediaUploader; use App\Models\Common\Media as MediaModel; +use MediaUploader; trait Uploads { @@ -16,7 +16,7 @@ trait Uploads } if (!$company_id) { - $company_id = session('company_id'); + $company_id = company_id(); } $file_name = $file->getClientOriginalName(); @@ -39,7 +39,7 @@ trait Uploads } if (!$company_id) { - $company_id = session('company_id'); + $company_id = company_id(); } $path = $company_id . '/' . $folder; @@ -56,7 +56,7 @@ trait Uploads } if (!$company_id) { - $company_id = session('company_id'); + $company_id = company_id(); } $path = $company_id . '/' . $folder . '/' . basename($file); diff --git a/app/Traits/Users.php b/app/Traits/Users.php index fedc0903c..11fd52578 100644 --- a/app/Traits/Users.php +++ b/app/Traits/Users.php @@ -29,11 +29,18 @@ trait Users return false; } - $company = $user->companies()->where('id', $id)->first(); + $company = $user->withoutEvents(function () use ($user, $id) { + return $user->companies()->where('id', $id)->first(); + }); return !empty($company); } + public function isNotUserCompany($id) + { + return !$this->isUserCompany($id); + } + /** * Check user dashboard assignment * @@ -49,8 +56,54 @@ trait Users return app()->runningInConsole() ? true : false; } - $dashboard = $user->dashboards()->where('id', $id)->first(); + $dashboard = $user->withoutEvents(function () use ($user, $id) { + return $user->dashboards()->where('id', $id)->first(); + }); return !empty($dashboard); } + + public function isNotUserDashboard($id) + { + return !$this->isUserDashboard($id); + } + + /** + * Get the fist company of active user + * + * @return null|\App\Models\Common\Company + */ + public function getFirstCompanyOfUser() + { + $user = user(); + + if (empty($user)) { + return null; + } + + $company = $user->withoutEvents(function () use ($user) { + return $user->companies()->enabled()->first(); + }); + + if (empty($company)) { + return null; + } + + return $company; + } + + public function getLandingPageOfUser() + { + $user = user(); + + if (empty($user)) { + return route('login'); + } + + $route_name = $user->contact ? 'portal.dashboard' : $user->landing_page; + + $company_id = company_id() ?: optional($this->getFirstCompanyOfUser())->id; + + return route($route_name, ['company_id' => $company_id]); + } } diff --git a/app/Utilities/Modules.php b/app/Utilities/Modules.php index 69a8f68c9..e55b9c6e3 100644 --- a/app/Utilities/Modules.php +++ b/app/Utilities/Modules.php @@ -72,7 +72,7 @@ class Modules public static function getPaymentMethodsCacheKey($type) { - return 'payment_methods.' . session('company_id') . '.' . $type; + return 'payment_methods.' . company_id() . '.' . $type; } protected static function sortPaymentMethods(&$list) diff --git a/app/Utilities/Overrider.php b/app/Utilities/Overrider.php index 73fbec4dd..6622cdce7 100644 --- a/app/Utilities/Overrider.php +++ b/app/Utilities/Overrider.php @@ -11,7 +11,7 @@ class Overrider public static function load($type) { // Overrides apply per company - $company_id = session('company_id'); + $company_id = company_id(); if (empty($company_id)) { return; } @@ -25,11 +25,6 @@ class Overrider protected static function loadSettings() { - // Set the active company settings - setting()->setExtraColumns(['company_id' => static::$company_id]); - setting()->forgetAll(); - setting()->load(true); - // Timezone config(['app.timezone' => setting('localisation.timezone', 'UTC')]); date_default_timezone_set(config('app.timezone')); @@ -56,7 +51,7 @@ class Overrider } // Set app url dynamically - config(['app.url' => route('dashboard')]); + config(['app.url' => url('/')]); } protected static function loadCurrencies() diff --git a/app/Utilities/Widgets.php b/app/Utilities/Widgets.php index 60f5d709b..4cc5fcfb9 100644 --- a/app/Utilities/Widgets.php +++ b/app/Utilities/Widgets.php @@ -68,7 +68,7 @@ class Widgets $model = new Widget(); $model->id = 0; - $model->company_id = session('company_id'); + $model->company_id = company_id(); $model->dashboard_id = session('dashboard_id'); $model->class = $class_name; $model->name = $class->getDefaultName(); diff --git a/app/Utilities/helpers.php b/app/Utilities/helpers.php index 92e750aa7..6a14ac412 100644 --- a/app/Utilities/helpers.php +++ b/app/Utilities/helpers.php @@ -1,5 +1,6 @@ id; + } +} + +if (!function_exists('should_queue')) { + /** + * Check if queue is enabled. + * + * @return bool + */ + function should_queue() { + return config('queue.default') != 'sync'; + } +} + if (!function_exists('cache_prefix')) { /** * Cache system added company_id prefix. @@ -74,6 +122,6 @@ if (!function_exists('cache_prefix')) { * @return string */ function cache_prefix() { - return session('company_id') . '_'; + return company_id() . '_'; } } diff --git a/app/View/Components/Documents/Form/Company.php b/app/View/Components/Documents/Form/Company.php index 1c0e1aa10..9dce2ab4f 100644 --- a/app/View/Components/Documents/Form/Company.php +++ b/app/View/Components/Documents/Form/Company.php @@ -14,7 +14,7 @@ class Company extends Component */ public function render() { - $company = Model::find(session('company_id')); + $company = Model::find(company_id()); $inputNameType = config('type.' . $this->type . '.route.parameter'); diff --git a/composer.json b/composer.json index 429f753e2..6fa3ba084 100644 --- a/composer.json +++ b/composer.json @@ -88,6 +88,7 @@ "Database\\Seeds\\": "database/seeds/", "Illuminate\\Translation\\": "overrides/Illuminate/Translation/", "Illuminate\\View\\Concerns\\": "overrides/Illuminate/View/Concerns/", + "Maatwebsite\\Excel\\": "overrides/maatwebsite/excel/", "Modules\\": "modules/", "Symfony\\Component\\Process\\": "overrides/symfony/process/" }, @@ -98,6 +99,7 @@ "vendor/akaunting/laravel-module/src/Commands/InstallCommand.php", "vendor/laravel/framework/src/Illuminate/Translation/MessageSelector.php", "vendor/laravel/framework/src/Illuminate/View/Concerns/ManagesStacks.php", + "vendor/maatwebsite/excel/src/QueuedWriter.php", "vendor/symfony/process/PhpExecutableFinder.php" ], "files": [ diff --git a/composer.lock b/composer.lock index 12f7a4f39..f9dd6458a 100644 --- a/composer.lock +++ b/composer.lock @@ -674,16 +674,16 @@ }, { "name": "barryvdh/laravel-debugbar", - "version": "v3.5.2", + "version": "v3.5.5", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "cae0a8d1cb89b0f0522f65e60465e16d738e069b" + "reference": "6420113d90bb746423fa70b9940e9e7c26ebc121" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/cae0a8d1cb89b0f0522f65e60465e16d738e069b", - "reference": "cae0a8d1cb89b0f0522f65e60465e16d738e069b", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/6420113d90bb746423fa70b9940e9e7c26ebc121", + "reference": "6420113d90bb746423fa70b9940e9e7c26ebc121", "shasum": "" }, "require": { @@ -743,7 +743,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.5.2" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.5.5" }, "funding": [ { @@ -751,7 +751,7 @@ "type": "github" } ], - "time": "2021-01-06T14:21:44+00:00" + "time": "2021-04-07T11:19:20+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -1595,76 +1595,6 @@ ], "time": "2020-09-10T14:20:26+00:00" }, - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.1", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "fe390591e0241955f22eb9ba327d137e501c771c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/fe390591e0241955f22eb9ba327d137e501c771c", - "reference": "fe390591e0241955f22eb9ba327d137e501c771c", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "phpcompatibility/php-compatibility": "^9.0", - "sensiolabs/security-checker": "^4.1.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2020-12-07T18:04:37+00:00" - }, { "name": "dingo/api", "version": "v3.0.5", @@ -1811,43 +1741,6 @@ }, "time": "2020-09-13T12:32:17+00:00" }, - { - "name": "dnoegel/php-xdg-base-dir", - "version": "v0.1.1", - "source": { - "type": "git", - "url": "https://github.com/dnoegel/php-xdg-base-dir.git", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "XdgBaseDir\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "implementation of xdg base directory specification for php", - "support": { - "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", - "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" - }, - "time": "2019-12-04T15:06:13+00:00" - }, { "name": "doctrine/annotations", "version": "1.12.1", @@ -2670,16 +2563,16 @@ }, { "name": "enlightn/enlightn", - "version": "v1.21.0", + "version": "v1.22.0", "source": { "type": "git", "url": "https://github.com/enlightn/enlightn.git", - "reference": "959c9dc6d4dc95c6182569e96fc6e3c48ac538fc" + "reference": "0958011684210838bb50646bedd6a33f73994e7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/enlightn/enlightn/zipball/959c9dc6d4dc95c6182569e96fc6e3c48ac538fc", - "reference": "959c9dc6d4dc95c6182569e96fc6e3c48ac538fc", + "url": "https://api.github.com/repos/enlightn/enlightn/zipball/0958011684210838bb50646bedd6a33f73994e7c", + "reference": "0958011684210838bb50646bedd6a33f73994e7c", "shasum": "" }, "require": { @@ -2747,9 +2640,9 @@ "support": { "docs": "https://www.laravel-enlightn.com/docs/", "issues": "https://github.com/enlightn/enlightn/issues", - "source": "https://github.com/enlightn/enlightn/tree/v1.21.0" + "source": "https://github.com/enlightn/enlightn/tree/v1.22.0" }, - "time": "2021-03-23T07:38:44+00:00" + "time": "2021-04-15T15:05:41+00:00" }, { "name": "enlightn/security-checker", @@ -3007,16 +2900,16 @@ }, { "name": "genealabs/laravel-model-caching", - "version": "0.11.2", + "version": "0.11.3", "source": { "type": "git", "url": "https://github.com/GeneaLabs/laravel-model-caching.git", - "reference": "7019f1e1a9ab070ebdee5cbb563449f05e161a7e" + "reference": "b8ffa347f6269f32dbd4b74ed7cb0bcd700e0793" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GeneaLabs/laravel-model-caching/zipball/7019f1e1a9ab070ebdee5cbb563449f05e161a7e", - "reference": "7019f1e1a9ab070ebdee5cbb563449f05e161a7e", + "url": "https://api.github.com/repos/GeneaLabs/laravel-model-caching/zipball/b8ffa347f6269f32dbd4b74ed7cb0bcd700e0793", + "reference": "b8ffa347f6269f32dbd4b74ed7cb0bcd700e0793", "shasum": "" }, "require": { @@ -3028,8 +2921,7 @@ "illuminate/database": "^8.0", "illuminate/http": "^8.0", "illuminate/support": "^8.0", - "php": "^7.3|^8.0", - "slevomat/coding-standard": "^6.4" + "php": "^7.3|^8.0" }, "require-dev": { "doctrine/dbal": "^2.10", @@ -3041,6 +2933,7 @@ "php-coveralls/php-coveralls": "^2.2", "phpmd/phpmd": "^2.7", "phpunit/phpunit": "^9.0", + "slevomat/coding-standard": "^6.4", "squizlabs/php_codesniffer": "^3.4", "symfony/thanks": "^1.2" }, @@ -3070,9 +2963,9 @@ "description": "Automatic caching for Eloquent models.", "support": { "issues": "https://github.com/GeneaLabs/laravel-model-caching/issues", - "source": "https://github.com/GeneaLabs/laravel-model-caching/tree/0.11.2" + "source": "https://github.com/GeneaLabs/laravel-model-caching/tree/0.11.3" }, - "time": "2021-03-16T15:04:15+00:00" + "time": "2021-04-13T14:47:33+00:00" }, { "name": "genealabs/laravel-pivot-events", @@ -4894,16 +4787,16 @@ }, { "name": "laravel/framework", - "version": "v8.35.1", + "version": "v8.37.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "d118c0df39e7524131176aaf76493eae63a8a602" + "reference": "cf4082973abc796ec285190f0603380021f6d26f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/d118c0df39e7524131176aaf76493eae63a8a602", - "reference": "d118c0df39e7524131176aaf76493eae63a8a602", + "url": "https://api.github.com/repos/laravel/framework/zipball/cf4082973abc796ec285190f0603380021f6d26f", + "reference": "cf4082973abc796ec285190f0603380021f6d26f", "shasum": "" }, "require": { @@ -5058,7 +4951,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-03-30T21:34:17+00:00" + "time": "2021-04-13T13:49:49+00:00" }, { "name": "laravel/slack-notification-channel", @@ -5898,16 +5791,16 @@ }, { "name": "maatwebsite/excel", - "version": "3.1.29", + "version": "3.1.30", "source": { "type": "git", "url": "https://github.com/Maatwebsite/Laravel-Excel.git", - "reference": "1e567e6e19a04fd65b5876d5bc92f4015f09fab4" + "reference": "aa5d2e4d25c5c8218ea0a15103da95f5f8728953" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Maatwebsite/Laravel-Excel/zipball/1e567e6e19a04fd65b5876d5bc92f4015f09fab4", - "reference": "1e567e6e19a04fd65b5876d5bc92f4015f09fab4", + "url": "https://api.github.com/repos/Maatwebsite/Laravel-Excel/zipball/aa5d2e4d25c5c8218ea0a15103da95f5f8728953", + "reference": "aa5d2e4d25c5c8218ea0a15103da95f5f8728953", "shasum": "" }, "require": { @@ -5960,7 +5853,7 @@ ], "support": { "issues": "https://github.com/Maatwebsite/Laravel-Excel/issues", - "source": "https://github.com/Maatwebsite/Laravel-Excel/tree/3.1.29" + "source": "https://github.com/Maatwebsite/Laravel-Excel/tree/3.1.30" }, "funding": [ { @@ -5972,7 +5865,7 @@ "type": "github" } ], - "time": "2021-03-16T11:56:39+00:00" + "time": "2021-04-06T17:17:02+00:00" }, { "name": "maennchen/zipstream-php", @@ -6925,16 +6818,16 @@ }, { "name": "nunomaduro/larastan", - "version": "v0.7.1", + "version": "v0.7.3", "source": { "type": "git", "url": "https://github.com/nunomaduro/larastan.git", - "reference": "bbbe09ec16a565e6423878bd17fc4355fa0d0a4c" + "reference": "9c515d46851dca5a99fc82c0a69392c362b7affd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/bbbe09ec16a565e6423878bd17fc4355fa0d0a4c", - "reference": "bbbe09ec16a565e6423878bd17fc4355fa0d0a4c", + "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/9c515d46851dca5a99fc82c0a69392c362b7affd", + "reference": "9c515d46851dca5a99fc82c0a69392c362b7affd", "shasum": "" }, "require": { @@ -6949,7 +6842,7 @@ "illuminate/support": "^6.0 || ^7.0 || ^8.0 || ^9.0", "mockery/mockery": "^0.9 || ^1.0", "php": "^7.2 || ^8.0", - "phpstan/phpstan": "0.12.81", + "phpstan/phpstan": "^0.12.83", "symfony/process": "^4.3 || ^5.0" }, "require-dev": { @@ -6998,7 +6891,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/larastan/issues", - "source": "https://github.com/nunomaduro/larastan/tree/v0.7.1" + "source": "https://github.com/nunomaduro/larastan/tree/v0.7.3" }, "funding": [ { @@ -7018,7 +6911,7 @@ "type": "patreon" } ], - "time": "2021-03-19T09:41:48+00:00" + "time": "2021-04-12T11:01:46+00:00" }, { "name": "omnipay/common", @@ -7108,16 +7001,16 @@ }, { "name": "opis/closure", - "version": "3.6.1", + "version": "3.6.2", "source": { "type": "git", "url": "https://github.com/opis/closure.git", - "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5" + "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5", - "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5", + "url": "https://api.github.com/repos/opis/closure/zipball/06e2ebd25f2869e54a306dda991f7db58066f7f6", + "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6", "shasum": "" }, "require": { @@ -7167,9 +7060,9 @@ ], "support": { "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.1" + "source": "https://github.com/opis/closure/tree/3.6.2" }, - "time": "2020-11-07T02:01:34+00:00" + "time": "2021-04-09T13:42:10+00:00" }, { "name": "paragonie/random_compat", @@ -8012,71 +7905,18 @@ ], "time": "2020-07-20T17:29:33+00:00" }, - { - "name": "phpstan/phpdoc-parser", - "version": "0.4.9", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", - "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "consistence/coding-standard": "^3.5", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "phing/phing": "^2.16.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.26", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^6.3", - "slevomat/coding-standard": "^4.7.2", - "symfony/process": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/master" - }, - "time": "2020-08-03T20:32:43+00:00" - }, { "name": "phpstan/phpstan", - "version": "0.12.81", + "version": "0.12.83", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "0dd5b0ebeff568f7000022ea5f04aa86ad3124b8" + "reference": "4a967cec6efb46b500dd6d768657336a3ffe699f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0dd5b0ebeff568f7000022ea5f04aa86ad3124b8", - "reference": "0dd5b0ebeff568f7000022ea5f04aa86ad3124b8", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/4a967cec6efb46b500dd6d768657336a3ffe699f", + "reference": "4a967cec6efb46b500dd6d768657336a3ffe699f", "shasum": "" }, "require": { @@ -8107,7 +7947,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/0.12.81" + "source": "https://github.com/phpstan/phpstan/tree/0.12.83" }, "funding": [ { @@ -8123,7 +7963,7 @@ "type": "tidelift" } ], - "time": "2021-03-08T22:03:02+00:00" + "time": "2021-04-03T15:35:45+00:00" }, { "name": "plank/laravel-mediable", @@ -8560,20 +8400,19 @@ }, { "name": "psy/psysh", - "version": "v0.10.7", + "version": "v0.10.8", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "a395af46999a12006213c0c8346c9445eb31640c" + "reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/a395af46999a12006213c0c8346c9445eb31640c", - "reference": "a395af46999a12006213c0c8346c9445eb31640c", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e4573f47750dd6c92dca5aee543fa77513cbd8d3", + "reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3", "shasum": "" }, "require": { - "dnoegel/php-xdg-base-dir": "0.1.*", "ext-json": "*", "ext-tokenizer": "*", "nikic/php-parser": "~4.0|~3.0|~2.0|~1.3", @@ -8630,9 +8469,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.10.7" + "source": "https://github.com/bobthecow/psysh/tree/v0.10.8" }, - "time": "2021-03-14T02:14:56+00:00" + "time": "2021-04-10T16:23:39+00:00" }, { "name": "ralouphie/getallheaders", @@ -9320,123 +9159,6 @@ }, "time": "2021-03-25T23:00:49+00:00" }, - { - "name": "slevomat/coding-standard", - "version": "6.4.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", - "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpstan/phpstan": "0.12.48", - "phpstan/phpstan-deprecation-rules": "0.12.5", - "phpstan/phpstan-phpunit": "0.12.16", - "phpstan/phpstan-strict-rules": "0.12.5", - "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2020-10-05T12:39:37+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.5.8", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "9d583721a7157ee997f235f327de038e7ea6dac4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/9d583721a7157ee997f235f327de038e7ea6dac4", - "reference": "9d583721a7157ee997f235f327de038e7ea6dac4", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2020-10-23T02:01:07+00:00" - }, { "name": "staudenmeir/belongs-to-through", "version": "v2.11.1", @@ -12479,16 +12201,16 @@ }, { "name": "facade/flare-client-php", - "version": "1.5.0", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/facade/flare-client-php.git", - "reference": "9dd6f2b56486d939c4467b3f35475d44af57cf17" + "reference": "6bf380035890cb0a09b9628c491ae3866b858522" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facade/flare-client-php/zipball/9dd6f2b56486d939c4467b3f35475d44af57cf17", - "reference": "9dd6f2b56486d939c4467b3f35475d44af57cf17", + "url": "https://api.github.com/repos/facade/flare-client-php/zipball/6bf380035890cb0a09b9628c491ae3866b858522", + "reference": "6bf380035890cb0a09b9628c491ae3866b858522", "shasum": "" }, "require": { @@ -12532,7 +12254,7 @@ ], "support": { "issues": "https://github.com/facade/flare-client-php/issues", - "source": "https://github.com/facade/flare-client-php/tree/1.5.0" + "source": "https://github.com/facade/flare-client-php/tree/1.7.0" }, "funding": [ { @@ -12540,26 +12262,26 @@ "type": "github" } ], - "time": "2021-03-31T07:32:54+00:00" + "time": "2021-04-12T09:30:36+00:00" }, { "name": "facade/ignition", - "version": "2.7.0", + "version": "2.8.3", "source": { "type": "git", "url": "https://github.com/facade/ignition.git", - "reference": "bdc8b0b32c888f6edc838ca641358322b3d9506d" + "reference": "a8201d51aae83addceaef9344592a3b068b5d64d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facade/ignition/zipball/bdc8b0b32c888f6edc838ca641358322b3d9506d", - "reference": "bdc8b0b32c888f6edc838ca641358322b3d9506d", + "url": "https://api.github.com/repos/facade/ignition/zipball/a8201d51aae83addceaef9344592a3b068b5d64d", + "reference": "a8201d51aae83addceaef9344592a3b068b5d64d", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", - "facade/flare-client-php": "^1.3.7", + "facade/flare-client-php": "^1.6", "facade/ignition-contracts": "^1.0.2", "filp/whoops": "^2.4", "illuminate/support": "^7.0|^8.0", @@ -12617,7 +12339,7 @@ "issues": "https://github.com/facade/ignition/issues", "source": "https://github.com/facade/ignition" }, - "time": "2021-03-30T15:55:38+00:00" + "time": "2021-04-09T20:45:59+00:00" }, { "name": "facade/ignition-contracts", @@ -12868,16 +12590,16 @@ }, { "name": "nunomaduro/collision", - "version": "v5.3.0", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "aca63581f380f63a492b1e3114604e411e39133a" + "reference": "41b7e9999133d5082700d31a1d0977161df8322a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/aca63581f380f63a492b1e3114604e411e39133a", - "reference": "aca63581f380f63a492b1e3114604e411e39133a", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/41b7e9999133d5082700d31a1d0977161df8322a", + "reference": "41b7e9999133d5082700d31a1d0977161df8322a", "shasum": "" }, "require": { @@ -12952,7 +12674,7 @@ "type": "patreon" } ], - "time": "2021-01-25T15:34:13+00:00" + "time": "2021-04-09T13:38:32+00:00" }, { "name": "phar-io/manifest", diff --git a/config/app.php b/config/app.php index d8831bcbb..6d844fa16 100644 --- a/config/app.php +++ b/config/app.php @@ -180,6 +180,7 @@ return [ App\Providers\Form::class, App\Providers\Macro::class, App\Providers\Observer::class, + App\Providers\Queue::class, App\Providers\Route::class, App\Providers\Validation::class, App\Providers\ViewComposer::class, diff --git a/config/excel.php b/config/excel.php index 5557fbae3..6d06f6a38 100644 --- a/config/excel.php +++ b/config/excel.php @@ -13,7 +13,7 @@ return [ | Here you can specify how big the chunk should be. | */ - 'chunk_size' => 1000, + 'chunk_size' => 100, /* |-------------------------------------------------------------------------- @@ -114,20 +114,75 @@ return [ 'pdf' => 'Dompdf', ], + /* + |-------------------------------------------------------------------------- + | Value Binder + |-------------------------------------------------------------------------- + | + | PhpSpreadsheet offers a way to hook into the process of a value being + | written to a cell. In there some assumptions are made on how the + | value should be formatted. If you want to change those defaults, + | you can implement your own default value binder. + | + | Possible value binders: + | + | [x] Maatwebsite\Excel\DefaultValueBinder::class + | [x] PhpOffice\PhpSpreadsheet\Cell\StringValueBinder::class + | [x] PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class + | + */ 'value_binder' => [ + 'default' => 'Maatwebsite\Excel\DefaultValueBinder', + ], + + 'cache' => [ + /* + |-------------------------------------------------------------------------- + | Default cell caching driver + |-------------------------------------------------------------------------- + | + | By default PhpSpreadsheet keeps all cell values in memory, however when + | dealing with large files, this might result into memory issues. If you + | want to mitigate that, you can configure a cell caching driver here. + | When using the illuminate driver, it will store each value in a the + | cache store. This can slow down the process, because it needs to + | store each value. You can use the "batch" store if you want to + | only persist to the store when the memory limit is reached. + | + | Drivers: memory|illuminate|batch + | + */ + 'driver' => env('EXCEL_CACHE_DRIVER', 'memory'), /* |-------------------------------------------------------------------------- - | Default Value Binder + | Batch memory caching |-------------------------------------------------------------------------- | - | PhpSpreadsheet offers a way to hook into the process of a value being - | written to a cell. In there some assumptions are made on how the - | value should be formatted. If you want to change those defaults, - | you can implement your own default value binder. + | When dealing with the "batch" caching driver, it will only + | persist to the store when the memory limit is reached. + | Here you can tweak the memory limit to your liking. | */ - 'default' => 'Maatwebsite\Excel\DefaultValueBinder', + 'batch' => [ + 'memory_limit' => env('EXCEL_CACHE_BATCH_MEMORY_LIMIT', 60000), + ], + + /* + |-------------------------------------------------------------------------- + | Illuminate cache + |-------------------------------------------------------------------------- + | + | When using the "illuminate" caching driver, it will automatically use + | your default cache store. However if you prefer to have the cell + | cache on a separate store, you can configure the store name here. + | You can use any store defined in your cache config. When leaving + | at "null" it will use the default store. + | + */ + 'illuminate' => [ + 'store' => null, + ], ], 'transactions' => [ @@ -177,7 +232,25 @@ return [ | in conjunction with queued imports and exports. | */ - 'remote_disk' => null, + 'remote_disk' => env('EXCEL_TEMPORARY_FILES_REMOTE_DISK', null), + 'remote_prefix' => env('EXCEL_TEMPORARY_FILES_REMOTE_PREFIX', null), + + /* + |-------------------------------------------------------------------------- + | Force Resync + |-------------------------------------------------------------------------- + | + | When dealing with a multi server setup as above, it's possible + | for the clean up that occurs after entire queue has been run to only + | cleanup the server that the last AfterImportJob runs on. The rest of the server + | would still have the local temporary file stored on it. In this case your + | local storage limits can be exceeded and future imports won't be processed. + | To mitigate this you can set this config value to be true, so that after every + | queued chunk is processed the local temporary file is deleted on the server that + | processed it. + | + */ + 'force_resync_remote' => env('EXCEL_TEMPORARY_FILES_FORCE_RESYNC_REMOTE', null), ], diff --git a/config/mediable.php b/config/mediable.php index 9f468220e..1e4ad4703 100644 --- a/config/mediable.php +++ b/config/mediable.php @@ -6,12 +6,12 @@ return [ * * Should extend `Plank\Mediable\Media` */ - 'model' => Plank\Mediable\Media::class, + 'model' => env('MEDIABLE_MODEL', 'Plank\Mediable\Media'), /* * Filesystem disk to use if none is specified */ - 'default_disk' => 'uploads', + 'default_disk' => env('MEDIABLE_DEFAULT_DISK', 'uploads'), /* * Filesystems that can be used for media storage @@ -20,6 +20,7 @@ return [ */ 'allowed_disks' => [ 'uploads', + 's3', ], /* diff --git a/database/seeds/TestCompany.php b/database/seeds/TestCompany.php index 0029cfc94..426231482 100644 --- a/database/seeds/TestCompany.php +++ b/database/seeds/TestCompany.php @@ -53,12 +53,7 @@ class TestCompany extends Seeder ], ])); - session(['company_id' => $company->id]); - - // Set Company settings - setting()->setExtraColumns(['company_id' => $company->id]); - setting()->forgetAll(); - setting()->load(true); + $company->makeCurrent(); setting()->set(['email.protocol' => 'array']); setting()->save(); @@ -73,7 +68,7 @@ class TestCompany extends Seeder 'email' => 'test@company.com', 'password' => '123456', 'locale' => 'en-GB', - 'companies' => [session('company_id')], + 'companies' => [company_id()], 'roles' => ['1'], 'enabled' => '1', ])); @@ -90,7 +85,7 @@ class TestCompany extends Seeder 'currency_code' => setting('default.currency'), 'password' => '123456', 'password_confirmation' => '123456', - 'company_id' => session('company_id'), + 'company_id' => company_id(), 'enabled' => '1', 'create_user' => 'true', ])); @@ -113,7 +108,7 @@ class TestCompany extends Seeder Artisan::call('module:install', [ 'alias' => $alias, - 'company' => session('company_id'), + 'company' => company_id(), 'locale' => session('locale', app()->getLocale()), ]); } diff --git a/overrides/maatwebsite/excel/QueuedWriter.php b/overrides/maatwebsite/excel/QueuedWriter.php new file mode 100644 index 000000000..e1949045f --- /dev/null +++ b/overrides/maatwebsite/excel/QueuedWriter.php @@ -0,0 +1,215 @@ +writer = $writer; + $this->chunkSize = config('excel.exports.chunk_size', 1000); + $this->temporaryFileFactory = $temporaryFileFactory; + } + + /** + * @param object $export + * @param string $filePath + * @param string $disk + * @param string|null $writerType + * @param array|string $diskOptions + * + * @return \Illuminate\Foundation\Bus\PendingDispatch + */ + public function store($export, string $filePath, string $disk = null, string $writerType = null, $diskOptions = []) + { + $extension = pathinfo($filePath, PATHINFO_EXTENSION); + $temporaryFile = $this->temporaryFileFactory->make($extension); + + $jobs = $this->buildExportJobs($export, $temporaryFile, $writerType); + + $jobs->push(new StoreQueuedExport( + $temporaryFile, + $filePath, + $disk, + $diskOptions + )); + + return new PendingDispatch( + (new QueueExport($export, $temporaryFile, $writerType))->chain($jobs->toArray()) + ); + } + + /** + * @param object $export + * @param TemporaryFile $temporaryFile + * @param string $writerType + * + * @return Collection + */ + private function buildExportJobs($export, TemporaryFile $temporaryFile, string $writerType): Collection + { + $sheetExports = [$export]; + if ($export instanceof WithMultipleSheets) { + $sheetExports = $export->sheets(); + } + + $jobs = new Collection; + foreach ($sheetExports as $sheetIndex => $sheetExport) { + if ($sheetExport instanceof FromCollection) { + $jobs = $jobs->merge($this->exportCollection($sheetExport, $temporaryFile, $writerType, $sheetIndex)); + } elseif ($sheetExport instanceof FromQuery) { + $jobs = $jobs->merge($this->exportQuery($sheetExport, $temporaryFile, $writerType, $sheetIndex)); + } elseif ($sheetExport instanceof FromView) { + $jobs = $jobs->merge($this->exportView($sheetExport, $temporaryFile, $writerType, $sheetIndex)); + } + + $jobs->push(new CloseSheet($sheetExport, $temporaryFile, $writerType, $sheetIndex)); + } + + return $jobs; + } + + /** + * @param FromCollection $export + * @param TemporaryFile $temporaryFile + * @param string $writerType + * @param int $sheetIndex + * + * @return LazyCollection // @todo original: Collection + */ + private function exportCollection( + FromCollection $export, + TemporaryFile $temporaryFile, + string $writerType, + int $sheetIndex + ): LazyCollection { // @todo original: Collection + return $export + ->collection() + ->chunk($this->getChunkSize($export)) + ->map(function ($rows) use ($writerType, $temporaryFile, $sheetIndex, $export) { + if ($rows instanceof Traversable) { + $rows = iterator_to_array($rows); + } + + return new AppendDataToSheet( + $export, + $temporaryFile, + $writerType, + $sheetIndex, + $rows + ); + }); + } + + /** + * @param FromQuery $export + * @param TemporaryFile $temporaryFile + * @param string $writerType + * @param int $sheetIndex + * + * @return Collection + */ + private function exportQuery( + FromQuery $export, + TemporaryFile $temporaryFile, + string $writerType, + int $sheetIndex + ): Collection { + $query = $export->query(); + + $count = $export instanceof WithCustomQuerySize ? $export->querySize() : $query->count(); + $spins = ceil($count / $this->getChunkSize($export)); + + $jobs = new Collection(); + + for ($page = 1; $page <= $spins; $page++) { + $jobs->push(new AppendQueryToSheet( + $export, + $temporaryFile, + $writerType, + $sheetIndex, + $page, + $this->getChunkSize($export) + )); + } + + return $jobs; + } + + /** + * @param FromView $export + * @param TemporaryFile $temporaryFile + * @param string $writerType + * @param int $sheetIndex + * + * @return Collection + */ + private function exportView( + FromView $export, + TemporaryFile $temporaryFile, + string $writerType, + int $sheetIndex + ): Collection { + $jobs = new Collection(); + $jobs->push(new AppendViewToSheet( + $export, + $temporaryFile, + $writerType, + $sheetIndex + )); + + return $jobs; + } + + /** + * @param object|WithCustomChunkSize $export + * + * @return int + */ + private function getChunkSize($export): int + { + if ($export instanceof WithCustomChunkSize) { + return $export->chunkSize(); + } + + return $this->chunkSize; + } +} diff --git a/public/files/import/bills.xlsx b/public/files/import/bills.xlsx index d04e1ff729b32a4f07b0001b8f899b4c8b4fcd24..b4f3c2e491117193968ecf5bbbc73090fd0916ff 100644 GIT binary patch literal 13820 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 CWw}@Y diff --git a/resources/lang/en-GB/messages.php b/resources/lang/en-GB/messages.php index e14d19be0..366d9f8dd 100644 --- a/resources/lang/en-GB/messages.php +++ b/resources/lang/en-GB/messages.php @@ -8,7 +8,9 @@ return [ 'deleted' => ':type deleted!', 'duplicated' => ':type duplicated!', 'imported' => ':type imported!', + 'import_queued' => ':type import has been scheduled! You will receive an email when it is finished.', 'exported' => ':type exported!', + 'export_queued' => ':type export has been scheduled! You will receive an email when it is ready to download.', 'enabled' => ':type enabled!', 'disabled' => ':type disabled!', ], @@ -21,7 +23,7 @@ return [ 'last_category' => 'Error: Can not delete the last :type category!', 'change_type' => 'Error: Can not change the type because it has :text related!', 'invalid_apikey' => 'Error: The API Key entered is invalid!', - 'import_column' => 'Error: :message Sheet name: :sheet. Line number: :line.', + 'import_column' => 'Error: :message Column name: :column. Line number: :line.', 'import_sheet' => 'Error: Sheet name is not valid. Please, check the sample file.', ], diff --git a/resources/lang/en-GB/notifications.php b/resources/lang/en-GB/notifications.php index 7ae710144..384577473 100644 --- a/resources/lang/en-GB/notifications.php +++ b/resources/lang/en-GB/notifications.php @@ -24,4 +24,31 @@ return [ ], + 'import' => [ + + 'completed' => [ + 'subject' => 'Import completed', + 'description' => 'The import has been completed and the records are available in your panel.', + ], + + 'failed' => [ + 'subject' => 'Import failed', + 'description' => 'Not able to import the file due to the following issues:', + ], + ], + + 'export' => [ + + 'completed' => [ + 'subject' => 'Export is ready', + 'description' => 'The export file is ready to download from the following link:', + ], + + 'failed' => [ + 'subject' => 'Export failed', + 'description' => 'Not able to create the export file due to the following issue:', + ], + + ], + ]; diff --git a/resources/views/common/companies/index.blade.php b/resources/views/common/companies/index.blade.php index 40049bbe9..513874347 100644 --- a/resources/views/common/companies/index.blade.php +++ b/resources/views/common/companies/index.blade.php @@ -43,7 +43,7 @@ @foreach($companies as $item) - @if ((session('company_id') != $item->id)) + @if ((company_id() != $item->id)) {{ Form::bulkActionGroup($item->id, $item->name) }} @else {{ Form::bulkActionGroup($item->id, $item->name, ['disabled' => 'true']) }} @@ -54,7 +54,7 @@ {{ $item->email }} @date($item->created_at) - @if ((session('company_id') != $item->id) && user()->can('update-common-companies')) + @if ((company_id() != $item->id) && user()->can('update-common-companies')) {{ Form::enabledGroup($item->id, $item->name, $item->enabled) }} @else @if ($item->enabled) diff --git a/resources/views/components/documents/form/company.blade.php b/resources/views/components/documents/form/company.blade.php index 24bcf6f8d..c43a1aa14 100644 --- a/resources/views/components/documents/form/company.blade.php +++ b/resources/views/components/documents/form/company.blade.php @@ -23,7 +23,7 @@ @endif @if (!$hideCompanyEdit) -

{{ trans('errors.message.403') }}

- @php $landing_page = user() ? route(user()->landing_page) : route('login'); @endphp + @php $landing_page = user() ? user()->getLandingPageOfUser() : route('login'); @endphp
{{ trans('general.go_to_dashboard') }} diff --git a/resources/views/errors/404.blade.php b/resources/views/errors/404.blade.php index 3cf4cdeca..96cd230ed 100644 --- a/resources/views/errors/404.blade.php +++ b/resources/views/errors/404.blade.php @@ -13,7 +13,7 @@

{{ trans('errors.message.404') }}

- @php $landing_page = user() ? route(user()->landing_page) : route('login'); @endphp + @php $landing_page = user() ? user()->getLandingPageOfUser() : route('login'); @endphp {{ trans('general.go_to_dashboard') }}
diff --git a/resources/views/errors/500.blade.php b/resources/views/errors/500.blade.php index 301fef4cb..572546a6f 100644 --- a/resources/views/errors/500.blade.php +++ b/resources/views/errors/500.blade.php @@ -13,7 +13,7 @@

{{ trans('errors.message.500') }}

- @php $landing_page = user() ? route(user()->landing_page) : route('login'); @endphp + @php $landing_page = user() ? user()->getLandingPageOfUser() : route('login'); @endphp {{ trans('general.go_to_dashboard') }}
diff --git a/resources/views/partials/admin/head.blade.php b/resources/views/partials/admin/head.blade.php index 8e59fa5ba..5f6a93648 100644 --- a/resources/views/partials/admin/head.blade.php +++ b/resources/views/partials/admin/head.blade.php @@ -35,7 +35,7 @@ @livewireStyles diff --git a/routes/guest.php b/routes/guest.php index 5dd0c2f54..a473932b9 100644 --- a/routes/guest.php +++ b/routes/guest.php @@ -20,3 +20,7 @@ Route::group(['prefix' => 'auth'], function () { Route::get('reset/{token}', 'Auth\Reset@create')->name('reset'); Route::post('reset', 'Auth\Reset@store')->name('reset.store'); }); + +Route::get('/', function () { + return redirect()->route('login'); +}); diff --git a/tests/Feature/FeatureTestCase.php b/tests/Feature/FeatureTestCase.php index 7d30ddff8..f79a90d3e 100644 --- a/tests/Feature/FeatureTestCase.php +++ b/tests/Feature/FeatureTestCase.php @@ -4,8 +4,7 @@ namespace Tests\Feature; use App\Models\Auth\User; use App\Models\Common\Company; -use App\Utilities\Overrider; -use Faker\Factory; +use Faker\Factory as Faker; use Tests\TestCase; abstract class FeatureTestCase extends TestCase @@ -22,14 +21,14 @@ abstract class FeatureTestCase extends TestCase $this->withoutExceptionHandling(); - $this->faker = Factory::create(); + $this->faker = Faker::create(); $this->user = User::first(); $this->company = $this->user->companies()->first(); // Disable debugbar config(['debugbar.enabled', false]); - Overrider::load('currencies'); + app('url')->defaults(['company_id' => $this->company->id]); } /** @@ -45,13 +44,13 @@ abstract class FeatureTestCase extends TestCase $user = $this->user; } - if (!$company) { - $company = $this->company; + if ($company) { + $company->makeCurrent(); + + app('url')->defaults(['company_id' => $company->id]); } - $this->startSession(); - - return $this->actingAs($user)->withSession(['company_id' => $company->id]); + return $this->actingAs($user); } public function assertFlashLevel($excepted)