fix(tahesab): show missing docs and create local table if absent

Pending list was empty because a missing tahesab table hid unsynced orders and charges. Create the table when needed and keep customer on each row.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-02 21:51:21 +03:30
co-authored by Cursor
parent ad12de87c7
commit bb52e4c35a
3 changed files with 136 additions and 65 deletions
@@ -45,6 +45,7 @@ class TahesabController extends Controller
public function table(Request $request) public function table(Request $request)
{ {
app(TahesabSyncService::class)->ensureLocalTable();
if (! Schema::hasTable('tahesab')) { if (! Schema::hasTable('tahesab')) {
return response()->json([]); return response()->json([]);
} }
+95 -65
View File
@@ -5,6 +5,7 @@ namespace App\Services;
use App\Http\Controllers\Admin\TahesabController; use App\Http\Controllers\Admin\TahesabController;
use App\Models\Customer; use App\Models\Customer;
use App\Models\Tahesab; use App\Models\Tahesab;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
@@ -222,13 +223,12 @@ class TahesabSyncService
*/ */
public function pendingSummary(?int $customerId = null): array public function pendingSummary(?int $customerId = null): array
{ {
if (! Schema::hasTable('tahesab')) { $hasTable = $this->ensureLocalTable();
return $this->emptyPendingPayload(); $failedRows = $hasTable
} ? (int) $this->unsyncedRowsQuery()
->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
$failedRows = (int) $this->unsyncedRowsQuery() ->count()
->when($customerId, fn ($q) => $q->where('customer_id', $customerId)) : 0;
->count();
$missingWithdraws = count($this->missingWithdrawIds(200, $customerId)); $missingWithdraws = count($this->missingWithdrawIds(200, $customerId));
$missingOrders = count($this->missingOrderIds(200, $customerId)); $missingOrders = count($this->missingOrderIds(200, $customerId));
@@ -248,16 +248,12 @@ class TahesabSyncService
*/ */
public function pendingTotals(?int $customerId = null): array public function pendingTotals(?int $customerId = null): array
{ {
if (! Schema::hasTable('tahesab')) { $hasTable = $this->ensureLocalTable();
$empty = $this->emptyPendingPayload(); $failedRows = $hasTable
unset($empty['doc_sync']); ? (int) $this->unsyncedRowsQuery()
->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
return $empty; ->count()
} : 0;
$failedRows = (int) $this->unsyncedRowsQuery()
->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
->count();
$missingWithdraws = $this->countMissingWithdraws($customerId); $missingWithdraws = $this->countMissingWithdraws($customerId);
$missingOrders = $this->countMissingOrders($customerId); $missingOrders = $this->countMissingOrders($customerId);
@@ -275,42 +271,46 @@ class TahesabSyncService
*/ */
public function peekNextPending(array $skip = []): ?array public function peekNextPending(array $skip = []): ?array
{ {
if (! Schema::hasTable('tahesab')) { if ($this->ensureLocalTable()) {
return null; $skipRow = array_values(array_filter($skip['row'] ?? []));
} $row = $this->unsyncedRowsQuery()
->with('customer')
$skipRow = array_values(array_filter($skip['row'] ?? [])); ->when($skipRow !== [], fn ($q) => $q->whereNotIn('id', $skipRow))
$row = $this->unsyncedRowsQuery() ->orderBy('id')
->when($skipRow !== [], fn ($q) => $q->whereNotIn('id', $skipRow)) ->first();
->orderBy('id') if ($row) {
->first(); $name = optional($row->customer)->name;
if ($row) { return [
return [ 'type' => 'row',
'type' => 'row', 'id' => (int) $row->id,
'id' => (int) $row->id, 'label' => trim('سند محلی #'.$row->id.($name ? ' — '.$name : '')),
'label' => 'سند محلی #'.$row->id, ];
]; }
} }
$wIds = $this->missingWithdrawIds(1, null, array_values(array_filter($skip['withdraw'] ?? []))); $wIds = $this->missingWithdrawIds(1, null, array_values(array_filter($skip['withdraw'] ?? [])));
if ($wIds !== []) { if ($wIds !== []) {
$id = (int) $wIds[0]; $id = (int) $wIds[0];
$w = \App\Models\Withdraw::query()->with('customer')->find($id);
$name = optional(optional($w)->customer)->name;
return [ return [
'type' => 'withdraw', 'type' => 'withdraw',
'id' => $id, 'id' => $id,
'label' => 'شارژ/برداشت #'.$id, 'label' => trim('شارژ/برداشت #'.$id.($name ? ' — '.$name : '')),
]; ];
} }
$oIds = $this->missingOrderIds(1, null, array_values(array_filter($skip['order'] ?? []))); $oIds = $this->missingOrderIds(1, null, array_values(array_filter($skip['order'] ?? [])));
if ($oIds !== []) { if ($oIds !== []) {
$id = (int) $oIds[0]; $id = (int) $oIds[0];
$order = \App\Models\Order::query()->with('customer')->find($id);
$name = optional(optional($order)->customer)->name;
return [ return [
'type' => 'order', 'type' => 'order',
'id' => $id, 'id' => $id,
'label' => 'سفارش #'.$id, 'label' => trim('سفارش #'.$id.($name ? ' — '.$name : '')),
]; ];
} }
@@ -324,25 +324,23 @@ class TahesabSyncService
*/ */
public function pendingDocs(?int $customerId = null, int $limit = 200): array public function pendingDocs(?int $customerId = null, int $limit = 200): array
{ {
if (! Schema::hasTable('tahesab')) {
return [];
}
$limit = max(1, min(500, $limit)); $limit = max(1, min(500, $limit));
$rows = []; $rows = [];
$local = $this->unsyncedRowsQuery() if ($this->ensureLocalTable()) {
->with('customer') $local = $this->unsyncedRowsQuery()
->when($customerId, fn ($q) => $q->where('customer_id', $customerId)) ->with('customer')
->orderByDesc('id') ->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
->limit($limit) ->orderByDesc('id')
->get(); ->limit($limit)
->get();
foreach ($local as $row) { foreach ($local as $row) {
$arr = $row->toArray(); $arr = $row->toArray();
$arr['issue_kind'] = 'failed'; $arr['issue_kind'] = 'failed';
$arr['sync_target'] = ['type' => 'row', 'id' => (int) $row->id]; $arr['sync_target'] = ['type' => 'row', 'id' => (int) $row->id];
$rows[] = $arr; $rows[] = $arr;
}
} }
$remaining = $limit - count($rows); $remaining = $limit - count($rows);
@@ -575,11 +573,11 @@ class TahesabSyncService
private function missingWithdrawQuery(?int $customerId = null, array $exclude = []) private function missingWithdrawQuery(?int $customerId = null, array $exclude = [])
{ {
if (! Schema::hasTable('withdraws') || ! Schema::hasTable('tahesab')) { if (! Schema::hasTable('withdraws')) {
return null; return null;
} }
return \App\Models\Withdraw::query() $query = \App\Models\Withdraw::query()
->where('sts', 4) ->where('sts', 4)
->where(function ($q) { ->where(function ($q) {
$q->where('cash', '!=', 0)->orWhere('gold', '!=', 0); $q->where('cash', '!=', 0)->orWhere('gold', '!=', 0);
@@ -589,10 +587,15 @@ class TahesabSyncService
->orWhere('note', 'not like', 'fix:intval-gold-loss%'); ->orWhere('note', 'not like', 'fix:intval-gold-loss%');
}) })
->when($customerId, fn ($q) => $q->where('customer_id', $customerId)) ->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
->when($exclude !== [], fn ($q) => $q->whereNotIn('id', $exclude)) ->when($exclude !== [], fn ($q) => $q->whereNotIn('id', $exclude));
->whereNotExists(function ($sub) {
if (Schema::hasTable('tahesab')) {
$query->whereNotExists(function ($sub) {
$sub->from('tahesab')->selectRaw('1')->whereColumn('tahesab.withdraw_id', 'withdraws.id'); $sub->from('tahesab')->selectRaw('1')->whereColumn('tahesab.withdraw_id', 'withdraws.id');
}); });
}
return $query;
} }
/** @return list<int> */ /** @return list<int> */
@@ -620,19 +623,24 @@ class TahesabSyncService
private function missingOrderQuery(?int $customerId = null, array $exclude = []) private function missingOrderQuery(?int $customerId = null, array $exclude = [])
{ {
if (! Schema::hasTable('orders') || ! Schema::hasTable('tahesab')) { if (! Schema::hasTable('orders')) {
return null; return null;
} }
return \App\Models\Order::query() $query = \App\Models\Order::query()
->where('sts', 2) ->where('sts', 2)
->where('payment_sts', 4) ->where('payment_sts', 4)
->whereIn('direction', [1, 2]) ->whereIn('direction', [1, 2])
->when($customerId, fn ($q) => $q->where('customer_id', $customerId)) ->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
->when($exclude !== [], fn ($q) => $q->whereNotIn('id', $exclude)) ->when($exclude !== [], fn ($q) => $q->whereNotIn('id', $exclude));
->whereNotExists(function ($sub) {
if (Schema::hasTable('tahesab')) {
$query->whereNotExists(function ($sub) {
$sub->from('tahesab')->selectRaw('1')->whereColumn('tahesab.order_id', 'orders.id'); $sub->from('tahesab')->selectRaw('1')->whereColumn('tahesab.order_id', 'orders.id');
}); });
}
return $query;
} }
private function backfillWithdraw(TahesabController $controller, int $withdrawId): bool private function backfillWithdraw(TahesabController $controller, int $withdrawId): bool
@@ -705,15 +713,37 @@ class TahesabSyncService
return $result !== false; return $result !== false;
} }
private function emptyPendingPayload(): array public function ensureLocalTable(): bool
{ {
return [ if (Schema::hasTable('tahesab')) {
'failed_rows' => 0, return true;
'missing_withdraws' => 0, }
'missing_orders' => 0,
'total' => 0, try {
'doc_sync' => app(TahesabDocSyncService::class)->snapshot(), Schema::create('tahesab', function (Blueprint $table) {
]; $table->id();
$table->unsignedBigInteger('customer_id')->nullable()->index();
$table->unsignedBigInteger('order_id')->nullable()->index();
$table->unsignedBigInteger('withdraw_id')->nullable()->index();
$table->decimal('amount', 18, 4)->nullable();
$table->string('desc')->nullable();
$table->string('type', 32)->nullable();
$table->timestamp('date')->nullable();
$table->string('tahesab_id')->nullable()->index();
$table->string('sh_factor')->nullable();
$table->string('factor_number')->nullable();
$table->string('tah_id')->nullable();
$table->longText('data')->nullable();
$table->longText('res')->nullable();
$table->string('sync_status', 20)->default('pending');
$table->text('sync_log')->nullable();
$table->timestamps();
});
} catch (\Throwable $e) {
Log::warning('Could not create tahesab table', ['error' => $e->getMessage()]);
}
return Schema::hasTable('tahesab');
} }
private function hasSyncColumns(): bool private function hasSyncColumns(): bool
@@ -0,0 +1,40 @@
<?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('tahesab')) {
return;
}
Schema::create('tahesab', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('customer_id')->nullable()->index();
$table->unsignedBigInteger('order_id')->nullable()->index();
$table->unsignedBigInteger('withdraw_id')->nullable()->index();
$table->decimal('amount', 18, 4)->nullable();
$table->string('desc')->nullable();
$table->string('type', 32)->nullable();
$table->timestamp('date')->nullable();
$table->string('tahesab_id')->nullable()->index();
$table->string('sh_factor')->nullable();
$table->string('factor_number')->nullable();
$table->string('tah_id')->nullable();
$table->longText('data')->nullable();
$table->longText('res')->nullable();
$table->string('sync_status', 20)->default('pending');
$table->text('sync_log')->nullable();
$table->timestamps();
});
}
public function down(): void
{
// جدول ممکن است از قبل روی نصب‌های قدیمی بوده باشد؛ drop نمی‌کنیم.
}
};