fix some bugs 4
This commit is contained in:
@@ -1,5 +1,36 @@
|
|||||||
import type { Customer } from '@/model/customer'
|
import type { Customer } from '@/model/customer'
|
||||||
|
|
||||||
|
export interface OrderFeeBreakdownLine {
|
||||||
|
kind?: string
|
||||||
|
key?: string
|
||||||
|
label?: string
|
||||||
|
is_percent?: boolean
|
||||||
|
raw?: number
|
||||||
|
percent_of?: number | null
|
||||||
|
per_unit?: number
|
||||||
|
amount_units?: number
|
||||||
|
total?: number
|
||||||
|
gold?: number
|
||||||
|
rial?: number
|
||||||
|
detail?: string
|
||||||
|
line_text?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderFeeBreakdown {
|
||||||
|
uses_app_price?: boolean
|
||||||
|
amount_units?: number
|
||||||
|
price_original?: number
|
||||||
|
price_customer?: number
|
||||||
|
base_total?: number
|
||||||
|
customer_total?: number
|
||||||
|
spread_per_unit?: number
|
||||||
|
spread_total?: number
|
||||||
|
service_charge_gold?: number
|
||||||
|
service_charge_rial?: number
|
||||||
|
grand_total?: number
|
||||||
|
lines?: OrderFeeBreakdownLine[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface Order {
|
export interface Order {
|
||||||
amount?: number
|
amount?: number
|
||||||
amount_display?: number
|
amount_display?: number
|
||||||
@@ -7,6 +38,19 @@ export interface Order {
|
|||||||
order_metal_id?: number
|
order_metal_id?: number
|
||||||
order_metal_label?: string
|
order_metal_label?: string
|
||||||
metal_prices?: Record<string, number>
|
metal_prices?: Record<string, number>
|
||||||
|
product_id?: number | null
|
||||||
|
product_title?: string | null
|
||||||
|
fee_source?: 'settings' | 'product' | 'app+product' | 'none' | string
|
||||||
|
fee_source_key?: string | null
|
||||||
|
fee_source_label?: string | null
|
||||||
|
fee_breakdown?: OrderFeeBreakdown | null
|
||||||
|
fee_display?: number
|
||||||
|
total_fee_display?: number
|
||||||
|
transactions?: Array<{
|
||||||
|
title?: string | null
|
||||||
|
product_id?: number | null
|
||||||
|
product?: { id?: number; title?: string | null } | null
|
||||||
|
}>
|
||||||
created_at?: string
|
created_at?: string
|
||||||
customer?: Customer
|
customer?: Customer
|
||||||
customer_id?: number
|
customer_id?: number
|
||||||
|
|||||||
@@ -0,0 +1,440 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="visibleProxy"
|
||||||
|
:title="null"
|
||||||
|
:footer="null"
|
||||||
|
centered
|
||||||
|
:width="modalWidth"
|
||||||
|
destroy-on-close
|
||||||
|
class="order-detail-modal"
|
||||||
|
@cancel="close"
|
||||||
|
>
|
||||||
|
<div v-if="order" class="order-detail">
|
||||||
|
<header class="order-detail__header">
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<div class="flex flex-wrap items-center gap-2 mb-1">
|
||||||
|
<p class="order-detail__eyebrow m-0">جزئیات سفارش</p>
|
||||||
|
<span class="order-detail__badge" :class="directionClass">{{ directionLabel }}</span>
|
||||||
|
<span v-if="order.sts_str" class="order-detail__badge order-detail__badge--muted">
|
||||||
|
{{ order.sts_str }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="order-detail__title">{{ order.customer?.name || `سفارش #${order.id ?? '-'}` }}</h2>
|
||||||
|
<p class="order-detail__sub">
|
||||||
|
<span>{{ order.customer?.mobile || '-' }}</span>
|
||||||
|
<span v-if="order.id" class="order-detail__dot">·</span>
|
||||||
|
<span v-if="order.id">کد {{ order.id }}</span>
|
||||||
|
<span class="order-detail__dot">·</span>
|
||||||
|
<span class="order-detail__muted">{{ dateLabel }} {{ timeLabel }}</span>
|
||||||
|
</p>
|
||||||
|
<p class="order-detail__product-line m-0">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<section class="order-detail__stats">
|
||||||
|
<article class="order-detail__stat order-detail__stat--accent">
|
||||||
|
<span class="order-detail__stat-label">{{ paymentLabel }}</span>
|
||||||
|
<strong class="order-detail__stat-value">{{ formatOrderRial(paymentAmount) }}</strong>
|
||||||
|
<span class="order-detail__stat-unit">ریال</span>
|
||||||
|
</article>
|
||||||
|
<article class="order-detail__stat">
|
||||||
|
<span class="order-detail__stat-label">مقدار</span>
|
||||||
|
<strong class="order-detail__stat-value">{{ amountLabel }}</strong>
|
||||||
|
</article>
|
||||||
|
<article class="order-detail__stat">
|
||||||
|
<span class="order-detail__stat-label">جمع سود</span>
|
||||||
|
<strong class="order-detail__stat-value">{{ formatOrderRial(grandTotal) }}</strong>
|
||||||
|
<span class="order-detail__stat-unit">ریال</span>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="order-detail__receipt">
|
||||||
|
<div class="order-detail__receipt-head">
|
||||||
|
<h3 class="order-detail__receipt-title m-0">حساب سود</h3>
|
||||||
|
<p class="order-detail__receipt-eq m-0">{{ equationHint }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="order-detail__receipt-lines">
|
||||||
|
<div class="order-detail__receipt-line order-detail__receipt-line--base">
|
||||||
|
<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">بدون سود</strong>
|
||||||
|
<p class="order-detail__receipt-detail m-0">
|
||||||
|
{{ formatOrderRial(order.price_original ?? 0) }} ریال / گرم × {{ amountLabel }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<strong class="order-detail__receipt-amount">
|
||||||
|
{{ formatOrderRial(baseTotal) }}
|
||||||
|
<span class="order-detail__stat-unit">ریال</span>
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="receiptLines.length">
|
||||||
|
<div
|
||||||
|
v-for="(line, idx) in receiptLines"
|
||||||
|
: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>
|
||||||
|
<p class="order-detail__receipt-detail m-0">{{ line.line_text || line.detail || '—' }}</p>
|
||||||
|
</div>
|
||||||
|
<strong class="order-detail__receipt-amount">
|
||||||
|
{{ formatOrderRial(line.total ?? 0) }}
|
||||||
|
<span class="order-detail__stat-unit">ریال</span>
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<p v-else class="order-detail__receipt-empty m-0">
|
||||||
|
اقلام سود برای این سفارش ذخیره نشده — سفارش جدید بعد از آپدیت بزنید.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="order-detail__receipt-line order-detail__receipt-line--result">
|
||||||
|
<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">قیمت مشتری</strong>
|
||||||
|
<p class="order-detail__receipt-detail m-0">
|
||||||
|
{{ formatOrderRial(order.price ?? 0) }} ریال / گرم × {{ amountLabel }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<strong class="order-detail__receipt-amount order-detail__receipt-amount--focus">
|
||||||
|
{{ formatOrderRial(customerTotal) }}
|
||||||
|
<span class="order-detail__stat-unit">ریال</span>
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="order-detail__customer">
|
||||||
|
<div class="order-detail__customer-grid">
|
||||||
|
<div class="order-detail__customer-cell">
|
||||||
|
<span class="order-detail__field-label">کد کاربری</span>
|
||||||
|
<strong>{{ order.customer?.id ?? '—' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="order-detail__customer-cell">
|
||||||
|
<span class="order-detail__field-label">شماره ملی</span>
|
||||||
|
<strong>{{ order.customer?.n_code || '—' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="order-detail__customer-cell">
|
||||||
|
<span class="order-detail__field-label">تلفن</span>
|
||||||
|
<strong>{{ order.customer?.phone_number || '—' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="order-detail__customer-cell">
|
||||||
|
<span class="order-detail__field-label">استان</span>
|
||||||
|
<strong>{{ order.customer?.province || '—' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="order-detail__customer-cell">
|
||||||
|
<span class="order-detail__field-label">شهر</span>
|
||||||
|
<strong>{{ order.customer?.city || '—' }}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Order } from '@/model/order'
|
||||||
|
import { isMobile } from '@core/utils/is'
|
||||||
|
import { toJalaliNoTime, toTime } from '@core/utils/useDataTime'
|
||||||
|
import { formatAmountWithUnit } from '@/utils/weightUnit'
|
||||||
|
import {
|
||||||
|
formatOrderRial,
|
||||||
|
orderFeeBaseTotal,
|
||||||
|
orderFeeBreakdown,
|
||||||
|
orderFeeBreakdownLines,
|
||||||
|
orderFeeCustomerTotal,
|
||||||
|
orderFeeGrandTotal,
|
||||||
|
resolveOrderFeeSourceLabel,
|
||||||
|
resolveOrderProductTitle
|
||||||
|
} from '../utils/orderDetailDisplay'
|
||||||
|
|
||||||
|
type OrderRow = Order & {
|
||||||
|
sts_str?: string
|
||||||
|
transactions?: Array<{
|
||||||
|
title?: string | null
|
||||||
|
product_id?: number | null
|
||||||
|
product?: { id?: number; title?: string | null } | null
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean
|
||||||
|
order: OrderRow | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', value: boolean): void
|
||||||
|
(e: 'close'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const visibleProxy = computed({
|
||||||
|
get: () => props.visible,
|
||||||
|
set: (value: boolean) => emit('update:visible', value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const modalWidth = computed(() => (isMobile.value ? '96vw' : '72rem'))
|
||||||
|
|
||||||
|
const order = computed(() => props.order)
|
||||||
|
|
||||||
|
const directionLabel = computed(() => (order.value?.direction === 2 ? 'فروش' : 'خرید'))
|
||||||
|
const directionClass = computed(() =>
|
||||||
|
order.value?.direction === 2 ? 'order-detail__badge--sell' : 'order-detail__badge--buy'
|
||||||
|
)
|
||||||
|
|
||||||
|
const dateLabel = computed(() =>
|
||||||
|
order.value?.created_at ? toJalaliNoTime(order.value.created_at) : '—'
|
||||||
|
)
|
||||||
|
const timeLabel = computed(() => (order.value?.created_at ? toTime(order.value.created_at) : ''))
|
||||||
|
|
||||||
|
const paymentLabel = computed(() =>
|
||||||
|
order.value?.direction === 2 ? 'مبلغ دریافتی مشتری' : 'مبلغ پرداختی مشتری'
|
||||||
|
)
|
||||||
|
const paymentAmount = computed(() =>
|
||||||
|
Number(order.value?.payment_amount ?? order.value?.total_price ?? 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
const amountLabel = computed(() => {
|
||||||
|
if (!order.value) return '—'
|
||||||
|
return formatAmountWithUnit(order.value.amount_display ?? order.value.amount, order.value.amount_unit)
|
||||||
|
})
|
||||||
|
|
||||||
|
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 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 isSell = computed(() => order.value?.direction === 2)
|
||||||
|
const profitSign = computed(() => (isSell.value ? '−' : '+'))
|
||||||
|
const equationHint = computed(() =>
|
||||||
|
isSell.value
|
||||||
|
? 'بدون سود − سود = قیمت مشتری'
|
||||||
|
: 'بدون سود + سود = قیمت مشتری'
|
||||||
|
)
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
emit('update:visible', false)
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.order-detail {
|
||||||
|
@apply flex flex-col gap-3 pt-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__muted,
|
||||||
|
.order-detail__field-label {
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__header {
|
||||||
|
@apply flex items-start justify-between gap-3 rounded-xl px-4 py-3;
|
||||||
|
background: var(--admin-stat-muted, rgba(255, 255, 255, 0.04));
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__eyebrow {
|
||||||
|
@apply text-xs;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__title {
|
||||||
|
@apply m-0 text-lg font-bold;
|
||||||
|
color: var(--admin-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__sub {
|
||||||
|
@apply m-0 mt-0.5 flex flex-wrap items-center gap-1 text-sm text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__dot {
|
||||||
|
@apply opacity-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__product-line {
|
||||||
|
@apply mt-2 flex flex-wrap items-center gap-2 text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__product-line strong {
|
||||||
|
@apply font-bold;
|
||||||
|
color: var(--admin-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__badge {
|
||||||
|
@apply rounded-lg border px-2 py-0.5 text-xs font-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__badge--buy {
|
||||||
|
@apply border-green-300/40 text-green-500;
|
||||||
|
background: rgba(34, 197, 94, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__badge--sell {
|
||||||
|
@apply border-red-300/40 text-red-400;
|
||||||
|
background: rgba(248, 113, 113, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__badge--muted {
|
||||||
|
border-color: var(--admin-border);
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__stats {
|
||||||
|
@apply grid grid-cols-1 gap-2 sm:grid-cols-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__stat {
|
||||||
|
@apply rounded-xl px-4 py-2.5;
|
||||||
|
border: 1px solid var(--admin-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__stat--accent {
|
||||||
|
border-color: rgba(34, 197, 94, 0.35);
|
||||||
|
background: rgba(34, 197, 94, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__stat-label,
|
||||||
|
.order-detail__field-label {
|
||||||
|
@apply block text-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__stat-value {
|
||||||
|
@apply mt-0.5 block text-lg font-bold;
|
||||||
|
color: var(--admin-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__stat--accent .order-detail__stat-value {
|
||||||
|
@apply text-green-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__stat-unit {
|
||||||
|
@apply text-xs;
|
||||||
|
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);
|
||||||
|
color: var(--admin-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__chip--soft {
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
font-weight: 500;
|
||||||
|
max-width: 18rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt {
|
||||||
|
@apply rounded-xl px-4 py-3;
|
||||||
|
border: 1px solid var(--admin-border);
|
||||||
|
background: var(--admin-stat-muted, rgba(255, 255, 255, 0.03));
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt-head {
|
||||||
|
@apply mb-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt-title {
|
||||||
|
@apply text-sm font-bold;
|
||||||
|
color: var(--admin-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt-eq {
|
||||||
|
@apply mt-0.5 text-xs;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt-lines {
|
||||||
|
@apply flex flex-col gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt-line {
|
||||||
|
@apply rounded-lg px-3 py-2;
|
||||||
|
border: 1px solid var(--admin-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt-line--base {
|
||||||
|
background: var(--admin-stat-muted, rgba(255, 255, 255, 0.02));
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt-line--result {
|
||||||
|
border-color: rgba(34, 197, 94, 0.35);
|
||||||
|
background: rgba(34, 197, 94, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt-line-main {
|
||||||
|
@apply flex items-start gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt-op {
|
||||||
|
@apply mt-0.5 w-3 shrink-0 text-center text-sm font-bold text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt-label {
|
||||||
|
@apply block text-sm;
|
||||||
|
color: var(--admin-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt-detail {
|
||||||
|
@apply mt-0.5 text-xs leading-relaxed;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt-amount {
|
||||||
|
@apply shrink-0 text-sm font-bold;
|
||||||
|
color: var(--admin-text);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt-amount--focus {
|
||||||
|
@apply text-green-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__receipt-empty {
|
||||||
|
@apply text-xs;
|
||||||
|
color: var(--admin-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__customer {
|
||||||
|
@apply rounded-xl px-4 py-2.5;
|
||||||
|
border: 1px solid var(--admin-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__customer-grid {
|
||||||
|
@apply grid grid-cols-2 gap-x-4 gap-y-2 md:grid-cols-3 lg:grid-cols-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail__customer-cell strong {
|
||||||
|
@apply mt-0.5 block text-sm font-600;
|
||||||
|
color: var(--admin-text);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.order-detail-modal.ant-modal {
|
||||||
|
max-width: min(72rem, 96vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-modal .ant-modal-body {
|
||||||
|
padding: 0.75rem 1.25rem 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -36,17 +36,40 @@
|
|||||||
:label="row.direction === 2 ? 'مبلغ دریافتی مشتری (ریال)' : 'مبلغ پرداختی مشتری (ریال)'"
|
:label="row.direction === 2 ? 'مبلغ دریافتی مشتری (ریال)' : 'مبلغ پرداختی مشتری (ریال)'"
|
||||||
:value="usePrice(row.payment_amount ?? row.total_price ?? 0, true, '')"
|
:value="usePrice(row.payment_amount ?? row.total_price ?? 0, true, '')"
|
||||||
/>
|
/>
|
||||||
<MobileStatCard bordered label="کارمزد (طلا)" :value="serviceChargeLabel(row)" />
|
|
||||||
<MobileStatCard bordered label="کارمزد (ریال)" :value="usePrice(row.service_charge_price ?? 0, true, '')" />
|
|
||||||
<MobileStatCard bordered label="قیمت کل بدون سود (ریال)" :value="usePrice(row.total_price_original ?? 0, true, '')" />
|
<MobileStatCard bordered label="قیمت کل بدون سود (ریال)" :value="usePrice(row.total_price_original ?? 0, true, '')" />
|
||||||
<MobileStatCard bordered label="سود واحد (ریال)" :value="usePrice(row.fee ?? 0, true, '')" />
|
<MobileStatCard
|
||||||
|
bordered
|
||||||
|
variant="warning"
|
||||||
|
label="جمع سود معامله (ریال)"
|
||||||
|
:value="usePrice(orderFeeGrandTotal(row), true, '')"
|
||||||
|
/>
|
||||||
|
</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>
|
</div>
|
||||||
|
|
||||||
<MobileInfoGrid>
|
<MobileInfoGrid>
|
||||||
|
<MobileDetailRow label="محصول" :value="productTitle(row) || '-'" />
|
||||||
<MobileDetailRow label="نوع فلز" :value="row.order_metal_label ?? '-'" />
|
<MobileDetailRow label="نوع فلز" :value="row.order_metal_label ?? '-'" />
|
||||||
|
<MobileDetailRow label="منبع اختلاف قیمت" :value="feeSourceLabel(row) || '-'" />
|
||||||
<MobileDetailRow label="کد سفارش" :value="row.id ?? '-'" />
|
<MobileDetailRow label="کد سفارش" :value="row.id ?? '-'" />
|
||||||
<MobileDetailRow label="شماره موبایل" :value="row.customer?.mobile || '-'" />
|
<MobileDetailRow label="شماره موبایل" :value="row.customer?.mobile || '-'" />
|
||||||
<MobileDetailRow label="سود کل (ریال)" :value="usePrice(row.total_fee ?? 0, true, '')" />
|
<MobileDetailRow
|
||||||
|
label="سود اختلاف قیمت (ریال)"
|
||||||
|
:value="usePrice(orderProfitTotal(row), true, '')"
|
||||||
|
/>
|
||||||
|
<MobileDetailRow label="کارمزد (طلا)" :value="serviceChargeLabel(row)" />
|
||||||
|
<MobileDetailRow label="کارمزد (ریال)" :value="usePrice(row.service_charge_price ?? 0, true, '')" />
|
||||||
<MobileDetailRow label="کد کاربری" :value="row.customer?.id ?? '-'" />
|
<MobileDetailRow label="کد کاربری" :value="row.customer?.id ?? '-'" />
|
||||||
<MobileDetailRow label="شماره ملی" :value="row.customer?.n_code || '-'" />
|
<MobileDetailRow label="شماره ملی" :value="row.customer?.n_code || '-'" />
|
||||||
<MobileDetailRow label="شماره تلفن" :value="row.customer?.phone_number || '-'" />
|
<MobileDetailRow label="شماره تلفن" :value="row.customer?.phone_number || '-'" />
|
||||||
@@ -68,12 +91,24 @@ import { isMobile } from '@core/utils/is'
|
|||||||
import { toJalali } from '@core/utils/useDataTime'
|
import { toJalali } from '@core/utils/useDataTime'
|
||||||
import { usePrice } from '@core/utils/usePrice'
|
import { usePrice } from '@core/utils/usePrice'
|
||||||
import { formatAmountWithUnit } from '@/utils/weightUnit'
|
import { formatAmountWithUnit } from '@/utils/weightUnit'
|
||||||
|
import {
|
||||||
|
orderFeeBreakdownLines,
|
||||||
|
orderFeeGrandTotal,
|
||||||
|
orderProfitTotal,
|
||||||
|
resolveOrderFeeSourceLabel,
|
||||||
|
resolveOrderProductTitle
|
||||||
|
} from '../utils/orderDetailDisplay'
|
||||||
|
|
||||||
type OrderRow = Order & {
|
type OrderRow = Order & {
|
||||||
sts_str?: string
|
sts_str?: string
|
||||||
service_charge?: number | string
|
service_charge?: number | string
|
||||||
service_charge_price?: number | string
|
service_charge_price?: number | string
|
||||||
payment_amount?: number | string
|
payment_amount?: number | string
|
||||||
|
transactions?: Array<{
|
||||||
|
title?: string | null
|
||||||
|
product_id?: number | null
|
||||||
|
product?: { id?: number; title?: string | null } | null
|
||||||
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -130,4 +165,12 @@ function serviceChargeLabel(row: OrderRow) {
|
|||||||
if (Number.isNaN(value)) return String(row.service_charge)
|
if (Number.isNaN(value)) return String(row.service_charge)
|
||||||
return formatAmountWithUnit(value, row.amount_unit)
|
return formatAmountWithUnit(value, row.amount_unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function productTitle(row: OrderRow) {
|
||||||
|
return resolveOrderProductTitle(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
function feeSourceLabel(row: OrderRow) {
|
||||||
|
return resolveOrderFeeSourceLabel(row)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -54,15 +54,10 @@
|
|||||||
@calculate-cash="calculateCash"
|
@calculate-cash="calculateCash"
|
||||||
/>
|
/>
|
||||||
</BasicActionSheet>
|
</BasicActionSheet>
|
||||||
<BasicInfoModal
|
<OrderDetailModal
|
||||||
v-model:visible="userInfoVisible"
|
v-model:visible="userInfoVisible"
|
||||||
|
:order="selectedOrderInfo"
|
||||||
@close="userInfoVisible = false"
|
@close="userInfoVisible = false"
|
||||||
:title="infoParams.title"
|
|
||||||
:date="infoParams.date"
|
|
||||||
:time="infoParams.time"
|
|
||||||
:type="infoParams.type"
|
|
||||||
:name="infoParams.name"
|
|
||||||
:items="infoParams.items"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -73,24 +68,19 @@ import { useStaticApiListsStore } from '@/store/staticApisStore'
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import tbl from './components/table.vue'
|
import tbl from './components/table.vue'
|
||||||
import type { FormInstance } from 'ant-design-vue'
|
import type { FormInstance } from 'ant-design-vue'
|
||||||
import BasicInfoModal from '@/components/BasicInfoModal/index.vue'
|
|
||||||
import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
||||||
import OrderEditorFormBody from './components/OrderEditorFormBody.vue'
|
import OrderEditorFormBody from './components/OrderEditorFormBody.vue'
|
||||||
|
import OrderDetailModal from './components/OrderDetailModal.vue'
|
||||||
import { message } from 'ant-design-vue/es'
|
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 { confirmDeleteItem } from '@/utils/confirmModal'
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
import type { Customer } from '@/model/customer'
|
import type { Customer } from '@/model/customer'
|
||||||
import { deleteCustomer, getCustomersList } from '@/api/customer'
|
import { deleteCustomer, getCustomersList } from '@/api/customer'
|
||||||
import type { Order } from '@/model/order'
|
import type { Order } from '@/model/order'
|
||||||
import { usePrice } from '@core/utils/usePrice'
|
|
||||||
import { getLastPrice } from '@/api/price'
|
import { getLastPrice } from '@/api/price'
|
||||||
import type { last } from '@/model/price'
|
import type { last } from '@/model/price'
|
||||||
import { createOrder } from '@/api/order'
|
import { createOrder } from '@/api/order'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { metalLabel } from '@/utils/adminMetalLabels'
|
|
||||||
import { formatAmountWithUnit } from '@/utils/weightUnit'
|
|
||||||
import { initWalletSettingsFromApi } from '@/utils/config'
|
import { initWalletSettingsFromApi } from '@/utils/config'
|
||||||
|
|
||||||
//loading
|
//loading
|
||||||
@@ -198,99 +188,10 @@ function stopGetPrice() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//info
|
//info
|
||||||
let infoParams = reactive<Info>({})
|
const selectedOrderInfo = ref<Order | null>(null)
|
||||||
|
|
||||||
function info(val: Order) {
|
function info(val: Order) {
|
||||||
infoParams.name = val.customer.name
|
selectedOrderInfo.value = val
|
||||||
infoParams.type = val.customer.mobile
|
|
||||||
infoParams.date = toJalaliNoTime(val.created_at)
|
|
||||||
infoParams.time = toTime(val.created_at)
|
|
||||||
infoParams.itemsTitle = 'اطلاعات کاربری'
|
|
||||||
infoParams.items = [
|
|
||||||
{
|
|
||||||
key: 'مقدار',
|
|
||||||
value: formatAmountWithUnit(val.amount_display ?? val.amount, val.amount_unit)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'نوع فلز',
|
|
||||||
value: val.order_metal_label ?? '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'قیمت زمان معامله (ریال)',
|
|
||||||
value: usePrice(val.price ?? 0) ?? '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'قیمت کل برای مشتری (ریال)',
|
|
||||||
value: usePrice(val.total_price ?? 0) ?? '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: metalLabel.goldGramPriceOriginal(),
|
|
||||||
value: usePrice(val.price_original ?? 0) ?? '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'قیمت کل بدون سود (ریال)',
|
|
||||||
value: usePrice(val.total_price_original ?? 0) ?? '-'
|
|
||||||
},
|
|
||||||
/* {
|
|
||||||
key: 'قیمت کل لیمان (ریال)',
|
|
||||||
value: usePrice(val.total_price_liman ?? 0) ?? '-'
|
|
||||||
},*/
|
|
||||||
{
|
|
||||||
key: 'سود هر گرم (ریال)',
|
|
||||||
value: usePrice(val.fee ?? 0) ?? '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'سود کل (ریال)',
|
|
||||||
value: usePrice(val.total_fee ?? 0) ?? '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'کد کاربری',
|
|
||||||
value: val.customer.id ?? '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'شماره ملی',
|
|
||||||
value: val.customer.n_code ?? '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'کارمزد (طلا)',
|
|
||||||
value:
|
|
||||||
val.service_charge != null && val.service_charge !== ''
|
|
||||||
? formatAmountWithUnit(Number(val.service_charge), val.amount_unit)
|
|
||||||
: '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'کارمزد (ریال)',
|
|
||||||
value: usePrice(Number(val.service_charge_price ?? 0)) ?? '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: val.direction === 2 ? 'مبلغ دریافتی مشتری (ریال)' : 'مبلغ پرداختی مشتری (ریال)',
|
|
||||||
value: usePrice(val.payment_amount ?? val.total_price ?? 0) ?? '-'
|
|
||||||
},
|
|
||||||
/* {
|
|
||||||
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
|
userInfoVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import type { Order, OrderFeeBreakdown, OrderFeeBreakdownLine } from '@/model/order'
|
||||||
|
|
||||||
|
type TxLike = {
|
||||||
|
title?: string | null
|
||||||
|
product_id?: number | null
|
||||||
|
product?: { id?: number; title?: string | null } | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveOrderProductTitle(order: Order & { transactions?: TxLike[] }): string {
|
||||||
|
const direct = (order.product_title ?? '').trim()
|
||||||
|
if (direct) return direct
|
||||||
|
|
||||||
|
const txs = order.transactions ?? []
|
||||||
|
for (const tx of txs) {
|
||||||
|
const fromProduct = (tx.product?.title ?? '').trim()
|
||||||
|
if (fromProduct) return fromProduct
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tx of txs) {
|
||||||
|
const title = (tx.title ?? '').trim()
|
||||||
|
if (title.startsWith('خرید ')) return title.slice(5).trim()
|
||||||
|
if (title.startsWith('فروش ')) return title.slice(5).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveOrderFeeSourceLabel(order: Order): string {
|
||||||
|
const labeled = (order.fee_source_label ?? '').trim()
|
||||||
|
if (labeled) return labeled
|
||||||
|
|
||||||
|
if (order.fee_source === 'app+product') {
|
||||||
|
return order.direction === 2
|
||||||
|
? 'قیمت اپ (sell_fee) + تب اختلاف محصول'
|
||||||
|
: 'قیمت اپ (buy_fee) + تب اختلاف محصول'
|
||||||
|
}
|
||||||
|
if (order.fee_source === 'settings') {
|
||||||
|
const key = order.fee_source_key || (order.direction === 2 ? 'sell_fee' : 'buy_fee')
|
||||||
|
return order.direction === 2
|
||||||
|
? `تنظیمات سراسری · کسر قیمت فروش (${key})`
|
||||||
|
: `تنظیمات سراسری · اضافه قیمت خرید (${key})`
|
||||||
|
}
|
||||||
|
if (order.fee_source === 'product') {
|
||||||
|
return order.direction === 2
|
||||||
|
? 'تب اختلاف محصول · هزینه فروش (cash_fee_sell)'
|
||||||
|
: 'تب اختلاف محصول · هزینه خرید (cash_fee_buy)'
|
||||||
|
}
|
||||||
|
|
||||||
|
const per = orderProfitPerUnit(order)
|
||||||
|
if (per > 0) {
|
||||||
|
return `اختلاف قیمت ذخیرهشده روی سفارش (${per.toLocaleString('fa-IR')} ریال/گرم)`
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export function orderProfitPerUnit(order: Order): number {
|
||||||
|
return Math.abs(Number(order.fee_display ?? order.fee ?? 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function orderProfitTotal(order: Order): number {
|
||||||
|
return Math.abs(Number(order.total_fee_display ?? order.total_fee ?? 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function orderFeeBreakdown(order: Order): OrderFeeBreakdown | null {
|
||||||
|
const raw = order.fee_breakdown
|
||||||
|
if (!raw || typeof raw !== 'object') return null
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
export function orderFeeBreakdownLines(order: Order): OrderFeeBreakdownLine[] {
|
||||||
|
return orderFeeBreakdown(order)?.lines ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
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 spread = orderProfitTotal(order)
|
||||||
|
const service = Math.abs(Number(order.service_charge_price ?? 0))
|
||||||
|
return Math.round(spread + service)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function orderFeeBaseTotal(order: Order): number {
|
||||||
|
const breakdown = orderFeeBreakdown(order)
|
||||||
|
if (breakdown?.base_total != null) {
|
||||||
|
return Math.round(Math.abs(Number(breakdown.base_total)))
|
||||||
|
}
|
||||||
|
return Math.round(Math.abs(Number(order.total_price_original ?? 0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function orderFeeCustomerTotal(order: Order): number {
|
||||||
|
const breakdown = orderFeeBreakdown(order)
|
||||||
|
if (breakdown?.customer_total != null) {
|
||||||
|
return Math.round(Math.abs(Number(breakdown.customer_total)))
|
||||||
|
}
|
||||||
|
return Math.round(Math.abs(Number(order.total_price ?? 0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** مبالغ ریالی — بدون اعشار تا usePrice ویرگول اشتباه نگذارد */
|
||||||
|
export function formatOrderRial(value: number | string | null | undefined): string {
|
||||||
|
const n = Math.round(Math.abs(Number(value ?? 0)))
|
||||||
|
if (!Number.isFinite(n)) return '0'
|
||||||
|
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user