feat(server): evaluate price alerts per product and fix gold discharge rounding
Alerts now bind to a product_id and share one evaluator; displayed gold within a rounding step discharges the full wallet. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,6 +13,9 @@ class PriceTriggerController extends Controller
|
||||
public function table(Request $request)
|
||||
{
|
||||
$query = PriceTrigger::query()->with('customer');
|
||||
if (PriceTrigger::hasProductIdColumn()) {
|
||||
$query->with('product:id,title');
|
||||
}
|
||||
|
||||
TableEngine::create($query, $request->all())
|
||||
->initSearch(['customer_id', 'target_price', 'action', 'price_condition'])
|
||||
@@ -31,7 +34,12 @@ class PriceTriggerController extends Controller
|
||||
'is_active' => $data['is_active'] ?? true,
|
||||
]);
|
||||
|
||||
return response()->json($trigger->load('customer'), 201);
|
||||
$relations = ['customer'];
|
||||
if (PriceTrigger::hasProductIdColumn()) {
|
||||
$relations[] = 'product:id,title';
|
||||
}
|
||||
|
||||
return response()->json($trigger->load($relations), 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id)
|
||||
@@ -71,12 +79,17 @@ class PriceTriggerController extends Controller
|
||||
{
|
||||
$rules = [
|
||||
'customer_id' => ($partial ? 'sometimes|' : '') . 'required|integer|exists:customers,id',
|
||||
'product_id' => ($partial ? 'sometimes|' : '') . 'required|integer|exists:products,id',
|
||||
'target_price' => ($partial ? 'sometimes|' : '') . 'required|numeric|min:1',
|
||||
'price_condition' => ($partial ? 'sometimes|' : '') . 'required|in:>,<',
|
||||
'action' => ($partial ? 'sometimes|' : '') . 'required|in:sms,email',
|
||||
'is_active' => 'sometimes|boolean',
|
||||
];
|
||||
|
||||
if (! PriceTrigger::hasProductIdColumn()) {
|
||||
unset($rules['product_id']);
|
||||
}
|
||||
|
||||
$data = $request->validate($rules);
|
||||
|
||||
if (isset($data['price_condition']) && !in_array($data['price_condition'], ['>', '<'], true)) {
|
||||
|
||||
@@ -67,6 +67,73 @@ class WalletController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* عدد نمایشی موجودی تا ۳ رقم گرد میشود؛ اگر ادمین همان عدد را بزند، کل ماندهٔ واقعی کسر شود.
|
||||
*/
|
||||
private function resolveGoldDischargeAmount(float $available, float $requested, string $error): float
|
||||
{
|
||||
if ($requested <= 0) {
|
||||
throw new UnprocessableEntityHttpException($error);
|
||||
}
|
||||
if ($requested <= $available) {
|
||||
return $requested;
|
||||
}
|
||||
$sameDisplayed = round($requested, 3) <= round($available, 3);
|
||||
$withinDisplayStep = ($requested - $available) <= self::GOLD_DISPLAY_STEP;
|
||||
if ($sameDisplayed || $withinDisplayStep) {
|
||||
return $available;
|
||||
}
|
||||
|
||||
throw new UnprocessableEntityHttpException($error);
|
||||
}
|
||||
|
||||
/**
|
||||
* طلای مانده روی کیف legacy محصولات کیف اصلی را به کیف اصلی منتقل میکند
|
||||
* تا موجودی نمایشی (effective) با کیف قابلکسر یکی شود.
|
||||
*/
|
||||
private function consolidateOrphanedMainWalletGold(int $ownerId, int $ownerType): void
|
||||
{
|
||||
$productIds = app(ConfigService::class)->mainWalletProductIds();
|
||||
if ($productIds === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$orphans = Wallet::query()
|
||||
->where('owner', $ownerId)
|
||||
->where('owner_type', $ownerType)
|
||||
->whereNull('fund_id')
|
||||
->whereIn('product_id', $productIds)
|
||||
->get();
|
||||
|
||||
$main = new WalletInterface($ownerId, $ownerType, null, null);
|
||||
|
||||
foreach ($orphans as $orphan) {
|
||||
$amount = (float) $orphan->gold;
|
||||
if ($amount <= 1e-12) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$orphanWallet = new WalletInterface($ownerId, $ownerType, null, (int) $orphan->product_id);
|
||||
new TransactionInterface($orphanWallet, [
|
||||
'gold' => $amount,
|
||||
'cash' => 0,
|
||||
'expire_at' => now(),
|
||||
'note' => 'انتقال به کیف اصلی',
|
||||
'active' => true,
|
||||
], 7, false, false, 0);
|
||||
|
||||
new TransactionInterface($main, [
|
||||
'gold' => $amount,
|
||||
'cash' => 0,
|
||||
'expire_at' => now(),
|
||||
'note' => 'انتقال از کیف محصول',
|
||||
'active' => true,
|
||||
], 7, true, false, 0);
|
||||
|
||||
$main = new WalletInterface($ownerId, $ownerType, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
public function charge2(Request $request)
|
||||
{
|
||||
$data = $request->all();
|
||||
@@ -271,20 +338,17 @@ class WalletController extends Controller
|
||||
$deposit = true;
|
||||
|
||||
}
|
||||
if (!$deposit) {
|
||||
$this->consolidateOrphanedMainWalletGold((int) $customer->id, (int) $customer->type);
|
||||
}
|
||||
$wallet_interface = new WalletInterface($data['customer_id'], (int) $customer->type);
|
||||
$wallet = $wallet_interface->wallet;
|
||||
// کسر دستی نباید موجودی طلا را منفی کند؛ شارژ مثبت بدون هیچ شرطی باز است
|
||||
if (!$deposit) {
|
||||
$available = is_null($wallet) ? 0.0 : (float) $wallet->gold;
|
||||
$requested = abs((float) $data['value']);
|
||||
// ادمین عدد را از نمایش سهرقمی میخواند؛ اختلاف تا نصف پلهٔ نمایش یعنی «کل موجودی»
|
||||
if ($requested > $available && $requested - $available <= self::GOLD_DISPLAY_STEP) {
|
||||
$requested = $available;
|
||||
$data['value'] = -$available;
|
||||
}
|
||||
if ($requested <= 0 || $requested > $available) {
|
||||
throw new UnprocessableEntityHttpException('مقدار موجودی طلا کمتر از درخواست شماست');
|
||||
}
|
||||
$resolved = $this->resolveGoldDischargeAmount($available, $requested, 'مقدار موجودی طلا کمتر از درخواست شماست');
|
||||
$data['value'] = -$resolved;
|
||||
}
|
||||
$this->assertNotDuplicateCharge((int) $customer->id, (float) $data['value'], 'gold');
|
||||
$gold_block = 0;
|
||||
@@ -489,6 +553,11 @@ class WalletController extends Controller
|
||||
throw new UnprocessableEntityHttpException('برای محصول تعدادی فقط عدد صحیح مجاز است');
|
||||
}
|
||||
|
||||
$isMainWalletProduct = Controller::isMainWalletProduct($productId);
|
||||
if (!$deposit && $isMainWalletProduct) {
|
||||
$this->consolidateOrphanedMainWalletGold((int) $customer->id, (int) $customer->type);
|
||||
}
|
||||
|
||||
$effectiveProductId = Controller::effectiveProductWalletId($productId);
|
||||
$wallet_interface = new WalletInterface(
|
||||
(int) $data['customer_id'],
|
||||
@@ -498,8 +567,15 @@ class WalletController extends Controller
|
||||
);
|
||||
$wallet = $wallet_interface->wallet;
|
||||
|
||||
if (!$deposit && $wallet !== null && (float) $wallet->gold < abs((float) $data['value'])) {
|
||||
throw new UnprocessableEntityHttpException('مقدار موجودی کمتر از درخواست شماست');
|
||||
if (!$deposit) {
|
||||
$available = is_null($wallet) ? 0.0 : (float) $wallet->gold;
|
||||
$resolved = $this->resolveGoldDischargeAmount(
|
||||
$available,
|
||||
abs($value),
|
||||
'مقدار موجودی کمتر از درخواست شماست'
|
||||
);
|
||||
$value = -$resolved;
|
||||
$data['value'] = $value;
|
||||
}
|
||||
$this->assertNotDuplicateCharge((int) $customer->id, (float) $data['value'], 'gold');
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
namespace App\Http\Controllers\Customer;
|
||||
|
||||
use App\Http\Controllers\SuperController;
|
||||
use App\Models\PriceTrigger;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\PriceTrigger; // اطمینان حاصل کنید که این مدل به درستی وارد شده است
|
||||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||||
|
||||
class PriceTriggerController extends SuperController
|
||||
{
|
||||
@@ -24,10 +26,55 @@ class PriceTriggerController extends SuperController
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$data=PriceTrigger::where("customer_id",auth()->id())->get();
|
||||
return response()->json($data);
|
||||
$query = PriceTrigger::where("customer_id", auth()->id());
|
||||
if (PriceTrigger::hasProductIdColumn()) {
|
||||
$query->with(['product:id,title']);
|
||||
}
|
||||
|
||||
return response()->json($query->get());
|
||||
}
|
||||
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->sanitizeProductId($request, true);
|
||||
|
||||
return parent::store($request);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->sanitizeProductId($request, false);
|
||||
|
||||
return parent::update($request, $id);
|
||||
}
|
||||
|
||||
private function sanitizeProductId(Request $request, bool $required): void
|
||||
{
|
||||
if (! PriceTrigger::hasProductIdColumn()) {
|
||||
$request->request->remove('product_id');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$productId = $request->input('product_id');
|
||||
if ($productId === null || $productId === '') {
|
||||
if ($required) {
|
||||
throw new UnprocessableEntityHttpException('محصول را انتخاب کنید');
|
||||
}
|
||||
$request->request->remove('product_id');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$exists = Product::query()
|
||||
->where('id', (int) $productId)
|
||||
->where('active', 1)
|
||||
->exists();
|
||||
|
||||
if (! $exists) {
|
||||
throw new UnprocessableEntityHttpException('محصول نامعتبر است');
|
||||
}
|
||||
|
||||
$request->merge(['product_id' => (int) $productId]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,21 +4,17 @@ namespace App\Http\Controllers\Site;
|
||||
|
||||
use App\Http\Controllers\Admin\PriceController;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Mail\YourMailableClass;
|
||||
use App\Models\Cnt;
|
||||
use App\Models\Setting;
|
||||
use App\Models\ConfigStatic;
|
||||
use App\Models\Customer;
|
||||
use App\Services\MontzarService;
|
||||
use App\Services\ZaarboxPriceService;
|
||||
use App\Models\Price;
|
||||
use App\Models\PriceCandle;
|
||||
use App\Models\PriceTrigger;
|
||||
use App\Models\Product;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
use App\Services\ConfigService;
|
||||
use App\Services\LivePriceCacheService;
|
||||
@@ -51,41 +47,7 @@ class ReportController extends Controller
|
||||
|
||||
public function updatePrise()
|
||||
{
|
||||
$config = app(ConfigService::class);
|
||||
$lastPrice = Price::latest()->first();
|
||||
|
||||
if (!$lastPrice) {
|
||||
return response()->json();
|
||||
}
|
||||
|
||||
$PriceTriggers = PriceTrigger::where("is_active", true)->get();
|
||||
foreach ($PriceTriggers as $PriceTrigger) {
|
||||
$diff = $PriceTrigger->dirction == 1
|
||||
? $config->displaySellPriceDiffRial()
|
||||
: ($PriceTrigger->dirction == 2 ? $config->displayBuyPriceDiffRial() : 0.0);
|
||||
$priseGold = $PriceTrigger->dirction == 1
|
||||
? $lastPrice->price - $diff
|
||||
: ($PriceTrigger->dirction == 2 ? $lastPrice->price + $diff : $lastPrice->price);
|
||||
|
||||
if (eval('return $priseGold ' . $PriceTrigger->price_condition . ' $PriceTrigger->target_price;')) {
|
||||
$customer = Customer::find($PriceTrigger->customer_id);
|
||||
if (!$customer) {
|
||||
continue;
|
||||
}
|
||||
$message = "قیمت طلا به " . number_format($priseGold, -1) . " در هر گرم رسید\n" .$this->Config('name_fa');
|
||||
if ($PriceTrigger->action == 'email') {
|
||||
Mail::to("$customer->email")->send(new YourMailableClass($message, '', '', '', ''));
|
||||
} elseif ($PriceTrigger->action == 'sms') {
|
||||
if (self::isSmsNotifEnabled('sms_notif_price_trigger')) {
|
||||
$this->send($customer->mobile, $message);
|
||||
}
|
||||
}
|
||||
$PriceTrigger->is_active = false;
|
||||
$PriceTrigger->triggered_at = Carbon::now();
|
||||
$PriceTrigger->save();
|
||||
}
|
||||
|
||||
}
|
||||
app(\App\Services\PriceTriggerService::class)->evaluateActive();
|
||||
|
||||
return response()->json();
|
||||
}
|
||||
@@ -164,9 +126,11 @@ class ReportController extends Controller
|
||||
$pricePayload = $payload;
|
||||
if (app()->runningInConsole()) {
|
||||
app(\App\Services\ConditionalOrderService::class)->evaluateFromLastPrice($pricePayload);
|
||||
app(\App\Services\PriceTriggerService::class)->evaluateActive($pricePayload);
|
||||
} else {
|
||||
dispatch(function () use ($pricePayload) {
|
||||
app(\App\Services\ConditionalOrderService::class)->evaluateFromLastPrice($pricePayload);
|
||||
app(\App\Services\PriceTriggerService::class)->evaluateActive($pricePayload);
|
||||
})->afterResponse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,11 @@ class PriceTrigger extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
private static ?bool $productIdColumnExists = null;
|
||||
|
||||
protected $fillable = [
|
||||
'customer_id',
|
||||
'product_id',
|
||||
'target_price',
|
||||
'price_condition',
|
||||
'action',
|
||||
@@ -20,8 +23,32 @@ class PriceTrigger extends Model
|
||||
protected $casts = [
|
||||
"is_active" => "boolean",
|
||||
];
|
||||
|
||||
public static function hasProductIdColumn(): bool
|
||||
{
|
||||
if (static::$productIdColumnExists !== null) {
|
||||
return static::$productIdColumnExists;
|
||||
}
|
||||
|
||||
try {
|
||||
static::$productIdColumnExists = \Illuminate\Support\Facades\Schema::hasColumn(
|
||||
(new static)->getTable(),
|
||||
'product_id'
|
||||
);
|
||||
} catch (\Throwable) {
|
||||
static::$productIdColumnExists = false;
|
||||
}
|
||||
|
||||
return static::$productIdColumnExists;
|
||||
}
|
||||
|
||||
public function customer()
|
||||
{
|
||||
return $this->belongsTo(Customer::class);
|
||||
}
|
||||
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Mail\YourMailableClass;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Price;
|
||||
use App\Models\PriceTrigger;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class PriceTriggerService
|
||||
{
|
||||
public function evaluateActive(array $payload = []): void
|
||||
{
|
||||
$triggers = PriceTrigger::query()->where('is_active', true)->get();
|
||||
if ($triggers->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = app(ConfigService::class);
|
||||
$display = app(ProductDisplayPriceService::class);
|
||||
$lastPrice = Price::query()->latest('id')->first();
|
||||
$boardPrice = (float) ($payload['price'] ?? $lastPrice?->price ?? 0);
|
||||
|
||||
$productsById = collect();
|
||||
if (PriceTrigger::hasProductIdColumn()) {
|
||||
$ids = $triggers->pluck('product_id')->filter()->unique()->all();
|
||||
if ($ids !== []) {
|
||||
$productsById = Product::query()->whereIn('id', $ids)->get()->keyBy('id');
|
||||
if ($productsById->isNotEmpty()) {
|
||||
$display->warmLivePriceCache();
|
||||
$display->enrichForApi($productsById);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($triggers as $trigger) {
|
||||
try {
|
||||
$this->tryFire($trigger, $productsById, $display, $config, $boardPrice);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Price trigger evaluate failed', [
|
||||
'id' => $trigger->id,
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function tryFire(
|
||||
PriceTrigger $trigger,
|
||||
$productsById,
|
||||
ProductDisplayPriceService $display,
|
||||
ConfigService $config,
|
||||
float $boardPrice
|
||||
): void {
|
||||
$product = $this->resolveProduct($trigger, $productsById);
|
||||
$current = $this->resolveCurrentPrice($trigger, $product, $display, $config, $boardPrice);
|
||||
if ($current <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->conditionMet((string) $trigger->price_condition, $current, (float) $trigger->target_price)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$customer = Customer::query()->find($trigger->customer_id);
|
||||
if (! $customer) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->notify($trigger, $customer, $product, $current);
|
||||
|
||||
$trigger->is_active = false;
|
||||
$trigger->triggered_at = now();
|
||||
$trigger->save();
|
||||
}
|
||||
|
||||
private function resolveProduct(PriceTrigger $trigger, $productsById): ?Product
|
||||
{
|
||||
if (! PriceTrigger::hasProductIdColumn()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$productId = (int) ($trigger->product_id ?? 0);
|
||||
if ($productId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$product = $productsById->get($productId);
|
||||
if ($product instanceof Product) {
|
||||
return $product;
|
||||
}
|
||||
|
||||
return Product::query()->find($productId);
|
||||
}
|
||||
|
||||
private function resolveCurrentPrice(
|
||||
PriceTrigger $trigger,
|
||||
?Product $product,
|
||||
ProductDisplayPriceService $display,
|
||||
ConfigService $config,
|
||||
float $boardPrice
|
||||
): float {
|
||||
if ($product instanceof Product) {
|
||||
$enriched = (int) (($product->price ?? 0) + ($product->price_profit ?? 0));
|
||||
if ($enriched > 0) {
|
||||
return (float) $enriched;
|
||||
}
|
||||
|
||||
if ($display->usesAppPrice($product)) {
|
||||
$base = $boardPrice > 0 ? $boardPrice : $display->resolveOrderBasePrice($product);
|
||||
if ($base <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$trade = $config->buildTradeDisplayPrices((float) $base, $config->priceProvider());
|
||||
$direction = (int) ($trigger->direction ?? 0);
|
||||
|
||||
return (float) max(0, (int) ($direction === 2 ? $trade['sell'] : $trade['buy']));
|
||||
}
|
||||
|
||||
return max(0.0, (float) $display->resolveOrderBasePrice($product));
|
||||
}
|
||||
|
||||
if ($boardPrice <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$direction = (int) ($trigger->direction ?? $trigger->dirction ?? 0);
|
||||
$diff = $direction === 1
|
||||
? $config->displaySellPriceDiffRial()
|
||||
: ($direction === 2 ? $config->displayBuyPriceDiffRial() : 0.0);
|
||||
|
||||
if ($direction === 1) {
|
||||
return max(0.0, $boardPrice - $diff);
|
||||
}
|
||||
|
||||
if ($direction === 2) {
|
||||
return $boardPrice + $diff;
|
||||
}
|
||||
|
||||
return $boardPrice;
|
||||
}
|
||||
|
||||
private function conditionMet(string $op, float $current, float $target): bool
|
||||
{
|
||||
return match ($op) {
|
||||
'>' => $current > $target,
|
||||
'<' => $current < $target,
|
||||
'>=', 'gte' => $current >= $target,
|
||||
'<=', 'lte' => $current <= $target,
|
||||
'==', '=' => abs($current - $target) < 1,
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
private function notify(PriceTrigger $trigger, Customer $customer, ?Product $product, float $current): void
|
||||
{
|
||||
$title = trim((string) ($product?->title ?? ''));
|
||||
if ($title === '') {
|
||||
$title = 'طلا';
|
||||
}
|
||||
|
||||
$shopName = (string) Controller::Config('name_fa');
|
||||
$message = 'قیمت ' . $title . ' به ' . number_format((int) $current) . " رسید\n" . $shopName;
|
||||
|
||||
if ($trigger->action === 'email' && ! empty($customer->email)) {
|
||||
Mail::to($customer->email)->send(new YourMailableClass($message, '', '', '', ''));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($trigger->action === 'sms' && Controller::isSmsNotifEnabled('sms_notif_price_trigger')) {
|
||||
Controller::Send($customer->mobile, $message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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('price_triggers')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schema::table('price_triggers', function (Blueprint $table) {
|
||||
if (! Schema::hasColumn('price_triggers', 'product_id')) {
|
||||
$table->unsignedBigInteger('product_id')->nullable()->after('customer_id');
|
||||
$table->index('product_id');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (! Schema::hasTable('price_triggers') || ! Schema::hasColumn('price_triggers', 'product_id')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schema::table('price_triggers', function (Blueprint $table) {
|
||||
$table->dropIndex(['product_id']);
|
||||
$table->dropColumn('product_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -593,6 +593,7 @@ DROP TABLE IF EXISTS `price_triggers`;
|
||||
CREATE TABLE `price_triggers` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`customer_id` bigint unsigned NOT NULL,
|
||||
`product_id` bigint unsigned DEFAULT NULL,
|
||||
`target_price` bigint NOT NULL,
|
||||
`price_condition` varchar(50) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
|
||||
`action` varchar(50) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
-- محصول هشدار قیمت (price_triggers.product_id)
|
||||
-- امن برای اجرای مجدد
|
||||
-- USE دیتابیس_خودتان;
|
||||
|
||||
SET @db = DATABASE();
|
||||
|
||||
SET @sql = IF(
|
||||
(SELECT COUNT(*) FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'price_triggers') = 0,
|
||||
'SELECT ''skip: price_triggers table not found'' AS info',
|
||||
IF(
|
||||
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'price_triggers' AND COLUMN_NAME = 'product_id') > 0,
|
||||
'SELECT ''skip: product_id exists'' AS info',
|
||||
IF(
|
||||
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'price_triggers' AND COLUMN_NAME = 'customer_id') > 0,
|
||||
'ALTER TABLE `price_triggers` ADD COLUMN `product_id` BIGINT UNSIGNED NULL DEFAULT NULL COMMENT ''محصول هشدار'' AFTER `customer_id`, ADD INDEX `price_triggers_product_id_index` (`product_id`)',
|
||||
'ALTER TABLE `price_triggers` ADD COLUMN `product_id` BIGINT UNSIGNED NULL DEFAULT NULL COMMENT ''محصول هشدار'', ADD INDEX `price_triggers_product_id_index` (`product_id`)'
|
||||
)
|
||||
)
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
@@ -523,6 +523,38 @@ PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- price_triggers.product_id (هشدار قیمت روی محصول)
|
||||
-- ---------------------------------------------------------------------------
|
||||
SET @sql = IF(
|
||||
(SELECT COUNT(*) FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'price_triggers') > 0
|
||||
AND (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'price_triggers' AND COLUMN_NAME = 'product_id') = 0,
|
||||
IF(
|
||||
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'price_triggers' AND COLUMN_NAME = 'customer_id') > 0,
|
||||
'ALTER TABLE `price_triggers` ADD COLUMN `product_id` BIGINT UNSIGNED NULL DEFAULT NULL COMMENT ''محصول هشدار'' AFTER `customer_id`',
|
||||
'ALTER TABLE `price_triggers` ADD COLUMN `product_id` BIGINT UNSIGNED NULL DEFAULT NULL COMMENT ''محصول هشدار'''
|
||||
),
|
||||
'SELECT ''skip price_triggers.product_id'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql = IF(
|
||||
(SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'price_triggers' AND INDEX_NAME = 'price_triggers_product_id_index') = 0
|
||||
AND (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'price_triggers' AND COLUMN_NAME = 'product_id') > 0,
|
||||
'ALTER TABLE `price_triggers` ADD INDEX `price_triggers_product_id_index` (`product_id`)',
|
||||
'SELECT ''skip price_triggers.product_id index'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- پایان — برای بورسمس (اختیاری، جدا اجرا کنید):
|
||||
-- UPDATE `settings` SET `value` = 'کیلوگرم' WHERE `key` = 'unit';
|
||||
|
||||
Reference in New Issue
Block a user