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 ()
{
2026-05-17 16:39:53 +00:00
$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 ;
}
2026-07-21 16:20:36 +03:30
// قیمت دستی ⇒ تعدیل مرجع بیمعناست و صفر میشود.
// قیمت اپ در filterProductData عمداً price=0 میگذارد؛ نباید تعدیل را پاک کند.
if ( isset ( $data [ 'price' ]) && empty ( $data [ 'app_price' ])) {
$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
}