header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('sales.products.index')); } $categories = SalesProductCategory::active() ->ordered() ->with(['products' => fn ($q) => $q->ordered()]) ->get(); $currentCategoryCode = $request->input('category', $categories->first()?->code); $currentCategory = $categories->firstWhere('code', $currentCategoryCode) ?? $categories->first(); return view('sales.products.index', compact('categories', 'currentCategory')); } /** * 상품 목록 (HTMX용) */ public function productList(Request $request): View { $categoryCode = $request->input('category'); $category = SalesProductCategory::where('code', $categoryCode) ->with(['products' => fn ($q) => $q->ordered()]) ->first(); return view('sales.products.partials.product-list', compact('category')); } /** * 상품 저장 */ public function store(Request $request): JsonResponse { $validated = $request->validate([ 'category_id' => 'required|exists:sales_product_categories,id', 'code' => 'required|string|max:50', 'name' => 'required|string|max:100', 'description' => 'nullable|string', 'development_fee' => 'required|numeric|min:0', 'registration_fee' => 'required|numeric|min:0', 'subscription_fee' => 'required|numeric|min:0', 'partner_commission_rate' => 'nullable|numeric|min:0|max:100', 'manager_commission_rate' => 'nullable|numeric|min:0|max:100', 'allow_flexible_pricing' => 'boolean', 'is_required' => 'boolean', ]); // 코드 중복 체크 $exists = SalesProduct::where('category_id', $validated['category_id']) ->where('code', $validated['code']) ->exists(); if ($exists) { return response()->json([ 'success' => false, 'message' => '이미 존재하는 상품 코드입니다.', ], 422); } // 순서 설정 (마지막) $maxOrder = SalesProduct::where('category_id', $validated['category_id'])->max('display_order') ?? 0; $validated['display_order'] = $maxOrder + 1; $validated['partner_commission_rate'] = $validated['partner_commission_rate'] ?? 20.00; $validated['manager_commission_rate'] = $validated['manager_commission_rate'] ?? 5.00; $product = SalesProduct::create($validated); return response()->json([ 'success' => true, 'message' => '상품이 등록되었습니다.', 'product' => $product, ]); } /** * 상품 수정 */ public function update(Request $request, int $id): JsonResponse { $product = SalesProduct::findOrFail($id); $validated = $request->validate([ 'name' => 'sometimes|string|max:100', 'description' => 'nullable|string', 'development_fee' => 'sometimes|numeric|min:0', 'registration_fee' => 'sometimes|numeric|min:0', 'subscription_fee' => 'sometimes|numeric|min:0', 'partner_commission_rate' => 'nullable|numeric|min:0|max:100', 'manager_commission_rate' => 'nullable|numeric|min:0|max:100', 'allow_flexible_pricing' => 'boolean', 'is_required' => 'boolean', 'is_active' => 'boolean', ]); $product->update($validated); return response()->json([ 'success' => true, 'message' => '상품이 수정되었습니다.', 'product' => $product->fresh(), ]); } /** * 상품 삭제 (soft delete) */ public function destroy(int $id): JsonResponse { $product = SalesProduct::findOrFail($id); $product->delete(); return response()->json([ 'success' => true, 'message' => '상품이 삭제되었습니다.', ]); } /** * 활성화 토글 */ public function toggleActive(int $id): JsonResponse { $product = SalesProduct::findOrFail($id); $product->update(['is_active' => ! $product->is_active]); return response()->json([ 'success' => true, 'message' => $product->is_active ? '상품이 활성화되었습니다.' : '상품이 비활성화되었습니다.', 'is_active' => $product->is_active, ]); } /** * 순서 변경 */ public function reorder(Request $request): JsonResponse { $validated = $request->validate([ 'orders' => 'required|array', 'orders.*.id' => 'required|exists:sales_products,id', 'orders.*.order' => 'required|integer|min:0', ]); foreach ($validated['orders'] as $item) { SalesProduct::where('id', $item['id'])->update(['display_order' => $item['order']]); } return response()->json([ 'success' => true, 'message' => '순서가 변경되었습니다.', ]); } // ==================== 카테고리 관리 ==================== /** * 카테고리 목록 */ public function categories(): JsonResponse { $categories = SalesProductCategory::ordered()->get(); return response()->json([ 'success' => true, 'data' => $categories, ]); } /** * 카테고리 생성 */ public function storeCategory(Request $request): JsonResponse { $validated = $request->validate([ 'code' => 'required|string|max:50|unique:sales_product_categories,code', 'name' => 'required|string|max:100', 'description' => 'nullable|string', 'base_storage' => 'nullable|string|max:20', ]); $maxOrder = SalesProductCategory::max('display_order') ?? 0; $validated['display_order'] = $maxOrder + 1; $category = SalesProductCategory::create($validated); return response()->json([ 'success' => true, 'message' => '카테고리가 생성되었습니다.', 'category' => $category, ]); } /** * 카테고리 수정 */ public function updateCategory(Request $request, int $id): JsonResponse { $category = SalesProductCategory::findOrFail($id); $validated = $request->validate([ 'name' => 'sometimes|string|max:100', 'description' => 'nullable|string', 'base_storage' => 'nullable|string|max:20', 'is_active' => 'boolean', ]); $category->update($validated); return response()->json([ 'success' => true, 'message' => '카테고리가 수정되었습니다.', 'category' => $category->fresh(), ]); } /** * 카테고리 삭제 */ public function deleteCategory(int $id): JsonResponse { $category = SalesProductCategory::findOrFail($id); // 상품이 있으면 삭제 불가 if ($category->products()->exists()) { return response()->json([ 'success' => false, 'message' => '상품이 있는 카테고리는 삭제할 수 없습니다.', ], 422); } $category->delete(); return response()->json([ 'success' => true, 'message' => '카테고리가 삭제되었습니다.', ]); } // ==================== API (영업 시나리오용) ==================== /** * 상품 목록 API (카테고리 포함) */ public function getProductsApi(): JsonResponse { $categories = SalesProductCategory::active() ->ordered() ->with(['products' => fn ($q) => $q->active()->ordered()]) ->get(); return response()->json([ 'success' => true, 'data' => $categories, ]); } }