fix bug 22/4 2
This commit is contained in:
@@ -671,6 +671,22 @@ class ProductController extends SuperController
|
||||
unset($data['direct_price']);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Product::hasInventoryColumn()) {
|
||||
unset($data['inventory']);
|
||||
}
|
||||
@@ -682,24 +698,35 @@ class ProductController extends SuperController
|
||||
}
|
||||
|
||||
if (!Product::hasPhysicalDeliveryColumns()) {
|
||||
unset($data['has_physical_delivery'], $data['min_physical_delivery_grams']);
|
||||
unset(
|
||||
$data['has_physical_delivery'],
|
||||
$data['min_physical_delivery_quantity'],
|
||||
$data['min_physical_delivery_grams']
|
||||
);
|
||||
} else {
|
||||
// سازگاری با نسخهٔ قبلی که فیلد را 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']);
|
||||
|
||||
$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) {
|
||||
$data['min_physical_delivery_grams'] = null;
|
||||
$data['min_physical_delivery_quantity'] = null;
|
||||
} else {
|
||||
$rawMin = $data['min_physical_delivery_grams'] ?? null;
|
||||
$rawMin = $data['min_physical_delivery_quantity'] ?? null;
|
||||
$min = $this->normalizeCashFeeValue($rawMin);
|
||||
if ($min <= 0) {
|
||||
throw new UnprocessableEntityHttpException(
|
||||
'برای فعال کردن تحویل فیزیکی باید حداقل گرم معتبر وارد شود'
|
||||
'برای فعال کردن تحویل فیزیکی باید حداقل مقدار معتبر وارد شود'
|
||||
);
|
||||
}
|
||||
$data['min_physical_delivery_grams'] = $min;
|
||||
$data['min_physical_delivery_quantity'] = $min;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -417,7 +417,7 @@ class WalletController extends Controller
|
||||
return response()->json($Withdraw);
|
||||
}
|
||||
|
||||
private function formatGramsNumber(float $value): string
|
||||
private function formatDeliveryQuantity(float $value): string
|
||||
{
|
||||
$rounded = round($value, 3);
|
||||
if (fmod($rounded, 1.0) === 0.0) {
|
||||
@@ -466,7 +466,7 @@ class WalletController extends Controller
|
||||
$availableGold = $assetSummaryService
|
||||
->productDisplayQuantity($customerId, $ownerType, $productId);
|
||||
|
||||
// گیت تحویل فیزیکی per-product — سوییچ محصول و حداقل گرم
|
||||
// گیت تحویل فیزیکی per-product — سوییچ محصول و حداقل مقدار (به واحد خود محصول)
|
||||
$walletProduct = $data['wallets'][$key]['wallet']['product'];
|
||||
if (Product::hasPhysicalDeliveryColumns() && !$walletProduct->physicalDeliveryEnabled()) {
|
||||
return response()->json([
|
||||
@@ -474,20 +474,22 @@ class WalletController extends Controller
|
||||
], 422);
|
||||
}
|
||||
|
||||
$minDeliveryGrams = Product::hasPhysicalDeliveryColumns()
|
||||
? $walletProduct->minPhysicalDeliveryGrams()
|
||||
$minDeliveryQuantity = Product::hasPhysicalDeliveryColumns()
|
||||
? $walletProduct->minPhysicalDeliveryQuantity()
|
||||
: 0.0;
|
||||
if ($minDeliveryGrams > 0) {
|
||||
if ($minDeliveryQuantity > 0) {
|
||||
// موجودی و حداقل هر دو با واحد خود محصول سنجیده میشوند:
|
||||
// تعدادی → عدد، وزنی → گرم/کیلوگرم (همسان با inventory_cards)
|
||||
$card = $assetSummaryService->productInventoryCard($customerId, $ownerType, $productId);
|
||||
$isCount = ($card['display_type'] ?? null) === 'count';
|
||||
$unitWeight = (float) ($walletProduct['amount'] ?? 0);
|
||||
$availableGrams = $isCount ? $availableGold * $unitWeight : $availableGold;
|
||||
$unitLabel = $card['unit'] ?? Controller::productQuantityUnit(
|
||||
Controller::productUnitPayload($walletProduct)
|
||||
);
|
||||
|
||||
if ($availableGrams + 0.0001 < $minDeliveryGrams) {
|
||||
$remaining = max(0, $minDeliveryGrams - $availableGrams);
|
||||
if ($availableGold + 0.0001 < $minDeliveryQuantity) {
|
||||
$remaining = max(0, $minDeliveryQuantity - $availableGold);
|
||||
return response()->json([
|
||||
'message' => $this->formatGramsNumber($remaining)
|
||||
. " گرم مانده تا رسیدن به حداقل تحویل فیزیکی "
|
||||
'message' => $this->formatDeliveryQuantity($remaining)
|
||||
. " {$unitLabel} مانده تا رسیدن به حداقل تحویل فیزیکی "
|
||||
. "{$walletProduct['title']}"
|
||||
], 422);
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ class SingleOrderController extends Controller
|
||||
$data['service_charge_percentage'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : ($data['product']['percent_fee_buy']) * $data['number'];
|
||||
$data['difference'] = $data['fee'] = $this->resolveCashPriceDiff($config, $data['product'], 1);
|
||||
$data['total_fee'] = $data['number'] * $data['fee'];
|
||||
$data['price_original'] = $data['product']['price'] + $data['product']['price_profit'];
|
||||
$data['price_original'] = $this->resolveProductUnitBasePrice($data['product']);
|
||||
$data['total_price_original'] = $data['product']['amount'] * ($data['price_original'] ?? 0);
|
||||
|
||||
// Check if the method is cash
|
||||
@@ -134,7 +134,7 @@ class SingleOrderController extends Controller
|
||||
$data['service_charge_percentage'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : ($data['product']['percent_fee_sell']) * $data['number'];
|
||||
$data['difference'] = $data['fee'] = $this->resolveCashPriceDiff($config, $data['product'], 2);
|
||||
$data['total_fee'] = $data['number'] * $data['fee'];
|
||||
$data['price_original'] = $data['product']['price'] + $data['product']['price_profit'];
|
||||
$data['price_original'] = $this->resolveProductUnitBasePrice($data['product']);
|
||||
$data['total_price_original'] = $data['product']['amount'] * ($data['price_original'] ?? 0);
|
||||
|
||||
$this->assertMinimumSellCash($data);
|
||||
@@ -1171,6 +1171,22 @@ class SingleOrderController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* قیمت پایهٔ هر واحد محصول (بدون ضرب در وزن — وزن در total_price_original اعمال میشود).
|
||||
* محصول با پرچم «قیمت اپ» → قیمت زندهٔ هر گرم اپ (با تعدیل درصدی)؛
|
||||
* بقیه → همان قیمت ذخیرهشدهٔ محصول.
|
||||
*/
|
||||
private function resolveProductUnitBasePrice(Product $product): int
|
||||
{
|
||||
$display = app(ProductDisplayPriceService::class);
|
||||
|
||||
if ($display->usesFlaggedAppPrice($product)) {
|
||||
return (int) round($display->resolveOrderBasePrice($product));
|
||||
}
|
||||
|
||||
return (int) (($product->price ?? 0) + ($product->price_profit ?? 0));
|
||||
}
|
||||
|
||||
private function bonusGift($customer, $referrer)
|
||||
{
|
||||
$bonus = Setting::where('key', 'referral_bonus_amount')->where('is_active', 1)->first();
|
||||
|
||||
+33
-8
@@ -21,6 +21,7 @@ class Product extends Model
|
||||
private static ?bool $showInAssetsColumnExists = null;
|
||||
private static ?bool $useLastPriceIfZeroColumnExists = null;
|
||||
private static ?bool $directPriceColumnExists = null;
|
||||
private static ?bool $appPriceColumnExists = null;
|
||||
private static ?bool $tradeByQuantityColumnExists = null;
|
||||
private static ?bool $inventoryColumnExists = null;
|
||||
private static ?bool $overrideTradingHoursColumnExists = null;
|
||||
@@ -49,6 +50,9 @@ class Product extends Model
|
||||
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']);
|
||||
}
|
||||
@@ -67,7 +71,7 @@ class Product extends Model
|
||||
if (!static::hasPhysicalDeliveryColumns()) {
|
||||
unset(
|
||||
$product->attributes['has_physical_delivery'],
|
||||
$product->attributes['min_physical_delivery_grams']
|
||||
$product->attributes['min_physical_delivery_quantity']
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -82,7 +86,7 @@ class Product extends Model
|
||||
try {
|
||||
$table = (new static)->getTable();
|
||||
static::$physicalDeliveryColumnsExist = Schema::hasColumn($table, 'has_physical_delivery')
|
||||
&& Schema::hasColumn($table, 'min_physical_delivery_grams');
|
||||
&& Schema::hasColumn($table, 'min_physical_delivery_quantity');
|
||||
} catch (\Throwable) {
|
||||
static::$physicalDeliveryColumnsExist = false;
|
||||
}
|
||||
@@ -235,6 +239,24 @@ class Product extends Model
|
||||
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) {
|
||||
@@ -315,11 +337,11 @@ class Product extends Model
|
||||
return (bool) ($this->attributes['has_physical_delivery'] ?? false);
|
||||
}
|
||||
|
||||
public function minPhysicalDeliveryGrams(): float
|
||||
public function minPhysicalDeliveryQuantity(): float
|
||||
{
|
||||
$value = static::hasPhysicalDeliveryColumns()
|
||||
? ($this->min_physical_delivery_grams ?? 0)
|
||||
: ($this->attributes['min_physical_delivery_grams'] ?? 0);
|
||||
? ($this->min_physical_delivery_quantity ?? 0)
|
||||
: ($this->attributes['min_physical_delivery_quantity'] ?? 0);
|
||||
|
||||
return max(0.0, (float) $value);
|
||||
}
|
||||
@@ -363,8 +385,8 @@ class Product extends Model
|
||||
'order_limit_enabled',
|
||||
'min_order_cash',
|
||||
'max_order_cash',
|
||||
'has_physical_delivery', // روشن: این محصول امکان تحویل فیزیکی دارد
|
||||
'min_physical_delivery_grams', // حداقل موجودی (گرم) برای فعال شدن تحویل فیزیکی
|
||||
'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',
|
||||
@@ -372,6 +394,7 @@ class Product extends Model
|
||||
'inventory',
|
||||
'description',
|
||||
'direct_price',
|
||||
'app_price', // روشن: قیمت واحد از قیمت زندهٔ اپ هر گرم × وزن محاسبه میشود
|
||||
];
|
||||
|
||||
protected $attributes = [
|
||||
@@ -380,6 +403,7 @@ class Product extends Model
|
||||
'use_last_price_if_zero' => false,
|
||||
'cash_fee_buy_is_percent' => false,
|
||||
'cash_fee_sell_is_percent' => false,
|
||||
'app_price' => false,
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -402,13 +426,14 @@ class Product extends Model
|
||||
'min_order_cash' => 'int',
|
||||
'max_order_cash' => 'int',
|
||||
'has_physical_delivery' => 'boolean',
|
||||
'min_physical_delivery_grams' => 'float',
|
||||
'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',
|
||||
];
|
||||
|
||||
static $METAL_ID = [
|
||||
|
||||
@@ -26,6 +26,11 @@ class ProductDisplayPriceService
|
||||
return false;
|
||||
}
|
||||
|
||||
// پرچم صریح «قیمت اپ» برای هر دستهای (شمش، سکه، …) — قیمت هر گرم × وزن
|
||||
if (Product::hasAppPriceColumn() && (bool) ($product->app_price ?? false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((int) ($product->direct_price ?? 0) === 1) {
|
||||
return false;
|
||||
}
|
||||
@@ -34,6 +39,22 @@ class ProductDisplayPriceService
|
||||
|| $this->usesQuantityAppPrice($product);
|
||||
}
|
||||
|
||||
/** محصولی که با پرچم صریح app_price قیمت اپ میگیرد (نه استنتاج آبشده/مقداری) */
|
||||
public function usesFlaggedAppPrice(Product $product): bool
|
||||
{
|
||||
return Product::hasAppPriceColumn()
|
||||
&& (bool) ($product->app_price ?? false)
|
||||
&& ! $product->reading_method;
|
||||
}
|
||||
|
||||
/** ضریب وزن برای محصول قیمتاپ: وزن واحد به گرمواحد محصول (amount) یا ۱ */
|
||||
public function appPriceWeightFactor(Product $product): float
|
||||
{
|
||||
$amount = (float) ($product->amount ?? 0);
|
||||
|
||||
return $amount > 0 ? $amount : 1.0;
|
||||
}
|
||||
|
||||
/** معامله مقداری با قیمت اپ — نه قیمت دستی قدیمی با price پر در DB */
|
||||
private function usesQuantityAppPrice(Product $product): bool
|
||||
{
|
||||
|
||||
@@ -16,8 +16,12 @@ return new class extends Migration
|
||||
if (! Schema::hasColumn('products', 'has_physical_delivery')) {
|
||||
$table->boolean('has_physical_delivery')->default(false);
|
||||
}
|
||||
if (! Schema::hasColumn('products', 'min_physical_delivery_grams')) {
|
||||
$table->decimal('min_physical_delivery_grams', 12, 3)->nullable();
|
||||
// مقدار حداقل تحویل بر حسب واحد خود محصول (عدد برای تعدادی، گرم/کیلوگرم برای وزنی)
|
||||
if (
|
||||
! Schema::hasColumn('products', 'min_physical_delivery_quantity')
|
||||
&& ! Schema::hasColumn('products', 'min_physical_delivery_grams')
|
||||
) {
|
||||
$table->decimal('min_physical_delivery_quantity', 12, 3)->nullable();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -29,6 +33,9 @@ return new class extends Migration
|
||||
}
|
||||
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('products', 'min_physical_delivery_quantity')) {
|
||||
$table->dropColumn('min_physical_delivery_quantity');
|
||||
}
|
||||
if (Schema::hasColumn('products', 'min_physical_delivery_grams')) {
|
||||
$table->dropColumn('min_physical_delivery_grams');
|
||||
}
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* حد تحویل فیزیکی از «گرم» به «واحد خود محصول» تغییر کرد
|
||||
* (عدد برای محصولات تعدادی، گرم/کیلوگرم برای وزنی).
|
||||
* سرورهایی که نسخهٔ قبلی مایگریشن را اجرا کردهاند ستون min_physical_delivery_grams دارند
|
||||
* که اینجا به min_physical_delivery_quantity تغییر نام مییابد (بدون از دست رفتن داده).
|
||||
*
|
||||
* از SQL خام استفاده شده تا نیاز به doctrine/dbal نباشد. پایگاهداده MySQL است.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable('products')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
Schema::hasColumn('products', 'min_physical_delivery_grams')
|
||||
&& ! Schema::hasColumn('products', 'min_physical_delivery_quantity')
|
||||
) {
|
||||
$table = DB::getTablePrefix().'products';
|
||||
DB::statement(
|
||||
"ALTER TABLE `{$table}` CHANGE `min_physical_delivery_grams` `min_physical_delivery_quantity` DECIMAL(12,3) NULL DEFAULT NULL"
|
||||
);
|
||||
DB::purge();
|
||||
DB::reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (! Schema::hasTable('products')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
Schema::hasColumn('products', 'min_physical_delivery_quantity')
|
||||
&& ! Schema::hasColumn('products', 'min_physical_delivery_grams')
|
||||
) {
|
||||
$table = DB::getTablePrefix().'products';
|
||||
DB::statement(
|
||||
"ALTER TABLE `{$table}` CHANGE `min_physical_delivery_quantity` `min_physical_delivery_grams` DECIMAL(12,3) NULL DEFAULT NULL"
|
||||
);
|
||||
DB::purge();
|
||||
DB::reconnect();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable('products')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('products', 'app_price')) {
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('products', 'direct_price')) {
|
||||
$table->boolean('app_price')->default(false)->after('direct_price');
|
||||
} else {
|
||||
$table->boolean('app_price')->default(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (! Schema::hasTable('products') || ! Schema::hasColumn('products', 'app_price')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
$table->dropColumn('app_price');
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user