import { defineStore } from 'pinia' import { ref, computed } from 'vue' import type { ForecastOrder, ForecastChangeOrder } from '@/types/forecast-order' import { getById, withdrawForecastOrder, discardForecastOrder, reviewAccept, reviewReject, resubmit, findChangeOrdersByForecastId } from '@/api/forecast/forecast-order' import { useI18n } from 'vue-i18n' import { reqIsSucceed } from '@/utils' import { handleApiError } from '@/utils/errorHandler' import { ElMessage } from 'element-plus' import type { Role } from '@/utils/forecastOrderActions' import { getAvailableActions } from '@/utils/forecastOrderActions' export const useForecastOrderDetailStore = defineStore('forecastOrderDetail', () => { const { t } = useI18n() // State const detail = ref(null) const detailFcId = ref(undefined) const drawerVisible = ref(false) const drawerMode = ref<'detail' | 'review'>('detail') const detailLoading = ref(false) const actionLoading = ref(false) const currentRole = ref('salesman') const changeList = ref([]) const changeListLoading = ref(false) // Getters /** * 是否有未完成的变更单(草稿/已提交状态) */ const hasPendingChange = computed(() => { return changeList.value.some( (ch) => ch.coStatus === 'DRAFT' || ch.coStatus === 'SUBMITTED', ) }) const visibleButtons = computed(() => { if (!detail.value) return [] const status = detail.value.foStatus const role = currentRole.value as Role const buttons: string[] = [] const actions = getAvailableActions(status, role, { hasForecastOrderNo: !!detail.value.forecastOrderNo, hasPendingChange: hasPendingChange.value, isDetailView: true, }) // 将共享 action key 映射为详情抽屉内需要的按钮 key // 'review' → 'reviewAccept' + 'reviewReject'(分开的两个按钮) for (const action of actions) { if (action === 'review') { buttons.push('reviewAccept', 'reviewReject') } else { buttons.push(action) } } return buttons }) // Actions async function fetchDetail(id: number) { detailFcId.value = id detailLoading.value = true try { const res = await getById(id) if (reqIsSucceed(res)) { detail.value = res.data } else { handleApiError(res.code, res.msg) } } catch (error: unknown) { const err = error as { code?: string; message?: string } handleApiError(err?.code || 'unknown', err?.message) } finally { detailLoading.value = false } } function openDrawer(id: number, mode: 'detail' | 'review' = 'detail') { drawerMode.value = mode drawerVisible.value = true fetchDetail(id) } function closeDrawer() { drawerVisible.value = false detail.value = null } function switchToReview() { drawerMode.value = 'review' } async function withdraw() { if (!detail.value) return actionLoading.value = true try { const res = await withdrawForecastOrder(detail.value.forecastOrderId) if (reqIsSucceed(res)) { ElMessage.success(t('forecast-order.toast.withdrawSuccess')) detail.value = res.data } else { handleApiError(res.code, res.msg) } } catch (error: unknown) { const err = error as { code?: string; message?: string } handleApiError(err?.code || 'unknown', err?.message) } finally { actionLoading.value = false } } async function doDiscard() { if (!detail.value) return actionLoading.value = true try { const res = await discardForecastOrder(detail.value.forecastOrderId) if (reqIsSucceed(res)) { ElMessage.success(t('forecast-order.toast.discardSuccess')) drawerVisible.value = false } else { handleApiError(res.code, res.msg) } } catch (error: unknown) { const err = error as { code?: string; message?: string } handleApiError(err?.code || 'unknown', err?.message) } finally { actionLoading.value = false } } async function doReviewAccept(data: { forecastOrderId: number; planSuggestedQty: number; planReplyDate: string; planReplyComment?: string }) { actionLoading.value = true try { const res = await reviewAccept({ params: data }) if (reqIsSucceed(res)) { ElMessage.success(t('forecast-order.toast.reviewAcceptSuccess')) detail.value = res.data drawerMode.value = 'detail' } else { handleApiError(res.code, res.msg) } } catch (error: unknown) { const err = error as { code?: string; message?: string } handleApiError(err?.code || 'unknown', err?.message) } finally { actionLoading.value = false } } async function doReviewReject(data: { forecastOrderId: number; rejectReason: string }) { if (!data.rejectReason) { ElMessage.error(t('forecast-order.toast.rejectReasonRequired')) return } actionLoading.value = true try { const res = await reviewReject({ params: data }) if (reqIsSucceed(res)) { ElMessage.success(t('forecast-order.toast.reviewRejectSuccess')) detail.value = res.data drawerMode.value = 'detail' } else { handleApiError(res.code, res.msg) } } catch (error: unknown) { const err = error as { code?: string; message?: string } handleApiError(err?.code || 'unknown', err?.message) } finally { actionLoading.value = false } } async function doResubmit() { if (!detail.value) return actionLoading.value = true try { const res = await resubmit({ params: { forecastOrderId: detail.value.forecastOrderId } }) if (reqIsSucceed(res)) { ElMessage.success(t('forecast-order.toast.resubmitSuccess')) detail.value = res.data } else { handleApiError(res.code, res.msg) } } catch (error: unknown) { const err = error as { code?: string; message?: string } handleApiError(err?.code || 'unknown', err?.message) } finally { actionLoading.value = false } } function setRole(role: string) { currentRole.value = role } /** * 加载变更单列表(按预测单ID) */ async function fetchChangeList(forecastOrderId?: number) { const fcId = forecastOrderId ?? detailFcId.value if (!fcId) return changeListLoading.value = true try { const res = await findChangeOrdersByForecastId(fcId) if (reqIsSucceed(res)) { changeList.value = res.data || [] } else { handleApiError(res.code, res.msg) } } catch (error: unknown) { const err = error as { code?: string; message?: string } handleApiError(err?.code || 'unknown', err?.message) } finally { changeListLoading.value = false } } return { detail, detailFcId, drawerVisible, drawerMode, detailLoading, actionLoading, currentRole, changeList, changeListLoading, visibleButtons, hasPendingChange, fetchDetail, openDrawer, closeDrawer, switchToReview, withdraw, doDiscard, doReviewAccept, doReviewReject, doResubmit, setRole, fetchChangeList, } })