import { approveSalesForecastSummary, getSalesForecastMainPage, approveSalesForecastSummaryParticulars } from '@/api/forecast/forecast-summary' import { mapGetters } from 'vuex' import { APPROVAL_STATUS, APPROVAL_STATUS_OPTIONS, getApprovalStatusLabel, getApprovalStatusType, canApprove as canApproveStatus, canReject as canRejectStatus, formatNumber, formatDateTime as formatDateTimeUtil } from '@/constants/forecast' import { safeBigInt } from '@/util/util' /** * @typedef {import('./types').ForecastRecord} ForecastRecord * @typedef {import('./types').PageConfig} PageConfig * @typedef {import('./types').ApprovalFormData} ApprovalFormData * @typedef {import('./types').PermissionConfig} PermissionConfig * @typedef {import('./types').ForecastAuditComponent} ForecastAuditComponent */ /** * 销售预测审核页面业务逻辑混入 * @description 提供预测申报的审核功能,包括列表展示、详情查看、审批操作等 */ export default { /** * @this {Vue & ForecastAuditComponent} */ data() { return { /** @type {Record} 表单数据 */ form: {}, /** @type {Record} 查询参数 */ query: {}, /** @type {boolean} 表格加载状态 */ loading: true, /** @type {boolean} 提交状态 */ submitting: false, /** @type {boolean} 详情弹窗显示状态 */ detailDialogVisible: false, /** @type {boolean} 审批弹窗显示状态 */ approvalDialogVisible: false, /** @type {PageConfig} 分页配置 */ page: { pageSize: 10, currentPage: 1, total: 0 }, /** @type {Array} 表格数据 */ data: [], /** @type {Record} 详情表单数据 */ detailForm: {}, /** 当前正在审批的主表记录(用于批量审批子表) */ selectedMainRecord: null, /** @type {ApprovalFormData} 审批表单数据 */ approvalForm: { id: null, isApprove: true }, option: { height: 'auto', calcHeight: 30, searchShow: true, searchMenuSpan: 18, searchIndex: 3, border: true, index: false, viewBtn: false, selection: false, addBtn: false, editBtn: false, delBtn: false, excelBtn: false, columnBtn: false, refreshBtn: true, dialogClickModal: false, menu: true, menuWidth: 280, menuFixed: false, // 行展开配置,展示子表明细 expand: true, expandRowKeys: [], defaultExpandAll: false, column: [ { label: 'ID', prop: 'id', width: 200, overHidden: false }, { label: '序号', prop: 'seq', width: 70, align: 'center', slot: true }, { label: '年份', prop: 'year', type: 'year', valueFormat: 'yyyy', search: true, searchSpan: 6, width: 100 }, { label: '月份', prop: 'month', type: 'select', dicData: [ { label: '1月', value: 1 }, { label: '2月', value: 2 }, { label: '3月', value: 3 }, { label: '4月', value: 4 }, { label: '5月', value: 5 }, { label: '6月', value: 6 }, { label: '7月', value: 7 }, { label: '8月', value: 8 }, { label: '9月', value: 9 }, { label: '10月', value: 10 }, { label: '11月', value: 11 }, { label: '12月', value: 12 } ], search: true, searchSpan: 6, width: 100 }, { label: '客户编码', prop: 'customerCode', search: true, searchSpan: 6, width: 150, overHidden: true }, { label: '客户名称', prop: 'customerName', search: true, searchSpan: 6, width: 180, overHidden: true }, { label: '审批状态', prop: 'approvalStatus', type: 'select', dicData: APPROVAL_STATUS_OPTIONS, search: true, searchSpan: 6, width: 100, slot: true }, { label: '审批人', prop: 'approvedName', search: false, width: 120, overHidden: true }, { label: '审批时间', prop: 'approvedTime', type: 'datetime', search: false, width: 160, overHidden: true }, { label: '创建时间', prop: 'createTime', type: 'datetime', search: false, width: 160, overHidden: true }, { label: '更新时间', prop: 'updateTime', type: 'datetime', search: false, width: 160, overHidden: true } ] }, // 子表(展开区)配置,展示明细列表 childOption: { // width: '100%', // height: 'auto', // calcHeight: 0, tip: false, searchShow: false, border: true, index: true, viewBtn: false, editBtn: false, delBtn: false, addBtn: false, selection: false, menu: true, menuWidth: 160, menuFixed: false, expand: false, column: [ { label: '物料编码', prop: 'itemCode', minWidth: 120 }, { label: '物料名称', prop: 'itemName', minWidth: 180, overHidden: true }, { label: '规格型号', prop: 'specs', minWidth: 140, overHidden: true }, { label: '花型/图案', prop: 'pattern', minWidth: 120, overHidden: true }, { label: '品牌名称', prop: 'brandName', minWidth: 120, overHidden: true }, { label: '预测数量', prop: 'forecastQuantity', minWidth: 120, align: 'right', slot: true }, { label: '审核状态', prop: 'approvalStatus', type: 'select', dicData: APPROVAL_STATUS_OPTIONS, minWidth: 100, slot: true }, // { label: '客户名称', prop: 'customerName', minWidth: 160, overHidden: true }, // { label: '年份', prop: 'year', minWidth: 100 }, // { label: '月份', prop: 'month', minWidth: 100 }, { label: '审批人', prop: 'approvedName', minWidth: 120, overHidden: true }, { label: '审批时间', prop: 'approvedTime', type: 'datetime', minWidth: 160, overHidden: true, slot: true } ] } } }, computed: { ...mapGetters(['permission', 'userInfo']), /** * 权限列表 * @this {Vue & ForecastAuditComponent} * @returns {PermissionConfig} 权限配置对象 */ permissionList() { return { // addBtn: this.vaildData(this.permission.forecast_audit_add, false), // viewBtn: this.vaildData(this.permission.forecast_audit_view, false), // editBtn: this.vaildData(this.permission.forecast_audit_edit, false), // delBtn: this.vaildData(this.permission.forecast_audit_delete, false) addBtn: false, viewBtn: false, editBtn: false, delBtn: false } }, /** * 审批弹窗标题 * @this {Vue & ForecastAuditComponent} * @returns {string} 弹窗标题文本 */ approvalDialogTitle() { return this.approvalForm.isApprove ? '审批通过确认' : '审批拒绝确认' } }, created() { // 初始化时不需要加载额外数据,只等待表格自动加载 }, methods: { /** * 获取审批状态标签 * @param {import('@/constants/forecast').ApprovalStatus} status - 审批状态值 * @returns {string} 状态标签文本 */ getApprovalStatusLabel(status) { return getApprovalStatusLabel(status) }, /** * 获取审批状态类型 * @param {import('@/constants/forecast').ApprovalStatus} status - 审批状态值 * @returns {string} 状态类型 */ getApprovalStatusType(status) { return getApprovalStatusType(status) }, /** * 判断是否可以审批通过 * @this {Vue & ForecastAuditComponent} * @param {ForecastRecord | {approvalStatus: number}} row - 数据行对象 * @returns {boolean} 是否可以审批通过 */ canApprove(row) { return canApproveStatus(/** @type {import('@/constants/forecast').ApprovalStatus} */ (row.approvalStatus ?? APPROVAL_STATUS.PENDING)) }, /** * 判断是否可以审批拒绝 * @this {Vue & ForecastAuditComponent} * @param {ForecastRecord | {approvalStatus: number}} row - 数据行对象 * @returns {boolean} 是否可以审批拒绝 */ canReject(row) { return canRejectStatus(/** @type {import('@/constants/forecast').ApprovalStatus} */ (row.approvalStatus ?? APPROVAL_STATUS.PENDING)) }, /** * 格式化数字显示 * @this {Vue & ForecastAuditComponent} * @param {number|null|undefined} value - 数值 * @returns {string} 格式化后的字符串 */ formatNumber(value) { return formatNumber(value) }, // 新增:格式化日期时间显示,供子表“审批时间”列使用 /** * @param {string|null|undefined} dateTime - 日期时间值 * @returns {string} 格式化后的日期时间字符串 */ formatDateTime(dateTime) { return formatDateTimeUtil(dateTime) }, /** * 查看详情 * @this {Vue & ForecastAuditComponent} * @param {ForecastRecord} row - 数据行对象 * @param {number} index - 行索引 * @returns {void} */ viewDetail(row, index) { this.detailForm = { ...row } this.detailDialogVisible = true }, /** * 审批通过记录(主表) * @this {Vue & ForecastAuditComponent} * @param {ForecastRecord} row - 数据行对象 * @param {number} index - 行索引 * @returns {Promise} */ async approveRecord(row, index) { /** @type {Vue & ForecastAuditComponent} */ const self = /** @type {any} */ (this) try { await self.$confirm('确认通过此预测申报吗?', '审批确认', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'success' }) self.performApproval(row, true) } catch (e) { // 用户取消或错误 } }, /** * 审批拒绝记录(主表) * @this {Vue & ForecastAuditComponent} * @param {ForecastRecord} row - 数据行对象 * @param {number} index - 行索引 * @returns {Promise} */ async rejectRecord(row, index) { /** @type {Vue & ForecastAuditComponent} */ const self = /** @type {any} */ (this) try { await self.$confirm('确认拒绝此预测申报吗?', '审批确认', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }) self.performApproval(row, false) } catch (e) { // 用户取消或错误 } }, /** * 执行审批(主表) * @this {Vue & ForecastAuditComponent} * @param {ForecastRecord} row - 主表记录 * @param {boolean} isApprove - 是否通过 * @returns {Promise} */ async performApproval(row, isApprove) { /** @type {Vue & ForecastAuditComponent} */ const self = /** @type {any} */ (this) if (!row) { self.$message.error('记录不存在,无法审批') return } const id = (row && row.id != null && row.id !== '') ? String(row.id) : (row && row.idBigint != null ? String(row.idBigint) : '') if (!id) { self.$message.error('主表ID缺失,无法提交审批') return } self.submitting = true try { const payload = { id, approvalStatus: isApprove ? APPROVAL_STATUS.APPROVED : APPROVAL_STATUS.REJECTED } await approveSalesForecastSummary(payload) self.$message.success(isApprove ? '审批通过成功' : '审批拒绝成功') await self.onLoad(self.page, self.query) } catch (e) { self.$message.error('审批操作失败') } finally { self.submitting = false } }, /** * 子表明细审批通过 * @param {import('./types').ForecastChildRecord} sub - 子项记录 * @returns {Promise} */ async approveChildRecord(sub) { /** @type {Vue & ForecastAuditComponent} */ const self = /** @type {any} */ (this) try { await self.$confirm('确认通过此明细吗?', '审批确认', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'success' }) await self.submitChildApproval(sub, true) } catch (e) { // 用户取消或错误 } }, /** * 子表明细审批拒绝 * @param {import('./types').ForecastChildRecord} sub - 子项记录 * @returns {Promise} */ async rejectChildRecord(sub) { /** @type {Vue & ForecastAuditComponent} */ const self = /** @type {any} */ (this) try { await self.$confirm('确认拒绝此明细吗?', '审批确认', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }) await self.submitChildApproval(sub, false) } catch (e) { // 用户取消或错误 } }, /** * 执行子表明细审批 * @param {import('./types').ForecastChildRecord} sub - 子项记录 * @param {boolean} isApprove - 是否通过 * @returns {Promise} */ async submitChildApproval(sub, isApprove) { /** @type {Vue & ForecastAuditComponent} */ const self = /** @type {any} */ (this) self.submitting = true const id = /** @type {string|number} */ (sub && sub.id) const forecastMainId = /** @type {string|number} */ (sub && sub.forecastMainId) if (id === '' || id === null || id === undefined || forecastMainId === '' || forecastMainId === null || forecastMainId === undefined) { self.submitting = false self.$message.error('子表或主表ID缺失,无法提交审批') return } const payload = { id, forecastMainId, approvalStatus: isApprove ? APPROVAL_STATUS.APPROVED : APPROVAL_STATUS.REJECTED } try { await approveSalesForecastSummaryParticulars(payload) self.$message.success('明细审批成功') await self.onLoad(self.page, self.query) } catch (e) { self.$message.error('明细审批失败') } finally { self.submitting = false } }, /** * 从详情页面审批通过 * @this {Vue & ForecastAuditComponent} * @returns {Promise} */ async approveFromDetail() { /** @type {Vue & ForecastAuditComponent} */ const self = /** @type {any} */ (this) try { await self.$confirm('确认通过此预测申报吗?', '审批确认', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'success' }) self.detailDialogVisible = false self.performApproval(/** @type {ForecastRecord} */ (self.detailForm), true) } catch (e) { // 用户取消或错误 } }, /** * 从详情页面审批拒绝 * @this {Vue & ForecastAuditComponent} * @returns {Promise} */ async rejectFromDetail() { /** @type {Vue & ForecastAuditComponent} */ const self = /** @type {any} */ (this) try { await self.$confirm('确认拒绝此预测申报吗?', '审批确认', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }) self.detailDialogVisible = false self.performApproval(/** @type {ForecastRecord} */ (self.detailForm), false) } catch (e) { // 用户取消或错误 } }, /** * 提交审批(主表) * @this {Vue & ForecastAuditComponent} * @returns {Promise} */ async submitApproval() { /** @type {Vue & ForecastAuditComponent} */ const self = /** @type {any} */ (this) self.submitting = true const approvalData = { id: /** @type {string|number} */ (self.approvalForm.id), approvalStatus: self.approvalForm.isApprove ? APPROVAL_STATUS.APPROVED : APPROVAL_STATUS.REJECTED } // id 为空保护 if (approvalData.id === null || approvalData.id === undefined || approvalData.id === '') { self.submitting = false self.$message.error('审批记录ID缺失,无法提交') return } try { await approveSalesForecastSummary(approvalData) self.$message.success('审批操作成功') self.approvalDialogVisible = false await self.onLoad(self.page) } catch (e) { self.$message.error('审批操作失败') } finally { self.submitting = false } }, /** * 取消审批 * @this {Vue & ForecastAuditComponent} * @returns {void} */ cancelApproval() { this.approvalDialogVisible = false this.approvalForm = { id: null, isApprove: true, } }, /** * 搜索变化事件 * @this {Vue & ForecastAuditComponent} * @param {Record} params - 搜索参数 * @param {() => void} done - 完成回调 * @returns {void} */ searchChange(params, done) { this.query = params this.onLoad(this.page, params) done() }, /** * 搜索重置事件 * @this {Vue & ForecastAuditComponent} * @returns {void} */ searchReset() { this.query = {} this.onLoad(this.page) }, /** * 刷新事件 * @this {Vue & ForecastAuditComponent} * @returns {void} */ refreshChange() { this.onLoad(this.page, this.query) }, /** * 加载数据(使用销售预测主表分页) * @this {Vue & ForecastAuditComponent} * @param {import('./types').PageConfig} page - 分页配置 * @param {Partial<{year: number, month: number, customerName: string}>} [params={}] - 查询参数 */ async onLoad(page, params = {}) { try { this.loading = true const queryParams = { year: params.year ?? this.query.year, month: params.month ?? this.query.month, customerName: params.customerName ?? this.query.customerName, // 补充:将“客户编码、审批状态”一并传给接口 customerCode: params.customerCode ?? this.query.customerCode, approvalStatus: params.approvalStatus ?? this.query.approvalStatus } // 使用新分页接口(主表分页) const res = await getSalesForecastMainPage( page.currentPage, page.pageSize, queryParams ) const { records = [], total = 0, current = 1, size = 10 } = (res.data && res.data.data) || {} // 处理ID为 BigInt 字符串,同时保留字符串ID以便传输 const mapped = (records || []).map(rec => { const idStr = rec && rec.id != null ? String(rec.id) : '' const idBigint = safeBigInt(idStr) const subList = Array.isArray(rec.pcBladeSalesForecastSummaryList) ? rec.pcBladeSalesForecastSummaryList.map(sub => { const subIdStr = sub && sub.id != null ? String(sub.id) : '' const subIdBigint = safeBigInt(subIdStr) const fmIdStr = sub && sub.forecastMainId != null ? String(sub.forecastMainId) : idStr const fmIdBigint = safeBigInt(fmIdStr) return { ...sub, id: subIdStr, forecastMainId: fmIdStr, year: Number(sub.year), month: Number(sub.month), forecastQuantity: typeof sub.forecastQuantity === 'string' ? Number(sub.forecastQuantity) : Number(sub.forecastQuantity), idBigint: subIdBigint, forecastMainIdBigint: fmIdBigint, // 继承父级字段,保证子表新增列有值可展示 approvalStatus: sub && sub.approvalStatus != null ? sub.approvalStatus : (rec && rec.approvalStatus != null ? rec.approvalStatus : APPROVAL_STATUS.PENDING), customerName: sub && sub.customerName != null ? sub.customerName : rec && rec.customerName, approvedName: sub && sub.approvedName != null ? sub.approvedName : rec && rec.approvedName, approvedTime: sub && sub.approvedTime != null ? sub.approvedTime : rec && rec.approvedTime } }) : [] const approvalStatus = /** @type {import('@/constants/forecast').ApprovalStatus} */ (rec.approvalStatus ?? APPROVAL_STATUS.PENDING) return { ...rec, id: idStr, idBigint, year: Number(rec.year), month: Number(rec.month), approvalStatus, pcBladeSalesForecastSummaryList: subList } }) this.data = /** @type {import('./types').ForecastRecord[]} */ (mapped) this.page.total = Number(total) this.page.currentPage = Number(current) this.page.pageSize = Number(size) } catch (error) { // eslint-disable-next-line no-console console.error('加载列表失败', error) } finally { this.loading = false } } } }