feat(outbound,common): 补发可指定「补发给谁」+ 抽出通用人员名单接口
一、补发申请人可选择(原单退回)
退回接口新增 reissue_applicant_id:
① 前端指定 → 校验用户存在后落库;
② 未指定 → 回退为**当前操作人**(原行为不变,向后兼容)。
为何不自动推断原申请人:trans_outbound **没有申请人字段,也没有指回原审批单
的关联**(扫码出库时只把审批单状态置为 3),按 consumer_name 反查会重蹈
「重名错绑」的覆辙(借用人姓名回填那轮刚踩过)。故把选择权交给现场,不猜。
二、抽出中性人员名单 GET /api/v1/common/active-users
实现抽到 common.active_user_options(),借库的 /transactions/borrow/users
改为调同一函数 —— 实现只有一份,但出库补发走**中性路径**,不再出现
「出库为什么在调借库的接口」这种跨模块语义错位。
仅要求登录、只返回 id 与姓名(与 /auth/users/approvers 同一处理)。
★ 本次无需 DB 迁移:未新增任何列,补发申请人是复用已有的
outbound_approval.applicant_id。
验证(打桩/真实 token 直连接口,12 项断言全通过)
· 名单只含 id/name,无邮箱/角色/部门;借库原路径返回值与新路径完全一致
· 指定「补发给谁」→ 补发单申请人 = 指定的人;备注仍含原领用人
· 不指定 → 回退为当前操作人
★ 指定不存在的用户 → 被拒,且整笔退回回滚(流水未落库)
库存与数据零残留。
This commit is contained in:
@ -5,3 +5,4 @@ common_bp = Blueprint('common', __name__)
|
||||
|
||||
# 导入子模块,使其路由装饰器注册到 common_bp
|
||||
from . import search
|
||||
from . import users # noqa: F401 「选择某人」类下拉框的共用人员名单
|
||||
|
||||
46
inventory-backend/app/api/v1/common/users.py
Normal file
46
inventory-backend/app/api/v1/common/users.py
Normal file
@ -0,0 +1,46 @@
|
||||
# inventory-backend/app/api/v1/common/users.py
|
||||
from flask import jsonify
|
||||
from flask_jwt_extended import jwt_required
|
||||
|
||||
from . import common_bp
|
||||
|
||||
|
||||
def active_user_options():
|
||||
"""
|
||||
在职人员名单(id + 姓名)——「选择某人」类下拉框的**共用实现**。
|
||||
|
||||
★ 为什么单独开一条中性路径,而不是复用 /transactions/borrow/users:
|
||||
那条路径在语义上属于借库模块,出库补发、报废执行等处若直接复用,
|
||||
后人读代码时会困惑「出库为什么在调借库的接口」。这里提供统一入口,
|
||||
借库那条路径改为调本函数,实现只有一份。
|
||||
|
||||
★ 公司隔离与业务台账同口径(get_current_company_filter):
|
||||
否则 A 公司的人能在选择器里看到 B 公司人员。
|
||||
|
||||
★ 只返回 id 与姓名:不含邮箱 / 角色 / 部门,最小披露。
|
||||
"""
|
||||
from app.utils.decorators import get_current_company_filter
|
||||
from app.models.system import SysUser
|
||||
from app.services.trans_service import user_display_name
|
||||
|
||||
company_limit = get_current_company_filter()
|
||||
query = SysUser.query.filter(SysUser.status == 'active')
|
||||
if company_limit is not None:
|
||||
# 与 borrow_service.get_request_list 一致:SysUser.department 即公司维度
|
||||
query = query.filter(SysUser.department == company_limit)
|
||||
|
||||
return [{'id': u.id, 'name': user_display_name(u)}
|
||||
for u in query.order_by(SysUser.username).all()]
|
||||
|
||||
|
||||
@common_bp.route('/active-users', methods=['GET'])
|
||||
@jwt_required()
|
||||
def get_active_users():
|
||||
"""
|
||||
在职人员名单,供借出 / 转交 / 归还 / 出库补发等多个页面的选择器共用。
|
||||
|
||||
★ 无 permission_required,仅要求登录:
|
||||
同一份名单要被多个页面共用,绑定其中任一权限码都会让其他页面 403;
|
||||
且只暴露 id 与姓名(与 /auth/users/approvers 同一处理方式)。
|
||||
"""
|
||||
return jsonify({'code': 200, 'msg': 'success', 'data': active_user_options()})
|
||||
@ -2768,7 +2768,8 @@ def return_from_outbound():
|
||||
"is_defective": false, # 必填,true=不良品退回,false=良品退回
|
||||
"reason": "错领退回", # 可选
|
||||
"need_reissue": true, # 可选,退回后是否自动生成补发单
|
||||
"reissue_qty": 2 # 可选,补发数量,默认 = return_qty
|
||||
"reissue_qty": 2, # 可选,补发数量,默认 = return_qty
|
||||
"reissue_applicant_id": 12 # 可选,补发给谁;不传则=当前操作人
|
||||
}
|
||||
|
||||
两条分支的差异:
|
||||
@ -2790,6 +2791,8 @@ def return_from_outbound():
|
||||
# 补发(可选):退回后申请人往往仍需这件东西。勾选则自动生成一张免审批出库单。
|
||||
need_reissue = bool(data.get('need_reissue'))
|
||||
reissue_qty = data.get('reissue_qty')
|
||||
# 补发给谁:不传则回退为当前操作人(见下方补发块)
|
||||
reissue_applicant_id = data.get('reissue_applicant_id')
|
||||
|
||||
# ---- 1. 入参校验(脏值一律挡在入口)----
|
||||
if not outbound_id:
|
||||
@ -2940,17 +2943,27 @@ def return_from_outbound():
|
||||
strict=True,
|
||||
)
|
||||
|
||||
# ★ 申请人取**当前操作人**(办理退回的库管)。
|
||||
# 为什么不是「原申请人」:trans_outbound **没有申请人字段,也没有
|
||||
# 指回原审批单的关联**(扫码出库时只把审批单状态置为 3),因此
|
||||
# 无法可靠判定原申请人是谁 —— 按 consumer_name 反查会重蹈「重名
|
||||
# 错绑」的覆辙(借用人姓名回填那轮刚踩过)。
|
||||
# 原领用人写入备注,便于人工追溯。
|
||||
_applicant = get_jwt_identity()
|
||||
try:
|
||||
_applicant = int(_applicant)
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError('无法确定补发单申请人:当前登录用户缺失')
|
||||
# ★ 申请人(补发给谁)优先级:
|
||||
# ① 前端显式指定 reissue_applicant_id —— 现场最清楚该给谁;
|
||||
# ② 未指定则回退到**当前操作人**(办理退回的库管)。
|
||||
# 为什么不自动推断成「原申请人」:trans_outbound **没有申请人字段,
|
||||
# 也没有指回原审批单的关联**(扫码出库时只把审批单状态置为 3),
|
||||
# 按 consumer_name 反查会重蹈「重名错绑」的覆辙(借用人姓名回填
|
||||
# 那轮刚踩过)。故把选择权交给现场,而不是猜。
|
||||
if reissue_applicant_id:
|
||||
try:
|
||||
_applicant = int(reissue_applicant_id)
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError('补发申请人ID格式无效')
|
||||
from app.models.system import SysUser
|
||||
if not SysUser.query.get(_applicant):
|
||||
raise ValueError(f'补发申请人不存在(ID:{reissue_applicant_id})')
|
||||
else:
|
||||
_applicant = get_jwt_identity()
|
||||
try:
|
||||
_applicant = int(_applicant)
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError('无法确定补发单申请人:当前登录用户缺失')
|
||||
|
||||
reissue = OutboundApproval(
|
||||
request_no=OutboundApprovalService.generate_request_no(),
|
||||
|
||||
@ -596,22 +596,13 @@ def get_borrow_user_options():
|
||||
★ 公司隔离与借用台账同口径(get_current_company_filter):
|
||||
否则 A 公司库管能在选择器里看到 B 公司人员,虽转交时会被 company
|
||||
校验二次拦截,但名单本身已属越权披露。
|
||||
|
||||
★ 实现已抽到 common.active_user_options(),与 /common/active-users 共用同一份
|
||||
逻辑 —— 出库补发等场景走那条中性路径,避免跨模块引用借库接口。
|
||||
"""
|
||||
from app.utils.decorators import get_current_company_filter
|
||||
from app.models.system import SysUser
|
||||
from app.api.v1.common.users import active_user_options
|
||||
|
||||
company_limit = get_current_company_filter()
|
||||
query = SysUser.query.filter(SysUser.status == 'active')
|
||||
if company_limit is not None:
|
||||
# 与 borrow_service.get_request_list 一致:SysUser.department 即公司维度
|
||||
query = query.filter(SysUser.department == company_limit)
|
||||
|
||||
users = query.order_by(SysUser.username).all()
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
'msg': 'success',
|
||||
'data': [{'id': u.id, 'name': user_display_name(u)} for u in users],
|
||||
})
|
||||
return jsonify({'code': 200, 'msg': 'success', 'data': active_user_options()})
|
||||
|
||||
|
||||
# --- 发起借库转交(双向握手第一步)---
|
||||
|
||||
Reference in New Issue
Block a user