attributes['price_offset_percent']); } if (!static::hasOrderLimitColumns()) { unset( $product->attributes['order_limit_enabled'], $product->attributes['min_order_cash'], $product->attributes['max_order_cash'] ); } if (!static::hasShowInAssetsColumn()) { unset($product->attributes['show_in_assets']); } if (!static::hasShowOnTvColumn()) { unset($product->attributes['show_on_tv']); } if (!static::hasShowInListColumn()) { unset($product->attributes['show_in_list']); } if (!static::hasUseLastPriceIfZeroColumn()) { unset($product->attributes['use_last_price_if_zero']); } if (!static::hasDirectPriceColumn()) { unset($product->attributes['direct_price']); } if (!static::hasAppPriceColumn()) { unset($product->attributes['app_price']); } if (!static::hasTradeByQuantityColumn()) { unset($product->attributes['trade_by_quantity']); } if (!static::hasInventoryColumn()) { unset($product->attributes['inventory']); } if (!static::hasDescriptionColumn()) { unset($product->attributes['description']); } if (!static::hasKaratColumn()) { unset($product->attributes['karat']); } if (!static::hasOverrideTradingHoursColumn()) { unset($product->attributes['override_trading_hours']); } if (!static::hasCashFeePercentModeColumns()) { unset( $product->attributes['cash_fee_buy_is_percent'], $product->attributes['cash_fee_sell_is_percent'] ); } if (!static::hasPhysicalDeliveryColumns()) { unset( $product->attributes['has_physical_delivery'], $product->attributes['min_physical_delivery_quantity'] ); } if (!static::hasPriceFormulaColumns()) { unset( $product->attributes['price_formula_enabled'], $product->attributes['price_formula'] ); } if (!static::hasTvPriceFormulaColumns()) { unset( $product->attributes['tv_price_formula_enabled'], $product->attributes['tv_price_formula'] ); } }); } public static function hasTvPriceFormulaColumns(): bool { if (static::$tvPriceFormulaColumnsExist !== null) { return static::$tvPriceFormulaColumnsExist; } try { $table = (new static)->getTable(); static::$tvPriceFormulaColumnsExist = Schema::hasColumn($table, 'tv_price_formula_enabled') && Schema::hasColumn($table, 'tv_price_formula'); } catch (\Throwable) { static::$tvPriceFormulaColumnsExist = false; } return static::$tvPriceFormulaColumnsExist; } public static function hasPriceFormulaColumns(): bool { if (static::$priceFormulaColumnsExist !== null) { return static::$priceFormulaColumnsExist; } try { $table = (new static)->getTable(); static::$priceFormulaColumnsExist = Schema::hasColumn($table, 'price_formula_enabled') && Schema::hasColumn($table, 'price_formula'); } catch (\Throwable) { static::$priceFormulaColumnsExist = false; } return static::$priceFormulaColumnsExist; } public static function hasPhysicalDeliveryColumns(): bool { if (static::$physicalDeliveryColumnsExist !== null) { return static::$physicalDeliveryColumnsExist; } try { $table = (new static)->getTable(); static::$physicalDeliveryColumnsExist = Schema::hasColumn($table, 'has_physical_delivery') && Schema::hasColumn($table, 'min_physical_delivery_quantity'); } catch (\Throwable) { static::$physicalDeliveryColumnsExist = false; } return static::$physicalDeliveryColumnsExist; } public static function hasOverrideTradingHoursColumn(): bool { if (static::$overrideTradingHoursColumnExists !== null) { return static::$overrideTradingHoursColumnExists; } try { static::$overrideTradingHoursColumnExists = Schema::hasColumn( (new static)->getTable(), 'override_trading_hours' ); } catch (\Throwable) { static::$overrideTradingHoursColumnExists = false; } return static::$overrideTradingHoursColumnExists; } public static function hasCashFeePercentModeColumns(): bool { if (static::$cashFeePercentModeColumnsExist !== null) { return static::$cashFeePercentModeColumnsExist; } try { static::$cashFeePercentModeColumnsExist = Schema::hasColumn( (new static)->getTable(), 'cash_fee_buy_is_percent' ) && Schema::hasColumn( (new static)->getTable(), 'cash_fee_sell_is_percent' ); } catch (\Throwable) { static::$cashFeePercentModeColumnsExist = false; } return static::$cashFeePercentModeColumnsExist; } public static function hasPriceOffsetColumn(): bool { if (static::$priceOffsetColumnExists !== null) { return static::$priceOffsetColumnExists; } try { static::$priceOffsetColumnExists = Schema::hasColumn( (new static)->getTable(), 'price_offset_percent' ); } catch (\Throwable) { static::$priceOffsetColumnExists = false; } return static::$priceOffsetColumnExists; } /** گلد علی: جدول products ممکن است ستون price نداشته باشد */ public static function hasStoredPriceColumns(): bool { if (static::$priceColumnExists !== null) { return static::$priceColumnExists; } try { static::$priceColumnExists = Schema::hasColumn((new static)->getTable(), 'price'); } catch (\Throwable) { static::$priceColumnExists = false; } return static::$priceColumnExists; } public static function hasOrderLimitColumns(): bool { if (static::$orderLimitColumnsExist !== null) { return static::$orderLimitColumnsExist; } try { $table = (new static)->getTable(); static::$orderLimitColumnsExist = Schema::hasColumn($table, 'order_limit_enabled') && Schema::hasColumn($table, 'min_order_cash') && Schema::hasColumn($table, 'max_order_cash'); } catch (\Throwable) { static::$orderLimitColumnsExist = false; } return static::$orderLimitColumnsExist; } public static function hasShowInAssetsColumn(): bool { if (static::$showInAssetsColumnExists !== null) { return static::$showInAssetsColumnExists; } try { static::$showInAssetsColumnExists = Schema::hasColumn( (new static)->getTable(), 'show_in_assets' ); } catch (\Throwable) { static::$showInAssetsColumnExists = false; } return static::$showInAssetsColumnExists; } public static function hasShowOnTvColumn(): bool { if (static::$showOnTvColumnExists !== null) { return static::$showOnTvColumnExists; } try { static::$showOnTvColumnExists = Schema::hasColumn( (new static)->getTable(), 'show_on_tv' ); } catch (\Throwable) { static::$showOnTvColumnExists = false; } return static::$showOnTvColumnExists; } public static function hasShowInListColumn(): bool { if (static::$showInListColumnExists !== null) { return static::$showInListColumnExists; } try { static::$showInListColumnExists = Schema::hasColumn( (new static)->getTable(), 'show_in_list' ); } catch (\Throwable) { static::$showInListColumnExists = false; } return static::$showInListColumnExists; } public static function hasUseLastPriceIfZeroColumn(): bool { if (static::$useLastPriceIfZeroColumnExists !== null) { return static::$useLastPriceIfZeroColumnExists; } try { static::$useLastPriceIfZeroColumnExists = Schema::hasColumn( (new static)->getTable(), 'use_last_price_if_zero' ); } catch (\Throwable) { static::$useLastPriceIfZeroColumnExists = false; } return static::$useLastPriceIfZeroColumnExists; } public static function hasDirectPriceColumn(): bool { if (static::$directPriceColumnExists !== null) { return static::$directPriceColumnExists; } try { static::$directPriceColumnExists = Schema::hasColumn( (new static)->getTable(), 'direct_price' ); } catch (\Throwable) { static::$directPriceColumnExists = false; } return static::$directPriceColumnExists; } public static function hasAppPriceColumn(): bool { if (static::$appPriceColumnExists !== null) { return static::$appPriceColumnExists; } try { static::$appPriceColumnExists = Schema::hasColumn( (new static)->getTable(), 'app_price' ); } catch (\Throwable) { static::$appPriceColumnExists = false; } return static::$appPriceColumnExists; } public static function hasTradeByQuantityColumn(): bool { if (static::$tradeByQuantityColumnExists !== null) { return static::$tradeByQuantityColumnExists; } try { static::$tradeByQuantityColumnExists = Schema::hasColumn( (new static)->getTable(), 'trade_by_quantity' ); } catch (\Throwable) { static::$tradeByQuantityColumnExists = false; } return static::$tradeByQuantityColumnExists; } public static function hasInventoryColumn(): bool { if (static::$inventoryColumnExists !== null) { return static::$inventoryColumnExists; } try { static::$inventoryColumnExists = Schema::hasColumn( (new static)->getTable(), 'inventory' ); } catch (\Throwable) { static::$inventoryColumnExists = false; } return static::$inventoryColumnExists; } public static function hasDescriptionColumn(): bool { if (static::$descriptionColumnExists !== null) { return static::$descriptionColumnExists; } try { static::$descriptionColumnExists = Schema::hasColumn( (new static)->getTable(), 'description' ); } catch (\Throwable) { static::$descriptionColumnExists = false; } return static::$descriptionColumnExists; } public static function hasKaratColumn(): bool { if (static::$karatColumnExists !== null) { return static::$karatColumnExists; } try { static::$karatColumnExists = Schema::hasColumn( (new static)->getTable(), 'karat' ); } catch (\Throwable) { static::$karatColumnExists = false; } return static::$karatColumnExists; } public function orderLimitsEnabled(): bool { if (!static::hasOrderLimitColumns() || !($this->order_limit_enabled ?? false)) { return false; } return $this->min_order_cash !== null || $this->max_order_cash !== null; } public function validateOrderCashLimit(float $totalPriceRial): void { if (!$this->orderLimitsEnabled()) { return; } $min = $this->min_order_cash; $max = $this->max_order_cash; // یک ریال تلرانس — وقتی target_cash به گرم تبدیل و دوباره ضرب می‌شود ممکن است // total_price موقت چند ریال از حداقل کمتر شود (مثلاً دقیقاً ۲۰۰,۰۰۰,۰۰۰). $toleranceRial = 1.0; if ($min !== null && $totalPriceRial + $toleranceRial < (float) $min) { throw new \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException( 'حداقل مبلغ این محصول ' . number_format((float) $min) . ' ریال می‌باشد' ); } if ($max !== null && $totalPriceRial > (float) $max + $toleranceRial) { throw new \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException( 'حداکثر مبلغ این محصول ' . number_format((float) $max) . ' ریال می‌باشد' ); } } public function physicalDeliveryEnabled(): bool { if (static::hasPhysicalDeliveryColumns()) { return (bool) ($this->has_physical_delivery ?? false); } return (bool) ($this->attributes['has_physical_delivery'] ?? false); } public function minPhysicalDeliveryQuantity(): float { $value = static::hasPhysicalDeliveryColumns() ? ($this->min_physical_delivery_quantity ?? 0) : ($this->attributes['min_physical_delivery_quantity'] ?? 0); return max(0.0, (float) $value); } public function getPriceOffsetPercentAttribute($value): float { if (!static::hasPriceOffsetColumn()) { return 0.0; } return (float) ($value ?? 0); } protected $fillable = [ 'title', // The title of the record. 'price', // The amount of the record. 'amount', // The amount of the record. 'unit', // The unit of the item. 'group_type', // The unit of the item. 'finance_p_name', // کد کار زرین (idKar) 'file_id', // The ID of the associated file. 'can_canceled', // Whether the record can be canceled (boolean). 'active', // The active status of the record (boolean). 'for_barayand', // Whether it is for Barayand (boolean). 'show', // Whether to show the record (boolean). 'show_in_assets', // Show balance on customer assets page cards 'show_on_tv', // Show this product on customer /tv board 'show_in_list', // Show this product in customer product list 'trade_by_quantity', // UI معامله مقداری: ارزش کل + مقدار 'use_last_price_if_zero', // Keep last DB price when automatic provider returns zero 'open_sell', // Whether selling is open (boolean). 'auto_open_at', // Whether selling is open (boolean). 'auto_close_at', // Whether selling is open (boolean). 'override_trading_hours', // روشن: بازه ساعت خود محصول جایگزین بازه سراسری می‌شود 'kalhor_name', // The name of the Kalhor entity. 'default_credit', // Default credit amount. 'reading_method', // The method of reading. 'open_buy', // Whether buying is open (boolean). 'percent_fee_sell', // Add percent_fee_sell to fillable 'cash_fee_buy', // Add cash_fee_buy to fillable 'cash_fee_sell', // Add cash_fee_sell to fillable 'cash_fee_buy_is_percent', 'cash_fee_sell_is_percent', 'percent_fee_buy', // Add percent_fee_buy to fillable 'order_limit_enabled', 'min_order_cash', 'max_order_cash', 'has_physical_delivery', // روشن: این محصول امکان تحویل فیزیکی دارد 'min_physical_delivery_quantity', // حداقل موجودی برای فعال شدن تحویل (عدد برای تعدادی، گرم/کیلوگرم برای وزنی) 'metal_id', //the material of metal (#gold or #silver) 'price_offset_percent', // admin only: +/- % vs automatic reference (baked into price on sync) 'price_profit', 'bubble', 'inventory', 'description', 'karat', 'direct_price', 'app_price', // روشن: قیمت واحد از قیمت زندهٔ اپ هر گرم × وزن محاسبه می‌شود 'price_formula_enabled', 'price_formula', 'tv_price_formula_enabled', 'tv_price_formula', ]; protected $attributes = [ 'show_in_assets' => false, 'show_on_tv' => false, 'show_in_list' => true, 'trade_by_quantity' => false, 'use_last_price_if_zero' => false, 'cash_fee_buy_is_percent' => false, 'cash_fee_sell_is_percent' => false, 'app_price' => false, 'price_formula_enabled' => false, 'tv_price_formula_enabled' => false, ]; protected $casts = [ 'can_canceled' => 'boolean', // Cast to boolean. 'active' => 'boolean', // Cast to boolean. 'show' => 'boolean', // Cast to boolean. 'show_in_assets' => 'boolean', 'show_on_tv' => 'boolean', 'show_in_list' => 'boolean', 'trade_by_quantity' => 'boolean', 'use_last_price_if_zero' => 'boolean', 'override_trading_hours' => 'boolean', 'for_barayand' => 'boolean', // Cast to boolean. 'open_sell' => 'boolean', // Cast to boolean. 'auto_sell' => 'boolean', // Cast to boolean (you mentioned this in your casts but it's missing in `$fillable`). 'open_buy' => 'boolean', // Cast to boolean. 'auto_buy' => 'boolean', // Cast to boolean. 'price' => 'int', // Cast to float. 'percent_fee_sell' => 'float', // Cast to float. 'percent_fee_buy' => 'float', // Cast to float. 'order_limit_enabled' => 'boolean', 'min_order_cash' => 'int', 'max_order_cash' => 'int', 'has_physical_delivery' => 'boolean', 'min_physical_delivery_quantity' => 'float', 'cash_fee_buy' => 'float', 'cash_fee_sell' => 'float', 'cash_fee_buy_is_percent' => 'boolean', 'cash_fee_sell_is_percent' => 'boolean', 'price_offset_percent' => 'float', 'price_profit' => 'int', 'app_price' => 'boolean', 'price_formula_enabled' => 'boolean', 'tv_price_formula_enabled' => 'boolean', ]; static $METAL_ID = [ 1=>'طلا', 2=>'نقره', ]; public function wallet() { return $this->hasOne(Wallet::class); } public function walletMy() { return $this->hasOne(Wallet::class)->where('owner',auth()->user()->id); } public function file(){ return $this->belongsTo(File::class); } public function price(){ return $this->hasOne(Price::class)->orderBy('id','desc'); } public function getPrices() { $now = Carbon::now(); $lastMonth = Carbon::now()->subMonth(); $subQuery = DB::table('prices') ->select('product_id', 'date_at', 'price') ->whereBetween('date_at', [$lastMonth, $now]) ->where('product_id', $this->id) ->orderBy('date_at', 'desc') ->distinct('date_at') ->groupBy('date_at', 'product_id', 'price'); return DB::table(DB::raw("({$subQuery->toSql()}) as sub")) ->mergeBindings($subQuery->getQuery()) ->get(['product_id', 'date_at', 'price']); } public function prices(){ $now = Carbon::now(); $lastMonth = Carbon::now()->subMonth(); $data = []; $datas= $this->hasMany(Price::class, 'product_id') ->whereBetween('date_at', [$lastMonth, $now]) ->distinct('date_at') ->select(['product_id', 'date_at', 'difference', 'price']); return $datas; } public function default(){ return $this->hasMany(ProductCredit::class); } public function sub(){ return $this->hasOne(PackageProduct::class)->where('is_base',true); } // public function wallet(){ // return $this->hasOne(Wallet::class); // } public function transaction(){ return $this->hasMany(Transaction::class); } public function limitedTransactions() { return $this->hasMany(Transaction::class)->select('id', 'gold', 'title', 'created_at', 'product_id'); } public function transactionAgentPanel(){ return $this->hasMany(Transaction::class)->where('note','برداشت بابت فروش')->orwhere('note','واریز بابت خرید')->orwhere('note','سند ورود طلا'); } }