Compare commits
3 Commits
6f2f077751
...
e5a7cb2736
| Author | SHA1 | Date | |
|---|---|---|---|
| e5a7cb2736 | |||
| ce767e39ac | |||
| 33acdc758c |
@ -559,6 +559,14 @@ class BorrowApprovalService:
|
|||||||
from app.services.inventory_reservation import release_reserved
|
from app.services.inventory_reservation import release_reserved
|
||||||
restored = release_reserved(approval.get_items())
|
restored = release_reserved(approval.get_items())
|
||||||
|
|
||||||
|
# ★ 清理该单据的扫码草稿(含其他用户的)。
|
||||||
|
# 撤回后单据状态变为 4,不再出现在扫码页的下拉里(那里只拉 status=1),
|
||||||
|
# 草稿从此成为永远读不到的死数据。
|
||||||
|
from app.models.scan_draft import ScanDraft
|
||||||
|
ScanDraft.query.filter_by(
|
||||||
|
biz_type='borrow', request_id=approval.id
|
||||||
|
).delete(synchronize_session=False)
|
||||||
|
|
||||||
approval.status = 4 # 已撤回/已完结
|
approval.status = 4 # 已撤回/已完结
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return True, f"申请单已撤回,释放 {restored} 项预占库存", approval
|
return True, f"申请单已撤回,释放 {restored} 项预占库存", approval
|
||||||
|
|||||||
@ -1327,6 +1327,18 @@ class OutboundApprovalService:
|
|||||||
from app.services.inventory_reservation import release_reserved
|
from app.services.inventory_reservation import release_reserved
|
||||||
restored = release_reserved(approval.get_items())
|
restored = release_reserved(approval.get_items())
|
||||||
|
|
||||||
|
# ★ 清理该单据的扫码草稿。
|
||||||
|
# 草稿按 (user_id, request_id) 隔离,撤回后单据状态变为 4,
|
||||||
|
# 不会再出现在扫码页的单据下拉里(那里只拉 status=1),
|
||||||
|
# 草稿从此成为永远读不到的死数据。此处一并清掉。
|
||||||
|
#
|
||||||
|
# 注意:要清**所有用户**在这张单上的草稿,而非仅撤回者自己的 ——
|
||||||
|
# 可能有多人扫过同一张单,只清自己的会留下他人的孤儿数据。
|
||||||
|
from app.models.scan_draft import ScanDraft
|
||||||
|
ScanDraft.query.filter_by(
|
||||||
|
biz_type='outbound', request_id=approval.id
|
||||||
|
).delete(synchronize_session=False)
|
||||||
|
|
||||||
approval.status = 4 # 4-已撤回/已完结
|
approval.status = 4 # 4-已撤回/已完结
|
||||||
approval.actual_approver_id = user_id
|
approval.actual_approver_id = user_id
|
||||||
approval.approved_at = None
|
approval.approved_at = None
|
||||||
|
|||||||
@ -239,7 +239,7 @@ const handleLogout = () => {
|
|||||||
<footer v-if="!isLoginPage" class="app-footer">
|
<footer v-if="!isLoginPage" class="app-footer">
|
||||||
<span class="version-tag">
|
<span class="version-tag">
|
||||||
<el-icon style="vertical-align: middle; margin-right: 4px"><InfoFilled /></el-icon>
|
<el-icon style="vertical-align: middle; margin-right: 4px"><InfoFilled /></el-icon>
|
||||||
当前版本:V3.75
|
当前版本:V3.76
|
||||||
</span>
|
</span>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
@ -1133,6 +1133,7 @@ const handleSignCancel = () => { showSignatureDialog.value = false }
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (signaturePreviewUrl.value) URL.revokeObjectURL(signaturePreviewUrl.value)
|
if (signaturePreviewUrl.value) URL.revokeObjectURL(signaturePreviewUrl.value)
|
||||||
if (draftTimer) clearTimeout(draftTimer)
|
if (draftTimer) clearTimeout(draftTimer)
|
||||||
|
// 其余清理(定时检测、事件监听)见文件末尾的统一 onUnmounted
|
||||||
})
|
})
|
||||||
|
|
||||||
// ★ 离开页面前:立即存草稿(防抖中未落盘的内容会丢)
|
// ★ 离开页面前:立即存草稿(防抖中未落盘的内容会丢)
|
||||||
@ -1181,8 +1182,65 @@ const handleBeforeUnload = () => {
|
|||||||
} catch (e) { /* 尽力而为,失败不影响 */ }
|
} catch (e) { /* 尽力而为,失败不影响 */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => { window.addEventListener('beforeunload', handleBeforeUnload) })
|
// ============================================================================
|
||||||
onUnmounted(() => { window.removeEventListener('beforeunload', handleBeforeUnload) })
|
// ★ 单据失效检测
|
||||||
|
//
|
||||||
|
// 场景:库管正在扫码,管理员/申请人把这张单撤回了(或已执行完)。
|
||||||
|
// 若不检测,库管会一直扫到提交时才发现单据已作废 —— 白扫一场。
|
||||||
|
//
|
||||||
|
// 检测方式:重新拉一次「已通过(status=1)」列表,看当前单据是否还在其中。
|
||||||
|
// 不在 → 说明已被撤回/驳回/执行,立即告知并清空界面。
|
||||||
|
// 复用现有列表接口,无需新增端点。
|
||||||
|
//
|
||||||
|
// 触发时机:
|
||||||
|
// · 页面重新可见时(PDA 熄屏唤醒、切回浏览器)
|
||||||
|
// · 每 60 秒一次(仅在购物车非空、即正在作业时)
|
||||||
|
// ============================================================================
|
||||||
|
const checkRequestStillValid = async () => {
|
||||||
|
const rid = activeRequestId.value
|
||||||
|
if (!rid || cartItems.value.length === 0) return // 没在作业,不打扰
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res: any = await getApprovalRequestList({ status: 1, page: 1, pageSize: 100 })
|
||||||
|
const stillValid = (res.data?.items || []).some((r: any) => r.id === rid)
|
||||||
|
if (stillValid) return
|
||||||
|
|
||||||
|
// 单据已不在「已通过」列表中 → 失效
|
||||||
|
const no = selectedRequest.value?.request_no || rid
|
||||||
|
cartItems.value = []
|
||||||
|
selectedRequest.value = null
|
||||||
|
activeRequestId.value = null
|
||||||
|
ElMessageBox.alert(
|
||||||
|
`申请单【${no}】已被撤回或已执行,不能再继续出库。\n\n` +
|
||||||
|
`本次已扫的内容已清空(草稿也已随之清除)。\n` +
|
||||||
|
`如需出库,请重新提交申请。`,
|
||||||
|
'⚠️ 申请单已失效',
|
||||||
|
{ type: 'warning', confirmButtonText: '知道了' }
|
||||||
|
).catch(() => {})
|
||||||
|
} catch (e) {
|
||||||
|
// 网络异常时不打断作业 —— 后端在提交时仍会做最终校验
|
||||||
|
console.warn('单据状态检测失败', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面重新可见时检测(PDA 熄屏唤醒、切回标签页)
|
||||||
|
const onVisibilityChange = () => {
|
||||||
|
if (document.visibilityState === 'visible') checkRequestStillValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
let validCheckTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||||
|
document.addEventListener('visibilitychange', onVisibilityChange)
|
||||||
|
validCheckTimer = setInterval(checkRequestStillValid, 60000)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (validCheckTimer) clearInterval(validCheckTimer)
|
||||||
|
document.removeEventListener('visibilitychange', onVisibilityChange)
|
||||||
|
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -1034,6 +1034,7 @@ onUnmounted(() => {
|
|||||||
if (signaturePreviewUrl.value) URL.revokeObjectURL(signaturePreviewUrl.value)
|
if (signaturePreviewUrl.value) URL.revokeObjectURL(signaturePreviewUrl.value)
|
||||||
if (draftTimer) clearTimeout(draftTimer)
|
if (draftTimer) clearTimeout(draftTimer)
|
||||||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||||
|
// 其余清理(定时检测、事件监听)见文件末尾的统一 onUnmounted
|
||||||
})
|
})
|
||||||
|
|
||||||
// ★ 离开页面前立即存草稿(防抖中未落盘的内容会丢)
|
// ★ 离开页面前立即存草稿(防抖中未落盘的内容会丢)
|
||||||
@ -1071,7 +1072,57 @@ const handleBeforeUnload = () => {
|
|||||||
} catch (e) { /* 尽力而为 */ }
|
} catch (e) { /* 尽力而为 */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => { window.addEventListener('beforeunload', handleBeforeUnload) })
|
// ============================================================================
|
||||||
|
// ★ 单据失效检测(与扫码出库页同款)
|
||||||
|
//
|
||||||
|
// 场景:库管正在扫码,这张单被撤回/驳回了。若不检测,库管会一直扫到
|
||||||
|
// 提交时才发现单据已作废 —— 白扫一场。
|
||||||
|
//
|
||||||
|
// 检测方式:重新拉一次「已通过(status=1)」列表,看当前单据是否还在其中。
|
||||||
|
// 触发:页面重新可见时(PDA 熄屏唤醒)+ 每 60 秒(仅购物车非空时)。
|
||||||
|
// ============================================================================
|
||||||
|
const checkRequestStillValid = async () => {
|
||||||
|
const rid = activeApprovalId.value
|
||||||
|
if (!rid || cartItems.value.length === 0) return // 没在作业,不打扰
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res: any = await getBorrowApprovalList({ status: 1, page: 1, limit: 100 })
|
||||||
|
const stillValid = (res.data?.items || []).some((r: any) => r.id === rid)
|
||||||
|
if (stillValid) return
|
||||||
|
|
||||||
|
const no = selectedApproval.value?.request_no || rid
|
||||||
|
cartItems.value = []
|
||||||
|
selectedApprovalId.value = null
|
||||||
|
activeApprovalId.value = null
|
||||||
|
ElMessageBox.alert(
|
||||||
|
`申请单【${no}】已被撤回或已执行,不能再继续借出。\n\n` +
|
||||||
|
`本次已扫的内容已清空(草稿也已随之清除)。\n` +
|
||||||
|
`如需借出,请重新提交申请。`,
|
||||||
|
'⚠️ 申请单已失效',
|
||||||
|
{ type: 'warning', confirmButtonText: '知道了' }
|
||||||
|
).catch(() => {})
|
||||||
|
} catch (e) {
|
||||||
|
// 网络异常时不打断作业 —— 后端在提交时仍会做最终校验
|
||||||
|
console.warn('单据状态检测失败', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onVisibilityChange = () => {
|
||||||
|
if (document.visibilityState === 'visible') checkRequestStillValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
let validCheckTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||||
|
document.addEventListener('visibilitychange', onVisibilityChange)
|
||||||
|
validCheckTimer = setInterval(checkRequestStillValid, 60000)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (validCheckTimer) clearInterval(validCheckTimer)
|
||||||
|
document.removeEventListener('visibilitychange', onVisibilityChange)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user