diff --git a/inventory-backend/app/api/v1/inbound/stock.py b/inventory-backend/app/api/v1/inbound/stock.py index ad82520..fa8580c 100644 --- a/inventory-backend/app/api/v1/inbound/stock.py +++ b/inventory-backend/app/api/v1/inbound/stock.py @@ -50,6 +50,52 @@ def _normalize_user_id(user_id=None): return 'unknown' +def _resolve_user_name(user_id): + """把 user_id 解析为真实姓名,解析不出来就原样返回。 + + 需要兼容三种来源: + 1. "孙霞(sunxia)" —— 草稿/流水里实际存的格式,来自 JWT 的 display_name + 2. "张三/zhangsan01" —— SysUser.username 的存储格式 + 3. 纯数字用户ID —— 直接按主键查 + """ + if not user_id: + return '-' + raw = str(user_id).strip() + + # 1. "姓名(账号)" / "姓名/账号" —— 分隔符前就是姓名,无需查库 + if '(' in raw and raw.endswith(')'): + name = raw.split('(', 1)[0].strip() + if name: + return name + if '/' in raw: + name = raw.split('/', 1)[0].strip() + if name: + return name + + if not SysUser: + return raw + try: + user = None + # 尝试通过ID或用户名查找 + if raw.isdigit(): + user = SysUser.query.get(int(raw)) + if not user: + user = SysUser.query.filter(SysUser.username.like(f"%/{raw}")).first() + # 注意:此处不做 filter_by(username=...) 兜底, + # 避免 PostgreSQL 把 user_id 数字与 username 字符串列做类型比较导致报错 + + if not user: + return raw + + # 2. 解析 username 格式: "张三/zhangsan01" -> 取前面的真实姓名 + raw_username = getattr(user, 'username', None) or raw + if '/' in raw_username: + return raw_username.split('/')[0] + return raw_username + except Exception: + return raw + + def get_stock_model(source_table): """根据source_table获取对应的库存模型""" if source_table == 'stock_buy': @@ -790,7 +836,13 @@ def get_active_session(): 「活跃」的定义:该公司所有草稿中 scan_time 最大的那个 session_id。 注意 /draft/list 是按 SKU 排序的,拿不到「最近会话」,故单列此接口。 - 返回: { session_id: str|null, total: int, last_scan_time: str|null } + 返回: { + session_id: str|null, + total: int, # 该会话的草稿行数 + scanned: int, # 实际扫到的件数(按物料去重,排除系统自动生成的漏盘记录) + last_scan_time: str|null, + initiator: str|null, # 发起人显示名(该会话最早一条扫码记录的操作人) + } """ company_name = get_current_company_filter() @@ -811,15 +863,45 @@ def get_active_session(): if not row or not row.session_id: return jsonify({ 'code': 200, - 'data': {'session_id': None, 'total': 0, 'last_scan_time': None} + 'data': { + 'session_id': None, 'total': 0, 'scanned': 0, + 'last_scan_time': None, 'initiator': None + } }), 200 + session_id = row.session_id + + # 该会话的明细统计 —— 必须同样带上公司条件,避免跨公司串号 + def _scoped(q): + if company_name is not None: + q = q.filter(StocktakeDraft.company_name == company_name) + return q.filter(StocktakeDraft.session_id == session_id) + + # 已扫件数:按 (source_table, stock_id) 去重。 + # 排除 user_id == 'system' 的记录 —— 那是「结束盘点」时 generate-missing + # 自动生成的漏盘(实盘=0),不是人扫到的,计入会让进度虚高。 + scanned = _scoped( + db.session.query(StocktakeDraft.source_table, StocktakeDraft.stock_id) + .filter(StocktakeDraft.user_id != 'system') + ).distinct().count() + + # 发起人:该会话最早一条非系统扫码记录的操作人。 + # 注:/draft/add 更新已有记录时会把 user_id 改写成「最后操作者」, + # 所以这里取到的是最早那条记录的最后操作人,是当前模型下最接近发起人的信号。 + first_row = _scoped( + db.session.query(StocktakeDraft.user_id) + .filter(StocktakeDraft.user_id != 'system') + ).order_by(StocktakeDraft.scan_time.asc()).first() + initiator = _resolve_user_name(first_row[0]) if first_row and first_row[0] else None + return jsonify({ 'code': 200, 'data': { - 'session_id': row.session_id, + 'session_id': session_id, 'total': int(row.total or 0), - 'last_scan_time': row.last_scan_time.strftime('%Y-%m-%d %H:%M:%S') if row.last_scan_time else None + 'scanned': int(scanned or 0), + 'last_scan_time': row.last_scan_time.strftime('%Y-%m-%d %H:%M:%S') if row.last_scan_time else None, + 'initiator': initiator } }), 200 except Exception as e: @@ -1165,31 +1247,7 @@ def export_stocktake(): } def get_user_name(user_id): - """获取用户真实姓名 - SysUser.username 存储格式为 "真实姓名/登录账号" (例如: 张三/zhangsan01) - """ - if not SysUser or not user_id: - return str(user_id) if user_id else '-' - try: - user = None - # 尝试通过ID或用户名查找 - if str(user_id).isdigit(): - user = SysUser.query.get(int(user_id)) - if not user: - user = SysUser.query.filter(SysUser.username.like(f"%/{user_id}")).first() - # 注意:此处不再 fallback filter_by(username=...), - # 避免 PostgreSQL 将 user_id 数字与 username 字符串列做类型比较导致报错 - - if not user: - return str(user_id) - - # 解析 username 格式: "张三/zhangsan01" -> 取前面的真实姓名 - raw_username = getattr(user, 'username', None) or str(user_id) - if '/' in raw_username: - return raw_username.split('/')[0] - return raw_username - except: - return str(user_id) + return _resolve_user_name(user_id) def to_beijing_time(dt): """直接使用数据库中存储的标准时间(服务器时区已正确)"""