|
@@ -193,7 +193,6 @@ export default {
|
|
|
itemSpecs: '',
|
|
|
forecastQuantity: null,
|
|
|
currentInventory: null,
|
|
|
- approvalStatus: APPROVAL_STATUS.PENDING,
|
|
|
approvedName: '',
|
|
|
approvedTime: null,
|
|
|
approvalRemark: '',
|
|
@@ -261,6 +260,24 @@ export default {
|
|
|
/** @type {Array<import('@/api/types/order').PjpfBrandDesc>} */
|
|
|
brandDescList: [],
|
|
|
|
|
|
+ /**
|
|
|
+ * 用户关联库存物料列表(不直接展示在表格中)
|
|
|
+ * @type {Array<import('@/api/types/order').PjpfStockDesc>}
|
|
|
+ */
|
|
|
+ stockDescList: [],
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 物料选择下拉选项(通过 cname 搜索)
|
|
|
+ * @type {Array<SelectOption<string>>}
|
|
|
+ */
|
|
|
+ stockSelectOptions: [],
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 当前选择待导入的物料ID
|
|
|
+ * @type {string | null}
|
|
|
+ */
|
|
|
+ selectedStockId: null,
|
|
|
+
|
|
|
/** 当前库存 */
|
|
|
currentInventory: null
|
|
|
}
|
|
@@ -420,30 +437,32 @@ export default {
|
|
|
* @private
|
|
|
*/
|
|
|
createInitialFormData() {
|
|
|
- return {
|
|
|
+ /** @type {ForecastFormModel} */
|
|
|
+ const initial = {
|
|
|
+ id: null,
|
|
|
forecastCode: '',
|
|
|
- year: new Date().getFullYear(),
|
|
|
+ year: new Date().getFullYear().toString(),
|
|
|
month: new Date().getMonth() + 1,
|
|
|
- customerId: 0,
|
|
|
+ customerId: null,
|
|
|
customerCode: '',
|
|
|
customerName: '',
|
|
|
- brandId: 0,
|
|
|
+ brandId: null,
|
|
|
brandCode: '',
|
|
|
brandName: '',
|
|
|
- itemId: 0,
|
|
|
+ itemId: null,
|
|
|
itemCode: '',
|
|
|
itemName: '',
|
|
|
specs: '',
|
|
|
- forecastQuantity: 0,
|
|
|
itemSpecs: '',
|
|
|
+ forecastQuantity: null,
|
|
|
currentInventory: null,
|
|
|
- approvalStatus: APPROVAL_STATUS.PENDING,
|
|
|
approvedName: '',
|
|
|
approvedTime: null,
|
|
|
approvalRemark: '',
|
|
|
createTime: null,
|
|
|
updateTime: null
|
|
|
}
|
|
|
+ return initial
|
|
|
},
|
|
|
|
|
|
/**
|
|
@@ -810,6 +829,119 @@ export default {
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
+ * 表单提交事件处理(Avue表单 @submit 入口)
|
|
|
+ * @description 响应 avue-form 的提交事件,统一走 submitForm 逻辑
|
|
|
+ * @returns {Promise<void>}
|
|
|
+ * @this {ForecastFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ handleSubmit(form, done, loading) {
|
|
|
+ try {
|
|
|
+ // 先结束 Avue 内置的按钮loading,避免未调用 done 导致一直loading
|
|
|
+ if (typeof done === 'function') done()
|
|
|
+
|
|
|
+ // 采用旧实现风格:通过 this.$refs.forecastForm.validate 回调进行校验
|
|
|
+ if (this.$refs && this.$refs.forecastForm && typeof this.$refs.forecastForm.validate === 'function') {
|
|
|
+ this.$refs.forecastForm.validate((valid) => {
|
|
|
+ if (!valid) {
|
|
|
+ // 校验失败时,如存在 loading 回调(部分版本提供),尝试恢复按钮状态
|
|
|
+ if (typeof loading === 'function') loading()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 校验通过后执行提交
|
|
|
+ this.submitForm()
|
|
|
+ .catch((e) => {
|
|
|
+ console.error('提交异常:', e)
|
|
|
+ this.$message && this.$message.error(e && e.message ? e.message : '提交失败,请稍后重试')
|
|
|
+ })
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ // 无法获取到 validate 时,直接尝试提交
|
|
|
+ this.submitForm()
|
|
|
+ .catch((e) => {
|
|
|
+ console.error('提交异常:', e)
|
|
|
+ this.$message && this.$message.error(e && e.message ? e.message : '提交失败,请稍后重试')
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('提交异常:', e)
|
|
|
+ this.$message && this.$message.error(e && e.message ? e.message : '提交异常,请稍后重试')
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 提交表单数据(仅批量保存预测汇总)
|
|
|
+ * @returns {Promise<void>}
|
|
|
+ */
|
|
|
+ async submitForm() {
|
|
|
+ try {
|
|
|
+ // 基础校验(客户必选)
|
|
|
+ if (!this.formData.customerId) {
|
|
|
+ this.$message && this.$message.warning('请选择客户')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 组装批量保存载荷,仅保留预测数量>0的行
|
|
|
+ const year = typeof this.formData.year === 'string' ? parseInt(this.formData.year, 10) : this.formData.year
|
|
|
+ const month = this.formData.month
|
|
|
+
|
|
|
+ const payload = this.stockTableData
|
|
|
+ .filter(row => Number(row.forecastQuantity) > 0)
|
|
|
+ .map(row => {
|
|
|
+ const matchedBrand = this.brandDescList.find(b => b.cname === row.brandName)
|
|
|
+
|
|
|
+ const brandIdRaw = row.brandId != null && row.brandId !== ''
|
|
|
+ ? row.brandId
|
|
|
+ : (matchedBrand ? matchedBrand.id : null)
|
|
|
+ const itemIdRaw = row.goodsId
|
|
|
+
|
|
|
+ const toSafeNumberOrString = (val) => {
|
|
|
+ if (val == null || val === '') return 0
|
|
|
+ if (typeof val === 'number') {
|
|
|
+ return Number.isSafeInteger(val) ? val : String(val)
|
|
|
+ }
|
|
|
+ const parsed = Number(val)
|
|
|
+ return Number.isSafeInteger(parsed) ? parsed : String(val)
|
|
|
+ }
|
|
|
+
|
|
|
+ const brandId = toSafeNumberOrString(brandIdRaw)
|
|
|
+ const itemId = toSafeNumberOrString(itemIdRaw)
|
|
|
+
|
|
|
+ return {
|
|
|
+ year: year || new Date().getFullYear(),
|
|
|
+ month: month || (new Date().getMonth() + 1),
|
|
|
+ brandId: brandId,
|
|
|
+ brandCode: '',
|
|
|
+ brandName: row.brandName || (matchedBrand ? matchedBrand.cname : ''),
|
|
|
+ itemId: itemId,
|
|
|
+ itemCode: row.code || '',
|
|
|
+ itemName: row.cname || '',
|
|
|
+ specs: row.typeNo || '',
|
|
|
+ pattern: row.productDescription || row.brandItem || '',
|
|
|
+ forecastQuantity: Number(row.forecastQuantity) || 0,
|
|
|
+ approvalStatus: this.formData.approvalStatus || 0
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ if (!payload.length) {
|
|
|
+ this.$message && this.$message.warning('请至少填写一条有效的预测数量')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const res = await batchSaveSalesForecastSummary(payload)
|
|
|
+ if (res && res.data && res.data.success) {
|
|
|
+ this.$message && this.$message.success('保存成功')
|
|
|
+ this.$emit && this.$emit(FORECAST_FORM_EVENTS.SUBMIT, res.data)
|
|
|
+ } else {
|
|
|
+ const msg = (res && res.data && (res.data.msg || res.data.message)) || '保存失败'
|
|
|
+ this.$message && this.$message.error(msg)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('提交表单失败:', error)
|
|
|
+ this.$message && this.$message.error(error && error.message ? error.message : '操作失败,请重试')
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
* 客户选择事件处理
|
|
|
* @description 处理CustomerSelect组件的客户选择事件
|
|
|
* @param {CustomerSelectData} customerData - 客户选择数据
|
|
@@ -840,15 +972,24 @@ export default {
|
|
|
async loadUserLinkGoods() {
|
|
|
try {
|
|
|
this.tableLoading = true
|
|
|
+ // 初始化容器
|
|
|
this.stockTableData = []
|
|
|
this.brandDescList = []
|
|
|
+ this.stockDescList = []
|
|
|
+ this.stockSelectOptions = []
|
|
|
+ this.selectedStockId = null
|
|
|
const res = await getUserLinkGoods()
|
|
|
const payload = res && res.data && res.data.data ? res.data.data : null
|
|
|
const brandList = (payload && payload.pjpfBrandDescList) || []
|
|
|
const stockList = (payload && payload.pjpfStockDescList) || []
|
|
|
this.brandDescList = brandList
|
|
|
- // 将库存列表转为表格数据,并默认预测数量为1
|
|
|
- this.stockTableData = stockList.map(row => ({ ...row, forecastQuantity: 1 }))
|
|
|
+ // 存储库存列表供选择用,不直接展示到表格
|
|
|
+ this.stockDescList = stockList
|
|
|
+ // 构造下拉选项,label 使用 cname,value 使用 id
|
|
|
+ this.stockSelectOptions = stockList.map(item => ({
|
|
|
+ label: item.cname,
|
|
|
+ value: item.id
|
|
|
+ }))
|
|
|
} catch (e) {
|
|
|
console.error('加载用户关联商品失败:', e)
|
|
|
this.$message.error(e.message || '加载用户关联商品失败')
|
|
@@ -858,6 +999,37 @@ export default {
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
+ * 导入所选物料到下方表格
|
|
|
+ * @description 仅在点击“导入物料”按钮后,将选择的物料行添加到表格,默认预测数量为 1
|
|
|
+ * @returns {void}
|
|
|
+ * @this {ForecastFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ handleImportSelectedStock() {
|
|
|
+ // 未选择则提示
|
|
|
+ if (!this.selectedStockId) {
|
|
|
+ this.$message.warning('请先在上方选择要导入的物料')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 查找明细
|
|
|
+ const stock = this.stockDescList.find(s => s.id === this.selectedStockId)
|
|
|
+ if (!stock) {
|
|
|
+ this.$message.error('未找到所选物料数据,请重新选择')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 防止重复导入(按 id 去重)
|
|
|
+ const exists = this.stockTableData.some(row => row.id === stock.id)
|
|
|
+ if (exists) {
|
|
|
+ this.$message.warning('该物料已在列表中')
|
|
|
+ this.selectedStockId = null
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 添加到表格,默认预测数量为 1
|
|
|
+ this.stockTableData.push({ ...stock, forecastQuantity: 1 })
|
|
|
+ // 清空已选
|
|
|
+ this.selectedStockId = null
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
* 品牌变更处理
|
|
|
* @param {number} brandId - 品牌ID
|
|
|
* @returns {void}
|
|
@@ -898,200 +1070,6 @@ export default {
|
|
|
this.formData.itemSpecs = ''
|
|
|
this.currentInventory = null
|
|
|
}
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * 获取当前库存
|
|
|
- * @description 根据物料ID获取当前库存数量
|
|
|
- * @param {string|number} itemId - 物料ID
|
|
|
- * @returns {void}
|
|
|
- * @this {ForecastFormMixinComponent & Vue}
|
|
|
- */
|
|
|
- getCurrentInventory(/** @type {string|number} */ itemId) {
|
|
|
- // 简化实现,实际应该调用API
|
|
|
- setTimeout(() => {
|
|
|
- // 模拟随机库存数量
|
|
|
- this.currentInventory = Math.floor(Math.random() * 1000)
|
|
|
- }, 300)
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * 表单提交处理
|
|
|
- * @description 验证表单并提交数据
|
|
|
- * @returns {void}
|
|
|
- * @this {ForecastFormMixinComponent & Vue & {$refs: {forecastForm: {validate: (callback: (valid: boolean) => void) => void}}}}
|
|
|
- */
|
|
|
- handleSubmit() {
|
|
|
- // 表单验证
|
|
|
- this.$refs.forecastForm.validate((/** @type {boolean} */ valid) => {
|
|
|
- if (!valid) {
|
|
|
- return
|
|
|
- }
|
|
|
- this.submitForm()
|
|
|
- })
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * 表单重置处理
|
|
|
- * @description 重置表单数据到初始状态
|
|
|
- * @returns {void}
|
|
|
- * @this {ForecastFormMixinComponent & Vue}
|
|
|
- */
|
|
|
- handleReset() {
|
|
|
- this.initFormData()
|
|
|
- this.currentInventory = null
|
|
|
- this.$emit(FORECAST_FORM_EVENTS.RESET)
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * 提交表单数据
|
|
|
- * @description 执行表单数据的提交操作,包括数据验证和API调用
|
|
|
- * @this {ForecastFormMixinComponent & Vue}
|
|
|
- * @returns {Promise<void>} 提交操作的Promise
|
|
|
- * @throws {Error} 当提交失败时抛出错误
|
|
|
- */
|
|
|
- async submitForm() {
|
|
|
- try {
|
|
|
- // 校验基础表单(年份、月份、客户等)
|
|
|
- const year = typeof this.formData.year === 'string' ? parseInt(this.formData.year, 10) : this.formData.year
|
|
|
- const month = this.formData.month
|
|
|
-
|
|
|
- // 组装批量保存载荷
|
|
|
- /** @type {import('@/api/forecast/types').SalesForecastSummaryBatchSaveRequest} */
|
|
|
- const payload = this.stockTableData
|
|
|
- .filter(row => Number(row.forecastQuantity) > 0)
|
|
|
- .map(row => {
|
|
|
- // 使用品牌描述列表按名称匹配品牌,匹配不到则留空/置0
|
|
|
- const matchedBrand = this.brandDescList.find(b => b.cname === row.brandName)
|
|
|
-
|
|
|
- // 原始 id 值(可能为字符串或数字,且可能超出 JS 安全整数范围)
|
|
|
- const brandIdRaw = row.brandId != null && row.brandId !== ''
|
|
|
- ? row.brandId
|
|
|
- : (matchedBrand ? matchedBrand.id : null)
|
|
|
- const itemIdRaw = row.goodsId
|
|
|
-
|
|
|
- // 将可能超出安全整数范围的数值以字符串形式透传,避免 Number 精度丢失
|
|
|
- /**
|
|
|
- * 将可能超出安全整数范围的 id 值转换为安全的 number 或保留为 string
|
|
|
- * @param {string|number|null|undefined} val
|
|
|
- * @returns {string|number}
|
|
|
- */
|
|
|
- const toSafeNumberOrString = (val) => {
|
|
|
- if (val == null || val === '') return 0
|
|
|
- if (typeof val === 'number') {
|
|
|
- return Number.isSafeInteger(val) ? val : String(val)
|
|
|
- }
|
|
|
- // 字符串:尝试转为数字,若安全则用数字,否则保留为原字符串
|
|
|
- const parsed = Number(val)
|
|
|
- return Number.isSafeInteger(parsed) ? parsed : String(val)
|
|
|
- }
|
|
|
-
|
|
|
- const brandId = toSafeNumberOrString(brandIdRaw)
|
|
|
- const itemId = toSafeNumberOrString(itemIdRaw)
|
|
|
-
|
|
|
- /** @type {import('@/api/forecast/types').SalesForecastSummaryBatchSaveItem} */
|
|
|
- const item = {
|
|
|
- year: year || new Date().getFullYear(),
|
|
|
- month: month || (new Date().getMonth() + 1),
|
|
|
- brandId: brandId,
|
|
|
- brandCode: '', // 接口未返回品牌编码,按要求匹配不到留空
|
|
|
- brandName: row.brandName || (matchedBrand ? matchedBrand.cname : ''),
|
|
|
- itemId: itemId,
|
|
|
- itemCode: row.code || '',
|
|
|
- itemName: row.cname || '',
|
|
|
- specs: row.typeNo || '',
|
|
|
- pattern: row.productDescription || '',
|
|
|
- forecastQuantity: Number(row.forecastQuantity) || 0,
|
|
|
- approvalStatus: this.formData.approvalStatus || 0
|
|
|
- }
|
|
|
- return item
|
|
|
- })
|
|
|
-
|
|
|
- if (!payload.length) {
|
|
|
- this.$message.warning('请至少填写一条有效的预测数量')
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- // 提交批量保存
|
|
|
- const res = await batchSaveSalesForecastSummary(payload)
|
|
|
- this.$emit(FORECAST_FORM_EVENTS.SUBMIT, res.data)
|
|
|
- } catch (error) {
|
|
|
- console.error('提交表单失败:', error)
|
|
|
- this.$message.error(error.message || '操作失败,请重试')
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * 准备提交数据
|
|
|
- * @description 复制表单数据并进行清理和格式化处理
|
|
|
- * @returns {ForecastFormModel} 准备好的提交数据
|
|
|
- * @this {ForecastFormMixinComponent & Vue}
|
|
|
- * @private
|
|
|
- */
|
|
|
- prepareSubmitData() {
|
|
|
- const submitData = { ...this.formData }
|
|
|
-
|
|
|
- // 转换年份为数字
|
|
|
- if (submitData.year && typeof submitData.year === 'string') {
|
|
|
- submitData.year = parseInt(submitData.year, 10)
|
|
|
- }
|
|
|
-
|
|
|
- // 确保数值字段为数字类型
|
|
|
- if (submitData.forecastQuantity) {
|
|
|
- submitData.forecastQuantity = Number(submitData.forecastQuantity)
|
|
|
- }
|
|
|
-
|
|
|
- if (submitData.currentInventory) {
|
|
|
- submitData.currentInventory = Number(submitData.currentInventory)
|
|
|
- }
|
|
|
-
|
|
|
- return submitData
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * 关闭表单
|
|
|
- * @description 关闭表单并重置数据
|
|
|
- * @this {ForecastFormMixinComponent & Vue}
|
|
|
- * @returns {void}
|
|
|
- */
|
|
|
- closeForm() {
|
|
|
- // 触发取消事件
|
|
|
- this.$emit(FORECAST_FORM_EVENTS.CANCEL)
|
|
|
-
|
|
|
- // 更新可见性
|
|
|
- this.$emit(FORECAST_FORM_EVENTS.UPDATE_VISIBLE, false)
|
|
|
-
|
|
|
- // 重置表单数据
|
|
|
- this.formData = this.createInitialFormData()
|
|
|
-
|
|
|
- // 重置表单验证
|
|
|
- if (this.$refs.form) {
|
|
|
- /** @type {{resetFields: () => void}} */ (this.$refs.form).resetFields()
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * 获取审批状态标签
|
|
|
- * @description 根据审批状态获取对应的标签文本
|
|
|
- * @param {ApprovalStatus} status - 审批状态
|
|
|
- * @returns {string} 状态标签
|
|
|
- */
|
|
|
- getApprovalStatusLabel,
|
|
|
-
|
|
|
- /**
|
|
|
- * 获取审批状态类型
|
|
|
- * @description 根据审批状态获取对应的类型(用于标签样式)
|
|
|
- * @param {ApprovalStatus} status - 审批状态
|
|
|
- * @returns {string} 状态类型
|
|
|
- */
|
|
|
- getApprovalStatusType,
|
|
|
-
|
|
|
- /**
|
|
|
- * 判断是否可以编辑
|
|
|
- * @description 根据审批状态判断记录是否可以编辑
|
|
|
- * @param {ApprovalStatus} status - 审批状态
|
|
|
- * @returns {boolean} 是否可编辑
|
|
|
- */
|
|
|
- canEdit
|
|
|
+ }
|
|
|
}
|
|
|
}
|