diff --git a/inventory-backend/app/api/v1/common/__init__.py b/inventory-backend/app/api/v1/common/__init__.py index 408784d..a77ac55 100644 --- a/inventory-backend/app/api/v1/common/__init__.py +++ b/inventory-backend/app/api/v1/common/__init__.py @@ -5,3 +5,4 @@ common_bp = Blueprint('common', __name__) # 导入子模块,使其路由装饰器注册到 common_bp from . import search +from . import users # noqa: F401 「选择某人」类下拉框的共用人员名单 diff --git a/inventory-backend/app/api/v1/common/users.py b/inventory-backend/app/api/v1/common/users.py new file mode 100644 index 0000000..79dea76 --- /dev/null +++ b/inventory-backend/app/api/v1/common/users.py @@ -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()}) diff --git a/inventory-backend/app/api/v1/inbound/stock.py b/inventory-backend/app/api/v1/inbound/stock.py index b516717..d32412d 100644 --- a/inventory-backend/app/api/v1/inbound/stock.py +++ b/inventory-backend/app/api/v1/inbound/stock.py @@ -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(), diff --git a/inventory-backend/app/api/v1/transactions.py b/inventory-backend/app/api/v1/transactions.py index c6b8d53..e1b26c5 100644 --- a/inventory-backend/app/api/v1/transactions.py +++ b/inventory-backend/app/api/v1/transactions.py @@ -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()}) # --- 发起借库转交(双向握手第一步)---