文件
hyb-oa/.trellis/spec/hyb-oa/wecom-boundary.md
T

15 KiB

Enterprise WeChat Boundary

Ownership

Use an Enterprise-WeChat-first, OA-complements model for this single-company, 5–10-person internal system:

  • Enterprise WeChat owns mature commodity capabilities: workbench entry, OAuth/SSO, directory and basic organization, application messages, HR/attendance actions and low-risk, simple-field, mobile-first approvals.
  • General administration and simple procurement with no downstream financial handling, sensitive attachment or cross-business ledger may use Enterprise WeChat for submission and approval after a dedicated task verifies the real template and approval chain. OA retains only a minimum reference when needed.
  • OA owns the complete reimbursement journey: request, invoice upload/PDF/OCR and duplicate detection, approval, finance review, payment status and receipt. Enterprise WeChat provides SSO/workbench entry, dehydrated notifications and exact deeplinks into the relevant OA page; users must not re-enter or chase the same reimbursement across both systems.
  • OA also owns financial ledgers, payment/AP and maker-checker, contract bodies, sensitive attachments, cross-business relationships and long-term audit.

This boundary is a selection rule, not evidence that any existing procurement or administration workflow has already migrated.

Rules

  • Enterprise WeChat API checks that need trusted IP must run from the deployment server, not local.
  • Do not write Enterprise WeChat, create templates, create approvals, expand real users, or enable live trials without explicit approval and a Trellis task.
  • Approval-rule configuration cannot be fully read through official APIs; keep manual backend/real-device gates where specified.
  • Messages to Enterprise WeChat must be dehydrated and link back to OA; do not send sensitive attachments or business documents into native WeCom approvals.
  • Prefer a WeCom-native mobile form over duplicating it in OA when the process is simple and low-risk. Do not build full payload mirrors, bidirectional writeback, duplicate native approval UIs or generic approval-rule operations unless a later task proves a concrete business and audit need.
  • For every WeCom approval promoted to formal use, manually verify the backend rule, applicant-visible mobile submission page and actual post-submission approval chain; API template metadata alone is insufficient.

Scenario: Compensatory Leave Is A WeCom Leave Category

1. Scope / Trigger

  • Trigger: adding, displaying, validating or operating compensatory leave (调休) in the WeCom-owned HR/attendance flow.
  • Purpose: support the required leave category without creating a fifth approval integration, a parallel OA submission journey or an unneeded balance engine.

2. Signatures

  • WeCom approval target remains leave; the existing target set remains leave, missed_punch, outing, overtime.
  • WeCom template/form category label: 调休 under the leave template.
  • OA read-only archive fields: normalized leave category, interval, status and minimum approval summary. No new ordinary OA submit/approve API is implied.

3. Contracts

  • WeCom owns employee submission, approval and normal mobile history. OA must distinguish 调休 from other leave categories in read-only history but must not expose a parallel ordinary submission or approval flow.
  • The initial internal-team scope does not build an OA compensatory-leave balance, automatic overtime-to-leave conversion, carry-over engine or payroll calculation. If WeCom native balance management is enabled, it is the source of truth; otherwise HR uses a controlled manual rule until a later task proves automation is worth maintaining.
  • Template metadata is insufficient acceptance evidence. Formal use requires a backend check, applicant-visible real-device form and actual submitted approval chain with no self-approval.

4. Validation & Error Matrix

  • WeCom backend lacks 调休 -> remain not ready; do not emulate it with an OA ordinary form.
  • Real-device form does not show 调休 -> PAUSED_MANUAL_CONFIRMATION.
  • Applicant can select/change the approver or becomes the approver -> blocker; do not enable real-team use.
  • OA archive cannot distinguish the leave category -> keep the data read-only but do not claim compensatory-leave acceptance.
  • WeCom native balance is absent -> use the approved manual HR rule; do not silently create an OA balance ledger.

5. Good/Base/Bad Cases

  • Good: a test user selects 调休 in WeCom, a different real approver handles it, and OA read-only history displays 调休, interval and final status.
  • Base: WeCom manages the request while HR maintains the small-team balance by a controlled manual rule; OA stores no balance.
  • Bad: introduce a compensatory_leave approval target and duplicate submit, balance and approval state across OA and WeCom.

6. Tests Required

  • Unit/fixture coverage proves a WeCom leave detail containing 调休 remains target leave and preserves the category in the minimum archive/display path.
  • Mocked frontend coverage proves ordinary employees see read-only history and no OA ordinary compensatory-leave submit action.
  • Formal-use evidence must include the backend option, real-device form and actual no-self-approval chain; live writes require a separately approved task.

7. Wrong vs Correct

Wrong

调休 -> new compensatory_leave target -> OA balance -> OA approval -> WeCom sync

Correct

WeCom leave(category=调休) -> WeCom approval -> OA minimum read-only history

HR / Attendance UI Contracts

  • 请假记录 is the PC leave entry label and page title. Ordinary employees must see records, balances and a WeCom-owned explanation, not an OA leave creation form.
  • OA leave creation, edit, submit and withdraw controls are emergency-only UI. They require leave.manage and must be labeled with OA 应急 / 应急草稿 / 提交应急审批; do not expose them based only on leave.create.
  • Mobile leave defaults to records for ordinary employees. Hide the form tab and row actions unless the user has leave.manage; keep the first card explicit: 企业微信主办 and 只读.
  • Attendance correction forms are emergency-only UI. They require attendance.manage and use OA 应急修正 / 提交应急修正. Ordinary employees see a WeCom handling explanation for missed punch, outing and overtime.
  • WeCom approval archive pages are read-only history and reconciliation. They may show status/action history labels from WeCom data, but must not offer OA approve/reject/return/transfer actions.

Tests Required

  • Mocked frontend Playwright coverage should assert that ordinary employees do not see OA leave/correction create actions, while admin/HR roles retain explicitly labeled emergency entry points.
  • Full-stack Playwright coverage that writes local data still requires a running local backend/API environment; a Vite-only run is not a valid failure signal for those real-chain tests.

Scenario: One-Time Private-Info OAuth Onboarding

1. Scope / Trigger

  • Trigger: obtaining phone/email for an app-visible member or allowing an unbound active member to create the minimum OA account.
  • This is a one-time onboarding path, not the ordinary WeCom login path and not a replacement for administrator-assigned finance, CEO or admin roles.

2. Signatures

  • Entry: GET /api/auth/wecom/login?mode=onboard&returnTo=<internal-path>.
  • Ordinary entry: the same route with mode=login|bind.
  • Provider chain: snsapi_privateinfo -> /cgi-bin/auth/getuserinfo -> /cgi-bin/auth/getuserdetail with { "user_ticket": "<short-lived>" }.
  • Config: WECOM_OAUTH_ONBOARDING_ENABLED=false by default.
  • Directory chain: WECOM_CONTACT_SECRET only for /user/list_id; WECOM_AGENT_SECRET for /user/get, /department/list and /hr/get_staff_info.

3. Contracts

  • login|bind always keeps snsapi_base; only explicit onboard uses snsapi_privateinfo.
  • The member must be visible to the app, active, return the same userid from both identity calls, and authorize a valid personal email or business email. Phone may be absent; when returned it must be normalized and checked for uniqueness. A blank/invalid personal email may fall back to a valid business email.
  • user_ticket, OAuth code, access token and raw provider responses exist only in request memory. Do not persist them or include them in Cookie, audit, response details or logs; provider/network failures use fixed safe codes.
  • Resolve userid, phone and email conflicts before writing. A member already bound to another employee or an employee bound to another member is rejected.
  • New accounts are created transactionally with employee, exactly the employee role, identity binding and dehydrated audit. Existing active accounts may complete normalized profile data and binding but keep all roles.
  • The feature flag controls the entry and callback provisioning path. Enabling it and changing real app permissions are production configuration operations requiring separate approval.

4. Validation & Error Matrix

  • Flag disabled -> entry is 404; no private scope or account write.
  • Missing user_ticket -> safe consent_required redirect; no write.
  • Provider/network failure -> wecom_api_failed or safe auth error; no raw payload, token or ticket in output/logs.
  • Userid mismatch -> identity_mismatch; inactive member -> member_inactive.
  • Both emails invalid or unresolved department -> missing_required_field.
  • Phone absent -> continue with NULL; phone present but conflicting -> employee_conflict.
  • Multiple employees, unique-field race or cross-binding -> employee_conflict.
  • Department binding points to missing/archived data -> department_invalid; multiple same-name OA departments -> department_ambiguous.

5. Good/Base/Bad Cases

  • Good: an unbound active member explicitly consents, receives an employee-only account and authenticated session, then an administrator separately assigns any business role with existing audit/session-revocation semantics.
  • Base: a bound active employee repeats onboarding to complete normalized profile data; no role is added or removed.
  • Bad: change ordinary login to snsapi_privateinfo, use batch /user/get absence as proof that email is unavailable, or auto-grant finance/ceo.

6. Tests Required

  • Route tests for default-off, scope separation, missing ticket, safe provider failure, successful session and employee-only provisioning.
  • Service tests for active state, userid match, personal-to-business email fallback, invalid fields, multiple matches, cross-binding and existing-role preservation.
  • Assertions must prove ticket, token, raw payload, phone and email are absent from audit/error serialization; run full backend tests and Prisma validation.

7. Wrong vs Correct

Wrong

ordinary login -> snsapi_privateinfo -> batch sync guesses missing fields
-> auto-grant finance/ceo

Correct

ordinary login -> snsapi_base
one-time onboard -> explicit private consent -> safe unique employee-only write
business role assignment -> separate administrator-audited operation

Scenario: WeCom Directory Export With Optional Phone

1. Scope / Trigger

  • Trigger: importing an administrator-exported WeCom directory XLSX, or changing Employee.phone across database, API, HR sync, OAuth and UI layers.
  • Purpose: allow a real member to remain temporarily without a phone while preserving stable identity, uniqueness and privacy controls.

2. Signatures

  • DB: Employee.phone String? @unique; migration drops only the NOT NULL constraint and keeps the unique index.
  • CLI: npm run import:wecom-directory-export -- --input=- --dry-run.
  • Apply: WECOM_DIRECTORY_EXPORT_APPLY=1 ... --input=- --apply --actor-employee-no=<employee-no>.
  • User API and frontend employee/user types expose phone: string | null; create/update accepts an omitted or explicit null phone.

3. Contracts

  • Missing phone is stored as SQL NULL, never "", a repeated placeholder or a synthesized fake number. Multiple NULL rows are valid; non-null phones remain unique.
  • WeCom account and valid enterprise email remain required for directory-export onboarding. Account, non-null phone and enterprise email duplicates block the input.
  • An empty source phone never clears an existing OA phone. Only an explicitly supplied valid phone participates in update and unique-conflict checks.
  • The XLSX is accepted only from stdin and defaults to dry-run. Apply requires both --apply and WECOM_DIRECTORY_EXPORT_APPLY=1, plus an active actor.
  • CLI output contains counts and categories only. The workbook, full local path, names, accounts, phones, emails, departments and raw rows must not enter Git, Trellis artifacts or reports.
  • New accounts receive only employee; finance, CEO and admin roles remain a separate administrator-audited operation.

4. Validation & Error Matrix

  • Missing phone cell -> valid profile with phone=null.
  • Supplied malformed phone -> incomplete/blocked; do not downgrade it to an absent phone and do not write it.
  • Missing/invalid enterprise email, account, name or department -> skipped or blocked as missing_required_field.
  • Duplicate account/non-null phone/enterprise email in the workbook -> duplicate_values; error output contains counts only.
  • Existing OA unique-field or binding conflict -> conflict; no automatic merge.
  • --apply without confirmation env or active actor -> refuse before sync.
  • Migration rollback with any phone IS NULL row -> stop; do not fill fake values merely to restore NOT NULL.

5. Good/Base/Bad Cases

  • Good: five members have unique phones, one member has no phone, all six have unique accounts and enterprise emails; dry-run plans six records and reports only aggregate counts.
  • Base: a later export still omits a member phone; the member's existing OA phone is preserved.
  • Bad: write an empty string or shared placeholder, use personal email in place of the required enterprise email for bulk identity, or auto-grant a business role during import.

6. Tests Required

  • Prisma validation plus a migration review proving only DROP NOT NULL.
  • Unit tests for instruction rows/Chinese headers, five-present/one-missing, malformed phone, missing enterprise email and duplicate account/phone/email.
  • Route/service tests for create/update/import null, non-empty unique checks, OAuth missing phone and empty-source preservation of an existing phone.
  • Frontend typecheck/build and displays that render a clear empty-phone label.
  • Full backend regression, Trellis governance, sensitive-content scan and git diff --check; a real workbook may be parsed only into aggregate counts and remains Git-ignored.

7. Wrong vs Correct

Wrong

phone: source.phone || "00000000000"

Correct

phone: source.phone || null
// Existing employee updates omit `phone` entirely when the source is empty.