fix pargar mande
This commit is contained in:
@@ -11,6 +11,7 @@ use App\Models\PargarSyncLog;
|
|||||||
use App\Services\ConfigService;
|
use App\Services\ConfigService;
|
||||||
use App\Services\PargarInboundWalletService;
|
use App\Services\PargarInboundWalletService;
|
||||||
use App\Services\PargarSyncService;
|
use App\Services\PargarSyncService;
|
||||||
|
use App\Services\WalletBalanceService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
use Morilog\Jalali\Jalalian;
|
use Morilog\Jalali\Jalalian;
|
||||||
@@ -322,7 +323,9 @@ class PargarInboundController extends Controller
|
|||||||
$walletInterface = new WalletInterface($customer->id, $ownerType);
|
$walletInterface = new WalletInterface($customer->id, $ownerType);
|
||||||
$wallet = $walletInterface->wallet;
|
$wallet = $walletInterface->wallet;
|
||||||
|
|
||||||
$appGold = (float) ($wallet->gold ?? 0);
|
// GoldInv پرگار فقط با طلای کیف اصلی مقایسه میشود — محصولات جدا سهمی ندارند
|
||||||
|
$appGold = app(WalletBalanceService::class)
|
||||||
|
->effectiveMainWalletGold((int) $customer->id, $ownerType);
|
||||||
$appCash = (float) ($wallet->cash ?? 0);
|
$appCash = (float) ($wallet->cash ?? 0);
|
||||||
$goldDiff = $goldInv - $appGold;
|
$goldDiff = $goldInv - $appGold;
|
||||||
$rialDiff = $rialInv - $appCash;
|
$rialDiff = $rialInv - $appCash;
|
||||||
@@ -333,7 +336,7 @@ class PargarInboundController extends Controller
|
|||||||
$desc = $pendingOutbound
|
$desc = $pendingOutbound
|
||||||
? 'مانده دریافت شد — منتظر ثبت سند پنل در پرگار'
|
? 'مانده دریافت شد — منتظر ثبت سند پنل در پرگار'
|
||||||
: ($hasDiff
|
: ($hasDiff
|
||||||
? 'اختلاف مانده پرگار با اپ — در صف همگامسازی خودکار'
|
? 'اختلاف مانده پرگار با اپ — پس از تأیید پایداری اعمال میشود'
|
||||||
: 'دریافت مانده از پرگار — بدون اختلاف');
|
: 'دریافت مانده از پرگار — بدون اختلاف');
|
||||||
|
|
||||||
PargarSyncLog::create([
|
PargarSyncLog::create([
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
class PargarSyncLog extends Model
|
class PargarSyncLog extends Model
|
||||||
{
|
{
|
||||||
@@ -22,8 +23,29 @@ class PargarSyncLog extends Model
|
|||||||
'pargar_ref',
|
'pargar_ref',
|
||||||
'res',
|
'res',
|
||||||
'data',
|
'data',
|
||||||
|
'confirmed_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'confirmed_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
private static ?bool $hasConfirmedAtColumn = null;
|
||||||
|
|
||||||
|
/** سازگاری با نصبهایی که هنوز مایگریشن confirmed_at را اجرا نکردهاند */
|
||||||
|
public static function hasConfirmedAtColumn(): bool
|
||||||
|
{
|
||||||
|
if (self::$hasConfirmedAtColumn === null) {
|
||||||
|
try {
|
||||||
|
self::$hasConfirmedAtColumn = Schema::hasColumn('pargar_sync_logs', 'confirmed_at');
|
||||||
|
} catch (\Throwable) {
|
||||||
|
self::$hasConfirmedAtColumn = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$hasConfirmedAtColumn;
|
||||||
|
}
|
||||||
|
|
||||||
public function customer(): BelongsTo
|
public function customer(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Customer::class);
|
return $this->belongsTo(Customer::class);
|
||||||
|
|||||||
@@ -4,22 +4,24 @@ namespace App\Services;
|
|||||||
|
|
||||||
use App\Models\Customer;
|
use App\Models\Customer;
|
||||||
use App\Models\PargarSyncLog;
|
use App\Models\PargarSyncLog;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Rooyesh\Wallet\interfaces\TransactionInterface;
|
use Rooyesh\Wallet\interfaces\TransactionInterface;
|
||||||
use Rooyesh\Wallet\interfaces\WalletInterface;
|
use Rooyesh\Wallet\interfaces\WalletInterface;
|
||||||
use Rooyesh\Wallet\models\Wallet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* همگامسازی کیف با پرگار.
|
* همگامسازی کیف با پرگار.
|
||||||
*
|
*
|
||||||
* قواعد سخت:
|
* قواعد سخت:
|
||||||
* 1) webhook اسناد inbound فقط لاگ میکند؛ کیف را با replay تاریخچه عوض نمیکند.
|
* 1) webhook اسناد inbound فقط لاگ میکند؛ کیف را با replay تاریخچه عوض نمیکند.
|
||||||
* 2) مانده برای مشتریانی که سند ارسالیِ pending به پرگار ندارند → خودکار روی کیف ست میشود.
|
* 2) دامنهٔ مانده: RialInv = کیف ریالی، GoldInv = طلای کیف اصلی (main_wallet_products).
|
||||||
* 3) اگر سند پنل هنوز در پرگار ثبت نشده، مانده اعمال نمیشود؛ بعد از تأیید ثبت در پرگار،
|
* محصولات خارج از کیف اصلی هرگز از مانده اثر نمیگیرند.
|
||||||
* ماندهٔ جدید خودکار سینک میشود (تا موجودی مشتری بیدلیل کم/زیاد نشود).
|
* 3) ماندهٔ کهنه دور ریخته میشود، نه معوق: اگر سند ما هنوز pending باشد یا snapshot
|
||||||
* 4) دکمهٔ ادمین میتواند force کند.
|
* پیش از ثبت سند گرفته شده باشد، اعمال نمیشود و منتظر ماندهٔ بعدی میمانیم.
|
||||||
* 5) اعمال تکتک سطرهای تاریخچهٔ پرگار ممنوع است.
|
* 4) اعمال فقط با دو snapshot پیاپیِ هممقدار — تا ماندهای که وسط ثبت اسناد گرفته شده
|
||||||
|
* موجودی مشتری را عقب نبرد.
|
||||||
|
* 5) دکمهٔ ادمین با force میتواند همهٔ گیتهای بالا را رد کند (ابزار اصلاح دستی).
|
||||||
*/
|
*/
|
||||||
class PargarInboundWalletService
|
class PargarInboundWalletService
|
||||||
{
|
{
|
||||||
@@ -67,8 +69,8 @@ class PargarInboundWalletService
|
|||||||
* ۱) علامت مصرف دستههای سند inbound (بدون نوشتن روی کیف)
|
* ۱) علامت مصرف دستههای سند inbound (بدون نوشتن روی کیف)
|
||||||
* ۲) همترازی مانده با آخرین لاگ mande
|
* ۲) همترازی مانده با آخرین لاگ mande
|
||||||
*
|
*
|
||||||
* @param bool $forceMande ادمین: حتی با سند ارسالی pending
|
* @param bool $forceMande اصلاح دستی ادمین: گیتهای اعتبارسنجی مانده رد شوند
|
||||||
* @param bool $auto مسیر خودکار: سند ارسالی pending محترم بماند
|
* @param bool $auto مسیر خودکار (webhook/صف) — همیشه با گیت کامل
|
||||||
* @return array{
|
* @return array{
|
||||||
* documents: array{applied: int, skipped: int, batches: int, acknowledged: int, errors: list<string>},
|
* documents: array{applied: int, skipped: int, batches: int, acknowledged: int, errors: list<string>},
|
||||||
* mande: array{applied: bool, gold_delta?: float, rial_delta?: float, message: string}
|
* mande: array{applied: bool, gold_delta?: float, rial_delta?: float, message: string}
|
||||||
@@ -113,12 +115,10 @@ class PargarInboundWalletService
|
|||||||
// ۱) تاریخچهٔ اسناد inbound را روی کیف replay نکن — فقط مصرفشده علامت بزن
|
// ۱) تاریخچهٔ اسناد inbound را روی کیف replay نکن — فقط مصرفشده علامت بزن
|
||||||
$documents = $this->acknowledgePendingDocumentBatches($customer);
|
$documents = $this->acknowledgePendingDocumentBatches($customer);
|
||||||
|
|
||||||
// ۲) منبع حقیقت موجودی = ماندهٔ پرگار
|
// ۲) منبع حقیقت موجودی = ماندهٔ پرگار، اما فقط ماندهٔ معتبر
|
||||||
// خودکار: اگر سند پنل هنوز در پرگار ثبت نشده → اعمال نکن (force=false)
|
|
||||||
// ادمین: force تا بتوان اصلاح کرد
|
|
||||||
$mande = $this->applyLatestMandeDifference(
|
$mande = $this->applyLatestMandeDifference(
|
||||||
$customer,
|
$customer,
|
||||||
force: $auto ? false : ($forceMande || true)
|
force: ! $auto && $forceMande
|
||||||
);
|
);
|
||||||
|
|
||||||
if (($documents['acknowledged'] ?? 0) > 0 || ($mande['applied'] ?? false)) {
|
if (($documents['acknowledged'] ?? 0) > 0 || ($mande['applied'] ?? false)) {
|
||||||
@@ -274,18 +274,16 @@ class PargarInboundWalletService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* ماندهٔ پرگار روی کیف اپ:
|
||||||
|
* RialInv → کیف ریالی، GoldInv → طلای کیف اصلی (main_wallet_products).
|
||||||
|
* محصولات خارج از کیف اصلی (قلک سرمایهگذاری، سکه و …) هیچ سهمی در مانده ندارند و
|
||||||
|
* هرگز از آن اثر نمیگیرند.
|
||||||
|
*
|
||||||
|
* @param bool $force اعمال دستی ادمین — گیتهای اعتبارسنجی نادیده گرفته میشوند
|
||||||
* @return array{applied: bool, gold_delta?: float, rial_delta?: float, message: string}
|
* @return array{applied: bool, gold_delta?: float, rial_delta?: float, message: string}
|
||||||
*/
|
*/
|
||||||
public function applyLatestMandeDifference(Customer $customer, bool $force = false): array
|
public function applyLatestMandeDifference(Customer $customer, bool $force = false): array
|
||||||
{
|
{
|
||||||
// دکمهٔ ادمین (force): حتی با سند pending خروجی، مانده را همتراز کن
|
|
||||||
if (! $force && $this->hasPendingOutboundToPargar($customer)) {
|
|
||||||
return [
|
|
||||||
'applied' => false,
|
|
||||||
'message' => 'سند پنل هنوز در پرگار ثبت نشده — مانده بعد از ثبت سند اعمال میشود',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$log = PargarSyncLog::query()
|
$log = PargarSyncLog::query()
|
||||||
->where('customer_id', $customer->id)
|
->where('customer_id', $customer->id)
|
||||||
->where('type', PargarSyncService::TYPE_MANDE)
|
->where('type', PargarSyncService::TYPE_MANDE)
|
||||||
@@ -301,6 +299,15 @@ class PargarInboundWalletService
|
|||||||
return ['applied' => false, 'message' => 'مانده قبلاً همتراز شده است'];
|
return ['applied' => false, 'message' => 'مانده قبلاً همتراز شده است'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! $force) {
|
||||||
|
$block = $this->mandeBlockReason($customer, $log, $data);
|
||||||
|
if ($block !== null) {
|
||||||
|
$this->markMandeSkipped($log, $data, $block['reason']);
|
||||||
|
|
||||||
|
return ['applied' => false, 'message' => $block['message']];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$goldInv = $this->parseNumber($data['GoldInv'] ?? 0);
|
$goldInv = $this->parseNumber($data['GoldInv'] ?? 0);
|
||||||
$rialInv = $this->parseNumber($data['RialInv'] ?? 0);
|
$rialInv = $this->parseNumber($data['RialInv'] ?? 0);
|
||||||
|
|
||||||
@@ -308,30 +315,10 @@ class PargarInboundWalletService
|
|||||||
$walletInterface = new WalletInterface((int) $customer->id, $ownerType);
|
$walletInterface = new WalletInterface((int) $customer->id, $ownerType);
|
||||||
$wallet = $walletInterface->wallet;
|
$wallet = $walletInterface->wallet;
|
||||||
|
|
||||||
$balanceService = app(WalletBalanceService::class);
|
$mainGold = app(WalletBalanceService::class)
|
||||||
$mainGold = $balanceService->effectiveMainWalletGold((int) $customer->id, $ownerType);
|
->effectiveMainWalletGold((int) $customer->id, $ownerType);
|
||||||
$nonMainProductGold = $this->sumNonMainProductWalletGold((int) $customer->id, $ownerType);
|
|
||||||
|
|
||||||
// GoldInv پرگار = آبشدهٔ کیف اصلی. طلای محصولات جدا (مثل قلک سرمایهگذاری) نباید کپی شود.
|
|
||||||
$goldDiff = $goldInv - $mainGold;
|
$goldDiff = $goldInv - $mainGold;
|
||||||
|
|
||||||
// تکرار شناختهشده: مانده ≈ طلای محصول جدا و کیف اصلی هم همان مقدار را دارد
|
|
||||||
if (
|
|
||||||
$nonMainProductGold >= 0.01
|
|
||||||
&& abs($goldInv - $nonMainProductGold) < 0.05
|
|
||||||
&& abs($mainGold - $nonMainProductGold) < 0.05
|
|
||||||
) {
|
|
||||||
$goldDiff = -$mainGold;
|
|
||||||
} elseif (
|
|
||||||
$goldDiff > 0.01
|
|
||||||
&& $nonMainProductGold >= 0.01
|
|
||||||
&& abs($goldInv - $nonMainProductGold) < 0.05
|
|
||||||
&& $mainGold < 0.01
|
|
||||||
) {
|
|
||||||
// GoldInv فقط همان موجودی محصول است — روی کیف اصلی نریز
|
|
||||||
$goldDiff = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rialDiff = $rialInv - (float) ($wallet->cash ?? 0);
|
$rialDiff = $rialInv - (float) ($wallet->cash ?? 0);
|
||||||
|
|
||||||
if (abs($goldDiff) < 0.01 && abs($rialDiff) < 1) {
|
if (abs($goldDiff) < 0.01 && abs($rialDiff) < 1) {
|
||||||
@@ -367,12 +354,21 @@ class PargarInboundWalletService
|
|||||||
$data['applied_gold_delta'] = $goldDiff;
|
$data['applied_gold_delta'] = $goldDiff;
|
||||||
$data['applied_rial_delta'] = $rialDiff;
|
$data['applied_rial_delta'] = $rialDiff;
|
||||||
$data['applied_at'] = now()->toDateTimeString();
|
$data['applied_at'] = now()->toDateTimeString();
|
||||||
|
$data['applied_forced'] = $force;
|
||||||
$data['main_gold_before'] = $mainGold;
|
$data['main_gold_before'] = $mainGold;
|
||||||
$data['non_main_product_gold'] = $nonMainProductGold;
|
unset($data['mande_skip_reason'], $data['mande_skipped_at']);
|
||||||
$log->data = json_encode($data, JSON_UNESCAPED_UNICODE);
|
$log->data = json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||||
$log->desc = 'اعمال ماندهٔ پرگار روی کیف اپ';
|
$log->desc = 'اعمال ماندهٔ پرگار روی کیف اپ';
|
||||||
$log->save();
|
$log->save();
|
||||||
|
|
||||||
|
Log::info('Pargar mande applied to wallet', [
|
||||||
|
'customer_id' => (int) $customer->id,
|
||||||
|
'mande_log_id' => (int) $log->id,
|
||||||
|
'gold_delta' => $goldDiff,
|
||||||
|
'rial_delta' => $rialDiff,
|
||||||
|
'forced' => $force,
|
||||||
|
]);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'applied' => true,
|
'applied' => true,
|
||||||
'gold_delta' => $goldDiff,
|
'gold_delta' => $goldDiff,
|
||||||
@@ -382,23 +378,160 @@ class PargarInboundWalletService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* جمع طلای کیفهای محصول خارج از main_wallet_products (مثل قلک سرمایهگذاری).
|
* ماندهٔ پرگار فقط وقتی معتبر است که همهٔ اسناد ما در پرگار ثبت شده باشند و
|
||||||
|
* snapshot بعد از آن ثبت گرفته شده باشد. اعمال ماندهٔ قدیمی یعنی برگرداندن
|
||||||
|
* موجودی مشتری به قبل از سند — همان «وزن/ریالم کم شد».
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $data
|
||||||
|
* @return array{reason: string, message: string}|null null یعنی معتبر
|
||||||
*/
|
*/
|
||||||
private function sumNonMainProductWalletGold(int $customerId, int $ownerType): float
|
private function mandeBlockReason(Customer $customer, PargarSyncLog $log, array $data): ?array
|
||||||
{
|
{
|
||||||
$mainWalletProductIds = app(ConfigService::class)->mainWalletProductIds();
|
if ($this->hasPendingOutboundToPargar($customer)) {
|
||||||
|
return [
|
||||||
$query = Wallet::query()
|
'reason' => 'pending_outbound',
|
||||||
->where('owner', $customerId)
|
'message' => 'سند پنل هنوز در پرگار ثبت نشده — مانده اعمال نمیشود',
|
||||||
->where('owner_type', $ownerType)
|
];
|
||||||
->whereNull('fund_id')
|
|
||||||
->whereNotNull('product_id');
|
|
||||||
|
|
||||||
if ($mainWalletProductIds !== []) {
|
|
||||||
$query->whereNotIn('product_id', $mainWalletProductIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return round((float) $query->sum('gold'), 3);
|
$lastConfirmedAt = $this->lastOutboundConfirmedAt($customer);
|
||||||
|
$takenAt = $log->created_at;
|
||||||
|
|
||||||
|
if ($lastConfirmedAt !== null && ($takenAt === null || $takenAt->lte($lastConfirmedAt))) {
|
||||||
|
return [
|
||||||
|
'reason' => 'taken_before_document',
|
||||||
|
'message' => 'این مانده پیش از ثبت آخرین سند گرفته شده — نادیده گرفته شد',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$previous = $this->previousComparableMande($log, $lastConfirmedAt);
|
||||||
|
if ($previous === null) {
|
||||||
|
return [
|
||||||
|
'reason' => 'awaiting_second_snapshot',
|
||||||
|
'message' => 'منتظر ماندهٔ بعدی برای تأیید پایداری',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->mandeValuesMatch($data, $previous)) {
|
||||||
|
return [
|
||||||
|
'reason' => 'unstable_snapshot',
|
||||||
|
'message' => 'ماندهٔ پرگار هنوز در حال تغییر است — منتظر مقدار پایدار',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isSuspiciousZeroSnapshot($customer, $data)) {
|
||||||
|
return [
|
||||||
|
'reason' => 'zero_snapshot',
|
||||||
|
'message' => 'ماندهٔ پرگار صفر است ولی کیف مشتری خالی نیست — برای جلوگیری از صفر شدن اشتباه اعمال نشد',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ماندهٔ صفرِ هر دو فیلد برای مشتریای که کیفش خالی نیست، تقریباً همیشه یعنی
|
||||||
|
* پرگار حساب را پیدا نکرده — نه اینکه مشتری تسویه کرده باشد.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $data
|
||||||
|
*/
|
||||||
|
private function isSuspiciousZeroSnapshot(Customer $customer, array $data): bool
|
||||||
|
{
|
||||||
|
$goldInv = $this->parseNumber($data['GoldInv'] ?? 0);
|
||||||
|
$rialInv = $this->parseNumber($data['RialInv'] ?? 0);
|
||||||
|
|
||||||
|
if (abs($goldInv) >= 0.001 || abs($rialInv) >= 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ownerType = (int) ($customer->type ?? 2);
|
||||||
|
$mainGold = app(WalletBalanceService::class)
|
||||||
|
->effectiveMainWalletGold((int) $customer->id, $ownerType);
|
||||||
|
$cash = (float) ((new WalletInterface((int) $customer->id, $ownerType))->wallet->cash ?? 0);
|
||||||
|
|
||||||
|
return abs($mainGold) >= 0.01 || abs($cash) >= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* آخرین لحظهای که پرگار یکی از اسناد ما را ثبت کرده است.
|
||||||
|
*/
|
||||||
|
private function lastOutboundConfirmedAt(Customer $customer): ?Carbon
|
||||||
|
{
|
||||||
|
if (! PargarSyncLog::hasConfirmedAtColumn()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = PargarSyncLog::query()
|
||||||
|
->where('customer_id', $customer->id)
|
||||||
|
->whereIn('type', [
|
||||||
|
PargarSyncService::TYPE_WALLET_CASH,
|
||||||
|
PargarSyncService::TYPE_WALLET_GOLD,
|
||||||
|
PargarSyncService::TYPE_ORDER,
|
||||||
|
])
|
||||||
|
->whereNotNull('confirmed_at')
|
||||||
|
->max('confirmed_at');
|
||||||
|
|
||||||
|
return $value !== null ? Carbon::parse($value) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ماندهٔ قبلی که آن هم بعد از ثبت آخرین سند گرفته شده باشد.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>|null
|
||||||
|
*/
|
||||||
|
private function previousComparableMande(PargarSyncLog $log, ?Carbon $lastConfirmedAt): ?array
|
||||||
|
{
|
||||||
|
$query = PargarSyncLog::query()
|
||||||
|
->where('customer_id', $log->customer_id)
|
||||||
|
->where('type', PargarSyncService::TYPE_MANDE)
|
||||||
|
->where('id', '<', $log->id)
|
||||||
|
->orderByDesc('id');
|
||||||
|
|
||||||
|
if ($lastConfirmedAt !== null) {
|
||||||
|
$query->where('created_at', '>', $lastConfirmedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
$previous = $query->first();
|
||||||
|
if ($previous === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode((string) $previous->data, true);
|
||||||
|
|
||||||
|
return is_array($data) ? $data : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $current
|
||||||
|
* @param array<string, mixed> $previous
|
||||||
|
*/
|
||||||
|
private function mandeValuesMatch(array $current, array $previous): bool
|
||||||
|
{
|
||||||
|
$goldMatch = abs(
|
||||||
|
$this->parseNumber($current['GoldInv'] ?? 0) - $this->parseNumber($previous['GoldInv'] ?? 0)
|
||||||
|
) < 0.001;
|
||||||
|
|
||||||
|
$rialMatch = abs(
|
||||||
|
$this->parseNumber($current['RialInv'] ?? 0) - $this->parseNumber($previous['RialInv'] ?? 0)
|
||||||
|
) < 1;
|
||||||
|
|
||||||
|
return $goldMatch && $rialMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $data
|
||||||
|
*/
|
||||||
|
private function markMandeSkipped(PargarSyncLog $log, array $data, string $reason): void
|
||||||
|
{
|
||||||
|
if (($data['mande_skip_reason'] ?? null) === $reason) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['mande_skip_reason'] = $reason;
|
||||||
|
$data['mande_skipped_at'] = now()->toDateTimeString();
|
||||||
|
|
||||||
|
$log->data = json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||||
|
$log->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function parseNumber(mixed $value): float
|
private function parseNumber(mixed $value): float
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ class PargarSyncService
|
|||||||
$update['data'] = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
$update['data'] = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// لحظهٔ ثبت سند در پرگار: ماندهٔ قدیمیتر از این لحظه دیگر معتبر نیست
|
||||||
|
if (PargarSyncLog::hasConfirmedAtColumn()) {
|
||||||
|
$update['confirmed_at'] = now();
|
||||||
|
}
|
||||||
|
|
||||||
PargarSyncLog::where('id', $log->id)->update($update);
|
PargarSyncLog::where('id', $log->id)->update($update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?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('pargar_sync_logs') || Schema::hasColumn('pargar_sync_logs', 'confirmed_at')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::table('pargar_sync_logs', function (Blueprint $table) {
|
||||||
|
$table->timestamp('confirmed_at')->nullable()->after('sync_status')->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
if (! Schema::hasTable('pargar_sync_logs') || ! Schema::hasColumn('pargar_sync_logs', 'confirmed_at')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::table('pargar_sync_logs', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('confirmed_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user