fix some bugs from accounting 4

This commit is contained in:
2026-07-28 18:40:06 +03:30
parent d81cea3d4f
commit 5122d7e2b7
2 changed files with 271 additions and 97 deletions
+257 -97
View File
@@ -274,20 +274,20 @@ class ZarinController extends Controller
* پیدا کردن کار/کالای محصول در زرین.
* اول infokarbarcodi (اگر روی آن سرور فعال باشد)؛ وگرنه infosakhte (لیست کامل).
*/
private function findZarinKarForProduct(Product $product, $goroheKar = null)
private function findZarinKarForProduct(Product $product, $goroheKar = null, bool $exactOnly = false)
{
$goroheKar = $goroheKar ?: $this->zarinDefaultGoroheKar();
$productName = $this->normalizeZarinName($product->title);
$fromBarcode = $this->findZarinKarInInfokarbarcodi($productName, $goroheKar);
$fromBarcode = $this->findZarinKarInInfokarbarcodi($productName, $goroheKar, $exactOnly);
if ($fromBarcode) {
return $fromBarcode;
}
return $this->findZarinKarInInfosakhte($productName, $goroheKar);
return $this->findZarinKarInInfosakhte($productName, $goroheKar, $exactOnly);
}
private function matchZarinKarRow(string $productName, array $row): ?array
private function matchZarinKarRow(string $productName, array $row, bool $exactOnly = false): ?array
{
$name = $row['nameKar']
?? $row['nam_kar_sakhte']
@@ -300,26 +300,41 @@ class ZarinController extends Controller
$rowName = $this->normalizeZarinName($name);
$idKar = $this->extractUsableZarinKarId($row);
if (!$idKar) {
if (!$idKar || $rowName === '') {
return null;
}
if ($rowName === $productName) {
return [
'idKar' => $idKar,
'row' => $row,
'match' => 'exact',
];
}
if ($exactOnly) {
return null;
}
if (
$rowName === $productName ||
$this->containsText($rowName, $productName) ||
$this->containsText($productName, $rowName)
) {
return [
'idKar' => $idKar,
'row' => $row,
'match' => 'fuzzy',
];
}
return null;
}
private function findZarinKarInInfokarbarcodi(string $productName, $goroheKar): ?array
private function findZarinKarInInfokarbarcodi(string $productName, $goroheKar, bool $exactOnly = false): ?array
{
$exact = null;
$fuzzy = null;
for ($page = 1; $page <= 10; $page++) {
$response = $this->zarinRequest([
'kind' => 'infokarbarcodi',
@@ -343,10 +358,15 @@ class ZarinController extends Controller
if (!is_array($row)) {
continue;
}
$hit = $this->matchZarinKarRow($productName, $row);
if ($hit) {
return $hit;
$hit = $this->matchZarinKarRow($productName, $row, $exactOnly);
if (!$hit) {
continue;
}
if (($hit['match'] ?? '') === 'exact') {
$exact = $hit;
break 2;
}
$fuzzy ??= $hit;
}
if (count($rows) < 250) {
@@ -354,11 +374,11 @@ class ZarinController extends Controller
}
}
return null;
return $exact ?? ($exactOnly ? null : $fuzzy);
}
/** لیست کارهای ساخته — فیلد nam_kar_sakhte / id (تست‌شده روی API زرین) */
private function findZarinKarInInfosakhte(string $productName, $goroheKar): ?array
private function findZarinKarInInfosakhte(string $productName, $goroheKar, bool $exactOnly = false): ?array
{
$response = $this->zarinRequest([
'kind' => 'infosakhte',
@@ -375,29 +395,42 @@ class ZarinController extends Controller
$noeKar = (string) $this->zarinDefaultNoeKar();
$gorohe = (string) $goroheKar;
$preferred = null;
$fallback = null;
$exactPreferred = null;
$exactFallback = null;
$fuzzyPreferred = null;
$fuzzyFallback = null;
foreach ($rows as $row) {
if (!is_array($row)) {
continue;
}
$hit = $this->matchZarinKarRow($productName, $row);
$hit = $this->matchZarinKarRow($productName, $row, $exactOnly);
if (!$hit) {
continue;
}
$idGp = (string) ($row['idGp'] ?? $row['idgp'] ?? '');
if ($idGp !== '' && ($idGp === $gorohe || $idGp === $noeKar)) {
$preferred = $hit;
$idGp = (string) ($row['idGpFelez'] ?? $row['idgpfelez'] ?? $row['gorooheKar'] ?? '');
$inGroup = $idGp !== '' && ($idGp === $gorohe || $idGp === $noeKar);
$isExact = ($hit['match'] ?? '') === 'exact';
if ($isExact && $inGroup) {
$exactPreferred = $hit;
break;
}
if ($fallback === null) {
$fallback = $hit;
if ($isExact) {
$exactFallback ??= $hit;
} elseif ($inGroup) {
$fuzzyPreferred ??= $hit;
} else {
$fuzzyFallback ??= $hit;
}
}
return $preferred ?? $fallback;
if ($exactOnly) {
return $exactPreferred ?? $exactFallback;
}
return $exactPreferred ?? $exactFallback ?? $fuzzyPreferred ?? $fuzzyFallback;
}
private function getZarinKarById($idKar)
@@ -423,14 +456,11 @@ class ZarinController extends Controller
continue;
}
$rowId = $row['pkFelez']
?? $row['pkfelez']
?? $row['PKFelez']
?? $row['id']
?? $row['idKar']
// infosakhte: کد کار = id (نه pkFelez)
$rowId = $row['idKar']
?? $row['idkar']
?? $row['zarrinId']
?? $row['zarinId']
?? $row['IDKar']
?? $row['id']
?? null;
if ((string) $rowId === (string) $idKar) {
return $row;
@@ -440,19 +470,24 @@ class ZarinController extends Controller
return null;
}
/**
* کد کار قابل استفاده برای sabtSanadTedadi.
* infosakhte → فیلد id؛ addFelezsakhteModel → idKar.
* pkFelez هرگز idKar نیست.
*/
private function extractUsableZarinKarId(array $row)
{
foreach ([
'zarrinId',
'zarinId',
'idKar',
'idkar',
'IDKar',
'id',
'zarrinId',
'zarinId',
'code',
'Code',
] as $key) {
if (isset($row[$key]) && is_numeric($row[$key])) {
if (isset($row[$key]) && is_numeric($row[$key]) && (int) $row[$key] > 0) {
return (int) $row[$key];
}
}
@@ -460,6 +495,36 @@ class ZarinController extends Controller
return null;
}
/** پاسخ addFelezsakhteModel: {"state":true,"msg":"...","idKar":3130} */
private function extractIdKarFromCreateResponse($body): ?int
{
if (!is_array($body)) {
return null;
}
foreach (['idKar', 'idkar', 'IDKar'] as $key) {
if (isset($body[$key]) && is_numeric($body[$key]) && (int) $body[$key] > 0) {
return (int) $body[$key];
}
}
$data = $body['data'] ?? null;
if (is_array($data)) {
if (isset($data[0]) && is_array($data[0])) {
$fromRow = $this->extractUsableZarinKarId($data[0]);
if ($fromRow) {
return $fromRow;
}
}
$fromData = $this->extractUsableZarinKarId($data);
if ($fromData) {
return $fromData;
}
}
return null;
}
private function createZarinKarForProduct(Product $product)
{
$params = [
@@ -493,11 +558,23 @@ class ZarinController extends Controller
}
$body = $response->json();
$createdIdKar = $this->extractIdKarFromCreateResponse(is_array($body) ? $body : null);
$createdPkFelez = $body['data'][0]['pkFelez']
?? $body['data']['pkFelez']
?? $body['pkFelez']
?? null;
// اولویت با idKar برگشتی از ساخت — جستجوی فازی نام می‌تواند کالای اشتباه بچسباند
if ($createdIdKar) {
$product->finance_p_name = (string) $createdIdKar;
$product->save();
return [
'ok' => true,
'source' => 'created_idKar',
'idKar' => $createdIdKar,
'product' => $product->fresh(),
'zarin_row' => $this->getZarinKarById($createdIdKar),
'zarin_response' => $body,
'sent_params_success' => $params,
];
}
$foundAfterCreate = $this->findZarinKarForProduct($product, $this->zarinDefaultGoroheKar());
@@ -512,16 +589,14 @@ class ZarinController extends Controller
'product' => $product->fresh(),
'zarin_row' => $foundAfterCreate['row'],
'zarin_response' => $body,
'created_pkFelez' => $createdPkFelez,
'sent_params_success' => $params,
];
}
return [
'ok' => false,
'message' => 'زرین کالا را ساخت، اما idKar قابل استفاده از infosakhte/infokarbarcodi پیدا نشد.',
'message' => 'زرین کالا را ساخت، اما idKar قابل استفاده برنگشت.',
'status_code' => 422,
'created_pkFelez' => $createdPkFelez,
'zarin_response' => $body,
'sent_params' => $params,
];
@@ -530,48 +605,93 @@ class ZarinController extends Controller
private function ensureZarinKarForProduct(Product $product, $forceCreate = false)
{
$currentIdKar = $product->finance_p_name;
$productName = $this->normalizeZarinName($product->title);
if (!$forceCreate && !empty($currentIdKar) && is_numeric($currentIdKar)) {
$existingRow = $this->getZarinKarById($currentIdKar);
if ($existingRow) {
$usableIdKar = $this->extractUsableZarinKarId($existingRow) ?: (int) $currentIdKar;
$rowName = $this->normalizeZarinName(
$existingRow['nameKar']
?? $existingRow['nam_kar_sakhte']
?? $existingRow['nam']
?? $existingRow['name']
?? $existingRow['namKar']
?? $existingRow['title']
?? ''
);
// اگر idKar موجود مال کالای دیگری است (مثلاً شمش برای گوی) قبول نکن
if ($rowName !== '' && $productName !== '' && $rowName !== $productName) {
$this->clearProductZarinKar($product);
$product->refresh();
} else {
$usableIdKar = $this->extractUsableZarinKarId($existingRow) ?: (int) $currentIdKar;
if ((string) $product->finance_p_name !== (string) $usableIdKar) {
$product->finance_p_name = (string) $usableIdKar;
$product->save();
if ((string) $product->finance_p_name !== (string) $usableIdKar) {
$product->finance_p_name = (string) $usableIdKar;
$product->save();
}
return [
'ok' => true,
'source' => 'existing_verified',
'idKar' => (int) $usableIdKar,
'product' => $product->fresh(),
'zarin_row' => $existingRow,
];
}
return [
'ok' => true,
'source' => 'existing_verified',
'idKar' => (int) $usableIdKar,
'product' => $product->fresh(),
'zarin_row' => $existingRow,
];
} else {
// finance_p_name کهنه / اشتباه — پاک کن تا دوباره پیدا یا ساخته شود
$this->clearProductZarinKar($product);
$product->refresh();
}
}
if (!$forceCreate) {
$found = $this->findZarinKarForProduct($product, $this->zarinDefaultGoroheKar());
// بعد از خطای «کد کار اشتباه»: فقط تطبیق دقیق نام، نه فازی
$exactOnly = (bool) $forceCreate;
$found = $this->findZarinKarForProduct($product, $this->zarinDefaultGoroheKar(), $exactOnly);
if ($found && !empty($found['idKar']) && is_numeric($found['idKar'])) {
$product->finance_p_name = (string) $found['idKar'];
$product->save();
if ($found && !empty($found['idKar']) && is_numeric($found['idKar'])) {
$product->finance_p_name = (string) $found['idKar'];
$product->save();
return [
'ok' => true,
'source' => 'found_by_name',
'idKar' => (int) $found['idKar'],
'product' => $product->fresh(),
'zarin_row' => $found['row'],
];
}
return [
'ok' => true,
'source' => $exactOnly ? 'found_exact_after_invalid' : 'found_by_name',
'idKar' => (int) $found['idKar'],
'product' => $product->fresh(),
'zarin_row' => $found['row'],
'match' => $found['match'] ?? null,
];
}
return $this->createZarinKarForProduct($product);
}
private function clearProductZarinKar(Product $product): void
{
if ($product->finance_p_name === null || $product->finance_p_name === '') {
return;
}
$product->finance_p_name = null;
$product->save();
}
private function isInvalidZarinKarError($response, ?string $httpError = null): bool
{
$haystack = '';
if (is_string($httpError)) {
$haystack .= $httpError;
}
if (is_array($response)) {
$haystack .= ' ' . json_encode($response, JSON_UNESCAPED_UNICODE);
} elseif (is_string($response)) {
$haystack .= ' ' . $response;
}
return str_contains($haystack, 'کد کار اشتباه');
}
public function table(Request $request)
{
$query = Zarin::with('customer')->orderByDesc('id');
@@ -1141,10 +1261,17 @@ class ZarinController extends Controller
'amount' => $syncAmount,
'order_id' => $orderId,
'date' => $data['pay_at'] ?? Carbon::now(),
'type' => $mapper->metalLabel($product),
'type' => $mapper->zarinSyncTypeLabel($product),
'created_at' => Carbon::now(),
'data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
]);
if ($existing) {
$existing->update([
'type' => $mapper->zarinSyncTypeLabel($product),
'desc' => $sharh,
'amount' => $syncAmount,
]);
}
try {
if ($mapper->isCountDoc($product)) {
@@ -1465,11 +1592,18 @@ class ZarinController extends Controller
'desc' => $sharh,
'amount' => $syncAmount,
'date' => Carbon::now(),
'type' => $mapper->metalLabel($product),
'type' => $mapper->zarinSyncTypeLabel($product),
'created_at' => Carbon::now(),
'withdraw_id' => $data['withdraw_id'] ?? null,
'data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
]);
if ($existing) {
$existing->update([
'type' => $mapper->zarinSyncTypeLabel($product),
'desc' => $sharh,
'amount' => $syncAmount,
]);
}
if ($existing && empty($existing->data)) {
$existing->update(['data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)]);
@@ -1525,6 +1659,7 @@ class ZarinController extends Controller
/**
* سند تعدادی زرین (سکه / شمش / گوی / مکعب).
* در زرین همه با sabtSanadTedadi می‌روند؛ تفکیک فقط با idKar جداگانهٔ هر محصول است.
*/
private function postZarinCountDoc(
Zarin $zarin,
@@ -1542,46 +1677,71 @@ class ZarinController extends Controller
return false;
}
$kar = $this->ensureZarinKarForProduct($product);
if (empty($kar['ok']) || empty($kar['idKar'])) {
$this->sync()->markFailed($zarin, $kar ?? null, $kar['message'] ?? 'کد کار زرین برای محصول یافت نشد');
return false;
}
if ($fi <= 0) {
$fi = 1;
}
$http = $this->zarinRequest([
'kind' => 'sabtSanadTedadi',
'idHesab' => $customer->moshtari_code,
'idKar' => (int) $kar['idKar'],
'fi' => $fi,
'tedad' => $tedad,
'parDar' => $parDar,
'sharh' => $sharh,
'uid' => $uid,
]);
$retriedInvalidKar = false;
if (!$http) {
$this->sync()->markFailed($zarin, null, 'تنظیمات زرین کامل نیست');
return 'تنظیمات زرین کامل نیست. zarin_api یا zarin_token تنظیم نشده است.';
}
while (true) {
$kar = $this->ensureZarinKarForProduct($product, $retriedInvalidKar);
if (empty($kar['ok']) || empty($kar['idKar'])) {
$this->sync()->markFailed($zarin, $kar ?? null, $kar['message'] ?? 'کد کار زرین برای محصول یافت نشد');
return false;
}
[$response, $httpError] = $this->zarinDecodeResponse($http);
if ($httpError) {
$this->sync()->markFailed($zarin, $response ?? $httpError, $httpError);
$http = $this->zarinRequest([
'kind' => 'sabtSanadTedadi',
'idHesab' => $customer->moshtari_code,
'idKar' => (int) $kar['idKar'],
'fi' => $fi,
'tedad' => $tedad,
'parDar' => $parDar,
'sharh' => $sharh,
'uid' => $uid,
]);
if (!$http) {
$this->sync()->markFailed($zarin, null, 'تنظیمات زرین کامل نیست');
return 'تنظیمات زرین کامل نیست. zarin_api یا zarin_token تنظیم نشده است.';
}
[$response, $httpError] = $this->zarinDecodeResponse($http);
if (
!$retriedInvalidKar
&& $this->isInvalidZarinKarError($response, $httpError)
) {
$this->clearProductZarinKar($product);
$product->refresh();
$retriedInvalidKar = true;
continue;
}
if ($httpError) {
$this->sync()->markFailed($zarin, $response ?? $httpError, $httpError);
return false;
}
$flat = $this->extractResponseRow(is_array($response) ? $response : null);
if ($this->sync()->responseOk(is_array($response) ? $response : []) || $this->sync()->responseOk($flat)) {
$this->sync()->markSuccess($zarin, is_array($response) ? $response : $flat, $data);
return $data;
}
if (
!$retriedInvalidKar
&& $this->isInvalidZarinKarError($response)
) {
$this->clearProductZarinKar($product);
$product->refresh();
$retriedInvalidKar = true;
continue;
}
$this->sync()->markFailed($zarin, $response, 'ثبت سند تعدادی در زرین ناموفق بود');
return false;
}
$flat = $this->extractResponseRow(is_array($response) ? $response : null);
if ($this->sync()->responseOk(is_array($response) ? $response : []) || $this->sync()->responseOk($flat)) {
$this->sync()->markSuccess($zarin, is_array($response) ? $response : $flat, $data);
return $data;
}
$this->sync()->markFailed($zarin, $response, 'ثبت سند تعدادی در زرین ناموفق بود');
return false;
}
/**