Files
KCGL/inventory-web/src/api/transaction.ts
yueli f4f887c2b4 feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」
提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类:

  ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上
  ② 待我接收   —— 别人转给我、等我确认

弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断):
  标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数),
  按钮【去处理】(跳借还记录) /【知道了】。

★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。
  前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办;
  本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack),
  避免两个 Modal 叠加打扰。

★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条,
  从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次,
  也不能漏)。

防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。

顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交
不再与成功的长得一模一样。

验证(node 复刻判定链)
  既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack;
  下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。
  拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00

380 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import request from '@/utils/request'
// 购物车商品项接口
export interface CartItem {
id: number
sku: string
name: string
spec_model: string
source_table: string
stock_quantity: number
available_quantity: number
barcode: string
price: number // 单价
out_quantity: number // 本次出库数量
}
// 提交出库单的数据结构
export interface OutboundSubmitData {
items: Array<{
sku: string
source_table: string
stock_id: number
barcode: string
quantity: number
price: number
}>
outbound_type: string
consumer_name: string
operator_name: string
signature_path: string // 上传后返回的图片路径
remark?: string
}
export interface ScanResult {
id: number
sku: string
name: string
spec_model: string
source_table: string // 'stock_buy' | 'stock_product' ...
stock_quantity: number
available_quantity: number
batch_number?: string
warehouse_location?: string
barcode?: string
price?: number // 扫描返回的价格
}
/**
* 根据条码获取库存物品详情
*
* ⚠ 本文件是与 `@/api/outbound` 同名的**历史副本**(同 URL、同返回形状
* 当前无任何调用方 —— 借库扫码页 `views/transaction/borrow.vue` 是从
* `@/api/outbound` 导入的。改动请落到那边,这里同步仅为避免下次改漏。
*
* @param barcode 扫描到的条码
* @param requestId 本单 ID出库审批单 / 借库审批单)
* @param bizType 单据类型,决定 requestId 查哪张表(默认 outbound
*/
export function getStockByBarcode(
barcode: string,
requestId?: number | null,
bizType: 'outbound' | 'borrow' = 'outbound'
) {
return request<any, ScanResult>({
url: '/v1/outbound/scan',
method: 'get',
params: { barcode, request_id: requestId, biz_type: bizType }
})
}
/**
* 提交出库单 (批量)
*/
export function submitOutbound(data: OutboundSubmitData) {
return request({
url: '/v1/outbound',
method: 'post',
data
})
}
/**
* 获取出库记录列表
*/
export function getOutboundList(params: any) {
return request({
url: '/v1/outbound',
method: 'get',
params
})
}
/**
* 提交出库申请单(申请人 → 审批流)
*/
export function submitOutboundRequest(data: {
items: Array<{
material_type?: string
name: string
spec_model: string
warehouse_location?: string
quantity: number
}>
remark: string
}) {
return request({
url: '/v1/outbound/request',
method: 'post',
data
})
}
/**
* 获取出库审批申请单列表
* @param params 支持 status, page, limit
*/
export function getApprovalRequestList(params: { status?: number | ''; page?: number; limit?: number }) {
return request({
url: '/v1/outbound/request',
method: 'get',
params
})
}
/**
* 审批(通过 / 驳回)出库申请单
* @param id 审批单ID
* @param data action: 'approve' | 'reject'reject 时需传 reject_reason
*/
export function approveRequest(id: number, data: { action: 'approve' | 'reject'; reject_reason?: string }) {
return request({
url: `/v1/outbound/request/${id}/approve`,
method: 'patch',
data
})
}
// ==============================================================================
// 借库审批流 API
// ==============================================================================
/**
* 提交借库申请单(申请人 → 审批流)
*/
export function submitBorrowRequest(data: {
items: Array<{
name: string
spec_model: string
warehouse_location?: string
quantity: number
}>
remark?: string
allowed_approvers?: Array<{ type: string; value: string }>
approver_id?: number
}) {
return request({
url: '/v1/transactions/borrow/request',
method: 'post',
data
})
}
// 借库申请预检:判断所选物料是否需审批
export function checkBorrowApproval(data: { items: Array<{ name?: string; spec_model?: string }> }) {
return request({
url: '/v1/transactions/borrow/request/check-approval',
method: 'post',
data
})
}
/**
* 获取借库审批申请单列表
* @param params 支持 status, page, limit
*/
export function getBorrowApprovalList(params: { status?: number | ''; page?: number; limit?: number }) {
return request({
url: '/v1/transactions/borrow/request',
method: 'get',
params
})
}
/**
* 审批(通过 / 驳回)借库申请单
* @param id 审批单ID
* @param data action: 'approve' | 'reject'reject 时需传 reject_reason
*/
export function approveBorrowRequest(id: number, data: { action: 'approve' | 'reject'; reject_reason?: string }) {
return request({
url: `/v1/transactions/borrow/request/${id}/approve`,
method: 'patch',
data
})
}
// 手动完结已通过的借库审批单status 1 → 3
export function closeBorrowRequest(id: number) {
return request({
url: `/v1/transactions/borrow/request/${id}/close`,
method: 'post'
})
}
/**
* 借库选单专用库存列表(独立权限 op_borrow_apply
*/
export function getBorrowStockList(params: { page?: number; pageSize?: number; keyword?: string }) {
return request({
url: '/v1/transactions/borrow/stock-list',
method: 'get',
params
})
}
/**
* 执行借库扣减(审批通过后调用)
*
* ★ borrower_id 为**必填**借用人身份从「姓名」升级为「ID 锚点」后,
* 后端以 borrower_id 反查 sys_user 生成姓名快照;缺失会被直接拒绝。
* borrower_name 仅作兼容保留,后端不采信。
*
* @param data approval_id + 扫码选中的物品 + 借用人ID + 签名
*/
export function dispatchBorrow(data: {
approval_id: number
items: Array<any>
borrower_id: number
borrower_name?: string
signature_path: string
remark?: string
expected_return_time?: string | null
}) {
return request({
url: '/v1/transactions/borrow/dispatch',
method: 'post',
data
})
}
/**
* 借库责任链人员名单(借用人 / 转交接收人 / 实际归还人共用)
* @returns [{ id, name }]
*/
export function getBorrowUsers() {
return request({
url: '/v1/transactions/borrow/users',
method: 'get'
})
}
/**
* 借库转交(一期:仅支持整单全量转交)
*
* ★ transfer_qty 必须严格等于待还量,部分转交会被后端拒绝 ——
* 单行模型无法同时追踪两个持有人。
* ★ 转交是纯持有权变更,不触碰任何库存字段。
*
* @param borrowId trans_borrow.id
* @param data transfer_qty + to_user_id + remark
*/
export function transferBorrow(borrowId: number, data: {
transfer_qty: number
to_user_id: number
to_user_name?: string
remark?: string
}) {
return request({
url: `/v1/transactions/borrow/${borrowId}/transfer`,
method: 'post',
data
})
}
/**
* 借出单的流转历史(**单品**维度):转交链 + 逐次归还明细
* @returns { record, transfers[], returns[] }
*/
export function getBorrowHistory(borrowId: number) {
return request({
url: `/v1/transactions/borrow/${borrowId}/history`,
method: 'get'
})
}
/**
* 确认接收转交(双向握手第二步)—— 责任正式转移到接收人名下。
*
* ★ 仅该转交的 to_user_id 本人可调用(后端强校验,非库管权限)。
* ★ 副作用:该单**全部未归还明细**的当前持有人一并变更。
*/
export function acceptBorrowTransfer(transferId: number) {
return request({
url: `/v1/transactions/borrow/transfer/${transferId}/accept`,
method: 'post'
})
}
/**
* 拒绝转交 —— 主表不动,责任仍在原持有人。权限同上。
* @param reason 可选拒绝原因,写入转交流水备注
*/
export function rejectBorrowTransfer(transferId: number, reason?: string) {
return request({
url: `/v1/transactions/borrow/transfer/${transferId}/reject`,
method: 'post',
data: { reason }
})
}
/**
* 全局待办强提醒的轮询数据源(初始化 + 轮询)。
*
* ★ 一次请求返回两类信息,避免前端多打一个请求:
* count —— 待**我接收**的转交数量
* rejects —— **我发起、被对方拒绝、尚未告知我**的转交
* (被拒时物品责任仍在我手上,不告知就会误以为已交接出去)
*
* @returns { count, rejects: [{ id, borrow_no, sku, material_name, to_user_name, remark, ... }] }
*/
export function getPendingTransferCount() {
return request({
url: '/v1/transactions/borrow/transfer/pending-count',
method: 'get'
})
}
/**
* 确认已知悉「转交被拒绝」—— 写 reject_seen_at之后不再提醒。
* @param ids 留空表示该用户全部待告知的拒绝
*/
export function ackTransferRejects(ids?: number[]) {
return request({
url: '/v1/transactions/borrow/transfer/reject-ack',
method: 'post',
data: { ids }
})
}
/**
* 整单流转时间线(**borrow_no** 维度):借出 → 转交(可多次) → 归还 → 报废
*
* ★ 与 getBorrowHistory 的分工:列表页是 borrow_no 主子表结构(单张单最多
* 21 条明细),逐条明细调用单品接口会产生 21 个请求,且各条时间线无法全局
* 排序。本接口一次返回合并后的事件流,按时间**倒序**。
*
* @returns { borrow_no, events[], records[] }
*/
export function getBorrowSlipHistory(borrowNo: string) {
return request({
url: `/v1/transactions/borrow/slip/${encodeURIComponent(borrowNo)}/history`,
method: 'get'
})
}
/**
* 提交「借出未归还」的**报废申请**(需审批人审批,通过后由库管执行报废)。
*
* ★ 已由「直接报废」改为「申请」:
* 原 POST /v1/transactions/borrow/scrap 直接写 trans_scrap 并扣总库存,
* 绕过审批、构成职责分离漏洞,已删除。现统一走 申请 → 审批 → 执行。
*
* ★ 副作用提醒:申请**不立即改变**借用记录状态,提交后该笔仍显示为未归还。
* 前端必须提示用户「待审批并执行后才会标记为已报废」。
*
* @param data record_ids: trans_borrow.id 列表(按整条待还量报废)
* reason?: 写入申请单备注
* approver_id: 必填,指定审批人
*/
export function submitBorrowScrapRequest(data: {
record_ids: number[]
reason?: string
approver_id: number
}) {
return request({
url: '/v1/transactions/borrow/scrap-request',
method: 'post',
data
})
}