fix pargar bugs

This commit is contained in:
2026-07-21 12:35:45 +03:30
parent 5ea97e873a
commit c1c1ab501d
6 changed files with 261 additions and 20 deletions
@@ -24,17 +24,40 @@
<MobileInfoGrid>
<MobileDetailRow label="شناسه" :value="row.id" />
<MobileDetailRow label="مشتری" :value="row.customer?.name || '-'" />
<MobileDetailRow label="مشتری" :value="pargarCustomerLabel(row)" />
<MobileDetailRow label="موبایل" :value="row.customer?.mobile || '-'" />
<MobileDetailRow label="نوع" :value="row.type || '-'" />
<MobileDetailRow label="مقدار" :value="row.amount ?? '-'" />
<MobileDetailRow label="شرح" full-width :value="row.desc || '-'" />
<MobileDetailRow label="نوع" :value="pargarTypeLabel(row.type)" />
<MobileDetailRow label="مقدار" :value="pargarAmountLabel(row)" />
<MobileDetailRow label="شرح" full-width :value="pargarDescLabel(row)" />
<MobileDetailRow label="وضعیت" :value="pargarStatusLabel(row.sync_status, row.pargar_ref, row.desc)" />
<MobileDetailRow v-if="parsedPayload?.accountId" label="کد حساب پرگار" :value="parsedPayload.accountId" />
<MobileDetailRow
v-if="parsedPayload?.transactions.length"
label="تعداد سطر"
:value="String(parsedPayload.transactions.length)"
/>
<MobileDetailRow label="کد پرگار" :value="row.pargar_ref || '-'" />
<MobileDetailRow label="زمان" :value="row.created_at ? toJalali(row.created_at) : '-'" />
<MobileDetailRow v-if="row.res" label="پاسخ API" full-width :value="row.res" />
</MobileInfoGrid>
<div
v-if="parsedPayload?.transactions.length"
class="mt-3 space-y-2"
>
<div class="text-sm font-600 text-gray-700">جزئیات اسناد</div>
<div
v-for="(tx, idx) in parsedPayload.transactions"
:key="idx"
class="rounded-lg border border-gray-100 bg-gray-50 p-3 text-xs space-y-1"
>
<MobileDetailRow label="شرح" full-width :value="tx.desc || tx.docType || '-'" />
<MobileDetailRow label="تاریخ" :value="tx.date || '-'" />
<MobileDetailRow label="شماره سند" :value="tx.docId || '-'" />
<MobileDetailRow label="مبالغ" full-width :value="formatTransactionAmount(tx)" />
</div>
</div>
<div
v-if="row.sync_log"
class="mt-3 rounded-lg bg-red-50 p-3 text-sm text-red-700 whitespace-pre-wrap"
@@ -53,6 +76,14 @@ import MobileIconButton from '@/components/mobile/MobileIconButton.vue'
import MobileInfoGrid from '@/components/mobile/MobileInfoGrid.vue'
import MobileSheetHeader from '@/components/mobile/MobileSheetHeader.vue'
import { canRetryPargar, pargarStatusLabel } from '@/pages/menuPages/accounting/utils/pargarStatus'
import {
formatTransactionAmount,
parsePargarPayload,
pargarAmountLabel,
pargarCustomerLabel,
pargarDescLabel,
pargarTypeLabel
} from '@/pages/menuPages/accounting/utils/pargarDisplay'
import { isMobile } from '@core/utils/is'
import { toJalali } from '@core/utils/useDataTime'
@@ -72,7 +103,11 @@ const visibleProxy = computed({
set: (value: boolean) => emit('update:visible', value)
})
const sheetTitle = computed(() => props.item?.customer?.name || 'سند پرگار')
const sheetTitle = computed(() =>
props.item ? pargarCustomerLabel(props.item) : 'سند پرگار'
)
const parsedPayload = computed(() => parsePargarPayload(props.item?.data))
function canRetry(row: PargarRecord) {
return canRetryPargar(row)
@@ -8,8 +8,8 @@
</span>
</div>
<div class="flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-gray-600">
<span>نوع: <span class="font-600 text-gray-900">{{ item.type || '-' }}</span></span>
<span>مقدار: <span class="font-600 text-gray-900">{{ item.amount ?? '-' }}</span></span>
<span>نوع: <span class="font-600 text-gray-900">{{ pargarTypeLabel(item.type) }}</span></span>
<span>مقدار: <span class="font-600 text-gray-900">{{ pargarAmountLabel(item) }}</span></span>
</div>
<div class="text-xs text-gray-400">{{ createdAtLabel }}</div>
</div>
@@ -21,6 +21,11 @@ import type { PargarRecord } from '@/api/pargar'
import MobileCard from '@/components/mobile/MobileCard.vue'
import { toJalali } from '@core/utils/useDataTime'
import { pargarStatusLabel } from '@/pages/menuPages/accounting/utils/pargarStatus'
import {
pargarAmountLabel,
pargarCustomerLabel,
pargarTypeLabel
} from '@/pages/menuPages/accounting/utils/pargarDisplay'
const props = defineProps<{
item: PargarRecord
@@ -30,7 +35,7 @@ defineEmits<{
(e: 'open', item: PargarRecord): void
}>()
const customerName = computed(() => props.item.customer?.name || '-')
const customerName = computed(() => pargarCustomerLabel(props.item))
const createdAtLabel = computed(() =>
props.item.created_at ? toJalali(props.item.created_at) : '-'
)
@@ -56,6 +56,41 @@
<div v-if="selected?.res" class="rounded-lg bg-gray-100 p-3 text-xs font-mono text-gray-700 whitespace-pre-wrap">
{{ selected.res }}
</div>
<div v-if="parsedPayload?.transactions.length" class="space-y-2">
<div class="text-sm font-600 text-gray-700">جزئیات اسناد دریافتی از پرگار</div>
<div
v-for="(tx, idx) in parsedPayload.transactions"
:key="idx"
class="rounded-lg border border-gray-100 bg-gray-50 p-3 text-xs space-y-1"
>
<div class="flex justify-between gap-2">
<span class="text-gray-500">شرح</span>
<span class="text-left">{{ tx.desc || tx.docType || '-' }}</span>
</div>
<div class="flex justify-between gap-2">
<span class="text-gray-500">تاریخ</span>
<span class="text-left">{{ tx.date || '-' }}</span>
</div>
<div class="flex justify-between gap-2">
<span class="text-gray-500">شماره سند</span>
<span class="text-left">{{ tx.docId || '-' }}</span>
</div>
<div class="flex justify-between gap-2">
<span class="text-gray-500">مبالغ</span>
<span class="text-left">{{ formatTransactionAmount(tx) }}</span>
</div>
<div v-if="tx.remainGold || tx.remainRial" class="flex justify-between gap-2">
<span class="text-gray-500">مانده</span>
<span class="text-left">
{{ tx.remainGold ? `${tx.remainGold} گرم` : '' }}
{{ tx.remainRial ? `${tx.remainRial} ریال` : '' }}
</span>
</div>
</div>
</div>
<div v-else-if="selected?.data" class="rounded-lg bg-gray-100 p-3 text-xs font-mono text-gray-700 whitespace-pre-wrap overflow-x-auto">
{{ selected.data }}
</div>
<div v-if="selected?.sync_log" class="rounded-lg bg-red-50 p-3 text-red-700 whitespace-pre-wrap">
{{ selected.sync_log }}
</div>
@@ -92,6 +127,14 @@ import BasicModal from '@/components/BasicModal.vue'
import PargarRetryModal from '@/components/PargarRetryModal.vue'
import PargarMobileCardList from '@/pages/menuPages/accounting/components/PargarMobileCardList.vue'
import { canRetryPargar, pargarStatusLabel } from '@/pages/menuPages/accounting/utils/pargarStatus'
import {
formatTransactionAmount,
parsePargarPayload,
pargarAmountLabel,
pargarCustomerLabel,
pargarDescLabel,
pargarTypeLabel
} from '@/pages/menuPages/accounting/utils/pargarDisplay'
import { toJalali } from '@core/utils/useDataTime'
const [submit, reload] = useBaseTable()
@@ -110,10 +153,10 @@ const columns = [
sortable: false,
customRender: (r: any) => r.index + 1
},
{ title: 'مشتری', dataIndex: 'customer.name', key: 'customer.name', type: 'Text' },
{ title: 'نوع', dataIndex: 'type', key: 'type', type: 'Text' },
{ title: 'مقدار', dataIndex: 'amount', key: 'amount', type: 'Number' },
{ title: 'شرح', dataIndex: 'desc', key: 'desc', type: 'Text' },
{ title: 'مشتری', dataIndex: 'customer.name', key: 'customer.name', type: 'Text', customRender: (r: any) => pargarCustomerLabel(r.record) },
{ title: 'نوع', dataIndex: 'type', key: 'type', type: 'Text', customRender: (r: any) => pargarTypeLabel(r.record.type) },
{ title: 'مقدار', dataIndex: 'amount', key: 'amount', type: 'Number', customRender: (r: any) => pargarAmountLabel(r.record) },
{ title: 'شرح', dataIndex: 'desc', key: 'desc', type: 'Text', customRender: (r: any) => pargarDescLabel(r.record) },
{
title: 'وضعیت',
dataIndex: 'sync_status',
@@ -143,23 +186,28 @@ const tblProps = ref({
api: (params: TableInput) => getPargarTable(params)
})
/** اطلاعاتی که برای هر سند حسابداری نیاز است (بدون جزئیات پرگار‌محور) */
/** اطلاعاتی که برای هر سند حسابداری نیاز است */
const infoItems = computed(() => {
const row = selected.value
if (!row) return []
const payload = parsePargarPayload(row.data)
return [
{ label: 'شناسه سند', value: row.id },
{ label: 'مشتری', value: row.customer?.name },
{ label: 'موبایل', value: row.customer?.mobile },
{ label: 'نوع', value: row.type },
{ label: 'مقدار', value: row.amount },
{ label: 'شرح', value: row.desc },
{ label: 'مشتری', value: pargarCustomerLabel(row) },
{ label: 'موبایل', value: row.customer?.mobile || '-' },
{ label: 'کد حساب پرگار', value: payload?.accountId || row.pargar_ref || '-' },
{ label: 'نوع', value: pargarTypeLabel(row.type) },
{ label: 'مقدار', value: pargarAmountLabel(row) },
{ label: 'شرح', value: pargarDescLabel(row) },
{ label: 'تعداد سطر سند', value: payload?.transactions.length ? String(payload.transactions.length) : '-' },
{ label: 'زمان ثبت', value: row.created_at ? toJalali(row.created_at) : '-' },
{ label: 'وضعیت', value: pargarStatusLabel(row.sync_status, row.pargar_ref, row.desc) },
{ label: 'کد پرگار', value: row.pargar_ref || 'ثبت نشده' }
]
})
const parsedPayload = computed(() => parsePargarPayload(selected.value?.data))
function openInfo(record: PargarRecord) {
selected.value = record
infoVisible.value = true
@@ -0,0 +1,153 @@
import type { PargarRecord } from '@/api/pargar'
export interface PargarTransactionRow {
docId?: string
date?: string
docType?: string
desc?: string
by?: string
bedGold?: number | string
besGold?: number | string
bedRial?: number | string
besRial?: number | string
remainGold?: number | string
remainRial?: number | string
}
export interface PargarParsedPayload {
accountId?: string
accountName?: string
transactions: PargarTransactionRow[]
}
const TYPE_LABELS: Record<string, string> = {
document: 'سند ورودی از پرگار',
order: 'سفارش',
mande: 'مانده حساب',
customer: 'ثبت مشتری',
wallet_cash: 'کیف ریالی',
wallet_gold: 'کیف طلا'
}
function parseNum(value: unknown): number {
if (value == null || value === '') return 0
const n = Number(String(value).replace('/', '.'))
return Number.isFinite(n) ? n : 0
}
export function pargarTypeLabel(type?: string | null): string {
if (!type) return '-'
return TYPE_LABELS[type] ?? type
}
export function parsePargarPayload(data?: string | null): PargarParsedPayload | null {
if (!data?.trim()) return null
try {
const raw = JSON.parse(data) as Record<string, unknown>
let transactions: PargarTransactionRow[] = []
if (Array.isArray(raw.transactions)) {
transactions = raw.transactions as PargarTransactionRow[]
} else if (raw.transaction && typeof raw.transaction === 'object') {
transactions = [raw.transaction as PargarTransactionRow]
}
return {
accountId: raw.accountId != null ? String(raw.accountId) : undefined,
accountName: raw.accountName != null ? String(raw.accountName) : undefined,
transactions
}
} catch {
return null
}
}
export function pargarCustomerLabel(record: PargarRecord): string {
if (record.customer?.name?.trim()) return record.customer.name.trim()
const payload = parsePargarPayload(record.data)
if (payload?.accountName?.trim()) return payload.accountName.trim()
if (record.pargar_ref) return `کد پرگار ${record.pargar_ref}`
return '-'
}
export function pargarAmountLabel(record: PargarRecord): string {
if (record.amount != null && record.amount !== 0) {
return formatPargarAmount(record.amount, record)
}
const payload = parsePargarPayload(record.data)
if (!payload?.transactions.length) return '-'
let maxGold = 0
let maxRial = 0
for (const tx of payload.transactions) {
maxGold = Math.max(maxGold, Math.abs(parseNum(tx.bedGold)), Math.abs(parseNum(tx.besGold)))
maxRial = Math.max(maxRial, Math.abs(parseNum(tx.bedRial)), Math.abs(parseNum(tx.besRial)))
}
if (maxGold >= 0.01) return `${maxGold} گرم`
if (maxRial >= 1) return `${maxRial.toLocaleString('fa-IR')} ریال`
return '-'
}
function formatPargarAmount(amount: number, record: PargarRecord): string {
const payload = parsePargarPayload(record.data)
let summaryUnit: string | undefined
if (payload && '_summary' in (JSON.parse(record.data || '{}') as Record<string, unknown>)) {
const raw = JSON.parse(record.data || '{}') as { _summary?: { amount_unit?: string } }
summaryUnit = raw._summary?.amount_unit
}
if (summaryUnit === 'rial' || record.type === 'wallet_cash') {
return `${amount.toLocaleString('fa-IR')} ریال`
}
if (summaryUnit === 'gold' || record.type === 'wallet_gold' || record.type === 'mande') {
return `${amount} گرم`
}
return String(amount)
}
export function pargarDescLabel(record: PargarRecord): string {
const desc = record.desc?.trim()
if (desc && desc !== 'دریافت اسناد از پرگار') return desc
try {
const raw = record.data ? JSON.parse(record.data) : null
if (raw?.wallet_applied === false && raw?.skip_reason === 'echo_outbound') {
return 'سند پرگار — از پنل ارسال شده (بدون اعمال مجدد)'
}
if (raw?.wallet_applied === true) {
return desc || 'اعمال‌شده روی کیف'
}
} catch {
// ignore
}
const payload = parsePargarPayload(record.data)
if (!payload?.transactions.length) return desc || '-'
const lines = payload.transactions
.map((tx) => tx.desc?.trim() || tx.docType?.trim() || '')
.filter(Boolean)
if (lines.length) {
const head = lines.slice(0, 2).join(' | ')
return lines.length > 2 ? `${head} (+${lines.length - 2} سند دیگر)` : head
}
return desc || '-'
}
export function formatTransactionAmount(tx: PargarTransactionRow): string {
const parts: string[] = []
const bedGold = parseNum(tx.bedGold)
const besGold = parseNum(tx.besGold)
const bedRial = parseNum(tx.bedRial)
const besRial = parseNum(tx.besRial)
if (bedGold >= 0.01) parts.push(`بدهکار طلا: ${bedGold}`)
if (besGold >= 0.01) parts.push(`بستانکار طلا: ${besGold}`)
if (bedRial >= 1) parts.push(`بدهکار ریال: ${bedRial.toLocaleString('fa-IR')}`)
if (besRial >= 1) parts.push(`بستانکار ریال: ${besRial.toLocaleString('fa-IR')}`)
return parts.length ? parts.join(' — ') : '-'
}
@@ -119,7 +119,7 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [
title: 'عمومی و برند',
icon: 'vuesax-linear:home-2',
match: (k) =>
/^(name_fa|name_en|app_url|admin_url|apk_download_link|permision__enabled|bace_url|server_url|logo_|title_pwa|title_fav|metal_type|main_metal|unit$|open_register|auto_active_customer|store_status|key$|is_store|group_sms|customer_direct_sms|has_page_earn_money|sms_panel|main_wallet_products|require_zero_main_gold_before_other_buy)/.test(
/^(name_fa|name_en|app_url|admin_url|apk_download_link|permision__enabled|bace_url|server_url|logo_|title_pwa|title_fav|metal_type|main_metal|unit$|open_register|auto_active_customer|store_status|key$|is_store|group_sms|customer_direct_sms|has_page_earn_money|sms_panel|main_wallet_products)/.test(
k
)
},
@@ -864,7 +864,6 @@ export const SETTING_LOCKED_KEYS = new Set([
'sms_panel',
'logo_login', 'logo_register', 'title_pwa', 'title_fav',
'main_product', 'main_wallet_products', 'metal_type', 'main_metal',
'require_zero_main_gold_before_other_buy',
'montify_name',
'montzar_base_url', 'montzar_closed',
'montzar_diff', 'montzar_last_product_log', 'montzar_live_price', 'montzar_mazaneh',
@@ -895,6 +894,7 @@ export const SETTING_EDITABLE_KEYS = new Set([
'name_fa',
'auto_active_customer', 'balance_report_exclude_customer_id',
'buy_fee', 'charge_notif', 'close_price',
'require_zero_main_gold_before_other_buy',
'sms_notif_order_admin', 'sms_notif_order_customer',
'sms_notif_withdraw_admin', 'sms_notif_withdraw_otp', 'sms_notif_login_otp',
'sms_notif_gateway_charge_admin', 'sms_notif_register_customer', 'sms_notif_register_admin',