From 1b2cc64118bb35e7813332e974d0f4ba4f71adb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihan=20=C5=9Eent=C3=BCrk?= <53110792+CihanSenturk@users.noreply.github.com> Date: Wed, 24 Apr 2024 18:10:36 +0300 Subject: [PATCH 1/9] added export validation --- app/Abstracts/Export.php | 211 ++++++++++++++++++++++++++++++- resources/lang/en-GB/general.php | 1 + 2 files changed, 211 insertions(+), 1 deletion(-) diff --git a/app/Abstracts/Export.php b/app/Abstracts/Export.php index b9b27bfee..6646105bf 100644 --- a/app/Abstracts/Export.php +++ b/app/Abstracts/Export.php @@ -2,6 +2,7 @@ namespace App\Abstracts; +use App\Abstracts\Http\FormRequest; use App\Events\Export\HeadingsPreparing; use App\Events\Export\RowsPreparing; use App\Notifications\Common\ExportFailed; @@ -14,11 +15,16 @@ use Maatwebsite\Excel\Concerns\FromCollection; use Maatwebsite\Excel\Concerns\ShouldAutoSize; use Maatwebsite\Excel\Concerns\WithHeadings; use Maatwebsite\Excel\Concerns\WithMapping; +use Maatwebsite\Excel\Concerns\WithEvents; use Maatwebsite\Excel\Concerns\WithTitle; use Maatwebsite\Excel\Concerns\WithStrictNullComparison; +use Maatwebsite\Excel\Events\AfterSheet; +use Maatwebsite\Excel\Events\BeforeSheet; use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate; +use PhpOffice\PhpSpreadsheet\Cell\DataValidation; +use PhpOffice\PhpSpreadsheet\Cell\Coordinate; -abstract class Export implements FromCollection, HasLocalePreference, ShouldAutoSize, ShouldQueue, WithHeadings, WithMapping, WithTitle, WithStrictNullComparison +abstract class Export implements FromCollection, HasLocalePreference, ShouldAutoSize, ShouldQueue, WithHeadings, WithMapping, WithTitle, WithStrictNullComparison, WithEvents { use Exportable; @@ -28,10 +34,19 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto public $user; + public $request_class = null; + + public $row_count = 250; //number of rows that will have the dropdown + + public $column_count = 25; //number of columns to be auto sized + + public $column_validations; //selects should have column_name and options + public function __construct($ids = null) { $this->ids = $ids; $this->fields = $this->fields(); + $this->column_validations = $this->columnValidations(); $this->user = user(); } @@ -99,4 +114,198 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto { $this->user->notify(new ExportFailed($exception->getMessage())); } + + public function columnValidations(): array + { + return []; + } + + public function afterSheet($event) + { + $condition = class_exists($this->request_class) + ? ! ($request = new $this->request_class) instanceof FormRequest + : true; + + + if (empty($this->column_validations) && $condition) { + return []; + } + + $alphas = range('A', 'Z'); + + foreach ($this->fields as $key => $value) { + $drop_column = $alphas[$key]; + + if ($this->setColumnValidations($drop_column, $event, $value)) { + continue; + }; + + $this->validationWarning($drop_column, $event, $value, $request); + } + } + + public function beforeSheet($event) + { + $condition = class_exists($this->request_class) + ? ! ($request = new $this->request_class) instanceof FormRequest + : true; + + + if (empty($this->column_validations) && $condition) { + return []; + } + + $alphas = range('A', 'Z'); + + foreach ($this->fields as $key => $value) { + $drop_column = $alphas[$key]; + + if ($this->setColumnValidations($drop_column, $event, $value)) { + continue; + }; + + $this->validationWarning($drop_column, $event, $value, $request); + } + } + + public function setColumnValidations($drop_column, $event, $value) + { + if (! isset($this->column_validations[$value])) { + return false; + } + + $column_validation = $this->column_validations[$value]; + + // set dropdown list for first data row + $validation = $event->sheet->getCell("{$drop_column}2")->getDataValidation(); + + $validation->setType($column_validation['type'] ?? DataValidation::TYPE_LIST); + + if (empty($column_validation['hide_prompt'])) { + $validation->setAllowBlank($column_validation['allow_blank'] ?? false); + $validation->setShowInputMessage($column_validation['show_input_message'] ?? true); + $validation->setPromptTitle($column_validation['prompt_title'] ?? null); + $validation->setPrompt($column_validation['prompt'] ?? null); + } + + if (empty($column_validation['hide_error'])) { + $validation->setErrorStyle($column_validation['error_style'] ?? DataValidation::STYLE_INFORMATION); + $validation->setShowErrorMessage($column_validation['show_error_message'] ?? true); + $validation->setErrorTitle($column_validation['error_title'] ?? null); + $validation->setError($column_validation['error'] ?? null); + } + + if (! empty($column_validation['options'])) { + $validation->setFormula1(sprintf('"%s"', implode(',', $column_validation['options']))); + $validation->setShowDropDown($column_validation['show_dropdown'] ?? true); + } + + // clone validation to remaining rows + for ($i = 3; $i <= $this->row_count; $i++) { + $event->sheet->getCell("{$drop_column}{$i}")->setDataValidation(clone $validation); + } + + // set columns to autosize + for ($i = 1; $i <= $this->column_count; $i++) { + $column = Coordinate::stringFromColumnIndex($i); + $event->sheet->getColumnDimension($column)->setAutoSize(true); + } + + return true; + } + + public function validationWarning($drop_column, $event, $value, $request) + { + $rules = $this->prepareRules($request->rules()); + + if (! isset($rules[$value])) { + return false; + } + + $rule = explode('|', $rules[$value]); + + $prompt = ''; + + foreach ($rule as $r) { + if (strpos($r, 'unique') !== false) { + $r = 'unique'; + } + + if (strpos($r, 'amount') !== false) { + $r = 'integer'; + } + + if (strpos($r, 'date_format') !== false) { + $prompt = $prompt . trans('validation.date_format', [ + 'attribute' => $value, + 'format' => str_replace('date_format:', '', $r) + ]) . ' '; + } + + if (strpos($r, 'required_without') !== false) { + $prompt = $prompt . trans('validation.required_without', [ + 'attribute' => $value, + 'values' => str_replace('required_without:', '', $r) + ]) . ' '; + } + + if (in_array($r, ['required', 'email', 'integer', 'unique', 'date_format'])) { + $prompt = $prompt . trans('validation.' . $r, ['attribute' => $value]) . ' '; + } + } + + $validation = $event->sheet->getCell("{$drop_column}2")->getDataValidation(); + + $validation->setAllowBlank(false); + $validation->setShowInputMessage(true); + $validation->setPromptTitle(trans('general.validation_warning')); + $validation->setPrompt($prompt ?? null); + + for ($i = 3; $i <= $this->row_count; $i++) { + $event->sheet->getCell("{$drop_column}{$i}")->setDataValidation(clone $validation); + } + + for ($i = 1; $i <= $this->column_count; $i++) { + $column = Coordinate::stringFromColumnIndex($i); + $event->sheet->getColumnDimension($column)->setAutoSize(true); + } + } + + public function getDropdownOptions($model, $select): array + { + $limit = 253; + $selects = []; + $totalLength = 0; + + $model::select($select)->each(function ($row) use (&$selects, &$totalLength, $limit, $select) { + $nameLength = mb_strlen($row->$select); + + if ($totalLength + $nameLength <= $limit && $nameLength !== 0) { + $selects[] = $row->$select; + $totalLength += $nameLength; + } + }); + + return $selects; + } + + /** + * You can override this method to add custom rules for each row. + */ + public function prepareRules(array $rules): array + { + return $rules; + } + + public function registerEvents(): array + { + return [ + AfterSheet::class => function(AfterSheet $event) { + $this->afterSheet($event); + }, + BeforeSheet::class => function(BeforeSheet $event) { + $this->beforeSheet($event); + }, + ]; + } } diff --git a/resources/lang/en-GB/general.php b/resources/lang/en-GB/general.php index 3dff87570..63659fad5 100644 --- a/resources/lang/en-GB/general.php +++ b/resources/lang/en-GB/general.php @@ -236,6 +236,7 @@ return [ 'preview_mode' => 'Preview Mode', 'go_back' => 'Go back to :type', 'validation_error' => 'Validation error', + 'validation_warning' => 'Validation warning', 'dismiss' => 'Dismiss', 'size' => 'Size', 'media' => 'Media', From e84c3af16f392a06c2c42d09955be632875449b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihan=20=C5=9Eent=C3=BCrk?= <53110792+CihanSenturk@users.noreply.github.com> Date: Wed, 24 Apr 2024 18:11:35 +0300 Subject: [PATCH 2/9] added export transactions validation --- app/Exports/Banking/Transactions.php | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/app/Exports/Banking/Transactions.php b/app/Exports/Banking/Transactions.php index b4a3ad611..b44db8965 100644 --- a/app/Exports/Banking/Transactions.php +++ b/app/Exports/Banking/Transactions.php @@ -4,11 +4,15 @@ namespace App\Exports\Banking; use App\Abstracts\Export; use App\Models\Banking\Transaction as Model; -use Maatwebsite\Excel\Concerns\WithColumnFormatting; +use App\Http\Requests\Banking\Transaction as Request; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; +use PhpOffice\PhpSpreadsheet\Cell\DataValidation; +use Maatwebsite\Excel\Concerns\WithColumnFormatting; class Transactions extends Export implements WithColumnFormatting { + public $request_class = Request::class; + public function collection() { return Model::with('account', 'category', 'contact', 'document')->collectForExport($this->ids, ['paid_at' => 'desc']); @@ -46,6 +50,27 @@ class Transactions extends Export implements WithColumnFormatting ]; } + public function columnValidations(): array + { + return [ + 'type' => [ + 'options' => [ + Model::INCOME_TYPE, Model::INCOME_TRANSFER_TYPE, Model::INCOME_SPLIT_TYPE, Model::INCOME_RECURRING_TYPE, + Model::EXPENSE_TYPE, Model::EXPENSE_TRANSFER_TYPE, Model::EXPENSE_SPLIT_TYPE, Model::EXPENSE_RECURRING_TYPE, + ] + ], + // 'paid_at' => [ + // 'type' => DataValidation::TYPE_NONE, + // 'prompt_title' => trans('general.validation_warning'), + // 'prompt' => trans('validation.date_format', ['attribute' => 'paid_at', 'format' => 'yyyy-mm-dd']), + // 'hide_error' => true, + // ], + // 'contact_email' => [ + // 'options' => $this->getDropdownOptions(Contact::class, 'email'), + // ] + ]; + } + public function columnFormats(): array { return [ From 8755745e06b63d6fa997e5a48308a0c827c3c694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihan=20=C5=9Eent=C3=BCrk?= <53110792+CihanSenturk@users.noreply.github.com> Date: Wed, 24 Apr 2024 18:11:53 +0300 Subject: [PATCH 3/9] added export transfers validation --- app/Exports/Banking/Transfers.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Exports/Banking/Transfers.php b/app/Exports/Banking/Transfers.php index fbcbb9183..2bcd522ec 100644 --- a/app/Exports/Banking/Transfers.php +++ b/app/Exports/Banking/Transfers.php @@ -3,6 +3,7 @@ namespace App\Exports\Banking; use App\Abstracts\Export; +use App\Http\Requests\Banking\Transfer as Request; use App\Models\Banking\Transfer as Model; use App\Utilities\Date; use Maatwebsite\Excel\Concerns\WithColumnFormatting; @@ -10,6 +11,8 @@ use PhpOffice\PhpSpreadsheet\Style\NumberFormat; class Transfers extends Export implements WithColumnFormatting { + public $request_class = Request::class; + public function collection() { return Model::with( From 38a0f6711da0e68fd5be7462e6c320d9483721ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihan=20=C5=9Eent=C3=BCrk?= <53110792+CihanSenturk@users.noreply.github.com> Date: Wed, 24 Apr 2024 18:12:05 +0300 Subject: [PATCH 4/9] added export items validation --- app/Exports/Common/Sheets/ItemTaxes.php | 3 +++ app/Exports/Common/Sheets/Items.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/app/Exports/Common/Sheets/ItemTaxes.php b/app/Exports/Common/Sheets/ItemTaxes.php index 68a36b62a..e1e408562 100644 --- a/app/Exports/Common/Sheets/ItemTaxes.php +++ b/app/Exports/Common/Sheets/ItemTaxes.php @@ -3,10 +3,13 @@ namespace App\Exports\Common\Sheets; use App\Abstracts\Export; +use App\Http\Requests\Common\ItemTax as Request; use App\Models\Common\ItemTax as Model; class ItemTaxes extends Export { + public $request_class = Request::class; + public function collection() { return Model::with('item', 'tax')->collectForExport($this->ids, null, 'item_id'); diff --git a/app/Exports/Common/Sheets/Items.php b/app/Exports/Common/Sheets/Items.php index 57fef15e7..891232ed0 100644 --- a/app/Exports/Common/Sheets/Items.php +++ b/app/Exports/Common/Sheets/Items.php @@ -3,10 +3,13 @@ namespace App\Exports\Common\Sheets; use App\Abstracts\Export; +use App\Http\Requests\Common\Item as Request; use App\Models\Common\Item as Model; class Items extends Export { + public $request_class = Request::class; + public function collection() { return Model::with('category')->collectForExport($this->ids); From 3f0067f41ea907556bbe75984f9e3e9e4c9c9add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihan=20=C5=9Eent=C3=BCrk?= <53110792+CihanSenturk@users.noreply.github.com> Date: Wed, 24 Apr 2024 18:12:14 +0300 Subject: [PATCH 5/9] added export categories validation --- app/Exports/Settings/Categories.php | 3 +++ app/Exports/Settings/Taxes.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/app/Exports/Settings/Categories.php b/app/Exports/Settings/Categories.php index d5b0559eb..c4a486493 100644 --- a/app/Exports/Settings/Categories.php +++ b/app/Exports/Settings/Categories.php @@ -3,10 +3,13 @@ namespace App\Exports\Settings; use App\Abstracts\Export; +use App\Http\Requests\Setting\Category as Request; use App\Models\Setting\Category as Model; class Categories extends Export { + public $request_class = Request::class; + public function collection() { return Model::collectForExport($this->ids); diff --git a/app/Exports/Settings/Taxes.php b/app/Exports/Settings/Taxes.php index 92057dd89..42ca69c49 100644 --- a/app/Exports/Settings/Taxes.php +++ b/app/Exports/Settings/Taxes.php @@ -3,10 +3,13 @@ namespace App\Exports\Settings; use App\Abstracts\Export; +use App\Http\Requests\Setting\Tax as Request; use App\Models\Setting\Tax as Model; class Taxes extends Export { + public $request_class = Request::class; + public function collection() { return Model::collectForExport($this->ids); From eef5227b45f1c105623af1b82c9d444532ef6e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihan=20=C5=9Eent=C3=BCrk?= <53110792+CihanSenturk@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:18:25 +0300 Subject: [PATCH 6/9] updated excel config --- app/Abstracts/Export.php | 8 +++++--- config/excel.php | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/app/Abstracts/Export.php b/app/Abstracts/Export.php index 6646105bf..9412b8039 100644 --- a/app/Abstracts/Export.php +++ b/app/Abstracts/Export.php @@ -36,17 +36,19 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto public $request_class = null; - public $row_count = 250; //number of rows that will have the dropdown - - public $column_count = 25; //number of columns to be auto sized + public $column_count; //number of columns to be auto sized public $column_validations; //selects should have column_name and options + public $row_count; //number of rows that will have the dropdown + public function __construct($ids = null) { $this->ids = $ids; $this->fields = $this->fields(); $this->column_validations = $this->columnValidations(); + $this->column_count = config('excel.exports.column_count'); + $this->row_count = config('excel.exports.row_count'); $this->user = user(); } diff --git a/config/excel.php b/config/excel.php index f82fbca38..6d107518a 100644 --- a/config/excel.php +++ b/config/excel.php @@ -68,6 +68,26 @@ return [ 'manager' => '', 'company' => '', ], + + /* + |-------------------------------------------------------------------------- + | Export validations + |-------------------------------------------------------------------------- + | + | Number of rows that will have the dropdown + | + */ + 'row_count' => env('EXCEL_EXPORTS_ROW_COUNT', 250), + + /* + |-------------------------------------------------------------------------- + | Export validations + |-------------------------------------------------------------------------- + | + | Number of columns to be auto sized + | + */ + 'column_count' => env('EXCEL_EXPORTS_COLUMN_COUNT', 25), ], 'imports' => [ From cd00f26db9afb932f512c8972f57b4b57eaa0bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihan=20=C5=9Eent=C3=BCrk?= <53110792+CihanSenturk@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:19:18 +0300 Subject: [PATCH 7/9] removed before sheet event --- app/Abstracts/Export.php | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/app/Abstracts/Export.php b/app/Abstracts/Export.php index 9412b8039..ab6ff5b50 100644 --- a/app/Abstracts/Export.php +++ b/app/Abstracts/Export.php @@ -146,30 +146,6 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto } } - public function beforeSheet($event) - { - $condition = class_exists($this->request_class) - ? ! ($request = new $this->request_class) instanceof FormRequest - : true; - - - if (empty($this->column_validations) && $condition) { - return []; - } - - $alphas = range('A', 'Z'); - - foreach ($this->fields as $key => $value) { - $drop_column = $alphas[$key]; - - if ($this->setColumnValidations($drop_column, $event, $value)) { - continue; - }; - - $this->validationWarning($drop_column, $event, $value, $request); - } - } - public function setColumnValidations($drop_column, $event, $value) { if (! isset($this->column_validations[$value])) { @@ -305,9 +281,6 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto AfterSheet::class => function(AfterSheet $event) { $this->afterSheet($event); }, - BeforeSheet::class => function(BeforeSheet $event) { - $this->beforeSheet($event); - }, ]; } } From 0ce1eebd31ed4b4acf84a7643fd99d13e44edbe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihan=20=C5=9Eent=C3=BCrk?= <53110792+CihanSenturk@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:20:44 +0300 Subject: [PATCH 8/9] added double validation message --- app/Abstracts/Export.php | 4 ++-- resources/lang/en-GB/validation.php | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Abstracts/Export.php b/app/Abstracts/Export.php index ab6ff5b50..b7a73e55d 100644 --- a/app/Abstracts/Export.php +++ b/app/Abstracts/Export.php @@ -210,7 +210,7 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto } if (strpos($r, 'amount') !== false) { - $r = 'integer'; + $r = 'double'; } if (strpos($r, 'date_format') !== false) { @@ -227,7 +227,7 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto ]) . ' '; } - if (in_array($r, ['required', 'email', 'integer', 'unique', 'date_format'])) { + if (in_array($r, ['required', 'email', 'integer', 'unique', 'date_format', 'double'])) { $prompt = $prompt . trans('validation.' . $r, ['attribute' => $value]) . ' '; } } diff --git a/resources/lang/en-GB/validation.php b/resources/lang/en-GB/validation.php index 1d35b5917..4ffe075a3 100644 --- a/resources/lang/en-GB/validation.php +++ b/resources/lang/en-GB/validation.php @@ -44,6 +44,7 @@ return [ 'dimensions' => 'The :attribute has invalid image dimensions.', 'distinct' => 'The :attribute field has a duplicate value.', 'doesnt_start_with' => 'The :attribute may not start with one of the following: :values.', + 'double' => 'The :attribute must be a valid double.', 'email' => 'The :attribute must be a valid email address.', 'ends_with' => 'The :attribute must end with one of the following: :values.', 'enum' => 'The selected :attribute is invalid.', From 848c1444f8e0fac699c080ea563f1aaac504f6fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihan=20=C5=9Eent=C3=BCrk?= <53110792+CihanSenturk@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:22:17 +0300 Subject: [PATCH 9/9] fixed transaction type options export --- app/Exports/Banking/Transactions.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/Exports/Banking/Transactions.php b/app/Exports/Banking/Transactions.php index b44db8965..7fb7668de 100644 --- a/app/Exports/Banking/Transactions.php +++ b/app/Exports/Banking/Transactions.php @@ -54,10 +54,7 @@ class Transactions extends Export implements WithColumnFormatting { return [ 'type' => [ - 'options' => [ - Model::INCOME_TYPE, Model::INCOME_TRANSFER_TYPE, Model::INCOME_SPLIT_TYPE, Model::INCOME_RECURRING_TYPE, - Model::EXPENSE_TYPE, Model::EXPENSE_TRANSFER_TYPE, Model::EXPENSE_SPLIT_TYPE, Model::EXPENSE_RECURRING_TYPE, - ] + 'options' => array_keys(config('type.transaction')) ], // 'paid_at' => [ // 'type' => DataValidation::TYPE_NONE,