Files
boursecu_server/app/Services/DatabaseLogPruneService.php
T
mohmdsdgandCursor 0b1e5c687b feat(order): wait for colleague callback before settling wallet
Keep cover orders pending until colleague sts 2/4/6 via callback or 35s poll. Nightly prices:prune also drops old log tables.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 15:55:03 +03:30

141 lines
4.5 KiB
PHP

<?php
namespace App\Services;
use App\Models\Log;
use App\Models\PargarSyncLog;
use App\Models\PriceSyncLog;
use App\Models\PriceSyncLogItem;
use App\Models\Tahesab;
use App\Models\Zarin;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Schema;
/**
* پاک‌سازی شبانهٔ لاگ‌های حجیم — همان سقف نگه‌داری جدول prices (۳۱ روز).
*/
class DatabaseLogPruneService
{
/** هم‌راستا با حذف قطعی در PriceSnapshotService::pruneHistory */
public const RETENTION_DAYS = 31;
private const CHUNK = 500;
public function pruneHistory(?Carbon $now = null): array
{
$cutoff = ($now ?? now())->copy()->subDays(self::RETENTION_DAYS);
return [
'retention_days' => self::RETENTION_DAYS,
'cutoff' => $cutoff->toDateTimeString(),
'price_sync_logs' => $this->prunePriceSyncLogs($cutoff),
'logs' => $this->pruneGenericLogs($cutoff),
'pargar_sync_logs' => $this->pruneSuccessfulRows(
PargarSyncLog::query(),
'pargar_sync_logs',
PargarSyncService::STATUS_SUCCESS,
$cutoff
),
'zarin' => $this->pruneSuccessfulRows(
Zarin::query(),
'zarin',
ZarinSyncService::STATUS_SUCCESS,
$cutoff
),
'tahesab' => $this->pruneSuccessfulRows(
Tahesab::query(),
'tahesab',
TahesabSyncService::STATUS_SUCCESS,
$cutoff
),
];
}
/** @return array{deleted: int, items_deleted: int} */
private function prunePriceSyncLogs(Carbon $cutoff): array
{
if (! Schema::hasTable('price_sync_logs')) {
return ['deleted' => 0, 'items_deleted' => 0];
}
$deleted = 0;
$itemsDeleted = 0;
$hasItems = Schema::hasTable('price_sync_log_items');
PriceSyncLog::query()
->where('created_at', '<', $cutoff)
->orderBy('id')
->select(['id'])
->chunkById(self::CHUNK, function ($rows) use (&$deleted, &$itemsDeleted, $hasItems) {
$ids = $rows->pluck('id')->all();
if ($ids === []) {
return;
}
if ($hasItems) {
$itemsDeleted += (int) PriceSyncLogItem::query()->whereIn('log_id', $ids)->delete();
}
$deleted += (int) PriceSyncLog::query()->whereIn('id', $ids)->delete();
});
return ['deleted' => $deleted, 'items_deleted' => $itemsDeleted];
}
/** @return array{deleted: int} */
private function pruneGenericLogs(Carbon $cutoff): array
{
if (! Schema::hasTable('logs')) {
return ['deleted' => 0];
}
$deleted = 0;
Log::query()
->where('created_at', '<', $cutoff)
->orderBy('id')
->select(['id'])
->chunkById(self::CHUNK, function ($rows) use (&$deleted) {
$ids = $rows->pluck('id')->all();
if ($ids === []) {
return;
}
$deleted += (int) Log::query()->whereIn('id', $ids)->delete();
});
return ['deleted' => $deleted];
}
/**
* فقط ردیف‌های موفق حسابداری — pending/failed برای retry می‌مانند.
*
* @return array{deleted: int, skipped: bool}
*/
private function pruneSuccessfulRows(Builder $query, string $table, string $successStatus, Carbon $cutoff): array
{
if (! Schema::hasTable($table) || ! Schema::hasColumn($table, 'sync_status')) {
return ['deleted' => 0, 'skipped' => true];
}
if (! Schema::hasColumn($table, 'created_at')) {
return ['deleted' => 0, 'skipped' => true];
}
$deleted = 0;
(clone $query)
->where('sync_status', $successStatus)
->where('created_at', '<', $cutoff)
->orderBy('id')
->select(['id'])
->chunkById(self::CHUNK, function ($rows) use (&$deleted, $query) {
$ids = $rows->pluck('id')->all();
if ($ids === []) {
return;
}
$deleted += (int) (clone $query)->whereIn('id', $ids)->delete();
});
return ['deleted' => $deleted, 'skipped' => false];
}
}