Files
KCGL/inventory-backend/app/api/v1/common/users.py
yueli 9c1a9886a5 feat(outbound): 领用人改为可搜索下拉,选中申请单自动带出申请人
背景:出库的「领用人/客户」一直是自由文本框(create.vue 的 el-input),
后端 consumer_name 必填但内容不限。实测 1497 条出库里有 7 条领用人对不上
任何在职人员,其中就有把姓名打成「刘」这种错字 —— 手输必然产生这类问题。
而同一个表单里紧挨着的「经办人(库管)」已经是下拉选择了。

改动:

前端 outbound/create.vue
  · 领用人由 el-input 改为 el-select(filterable + allow-create +
    default-first-option),数据源复用统一的在职人员接口 getActiveUsers()
    (与出库补发的「补发给谁」同一份,公司隔离由后端负责)。
  · 选中审批单时自动带出申请人(fillConsumerFromRequest),仍可改成别人。
    ★ 按 applicant_id 在在职名单里**反查**姓名,而不是直接用接口返回的
      applicant_name —— 后者是 '高雪/gaoxue'(后端 _get_user_name 返回完整
      username),直接落库会把台账的姓名口径搞乱(实测 1497 行无一带 '/')。
  · 动态模糊匹配:姓名与**拼音**都能搜。默认 filterable 只匹配 label(姓名),
    输 gaoxue 匹配不到高雪,故自定义 filter-method 一并比对账号段。
  · 软提示而非硬拦截:填了不在名单里的名字会显示
    「「X」不在在职人员名单中,请确认没写错」。保留 allow-create 是因为
    销售出库的客户可能是外部人员、没有账号,封死会让这类出库没法登记。

后端 common/users.py
  · active_user_options() 增加 account 字段(username 里 '/' 之后那段拼音),
    供前端拼音搜索。仍是 id/姓名/账号三项,不含邮箱、角色、部门。

★ 没有做后端强校验(拒绝非系统人员):用户明确选择「下拉为主,保留手输兜底」,
  硬拦会挡住外部客户。错字的防线是"默认从名单里选 + 不在名单时给提示"。

验证(12 + 6 项断言全过):
  · 39 个在职人员,每项含 id/name/account;name 是裸姓名、account 非空
  · 拼音搜索:输 gaoxue / GAOXUE → 高雪;输 gao → 高前赐/高闯/高雪;输 高雪 → 高雪
  · 在职人员无重名(按姓名绑定才无歧义)
  · 已通过的审批单能反查到在职申请人(单 514/513/512/511/509 全部命中)
  · 公司隔离:IRIS 用户 20 人只看到「石利」、LICA 用户 19 人只看到
    「石利LICA」、超管 39 人 —— 跨公司同名在各自视角下不会同时出现
  · 借库人员选择器(共用同一实现)仍正常,新增字段向后兼容
  · 日报三天附件 490.5K/193.4K/92.3K 与 6 列结构未变;出库列表接口未受影响
  · vue-tsc 与 vite build exit=0

注意:
  · 历史 7 条不匹配的领用人**未改动**(其中「常国玺」疑似离职人员)。
  · 前端只做了编译验证,未在浏览器实际点过 —— 下拉交互与提示样式需人工确认。
2026-09-28 15:18:43 +08:00

60 lines
2.6 KiB
Python
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.

# 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 / 姓名 / 账号:不含邮箱、角色、部门 —— 仍是最小披露。
账号(username 里 '/' 之后那一段,如 'gaoxue')是**拼音**,
选择器靠它支持「输拼音找人」(输 gaoxue 匹配到高雪)——
这是加它的唯一原因,不是要把登录名铺开。
★ 姓名口径用 user_display_name('高雪/gaoxue' → '高雪'),
与出库/借库台账里存姓名的写法一致;否则同一人在台账里会出现两种写法。
"""
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)
out = []
for u in query.order_by(SysUser.username).all():
raw = str(u.username or '')
out.append({
'id': u.id,
'name': user_display_name(u),
'account': raw.split('/')[-1] if '/' in raw else '',
})
return out
@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()})