fix(tahesab): polish sales report page chrome
Match leads/invoices header layout, keep title clean, and add a clear apply-filters action for the date range.
This commit is contained in:
@@ -1,8 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
import { theme } from 'ant-design-vue';
|
||||||
import type { TableColumnsType, TablePaginationConfig } from 'ant-design-vue';
|
import type { TableColumnsType, TablePaginationConfig } from 'ant-design-vue';
|
||||||
import { ReloadOutlined, SyncOutlined } from '@ant-design/icons-vue';
|
import {
|
||||||
|
ArrowRightOutlined,
|
||||||
|
FilterOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
SyncOutlined,
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
import {
|
import {
|
||||||
TAHESAB_ASNAD_KIND_LABELS,
|
TAHESAB_ASNAD_KIND_LABELS,
|
||||||
TAHESAB_ASNAD_KINDS,
|
TAHESAB_ASNAD_KINDS,
|
||||||
@@ -28,6 +34,7 @@ import { useCustomerGroups } from '@/lib/customer-groups';
|
|||||||
import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { token } = theme.useToken();
|
||||||
const { groups: customerGroups, load: loadCustomerGroups } = useCustomerGroups();
|
const { groups: customerGroups, load: loadCustomerGroups } = useCustomerGroups();
|
||||||
|
|
||||||
const connections = ref<TahesabConnectionDto[]>([]);
|
const connections = ref<TahesabConnectionDto[]>([]);
|
||||||
@@ -87,7 +94,9 @@ function metalLabel(v: number | null) {
|
|||||||
|
|
||||||
function formatNum(n: number | null | undefined) {
|
function formatNum(n: number | null | undefined) {
|
||||||
if (n == null) return '—';
|
if (n == null) return '—';
|
||||||
return toFaDigits(new Intl.NumberFormat('fa-IR', { maximumFractionDigits: 3 }).format(n));
|
return toFaDigits(
|
||||||
|
new Intl.NumberFormat('fa-IR', { maximumFractionDigits: 3 }).format(n),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatMali(n: number | null | undefined) {
|
function formatMali(n: number | null | undefined) {
|
||||||
@@ -96,7 +105,13 @@ function formatMali(n: number | null | undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const columns: TableColumnsType<TahesabAsnadLineDto> = [
|
const columns: TableColumnsType<TahesabAsnadLineDto> = [
|
||||||
{ title: 'تاریخ', dataIndex: 'tarikh', key: 'tarikh', width: 110 },
|
{
|
||||||
|
title: 'تاریخ',
|
||||||
|
dataIndex: 'tarikh',
|
||||||
|
key: 'tarikh',
|
||||||
|
width: 110,
|
||||||
|
customRender: ({ text }) => toFaDigits(String(text ?? '')),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'شخص',
|
title: 'شخص',
|
||||||
key: 'person',
|
key: 'person',
|
||||||
@@ -162,6 +177,16 @@ const filterParams = computed(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const lastSyncedLabel = computed(() => {
|
||||||
|
const raw = summary.value?.lastSyncedAt;
|
||||||
|
if (!raw) return null;
|
||||||
|
try {
|
||||||
|
return toFaDigits(new Date(raw).toLocaleString('fa-IR'));
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function loadConnections() {
|
async function loadConnections() {
|
||||||
const { data } = await api.get<TahesabConnectionDto[]>(
|
const { data } = await api.get<TahesabConnectionDto[]>(
|
||||||
'/integrations/tahesab/connections/public',
|
'/integrations/tahesab/connections/public',
|
||||||
@@ -263,7 +288,7 @@ async function pollJob(jobId: string) {
|
|||||||
async function startSync() {
|
async function startSync() {
|
||||||
const params = filterParams.value;
|
const params = filterParams.value;
|
||||||
if (!params) {
|
if (!params) {
|
||||||
error.value = 'تاریخ از و تا را انتخاب کنید.';
|
error.value = 'ابتدا بازه تاریخ را مشخص کنید، بعد بهروزرسانی را بزنید.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
syncing.value = true;
|
syncing.value = true;
|
||||||
@@ -289,6 +314,11 @@ async function startSync() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyFilters() {
|
||||||
|
page.value = 1;
|
||||||
|
void loadReport();
|
||||||
|
}
|
||||||
|
|
||||||
function onTableChange(pag: TablePaginationConfig) {
|
function onTableChange(pag: TablePaginationConfig) {
|
||||||
page.value = pag.current ?? 1;
|
page.value = pag.current ?? 1;
|
||||||
pageSize.value = pag.pageSize ?? 50;
|
pageSize.value = pag.pageSize ?? 50;
|
||||||
@@ -313,190 +343,230 @@ onUnmounted(() => stopPoll());
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a-flex vertical :gap="16">
|
<a-row :gutter="[24, 24]">
|
||||||
<a-row :gutter="[16, 16]" align="middle" justify="space-between">
|
<a-col :span="24">
|
||||||
<a-col :xs="24" :md="14">
|
<a-flex
|
||||||
<a-typography-title :level="3" style="margin: 0">
|
justify="space-between"
|
||||||
گزارش فروش تهحساب
|
align="center"
|
||||||
</a-typography-title>
|
wrap="wrap"
|
||||||
<a-typography-text type="secondary">
|
:gap="token.marginSM"
|
||||||
اسنپشات اسناد مشتریان لینکشده — طبقهبندی فروش/مرجوعی تقریبی است و بعد از بازخورد کارفرما اصلاح میشود.
|
>
|
||||||
</a-typography-text>
|
<div>
|
||||||
</a-col>
|
<a-typography-title :level="3" :style="{ margin: 0 }">
|
||||||
<a-col :xs="24" :md="10">
|
گزارش فروش تهحساب
|
||||||
<a-flex justify="flex-end" wrap="wrap" :gap="8">
|
</a-typography-title>
|
||||||
<a-button @click="router.push({ name: 'reports' })">بازگشت به گزارشها</a-button>
|
<a-typography-text type="secondary">
|
||||||
<a-button :loading="loading" @click="loadReport">
|
فروش و مرجوعی مشتریان لینکشده بر اساس بازه تاریخ
|
||||||
<template #icon><ReloadOutlined /></template>
|
</a-typography-text>
|
||||||
نمایش
|
</div>
|
||||||
|
<a-space :size="8" wrap>
|
||||||
|
<a-button @click="router.push({ name: 'reports' })">
|
||||||
|
<template #icon><ArrowRightOutlined /></template>
|
||||||
|
بازگشت
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" :loading="syncing" @click="startSync">
|
<a-button type="primary" :loading="syncing" @click="startSync">
|
||||||
<template #icon><SyncOutlined /></template>
|
<template #icon><SyncOutlined /></template>
|
||||||
بهروزرسانی از تهحساب
|
بهروزرسانی از تهحساب
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-flex>
|
</a-space>
|
||||||
</a-col>
|
</a-flex>
|
||||||
</a-row>
|
</a-col>
|
||||||
|
|
||||||
<a-alert v-if="error" type="error" show-icon :message="error" closable @close="error = ''" />
|
<a-col v-if="error" :span="24">
|
||||||
<a-alert v-if="syncHint" type="info" show-icon :message="syncHint" />
|
<a-alert type="error" show-icon :message="error" closable @close="error = ''" />
|
||||||
|
</a-col>
|
||||||
|
<a-col v-if="syncHint" :span="24">
|
||||||
|
<a-alert type="info" show-icon :message="syncHint" />
|
||||||
|
</a-col>
|
||||||
|
|
||||||
<a-card :bordered="false" title="فیلترها">
|
<a-col :span="24">
|
||||||
<a-form layout="vertical">
|
<a-card :bordered="false">
|
||||||
<a-row :gutter="[16, 8]">
|
<template #title>
|
||||||
<a-col :xs="24" :sm="12" :md="8" :lg="6">
|
<a-space :size="8">
|
||||||
<a-form-item label="اتصال تهحساب">
|
<FilterOutlined />
|
||||||
<a-select
|
<span>فیلترها</span>
|
||||||
v-model:value="connectionId"
|
</a-space>
|
||||||
:options="connections.map((c) => ({ value: c.id, label: c.label }))"
|
</template>
|
||||||
placeholder="اتصال"
|
<a-form layout="vertical" @finish="applyFilters">
|
||||||
allow-clear
|
<a-row :gutter="[16, 8]">
|
||||||
/>
|
<a-col :xs="24" :sm="12" :md="8" :lg="6">
|
||||||
</a-form-item>
|
<a-form-item label="اتصال">
|
||||||
</a-col>
|
<a-select
|
||||||
<a-col :xs="24" :sm="12" :md="8" :lg="6">
|
v-model:value="connectionId"
|
||||||
<a-form-item label="از تاریخ">
|
:options="
|
||||||
<JalaliDateInput v-model="fromDate" placeholder="از" />
|
connections.map((c) => ({ value: c.id, label: c.label }))
|
||||||
</a-form-item>
|
"
|
||||||
</a-col>
|
placeholder="اتصال تهحساب"
|
||||||
<a-col :xs="24" :sm="12" :md="8" :lg="6">
|
allow-clear
|
||||||
<a-form-item label="تا تاریخ">
|
/>
|
||||||
<JalaliDateInput v-model="toDate" placeholder="تا" />
|
</a-form-item>
|
||||||
</a-form-item>
|
</a-col>
|
||||||
</a-col>
|
<a-col :xs="24" :sm="12" :md="8" :lg="6">
|
||||||
<a-col :xs="24" :sm="12" :md="8" :lg="6">
|
<a-form-item label="از تاریخ" required>
|
||||||
<a-form-item label="گروه مشتری">
|
<JalaliDateInput v-model="fromDate" placeholder="از تاریخ" />
|
||||||
<a-select
|
</a-form-item>
|
||||||
v-model:value="groupIds"
|
</a-col>
|
||||||
mode="multiple"
|
<a-col :xs="24" :sm="12" :md="8" :lg="6">
|
||||||
allow-clear
|
<a-form-item label="تا تاریخ" required>
|
||||||
placeholder="همه گروهها"
|
<JalaliDateInput v-model="toDate" placeholder="تا تاریخ" />
|
||||||
:options="(customerGroups as CustomerGroupDto[]).map((g) => ({ value: g.id, label: g.name }))"
|
</a-form-item>
|
||||||
:max-tag-count="2"
|
</a-col>
|
||||||
/>
|
<a-col :xs="24" :sm="12" :md="8" :lg="6">
|
||||||
</a-form-item>
|
<a-form-item label="گروه مشتری">
|
||||||
</a-col>
|
<a-select
|
||||||
<a-col :xs="24" :sm="12" :md="8" :lg="6">
|
v-model:value="groupIds"
|
||||||
<a-form-item label="نوع سند">
|
mode="multiple"
|
||||||
<a-select
|
allow-clear
|
||||||
v-model:value="kinds"
|
placeholder="همه گروهها"
|
||||||
mode="multiple"
|
:options="
|
||||||
:options="kindOptions"
|
(customerGroups as CustomerGroupDto[]).map((g) => ({
|
||||||
placeholder="نوع"
|
value: g.id,
|
||||||
:max-tag-count="2"
|
label: g.name,
|
||||||
/>
|
}))
|
||||||
</a-form-item>
|
"
|
||||||
</a-col>
|
:max-tag-count="2"
|
||||||
<a-col :xs="24" :sm="12" :md="8" :lg="6">
|
/>
|
||||||
<a-form-item label="فلز">
|
</a-form-item>
|
||||||
<a-select
|
</a-col>
|
||||||
v-model:value="jensFelez"
|
<a-col :xs="24" :sm="12" :md="8" :lg="6">
|
||||||
allow-clear
|
<a-form-item label="نوع سند">
|
||||||
placeholder="همه"
|
<a-select
|
||||||
:options="metalOptions"
|
v-model:value="kinds"
|
||||||
/>
|
mode="multiple"
|
||||||
</a-form-item>
|
:options="kindOptions"
|
||||||
</a-col>
|
placeholder="فروش / مرجوعی / …"
|
||||||
<a-col :xs="24" :sm="12" :md="8" :lg="6">
|
:max-tag-count="2"
|
||||||
<a-form-item label="شخص">
|
/>
|
||||||
<a-select
|
</a-form-item>
|
||||||
v-model:value="personId"
|
</a-col>
|
||||||
show-search
|
<a-col :xs="24" :sm="12" :md="8" :lg="6">
|
||||||
allow-clear
|
<a-form-item label="فلز">
|
||||||
placeholder="جستجوی نام…"
|
<a-select
|
||||||
:filter-option="false"
|
v-model:value="jensFelez"
|
||||||
:options="personOptions"
|
allow-clear
|
||||||
@search="searchPeople"
|
placeholder="همه"
|
||||||
/>
|
:options="metalOptions"
|
||||||
</a-form-item>
|
/>
|
||||||
</a-col>
|
</a-form-item>
|
||||||
</a-row>
|
</a-col>
|
||||||
</a-form>
|
<a-col :xs="24" :sm="12" :md="8" :lg="6">
|
||||||
</a-card>
|
<a-form-item label="شخص">
|
||||||
|
<a-select
|
||||||
|
v-model:value="personId"
|
||||||
|
show-search
|
||||||
|
allow-clear
|
||||||
|
placeholder="جستجوی نام…"
|
||||||
|
:filter-option="false"
|
||||||
|
:options="personOptions"
|
||||||
|
@search="searchPeople"
|
||||||
|
>
|
||||||
|
<template #suffixIcon><SearchOutlined /></template>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-flex justify="space-between" align="center" wrap="wrap" :gap="8">
|
||||||
|
<a-typography-text v-if="lastSyncedLabel" type="secondary">
|
||||||
|
آخرین همگامسازی: {{ lastSyncedLabel }}
|
||||||
|
</a-typography-text>
|
||||||
|
<a-typography-text v-else type="secondary">
|
||||||
|
بعد از انتخاب بازه، فیلتر را اعمال کنید یا از تهحساب بهروز کنید.
|
||||||
|
</a-typography-text>
|
||||||
|
<a-button type="primary" html-type="submit" :loading="loading">
|
||||||
|
اعمال فیلتر
|
||||||
|
</a-button>
|
||||||
|
</a-flex>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
<a-row v-if="summary" :gutter="[16, 16]">
|
<a-col v-if="summary" :xs="24" :sm="8">
|
||||||
<a-col :xs="24" :sm="8">
|
<a-card :bordered="false">
|
||||||
<a-card :bordered="false">
|
<a-statistic
|
||||||
<a-statistic title="تعداد ردیف" :value="summary.totalLines" />
|
title="تعداد ردیف"
|
||||||
</a-card>
|
:value="summary.totalLines"
|
||||||
</a-col>
|
:formatter="(val) => toFaDigits(String(val))"
|
||||||
<a-col :xs="24" :sm="8">
|
/>
|
||||||
<a-card :bordered="false">
|
</a-card>
|
||||||
<a-statistic title="جمع مبلغ" :value="formatMali(summary.maliSum)" />
|
</a-col>
|
||||||
</a-card>
|
<a-col v-if="summary" :xs="24" :sm="8">
|
||||||
</a-col>
|
<a-card :bordered="false">
|
||||||
<a-col :xs="24" :sm="8">
|
<a-statistic title="جمع مبلغ" :value="formatMali(summary.maliSum)" />
|
||||||
<a-card :bordered="false">
|
</a-card>
|
||||||
<a-statistic title="جمع وزن" :value="formatNum(summary.vaznSum)" />
|
</a-col>
|
||||||
</a-card>
|
<a-col v-if="summary" :xs="24" :sm="8">
|
||||||
</a-col>
|
<a-card :bordered="false">
|
||||||
</a-row>
|
<a-statistic title="جمع وزن" :value="formatNum(summary.vaznSum)" />
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
<a-row v-if="summary?.byKind?.length" :gutter="[16, 16]">
|
<a-col v-if="summary?.byKind?.length" :xs="24" :lg="12">
|
||||||
<a-col :xs="24" :lg="12">
|
<a-card :bordered="false" title="به تفکیک نوع">
|
||||||
<a-card :bordered="false" title="به تفکیک نوع">
|
<a-list :data-source="summary.byKind" item-layout="horizontal">
|
||||||
<a-list
|
<template #renderItem="{ item }">
|
||||||
:data-source="summary.byKind"
|
<a-list-item>
|
||||||
item-layout="horizontal"
|
<a-list-item-meta :title="item.label">
|
||||||
size="small"
|
<template #description>
|
||||||
>
|
{{ toFaDigits(String(item.lineCount)) }} ردیف ·
|
||||||
<template #renderItem="{ item }">
|
{{ formatMali(item.maliSum) }} · وزن {{ formatNum(item.vaznSum) }}
|
||||||
<a-list-item>
|
</template>
|
||||||
<a-list-item-meta :title="item.label">
|
</a-list-item-meta>
|
||||||
<template #description>
|
</a-list-item>
|
||||||
{{ toFaDigits(String(item.lineCount)) }} ردیف ·
|
</template>
|
||||||
{{ formatMali(item.maliSum) }} · وزن {{ formatNum(item.vaznSum) }}
|
</a-list>
|
||||||
</template>
|
</a-card>
|
||||||
</a-list-item-meta>
|
</a-col>
|
||||||
</a-list-item>
|
<a-col v-if="summary?.byGroup?.length" :xs="24" :lg="12">
|
||||||
</template>
|
<a-card :bordered="false" title="به تفکیک گروه">
|
||||||
</a-list>
|
<a-list
|
||||||
</a-card>
|
:data-source="summary.byGroup.slice(0, 8)"
|
||||||
</a-col>
|
item-layout="horizontal"
|
||||||
<a-col :xs="24" :lg="12">
|
>
|
||||||
<a-card :bordered="false" title="به تفکیک گروه">
|
<template #renderItem="{ item }">
|
||||||
<a-list
|
<a-list-item>
|
||||||
:data-source="summary.byGroup.slice(0, 8)"
|
<a-list-item-meta :title="item.label">
|
||||||
item-layout="horizontal"
|
<template #description>
|
||||||
size="small"
|
{{ toFaDigits(String(item.lineCount)) }} ردیف ·
|
||||||
>
|
{{ formatMali(item.maliSum) }}
|
||||||
<template #renderItem="{ item }">
|
</template>
|
||||||
<a-list-item>
|
</a-list-item-meta>
|
||||||
<a-list-item-meta :title="item.label">
|
</a-list-item>
|
||||||
<template #description>
|
</template>
|
||||||
{{ toFaDigits(String(item.lineCount)) }} ردیف ·
|
</a-list>
|
||||||
{{ formatMali(item.maliSum) }}
|
</a-card>
|
||||||
</template>
|
</a-col>
|
||||||
</a-list-item-meta>
|
|
||||||
</a-list-item>
|
|
||||||
</template>
|
|
||||||
</a-list>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
|
|
||||||
<a-card v-if="summary?.distinctNos?.length" :bordered="false" title="مقادیر خام NO در این بازه">
|
<a-col v-if="summary?.distinctNos?.length" :span="24">
|
||||||
<a-space wrap>
|
<a-card :bordered="false" title="انواع سند خام (NO)">
|
||||||
<a-tag v-for="no in summary.distinctNos" :key="no">{{ no }}</a-tag>
|
<a-space wrap>
|
||||||
</a-space>
|
<a-tag v-for="no in summary.distinctNos" :key="no">{{ no }}</a-tag>
|
||||||
</a-card>
|
</a-space>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
<a-card :bordered="false" title="ردیفها">
|
<a-col :span="24">
|
||||||
<a-table
|
<a-card :bordered="false" title="ردیفهای گزارش">
|
||||||
:columns="columns"
|
<a-table
|
||||||
:data-source="report?.items ?? []"
|
:columns="columns"
|
||||||
:loading="loading"
|
:data-source="report?.items ?? []"
|
||||||
:pagination="{
|
:loading="loading"
|
||||||
current: page,
|
:pagination="{
|
||||||
pageSize,
|
current: page,
|
||||||
total: report?.total ?? 0,
|
pageSize,
|
||||||
showSizeChanger: true,
|
total: report?.total ?? 0,
|
||||||
pageSizeOptions: ['20', '50', '100'],
|
showSizeChanger: true,
|
||||||
}"
|
showTotal: (total) => `${toFaDigits(String(total))} ردیف`,
|
||||||
row-key="id"
|
pageSizeOptions: ['20', '50', '100'],
|
||||||
:scroll="{ x: 1100 }"
|
}"
|
||||||
@change="onTableChange"
|
row-key="id"
|
||||||
/>
|
:scroll="{ x: 1100 }"
|
||||||
</a-card>
|
@change="onTableChange"
|
||||||
</a-flex>
|
>
|
||||||
|
<template #emptyText>
|
||||||
|
<a-empty description="برای این بازه ردیفی نیست. بازه را اعمال کنید یا از تهحساب بهروز کنید." />
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user