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>
This commit is contained in:
@@ -43,9 +43,10 @@ import logging
|
||||
from collections.abc import AsyncIterator, Callable
|
||||
from typing import Any
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db.models import SyncRun
|
||||
from app.db.models import Department, SyncRun
|
||||
from app.scraper.call import scrape_phone_calls, scrape_sms
|
||||
from app.scraper.media import download_message_media
|
||||
from app.scraper.wechat import (
|
||||
@@ -385,16 +386,72 @@ async def sync_departments(
|
||||
)
|
||||
|
||||
|
||||
async def _find_root_department_id(
|
||||
session: AsyncSession,
|
||||
) -> str | None:
|
||||
"""Resolve the company-root department id from the mirrored ``Department`` table.
|
||||
|
||||
The ``wxUser/list`` form body REQUIRES ``departmentId`` (the company-root
|
||||
dept id) — empty fails with ``code -90000``. The root is a node whose
|
||||
``parent_id`` is falsy (``None`` / ``""`` / ``"0"`` — the yunke tree
|
||||
convention). If several roots exist, the first by ``dept_id`` is returned
|
||||
(with a warning) so a multi-root org still scrapes. Returns ``None`` when
|
||||
no root is present so the caller can skip gracefully rather than send a
|
||||
known-bad empty filter.
|
||||
"""
|
||||
stmt = (
|
||||
sa.select(Department)
|
||||
.where(
|
||||
sa.or_(
|
||||
Department.parent_id.is_(None),
|
||||
Department.parent_id == "",
|
||||
Department.parent_id == "0",
|
||||
)
|
||||
)
|
||||
.order_by(Department.dept_id.asc())
|
||||
.limit(1)
|
||||
)
|
||||
row = (await session.execute(stmt)).scalars().first()
|
||||
return row.dept_id if row is not None else None
|
||||
|
||||
|
||||
async def sync_wechat_accounts(
|
||||
session: AsyncSession, client: YunkeClient
|
||||
session: AsyncSession,
|
||||
client: YunkeClient,
|
||||
*,
|
||||
department_id: str | None = None,
|
||||
) -> SyncResult:
|
||||
"""Full re-scrape of all WeChat staff accounts (+ embedded staff rows)."""
|
||||
"""Full re-scrape of all WeChat staff accounts (+ embedded staff rows).
|
||||
|
||||
``department_id`` is the company-root dept id required by the
|
||||
``wxUser/list`` form body. When omitted, the orchestrator resolves it from
|
||||
the mirrored ``Department`` table (a root node whose ``parent_id`` is
|
||||
falsy) — which is why :func:`sync_departments` must run first. If no root
|
||||
can be resolved, the scrape is SKIPPED with a logged warning (returns a
|
||||
``status='skipped'`` :class:`SyncResult` and records no ``SyncRun``) so a
|
||||
missing org tree does not abort a multi-entity batch.
|
||||
"""
|
||||
resolved = department_id
|
||||
if resolved is None:
|
||||
resolved = await _find_root_department_id(session)
|
||||
if not resolved:
|
||||
logger.warning(
|
||||
"sync_wechat_accounts: no root department_id resolved"
|
||||
" (sync departments first?); skipping wxUser/list scrape"
|
||||
)
|
||||
return SyncResult(
|
||||
entity=ENTITY_WECHAT_ACCOUNT,
|
||||
status="skipped",
|
||||
fetched=0,
|
||||
upserted=0,
|
||||
error="no root department_id available",
|
||||
)
|
||||
return await _sync_full_rescrape(
|
||||
session,
|
||||
entity_type=ENTITY_WECHAT_ACCOUNT,
|
||||
channel=None,
|
||||
scope_id=None,
|
||||
scrape=lambda: scrape_wechat_accounts(client),
|
||||
scrape=lambda: scrape_wechat_accounts(client, department_id=resolved),
|
||||
upsert=upsert_wechat_account,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user