Files
yunke/yunke-server/tests/cassettes/wxUser_fixture.py
T
浅念 0df53c6b1a fix: wechat account scrape (form-data+dates) + frontend staff page
Backend (scrape_wechat_accounts):
- Send wxUser/list as form-urlencoded (not JSON) with the REQUIRED filters
  isOnJob / departmentId / startdate / enddate + empty userId/salesname/
  weChatNickName, matching the live request (empty fails with code -90000).
- scrape_wechat_accounts now takes department_id (the company-root dept id)
  plus optional start/end defaulting to a recent 7-day UTC window.
- paginate_by_page gains a form=True mode (data= + form content-type).
- Orchestrator sync_wechat_accounts resolves the root dept from the
  Department table (or accepts one) and skips with a warning when unknown.
- seed_accounts.py resolves the root via scrape_departments.

Frontend (yunkes):
- Add StaffList.vue (/staff) with is_on_job filter + pagination.
- Add /staff route and a 员工 nav item between 客户 and 群.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 17:11:17 +08:00

155 lines
5.2 KiB
Python

"""Fixture payloads for ``POST /wechatweb/wxUser/list`` (Task 2.1).
These records mirror the real field names captured from the live CRM (see the
data dictionary §2: ``wechatId, nickName, alias, phone, userId,
userName(员工真名), contactsNum, groupNum, sendNum/receiveNum, isOnJob,
dbSize, 在线状态, lastUpdate``; total=55).
The REQUEST is **form-urlencoded** (NOT JSON): the endpoint binds Spring
``@RequestParam`` from the form body and REQUIRES ``isOnJob`` /
``departmentId`` (the company-root dept id) / ``startdate`` / ``enddate`` or
it answers ``code -90000`` ("缺少..."). ``userId`` / ``salesname`` /
``weChatNickName`` are sent empty to mirror the real client.
The RESPONSE envelope differs from ``wxFriends/friend_list/es``: there is
**no** ``basicResult`` wrapper. The pagination fields sit directly under
``data``::
{"code":"10000","data":{"data":[...], "total":.., "pageNum":.., "pageSize":..}}
The cassette ships two pages so a test can drive the page-number paginator
across the empty-page stop signal:
* page 1 -> 2 of the 4 fixture records, ``total=4``
* page 2 -> the other 2 records, ``total=4``
* (page 3 -> empty ``data`` array, served inline by the test)
Records carry every field with a target ``WechatAccount`` column so the test
can assert a full mapping end-to-end, plus the embedded ``userId``+``userName``
staff pair (the owning employee) so the embedded-staff repo upsert can be
exercised against capture-shaped input.
"""
# The module name uses camelCase deliberately to mirror the upstream endpoint
# name (``wxUser``); the Task 2.1 spec mandates this exact filename.
# ruff: noqa: N999
from __future__ import annotations
from typing import Any
# ---------------------------------------------------------------------------
# Capture-shaped WeChat staff-account records (4 total across 2 pages).
# ---------------------------------------------------------------------------
WXUSER_RECORDS: list[dict[str, Any]] = [
{
"wechatId": "wxid_staff_alice01",
"nickName": "Alice WeChat",
"alias": "alice_wx",
"phone": "13800000001",
"userId": "staff_user_001",
"userName": "Alice Wang",
"contactsNum": 312,
"groupNum": 18,
"sendNum": 1204,
"receiveNum": 2387,
"isOnJob": 1,
"dbSize": 482103,
"onlineStatus": "online",
"headImageUrl": "https://wx.qlogo.cn/avatar_alice.png",
"lastUpdate": 1700000000123,
"version": "v1.20260731.alice",
},
{
"wechatId": "wxid_staff_bob02",
"nickName": "Bob WeChat",
"alias": "bob_wx",
"phone": "13800000002",
"userId": "staff_user_002",
"userName": "Bob Chen",
"contactsNum": 87,
"groupNum": 4,
"sendNum": 211,
"receiveNum": 402,
"isOnJob": 1,
"dbSize": 91205,
"onlineStatus": "offline",
"headImageUrl": "https://wx.qlogo.cn/avatar_bob.png",
"lastUpdate": 1700000000456,
"version": "v1.20260731.bob",
},
{
"wechatId": "wxid_staff_carol03",
"nickName": "Carol We Chat",
"alias": "carol_wx",
"phone": "13800000003",
"userId": "staff_user_003",
"userName": "Carol Liu",
"contactsNum": 1502,
"groupNum": 72,
"sendNum": 9034,
"receiveNum": 11245,
"isOnJob": 0, # departed staff — exercises the falsy isOnJob path
"dbSize": 2_100_445,
"onlineStatus": "offline",
"headImageUrl": "https://wx.qlogo.cn/avatar_carol.png",
"lastUpdate": 1700000000789,
"version": "v1.20260731.carol",
},
{
# Account with NO embedded staff (userId missing) — the scraper must
# still yield it; the embedded-staff repo upsert must skip it cleanly.
"wechatId": "wxid_staff_orphan04",
"nickName": "Orphan WeChat",
"alias": "orphan_wx",
"phone": "13800000004",
"userId": None,
"userName": None,
"contactsNum": 5,
"groupNum": 0,
"sendNum": 1,
"receiveNum": 2,
"isOnJob": 0,
"dbSize": 1024,
"onlineStatus": "offline",
"headImageUrl": "https://wx.qlogo.cn/avatar_orphan.png",
"lastUpdate": 1700000001000,
"version": "v1.20260731.orphan",
},
]
# Default page size for the fixture (and the scraper). total=4 means the
# fixture fills exactly 2 pages of size 2 plus a terminating empty page.
TOTAL = len(WXUSER_RECORDS)
DEFAULT_PAGE_SIZE = 2
def make_wxuser_list_response(
records: list[dict[str, Any]],
*,
total: int,
page_num: int = 1,
page_size: int = DEFAULT_PAGE_SIZE,
trace_id: str | None = None,
) -> dict[str, Any]:
"""Build a capture-shaped ``wxUser/list`` response envelope.
Shape (no ``basicResult`` wrapper)::
{"message":"调用成功","code":"10000","success":true,"traceId":"...",
"data":{"data":[...], "total":.., "pageNum":.., "pageSize":..}}
"""
return {
"message": "调用成功",
"code": "10000",
"success": True,
"traceId": trace_id or f"trace-{page_num}",
"data": {
"data": list(records),
"total": total,
"pageNum": page_num,
"pageSize": page_size,
},
}