Customer names stored with ي/ك were missed when typing ی/ک; fold both sides of LIKE. Co-authored-by: Cursor <cursoragent@cursor.com>
288 lines
10 KiB
PHP
288 lines
10 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Admin;
|
||
|
||
use App\DB\Te\TableEngine;
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\Customer;
|
||
use App\Models\CustomerSpecialPricePackage;
|
||
use App\Models\Product;
|
||
use App\Services\ConfigService;
|
||
use App\Services\CustomerSpecialPricingService;
|
||
use App\Support\PersianText;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Schema;
|
||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||
|
||
class CustomerSpecialPricePackageController extends Controller
|
||
{
|
||
public function table(Request $request)
|
||
{
|
||
$this->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->whereRaw(
|
||
PersianText::sqlFoldExpr('lower(`name`)').' like ?',
|
||
[PersianText::likePattern(strtolower($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('قابلیت قیمت ویژه مشتری غیرفعال است.');
|
||
}
|
||
}
|
||
}
|