diff --git a/app/Http/Controllers/Admin/PriceController.php b/app/Http/Controllers/Admin/PriceController.php index 705f89d..fbfdec3 100644 --- a/app/Http/Controllers/Admin/PriceController.php +++ b/app/Http/Controllers/Admin/PriceController.php @@ -6,6 +6,7 @@ use App\DB\Te\TableEngine; use App\Http\Controllers\Controller; use App\Models\Price; use App\Models\PriceCandle; +use App\Services\ZaarboxPriceService; use Illuminate\Http\Request; class PriceController extends Controller @@ -46,6 +47,18 @@ class PriceController extends Controller ->export($prise); return response()->json($prise); } + + public function chartPrices(ZaarboxPriceService $zaarboxPriceService) + { + return response()->json([ + 'code' => 0, + 'message' => 'عملیات با موفقیت انجام شد.', + 'data' => [ + 'prices' => $zaarboxPriceService->getAllPrices(), + ], + ]); + } + public function table(Request $request){ $records = Price::with(['user']); TableEngine::create($records, $request->all()) diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 8b08d51..3e00a51 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -349,6 +349,33 @@ class ProductController extends SuperController unset($data['price_offset_percent']); } + if (!Product::hasOrderLimitColumns()) { + unset($data['order_limit_enabled'], $data['min_order_cash'], $data['max_order_cash']); + } elseif (empty($data['order_limit_enabled'])) { + $data['order_limit_enabled'] = false; + $data['min_order_cash'] = null; + $data['max_order_cash'] = null; + } else { + $min = $data['min_order_cash'] ?? null; + $max = $data['max_order_cash'] ?? null; + $hasMin = $min !== null && $min !== ''; + $hasMax = $max !== null && $max !== ''; + + if (!$hasMin && !$hasMax) { + throw new UnprocessableEntityHttpException( + 'با فعال بودن محدودیت مبلغ سفارش، حداقل یکی از حداقل یا حداکثر مبلغ باید وارد شود' + ); + } + + if ($hasMin && $hasMax && (float) $min > (float) $max) { + throw new UnprocessableEntityHttpException('حداقل مبلغ نمی‌تواند بیشتر از حداکثر باشد'); + } + + $data['order_limit_enabled'] = true; + $data['min_order_cash'] = $hasMin ? (int) $min : null; + $data['max_order_cash'] = $hasMax ? (int) $max : null; + } + return $data; } } diff --git a/app/Http/Controllers/Single/SingleOrderController.php b/app/Http/Controllers/Single/SingleOrderController.php index 43dd385..504ec1e 100644 --- a/app/Http/Controllers/Single/SingleOrderController.php +++ b/app/Http/Controllers/Single/SingleOrderController.php @@ -219,6 +219,8 @@ class SingleOrderController extends Controller $data['total_price'] = $data['amount'] * $data['price']; $data['payment_amount'] = $data['total_price']; + $this->enforceProductOrderLimits($data, (float) $data['total_price']); + if (!empty($data['first_buy']) && ($minFirstBuy = Setting::where('is_active', 1)->where('key', 'min_first_buy')->value('value'))) { if ($data['total_price'] < (int) $minFirstBuy) { throw new UnprocessableEntityHttpException('حداقل سفارش اول ' . number_format((int) $minFirstBuy) . ' ریال میباشد.'); @@ -256,6 +258,8 @@ class SingleOrderController extends Controller $data['total_price'] = $data['total_amount'] * $data['price']; $data['payment_amount'] = $data['total_price']; + $this->enforceProductOrderLimits($data, (float) $data['total_price']); + if (!empty($data['first_buy']) && ($minFirstBuy = Setting::where('is_active', 1)->where('key', 'min_first_buy')->value('value'))) { if ($data['total_price'] < (int) $minFirstBuy) { throw new UnprocessableEntityHttpException('حداقل سفارش اول ' . number_format((int) $minFirstBuy) . ' ریال میباشد.'); @@ -306,6 +310,7 @@ class SingleOrderController extends Controller $data['total_amount'] = $data['amount'] + ($data['product']['percent_fee_buy'] * $data['amount']); $data['total_price'] = $data['total_amount'] * $data['price']; $data['payment_amount'] = $data['total_price']; + $this->enforceProductOrderLimits($data, (float) $data['total_price']); $data['service_charge'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : ($data['product']['percent_fee_buy'] * $data['amount']); $data['total_fee'] = 0; $data['service_charge_price'] = $data['service_charge'] * $data['price_original']; @@ -345,6 +350,8 @@ class SingleOrderController extends Controller { $response = []; $data['price'] -= $data['difference']; + $grossTotal = $data['amount'] * $data['price']; + $this->enforceProductOrderLimits($data, (float) $grossTotal); $data['service_charge'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : $data['service_charge_percentage'] * $data['amount']; $data['service_charge_price'] = $data['service_charge'] * $data['price_original']; $data['total_price'] = $data['amount'] * $data['price'] - $data['service_charge_price']; @@ -376,6 +383,8 @@ class SingleOrderController extends Controller $data['total_amount'] = $data['amount'] - ($data['product']['percent_fee_sell'] * $data['amount']); $data['total_price'] = $data['total_amount'] * $data['price']; $data['payment_amount'] = $data['total_price']; + $grossTotal = (float) ($data['amount'] * $data['price']); + $this->enforceProductOrderLimits($data, $grossTotal); $data['service_charge'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : ($data['product']['percent_fee_sell'] * $data['amount']); $data['service_charge_price'] = $data['service_charge'] * $data['price_original']; $wallet_interface_product = new WalletInterface(auth('customer')->id(), auth('customer')->user()->type, null, $data["product_id"]); @@ -417,6 +426,8 @@ class SingleOrderController extends Controller $data['total_amount'] = $data['amount'] - ($data['product']['percent_fee_sell'] * $data['amount']); $data['total_price'] = $data['total_amount'] * $data['price']; $data['payment_amount'] = $data['total_price']; + $grossTotal = (float) ($data['amount'] * $data['price']); + $this->enforceProductOrderLimits($data, $grossTotal); $data['service_charge'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : ($data['product']['percent_fee_sell'] * $data['amount']); $data['total_fee'] = 0; $data['service_charge_price'] = $data['service_charge'] * $data['price_original']; @@ -843,4 +854,28 @@ class SingleOrderController extends Controller return false; } + + private function enforceProductOrderLimits(array &$data, float $totalPriceRial): void + { + $product = $data['product'] ?? null; + + if (!$product instanceof Product) { + $productId = (int) ($data['abshode_id'] ?? $data['product_id'] ?? 0); + if ($productId) { + $product = Product::find($productId); + if ($product) { + $data['product'] = $product; + } + } + } elseif (is_array($product)) { + $product = Product::find($product['id'] ?? $data['product_id'] ?? 0); + if ($product) { + $data['product'] = $product; + } + } + + if ($product instanceof Product) { + $product->validateOrderCashLimit($totalPriceRial); + } + } } diff --git a/app/Models/Product.php b/app/Models/Product.php index 7d613b9..f2b3f6d 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -17,6 +17,7 @@ class Product extends Model private static ?bool $priceOffsetColumnExists = null; private static ?bool $priceColumnExists = null; + private static ?bool $orderLimitColumnsExist = null; protected static function booted(): void { @@ -24,6 +25,13 @@ class Product extends Model if (!static::hasPriceOffsetColumn()) { unset($product->attributes['price_offset_percent']); } + if (!static::hasOrderLimitColumns()) { + unset( + $product->attributes['order_limit_enabled'], + $product->attributes['min_order_cash'], + $product->attributes['max_order_cash'] + ); + } }); } @@ -61,6 +69,55 @@ class Product extends Model return static::$priceColumnExists; } + public static function hasOrderLimitColumns(): bool + { + if (static::$orderLimitColumnsExist !== null) { + return static::$orderLimitColumnsExist; + } + + try { + static::$orderLimitColumnsExist = Schema::hasColumn( + (new static)->getTable(), + 'order_limit_enabled' + ); + } catch (\Throwable) { + static::$orderLimitColumnsExist = false; + } + + return static::$orderLimitColumnsExist; + } + + 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; + + if ($min !== null && $totalPriceRial < (float) $min) { + throw new \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException( + 'حداقل مبلغ این محصول ' . number_format((float) $min) . ' ریال می‌باشد' + ); + } + + if ($max !== null && $totalPriceRial > (float) $max) { + throw new \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException( + 'حداکثر مبلغ این محصول ' . number_format((float) $max) . ' ریال می‌باشد' + ); + } + } + public function getPriceOffsetPercentAttribute($value): float { if (!static::hasPriceOffsetColumn()) { @@ -91,6 +148,9 @@ class Product extends Model 'cash_fee_buy', // Add cash_fee_buy to fillable 'cash_fee_sell', // Add cash_fee_sell to fillable 'percent_fee_buy', // Add percent_fee_buy to fillable + 'order_limit_enabled', + 'min_order_cash', + 'max_order_cash', 'metal_id', //the material of metal (#gold or #silver) 'price_offset_percent', // admin only: +/- % vs automatic reference (baked into price on sync) 'price_profit', @@ -109,6 +169,9 @@ class Product extends Model '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', 'cash_fee_buy' => 'int', // Cast to int or decimal if needed 'cash_fee_sell' => 'int', // Cast to int or decimal if needed 'price_offset_percent' => 'float', diff --git a/database/migrations/2026_06_06_000003_add_order_limit_columns_to_products_table.php b/database/migrations/2026_06_06_000003_add_order_limit_columns_to_products_table.php new file mode 100644 index 0000000..16d0c19 --- /dev/null +++ b/database/migrations/2026_06_06_000003_add_order_limit_columns_to_products_table.php @@ -0,0 +1,43 @@ +boolean('order_limit_enabled')->default(false)->after('percent_fee_buy'); + } + if (!Schema::hasColumn('products', 'min_order_cash')) { + $table->unsignedBigInteger('min_order_cash')->nullable()->after('order_limit_enabled'); + } + if (!Schema::hasColumn('products', 'max_order_cash')) { + $table->unsignedBigInteger('max_order_cash')->nullable()->after('min_order_cash'); + } + }); + } + + public function down(): void + { + if (!Schema::hasTable('products')) { + return; + } + + Schema::table('products', function (Blueprint $table) { + $columns = ['order_limit_enabled', 'min_order_cash', 'max_order_cash']; + foreach ($columns as $column) { + if (Schema::hasColumn('products', $column)) { + $table->dropColumn($column); + } + } + }); + } +}; diff --git a/database/sql/add_product_order_limit_columns.sql b/database/sql/add_product_order_limit_columns.sql new file mode 100644 index 0000000..3b4544d --- /dev/null +++ b/database/sql/add_product_order_limit_columns.sql @@ -0,0 +1,37 @@ +-- محدودیت مبلغ سفارش روی محصول +-- امن برای اجرای مجدد: اگر ستون وجود داشته باشد خطا نمی‌دهد +-- USE دیتابیس_خودتان; + +SET @db = DATABASE(); + +SET @sql = IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' AND COLUMN_NAME = 'order_limit_enabled') = 0, + 'ALTER TABLE `products` ADD COLUMN `order_limit_enabled` TINYINT(1) NOT NULL DEFAULT 0 AFTER `percent_fee_buy`', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @sql = IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' AND COLUMN_NAME = 'min_order_cash') = 0, + 'ALTER TABLE `products` ADD COLUMN `min_order_cash` BIGINT UNSIGNED NULL DEFAULT NULL AFTER `order_limit_enabled`', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @sql = IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' AND COLUMN_NAME = 'max_order_cash') = 0, + 'ALTER TABLE `products` ADD COLUMN `max_order_cash` BIGINT UNSIGNED NULL DEFAULT NULL AFTER `min_order_cash`', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- مقادیر محصولات (مثلاً گلد علی) را فقط از پنل ادمین تنظیم کنید؛ این اسکریپت فقط ستون‌ها را اضافه می‌کند. diff --git a/routes/admin.php b/routes/admin.php index 5aae25f..d10cd43 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -110,6 +110,7 @@ Route::middleware('auth:user')->group(function (){ Route::post('price', [PriceController::class, 'store']); Route::post('price/table', [PriceController::class, 'table']); Route::post('prices', [PriceController::class, 'priceData']); + Route::post('prices/chart', [PriceController::class, 'chartPrices']); Route::post('finance', [FinanceController::class, 'fall']);