diff --git a/app/Admin/Controllers/OldmenController.php b/app/Admin/Controllers/OldmenController.php index f651b5d..664136d 100644 --- a/app/Admin/Controllers/OldmenController.php +++ b/app/Admin/Controllers/OldmenController.php @@ -7,7 +7,6 @@ use Slowlyo\OwlAdmin\Models\AdminSetting; use Slowlyo\OwlAdmin\Renderers\Page; use Slowlyo\OwlAdmin\Renderers\Form; use Slowlyo\OwlAdmin\Renderers\TableColumn; -use Slowlyo\OwlAdmin\Renderers\TextControl; use Slowlyo\OwlAdmin\Controllers\AdminController; use App\Services\Admin\OldmenService; use Illuminate\Http\Request; @@ -15,6 +14,9 @@ use App\Admin\Components; use App\Models\ConstFlow; use App\Models\Oldmen; use Carbon\Carbon; +use App\Services\Admin\ImportService; +use App\Models\ImportJob; +use App\Imports\Oldmen as OldmenImport; /** * @property OldmenService $service @@ -40,7 +42,7 @@ class OldmenController extends AdminController amisMake()->DialogAction()->dialog( amisMake()->Dialog()->title('导入客人信息')->body([ amisMake()->Form()->title('') - ->api('')//处理实际上传逻辑-todo + ->api(admin_url('oldmen-import'))//处理实际上传逻辑 ->body([ amisMake()->FileControl('file', '导入文件')->accept('.xlsx')->receiver('/upload_file'),//文件上传地址待处理 ]), @@ -502,4 +504,19 @@ class OldmenController extends AdminController } return $this->response()->success($page); } + + public function import(Request $request){ + + + $job = new ImportJob(); + $job->name = '客人基础信息导入【'.now()->toDateTimeString().'】'; + $job->file = $request->input('file'); + $job->type = OldmenImport::class; + $job->status = 1; + $job->save(); + $importService = new ImportService(); + $importService->import($job); + + return $this->response()->success(); + } } diff --git a/app/Admin/routes.php b/app/Admin/routes.php index 08c8fd9..e22af3a 100644 --- a/app/Admin/routes.php +++ b/app/Admin/routes.php @@ -25,6 +25,8 @@ Route::group([ //客人管理 $router->resource('oldmen', \App\Admin\Controllers\OldmenController::class)->names('oldmen'); + $router->post('oldmen-import', '\App\Admin\Controllers\OldmenController@import'); + $router->get('live-feelist', '\App\Admin\Controllers\OldmenController@liveFeelist'); $router->get('exit-feelist', '\App\Admin\Controllers\OldmenController@exitFeelist'); $router->get('live-fee-form', '\App\Admin\Controllers\OldmenController@liveSchemaForm'); diff --git a/app/Exceptions/BizException.php b/app/Exceptions/BizException.php new file mode 100644 index 0000000..f4d3520 --- /dev/null +++ b/app/Exceptions/BizException.php @@ -0,0 +1,37 @@ +status = $status; + + return $this; + } + + /** + * 报告异常 + * + * @return mixed + */ + public function report() + { + } +} diff --git a/app/Exceptions/ImportException.php b/app/Exceptions/ImportException.php new file mode 100644 index 0000000..12ad709 --- /dev/null +++ b/app/Exceptions/ImportException.php @@ -0,0 +1,11 @@ +put($path, file_get_contents(str_replace(config('filesystems.disks.aliyun.domain'), config('filesystems.disks.aliyun.bucket').'.'.config('filesystems.disks.aliyun.endpoint'), $url))); + $file = new File(storage_path('app'.$path)); + return $this->readFile($file); + } + + public function readFile($file) + { + $reader = ReaderEntityFactory::createXLSXReader(); + $reader->open($file); + + $success = 0; + $fails = 0; + $errors = []; + + foreach ($reader->getSheetIterator() as $sheet) { + foreach ($sheet->getRowIterator() as $num => $row) { + if ($num === 1) { + continue; + } + try { + DB::beginTransaction(); + $this->loadRow($row); + $success++; + DB::commit(); + } catch (ImportException $e) { + DB::rollBack(); + $fails++; + $errors[] = [ + 'row'=>$num, + 'reason'=>$e->getMessage(), + ]; + } catch (Throwable $e) { + DB::rollBack(); + $fails++; + $errors[] = [ + 'row'=>$num, + 'reason'=>$e->getMessage(), + ]; + } + } + + break; + } + + $reader->close(); + return [ + 'success'=>$success, + 'fails'=>$fails, + 'errors'=>$errors, + ]; + } + + public function loadRow($row) + { + return; + } +} diff --git a/app/Imports/Oldmen.php b/app/Imports/Oldmen.php new file mode 100644 index 0000000..f69b010 --- /dev/null +++ b/app/Imports/Oldmen.php @@ -0,0 +1,60 @@ +getCellAtIndex(0)?->getValue() ?? throw new ImportException('未填写身份证号码');//身份证号 + $name = $row->getCellAtIndex(1)?->getValue() ?? throw new ImportException('未填写姓名');//姓名 + $sex = $row->getCellAtIndex(2)?->getValue() ?? throw new ImportException('未填写性别');//性别 + $birthDate = $row->getCellAtIndex(3)?->getValue() ?? throw new ImportException('未填写生日');//生日 + $cardCity = $row->getCellAtIndex(4)?->getValue() ?? throw new ImportException('未填写地址省份');//省 + $cardProvince = $row->getCellAtIndex(5)?->getValue() ?? throw new ImportException('未填写地址-市');//市 + $cardArea = $row->getCellAtIndex(6)?->getValue() ?? throw new ImportException('未填写地址-区');//区 + $cardAddress = $row->getCellAtIndex(7)?->getValue() ?? throw new ImportException('未填写详细地址');//详细地址 + $agreementNo = $row->getCellAtIndex(8)?->getValue() ?? '';//协议号码 + $nurseLvName = $row->getCellAtIndex(9)?->getValue() ?? throw new ImportException('未填写护理等级');//护理等级 + $clientName = $row->getCellAtIndex(10)?->getValue() ?? throw new ImportException('未填写监护人姓名');//监护人姓名 + $clientPhone = $row->getCellAtIndex(11)?->getValue() ?? throw new ImportException('未填写监护人手机号码');//监护人手机号 + $clientCity = $row->getCellAtIndex(12)?->getValue() ?? throw new ImportException('未填写监护人地址省份'); + $clientProvince = $row->getCellAtIndex(13)?->getValue() ?? throw new ImportException('未填写监护人地址-市');//市 + $clientArea = $row->getCellAtIndex(14)?->getValue() ?? throw new ImportException('未填写监护人地址-区');//区 + $clientAddress = $row->getCellAtIndex(15)?->getValue() ?? throw new ImportException('未填写监护人详细地址');//详细地址 + + if(ModelsOldmen::where('card_no', $cardNo)->exists()){//如果已存在,则为更新 + $oldman = ModelsOldmen::where('card_no', $cardNo)->first(); + $newLv = Keyword::where(['type_key'=>'nurse_lv', 'name'=>$nurseLvName])->value('value'); + if($oldman->nurse_lv !== $newLv && $oldman->live_in > 0){ + throw new ImportException('当前入住状态无法直接变更护理等级'); + }K; + }else{ + $oldman = new ModelsOldmen(); + $oldman->card_no = $cardNo; + $oldman->nurse_lv = Keyword::where(['type_key'=>'nurse_lv', 'name'=>$nurseLvName])->value('value'); + } + $oldman->name = $name; + $sexArr = [ + '未知'=>0, + '男'=>1, + '女'=>2 + ]; + $oldman->sex = $sexArr[$sex]; + $oldman->birthday = Carbon::parse($birthDate); + $oldman->card_city_code = Zone::where(['name' => $cardArea, 'type'=>'area'])->value('code') ?? ''; + $oldman->card_address = $cardAddress; + $oldman->agreement_no = $agreementNo; + $oldman->client_name = $clientName; + $oldman->client_phone = $clientPhone; + $oldman->client_city_code = Zone::where(['name' => $clientArea, 'type'=>'area'])->value('code') ?? ''; + $oldman->client_address = $clientAddress; + $oldman->save(); + } +} diff --git a/app/Jobs/ImportJob.php b/app/Jobs/ImportJob.php new file mode 100644 index 0000000..f0b31cf --- /dev/null +++ b/app/Jobs/ImportJob.php @@ -0,0 +1,41 @@ +importJob = $job; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + if(env('APP_DEBUG')){ + \Log::info('执行文件导入'); + } + (new ImportService())->import($this->importJob); + } +} diff --git a/app/Models/ImportJob.php b/app/Models/ImportJob.php new file mode 100644 index 0000000..65eafea --- /dev/null +++ b/app/Models/ImportJob.php @@ -0,0 +1,19 @@ +'失败', + self::STATUS_SUCCESS=>'成功', + ]; + + public function job() + { + return $this->belongsTo(ImportJob::class); + } +} diff --git a/app/Services/Admin/ImportService.php b/app/Services/Admin/ImportService.php new file mode 100644 index 0000000..9c6b0d8 --- /dev/null +++ b/app/Services/Admin/ImportService.php @@ -0,0 +1,47 @@ +status == 1) { + $this->driver = new $job->type(); + if(strpos($job->file, 'http') !== false) { + $res = $this->driver->readFileByUrl($job->file); + }else{ + $file = new File(public_path('storage/'.$job->file)); + $res = $this->driver->readFile($file); + } + + if ($res) { + $job->update([ + 'status'=>2, + 'success' => $res['success']??0, + 'fails'=> $res['fails']??0, + ]); + } + if (isset($res['errors']) && count($res['errors']) > 0) { + $this->createErrorLogs($job, $res['errors']); + } + } + } + + public function createErrorLogs(ImportJob $job, array $errors) + { + ImportJobLog::insert(array_map(function ($value) use ($job) { + return array_merge($value, [ + 'job_id'=>$job->id, + 'created_at' => now(), + 'updated_at' => now(), + ]); + }, $errors)); + } +} diff --git a/composer.json b/composer.json index 9c7a4f1..a1f7b1c 100644 --- a/composer.json +++ b/composer.json @@ -6,6 +6,7 @@ "license": "MIT", "require": { "php": "^8.1", + "box/spout": "^3.3", "guzzlehttp/guzzle": "^7.2", "laravel/framework": "^10.10", "laravel/sanctum": "^3.2", diff --git a/composer.lock b/composer.lock index 9f3cdd3..5d777cf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,83 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a483aafe1cb9cc540a4db897e1c35ac4", + "content-hash": "4a5297a88397d5f65572b83c55b33701", "packages": [ + { + "name": "box/spout", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/box/spout.git", + "reference": "9bdb027d312b732515b884a341c0ad70372c6295" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/box/spout/zipball/9bdb027d312b732515b884a341c0ad70372c6295", + "reference": "9bdb027d312b732515b884a341c0ad70372c6295", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlreader": "*", + "ext-zip": "*", + "php": ">=7.2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2", + "phpunit/phpunit": "^8" + }, + "suggest": { + "ext-iconv": "To handle non UTF-8 CSV files (if \"php-intl\" is not already installed or is too limited)", + "ext-intl": "To handle non UTF-8 CSV files (if \"iconv\" is not already installed)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Box\\Spout\\": "src/Spout" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Adrien Loison", + "email": "adrien@box.com" + } + ], + "description": "PHP Library to read and write spreadsheet files (CSV, XLSX and ODS), in a fast and scalable way", + "homepage": "https://www.github.com/box/spout", + "keywords": [ + "OOXML", + "csv", + "excel", + "memory", + "odf", + "ods", + "office", + "open", + "php", + "read", + "scale", + "spreadsheet", + "stream", + "write", + "xlsx" + ], + "support": { + "issues": "https://github.com/box/spout/issues", + "source": "https://github.com/box/spout/tree/v3.3.0" + }, + "abandoned": true, + "time": "2021-05-14T21:18:09+00:00" + }, { "name": "brick/math", "version": "0.11.0", diff --git a/database/migrations/2023_06_29_152138_create_import_jobs_table.php b/database/migrations/2023_06_29_152138_create_import_jobs_table.php new file mode 100644 index 0000000..23cdd1e --- /dev/null +++ b/database/migrations/2023_06_29_152138_create_import_jobs_table.php @@ -0,0 +1,44 @@ +id(); + $table->string('name')->nullable()->comment('名称'); + $table->string('file')->comment('导入的文件路径'); + $table->string('type')->comment('导入执行类'); + $table->unsignedTinyInteger('status')->default(0)->comment('状态:0未开始,1导入中,2完成'); + $table->unsignedInteger('success')->default(0)->comment('成功条数'); + $table->unsignedInteger('fails')->default(0)->comment('失败条数'); + // $table->unsignedBigInteger('') + $table->timestamps(); + }); + + Schema::create('import_job_logs', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('job_id')->comment('任务ID'); + $table->unsignedInteger('row')->default(0)->comment('行数'); + $table->unsignedTinyInteger('status')->default(0)->comment('状态1成功'); + $table->text('reason')->nullable()->comment('原因'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('import_jobs'); + Schema::dropIfExists('import_job_logs'); + } +}; diff --git a/public/tmp/客人信息导入模板.xlsx b/public/tmp/客人信息导入模板.xlsx index 502b986..7014f87 100644 Binary files a/public/tmp/客人信息导入模板.xlsx and b/public/tmp/客人信息导入模板.xlsx differ