fix zarin duplicate hesab and convert mesghal unit price to gram before cash/weight.
Reuse an existing Zarin hesab instead of a second addhesab, and stop rial-locked orders from treating mesghal as per-gram. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,6 +11,7 @@ use App\Models\Zarin;
|
||||
use App\Services\AccountingProductDocMapper;
|
||||
use App\Services\ZarinSyncService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Cache\LockTimeoutException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
@@ -1146,6 +1147,79 @@ class ZarinController extends Controller
|
||||
return '';
|
||||
}
|
||||
|
||||
/** کد حساب زرین از پاسخ addhesab / listhesabha — OK و Code هم مثل idHesab معتبرند */
|
||||
private function extractZarinHesabId(array $row): ?int
|
||||
{
|
||||
foreach (['idHesab', 'idhesab', 'Code', 'code', 'OK', 'id'] as $key) {
|
||||
if (! array_key_exists($key, $row)) {
|
||||
continue;
|
||||
}
|
||||
$value = $row[$key];
|
||||
if ($value === null || $value === '' || $value === false || $value === true) {
|
||||
continue;
|
||||
}
|
||||
if (is_numeric($value) && (int) $value > 0) {
|
||||
return (int) $value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** حساب از قبل ساختهشده در زرین — با موبایل، تا addhesab دوم نسازیم */
|
||||
private function findExistingZarinHesabId(?string $mobile): ?int
|
||||
{
|
||||
$want = Customer::normalizeMobile($mobile ?? '');
|
||||
if ($want === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$fetched = $this->fetchAllZarinCustomers();
|
||||
$rows = $fetched['rows'] ?? [];
|
||||
if ($rows === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$groupFilter = $this->zarinCustomerGroupName();
|
||||
$sameGroup = null;
|
||||
$any = null;
|
||||
|
||||
foreach ($rows as $item) {
|
||||
if (! is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$phone = $this->extractZarinPhone($item);
|
||||
$got = Customer::normalizeMobile($phone);
|
||||
if ($got === null || $got !== $want) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$id = $this->extractZarinHesabId($item);
|
||||
if ($id === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$g = $this->extractZarinGroup($item);
|
||||
if ($groupFilter !== '' && $g !== '' && $this->zarinGroupNamesMatch($g, $groupFilter)) {
|
||||
$sameGroup = $id;
|
||||
break;
|
||||
}
|
||||
|
||||
$any ??= $id;
|
||||
}
|
||||
|
||||
return $sameGroup ?? $any;
|
||||
}
|
||||
|
||||
private function bindCustomerZarinHesab(Customer $customer, int $idHesab): int
|
||||
{
|
||||
$customer->moshtari_code = $idHesab;
|
||||
$customer->save();
|
||||
|
||||
return $idHesab;
|
||||
}
|
||||
|
||||
public function syncPanelToZarin(): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
$customers = Customer::whereNull('deleted_at')
|
||||
@@ -1450,7 +1524,7 @@ class ZarinController extends Controller
|
||||
$isColleagueLeg = ! empty($data['colleague_leg']) && $accountOverride !== '';
|
||||
|
||||
if (! $isColleagueLeg) {
|
||||
if ($customer && ($customer->moshtari_code === null)) {
|
||||
if ($customer && ! $customer->hasValidMoshtariCode()) {
|
||||
$this->createCustomerInZarin($customer->id);
|
||||
$customer = Customer::where('id', $data['customer_id'])->first();
|
||||
}
|
||||
@@ -1472,6 +1546,9 @@ class ZarinController extends Controller
|
||||
$count = $mapper->accountingQty($product, $rawAmount, $data);
|
||||
$vazn = $mapper->accountingWeight($product, $rawAmount, $data);
|
||||
$isBuy = (int) ($data['direction'] ?? 0) === 1;
|
||||
// زرین korf/parDar از دید حساب مشتری است، نه فروشگاه:
|
||||
// خرید مشتری → k / +1 : طلا بستانکار (سوت اضافه) + ریال بدهکار (پول کم)
|
||||
// فروش مشتری → f / −1 : طلا بدهکار + ریال بستانکار
|
||||
$orderId = is_object($data['order'] ?? null)
|
||||
? ($data['order']->id ?? null)
|
||||
: ($data['order']['id'] ?? ($existing->order_id ?? null));
|
||||
@@ -1530,7 +1607,7 @@ class ZarinController extends Controller
|
||||
$accountCustomer,
|
||||
$product,
|
||||
$count,
|
||||
$isBuy ? -1 : 1,
|
||||
$isBuy ? 1 : -1,
|
||||
$fi,
|
||||
$sharh,
|
||||
$this->zarinUid($uidKind, $uidSource ?? $zarin->id)
|
||||
@@ -1541,7 +1618,7 @@ class ZarinController extends Controller
|
||||
$data,
|
||||
$accountCustomer,
|
||||
$vazn,
|
||||
$isBuy ? 'f' : 'k',
|
||||
$isBuy ? 'k' : 'f',
|
||||
(float) ($data['price'] ?? 0),
|
||||
$sharh,
|
||||
$mapper->jensFelez($product) ?? 0,
|
||||
@@ -1576,7 +1653,7 @@ class ZarinController extends Controller
|
||||
|
||||
$customer = Customer::where('id', $data['customer_id'])->first();
|
||||
|
||||
if ($customer && ($customer->moshtari_code === null)) {
|
||||
if ($customer && ! $customer->hasValidMoshtariCode()) {
|
||||
$this->createCustomerInZarin($customer->id);
|
||||
$customer = Customer::where('id', $data['customer_id'])->first();
|
||||
}
|
||||
@@ -1698,7 +1775,7 @@ class ZarinController extends Controller
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($customer->moshtari_code === null) {
|
||||
if (! $customer->hasValidMoshtariCode()) {
|
||||
$this->createCustomerInZarin($customer->id);
|
||||
$customer = Customer::where('id', $data['customer_id'])->first();
|
||||
}
|
||||
@@ -1806,7 +1883,7 @@ class ZarinController extends Controller
|
||||
|
||||
$customer = Customer::where('id', $data['customer_id'])->first();
|
||||
|
||||
if ($customer && ($customer->moshtari_code === null)) {
|
||||
if ($customer && ! $customer->hasValidMoshtariCode()) {
|
||||
$this->createCustomerInZarin($customer->id);
|
||||
$customer = Customer::where('id', $data['customer_id'])->first();
|
||||
}
|
||||
@@ -2066,16 +2143,18 @@ class ZarinController extends Controller
|
||||
{
|
||||
$customer = Customer::findOrFail($customer_id);
|
||||
|
||||
if ($customer->moshtari_code != null) {
|
||||
if ($customer->hasValidMoshtariCode()) {
|
||||
return $customer->moshtari_code;
|
||||
}
|
||||
|
||||
$lock = Cache::lock('zarin:register-customer:' . $customer->id, 30);
|
||||
$lock = Cache::lock('zarin:register-customer:' . $customer->id, 90);
|
||||
|
||||
if (!$lock->get()) {
|
||||
try {
|
||||
$lock->block(25);
|
||||
} catch (LockTimeoutException $e) {
|
||||
$customer->refresh();
|
||||
|
||||
if ($customer->moshtari_code != null) {
|
||||
if ($customer->hasValidMoshtariCode()) {
|
||||
return $customer->moshtari_code;
|
||||
}
|
||||
|
||||
@@ -2085,10 +2164,15 @@ class ZarinController extends Controller
|
||||
try {
|
||||
$customer->refresh();
|
||||
|
||||
if ($customer->moshtari_code != null) {
|
||||
if ($customer->hasValidMoshtariCode()) {
|
||||
return $customer->moshtari_code;
|
||||
}
|
||||
|
||||
$existingId = $this->findExistingZarinHesabId($customer->mobile);
|
||||
if ($existingId !== null) {
|
||||
return $this->bindCustomerZarinHesab($customer, $existingId);
|
||||
}
|
||||
|
||||
$name = $customer->fname ?: ($customer->name ?? ('مشتری ' . $customer->id));
|
||||
$family = $customer->lname ?: '';
|
||||
$mobile = $customer->mobile ?? '';
|
||||
@@ -2119,14 +2203,15 @@ class ZarinController extends Controller
|
||||
}
|
||||
|
||||
$data = $this->extractResponseRow($response);
|
||||
$idHesab = $data['idHesab'] ?? null;
|
||||
$idHesab = $this->extractZarinHesabId($data);
|
||||
|
||||
if (!empty($idHesab)) {
|
||||
$customer->moshtari_code = $idHesab;
|
||||
$customer->save();
|
||||
if ($idHesab !== null) {
|
||||
return $this->bindCustomerZarinHesab($customer, $idHesab);
|
||||
}
|
||||
|
||||
// int برگردان تا با پیام خطای string اشتباه نشود
|
||||
return is_numeric($idHesab) ? (0 + $idHesab) : $idHesab;
|
||||
$existingId = $this->findExistingZarinHesabId($customer->mobile);
|
||||
if ($existingId !== null) {
|
||||
return $this->bindCustomerZarinHesab($customer, $existingId);
|
||||
}
|
||||
|
||||
$detail = $data['msg'] ?? $data['message'] ?? $data['detail'] ?? null;
|
||||
|
||||
@@ -99,6 +99,7 @@ class SingleOrderController extends Controller
|
||||
} else if (auth('user')->id()) {//admin
|
||||
$data['service_charge_percentage'] = 0;
|
||||
}
|
||||
$this->ensureOrderUnitPriceIsPerGram($data);
|
||||
if($data['price'] === 0){
|
||||
throw new UnprocessableEntityHttpException(' سفارشات بسته میباشد.');
|
||||
}
|
||||
@@ -320,6 +321,7 @@ class SingleOrderController extends Controller
|
||||
$feeRate = isset($data['fee_free']) && $data['fee_free'] === true
|
||||
? 0.0
|
||||
: (float) ($data['service_charge_percentage'] ?? 0);
|
||||
$this->ensureOrderUnitPriceIsPerGram($data);
|
||||
$data['price'] += $data['difference'];
|
||||
|
||||
if ($rialLocked) {
|
||||
@@ -586,6 +588,7 @@ class SingleOrderController extends Controller
|
||||
$feeRate = isset($data['fee_free']) && $data['fee_free'] === true
|
||||
? 0.0
|
||||
: (float) ($data['service_charge_percentage'] ?? 0);
|
||||
$this->ensureOrderUnitPriceIsPerGram($data);
|
||||
$data['price'] -= $data['difference'];
|
||||
|
||||
if ($rialLocked) {
|
||||
@@ -1330,6 +1333,44 @@ class SingleOrderController extends Controller
|
||||
return round($amount, 10);
|
||||
}
|
||||
|
||||
/** مظنهٔ زندهٔ هر گرم (ریال) — برای تشخیص قیمت مثقال بهجای گرم */
|
||||
private function liveGramQuoteRial(): float
|
||||
{
|
||||
$base = (float) app(ProductDisplayPriceService::class)->resolveAppBasePrice();
|
||||
if ($base > 0) {
|
||||
return $base;
|
||||
}
|
||||
|
||||
$montzar = (float) app(ConfigService::class)->get('montzar_live_price', 0);
|
||||
if ($montzar > 0) {
|
||||
return $montzar;
|
||||
}
|
||||
|
||||
return (float) (Price::latest()->first()?->price ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* اگر قیمت واحد سفارش حدود قیمت مثقال باشد، به قیمت هر گرم برگردان.
|
||||
* جلوگیری از ۱۰ میلیون تومان → ۱۰۵ سوت وقتی مثقال در فیلد «هر گرم» نشسته.
|
||||
*/
|
||||
private function ensureOrderUnitPriceIsPerGram(array &$data): void
|
||||
{
|
||||
$live = $this->liveGramQuoteRial();
|
||||
$config = app(ConfigService::class);
|
||||
$price = $config->gramUnitPriceIfMesghalQuoted((float) ($data['price'] ?? 0), $live);
|
||||
if ($price <= 0 || abs($price - (float) ($data['price'] ?? 0)) < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data['price'] = $price;
|
||||
if (isset($data['price_original'])) {
|
||||
$data['price_original'] = $config->gramUnitPriceIfMesghalQuoted(
|
||||
(float) $data['price_original'],
|
||||
$live
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function resolveGoldPaymentProductId(array $data): ?int
|
||||
{
|
||||
if (!empty($data['payment_product_id'])) {
|
||||
|
||||
@@ -193,6 +193,34 @@ class ConfigService
|
||||
return $raw === '' ? 0.0 : (float) $raw;
|
||||
}
|
||||
|
||||
public const DEFAULT_CHANGE_MESGHAL = 4.3318;
|
||||
|
||||
/** وزن هر مثقال به گرم — برای تشخیص قیمت مثقال بهجای قیمت گرم */
|
||||
public function changeMesghalFactor(): float
|
||||
{
|
||||
$value = (float) $this->get('change_mesghal', self::DEFAULT_CHANGE_MESGHAL);
|
||||
|
||||
return $value > 0 ? $value : self::DEFAULT_CHANGE_MESGHAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* اگر قیمت واحد حدود قیمت مثقال باشد (گرم زنده × ضریب)، به قیمت هر گرم برگردان.
|
||||
*/
|
||||
public function gramUnitPriceIfMesghalQuoted(float $unitPrice, float $liveGramPrice): float
|
||||
{
|
||||
$factor = $this->changeMesghalFactor();
|
||||
if ($unitPrice <= 0 || $liveGramPrice <= 0 || $factor <= 1) {
|
||||
return $unitPrice;
|
||||
}
|
||||
|
||||
$ratio = $unitPrice / $liveGramPrice;
|
||||
if ($ratio >= $factor * 0.92 && $ratio <= $factor * 1.08) {
|
||||
return $unitPrice / $factor;
|
||||
}
|
||||
|
||||
return $unitPrice;
|
||||
}
|
||||
|
||||
/** اختلاف فروش نمایش — فقط sell_fee (صفر یعنی بدون اختلاف) */
|
||||
public function displaySellPriceDiffRial(): float
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user