diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 8e7fbd1..802d61a 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -37,5 +37,18 @@ class Handler extends ExceptionHandler $this->reportable(function (Throwable $e) { // }); + + $this->renderable(function (\Illuminate\Database\QueryException $e, $request) { + if ((int) ($e->errorInfo[1] ?? 0) !== 1264) { + return null; + } + if (! $request->expectsJson()) { + return null; + } + + return response()->json([ + 'message' => 'ثبت سفارش به‌خاطر محدودیت فیلد قیمت ممکن نشد. لطفاً دوباره تلاش کنید.', + ], 422); + }); } } diff --git a/app/Http/Controllers/Single/SingleOrderController.php b/app/Http/Controllers/Single/SingleOrderController.php index d0268f4..9ec2bea 100644 --- a/app/Http/Controllers/Single/SingleOrderController.php +++ b/app/Http/Controllers/Single/SingleOrderController.php @@ -1801,6 +1801,30 @@ class SingleOrderController extends Controller $data['_fee_uses_app_price'], $data['_fee_base_price'] ); + + $this->roundOrderMoneyFields($data); + } + + /** اعداد پولی سفارش را قبل از INSERT گرد کن تا با DECIMAL(20,4) سازگار باشد. */ + private function roundOrderMoneyFields(array &$data): void + { + foreach ([ + 'price', + 'price_original', + 'total_price', + 'total_price_original', + 'total_price_liman', + 'fee', + 'total_fee', + 'service_charge_price', + 'payment_amount', + 'payment_remind', + ] as $key) { + if (!isset($data[$key]) || !is_numeric($data[$key])) { + continue; + } + $data[$key] = round((float) $data[$key], 4); + } } private function bonusGift($customer, $referrer) diff --git a/database/migrations/2026_08_31_000001_widen_orders_price_columns.php b/database/migrations/2026_08_31_000001_widen_orders_price_columns.php new file mode 100644 index 0000000..a2d7fde --- /dev/null +++ b/database/migrations/2026_08_31_000001_widen_orders_price_columns.php @@ -0,0 +1,78 @@ + */ + private const ORDER_MONEY_COLUMNS = [ + 'price', + 'price_original', + 'total_price', + 'total_price_original', + 'total_price_liman', + 'fee', + 'total_fee', + 'service_charge_price', + 'payment_amount', + 'payment_remind', + ]; + + public function up(): void + { + if (Schema::hasTable('orders')) { + foreach (self::ORDER_MONEY_COLUMNS as $column) { + $this->widenMoneyColumn('orders', $column); + } + } + + if (Schema::hasTable('prices')) { + $this->widenMoneyColumn('prices', 'price'); + } + } + + public function down(): void + { + // کوچک‌کردن ستون دادهٔ موجود را خراب می‌کند + } + + private function widenMoneyColumn(string $table, string $column): void + { + $schema = DB::connection()->getDatabaseName(); + $tableName = DB::getTablePrefix().$table; + + $meta = DB::selectOne( + 'SELECT DATA_TYPE, NUMERIC_PRECISION, NUMERIC_SCALE + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?', + [$schema, $tableName, $column] + ); + + if ($meta === null) { + return; + } + + $type = strtolower((string) $meta->DATA_TYPE); + $precision = (int) ($meta->NUMERIC_PRECISION ?? 0); + $scale = (int) ($meta->NUMERIC_SCALE ?? 0); + $integerDigits = in_array($type, ['decimal', 'numeric'], true) + ? max(0, $precision - $scale) + : $precision; + + $alreadyWideDecimal = in_array($type, ['decimal', 'numeric'], true) + && $integerDigits >= 16 + && $scale >= 4; + $alreadyBigint = $type === 'bigint'; + + if ($alreadyWideDecimal || $alreadyBigint) { + return; + } + + DB::statement("ALTER TABLE `{$tableName}` MODIFY `{$column}` DECIMAL(20,4) NULL"); + } +}; diff --git a/database/sql/deploy_safe_schema.sql b/database/sql/deploy_safe_schema.sql index bb79cd7..64aacec 100644 --- a/database/sql/deploy_safe_schema.sql +++ b/database/sql/deploy_safe_schema.sql @@ -610,6 +610,142 @@ PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; +-- --------------------------------------------------------------------------- +-- orders / prices: ظرفیت مظنهٔ ریالی (خطای 1264 Out of range column price) +-- DECIMAL(10,2) حداکثر ~۹۹ میلیون است؛ مظنهٔ گرم طلا از این سقف گذشته. +-- --------------------------------------------------------------------------- +SET @sql = IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='price') > 0 + AND (SELECT DATA_TYPE FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='price') <> 'bigint' + AND (SELECT IFNULL(NUMERIC_PRECISION, 0) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='price') < 20, + 'ALTER TABLE `orders` MODIFY `price` DECIMAL(20,4) NULL', + 'SELECT ''skip orders.price widen'' AS info' +); +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='orders' AND COLUMN_NAME='price_original') > 0 + AND (SELECT DATA_TYPE FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='price_original') <> 'bigint' + AND (SELECT IFNULL(NUMERIC_PRECISION, 0) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='price_original') < 20, + 'ALTER TABLE `orders` MODIFY `price_original` DECIMAL(20,4) NULL', + 'SELECT ''skip orders.price_original widen'' AS info' +); +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='orders' AND COLUMN_NAME='total_price') > 0 + AND (SELECT DATA_TYPE FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='total_price') <> 'bigint' + AND (SELECT IFNULL(NUMERIC_PRECISION, 0) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='total_price') < 20, + 'ALTER TABLE `orders` MODIFY `total_price` DECIMAL(20,4) NULL', + 'SELECT ''skip orders.total_price widen'' AS info' +); +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='orders' AND COLUMN_NAME='total_price_original') > 0 + AND (SELECT DATA_TYPE FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='total_price_original') <> 'bigint' + AND (SELECT IFNULL(NUMERIC_PRECISION, 0) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='total_price_original') < 20, + 'ALTER TABLE `orders` MODIFY `total_price_original` DECIMAL(20,4) NULL', + 'SELECT ''skip orders.total_price_original widen'' AS info' +); +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='orders' AND COLUMN_NAME='fee') > 0 + AND (SELECT DATA_TYPE FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='fee') <> 'bigint' + AND (SELECT IFNULL(NUMERIC_PRECISION, 0) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='fee') < 20, + 'ALTER TABLE `orders` MODIFY `fee` DECIMAL(20,4) NULL', + 'SELECT ''skip orders.fee widen'' AS info' +); +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='orders' AND COLUMN_NAME='total_fee') > 0 + AND (SELECT DATA_TYPE FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='total_fee') <> 'bigint' + AND (SELECT IFNULL(NUMERIC_PRECISION, 0) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='total_fee') < 20, + 'ALTER TABLE `orders` MODIFY `total_fee` DECIMAL(20,4) NULL', + 'SELECT ''skip orders.total_fee widen'' AS info' +); +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='orders' AND COLUMN_NAME='service_charge_price') > 0 + AND (SELECT DATA_TYPE FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='service_charge_price') <> 'bigint' + AND (SELECT IFNULL(NUMERIC_PRECISION, 0) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='service_charge_price') < 20, + 'ALTER TABLE `orders` MODIFY `service_charge_price` DECIMAL(20,4) NULL', + 'SELECT ''skip orders.service_charge_price widen'' AS info' +); +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='orders' AND COLUMN_NAME='payment_amount') > 0 + AND (SELECT DATA_TYPE FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='payment_amount') <> 'bigint' + AND (SELECT IFNULL(NUMERIC_PRECISION, 0) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='payment_amount') < 20, + 'ALTER TABLE `orders` MODIFY `payment_amount` DECIMAL(20,4) NULL', + 'SELECT ''skip orders.payment_amount widen'' AS info' +); +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='orders' AND COLUMN_NAME='payment_remind') > 0 + AND (SELECT DATA_TYPE FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='payment_remind') <> 'bigint' + AND (SELECT IFNULL(NUMERIC_PRECISION, 0) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='payment_remind') < 20, + 'ALTER TABLE `orders` MODIFY `payment_remind` DECIMAL(20,4) NULL', + 'SELECT ''skip orders.payment_remind widen'' AS info' +); +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='orders' AND COLUMN_NAME='total_price_liman') > 0 + AND (SELECT DATA_TYPE FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='total_price_liman') <> 'bigint' + AND (SELECT IFNULL(NUMERIC_PRECISION, 0) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='orders' AND COLUMN_NAME='total_price_liman') < 20, + 'ALTER TABLE `orders` MODIFY `total_price_liman` DECIMAL(20,4) NULL', + 'SELECT ''skip orders.total_price_liman widen'' AS info' +); +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='prices' AND COLUMN_NAME='price') > 0 + AND (SELECT DATA_TYPE FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='prices' AND COLUMN_NAME='price') <> 'bigint' + AND (SELECT IFNULL(NUMERIC_PRECISION, 0) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA=@db AND TABLE_NAME='prices' AND COLUMN_NAME='price') < 20, + 'ALTER TABLE `prices` MODIFY `price` DECIMAL(20,4) NULL', + 'SELECT ''skip prices.price widen'' AS info' +); +PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; + -- --------------------------------------------------------------------------- -- پایان — برای بورس‌مس (اختیاری، جدا اجرا کنید): -- UPDATE `settings` SET `value` = 'کیلوگرم' WHERE `key` = 'unit';