added setting for user

This commit is contained in:
2026-08-16 22:00:23 +03:30
parent c3c3a60088
commit bd8284e4f8
11 changed files with 769 additions and 12 deletions
@@ -0,0 +1,287 @@
<?php
namespace App\Services;
use App\Models\CustomerSpecialPricePackage;
use App\Models\Product;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
/**
* قیمت ویژه مشتری — افزون بر buy_fee/sell_fee و cash_fee محصول.
* تداخل: آخرین پکیج (updated_at، سپس id).
*/
class CustomerSpecialPricingService
{
public const SETTING_KEY = 'customer_special_pricing';
public function tablesReady(): bool
{
return Schema::hasTable('customer_special_price_packages')
&& Schema::hasTable('customer_special_price_package_customer')
&& Schema::hasTable('customer_special_price_package_product');
}
public function isEnabled(): bool
{
if (! $this->tablesReady()) {
return false;
}
return app(ConfigService::class)->getBool(self::SETTING_KEY, false);
}
/**
* @return array{package_id:int,title:string,profit_buy:int,profit_sell:int,show_product:bool}|null
*/
public function ruleForCustomerProduct(int $customerId, int $productId): ?array
{
if (! $this->isEnabled() || $customerId <= 0 || $productId <= 0) {
return null;
}
$row = CustomerSpecialPricePackage::query()
->active()
->whereHas('customers', fn ($q) => $q->where('customers.id', $customerId))
->whereHas('products', fn ($q) => $q->where('products.id', $productId))
->orderByDesc('updated_at')
->orderByDesc('id')
->first(['id', 'title', 'profit_buy', 'profit_sell', 'show_product', 'updated_at']);
if (! $row) {
return null;
}
return [
'package_id' => (int) $row->id,
'title' => (string) $row->title,
'profit_buy' => (int) ($row->profit_buy ?? 0),
'profit_sell' => (int) ($row->profit_sell ?? 0),
'show_product' => (bool) $row->show_product,
];
}
/**
* قوانین مؤثر برای مشتری — هر محصول یک قانون (آخرین پکیج).
*
* @return array<int, array{package_id:int,title:string,profit_buy:int,profit_sell:int,show_product:bool}>
*/
public function rulesMapForCustomer(int $customerId): array
{
if (! $this->isEnabled() || $customerId <= 0) {
return [];
}
$packages = CustomerSpecialPricePackage::query()
->active()
->whereHas('customers', fn ($q) => $q->where('customers.id', $customerId))
->with(['products:id'])
->orderByDesc('updated_at')
->orderByDesc('id')
->get(['id', 'title', 'profit_buy', 'profit_sell', 'show_product', 'updated_at']);
$map = [];
foreach ($packages as $package) {
foreach ($package->products as $product) {
$pid = (int) $product->id;
if (isset($map[$pid])) {
continue;
}
$map[$pid] = [
'package_id' => (int) $package->id,
'title' => (string) $package->title,
'profit_buy' => (int) ($package->profit_buy ?? 0),
'profit_sell' => (int) ($package->profit_sell ?? 0),
'show_product' => (bool) $package->show_product,
];
}
}
return $map;
}
public function profitForDirection(?array $rule, int $direction): int
{
if (! $rule) {
return 0;
}
return $direction === 2
? (int) ($rule['profit_sell'] ?? 0)
: (int) ($rule['profit_buy'] ?? 0);
}
/**
* اعمال روی payload تابلو last (buy/sell/price_last) برای محصول اصلی یا اولین قانون آبشده.
*/
public function applyToLastPayload(array $payload, int $customerId): array
{
if (! $this->isEnabled() || $customerId <= 0) {
return $payload;
}
$mainId = app(ConfigService::class)->mainProductId();
$rule = null;
if ($mainId) {
$rule = $this->ruleForCustomerProduct($customerId, (int) $mainId);
}
if (! $rule) {
$map = $this->rulesMapForCustomer($customerId);
$rule = $map ? reset($map) : null;
if (is_array($rule) && ! ($rule['show_product'] ?? true)) {
// فقط برای اختلاف قیمت؛ مخفی‌سازی روی لیست محصول است
}
}
if (! $rule) {
return $payload;
}
$buyExtra = (int) ($rule['profit_buy'] ?? 0);
$sellExtra = (int) ($rule['profit_sell'] ?? 0);
if (isset($payload['buy']) && (int) $payload['buy'] > 0 && $buyExtra > 0) {
$payload['buy'] = (int) $payload['buy'] + $buyExtra;
}
if (isset($payload['sell']) && (int) $payload['sell'] > 0 && $sellExtra > 0) {
$payload['sell'] = max(0, (int) $payload['sell'] - $sellExtra);
}
if (isset($payload['price_last']) && (int) $payload['price_last'] > 0 && $buyExtra > 0) {
$payload['price_last'] = (int) $payload['price_last'] + $buyExtra;
}
if (isset($payload['priceLast']) && (int) $payload['priceLast'] > 0 && $buyExtra > 0) {
$payload['priceLast'] = (int) $payload['priceLast'] + $buyExtra;
}
$payload['special_pricing'] = [
'package_id' => $rule['package_id'],
'title' => $rule['title'],
'profit_buy' => $buyExtra,
'profit_sell' => $sellExtra,
];
return $payload;
}
/**
* روی کالکشن محصول: ویژه را روی مدل می‌نشاند و در صورت نیاز show را false می‌کند.
*
* @param Collection<int, Product>|iterable<Product> $products
*/
public function applyToProducts($products, int $customerId): void
{
if (! $this->isEnabled() || $customerId <= 0) {
return;
}
$map = $this->rulesMapForCustomer($customerId);
if ($map === []) {
return;
}
$collection = $products instanceof Collection ? $products : collect($products);
foreach ($collection as $product) {
if (! $product instanceof Product) {
continue;
}
$pid = (int) ($product->id ?? 0);
$rule = $map[$pid] ?? null;
if (! $rule) {
continue;
}
$product->setAttribute('special_profit_buy', (int) $rule['profit_buy']);
$product->setAttribute('special_profit_sell', (int) $rule['profit_sell']);
$product->setAttribute('special_package_id', (int) $rule['package_id']);
$product->setAttribute('special_package_title', (string) $rule['title']);
if (! ($rule['show_product'] ?? true)) {
$product->setAttribute('show', false);
}
}
}
/**
* هشدار تداخل: مشتری+محصول در پکیج دیگری هم هست.
*
* @param list<int> $customerIds
* @param list<int> $productIds
* @return list<string>
*/
public function overlapWarnings(array $customerIds, array $productIds, ?int $excludePackageId = null): array
{
if (! $this->tablesReady() || $customerIds === [] || $productIds === []) {
return [];
}
$customerIds = array_values(array_unique(array_map('intval', $customerIds)));
$productIds = array_values(array_unique(array_map('intval', $productIds)));
$q = DB::table('customer_special_price_packages as p')
->join('customer_special_price_package_customer as pc', 'pc.package_id', '=', 'p.id')
->join('customer_special_price_package_product as pp', 'pp.package_id', '=', 'p.id')
->join('customers as c', 'c.id', '=', 'pc.customer_id')
->join('products as pr', 'pr.id', '=', 'pp.product_id')
->where('p.is_active', true)
->whereIn('pc.customer_id', $customerIds)
->whereIn('pp.product_id', $productIds)
->select([
'p.id as package_id',
'p.title as package_title',
'c.id as customer_id',
'c.name as customer_name',
'pr.id as product_id',
'pr.title as product_title',
]);
if ($excludePackageId) {
$q->where('p.id', '!=', $excludePackageId);
}
$rows = $q->limit(50)->get();
if ($rows->isEmpty()) {
return [];
}
$warnings = [];
foreach ($rows as $row) {
$warnings[] = sprintf(
'تداخل: مشتری «%s» (#%d) و محصول «%s» (#%d) در پکیج «%s» (#%d) هم هست — آخرین پکیج اعمال می‌شود.',
$row->customer_name ?: '-',
(int) $row->customer_id,
$row->product_title ?: '-',
(int) $row->product_id,
$row->package_title ?: '-',
(int) $row->package_id
);
}
return $warnings;
}
/** payload سبک برای SSE / me */
public function clientPayloadForCustomer(int $customerId): array
{
$map = $this->rulesMapForCustomer($customerId);
$products = [];
foreach ($map as $productId => $rule) {
$products[] = array_merge(['product_id' => (int) $productId], $rule);
}
$mainId = app(ConfigService::class)->mainProductId();
$board = null;
if ($mainId && isset($map[(int) $mainId])) {
$board = $map[(int) $mainId];
} elseif ($products !== []) {
$board = $products[0];
}
return [
'enabled' => $this->isEnabled(),
'products' => $products,
'board' => $board,
];
}
}