311 lines
8.4 KiB
TypeScript
311 lines
8.4 KiB
TypeScript
import { computed, inject, onMounted, provide, reactive, ref, watch, type InjectionKey } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { companiesApi, customersApi, mapAdminOrder, ordersApi } from '@/services/api'
|
|
import { ApiError } from '@/services/http'
|
|
import type { Company, Customer, Order, RawOrderStatus, SettlementMethod } from '@/types'
|
|
import { rawStatusLabel } from '@/utils/orderStatus'
|
|
|
|
import { useToast } from '@/composables/useToast'
|
|
|
|
export function useOrdersPage() {
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const toast = useToast()
|
|
|
|
const companies = ref<Company[]>([])
|
|
const customers = ref<Customer[]>([])
|
|
const orderList = ref<Order[]>([])
|
|
const activeTab = ref(typeof route.query.tab === 'string' && route.query.tab.trim() ? route.query.tab.trim() : 'all')
|
|
const selectedOrderId = ref<string>('')
|
|
const searchQuery = ref('')
|
|
const showAddModal = ref(false)
|
|
const showDetailPanel = ref(false)
|
|
const showDetailModal = ref(false)
|
|
const formError = ref('')
|
|
const saving = ref(false)
|
|
|
|
const emptyForm = () => ({
|
|
customerId: '',
|
|
customerName: '',
|
|
companyId: companies.value[0]?.id ?? '',
|
|
settlementMethod: 'check' as SettlementMethod,
|
|
meterage: 1000,
|
|
customerPhone: '',
|
|
})
|
|
|
|
const form = reactive(emptyForm())
|
|
|
|
const isFormValid = computed(
|
|
() => form.customerId.length > 0 && form.companyId.length > 0,
|
|
)
|
|
|
|
const activeCustomers = computed(() => {
|
|
const list = [...customers.value]
|
|
list.sort((a, b) => Number(b.isActive) - Number(a.isActive) || a.name.localeCompare(b.name, 'fa'))
|
|
return list
|
|
})
|
|
|
|
function applyCustomer(customerId: string) {
|
|
const customer = customers.value.find((c) => c.id === customerId)
|
|
form.customerId = customerId
|
|
form.customerName = customer?.name ?? ''
|
|
form.customerPhone = customer?.phone ?? ''
|
|
}
|
|
|
|
async function loadCustomers() {
|
|
try {
|
|
customers.value = await customersApi.list()
|
|
} catch (err) {
|
|
toast.error(err instanceof ApiError ? err.message : 'بارگذاری مشتریان انجام نشد.')
|
|
}
|
|
}
|
|
|
|
const filteredOrders = computed(() => {
|
|
let list = orderList.value
|
|
if (activeTab.value !== 'all') {
|
|
list = list.filter((o) => o.status === activeTab.value)
|
|
}
|
|
if (searchQuery.value.trim()) {
|
|
const q = searchQuery.value.trim()
|
|
list = list.filter(
|
|
(o) => o.orderNumber.includes(q) || o.customerName.includes(q),
|
|
)
|
|
}
|
|
return list
|
|
})
|
|
|
|
const selectedOrder = computed(() =>
|
|
orderList.value.find((o) => o.id === selectedOrderId.value) ?? null,
|
|
)
|
|
|
|
function findOrderByRef(ref: string): Order | undefined {
|
|
const key = ref.trim()
|
|
if (!key) return undefined
|
|
return orderList.value.find(
|
|
(o) => o.id === key || o.orderNumber.toUpperCase() === key.toUpperCase(),
|
|
)
|
|
}
|
|
|
|
async function applyOrderDeepLink() {
|
|
const orderRef = typeof route.query.order === 'string' ? route.query.order : ''
|
|
if (!orderRef || !orderList.value.length) return
|
|
|
|
const order = findOrderByRef(orderRef)
|
|
if (!order) {
|
|
searchQuery.value = orderRef
|
|
return
|
|
}
|
|
|
|
const focus = typeof route.query.focus === 'string' ? route.query.focus : ''
|
|
if (focus === 'pricing') {
|
|
await router.replace(`/orders/${order.id}/pricing`)
|
|
return
|
|
}
|
|
if (focus === 'checks') {
|
|
await router.replace(`/orders/${order.id}/checks`)
|
|
return
|
|
}
|
|
|
|
openOrderDetail(order)
|
|
searchQuery.value = order.orderNumber
|
|
}
|
|
|
|
async function loadData() {
|
|
try {
|
|
const [ordersData, companiesData, customersData] = await Promise.all([
|
|
ordersApi.list(),
|
|
companiesApi.list(),
|
|
customersApi.list(),
|
|
])
|
|
orderList.value = ordersData.map((o) => mapAdminOrder(o) as Order)
|
|
companies.value = companiesData
|
|
customers.value = customersData
|
|
if (!form.companyId && companiesData[0]) {
|
|
form.companyId = companiesData[0].id
|
|
}
|
|
await applyOrderDeepLink()
|
|
} catch (err) {
|
|
toast.error(err instanceof ApiError ? err.message : 'بارگذاری سفارشها انجام نشد.')
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
void loadData()
|
|
})
|
|
|
|
watch(
|
|
() => [route.query.order, route.query.focus] as const,
|
|
() => {
|
|
void applyOrderDeepLink()
|
|
},
|
|
)
|
|
|
|
watch(
|
|
() => route.query.tab,
|
|
(tab) => {
|
|
if (typeof tab === 'string' && tab.trim()) {
|
|
activeTab.value = tab.trim()
|
|
}
|
|
},
|
|
)
|
|
|
|
function selectOrder(order: Order, openDetail = false) {
|
|
selectedOrderId.value = order.id
|
|
if (openDetail) {
|
|
showDetailPanel.value = true
|
|
}
|
|
}
|
|
|
|
function openOrderDetail(order: Order) {
|
|
selectedOrderId.value = order.id
|
|
showDetailModal.value = true
|
|
}
|
|
|
|
function closeDetailPanel() {
|
|
showDetailPanel.value = false
|
|
}
|
|
|
|
function closeDetailModal() {
|
|
showDetailModal.value = false
|
|
}
|
|
|
|
function settlementLabel(method?: string | null) {
|
|
if (!method) return 'انتخابنشده'
|
|
return method === 'check' ? 'چکی' : 'نقدی'
|
|
}
|
|
|
|
const statusSaving = ref(false)
|
|
|
|
async function changeStatus(payload: {
|
|
orderId: string
|
|
status: RawOrderStatus
|
|
note?: string
|
|
reason?: string
|
|
force?: boolean
|
|
}) {
|
|
if (statusSaving.value) return null
|
|
statusSaving.value = true
|
|
try {
|
|
const updated = await ordersApi.updateStatus(payload.orderId, {
|
|
status: payload.status,
|
|
note: payload.note,
|
|
reason: payload.reason,
|
|
force: payload.force,
|
|
})
|
|
const mapped = mapAdminOrder(updated) as Order
|
|
const index = orderList.value.findIndex((o) => o.id === payload.orderId)
|
|
if (index >= 0) orderList.value[index] = { ...orderList.value[index]!, ...mapped }
|
|
toast.success(`وضعیت سفارش به «${rawStatusLabel(payload.status)}» تغییر کرد.`)
|
|
return mapped
|
|
} catch (err) {
|
|
toast.error(err instanceof ApiError ? err.message : 'تغییر وضعیت انجام نشد.')
|
|
return null
|
|
} finally {
|
|
statusSaving.value = false
|
|
}
|
|
}
|
|
|
|
function goPricing(order: Order) {
|
|
router.push(`/orders/${order.id}/pricing`)
|
|
}
|
|
|
|
function goChecks(order: Order) {
|
|
router.push(`/orders/${order.id}/checks`)
|
|
}
|
|
|
|
function goCashReceipt(order: Order) {
|
|
router.push({ path: '/cash-payments', query: { order: order.orderNumber } })
|
|
}
|
|
|
|
function openAddModal() {
|
|
Object.assign(form, emptyForm())
|
|
formError.value = ''
|
|
showAddModal.value = true
|
|
void loadCustomers()
|
|
}
|
|
|
|
function closeAddModal() {
|
|
showAddModal.value = false
|
|
formError.value = ''
|
|
}
|
|
|
|
async function submitAddOrder() {
|
|
if (!isFormValid.value) {
|
|
formError.value = 'انتخاب مشتری و شرکت الزامی است.'
|
|
return
|
|
}
|
|
|
|
saving.value = true
|
|
try {
|
|
const created = await ordersApi.create({
|
|
companyId: form.companyId,
|
|
customerId: form.customerId || undefined,
|
|
userId: form.customerId || undefined,
|
|
customerName: form.customerName.trim(),
|
|
customerPhone: form.customerPhone.trim() || undefined,
|
|
settlementMethod: form.settlementMethod,
|
|
meterage: form.meterage,
|
|
})
|
|
const mapped = mapAdminOrder(created) as Order
|
|
orderList.value.unshift(mapped)
|
|
selectedOrderId.value = mapped.id
|
|
toast.success('سفارش جدید ثبت شد.')
|
|
closeAddModal()
|
|
} catch (err) {
|
|
toast.error(err instanceof ApiError ? err.message : 'ثبت سفارش انجام نشد.')
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
|
|
return {
|
|
companies,
|
|
customers,
|
|
activeCustomers,
|
|
applyCustomer,
|
|
loadCustomers,
|
|
orderList,
|
|
activeTab,
|
|
selectedOrderId,
|
|
searchQuery,
|
|
showAddModal,
|
|
showDetailPanel,
|
|
showDetailModal,
|
|
formError,
|
|
form,
|
|
isFormValid,
|
|
filteredOrders,
|
|
selectedOrder,
|
|
selectOrder,
|
|
openOrderDetail,
|
|
closeDetailPanel,
|
|
closeDetailModal,
|
|
settlementLabel,
|
|
changeStatus,
|
|
statusSaving,
|
|
goPricing,
|
|
goChecks,
|
|
goCashReceipt,
|
|
openAddModal,
|
|
closeAddModal,
|
|
submitAddOrder,
|
|
saving,
|
|
}
|
|
}
|
|
|
|
export type OrdersPageContext = ReturnType<typeof useOrdersPage>
|
|
|
|
export const ORDERS_PAGE_KEY: InjectionKey<OrdersPageContext> = Symbol('orders-page')
|
|
|
|
export function provideOrdersPage() {
|
|
const ctx = useOrdersPage()
|
|
provide(ORDERS_PAGE_KEY, ctx)
|
|
return ctx
|
|
}
|
|
|
|
export function useOrdersPageContext() {
|
|
const ctx = inject(ORDERS_PAGE_KEY)
|
|
if (!ctx) throw new Error('Orders page context is missing')
|
|
return ctx
|
|
}
|