From dd97ba2e25b2c84d41846f0fe2a9fdb2cc653461 Mon Sep 17 00:00:00 2001 From: Dell Date: Wed, 27 Jul 2022 11:04:44 +0700 Subject: [PATCH] Update Handle Multi Sheet Plan & Benefit --- .../Controllers/Api/CorporateController.php | 109 ++++++++++++++++++ .../Http/Controllers/Api/PlanController.php | 74 ++++++++---- Modules/Internal/Routes/api.php | 1 + .../Internal/Services/CorporateService.php | 99 ++++++++++++++++ app/Models/Plan.php | 1 + app/Services/ImportService.php | 85 ++++++++++++++ .../2022_06_23_083834_create_plans_table.php | 3 + .../Corporates/CorporateTabNavigations.tsx | 8 +- 8 files changed, 355 insertions(+), 25 deletions(-) create mode 100644 Modules/Internal/Services/CorporateService.php create mode 100644 app/Services/ImportService.php diff --git a/Modules/Internal/Http/Controllers/Api/CorporateController.php b/Modules/Internal/Http/Controllers/Api/CorporateController.php index 8bafc8cb..6eb07399 100644 --- a/Modules/Internal/Http/Controllers/Api/CorporateController.php +++ b/Modules/Internal/Http/Controllers/Api/CorporateController.php @@ -2,9 +2,12 @@ namespace Modules\Internal\Http\Controllers\Api; +use App\Exceptions\ImportRowException; use App\Imports\PlansImport; +use App\Models\Benefit; use App\Models\Corporate; use App\Models\Plan; +use App\Services\ImportService; use DB; use Illuminate\Contracts\Support\Renderable; use Illuminate\Http\Request; @@ -12,6 +15,7 @@ use Illuminate\Routing\Controller; use Maatwebsite\Excel\Facades\Excel; use Box\Spout\Reader\Common\Creator\ReaderEntityFactory; use Illuminate\Support\Facades\Storage; +use Modules\Internal\Services\CorporateService; class CorporateController extends Controller { @@ -169,4 +173,109 @@ class CorporateController extends Controller { // } + + public function importPlanBenefit(Request $request, $corporate_id) + { + $request->validate([ + 'file' => 'required|file|mimes:xls,xlsx,csv,txt', + ]); + // dd($request->toArray()); + $file_name = now()->getPreciseTimestamp(3).'-'.$request->file('file')->getClientOriginalName(); + $file = $request->file('file')->storeAs('temp', $file_name); + $corporate = Corporate::findOrFail($corporate_id); + + $import = new ImportService(); + $import->read(Storage::path('temp/'.$file_name)); + $import->write(Storage::disk('public')->path('temp/result-'.$file_name), 'xsls'); + + foreach ($import->sheetsIterator() as $sheetIndex => $sheet) { + if ( !in_array($sheet->getName(), ['Plan', 'Benefit']) ) { + continue; + } else { + if ($sheetIndex == 1) { // Rename First Sheet to Writer + $firstWriterSheet = $import->writer->getCurrentSheet(); + $firstWriterSheet->setName($sheet->getName()); + } else { // Add New Sheet to Writer + $nextWriterSheet = $import->writer->addNewSheetAndMakeItCurrent(); + $nextWriterSheet->setName($sheet->getName()); + } + + if ( $sheet->getName() == 'Plan' ) { + $headers_map_to_table_fields = Plan::$doc_headers_to_field_map; + } else if ( $sheet->getName() == 'Benefit' ) { + $headers_map_to_table_fields = Benefit::$doc_headers_to_field_map; + } + + // Write Header to File + $result_headers = array_keys($headers_map_to_table_fields); + $result_headers = array_merge($result_headers, ['Ingest Code', 'Ingest Note']); + $import->addArrayToRow($result_headers); + + } + + $doc_headers_indexes = []; + foreach ($sheet->getRowIterator() as $index => $row) { + if ($index == 1) { // First Row Must be Header + foreach ($row->getCells() as $index => $cell) { + $title = $cell->getValue(); + $title = preg_replace( "/\r|\n/", " ", $title ); + $title = preg_replace('/\xc2\xa0/', " ", $title ); + $title = rtrim($title); + $title = ltrim($title); + $doc_headers_indexes[$index] = $title; + } + // TODO Validate if First Row not Header + } else { // Next Row Should be Data + $row_data = []; + + foreach ($row->getCells() as $header_index => $cell) { + if (isset($headers_map_to_table_fields[$doc_headers_indexes[$header_index]])) + $row_data[$headers_map_to_table_fields[$doc_headers_indexes[$header_index]]] = $cell->getValue(); + } + + try { // Process the Row Data + $corporateService = new CorporateService(); + if ( $sheet->getName() == 'Plan' ) { + $corporateService->handlePlanRow($corporate, $row_data); + } else if ( $sheet->getName() == 'Benefit' ) { + $corporateService->handleBenefitRow($corporate, $row_data); + } + + // Write Success Result to File + $import->addArrayToRow(array_merge($row_data, [ + 'Ingest Code' => 200, + 'Ingest Note' => 'Success', + ]), $sheet->getName()); + + } catch (ImportRowException $e) { + // Write Data Validation Error to File + $import->addArrayToRow(array_merge($row_data, [ + 'Ingest Code' => $e->getCode(), + 'Ingest Note' => $e->getMessage(), + ]), $sheet->getName()); + } catch (\Exception $e) { + // throw new \Exception($e); + // Write Server Error to File + $import->addArrayToRow(array_merge($row_data, [ + 'Ingest Code' => 500, + 'Ingest Note' => env('APP_DEBUG') ? $e->getMessage() : 'Server Error', + ]), $sheet->getName()); + } + } + } + } + $import->reader->close(); + Storage::delete('temp/'.$file_name); + $import->writer->close(); + + return [ + // 'total_successed_row' => $imported_plan_data, + // 'total_failed_row' => count($failed_plan_data), + // 'failed_row' => $failed_plan_data, + 'result_file' => [ + 'url' => Storage::disk('public')->url('temp/result-'.$file_name), + 'name' => 'result-'.$file_name, + ] + ]; + } } diff --git a/Modules/Internal/Http/Controllers/Api/PlanController.php b/Modules/Internal/Http/Controllers/Api/PlanController.php index 0a318bb3..7a440180 100644 --- a/Modules/Internal/Http/Controllers/Api/PlanController.php +++ b/Modules/Internal/Http/Controllers/Api/PlanController.php @@ -2,15 +2,25 @@ namespace Modules\Internal\Http\Controllers\Api; +use App\Exceptions\ImportRowException; +use App\Models\Corporate; use App\Models\Plan; +use App\Services\ImportService; use Illuminate\Contracts\Support\Renderable; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Illuminate\Support\Facades\Storage; use Box\Spout\Reader\Common\Creator\ReaderEntityFactory; +use Box\Spout\Writer\Common\Creator\WriterEntityFactory; +use Modules\Internal\Services\CorporateService; class PlanController extends Controller { + public function __construct(CorporateService $corporateService, ImportService $importService) + { + $this->corporateService = $corporateService; + $this->importService = $importService; + } /** * Display a listing of the resource. * @return Renderable @@ -19,10 +29,11 @@ class PlanController extends Controller { $plans = Plan::query() ->filter($request->all()) - ->whereHas('corporatePlan', function ($corporatePlan) use ($corporate_id) { - $corporatePlan->where('corporate_id', $corporate_id); - }) - ->with('corporatePlan') + ->where('corporate_id', $corporate_id) + // ->whereHas('corporatePlan', function ($corporatePlan) use ($corporate_id) { + // $corporatePlan->where('corporate_id', $corporate_id); + // }) + // ->with('corporatePlan') ->paginate(0) ->appends($request->all()); @@ -96,17 +107,23 @@ class PlanController extends Controller ]); $file_name = now()->getPreciseTimestamp(3).'-'.$request->file('file')->getClientOriginalName(); $file = $request->file('file')->storeAs('temp', $file_name); - - // return false; - - $reader = ReaderEntityFactory::createReaderFromFile(Storage::path('temp/'.$file_name)); - $reader->open(Storage::path('temp/'.$file_name)); + $corporate = Corporate::findOrFail($corporate_id); + + $import = $this->importService; + $import->read(Storage::path('temp/'.$file_name)); + $import->write(Storage::disk('public')->path('temp/result-'.$file_name), 'xsls'); $headers_map_to_table_fields = Plan::$doc_headers_to_field_map; + // Write Header to File + $result_headers = array_keys($headers_map_to_table_fields); + array_push($result_headers, ['Ingest Code', 'Ingest Note']); + $import->addArrayToRow($result_headers); + die('fuck'); + $imported_plan_data = 0; $failed_plan_data = []; - foreach ($reader->getSheetIterator() as $sheet) { + foreach ($import->reader->getSheetIterator() as $sheet) { $doc_headers_indexes = []; foreach ($sheet->getRowIterator() as $index => $row) { if ($index == 1) { // First Row Must be Header @@ -114,35 +131,50 @@ class PlanController extends Controller $doc_headers_indexes[$index] = $cell->getValue(); } } else { // Next Row Should be Data - $new_plan_data = []; - foreach ($row->getCells() as $index => $cell) { - $new_plan_data[$headers_map_to_table_fields[$doc_headers_indexes[$index]]] = $cell->getValue(); + $plan_row = []; + foreach ($row->getCells() as $header_index => $cell) { + if (isset($headers_map_to_table_fields[$doc_headers_indexes[$header_index]])) + $plan_row[$headers_map_to_table_fields[$doc_headers_indexes[$header_index]]] = $cell->getValue(); } // $imported_plan_data[] = $new_row; // Insert to Array // Create Directly try { - Plan::updateOrCreate([ - 'code' => $new_plan_data['code'] - ], $new_plan_data); - + $rowResponse = $this->corporateService->handlePlanRow($corporate, $plan_row); + + // Write Success Result to File + array_push($plan_row, 'SUCCESS'); + $import->addArrayToRow($plan_row); $imported_plan_data++; - } catch(\Exception $e) { - $failed_plan_data[] = $new_plan_data; + } catch (ImportRowException $e) { + // Write Data Validation Error to File + array_push($plan_row, $e->getMessage()); + $import->addArrayToRow($plan_row); + $failed_plan_data[] = ['row_number' => $index, 'error' => $e->getMessage()]; + } catch (\Exception $e) { + // Write Server Error to File + array_push($plan_row, "Server Error"); + $import->addArrayToRow($plan_row); + $failed_plan_data[] = ['row_number' => $index, 'error' => $e->getMessage()]; } } } break; //only read first sheet } - $reader->close(); + $import->reader->close(); Storage::delete('temp/'.$file_name); + $import->writer->close(); // throw(404); return [ 'total_successed_row' => $imported_plan_data, 'total_failed_row' => count($failed_plan_data), - 'failed_row' => $failed_plan_data + 'failed_row' => $failed_plan_data, + 'result_file' => [ + 'url' => Storage::disk('public')->url('temp/result-'.$file_name), + 'name' => 'result-'.$file_name, + ] ]; } } diff --git a/Modules/Internal/Routes/api.php b/Modules/Internal/Routes/api.php index 258ac329..85e31a5b 100644 --- a/Modules/Internal/Routes/api.php +++ b/Modules/Internal/Routes/api.php @@ -37,6 +37,7 @@ Route::prefix('internal')->group(function () { }); Route::resource('corporates', CorporateController::class); + Route::post('corporates/{corporate_id}/import-plan-benefit', [CorporateController::class, 'importPlanBenefit']); Route::get('corporates/{corporate_id}/corporate-plans', [CorporatePlanController::class, 'index']); Route::post('corporates/{corporate_id}/corporate-plans', [CorporatePlanController::class, 'store']); diff --git a/Modules/Internal/Services/CorporateService.php b/Modules/Internal/Services/CorporateService.php new file mode 100644 index 00000000..a7c0e6a4 --- /dev/null +++ b/Modules/Internal/Services/CorporateService.php @@ -0,0 +1,99 @@ +id; + + $this->validatePlanRow($plan_data); + + $plan = Plan::updateOrCreate([ + 'code' => $plan_data['code'], + ], $plan_data); + + return $plan; + } catch (\Exception $e) { + throw $e; + } + } + + protected function validateBenefitRow($row) + { + if (empty($row['service_code'])) { + throw new ImportRowException(__('benefit.SERVICE_CODE_REQUIRED'), 0, null, $row); + } + if (empty($row['plan_code'])) { + throw new ImportRowException(__('benefit.PLAN_CODE_REQUIRED'), 0, null, $row); + } + if (empty($row['benefit_code'])) { + throw new ImportRowException(__('benefit.BENEFIT_CODE_REQUIRED'), 0, null, $row); + } + if (empty($row['code'])) { + throw new ImportRowException(__('benefit.CUSTOMER_BENEFIT_CODE_REQUIRED'), 0, null, $row); + } + if (empty($row['description'])) { + throw new ImportRowException(__('benefit.DESCRIPTION_REQUIRED'), 0, null, $row); + } + if (empty($row['limit_amount'])) { + throw new ImportRowException(__('benefit.LIMIT_AMOUNT_REQUIRED'), 0, null, $row); + } + if (empty($row['msc'])) { + throw new ImportRowException(__('benefit.MSC_REQUIRED'), 0, null, $row); + } + if (empty($row['genders'])) { + throw new ImportRowException(__('benefit.GENDER_REQUIRED'), 0, null, $row); + } + } + + public function handleBenefitRow(Corporate $corporate, $row) + { + try { + $benefit_data = $row; + $benefit_data["corporate_id"] = $corporate->id; + + $this->validateBenefitRow($benefit_data); + + $plan = Benefit::updateOrCreate([ + 'code' => $benefit_data['code'], + ], $benefit_data); + + return $plan; + } catch (\Exception $e) { + throw $e; + } + } +} diff --git a/app/Models/Plan.php b/app/Models/Plan.php index 8bf8308b..b31427e3 100644 --- a/app/Models/Plan.php +++ b/app/Models/Plan.php @@ -13,6 +13,7 @@ class Plan extends Model protected $fillable = [ "service_code", + "corporate_id", "corporate_plan_id", "code", "type", diff --git a/app/Services/ImportService.php b/app/Services/ImportService.php new file mode 100644 index 00000000..037800f9 --- /dev/null +++ b/app/Services/ImportService.php @@ -0,0 +1,85 @@ +reader = ReaderEntityFactory::createReaderFromFile($filePath); + $this->reader->open($filePath); + + return $this; + } + + public function readRow() + { + $row = $this->reader->getNextRow(); + return $row; + } + + public function sheetsIterator() + { + return $this->reader->getSheetIterator(); + } + + public function write($filePath, $format = 'xsls') + { + switch ($format) { + case 'xsls': + $this->writer = WriterEntityFactory::createXLSXWriter(); + break; + case 'csv': + $this->writer = WriterEntityFactory::createCSVWriter(); + break; + default: + throw new \Exception('Unknown format'); + } + $this->writer->openToFile($filePath); + $this->format = $format; + + return $this; + } + + public function makeCells($array_row_data) + { + $cells = []; + foreach ($array_row_data as $cellValue) { + $cells[] = WriterEntityFactory::createCell($cellValue); + } + + return $cells; + } + + public function makeRow($array) + { + return WriterEntityFactory::createRow($this->makeCells($array)); + } + + public function addArrayToRow($array, $sheetName = null) + { + // Switch to the correct Sheet Before Write + if ($sheetName) { + if ($sheetName != $this->writer->getCurrentSheet()->getName()) { + + foreach ($this->writer->getSheetIterator() as $sheet) { + if ($sheet->getName() == $sheet) { + $this->writer->setCurrentSheet($sheet); + break; + } + } + } + } + + $newRow = $this->makeRow($array); + $this->writer->addRow($newRow); + + return $this; + } +} diff --git a/database/migrations/2022_06_23_083834_create_plans_table.php b/database/migrations/2022_06_23_083834_create_plans_table.php index 5bf4d669..6bccc9b3 100644 --- a/database/migrations/2022_06_23_083834_create_plans_table.php +++ b/database/migrations/2022_06_23_083834_create_plans_table.php @@ -16,6 +16,7 @@ return new class extends Migration Schema::create('plans', function (Blueprint $table) { $table->id(); $table->string('service_code')->index(); + $table->string('corporate_id')->index(); $table->string('corporate_plan_id')->index(); $table->string('code')->index(); $table->string('type')->nullable(); @@ -70,6 +71,8 @@ return new class extends Migration $table->foreignId('created_by')->nullable()->index(); $table->foreignId('updated_by')->nullable()->index(); $table->foreignId('deleted_by')->nullable()->index(); + + $table->index(['corporate_id', 'code']); }); } diff --git a/frontend/dashboard/src/pages/Corporates/CorporateTabNavigations.tsx b/frontend/dashboard/src/pages/Corporates/CorporateTabNavigations.tsx index f11e7b35..b404c5e5 100644 --- a/frontend/dashboard/src/pages/Corporates/CorporateTabNavigations.tsx +++ b/frontend/dashboard/src/pages/Corporates/CorporateTabNavigations.tsx @@ -19,10 +19,10 @@ export default function CorporateTabNavigations({ position }: Props) { 'path' : '', 'label': 'Dashboard', }, - { - 'path' : 'corporate-plans', - 'label': 'Corporate Plan', - }, + // { + // 'path' : 'corporate-plans', + // 'label': 'Corporate Plan', + // }, { 'path' : 'plans', 'label': 'Plans',