Apply Tahesab account balances to wallets with admin overwrite control.
Add inbound mande sync on customer import plus per-customer and bulk wallet-sync endpoints so previously linked accounts can catch up. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,6 +11,8 @@ use Illuminate\Support\Facades\Cache;
|
||||
use App\Models\Tahesab;
|
||||
use App\Models\Customer;
|
||||
use App\Services\AccountingProductDocMapper;
|
||||
use App\Services\ConfigService;
|
||||
use App\Services\TahesabInboundWalletService;
|
||||
use App\Services\TahesabSyncService;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||||
@@ -237,6 +239,8 @@ class TahesabController extends Controller
|
||||
$success = 0;
|
||||
$failed = 0;
|
||||
$errors = [];
|
||||
/** @var list<Customer> $importedForWallet */
|
||||
$importedForWallet = [];
|
||||
|
||||
foreach ($validated['customers'] as $item) {
|
||||
$mobile = Customer::normalizeMobile($item['tel'] ?? '') ?? '';
|
||||
@@ -282,6 +286,7 @@ class TahesabController extends Controller
|
||||
if ($changed) {
|
||||
$existing->save();
|
||||
}
|
||||
$importedForWallet[] = $existing;
|
||||
$success++;
|
||||
continue;
|
||||
}
|
||||
@@ -300,6 +305,7 @@ class TahesabController extends Controller
|
||||
$byMobile->password = $mobile;
|
||||
}
|
||||
$byMobile->save();
|
||||
$importedForWallet[] = $byMobile;
|
||||
$success++;
|
||||
continue;
|
||||
}
|
||||
@@ -340,6 +346,7 @@ class TahesabController extends Controller
|
||||
$customer->password = $mobile;
|
||||
}
|
||||
$customer->save();
|
||||
$importedForWallet[] = $customer;
|
||||
$success++;
|
||||
} catch (\Throwable $e) {
|
||||
$failed++;
|
||||
@@ -353,6 +360,18 @@ class TahesabController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
// مانده طلا/ریال تهحساب را روی کیف مشتریان واردشده بنشان
|
||||
if ($importedForWallet !== []) {
|
||||
try {
|
||||
app(TahesabInboundWalletService::class)->syncAfterImport($importedForWallet);
|
||||
} catch (\Throwable $e) {
|
||||
\Log::warning('Tahesab import wallet sync batch failed', [
|
||||
'count' => count($importedForWallet),
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'success' => $success,
|
||||
@@ -452,6 +471,161 @@ class TahesabController extends Controller
|
||||
return response()->json($result, ($result['ok'] ?? false) ? 200 : 422);
|
||||
}
|
||||
|
||||
/**
|
||||
* همگامسازی مانده یک مشتری با تهحساب.
|
||||
* body: overwrite=1 | keep=1 | ask_if_conflict=1 (پیشفرض ask برای جلوگیری از overwrite ناخواسته)
|
||||
*/
|
||||
public function syncCustomerWallet(Request $request, $customerId)
|
||||
{
|
||||
if (! app(ConfigService::class)->isTahesabAccounting()) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'حسابداری تهحساب فعال نیست',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$customer = Customer::findOrFail($customerId);
|
||||
if ((int) ($customer->moshtari_code ?? 0) <= 0) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'این مشتری کد تهحساب ندارد',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$overwrite = $request->boolean('overwrite');
|
||||
$keep = $request->boolean('keep');
|
||||
// اگر صراحتاً overwrite/keep نباشد → از ادمین بپرس وقتی تداخل هست
|
||||
$askIfConflict = $request->boolean('ask_if_conflict', ! $overwrite && ! $keep);
|
||||
|
||||
$result = app(TahesabInboundWalletService::class)->syncWalletFromTahesab(
|
||||
$customer,
|
||||
null,
|
||||
[
|
||||
'overwrite' => $overwrite,
|
||||
'keep' => $keep,
|
||||
'ask_if_conflict' => $askIfConflict,
|
||||
]
|
||||
);
|
||||
|
||||
$needsDecision = (bool) ($result['needs_decision'] ?? false);
|
||||
$applied = (bool) ($result['applied'] ?? false);
|
||||
$skipped = (bool) ($result['skipped'] ?? false);
|
||||
$same = ! $applied && ! $needsDecision && ! $skipped
|
||||
&& str_contains((string) ($result['message'] ?? ''), 'یکسان');
|
||||
|
||||
return response()->json([
|
||||
'ok' => $applied || $skipped || $same,
|
||||
'needs_decision' => $needsDecision,
|
||||
'message' => $result['message'] ?? '',
|
||||
'result' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* همگامسازی مانده همه مشتریان دارای کد تهحساب — تکهتکه.
|
||||
* body: overwrite=1 برای اعمال اجباری اختلافها؛ بدون آن فقط کیف خالی یا یکسان پردازش میشود و تداخلها در needs_decision میآیند.
|
||||
*/
|
||||
public function syncAllWallets(Request $request)
|
||||
{
|
||||
if (! app(ConfigService::class)->isTahesabAccounting()) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'حسابداری تهحساب فعال نیست',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$limit = max(1, min(50, (int) $request->input('limit', 20)));
|
||||
$offset = max(0, (int) $request->input('offset', 0));
|
||||
$overwrite = $request->boolean('overwrite');
|
||||
|
||||
$base = Customer::query()
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('moshtari_code')
|
||||
->where('moshtari_code', '!=', '')
|
||||
->where('moshtari_code', '!=', '0');
|
||||
|
||||
$total = (clone $base)->count();
|
||||
$customers = (clone $base)
|
||||
->orderBy('id')
|
||||
->offset($offset)
|
||||
->limit($limit)
|
||||
->get(['id', 'name', 'mobile', 'moshtari_code', 'type']);
|
||||
|
||||
$walletService = app(TahesabInboundWalletService::class);
|
||||
$codes = $customers->pluck('moshtari_code')->all();
|
||||
$mandes = $walletService->fetchMandeByCodes($codes);
|
||||
|
||||
$success = 0;
|
||||
$changed = 0;
|
||||
$needsDecision = 0;
|
||||
$failed = 0;
|
||||
$errors = [];
|
||||
$decisions = [];
|
||||
|
||||
foreach ($customers as $customer) {
|
||||
try {
|
||||
$code = (string) ((int) $customer->moshtari_code);
|
||||
$result = $walletService->syncWalletFromTahesab(
|
||||
$customer,
|
||||
$mandes[$code] ?? null,
|
||||
[
|
||||
'overwrite' => $overwrite,
|
||||
'ask_if_conflict' => ! $overwrite,
|
||||
]
|
||||
);
|
||||
|
||||
if ($result['needs_decision'] ?? false) {
|
||||
$needsDecision++;
|
||||
$decisions[] = [
|
||||
'id' => $customer->id,
|
||||
'name' => $customer->name,
|
||||
'mobile' => $customer->mobile,
|
||||
'app_gold' => $result['app_gold'] ?? null,
|
||||
'app_cash' => $result['app_cash'] ?? null,
|
||||
'tahesab_gold' => $result['tahesab_gold'] ?? null,
|
||||
'tahesab_cash' => $result['tahesab_cash'] ?? null,
|
||||
'message' => $result['message'] ?? '',
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$success++;
|
||||
if ($result['applied'] ?? false) {
|
||||
$changed++;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$failed++;
|
||||
$errors[] = [
|
||||
'id' => $customer->id,
|
||||
'name' => $customer->name,
|
||||
'mobile' => $customer->mobile,
|
||||
'error' => $e->getMessage() !== '' ? $e->getMessage() : 'خطای ناشناخته',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$processed = $customers->count();
|
||||
$nextOffset = $offset + $processed;
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'message' => $total === 0
|
||||
? 'مشتری دارای کد تهحساب یافت نشد'
|
||||
: "دستهٔ {$offset} تا {$nextOffset} از {$total}",
|
||||
'total' => $total,
|
||||
'offset' => $offset,
|
||||
'processed' => $processed,
|
||||
'next_offset' => $nextOffset,
|
||||
'done' => $nextOffset >= $total,
|
||||
'success' => $success,
|
||||
'changed' => $changed,
|
||||
'needs_decision' => $needsDecision,
|
||||
'failed' => $failed,
|
||||
'errors' => $errors,
|
||||
'decisions' => $decisions,
|
||||
]);
|
||||
}
|
||||
|
||||
private function tahesabApiUrl(): string
|
||||
{
|
||||
$host = trim((string) $this->Config('tahesab_host'));
|
||||
|
||||
Reference in New Issue
Block a user