added rahimi dashboard
This commit is contained in:
@@ -5,6 +5,7 @@ import type {
|
|||||||
DashboardFinance,
|
DashboardFinance,
|
||||||
DashboardMaster,
|
DashboardMaster,
|
||||||
DashboardOrderPeakHours,
|
DashboardOrderPeakHours,
|
||||||
|
MontzarReconcilePayload,
|
||||||
PriceChartPoint,
|
PriceChartPoint,
|
||||||
ProfitConversion,
|
ProfitConversion,
|
||||||
ProfitConversionsResponse,
|
ProfitConversionsResponse,
|
||||||
@@ -70,3 +71,9 @@ export const storeProfitConversion = async (payload: {
|
|||||||
|
|
||||||
export const deleteProfitConversion = async (id: number) =>
|
export const deleteProfitConversion = async (id: number) =>
|
||||||
await useAxios.delete(`/admin/dashboard/profit-conversions/${id}`)
|
await useAxios.delete(`/admin/dashboard/profit-conversions/${id}`)
|
||||||
|
|
||||||
|
export const getMontzarReconcile = async (startDate: string, endDate: string) =>
|
||||||
|
await useAxios.post<MontzarReconcilePayload>('/admin/dashboard/montzar-reconcile', {
|
||||||
|
startDate: startDate.slice(0, 10),
|
||||||
|
endDate: endDate.slice(0, 10),
|
||||||
|
})
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ html {
|
|||||||
--admin-shadow: rgba(0, 0, 0, 0.06);
|
--admin-shadow: rgba(0, 0, 0, 0.06);
|
||||||
--admin-menu-selected: @gold-color-muted;
|
--admin-menu-selected: @gold-color-muted;
|
||||||
--admin-menu-selected-text: @gold-color-dark;
|
--admin-menu-selected-text: @gold-color-dark;
|
||||||
|
/* متن روی دکمه/سطح طلایی — سفید روی طلا خوانا نیست */
|
||||||
|
--admin-on-primary: #141414;
|
||||||
--admin-gradient-surface: #ffffff;
|
--admin-gradient-surface: #ffffff;
|
||||||
--admin-gradient-surface-fade: rgba(255, 255, 255, 0.85);
|
--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-shadow: rgba(0, 0, 0, 0.45);
|
||||||
--admin-menu-selected: @gold-color-light;
|
--admin-menu-selected: @gold-color-light;
|
||||||
--admin-menu-selected-text: #141414;
|
--admin-menu-selected-text: #141414;
|
||||||
|
--admin-on-primary: #141414;
|
||||||
--admin-gradient-surface: #141414;
|
--admin-gradient-surface: #141414;
|
||||||
--admin-gradient-surface-fade: rgba(20, 20, 20, 0.85);
|
--admin-gradient-surface-fade: rgba(20, 20, 20, 0.85);
|
||||||
|
|
||||||
@@ -70,20 +73,24 @@ body {
|
|||||||
line-height: 1 !important;
|
line-height: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-btn-primary {
|
.ant-btn-primary:not(.ant-btn-dangerous) {
|
||||||
background: var(--admin-gold-light) !important;
|
background: var(--admin-gold-light) !important;
|
||||||
border-color: 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(.ant-btn-dangerous):not([disabled]):hover,
|
||||||
.ant-btn-primary:not([disabled]):focus {
|
.ant-btn-primary:not(.ant-btn-dangerous):not([disabled]):focus {
|
||||||
background: var(--admin-gold-hover) !important;
|
background: var(--admin-gold-hover) !important;
|
||||||
border-color: 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;
|
background: var(--admin-gold) !important;
|
||||||
border-color: 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,
|
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected,
|
||||||
|
|||||||
@@ -80,13 +80,13 @@ html.is-dark .ant-picker-focused {
|
|||||||
box-shadow: 0 0 0 2px rgba(212, 175, 55, 0.18) !important;
|
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) {
|
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;
|
background: var(--admin-gold-light) !important;
|
||||||
border-color: 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):hover,
|
||||||
html.is-dark .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active {
|
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;
|
background: var(--admin-gold-hover) !important;
|
||||||
border-color: 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 {
|
html.is-dark .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before {
|
||||||
background-color: var(--admin-gold-light) !important;
|
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;
|
background: var(--admin-gold-light) !important;
|
||||||
border-color: 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(.ant-btn-dangerous):not([disabled]):hover,
|
||||||
html.is-dark .ant-btn-primary:not([disabled]):focus {
|
html.is-dark .ant-btn-primary:not(.ant-btn-dangerous):not([disabled]):focus {
|
||||||
background: var(--admin-gold-hover) !important;
|
background: var(--admin-gold-hover) !important;
|
||||||
border-color: 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]):hover,
|
||||||
html.is-dark .ant-btn-default:not([disabled]):focus {
|
html.is-dark .ant-btn-default:not([disabled]):focus {
|
||||||
|
|||||||
@@ -143,3 +143,100 @@ export interface DashboardWalletSums {
|
|||||||
cash?: number
|
cash?: number
|
||||||
cashToCash?: 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 }
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,320 @@
|
|||||||
|
<template>
|
||||||
|
<div class="income-mix">
|
||||||
|
<DashboardSectionHeader title="ترکیب درآمد" :help="help" />
|
||||||
|
|
||||||
|
<div v-if="total <= 0" class="income-mix__empty">
|
||||||
|
در این بازه داده مالی ثبت نشده
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="income-mix__body">
|
||||||
|
<div class="income-mix__chart">
|
||||||
|
<div class="income-mix__donut">
|
||||||
|
<apexchart
|
||||||
|
type="donut"
|
||||||
|
width="100%"
|
||||||
|
height="168"
|
||||||
|
:options="chartOptions"
|
||||||
|
:series="chartSeries"
|
||||||
|
/>
|
||||||
|
<div class="income-mix__center">
|
||||||
|
<span class="income-mix__center-label">جمع سود</span>
|
||||||
|
<span class="income-mix__center-amount" dir="ltr">
|
||||||
|
<bdi>{{ compactTotal }}</bdi>
|
||||||
|
</span>
|
||||||
|
<span class="income-mix__center-unit">{{ DASHBOARD_RIAL_UNIT }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="income-mix__list">
|
||||||
|
<li v-for="row in rows" :key="row.key" class="income-mix__row">
|
||||||
|
<span class="income-mix__dot" :style="{ background: row.color }" />
|
||||||
|
<div class="income-mix__meta">
|
||||||
|
<div class="income-mix__name-row">
|
||||||
|
<span class="income-mix__name">{{ row.label }}</span>
|
||||||
|
<span class="income-mix__pct-pill" :style="{ color: row.color, background: row.tint }">
|
||||||
|
{{ row.pctLabel }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span class="income-mix__hint">{{ row.hint }}</span>
|
||||||
|
<span class="income-mix__amount" dir="ltr">
|
||||||
|
<bdi>{{ formatDashboardAmount(row.value) }}</bdi>
|
||||||
|
<span class="income-mix__unit">{{ DASHBOARD_RIAL_UNIT }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import DashboardSectionHeader from '@/pages/dashboard/components/DashboardSectionHeader.vue'
|
||||||
|
import { dashboardCharts } from '@/pages/dashboard/dashboardSectionCopy'
|
||||||
|
import { DASHBOARD_RIAL_UNIT, formatDashboardAmount } from '@/pages/dashboard/dashboardFormat'
|
||||||
|
import { useAdminTheme } from '@core/composable/useAdminTheme'
|
||||||
|
import type { DashboardFinance } from '@/model/dashboard'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
finance?: DashboardFinance | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { isDark } = useAdminTheme()
|
||||||
|
const help = dashboardCharts.incomeMix
|
||||||
|
|
||||||
|
const sood = computed(() => Number(props.finance?.fullPriceSood ?? 0))
|
||||||
|
const karmozd = computed(
|
||||||
|
() => Number(props.finance?.fullPriceKarmozd ?? props.finance?.fullPriceKaermozd ?? 0),
|
||||||
|
)
|
||||||
|
const total = computed(() => sood.value + karmozd.value)
|
||||||
|
|
||||||
|
function tint(hex: string, dark: boolean) {
|
||||||
|
return dark ? `${hex}33` : `${hex}1a`
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = computed(() => {
|
||||||
|
const t = total.value
|
||||||
|
const dark = isDark.value
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
key: 'sood',
|
||||||
|
label: 'سود اختلاف قیمت',
|
||||||
|
hint: 'فاصله مظنه و قیمت مشتری',
|
||||||
|
value: sood.value,
|
||||||
|
color: '#10b981',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fee',
|
||||||
|
label: 'کارمزد معامله',
|
||||||
|
hint: 'کارمزد گرفتهشده از سفارشها',
|
||||||
|
value: karmozd.value,
|
||||||
|
color: '#f59e0b',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return items.map((item) => {
|
||||||
|
const pct = t > 0 ? Math.round((item.value / t) * 1000) / 10 : 0
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
pct,
|
||||||
|
pctLabel: `${pct.toLocaleString('fa-IR')}٪`,
|
||||||
|
tint: tint(item.color, dark),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const chartSeries = computed(() => rows.value.map((r) => r.value))
|
||||||
|
|
||||||
|
const compactTotal = computed(() => {
|
||||||
|
const n = total.value
|
||||||
|
if (n >= 1_000_000_000) return `${(n / 1_000_000_000).toFixed(1)}B`
|
||||||
|
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
|
||||||
|
return formatDashboardAmount(n)
|
||||||
|
})
|
||||||
|
|
||||||
|
const chartOptions = computed(() => {
|
||||||
|
const dark = isDark.value
|
||||||
|
return {
|
||||||
|
chart: {
|
||||||
|
type: 'donut',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
foreColor: dark ? 'rgba(255,255,255,0.55)' : 'rgba(0,0,0,0.55)',
|
||||||
|
animations: { enabled: true, speed: 450 },
|
||||||
|
},
|
||||||
|
theme: { mode: dark ? 'dark' : 'light' },
|
||||||
|
labels: rows.value.map((r) => r.label),
|
||||||
|
colors: rows.value.map((r) => r.color),
|
||||||
|
stroke: { width: 0 },
|
||||||
|
legend: { show: false },
|
||||||
|
dataLabels: { enabled: false },
|
||||||
|
plotOptions: {
|
||||||
|
pie: {
|
||||||
|
expandOnClick: false,
|
||||||
|
donut: {
|
||||||
|
size: '74%',
|
||||||
|
labels: { show: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: dark ? 'dark' : 'light',
|
||||||
|
y: {
|
||||||
|
formatter: (v: number) => `${formatDashboardAmount(v)} ${DASHBOARD_RIAL_UNIT}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
states: {
|
||||||
|
hover: { filter: { type: 'lighten', value: 0.08 } },
|
||||||
|
active: { filter: { type: 'none' } },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.income-mix {
|
||||||
|
color: var(--admin-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__empty {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 11rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(140px, 168px) minmax(0, 1fr);
|
||||||
|
gap: 0.75rem 1rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.income-mix__body {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__list {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__chart {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 168px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__donut {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__donut :deep(.apexcharts-canvas) {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__center {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
padding: 1.25rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__center-label {
|
||||||
|
font-size: 0.625rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__center-amount {
|
||||||
|
margin-top: 0.15rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 800;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
color: var(--admin-text);
|
||||||
|
line-height: 1.2;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__center-unit {
|
||||||
|
margin-top: 0.1rem;
|
||||||
|
font-size: 0.5625rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0.55rem minmax(0, 1fr);
|
||||||
|
gap: 0.55rem;
|
||||||
|
align-items: start;
|
||||||
|
padding: 0.55rem 0.6rem;
|
||||||
|
border-radius: 0.6rem;
|
||||||
|
border: 1px solid var(--admin-border);
|
||||||
|
background: var(--admin-bg-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__dot {
|
||||||
|
width: 0.55rem;
|
||||||
|
height: 0.55rem;
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 12%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__meta {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__name-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__name {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__pct-pill {
|
||||||
|
font-size: 0.625rem;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__hint {
|
||||||
|
font-size: 0.625rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__amount {
|
||||||
|
margin-top: 0.1rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 800;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.income-mix__unit {
|
||||||
|
font-size: 0.625rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,584 @@
|
|||||||
|
<template>
|
||||||
|
<section v-if="visible" class="montzar-simple">
|
||||||
|
<DashboardSectionHeader title="آبشده و همکار" :help="sectionHelp" />
|
||||||
|
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<div v-if="error" class="montzar-simple__error">
|
||||||
|
{{ error }}
|
||||||
|
<a-button class="mt-2" size="small" @click="load">تلاش مجدد</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-else-if="data">
|
||||||
|
<div class="montzar-simple__grid">
|
||||||
|
<article class="montzar-simple__card">
|
||||||
|
<span class="montzar-simple__label">خرید مشتری</span>
|
||||||
|
<p class="montzar-simple__hint">آبشده در این بازه</p>
|
||||||
|
<p class="montzar-simple__value montzar-simple__value--inline">
|
||||||
|
<bdi class="montzar-simple__amount number-ltr">{{ formatGram(data.summary.local_all.buy_gold) }}</bdi>
|
||||||
|
<span class="montzar-simple__unit">گرم</span>
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="montzar-simple__card">
|
||||||
|
<span class="montzar-simple__label">فروش مشتری</span>
|
||||||
|
<p class="montzar-simple__hint">آبشده در این بازه</p>
|
||||||
|
<p class="montzar-simple__value montzar-simple__value--inline">
|
||||||
|
<bdi class="montzar-simple__amount number-ltr">{{ formatGram(data.summary.local_all.sell_gold) }}</bdi>
|
||||||
|
<span class="montzar-simple__unit">گرم</span>
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="montzar-simple__card">
|
||||||
|
<span class="montzar-simple__label">ثبتشده نزد همکار</span>
|
||||||
|
<p class="montzar-simple__hint">از همکار خرید یا فروش شده</p>
|
||||||
|
<div class="montzar-simple__value montzar-simple__value--stack">
|
||||||
|
<p class="montzar-simple__ratio">
|
||||||
|
<bdi class="montzar-simple__amount">{{ data.summary.covered_count }}</bdi>
|
||||||
|
<span class="montzar-simple__sep">از</span>
|
||||||
|
<bdi class="montzar-simple__amount montzar-simple__amount--muted">{{ data.summary.order_count }}</bdi>
|
||||||
|
</p>
|
||||||
|
<span class="montzar-simple__unit">سفارش</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article
|
||||||
|
class="montzar-simple__card"
|
||||||
|
:class="data.summary.uncovered_count ? 'montzar-simple__card--bad' : 'montzar-simple__card--ok'"
|
||||||
|
>
|
||||||
|
<span class="montzar-simple__label">
|
||||||
|
{{ data.summary.uncovered_count ? 'ثبتنشده نزد همکار' : 'ثبتنشده نزد همکار' }}
|
||||||
|
</span>
|
||||||
|
<p class="montzar-simple__hint">
|
||||||
|
{{
|
||||||
|
data.summary.uncovered_count
|
||||||
|
? 'در اپ هست ولی از همکار خرید/فروش نشده'
|
||||||
|
: 'همه سفارشها نزد همکار ثبت شدهاند'
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
<div class="montzar-simple__value montzar-simple__value--stack">
|
||||||
|
<p class="montzar-simple__ratio">
|
||||||
|
<bdi class="montzar-simple__amount">{{ data.summary.uncovered_count }}</bdi>
|
||||||
|
<span class="montzar-simple__unit">سفارش</span>
|
||||||
|
</p>
|
||||||
|
<p v-if="uncoveredGold > 0" class="montzar-simple__sub-line">
|
||||||
|
<bdi class="number-ltr">{{ formatGram(uncoveredGold) }}</bdi>
|
||||||
|
<span>گرم</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="montzar-simple__actions">
|
||||||
|
<a-button
|
||||||
|
v-if="data.summary.uncovered_count"
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
danger
|
||||||
|
@click="openTab('uncovered')"
|
||||||
|
>
|
||||||
|
ببین کدام سفارشها نزد همکار ثبت نشده
|
||||||
|
</a-button>
|
||||||
|
<a-button size="small" @click="openTab('orders')">همه سفارشها</a-button>
|
||||||
|
<a-button size="small" type="link" class="!px-1" @click="showMore = !showMore">
|
||||||
|
{{ showMore ? 'بستن جزئیات' : 'جزئیات بیشتر' }}
|
||||||
|
</a-button>
|
||||||
|
<a-button size="small" @click="load">بروزرسانی</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="showMore" class="montzar-simple__more">
|
||||||
|
<p v-if="data.mandeh" class="montzar-simple__more-line">
|
||||||
|
مانده حساب نزد همکار الان:
|
||||||
|
<b class="number-ltr" dir="ltr">
|
||||||
|
{{ formatGram(data.mandeh.gold_abs) }} گرم ·
|
||||||
|
{{ formatDashboardAmount(data.mandeh.cash_abs) }} ریال
|
||||||
|
</b>
|
||||||
|
({{ data.mandeh.gold_state }} / {{ data.mandeh.rial_state }})
|
||||||
|
</p>
|
||||||
|
<p v-if="data.colleague_available" class="montzar-simple__more-line">
|
||||||
|
اختلاف گرم دفتر همکار با سفارشهای ثبتشده این بازه:
|
||||||
|
<b class="number-ltr" dir="ltr">{{ formatSignedGram(data.summary.diff_site_gold) }} گرم</b>
|
||||||
|
</p>
|
||||||
|
<div class="montzar-simple__actions mt-1">
|
||||||
|
<a-button
|
||||||
|
v-if="data.summary.diff_count"
|
||||||
|
size="small"
|
||||||
|
danger
|
||||||
|
@click="openTab('diffs')"
|
||||||
|
>
|
||||||
|
موارد اختلاف ({{ data.summary.diff_count }})
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
v-if="data.colleague_available"
|
||||||
|
size="small"
|
||||||
|
@click="openTab('colleague')"
|
||||||
|
>
|
||||||
|
ریز دفتر همکار
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-spin>
|
||||||
|
|
||||||
|
<BasicModal
|
||||||
|
v-model:visible="modalOpen"
|
||||||
|
:title="modalTitle"
|
||||||
|
width="920px"
|
||||||
|
:custom-footer="true"
|
||||||
|
@close="modalOpen = false"
|
||||||
|
>
|
||||||
|
<div v-if="activeTab === 'uncovered'" class="overflow-x-auto">
|
||||||
|
<p class="montzar-simple__modal-intro">
|
||||||
|
این سفارشها در اپ ثبت شدهاند ولی از همکار خرید یا فروش نشدهاند.
|
||||||
|
</p>
|
||||||
|
<table class="montzar-simple__table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>شناسه</th>
|
||||||
|
<th>زمان</th>
|
||||||
|
<th>خرید/فروش</th>
|
||||||
|
<th>مشتری</th>
|
||||||
|
<th>گرم</th>
|
||||||
|
<th>ریال</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="row in uncoveredOrders" :key="`u-${row.id}`">
|
||||||
|
<td>{{ row.id }}</td>
|
||||||
|
<td class="whitespace-nowrap">{{ row.created_at }}</td>
|
||||||
|
<td>{{ row.direction_label }}</td>
|
||||||
|
<td>{{ row.customer_name || '—' }}</td>
|
||||||
|
<td class="number-ltr" dir="ltr">{{ formatGram(row.amount) }}</td>
|
||||||
|
<td class="number-ltr" dir="ltr">{{ formatDashboardAmount(row.cash) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div v-if="!uncoveredOrders.length" class="montzar-simple__empty">موردی نیست</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="activeTab === 'orders'" class="overflow-x-auto">
|
||||||
|
<table class="montzar-simple__table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>شناسه</th>
|
||||||
|
<th>زمان</th>
|
||||||
|
<th>خرید/فروش</th>
|
||||||
|
<th>مشتری</th>
|
||||||
|
<th>گرم</th>
|
||||||
|
<th>نزد همکار؟</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="row in data?.local_orders ?? []" :key="`o-${row.id}`">
|
||||||
|
<td>{{ row.id }}</td>
|
||||||
|
<td class="whitespace-nowrap">{{ row.created_at }}</td>
|
||||||
|
<td>{{ row.direction_label }}</td>
|
||||||
|
<td>{{ row.customer_name || '—' }}</td>
|
||||||
|
<td class="number-ltr" dir="ltr">{{ formatGram(row.amount) }}</td>
|
||||||
|
<td>{{ row.covered ? 'بله' : 'خیر' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="activeTab === 'colleague'" class="overflow-x-auto">
|
||||||
|
<table class="montzar-simple__table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>تاریخ</th>
|
||||||
|
<th>شرح</th>
|
||||||
|
<th>نوع</th>
|
||||||
|
<th>طلا</th>
|
||||||
|
<th>ریال</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="(row, i) in data?.colleague_rows ?? []"
|
||||||
|
:key="`c-${row.id ?? i}`"
|
||||||
|
>
|
||||||
|
<td class="whitespace-nowrap">{{ row.date }}</td>
|
||||||
|
<td>{{ row.desc || '—' }}</td>
|
||||||
|
<td>{{ kindLabel(row.kind) }}</td>
|
||||||
|
<td class="number-ltr" dir="ltr">{{ formatSignedGram(row.net_gold ?? 0) }}</td>
|
||||||
|
<td class="number-ltr" dir="ltr">{{ formatSignedAmount(row.net_rial ?? 0) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="space-y-2">
|
||||||
|
<div
|
||||||
|
v-for="(diff, i) in data?.diffs ?? []"
|
||||||
|
:key="`d-${i}`"
|
||||||
|
class="montzar-simple__diff"
|
||||||
|
>
|
||||||
|
<b>{{ diffCodeLabel(diff.code) }}</b>
|
||||||
|
<p class="m-0 mt-1">{{ diff.reason }}</p>
|
||||||
|
<div v-if="diff.local" class="montzar-simple__diff-meta">
|
||||||
|
سفارش اپ شماره {{ diff.local.id }} · {{ formatGram(diff.local.amount) }} گرم
|
||||||
|
</div>
|
||||||
|
<div v-if="diff.colleague" class="montzar-simple__diff-meta">
|
||||||
|
دفتر همکار: {{ diff.colleague.desc || '—' }} ·
|
||||||
|
{{ formatSignedGram(diff.colleague.net_gold ?? 0) }} گرم
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!(data?.diffs?.length)" class="montzar-simple__empty">مورد اختلافی نیست</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-button @click="modalOpen = false">بستن</a-button>
|
||||||
|
</template>
|
||||||
|
</BasicModal>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import BasicModal from '@/components/BasicModal.vue'
|
||||||
|
import DashboardSectionHeader from '@/pages/dashboard/components/DashboardSectionHeader.vue'
|
||||||
|
import { getMontzarReconcile } from '@/api/dashboard'
|
||||||
|
import type { DashboardHelp } from '@/pages/dashboard/dashboardSectionCopy'
|
||||||
|
import { formatDashboardAmount } from '@/pages/dashboard/dashboardFormat'
|
||||||
|
import type { MontzarLocalItem, MontzarReconcilePayload } from '@/model/dashboard'
|
||||||
|
import { priceProviderSetting } from '@/utils/config'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
startDate: string
|
||||||
|
endDate: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const visible = computed(() => priceProviderSetting.value === 'montzar')
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref('')
|
||||||
|
const data = ref<MontzarReconcilePayload | null>(null)
|
||||||
|
const modalOpen = ref(false)
|
||||||
|
const showMore = ref(false)
|
||||||
|
const activeTab = ref<'uncovered' | 'orders' | 'colleague' | 'diffs'>('uncovered')
|
||||||
|
|
||||||
|
const sectionHelp: DashboardHelp = {
|
||||||
|
description:
|
||||||
|
'هر خرید یا فروش آبشده مشتری باید نزد همکار هم ثبت شود. اینجا میبینید چند تا ثبت شده و چند تا جا مانده.',
|
||||||
|
formula: 'ثبتنشده = سفارش موفق آبشده بدون شناسه ثبت نزد همکار',
|
||||||
|
}
|
||||||
|
|
||||||
|
const uncoveredGold = computed(() => {
|
||||||
|
const u = data.value?.summary.local_uncovered
|
||||||
|
if (!u) return 0
|
||||||
|
return Number(u.buy_gold || 0) + Number(u.sell_gold || 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
const uncoveredOrders = computed<MontzarLocalItem[]>(() => {
|
||||||
|
if (data.value?.local_uncovered_orders?.length) {
|
||||||
|
return data.value.local_uncovered_orders
|
||||||
|
}
|
||||||
|
return (data.value?.local_orders ?? []).filter((o) => !o.covered)
|
||||||
|
})
|
||||||
|
|
||||||
|
const modalTitle = computed(() => {
|
||||||
|
if (activeTab.value === 'uncovered') return 'سفارشهایی که نزد همکار ثبت نشدهاند'
|
||||||
|
if (activeTab.value === 'orders') return 'همه خرید و فروش آبشده'
|
||||||
|
if (activeTab.value === 'diffs') return 'موارد اختلاف'
|
||||||
|
return 'ریز دفتر همکار'
|
||||||
|
})
|
||||||
|
|
||||||
|
function formatGram(value?: number) {
|
||||||
|
return Number(value ?? 0).toLocaleString('en-US', { maximumFractionDigits: 3 })
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSignedGram(value?: number) {
|
||||||
|
const n = Number(value ?? 0)
|
||||||
|
return `${n > 0 ? '+' : ''}${formatGram(n)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSignedAmount(value?: number) {
|
||||||
|
const n = Math.round(Number(value ?? 0))
|
||||||
|
return `${n > 0 ? '+' : ''}${formatDashboardAmount(Math.abs(n))}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function kindLabel(kind?: string) {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
site_sell: 'فروش از سایت',
|
||||||
|
site_buy: 'خرید از سایت',
|
||||||
|
settlement: 'تسویه',
|
||||||
|
physical: 'فیزیکی',
|
||||||
|
gift: 'پاداش',
|
||||||
|
other: 'سایر',
|
||||||
|
}
|
||||||
|
return map[kind ?? ''] ?? kind ?? '—'
|
||||||
|
}
|
||||||
|
|
||||||
|
function diffCodeLabel(code: string) {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
local_uncovered: 'در اپ هست، نزد همکار ثبت نشده',
|
||||||
|
colleague_site_only: 'فقط در دفتر همکار هست',
|
||||||
|
local_covered_not_in_ledger: 'نزد همکار ثبت شده، در دفتر بازه نیست',
|
||||||
|
gift_uncovered: 'پاداش بدون ثبت نزد همکار',
|
||||||
|
}
|
||||||
|
return map[code] ?? code
|
||||||
|
}
|
||||||
|
|
||||||
|
function openTab(tab: 'uncovered' | 'orders' | 'colleague' | 'diffs') {
|
||||||
|
activeTab.value = tab
|
||||||
|
modalOpen.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
if (!visible.value) {
|
||||||
|
data.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loading.value = true
|
||||||
|
error.value = ''
|
||||||
|
try {
|
||||||
|
data.value = (await getMontzarReconcile(props.startDate, props.endDate)) as MontzarReconcilePayload
|
||||||
|
} catch (e: unknown) {
|
||||||
|
data.value = null
|
||||||
|
error.value = String(
|
||||||
|
(e as { response?: { data?: { message?: string } }; message?: string })?.response?.data
|
||||||
|
?.message
|
||||||
|
|| (e as { message?: string })?.message
|
||||||
|
|| 'خطا در محاسبه'
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [visible.value, props.startDate, props.endDate] as const,
|
||||||
|
() => {
|
||||||
|
void load()
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
defineExpose({ load })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.montzar-simple {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
border: 1px solid var(--admin-border);
|
||||||
|
background: var(--admin-bg-elevated);
|
||||||
|
padding: 0.65rem 0.75rem 0.75rem;
|
||||||
|
color: var(--admin-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.montzar-simple__grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.montzar-simple__grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.15rem;
|
||||||
|
min-height: 4.5rem;
|
||||||
|
padding: 0.5rem 0.6rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid var(--admin-border);
|
||||||
|
background: var(--admin-bg-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__card--bad {
|
||||||
|
border-color: rgba(244, 63, 94, 0.45);
|
||||||
|
background: rgba(244, 63, 94, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__card--ok {
|
||||||
|
border-color: rgba(16, 185, 129, 0.4);
|
||||||
|
background: rgba(16, 185, 129, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--admin-text);
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__hint {
|
||||||
|
margin: 0;
|
||||||
|
flex: 1;
|
||||||
|
font-size: 0.625rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__value {
|
||||||
|
margin: 0.15rem 0 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__value--inline {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__value--stack {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__ratio {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__sep {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__amount {
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
font-weight: 800;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
color: var(--admin-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__amount--muted {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--admin-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__card--bad .montzar-simple__amount {
|
||||||
|
color: #e11d48;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__card--ok .montzar-simple__amount {
|
||||||
|
color: #059669;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__unit {
|
||||||
|
font-size: 0.625rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__sub-line {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.2rem;
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
margin-top: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__more {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
border-top: 1px dashed var(--admin-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__more-line {
|
||||||
|
margin: 0 0 0.35rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__more-line b {
|
||||||
|
color: var(--admin-text);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__error {
|
||||||
|
padding: 0.75rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #e11d48;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__empty {
|
||||||
|
padding: 1.5rem 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__modal-intro {
|
||||||
|
margin: 0 0 0.75rem;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__table {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 520px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--admin-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__table th,
|
||||||
|
.montzar-simple__table td {
|
||||||
|
padding: 0.4rem 0.5rem;
|
||||||
|
border-bottom: 1px solid var(--admin-border);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__table th {
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__diff {
|
||||||
|
border: 1px solid var(--admin-border);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 0.6rem 0.75rem;
|
||||||
|
font-size: 12px;
|
||||||
|
background: var(--admin-bg-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__diff-meta {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
html.is-dark {
|
||||||
|
.montzar-simple__card--bad .montzar-simple__amount {
|
||||||
|
color: #fda4af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.montzar-simple__card--ok .montzar-simple__amount {
|
||||||
|
color: #6ee7b7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -275,47 +275,6 @@ export function useDashboardPage() {
|
|||||||
?? (order.sts === 2 ? 'موفق' : order.sts === 1 ? 'باز' : 'ناموفق')
|
?? (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 flowChartOptions = computed(() => {
|
||||||
const mobile = isMobile.value
|
const mobile = isMobile.value
|
||||||
const surface = chartSurfaceOptions()
|
const surface = chartSurfaceOptions()
|
||||||
@@ -628,7 +587,6 @@ export function useDashboardPage() {
|
|||||||
directionClass,
|
directionClass,
|
||||||
stsClass,
|
stsClass,
|
||||||
stsLabel,
|
stsLabel,
|
||||||
financeChartOptions,
|
|
||||||
flowChartOptions,
|
flowChartOptions,
|
||||||
flowChartHeight,
|
flowChartHeight,
|
||||||
peakHoursChartOptions,
|
peakHoursChartOptions,
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ export const dashboardDateMetrics = {
|
|||||||
|
|
||||||
export const dashboardCharts = {
|
export const dashboardCharts = {
|
||||||
incomeMix: help(
|
incomeMix: help(
|
||||||
'تفکیک درآمد: سود اختلاف قیمت، کارمزد ریالی، و ارزش هدایا.',
|
'سهم سود اختلاف قیمت و کارمزد از جمع سود معاملات در بازه انتخابی. هدایا هزینهٔ پاداشاند و اینجا جزو سود نیستند.',
|
||||||
'درآمد = سود اختلاف قیمت + کارمزد + (گرم هدیه × مظنه)'
|
'جمع سود = سود اختلاف قیمت + کارمزد'
|
||||||
),
|
),
|
||||||
buySellVolume: help(
|
buySellVolume: help(
|
||||||
'مقدار طلای خرید و فروش مشتری در هر روز (فقط سفارش موفق).',
|
'مقدار طلای خرید و فروش مشتری در هر روز (فقط سفارش موفق).',
|
||||||
@@ -129,12 +129,18 @@ export const dashboardCustomerMetrics = {
|
|||||||
} as const satisfies Record<string, DashboardHelp>
|
} as const satisfies Record<string, DashboardHelp>
|
||||||
|
|
||||||
export const dashboardWallet = help(
|
export const dashboardWallet = help(
|
||||||
'مجموع موجودی طلا و ریال همه مشتریان.',
|
'مجموع موجودی طلا و ریال همه مشتریان (بدهی اپ به کاربران). این عدد دفتر اکانت همکار نیست.',
|
||||||
'ارزش طلا به ریال = جمع گرم × مظنه لحظهای'
|
'ارزش طلا به ریال = جمع گرم × مظنه لحظهای'
|
||||||
)
|
)
|
||||||
|
|
||||||
export const dashboardWalletMetrics = {
|
export const dashboardWalletMetrics = {
|
||||||
gold: help('مجموع طلای موجود در کیف همه مشتریان.', 'جمع گرم طلا در کیفها'),
|
gold: help(
|
||||||
cash: help('مجموع ریال موجود در کیف همه مشتریان.', 'جمع ریال در کیفها'),
|
'مجموع طلای موجود در کیف همه مشتریان — بدهی به مشتری؛ با ماندهٔ پنل همکار یکی نیست.',
|
||||||
|
'جمع گرم طلا در کیفها'
|
||||||
|
),
|
||||||
|
cash: help(
|
||||||
|
'مجموع ریال موجود در کیف همه مشتریان — شارژ/مانده مشتری؛ با ماندهٔ ریالی همکار یکی نیست.',
|
||||||
|
'جمع ریال در کیفها'
|
||||||
|
),
|
||||||
goldToCash: help('ارزش ریالی کل طلای مشتریان با مظنه فعلی.', 'جمع گرم × قیمت هر گرم'),
|
goldToCash: help('ارزش ریالی کل طلای مشتریان با مظنه فعلی.', 'جمع گرم × قیمت هر گرم'),
|
||||||
} as const satisfies Record<string, DashboardHelp>
|
} as const satisfies Record<string, DashboardHelp>
|
||||||
|
|||||||
@@ -139,25 +139,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<DashboardMontzarReconcile :start-date="startDate" :end-date="endDate" />
|
||||||
|
|
||||||
<!-- Charts row 1 -->
|
<!-- Charts row 1 -->
|
||||||
<section class="mt-2 grid grid-cols-1 gap-2 xl:grid-cols-2">
|
<section class="mt-2 grid grid-cols-1 gap-2 xl:grid-cols-2">
|
||||||
<div class="dashboard-panel">
|
<div class="dashboard-panel">
|
||||||
<DashboardSectionHeader title="ترکیب درآمد" :help="copy.charts.incomeMix" />
|
<DashboardIncomeMix :finance="finance" />
|
||||||
<div class="dashboard-chart-wrap">
|
|
||||||
<apexchart
|
|
||||||
v-if="(finance?.fullPriceSood ?? 0) + (finance?.fullPriceKarmozd ?? 0) + (finance?.GiftsPrise ?? 0) > 0"
|
|
||||||
type="donut"
|
|
||||||
height="220"
|
|
||||||
:options="financeChartOptions"
|
|
||||||
:series="financeChartOptions.series"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="dashboard-empty flex h-44 items-center justify-center text-xs"
|
|
||||||
>
|
|
||||||
در این بازه داده مالی ثبت نشده
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dashboard-panel">
|
<div class="dashboard-panel">
|
||||||
@@ -461,6 +448,8 @@ import DashboardQuickWalletModal from '@/pages/dashboard/components/DashboardQui
|
|||||||
import DashboardSectionHeader from '@/pages/dashboard/components/DashboardSectionHeader.vue'
|
import DashboardSectionHeader from '@/pages/dashboard/components/DashboardSectionHeader.vue'
|
||||||
import DashboardStatCard from '@/pages/dashboard/components/DashboardStatCard.vue'
|
import DashboardStatCard from '@/pages/dashboard/components/DashboardStatCard.vue'
|
||||||
import DashboardProfitSplit from '@/pages/dashboard/components/DashboardProfitSplit.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 {
|
import {
|
||||||
dashboardCharts,
|
dashboardCharts,
|
||||||
dashboardCustomerMetrics,
|
dashboardCustomerMetrics,
|
||||||
@@ -481,12 +470,13 @@ import { metalBalanceTitle, metalLabel } from '@/utils/adminMetalLabels'
|
|||||||
import { formatOrderServiceFee } from '@/utils/orderServiceFee'
|
import { formatOrderServiceFee } from '@/utils/orderServiceFee'
|
||||||
import { getMainMetal } from '@/utils/metalLabels'
|
import { getMainMetal } from '@/utils/metalLabels'
|
||||||
import { TRADE_WEIGHT_UNIT_GRAM, TRADE_WEIGHT_UNIT_KILOGRAM } from '@/utils/weightUnit'
|
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
|
const pargarEnabled = pargarEnabledSetting
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
void initAccountingSettingsFromApi()
|
void initAccountingSettingsFromApi()
|
||||||
|
void initStoreModeFromSettings()
|
||||||
})
|
})
|
||||||
|
|
||||||
const copy = {
|
const copy = {
|
||||||
@@ -533,7 +523,6 @@ const {
|
|||||||
directionClass,
|
directionClass,
|
||||||
stsClass,
|
stsClass,
|
||||||
stsLabel,
|
stsLabel,
|
||||||
financeChartOptions,
|
|
||||||
flowChartOptions,
|
flowChartOptions,
|
||||||
flowChartHeight,
|
flowChartHeight,
|
||||||
peakHoursChartOptions,
|
peakHoursChartOptions,
|
||||||
|
|||||||
Reference in New Issue
Block a user