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)
{
app(TahesabSyncService::class)->ensureLocalTable();
if (! Schema::hasTable('tahesab')) {
return response()->json([]);
}
+95 -65
View File
@@ -5,6 +5,7 @@ namespace App\Services;
use App\Http\Controllers\Admin\TahesabController;
use App\Models\Customer;
use App\Models\Tahesab;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
@@ -222,13 +223,12 @@ class TahesabSyncService
*/
public function pendingSummary(?int $customerId = null): array
{
if (! Schema::hasTable('tahesab')) {
return $this->emptyPendingPayload();
}
$failedRows = (int) $this->unsyncedRowsQuery()
->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
->count();
$hasTable = $this->ensureLocalTable();
$failedRows = $hasTable
? (int) $this->unsyncedRowsQuery()
->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
->count()
: 0;
$missingWithdraws = count($this->missingWithdrawIds(200, $customerId));
$missingOrders = count($this->missingOrderIds(200, $customerId));
@@ -248,16 +248,12 @@ class TahesabSyncService
*/
public function pendingTotals(?int $customerId = null): array
{
if (! Schema::hasTable('tahesab')) {
$empty = $this->emptyPendingPayload();
unset($empty['doc_sync']);
return $empty;
}
$failedRows = (int) $this->unsyncedRowsQuery()
->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
->count();
$hasTable = $this->ensureLocalTable();
$failedRows = $hasTable
? (int) $this->unsyncedRowsQuery()
->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
->count()
: 0;
$missingWithdraws = $this->countMissingWithdraws($customerId);
$missingOrders = $this->countMissingOrders($customerId);
@@ -275,42 +271,46 @@ class TahesabSyncService
*/
public function peekNextPending(array $skip = []): ?array
{
if (! Schema::hasTable('tahesab')) {
return null;
}
$skipRow = array_values(array_filter($skip['row'] ?? []));
$row = $this->unsyncedRowsQuery()
->when($skipRow !== [], fn ($q) => $q->whereNotIn('id', $skipRow))
->orderBy('id')
->first();
if ($row) {
return [
'type' => 'row',
'id' => (int) $row->id,
'label' => 'سند محلی #'.$row->id,
];
if ($this->ensureLocalTable()) {
$skipRow = array_values(array_filter($skip['row'] ?? []));
$row = $this->unsyncedRowsQuery()
->with('customer')
->when($skipRow !== [], fn ($q) => $q->whereNotIn('id', $skipRow))
->orderBy('id')
->first();
if ($row) {
$name = optional($row->customer)->name;
return [
'type' => 'row',
'id' => (int) $row->id,
'label' => trim('سند محلی #'.$row->id.($name ? ' — '.$name : '')),
];
}
}
$wIds = $this->missingWithdrawIds(1, null, array_values(array_filter($skip['withdraw'] ?? [])));
if ($wIds !== []) {
$id = (int) $wIds[0];
$w = \App\Models\Withdraw::query()->with('customer')->find($id);
$name = optional(optional($w)->customer)->name;
return [
'type' => 'withdraw',
'id' => $id,
'label' => 'شارژ/برداشت #'.$id,
'label' => trim('شارژ/برداشت #'.$id.($name ? ' — '.$name : '')),
];
}
$oIds = $this->missingOrderIds(1, null, array_values(array_filter($skip['order'] ?? [])));
if ($oIds !== []) {
$id = (int) $oIds[0];
$order = \App\Models\Order::query()->with('customer')->find($id);
$name = optional(optional($order)->customer)->name;
return [
'type' => 'order',
'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
{
if (! Schema::hasTable('tahesab')) {
return [];
}
$limit = max(1, min(500, $limit));
$rows = [];
$local = $this->unsyncedRowsQuery()
->with('customer')
->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
->orderByDesc('id')
->limit($limit)
->get();
if ($this->ensureLocalTable()) {
$local = $this->unsyncedRowsQuery()
->with('customer')
->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
->orderByDesc('id')
->limit($limit)
->get();
foreach ($local as $row) {
$arr = $row->toArray();
$arr['issue_kind'] = 'failed';
$arr['sync_target'] = ['type' => 'row', 'id' => (int) $row->id];
$rows[] = $arr;
foreach ($local as $row) {
$arr = $row->toArray();
$arr['issue_kind'] = 'failed';
$arr['sync_target'] = ['type' => 'row', 'id' => (int) $row->id];
$rows[] = $arr;
}
}
$remaining = $limit - count($rows);
@@ -575,11 +573,11 @@ class TahesabSyncService
private function missingWithdrawQuery(?int $customerId = null, array $exclude = [])
{
if (! Schema::hasTable('withdraws') || ! Schema::hasTable('tahesab')) {
if (! Schema::hasTable('withdraws')) {
return null;
}
return \App\Models\Withdraw::query()
$query = \App\Models\Withdraw::query()
->where('sts', 4)
->where(function ($q) {
$q->where('cash', '!=', 0)->orWhere('gold', '!=', 0);
@@ -589,10 +587,15 @@ class TahesabSyncService
->orWhere('note', 'not like', 'fix:intval-gold-loss%');
})
->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
->when($exclude !== [], fn ($q) => $q->whereNotIn('id', $exclude))
->whereNotExists(function ($sub) {
->when($exclude !== [], fn ($q) => $q->whereNotIn('id', $exclude));
if (Schema::hasTable('tahesab')) {
$query->whereNotExists(function ($sub) {
$sub->from('tahesab')->selectRaw('1')->whereColumn('tahesab.withdraw_id', 'withdraws.id');
});
}
return $query;
}
/** @return list<int> */
@@ -620,19 +623,24 @@ class TahesabSyncService
private function missingOrderQuery(?int $customerId = null, array $exclude = [])
{
if (! Schema::hasTable('orders') || ! Schema::hasTable('tahesab')) {
if (! Schema::hasTable('orders')) {
return null;
}
return \App\Models\Order::query()
$query = \App\Models\Order::query()
->where('sts', 2)
->where('payment_sts', 4)
->whereIn('direction', [1, 2])
->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
->when($exclude !== [], fn ($q) => $q->whereNotIn('id', $exclude))
->whereNotExists(function ($sub) {
->when($exclude !== [], fn ($q) => $q->whereNotIn('id', $exclude));
if (Schema::hasTable('tahesab')) {
$query->whereNotExists(function ($sub) {
$sub->from('tahesab')->selectRaw('1')->whereColumn('tahesab.order_id', 'orders.id');
});
}
return $query;
}
private function backfillWithdraw(TahesabController $controller, int $withdrawId): bool
@@ -705,15 +713,37 @@ class TahesabSyncService
return $result !== false;
}
private function emptyPendingPayload(): array
public function ensureLocalTable(): bool
{
return [
'failed_rows' => 0,
'missing_withdraws' => 0,
'missing_orders' => 0,
'total' => 0,
'doc_sync' => app(TahesabDocSyncService::class)->snapshot(),
];
if (Schema::hasTable('tahesab')) {
return true;
}
try {
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
@@ -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 نمی‌کنیم.
}
};