assertFeatureEnabled(); $query = CustomerSpecialPricePackage::query() ->withCount(['customers', 'products']) ->orderByDesc('id'); TableEngine::create($query, $request->all()) ->initSearch(['title']) ->select(['*']) ->export($query); return $query; } public function show(int $id) { $this->assertFeatureEnabled(); $package = CustomerSpecialPricePackage::query() ->with([ 'customers:id,name,mobile', 'products:id,title,group_type', ]) ->findOrFail($id); $categoryNames = []; if (Schema::hasTable('customer_special_price_package_category')) { $categoryNames = DB::table('customer_special_price_package_category') ->where('package_id', $package->id) ->pluck('category_name') ->all(); } return response()->json([ 'package' => $package, 'customer_ids' => $package->customers->pluck('id')->values(), 'product_ids' => $package->products->pluck('id')->values(), 'category_names' => array_values($categoryNames), 'customers' => $package->customers, 'products' => $package->products, ]); } public function store(Request $request) { $this->assertFeatureEnabled(); $data = $this->validatedPayload($request); $warnings = app(CustomerSpecialPricingService::class)->overlapWarnings( $data['customer_ids'], $data['product_ids'] ); $package = DB::transaction(function () use ($data) { $package = CustomerSpecialPricePackage::create([ 'title' => $data['title'], 'profit_buy' => $data['profit_buy'], 'profit_sell' => $data['profit_sell'], 'show_product' => $data['show_product'], 'is_active' => $data['is_active'], ]); $this->syncRelations($package, $data); return $package->fresh(); }); return response()->json([ 'message' => 'پکیج قیمت ویژه ثبت شد.', 'package' => $package, 'warnings' => $warnings, ]); } public function update(Request $request, int $id) { $this->assertFeatureEnabled(); $package = CustomerSpecialPricePackage::findOrFail($id); $data = $this->validatedPayload($request); $warnings = app(CustomerSpecialPricingService::class)->overlapWarnings( $data['customer_ids'], $data['product_ids'], (int) $package->id ); DB::transaction(function () use ($package, $data) { $package->update([ 'title' => $data['title'], 'profit_buy' => $data['profit_buy'], 'profit_sell' => $data['profit_sell'], 'show_product' => $data['show_product'], 'is_active' => $data['is_active'], ]); $this->syncRelations($package, $data); }); return response()->json([ 'message' => 'پکیج قیمت ویژه به‌روز شد.', 'package' => $package->fresh(), 'warnings' => $warnings, ]); } public function destroy(int $id) { $this->assertFeatureEnabled(); $package = CustomerSpecialPricePackage::findOrFail($id); DB::transaction(function () use ($package) { $package->customers()->detach(); $package->products()->detach(); if (Schema::hasTable('customer_special_price_package_category')) { DB::table('customer_special_price_package_category')->where('package_id', $package->id)->delete(); } $package->delete(); }); return response()->json(['message' => 'پکیج حذف شد.']); } public function toggleActive(int $id) { $this->assertFeatureEnabled(); $package = CustomerSpecialPricePackage::findOrFail($id); $package->is_active = ! $package->is_active; $package->save(); return response()->json([ 'message' => $package->is_active ? 'پکیج فعال شد.' : 'پکیج غیرفعال شد.', 'is_active' => $package->is_active, ]); } public function searchCustomers(Request $request) { $this->assertFeatureEnabled(); $q = trim((string) $request->get('q', '')); $rows = Customer::query() ->where('type', 2) ->where(function ($query) { $query->where('is_archive', false)->orWhereNull('is_archive'); }) ->when($q !== '', function ($query) use ($q) { $query->where(function ($inner) use ($q) { $inner->where('name', 'like', '%' . $q . '%') ->orWhere('mobile', 'like', '%' . $q . '%') ->orWhere('id', $q); }); }) ->orderByDesc('id') ->limit(40) ->get(['id', 'name', 'mobile']); return response()->json(['customers' => $rows]); } public function productsByCategories(Request $request) { $this->assertFeatureEnabled(); $names = array_values(array_filter(array_map('strval', (array) $request->input('category_names', [])))); if ($names === []) { return response()->json(['products' => []]); } $products = Product::query() ->whereIn('group_type', $names) ->orderBy('title') ->get(['id', 'title', 'group_type', 'active']); return response()->json(['products' => $products]); } private function validatedPayload(Request $request): array { $title = trim((string) $request->input('title', '')); if ($title === '') { throw new UnprocessableEntityHttpException('عنوان پکیج الزامی است.'); } $customerIds = array_values(array_unique(array_filter(array_map('intval', (array) $request->input('customer_ids', []))))); $productIds = array_values(array_unique(array_filter(array_map('intval', (array) $request->input('product_ids', []))))); $categoryNames = array_values(array_unique(array_filter(array_map( fn ($n) => trim((string) $n), (array) $request->input('category_names', []) )))); if ($customerIds === []) { throw new UnprocessableEntityHttpException('حداقل یک کاربر انتخاب کنید.'); } if ($categoryNames === []) { throw new UnprocessableEntityHttpException('حداقل یک گروه دسته‌بندی انتخاب کنید.'); } if ($productIds === []) { throw new UnprocessableEntityHttpException('حداقل یک محصول انتخاب کنید.'); } $profitBuy = $this->parseSignedRial($request->input('profit_buy', 0)); $profitSell = $this->parseSignedRial($request->input('profit_sell', 0)); return [ 'title' => $title, 'customer_ids' => $customerIds, 'product_ids' => $productIds, 'category_names' => $categoryNames, 'profit_buy' => $profitBuy, 'profit_sell' => $profitSell, 'show_product' => filter_var($request->input('show_product', true), FILTER_VALIDATE_BOOLEAN), 'is_active' => filter_var($request->input('is_active', true), FILTER_VALIDATE_BOOLEAN), ]; } private function parseSignedRial(mixed $value): int { $raw = trim((string) $value); $raw = str_replace(["\u{066C}", ',', '٬', ' ', 'ریال'], '', $raw); $raw = strtr($raw, [ '۰' => '0', '۱' => '1', '۲' => '2', '۳' => '3', '۴' => '4', '۵' => '5', '۶' => '6', '۷' => '7', '۸' => '8', '۹' => '9', '٠' => '0', '١' => '1', '٢' => '2', '٣' => '3', '٤' => '4', '٥' => '5', '٦' => '6', '٧' => '7', '٨' => '8', '٩' => '9', '−' => '-', '–' => '-', '—' => '-', ]); $negative = str_starts_with($raw, '-'); $digits = preg_replace('/\D/', '', $raw) ?? '0'; $amount = (int) ($digits === '' ? '0' : $digits); return $negative ? -$amount : $amount; } private function syncRelations(CustomerSpecialPricePackage $package, array $data): void { $package->customers()->sync($data['customer_ids']); $package->products()->sync($data['product_ids']); if (Schema::hasTable('customer_special_price_package_category')) { DB::table('customer_special_price_package_category')->where('package_id', $package->id)->delete(); $rows = []; foreach ($data['category_names'] as $name) { $rows[] = [ 'package_id' => $package->id, 'category_name' => $name, ]; } if ($rows !== []) { DB::table('customer_special_price_package_category')->insert($rows); } } } private function assertFeatureEnabled(): void { if (! app(CustomerSpecialPricingService::class)->tablesReady()) { throw new UnprocessableEntityHttpException('جدول پکیج قیمت ویژه وجود ندارد. مایگریشن را اجرا کنید.'); } if (! app(ConfigService::class)->getBool(CustomerSpecialPricingService::SETTING_KEY, false)) { throw new UnprocessableEntityHttpException('قابلیت قیمت ویژه مشتری غیرفعال است.'); } } }