Files
boursecu_server/app/Http/Controllers/Admin/ProductController.php
T

778 lines
26 KiB
PHP
Raw Normal View History

2026-01-24 14:03:11 +00:00
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Controllers\SuperController;
2026-05-25 20:55:05 +03:30
use App\Models\Cnt;
2026-01-24 14:03:11 +00:00
use App\Models\Product;
2026-06-02 11:18:05 +03:30
use App\Models\Setting;
2026-01-24 14:03:11 +00:00
use App\DB\Te\TableEngine;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
use Illuminate\Support\Facades\Http;
use \App\Http\Controllers\Customer\FundController;
2026-06-01 18:46:57 +03:30
use App\Http\Controllers\Site\ReportController;
2026-06-12 19:49:53 +03:30
use App\Services\ConfigService;
2026-06-11 16:41:28 +03:30
use App\Services\ProductDisplayPriceService;
2026-06-02 16:00:33 +03:30
use App\Services\ProductPriceService;
2026-06-12 19:49:53 +03:30
use App\Services\ProductPriceSyncService;
2026-06-17 12:12:35 +03:30
use App\Services\PriceBroadcastService;
2026-06-13 11:17:32 +03:30
use App\Services\CategoryService;
2026-06-04 13:48:37 +03:30
use Illuminate\Support\Facades\Schema;
2026-05-25 20:55:05 +03:30
2026-01-24 14:03:11 +00:00
class ProductController extends SuperController
{
public function __construct()
{
$this->with = ['file'/*, 'price', 'default', 'sub'*/];
2026-05-25 20:55:05 +03:30
// $this->with = [];
2026-01-24 14:03:11 +00:00
parent::__construct(Product::class, $this->with, ['*'], null, null, ['title']);
}
public function tableProduct(Request $request)
{
$data = $request->all();
2026-05-25 20:55:05 +03:30
if (isset($data["select"]) && is_null($data["select"])){
2026-01-24 14:03:11 +00:00
$data["select"] = [];
}
2026-05-25 20:55:05 +03:30
if (is_null($this->query)){
2026-01-24 14:03:11 +00:00
$query_table = $this->model::with($this->with);
//*******کداضافه شده
// ->where('active', '<=', now()) // فیلتر محصولات فعال
// ->get();
//*********
2026-05-25 20:55:05 +03:30
}else{
2026-01-24 14:03:11 +00:00
$query_table = $this->query;
// $query_table = $query_table->where('active', '<=', now());
}
TableEngine::create($query_table, $data)
->initSearch($this->search)
->select($this->select)
->export($query_table);
2026-06-04 13:48:37 +03:30
2026-06-28 15:16:39 +03:30
$this->enrichAdminTablePrices($query_table['rows'] ?? null);
2026-06-04 13:48:37 +03:30
if (Schema::hasColumn((new Product)->getTable(), 'auto_open_at')) {
$query_table['auto_open_at'] = Product::selectRaw(
'SEC_TO_TIME(AVG(TIME_TO_SEC(TIME(auto_open_at)))) as avg_time'
)->value('avg_time');
}
if (Schema::hasColumn((new Product)->getTable(), 'auto_close_at')) {
$query_table['auto_close_at'] = Product::selectRaw(
'SEC_TO_TIME(AVG(TIME_TO_SEC(TIME(auto_close_at)))) as avg_time'
)->value('avg_time');
}
2026-01-24 14:03:11 +00:00
// $query_table['11'] = $avgDuration;
// $query_table['11'] = Product::avg('auto_open_at');
// $query_table['112'] = Product::avg('auto_close_at');
return $query_table;
}
2026-06-14 21:09:43 +03:30
public function archiveTable(Request $request)
{
$data = $request->all();
if (isset($data['select']) && is_null($data['select'])) {
$data['select'] = [];
}
$query_table = Product::onlyTrashed()->with($this->with);
TableEngine::create($query_table, $data)
->initSearch($this->search)
->select($this->select)
->export($query_table);
2026-06-28 15:16:39 +03:30
$this->enrichAdminTablePrices($query_table['rows'] ?? null);
2026-06-14 21:09:43 +03:30
return $query_table;
}
public function archive($id)
{
$product = Product::onlyTrashed()->findOrFail($id);
$product->restore();
2026-07-01 20:14:47 +03:30
$this->broadcastPricesAfterResponse();
2026-06-14 21:09:43 +03:30
return response()->json(true);
}
2026-01-24 14:03:11 +00:00
public function editProducts(Request $request)
{
if (isset($request->auto_open_at, $request->auto_close_at)) {
Product::query()->update([
'auto_open_at' => $request->auto_open_at,
'auto_close_at' => $request->auto_close_at,
]);
return response()->json(['message' => 'Products updated successfully.']);
}
return response()->json(['error' => 'auto_open_at and auto_close_at must be set'], 400);
}
public function store(Request $request)
{
2026-06-02 16:00:33 +03:30
$data = $this->filterProductData($request->all());
2026-01-24 14:03:11 +00:00
$product = Product::create($data);
2026-07-14 13:45:59 +03:30
$this->broadcastPricesAfterResponse(true);
2026-01-24 14:03:11 +00:00
return $this->resultCreate($product->id);
}
public function update(Request $request, $id)
{
// return 1;
2026-06-02 16:00:33 +03:30
$data = $this->filterProductData($request->all());
2026-06-09 12:43:41 +03:30
$clearingReadingMethod = array_key_exists('reading_method', $data)
&& ($data['reading_method'] === null || $data['reading_method'] === '');
if ($clearingReadingMethod) {
$data['reading_method'] = null;
}
if (isset($data["price"])) {
2026-05-25 19:42:48 +03:30
$data["reading_method"] = null;
2026-06-02 16:00:33 +03:30
if (Product::hasPriceOffsetColumn()) {
$data['price_offset_percent'] = 0;
}
2026-05-25 19:42:48 +03:30
}
2026-05-25 20:55:05 +03:30
if (isset($data["auto_close_at"]) and isset($data["auto_close_at"])){
if ($data["auto_close_at"] == $data["auto_open_at"]){
unset($data["auto_open_at"]);
unset($data["auto_close_at"]);
2026-05-25 19:42:48 +03:30
}
}
2026-01-24 14:03:11 +00:00
// return $data['price'];
// if(!isset($data['default'])){
// throw new UnprocessableEntityHttpException('مقادیر اعتبارات را وارد کنید');
// }
// $data['auto_open_at'] = Carbon::parse($data['auto_open_at'])->format('Y-m-d H:i:s');
// $data['auto_close_at'] = Carbon::parse($data['auto_close_at'])->format('Y-m-d H:i:s');
2026-06-02 16:00:33 +03:30
$product = Product::findOrFail($id);
$oldOffsetPercent = (float) ($product->price_offset_percent ?? 0);
2026-06-17 21:26:08 +03:30
unset($data['trade_by_quantity']);
2026-06-02 16:00:33 +03:30
if (
Product::hasPriceOffsetColumn()
&& $product->reading_method
&& array_key_exists('price_offset_percent', $data)
2026-06-09 12:43:41 +03:30
&& !$clearingReadingMethod
2026-06-02 16:00:33 +03:30
) {
ProductPriceService::reapplyOffsetPercent(
$product,
(float) $data['price_offset_percent'],
$oldOffsetPercent
);
unset($data['price'], $data['price_profit']);
}
$product->update($data);
2026-07-14 13:45:59 +03:30
$this->broadcastPricesAfterResponse(true);
2026-01-24 14:03:11 +00:00
return response()->json(true);
}
public function destroy($id)
{
2026-07-01 20:14:47 +03:30
$product = Product::findOrFail($id);
$product->update([
'active' => 0,
'show' => 0,
'open_buy' => 0,
'open_sell' => 0,
]);
$product->delete();
$this->broadcastPricesAfterResponse();
2026-01-24 14:03:11 +00:00
2026-07-01 20:14:47 +03:30
return response()->json(true);
2026-01-24 14:03:11 +00:00
}
public function listProduct()
{
2026-06-12 19:49:53 +03:30
if (app(ConfigService::class)->isStore()) {
2026-06-29 18:22:41 +03:30
// اپ مشتری: فقط DB — sync با HTTP فقط cron/admin (جلوگیری از pending طولانی list)
2026-06-15 00:58:15 +03:30
if (! $this->isCustomerApiRequest()) {
2026-06-29 18:22:41 +03:30
app(ProductPriceSyncService::class)->syncStorePrices(true);
2026-06-12 20:10:14 +03:30
$this->auto_close_open();
}
2026-06-29 18:22:41 +03:30
return response()->json($this->customerStoreProductListPayload());
2026-06-12 19:49:53 +03:30
}
2026-06-13 17:35:40 +03:30
// آبشده خانگی — کران قیمت را آپدیت می‌کند؛ list فقط از DB می‌خواند
2026-06-02 16:00:33 +03:30
$products = Product::with(['file'])->where('active', 1)->get();
2026-06-15 00:58:15 +03:30
if ($this->isCustomerApiRequest()) {
2026-06-15 00:52:13 +03:30
$this->applyCustomerShow($products);
2026-06-11 16:41:28 +03:30
}
2026-06-15 00:58:15 +03:30
if (Product::hasPriceOffsetColumn() && $this->isCustomerApiRequest()) {
2026-06-09 12:43:41 +03:30
$products->each(function (Product $product) {
if ($product->reading_method) {
$product->makeHidden(['price_offset_percent']);
}
});
2026-06-02 16:00:33 +03:30
}
return response()->json($products);
2026-01-24 14:03:11 +00:00
}
2026-06-11 22:08:47 +03:30
public function ProductById($id)
{
2026-06-29 18:22:41 +03:30
if (! $this->isCustomerApiRequest()) {
app(ProductPriceSyncService::class)->syncStorePrices(true);
$this->auto_close_open();
}
2026-06-11 22:08:47 +03:30
$product = Product::with(['file'])
->where('id', $id)
->first();
if ($product) {
2026-06-15 00:58:15 +03:30
if ($this->isCustomerApiRequest()) {
2026-06-15 00:52:13 +03:30
$this->applyCustomerShow(collect([$product]));
}
2026-06-11 22:08:47 +03:30
$product->similar = Product::with(['file'])
->where('group_type', $product->group_type)
->where('id', '!=', $id)
->get();
2026-06-15 00:52:13 +03:30
2026-06-15 00:58:15 +03:30
if ($this->isCustomerApiRequest()) {
2026-06-15 00:52:13 +03:30
$this->applyCustomerShow($product->similar);
}
2026-06-11 22:08:47 +03:30
} else {
return response()->json(false);
}
return response()->json($product);
}
2026-01-24 14:03:11 +00:00
public function getPrice()
{
2026-06-12 19:49:53 +03:30
if (app(ConfigService::class)->isStore()) {
app(ProductPriceSyncService::class)->syncStorePrices(true);
} else {
app(ProductPriceSyncService::class)->syncAll(true);
}
2026-05-25 19:42:48 +03:30
2026-06-17 12:12:35 +03:30
dispatch(function () {
app(PriceBroadcastService::class)->broadcastAfterPriceUpdate();
2026-07-07 14:34:00 +03:30
app(\App\Services\PriceChannelBroadcastService::class)->broadcastIfEligible();
2026-06-17 12:12:35 +03:30
})->afterResponse();
2026-05-25 20:55:05 +03:30
return response()->json(true);
2026-05-25 19:42:48 +03:30
}
2026-01-24 14:03:11 +00:00
public function auto_close_open()
{
2026-06-14 22:00:21 +03:30
$config = app(ConfigService::class);
2026-01-24 14:03:11 +00:00
$products = Product::with([])->get();
foreach ($products as $product) {
Product::where('id', $product->id)->update([
2026-06-14 22:00:21 +03:30
'show' => $config->isProductWithinTradingHours($product),
2026-01-24 14:03:11 +00:00
]);
}
2026-06-12 17:02:12 +03:30
2026-05-25 20:55:05 +03:30
return response()->json(true);
2026-01-24 14:03:11 +00:00
}
public function getProduct($id)
{
$product = Product::with($this->with)->where('id', $id)->first();
return response()->json($product);
}
/**
2026-06-06 13:47:26 +03:30
* فعال/غیرفعال محصول — show باید با active هم‌گام باشد (اپ کاربر از show استفاده می‌کند).
2026-01-24 14:03:11 +00:00
*/
2026-06-06 13:47:26 +03:30
public function active($id)
{
$product = Product::findOrFail($id);
$active = !$product->active;
$product->update([
'active' => $active,
'show' => $active,
]);
return response()->json(true);
}
2026-01-24 14:03:11 +00:00
public function hide($id)
{
$query = Product::findOrFail($id);
$query->update([
'show' => !$query->show
]);
return response()->json(true);
}
public function openSell($id)
{
return $this->toggleBooleanStatus($id, 'open_sell');
}
public function openBuy($id)
{
return $this->toggleBooleanStatus($id, 'open_buy');
}
public function auto_buy($id)
{
return $this->toggleBooleanStatus($id, 'auto_buy');
}
public function auto_sell($id)
{
return $this->toggleBooleanStatus($id, 'auto_sell');
}
private function toggleBooleanStatus($id, $field)
{
$product = Product::findOrFail($id);
$product->$field = !$product->$field;
$product->save();
if (!$product->open_buy and !$product->open_sell) {
$product->show = false;
$product->active = false;
} else {
$product->show = true;
$product->active = true;
}
$product->save();
return response()->json($product->$field);
}
public function auto_group($action, $method)
{
// Define valid actions and methods
$validActions = ['sell' => 'auto_sell', 'buy' => 'auto_buy', 'all' => ['auto_buy', 'auto_sell']];
$validMethods = ['true' => 1, 'false' => 0];
// Validate action and method
if (!isset($validActions[$action]) || !array_key_exists($method, $validMethods)) {
return response()->json(['error' => 'Invalid action or method'], 400);
}
// Get fields to update and value to set
$fields = $validActions[$action];
$value = $validMethods[$method];
// Update products
Product::query()->update(array_fill_keys((array)$fields, $value));
return response()->json(true);
}
2026-06-21 21:44:44 +03:30
private const ACTIVATION_SNAPSHOT_KEY = 'products_activation_snapshot';
public function activationSnapshotStatus()
{
$row = Cnt::where('key', self::ACTIVATION_SNAPSHOT_KEY)->first();
2026-06-30 21:44:39 +03:30
$payload = $row?->jsonPayload() ?? [];
$items = $payload['items'] ?? [];
2026-06-21 21:44:44 +03:30
return response()->json([
'has_snapshot' => ! empty($items),
2026-06-30 21:44:39 +03:30
'saved_at' => $payload['saved_at'] ?? null,
2026-06-21 21:44:44 +03:30
'count' => count($items),
]);
}
public function deactivateAllProducts()
{
$products = Product::query()
->where(function ($query) {
$query->where('active', 1)
->orWhere('open_buy', 1)
->orWhere('open_sell', 1);
})
->get(['id', 'active', 'show', 'open_buy', 'open_sell']);
$items = $products->map(static fn (Product $product) => [
'id' => $product->id,
'active' => (bool) $product->active,
'show' => (bool) $product->show,
'open_buy' => (bool) $product->open_buy,
'open_sell' => (bool) $product->open_sell,
])->values()->all();
2026-06-30 21:44:39 +03:30
Cnt::saveJsonPayload(
self::ACTIVATION_SNAPSHOT_KEY,
2026-06-21 21:44:44 +03:30
[
2026-06-30 21:44:39 +03:30
'saved_at' => now()->timezone('Asia/Tehran')->format('Y-m-d H:i:s'),
'items' => $items,
],
'ذخیره وضعیت محصولات'
2026-06-21 21:44:44 +03:30
);
$affected = Product::query()->update([
'active' => 0,
'show' => 0,
'open_buy' => 0,
'open_sell' => 0,
]);
$this->broadcastPricesAfterResponse();
return response()->json([
'message' => 'همه محصولات بسته شد.',
'snapshot_count' => count($items),
'affected' => $affected,
'has_snapshot' => ! empty($items),
]);
}
public function restoreActivationSnapshot()
{
$row = Cnt::where('key', self::ACTIVATION_SNAPSHOT_KEY)->first();
2026-06-30 21:44:39 +03:30
$items = $row?->jsonPayload()['items'] ?? [];
2026-06-21 21:44:44 +03:30
if (empty($items)) {
return response()->json(['error' => 'اسنAPSHOTی برای بازگردانی وجود ندارد.'], 422);
}
$restored = 0;
foreach ($items as $item) {
$updated = Product::where('id', $item['id'] ?? 0)->update([
'active' => ! empty($item['active']) ? 1 : 0,
'show' => ! empty($item['show']) ? 1 : 0,
'open_buy' => ! empty($item['open_buy']) ? 1 : 0,
'open_sell' => ! empty($item['open_sell']) ? 1 : 0,
]);
if ($updated) {
$restored++;
}
}
$this->auto_close_open();
$this->broadcastPricesAfterResponse();
return response()->json([
'message' => 'وضعیت قبلی محصولات بازگردانی شد.',
'restored' => $restored,
'has_snapshot' => true,
]);
}
public function closeAllBuy()
{
Product::query()->update(['open_buy' => 0]);
$inactive = Product::where('open_buy', 0)->where('open_sell', 0)->update([
'active' => 0,
'show' => 0,
]);
$this->broadcastPricesAfterResponse();
return response()->json([
'message' => 'خرید همه محصولات بسته شد.',
'inactive' => $inactive,
]);
}
public function closeAllSell()
{
Product::query()->update(['open_sell' => 0]);
$inactive = Product::where('open_buy', 0)->where('open_sell', 0)->update([
'active' => 0,
'show' => 0,
]);
$this->broadcastPricesAfterResponse();
return response()->json([
'message' => 'فروش همه محصولات بسته شد.',
'inactive' => $inactive,
]);
}
public function syncProductShow()
{
$this->auto_close_open();
$this->broadcastPricesAfterResponse();
return response()->json(['message' => 'نمایش محصولات طبق ساعات به‌روز شد.']);
}
2026-07-14 13:45:59 +03:30
private function broadcastPricesAfterResponse(bool $force = false): void
2026-06-21 21:44:44 +03:30
{
2026-07-14 13:45:59 +03:30
dispatch(function () use ($force) {
app(PriceBroadcastService::class)->broadcastAfterPriceUpdate(null, $force);
2026-06-21 21:44:44 +03:30
})->afterResponse();
}
2026-05-25 20:55:05 +03:30
public function updatePrice(Request $request){
2026-01-24 14:03:11 +00:00
}
2026-06-02 16:00:33 +03:30
2026-06-15 00:58:15 +03:30
private function isCustomerApiRequest(): bool
{
2026-06-29 18:22:41 +03:30
$path = trim((string) request()->path(), '/');
return str_starts_with($path, 'customer/')
|| str_contains($path, '/customer/');
}
/**
* لیست محصولات فروشگاه برای اپ/SSE — فقط DB، بدون sync خارجی.
*
* @return array<int, mixed>
*/
public function customerStoreProductListPayload(): array
{
$products = Product::with(['file'])->where('active', 1)->get();
$categoryService = app(CategoryService::class);
$products = $categoryService->filterProductsByVisibleCategories($products);
$products = $categoryService->sortProductsForStore($products);
$this->applyCustomerShow($products);
return $products->values()->all();
2026-06-15 00:58:15 +03:30
}
2026-06-15 00:52:13 +03:30
private function applyCustomerShow($products): void
{
$products = $products instanceof \Illuminate\Support\Collection
? $products
: collect($products);
if ($products->isEmpty()) {
return;
}
2026-07-16 14:33:57 +03:30
// فروشگاه و خانگی: همان enrich قیمت لحظه‌ای (آبشده/قیمت اپ/مرجع) — بدون UPDATE دیتابیس
2026-06-15 00:52:13 +03:30
$displayPriceService = app(ProductDisplayPriceService::class);
$displayPriceService->warmLivePriceCache();
$displayPriceService->enrichForApi($products);
$products->each(function (Product $product) use ($displayPriceService) {
$product->setAttribute('show', $displayPriceService->resolveCustomerShow($product));
});
}
2026-06-28 15:16:39 +03:30
/**
* جدول ادمین — قیمت لحظه‌ای روی مدل (بدون UPDATE دیتابیس)، مثل فرم ویرایش و اپ مشتری.
*
* @param iterable<Product>|null $rows
*/
private function enrichAdminTablePrices($rows): void
{
2026-07-16 14:33:57 +03:30
if ($rows === null) {
2026-06-28 15:16:39 +03:30
return;
}
$products = collect($rows)->filter(fn ($row) => $row instanceof Product);
if ($products->isEmpty()) {
return;
}
$displayPriceService = app(ProductDisplayPriceService::class);
$displayPriceService->warmLivePriceCache();
$displayPriceService->enrichForApi($products);
}
2026-06-02 16:00:33 +03:30
private function filterProductData(array $data): array
{
if (!Product::hasPriceOffsetColumn()) {
unset($data['price_offset_percent']);
}
2026-06-06 22:06:53 +03:30
if (!Product::hasOrderLimitColumns()) {
2026-07-08 17:13:04 +03:30
$requestedLimit = filter_var($data['order_limit_enabled'] ?? false, FILTER_VALIDATE_BOOLEAN)
|| $this->normalizeOrderLimitCash($data['min_order_cash'] ?? null) !== null
|| $this->normalizeOrderLimitCash($data['max_order_cash'] ?? null) !== null;
if ($requestedLimit) {
throw new UnprocessableEntityHttpException(
'ستون‌های محدودیت مبلغ سفارش در دیتابیس وجود ندارد. مایگریشن را اجرا کنید: php artisan migrate — یا اسکریپت database/sql/add_product_order_limit_columns.sql'
);
}
2026-06-06 22:06:53 +03:30
unset($data['order_limit_enabled'], $data['min_order_cash'], $data['max_order_cash']);
} else {
2026-07-08 12:45:14 +03:30
$min = $this->normalizeOrderLimitCash($data['min_order_cash'] ?? null);
$max = $this->normalizeOrderLimitCash($data['max_order_cash'] ?? null);
$orderLimitEnabled = filter_var($data['order_limit_enabled'] ?? false, FILTER_VALIDATE_BOOLEAN)
|| $min !== null
|| $max !== null;
2026-06-06 22:06:53 +03:30
2026-07-08 12:45:14 +03:30
if (!$orderLimitEnabled) {
$data['order_limit_enabled'] = false;
$data['min_order_cash'] = null;
$data['max_order_cash'] = null;
} else {
if ($min === null && $max === null) {
throw new UnprocessableEntityHttpException(
'با فعال بودن محدودیت مبلغ سفارش، حداقل یکی از حداقل یا حداکثر مبلغ باید وارد شود'
);
}
if ($min !== null && $max !== null && $min > $max) {
throw new UnprocessableEntityHttpException('حداقل مبلغ نمی‌تواند بیشتر از حداکثر باشد');
}
$data['order_limit_enabled'] = true;
$data['min_order_cash'] = $min;
$data['max_order_cash'] = $max;
2026-06-06 22:06:53 +03:30
}
}
2026-06-09 16:44:02 +03:30
if (!Product::hasShowInAssetsColumn()) {
unset($data['show_in_assets']);
} elseif (array_key_exists('show_in_assets', $data)) {
$data['show_in_assets'] = !empty($data['show_in_assets']);
}
2026-06-10 13:11:51 +03:30
if (!Product::hasUseLastPriceIfZeroColumn()) {
unset($data['use_last_price_if_zero']);
} elseif (array_key_exists('use_last_price_if_zero', $data)) {
$data['use_last_price_if_zero'] = !empty($data['use_last_price_if_zero']);
}
2026-06-17 16:13:18 +03:30
if (!Product::hasTradeByQuantityColumn()) {
unset($data['trade_by_quantity']);
} elseif (array_key_exists('trade_by_quantity', $data)) {
$data['trade_by_quantity'] = !empty($data['trade_by_quantity']);
}
2026-06-14 13:31:32 +03:30
if (!Product::hasDirectPriceColumn()) {
unset($data['direct_price']);
}
2026-07-13 19:03:30 +03:30
if (!Product::hasAppPriceColumn()) {
unset($data['app_price']);
} elseif (array_key_exists('app_price', $data)) {
$data['app_price'] = filter_var($data['app_price'], FILTER_VALIDATE_BOOLEAN);
// قیمت اپ: مرجع خارجی ندارد، متغیر است (هر گرم × وزن) و قیمت دستی نگه داشته نمی‌شود
if ($data['app_price']) {
$data['reading_method'] = null;
if (Product::hasDirectPriceColumn()) {
$data['direct_price'] = 0;
}
$data['price'] = 0;
$data['price_profit'] = 0;
}
}
2026-06-22 20:09:56 +03:30
if (!Product::hasInventoryColumn()) {
unset($data['inventory']);
}
2026-06-28 15:16:39 +03:30
if (!Product::hasOverrideTradingHoursColumn()) {
unset($data['override_trading_hours']);
} elseif (array_key_exists('override_trading_hours', $data)) {
$data['override_trading_hours'] = !empty($data['override_trading_hours']);
}
2026-07-13 13:31:44 +03:30
if (!Product::hasPhysicalDeliveryColumns()) {
2026-07-13 19:03:30 +03:30
unset(
$data['has_physical_delivery'],
$data['min_physical_delivery_quantity'],
$data['min_physical_delivery_grams']
);
2026-07-13 13:31:44 +03:30
} else {
2026-07-13 19:03:30 +03:30
// سازگاری با نسخهٔ قبلی که فیلد را min_physical_delivery_grams می‌فرستاد
if (!array_key_exists('min_physical_delivery_quantity', $data)
&& array_key_exists('min_physical_delivery_grams', $data)) {
$data['min_physical_delivery_quantity'] = $data['min_physical_delivery_grams'];
}
unset($data['min_physical_delivery_grams']);
2026-07-13 13:31:44 +03:30
$hasDelivery = array_key_exists('has_physical_delivery', $data)
? filter_var($data['has_physical_delivery'], FILTER_VALIDATE_BOOLEAN)
: false;
$data['has_physical_delivery'] = $hasDelivery;
if (!$hasDelivery) {
2026-07-13 19:03:30 +03:30
$data['min_physical_delivery_quantity'] = null;
2026-07-13 13:31:44 +03:30
} else {
2026-07-13 19:03:30 +03:30
$rawMin = $data['min_physical_delivery_quantity'] ?? null;
2026-07-13 13:31:44 +03:30
$min = $this->normalizeCashFeeValue($rawMin);
if ($min <= 0) {
throw new UnprocessableEntityHttpException(
2026-07-13 19:03:30 +03:30
'برای فعال کردن تحویل فیزیکی باید حداقل مقدار معتبر وارد شود'
2026-07-13 13:31:44 +03:30
);
}
2026-07-13 19:03:30 +03:30
$data['min_physical_delivery_quantity'] = $min;
2026-07-13 13:31:44 +03:30
}
}
2026-07-04 22:06:20 +03:30
$data['cash_fee_buy'] = $this->normalizeCashFeeValue($data['cash_fee_buy'] ?? 0);
$data['cash_fee_sell'] = $this->normalizeCashFeeValue($data['cash_fee_sell'] ?? 0);
if (Product::hasCashFeePercentModeColumns()) {
$data['cash_fee_buy_is_percent'] = filter_var(
$data['cash_fee_buy_is_percent'] ?? false,
FILTER_VALIDATE_BOOLEAN
);
$data['cash_fee_sell_is_percent'] = filter_var(
$data['cash_fee_sell_is_percent'] ?? false,
FILTER_VALIDATE_BOOLEAN
);
} else {
unset($data['cash_fee_buy_is_percent'], $data['cash_fee_sell_is_percent']);
}
2026-06-02 16:00:33 +03:30
return $data;
}
2026-07-04 22:06:20 +03:30
private function normalizeCashFeeValue(mixed $value): float
{
if ($value === null || $value === '') {
return 0.0;
}
if (is_string($value)) {
2026-07-04 22:14:18 +03:30
$value = trim(str_replace([' ', '٬'], '', $value));
if (str_contains($value, ',') && ! str_contains($value, '.')) {
$value = str_replace(',', '.', $value);
} else {
$value = str_replace(',', '', $value);
}
2026-07-04 22:06:20 +03:30
}
return (float) $value;
}
2026-07-08 12:45:14 +03:30
private function normalizeOrderLimitCash(mixed $value): ?int
{
if ($value === null || $value === '') {
return null;
}
if (is_int($value)) {
return $value >= 0 ? $value : null;
}
if (is_float($value)) {
return $value >= 0 ? (int) $value : null;
}
if (! is_string($value)) {
return null;
}
$value = $this->normalizeAsciiDigits(trim(str_replace([' ', ',', '٬'], '', $value)));
if ($value === '' || ! ctype_digit($value)) {
return null;
}
return (int) $value;
}
private function normalizeAsciiDigits(string $value): string
{
$persian = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
$arabic = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'];
$latin = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
return str_replace(array_merge($persian, $arabic), array_merge($latin, $latin), $value);
}
2026-01-24 14:03:11 +00:00
}