Files
boursecu_server/app/Http/Controllers/Admin/CustomerSpecialPricePackageController.php
T
mohmdsdgandCursor 7d405cb83b feat(server): store and apply negative special price profits
Keep signed buy/sell extras so discounts work on quotes and orders, and ignore the nested user clone in this repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-17 13:11:00 +03:30

284 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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('حداقل یک محصول انتخاب کنید.');
}
$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('قابلیت قیمت ویژه مشتری غیرفعال است.');
}
}
}