This commit is contained in:
2026-09-29 22:00:50 +03:30
parent 148f00a75d
commit 4d27da98fc
85 changed files with 865 additions and 522 deletions
@@ -3,9 +3,10 @@ import { computed, ref, watch } from 'vue'
import AdminIcon from '@/components/icons/AdminIcon.vue'
import AdminSelect from '@/components/ui/AdminSelect.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue'
import { formatCurrency, formatMeterage, formatNumber } from '@/utils/format'
import { CUSTOMER_TYPE_LABELS } from '@/utils/adminLabels'
import { ORDER_STATUS_META, RAW_ORDER_STATUS_LABELS, canFinalApproveOrder, canPriceOrder, rawStatusLabel } from '@/utils/orderStatus'
import { formatCurrency, formatDateTime, formatMeterage, formatNumber } from '@/utils/format'
import { CASH_PAYMENT_STATUS_LABELS, CUSTOMER_TYPE_LABELS } from '@/utils/adminLabels'
import AdminImageLightbox from '@/components/ui/AdminImageLightbox.vue'
import { ORDER_STATUS_META, canFinalApproveOrder, canPriceOrder, rawStatusLabel } from '@/utils/orderStatus'
import type { CustomerType, Order, RawOrderStatus } from '@/types'
const props = withDefaults(
@@ -28,25 +29,19 @@ const emit = defineEmits<{
orderId: string
status: RawOrderStatus
note?: string
reason?: string
force?: boolean
},
]
}>()
const manualMode = ref(false)
const nextStatus = ref<RawOrderStatus | ''>('')
const statusNote = ref('')
const receiptPreviewOpen = ref(false)
const allStatuses = Object.keys(RAW_ORDER_STATUS_LABELS) as RawOrderStatus[]
const cashReceipt = computed(() => props.order.cashReceipt ?? null)
const transitions = computed(() => props.order.allowedTransitions ?? [])
const statusOptions = computed<RawOrderStatus[]>(() =>
manualMode.value
? allStatuses.filter((s) => s !== props.order.rawStatus)
: [...transitions.value],
)
const statusOptions = computed<RawOrderStatus[]>(() => [...transitions.value])
const statusSelectOptions = computed(() => [
{ value: '', label: 'انتخاب کنید…' },
@@ -56,10 +51,7 @@ const statusSelectOptions = computed(() => [
})),
])
const noteRequired = computed(() => manualMode.value)
const canSubmitStatus = computed(
() => !!nextStatus.value && (!noteRequired.value || statusNote.value.trim().length > 0),
)
const canSubmitStatus = computed(() => !!nextStatus.value)
const useTable = computed(() => props.layout === 'desktop')
@@ -100,10 +92,13 @@ const primaryAction = computed<'pricing' | 'final' | 'checks' | 'cash' | null>((
return null
})
watch([() => props.order.id, manualMode], () => {
nextStatus.value = ''
statusNote.value = ''
})
watch(
() => props.order.id,
() => {
nextStatus.value = ''
statusNote.value = ''
},
)
function submitStatus() {
if (!nextStatus.value || !canSubmitStatus.value) return
@@ -112,12 +107,18 @@ function submitStatus() {
orderId: props.order.id,
status: nextStatus.value,
note,
reason: manualMode.value ? note : undefined,
force: manualMode.value || undefined,
})
nextStatus.value = ''
statusNote.value = ''
}
function onCashReceiptAction() {
if (cashReceipt.value?.receiptImage) {
receiptPreviewOpen.value = true
return
}
emit('cashReceipt')
}
</script>
<template>
@@ -157,9 +158,9 @@ function submitStatus() {
type="button"
class="admin-btn admin-btn--sm"
:class="primaryAction === 'cash' ? 'admin-btn--primary' : 'admin-btn--outline'"
@click="emit('cashReceipt')"
@click="onCashReceiptAction"
>
رسید پرداخت
{{ cashReceipt?.receiptImage ? 'مشاهده رسید' : 'رسید پرداخت' }}
</button>
<button
v-if="showFinalApprove"
@@ -275,20 +276,75 @@ function submitStatus() {
</ul>
</section>
<section class="order-detail__section order-detail__status no-print">
<div class="order-detail__status-head">
<h3 class="order-detail__section-title">تغییر وضعیت</h3>
<label class="order-detail__manual">
<input v-model="manualMode" type="checkbox" />
<span>تغییر دستی</span>
</label>
<section v-if="cashReceipt" class="order-detail__section">
<h3 class="order-detail__section-title">رسید پرداخت نقدی</h3>
<div class="order-detail__info">
<div class="order-detail__info-row">
<span class="order-detail__info-label">وضعیت</span>
<span class="order-detail__info-value">
{{ CASH_PAYMENT_STATUS_LABELS[cashReceipt.status] }}
</span>
</div>
<div class="order-detail__info-row">
<span class="order-detail__info-label">مبلغ واریزی</span>
<span class="order-detail__info-value">
{{
cashReceipt.paidAmount != null
? formatCurrency(cashReceipt.paidAmount)
: formatCurrency(cashReceipt.amount)
}}
</span>
</div>
<div v-if="cashReceipt.transferDate" class="order-detail__info-row">
<span class="order-detail__info-label">تاریخ واریز</span>
<span class="order-detail__info-value">
{{ formatDateTime(cashReceipt.transferDate) }}
<template v-if="cashReceipt.transferTime"> · {{ cashReceipt.transferTime }}</template>
</span>
</div>
<div v-if="cashReceipt.trackingNumber || cashReceipt.trackingCode" class="order-detail__info-row">
<span class="order-detail__info-label">پیگیری</span>
<span class="order-detail__info-value" dir="ltr">
{{ cashReceipt.trackingNumber || cashReceipt.trackingCode }}
</span>
</div>
<div v-if="cashReceipt.notes" class="order-detail__info-row">
<span class="order-detail__info-label">یادداشت</span>
<span class="order-detail__info-value">{{ cashReceipt.notes }}</span>
</div>
</div>
<p v-if="manualMode" class="order-detail__status-hint">
این گذر خارج از روال استاندارد است؛ ثبت یادداشت اجباری است.
</p>
<p v-else-if="!statusOptions.length" class="order-detail__status-hint">
در این وضعیت گذر خودکاری وجود ندارد. برای تغییر، حالت دستی را فعال کنید.
<div v-if="cashReceipt.receiptImage" class="order-detail__receipt">
<button
type="button"
class="order-detail__receipt-thumb"
@click="receiptPreviewOpen = true"
>
<img :src="cashReceipt.receiptImage" alt="تصویر رسید پرداخت" />
</button>
<button
type="button"
class="admin-btn admin-btn--outline admin-btn--sm"
@click="receiptPreviewOpen = true"
>
مشاهده تصویر رسید
</button>
</div>
<p v-else class="order-detail__status-hint">تصویر رسید هنوز آپلود نشده است.</p>
</section>
<AdminImageLightbox
v-if="cashReceipt?.receiptImage"
v-model="receiptPreviewOpen"
:src="cashReceipt.receiptImage"
alt="تصویر رسید پرداخت"
/>
<section class="order-detail__section order-detail__status no-print">
<h3 class="order-detail__section-title">تغییر وضعیت</h3>
<p v-if="!statusOptions.length" class="order-detail__status-hint">
در این مرحله گذر مجازی از این بخش نیست؛ از اقدام‌های مرحله‌ای بالا (قیمت‌گذاری، چک، رسید نقدی و …) استفاده کنید.
</p>
<div v-if="statusOptions.length" class="order-detail__status-form">
@@ -304,9 +360,7 @@ function submitStatus() {
</div>
<div class="admin-form__field">
<label for="status-note">
یادداشت{{ noteRequired ? ' (اجباری)' : ' (اختیاری)' }}
</label>
<label for="status-note">یادداشت (اختیاری)</label>
<input
id="status-note"
v-model="statusNote"
@@ -477,31 +531,33 @@ function submitStatus() {
color: var(--color-text);
}
.order-detail__status-head {
.order-detail__receipt {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 8px;
margin-top: 12px;
}
.order-detail__status-head .order-detail__section-title {
margin: 0;
}
.order-detail__manual {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.8125rem;
color: var(--color-text-muted);
.order-detail__receipt-thumb {
padding: 0;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: var(--color-bg);
cursor: pointer;
overflow: hidden;
line-height: 0;
}
.order-detail__manual input {
width: 15px;
height: 15px;
accent-color: var(--color-primary);
.order-detail__receipt-thumb img {
display: block;
width: 120px;
height: 120px;
object-fit: cover;
}
.order-detail__status .order-detail__section-title {
margin: 0 0 8px;
}
.order-detail__status-hint {