Tahesab: show raw data from Tahesab, invoice-style document view with metal labels
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
import { formatMoney } from '@/lib/money';
|
||||
import {
|
||||
dateToJalali,
|
||||
formatClockFa,
|
||||
@@ -79,8 +78,171 @@ const currentLink = computed(() =>
|
||||
|
||||
const hasMultipleConnections = computed(() => connections.value.length > 1);
|
||||
|
||||
const goldBalance = computed(() => assets.value?.goldBalance ?? null);
|
||||
const rialBalance = computed(() => assets.value?.rialBalance ?? null);
|
||||
/** برچسب فارسی فیلدهای شناختهشدهٔ پاسخ مانده؛ کلیدهای ناشناخته با همان نام خام نمایش داده میشوند */
|
||||
const MANDEH_FIELD_LABELS: Record<string, string> = {
|
||||
Code: 'کد حساب',
|
||||
MandeyeMali: 'مانده مالی',
|
||||
MandeyeVazni: 'مانده وزنی (طلا)',
|
||||
MandeyeVazniNoghre: 'مانده وزنی نقره',
|
||||
MandeMenhayeSeke: 'مانده منهای سکه',
|
||||
MandeyeKolBeTALA: 'مانده کل به طلا',
|
||||
MandeyeKolBePool: 'مانده کل به پول',
|
||||
};
|
||||
|
||||
function mandehFieldLabel(key: string) {
|
||||
if (MANDEH_FIELD_LABELS[key]) return MANDEH_FIELD_LABELS[key]!;
|
||||
if (key.startsWith('Seke_')) return `سکه ${key.slice(5)}`;
|
||||
if (key.startsWith('MandeyeArz_')) return `مانده ارز ${key.slice(11)}`;
|
||||
return key;
|
||||
}
|
||||
|
||||
/** فقط قالببندی نمایشی (جداکنندهٔ هزارگان)؛ مقدار و علامت همان پاسخ تهحساب است */
|
||||
function faDigits(s: string) {
|
||||
return s.replace(/\d/g, (d) => '۰۱۲۳۴۵۶۷۸۹'[Number(d)]!);
|
||||
}
|
||||
|
||||
function showValue(v: unknown): string {
|
||||
const s = dash(v, '');
|
||||
if (!s) return '—';
|
||||
if (/^-?\d+(\.\d+)?$/.test(s)) {
|
||||
const [int, frac] = s.replace('-', '').split('.');
|
||||
const grouped = int!.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
return `${s.startsWith('-') ? '-' : ''}${faDigits(grouped)}${frac ? `٫${faDigits(frac)}` : ''}`;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/** شناسهها/کدها بدون جداکنندهٔ هزارگان */
|
||||
function showRaw(v: unknown): string {
|
||||
const s = dash(v, '');
|
||||
return s ? faDigits(s) : '—';
|
||||
}
|
||||
|
||||
const mandehFields = computed(() => assets.value?.mandehFields ?? null);
|
||||
const mandehDetailRaw = computed(() => assets.value?.mandehDetailRaw ?? null);
|
||||
const DETAIL_VALUE_COLS = [
|
||||
{ key: 'Value', label: 'مقدار' },
|
||||
{ key: 'Value1', label: 'وزن (Value1)' },
|
||||
{ key: 'Value2', label: 'تعداد (Value2)' },
|
||||
{ key: 'Value3', label: 'ارزی (Value3)' },
|
||||
{ key: 'Value4', label: 'ریالی (Value4)' },
|
||||
] as const;
|
||||
const detailCols = computed(() =>
|
||||
DETAIL_VALUE_COLS.filter((c) => (mandehDetailRaw.value ?? []).some((d) => d[c.key] != null)),
|
||||
);
|
||||
|
||||
/** برچسب فیلدهای سند تهحساب (DoListAsnad) */
|
||||
const ASNAD_FIELD_LABELS: Record<string, string> = {
|
||||
NO: 'نوع',
|
||||
Radif: 'ردیف',
|
||||
Vazn: 'وزن',
|
||||
Ayar: 'عیار',
|
||||
TabdilVazn: 'وزن تبدیلی',
|
||||
Count: 'تعداد',
|
||||
No_Kar: 'نوع کار',
|
||||
Fi: 'فی',
|
||||
Mazaneh: 'مظنه',
|
||||
Mali: 'مالی',
|
||||
Arz: 'ارز',
|
||||
Name_Bank: 'بانک',
|
||||
Tarikh_Cheq: 'تاریخ چک',
|
||||
Sh_Cheq: 'شماره چک',
|
||||
Sh_Posht_Check: 'شماره پشت چک',
|
||||
Operation_Date: 'تاریخ عملیات',
|
||||
Sh_Sharti: 'شماره شرطی',
|
||||
Name_az: 'آزمایشگاه',
|
||||
Baabat: 'بابت',
|
||||
Sharh1: 'شرح',
|
||||
Sharh2: 'شرح ۲',
|
||||
TasirBarMande: 'تأثیر بر مانده',
|
||||
TahesabVazni: 'حساب وزنی',
|
||||
TahesabMali: 'حساب مالی',
|
||||
TahesabArzi: 'حساب ارزی',
|
||||
Jens_Felez: 'جنس فلز',
|
||||
Seller: 'فروشنده',
|
||||
User: 'کاربر ثبت',
|
||||
Tarikh_Time: 'ساعت',
|
||||
ZamanSabt: 'زمان ثبت',
|
||||
ZamanEdit: 'زمان ویرایش',
|
||||
isSanadMovaghat: 'سند موقت',
|
||||
MCode: 'کد مشتری',
|
||||
Factor_Code: 'کد فاکتور',
|
||||
ID: 'شناسه',
|
||||
};
|
||||
const ASNAD_ORDER = Object.keys(ASNAD_FIELD_LABELS);
|
||||
/** در سرتیتر سند/ردیف نمایش داده میشوند */
|
||||
const ASNAD_HEAD_KEYS = new Set(['Sh_Factor', 'Sh_factor', 'Tarikh', 'NO']);
|
||||
/** مقادیر کمّی که جداکنندهٔ هزارگان میگیرند؛ بقیه (کدها، شناسهها، عیار، تاریخ) خام */
|
||||
const ASNAD_AMOUNT_KEYS = new Set(['Vazn', 'TabdilVazn', 'Mali', 'Fi', 'Mazaneh', 'Count']);
|
||||
|
||||
function asnadFields(item: Record<string, unknown>) {
|
||||
const keys = Object.keys(item).filter((k) => !ASNAD_HEAD_KEYS.has(k));
|
||||
keys.sort((a, b) => {
|
||||
const ia = ASNAD_ORDER.indexOf(a);
|
||||
const ib = ASNAD_ORDER.indexOf(b);
|
||||
return (ia < 0 ? 999 : ia) - (ib < 0 ? 999 : ib);
|
||||
});
|
||||
return keys
|
||||
.map((k) => {
|
||||
const v = item[k];
|
||||
if (typeof v === 'boolean') return { key: k, label: ASNAD_FIELD_LABELS[k] ?? k, value: v ? 'بله' : 'خیر' };
|
||||
if (v != null && typeof v === 'object') return null;
|
||||
const s = dash(v, '');
|
||||
if (!s) return null;
|
||||
return { key: k, label: ASNAD_FIELD_LABELS[k] ?? k, value: asnadCell(k, s) };
|
||||
})
|
||||
.filter((x): x is { key: string; label: string; value: string } => x != null);
|
||||
}
|
||||
|
||||
/** کدهای جنس فلز طبق مستندات تهحساب */
|
||||
const JENS_FELEZ_LABELS: Record<string, string> = { '0': 'طلا', '1': 'پلاتین', '2': 'نقره' };
|
||||
|
||||
function asnadCell(k: string, v: unknown): string {
|
||||
const s = dash(v, '');
|
||||
if (!s) return '—';
|
||||
if (k === 'Jens_Felez') return JENS_FELEZ_LABELS[s] ?? showRaw(s);
|
||||
if (typeof v === 'boolean') return v ? 'بله' : 'خیر';
|
||||
return ASNAD_AMOUNT_KEYS.has(k) ? showValue(s) : showRaw(s);
|
||||
}
|
||||
|
||||
function isZeroish(v: unknown) {
|
||||
const s = dash(v, '');
|
||||
return !s || /^-?0+(\.0+)?$/.test(s);
|
||||
}
|
||||
|
||||
/** جنس فلز فقط برای ردیفهای وزنی معنا دارد؛ ردیفهای صرفاً مالی هم Jens_Felez=0 دارند */
|
||||
function rowMetal(r: Record<string, unknown>) {
|
||||
if (isZeroish(r.Vazn) && isZeroish(r.TabdilVazn)) return '—';
|
||||
return asnadCell('Jens_Felez', r.Jens_Felez);
|
||||
}
|
||||
|
||||
const INVOICE_COLS = [
|
||||
{ key: 'Count', label: 'تعداد' },
|
||||
{ key: 'Vazn', label: 'وزن' },
|
||||
{ key: 'Ayar', label: 'عیار' },
|
||||
{ key: 'TabdilVazn', label: 'وزن تبدیلی' },
|
||||
{ key: 'Fi', label: 'فی' },
|
||||
{ key: 'Mazaneh', label: 'مظنه' },
|
||||
{ key: 'Mali', label: 'مالی' },
|
||||
{ key: 'Arz', label: 'ارز' },
|
||||
] as const;
|
||||
const INVOICE_HEAD_KEYS = ['Tarikh_Time', 'MCode', 'User', 'isSanadMovaghat'] as const;
|
||||
const INVOICE_SHOWN_KEYS = new Set<string>([
|
||||
...ASNAD_HEAD_KEYS,
|
||||
...INVOICE_COLS.map((c) => c.key),
|
||||
...INVOICE_HEAD_KEYS,
|
||||
'No_Kar',
|
||||
'Sharh1',
|
||||
'Jens_Felez',
|
||||
]);
|
||||
|
||||
function invoiceCols(rows: Record<string, unknown>[]) {
|
||||
return INVOICE_COLS.filter((c) => rows.some((r) => !isZeroish(r[c.key])));
|
||||
}
|
||||
|
||||
function invoiceExtra(r: Record<string, unknown>) {
|
||||
return asnadFields(r).filter((f) => !INVOICE_SHOWN_KEYS.has(f.key));
|
||||
}
|
||||
|
||||
const fetchedAtLabel = computed(() => {
|
||||
const raw = assets.value?.fetchedAt;
|
||||
@@ -90,57 +252,6 @@ const fetchedAtLabel = computed(() => {
|
||||
return `${formatJalaliDay(d)} ${formatClockFa(d)}`;
|
||||
});
|
||||
|
||||
const mandehGroups = computed(() => {
|
||||
const items = assets.value?.mandehDetail ?? [];
|
||||
const groups: Record<string, typeof items> = {
|
||||
مالی: [],
|
||||
'فلزات/وزنی': [],
|
||||
سکه: [],
|
||||
کالا: [],
|
||||
ارز: [],
|
||||
سایر: [],
|
||||
};
|
||||
for (const item of items) {
|
||||
const name = item.Name || '';
|
||||
if (/ریال|تومان|مالی|پول|وجه/i.test(name)) groups['مالی']!.push(item);
|
||||
else if (/سکه/i.test(name)) groups['سکه']!.push(item);
|
||||
else if (/ارز|دلار|یورو/i.test(name)) groups['ارز']!.push(item);
|
||||
else if (/طلا|نقره|پلاتین|وزن|آبشده|شمش/i.test(name))
|
||||
groups['فلزات/وزنی']!.push(item);
|
||||
else if (/کار|اتیکت|کالا/i.test(name)) groups['کالا']!.push(item);
|
||||
else groups['سایر']!.push(item);
|
||||
}
|
||||
return Object.entries(groups)
|
||||
.filter(([, list]) => list.length > 0)
|
||||
.map(([label, list]) => ({ label, items: list }));
|
||||
});
|
||||
|
||||
function balanceTone(v: number | null) {
|
||||
if (v == null || v === 0) return 'tone-neutral';
|
||||
return v > 0 ? 'tone-credit' : 'tone-debt';
|
||||
}
|
||||
|
||||
function balanceLabel(v: number | null) {
|
||||
if (v == null || v === 0) return '';
|
||||
return v > 0 ? 'بستانکار' : 'بدهکار';
|
||||
}
|
||||
|
||||
function formatAmt(v: number | null, digits = 3) {
|
||||
if (v == null) return '—';
|
||||
return Math.abs(v).toLocaleString('fa-IR', {
|
||||
maximumFractionDigits: digits,
|
||||
minimumFractionDigits: 0,
|
||||
});
|
||||
}
|
||||
|
||||
function formatMandehAmount(amount: string | number, unit: string) {
|
||||
const n = Number(amount);
|
||||
if (!Number.isFinite(n)) return String(amount);
|
||||
const abs = Math.abs(n);
|
||||
if (unit === 'ریال') return formatMoney(abs, 'IRR');
|
||||
return abs.toLocaleString('fa-IR', { maximumFractionDigits: 3 });
|
||||
}
|
||||
|
||||
/** تهحساب گاهی رشتهٔ "null" میفرستد؛ ?? کافی نیست */
|
||||
function dash(v: unknown, fallback = '—'): string {
|
||||
if (v == null) return fallback;
|
||||
@@ -149,6 +260,39 @@ function dash(v: unknown, fallback = '—'): string {
|
||||
return s;
|
||||
}
|
||||
|
||||
type AsnadView = 'grouped' | 'rows';
|
||||
const ASNAD_VIEW_KEY = 'mento.asnadView';
|
||||
const asnadView = ref<AsnadView>(
|
||||
localStorage.getItem(ASNAD_VIEW_KEY) === 'rows' ? 'rows' : 'grouped',
|
||||
);
|
||||
watch(asnadView, (v) => localStorage.setItem(ASNAD_VIEW_KEY, v));
|
||||
|
||||
function sanadNo(item: Record<string, unknown>) {
|
||||
return dash(item.Sh_Factor ?? item.Sh_factor, '');
|
||||
}
|
||||
|
||||
/** تهحساب هر ردیف سند را جدا برمیگرداند؛ ردیفهای همشماره و همتاریخ یک سند هستند (فقط دستهبندی، بدون محاسبه) */
|
||||
const asnadGroups = computed(() => {
|
||||
const map = new Map<string, { no: string; date: string; rows: Record<string, unknown>[] }>();
|
||||
for (const item of asnadItems.value) {
|
||||
const no = sanadNo(item);
|
||||
const date = dash(item.Tarikh, '');
|
||||
const key = `${no}|${date}`;
|
||||
let g = map.get(key);
|
||||
if (!g) {
|
||||
g = { no, date, rows: [] };
|
||||
map.set(key, g);
|
||||
}
|
||||
g.rows.push(item);
|
||||
}
|
||||
return [...map.values()].map((g) => ({
|
||||
key: `${g.no}|${g.date}`,
|
||||
no: g.no || '—',
|
||||
date: g.date || '—',
|
||||
rows: g.rows,
|
||||
}));
|
||||
});
|
||||
|
||||
/** تبدیل تاریخ محلی میلادی به شمسی YYYY-MM-DD برای API تهحساب */
|
||||
function toShamsiYmd(localIso: string): string | null {
|
||||
const d = parseLocalInput(localIso);
|
||||
@@ -488,38 +632,26 @@ onMounted(async () => {
|
||||
|
||||
<template v-if="currentLink && assets">
|
||||
<section class="summary card">
|
||||
<h3>دارایی</h3>
|
||||
<div class="balance-grid">
|
||||
<article class="balance-box" :class="balanceTone(goldBalance)">
|
||||
<span>دارایی طلایی</span>
|
||||
<strong dir="ltr">
|
||||
{{ formatAmt(goldBalance) }}
|
||||
<em>(گرم)</em>
|
||||
</strong>
|
||||
<small v-if="balanceLabel(goldBalance)">{{ balanceLabel(goldBalance) }}</small>
|
||||
</article>
|
||||
<article class="balance-box" :class="balanceTone(rialBalance)">
|
||||
<span>دارایی ریالی</span>
|
||||
<strong dir="ltr">
|
||||
{{ rialBalance != null ? formatMoney(Math.abs(rialBalance), 'IRR') : '—' }}
|
||||
</strong>
|
||||
<small v-if="balanceLabel(rialBalance)">{{ balanceLabel(rialBalance) }}</small>
|
||||
</article>
|
||||
<article
|
||||
v-if="assets.totalGoldEquiv != null"
|
||||
class="balance-box"
|
||||
:class="balanceTone(assets.totalGoldEquiv)"
|
||||
>
|
||||
<span>دارایی کل به طلا</span>
|
||||
<strong dir="ltr">
|
||||
{{ formatAmt(assets.totalGoldEquiv) }}
|
||||
<em>(گرم)</em>
|
||||
</strong>
|
||||
<small v-if="balanceLabel(assets.totalGoldEquiv)">{{
|
||||
balanceLabel(assets.totalGoldEquiv)
|
||||
}}</small>
|
||||
<h3>مانده حساب (طبق تهحساب)</h3>
|
||||
<div v-if="mandehFields?.length" class="balance-grid">
|
||||
<article v-for="f in mandehFields" :key="f.key" class="balance-box">
|
||||
<span>{{ mandehFieldLabel(f.key) }}</span>
|
||||
<strong dir="ltr">{{ showValue(f.value) }}</strong>
|
||||
</article>
|
||||
</div>
|
||||
<div v-else class="balance-grid">
|
||||
<article class="balance-box">
|
||||
<span>مانده وزنی (طلا)</span>
|
||||
<strong dir="ltr">{{ showValue(assets.goldBalance) }}</strong>
|
||||
</article>
|
||||
<article class="balance-box">
|
||||
<span>مانده مالی</span>
|
||||
<strong dir="ltr">{{ showValue(assets.rialBalance) }}</strong>
|
||||
</article>
|
||||
</div>
|
||||
<p class="muted tiny">
|
||||
اعداد دقیقاً همان مقادیر تهحساب هستند (با همان علامت مثبت/منفی) و CRM هیچ محاسبه یا تبدیلی روی آنها انجام نمیدهد.
|
||||
</p>
|
||||
<p v-if="fetchedAtLabel" class="muted tiny">آخرین دریافت: {{ fetchedAtLabel }}</p>
|
||||
</section>
|
||||
|
||||
@@ -537,31 +669,37 @@ onMounted(async () => {
|
||||
</div>
|
||||
|
||||
<div v-show="activeTab === 'مانده حساب'" class="detail">
|
||||
<template v-for="group in mandehGroups" :key="group.label">
|
||||
<div class="group-title">
|
||||
<span class="line" />
|
||||
<strong>{{ group.label }}</strong>
|
||||
<span class="line" />
|
||||
<div v-if="mandehDetailRaw?.length" class="raw-table" role="table">
|
||||
<div class="raw-tr raw-th" role="row" :style="{ '--cols': detailCols.length }">
|
||||
<span role="columnheader">عنوان</span>
|
||||
<span v-for="c in detailCols" :key="c.key" role="columnheader">{{ c.label }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, idx) in group.items"
|
||||
:key="idx"
|
||||
class="mandeh-card card"
|
||||
v-for="(d, di) in mandehDetailRaw"
|
||||
:key="di"
|
||||
class="raw-tr"
|
||||
role="row"
|
||||
:style="{ '--cols': detailCols.length }"
|
||||
>
|
||||
<span role="cell" class="raw-name">{{ d.Name }}</span>
|
||||
<span v-for="c in detailCols" :key="c.key" role="cell" dir="ltr" class="raw-num">
|
||||
{{ showValue(d[c.key]) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else-if="assets.mandehDetail?.length">
|
||||
<div v-for="(item, idx) in assets.mandehDetail" :key="idx" class="mandeh-card card">
|
||||
<div class="name">{{ item.Name }}:</div>
|
||||
<div class="vals">
|
||||
<template v-for="(v, vi) in item.values" :key="vi">
|
||||
<span :class="Number(v.amount) >= 0 ? 'credit' : 'debt'">
|
||||
{{ formatMandehAmount(v.amount, v.unit) }}
|
||||
</span>
|
||||
<span dir="ltr">{{ showValue(v.amount) }}</span>
|
||||
<span class="unit">{{ v.unit }}</span>
|
||||
<span class="st">{{ Number(v.amount) >= 0 ? 'بد' : 'بس' }}</span>
|
||||
<span v-if="vi < item.values.length - 1" class="sep">|</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<p v-if="!mandehGroups.length" class="muted">جزئیات ماندهای نیست.</p>
|
||||
<p v-else class="muted">جزئیات ماندهای نیست.</p>
|
||||
</div>
|
||||
|
||||
<div v-show="activeTab === 'جزئیات حساب'" class="asnad">
|
||||
@@ -578,7 +716,101 @@ onMounted(async () => {
|
||||
{{ asnadLoading ? '…' : 'نمایش اسناد' }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="asnad-grid">
|
||||
<div v-if="asnadItems.length" class="view-bar">
|
||||
<span class="muted">
|
||||
{{ asnadGroups.length.toLocaleString('fa-IR') }} سند ·
|
||||
{{ asnadItems.length.toLocaleString('fa-IR') }} ردیف
|
||||
</span>
|
||||
<div class="seg" role="tablist" aria-label="نحوه نمایش اسناد">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ on: asnadView === 'grouped' }"
|
||||
:aria-selected="asnadView === 'grouped'"
|
||||
@click="asnadView = 'grouped'"
|
||||
>
|
||||
هر سند یکجا
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ on: asnadView === 'rows' }"
|
||||
:aria-selected="asnadView === 'rows'"
|
||||
@click="asnadView = 'rows'"
|
||||
>
|
||||
ردیفبهردیف
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="asnadView === 'grouped'" class="asnad-groups">
|
||||
<article v-for="g in asnadGroups" :key="g.key" class="invoice card">
|
||||
<header class="inv-head">
|
||||
<div class="inv-title">
|
||||
<strong>سند شماره {{ showRaw(g.no) }}</strong>
|
||||
<span v-if="g.rows[0]?.isSanadMovaghat === true" class="inv-badge">موقت</span>
|
||||
<span class="sg-count">{{ g.rows.length.toLocaleString('fa-IR') }} ردیف</span>
|
||||
</div>
|
||||
<dl class="inv-meta">
|
||||
<div>
|
||||
<dt>تاریخ</dt>
|
||||
<dd>{{ showRaw(g.date) }}</dd>
|
||||
</div>
|
||||
<div v-for="k in INVOICE_HEAD_KEYS.filter((x) => x !== 'isSanadMovaghat' && dash(g.rows[0]?.[x], ''))" :key="k">
|
||||
<dt>{{ ASNAD_FIELD_LABELS[k] }}</dt>
|
||||
<dd>{{ asnadCell(k, g.rows[0]?.[k]) }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</header>
|
||||
|
||||
<div class="inv-table-wrap">
|
||||
<table class="inv-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th class="inv-desc">شرح</th>
|
||||
<th>جنس فلز</th>
|
||||
<th v-for="c in invoiceCols(g.rows)" :key="c.key">{{ c.label }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="(r, ri) in g.rows" :key="String(r.Factor_Code ?? r.ID ?? ri)">
|
||||
<tr>
|
||||
<td class="inv-idx">{{ showRaw(r.Radif ?? ri + 1) }}</td>
|
||||
<td class="inv-desc">
|
||||
<strong>{{ dash(r.NO) }}</strong>
|
||||
<small v-if="dash(r.No_Kar, '')">{{ dash(r.No_Kar) }}</small>
|
||||
<small v-if="dash(r.Sharh1, '')">{{ dash(r.Sharh1) }}</small>
|
||||
</td>
|
||||
<td>{{ rowMetal(r) }}</td>
|
||||
<td v-for="c in invoiceCols(g.rows)" :key="c.key" class="inv-num">
|
||||
<bdi dir="ltr">{{ asnadCell(c.key, r[c.key]) }}</bdi>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="invoiceExtra(r).length" class="inv-extra-tr">
|
||||
<td :colspan="3 + invoiceCols(g.rows).length">
|
||||
<details>
|
||||
<summary>سایر جزئیات تهحساب</summary>
|
||||
<dl class="kv">
|
||||
<div
|
||||
v-for="f in invoiceExtra(r)"
|
||||
:key="f.key"
|
||||
class="kv-item"
|
||||
:class="`k-${f.key}`"
|
||||
>
|
||||
<dt>{{ f.label }}</dt>
|
||||
<dd>{{ f.value }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div v-else class="asnad-grid">
|
||||
<article
|
||||
v-for="(item, idx) in asnadItems"
|
||||
:key="String(item.Factor_Code ?? item.ID ?? idx)"
|
||||
@@ -606,6 +838,9 @@ onMounted(async () => {
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<p v-if="asnadItems.length && asnadView === 'grouped'" class="muted tiny">
|
||||
همهٔ فیلدهایی که تهحساب برای هر ردیف برگردانده نمایش داده میشود؛ فیلدهای خالی حذف شدهاند و هیچ جمع یا تبدیلی در CRM انجام نمیشود.
|
||||
</p>
|
||||
<p v-if="!asnadLoading && !asnadItems.length" class="muted">
|
||||
در این بازه سندی یافت نشد.
|
||||
</p>
|
||||
@@ -906,12 +1141,165 @@ onMounted(async () => {
|
||||
gap: 0.65rem;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
}
|
||||
.view-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.65rem;
|
||||
}
|
||||
.seg {
|
||||
display: inline-flex;
|
||||
padding: 3px;
|
||||
gap: 3px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg);
|
||||
}
|
||||
.seg button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
padding: 0.35rem 0.75rem;
|
||||
border-radius: calc(var(--radius) - 2px);
|
||||
font: inherit;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.seg button.on {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
.asnad-groups {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
.invoice {
|
||||
display: grid;
|
||||
gap: 0.6rem;
|
||||
border-top: 3px solid var(--accent);
|
||||
}
|
||||
.inv-head {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
padding: 0.6rem 0.75rem;
|
||||
border-radius: var(--radius);
|
||||
background: color-mix(in srgb, var(--accent) 10%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
|
||||
}
|
||||
.inv-title strong {
|
||||
color: var(--accent);
|
||||
}
|
||||
.inv-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.inv-badge {
|
||||
font-size: 0.72rem;
|
||||
padding: 0.1rem 0.45rem;
|
||||
border-radius: 999px;
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
.inv-meta {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem 1.25rem;
|
||||
}
|
||||
.inv-meta div {
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
align-items: baseline;
|
||||
}
|
||||
.inv-meta dt {
|
||||
font-size: 0.76rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
.inv-meta dd {
|
||||
margin: 0;
|
||||
font-size: 0.86rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.inv-table-wrap {
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.inv-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.inv-table th {
|
||||
background: var(--bg);
|
||||
color: var(--muted);
|
||||
font-weight: 600;
|
||||
font-size: 0.76rem;
|
||||
text-align: start;
|
||||
padding: 0.45rem 0.55rem;
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.inv-table td {
|
||||
padding: 0.45rem 0.55rem;
|
||||
border-bottom: 1px solid var(--line);
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.inv-table tbody tr:last-child td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.inv-table tr:has(+ .inv-extra-tr) td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.inv-num,
|
||||
.inv-idx {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.inv-idx {
|
||||
color: var(--muted);
|
||||
width: 2rem;
|
||||
}
|
||||
.inv-table .inv-desc {
|
||||
white-space: normal;
|
||||
min-width: 180px;
|
||||
}
|
||||
.inv-desc strong {
|
||||
display: block;
|
||||
}
|
||||
.inv-desc small {
|
||||
display: block;
|
||||
color: var(--muted);
|
||||
font-size: 0.76rem;
|
||||
}
|
||||
.inv-extra-tr td {
|
||||
padding-top: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
.inv-extra-tr summary {
|
||||
cursor: pointer;
|
||||
font-size: 0.76rem;
|
||||
color: var(--muted);
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
.sg-count {
|
||||
margin-inline-start: auto;
|
||||
font-size: 0.78rem;
|
||||
padding: 0.1rem 0.5rem;
|
||||
border-radius: 999px;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
.sc-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.4rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.sc-row {
|
||||
display: flex;
|
||||
@@ -928,6 +1316,63 @@ onMounted(async () => {
|
||||
.sc-row.cash {
|
||||
background: color-mix(in srgb, #16a34a 12%, transparent);
|
||||
}
|
||||
.kv {
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 0.35rem;
|
||||
}
|
||||
.kv-item {
|
||||
min-width: 0;
|
||||
padding: 0.3rem 0.45rem;
|
||||
border-radius: calc(var(--radius) - 2px);
|
||||
background: var(--bg);
|
||||
}
|
||||
.kv dt {
|
||||
font-size: 0.72rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
.kv dd {
|
||||
margin: 0;
|
||||
font-size: 0.86rem;
|
||||
font-weight: 600;
|
||||
overflow-wrap: anywhere;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.kv-item.k-Sharh1,
|
||||
.kv-item.k-Sharh2,
|
||||
.kv-item.k-Factor_Code {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.raw-table {
|
||||
display: grid;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
overflow-x: auto;
|
||||
}
|
||||
.raw-tr {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(150px, 1.4fr) repeat(var(--cols), minmax(110px, 1fr));
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.7rem;
|
||||
border-top: 1px solid var(--line);
|
||||
font-size: 0.88rem;
|
||||
align-items: center;
|
||||
}
|
||||
.raw-th {
|
||||
border-top: 0;
|
||||
background: var(--bg);
|
||||
color: var(--muted);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.raw-name {
|
||||
font-weight: 600;
|
||||
}
|
||||
.raw-num {
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.sanad-form {
|
||||
display: grid;
|
||||
gap: 0.65rem;
|
||||
|
||||
Reference in New Issue
Block a user