From 71e70b01dae31ade72405434a65fc7fd9295cda0 Mon Sep 17 00:00:00 2001 From: pbao9 Date: Sat, 21 Feb 2026 11:28:03 +0700 Subject: [PATCH] feat: implement import and export functionality for product categories and products --- app/Exports/ProductCategoriesExport.php | 44 ++++++++++++ app/Exports/ProductsExport.php | 72 +++++++++++++++++++ .../product/ProductCategoryController.php | 32 +++++++++ .../admin/product/ProductController.php | 33 ++++++++- app/Imports/ProductCategoriesImport.php | 46 ++++++++++++ app/Imports/ProductsImport.php | 59 +++++++++++++++ .../pages/block/product/btn.blade.php | 32 ++++++++- .../block/product/btn_categories.blade.php | 35 +++++++++ .../pages/product/categories/index.blade.php | 2 +- .../pages/product/index.blade.php | 4 +- routes/admin.php | 4 ++ 11 files changed, 359 insertions(+), 4 deletions(-) create mode 100644 app/Exports/ProductCategoriesExport.php create mode 100644 app/Exports/ProductsExport.php create mode 100644 app/Imports/ProductCategoriesImport.php create mode 100644 app/Imports/ProductsImport.php create mode 100644 resources/views/administrator/pages/block/product/btn_categories.blade.php diff --git a/app/Exports/ProductCategoriesExport.php b/app/Exports/ProductCategoriesExport.php new file mode 100644 index 0000000..88bdf48 --- /dev/null +++ b/app/Exports/ProductCategoriesExport.php @@ -0,0 +1,44 @@ +get(); + } + + public function headings(): array + { + return [ + 'ID', + 'Name', + 'Slug', + 'Parent Name', + 'Parent ID', + 'Type', + 'Is Active', + 'Sort Order', + ]; + } + + public function map($category): array + { + return [ + $category->id, + $category->name, + $category->slug, + $category->parent?->name, + $category->parent_id, + $category->type, + $category->is_active ? 'Yes' : 'No', + $category->sort_order, + ]; + } +} diff --git a/app/Exports/ProductsExport.php b/app/Exports/ProductsExport.php new file mode 100644 index 0000000..418d654 --- /dev/null +++ b/app/Exports/ProductsExport.php @@ -0,0 +1,72 @@ +get(); + } + + public function headings(): array + { + return [ + 'ID', + 'Name', + 'Slug', + 'Category', + 'Category ID', + 'Short Description', + 'Description', + 'Setup Fee', + 'Trial Days', + 'CPU', + 'RAM', + 'GPU', + 'Disk', + 'Bandwidth', + 'Stock', + 'Is Unlimited', + 'Is Active', + 'Sort Order', + 'Product Type', + 'Published At', + 'Monthly Price', + 'Sale Price', + ]; + } + + public function map($product): array + { + return [ + $product->id, + $product->name, + $product->slug, + $product->category?->name, + $product->product_categories_id, + $product->short_description, + $product->description, + $product->setup_fee, + $product->trial_days, + $product->cpu, + $product->ram, + $product->gpu, + $product->disk, + $product->bandwidth, + $product->stock, + $product->is_unlimited ? 'Yes' : 'No', + $product->is_active ? 'Yes' : 'No', + $product->sort_order, + $product->product_type, + $product->published_at?->format('Y-m-d'), + $product->pricings->where('billing_cycle', 'monthly')->where('is_active', 1)->first()?->price ?? '', + $product->pricings->where('billing_cycle', 'monthly')->where('is_active', 1)->first()?->price_sale ?? '', + ]; + } +} diff --git a/app/Http/Controllers/admin/product/ProductCategoryController.php b/app/Http/Controllers/admin/product/ProductCategoryController.php index b0bf0aa..2d0cab9 100644 --- a/app/Http/Controllers/admin/product/ProductCategoryController.php +++ b/app/Http/Controllers/admin/product/ProductCategoryController.php @@ -6,7 +6,10 @@ use App\Http\Controllers\Controller; use App\Http\Requests\ProductCategories\ProductCategoryRequest; use App\Http\Services\Admin\ProductCategory\ProductCategoryServiceInterface; use App\Repositories\Admin\ProductCategory\ProductCategoryRepositoryInterface; +use App\Exports\ProductCategoriesExport; +use App\Imports\ProductCategoriesImport; use Illuminate\Http\Request; +use Maatwebsite\Excel\Facades\Excel; use Yajra\DataTables\Facades\DataTables; @@ -139,4 +142,33 @@ class ProductCategoryController extends Controller ->rawColumns(['details_control', 'action', 'is_active', 'checkbox', 'sub_group_html']) ->make(true); } + + public function export(Request $request) + { + if ($request->get('sample') == 1) { + return Excel::download(new ProductCategoriesExport(), 'product_categories_sample.xlsx'); + } + return Excel::download(new ProductCategoriesExport(), 'product_categories_' . date('Y-m-d') . '.xlsx'); + } + + public function import(Request $request) + { + $request->validate([ + 'file' => 'required|mimes:xlsx,xls,csv' + ]); + + try { + Excel::import(new ProductCategoriesImport(), $request->file('file')); + return redirect()->route('products.group.index')->with('success', 'Import product categories successfully'); + } catch (\Maatwebsite\Excel\Validators\ValidationException $e) { + $failures = $e->failures(); + $errorMessage = 'Import failed at row(s): '; + foreach ($failures as $failure) { + $errorMessage .= $failure->row() . ' (' . implode(', ', $failure->errors()) . '); '; + } + return back()->withErrors($errorMessage); + } catch (\Exception $e) { + return back()->withErrors('Import failed: ' . $e->getMessage()); + } + } } \ No newline at end of file diff --git a/app/Http/Controllers/admin/product/ProductController.php b/app/Http/Controllers/admin/product/ProductController.php index 67c9c92..93a361f 100644 --- a/app/Http/Controllers/admin/product/ProductController.php +++ b/app/Http/Controllers/admin/product/ProductController.php @@ -2,14 +2,16 @@ namespace App\Http\Controllers\admin\product; +use App\Exports\ProductsExport; +use App\Imports\ProductsImport; use App\Enums\Product\ProductTypeEnums; use App\Http\Controllers\Controller; use App\Http\Requests\Product\ProductRequest; use App\Http\Services\ProductService; use App\Repositories\Group\GroupRepositoryInterface; use App\Repositories\Admin\Product\ProductRepositoryInterface; - use Illuminate\Http\Request; +use Maatwebsite\Excel\Facades\Excel; use Yajra\DataTables\Facades\DataTables; use Exception; @@ -222,4 +224,33 @@ class ProductController extends Controller default => 0 }; } + + public function export(Request $request) + { + if ($request->get('sample') == 1) { + return Excel::download(new ProductsExport(), 'products_sample.xlsx'); + } + return Excel::download(new ProductsExport(), 'products_' . date('Y-m-d') . '.xlsx'); + } + + public function import(Request $request) + { + $request->validate([ + 'file' => 'required|mimes:xlsx,xls,csv' + ]); + + try { + Excel::import(new ProductsImport(), $request->file('file')); + return redirect()->route('products.index')->with('success', 'Import products successfully'); + } catch (\Maatwebsite\Excel\Validators\ValidationException $e) { + $failures = $e->failures(); + $errorMessage = 'Import failed at row(s): '; + foreach ($failures as $failure) { + $errorMessage .= $failure->row() . ' (' . implode(', ', $failure->errors()) . '); '; + } + return back()->withErrors($errorMessage); + } catch (\Exception $e) { + return back()->withErrors('Import failed: ' . $e->getMessage()); + } + } } diff --git a/app/Imports/ProductCategoriesImport.php b/app/Imports/ProductCategoriesImport.php new file mode 100644 index 0000000..e4bc366 --- /dev/null +++ b/app/Imports/ProductCategoriesImport.php @@ -0,0 +1,46 @@ +first(); + $parentId = $parent?->id; + } + + if (!empty($row['parent_id'])) { + $parentId = (int) $row['parent_id']; + } + + return new ProductCategories([ + 'name' => $row['name'], + 'slug' => $row['slug'] ?? Str::slug($row['name']), + 'parent_id' => $parentId, + 'type' => $row['type'] ?? null, + 'is_active' => in_array(strtolower($row['is_active'] ?? ''), ['yes', '1', 'true']), + 'sort_order' => $row['sort_order'] ?? 0, + ]); + } + + public function rules(): array + { + return [ + 'name' => 'required|string|max:255', + 'slug' => 'nullable|string|max:255', + 'parent_name' => 'nullable|string|max:255', + 'parent_id' => 'nullable|integer|exists:product_categories,id', + 'type' => 'nullable|string|max:50', + ]; + } +} diff --git a/app/Imports/ProductsImport.php b/app/Imports/ProductsImport.php new file mode 100644 index 0000000..002778c --- /dev/null +++ b/app/Imports/ProductsImport.php @@ -0,0 +1,59 @@ +first(); + $categoryId = $category?->id; + } + + if (!empty($row['category_id'])) { + $categoryId = (int) $row['category_id']; + } + + return new Product([ + 'uuid' => (string) Str::uuid(), + 'name' => $row['name'], + 'slug' => $row['slug'] ?? Str::slug($row['name']), + 'product_categories_id' => $categoryId, + 'short_description' => $row['short_description'] ?? null, + 'description' => $row['description'] ?? null, + 'setup_fee' => $row['setup_fee'] ?? 0, + 'trial_days' => $row['trial_days'] ?? 0, + 'cpu' => $row['cpu'] ?? null, + 'ram' => $row['ram'] ?? null, + 'gpu' => $row['gpu'] ?? null, + 'disk' => $row['disk'] ?? null, + 'bandwidth' => $row['bandwidth'] ?? null, + 'stock' => $row['stock'] ?? 0, + 'is_unlimited' => in_array(strtolower($row['is_unlimited'] ?? ''), ['yes', '1', 'true']), + 'is_active' => in_array(strtolower($row['is_active'] ?? ''), ['yes', '1', 'true']), + 'sort_order' => $row['sort_order'] ?? 0, + 'published_at' => !empty($row['published_at']) ? \Carbon\Carbon::parse($row['published_at']) : null, + ]); + } + + public function rules(): array + { + return [ + 'name' => 'required|string|max:255', + 'slug' => 'nullable|string|max:255', + 'category' => 'nullable|string|max:255', + 'category_id' => 'nullable|integer|exists:product_categories,id', + ]; + } +} diff --git a/resources/views/administrator/pages/block/product/btn.blade.php b/resources/views/administrator/pages/block/product/btn.blade.php index fa02499..997499c 100644 --- a/resources/views/administrator/pages/block/product/btn.blade.php +++ b/resources/views/administrator/pages/block/product/btn.blade.php @@ -7,6 +7,36 @@ Thêm nhóm SP + Export + Import Thùng rác @if($trashedCount > 0){{ $trashedCount }}@endif -@endif \ No newline at end of file + +@push('modals') + +@endpush +@endif diff --git a/resources/views/administrator/pages/block/product/btn_categories.blade.php b/resources/views/administrator/pages/block/product/btn_categories.blade.php new file mode 100644 index 0000000..c829f42 --- /dev/null +++ b/resources/views/administrator/pages/block/product/btn_categories.blade.php @@ -0,0 +1,35 @@ +@if(auth()->guard('admins')->user()->can("manager product")) + Thêm + nhóm SP + Export + Import + Thùng rác + +@push('modals') + +@endpush +@endif diff --git a/resources/views/administrator/pages/product/categories/index.blade.php b/resources/views/administrator/pages/product/categories/index.blade.php index 9555baf..4cf76c0 100644 --- a/resources/views/administrator/pages/product/categories/index.blade.php +++ b/resources/views/administrator/pages/product/categories/index.blade.php @@ -36,7 +36,7 @@ @if (auth()->guard('admins')->user()->can('manager product'))
- @include('administrator.pages.block.product.btn') + @include('administrator.pages.block.product.btn_categories')
@endforeach -@endpush \ No newline at end of file +@endpush + +@stack('modals') \ No newline at end of file diff --git a/routes/admin.php b/routes/admin.php index b490b70..04827a6 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -147,6 +147,8 @@ Route::prefix('')->group(function () { Route::delete('remove-product-item/{id}', 'destroy')->name('destroy'); Route::post('duplicate-product/{id}', 'duplicate')->name('duplicate'); Route::get('datatable', 'datatable')->name('datatable'); + Route::get('export', 'export')->name('export'); + Route::post('import', 'import')->name('import'); Route::controller(ProductCategoryController::class)->prefix('group')->as('group.')->group(function () { Route::get('all-group', 'index')->name('index'); @@ -160,6 +162,8 @@ Route::prefix('')->group(function () { Route::get('trash', 'trash')->name('trash'); Route::get('restore/{id}', 'restore')->name('restore'); Route::delete('force-delete/{id}', 'forceDelete')->name('forceDelete'); + Route::get('export', 'export')->name('export'); + Route::post('import', 'import')->name('import'); }); });