From 45647e7266bb0c9b60b98575feae8ccf8bdabb1e Mon Sep 17 00:00:00 2001 From: adonis Date: Mon, 3 Aug 2026 22:22:39 +0330 Subject: [PATCH] added rahimi dashboard --- src/api/dashboard.ts | 7 + src/core/assets/css/theme.less | 15 +- src/core/utils/syncAntDarkStyle.ts | 13 +- src/model/dashboard.ts | 97 +++ .../components/DashboardIncomeMix.vue | 320 ++++++++++ .../components/DashboardMontzarReconcile.vue | 584 ++++++++++++++++++ .../dashboard/composables/useDashboardPage.ts | 42 -- src/pages/dashboard/dashboardSectionCopy.ts | 16 +- src/pages/dashboard/index.vue | 25 +- 9 files changed, 1045 insertions(+), 74 deletions(-) create mode 100644 src/pages/dashboard/components/DashboardIncomeMix.vue create mode 100644 src/pages/dashboard/components/DashboardMontzarReconcile.vue diff --git a/src/api/dashboard.ts b/src/api/dashboard.ts index 926a123..d1c269d 100644 --- a/src/api/dashboard.ts +++ b/src/api/dashboard.ts @@ -5,6 +5,7 @@ import type { DashboardFinance, DashboardMaster, DashboardOrderPeakHours, + MontzarReconcilePayload, PriceChartPoint, ProfitConversion, ProfitConversionsResponse, @@ -70,3 +71,9 @@ export const storeProfitConversion = async (payload: { export const deleteProfitConversion = async (id: number) => await useAxios.delete(`/admin/dashboard/profit-conversions/${id}`) + +export const getMontzarReconcile = async (startDate: string, endDate: string) => + await useAxios.post('/admin/dashboard/montzar-reconcile', { + startDate: startDate.slice(0, 10), + endDate: endDate.slice(0, 10), + }) diff --git a/src/core/assets/css/theme.less b/src/core/assets/css/theme.less index 77824be..3d71159 100644 --- a/src/core/assets/css/theme.less +++ b/src/core/assets/css/theme.less @@ -20,6 +20,8 @@ html { --admin-shadow: rgba(0, 0, 0, 0.06); --admin-menu-selected: @gold-color-muted; --admin-menu-selected-text: @gold-color-dark; + /* متن روی دکمه/سطح طلایی — سفید روی طلا خوانا نیست */ + --admin-on-primary: #141414; --admin-gradient-surface: #ffffff; --admin-gradient-surface-fade: rgba(255, 255, 255, 0.85); } @@ -44,6 +46,7 @@ html.is-dark { --admin-shadow: rgba(0, 0, 0, 0.45); --admin-menu-selected: @gold-color-light; --admin-menu-selected-text: #141414; + --admin-on-primary: #141414; --admin-gradient-surface: #141414; --admin-gradient-surface-fade: rgba(20, 20, 20, 0.85); @@ -70,20 +73,24 @@ body { line-height: 1 !important; } -.ant-btn-primary { +.ant-btn-primary:not(.ant-btn-dangerous) { background: var(--admin-gold-light) !important; border-color: var(--admin-gold-light) !important; + color: var(--admin-on-primary) !important; + text-shadow: none !important; } -.ant-btn-primary:not([disabled]):hover, -.ant-btn-primary:not([disabled]):focus { +.ant-btn-primary:not(.ant-btn-dangerous):not([disabled]):hover, +.ant-btn-primary:not(.ant-btn-dangerous):not([disabled]):focus { background: var(--admin-gold-hover) !important; border-color: var(--admin-gold-hover) !important; + color: var(--admin-on-primary) !important; } -.ant-btn-primary:not([disabled]):active { +.ant-btn-primary:not(.ant-btn-dangerous):not([disabled]):active { background: var(--admin-gold) !important; border-color: var(--admin-gold) !important; + color: var(--admin-on-primary) !important; } .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected, diff --git a/src/core/utils/syncAntDarkStyle.ts b/src/core/utils/syncAntDarkStyle.ts index baa5ebe..019c776 100644 --- a/src/core/utils/syncAntDarkStyle.ts +++ b/src/core/utils/syncAntDarkStyle.ts @@ -80,13 +80,13 @@ html.is-dark .ant-picker-focused { box-shadow: 0 0 0 2px rgba(212, 175, 55, 0.18) !important; } html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) { - color: var(--admin-menu-selected-text) !important; + color: var(--admin-on-primary) !important; background: var(--admin-gold-light) !important; border-color: var(--admin-gold-light) !important; } html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover, html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active { - color: var(--admin-menu-selected-text) !important; + color: var(--admin-on-primary) !important; background: var(--admin-gold-hover) !important; border-color: var(--admin-gold-hover) !important; } @@ -105,14 +105,17 @@ html.is-dark .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-dis html.is-dark .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before { background-color: var(--admin-gold-light) !important; } -html.is-dark .ant-btn-primary { +html.is-dark .ant-btn-primary:not(.ant-btn-dangerous) { background: var(--admin-gold-light) !important; border-color: var(--admin-gold-light) !important; + color: var(--admin-on-primary) !important; + text-shadow: none !important; } -html.is-dark .ant-btn-primary:not([disabled]):hover, -html.is-dark .ant-btn-primary:not([disabled]):focus { +html.is-dark .ant-btn-primary:not(.ant-btn-dangerous):not([disabled]):hover, +html.is-dark .ant-btn-primary:not(.ant-btn-dangerous):not([disabled]):focus { background: var(--admin-gold-hover) !important; border-color: var(--admin-gold-hover) !important; + color: var(--admin-on-primary) !important; } html.is-dark .ant-btn-default:not([disabled]):hover, html.is-dark .ant-btn-default:not([disabled]):focus { diff --git a/src/model/dashboard.ts b/src/model/dashboard.ts index 65b800b..c91fd51 100644 --- a/src/model/dashboard.ts +++ b/src/model/dashboard.ts @@ -143,3 +143,100 @@ export interface DashboardWalletSums { cash?: number cashToCash?: number } + +export interface MontzarReconcileMandeh { + gold: number + cash: number + gold_abs: number + cash_abs: number + gold_state: string + rial_state: string + gold_unit: string + cash_unit: string +} + +export interface MontzarSiteSummary { + buy_gold: number + sell_gold: number + buy_rial: number + sell_rial: number + net_gold: number + net_rial: number +} + +export interface MontzarColleagueRow { + index?: number + id?: number | string | null + doc_id?: number | string | null + date?: string | null + date_ymd?: string | null + desc?: string + kind?: string + bed_gold?: number + bes_gold?: number + bed_rial?: number + bes_rial?: number + net_gold?: number + net_rial?: number + remain_gold?: number + remain_rial?: number + gold_state?: string | null + rial_state?: string | null +} + +export interface MontzarLocalItem { + id: number + type: string + direction?: number + direction_label?: string + amount: number + cash: number + montzar_cash?: number + covered?: boolean + provider_id?: number | string | null + product_id?: number | string | null + product_title?: string | null + customer_name?: string | null + note?: string + created_at?: string | null + date_ymd?: string | null +} + +export interface MontzarReconcileDiff { + code: string + reason: string + colleague?: MontzarColleagueRow | null + local?: MontzarLocalItem | null +} + +export interface MontzarReconcilePayload { + mandeh: MontzarReconcileMandeh | null + colleague_available?: boolean + colleague_error?: string | null + last_table_remain?: { gold: number; rial: number } | null + summary: { + local_all: MontzarSiteSummary + local_covered: MontzarSiteSummary + local_uncovered: MontzarSiteSummary + local_matched: MontzarSiteSummary + colleague_site: MontzarSiteSummary + diff_site_gold: number + diff_site_rial: number + order_count: number + covered_count: number + uncovered_count: number + matched_count: number + diff_count: number + gift_count?: number + colleague_row_count: number + local_order_count: number + } + colleague_rows: MontzarColleagueRow[] + local_orders: MontzarLocalItem[] + local_uncovered_orders?: MontzarLocalItem[] + local_gifts: MontzarLocalItem[] + matched: Array<{ code: string; colleague: MontzarColleagueRow; local: MontzarLocalItem }> + diffs: MontzarReconcileDiff[] + colleague_other?: MontzarReconcileDiff[] + range: { start: string; end: string } +} diff --git a/src/pages/dashboard/components/DashboardIncomeMix.vue b/src/pages/dashboard/components/DashboardIncomeMix.vue new file mode 100644 index 0000000..e392e44 --- /dev/null +++ b/src/pages/dashboard/components/DashboardIncomeMix.vue @@ -0,0 +1,320 @@ + + + + + diff --git a/src/pages/dashboard/components/DashboardMontzarReconcile.vue b/src/pages/dashboard/components/DashboardMontzarReconcile.vue new file mode 100644 index 0000000..0c43eaa --- /dev/null +++ b/src/pages/dashboard/components/DashboardMontzarReconcile.vue @@ -0,0 +1,584 @@ + + + + + + + diff --git a/src/pages/dashboard/composables/useDashboardPage.ts b/src/pages/dashboard/composables/useDashboardPage.ts index 503c5ac..fdcf7c9 100644 --- a/src/pages/dashboard/composables/useDashboardPage.ts +++ b/src/pages/dashboard/composables/useDashboardPage.ts @@ -275,47 +275,6 @@ export function useDashboardPage() { ?? (order.sts === 2 ? 'موفق' : order.sts === 1 ? 'باز' : 'ناموفق') } - const financeChartOptions = computed(() => { - const sood = finance.value?.fullPriceSood ?? 0 - const karmozd = finance.value?.fullPriceKarmozd ?? finance.value?.fullPriceKaermozd ?? 0 - const gifts = finance.value?.GiftsPrise ?? 0 - const total = finance.value?.totalTradingProfit ?? sood + karmozd - - return { - ...chartSurfaceOptions(), - chart: { type: 'donut', fontFamily: 'inherit', offsetY: 4, foreColor: chartSurfaceOptions().chart.foreColor }, - labels: ['سود اختلاف قیمت', 'کارمزد معامله', 'ارزش هدایا (گرم)'], - colors: ['#10b981', '#f59e0b', '#8b5cf6'], - legend: { - position: 'bottom', - fontSize: '12px', - horizontalAlign: 'center', - offsetY: 6, - itemMargin: { horizontal: 12, vertical: 4 }, - }, - dataLabels: { enabled: false }, - plotOptions: { - pie: { - donut: { - size: '68%', - labels: { - show: true, - total: { - show: true, - label: 'جمع سود معاملات', - formatter: () => `${formatDashboardAmount(total)} ${DASHBOARD_RIAL_UNIT}`, - }, - }, - }, - }, - }, - tooltip: { - y: { formatter: (v: number) => `${formatDashboardAmount(v)} ${DASHBOARD_RIAL_UNIT}` }, - }, - series: [sood, karmozd, gifts], - } - }) - const flowChartOptions = computed(() => { const mobile = isMobile.value const surface = chartSurfaceOptions() @@ -628,7 +587,6 @@ export function useDashboardPage() { directionClass, stsClass, stsLabel, - financeChartOptions, flowChartOptions, flowChartHeight, peakHoursChartOptions, diff --git a/src/pages/dashboard/dashboardSectionCopy.ts b/src/pages/dashboard/dashboardSectionCopy.ts index 0bff160..9664617 100644 --- a/src/pages/dashboard/dashboardSectionCopy.ts +++ b/src/pages/dashboard/dashboardSectionCopy.ts @@ -88,8 +88,8 @@ export const dashboardDateMetrics = { export const dashboardCharts = { incomeMix: help( - 'تفکیک درآمد: سود اختلاف قیمت، کارمزد ریالی، و ارزش هدایا.', - 'درآمد = سود اختلاف قیمت + کارمزد + (گرم هدیه × مظنه)' + 'سهم سود اختلاف قیمت و کارمزد از جمع سود معاملات در بازه انتخابی. هدایا هزینهٔ پاداش‌اند و اینجا جزو سود نیستند.', + 'جمع سود = سود اختلاف قیمت + کارمزد' ), buySellVolume: help( 'مقدار طلای خرید و فروش مشتری در هر روز (فقط سفارش موفق).', @@ -129,12 +129,18 @@ export const dashboardCustomerMetrics = { } as const satisfies Record export const dashboardWallet = help( - 'مجموع موجودی طلا و ریال همه مشتریان.', + 'مجموع موجودی طلا و ریال همه مشتریان (بدهی اپ به کاربران). این عدد دفتر اکانت همکار نیست.', 'ارزش طلا به ریال = جمع گرم × مظنه لحظه‌ای' ) export const dashboardWalletMetrics = { - gold: help('مجموع طلای موجود در کیف همه مشتریان.', 'جمع گرم طلا در کیف‌ها'), - cash: help('مجموع ریال موجود در کیف همه مشتریان.', 'جمع ریال در کیف‌ها'), + gold: help( + 'مجموع طلای موجود در کیف همه مشتریان — بدهی به مشتری؛ با ماندهٔ پنل همکار یکی نیست.', + 'جمع گرم طلا در کیف‌ها' + ), + cash: help( + 'مجموع ریال موجود در کیف همه مشتریان — شارژ/مانده مشتری؛ با ماندهٔ ریالی همکار یکی نیست.', + 'جمع ریال در کیف‌ها' + ), goldToCash: help('ارزش ریالی کل طلای مشتریان با مظنه فعلی.', 'جمع گرم × قیمت هر گرم'), } as const satisfies Record diff --git a/src/pages/dashboard/index.vue b/src/pages/dashboard/index.vue index b3aba5f..fd60176 100644 --- a/src/pages/dashboard/index.vue +++ b/src/pages/dashboard/index.vue @@ -139,25 +139,12 @@ + +
- -
- -
- در این بازه داده مالی ثبت نشده -
-
+
@@ -461,6 +448,8 @@ import DashboardQuickWalletModal from '@/pages/dashboard/components/DashboardQui import DashboardSectionHeader from '@/pages/dashboard/components/DashboardSectionHeader.vue' import DashboardStatCard from '@/pages/dashboard/components/DashboardStatCard.vue' import DashboardProfitSplit from '@/pages/dashboard/components/DashboardProfitSplit.vue' +import DashboardMontzarReconcile from '@/pages/dashboard/components/DashboardMontzarReconcile.vue' +import DashboardIncomeMix from '@/pages/dashboard/components/DashboardIncomeMix.vue' import { dashboardCharts, dashboardCustomerMetrics, @@ -481,12 +470,13 @@ import { metalBalanceTitle, metalLabel } from '@/utils/adminMetalLabels' import { formatOrderServiceFee } from '@/utils/orderServiceFee' import { getMainMetal } from '@/utils/metalLabels' import { TRADE_WEIGHT_UNIT_GRAM, TRADE_WEIGHT_UNIT_KILOGRAM } from '@/utils/weightUnit' -import { initAccountingSettingsFromApi, pargarEnabledSetting } from '@/utils/config' +import { initAccountingSettingsFromApi, initStoreModeFromSettings, pargarEnabledSetting } from '@/utils/config' const pargarEnabled = pargarEnabledSetting onMounted(() => { void initAccountingSettingsFromApi() + void initStoreModeFromSettings() }) const copy = { @@ -533,7 +523,6 @@ const { directionClass, stsClass, stsLabel, - financeChartOptions, flowChartOptions, flowChartHeight, peakHoursChartOptions,