request()->fullUrl()]); $data = $this->service->list($request->all()); return view('admin.product.products.index', $data); } public function create() { $data = $this->service->getFormData(); return view('admin.product.products.create', $data); } public function store(Request $request) { $data = $this->validateProduct($request); $res = $this->service->storeProduct($data, (int)auth('admin')->id(), $request->ip(), $request->userAgent() ?? ''); if (!$res['ok']) { return redirect()->back()->withInput()->with('toast', ['type' => 'danger', 'title' => '저장 실패', 'message' => $res['message']]); } return redirect()->route('admin.products.index')->with('toast', ['type' => 'success', 'title' => '완료', 'message' => $res['message']]); } private function validateProduct(Request $request): array { $methods = $request->input('payment_methods', []); if (!is_array($methods)) $methods = []; $card = $request->input('payment_card_method'); if ($card !== null && $card !== '') { $methods[] = (int)$card; } $request->merge([ 'payment_methods' => array_values(array_unique(array_map('intval', $methods))), 'pin_check_methods' => array_values(array_unique((array)$request->input('pin_check_methods', []))), ]); return $request->validate([ 'category_id' => ['required', 'integer'], 'name' => ['required', 'string', 'max:150'], 'thumbnail_media_id' => ['nullable', 'integer'], 'status' => ['required', 'in:ACTIVE,HIDDEN,SOLD_OUT'], 'product_type' => ['required', 'in:ONLINE,DELIVERY'], 'payment_methods' => ['required', 'array', 'min:1'], 'payment_methods.*' => ['integer'], 'pin_check_methods' => ['required', 'array', 'min:1'], 'pin_check_methods.*' => ['string', 'in:PIN_INSTANT,SMS,BUYBACK'], 'is_always_on_sale' => ['required', 'in:0,1'], 'sales_start_at' => ['nullable', 'date'], 'sales_end_at' => ['nullable', 'date', 'after_or_equal:sales_start_at'], 'purchase_type' => ['required', 'in:SINGLE,MULTI_QTY,MULTI_SKU'], 'min_buy_qty' => ['required', 'integer', 'min:1'], 'max_buy_qty' => ['required', 'integer', 'min:0'], 'max_buy_amount' => ['required', 'integer', 'min:0'], 'description' => ['nullable', 'string'], 'guide' => ['nullable', 'string'], 'warning' => ['nullable', 'string'], // 다중 SKU 밸리데이션 'skus' => ['required', 'array', 'min:1'], 'skus.*.id' => ['nullable', 'integer'], 'skus.*.name' => ['required', 'string', 'max:50'], 'skus.*.tax_type' => ['required', 'in:TAX,FREE'], 'skus.*.face_value' => ['required', 'integer', 'min:0'], 'skus.*.discount_type' => ['required', 'in:PERCENT,FIXED'], 'skus.*.discount_value' => ['required', 'integer', 'min:0'], 'skus.*.sales_method' => ['required', 'in:OWN_PIN,API_LINK'], 'skus.*.api_provider_id' => ['nullable', 'integer', 'required_if:skus.*.sales_method,API_LINK'], 'skus.*.api_product_code' => ['nullable', 'string', 'max:50', 'required_if:skus.*.sales_method,API_LINK'], 'skus.*.is_active' => ['required', 'in:0,1'], ], [ 'payment_methods.required' => '최소 1개 이상의 결제 수단을 선택해주세요.', 'pin_check_methods.required' => '핀번호 확인 방법을 최소 1개 이상 선택해주세요.', 'skus.required' => '최소 1개 이상의 권종을 등록해야 합니다.', 'skus.*.api_provider_id.required_if' => 'API 연동 시 연동사를 선택해야 합니다.', 'skus.*.api_product_code.required_if' => 'API 연동 시 상품 코드를 선택해야 합니다.', ]); } // ... (기존 index, create, store 유지) ... public function edit(int $id) { $data = $this->service->getEditData($id); if (empty($data)) { return redirect()->route('admin.products.index')->with('toast', ['type' => 'danger', 'title' => '오류', 'message' => '상품을 찾을 수 없습니다.']); } return view('admin.product.products.edit', $data); } public function update(int $id, Request $request) { $data = $this->validateProduct($request); $res = $this->service->updateProduct($id, $data, (int)auth('admin')->id(), $request->ip(), $request->userAgent() ?? ''); if (!$res['ok']) { return redirect()->back()->withInput()->with('toast', ['type' => 'danger', 'title' => '수정 실패', 'message' => $res['message']]); } return redirect()->route('admin.products.index')->with('toast', ['type' => 'success', 'title' => '완료', 'message' => $res['message']]); } }