fix edits for borsemes
This commit is contained in:
@@ -8,3 +8,6 @@ export const changeAccountSts = async (obj: cardSts) =>
|
||||
|
||||
export const getAccountTable = async (params: TableInput) =>
|
||||
await useAxios.post<Array<Card>>('/admin/account/table', params)
|
||||
|
||||
export const deleteAccount = async (id: number) =>
|
||||
await useAxios.delete<boolean>(`/admin/account/${id}`)
|
||||
|
||||
@@ -78,6 +78,10 @@ export interface Order {
|
||||
service_charge_price?: number | string
|
||||
/** درصد کارمزد اعمالشده */
|
||||
service_charge_percentage?: number | string
|
||||
/**
|
||||
* سمت کسر کارمزد: gold = از کیف فلز/مقداری، rial = از ریال
|
||||
*/
|
||||
fee_asset?: 'gold' | 'rial' | string | null
|
||||
/** مبلغ نقدی جابهجاشده: خرید = پرداختی مشتری، فروش = دریافتی مشتری */
|
||||
payment_amount?: number
|
||||
}
|
||||
|
||||
@@ -10,16 +10,18 @@
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
|
||||
<DashboardLabelWithHelp
|
||||
label="مظنه"
|
||||
:help="copy.hero"
|
||||
:help="heroHelp"
|
||||
size="xs"
|
||||
text-class="text-[11px]"
|
||||
label-class="text-white/80"
|
||||
/>
|
||||
<span class="text-[11px] text-white/80">/گرم</span>
|
||||
<span class="text-[11px] text-white/80">/{{ quoteUnitLabel }}</span>
|
||||
<span class="text-lg font-bold leading-none sm:text-xl">{{ usePrice(master?.price ?? 0, true, 'ریال') }}</span>
|
||||
<span class="text-white/35">·</span>
|
||||
<span class="text-[11px] text-white/80">مثقال</span>
|
||||
<span class="text-sm font-semibold leading-none sm:text-base">{{ usePrice(master?.pricem ?? 0, true, 'ریال') }}</span>
|
||||
<template v-if="showMesghalQuote">
|
||||
<span class="text-white/35">·</span>
|
||||
<span class="text-[11px] text-white/80">مثقال</span>
|
||||
<span class="text-sm font-semibold leading-none sm:text-base">{{ usePrice(master?.pricem ?? 0, true, 'ریال') }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<p v-if="master?.update_date" class="mt-1.5 text-[10px] leading-none text-white/55">
|
||||
{{ toJalali(master.update_date) }}
|
||||
@@ -344,7 +346,7 @@
|
||||
<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>
|
||||
<th class="px-2 py-2 text-right">تاریخ</th>
|
||||
</tr>
|
||||
@@ -372,7 +374,15 @@
|
||||
{{ usePrice(order.total_fee ?? 0, true, 'ریال') }}
|
||||
</td>
|
||||
<td class="px-2 py-2.5">
|
||||
{{ usePrice(order.service_charge_price ?? 0, true, 'ریال') }}
|
||||
<div class="leading-snug">
|
||||
<div>{{ orderServiceFee(order).value }}</div>
|
||||
<div
|
||||
v-if="orderServiceFee(order).source"
|
||||
class="dashboard-text-muted text-[11px]"
|
||||
>
|
||||
{{ orderServiceFee(order).source }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-2.5">
|
||||
<span class="rounded-md px-2 py-0.5 text-xs" :class="stsClass(order.sts)">
|
||||
@@ -465,6 +475,9 @@ import { DASHBOARD_RIAL_UNIT, formatDashboardAmount } from '@/pages/dashboard/da
|
||||
import { useDashboardPage } from '@/pages/dashboard/composables/useDashboardPage'
|
||||
import { useDashboardQuickActions } from '@/pages/dashboard/composables/useDashboardQuickActions'
|
||||
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'
|
||||
|
||||
const copy = {
|
||||
hero: dashboardHero,
|
||||
@@ -523,6 +536,22 @@ const {
|
||||
customerStats,
|
||||
} = useDashboardPage()
|
||||
|
||||
const orderServiceFee = formatOrderServiceFee
|
||||
|
||||
const isCopperMainMetal = computed(() => getMainMetal().trim() === 'مس')
|
||||
const quoteUnitLabel = computed(() =>
|
||||
isCopperMainMetal.value ? TRADE_WEIGHT_UNIT_KILOGRAM : TRADE_WEIGHT_UNIT_GRAM
|
||||
)
|
||||
const showMesghalQuote = computed(() => !isCopperMainMetal.value)
|
||||
const heroHelp = computed(() =>
|
||||
isCopperMainMetal.value
|
||||
? {
|
||||
description: 'آخرین قیمت پایه هر کیلوگرم مس در سیستم.',
|
||||
formula: 'مظنه بر حسب کیلوگرم نمایش داده میشود؛ مثقال برای مس کاربرد ندارد.',
|
||||
}
|
||||
: copy.hero
|
||||
)
|
||||
|
||||
const tradingProfitTotal = computed(() => {
|
||||
const priceDiff = Number(finance.value?.fullPriceSood ?? 0)
|
||||
const commission = Number(finance.value?.fullPriceKarmozd ?? finance.value?.fullPriceKaermozd ?? 0)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
:item="item"
|
||||
@open="openSheet"
|
||||
@toggle-sts="confirmToggleSts"
|
||||
@delete="confirmDelete"
|
||||
/>
|
||||
</MobileCardList>
|
||||
|
||||
@@ -37,6 +38,7 @@ type AccountRow = Card & { sts_str?: string; created_at?: string; customer_id?:
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'toggleSts', item: AccountRow): void
|
||||
(e: 'delete', id: number): void
|
||||
}>()
|
||||
|
||||
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<AccountRow>()
|
||||
@@ -55,6 +57,11 @@ function confirmToggleSts(item: AccountRow) {
|
||||
})
|
||||
}
|
||||
|
||||
function confirmDelete(item: AccountRow) {
|
||||
if (item.id == null) return
|
||||
emit('delete', item.id)
|
||||
}
|
||||
|
||||
const { loading, search, items, hasMore, reload, loadMore, fetch } = useMobileTableList<AccountRow>({
|
||||
fetchApi: getAccountTable,
|
||||
onFetched: (rows) => syncSelectedItem(rows)
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
:size="20"
|
||||
@click="$emit('toggleSts', item)"
|
||||
/>
|
||||
<MobileIconButton
|
||||
icon="vuesax-linear:trash"
|
||||
color="#DC2626"
|
||||
:size="20"
|
||||
@click="$emit('delete', item)"
|
||||
/>
|
||||
</div>
|
||||
</MobileCard>
|
||||
</template>
|
||||
@@ -38,6 +44,7 @@ const props = defineProps<{
|
||||
defineEmits<{
|
||||
(e: 'open', item: AccountRow): void
|
||||
(e: 'toggleSts', item: AccountRow): void
|
||||
(e: 'delete', item: AccountRow): void
|
||||
}>()
|
||||
|
||||
const customerName = computed(() => props.item.customer?.name || '-')
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</BaseButton>-->
|
||||
</template>
|
||||
<template #action="{ column, record }">
|
||||
<div class="gap-2 flex justify-center">
|
||||
<div class="gap-2 flex justify-center items-center">
|
||||
<a-tooltip
|
||||
:key="record.sts"
|
||||
placement="bottom"
|
||||
@@ -25,6 +25,12 @@
|
||||
:color="record.sts !== 2 ? 'red' : 'green'"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" class="cursor-pointer" @click="emits('delete', record.id)">
|
||||
<template #title>
|
||||
<span>حذف کارت</span>
|
||||
</template>
|
||||
<Icon icon="vuesax-linear:trash" color="red" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</BaseTable>
|
||||
@@ -34,6 +40,7 @@
|
||||
ref="mobileListRef"
|
||||
class="min-h-0 flex-1"
|
||||
@toggle-sts="changeAccountStsFunction"
|
||||
@delete="(id) => emits('delete', id)"
|
||||
/>
|
||||
</div>
|
||||
</BasicShowContext>
|
||||
|
||||
@@ -2,220 +2,21 @@
|
||||
<div>
|
||||
<BasicMainCard>
|
||||
<div class="flex w-full h-full">
|
||||
<tbl
|
||||
ref="table"
|
||||
class="w-full h-full"
|
||||
@edit="edit"
|
||||
@delete="deleteItem"
|
||||
@withdraw="openWithdraw"
|
||||
/>
|
||||
|
||||
<!-- <BasicMenu :menu="menu" width="13rem" @onChange="tabSelected" />
|
||||
<component :is="selectedComponent" class="mr-5" />-->
|
||||
<!-- <BasicShowContext title="پروژه ها"> <Search /> </BasicShowContext>-->
|
||||
<tbl ref="table" class="w-full h-full" @delete="deleteItem" />
|
||||
</div>
|
||||
</BasicMainCard>
|
||||
<!-- <BasicInfoModal
|
||||
v-model:visible="userInfoVisible"
|
||||
@close="userInfoVisible = false"
|
||||
:title="infoParams.title"
|
||||
:date="infoParams.date"
|
||||
:time="infoParams.time"
|
||||
:type="infoParams.type"
|
||||
:name="infoParams.name"
|
||||
:items="infoParams.items"
|
||||
/>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// import BasicInfoModal from '@/components/BasicInfoModal'
|
||||
import { inputLenRules, inputRules } from '@/utils/inputRules'
|
||||
import { useStaticApiListsStore } from '@/store/staticApisStore'
|
||||
import { ref } from 'vue'
|
||||
import { DatePicker } from 'ant-design-vue'
|
||||
import tbl from './components/table.vue'
|
||||
import type { FormInstance } from 'ant-design-vue'
|
||||
import BasicInfoModal from '@/components/BasicInfoModal/index.vue'
|
||||
import { message } from 'ant-design-vue/es'
|
||||
import type { Info } from '@/components/BasicInfoModal/model/Info'
|
||||
import { toJalaliNoTime, toTime } from '@core/utils/useDataTime'
|
||||
import { confirmDeleteItem } from '@/utils/confirmModal'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import type { Item } from '@/model/Item'
|
||||
import type { Customer } from '@/model/customer'
|
||||
import { createCustomer, deleteCustomer, updateCustomer } from '@/api/customer'
|
||||
import type { Order } from '@/model/order'
|
||||
import type { Wallet, Withdraw } from '@/model/wallet'
|
||||
import { completeWithdraw } from '@/api/wallet'
|
||||
//import DatePicker from '@core/components/_sample/components/datePicker.vue'
|
||||
import { deleteAccount } from '@/api/card'
|
||||
|
||||
//loading
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
//define store
|
||||
const store = useStaticApiListsStore()
|
||||
|
||||
//define tbl
|
||||
const table = ref(null)
|
||||
const refreshTable = () => table?.value?.fetch()
|
||||
|
||||
let updateKey = ref<number>(0)
|
||||
|
||||
//form
|
||||
const form = ref<Withdraw>({})
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
//info
|
||||
let infoParams = reactive<Info>({})
|
||||
|
||||
/*
|
||||
function info(val: Order) {
|
||||
infoParams.name = val.customer.name
|
||||
infoParams.type = val.customer.mobile
|
||||
infoParams.date = toJalaliNoTime(val.created_at)
|
||||
infoParams.time = toTime(val.created_at)
|
||||
infoParams.itemsTitle = 'اطلاعات کاربری'
|
||||
infoParams.items = [
|
||||
{
|
||||
key: 'مقدار (گرم)',
|
||||
value: val.amount ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'قیمت هر گرم طلا برای مشتری (ریال)',
|
||||
value: val.price ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'قیمت کل برای مشتری (ریال)',
|
||||
value: val.total_price ?? '-'
|
||||
},
|
||||
{
|
||||
key: '/قیمت هر گرم طلا بدون سود (ریال)',
|
||||
value: val.price_original ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'قیمت کل بدون سود (ریال)',
|
||||
value: val.total_price_original ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'سود هر گرم (ریال)',
|
||||
value: val.fee ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'سود کل (ریال)',
|
||||
value: val.total_fee ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'کد کاربری',
|
||||
value: val.customer.id ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'شماره ملی',
|
||||
value: val.customer.n_code ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'تاریخ تولد',
|
||||
value: toJalaliNoTime(val.customer.birth_date) ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'نام فروشگاه',
|
||||
value: val.customer.shop_name ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'شماره تلفن فروشگاه',
|
||||
value: val.customer.phone_number ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'نام استان',
|
||||
value: val.customer.province ?? '-'
|
||||
},
|
||||
{
|
||||
key: 'نام شهر',
|
||||
value: val.customer.city ?? '-'
|
||||
}
|
||||
/!* {
|
||||
key: 'نام چاپی',
|
||||
value: val.print_name ?? '-'
|
||||
}*!/
|
||||
]
|
||||
userInfoVisible.value = true
|
||||
}
|
||||
*/
|
||||
|
||||
//edit
|
||||
let editId: number | undefined = undefined
|
||||
|
||||
async function edit(record: Customer) {
|
||||
confirmWithdrawVisible.value = true
|
||||
editId = record.id
|
||||
form.value = cloneDeep(record)
|
||||
}
|
||||
|
||||
//add
|
||||
/*async function add() {
|
||||
loading.value = true
|
||||
editId = undefined
|
||||
// await store.getUsersList()
|
||||
confirmWithdrawVisible.value = true
|
||||
updateKey.value++
|
||||
resetData()
|
||||
loading.value = false
|
||||
}*/
|
||||
|
||||
function openWithdraw(val: number) {
|
||||
confirmWithdrawId = val
|
||||
confirmWithdrawVisible.value = true
|
||||
}
|
||||
|
||||
//reset data
|
||||
const resetData = () => {
|
||||
formRef.value?.resetFields()
|
||||
form.value = {
|
||||
tracked_number: undefined
|
||||
}
|
||||
}
|
||||
|
||||
//delete
|
||||
const deleteItem = (id: number) => confirmDeleteItem('کاربر', id, deleteCustomer, refreshTable)
|
||||
|
||||
//define selects
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
// await getResponsible()
|
||||
// await getQC()
|
||||
// await getManager()
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
//add project modal
|
||||
const confirmWithdrawVisible = ref<boolean>(false)
|
||||
let confirmWithdrawId: number
|
||||
// const userInfoVisible = ref<boolean>(false)
|
||||
|
||||
//submit user
|
||||
async function okWithdraw() {
|
||||
formRef.value?.validateFields().then(async () => {
|
||||
// console.log('salam', form.value)
|
||||
// return
|
||||
loading.value = true
|
||||
try {
|
||||
form.value.id = confirmWithdrawId
|
||||
await completeWithdraw(form.value)
|
||||
confirmWithdrawVisible.value = false
|
||||
refreshTable()
|
||||
resetData()
|
||||
message.success('شماره پیگیری با موفقیت تایید شد')
|
||||
// await getResponsible()
|
||||
// await getQC()
|
||||
// await getManager()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
// message.error('خطا در بارگذاری اطلاعات')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
const deleteItem = (id: number) => confirmDeleteItem('کارت بانکی', id, deleteAccount, refreshTable)
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -31,10 +31,6 @@
|
||||
<span class="order-detail__field-label">محصول</span>
|
||||
<strong>{{ productTitle || '—' }}</strong>
|
||||
<span class="order-detail__chip">{{ order.order_metal_label || '—' }}</span>
|
||||
<span v-if="usesAppPrice" class="order-detail__chip order-detail__chip--soft">قیمت اپ</span>
|
||||
<span v-else-if="feeSourceLabel" class="order-detail__chip order-detail__chip--soft">
|
||||
{{ feeSourceLabel }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
@@ -53,13 +49,17 @@
|
||||
<span class="order-detail__stat-label">جمع سود</span>
|
||||
<strong class="order-detail__stat-value">{{ formatOrderRial(grandTotal) }}</strong>
|
||||
<span class="order-detail__stat-unit">ریال</span>
|
||||
<span class="order-detail__stat-hint">اختلاف قیمت + کارمزد</span>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="order-detail__receipt">
|
||||
<div class="order-detail__receipt-head">
|
||||
<h3 class="order-detail__receipt-title m-0">حساب سود</h3>
|
||||
<h3 class="order-detail__receipt-title m-0">سود اختلاف قیمت</h3>
|
||||
<p class="order-detail__receipt-eq m-0">{{ equationHint }}</p>
|
||||
<p v-if="feeSourceLabel" class="order-detail__receipt-source m-0">
|
||||
منبع اختلاف: {{ feeSourceLabel }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="order-detail__receipt-lines">
|
||||
@@ -69,7 +69,7 @@
|
||||
<div class="min-w-0 flex-1">
|
||||
<strong class="order-detail__receipt-label">بدون سود</strong>
|
||||
<p class="order-detail__receipt-detail m-0">
|
||||
{{ formatOrderRial(order.price_original ?? 0) }} ریال / گرم × {{ amountLabel }}
|
||||
{{ formatOrderRial(order.price_original ?? 0) }} {{ perUnitLabel }} × {{ amountLabel }}
|
||||
</p>
|
||||
</div>
|
||||
<strong class="order-detail__receipt-amount">
|
||||
@@ -79,16 +79,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="receiptLines.length">
|
||||
<template v-if="spreadLines.length">
|
||||
<div
|
||||
v-for="(line, idx) in receiptLines"
|
||||
v-for="(line, idx) in spreadLines"
|
||||
:key="`${line.key ?? line.kind}-${idx}`"
|
||||
class="order-detail__receipt-line"
|
||||
>
|
||||
<div class="order-detail__receipt-line-main">
|
||||
<span class="order-detail__receipt-op" aria-hidden="true">{{ profitSign }}</span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<strong class="order-detail__receipt-label">{{ line.label || 'سود' }}</strong>
|
||||
<strong class="order-detail__receipt-label">{{ line.label || 'سود اختلاف قیمت' }}</strong>
|
||||
<p class="order-detail__receipt-detail m-0">{{ line.line_text || line.detail || '—' }}</p>
|
||||
</div>
|
||||
<strong class="order-detail__receipt-amount">
|
||||
@@ -99,7 +99,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<p v-else class="order-detail__receipt-empty m-0">
|
||||
اقلام سود برای این سفارش ذخیره نشده — سفارش جدید بعد از آپدیت بزنید.
|
||||
سود اختلاف قیمت برای این سفارش ثبت نشده است.
|
||||
</p>
|
||||
|
||||
<div class="order-detail__receipt-line order-detail__receipt-line--result">
|
||||
@@ -108,7 +108,7 @@
|
||||
<div class="min-w-0 flex-1">
|
||||
<strong class="order-detail__receipt-label">قیمت مشتری</strong>
|
||||
<p class="order-detail__receipt-detail m-0">
|
||||
{{ formatOrderRial(order.price ?? 0) }} ریال / گرم × {{ amountLabel }}
|
||||
{{ formatOrderRial(order.price ?? 0) }} {{ perUnitLabel }} × {{ amountLabel }}
|
||||
</p>
|
||||
</div>
|
||||
<strong class="order-detail__receipt-amount order-detail__receipt-amount--focus">
|
||||
@@ -120,6 +120,48 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="order-detail__receipt order-detail__receipt--fee">
|
||||
<div class="order-detail__receipt-head">
|
||||
<h3 class="order-detail__receipt-title m-0">کارمزد معامله</h3>
|
||||
<p class="order-detail__receipt-eq m-0">
|
||||
فقط از درصد کارمزد سرویس — جدا از سود اختلاف قیمت
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="order-detail__receipt-lines">
|
||||
<div class="order-detail__receipt-line">
|
||||
<div class="order-detail__receipt-line-main">
|
||||
<span class="order-detail__receipt-op" aria-hidden="true"> </span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<strong class="order-detail__receipt-label">{{ serviceFee.label }}</strong>
|
||||
<p class="order-detail__receipt-detail m-0">{{ serviceFee.detail }}</p>
|
||||
<span
|
||||
v-if="serviceFee.source"
|
||||
class="order-detail__chip order-detail__chip--fee mt-1 inline-flex"
|
||||
>
|
||||
کسر {{ serviceFee.source }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="serviceFee.total <= 0"
|
||||
class="order-detail__chip order-detail__chip--soft mt-1 inline-flex"
|
||||
>
|
||||
کارمزدی کسر نشده
|
||||
</span>
|
||||
</div>
|
||||
<strong class="order-detail__receipt-amount">
|
||||
<template v-if="serviceFee.source && serviceFee.value !== '۰'">
|
||||
{{ serviceFee.value }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ formatOrderRial(serviceFee.total) }}
|
||||
<span class="order-detail__stat-unit">ریال</span>
|
||||
</template>
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="order-detail__customer">
|
||||
<div class="order-detail__customer-grid">
|
||||
<div class="order-detail__customer-cell">
|
||||
@@ -156,10 +198,11 @@ import { formatAmountWithUnit } from '@/utils/weightUnit'
|
||||
import {
|
||||
formatOrderRial,
|
||||
orderFeeBaseTotal,
|
||||
orderFeeBreakdown,
|
||||
orderFeeBreakdownLines,
|
||||
orderFeeCustomerTotal,
|
||||
orderFeeGrandTotal,
|
||||
orderPricePerUnitLabel,
|
||||
orderServiceChargeDisplay,
|
||||
orderSpreadProfitLines,
|
||||
resolveOrderFeeSourceLabel,
|
||||
resolveOrderProductTitle
|
||||
} from '../utils/orderDetailDisplay'
|
||||
@@ -216,18 +259,22 @@ const amountLabel = computed(() => {
|
||||
|
||||
const productTitle = computed(() => (order.value ? resolveOrderProductTitle(order.value) : ''))
|
||||
const feeSourceLabel = computed(() => (order.value ? resolveOrderFeeSourceLabel(order.value) : ''))
|
||||
const breakdown = computed(() => (order.value ? orderFeeBreakdown(order.value) : null))
|
||||
const usesAppPrice = computed(() => Boolean(breakdown.value?.uses_app_price))
|
||||
const receiptLines = computed(() => (order.value ? orderFeeBreakdownLines(order.value) : []))
|
||||
const spreadLines = computed(() => (order.value ? orderSpreadProfitLines(order.value) : []))
|
||||
const serviceFee = computed(() =>
|
||||
order.value
|
||||
? orderServiceChargeDisplay(order.value)
|
||||
: { total: 0, label: 'کارمزد معامله', detail: '—', source: '', value: '۰' }
|
||||
)
|
||||
const grandTotal = computed(() => (order.value ? orderFeeGrandTotal(order.value) : 0))
|
||||
const baseTotal = computed(() => (order.value ? orderFeeBaseTotal(order.value) : 0))
|
||||
const customerTotal = computed(() => (order.value ? orderFeeCustomerTotal(order.value) : 0))
|
||||
const perUnitLabel = computed(() => (order.value ? orderPricePerUnitLabel(order.value) : 'ریال / گرم'))
|
||||
const isSell = computed(() => order.value?.direction === 2)
|
||||
const profitSign = computed(() => (isSell.value ? '−' : '+'))
|
||||
const equationHint = computed(() =>
|
||||
isSell.value
|
||||
? 'بدون سود − سود = قیمت مشتری'
|
||||
: 'بدون سود + سود = قیمت مشتری'
|
||||
? 'بدون سود − سود اختلاف قیمت = قیمت مشتری'
|
||||
: 'بدون سود + سود اختلاف قیمت = قیمت مشتری'
|
||||
)
|
||||
|
||||
function close() {
|
||||
@@ -331,6 +378,11 @@ function close() {
|
||||
color: var(--admin-text-secondary);
|
||||
}
|
||||
|
||||
.order-detail__stat-hint {
|
||||
@apply mt-0.5 block text-[11px];
|
||||
color: var(--admin-text-secondary);
|
||||
}
|
||||
|
||||
.order-detail__chip {
|
||||
@apply rounded-lg px-2 py-0.5 text-xs font-600;
|
||||
border: 1px solid var(--admin-border);
|
||||
@@ -340,7 +392,12 @@ function close() {
|
||||
.order-detail__chip--soft {
|
||||
color: var(--admin-text-secondary);
|
||||
font-weight: 500;
|
||||
max-width: 18rem;
|
||||
}
|
||||
|
||||
.order-detail__chip--fee {
|
||||
@apply text-primary;
|
||||
border-color: var(--admin-border);
|
||||
background: var(--admin-stat-muted, rgba(255, 255, 255, 0.04));
|
||||
}
|
||||
|
||||
.order-detail__receipt {
|
||||
@@ -363,6 +420,10 @@ function close() {
|
||||
color: var(--admin-text-secondary);
|
||||
}
|
||||
|
||||
.order-detail__receipt-source {
|
||||
@apply mt-1 text-xs text-primary;
|
||||
}
|
||||
|
||||
.order-detail__receipt-lines {
|
||||
@apply flex flex-col gap-2;
|
||||
}
|
||||
|
||||
@@ -45,31 +45,45 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="orderFeeBreakdownLines(row).length" class="space-y-2 rounded-xl border border-[var(--admin-border)] p-3">
|
||||
<p class="m-0 text-sm font-bold">فاکتور سود</p>
|
||||
<div
|
||||
v-for="(line, idx) in orderFeeBreakdownLines(row)"
|
||||
:key="`${line.key ?? line.kind}-${idx}`"
|
||||
class="text-xs"
|
||||
>
|
||||
<p class="m-0 font-600">+ {{ line.label }}</p>
|
||||
<p class="m-0 mt-0.5 opacity-70">{{ line.line_text || line.detail }}</p>
|
||||
<p class="m-0 mt-0.5 font-bold">{{ usePrice(Number(line.total ?? 0), true, '') }} ریال</p>
|
||||
</div>
|
||||
<div class="space-y-2 rounded-xl border border-[var(--admin-border)] p-3">
|
||||
<p class="m-0 text-sm font-bold">سود اختلاف قیمت</p>
|
||||
<p v-if="feeSourceLabel(row)" class="m-0 text-xs opacity-70">منبع: {{ feeSourceLabel(row) }}</p>
|
||||
<template v-if="spreadLines(row).length">
|
||||
<div
|
||||
v-for="(line, idx) in spreadLines(row)"
|
||||
:key="`${line.key ?? line.kind}-${idx}`"
|
||||
class="text-xs"
|
||||
>
|
||||
<p class="m-0 font-600">{{ line.label }}</p>
|
||||
<p class="m-0 mt-0.5 opacity-70">{{ line.line_text || line.detail }}</p>
|
||||
<p class="m-0 mt-0.5 font-bold">{{ usePrice(Number(line.total ?? 0), true, '') }} ریال</p>
|
||||
</div>
|
||||
</template>
|
||||
<p v-else class="m-0 text-xs opacity-70">سود اختلاف قیمت ثبت نشده است.</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 rounded-xl border border-[var(--admin-border)] p-3">
|
||||
<p class="m-0 text-sm font-bold">کارمزد معامله</p>
|
||||
<p class="m-0 text-xs opacity-70">جدا از سود اختلاف قیمت · فقط درصد کارمزد سرویس</p>
|
||||
<MobileDetailRow label="مبلغ" :value="serviceFeeDisplay(row).value" />
|
||||
<MobileDetailRow
|
||||
v-if="serviceFeeDisplay(row).source"
|
||||
label="کسر از"
|
||||
:value="serviceFeeDisplay(row).source"
|
||||
/>
|
||||
<p v-else class="m-0 text-xs opacity-70">کارمزدی کسر نشده</p>
|
||||
</div>
|
||||
|
||||
<MobileInfoGrid>
|
||||
<MobileDetailRow label="محصول" :value="productTitle(row) || '-'" />
|
||||
<MobileDetailRow label="نوع فلز" :value="row.order_metal_label ?? '-'" />
|
||||
<MobileDetailRow label="منبع اختلاف قیمت" :value="feeSourceLabel(row) || '-'" />
|
||||
<MobileDetailRow label="کد سفارش" :value="row.id ?? '-'" />
|
||||
<MobileDetailRow label="شماره موبایل" :value="row.customer?.mobile || '-'" />
|
||||
<MobileDetailRow
|
||||
label="سود اختلاف قیمت (ریال)"
|
||||
:value="usePrice(orderProfitTotal(row), true, '')"
|
||||
:value="usePrice(spreadTotal(row), true, '')"
|
||||
/>
|
||||
<MobileDetailRow label="کارمزد (طلا)" :value="serviceChargeLabel(row)" />
|
||||
<MobileDetailRow label="کارمزد (ریال)" :value="usePrice(row.service_charge_price ?? 0, true, '')" />
|
||||
<MobileDetailRow label="جمع سود (اختلاف + کارمزد)" :value="usePrice(orderFeeGrandTotal(row), true, '')" />
|
||||
<MobileDetailRow label="کد کاربری" :value="row.customer?.id ?? '-'" />
|
||||
<MobileDetailRow label="شماره ملی" :value="row.customer?.n_code || '-'" />
|
||||
<MobileDetailRow label="شماره تلفن" :value="row.customer?.phone_number || '-'" />
|
||||
@@ -91,10 +105,10 @@ import { isMobile } from '@core/utils/is'
|
||||
import { toJalali } from '@core/utils/useDataTime'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { formatAmountWithUnit } from '@/utils/weightUnit'
|
||||
import { formatOrderServiceFee } from '@/utils/orderServiceFee'
|
||||
import {
|
||||
orderFeeBreakdownLines,
|
||||
orderFeeGrandTotal,
|
||||
orderProfitTotal,
|
||||
orderSpreadProfitLines,
|
||||
resolveOrderFeeSourceLabel,
|
||||
resolveOrderProductTitle
|
||||
} from '../utils/orderDetailDisplay'
|
||||
@@ -159,11 +173,16 @@ function amountLabel(row: OrderRow) {
|
||||
return formatAmountWithUnit(row.amount_display ?? row.amount, row.amount_unit)
|
||||
}
|
||||
|
||||
function serviceChargeLabel(row: OrderRow) {
|
||||
if (row.service_charge == null || row.service_charge === '') return '-'
|
||||
const value = Number(row.service_charge)
|
||||
if (Number.isNaN(value)) return String(row.service_charge)
|
||||
return formatAmountWithUnit(value, row.amount_unit)
|
||||
function serviceFeeDisplay(row: OrderRow) {
|
||||
return formatOrderServiceFee(row)
|
||||
}
|
||||
|
||||
function spreadLines(row: OrderRow) {
|
||||
return orderSpreadProfitLines(row)
|
||||
}
|
||||
|
||||
function spreadTotal(row: OrderRow) {
|
||||
return orderSpreadProfitLines(row).reduce((sum, line) => sum + Math.abs(Number(line.total ?? 0)), 0)
|
||||
}
|
||||
|
||||
function productTitle(row: OrderRow) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { toJalali } from '@core/utils/useDataTime'
|
||||
import CustomTag from '@/components/CustomTag.vue'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { formatAmountWithUnit } from '@/utils/weightUnit'
|
||||
import { formatOrderServiceFee } from '@/utils/orderServiceFee'
|
||||
|
||||
export type OrderMetalColumn = { metal_id: number; label: string; display_unit?: string }
|
||||
|
||||
@@ -103,13 +104,18 @@ export function buildOrderColumns(_metals: OrderMetalColumn[] = []): BaseTableCo
|
||||
title: 'کارمزد',
|
||||
dataIndex: 'service_charge',
|
||||
key: 'service_charge',
|
||||
type: 'Number',
|
||||
type: 'Text',
|
||||
customRender({ record }) {
|
||||
const value = record.service_charge
|
||||
if (value == null || value === '') {
|
||||
return '-'
|
||||
const fee = formatOrderServiceFee(record)
|
||||
if (!fee.source) {
|
||||
return fee.value
|
||||
}
|
||||
return formatAmountWithUnit(Number(value), record.amount_unit)
|
||||
return (
|
||||
<div class="leading-snug text-right">
|
||||
<div>{fee.value}</div>
|
||||
<div class="text-[11px] opacity-70">{fee.source}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { Order, OrderFeeBreakdown, OrderFeeBreakdownLine } from '@/model/order'
|
||||
import { formatOrderServiceFee } from '@/utils/orderServiceFee'
|
||||
import { formatAmountWithUnit } from '@/utils/weightUnit'
|
||||
|
||||
type TxLike = {
|
||||
title?: string | null
|
||||
@@ -6,6 +8,15 @@ type TxLike = {
|
||||
product?: { id?: number; title?: string | null } | null
|
||||
}
|
||||
|
||||
function orderAmountUnits(order: Order): number {
|
||||
const n = Number(order.amount_display ?? order.amount ?? 0)
|
||||
return Number.isFinite(n) ? Math.abs(n) : 0
|
||||
}
|
||||
|
||||
function isServiceChargeLine(line: OrderFeeBreakdownLine): boolean {
|
||||
return line.kind === 'service_charge' || line.key === 'service_charge'
|
||||
}
|
||||
|
||||
export function resolveOrderProductTitle(order: Order & { transactions?: TxLike[] }): string {
|
||||
const direct = (order.product_title ?? '').trim()
|
||||
if (direct) return direct
|
||||
@@ -72,32 +83,126 @@ export function orderFeeBreakdownLines(order: Order): OrderFeeBreakdownLine[] {
|
||||
return orderFeeBreakdown(order)?.lines ?? []
|
||||
}
|
||||
|
||||
/** اقلام سود اختلاف قیمت (بدون کارمزد سرویس) — با fallback از fee/total_fee یا اختلاف قیمتها */
|
||||
export function orderSpreadProfitLines(order: Order): OrderFeeBreakdownLine[] {
|
||||
const fromBreakdown = orderFeeBreakdownLines(order).filter((line) => !isServiceChargeLine(line))
|
||||
if (fromBreakdown.length) return fromBreakdown
|
||||
|
||||
const amount = orderAmountUnits(order)
|
||||
const unit = (order.amount_unit || 'گرم').trim() || 'گرم'
|
||||
let perUnit = orderProfitPerUnit(order)
|
||||
let total = orderProfitTotal(order)
|
||||
|
||||
if (perUnit <= 0 && total <= 0) {
|
||||
const base = orderFeeBaseTotal(order)
|
||||
const customer = orderFeeCustomerTotal(order)
|
||||
total = Math.round(Math.abs(customer - base))
|
||||
perUnit = amount > 0 ? Math.round(total / amount) : 0
|
||||
}
|
||||
|
||||
if (perUnit <= 0 && total <= 0) return []
|
||||
|
||||
const detail =
|
||||
perUnit > 0 && amount > 0
|
||||
? `${formatOrderRial(perUnit)} ریال / ${unit} × ${formatAmountWithUnit(amount, unit)}`
|
||||
: 'از فیلدهای ذخیرهشده سفارش'
|
||||
|
||||
return [
|
||||
{
|
||||
kind: 'spread',
|
||||
key: order.fee_source_key || 'fee',
|
||||
label: 'سود اختلاف قیمت',
|
||||
per_unit: perUnit,
|
||||
amount_units: amount,
|
||||
total,
|
||||
detail,
|
||||
line_text: `${detail} = ${formatOrderRial(total)} ریال`,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
/** کارمزد سرویس با منبع کسر (ریال / فلز) */
|
||||
export function orderServiceChargeDisplay(order: Order): {
|
||||
total: number
|
||||
label: string
|
||||
detail: string
|
||||
source: string
|
||||
value: string
|
||||
} {
|
||||
const fee = formatOrderServiceFee(order)
|
||||
const total = Math.round(Math.abs(Number(order.service_charge_price ?? 0)))
|
||||
const pct = Number(order.service_charge_percentage ?? 0)
|
||||
const fromLine = orderFeeBreakdownLines(order).find(isServiceChargeLine)
|
||||
const detailParts: string[] = []
|
||||
if (fromLine?.line_text || fromLine?.detail) {
|
||||
detailParts.push(String(fromLine.line_text || fromLine.detail))
|
||||
} else {
|
||||
if (Number.isFinite(pct) && pct > 0) {
|
||||
detailParts.push(`${pct.toLocaleString('fa-IR', { maximumFractionDigits: 4 })}٪ از fee_service_charge_percentage`)
|
||||
}
|
||||
if (fee.value && fee.value !== '۰') detailParts.push(fee.value)
|
||||
}
|
||||
if (fee.source) detailParts.push(fee.source)
|
||||
|
||||
return {
|
||||
total,
|
||||
label: fee.source ? `کارمزد معامله · ${fee.source}` : 'کارمزد معامله',
|
||||
detail: detailParts.filter(Boolean).join(' · ') || '—',
|
||||
source: fee.source,
|
||||
value: fee.value,
|
||||
}
|
||||
}
|
||||
|
||||
export function orderFeeGrandTotal(order: Order): number {
|
||||
const breakdown = orderFeeBreakdown(order)
|
||||
if (breakdown && breakdown.grand_total != null) {
|
||||
return Math.round(Math.abs(Number(breakdown.grand_total)))
|
||||
const fromBreakdown = Math.round(Math.abs(Number(breakdown.grand_total)))
|
||||
if (fromBreakdown > 0) return fromBreakdown
|
||||
}
|
||||
const spread = orderProfitTotal(order)
|
||||
const spreadLines = orderSpreadProfitLines(order)
|
||||
const spread = spreadLines.reduce((sum, line) => sum + Math.abs(Number(line.total ?? 0)), 0)
|
||||
const service = Math.abs(Number(order.service_charge_price ?? 0))
|
||||
return Math.round(spread + service)
|
||||
}
|
||||
|
||||
function looksLikeUnitPriceAsTotal(total: number, perUnit: number, amount: number): boolean {
|
||||
return amount > 1 && perUnit > 0 && total === Math.round(perUnit)
|
||||
}
|
||||
|
||||
export function orderFeeBaseTotal(order: Order): number {
|
||||
const amount = orderAmountUnits(order)
|
||||
const perUnit = Math.abs(Number(order.price_original ?? 0))
|
||||
const computed = amount > 0 && perUnit > 0 ? Math.round(amount * perUnit) : 0
|
||||
|
||||
const breakdown = orderFeeBreakdown(order)
|
||||
if (breakdown?.base_total != null) {
|
||||
return Math.round(Math.abs(Number(breakdown.base_total)))
|
||||
const fromBreakdown = Math.round(Math.abs(Number(breakdown.base_total)))
|
||||
if (fromBreakdown > 0 && !looksLikeUnitPriceAsTotal(fromBreakdown, perUnit, amount)) {
|
||||
return fromBreakdown
|
||||
}
|
||||
}
|
||||
return Math.round(Math.abs(Number(order.total_price_original ?? 0)))
|
||||
const stored = Math.round(Math.abs(Number(order.total_price_original ?? 0)))
|
||||
// اگر فقط مظنهٔ واحد ذخیره شده (نه جمع)، از حاصلضرب استفاده کن
|
||||
if (stored > 0 && !looksLikeUnitPriceAsTotal(stored, perUnit, amount)) {
|
||||
return stored
|
||||
}
|
||||
return computed || stored
|
||||
}
|
||||
|
||||
export function orderFeeCustomerTotal(order: Order): number {
|
||||
const breakdown = orderFeeBreakdown(order)
|
||||
if (breakdown?.customer_total != null) {
|
||||
return Math.round(Math.abs(Number(breakdown.customer_total)))
|
||||
const fromBreakdown = Math.round(Math.abs(Number(breakdown.customer_total)))
|
||||
if (fromBreakdown > 0) return fromBreakdown
|
||||
}
|
||||
return Math.round(Math.abs(Number(order.total_price ?? 0)))
|
||||
}
|
||||
|
||||
export function orderPricePerUnitLabel(order: Order): string {
|
||||
const unit = (order.amount_unit || 'گرم').trim() || 'گرم'
|
||||
return `ریال / ${unit}`
|
||||
}
|
||||
|
||||
/** مبالغ ریالی — بدون اعشار تا usePrice ویرگول اشتباه نگذارد */
|
||||
export function formatOrderRial(value: number | string | null | undefined): string {
|
||||
const n = Math.round(Math.abs(Number(value ?? 0)))
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { Order } from '@/model/order'
|
||||
import { usePrice } from '@core/utils/usePrice'
|
||||
import { getMainMetal } from '@/utils/metalLabels'
|
||||
|
||||
export type OrderServiceFeeDisplay = {
|
||||
value: string
|
||||
/** از ریال / از مس / از طلا … */
|
||||
source: string
|
||||
}
|
||||
|
||||
/**
|
||||
* نمایش کارمزد سفارش با منبع کسر (fee_asset).
|
||||
* gold → از کیف فلز (گرم + معادل ریالی)
|
||||
* rial / خالی → از ریال
|
||||
*/
|
||||
export function formatOrderServiceFee(order?: Pick<
|
||||
Order,
|
||||
'fee_asset' | 'service_charge' | 'service_charge_price' | 'order_metal_label' | 'amount_unit'
|
||||
> | null): OrderServiceFeeDisplay {
|
||||
const grams = Number(order?.service_charge ?? 0)
|
||||
const rial = Number(order?.service_charge_price ?? 0)
|
||||
|
||||
if ((!Number.isFinite(grams) || grams <= 0) && (!Number.isFinite(rial) || rial <= 0)) {
|
||||
return { value: '۰', source: '' }
|
||||
}
|
||||
|
||||
const fromMetal = String(order?.fee_asset ?? '').toLowerCase() === 'gold'
|
||||
if (fromMetal) {
|
||||
const metalName = (order?.order_metal_label || getMainMetal() || 'فلز').trim()
|
||||
const unit = (order?.amount_unit || 'گرم').trim() || 'گرم'
|
||||
const gramText = Number(grams || 0).toLocaleString('fa-IR', { maximumFractionDigits: 4 })
|
||||
const rialText = usePrice(rial || 0, true, 'ریال')
|
||||
return {
|
||||
value: `${gramText} ${unit} ≈ ${rialText}`,
|
||||
source: `از ${metalName}`,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
value: usePrice(rial || 0, true, 'ریال'),
|
||||
source: 'از ریال',
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user