From 218d3e9f5eb1a60f995cee573d4b7c8fd67c416a Mon Sep 17 00:00:00 2001 From: adonis Date: Sun, 28 Jun 2026 21:48:17 +0330 Subject: [PATCH] fixed ui and some bugs --- components.d.ts | 1 + src/api/accounting.ts | 2 +- src/api/tahesab.ts | 56 ++ src/components/TahesabRetryModal.vue | 14 +- src/model/accounting.ts | 2 +- src/model/product.ts | 1 + .../components/AccountingMobileCardList.vue | 14 +- .../AccountingMobileDetailSheet.vue | 41 +- .../components/AccountingMobileListItem.vue | 30 +- .../components/TahesabCustomerSync.vue | 823 ++++++++++++++++++ .../components/TahesabMobileCardList.vue | 79 ++ .../components/TahesabMobileDetailSheet.vue | 80 ++ .../components/TahesabMobileListItem.vue | 46 + .../menuPages/accounting/components/table.vue | 135 +-- .../accounting/components/tahesabTable.vue | 119 ++- .../accounting/hooks/accountingColumns.tsx | 143 +-- src/pages/menuPages/accounting/index.vue | 34 +- .../accounting/utils/tahesabStatus.ts | 12 + .../menuPages/configuration/SettingCard.vue | 11 +- .../configuration/SettingValueField.vue | 18 + .../menuPages/configuration/settingGroups.ts | 8 +- .../menuPages/configuration/settingTypes.ts | 47 +- .../customers/base/components/table.vue | 4 +- .../base/components/ProductEditorFormBody.vue | 23 +- src/pages/menuPages/products/base/index.vue | 2 + 25 files changed, 1374 insertions(+), 371 deletions(-) create mode 100644 src/pages/menuPages/accounting/components/TahesabCustomerSync.vue create mode 100644 src/pages/menuPages/accounting/components/TahesabMobileCardList.vue create mode 100644 src/pages/menuPages/accounting/components/TahesabMobileDetailSheet.vue create mode 100644 src/pages/menuPages/accounting/components/TahesabMobileListItem.vue create mode 100644 src/pages/menuPages/accounting/utils/tahesabStatus.ts diff --git a/components.d.ts b/components.d.ts index 4b61d74..8096755 100644 --- a/components.d.ts +++ b/components.d.ts @@ -53,6 +53,7 @@ declare module '@vue/runtime-core' { ATabPane: typeof import('ant-design-vue/es')['TabPane'] ATabs: typeof import('ant-design-vue/es')['Tabs'] ATextarea: typeof import('ant-design-vue/es')['Textarea'] + ATimePicker: typeof import('ant-design-vue/es')['TimePicker'] ATooltip: typeof import('ant-design-vue/es')['Tooltip'] AUpload: typeof import('ant-design-vue/es')['Upload'] BaseButton: typeof import('./src/core/components/BaseButton/index.ts')['default'] diff --git a/src/api/accounting.ts b/src/api/accounting.ts index f2d81ef..01fb275 100644 --- a/src/api/accounting.ts +++ b/src/api/accounting.ts @@ -5,6 +5,6 @@ import type { Accounting, TotalAccounting } from '@/model/accounting' export const getAccountingTable = async (start: string,end: string) => await useAxios.post('/admin/dashboard/finance', {startDate: start, endDate: end}); -//Accounting Tab +//Accounting Tab — دفترکل داخلی، مستقل از هر سیستم حسابداری بیرونی export const getTotalAccountingTable = async (params: TableInput) => await useAxios.post('/admin/finance', params) \ No newline at end of file diff --git a/src/api/tahesab.ts b/src/api/tahesab.ts index eba759d..526634a 100644 --- a/src/api/tahesab.ts +++ b/src/api/tahesab.ts @@ -34,3 +34,59 @@ export const registerCustomerInTahesab = async (customerId: number) => await useAxios.post<{ ok: boolean; message: string; moshtari_code?: string | number }>( `/admin/customer/${customerId}/tahesab/register` ) + +export interface PanelOnlyCustomer { + id: number + name: string + mobile: string + created_at: string +} + +export interface TahesabOnlyCustomer { + code: number + name: string + tel: string + gid: number | null + group: string +} + +export interface CustomerSyncStatus { + panel_total: number + panel_registered: number + panel_only: number + tahesab_total: number + tahesab_only_count: number + panel_only_list: { + data: PanelOnlyCustomer[] + current_page: number + last_page: number + total: number + } + tahesab_only_list: { + data: TahesabOnlyCustomer[] + current_page: number + last_page: number + total: number + } +} + +export const getCustomerSyncStatus = async (params?: { + search?: string + page?: number + tahesab_page?: number +}) => await useAxios.get('/admin/tahesab/customer-sync/status', { params }) + +export interface SyncPanelResult { + ok: boolean + success: number + failed: number + errors: { name: string; mobile: string; error: string }[] +} + +export const syncPanelToTahesab = async () => + await useAxios.post('/admin/tahesab/customer-sync/panel-to-tahesab') + +export const importFromTahesab = async (customers: TahesabOnlyCustomer[]) => + await useAxios.post('/admin/tahesab/customer-sync/import-from-tahesab', { + customers, + }) diff --git a/src/components/TahesabRetryModal.vue b/src/components/TahesabRetryModal.vue index 339e547..45758b9 100644 --- a/src/components/TahesabRetryModal.vue +++ b/src/components/TahesabRetryModal.vue @@ -13,6 +13,7 @@ {{ item.label }} {{ item.value ?? '-' }} +
{{ detail.res }}
{{ logText }}
@@ -23,6 +24,7 @@ import { computed, ref, watch } from 'vue' import BasicModal from '@/components/BasicModal.vue' import { message } from 'ant-design-vue/es' import { getTahesabDetail, retryTahesabSanad, registerCustomerInTahesab, type TahesabRecord } from '@/api/tahesab' +import { tahesabStatusLabel } from '@/pages/menuPages/accounting/utils/tahesabStatus' const props = defineProps<{ modelValue: boolean @@ -49,7 +51,9 @@ const title = computed(() => props.mode === 'register-customer' ? 'ثبت مشتری در ته‌حساب' : 'ارسال مجدد سند به ته‌حساب' ) -const confirmText = computed(() => (props.mode === 'register-customer' ? 'ثبت در ته‌حساب' : 'ارسال به ته‌حساب')) +const confirmText = computed(() => + props.mode === 'register-customer' ? 'ثبت در ته‌حساب' : 'ارسال مجدد' +) const logText = computed(() => detail.value?.sync_log || '') @@ -69,17 +73,11 @@ const items = computed(() => { { label: 'نوع', value: detail.value?.type }, { label: 'مقدار', value: detail.value?.amount }, { label: 'شرح', value: detail.value?.desc }, - { label: 'وضعیت', value: statusLabel(detail.value?.sync_status) }, + { label: 'وضعیت', value: tahesabStatusLabel(detail.value?.sync_status, detail.value?.tahesab_id) }, { label: 'کد ته‌حساب', value: detail.value?.tahesab_id } ] }) -function statusLabel(status?: string) { - if (status === 'success') return 'موفق' - if (status === 'failed') return 'ناموفق' - return 'در انتظار' -} - watch( () => [props.modelValue, props.tahesabId] as const, async ([open, id]) => { diff --git a/src/model/accounting.ts b/src/model/accounting.ts index 251f819..1b6f9ca 100644 --- a/src/model/accounting.ts +++ b/src/model/accounting.ts @@ -23,7 +23,7 @@ export interface AccountingCustomerRef { export interface TotalAccounting { id: number customer_id: number - order_id: null + order_id: number | null customer_to_id: number user_id: number wallet: null diff --git a/src/model/product.ts b/src/model/product.ts index 38d702c..df0c7c7 100644 --- a/src/model/product.ts +++ b/src/model/product.ts @@ -25,6 +25,7 @@ export interface Product{ show: boolean show_in_assets?: boolean trade_by_quantity?: boolean + override_trading_hours?: boolean use_last_price_if_zero?: boolean title: string updated_at: string diff --git a/src/pages/menuPages/accounting/components/AccountingMobileCardList.vue b/src/pages/menuPages/accounting/components/AccountingMobileCardList.vue index 9232019..f9d0fc5 100644 --- a/src/pages/menuPages/accounting/components/AccountingMobileCardList.vue +++ b/src/pages/menuPages/accounting/components/AccountingMobileCardList.vue @@ -16,12 +16,7 @@ /> - + diff --git a/src/pages/menuPages/accounting/components/AccountingMobileDetailSheet.vue b/src/pages/menuPages/accounting/components/AccountingMobileDetailSheet.vue index 1a03317..c44f5df 100644 --- a/src/pages/menuPages/accounting/components/AccountingMobileDetailSheet.vue +++ b/src/pages/menuPages/accounting/components/AccountingMobileDetailSheet.vue @@ -1,32 +1,13 @@ diff --git a/src/pages/menuPages/accounting/components/tahesabTable.vue b/src/pages/menuPages/accounting/components/tahesabTable.vue index a13bf6e..00893fa 100644 --- a/src/pages/menuPages/accounting/components/tahesabTable.vue +++ b/src/pages/menuPages/accounting/components/tahesabTable.vue @@ -5,43 +5,79 @@ + + - + +
-
+
{{ item.label }} {{ item.value ?? '-' }}
+
+ {{ selected.res }} +
{{ selected.sync_log }}
+ + + +
@@ -54,15 +90,26 @@ import Icon from '@core/components/BaseIcon/src/Icon.vue' import { isMobile } from '@core/utils/is' import BasicModal from '@/components/BasicModal.vue' import TahesabRetryModal from '@/components/TahesabRetryModal.vue' +import TahesabMobileCardList from '@/pages/menuPages/accounting/components/TahesabMobileCardList.vue' +import { tahesabStatusLabel } from '@/pages/menuPages/accounting/utils/tahesabStatus' import { toJalali } from '@core/utils/useDataTime' const [submit, reload] = useBaseTable() -const detailVisible = ref(false) -const retryVisible = ref(false) +const mobileListRef = ref Promise }>(null) +const infoVisible = ref(false) +const sendVisible = ref(false) +const registerVisible = ref(false) const selected = ref(null) const columns = [ - { title: 'ردیف', dataIndex: 'id', key: 'id', filterable: false, sortable: false, customRender: (r: any) => r.index + 1 }, + { + title: 'ردیف', + dataIndex: 'id', + key: 'id', + filterable: false, + 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' }, @@ -72,7 +119,8 @@ const columns = [ dataIndex: 'sync_status', key: 'sync_status', type: 'Text', - customRender: (r: any) => statusLabel(r.record.sync_status, r.record.tahesab_id) + filterable: false, + customRender: (r: any) => tahesabStatusLabel(r.record.sync_status, r.record.tahesab_id) }, { title: 'زمان', @@ -81,7 +129,13 @@ const columns = [ type: 'Text', customRender: (r: any) => toJalali(r.record.created_at) }, - { title: 'عملیات', dataIndex: 'action', key: 'action', slot: 'action', filterable: false } + { + title: 'عملیات', + dataIndex: 'action', + key: 'action', + slot: 'action', + filterable: false + } ] const tblProps = ref({ @@ -89,44 +143,43 @@ const tblProps = ref({ api: (params: TableInput) => getTahesabTable(params) }) -const detailItems = computed(() => { +/** اطلاعاتی که برای هر سند حسابداری نیاز است (بدون جزئیات ته‌حساب‌محور) */ +const infoItems = computed(() => { const row = selected.value if (!row) return [] return [ - { label: 'شناسه', value: row.id }, + { 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: statusLabel(row.sync_status, row.tahesab_id) }, - { label: 'کد ته‌حساب', value: row.tahesab_id }, - { label: 'پاسخ API', value: row.res } + { label: 'زمان ثبت', value: row.created_at ? toJalali(row.created_at) : '-' }, + { label: 'وضعیت', value: tahesabStatusLabel(row.sync_status, row.tahesab_id) }, + { label: 'کد ته‌حساب', value: row.tahesab_id || 'ثبت نشده' } ] }) -function statusLabel(status?: string, tahesabId?: string | null) { - if (tahesabId) return 'موفق' - if (status === 'failed') return 'ناموفق' - if (status === 'success') return 'موفق' - return 'در انتظار' -} - -function canRetry(record: TahesabRecord) { - return !record.tahesab_id || record.sync_status === 'failed' -} - -function openDetail(record: TahesabRecord) { +function openInfo(record: TahesabRecord) { selected.value = record - detailVisible.value = true + infoVisible.value = true } -function openRetry(record: TahesabRecord) { +function openSend(record: TahesabRecord) { selected.value = record - retryVisible.value = true + sendVisible.value = true +} + +function openRegister(record: TahesabRecord) { + selected.value = record + registerVisible.value = true } async function fetch() { + if (isMobile.value) { + await mobileListRef.value?.fetch?.() + return + } await reload() } diff --git a/src/pages/menuPages/accounting/hooks/accountingColumns.tsx b/src/pages/menuPages/accounting/hooks/accountingColumns.tsx index 2deccce..f3fe7f6 100644 --- a/src/pages/menuPages/accounting/hooks/accountingColumns.tsx +++ b/src/pages/menuPages/accounting/hooks/accountingColumns.tsx @@ -1,106 +1,51 @@ import type { BaseTableColumn } from '@core/type/baseTable' -import { toJalali, toJalaliNoTime, toTime } from '@core/utils/useDataTime' -import CustomTag from '@/components/CustomTag.vue' -import { usePrice } from '@core/utils/usePrice' +import { toJalali } from '@core/utils/useDataTime' import { metalLabel } from '@/utils/adminMetalLabels' -/*import BudgetSelect from '@/components/BudgetSelect.vue' -import { useStaticApiListsStore } from '@/store/staticApisStore' -import type { FilterSelect } from '@/model/filterSelect' -import type { DocItem } from '@/model/docType'*/ -//define store -//const store = useStaticApiListsStore() -//document types -/*let docTypes: DocItem[]*/ -/*onMounted(async () => { - docTypes = await store.readyDocTypeList() -}) -const filteredDocTypes: FilterSelect = docTypes?.map((x: DocItem) => { - return { - label: x.title, - value: x.id - } -})*/ - -export function getAccountingColumns(showTahesab = false): BaseTableColumn[] { +/** + * ستون‌های دفترکل (گزارشات) — مستقل از هر سیستم حسابداری بیرونی. + * هیچ ستون مربوط به ته‌حساب یا سایر حسابداری‌ها اینجا نیست. + */ +export function getAccountingColumns(): BaseTableColumn[] { return [ - { - title: 'ردیف', - dataIndex: 'id', - key: 'id', - filterable: false, - sortable: false, - customRender: (record: any) => { - return record.index + 1 + { + title: 'ردیف', + dataIndex: 'id', + key: 'id', + filterable: false, + sortable: false, + customRender: (record: any) => record.index + 1 + }, + { + title: 'نام مشتری', + dataIndex: 'customer_to.name', + key: 'customer_id', + type: 'Text' + }, + { + title: 'زمان ثبت', + dataIndex: 'created_at', + key: 'created_at', + type: 'Text', + customRender: (record: any) => toJalali(record.record.created_at) + }, + { + title: 'نقد (ریال)', + dataIndex: 'cash', + key: 'cash', + type: 'Number' + }, + { + title: metalLabel.goldUnit(), + dataIndex: 'gold', + key: 'gold', + type: 'Number' + }, + { + title: 'توضیحات', + dataIndex: 'note', + key: 'note', + type: 'Text' } - }, - { - title: 'نام مشتری', - dataIndex: 'customer_to.name', - key: 'customer_id', - type: 'Number' - }, - { - title: 'نام مشتری دعوت شده', - dataIndex: 'customer.name', - key: 'customer_id', - type: 'Number' - }, - { - title: 'زمان ثبت', - dataIndex: 'created_at', - key: 'created_at', - type: 'Text', - customRender: (record: any) => { - return toJalali(record.record.created_at) - } - }, - { - title: 'نقد', - dataIndex: 'cash', - key: 'cash', - type: 'Number', - // customRender(data) { - // return usePrice(data.record?.price ?? 0, true, '') - // } - }, - { - title: metalLabel.goldUnit(), - dataIndex: 'gold', - key: 'gold', - type: 'Number', - }, - { - title: 'توضیحات', - dataIndex: 'note', - key: 'note', - type: 'Text', - }, - ...(showTahesab - ? [ - { - title: 'ته‌حساب', - dataIndex: 'tahesab_sync_status', - key: 'tahesab_sync_status', - type: 'Text', - filterable: false, - customRender: (record: any) => { - const row = record.record - if (!row.tahesab_record_id && !row.tahesab_sync_status) return '-' - if (row.tahesab_failed_id) return 'ناموفق' - if (row.tahesab_sync_status === 'success') return 'موفق' - if (row.tahesab_sync_status === 'pending') return 'در انتظار' - return row.tahesab_sync_status ?? '-' - } - } as BaseTableColumn - ] - : []), - { - title: 'عملیات', - dataIndex: 'action', - key: 'action', - slot: 'action', - filterable: false - } ] } diff --git a/src/pages/menuPages/accounting/index.vue b/src/pages/menuPages/accounting/index.vue index 1f1e526..fa7b92c 100644 --- a/src/pages/menuPages/accounting/index.vue +++ b/src/pages/menuPages/accounting/index.vue @@ -1,20 +1,18 @@