Move settlement after invoice and wire real notification inbox.
Submit orders without choosing payment first, route cash/check flows under /orders/:id, show status-based next actions, and connect the header bell to unread notifications. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
import { useCartStore } from './cart'
|
||||
import { useOrderStore } from './order'
|
||||
import { useCatalogStore } from './catalog'
|
||||
import { useNotificationStore } from './notification'
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const user = ref<User | null>(getCurrentUser())
|
||||
@@ -82,6 +83,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
useCatalogStore().loadCompanies(),
|
||||
useCartStore().hydrate(),
|
||||
useOrderStore().hydrate(),
|
||||
useNotificationStore().hydrate(),
|
||||
])
|
||||
} else {
|
||||
error.value = result.message
|
||||
@@ -95,6 +97,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
clearPendingRegister()
|
||||
useCartStore().resetLocal()
|
||||
useOrderStore().resetLocal()
|
||||
useNotificationStore().resetLocal()
|
||||
}
|
||||
|
||||
async function updateProfile(data: Partial<Pick<User, 'firstName' | 'lastName' | 'nationalId'>>) {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { apiRequest, getAccessToken } from '@/services/http'
|
||||
import type { AppNotification } from '@/types'
|
||||
|
||||
export const useNotificationStore = defineStore('notification', () => {
|
||||
const items = ref<AppNotification[]>([])
|
||||
const unreadCount = ref(0)
|
||||
const loading = ref(false)
|
||||
|
||||
const hasUnread = computed(() => unreadCount.value > 0)
|
||||
|
||||
async function hydrate() {
|
||||
if (!getAccessToken()) {
|
||||
items.value = []
|
||||
unreadCount.value = 0
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
items.value = await apiRequest<AppNotification[]>('/notifications')
|
||||
unreadCount.value = items.value.filter((n) => !n.isRead).length
|
||||
} catch {
|
||||
items.value = []
|
||||
unreadCount.value = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** فقط شمارنده؛ برای زنگ هدر که نیازی به کل فهرست ندارد. */
|
||||
async function refreshUnreadCount() {
|
||||
if (!getAccessToken()) {
|
||||
unreadCount.value = 0
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await apiRequest<{ count: number }>('/notifications/unread-count')
|
||||
unreadCount.value = res.count
|
||||
} catch {
|
||||
/* شمارنده حیاتی نیست */
|
||||
}
|
||||
}
|
||||
|
||||
async function markRead(id: string) {
|
||||
const item = items.value.find((n) => n.id === id)
|
||||
if (!item || item.isRead) return
|
||||
item.isRead = true
|
||||
unreadCount.value = Math.max(0, unreadCount.value - 1)
|
||||
try {
|
||||
await apiRequest(`/notifications/${id}/read`, { method: 'PATCH' })
|
||||
} catch {
|
||||
await hydrate()
|
||||
}
|
||||
}
|
||||
|
||||
async function markAllRead() {
|
||||
if (!unreadCount.value) return
|
||||
items.value = items.value.map((n) => ({ ...n, isRead: true }))
|
||||
unreadCount.value = 0
|
||||
try {
|
||||
await apiRequest('/notifications/read-all', { method: 'PATCH' })
|
||||
} catch {
|
||||
await hydrate()
|
||||
}
|
||||
}
|
||||
|
||||
function resetLocal() {
|
||||
items.value = []
|
||||
unreadCount.value = 0
|
||||
}
|
||||
|
||||
return {
|
||||
items,
|
||||
unreadCount,
|
||||
loading,
|
||||
hasUnread,
|
||||
hydrate,
|
||||
refreshUnreadCount,
|
||||
markRead,
|
||||
markAllRead,
|
||||
resetLocal,
|
||||
}
|
||||
})
|
||||
+70
-49
@@ -18,7 +18,7 @@ export const useOrderStore = defineStore('order', () => {
|
||||
const draftChecks = ref<CheckData[]>([])
|
||||
const draftSettlementType = ref<SettlementType | null>(null)
|
||||
const draftCheckMode = ref<CheckRegistrationMode | null>(null)
|
||||
const checkoutCartId = ref<string | null>(null)
|
||||
const draftOrderId = ref<string | null>(null)
|
||||
const loading = ref(false)
|
||||
|
||||
const userOrders = computed(
|
||||
@@ -32,11 +32,27 @@ export const useOrderStore = defineStore('order', () => {
|
||||
return orders.value.find((o) => o.id === id)
|
||||
}
|
||||
|
||||
function startCheckout(cart: Cart, settlementType: SettlementType) {
|
||||
checkoutCartId.value = cart.id
|
||||
draftSettlementType.value = settlementType
|
||||
/** شروع مرحله ثبت چک روی سفارشی که فاکتورش صادر شده است. */
|
||||
function startCheckDraft(orderId: string, mode?: CheckRegistrationMode) {
|
||||
draftOrderId.value = orderId
|
||||
draftSettlementType.value = 'check'
|
||||
draftChecks.value = []
|
||||
draftCheckMode.value = mode ?? null
|
||||
}
|
||||
|
||||
/** برای اصلاح چک، چکهای ثبتشده سفارش به فرم برگردانده میشوند. */
|
||||
function loadChecksIntoDraft(order: Order) {
|
||||
draftOrderId.value = order.id
|
||||
draftSettlementType.value = 'check'
|
||||
draftCheckMode.value = order.checkMode ?? 'full'
|
||||
draftChecks.value = (order.checks ?? []).map((c) => ({ ...c }))
|
||||
}
|
||||
|
||||
function clearCheckDraft() {
|
||||
draftOrderId.value = null
|
||||
draftChecks.value = []
|
||||
draftCheckMode.value = null
|
||||
draftSettlementType.value = null
|
||||
}
|
||||
|
||||
function setCheckMode(mode: CheckRegistrationMode) {
|
||||
@@ -79,31 +95,30 @@ export const useOrderStore = defineStore('order', () => {
|
||||
|
||||
function resetLocal() {
|
||||
orders.value = []
|
||||
draftChecks.value = []
|
||||
draftSettlementType.value = null
|
||||
draftCheckMode.value = null
|
||||
checkoutCartId.value = null
|
||||
clearCheckDraft()
|
||||
}
|
||||
|
||||
function upsertOrder(order: Order) {
|
||||
const idx = orders.value.findIndex((o) => o.id === order.id)
|
||||
if (idx >= 0) orders.value[idx] = order
|
||||
else orders.value.unshift(order)
|
||||
}
|
||||
|
||||
async function fetchOrder(id: string): Promise<Order | null> {
|
||||
try {
|
||||
const order = await apiRequest<Order>(`/orders/${id}`)
|
||||
const idx = orders.value.findIndex((o) => o.id === id)
|
||||
if (idx >= 0) orders.value[idx] = order
|
||||
else orders.value.unshift(order)
|
||||
upsertOrder(order)
|
||||
return order
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function submitOrder(
|
||||
_userId: string,
|
||||
cart: Cart,
|
||||
settlementType: SettlementType,
|
||||
checks?: CheckData[],
|
||||
checkMode?: CheckRegistrationMode,
|
||||
): Promise<Order> {
|
||||
/**
|
||||
* سفارش بدون روش تسویه ثبت میشود؛ مشتری بعد از قیمتگذاری مدیریت
|
||||
* روش تسویه را انتخاب میکند.
|
||||
*/
|
||||
async function submitOrder(cart: Cart): Promise<Order> {
|
||||
const companyId = cart.companyId || cart.items[0]?.companyId || ''
|
||||
if (!companyId) {
|
||||
throw new Error('شرکت سبد مشخص نیست.')
|
||||
@@ -120,32 +135,12 @@ export const useOrderStore = defineStore('order', () => {
|
||||
|
||||
const body = {
|
||||
companyId,
|
||||
settlementType,
|
||||
checkMode: settlementType === 'check' ? (checkMode ?? 'full') : undefined,
|
||||
notes: cart.notes,
|
||||
items: itemsForOrder.map((item) => ({
|
||||
productId: item.productId,
|
||||
meterage: item.meterage,
|
||||
notes: item.notes,
|
||||
})),
|
||||
checks:
|
||||
settlementType === 'check' && checks
|
||||
? checks.map((c) => ({
|
||||
amount: c.amount,
|
||||
dueDate: c.dueDate,
|
||||
sayadId: c.sayadId,
|
||||
checkNumber: c.checkNumber,
|
||||
ownerNationalId: c.ownerNationalId,
|
||||
ownerFirstName: c.ownerFirstName,
|
||||
ownerLastName: c.ownerLastName,
|
||||
bankName: c.bankName,
|
||||
branchName: c.branchName,
|
||||
frontImage: c.frontImage,
|
||||
backImage: c.backImage,
|
||||
notes: c.notes,
|
||||
mode: c.mode,
|
||||
}))
|
||||
: undefined,
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -154,16 +149,34 @@ export const useOrderStore = defineStore('order', () => {
|
||||
body,
|
||||
})
|
||||
orders.value.unshift(order)
|
||||
draftChecks.value = []
|
||||
draftSettlementType.value = null
|
||||
draftCheckMode.value = null
|
||||
checkoutCartId.value = null
|
||||
clearCheckDraft()
|
||||
return order
|
||||
} catch (e) {
|
||||
throw new Error(e instanceof ApiError ? e.message : 'ثبت سفارش ناموفق بود.')
|
||||
}
|
||||
}
|
||||
|
||||
/** انتخاب روش تسویه پس از صدور فاکتور. */
|
||||
async function chooseSettlement(
|
||||
orderId: string,
|
||||
settlementType: SettlementType,
|
||||
checkMode?: CheckRegistrationMode,
|
||||
): Promise<Order> {
|
||||
try {
|
||||
const order = await apiRequest<Order>(`/orders/${orderId}/settlement`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
settlementType,
|
||||
checkMode: settlementType === 'check' ? (checkMode ?? 'full') : undefined,
|
||||
},
|
||||
})
|
||||
upsertOrder(order)
|
||||
return order
|
||||
} catch (e) {
|
||||
throw new Error(e instanceof ApiError ? e.message : 'ثبت روش تسویه ناموفق بود.')
|
||||
}
|
||||
}
|
||||
|
||||
async function submitCashReceipt(
|
||||
orderId: string,
|
||||
receipt: Omit<CashPaymentReceipt, 'id' | 'submittedAt'>,
|
||||
@@ -191,11 +204,16 @@ export const useOrderStore = defineStore('order', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateOrderChecks(orderId: string, checks: CheckData[]) {
|
||||
async function updateOrderChecks(
|
||||
orderId: string,
|
||||
checks: CheckData[],
|
||||
checkMode?: CheckRegistrationMode,
|
||||
): Promise<Order> {
|
||||
try {
|
||||
const order = await apiRequest<Order>(`/orders/${orderId}/checks`, {
|
||||
method: 'PATCH',
|
||||
body: {
|
||||
checkMode,
|
||||
checks: checks.map((c) => ({
|
||||
amount: c.amount,
|
||||
dueDate: c.dueDate,
|
||||
@@ -213,11 +231,11 @@ export const useOrderStore = defineStore('order', () => {
|
||||
})),
|
||||
},
|
||||
})
|
||||
const idx = orders.value.findIndex((o) => o.id === orderId)
|
||||
if (idx >= 0) orders.value[idx] = order
|
||||
upsertOrder(order)
|
||||
clearCheckDraft()
|
||||
return order
|
||||
} catch {
|
||||
return null
|
||||
} catch (e) {
|
||||
throw new Error(e instanceof ApiError ? e.message : 'ثبت چکها ناموفق بود.')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,17 +248,20 @@ export const useOrderStore = defineStore('order', () => {
|
||||
draftChecks,
|
||||
draftSettlementType,
|
||||
draftCheckMode,
|
||||
checkoutCartId,
|
||||
draftOrderId,
|
||||
loading,
|
||||
userOrders,
|
||||
getOrderById,
|
||||
startCheckout,
|
||||
startCheckDraft,
|
||||
loadChecksIntoDraft,
|
||||
clearCheckDraft,
|
||||
setCheckMode,
|
||||
addDraftCheck,
|
||||
updateDraftCheck,
|
||||
removeDraftCheck,
|
||||
getDraftRass,
|
||||
submitOrder,
|
||||
chooseSettlement,
|
||||
submitCashReceipt,
|
||||
updateOrderChecks,
|
||||
hydrate,
|
||||
|
||||
Reference in New Issue
Block a user