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>
M2 organization scrapers for yunke-server:
- scrape_departments: POST /usercenter/dept/tree. Single request (no
pagination). Defensively unwraps the envelope (data is node list OR an
object wrapping nodes under children), flattens BOTH nested and flat
shapes, and reconstructs the materialized path by walking the pId chain
(ancestor names joined with /). Maps id/pId/name -> dept_id/parent_id/name.
- scrape_wechat_accounts: POST /wechatweb/wxUser/list. Page-number
pagination via the existing paginate_by_page helper. Envelope differs
from friend_list (no basicResult wrapper): data.data/data.total. Maps
wechatId/nickName/alias/phone/userId/userName/contactsNum/groupNum/
sendNum/receiveNum/isOnJob/dbSize/onlineStatus/lastUpdate/version to
WechatAccount columns; carries embedded staff pair for the repo layer.
Repos:
- upsert_department: idempotent upsert into Department (PK dept_id).
- upsert_staff: primitive idempotent staff upsert (PK user_id, is_on_job
coerced to bool).
- upsert_wechat_account: now ALSO upserts the embedded Staff row via
upsert_staff_from_account (best-effort; skips when account has no user_id).
- upsert_staff_from_account: refactored to delegate to upsert_staff and
forward phone/sale_phone/is_on_job when present.
Tests (TDD red->green): test_scrape_departments (nested + flat + wrapped-data
+ empty), test_scrape_wechat_accounts (2-page pagination + body advance +
empty first page), test_repos extended (dept idempotency/root parent, direct
staff upsert + bool coercion, account-also-upserts-staff, re-upsert updates
both, no-user_id skips staff). Cassettes: dept_tree_fixture (same 4 nodes in
both nested + flat shapes), wxUser_fixture (4 capture-shaped accounts incl.
departed + orphan).
Suite: 97 -> 111 passing. ruff check clean.
Co-Authored-By: Claude <noreply@anthropic.com>