diff --git a/app/Abstracts/Export.php b/app/Abstracts/Export.php index b9b27bfee..b7a73e55d 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,21 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto public $user; + public $request_class = null; + + 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(); } @@ -99,4 +116,171 @@ 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 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 = 'double'; + } + + 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', 'double'])) { + $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); + }, + ]; + } } diff --git a/app/Exports/Banking/Transactions.php b/app/Exports/Banking/Transactions.php index b4a3ad611..7fb7668de 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,24 @@ class Transactions extends Export implements WithColumnFormatting ]; } + public function columnValidations(): array + { + return [ + 'type' => [ + 'options' => array_keys(config('type.transaction')) + ], + // '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 [ 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( 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); 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); 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' => [ 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', 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.',