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 条不匹配的领用人**未改动**(其中「常国玺」疑似离职人员)。
  · 前端只做了编译验证,未在浏览器实际点过 —— 下拉交互与提示样式需人工确认。
This commit is contained in:
yueli
2026-09-28 15:18:43 +08:00
parent d12435a801
commit 9c1a9886a5
2 changed files with 129 additions and 5 deletions

View File

@ -7,7 +7,7 @@ from . import common_bp
def active_user_options():
"""
在职人员名单(id + 姓名)——「选择某人」类下拉框的**共用实现**。
在职人员名单(id + 姓名 + 账号)——「选择某人」类下拉框的**共用实现**。
★ 为什么单独开一条中性路径,而不是复用 /transactions/borrow/users:
那条路径在语义上属于借库模块,出库补发、报废执行等处若直接复用,
@ -17,7 +17,13 @@ def active_user_options():
★ 公司隔离与业务台账同口径(get_current_company_filter):
否则 A 公司的人能在选择器里看到 B 公司人员。
★ 只返回 id 与姓名:不含邮箱 / 角色 / 部门,最小披露。
★ 只返回 id / 姓名 / 账号:不含邮箱、角色、部门 —— 仍是最小披露。
账号(username 里 '/' 之后那一段,如 'gaoxue')是**拼音**,
选择器靠它支持「输拼音找人」(输 gaoxue 匹配到高雪)——
这是加它的唯一原因,不是要把登录名铺开。
★ 姓名口径用 user_display_name('高雪/gaoxue' → '高雪'),
与出库/借库台账里存姓名的写法一致;否则同一人在台账里会出现两种写法。
"""
from app.utils.decorators import get_current_company_filter
from app.models.system import SysUser
@ -29,8 +35,15 @@ def active_user_options():
# 与 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()]
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'])