fix some bugs from accounting

This commit is contained in:
2026-07-28 17:44:24 +03:30
parent 91f6b24da4
commit d81cea3d4f
7 changed files with 995 additions and 247 deletions
+270 -101
View File
@@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use App\Models\Tahesab;
use App\Models\Customer;
use App\Services\AccountingProductDocMapper;
use App\Services\TahesabSyncService;
use Morilog\Jalali\Jalalian;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
@@ -421,6 +422,10 @@ class TahesabController extends Controller
if (empty($data['customer_id']) && $existing && $existing->order_id) {
$order = \App\Models\Order::find($existing->order_id);
if ($order) {
$productPayload = ['metal_id' => $existing->type === 'طلا' ? 1 : 2];
if ($order->product_id) {
$productPayload['id'] = (int) $order->product_id;
}
$data = array_merge($data, [
'customer_id' => $existing->customer_id,
'amount' => $existing->amount,
@@ -428,8 +433,9 @@ class TahesabController extends Controller
'price' => $order->price,
'total_price' => $order->total_price,
'pay_at' => $existing->date,
'product' => ['metal_id' => $existing->type === 'طلا' ? 1 : 2],
'order' => ['id' => $existing->order_id],
'product' => $productPayload,
'product_id' => $order->product_id ? (int) $order->product_id : null,
'order' => ['id' => $existing->order_id, 'product_id' => $order->product_id],
]);
}
}
@@ -440,71 +446,91 @@ class TahesabController extends Controller
: false;
}
// $customer = auth()->user();
// if (!$customer) {
// throw new UnprocessableEntityHttpException("Auth needed");
// }
$mapper = app(AccountingProductDocMapper::class);
$product = $mapper->resolveProductFromPayload($data);
if ($product && $mapper->shouldSkip($product)) {
return false;
}
$customer = Customer::where('id', $data['customer_id'])->first();
if ($customer && ($customer->moshtari_code === null)) {
$this->createCustomerInTahesab($customer->id);
$customer = Customer::where('id', $data['customer_id'])->first();
}
$date = Verta::instance(Carbon::parse($data['pay_at']))->format('Y-m-d');
$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');
[$year, $month, $day] = explode('-', $date);
$amount = $data['amount'];
$isBuy = (int) ($data['direction'] ?? 0) === 1;
$mablaghKol = $isBuy
? (int) round((float) $amount * (float) $data['price'])
: (int) ($data['total_price'] ?? round((float) $amount * (float) $data['price']));
$action = $isBuy ? 'خرید' : 'فروش';
$kindFa = $mapper->kindLabel($kind);
$sharh = trim($title !== '' ? "{$action} {$title}" : "{$action} {$kindFa}");
$data['handler'] = 'storeTrade';
if ($existing && empty($existing->data)) {
$existing->update(['data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)]);
}
$tahesab = $existing ?? Tahesab::create([
"customer_id" => $customer->id,
"desc" => $data['direction'] == 1 ? "خرید آبشده" : "فروش آبشده",
"amount" => $amount,
"order_id" => $data['order']['id'] ?? null,
"date" => $data['pay_at'],
"type" => $data['product']['metal_id'] == 1 ? "طلا" : "نقره",
"created_at" => Carbon::now(),
"data" => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
'customer_id' => $customer->id,
'desc' => $sharh,
'amount' => $mapper->isCountDoc($product) ? $count : $vazn,
'order_id' => $data['order']['id'] ?? null,
'date' => $data['pay_at'] ?? Carbon::now(),
'type' => $mapper->metalLabel($product),
'created_at' => Carbon::now(),
'data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
]);
if ($mapper->isCountDoc($product) && $title === '') {
$this->sync()->markFailed($tahesab, null, 'نام محصول برای سند تعدادی ته‌حساب خالی است');
return false;
}
$body = $this->buildBuySaleBody(
$kind,
(int) $customer->moshtari_code,
(int) $year,
(int) $month,
(int) $day,
$vazn,
$count,
$title,
$isBuy ? 1 : 0,
$mazaneh,
$mablaghKol,
$sharh,
$jens,
(string) Verta::instance($data['pay_at'] ?? now())
);
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(), [
"DoNewSanadBuySaleGOLD" => [
1,//Sabte_Kol_Or_Movaghat_1_0
(int)$customer->moshtari_code, //Moshtari_Code
-1, //Factor_Number
1, //Radif_Number
(int)$year, //Shamsi_Year
(int)$month, //Shamsi_Month
(int)$day, //Shamsi_Day
(float)$amount, //Vazn
$data['product']['metal_id'] == 1 ? 750 : "", //Ayar
0,
"",
$data['direction'] == 1 ? 1 : 0, //BuyOrSale_0_1
(int)$data['price'], //Mazaneh
1, //MazanehIsMesghalOrGeram_0_1
1, //IsMotefaregheOrAbshode_0_1
$mablaghKol, //MablaghKol — خرید: وزن خالص × قیمت (بعد از کارمزد)
$data['direction'] == 1 ? "خرید آبشده" : "فروش آبشده", //sharh
-1, //Factor_Code — خودکار
-1, //HavalehBe_Mcode
-1, //MultiRadif
$data['product']['metal_id'] == 1 ? 0 : 2, //Jens_Felez
(string)Verta::instance($data['pay_at']),
]
])->json();
$response = $this->postTahesab($body);
} catch (\Exception $e) {
$this->sync()->markFailed($tahesab, $e->getMessage());
return $e->getMessage();
@@ -517,7 +543,6 @@ class TahesabController extends Controller
$this->sync()->markFailed($tahesab, $response, 'ثبت سند معامله در ته‌حساب ناموفق بود');
return false;
}
// directon: 0 = daryaft az moshtari
@@ -802,7 +827,7 @@ class TahesabController extends Controller
return false;
}
// direction: 1 = واریز طلا (خرید)، 0 = برداشت طلا (فروش)
// direction: 1 = واریز طلا (ورود)، 0 = برداشت طلا (خروج) — VKH نه BuySale
public function walletGold($data, ?Tahesab $existing = null)
{
if (empty($data['customer_id']) && $existing) {
@@ -812,6 +837,15 @@ class TahesabController extends Controller
'direction' => str_contains((string) $existing->desc, 'برداشت') ? 0 : 1,
'withdraw_id' => $existing->withdraw_id,
]);
$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'];
}
}
}
if (!isset($data['customer_id'], $data['value'])) {
@@ -820,6 +854,12 @@ class TahesabController extends Controller
: false;
}
$mapper = app(AccountingProductDocMapper::class);
$product = $mapper->resolveProductFromPayload($data);
if ($product && $mapper->shouldSkip($product)) {
return false;
}
$customer = Customer::where('id', $data['customer_id'])->first();
if ($customer && ($customer->moshtari_code === null)) {
@@ -827,70 +867,65 @@ class TahesabController extends Controller
$customer = Customer::where('id', $data['customer_id'])->first();
}
$amount = abs($data['value']);
$buyOrSale = (int) ($data['direction'] ?? 1) === 1 ? 1 : 0;
$isDeposit = $buyOrSale === 1;
$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;
$action = $isDeposit ? 'شارژ' : 'دشارژ';
$kindFa = $mapper->kindLabel($kind);
$sharh = !empty($data['note'])
? (string) $data['note']
: (!empty($data['desc'])
? (string) $data['desc']
: trim($title !== '' ? "{$action} {$title}" : "{$action} {$kindFa}"));
$data['note'] = $sharh;
$data['handler'] = 'walletGold';
$tahesab = $existing ?? Tahesab::create([
"customer_id" => $customer->id,
"desc" => $isDeposit ? "شارژ کیف پول آبشده" : "برداشت از کیف پول آبشده",
"amount" => $amount,
"date" => Carbon::now(),
"type" => "طلا",
"created_at" => Carbon::now(),
"withdraw_id" => $data['withdraw_id'] ?? null,
"data" => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
'customer_id' => $customer->id,
'desc' => $sharh,
'amount' => $mapper->isCountDoc($product) ? $count : $vazn,
'date' => Carbon::now(),
'type' => $mapper->metalLabel($product),
'created_at' => Carbon::now(),
'withdraw_id' => $data['withdraw_id'] ?? null,
'data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
]);
if ($existing && empty($existing->data)) {
$existing->update(['data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)]);
}
if ($mapper->isCountDoc($product) && $title === '') {
$this->sync()->markFailed($tahesab, null, 'نام محصول برای سند تعدادی ته‌حساب خالی است');
return false;
}
$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);
$prices = $this->lastPriceData();
$mazaneh = $isDeposit ? (int) ($prices['buy'] ?? 0) : (int) ($prices['sell'] ?? 0);
$mablaghKol = (int) round($mazaneh * $amount);
$sharh = !empty($data['note'])
? $data['note']
: (!empty($data['desc']) ? $data['desc'] : ($isDeposit ? 'خرید آبشده' : 'فروش آبشده'));
$data['note'] = $sharh;
$body = $this->buildVkhBody(
$kind,
(int) $customer->moshtari_code,
(int) $year,
(int) $month,
(int) $day,
$vazn,
$count,
$title,
$isVorood,
$sharh,
$jens
);
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(), [
"DoNewSanadBuySaleGOLD" => [
1,//Sabte_Kol_Or_Movaghat_1_0
(int)$customer->moshtari_code, //Moshtari_Code
-1, //Factor_Number
1, //Radif_Number
(int)$year, //Shamsi_Year
(int)$month, //Shamsi_Month
(int)$day, //Shamsi_Day
(float)$amount, //Vazn
750, //Ayar
0,
"",
$buyOrSale, //BuyOrSale_0_1 — 1=خرید، 0=فروش
$mazaneh, //Mazaneh
1, //MazanehIsMesghalOrGeram_0_1
1, //IsMotefaregheOrAbshode_0_1
$mablaghKol, //MablaghKol
$sharh,//Sharh
-1, //Factor_Code — خودکار
-1, //HavalehBe_Mcode
-1, //MultiRadif
0, //Jens_Felez
(string)$date,
]
])->json();
$response = $this->postTahesab($body);
} catch (\Exception $e) {
$this->sync()->markFailed($tahesab, $e->getMessage());
return $e->getMessage();
@@ -903,7 +938,141 @@ class TahesabController extends Controller
$this->sync()->markFailed($tahesab, $response, 'ثبت سند طلایی کیف پول در ته‌حساب ناموفق بود');
return false;
}
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,
],
];
}
public function createCustomerInTahesab($customer_id)