支持企微通讯录缺失手机号(07-26-internal-ops-recovery-baseline)
这个提交包含在:
@@ -168,9 +168,10 @@ WeCom leave(category=调休) -> WeCom approval -> OA minimum read-only history
|
||||
- `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 phone plus personal email or
|
||||
business email. A blank/invalid personal email may fall back to a valid
|
||||
business email.
|
||||
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.
|
||||
@@ -190,8 +191,9 @@ WeCom leave(category=调休) -> WeCom approval -> OA minimum read-only history
|
||||
- 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`.
|
||||
- Missing/invalid phone, both emails invalid, or unresolved department ->
|
||||
`missing_required_field`.
|
||||
- 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`.
|
||||
@@ -232,3 +234,92 @@ 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
|
||||
|
||||
```js
|
||||
phone: source.phone || "00000000000"
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```js
|
||||
phone: source.phone || null
|
||||
// Existing employee updates omit `phone` entirely when the source is empty.
|
||||
```
|
||||
|
||||
在新工单中引用
屏蔽一个用户