From 827adfe75683ec270f5ccfa8ac59a62d6fc3efab Mon Sep 17 00:00:00 2001 From: adonis Date: Thu, 27 Aug 2026 16:15:11 +0330 Subject: [PATCH] fix accounting fails --- src/api/tahesab.ts | 47 +- src/api/zarin.ts | 47 +- src/model/product.ts | 1 + .../accounting/components/tahesabTable.vue | 456 +++++++++++++++--- .../accounting/components/zarinTable.vue | 455 ++++++++++++++--- .../accounting/utils/tahesabStatus.ts | 12 +- .../menuPages/accounting/utils/zarinStatus.ts | 9 +- .../components/CustomerTahesabDocsTable.vue | 18 + .../components/CustomerZarinDocsTable.vue | 18 + src/pages/menuPages/customers/base/index.vue | 23 + .../base/components/ProductEditorFormBody.vue | 15 + src/pages/menuPages/products/base/index.vue | 2 + 12 files changed, 937 insertions(+), 166 deletions(-) create mode 100644 src/pages/menuPages/customers/base/components/CustomerTahesabDocsTable.vue create mode 100644 src/pages/menuPages/customers/base/components/CustomerZarinDocsTable.vue diff --git a/src/api/tahesab.ts b/src/api/tahesab.ts index 526634a..a93c02d 100644 --- a/src/api/tahesab.ts +++ b/src/api/tahesab.ts @@ -2,17 +2,21 @@ import useAxios from '@core/utils/useAxios' import type { TableInput } from '@core/type/baseTable' export interface TahesabRecord { - id: number + id?: number | null customer_id?: number amount?: number desc?: string type?: string - sync_status?: 'pending' | 'success' | 'failed' + sync_status?: 'pending' | 'success' | 'failed' | 'missing' sync_log?: string | null tahesab_id?: string | null data?: string | null res?: string | null created_at?: string + withdraw_id?: number | null + order_id?: number | null + issue_kind?: 'failed' | 'missing' + sync_target?: { type: 'row' | 'withdraw' | 'order'; id: number } customer?: { id?: number name?: string @@ -21,7 +25,7 @@ export interface TahesabRecord { } } -export const getTahesabTable = async (params: TableInput) => +export const getTahesabTable = async (params: TableInput & { view?: string }) => await useAxios.post('/admin/tahesab/table', params) export const getTahesabDetail = async (id: number) => @@ -30,6 +34,43 @@ export const getTahesabDetail = async (id: number) => export const retryTahesabSanad = async (id: number) => await useAxios.post<{ ok: boolean; message: string; tahesab?: TahesabRecord }>(`/admin/tahesab/${id}/retry`) +export interface TahesabPendingSummary { + failed_rows: number + missing_withdraws: number + missing_orders: number + total: number +} + +export interface TahesabSyncAllResult { + ok: boolean + retried: number + backfilled_withdraws: number + backfilled_orders: number + failed: number + errors: string[] + summary: TahesabPendingSummary +} + +export const getTahesabPendingSummary = async (customerId?: number) => + await useAxios.get('/admin/tahesab/pending-summary', { + params: customerId ? { customer_id: customerId } : undefined + }) + +export const getTahesabPendingDocs = async (customerId?: number, limit = 200) => + await useAxios.get<{ rows: TahesabRecord[]; summary: TahesabPendingSummary }>( + '/admin/tahesab/pending-docs', + { params: { ...(customerId ? { customer_id: customerId } : {}), limit } } + ) + +export const syncOneTahesab = async (type: 'row' | 'withdraw' | 'order', id: number) => + await useAxios.post<{ ok: boolean; message: string }>('/admin/tahesab/sync-one', { type, id }) + +export const syncAllTahesabPending = async (limit = 80, customerId?: number) => + await useAxios.post('/admin/tahesab/sync-all-pending', { + limit, + ...(customerId ? { customer_id: customerId } : {}) + }) + export const registerCustomerInTahesab = async (customerId: number) => await useAxios.post<{ ok: boolean; message: string; moshtari_code?: string | number }>( `/admin/customer/${customerId}/tahesab/register` diff --git a/src/api/zarin.ts b/src/api/zarin.ts index 3a41303..9758395 100644 --- a/src/api/zarin.ts +++ b/src/api/zarin.ts @@ -2,17 +2,21 @@ import useAxios from '@core/utils/useAxios' import type { TableInput } from '@core/type/baseTable' export interface ZarinRecord { - id: number + id?: number | null customer_id?: number amount?: number desc?: string type?: string - sync_status?: 'pending' | 'success' | 'failed' + sync_status?: 'pending' | 'success' | 'failed' | 'missing' sync_log?: string | null zarin_id?: string | null data?: string | null res?: string | null created_at?: string + withdraw_id?: number | null + order_id?: number | null + issue_kind?: 'failed' | 'missing' + sync_target?: { type: 'row' | 'withdraw' | 'order'; id: number } customer?: { id?: number name?: string @@ -21,7 +25,7 @@ export interface ZarinRecord { } } -export const getZarinTable = async (params: TableInput) => +export const getZarinTable = async (params: TableInput & { view?: string }) => await useAxios.post('/admin/zarin/table', params) export const getZarinDetail = async (id: number) => @@ -30,6 +34,43 @@ export const getZarinDetail = async (id: number) => export const retryZarinSanad = async (id: number) => await useAxios.post<{ ok: boolean; message: string; zarin?: ZarinRecord }>(`/admin/zarin/${id}/retry`) +export interface ZarinPendingSummary { + failed_rows: number + missing_withdraws: number + missing_orders: number + total: number +} + +export interface ZarinSyncAllResult { + ok: boolean + retried: number + backfilled_withdraws: number + backfilled_orders: number + failed: number + errors: string[] + summary: ZarinPendingSummary +} + +export const getZarinPendingSummary = async (customerId?: number) => + await useAxios.get('/admin/zarin/pending-summary', { + params: customerId ? { customer_id: customerId } : undefined + }) + +export const getZarinPendingDocs = async (customerId?: number, limit = 200) => + await useAxios.get<{ rows: ZarinRecord[]; summary: ZarinPendingSummary }>( + '/admin/zarin/pending-docs', + { params: { ...(customerId ? { customer_id: customerId } : {}), limit } } + ) + +export const syncOneZarin = async (type: 'row' | 'withdraw' | 'order', id: number) => + await useAxios.post<{ ok: boolean; message: string }>('/admin/zarin/sync-one', { type, id }) + +export const syncAllZarinPending = async (limit = 80, customerId?: number) => + await useAxios.post('/admin/zarin/sync-all-pending', { + limit, + ...(customerId ? { customer_id: customerId } : {}) + }) + export const registerCustomerInZarin = async (customerId: number) => await useAxios.post<{ ok: boolean; message: string; moshtari_code?: string | number }>( `/admin/customer/${customerId}/zarin/register` diff --git a/src/model/product.ts b/src/model/product.ts index e8636c5..dcaaa34 100644 --- a/src/model/product.ts +++ b/src/model/product.ts @@ -56,6 +56,7 @@ export interface Product{ original_image?: File inventory?: number description?: string | null + karat?: string | null } export interface MethodePrices { diff --git a/src/pages/menuPages/accounting/components/tahesabTable.vue b/src/pages/menuPages/accounting/components/tahesabTable.vue index eeaded1..7b9e382 100644 --- a/src/pages/menuPages/accounting/components/tahesabTable.vue +++ b/src/pages/menuPages/accounting/components/tahesabTable.vue @@ -1,18 +1,116 @@ - + -
{{ item.label }} {{ item.value ?? '-' }}
-
+
{{ selected.res }}
-
+
{{ selected.sync_log }}
+
+ + ثبت در ته‌حساب + +
- -
diff --git a/src/pages/menuPages/accounting/components/zarinTable.vue b/src/pages/menuPages/accounting/components/zarinTable.vue index b11de9c..8760957 100644 --- a/src/pages/menuPages/accounting/components/zarinTable.vue +++ b/src/pages/menuPages/accounting/components/zarinTable.vue @@ -1,18 +1,115 @@
- + -
{{ item.label }} {{ item.value ?? '-' }}
-
+
{{ selected.res }}
-
+
{{ selected.sync_log }}
+
+ + ثبت در زرین + +
- -
diff --git a/src/pages/menuPages/accounting/utils/tahesabStatus.ts b/src/pages/menuPages/accounting/utils/tahesabStatus.ts index f2154a9..5aae751 100644 --- a/src/pages/menuPages/accounting/utils/tahesabStatus.ts +++ b/src/pages/menuPages/accounting/utils/tahesabStatus.ts @@ -1,12 +1,20 @@ export function tahesabStatusLabel(status?: string, tahesabId?: string | null): string { if (tahesabId) return 'موفق' + if (status === 'missing') return 'جاافتاده' if (status === 'failed') return 'ناموفق' if (status === 'success') return 'موفق' // pending = ردیف محلی ساخته شده ولی هنوز در نرم‌افزار ته‌حساب ثبت نشده (نه «در صف») return 'ارسال نشده' } -export function canRetryTahesab(record: { tahesab_id?: string | null; sync_status?: string }): boolean { - return !record.tahesab_id || record.sync_status === 'failed' +export function tahesabIssueKindLabel(kind?: string): string { + if (kind === 'missing') return 'جاافتاده' + return 'خطا خورده' } +export function canRetryTahesab(record: { + tahesab_id?: string | null + sync_status?: string +}): boolean { + return !record.tahesab_id || record.sync_status === 'failed' || record.sync_status === 'missing' +} diff --git a/src/pages/menuPages/accounting/utils/zarinStatus.ts b/src/pages/menuPages/accounting/utils/zarinStatus.ts index cabeff7..d44b2a2 100644 --- a/src/pages/menuPages/accounting/utils/zarinStatus.ts +++ b/src/pages/menuPages/accounting/utils/zarinStatus.ts @@ -1,12 +1,17 @@ export function zarinStatusLabel(status?: string, zarinId?: string | null): string { if (zarinId) return 'موفق' + if (status === 'missing') return 'جاافتاده' if (status === 'failed') return 'ناموفق' if (status === 'success') return 'موفق' // pending = ردیف محلی ساخته شده ولی هنوز در نرم‌افزار زرین ثبت نشده (نه «در صف») return 'ارسال نشده' } -export function canRetryZarin(record: { zarin_id?: string | null; sync_status?: string }): boolean { - return !record.zarin_id || record.sync_status === 'failed' +export function zarinIssueKindLabel(kind?: string): string { + if (kind === 'missing') return 'جاافتاده' + return 'خطا خورده' } +export function canRetryZarin(record: { zarin_id?: string | null; sync_status?: string }): boolean { + return !record.zarin_id || record.sync_status === 'failed' || record.sync_status === 'missing' +} diff --git a/src/pages/menuPages/customers/base/components/CustomerTahesabDocsTable.vue b/src/pages/menuPages/customers/base/components/CustomerTahesabDocsTable.vue new file mode 100644 index 0000000..db56c06 --- /dev/null +++ b/src/pages/menuPages/customers/base/components/CustomerTahesabDocsTable.vue @@ -0,0 +1,18 @@ + + + diff --git a/src/pages/menuPages/customers/base/components/CustomerZarinDocsTable.vue b/src/pages/menuPages/customers/base/components/CustomerZarinDocsTable.vue new file mode 100644 index 0000000..f43b082 --- /dev/null +++ b/src/pages/menuPages/customers/base/components/CustomerZarinDocsTable.vue @@ -0,0 +1,18 @@ + + + diff --git a/src/pages/menuPages/customers/base/index.vue b/src/pages/menuPages/customers/base/index.vue index e2b5889..b49c6aa 100644 --- a/src/pages/menuPages/customers/base/index.vue +++ b/src/pages/menuPages/customers/base/index.vue @@ -249,6 +249,12 @@ + + + + + + void } | null>(null) +const tahesabDocsTable = ref<{ fetch?: () => void } | null>(null) +const zarinDocsTable = ref<{ fetch?: () => void } | null>(null) const refreshTable = () => table?.value?.fetch() const refreshOrderTable = () => orderTable?.value?.fetch() const refreshTransactionTable = () => transactionTable?.value?.fetch() @@ -392,6 +402,8 @@ const refreshReferralTable = () => referralTable?.value?.fetch() const refreshFundTable = () => fundTable?.value?.fetch() const refreshPiggyFundTable = () => piggyFundTable?.value?.fetch() const refreshPargarLogsTable = () => pargarLogsTable.value?.fetch?.() +const refreshTahesabDocsTable = () => tahesabDocsTable.value?.fetch?.() +const refreshZarinDocsTable = () => zarinDocsTable.value?.fetch?.() let updateKey = ref(0) @@ -560,6 +572,14 @@ async function refreshInfoTab(key: string) { await nextTick() refreshPargarLogsTable() break + case 'اسناد ته‌حساب': + await nextTick() + refreshTahesabDocsTable() + break + case 'اسناد زرین': + await nextTick() + refreshZarinDocsTable() + break case 'سفارش شرطی': await refetchCustomerProfile() break @@ -723,6 +743,9 @@ const pargarWalletSyncVisible = computed( () => accountingProviderSetting.value === 'pargar' ) +const tahesabDocsVisible = computed(() => accountingProviderSetting.value === 'tahesab') +const zarinDocsVisible = computed(() => accountingProviderSetting.value === 'zarin') + const pargarWalletCanSync = computed(() => isValidAccountingCode(userInfo.value?.pargar_account_id) ) diff --git a/src/pages/menuPages/products/base/components/ProductEditorFormBody.vue b/src/pages/menuPages/products/base/components/ProductEditorFormBody.vue index 7509837..54c89b0 100644 --- a/src/pages/menuPages/products/base/components/ProductEditorFormBody.vue +++ b/src/pages/menuPages/products/base/components/ProductEditorFormBody.vue @@ -351,6 +351,16 @@ show-count /> + + + +
@@ -684,6 +694,11 @@ const productDescriptionHelp: DashboardHelp = { 'متن توضیح محصول که در اپ مشتری، بعد از انتخاب محصول در صفحه خرید/فروش نمایش داده می‌شود (اگر سوییچ «نمایش جزئیات اطلاعات محصول» در تنظیمات روشن باشد).', } +const productKaratHelp: DashboardHelp = { + description: + 'عیار نمایشی در مشخصات محصول اپ مشتری (مثلاً ۲۴ یا ۹۹۹). اگر خالی باشد، ردیف عیار در اپ نشان داده نمی‌شود.', +} + const categoryHelp: DashboardHelp = { description: 'دسته‌بندی محصول در تب‌های اپ (سکه، شمش، آبشده و …).', } diff --git a/src/pages/menuPages/products/base/index.vue b/src/pages/menuPages/products/base/index.vue index 97dd592..8afcb23 100644 --- a/src/pages/menuPages/products/base/index.vue +++ b/src/pages/menuPages/products/base/index.vue @@ -457,6 +457,7 @@ function resetData() { min_physical_delivery_quantity: undefined, inventory: 0, description: '', + karat: '', min_order_cash: undefined, max_order_cash: undefined, auto_open_at: dayjs('00:00:00', 'HH:mm:ss'), @@ -482,6 +483,7 @@ const PRICING_FIELDS = new Set([ const DETAILS_FIELDS = new Set([ 'title', 'description', + 'karat', 'group_type', 'metal_id', 'amount',