2026-08-16 22:00:23 +03:30
|
|
|
|
<?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 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->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('حداقل یک محصول انتخاب کنید.');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 13:11:00 +03:30
|
|
|
|
$profitBuy = $this->parseSignedRial($request->input('profit_buy', 0));
|
|
|
|
|
|
$profitSell = $this->parseSignedRial($request->input('profit_sell', 0));
|
2026-08-16 22:00:23 +03:30
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
'title' => $title,
|
|
|
|
|
|
'customer_ids' => $customerIds,
|
|
|
|
|
|
'product_ids' => $productIds,
|
|
|
|
|
|
'category_names' => $categoryNames,
|
2026-08-17 13:11:00 +03:30
|
|
|
|
'profit_buy' => $profitBuy,
|
|
|
|
|
|
'profit_sell' => $profitSell,
|
2026-08-16 22:00:23 +03:30
|
|
|
|
'show_product' => filter_var($request->input('show_product', true), FILTER_VALIDATE_BOOLEAN),
|
|
|
|
|
|
'is_active' => filter_var($request->input('is_active', true), FILTER_VALIDATE_BOOLEAN),
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 13:11:00 +03:30
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-16 22:00:23 +03:30
|
|
|
|
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('قابلیت قیمت ویژه مشتری غیرفعال است.');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|