2026-01-24 14:03:11 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
2026-03-14 11:08:30 +03:30
|
|
|
use App\Http\Controllers\Site\ReportController;
|
2026-01-24 14:03:11 +00:00
|
|
|
use Carbon\Carbon;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\Support\Facades\Http;
|
2026-06-27 22:10:29 +03:30
|
|
|
use Illuminate\Support\Facades\Cache;
|
2026-01-24 14:03:11 +00:00
|
|
|
use App\Models\Tahesab;
|
|
|
|
|
use App\Models\Customer;
|
2026-07-28 17:44:24 +03:30
|
|
|
use App\Services\AccountingProductDocMapper;
|
2026-08-30 14:16:36 +03:30
|
|
|
use App\Services\ConfigService;
|
|
|
|
|
use App\Services\TahesabInboundWalletService;
|
2026-06-25 22:20:40 +03:30
|
|
|
use App\Services\TahesabSyncService;
|
2026-02-08 08:02:11 +00:00
|
|
|
use Morilog\Jalali\Jalalian;
|
2026-01-24 14:03:11 +00:00
|
|
|
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
|
|
|
|
use Hekmatinasser\Verta\Verta;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TahesabController extends Controller
|
|
|
|
|
{
|
2026-06-25 22:20:40 +03:30
|
|
|
protected function sync(): TahesabSyncService
|
|
|
|
|
{
|
|
|
|
|
return app(TahesabSyncService::class);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-25 22:24:46 +03:30
|
|
|
private function lastPriceData(): array
|
|
|
|
|
{
|
|
|
|
|
$response = app(ReportController::class)->lastPrice(request());
|
|
|
|
|
|
|
|
|
|
if (is_array($response)) {
|
|
|
|
|
return $response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_object($response) && method_exists($response, 'getData')) {
|
|
|
|
|
return (array) $response->getData(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (array) $response;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
public function table(Request $request)
|
|
|
|
|
{
|
2026-08-27 16:16:20 +03:30
|
|
|
$view = (string) $request->input('view', 'all');
|
2026-06-25 22:20:40 +03:30
|
|
|
$query = Tahesab::with('customer')->orderByDesc('id');
|
2026-08-27 16:16:20 +03:30
|
|
|
|
|
|
|
|
if ($view === 'ok') {
|
|
|
|
|
$query->whereNotNull('tahesab_id')->where('tahesab_id', '!=', '');
|
|
|
|
|
} elseif ($view === 'failed') {
|
|
|
|
|
$query->where(function ($q) {
|
|
|
|
|
$q->where(function ($inner) {
|
|
|
|
|
$inner->whereNull('tahesab_id')->orWhere('tahesab_id', '');
|
|
|
|
|
})->orWhere('sync_status', 'failed');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
\App\DB\Te\TableEngine::create($query, $request)
|
|
|
|
|
->initSearch(['desc', 'type'])
|
|
|
|
|
->select(['*'])
|
|
|
|
|
->export($query);
|
|
|
|
|
|
|
|
|
|
return response()->json($query);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function show($id)
|
|
|
|
|
{
|
|
|
|
|
$tahesab = Tahesab::with('customer')->findOrFail($id);
|
|
|
|
|
|
|
|
|
|
return response()->json($tahesab);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 21:05:04 +03:30
|
|
|
/** همه مشتریان تهحساب را از API میگیرد و برمیگرداند (Code => [Code,Name,Tel]) */
|
|
|
|
|
private function fetchAllTahesabCustomers(): array
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
$response = Http::withoutVerifying()->timeout(30)->withHeaders([
|
|
|
|
|
'DBName' => $this->Config('tahesab_db'),
|
|
|
|
|
'Content-Type' => 'application/json',
|
|
|
|
|
'Authorization' => 'Bearer ' . $this->Config('tahesab_token'),
|
|
|
|
|
])->post($this->tahesabApiUrl(), [
|
|
|
|
|
'DoListMoshtari' => [1, 999999],
|
|
|
|
|
])->json();
|
|
|
|
|
|
|
|
|
|
if (! is_array($response)) return [];
|
|
|
|
|
return $response;
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 19:13:35 +03:30
|
|
|
public function customerSyncStatus(Request $request): \Illuminate\Http\JsonResponse
|
|
|
|
|
{
|
2026-06-28 21:05:04 +03:30
|
|
|
// مشتریان پنل
|
|
|
|
|
$panelTotal = Customer::whereNull('deleted_at')->count();
|
|
|
|
|
$panelRegistered = Customer::whereNull('deleted_at')->whereNotNull('moshtari_code')->count();
|
2026-06-28 21:28:44 +03:30
|
|
|
$panelOnly = $panelTotal - $panelRegistered;
|
2026-06-28 21:05:04 +03:30
|
|
|
|
|
|
|
|
$panelOnlyQuery = Customer::whereNull('deleted_at')->whereNull('moshtari_code')
|
2026-06-28 19:13:35 +03:30
|
|
|
->select('id', 'name', 'mobile', 'created_at');
|
|
|
|
|
|
|
|
|
|
if ($search = $request->input('search')) {
|
2026-06-28 21:05:04 +03:30
|
|
|
$panelOnlyQuery->where(function ($q) use ($search) {
|
2026-06-28 19:13:35 +03:30
|
|
|
$q->where('name', 'like', "%{$search}%")
|
|
|
|
|
->orWhere('mobile', 'like', "%{$search}%");
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-06-28 21:05:04 +03:30
|
|
|
$panelOnlyList = $panelOnlyQuery->orderBy('id')->paginate(20);
|
2026-06-28 19:13:35 +03:30
|
|
|
|
2026-06-28 21:28:44 +03:30
|
|
|
// مشتریان تهحساب — فقط وقتی include_tahesab=true ارسال شده (چون کند است)
|
|
|
|
|
$tahesabTotal = null;
|
|
|
|
|
$tahesabOnlyCount = null;
|
|
|
|
|
$tahesabOnlyResult = ['data' => [], 'total' => 0, 'current_page' => 1, 'last_page' => 1];
|
|
|
|
|
|
|
|
|
|
if ($request->boolean('include_tahesab')) {
|
|
|
|
|
$tahesabCustomers = $this->fetchAllTahesabCustomers();
|
|
|
|
|
$tahesabTotal = count($tahesabCustomers);
|
|
|
|
|
|
|
|
|
|
// موبایلهای پنل بدون صفر ابتدایی
|
|
|
|
|
$panelMobiles = Customer::whereNull('deleted_at')
|
|
|
|
|
->whereNotNull('mobile')
|
|
|
|
|
->pluck('mobile')
|
|
|
|
|
->mapWithKeys(fn ($m) => [ltrim((string) $m, '0') => true])
|
|
|
|
|
->toArray();
|
|
|
|
|
|
2026-06-29 00:41:42 +03:30
|
|
|
// کدهای مشتریِ قبلاً ثبتشده در پنل (برای جلوگیری از تکرار)
|
|
|
|
|
$panelCodes = Customer::whereNull('deleted_at')
|
|
|
|
|
->whereNotNull('moshtari_code')
|
|
|
|
|
->pluck('moshtari_code')
|
|
|
|
|
->mapWithKeys(fn ($c) => [(string) $c => true])
|
|
|
|
|
->toArray();
|
|
|
|
|
|
2026-06-28 21:28:44 +03:30
|
|
|
$tahesabOnlyList = [];
|
2026-06-29 01:13:53 +03:30
|
|
|
$debugSample = [];
|
2026-06-28 21:28:44 +03:30
|
|
|
foreach ($tahesabCustomers as $item) {
|
|
|
|
|
if (! is_array($item)) continue;
|
2026-06-29 01:13:53 +03:30
|
|
|
if (count($debugSample) < 3) $debugSample[] = $item; // نمونهٔ خام برای تشخیص نام فیلدها
|
2026-06-29 00:41:42 +03:30
|
|
|
$code = $item['Code'] ?? null;
|
|
|
|
|
if ($code !== null && isset($panelCodes[(string) $code])) continue;
|
2026-06-29 01:13:53 +03:30
|
|
|
$phone = $this->extractTahesabPhone($item);
|
|
|
|
|
$tel = ltrim($phone, '0');
|
2026-06-28 21:28:44 +03:30
|
|
|
if ($tel && isset($panelMobiles[$tel])) continue;
|
|
|
|
|
$tahesabOnlyList[] = [
|
|
|
|
|
'code' => $item['Code'] ?? null,
|
|
|
|
|
'name' => $item['Name'] ?? '',
|
2026-06-29 01:13:53 +03:30
|
|
|
'tel' => $phone,
|
2026-06-28 21:28:44 +03:30
|
|
|
'gid' => $item['GID'] ?? null,
|
|
|
|
|
'group' => $item['GoroupName'] ?? '',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
$tahesabOnlyCount = count($tahesabOnlyList);
|
|
|
|
|
// همه رو برمیگردونیم — frontend خودش paginate میکنه
|
|
|
|
|
$tahesabOnlyResult = [
|
|
|
|
|
'data' => array_values($tahesabOnlyList),
|
|
|
|
|
'total' => $tahesabOnlyCount,
|
|
|
|
|
'current_page' => 1,
|
|
|
|
|
'last_page' => 1,
|
|
|
|
|
];
|
|
|
|
|
}
|
2026-06-28 19:13:35 +03:30
|
|
|
|
2026-06-29 01:13:53 +03:30
|
|
|
$payload = [
|
2026-06-28 21:28:44 +03:30
|
|
|
'panel_total' => $panelTotal,
|
|
|
|
|
'panel_registered' => $panelRegistered,
|
|
|
|
|
'panel_only' => $panelOnly,
|
|
|
|
|
'tahesab_total' => $tahesabTotal,
|
|
|
|
|
'tahesab_only_count' => $tahesabOnlyCount,
|
|
|
|
|
'panel_only_list' => $panelOnlyList,
|
|
|
|
|
'tahesab_only_list' => $tahesabOnlyResult,
|
2026-06-29 01:13:53 +03:30
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// برای تشخیص نام دقیق فیلدها: ?include_tahesab=1&raw=1
|
|
|
|
|
if ($request->boolean('include_tahesab') && $request->boolean('raw')) {
|
|
|
|
|
$payload['tahesab_debug_sample'] = $debugSample ?? [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response()->json($payload);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** شماره تماس را از هر کلید محتمل در رکورد مشتری تهحساب استخراج میکند */
|
|
|
|
|
private function extractTahesabPhone(array $item): string
|
|
|
|
|
{
|
|
|
|
|
$candidates = ['Mobile', 'CellPhone', 'Cell', 'Tel', 'Tell', 'Phone', 'MobileNo', 'Telephone', 'Mobile1'];
|
|
|
|
|
foreach ($candidates as $key) {
|
|
|
|
|
if (isset($item[$key]) && trim((string) $item[$key]) !== '') {
|
|
|
|
|
return trim((string) $item[$key]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// جستجوی عمومی: هر کلیدی که شامل mobile/tel/phone/cell باشد و مقدار داشته باشد
|
|
|
|
|
foreach ($item as $k => $v) {
|
|
|
|
|
if ($v === null || trim((string) $v) === '') continue;
|
|
|
|
|
$lk = strtolower((string) $k);
|
|
|
|
|
if (str_contains($lk, 'mobile') || str_contains($lk, 'tel')
|
|
|
|
|
|| str_contains($lk, 'phone') || str_contains($lk, 'cell')) {
|
|
|
|
|
return trim((string) $v);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return '';
|
2026-06-28 19:13:35 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function syncPanelToTahesab(): \Illuminate\Http\JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$customers = Customer::whereNull('deleted_at')
|
|
|
|
|
->whereNull('moshtari_code')
|
|
|
|
|
->select('id', 'name', 'mobile')
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
$success = 0;
|
|
|
|
|
$failed = 0;
|
|
|
|
|
$errors = [];
|
|
|
|
|
|
|
|
|
|
foreach ($customers as $customer) {
|
|
|
|
|
$result = $this->sync()->registerCustomer($customer);
|
|
|
|
|
if ($result['ok'] ?? false) {
|
|
|
|
|
$success++;
|
|
|
|
|
} else {
|
|
|
|
|
$failed++;
|
|
|
|
|
$errors[] = ['name' => $customer->name, 'mobile' => $customer->mobile, 'error' => $result['message'] ?? ''];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'ok' => true,
|
|
|
|
|
'success' => $success,
|
|
|
|
|
'failed' => $failed,
|
|
|
|
|
'errors' => $errors,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 21:05:04 +03:30
|
|
|
public function importFromTahesab(Request $request): \Illuminate\Http\JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'customers' => 'required|array',
|
|
|
|
|
'customers.*.code' => 'required|integer',
|
|
|
|
|
'customers.*.name' => 'required|string',
|
|
|
|
|
'customers.*.tel' => 'nullable|string',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$success = 0;
|
|
|
|
|
$failed = 0;
|
|
|
|
|
$errors = [];
|
2026-08-30 14:16:36 +03:30
|
|
|
/** @var list<Customer> $importedForWallet */
|
|
|
|
|
$importedForWallet = [];
|
2026-06-28 21:05:04 +03:30
|
|
|
|
|
|
|
|
foreach ($validated['customers'] as $item) {
|
2026-07-05 21:48:11 +03:30
|
|
|
$mobile = Customer::normalizeMobile($item['tel'] ?? '') ?? '';
|
2026-06-29 01:13:53 +03:30
|
|
|
$fullName = trim($item['name']);
|
2026-07-05 21:48:11 +03:30
|
|
|
$code = (int) $item['code'];
|
2026-06-29 01:13:53 +03:30
|
|
|
|
2026-07-22 00:31:38 +03:30
|
|
|
// بدون موبایل ردیف placeholder نساز
|
|
|
|
|
if ($mobile === '') {
|
|
|
|
|
$failed++;
|
|
|
|
|
$errors[] = [
|
|
|
|
|
'name' => $item['name'],
|
|
|
|
|
'code' => $item['code'],
|
|
|
|
|
'error' => 'شماره موبایل خالی است — وارد نشد',
|
|
|
|
|
];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-05 21:48:11 +03:30
|
|
|
$existing = Customer::whereNull('deleted_at')->where('moshtari_code', $code)->first();
|
2026-06-29 01:13:53 +03:30
|
|
|
if ($existing) {
|
|
|
|
|
$changed = false;
|
|
|
|
|
if (($existing->name === null || $existing->name === '') && $fullName !== '') {
|
|
|
|
|
$existing->fname = $fullName;
|
|
|
|
|
$existing->lname = (string) ($existing->lname ?? '');
|
2026-07-05 21:48:11 +03:30
|
|
|
$existing->name = $fullName;
|
2026-06-29 01:13:53 +03:30
|
|
|
$changed = true;
|
|
|
|
|
}
|
2026-07-05 21:48:11 +03:30
|
|
|
if ($mobile !== '' && ($existing->mobile === null || $existing->mobile === '')) {
|
|
|
|
|
if (Customer::isMobileTaken($mobile, (int) $existing->id)) {
|
|
|
|
|
$failed++;
|
|
|
|
|
$errors[] = [
|
|
|
|
|
'name' => $item['name'],
|
|
|
|
|
'code' => $item['code'],
|
|
|
|
|
'error' => 'شماره موبایل تکراری است',
|
|
|
|
|
];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-06-29 01:13:53 +03:30
|
|
|
$existing->mobile = $mobile;
|
|
|
|
|
if (empty($existing->password)) {
|
2026-07-05 21:48:11 +03:30
|
|
|
$existing->password = $mobile;
|
2026-06-29 01:13:53 +03:30
|
|
|
}
|
|
|
|
|
$changed = true;
|
|
|
|
|
}
|
2026-07-05 21:48:11 +03:30
|
|
|
if ($changed) {
|
|
|
|
|
$existing->save();
|
|
|
|
|
}
|
2026-08-30 14:16:36 +03:30
|
|
|
$importedForWallet[] = $existing;
|
2026-06-29 00:41:42 +03:30
|
|
|
$success++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-06-28 21:05:04 +03:30
|
|
|
|
2026-07-05 21:48:11 +03:30
|
|
|
if ($mobile !== '') {
|
|
|
|
|
$byMobile = Customer::findActiveByMobile($mobile);
|
|
|
|
|
if ($byMobile) {
|
|
|
|
|
if (empty($byMobile->moshtari_code)) {
|
|
|
|
|
$byMobile->moshtari_code = $code;
|
|
|
|
|
if ($fullName !== '') {
|
|
|
|
|
$byMobile->fname = $fullName;
|
|
|
|
|
$byMobile->lname = '';
|
|
|
|
|
$byMobile->name = $fullName;
|
|
|
|
|
}
|
|
|
|
|
if (empty($byMobile->password)) {
|
|
|
|
|
$byMobile->password = $mobile;
|
|
|
|
|
}
|
|
|
|
|
$byMobile->save();
|
2026-08-30 14:16:36 +03:30
|
|
|
$importedForWallet[] = $byMobile;
|
2026-07-05 21:48:11 +03:30
|
|
|
$success++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$failed++;
|
|
|
|
|
$errors[] = [
|
|
|
|
|
'name' => $item['name'],
|
|
|
|
|
'code' => $item['code'],
|
|
|
|
|
'error' => 'شماره موبایل تکراری است',
|
|
|
|
|
];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 21:05:04 +03:30
|
|
|
// کد معرف یکتا
|
|
|
|
|
$characters = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
|
|
|
do {
|
|
|
|
|
$referralId = '';
|
|
|
|
|
for ($i = 0; $i < 4; $i++) {
|
|
|
|
|
$referralId .= $characters[random_int(0, strlen($characters) - 1)];
|
|
|
|
|
}
|
|
|
|
|
} while (Customer::where('referral_id', $referralId)->exists());
|
|
|
|
|
|
|
|
|
|
try {
|
2026-06-29 01:05:47 +03:30
|
|
|
// توجه: مدل Customer یک mutator روی name دارد که name را از fname+lname میسازد
|
|
|
|
|
// و مقدار مستقیم name را نادیده میگیرد. پس باید fname را قبل از name ست کنیم.
|
|
|
|
|
$customer = new Customer();
|
|
|
|
|
$customer->fname = $fullName;
|
|
|
|
|
$customer->lname = '';
|
|
|
|
|
$customer->name = $fullName; // mutator: name = fname (+ lname)
|
|
|
|
|
$customer->mobile = $mobile;
|
|
|
|
|
$customer->active = true;
|
|
|
|
|
$customer->moshtari_code = $item['code'];
|
|
|
|
|
$customer->referral_id = $referralId;
|
|
|
|
|
$customer->type = 2;
|
|
|
|
|
// رمز عبور = شماره موبایل (فقط وقتی موبایل موجود است)
|
|
|
|
|
if ($mobile !== '') {
|
|
|
|
|
$customer->password = $mobile;
|
|
|
|
|
}
|
|
|
|
|
$customer->save();
|
2026-08-30 14:16:36 +03:30
|
|
|
$importedForWallet[] = $customer;
|
2026-06-28 21:05:04 +03:30
|
|
|
$success++;
|
2026-06-28 23:41:01 +03:30
|
|
|
} catch (\Throwable $e) {
|
2026-06-28 21:05:04 +03:30
|
|
|
$failed++;
|
2026-06-28 23:41:01 +03:30
|
|
|
$msg = $e->getMessage();
|
2026-07-05 21:48:11 +03:30
|
|
|
if ($e instanceof UnprocessableEntityHttpException) {
|
|
|
|
|
$msg = $e->getMessage();
|
|
|
|
|
} elseif ($e instanceof \Illuminate\Database\QueryException && (int) ($e->errorInfo[1] ?? 0) === 1062) {
|
|
|
|
|
$msg = 'شماره موبایل تکراری است';
|
2026-06-28 23:41:01 +03:30
|
|
|
}
|
|
|
|
|
$errors[] = ['name' => $item['name'], 'code' => $item['code'], 'error' => $msg];
|
2026-06-28 21:05:04 +03:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-30 14:16:36 +03:30
|
|
|
// مانده طلا/ریال تهحساب را روی کیف مشتریان واردشده بنشان
|
|
|
|
|
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(),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 21:05:04 +03:30
|
|
|
return response()->json([
|
|
|
|
|
'ok' => true,
|
|
|
|
|
'success' => $success,
|
|
|
|
|
'failed' => $failed,
|
|
|
|
|
'errors' => $errors,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-29 01:05:47 +03:30
|
|
|
/**
|
|
|
|
|
* حذف مشتریانِ خرابِ واردشده از تهحساب (نام خالی) برای پاکسازی import ناقص.
|
|
|
|
|
* با dry_run=1 فقط تعداد و نمونه را نشان میدهد و چیزی حذف نمیکند.
|
|
|
|
|
*/
|
|
|
|
|
public function rollbackImport(Request $request): \Illuminate\Http\JsonResponse
|
|
|
|
|
{
|
|
|
|
|
// مشتریانِ ساختهشده توسط import که name آنها خالی مانده (بهخاطر mutator)
|
|
|
|
|
$baseQuery = fn () => Customer::whereNotNull('moshtari_code')
|
|
|
|
|
->where(function ($q) {
|
|
|
|
|
$q->whereNull('name')->orWhere('name', '');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$count = $baseQuery()->count();
|
|
|
|
|
$sample = $baseQuery()
|
|
|
|
|
->select('id', 'fname', 'name', 'mobile', 'moshtari_code', 'created_at')
|
|
|
|
|
->orderByDesc('id')
|
|
|
|
|
->limit(20)
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
if ($request->boolean('dry_run')) {
|
|
|
|
|
return response()->json([
|
|
|
|
|
'dry_run' => true,
|
|
|
|
|
'count' => $count,
|
|
|
|
|
'sample' => $sample,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$deleted = $baseQuery()->forceDelete();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'ok' => true,
|
|
|
|
|
'deleted' => $deleted,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
public function retrySanad($id)
|
|
|
|
|
{
|
|
|
|
|
$tahesab = Tahesab::with('customer')->findOrFail($id);
|
|
|
|
|
$result = $this->sync()->retry($tahesab);
|
|
|
|
|
|
|
|
|
|
return response()->json($result, ($result['ok'] ?? false) ? 200 : 422);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-27 16:16:20 +03:30
|
|
|
public function pendingSummary(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$customerId = $request->filled('customer_id') ? (int) $request->input('customer_id') : null;
|
|
|
|
|
|
|
|
|
|
return response()->json($this->sync()->pendingSummary($customerId));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function pendingDocs(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$customerId = $request->filled('customer_id') ? (int) $request->input('customer_id') : null;
|
|
|
|
|
$limit = max(1, min(500, (int) $request->input('limit', 200)));
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'rows' => $this->sync()->pendingDocs($customerId, $limit),
|
|
|
|
|
'summary' => $this->sync()->pendingSummary($customerId),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function syncOne(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$type = (string) $request->input('type', '');
|
|
|
|
|
$id = (int) $request->input('id', 0);
|
|
|
|
|
if ($id <= 0 || ! in_array($type, ['row', 'withdraw', 'order'], true)) {
|
|
|
|
|
return response()->json(['ok' => false, 'message' => 'پارامتر نامعتبر'], 422);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$result = $this->sync()->syncOne($type, $id);
|
|
|
|
|
|
|
|
|
|
return response()->json($result, ($result['ok'] ?? false) ? 200 : 422);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function syncAllPending(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$limit = max(1, min(200, (int) $request->input('limit', 80)));
|
|
|
|
|
$customerId = $request->filled('customer_id') ? (int) $request->input('customer_id') : null;
|
|
|
|
|
$result = $this->sync()->syncAllPending($limit, $customerId);
|
|
|
|
|
|
|
|
|
|
return response()->json($result, ($result['ok'] ?? false) ? 200 : 422);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
public function registerCustomer($customerId)
|
|
|
|
|
{
|
|
|
|
|
$customer = Customer::findOrFail($customerId);
|
|
|
|
|
$result = $this->sync()->registerCustomer($customer);
|
|
|
|
|
|
|
|
|
|
return response()->json($result, ($result['ok'] ?? false) ? 200 : 422);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-30 14:16:36 +03:30
|
|
|
/**
|
|
|
|
|
* همگامسازی مانده یک مشتری با تهحساب.
|
|
|
|
|
* 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,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 11:06:00 +03:30
|
|
|
private function tahesabApiUrl(): string
|
|
|
|
|
{
|
|
|
|
|
$host = trim((string) $this->Config('tahesab_host'));
|
|
|
|
|
$port = trim((string) $this->Config('tahesab_port'));
|
|
|
|
|
|
|
|
|
|
if ($host !== '' && $port !== '') {
|
|
|
|
|
$host = rtrim(preg_replace('#^https?://#i', '', $host), '/');
|
|
|
|
|
|
2026-06-26 21:57:37 +03:30
|
|
|
return 'httpS://' . $host . ':' . $port . '/';
|
2026-06-14 11:06:00 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$legacy = trim((string) env('TAHESAB', ''));
|
|
|
|
|
|
|
|
|
|
return $legacy !== '' ? $legacy : '';
|
|
|
|
|
}
|
2026-01-24 14:03:11 +00:00
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
public function storeTrade($data, ?Tahesab $existing = null)
|
2026-01-24 14:03:11 +00:00
|
|
|
{
|
2026-06-28 17:05:33 +03:30
|
|
|
// اگر payload خالی است اما Tahesab موجود و order_id دارد، از Order بازسازی کن
|
|
|
|
|
if (empty($data['customer_id']) && $existing && $existing->order_id) {
|
|
|
|
|
$order = \App\Models\Order::find($existing->order_id);
|
|
|
|
|
if ($order) {
|
2026-07-28 17:44:24 +03:30
|
|
|
$productPayload = ['metal_id' => $existing->type === 'طلا' ? 1 : 2];
|
|
|
|
|
if ($order->product_id) {
|
|
|
|
|
$productPayload['id'] = (int) $order->product_id;
|
|
|
|
|
}
|
2026-06-28 17:05:33 +03:30
|
|
|
$data = array_merge($data, [
|
|
|
|
|
'customer_id' => $existing->customer_id,
|
|
|
|
|
'amount' => $existing->amount,
|
|
|
|
|
'direction' => $order->direction,
|
|
|
|
|
'price' => $order->price,
|
|
|
|
|
'total_price' => $order->total_price,
|
|
|
|
|
'pay_at' => $existing->date,
|
2026-07-28 17:44:24 +03:30
|
|
|
'product' => $productPayload,
|
|
|
|
|
'product_id' => $order->product_id ? (int) $order->product_id : null,
|
|
|
|
|
'order' => ['id' => $existing->order_id, 'product_id' => $order->product_id],
|
2026-06-28 17:05:33 +03:30
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-24 14:03:11 +00:00
|
|
|
|
2026-06-28 16:56:51 +03:30
|
|
|
if (!isset($data['customer_id']) || !isset($data['amount'])) {
|
2026-01-24 14:03:11 +00:00
|
|
|
return request()->expectsJson()
|
2026-06-28 16:56:51 +03:30
|
|
|
? response()->json(['error' => 'Data missing: customer_id or amount'], 422)
|
2026-01-24 14:03:11 +00:00
|
|
|
: false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-28 17:44:24 +03:30
|
|
|
$mapper = app(AccountingProductDocMapper::class);
|
|
|
|
|
$product = $mapper->resolveProductFromPayload($data);
|
|
|
|
|
if ($product && $mapper->shouldSkip($product)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
$customer = Customer::where('id', $data['customer_id'])->first();
|
2026-08-26 16:59:27 +03:30
|
|
|
$accountOverride = trim((string) ($data['accounting_account_code'] ?? ''));
|
|
|
|
|
$isColleagueLeg = ! empty($data['colleague_leg']) && $accountOverride !== '';
|
2026-01-24 14:03:11 +00:00
|
|
|
|
2026-08-26 16:59:27 +03:30
|
|
|
if (! $isColleagueLeg) {
|
|
|
|
|
if ($customer && ($customer->moshtari_code === null)) {
|
|
|
|
|
$this->createCustomerInTahesab($customer->id);
|
|
|
|
|
$customer = Customer::where('id', $data['customer_id'])->first();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! $customer) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$hesabCode = $isColleagueLeg
|
|
|
|
|
? (int) $accountOverride
|
|
|
|
|
: (int) ($customer->moshtari_code ?? 0);
|
|
|
|
|
if ($hesabCode <= 0) {
|
|
|
|
|
return false;
|
2026-01-24 14:03:11 +00:00
|
|
|
}
|
2026-07-28 17:44:24 +03:30
|
|
|
|
|
|
|
|
$kind = $mapper->resolveKind($product);
|
|
|
|
|
$rawAmount = (float) $data['amount'];
|
|
|
|
|
$count = $mapper->accountingQty($product, $rawAmount, $data);
|
|
|
|
|
$vazn = $mapper->accountingWeight($product, $rawAmount, $data);
|
|
|
|
|
$jens = $mapper->jensFelez($product) ?? 0;
|
|
|
|
|
$title = $mapper->productTitle($product);
|
|
|
|
|
$isBuy = (int) ($data['direction'] ?? 0) === 1;
|
|
|
|
|
$price = (float) ($data['price'] ?? 0);
|
|
|
|
|
$mablaghKol = $isBuy
|
|
|
|
|
? (int) round($vazn * $price)
|
|
|
|
|
: (int) ($data['total_price'] ?? round($vazn * $price));
|
|
|
|
|
// سند تعدادی: مظنه = فی هر عدد (نه لزوماً قیمت هر گرم)
|
|
|
|
|
$mazaneh = (int) $price;
|
|
|
|
|
if ($mapper->isCountDoc($product) && $count > 0) {
|
|
|
|
|
if (!empty($data['total_price'])) {
|
|
|
|
|
$mazaneh = (int) round(((float) $data['total_price']) / $count);
|
|
|
|
|
} elseif ($price > 0 && abs($vazn - $count) > 0.0001) {
|
|
|
|
|
// price احتمالاً هر گرم است
|
|
|
|
|
$unitW = $vazn / $count;
|
|
|
|
|
$mazaneh = (int) round($price * $unitW);
|
|
|
|
|
}
|
|
|
|
|
if ($mazaneh <= 0 && $mablaghKol > 0) {
|
|
|
|
|
$mazaneh = (int) round($mablaghKol / $count);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$date = Verta::instance(Carbon::parse($data['pay_at'] ?? now()))->format('Y-m-d');
|
2026-01-24 14:03:11 +00:00
|
|
|
[$year, $month, $day] = explode('-', $date);
|
2026-02-08 08:02:11 +00:00
|
|
|
|
2026-07-28 17:44:24 +03:30
|
|
|
$action = $isBuy ? 'خرید' : 'فروش';
|
|
|
|
|
$kindFa = $mapper->kindLabel($kind);
|
2026-08-26 16:59:27 +03:30
|
|
|
$sharh = trim((string) ($data['sharh_override'] ?? ''));
|
|
|
|
|
if ($sharh === '') {
|
|
|
|
|
$sharh = trim($title !== '' ? "{$action} {$title}" : "{$action} {$kindFa}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$orderId = is_object($data['order'] ?? null)
|
|
|
|
|
? ($data['order']->id ?? null)
|
|
|
|
|
: ($data['order']['id'] ?? null);
|
2026-01-24 14:03:11 +00:00
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
$data['handler'] = 'storeTrade';
|
2026-06-28 17:05:33 +03:30
|
|
|
if ($existing && empty($existing->data)) {
|
|
|
|
|
$existing->update(['data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)]);
|
|
|
|
|
}
|
2026-06-25 22:20:40 +03:30
|
|
|
$tahesab = $existing ?? Tahesab::create([
|
2026-07-28 17:44:24 +03:30
|
|
|
'customer_id' => $customer->id,
|
|
|
|
|
'desc' => $sharh,
|
|
|
|
|
'amount' => $mapper->isCountDoc($product) ? $count : $vazn,
|
2026-08-26 16:59:27 +03:30
|
|
|
'order_id' => $orderId,
|
2026-07-28 17:44:24 +03:30
|
|
|
'date' => $data['pay_at'] ?? Carbon::now(),
|
2026-07-29 14:58:33 +03:30
|
|
|
'type' => $mapper->syncTypeLabel($product),
|
2026-07-28 17:44:24 +03:30
|
|
|
'created_at' => Carbon::now(),
|
|
|
|
|
'data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
|
2026-01-24 14:03:11 +00:00
|
|
|
]);
|
|
|
|
|
|
2026-08-26 16:59:27 +03:30
|
|
|
if ($mapper->isCountDoc($product) && $title === '' && ! $isColleagueLeg) {
|
2026-07-28 17:44:24 +03:30
|
|
|
$this->sync()->markFailed($tahesab, null, 'نام محصول برای سند تعدادی تهحساب خالی است');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$body = $this->buildBuySaleBody(
|
|
|
|
|
$kind,
|
2026-08-26 16:59:27 +03:30
|
|
|
$hesabCode,
|
2026-07-28 17:44:24 +03:30
|
|
|
(int) $year,
|
|
|
|
|
(int) $month,
|
|
|
|
|
(int) $day,
|
|
|
|
|
$vazn,
|
|
|
|
|
$count,
|
|
|
|
|
$title,
|
|
|
|
|
$isBuy ? 1 : 0,
|
|
|
|
|
$mazaneh,
|
|
|
|
|
$mablaghKol,
|
|
|
|
|
$sharh,
|
|
|
|
|
$jens,
|
|
|
|
|
(string) Verta::instance($data['pay_at'] ?? now())
|
|
|
|
|
);
|
|
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
try {
|
2026-07-28 17:44:24 +03:30
|
|
|
$response = $this->postTahesab($body);
|
2026-06-07 14:30:26 +03:30
|
|
|
} catch (\Exception $e) {
|
2026-06-25 22:20:40 +03:30
|
|
|
$this->sync()->markFailed($tahesab, $e->getMessage());
|
2026-01-24 14:03:11 +00:00
|
|
|
return $e->getMessage();
|
|
|
|
|
}
|
2026-03-15 09:30:06 +00:00
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
if ($this->sync()->responseOk($response)) {
|
|
|
|
|
$this->sync()->markSuccess($tahesab, $response, $data);
|
2026-01-24 14:03:11 +00:00
|
|
|
return $data;
|
|
|
|
|
}
|
2026-06-25 22:20:40 +03:30
|
|
|
|
|
|
|
|
$this->sync()->markFailed($tahesab, $response, 'ثبت سند معامله در تهحساب ناموفق بود');
|
2026-01-24 14:03:11 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// directon: 0 = daryaft az moshtari
|
|
|
|
|
// directon: 1 = pardakht be moshtari
|
2026-06-25 22:20:40 +03:30
|
|
|
public function wallet($data, ?Tahesab $existing = null)
|
2026-01-24 14:03:11 +00:00
|
|
|
{
|
2026-06-28 17:33:38 +03:30
|
|
|
if (empty($data['customer_id']) && $existing) {
|
|
|
|
|
$data = array_merge($data, [
|
|
|
|
|
'customer_id' => $existing->customer_id,
|
|
|
|
|
'value' => $existing->amount,
|
|
|
|
|
'direction' => str_contains((string) $existing->desc, 'برداشت') ? 1 : 0,
|
|
|
|
|
'withdraw_id' => $existing->withdraw_id,
|
|
|
|
|
]);
|
|
|
|
|
}
|
2026-01-24 14:03:11 +00:00
|
|
|
|
|
|
|
|
if (!isset($data['customer_id']) || !isset($data['value'])) {
|
|
|
|
|
return request()->expectsJson()
|
|
|
|
|
? response()->json(['error' => 'Data missing: customer_id or value'], 422)
|
|
|
|
|
: false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// $customer = auth()->user();
|
|
|
|
|
// if (!$customer) {
|
|
|
|
|
// throw new UnprocessableEntityHttpException("Auth needed");
|
|
|
|
|
// }
|
|
|
|
|
$customer = Customer::where('id', $data['customer_id'])->first();
|
|
|
|
|
|
2026-03-14 09:57:15 +03:30
|
|
|
if ($customer && ($customer->moshtari_code === null)) {
|
2026-06-25 22:20:40 +03:30
|
|
|
$this->createCustomerInTahesab($customer->id);
|
2026-03-14 09:57:15 +03:30
|
|
|
$customer = Customer::where('id', $data['customer_id'])->first();
|
2026-01-24 14:03:11 +00:00
|
|
|
}
|
2026-06-25 22:20:40 +03:30
|
|
|
$data['handler'] = 'wallet';
|
2026-08-03 11:51:16 +03:30
|
|
|
$isDeposit = (int) ($data['direction'] ?? 0) === 0;
|
|
|
|
|
$sharh = self::accountingWalletDesc($isDeposit, $data['note'] ?? null);
|
2026-06-25 22:20:40 +03:30
|
|
|
$tahesab = $existing ?? Tahesab::create([
|
2026-01-24 14:03:11 +00:00
|
|
|
"customer_id" => $customer->id,
|
2026-08-03 11:51:16 +03:30
|
|
|
"desc" => $sharh,
|
2026-01-24 14:03:11 +00:00
|
|
|
"amount" => $data['value'],
|
2026-03-15 09:30:06 +00:00
|
|
|
"type" => "ريال",
|
2026-01-24 14:03:11 +00:00
|
|
|
"date" => Carbon::now(),
|
|
|
|
|
"created_at" => Carbon::now(),
|
2026-06-25 22:20:40 +03:30
|
|
|
"withdraw_id" => $data['withdraw_id'] ?? null,
|
|
|
|
|
"data" => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
|
2026-01-24 14:03:11 +00:00
|
|
|
]);
|
2026-03-14 11:08:30 +03:30
|
|
|
|
2026-06-28 17:33:38 +03:30
|
|
|
if ($existing && empty($existing->data)) {
|
|
|
|
|
$existing->update(['data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$dateSource = ($existing !== null && !empty($existing->date)) ? Carbon::parse($existing->date) : Carbon::now();
|
|
|
|
|
$date = Verta::instance($dateSource)->format('Y-m-d');
|
2026-03-14 11:08:30 +03:30
|
|
|
[$year, $month, $day] = explode('-', $date);
|
|
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
try {
|
|
|
|
|
$response = Http::withoutVerifying()->timeout(10)->withHeaders([
|
|
|
|
|
'DBName' => $this->Config('tahesab_db'),
|
|
|
|
|
'Content-Type' => 'application/json',
|
|
|
|
|
'Authorization' => "Bearer " . $this->Config('tahesab_token'),
|
2026-06-14 11:06:00 +03:30
|
|
|
])->post($this->tahesabApiUrl(), [
|
2026-01-24 14:03:11 +00:00
|
|
|
"DoNewSanadVKHBank" => [
|
|
|
|
|
1,//Sabte_Kol_Or_Movaghat_1_0
|
|
|
|
|
$customer->moshtari_code,//Moshtari_Code
|
|
|
|
|
-1,//Factor_Number
|
|
|
|
|
1,//Radif_Number
|
2026-03-14 12:46:09 +00:00
|
|
|
(int)$year,//Shamsi_Year
|
|
|
|
|
(int)$month,//Shamsi_Month
|
|
|
|
|
(int)$day,//Shamsi_Day
|
|
|
|
|
(int)$data['direction'],//IsDayaftAzMoshtariOrPardakhtBeMoshtari_0_1
|
2026-01-24 14:03:11 +00:00
|
|
|
(int)$data['value'],//Mablagh
|
2026-08-03 11:51:16 +03:30
|
|
|
$sharh,//Sharh
|
2026-04-06 09:04:56 +00:00
|
|
|
(string)$this->Config('tahesab_bank_account'), //Name_Bank
|
2026-01-24 14:03:11 +00:00
|
|
|
"$tahesab->id",//Code_Rahgiri
|
|
|
|
|
-1,//Factor_Code
|
|
|
|
|
-1,
|
|
|
|
|
//MultiRadif
|
|
|
|
|
]
|
2026-03-14 16:21:42 +00:00
|
|
|
])->json();
|
2026-05-29 21:28:50 +03:30
|
|
|
|
|
|
|
|
|
|
|
|
|
\Log::info('Tahesab wallet response', [
|
|
|
|
|
'data' => $data,
|
|
|
|
|
'response' => $response ?? null,
|
|
|
|
|
]);
|
2026-01-24 14:03:11 +00:00
|
|
|
} catch (\Exception $e) {
|
2026-06-25 22:20:40 +03:30
|
|
|
$this->sync()->markFailed($tahesab, $e->getMessage());
|
2026-01-24 14:03:11 +00:00
|
|
|
return $e->getMessage();
|
|
|
|
|
}
|
2026-05-18 17:15:24 +00:00
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
if ($this->sync()->responseOk($response)) {
|
|
|
|
|
$this->sync()->markSuccess($tahesab, $response, $data);
|
2026-01-24 14:03:11 +00:00
|
|
|
return $data;
|
|
|
|
|
}
|
2026-05-18 17:15:24 +00:00
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
$this->sync()->markFailed($tahesab, $response, 'ثبت سند ریالی در تهحساب ناموفق بود');
|
|
|
|
|
return false;
|
2026-05-18 17:15:24 +00:00
|
|
|
// if($data['bank'] == true) {
|
|
|
|
|
// try {
|
|
|
|
|
// $response = Http::withoutVerifying()->timeout(10)->withHeaders([
|
2026-05-24 13:56:33 +03:30
|
|
|
// 'DBName' => $this->ConfigService('tahesab_db'),
|
2026-05-18 17:15:24 +00:00
|
|
|
// 'Content-Type' => 'application/json',
|
2026-05-24 13:56:33 +03:30
|
|
|
// 'Authorization' => "Bearer " . $this->ConfigService('tahesab_token'),
|
2026-06-14 11:06:00 +03:30
|
|
|
// ])->post($this->tahesabApiUrl(), [
|
2026-05-18 17:15:24 +00:00
|
|
|
// "DoNewSanadVKHBank" => [
|
|
|
|
|
// 1,//Sabte_Kol_Or_Movaghat_1_0
|
|
|
|
|
// $customer->moshtari_code,//Moshtari_Code
|
|
|
|
|
// -1,//Factor_Number
|
|
|
|
|
// 1,//Radif_Number
|
|
|
|
|
// (int)$year,//Shamsi_Year
|
|
|
|
|
// (int)$month,//Shamsi_Month
|
|
|
|
|
// (int)$day,//Shamsi_Day
|
|
|
|
|
// (int)$data['direction'],//IsDayaftAzMoshtariOrPardakhtBeMoshtari_0_1
|
|
|
|
|
// (int)$data['value'],//Mablagh
|
|
|
|
|
// $data['direction'] == 0 ? "شارژ حساب" : "برداشت از حساب",//Sharh
|
2026-05-24 13:56:33 +03:30
|
|
|
// (string)$this->ConfigService('tahesab_bank_account'), //Name_Bank
|
2026-05-18 17:15:24 +00:00
|
|
|
// "$tahesab->id",//Code_Rahgiri
|
|
|
|
|
// -1,//Factor_Code
|
|
|
|
|
// -1,
|
|
|
|
|
// //MultiRadif
|
|
|
|
|
// ]
|
|
|
|
|
// ])->json();
|
|
|
|
|
// } catch (\Exception $e) {
|
|
|
|
|
// return $e->getMessage();
|
|
|
|
|
// }
|
|
|
|
|
// if ($response['OK']) {
|
|
|
|
|
// Tahesab::where('id', $tahesab->id)->update([
|
|
|
|
|
// "sh_factor" => $response['Sh_factor'],
|
|
|
|
|
// "tahesab_id" => $response['OK'],
|
|
|
|
|
// "tah_id" => $response['ID'],
|
|
|
|
|
// "factor_number" => $response['Factor_Number'],
|
|
|
|
|
// "data" => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
|
|
|
|
|
// "res" => json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
|
|
|
|
|
// ]);
|
|
|
|
|
//
|
|
|
|
|
// return $data;
|
|
|
|
|
// }
|
|
|
|
|
// }else{
|
|
|
|
|
// try {
|
|
|
|
|
// $response = Http::withoutVerifying()->timeout(10)->withHeaders([
|
2026-05-24 13:56:33 +03:30
|
|
|
// 'DBName' => $this->ConfigService('tahesab_db'),
|
2026-05-18 17:15:24 +00:00
|
|
|
// 'Content-Type' => 'application/json',
|
2026-05-24 13:56:33 +03:30
|
|
|
// 'Authorization' => "Bearer " . $this->ConfigService('tahesab_token'),
|
2026-06-14 11:06:00 +03:30
|
|
|
// ])->post($this->tahesabApiUrl(), [
|
2026-05-18 17:15:24 +00:00
|
|
|
// "DoNewSanadTalabBedehi" => [
|
|
|
|
|
// 1,//Sabte_Kol_Or_Movaghat_1_0,
|
|
|
|
|
// $customer->moshtari_code,//Moshtari_Code
|
|
|
|
|
// -1,//Factor_Number
|
|
|
|
|
// 1,//Radif_Number
|
|
|
|
|
// (int)$year,//Shamsi_Year
|
|
|
|
|
// (int)$month,//Shamsi_Month
|
|
|
|
|
// (int)$day,//Shamsi_Day
|
|
|
|
|
// $data['value'] < 0 ? 0 : 1,//IsBedehiOrTalab_0_1,
|
|
|
|
|
// (int)abs($data['value']),//Mablagh
|
|
|
|
|
// $data['value'] < 0 ? "بدهکار" : "بستانکار",//Sharh
|
|
|
|
|
// $data['note'],//"Baabat",
|
|
|
|
|
// 0,//Vazn,
|
|
|
|
|
// -1,//Factor_Code
|
|
|
|
|
// -1,//HavalehBe_Mcode,
|
|
|
|
|
// null,//MultiRadif
|
|
|
|
|
// 0,//Jens_Felez,
|
|
|
|
|
// "ArzName"
|
|
|
|
|
// ]
|
|
|
|
|
// ])->json();
|
|
|
|
|
// } catch (\Exception $e) {
|
|
|
|
|
// return $e->getMessage();
|
|
|
|
|
// }
|
|
|
|
|
// if ($response['OK']) {
|
|
|
|
|
// Tahesab::where('id', $tahesab->id)->update([
|
|
|
|
|
// "sh_factor" => $response['Sh_factor'],
|
|
|
|
|
// "tahesab_id" => $response['OK'],
|
|
|
|
|
// "data" => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
|
|
|
|
|
// "res" => json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
|
|
|
|
|
// ]);
|
|
|
|
|
//
|
|
|
|
|
// return $data;
|
|
|
|
|
// }
|
|
|
|
|
// }
|
2026-01-24 14:03:11 +00:00
|
|
|
}
|
2026-03-14 09:57:15 +03:30
|
|
|
|
2026-06-07 18:27:04 +03:30
|
|
|
/**
|
|
|
|
|
* پاداش معرفی — همیشه روی حساب معرف (نه کاربر تازهثبتنامشده).
|
|
|
|
|
*/
|
2026-08-03 11:51:16 +03:30
|
|
|
public function referrerGoldReward(Customer $invitedCustomer, float $gold, string $desc = 'پاداش معرفی')
|
2026-06-07 18:27:04 +03:30
|
|
|
{
|
|
|
|
|
$referrer = $invitedCustomer->referrer();
|
|
|
|
|
if (!$referrer) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->goldReward([
|
|
|
|
|
'customer_id' => $referrer->id,
|
|
|
|
|
'value' => $gold,
|
|
|
|
|
'desc' => $desc,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-07 14:30:26 +03:30
|
|
|
/**
|
|
|
|
|
* پاداش/هدیه طلایی — سند بدهی وزنی (مثل ثبت دستی «بدهی / بابت معرفی» در تهحساب).
|
|
|
|
|
*/
|
2026-06-25 22:20:40 +03:30
|
|
|
public function goldReward($data, ?Tahesab $existing = null)
|
2026-06-07 14:30:26 +03:30
|
|
|
{
|
2026-06-28 17:33:38 +03:30
|
|
|
if (empty($data['customer_id']) && $existing) {
|
|
|
|
|
$data = array_merge($data, [
|
|
|
|
|
'customer_id' => $existing->customer_id,
|
|
|
|
|
'value' => $existing->amount,
|
|
|
|
|
'desc' => $existing->desc,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-07 14:30:26 +03:30
|
|
|
if (!isset($data['customer_id'], $data['value'])) {
|
|
|
|
|
return request()->expectsJson()
|
|
|
|
|
? response()->json(['error' => 'Data missing: customer_id or value'], 422)
|
|
|
|
|
: false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$customer = Customer::where('id', $data['customer_id'])->first();
|
|
|
|
|
if (!$customer) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($customer->moshtari_code === null) {
|
2026-06-25 22:20:40 +03:30
|
|
|
$this->createCustomerInTahesab($customer->id);
|
2026-06-07 14:30:26 +03:30
|
|
|
$customer = Customer::where('id', $data['customer_id'])->first();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$amount = abs((float) $data['value']);
|
|
|
|
|
$desc = !empty($data['desc']) ? (string) $data['desc'] : 'پاداش';
|
|
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
$data['handler'] = 'goldReward';
|
|
|
|
|
$tahesab = $existing ?? Tahesab::create([
|
2026-06-07 14:30:26 +03:30
|
|
|
'customer_id' => $customer->id,
|
|
|
|
|
'desc' => $desc,
|
|
|
|
|
'amount' => $amount,
|
|
|
|
|
'date' => Carbon::now(),
|
|
|
|
|
'type' => 'طلا',
|
|
|
|
|
'created_at' => Carbon::now(),
|
2026-06-25 22:20:40 +03:30
|
|
|
'data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
|
2026-06-07 14:30:26 +03:30
|
|
|
]);
|
|
|
|
|
|
2026-06-28 17:33:38 +03:30
|
|
|
if ($existing && empty($existing->data)) {
|
|
|
|
|
$existing->update(['data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$dateSource = ($existing !== null && !empty($existing->date)) ? Carbon::parse($existing->date) : Carbon::now();
|
|
|
|
|
$date = Verta::instance($dateSource)->format('Y-m-d');
|
|
|
|
|
[$year, $month, $day] = explode('-', $date);
|
|
|
|
|
|
2026-06-07 14:30:26 +03:30
|
|
|
try {
|
|
|
|
|
$response = Http::withoutVerifying()->timeout(10)->withHeaders([
|
|
|
|
|
'DBName' => $this->Config('tahesab_db'),
|
|
|
|
|
'Content-Type' => 'application/json',
|
|
|
|
|
'Authorization' => 'Bearer ' . $this->Config('tahesab_token'),
|
2026-06-14 11:06:00 +03:30
|
|
|
])->post($this->tahesabApiUrl(), [
|
2026-06-07 14:30:26 +03:30
|
|
|
'DoNewSanadTalabBedehi' => [
|
|
|
|
|
1, // Sabte_Kol
|
|
|
|
|
(int) $customer->moshtari_code,
|
|
|
|
|
-1, // Factor_Number
|
|
|
|
|
1, // Radif_Number
|
|
|
|
|
(int) $year,
|
|
|
|
|
(int) $month,
|
|
|
|
|
(int) $day,
|
|
|
|
|
0, // IsBedehiOrTalab — بدهی (فروشگاه به مشتری طلا بدهکار است)
|
|
|
|
|
0, // MablaghKol — بدون مبلغ ریالی
|
|
|
|
|
$desc,
|
|
|
|
|
$desc, // Baabat
|
|
|
|
|
(float) $amount,
|
|
|
|
|
-1, // Factor_Code
|
|
|
|
|
-1, // HavalehBe_Mcode
|
|
|
|
|
-1, // MultiRadif
|
|
|
|
|
0, // Jens_Felez — طلا
|
|
|
|
|
'IRR',
|
|
|
|
|
],
|
|
|
|
|
])->json();
|
|
|
|
|
} catch (\Exception $e) {
|
2026-06-25 22:20:40 +03:30
|
|
|
$this->sync()->markFailed($tahesab, $e->getMessage());
|
2026-06-07 14:30:26 +03:30
|
|
|
return $e->getMessage();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
if ($this->sync()->responseOk($response)) {
|
|
|
|
|
$this->sync()->markSuccess($tahesab, $response, $data);
|
2026-06-07 14:30:26 +03:30
|
|
|
return $data;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
$this->sync()->markFailed($tahesab, $response, 'ثبت پاداش طلایی در تهحساب ناموفق بود');
|
2026-06-07 14:30:26 +03:30
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-28 17:44:24 +03:30
|
|
|
// direction: 1 = واریز طلا (ورود)، 0 = برداشت طلا (خروج) — VKH نه BuySale
|
2026-06-25 22:20:40 +03:30
|
|
|
public function walletGold($data, ?Tahesab $existing = null)
|
2026-03-14 11:08:30 +03:30
|
|
|
{
|
2026-06-28 17:33:38 +03:30
|
|
|
if (empty($data['customer_id']) && $existing) {
|
|
|
|
|
$data = array_merge($data, [
|
|
|
|
|
'customer_id' => $existing->customer_id,
|
|
|
|
|
'value' => $existing->amount,
|
|
|
|
|
'direction' => str_contains((string) $existing->desc, 'برداشت') ? 0 : 1,
|
|
|
|
|
'withdraw_id' => $existing->withdraw_id,
|
|
|
|
|
]);
|
2026-07-28 17:44:24 +03:30
|
|
|
$payload = json_decode((string) ($existing->data ?? ''), true);
|
|
|
|
|
if (is_array($payload)) {
|
|
|
|
|
if (!empty($payload['product_id'])) {
|
|
|
|
|
$data['product_id'] = (int) $payload['product_id'];
|
|
|
|
|
}
|
|
|
|
|
if (!empty($payload['product']) && is_array($payload['product'])) {
|
|
|
|
|
$data['product'] = $payload['product'];
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-28 17:33:38 +03:30
|
|
|
}
|
2026-03-14 11:08:30 +03:30
|
|
|
|
2026-06-28 17:33:38 +03:30
|
|
|
if (!isset($data['customer_id'], $data['value'])) {
|
2026-03-14 11:08:30 +03:30
|
|
|
return request()->expectsJson()
|
|
|
|
|
? response()->json(['error' => 'Data missing: customer_id or value'], 422)
|
|
|
|
|
: false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-28 17:44:24 +03:30
|
|
|
$mapper = app(AccountingProductDocMapper::class);
|
|
|
|
|
$product = $mapper->resolveProductFromPayload($data);
|
|
|
|
|
if ($product && $mapper->shouldSkip($product)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-14 11:08:30 +03:30
|
|
|
$customer = Customer::where('id', $data['customer_id'])->first();
|
|
|
|
|
|
|
|
|
|
if ($customer && ($customer->moshtari_code === null)) {
|
2026-06-25 22:20:40 +03:30
|
|
|
$this->createCustomerInTahesab($customer->id);
|
2026-03-14 11:08:30 +03:30
|
|
|
$customer = Customer::where('id', $data['customer_id'])->first();
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-28 17:44:24 +03:30
|
|
|
$kind = $mapper->resolveKind($product);
|
|
|
|
|
$rawValue = (float) $data['value'];
|
|
|
|
|
$count = $mapper->accountingQty($product, $rawValue, $data);
|
|
|
|
|
$vazn = $mapper->accountingWeight($product, $rawValue, $data);
|
|
|
|
|
$jens = $mapper->jensFelez($product) ?? 0;
|
|
|
|
|
$title = $mapper->productTitle($product);
|
|
|
|
|
$isDeposit = (int) ($data['direction'] ?? 1) === 1;
|
|
|
|
|
$isVorood = $isDeposit ? 1 : 0;
|
2026-03-14 11:08:30 +03:30
|
|
|
|
2026-08-03 11:51:16 +03:30
|
|
|
$sharh = self::accountingWalletDesc(
|
|
|
|
|
$isDeposit,
|
|
|
|
|
!empty($data['note']) ? (string) $data['note'] : (!empty($data['desc']) ? (string) $data['desc'] : null)
|
|
|
|
|
);
|
2026-07-28 17:44:24 +03:30
|
|
|
$data['note'] = $sharh;
|
2026-06-25 22:20:40 +03:30
|
|
|
$data['handler'] = 'walletGold';
|
2026-07-28 17:44:24 +03:30
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
$tahesab = $existing ?? Tahesab::create([
|
2026-07-28 17:44:24 +03:30
|
|
|
'customer_id' => $customer->id,
|
|
|
|
|
'desc' => $sharh,
|
|
|
|
|
'amount' => $mapper->isCountDoc($product) ? $count : $vazn,
|
|
|
|
|
'date' => Carbon::now(),
|
2026-07-29 14:58:33 +03:30
|
|
|
'type' => $mapper->syncTypeLabel($product),
|
2026-07-28 17:44:24 +03:30
|
|
|
'created_at' => Carbon::now(),
|
|
|
|
|
'withdraw_id' => $data['withdraw_id'] ?? null,
|
|
|
|
|
'data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
|
2026-03-14 11:08:30 +03:30
|
|
|
]);
|
|
|
|
|
|
2026-06-28 17:33:38 +03:30
|
|
|
if ($existing && empty($existing->data)) {
|
|
|
|
|
$existing->update(['data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-28 17:44:24 +03:30
|
|
|
if ($mapper->isCountDoc($product) && $title === '') {
|
|
|
|
|
$this->sync()->markFailed($tahesab, null, 'نام محصول برای سند تعدادی تهحساب خالی است');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 17:33:38 +03:30
|
|
|
$dateSource = ($existing !== null && !empty($existing->date)) ? Carbon::parse($existing->date) : Carbon::now();
|
|
|
|
|
$date = Verta::instance($dateSource)->format('Y-m-d');
|
|
|
|
|
[$year, $month, $day] = explode('-', $date);
|
|
|
|
|
|
2026-07-28 17:44:24 +03:30
|
|
|
$body = $this->buildVkhBody(
|
|
|
|
|
$kind,
|
|
|
|
|
(int) $customer->moshtari_code,
|
|
|
|
|
(int) $year,
|
|
|
|
|
(int) $month,
|
|
|
|
|
(int) $day,
|
|
|
|
|
$vazn,
|
|
|
|
|
$count,
|
|
|
|
|
$title,
|
|
|
|
|
$isVorood,
|
|
|
|
|
$sharh,
|
|
|
|
|
$jens
|
|
|
|
|
);
|
2026-06-07 14:30:26 +03:30
|
|
|
|
2026-03-14 11:08:30 +03:30
|
|
|
try {
|
2026-07-28 17:44:24 +03:30
|
|
|
$response = $this->postTahesab($body);
|
2026-06-07 14:30:26 +03:30
|
|
|
} catch (\Exception $e) {
|
2026-06-25 22:20:40 +03:30
|
|
|
$this->sync()->markFailed($tahesab, $e->getMessage());
|
2026-03-14 11:08:30 +03:30
|
|
|
return $e->getMessage();
|
|
|
|
|
}
|
2026-03-14 16:21:42 +00:00
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
if ($this->sync()->responseOk($response)) {
|
|
|
|
|
$this->sync()->markSuccess($tahesab, $response, $data);
|
2026-03-14 11:08:30 +03:30
|
|
|
return $data;
|
|
|
|
|
}
|
2026-06-25 22:20:40 +03:30
|
|
|
|
|
|
|
|
$this->sync()->markFailed($tahesab, $response, 'ثبت سند طلایی کیف پول در تهحساب ناموفق بود');
|
2026-03-14 11:08:30 +03:30
|
|
|
return false;
|
2026-07-28 17:44:24 +03:30
|
|
|
}
|
2026-03-14 11:08:30 +03:30
|
|
|
|
2026-07-28 17:44:24 +03:30
|
|
|
private function postTahesab(array $body): array
|
|
|
|
|
{
|
|
|
|
|
$response = Http::withoutVerifying()->timeout(10)->withHeaders([
|
|
|
|
|
'DBName' => $this->Config('tahesab_db'),
|
|
|
|
|
'Content-Type' => 'application/json',
|
|
|
|
|
'Authorization' => 'Bearer ' . $this->Config('tahesab_token'),
|
|
|
|
|
])->post($this->tahesabApiUrl(), $body)->json();
|
|
|
|
|
|
|
|
|
|
return is_array($response) ? $response : [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* سند خرید/فروش — معامله.
|
|
|
|
|
*/
|
|
|
|
|
private function buildBuySaleBody(
|
|
|
|
|
string $kind,
|
|
|
|
|
int $moshtariCode,
|
|
|
|
|
int $year,
|
|
|
|
|
int $month,
|
|
|
|
|
int $day,
|
|
|
|
|
float $vazn,
|
|
|
|
|
float $count,
|
|
|
|
|
string $name,
|
|
|
|
|
int $buyOrSale,
|
|
|
|
|
int $mazaneh,
|
|
|
|
|
int $mablaghKol,
|
|
|
|
|
string $sharh,
|
|
|
|
|
int $jens,
|
|
|
|
|
string $zamanTasvie
|
|
|
|
|
): array {
|
|
|
|
|
$ayar = $jens === 0 ? 750 : '';
|
|
|
|
|
|
|
|
|
|
if ($kind === AccountingProductDocMapper::KIND_SEKEH) {
|
|
|
|
|
return [
|
|
|
|
|
'DoNewSanadBuySaleSEKEH' => [
|
|
|
|
|
1, $moshtariCode, -1, 1, $year, $month, $day,
|
|
|
|
|
(float) $vazn, $ayar, (float) $count, $name,
|
|
|
|
|
$buyOrSale, $mazaneh, $mablaghKol, $sharh,
|
|
|
|
|
-1, -1, -1, $jens, '',
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($kind === AccountingProductDocMapper::KIND_SHEMSH) {
|
|
|
|
|
return [
|
|
|
|
|
'DoNewSanadBuySaleShemsh' => [
|
|
|
|
|
1, $moshtariCode, -1, 1, $year, $month, $day,
|
|
|
|
|
(float) $vazn, $ayar, (float) $count, $name,
|
|
|
|
|
$buyOrSale, $mazaneh, $mablaghKol, $sharh,
|
|
|
|
|
-1, -1, -1, $jens, '',
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($kind === AccountingProductDocMapper::KIND_KAR) {
|
|
|
|
|
return [
|
|
|
|
|
'DoNewSanadBuySaleKar' => [
|
|
|
|
|
1, $moshtariCode, -1, 1, $year, $month, $day,
|
|
|
|
|
(float) $vazn, $ayar ?: 750, (float) $count, $name,
|
|
|
|
|
$buyOrSale, $mazaneh, 1, $mablaghKol,
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, '', 0, $sharh,
|
|
|
|
|
-1, -1, -1, $jens, '',
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// وزن / آبشده — همان payload قبلی با Jens_Felez و Sharh دقیقتر
|
|
|
|
|
return [
|
|
|
|
|
'DoNewSanadBuySaleGOLD' => [
|
|
|
|
|
1, $moshtariCode, -1, 1, $year, $month, $day,
|
|
|
|
|
(float) $vazn, $ayar, 0, '',
|
|
|
|
|
$buyOrSale, $mazaneh, 1, 1, $mablaghKol, $sharh,
|
|
|
|
|
-1, -1, -1, $jens, $zamanTasvie,
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* سند ورود/خروج — شارژ، دشارژ، برداشت فیزیکی.
|
|
|
|
|
*/
|
|
|
|
|
private function buildVkhBody(
|
|
|
|
|
string $kind,
|
|
|
|
|
int $moshtariCode,
|
|
|
|
|
int $year,
|
|
|
|
|
int $month,
|
|
|
|
|
int $day,
|
|
|
|
|
float $vazn,
|
|
|
|
|
float $count,
|
|
|
|
|
string $name,
|
|
|
|
|
int $isVorood,
|
|
|
|
|
string $sharh,
|
|
|
|
|
int $jens
|
|
|
|
|
): array {
|
|
|
|
|
$ayar = $jens === 0 ? 750 : '';
|
|
|
|
|
|
|
|
|
|
if ($kind === AccountingProductDocMapper::KIND_SEKEH) {
|
|
|
|
|
return [
|
|
|
|
|
'DoNewSanadVKHSEKEH' => [
|
|
|
|
|
1, $moshtariCode, -1, 1, $year, $month, $day,
|
|
|
|
|
(float) $vazn, $ayar, (float) $count, $name,
|
|
|
|
|
$isVorood, $sharh, -1, -1, -1, $jens,
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($kind === AccountingProductDocMapper::KIND_SHEMSH) {
|
|
|
|
|
return [
|
|
|
|
|
'DoNewSanadVKHShemsh' => [
|
|
|
|
|
1, $moshtariCode, -1, 1, $year, $month, $day,
|
|
|
|
|
(float) $vazn, $ayar, (float) $count, $name,
|
|
|
|
|
$isVorood, $sharh, -1, -1, -1, $jens,
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($kind === AccountingProductDocMapper::KIND_KAR) {
|
|
|
|
|
return [
|
|
|
|
|
'DoNewSanadVKHKar' => [
|
|
|
|
|
1, $moshtariCode, -1, 1, $year, $month, $day,
|
|
|
|
|
(float) $vazn, $ayar ?: 750, (float) $count, $name,
|
|
|
|
|
$isVorood, 0, 0, 0, 0, 0, 0, '', 0, 0, $sharh,
|
|
|
|
|
-1, -1, -1, $jens,
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'DoNewSanadVKHGOLD' => [
|
|
|
|
|
1, $moshtariCode, -1, 1, $year, $month, $day,
|
|
|
|
|
(float) $vazn, $ayar ?: 750, '', '',
|
|
|
|
|
$isVorood, 1, $sharh, -1, -1, -1, $jens, 0,
|
|
|
|
|
],
|
|
|
|
|
];
|
2026-03-14 11:08:30 +03:30
|
|
|
}
|
|
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
public function createCustomerInTahesab($customer_id)
|
2026-03-14 09:57:15 +03:30
|
|
|
{
|
|
|
|
|
$customer = Customer::findOrFail($customer_id);
|
2026-06-25 22:20:40 +03:30
|
|
|
$response = null;
|
2026-03-14 09:57:15 +03:30
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
if ($customer->moshtari_code == null) {
|
2026-06-27 22:10:29 +03:30
|
|
|
$lock = Cache::lock('tahesab:register-customer:'.$customer->id, 30);
|
2026-06-25 22:20:40 +03:30
|
|
|
|
2026-06-27 22:10:29 +03:30
|
|
|
if (! $lock->get()) {
|
|
|
|
|
$customer->refresh();
|
|
|
|
|
|
|
|
|
|
if ($customer->moshtari_code != null) {
|
|
|
|
|
return $customer->moshtari_code;
|
2026-03-14 09:57:15 +03:30
|
|
|
}
|
2026-06-27 22:10:29 +03:30
|
|
|
|
|
|
|
|
return 'ثبت مشتری در تهحساب در حال انجام است';
|
2026-03-14 09:57:15 +03:30
|
|
|
}
|
|
|
|
|
|
2026-06-27 22:10:29 +03:30
|
|
|
try {
|
|
|
|
|
$customer->refresh();
|
|
|
|
|
|
|
|
|
|
if ($customer->moshtari_code != null) {
|
|
|
|
|
return $customer->moshtari_code;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$response = Http::withoutVerifying()->timeout(10)->withHeaders([
|
|
|
|
|
'DBName' => $this->Config('tahesab_db'),
|
|
|
|
|
'Content-Type' => 'application/json',
|
|
|
|
|
'Authorization' => "Bearer " . $this->Config('tahesab_token'),
|
|
|
|
|
])->post($this->tahesabApiUrl(), [
|
|
|
|
|
"DoNewMoshtari" => [
|
|
|
|
|
(string)$customer->name,
|
|
|
|
|
$this->Config('name_fa'),
|
|
|
|
|
(string)$customer->mobile,
|
|
|
|
|
"",
|
|
|
|
|
"",
|
|
|
|
|
"",
|
|
|
|
|
"",
|
|
|
|
|
"",
|
|
|
|
|
-1,
|
|
|
|
|
0
|
|
|
|
|
]
|
|
|
|
|
])->json();
|
|
|
|
|
|
|
|
|
|
if ($this->sync()->responseOk($response)) {
|
|
|
|
|
$customer->moshtari_code = $response['OK'];
|
|
|
|
|
$customer->save();
|
|
|
|
|
return $response['OK'];
|
|
|
|
|
}
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
return $e->getMessage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 'tahesab api error!!!';
|
|
|
|
|
} finally {
|
|
|
|
|
$lock->release();
|
|
|
|
|
}
|
2026-03-14 09:57:15 +03:30
|
|
|
}
|
|
|
|
|
|
2026-06-25 22:20:40 +03:30
|
|
|
$birth_date = '';
|
|
|
|
|
if ($customer->birth_date !== null) {
|
|
|
|
|
$birth_date = Verta::instance(Carbon::parse($customer->birth_date))->format('Y-m-d');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$response = Http::withoutVerifying()->timeout(10)->withHeaders([
|
|
|
|
|
'DBName' => $this->Config('tahesab_db'),
|
|
|
|
|
'Content-Type' => 'application/json',
|
|
|
|
|
'Authorization' => "Bearer " . $this->Config('tahesab_token'),
|
|
|
|
|
])->post($this->tahesabApiUrl(), [
|
|
|
|
|
"DoEditMoshtari" => [
|
|
|
|
|
$customer->name,
|
|
|
|
|
$this->Config('name_fa'),
|
|
|
|
|
$customer->mobile,
|
|
|
|
|
"",
|
|
|
|
|
$customer->n_code ?? "",
|
|
|
|
|
$birth_date,
|
|
|
|
|
"",
|
|
|
|
|
"",
|
|
|
|
|
(int)$customer->moshtari_code,
|
|
|
|
|
0
|
|
|
|
|
]
|
|
|
|
|
])->json();
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
return $e->getMessage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! $this->sync()->responseOk($response)) {
|
|
|
|
|
return 'tahesab api error!!!';
|
|
|
|
|
}
|
2026-03-14 09:57:15 +03:30
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-16 10:04:14 +00:00
|
|
|
public function healthCheck()
|
|
|
|
|
{
|
|
|
|
|
$tahesab = Http::withoutVerifying()->withHeaders([
|
|
|
|
|
'Content-Type' => 'application/json',
|
2026-04-06 08:27:10 +00:00
|
|
|
'DBName' => $this->Config('tahesab_db'),
|
|
|
|
|
'Authorization' => "Bearer " . $this->Config('tahesab_token'),
|
2026-06-14 11:06:00 +03:30
|
|
|
])->post($this->tahesabApiUrl(), [
|
2026-04-06 08:27:10 +00:00
|
|
|
"CheckHealth" => []
|
|
|
|
|
])->json();
|
|
|
|
|
|
|
|
|
|
return $tahesab;
|
|
|
|
|
// if($tahesab['Api_Status'] === "OK" && $tahesab['DB_Status'] === "OK"){
|
|
|
|
|
// return true;
|
|
|
|
|
// }else{
|
|
|
|
|
// return false;
|
|
|
|
|
// }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function sanadList()
|
|
|
|
|
{
|
|
|
|
|
$tahesab = Http::withoutVerifying()->withHeaders([
|
|
|
|
|
'Content-Type' => 'application/json',
|
|
|
|
|
'DBName' => $this->Config('tahesab_db'),
|
|
|
|
|
'Authorization' => "Bearer " . $this->Config('tahesab_token'),
|
2026-06-14 11:06:00 +03:30
|
|
|
])->post($this->tahesabApiUrl(), [
|
2026-04-06 08:27:10 +00:00
|
|
|
"DoListAsnad" => [
|
|
|
|
|
10, /*Count_Last,*/
|
|
|
|
|
"", /*Moshtari_Code,*/
|
|
|
|
|
"", /*"AzTarikhShamsi"*/
|
|
|
|
|
(string)Verta::instance(Carbon::now()->format('Y-m-d')), /*"TaTarikhShamsi"*/
|
|
|
|
|
"", /*"FilterNoSanad"*/
|
|
|
|
|
0, /*Jens_Felez*/
|
|
|
|
|
]
|
|
|
|
|
])->json();
|
|
|
|
|
return $tahesab;
|
2026-03-16 10:04:14 +00:00
|
|
|
|
|
|
|
|
}
|
2026-01-24 14:03:11 +00:00
|
|
|
}
|