fix edits for borsemes

This commit is contained in:
2026-07-23 12:28:48 +03:30
parent d3727265e8
commit 73feee2048
6 changed files with 210 additions and 58 deletions
+22 -2
View File
@@ -31,15 +31,33 @@ export interface DashboardMaster {
}> }>
} }
export interface DashboardQuantityProductProfit {
product_id: number
title: string
group_type?: string | null
/** کارمزد کسرشده از کیف همان محصول (گرم/واحد) */
grams: number
value_rial: number
unit?: string
}
export interface DashboardDailyProductProfit {
product_id: number
title: string
grams: number
}
export interface DashboardDailyProfit { export interface DashboardDailyProfit {
date: string date: string
/** سود طلایی روز: کارمزد کسرشده از طلا (گرم) */ /** سود فلزی روز: کارمزد کسرشده از فلز (گرم) */
profit_gold: number profit_gold: number
/** سود ریالی روز: کارمزد کسرشده از ریال */ /** سود ریالی روز: کارمزد کسرشده از ریال */
profit_rial: number profit_rial: number
/** سود اختلاف قیمت روز (ریال) */ /** سود اختلاف قیمت روز (ریال) */
spread_rial: number spread_rial: number
order_count: number order_count: number
/** تفکیک سود مقداری به‌ازای هر محصول در همان روز */
profit_by_product?: DashboardDailyProductProfit[]
} }
export interface DashboardFinance { export interface DashboardFinance {
@@ -54,7 +72,7 @@ export interface DashboardFinance {
Gifts?: string Gifts?: string
GiftsPrise?: number GiftsPrise?: number
sumAll?: number sumAll?: number
/** سود طلایی: مجموع کارمزدهای کسرشده از طلا (گرم) */ /** سود طلایی/فلزی: مجموع کارمزدهای کسرشده از فلز (گرم) */
profitGoldGrams?: number profitGoldGrams?: number
/** سود ریالی: مجموع کارمزدهای کسرشده از ریال */ /** سود ریالی: مجموع کارمزدهای کسرشده از ریال */
profitRial?: number profitRial?: number
@@ -68,6 +86,8 @@ export interface DashboardFinance {
profitGoldConvertedGram?: number profitGoldConvertedGram?: number
/** ارزش ریالی سود طلایی با قیمت روز */ /** ارزش ریالی سود طلایی با قیمت روز */
profitGoldValueRial?: number profitGoldValueRial?: number
/** سود کارمزد فلزی به‌ازای هر محصول مقداری/آبشده */
profitQuantityProducts?: DashboardQuantityProductProfit[]
/** تفکیک روزانهٔ سود طلایی/ریالی */ /** تفکیک روزانهٔ سود طلایی/ریالی */
daily?: DashboardDailyProfit[] daily?: DashboardDailyProfit[]
} }
@@ -3,10 +3,10 @@
<div class="mb-2 flex flex-wrap items-start justify-between gap-2"> <div class="mb-2 flex flex-wrap items-start justify-between gap-2">
<DashboardSectionHeader <DashboardSectionHeader
class="!mb-0 min-w-0 flex-1" class="!mb-0 min-w-0 flex-1"
title="سود طلایی و ریالی" :title="sectionTitle"
:help="sectionHelp" :help="sectionHelp"
/> />
<a-button type="primary" size="small" @click="openSanad"> <a-button v-if="showProfitConversion" type="primary" size="small" @click="openSanad">
ثبت سند تبدیل سود به طلا ثبت سند تبدیل سود به طلا
</a-button> </a-button>
</div> </div>
@@ -40,22 +40,48 @@
<!-- محتوای اصلی --> <!-- محتوای اصلی -->
<div class="dashboard-profit-split__main"> <div class="dashboard-profit-split__main">
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<!-- دو کارت خلاصه --> <!-- کارت‌های خلاصه: هر محصول مقداری + سود ریالی -->
<div class="dashboard-profit-split__cards"> <div class="dashboard-profit-split__cards">
<article class="dashboard-profit-split__card dashboard-profit-split__card--gold"> <article
v-for="item in quantityProductCards"
:key="item.product_id"
class="dashboard-profit-split__card dashboard-profit-split__card--gold"
>
<DashboardLabelWithHelp <DashboardLabelWithHelp
label="سود طلایی" :label="`سود ${item.title}`"
:help="quantityProductHelp"
size="sm"
label-class="dashboard-profit-split__card-title"
/>
<p class="dashboard-profit-split__card-hint">
کارمزد کسرشده از کیف این محصول در بازهٔ انتخابی
</p>
<p class="dashboard-profit-split__value">
<span class="dashboard-profit-split__amount">{{ formatGram(item.grams) }}</span>
<span class="dashboard-profit-split__unit">{{ item.unit || 'گرم' }}</span>
</p>
<p class="dashboard-profit-split__sub">≈ {{ formatDashboardAmount(item.value_rial) }} ریال</p>
</article>
<article
v-if="!quantityProductCards.length"
class="dashboard-profit-split__card dashboard-profit-split__card--gold"
>
<DashboardLabelWithHelp
:label="metalProfitLabel"
:help="goldHelp" :help="goldHelp"
size="sm" size="sm"
label-class="dashboard-profit-split__card-title" label-class="dashboard-profit-split__card-title"
/> />
<p class="dashboard-profit-split__card-hint">کارمزد کسرشده از طلا در بازهٔ انتخابی</p> <p class="dashboard-profit-split__card-hint">
کارمزد کسرشده از {{ metalShortLabel }} در بازهٔ انتخابی
</p>
<p class="dashboard-profit-split__value"> <p class="dashboard-profit-split__value">
<span class="dashboard-profit-split__amount">{{ formatGram(profitGoldGrams) }}</span> <span class="dashboard-profit-split__amount">{{ formatGram(profitGoldGrams) }}</span>
<span class="dashboard-profit-split__unit">گرم</span> <span class="dashboard-profit-split__unit">گرم</span>
</p> </p>
<p class="dashboard-profit-split__sub">≈ {{ formatDashboardAmount(profitGoldValueRial) }} ریال</p> <p class="dashboard-profit-split__sub">≈ {{ formatDashboardAmount(profitGoldValueRial) }} ریال</p>
<p v-if="profitGoldConvertedGram > 0" class="dashboard-profit-split__note"> <p v-if="showProfitConversion && profitGoldConvertedGram > 0" class="dashboard-profit-split__note">
شامل {{ formatGram(profitGoldConvertedGram) }} گرم از تبدیل سود ریالی شامل {{ formatGram(profitGoldConvertedGram) }} گرم از تبدیل سود ریالی
</p> </p>
</article> </article>
@@ -73,7 +99,7 @@
<span class="dashboard-profit-split__unit">ریال</span> <span class="dashboard-profit-split__unit">ریال</span>
</p> </p>
<p class="dashboard-profit-split__sub"> <p class="dashboard-profit-split__sub">
<template v-if="profitRialConverted > 0"> <template v-if="showProfitConversion && profitRialConverted > 0">
{{ formatGram(profitGoldConvertedGram) }} گرم تبدیل‌شده به طلا {{ formatGram(profitGoldConvertedGram) }} گرم تبدیل‌شده به طلا
</template> </template>
<template v-else> <template v-else>
@@ -89,7 +115,16 @@
<thead> <thead>
<tr class="border-b text-xs"> <tr class="border-b text-xs">
<th class="px-2 py-2 text-right">تاریخ</th> <th class="px-2 py-2 text-right">تاریخ</th>
<th class="px-2 py-2 text-right">سود طلایی (گرم)</th> <th
v-for="col in quantityProductColumns"
:key="col.product_id"
class="px-2 py-2 text-right whitespace-nowrap"
>
سود {{ col.title }}
</th>
<th v-if="!quantityProductColumns.length" class="px-2 py-2 text-right">
{{ metalProfitLabel }} (گرم)
</th>
<th class="px-2 py-2 text-right">سود ریالی (ریال)</th> <th class="px-2 py-2 text-right">سود ریالی (ریال)</th>
<th class="px-2 py-2 text-right">سود اختلاف قیمت (ریال)</th> <th class="px-2 py-2 text-right">سود اختلاف قیمت (ریال)</th>
<th class="px-2 py-2 text-right">تعداد سفارش</th> <th class="px-2 py-2 text-right">تعداد سفارش</th>
@@ -97,7 +132,7 @@
</thead> </thead>
<tbody> <tbody>
<tr v-if="!dailyRows.length"> <tr v-if="!dailyRows.length">
<td colspan="5" class="dashboard-text-muted px-2 py-4 text-center text-xs"> <td :colspan="tableColspan" class="dashboard-text-muted px-2 py-4 text-center text-xs">
در این بازه معامله‌ای ثبت نشده در این بازه معامله‌ای ثبت نشده
</td> </td>
</tr> </tr>
@@ -107,7 +142,19 @@
class="border-b hover:bg-[var(--admin-bg-hover)]" class="border-b hover:bg-[var(--admin-bg-hover)]"
> >
<td class="whitespace-nowrap px-2 py-2 text-xs">{{ jalaliDate(row.date) }}</td> <td class="whitespace-nowrap px-2 py-2 text-xs">{{ jalaliDate(row.date) }}</td>
<td class="px-2 py-2 font-semibold" :class="row.profit_gold > 0 ? 'dashboard-profit' : ''"> <td
v-for="col in quantityProductColumns"
:key="`${row.date}-${col.product_id}`"
class="px-2 py-2 font-semibold"
:class="dailyProductGrams(row, col.product_id) > 0 ? 'dashboard-profit' : ''"
>
{{ formatGram(dailyProductGrams(row, col.product_id)) }}
</td>
<td
v-if="!quantityProductColumns.length"
class="px-2 py-2 font-semibold"
:class="row.profit_gold > 0 ? 'dashboard-profit' : ''"
>
{{ formatGram(row.profit_gold) }} {{ formatGram(row.profit_gold) }}
</td> </td>
<td class="px-2 py-2 font-semibold" :class="row.profit_rial > 0 ? 'dashboard-profit' : ''"> <td class="px-2 py-2 font-semibold" :class="row.profit_rial > 0 ? 'dashboard-profit' : ''">
@@ -132,8 +179,8 @@
</div> </div>
</div> </div>
<!-- سندهای تبدیل سود به طلا --> <!-- سندهای تبدیل سود به طلا — فقط وقتی main_metal طلاست -->
<div class="dashboard-profit-split__sanad mt-3"> <div v-if="showProfitConversion" class="dashboard-profit-split__sanad mt-3">
<div class="mb-2 flex flex-wrap items-center justify-between gap-2"> <div class="mb-2 flex flex-wrap items-center justify-between gap-2">
<span class="text-sm font-bold dashboard-text">سندهای تبدیل سود ریالی به طلا</span> <span class="text-sm font-bold dashboard-text">سندهای تبدیل سود ریالی به طلا</span>
<span v-if="conversions.length" class="dashboard-text-muted text-xs"> <span v-if="conversions.length" class="dashboard-text-muted text-xs">
@@ -189,6 +236,7 @@
<!-- مدال ثبت سند --> <!-- مدال ثبت سند -->
<BasicActionSheet <BasicActionSheet
v-if="showProfitConversion"
v-model:visible="sanadVisible" v-model:visible="sanadVisible"
title="ثبت سند تبدیل سود به طلا" title="ثبت سند تبدیل سود به طلا"
modal-width="24rem" modal-width="24rem"
@@ -259,7 +307,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch } from 'vue' import { computed, ref } from 'vue'
import moment from 'moment-jalaali' import moment from 'moment-jalaali'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import DashboardSectionHeader from '@/pages/dashboard/components/DashboardSectionHeader.vue' import DashboardSectionHeader from '@/pages/dashboard/components/DashboardSectionHeader.vue'
@@ -273,9 +321,10 @@ import {
getProfitConversions, getProfitConversions,
storeProfitConversion, storeProfitConversion,
} from '@/api/dashboard' } from '@/api/dashboard'
import type { DashboardFinance, ProfitConversion } from '@/model/dashboard' import type { DashboardDailyProfit, DashboardFinance, ProfitConversion } from '@/model/dashboard'
import type { DashboardHelp } from '@/pages/dashboard/dashboardSectionCopy' import type { DashboardHelp } from '@/pages/dashboard/dashboardSectionCopy'
import { toJalali } from '@core/utils/useDataTime' import { toJalali } from '@core/utils/useDataTime'
import { getMainMetal, m } from '@/utils/metalLabels'
const props = defineProps<{ const props = defineProps<{
/** قیمت هر گرم برای مقدار پیش‌فرض سند */ /** قیمت هر گرم برای مقدار پیش‌فرض سند */
@@ -298,19 +347,39 @@ const rangeEnd = ref(defaultEnd())
const activeStart = ref(rangeStart.value) const activeStart = ref(rangeStart.value)
const activeEnd = ref(rangeEnd.value) const activeEnd = ref(rangeEnd.value)
const isCopperMainMetal = computed(() => getMainMetal().trim() === 'مس')
/** برای مس فقط نمایش سود — بدون ثبت سند تبدیل */
const showProfitConversion = computed(() => !isCopperMainMetal.value)
const metalShortLabel = computed(() => getMainMetal())
const metalProfitLabel = computed(() => m('سود طلایی'))
const sectionTitle = computed(() =>
quantityProductCards.value.length > 0 ? 'سود مقداری و ریالی' : m('سود طلایی و ریالی'),
)
const sectionHelp: DashboardHelp = { const sectionHelp: DashboardHelp = {
description: description:
'کارمزد معاملات بر اساس فیلدی که مشتری وارد می‌کند تفکیک می‌شود: وقتی مشتری «مبلغ ریالی» ثابت می‌زند کارمزد از طلا کسر می‌شود (سود طلایی)، و وقتی «وزن» ثابت می‌زند کارمزد از ریال کسر می‌شود (سود ریالی). اعداد بر اساس بازهٔ انتخابی همین بخش محاسبه می‌شوند.', 'کارمزد معاملات بر اساس فیلدی که مشتری وارد می‌کند تفکیک می‌شود: وقتی مشتری «مبلغ ریالی» ثابت می‌زند کارمزد از کیف همان محصول مقداری/آبشده کسر می‌شود، و وقتی «وزن» ثابت می‌زند کارمزد از ریال کسر می‌شود (سود ریالی). برای هر محصول مقداری سود جداگانه نمایش داده می‌شود.',
formula: 'سود طلایی = جمع کارمزدِ کسرشده از طلا (گرم) + گرم سندهای تبدیل سود ریالی | سود ریالی = جمع کارمزدِ کسرشده از ریال − ریال تبدیل‌شده به طلا', formula:
'سود هر محصول مقداری = جمع کارمزدِ کسرشده از کیف همان محصول | سود ریالی = جمع کارمزدِ کسرشده از ریال',
} }
const goldHelp: DashboardHelp = { const goldHelp: DashboardHelp = {
description: 'مجموع کارمزدهای کسرشده از طلا به‌علاوهٔ گرم سندهای «تبدیل سود ریالی به طلا» در همین بازه.', description: m('مجموع کارمزدهای کسرشده از طلا به‌علاوهٔ گرم سندهای «تبدیل سود ریالی به طلا» در همین بازه.'),
formula: 'کارمزد طلا (گرم) + گرم تبدیل‌شده از سود ریالی', formula: m('کارمزد طلا (گرم) + گرم تبدیل‌شده از سود ریالی'),
} }
const rialHelp: DashboardHelp = { const quantityProductHelp: DashboardHelp = {
description: 'سود ریالی باقی‌مانده پس از کسر سندهای تبدیل به طلا در همین بازه.', description:
formula: 'کارمزد ریالی − ریال تبدیل‌شده به طلا', 'وقتی مشتری مبلغ ریالی ثابت می‌زند، کارمزد از کیف همین محصول مقداری کم می‌شود؛ این عدد مجموع همان کارمزدها در بازه است.',
formula: 'جمع service_charge سفارش‌های مبلغ‌محور همین محصول',
} }
const rialHelp = computed<DashboardHelp>(() => ({
description: showProfitConversion.value
? 'سود ریالی باقی‌مانده پس از کسر سندهای تبدیل به طلا در همین بازه.'
: 'مجموع کارمزدهای کسرشده از ریال در همین بازه.',
formula: showProfitConversion.value
? 'کارمزد ریالی − ریال تبدیل‌شده به طلا'
: 'کارمزد ریالی',
}))
const loading = ref(false) const loading = ref(false)
const finance = ref<DashboardFinance | null>(null) const finance = ref<DashboardFinance | null>(null)
@@ -320,7 +389,17 @@ const profitRial = computed(() => Number(finance.value?.profitRial ?? 0))
const profitGoldValueRial = computed(() => Number(finance.value?.profitGoldValueRial ?? 0)) const profitGoldValueRial = computed(() => Number(finance.value?.profitGoldValueRial ?? 0))
const profitRialConverted = computed(() => Number(finance.value?.profitRialConverted ?? 0)) const profitRialConverted = computed(() => Number(finance.value?.profitRialConverted ?? 0))
const profitGoldConvertedGram = computed(() => Number(finance.value?.profitGoldConvertedGram ?? 0)) const profitGoldConvertedGram = computed(() => Number(finance.value?.profitGoldConvertedGram ?? 0))
const quantityProductCards = computed(() => finance.value?.profitQuantityProducts ?? [])
const quantityProductColumns = computed(() => quantityProductCards.value)
const dailyRows = computed(() => finance.value?.daily ?? []) const dailyRows = computed(() => finance.value?.daily ?? [])
const tableColspan = computed(
() => 4 + (quantityProductColumns.value.length || 1),
)
function dailyProductGrams(row: DashboardDailyProfit, productId: number) {
const match = (row.profit_by_product ?? []).find((p) => Number(p.product_id) === productId)
return Number(match?.grams ?? 0)
}
// صفحه‌بندی جدول روزانه // صفحه‌بندی جدول روزانه
const dailyPage = ref(1) const dailyPage = ref(1)
@@ -416,6 +495,12 @@ async function loadFinance() {
} }
async function loadConversions() { async function loadConversions() {
if (!showProfitConversion.value) {
conversions.value = []
conversionsTotalRial.value = 0
conversionsTotalGram.value = 0
return
}
conversionsLoading.value = true conversionsLoading.value = true
try { try {
const res = await getProfitConversions(activeStart.value, activeEnd.value) const res = await getProfitConversions(activeStart.value, activeEnd.value)
@@ -537,16 +622,10 @@ reloadAll()
.dashboard-profit-split__cards { .dashboard-profit-split__cards {
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
gap: 0.5rem; gap: 0.5rem;
} }
@media (max-width: 640px) {
.dashboard-profit-split__cards {
grid-template-columns: 1fr;
}
}
.dashboard-profit-split__card { .dashboard-profit-split__card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -25,7 +25,7 @@
<span class="dashboard-income-summary__amount">{{ row.amount }}</span> <span class="dashboard-income-summary__amount">{{ row.amount }}</span>
<span class="dashboard-income-summary__unit">{{ unit }}</span> <span class="dashboard-income-summary__unit">{{ unit }}</span>
</p> </p>
<!-- تفکیک کارمزد به ریالی + طلایی --> <!-- تفکیک کارمزد به ریالی + مقداری (به‌ازای محصول) -->
<div v-if="row.breakdown" class="dashboard-income-summary__breakdown"> <div v-if="row.breakdown" class="dashboard-income-summary__breakdown">
<span class="dashboard-income-summary__breakdown-row"> <span class="dashboard-income-summary__breakdown-row">
<span>ریالی</span> <span>ریالی</span>
@@ -33,8 +33,21 @@
{{ formatDashboardAmount(rialFee) }} ریال {{ formatDashboardAmount(rialFee) }} ریال
</span> </span>
</span> </span>
<span class="dashboard-income-summary__breakdown-row"> <template v-if="quantityProducts.length">
<span>طلایی</span> <span
v-for="item in quantityProducts"
:key="item.product_id"
class="dashboard-income-summary__breakdown-row"
>
<span>{{ item.title }}</span>
<span class="dashboard-income-summary__breakdown-val">
{{ formatGram(item.grams) }} {{ item.unit || 'گرم' }} ≈
{{ formatDashboardAmount(item.value_rial) }} ریال
</span>
</span>
</template>
<span v-else class="dashboard-income-summary__breakdown-row">
<span>{{ metalFeeLabel }}</span>
<span class="dashboard-income-summary__breakdown-val"> <span class="dashboard-income-summary__breakdown-val">
{{ formatGram(goldFeeGrams) }} گرم ≈ {{ formatDashboardAmount(goldFeeRial) }} ریال {{ formatGram(goldFeeGrams) }} گرم ≈ {{ formatDashboardAmount(goldFeeRial) }} ریال
</span> </span>
@@ -53,6 +66,7 @@ import { dashboardTradingIncomeSummary, dashboardKpi } from '@/pages/dashboard/d
import { DASHBOARD_RIAL_UNIT, formatDashboardAmount } from '@/pages/dashboard/dashboardFormat' import { DASHBOARD_RIAL_UNIT, formatDashboardAmount } from '@/pages/dashboard/dashboardFormat'
import type { DashboardFinance } from '@/model/dashboard' import type { DashboardFinance } from '@/model/dashboard'
import type { DashboardHelp } from '@/pages/dashboard/dashboardSectionCopy' import type { DashboardHelp } from '@/pages/dashboard/dashboardSectionCopy'
import { m } from '@/utils/metalLabels'
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@@ -70,10 +84,12 @@ const unit = DASHBOARD_RIAL_UNIT
const priceDiffRial = computed(() => Number(props.finance?.fullPriceSood ?? 0)) const priceDiffRial = computed(() => Number(props.finance?.fullPriceSood ?? 0))
// کارمزد ریالی: از معامله‌های وزن‌محور (fee_asset = rial) // کارمزد ریالی: از معامله‌های وزن‌محور (fee_asset = rial)
const rialFee = computed(() => Number(props.finance?.profitRial ?? 0)) const rialFee = computed(() => Number(props.finance?.profitRial ?? 0))
// کارمزد طلایی: از معامله‌های مبلغ‌محور (fee_asset = gold) // کارمزد طلایی/فلزی: از معامله‌های مبلغ‌محور (fee_asset = gold)
const goldFeeGrams = computed(() => Number(props.finance?.profitGoldGrams ?? 0)) const goldFeeGrams = computed(() => Number(props.finance?.profitGoldGrams ?? 0))
const goldFeeRial = computed(() => Number(props.finance?.profitGoldValueRial ?? 0)) const goldFeeRial = computed(() => Number(props.finance?.profitGoldValueRial ?? 0))
// کارمزد ترکیبی به ریال = کارمزد ریالی + معادل ریالی کارمزد طلایی const quantityProducts = computed(() => props.finance?.profitQuantityProducts ?? [])
const metalFeeLabel = computed(() => m('طلایی'))
// کارمزد ترکیبی به ریال = کارمزد ریالی + معادل ریالی کارمزد فلزی
const commissionRial = computed(() => rialFee.value + goldFeeRial.value) const commissionRial = computed(() => rialFee.value + goldFeeRial.value)
// جمع سود = اختلاف قیمت + کارمزد ترکیبی // جمع سود = اختلاف قیمت + کارمزد ترکیبی
const totalRial = computed(() => priceDiffRial.value + commissionRial.value) const totalRial = computed(() => priceDiffRial.value + commissionRial.value)
@@ -84,8 +100,8 @@ function formatGram(value: number) {
const commissionHelp: DashboardHelp = { const commissionHelp: DashboardHelp = {
description: description:
'کارمزد ترکیبی از دو نوع معامله است: در معامله‌های مبلغ‌محور (مشتری مبلغ ریالی ثابت زده) کارمزد از طلا کسر می‌شود، و در معامله‌های وزن‌محور (مشتری وزن ثابت زده) از ریال. این مبلغ مجموع کارمزد ریالی به‌علاوهٔ معادل ریالی کارمزد طلایی است.', 'کارمزد ترکیبی از دو نوع معامله است: در معامله‌های مبلغ‌محور (مشتری مبلغ ریالی ثابت زده) کارمزد از کیف همان محصول مقداری کسر می‌شود، و در معامله‌های وزن‌محور از ریال. این مبلغ مجموع کارمزد ریالی به‌علاوهٔ معادل ریالی کارمزد مقداری است.',
formula: 'کارمزد = کارمزد ریالی + (گرم کارمزد طلایی × قیمت روز)', formula: 'کارمزد = کارمزد ریالی + جمع (گرم کارمزد هر محصول مقداری × قیمت همان محصول)',
} }
const rows = computed(() => [ const rows = computed(() => [
@@ -101,7 +117,9 @@ const rows = computed(() => [
{ {
key: 'commission', key: 'commission',
label: 'کارمزد', label: 'کارمزد',
hint: 'ترکیبی از کارمزد ریالی و طلایی', hint: quantityProducts.value.length
? 'ترکیبی از کارمزد ریالی و محصولات مقداری'
: m('ترکیبی از کارمزد ریالی و طلایی'),
amount: formatDashboardAmount(commissionRial.value), amount: formatDashboardAmount(commissionRial.value),
help: commissionHelp, help: commissionHelp,
breakdown: true, breakdown: true,
+27 -15
View File
@@ -174,7 +174,7 @@ import SettingValueField from './SettingValueField.vue'
import SettingCard from './SettingCard.vue' import SettingCard from './SettingCard.vue'
import { refreshAppModeMenu } from '@/router' import { refreshAppModeMenu } from '@/router'
import { useUserStore } from '@/store/userStore' import { useUserStore } from '@/store/userStore'
import { applyBannerEnabledFromRows, applyTrackingSystemsEnabledFromRows, applyConditionalOrderEnabledFromRows, setBannerEnabledFromSettings, setConditionalOrderEnabledFromSettings, setStoreModeFromSettings, setTrackingSystemsEnabledFromSettings } from '@/utils/config' import { applyBannerEnabledFromRows, applyTrackingSystemsEnabledFromRows, applyConditionalOrderEnabledFromRows, applyPriceProviderFromRows, setBannerEnabledFromSettings, setConditionalOrderEnabledFromSettings, setPriceProviderFromSettings, setStoreModeFromSettings, setTrackingSystemsEnabledFromSettings } from '@/utils/config'
import { import {
EDITABLE_SETTING_GROUPS, EDITABLE_SETTING_GROUPS,
SYSTEM_SETTING_GROUPS, SYSTEM_SETTING_GROUPS,
@@ -278,12 +278,9 @@ const filteredRows = computed(() =>
sortRows( sortRows(
(baseData.value || []).filter((row) => { (baseData.value || []).filter((row) => {
if (row.id == null || !matchesFilter(row)) return false if (row.id == null || !matchesFilter(row)) return false
if (isSystemMode.value) { if (!isSettingVisible(row.key)) return false
// کارمزد سفارش شرطی فقط وقتی قابلیت سراسری روشن است دیده شود if (isSystemMode.value) return true
if (row.key === 'fee_conditional_order' && !isSettingVisible(row.key)) return false return isEditable(row)
return true
}
return isEditable(row) && isSettingVisible(row.key)
}) })
) )
) )
@@ -423,14 +420,21 @@ async function initConfig() {
? await getSystemConfiguration() ? await getSystemConfiguration()
: await getEditableConfiguration()) ?? [] : await getEditableConfiguration()) ?? []
baseData.value = rows.map(normalizeSettingRow) baseData.value = rows.map(normalizeSettingRow)
if (isSystemMode.value) { applyBannerEnabledFromRows(baseData.value)
applyBannerEnabledFromRows(baseData.value) applyTrackingSystemsEnabledFromRows(baseData.value)
applyTrackingSystemsEnabledFromRows(baseData.value) applyConditionalOrderEnabledFromRows(baseData.value)
applyConditionalOrderEnabledFromRows(baseData.value) applyPriceProviderFromRows(baseData.value)
} else { // در صفحهٔ عادی price_provider (can_edit=0) نیست — از کل تنظیمات بخوان
applyBannerEnabledFromRows(baseData.value) if (!baseData.value.some((row) => row.key === 'price_provider')) {
applyTrackingSystemsEnabledFromRows(baseData.value) try {
applyConditionalOrderEnabledFromRows(baseData.value) const { getConfiguration } = await import('@/api/configuration')
const all = await getConfiguration()
applyPriceProviderFromRows(Array.isArray(all) ? all : [])
} catch {
/* کش قبلی می‌ماند */
}
}
if (!isSystemMode.value) {
const storeRow = baseData.value.find((row) => row.key === 'is_store') const storeRow = baseData.value.find((row) => row.key === 'is_store')
if (storeRow) { if (storeRow) {
setStoreModeFromSettings(isSwitchOn(storeRow.value)) setStoreModeFromSettings(isSwitchOn(storeRow.value))
@@ -459,6 +463,11 @@ function onValueChange(row: Const, value: string) {
drafts.value[row.id] = value drafts.value[row.id] = value
// بلافاصله گروه Montzar را با انتخاب منبع قیمت همگام کن
if (row.key === 'price_provider') {
setPriceProviderFromSettings(value)
}
// longtext هم باید ذخیره شود، فقط با delay بیشتر // longtext هم باید ذخیره شود، فقط با delay بیشتر
scheduleSave(row) scheduleSave(row)
} }
@@ -550,6 +559,9 @@ async function saveSettingRow(row: Const) {
if (row.key === 'enable_conditional_order') { if (row.key === 'enable_conditional_order') {
setConditionalOrderEnabledFromSettings(isSwitchOn(value)) setConditionalOrderEnabledFromSettings(isSwitchOn(value))
} }
if (row.key === 'price_provider') {
setPriceProviderFromSettings(value)
}
savedFlashIds.value.add(id) savedFlashIds.value.add(id)
if (saveTimers.has(id)) clearTimeout(saveTimers.get(id)) if (saveTimers.has(id)) clearTimeout(saveTimers.get(id))
saveTimers.set( saveTimers.set(
@@ -1,5 +1,5 @@
import type { Const } from '@/model/configuration' import type { Const } from '@/model/configuration'
import { conditionalOrderEnabledSetting, isStoreApp } from '@/utils/config' import { conditionalOrderEnabledSetting, isStoreApp, priceProviderSetting } from '@/utils/config'
export type SettingFieldType = export type SettingFieldType =
| 'text' | 'text'
@@ -209,6 +209,10 @@ export function isSettingVisible(key: string | undefined | null): boolean {
if (HIDDEN_LEGACY_SETTING_KEYS.has(k)) return false if (HIDDEN_LEGACY_SETTING_KEYS.has(k)) return false
if (STORE_ONLY_SETTING_KEYS.has(k) && !isStoreApp()) return false if (STORE_ONLY_SETTING_KEYS.has(k) && !isStoreApp()) return false
if (k === 'fee_conditional_order' && !conditionalOrderEnabledSetting.value) return false if (k === 'fee_conditional_order' && !conditionalOrderEnabledSetting.value) return false
// گروه همکار Montzar فقط وقتی منبع قیمت montzar است
if ((k.startsWith('montzar_') || k === 'display_montzar') && priceProviderSetting.value !== 'montzar') {
return false
}
return true return true
} }
@@ -552,7 +556,7 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
sell_fee: sell_fee:
'مبلغ کسر (ریال) از قیمت پایه برای سمت فروش. اگر خالی بگذارید، از «اختلاف پایه» استفاده می‌شود.', 'مبلغ کسر (ریال) از قیمت پایه برای سمت فروش. اگر خالی بگذارید، از «اختلاف پایه» استفاده می‌شود.',
fee_service_charge_percentage: fee_service_charge_percentage:
'درصد کارمزد هر معامله از مبلغ سفارش (اعشاری؛ مثلاً ۰٫۰۰۵ یعنی نیم در هزار).', 'درصد کارمزد هر معامله (اعشاری؛ مثلاً ۰٫۰۰۵). روی همهٔ سفارش‌ها اعمال می‌شود و با اختلاف قیمت محصول یا buy_fee/sell_fee قاطی نمی‌شود. محصول مقداری: اگر مبلغ ریالی قفل باشد کارمزد از فلز، اگر وزن قفل باشد از ریال. محصول تعدادی: کارمزد فقط از ریال.',
fee_deduct_from_gold: fee_deduct_from_gold:
'این کلید دیگر روی محاسبه اثری ندارد. مبنای کارمزد اکنون فیلدی است که مشتری وارد می‌کند: اگر «مبلغ ریالی» را وارد کند (مثلاً دقیقاً ۱۰۰ میلیون ریال) همان مبلغ معامله می‌شود و کارمزد از طلا کسر می‌شود؛ اگر «وزن» را وارد کند (مثلاً دقیقاً ۲ گرم) همان وزن معامله می‌شود و کارمزد از ریال کسر می‌شود. این قانون برای خرید و فروش یکسان است.', 'این کلید دیگر روی محاسبه اثری ندارد. مبنای کارمزد اکنون فیلدی است که مشتری وارد می‌کند: اگر «مبلغ ریالی» را وارد کند (مثلاً دقیقاً ۱۰۰ میلیون ریال) همان مبلغ معامله می‌شود و کارمزد از طلا کسر می‌شود؛ اگر «وزن» را وارد کند (مثلاً دقیقاً ۲ گرم) همان وزن معامله می‌شود و کارمزد از ریال کسر می‌شود. این قانون برای خرید و فروش یکسان است.',
require_zero_main_gold_before_other_buy: require_zero_main_gold_before_other_buy:
@@ -872,6 +876,8 @@ export const SETTING_LOCKED_KEYS = new Set([
'chart_id', 'chart_use_local_data', 'chart_id', 'chart_use_local_data',
'external_order_api_enabled', 'external_order_api_enabled',
'fee_deduct_from_gold', 'fee_deduct_from_gold',
'fee_single',
'referral_bonus_amount',
'group_sms', 'group_sms',
'customer_direct_sms', 'customer_direct_sms',
'enable_conditional_order', 'enable_conditional_order',
@@ -921,7 +927,7 @@ export const SETTING_EDITABLE_KEYS = new Set([
'deposit_sms_support_number', 'deposit_support_message_text', 'deposit_sms_support_number', 'deposit_support_message_text',
'deposit_account_holder', 'deposit_card_number', 'deposit_sheba', 'deposit_account_holder', 'deposit_card_number', 'deposit_sheba',
'endDay', 'endDay',
'fee_free', 'fee_service_charge_percentage', 'fee_single', 'fee_free', 'fee_service_charge_percentage',
'gift_for_referral', 'gift_for_referrer', 'gift_for_referral', 'gift_for_referrer',
'invite_link_text', 'invite_link_text',
'jibit_verification', 'jibit_verification',
@@ -930,7 +936,7 @@ export const SETTING_EDITABLE_KEYS = new Set([
'montzar_mobile', 'montzar_open_buy', 'montzar_open_sell', 'montzar_mobile', 'montzar_open_buy', 'montzar_open_sell',
'montzar_password', 'montzar_price', 'montzar_product_id', 'montzar_password', 'montzar_price', 'montzar_product_id',
'open_register', 'open_register',
'referral_bonus_amount', 'referral_id', 'referral_limit', 'referral_gift_on_first_order', 'referral_id', 'referral_limit', 'referral_gift_on_first_order',
'register_jibit_verify', 'register_referral_bonus_message', 'register_referral_sms_to_referrer', 'register_jibit_verify', 'register_referral_bonus_message', 'register_referral_sms_to_referrer',
'sell_fee', 'show_last_price', 'show_gold_ounce_price', 'startDay', 'sell_fee', 'show_last_price', 'show_gold_ounce_price', 'startDay',
'support_messengers', 'support_messenger_text', 'support_messengers', 'support_messenger_text',
+17
View File
@@ -43,6 +43,9 @@ export const customerDirectSmsEnabledSetting = ref(false)
/** reactive — منو و UI سفارش شرطی (ادمین + کنترل‌های مشتری) */ /** reactive — منو و UI سفارش شرطی (ادمین + کنترل‌های مشتری) */
export const conditionalOrderEnabledSetting = ref(false) export const conditionalOrderEnabledSetting = ref(false)
/** reactive — منبع قیمت فعال (montzar / zaarbox / main_product) */
export const priceProviderSetting = ref('')
export const accountingProviderSetting = ref<AccountingProvider>('none') export const accountingProviderSetting = ref<AccountingProvider>('none')
let tahesabEnabledFromSettings: boolean | null = null let tahesabEnabledFromSettings: boolean | null = null
@@ -119,6 +122,10 @@ export function setConditionalOrderEnabledFromSettings(value: boolean): void {
conditionalOrderEnabledSetting.value = value conditionalOrderEnabledSetting.value = value
} }
export function setPriceProviderFromSettings(value?: string | null): void {
priceProviderSetting.value = (value ?? '').trim().toLowerCase()
}
export function applyBannerEnabledFromRows(rows: Array<{ key?: string; value?: string | null }>) { export function applyBannerEnabledFromRows(rows: Array<{ key?: string; value?: string | null }>) {
const row = rows.find((item) => item.key === 'banner_enabled') const row = rows.find((item) => item.key === 'banner_enabled')
setBannerEnabledFromSettings(row ? isSwitchOn(row.value) : false) setBannerEnabledFromSettings(row ? isSwitchOn(row.value) : false)
@@ -143,6 +150,15 @@ export function applyConditionalOrderEnabledFromRows(
setConditionalOrderEnabledFromSettings(row ? isSwitchOn(row.value) : false) setConditionalOrderEnabledFromSettings(row ? isSwitchOn(row.value) : false)
} }
export function applyPriceProviderFromRows(
rows: Array<{ key?: string; value?: string | null }>
) {
const row = rows.find((item) => item.key === 'price_provider')
if (row) {
setPriceProviderFromSettings(row.value)
}
}
async function bootstrapSettingsFromApi(): Promise<void> { async function bootstrapSettingsFromApi(): Promise<void> {
if (settingsBootstrapped) return if (settingsBootstrapped) return
@@ -160,6 +176,7 @@ async function bootstrapSettingsFromApi(): Promise<void> {
applyTrackingSystemsEnabledFromRows(list) applyTrackingSystemsEnabledFromRows(list)
applyCustomerDirectSmsFromRows(list) applyCustomerDirectSmsFromRows(list)
applyConditionalOrderEnabledFromRows(list) applyConditionalOrderEnabledFromRows(list)
applyPriceProviderFromRows(list)
setCachedMainMetal(mainMetalRow?.value) setCachedMainMetal(mainMetalRow?.value)
const { setCachedMainWalletProductIds } = await import('@/components/mobile/utils/wallet') const { setCachedMainWalletProductIds } = await import('@/components/mobile/utils/wallet')
setCachedMainWalletProductIds(parseMainWalletProductIds(mainWalletRow?.value)) setCachedMainWalletProductIds(parseMainWalletProductIds(mainWalletRow?.value))