diff --git a/.gitignore b/.gitignore index c502df8..4fcd9bb 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ node_modules/ *.ntvs* *.njsproj *.sln +AGENTS.md +CLAUDE.md +/.claude diff --git a/src/api/order/poOrder.js b/src/api/order/poOrder.js index b6c10ab..6412948 100644 --- a/src/api/order/poOrder.js +++ b/src/api/order/poOrder.js @@ -39,3 +39,9 @@ export const getPoCustomerScope = (data) => createAPI(`/pODetail/customerScope`, /** 按 customer + scene 获取字段 schema */ export const getPoFieldSchema = (data) => createAPI(`/pODetail/fieldSchema`, 'post', data) + +/** 按供应商查询可供物料(SupplierforPart) */ +export const listPoSupplierPartOptions = (data) => createAPI(`/supplier/supplierPartMoqPrice/listSupplierPart`, 'post', data) + +/** 查询当前生效阶梯价(sp_current_tier_price) */ +export const listPoCurrentTierPrice = (data) => createAPI(`/supplier/supplierPartMoqPrice/listCurrentTierPrice`, 'post', data) diff --git a/src/views/modules/order/poBuyer.vue b/src/views/modules/order/poBuyer.vue index 5ac0aae..88021bb 100644 --- a/src/views/modules/order/poBuyer.vue +++ b/src/views/modules/order/poBuyer.vue @@ -7,6 +7,15 @@ + + + + + @@ -67,12 +76,23 @@ :rules="rules" style="margin-left: 7px; margin-right: 7px;"> - + - + + + + + + + + @@ -111,6 +131,7 @@ import { updatePoBuyer, deletePoBuyer } from '@/api/order/poBuyer.js' +import { getPoCustomerOptions } from '@/api/order/poOrder.js' import excel from '@/utils/excel-util.js' export default { @@ -128,16 +149,19 @@ export default { site: this.$store.state.user.site, buyerCode: '', buyerName: '', + customerCode: '', active: '', page: 1, limit: 10 }, + poCustomerOptions: [], modalFlag: false, modalDisableFlag: false, modalData: { site: this.$store.state.user.site, buyerCode: '', buyerName: '', + customerCode: '', active: 'Y', remark: '', flag: '1' @@ -176,6 +200,28 @@ export default { columnWidth: 260, showOverflowTooltip: true }, + { + columnProp: 'customerCode', + headerAlign: 'center', + align: 'center', + columnLabel: '所属Customer编码', + columnHidden: false, + columnSortable: false, + fixed: '', + columnWidth: 180, + showOverflowTooltip: true + }, + { + columnProp: 'customerName', + headerAlign: 'center', + align: 'left', + columnLabel: '所属Customer名称', + columnHidden: false, + columnSortable: false, + fixed: '', + columnWidth: 220, + showOverflowTooltip: true + }, { columnProp: 'active', headerAlign: 'center', @@ -208,6 +254,9 @@ export default { { required: true, message: '请输入名称', trigger: 'blur' }, { max: 300, message: '名称长度不能超过300', trigger: 'blur' } ], + customerCode: [ + { required: true, message: '请选择所属Customer', trigger: 'change' } + ], active: [ { required: true, message: '请选择启用状态', trigger: 'change' } ], @@ -223,9 +272,49 @@ export default { }) }, created () { + this.getCustomerOptions() this.getDataList() }, methods: { + formatCodeOptionLabel (code, name) { + if (!name) { + return code || '' + } + if (!code) { + return name + } + return `${code} - ${name}` + }, + getCustomerOptions () { + return getPoCustomerOptions({ + site: this.$store.state.user.site + }).then(({ data }) => { + if (data && data.code === 0) { + this.poCustomerOptions = (data.rows || []).map(item => ({ + label: this.formatCodeOptionLabel(item.code, item.name), + value: item.code + })) + } else { + this.$message.error((data && data.msg) || '获取Customer失败') + } + }).catch(err => { + this.$message.error((err && err.message) || '获取Customer失败') + }) + }, + ensureCustomerOptionExists (code, name) { + const customerCode = (code || '').trim() + if (!customerCode) { + return + } + const exists = this.poCustomerOptions.some(item => item && item.value === customerCode) + if (exists) { + return + } + this.poCustomerOptions.push({ + value: customerCode, + label: this.formatCodeOptionLabel(customerCode, name || '') + }) + }, getDataList () { this.dataListLoading = true const params = { @@ -268,6 +357,7 @@ export default { site: this.$store.state.user.site, buyerCode: '', buyerName: '', + customerCode: '', active: 'Y', remark: '', flag: '1' @@ -282,10 +372,12 @@ export default { updateModal (row) { this.modalDisableFlag = true + this.ensureCustomerOptionExists(row.customerCode, row.customerName) this.modalData = { site: row.site, buyerCode: row.buyerCode, buyerName: row.buyerName, + customerCode: row.customerCode || '', active: row.active, remark: row.remark || '', flag: '2' diff --git a/src/views/modules/order/poOrder.vue b/src/views/modules/order/poOrder.vue index c6a1a1f..c72da6d 100644 --- a/src/views/modules/order/poOrder.vue +++ b/src/views/modules/order/poOrder.vue @@ -162,7 +162,7 @@ @@ -380,7 +409,9 @@ import { createPoOrder, getPoCustomerOptions, getPoBuyerOptions, - getPoFieldSchema + getPoFieldSchema, + listPoSupplierPartOptions, + listPoCurrentTierPrice } from '@/api/order/poOrder.js' import { createAdvancedQueryData, @@ -408,6 +439,7 @@ import { PO_ORDER_TYPE_OPTIONS, PO_ORDER_TYPE_FILTER_OPTIONS, formatPoOrderTypeL import { getPartList, searchPartInfo } from '@/api/part/partInfo.js' import excel from '@/utils/excel-util.js' import { checkSrmSupplierList } from '@/api/srm/srmSupplier.js' +import { selectDictDataList } from '@/api/dict' import { SUPPLIER_CHOOSELIST_TAG, parseSupplierChooselistRow, @@ -490,6 +522,15 @@ export default { eamTagNo: '', eamPickField: '', addForm: {}, + supplierPartDialogVisible: false, + supplierPartDialogLoading: false, + supplierPartDialogRows: [], + supplierPartDialogContext: '', + supplierPartDialogTargetRow: null, + supplierPartDialogSupplierNo: '', + supplierPartDialogSite: '', + addPriceLookupTimer: null, + addPriceLookupSeq: 0, selectedCustomerScope: null, currentRow: null, @@ -510,6 +551,7 @@ export default { { label: 'N', value: 'N' } ] }, + dictSelectOptionsMap: {}, searchData: { site: this.$store.state.user.site, customer: '', @@ -1588,6 +1630,10 @@ export default { }, beforeDestroy () { window.removeEventListener('resize', this.onWindowResize) + if (this.addPriceLookupTimer) { + clearTimeout(this.addPriceLookupTimer) + this.addPriceLookupTimer = null + } }, methods: { canIssuePoOrder, @@ -1782,6 +1828,94 @@ export default { if (code === 'price' || code === 'qty') { this.recalcAddFormValue() } + if (code === 'qty') { + this.scheduleAutoFillAddPriceByTierPrice() + } + }, + scheduleAutoFillAddPriceByTierPrice () { + if (this.addPriceLookupTimer) { + clearTimeout(this.addPriceLookupTimer) + this.addPriceLookupTimer = null + } + this.addPriceLookupTimer = setTimeout(() => { + this.addPriceLookupTimer = null + this.autoFillAddPriceByTierPrice() + }, 250) + }, + autoFillAddPriceByTierPrice () { + if (!this.addDialogVisible) { + return Promise.resolve() + } + const site = (this.searchData.site || this.$store.state.user.site || '').trim() + const supplierCode = String(this.addForm.supplierNo || '').trim() + const partNo = String(this.addForm.partNo || '').trim() + const qtyVal = this.addForm.qty + if (!site || !supplierCode || !partNo || qtyVal === '' || qtyVal === null || qtyVal === undefined) { + return Promise.resolve() + } + const qty = Number(qtyVal) + if (Number.isNaN(qty) || qty < 0) { + return Promise.resolve() + } + const seq = ++this.addPriceLookupSeq + return listPoCurrentTierPrice({ + site, + supplierCode, + partNo + }).then(({ data }) => { + if (seq !== this.addPriceLookupSeq) { + return + } + if (!(data && data.code === 0)) { + return + } + const matchedRow = this.findCurrentTierPriceByQty(data.rows || [], qty) + if (!matchedRow) { + return + } + const normalizedPrice = this.normalizeAddNumericValue(matchedRow.unitPrice) + if (normalizedPrice === '') { + return + } + this.$set(this.addForm, 'price', normalizedPrice) + this.recalcAddFormValue() + }).catch(() => {}) + }, + findCurrentTierPriceByQty (rows, qty) { + if (!Array.isArray(rows) || rows.length === 0) { + return null + } + const qtyNum = Number(qty) + if (Number.isNaN(qtyNum)) { + return null + } + for (const row of rows) { + const start = Number(row.qtyStart) + const endRaw = row.qtyEnd + const end = (endRaw === '' || endRaw === null || endRaw === undefined) + ? Number.POSITIVE_INFINITY + : Number(endRaw) + if (Number.isNaN(start) || Number.isNaN(end)) { + continue + } + if (qtyNum >= start && qtyNum <= end) { + return row + } + } + return null + }, + normalizeAddNumericValue (value) { + if (value === '' || value === null || value === undefined) { + return '' + } + const num = Number(value) + if (Number.isNaN(num)) { + return '' + } + if (Number.isInteger(num)) { + return String(num) + } + return String(Number(num.toFixed(4))) }, handleClosePoOrder (row) { @@ -1866,6 +2000,13 @@ export default { * 解析列对应的下拉选项:优先列上 selectOptions,其次 columnSelectOptions[selectOptionsKey] */ getSelectOptionsForColumn (item) { + if (item && item.editType === 'dict-select') { + const dictType = this.getSchemaDictType(item) + if (!dictType) { + return [] + } + return this.dictSelectOptionsMap[dictType] || [] + } if (item.selectOptions && item.selectOptions.length) { return item.selectOptions } @@ -1876,6 +2017,74 @@ export default { return [] }, + getSchemaDictType (item) { + if (!item) { + return '' + } + if (item.dictType) { + return String(item.dictType).trim() + } + const prop = item.columnProp + if (!prop) { + return '' + } + const field = (this.fieldSchemaFields || []).find(f => { + const code = String((f && f.fieldCode) || '').trim() + const fieldProp = String((f && f.fieldProp) || '').trim() + return fieldProp === prop || this.snakeToCamel(code) === prop + }) + return field && field.dictType ? String(field.dictType).trim() : '' + }, + + preloadDictSelectOptions (schemaFields) { + const site = this.searchData.site || this.$store.state.user.site + if (!site || !Array.isArray(schemaFields) || schemaFields.length === 0) { + this.dictSelectOptionsMap = {} + return Promise.resolve() + } + const dictTypeSet = new Set() + schemaFields.forEach(field => { + const editorType = String((field && field.editorType) || '').trim().toLowerCase() + if (editorType !== 'dict-select' && editorType !== 'dict_select') { + return + } + const dictType = String((field && field.dictType) || '').trim() + if (dictType) { + dictTypeSet.add(dictType) + } + }) + const dictTypes = Array.from(dictTypeSet) + if (dictTypes.length === 0) { + this.dictSelectOptionsMap = {} + return Promise.resolve() + } + const nextMap = {} + const loaders = dictTypes.map(dictType => { + return selectDictDataList({ + site, + dictType, + dictTypeList: [], + status: 'Y' + }).then(({ data }) => { + if (data && data.code === 0) { + nextMap[dictType] = (data.rows || []).map(item => ({ + label: item.dictLabel || item.dictValue, + value: item.dictValue + })) + } else { + nextMap[dictType] = [] + this.$message.error((data && data.msg) || `字典加载失败: ${dictType}`) + } + }).catch(() => { + nextMap[dictType] = [] + this.$message.error(`字典加载失败: ${dictType}`) + }) + }) + return Promise.all(loaders).then(() => { + this.dictSelectOptionsMap = nextMap + }) + }, + buildQueryRequest() { return buildPoOrderQueryParams({ searchData: this.searchData, @@ -1888,6 +2097,8 @@ export default { onSessionCustomerChange (customer, customerOption) { this.searchData.customer = customer this.selectedCustomerScope = customerOption || null + this.dictSelectOptionsMap = {} + this.getBuyerOptions(customer) this.pageIndex = 1 this.masterRows = [] this.dataList = [] @@ -1895,6 +2106,8 @@ export default { if (!customer) { this.selectedCustomerScope = null this.fieldSchemaFields = [] + this.poBuyerOptions = [] + this.columnSelectOptions.poBuyerOptions = [] this.refreshTableLayout() return } @@ -1914,10 +2127,12 @@ export default { const queryFields = getAdvancedQueryFieldsFromSchema(this.fieldSchemaFields) const nextAdvanced = createAdvancedQueryDataFromSchema(queryFields) this.advancedQueryData.rows = nextAdvanced.rows + return this.preloadDictSelectOptions(this.fieldSchemaFields) } else { this.fieldSchemaFields = [] this.$message.error((data && data.msg) || '加载字段权限失败') } + }).then(() => { this.refreshTableLayout() }).catch(() => { this.fieldSchemaFields = [] @@ -1989,7 +2204,9 @@ export default { if (tag === SUPPLIER_CHOOSELIST_TAG || tag === 520) { const { supplierNo, supplierName } = parseSupplierChooselistRow(data) if (ctx === 'add') { - this.fillSupplierShortFields(this.addForm, supplierNo, supplierName, false) + this.fillSupplierShortFields(this.addForm, supplierNo, supplierName, false).then(() => { + this.scheduleAutoFillAddPriceByTierPrice() + }) } else if (this.currentSelectRow) { const row = this.currentSelectRow this.fillSupplierShortFields(row, supplierNo, supplierName, true) @@ -2003,6 +2220,7 @@ export default { if (ctx === 'add') { this.$set(this.addForm, 'partNo', partNo) this.fillPartCategorySku(partNo, this.addForm) + this.scheduleAutoFillAddPriceByTierPrice() } else if (this.currentPartNoRow) { this.$set(this.currentPartNoRow, 'partNo', partNo) this.fillPartCategorySku(partNo, this.currentPartNoRow) @@ -2241,9 +2459,18 @@ export default { }) }, - getBuyerOptions () { + getBuyerOptions (customer = this.searchData.customer) { + const customerCode = (customer || '').trim() + if (!customerCode) { + this.poBuyerOptions = [] + this.columnSelectOptions.poBuyerOptions = [] + return Promise.resolve([]) + } + this.poBuyerOptions = [] + this.columnSelectOptions.poBuyerOptions = [] return getPoBuyerOptions({ - site: this.$store.state.user.site + site: this.$store.state.user.site, + customer: customerCode }).then(({ data }) => { if (data && data.code === 0) { this.poBuyerOptions = (data.rows || []).map(item => ({ @@ -2629,15 +2856,19 @@ export default { }, fillSupplierShortFields (target, supplierNo, supplierName, markChanged) { - if (!target || !supplierNo) return - fetchSupplierDetail(supplierNo, this.$store.state.user.site).then(detail => { + if (!target || !supplierNo) { + return Promise.resolve() + } + this.applySupplierSelection(target, supplierNo, supplierName, target.shortOffice || '', target.shortNpc || '', markChanged) + return fetchSupplierDetail(supplierNo, this.$store.state.user.site).then(detail => { const { shortOffice, shortNpc } = resolveSupplierShortFieldsFromDetail(detail) this.applySupplierSelection(target, supplierNo, supplierName, shortOffice, shortNpc, markChanged) - }) + }).catch(() => {}) }, fillPartCategorySku (partNo, target) { if (!partNo || !target) return + const targetSite = String(target.site || this.$store.state.user.site || '').trim() const apply = (item, category, categoryName, sku) => { this.$set(target, 'item', item) this.$set(target, 'category', category) @@ -2649,12 +2880,95 @@ export default { apply(fromList.item || '', fromList.category || '', fromList.categoryName || '', fromList.sku || '') return } - searchPartInfo({ site: this.$store.state.user.site, partNo }).then(({ data }) => { + searchPartInfo({ site: targetSite, partNo }).then(({ data }) => { if (data && data.code === 0 && data.data) { apply(data.data.item || '', data.data.category || '', data.data.categoryName || '', data.data.sku || '') } }).catch(() => {}) }, + applySelectedPartToTarget (target, partRow, markChanged) { + if (!target || !partRow) { + return + } + const partNo = partRow.partNo || partRow.part_no || '' + if (!partNo) { + return + } + this.$set(target, 'partNo', partNo) + this.$set(target, 'partDesc', partRow.partDesc || '') + this.$set(target, 'item', partRow.item || '') + this.$set(target, 'sku', partRow.sku || '') + this.fillPartCategorySku(partNo, target) + if (markChanged) { + this.$set(target, 'changeFlag', 1) + } + }, + openSupplierPartDialog (context, row = null) { + const site = String((context === 'row' ? (row && row.site) : this.searchData.site) || this.$store.state.user.site || '').trim() + const supplierNo = String((context === 'row' + ? (row && (row.supplierNo || row.supplier_no || row.supplierCode || row.supplier_code)) + : this.addForm.supplierNo) || '').trim() + if (!site) { + this.$message.warning('请先选择site') + return + } + if (!supplierNo) { + this.$message.warning('请先选择供应商') + return + } + this.supplierPartDialogVisible = true + this.supplierPartDialogLoading = true + this.supplierPartDialogRows = [] + this.supplierPartDialogContext = context + this.supplierPartDialogTargetRow = context === 'row' ? row : null + this.supplierPartDialogSupplierNo = supplierNo + this.supplierPartDialogSite = site + listPoSupplierPartOptions({ + site, + supplierCode: supplierNo, + active: 'Y' + }).then(({ data }) => { + if (data && data.code === 0) { + this.supplierPartDialogRows = (data.rows || []).map(item => ({ + partNo: item.partNo || '', + partDesc: item.partDesc || '', + item: item.item || '', + sku: item.sku || '' + })) + if (this.supplierPartDialogRows.length === 0) { + this.$message.warning('该供应商未维护供应商可供物料') + } + return + } + this.supplierPartDialogRows = [] + this.$message.error((data && data.msg) || '获取供应商可供物料失败') + }).catch(() => { + this.supplierPartDialogRows = [] + this.$message.error('获取供应商可供物料失败') + }).finally(() => { + this.supplierPartDialogLoading = false + }) + }, + handleSupplierPartRowDblClick (row) { + if (!row) { + return + } + if (this.supplierPartDialogContext === 'add') { + this.applySelectedPartToTarget(this.addForm, row, false) + this.scheduleAutoFillAddPriceByTierPrice() + } else if (this.supplierPartDialogContext === 'row' && this.supplierPartDialogTargetRow) { + this.applySelectedPartToTarget(this.supplierPartDialogTargetRow, row, true) + } + this.supplierPartDialogVisible = false + }, + resetSupplierPartDialog () { + this.supplierPartDialogLoading = false + this.supplierPartDialogRows = [] + this.supplierPartDialogContext = '' + this.supplierPartDialogTargetRow = null + this.supplierPartDialogSupplierNo = '' + this.supplierPartDialogSite = '' + }, resolveAddOrderTypeByCustomer () { const customerOrderType = this.selectedCustomerScope && this.selectedCustomerScope.orderType @@ -2668,6 +2982,11 @@ export default { this.$message.warning('请选择customer') return } + if (this.addPriceLookupTimer) { + clearTimeout(this.addPriceLookupTimer) + this.addPriceLookupTimer = null + } + this.addPriceLookupSeq += 1 this.addForm = this.createEmptyAddForm(this.resolveAddOrderTypeByCustomer()) this.addForm.customer = this.searchData.customer this.addDialogVisible = true @@ -2678,9 +2997,7 @@ export default { if (!this.poCustomerOptions.length) { this.getCustomerOptions() } - if (!this.poBuyerOptions.length) { - this.getBuyerOptions() - } + this.getBuyerOptions(this.searchData.customer) }, loadAddFieldSchema (customer) { @@ -2763,7 +3080,7 @@ export default { this.openEamList(SUPPLIER_CHOOSELIST_TAG, '', conSql, 'add', 'supplier') }, openAddPartNoSelect () { - this.openEamList(507, '', '', 'add', 'partNo') + this.openSupplierPartDialog('add') }, openAddDepartureSelect () { this.openEamList(2018, '', this.buildDeparturePortConSql(), 'add', 'departure') @@ -2838,8 +3155,7 @@ export default { }, openPartNoSelect (row) { - this.currentPartNoRow = row - this.openEamList(507, '', '', 'row', 'partNo') + this.openSupplierPartDialog('row', row) }, openDepartureSelect (row) { diff --git a/src/views/modules/order/poOrderFieldSchemaHelper.js b/src/views/modules/order/poOrderFieldSchemaHelper.js index f20fbd1..4f72560 100644 --- a/src/views/modules/order/poOrderFieldSchemaHelper.js +++ b/src/views/modules/order/poOrderFieldSchemaHelper.js @@ -188,6 +188,7 @@ function buildDynamicColumnBySchema (field, existingProps) { showOverflowTooltip: true, editAble: String(field.editableFlag || '').toUpperCase() === 'Y', editType, + dictType: field && field.dictType ? String(field.dictType).trim() : '', selectOptionsKey: '', selectOptions: null, __sortNo: Number.isFinite(sortNo) ? sortNo : Number.MAX_SAFE_INTEGER @@ -231,9 +232,9 @@ function resolveEditType (field) { if (editorType === 'select') { return 'select' } - // dict-select 在表格批量编辑场景先回落为普通输入,避免无选项导致不可编辑 + // dict-select 在编辑场景仍应按下拉处理(选项由页面按 dictType 异步加载) if (editorType === 'dict-select' || editorType === 'dict_select') { - return 'text' + return 'dict-select' } if (editorType) { return 'text'