5 Commits

Author SHA1 Message Date
578c15dc95 fix(bom): 另存为版本自增基准改响应式,修复 V1.1→V1.2 未升级
- originalVersion/currentBomNo 为普通 let,computed 读不到变化;且 handleSaveAs 在赋值前就算版本候选,导致从 V1.1 另存得到 V1.1 覆盖原版本
- 新增响应式 upgradeBaseVersion/upgradeBaseBomNo,versionOptions 改读 ref;handleSaveAs 先锁定基准再计算
- 修复后:V1.1→V1.2、V1.9→V1.10、主版本→V2.0,占用号自动跳过
2026-09-08 18:19:28 +08:00
00dde3a896 fix(inbound/buy): 采购入库表单批号续号改用精准接口
- checkHistoryAndSetMode 改调 /inbound/buy/latest-record 续号,不再依赖 getBuyList 前1000条
- 采购单导入“仅有 base_id”分支也自动续批号并带历史库位
2026-09-08 18:16:08 +08:00
2b0e335790 fix(inbound/buy): 批号自增改为按物料精准取最近一条
- 新增 get_latest_batch_record_by_base_id:SQL 直接 filter(base_id)+order by in_date/id desc first(),O(1) 不分页
- 新增 GET /inbound/buy/latest-record
- 修复原“拉全表前1000条再前端过滤”导致的历史被截断→批号退回000001/重复入库被拦(如 CCAB0029)
2026-09-08 18:16:04 +08:00
b4971f057e fix(bom): 管理页启停/归档即时同步与界面健壮性大修
- 只读启停用后端权威 bom_no/version,修复改状态报 BOM不存在(400)
- 成功路径统一 await reloadCurrentGroups():先更新分组摘要,再静默覆盖已展开分类明细
- 本地秒移除 applyLocalRowState:按新状态+当前筛选把不符合行立即踢出缓存并强制 Map 换引用
- 接口层与界面层 try 分离:UI 刷新异常不再误报“状态更新失败”,避免红绿/黄提示同弹
- 遍历全部改为 Array.isArray/?.keys()/entries()/||[] 防御,杜绝 undefined 误报
- 兼容 el-collapse 手风琴(String)与多开(Array):新增 isCategoryActive/setActiveList,修复 activeCategories.filter is not a function
2026-09-08 13:58:48 +08:00
66b3e4897f fix(bom): get_bom_summary 关键词与 list 对齐 + 状态更新方法健壮化
- get_bom_summary 关键词过滤改为与 get_bom_list 同款子查询(同时匹配 bom_no、父件名/规格、子件名/规格),任意关键词下 summary 计数与 list 明细严格一致,消除“标题有数、表格为空”
- update_enabled/update_archived 对 bom_no/version 做 strip 规范化,并细化“BOM不存在”报错(带上实际参数便于排查)
2026-09-08 13:58:43 +08:00
6 changed files with 272 additions and 54 deletions

View File

@ -353,3 +353,18 @@ def get_last_location():
location = BuyInboundService.get_last_location_by_base_id(base_id)
return jsonify({"code": 200, "msg": "success", "data": {"location": location}})
@inbound_buy_bp.route('/latest-record', methods=['GET'])
@permission_required('inbound_buy')
def get_latest_batch_record():
"""
按物料精准取最近一次采购入库记录(批号自增用)。
返回 data: {'has_history': bool, 'mode': 'serial'|'batch', 'latest_batch': str}
"""
base_id = request.args.get('base_id', type=int)
if not base_id:
return jsonify({"code": 400, "msg": "base_id required"}), 400
data = BuyInboundService.get_latest_batch_record_by_base_id(base_id)
return jsonify({"code": 200, "msg": "success", "data": data})

View File

@ -267,14 +267,35 @@ class BomService:
elif status == 'archived':
query = query.filter(BomTable.is_archived == True)
# 关键词搜索
# 关键词搜索:与 get_bom_list 保持同一口径——子查询匹配 (bom_no, version),
# 同时覆盖 bom_no、父件名/规格、子件名/规格,保证任意关键字下 summary 计数与 list 明细绝对一致
if keyword:
kw = f'%{keyword.strip()}%'
query = query.filter(or_(
BomTable.bom_no.ilike(kw),
MaterialBase.name.ilike(kw),
MaterialBase.spec_model.ilike(kw)
))
kw_child_alias = db.aliased(MaterialBase)
match_subq = db.session.query(
BomTable.bom_no,
BomTable.version
).join(
MaterialBase, BomTable.parent_id == MaterialBase.id
).outerjoin(
kw_child_alias, BomTable.child_id == kw_child_alias.id
).filter(
or_(
BomTable.bom_no.ilike(kw),
MaterialBase.name.ilike(kw),
MaterialBase.spec_model.ilike(kw),
kw_child_alias.name.ilike(kw),
kw_child_alias.spec_model.ilike(kw)
)
).distinct().subquery()
query = query.join(
match_subq,
db.and_(
BomTable.bom_no == match_subq.c.bom_no,
BomTable.version == match_subq.c.version
)
)
query = query.group_by(MaterialBase.category) \
.order_by(MaterialBase.category)
@ -525,10 +546,16 @@ class BomService:
"""
仅更新某 BOM 版本(整组行)的启用状态,供列表/只读视图"启用/停用"开关使用。
不触碰子件结构;成功后清除该 bom_no 的缓存。
注意:只按 bom_no+version 精确匹配,不加 is_enabled/is_archived 等多余过滤——
停用/归档中的版本只要记录存在即可继续改状态。
"""
bom_no = (bom_no or '').strip()
version = (version or '').strip()
if not bom_no or not version:
raise ValueError('BOM编号/版本不能为空')
rows = BomTable.query.filter_by(bom_no=bom_no, version=version).all()
if not rows:
raise ValueError('BOM 不存在')
raise ValueError(f'BOM不存在(bom_no={bom_no}, version={version})')
for rec in rows:
rec.is_enabled = bool(is_enabled)
db.session.commit()
@ -542,9 +569,13 @@ class BomService:
仅更新某 BOM 版本(整组行)的归档标记。
归档=仍启用可查看/编辑,但不再作为其它 BOM 子件的引用候选;取消归档即恢复正常引用。
"""
bom_no = (bom_no or '').strip()
version = (version or '').strip()
if not bom_no or not version:
raise ValueError('BOM编号/版本不能为空')
rows = BomTable.query.filter_by(bom_no=bom_no, version=version).all()
if not rows:
raise ValueError('BOM 不存在')
raise ValueError(f'BOM不存在(bom_no={bom_no}, version={version})')
for rec in rows:
rec.is_archived = bool(archived)
db.session.commit()

View File

@ -601,6 +601,28 @@ class BuyInboundService:
return [r[0] for r in
db.session.query(StockBuy.warehouse_location).filter(StockBuy.base_id == base_id).distinct().all()]
@staticmethod
def get_latest_batch_record_by_base_id(base_id):
"""
按物料精准取“最近一次采购入库记录”(SQL 直接 filter base_id + limit,O(1)、不分页)。
用于采购入库表单的批号自增:避免以前端拉 getBuyList 前 1000 条再过滤造成的
“历史被截断 → 误判无历史 → 批号退回 000001/重复入库被拦”问题。
返回: {'has_history': bool, 'mode': 'serial'|'batch', 'latest_batch': str}
"""
if not base_id:
return {'has_history': False, 'mode': 'batch', 'latest_batch': ''}
latest = StockBuy.query.filter(
StockBuy.base_id == base_id
).order_by(
StockBuy.in_date.desc().nullslast(),
StockBuy.id.desc()
).first()
if latest is None:
return {'has_history': False, 'mode': 'batch', 'latest_batch': ''}
if latest.serial_number:
return {'has_history': True, 'mode': 'serial', 'latest_batch': latest.batch_number or ''}
return {'has_history': True, 'mode': 'batch', 'latest_batch': latest.batch_number or ''}
@staticmethod
def get_last_location_by_base_id(base_id):
"""

View File

@ -54,6 +54,15 @@ export function searchMaterialBase(keyword: string, page: number = 1, company?:
})
}
// 6.1 按物料精准取“最近一次采购入库记录”(批号自增用;不再全局拉前 N 条再前端过滤)
export function getBuyLatestRecord(baseId: number) {
return request({
url: '/inbound/buy/latest-record',
method: 'get',
params: { base_id: baseId }
})
}
// 7. 文件上传
export function uploadFile(data: FormData) {
return request({

View File

@ -41,7 +41,7 @@
:title="group.category + ' (' + group.count + ')'"
:name="group.category"
>
<el-table v-if="activeCategories.includes(group.category)" :data="group.items" border style="width: 100%">
<el-table v-if="isCategoryActive(group.category)" :data="group.items" border style="width: 100%">
<el-table-column v-if="hasColumnPermission('bom_no')" prop="bom_no" label="BOM编号" min-width="180" show-overflow-tooltip>
<template #default="{ row }">
<span style="cursor: pointer; color: #409EFF;" @click="handleView(row)">{{ row.bom_no }}</span>
@ -424,6 +424,9 @@ const isSaveAsMode = ref(false)
const isReadOnlyMode = ref(false)
let originalVersion = ''
let currentBomNo = ''
// ★ 版本升级的响应式基准(plain let 不是响应式,computed 读不到变化 → 1.1 升次版本会算出错的候选)
const upgradeBaseVersion = ref('')
const upgradeBaseBomNo = ref('')
let originalChildren: ChildRow[] = []
let pendingDraftBomNo = ''
let pendingDraftVersion = ''
@ -441,7 +444,15 @@ interface GroupSummary { category: string; count: number }
const groupSummary = ref<GroupSummary[]>([])
const groupCache = ref<Map<string, any[]>>(new Map())
const groupLoadingMap = ref<Map<string, boolean>>(new Map())
const activeCategories = ref<string[]>([])
// ★ el-collapse 手风琴(accordion=true)时 v-model 是单个 String,多开时才是 Array —— 统一按数组读写
const activeCategories = ref<string | string[]>([])
const activeList = (): string[] =>
Array.isArray(activeCategories.value) ? activeCategories.value : (activeCategories.value ? [activeCategories.value] : [])
const isCategoryActive = (c: string) => activeList().includes(c)
const setActiveList = (arr: string[]) => {
if (isAccordion.value) activeCategories.value = arr.length ? arr[0] : ''
else activeCategories.value = arr
}
const lastKeyword = ref('')
const groupedData = computed(() => {
@ -609,11 +620,11 @@ const form = reactive({
const pureBomNo = computed(() => form.bom_no)
const versionOptions = computed(() => {
const ver = originalVersion || 'V1.0'
const ver = upgradeBaseVersion.value || 'V1.0'
const allItems: any[] = []
groupCache.value.forEach((items) => allItems.push(...items))
const occupiedVersions = new Set(
allItems.filter((item: any) => item.bom_no === currentBomNo).map((item: any) => item.version)
allItems.filter((item: any) => item.bom_no === upgradeBaseBomNo.value).map((item: any) => item.version)
)
const getNextMinor = (baseMajor: number, baseMinor: number): string => {
let minor = baseMinor + 1
@ -646,9 +657,9 @@ const rules = reactive<FormRules>({
const dialogTitle = ref('新建 BOM')
const handleSearch = () => {
activeCategories.value = []
groupCache.value = new Map()
isAccordion.value = true // 搜索/筛选重置回默认手风琴模式
groupCache.value = new Map()
setActiveList([]) // 手风琴下折叠为 '',多开下折叠为 []
fetchBomSummary()
}
@ -664,7 +675,7 @@ const fetchBomSummary = async () => {
if (res?.code === 200) {
groupSummary.value = res.data ?? []
if (searchKeyword.value !== lastKeyword.value) {
activeCategories.value = []
setActiveList([])
lastKeyword.value = searchKeyword.value
}
}
@ -680,15 +691,14 @@ const expandAllGroups = () => {
}
// ★ 全部折叠:清空面板,并恢复手风琴模式
const collapseAllGroups = () => {
activeCategories.value = []
isAccordion.value = true
setActiveList([])
}
// ★ 展开分组时懒加载该分类下的 BOM
const loadGroupItems = async (category: string) => {
if (groupCache.value.has(category)) return
if (groupLoadingMap.value.get(category)) return
// ★ 静默获取单个分类的最新数据并“覆盖”缓存:
// 不清空该分类缓存、请求失败则保留旧数据(保证面板绝不闪白/变空)。
const fetchCategoryChildren = async (category: string): Promise<boolean> => {
if (groupLoadingMap.value.get(category)) return false
groupLoadingMap.value.set(category, true)
try {
const params: any = {
@ -703,8 +713,19 @@ const loadGroupItems = async (category: string) => {
if (res?.code === 200) {
groupCache.value.set(category, res.data?.items ?? [])
}
} catch (e) { console.error(`加载BOM分类 [${category}] 失败`, e) }
finally { groupLoadingMap.value.set(category, false) }
return true
} catch (e) {
console.error(`加载BOM分类 [${category}] 失败`, e)
return false
} finally {
groupLoadingMap.value.set(category, false)
}
}
// ★ 展开分组时懒加载该分类下的 BOM(仅在无缓存时拉取)
const loadGroupItems = async (category: string) => {
if (groupCache.value.has(category)) return
await fetchCategoryChildren(category)
}
// ★ collapse @change 事件 → 触发懒加载
@ -713,6 +734,47 @@ const handleCollapseChange = (val: string | string[]) => {
cats.forEach(c => loadGroupItems(c))
}
// ★ 启停/归档/删除等行操作后的平滑刷新:
// - 绝不清空整体缓存,也绝不清空/关闭任何已展开面板;
// - 先更新分组摘要计数,再对“当前已展开”的分类逐个静默重取最新数据覆盖缓存;
// - 不符合当前筛选的行会因重取自然消失、符合的保留;单次请求失败则沿用旧数据,面板不闪白。
const reloadCurrentGroups = async () => {
// 1. 获取最新分组摘要
await fetchBomSummary();
// 2. 有效分类集合(防御:摘要可能为空)
const validCategoryNames = (groupSummary.value || []).map(g => g && g.category);
// 3. 兼容手风琴(String)与多开(Array),统一转数组;再过滤掉已归零分类
const currentActiveList = Array.isArray(activeCategories.value)
? activeCategories.value
: (activeCategories.value ? [activeCategories.value] : []);
const validActiveList = currentActiveList.filter(c => validCategoryNames.includes(c));
// 4. 按当前模式安全写回(手风琴只保留第一个 → 触发折叠动画;多开写回数组)
if (isAccordion.value) {
activeCategories.value = validActiveList.length > 0 ? validActiveList[0] : '';
} else {
activeCategories.value = validActiveList;
}
// 5. 确定要刷新的打开面板
const opened = [...validActiveList];
// 6. 暴力清理不需要的旧缓存(遍历 cache 的 keys,而不是 summary)
for (const key of Array.from((groupCache.value && groupCache.value.keys ? groupCache.value.keys() : []) || [])) {
if (!opened.includes(key)) {
groupCache.value.delete(key);
}
}
// 7. 重新拉取仍在打开状态的分类明细
await Promise.all((opened || []).map(c => fetchCategoryChildren(c)));
// 8. [关键] 强制触发 Vue 对 Map 的深度响应式更新
groupCache.value = new Map(groupCache.value || new Map());
}
const onParentChange = (val: number) => {}
const onChildChange = (val: number | null, index: number) => {
@ -949,10 +1011,15 @@ const handleSaveAs = async (row: any) => {
form.bom_no = (raw.parent_spec || row.bom_no).split('/')[0].trim()
form.remark = raw.remark || ''
form.versionUpgradeType = 'minor'
form.version = versionOptions.value?.minor || 'V1.1'
// ★ 先锁定升级基准(原版本号 + 该 BOM),再计算目标版本,保证 V1.1 → V1.2 等正确且跳过已占用号
originalVersion = raw.version || ''
currentBomNo = row.bom_no
upgradeBaseVersion.value = originalVersion
upgradeBaseBomNo.value = currentBomNo
const upgradeOpts = versionOptions.value
form.version = upgradeOpts?.minor || upgradeOpts?.major || 'V1.1'
originalChildren = JSON.parse(JSON.stringify(form.children))
originalDraftHash.value = getDraftHash()
@ -989,14 +1056,11 @@ const loadDetail = async (bomNo: string, version: string) => {
parentNameInput.value = data.parent_name || '未知产品'
}
if (data.parent_spec) {
form.bom_no = (data.parent_spec || '').split('/')[0].trim()
} else {
form.bom_no = bomNo.split('/')[0].trim()
}
// ★ 只读视图的状态开关必须用后端返回的权威 bom_no/version,
// 不要再用 parent_spec 首位 token 去反推(规格首位可能不等于 BOM 编号 → /status 查不到 → 400)
form.bom_no = data.bom_no || bomNo
form.remark = data.remark || ''
// 查看/只读视图的独立启停需要版本与当前状态
form.version = version || data.version || 'V1.0'
form.version = data.version || version || 'V1.0'
form.is_enabled = data.is_enabled !== false
}
} catch (e) {
@ -1006,34 +1070,70 @@ const loadDetail = async (bomNo: string, version: string) => {
}
}
// ★ 前端本地“秒移除”:后端确认成功后,按“新状态 + 当前筛选”判断该行是否还可见,
// 不可见则立即从各分类缓存数组中踢掉并强制 Map 换引用(不等明细接口,列表立刻消失)
const applyLocalRowState = (bomNo: string, version: string, patch: { is_enabled?: boolean; is_archived?: boolean }) => {
const status = bomStatusFilter.value
let changed = false
for (const [, list] of Array.from((groupCache.value && groupCache.value.entries ? groupCache.value.entries() : []) || [])) {
if (!Array.isArray(list)) continue // 防御:缓存某项可能为空
const idx = list.findIndex((i: any) => i && i.bom_no === bomNo && i.version === version)
if (idx === -1) continue
const item = list[idx]
if (!item) continue
if (patch.is_enabled !== undefined) item.is_enabled = patch.is_enabled
if (patch.is_archived !== undefined) item.is_archived = patch.is_archived
let visible = true
if (status === 'enabled') visible = !!item.is_enabled && !item.is_archived
else if (status === 'disabled') visible = item.is_enabled === false
else if (status === 'archived') visible = !!item.is_archived
if (!visible) { list.splice(idx, 1); changed = true }
}
if (changed) groupCache.value = new Map(groupCache.value)
}
// ★ 启用/停用开关:查看/只读态下独立更新状态(不触发整表单保存);
// 新建/编辑/另存模式则仅记录到 form,随 submitForm 一并保存。
const onEnabledStatusChange = async () => {
if (!isReadOnlyMode.value) return
if (!pureBomNo.value || !form.version) {
const curBomNo = String(form.bom_no || '').trim()
const curVersion = String(form.version || '').trim()
if (!curBomNo || !curVersion) {
ElMessage.warning('缺少 BOM 编号/版本,无法更新状态')
return
}
const target = !!form.is_enabled
// ① 接口层:只负责提交;只有这一层失败才提示“状态更新失败”
saving.value = true
try {
const res: any = await updateBomStatus({ bom_no: pureBomNo.value, version: form.version, is_enabled: target })
const res: any = await updateBomStatus({ bom_no: curBomNo, version: curVersion, is_enabled: target })
if (res?.code === 200) {
ElMessage.success(target ? '已启用' : '已停用')
// 刷新左侧列表摘要(计数与状态列)
groupCache.value = new Map()
fetchBomSummary()
form.is_enabled = target
} else {
form.is_enabled = !target
ElMessage.error(res?.msg || '状态更新失败')
return
}
} catch (e) {
console.error('启用/停用接口异常:', e)
form.is_enabled = !target
ElMessage.error('状态更新失败')
ElMessage.error((e as any)?.response ? '状态更新失败' : '网络异常,状态可能未更新,请重试')
return
} finally {
saving.value = false
}
// ② 界面层:状态已成功,本地秒移除 + 整体同步;此处异常绝不回退成“状态更新失败”
try {
applyLocalRowState(curBomNo, curVersion, { is_enabled: target })
await reloadCurrentGroups()
} catch (e) {
console.error('状态已更新,但界面刷新异常:', e)
ElMessage.warning('状态已更新,界面刷新异常,请手动刷新查看')
}
}
// ★ 归档/取消归档:仍启用可编辑,但不再作为其它 BOM 子件的版本候选
@ -1045,25 +1145,60 @@ const handleArchive = async (row: any) => {
try {
await ElMessageBox.confirm(tip, '提示', { type: 'warning' })
} catch (e) { return }
// ① 接口层
try {
const res: any = await updateBomArchive({ bom_no: row.bom_no, version: row.version, is_archived: target })
if (res?.code === 200) {
ElMessage.success(target ? '已归档' : '已取消归档')
groupCache.value = new Map()
fetchBomSummary()
} else {
ElMessage.error(res?.msg || '操作失败')
return
}
} catch (e) { ElMessage.error('归档操作失败') }
} catch (e) {
console.error('归档接口异常:', e)
ElMessage.error((e as any)?.response ? '操作失败' : '网络异常,请重试')
return
}
// ② 界面层
try {
applyLocalRowState(row.bom_no, row.version, { is_archived: target })
await reloadCurrentGroups()
} catch (e) {
console.error('归档成功,但界面刷新异常:', e)
ElMessage.warning('操作已生效,界面刷新异常,请手动刷新查看')
}
}
const handleDelete = (row: BomItem) => {
ElMessageBox.confirm(`确定删除 ${row.bom_no} (${row.version}) 吗?`, '警告', { type: 'warning' })
.then(async () => {
let deleted = false
try {
const res = await deleteBom(row.bom_no, row.version)
if (res.code === 200) { ElMessage.success('删除成功'); groupCache.value = new Map(); fetchBomSummary() }
} catch (e) {}
if (res.code === 200) {
ElMessage.success('删除成功')
// 删除不受筛选限制:本地移除该行(带数组守卫)
for (const [, list] of Array.from((groupCache.value && groupCache.value.entries ? groupCache.value.entries() : []) || [])) {
if (!Array.isArray(list)) continue
const i = list.findIndex((it: any) => it && it.bom_no === row.bom_no && it.version === row.version)
if (i > -1) list.splice(i, 1)
}
groupCache.value = new Map(groupCache.value)
deleted = true
}
} catch (e) {
console.error('删除接口异常:', e)
ElMessage.error((e as any)?.response ? '删除失败' : '网络异常,请重试')
}
if (deleted) {
try {
await reloadCurrentGroups()
} catch (e) {
console.error('删除成功,但界面刷新异常:', e)
ElMessage.warning('已删除,界面刷新异常,请手动刷新查看')
}
}
})
.catch(() => {})
}
@ -1080,6 +1215,8 @@ const resetForm = () => {
isReadOnlyMode.value = false
originalVersion = ''
currentBomNo = ''
upgradeBaseVersion.value = ''
upgradeBaseBomNo.value = ''
childSearchKeyword.value = ''
parentNameInput.value = ''
originalDraftHash.value = ''
@ -1157,8 +1294,8 @@ const submitForm = async () => {
localStorage.removeItem('pending_bom_draft_version')
originalDraftHash.value = ''
dialogVisible.value = false
groupCache.value = new Map()
fetchBomSummary()
// 关窗后仅重载已展开分类,避免列表空白
reloadCurrentGroups()
} else {
ElMessage.error(res.msg || '保存失败')
}

View File

@ -799,7 +799,8 @@ import {
getSupplierSuggestions,
getUserSuggestions,
getLocationSuggestions,
getFilterOptions
getFilterOptions,
getBuyLatestRecord
} from '@/api/inbound/buy'
import { getApprovedUnstockedRequests } from '@/api/purchase'
import {getLabelPreview, executePrint} from '@/api/common/print'
@ -1352,24 +1353,25 @@ const rules = computed(() => {
const checkHistoryAndSetMode = async (baseId: number) => {
try {
const res: any = await getBuyList({page: 1, pageSize: 1000})
const historyItems = (res.data.items || []).filter((item: any) => item.base_id === baseId)
// ★ 改用后端“按物料精准取最近一条”接口(O(1)、不分页、不受历史行数/前1000条截断影响)
const res: any = await getBuyLatestRecord(baseId)
const rec = res?.data || {}
if (historyItems.length > 0) {
if (rec.has_history) {
modeLocked.value = true
const latest = historyItems.sort((a: any, b: any) => b.id - a.id)[0]
if (latest.serial_number) {
if (rec.mode === 'serial') {
entryMode.value = 'serial'
form.serial_number = ''
form.batch_number = ''
} else {
entryMode.value = 'batch'
form.serial_number = ''
form.batch_number = incrementBatchNumber(latest.batch_number || '000000')
form.batch_number = incrementBatchNumber(rec.latest_batch || '000000')
}
} else {
modeLocked.value = false
entryMode.value = 'batch'
form.serial_number = ''
form.batch_number = '000001'
}
if (formRef.value) {
@ -1379,6 +1381,7 @@ const checkHistoryAndSetMode = async (baseId: number) => {
} catch (e) {
modeLocked.value = false
entryMode.value = 'batch'
form.serial_number = ''
form.batch_number = '000001'
}
}
@ -1916,10 +1919,11 @@ const confirmPurchaseImport = () => {
isInspectionRequired: mat.is_inspection_required
}]
} else if (po.base_id) {
// 如果 material 为空但 base_id 有值,尝试通过 base_id 查询
// 这里简单处理:直接设置 base_id 让用户手动搜索
// 如果 material 为空但 base_id 有值:仍按 base_id 续批号/带历史库位,物料详情让用户手动搜索确认
form.base_id = po.base_id
ElMessage.info('该采购单未附带物料详情,已填充 base_id,请手动搜索物料确认')
checkHistoryAndSetMode(po.base_id)
fetchLastLocation(po.base_id)
ElMessage.info('该采购单未附带物料详情,已按物料填充批号/库位,请手动搜索确认物料信息')
} else {
ElMessage.warning('该采购单未关联基础物料,无法自动填充物料信息')
return