Files
star-cloud/app/Http/Controllers/Admin/ProductController.php
2026-03-30 17:11:15 +08:00

346 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Product\Product;
use App\Models\Product\ProductCategory;
use App\Models\System\Company;
use App\Models\System\Translation;
use App\Traits\ImageHandler;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class ProductController extends Controller
{
use \App\Traits\ImageHandler;
public function index(Request $request)
{
$user = auth()->user();
$query = Product::with(['category', 'translations', 'company']);
// 搜尋
if ($request->filled('search')) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('barcode', 'like', "%{$search}%")
->orWhere('spec', 'like', "%{$search}%");
});
}
// 分類篩選
if ($request->filled('category_id')) {
$query->where('category_id', $request->category_id);
}
$per_page = $request->input('per_page', 10);
$companyId = $user->company_id;
if ($user->isSystemAdmin()) {
if ($request->filled('company_id')) {
$companyId = $request->company_id;
$query->where('company_id', $companyId);
}
}
$products = $query->latest()->paginate($per_page)->withQueryString();
$categories = ProductCategory::all();
$companies = $user->isSystemAdmin() ? Company::all() : collect();
// 系統管理員在過濾特定公司時,應顯示該公司的功能開關 (如物料代碼、點數規則)
$selectedCompany = $companyId ? Company::find($companyId) : $user->company;
$companySettings = $selectedCompany ? ($selectedCompany->settings ?? []) : [];
$routeName = 'admin.data-config.products.index';
return view('admin.products.index', [
'products' => $products,
'categories' => $categories,
'companies' => $companies,
'companySettings' => $companySettings,
'routeName' => $routeName
]);
}
public function create(Request $request)
{
$user = auth()->user();
$categories = ProductCategory::all();
$companies = $user->isSystemAdmin() ? Company::all() : collect();
// If system admin, check if company_id is provided in URL to get settings
$companyId = $request->query('company_id') ?? $user->company_id;
$selectedCompany = $companyId ? Company::find($companyId) : $user->company;
$companySettings = $selectedCompany ? ($selectedCompany->settings ?? []) : [];
return view('admin.products.create', [
'categories' => $categories,
'companies' => $companies,
'companySettings' => $companySettings,
]);
}
public function edit($id)
{
$user = auth()->user();
// 繞過 TenantScoped 載入翻譯,確保系統管理員能看到租戶公司的翻譯資料
$product = Product::with(['company'])->findOrFail($id);
$product->setRelation('translations',
Translation::withoutGlobalScopes()
->where('group', 'product')
->where('key', $product->name_dictionary_key)
->get()
);
$categories = ProductCategory::all();
$companies = $user->isSystemAdmin() ? Company::all() : collect();
// Use the product's company settings for editing
$companySettings = $product->company ? ($product->company->settings ?? []) : [];
return view('admin.products.edit', [
'product' => $product,
'categories' => $categories,
'companies' => $companies,
'companySettings' => $companySettings,
]);
}
public function store(Request $request)
{
$validated = $request->validate([
'names.zh_TW' => 'required|string|max:255',
'names.en' => 'nullable|string|max:255',
'names.ja' => 'nullable|string|max:255',
'barcode' => 'nullable|string|max:100',
'spec' => 'nullable|string|max:255',
'category_id' => 'nullable|exists:product_categories,id',
'manufacturer' => 'nullable|string|max:255',
'track_limit' => 'required|integer|min:1',
'spring_limit' => 'required|integer|min:1',
'price' => 'required|numeric|min:0',
'cost' => 'required|numeric|min:0',
'member_price' => 'required|numeric|min:0',
'metadata' => 'nullable|array',
'is_active' => 'nullable|boolean',
'company_id' => 'nullable|exists:companies,id',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
]);
try {
DB::beginTransaction();
$dictKey = \Illuminate\Support\Str::uuid()->toString();
// Determine company_id: prioritized from request (for sys admin) then from user
$company_id = (auth()->user()->isSystemAdmin() && $request->filled('company_id'))
? $request->company_id
: auth()->user()->company_id;
// 儲存多語系翻譯(繞過 TenantScoped避免系統管理員操作租戶資料時被過濾
foreach ($request->names as $locale => $name) {
if (empty($name)) continue;
Translation::withoutGlobalScopes()->create([
'group' => 'product',
'key' => $dictKey,
'locale' => $locale,
'value' => $name,
'company_id' => $company_id,
]);
}
$imageUrl = null;
if ($request->hasFile('image')) {
$path = $this->storeAsWebp($request->file('image'), 'products');
$imageUrl = Storage::url($path);
}
$product = Product::create([
'company_id' => $company_id,
'category_id' => $request->category_id,
'name' => $request->names['zh_TW'] ?? (collect($request->names)->first() ?? 'Untitled'), // Fallback if zh_TW is missing
'name_dictionary_key' => $dictKey,
'image_url' => $imageUrl,
'barcode' => $request->barcode,
'spec' => $request->spec,
'manufacturer' => $request->manufacturer,
'track_limit' => $request->track_limit,
'spring_limit' => $request->spring_limit,
'price' => $request->price,
'cost' => $request->cost,
'member_price' => $request->member_price,
'metadata' => $request->metadata ?? [],
'is_active' => $request->boolean('is_active', true),
]);
DB::commit();
if ($request->wantsJson()) {
return response()->json([
'success' => true,
'message' => __('Product created successfully'),
'data' => $product
]);
}
return redirect()->route('admin.data-config.products.index')->with('success', __('Product created successfully'));
} catch (\Exception $e) {
DB::rollBack();
if ($request->wantsJson()) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
}
return redirect()->back()->with('error', $e->getMessage())->withInput();
}
}
public function update(Request $request, $id)
{
$product = Product::findOrFail($id);
$validated = $request->validate([
'names.zh_TW' => 'required|string|max:255',
'names.en' => 'nullable|string|max:255',
'names.ja' => 'nullable|string|max:255',
'barcode' => 'nullable|string|max:100',
'spec' => 'nullable|string|max:255',
'category_id' => 'nullable|exists:product_categories,id',
'manufacturer' => 'nullable|string|max:255',
'track_limit' => 'required|integer|min:1',
'spring_limit' => 'required|integer|min:1',
'price' => 'required|numeric|min:0',
'cost' => 'required|numeric|min:0',
'member_price' => 'required|numeric|min:0',
'metadata' => 'nullable|array',
'is_active' => 'nullable|boolean',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
'remove_image' => 'nullable|boolean',
]);
try {
DB::beginTransaction();
$dictKey = $product->name_dictionary_key ?: \Illuminate\Support\Str::uuid()->toString();
$company_id = $product->company_id;
// 更新或建立多語系翻譯(繞過 TenantScoped避免系統管理員操作租戶資料時被過濾
foreach ($request->names as $locale => $name) {
if (empty($name)) {
Translation::withoutGlobalScopes()->where([
'group' => 'product',
'key' => $dictKey,
'locale' => $locale
])->delete();
continue;
}
Translation::withoutGlobalScopes()->updateOrCreate(
[
'group' => 'product',
'key' => $dictKey,
'locale' => $locale,
],
[
'value' => $name,
'company_id' => $company_id,
]
);
}
$data = [
'category_id' => $request->category_id,
'name' => $request->names['zh_TW'] ?? ($product->name ?? 'Untitled'),
'name_dictionary_key' => $dictKey,
'barcode' => $request->barcode,
'spec' => $request->spec,
'manufacturer' => $request->manufacturer,
'track_limit' => $request->track_limit,
'spring_limit' => $request->spring_limit,
'price' => $request->price,
'cost' => $request->cost,
'member_price' => $request->member_price,
'metadata' => $request->metadata ?? [],
'is_active' => $request->boolean('is_active', true),
];
if ($request->hasFile('image')) {
// Delete old image
if ($product->image_url) {
$oldPath = str_replace('/storage/', '', $product->image_url);
Storage::disk('public')->delete($oldPath);
}
$path = $this->storeAsWebp($request->file('image'), 'products');
$data['image_url'] = Storage::url($path);
} elseif ($request->boolean('remove_image')) {
if ($product->image_url) {
$oldPath = str_replace('/storage/', '', $product->image_url);
Storage::disk('public')->delete($oldPath);
}
$data['image_url'] = null;
}
$product->update($data);
DB::commit();
if ($request->wantsJson()) {
return response()->json([
'success' => true,
'message' => __('Product updated successfully'),
'data' => $product
]);
}
return redirect()->route('admin.data-config.products.index')->with('success', __('Product updated successfully'));
} catch (\Exception $e) {
DB::rollBack();
if ($request->wantsJson()) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 500);
}
return redirect()->back()->with('error', $e->getMessage())->withInput();
}
}
public function toggleStatus($id)
{
try {
$product = Product::findOrFail($id);
$product->is_active = !$product->is_active;
$product->save();
$status = $product->is_active ? __('Enabled') : __('Disabled');
return redirect()->back()->with('success', __('Product status updated to :status', ['status' => $status]));
} catch (\Exception $e) {
return redirect()->back()->with('error', $e->getMessage());
}
}
public function destroy($id)
{
try {
$product = Product::findOrFail($id);
// 刪除與此商品關聯的翻譯資料(繞過 TenantScoped
if ($product->name_dictionary_key) {
Translation::withoutGlobalScopes()->where('key', $product->name_dictionary_key)->delete();
}
// Delete image
if ($product->image_url) {
$oldPath = str_replace('/storage/', '', $product->image_url);
Storage::disk('public')->delete($oldPath);
}
$product->delete();
return redirect()->back()->with('success', __('Product deleted successfully'));
} catch (\Exception $e) {
return redirect()->back()->with('error', $e->getMessage());
}
}
}