"""Canonical states, events, transition guards, and Feishu enums for the intern attendance monthly-report domain. Single source of truth so the service layer, the lifecycle state machine (PR-B), the frontend status colours, and the tests all agree on the vocabulary. Keep this module dependency-free (no DB / no app imports) so it can be imported from anywhere, including migrations and tests. """ from __future__ import annotations # ── Report states ─────────────────────────────────────────────────── STATUS_GENERATED = "generated" # pulled, not yet sent to intern STATUS_PENDING_INTERN = "pending_intern" # sent, awaiting intern confirm STATUS_PENDING_LEADER = "pending_leader" # intern confirmed, awaiting leader STATUS_RETURNED = "returned" # returned to HR (by intern or leader) STATUS_COMPLETED = "completed" # leader confirmed — closed ALL_STATUSES: frozenset[str] = frozenset({ STATUS_GENERATED, STATUS_PENDING_INTERN, STATUS_PENDING_LEADER, STATUS_RETURNED, STATUS_COMPLETED, }) # Statuses a (re)generation may safely refresh in place. In-flight / # confirmed reports are never clobbered by a cycle regeneration — HR # must explicitly resend (which bumps version), so a confirmation always # pins a concrete snapshot version. REGENERATABLE_STATUSES: frozenset[str] = frozenset({ STATUS_GENERATED, STATUS_RETURNED, }) # Terminal — no further transitions without an explicit undo. TERMINAL_STATUSES: frozenset[str] = frozenset({STATUS_COMPLETED}) # ── Event types (audit trail) ─────────────────────────────────────── EVENT_GENERATED = "generated" EVENT_REGENERATED = "regenerated" EVENT_SENT_TO_INTERN = "sent_to_intern" EVENT_INTERN_CONFIRMED = "intern_confirmed" EVENT_INTERN_RETURNED = "intern_returned" EVENT_SENT_TO_LEADER = "sent_to_leader" EVENT_LEADER_UNAVAILABLE = "leader_unavailable" EVENT_LEADER_CONFIRMED = "leader_confirmed" EVENT_LEADER_RETURNED = "leader_returned" EVENT_INTERN_UNDO = "intern_undo" EVENT_RESENT = "resent" EVENT_REMINDED = "reminded" EVENT_WITHDRAWN = "withdrawn" # HR pulled a sent report back to generated EVENT_REINITIATED = "reinitiated" # HR re-opened a completed report for a fresh round # ── Actor side (who returned) ─────────────────────────────────────── SIDE_INTERN = "intern" SIDE_LEADER = "leader" # ── Feishu attendance result enums (verbatim from the open-platform # user_tasks/query spec). Used by facts.py to classify each day. ─── RESULT_NORMAL = "Normal" RESULT_EARLY = "Early" RESULT_LATE = "Late" RESULT_LACK = "Lack" # 缺卡 — no punch recorded RESULT_NO_NEED = "NoNeedCheck" # not required to punch this segment RESULT_SYSTEM = "SystemCheck" # auto-resolved by system RESULT_TODO = "Todo" # segment not yet due # Supplement explains *why* a non-Normal result is acceptable. The # card-replacement values mean a 缺卡 was (or is being) fixed via 补卡. SUPPLEMENT_NONE = "None" SUPPLEMENT_MANAGER = "ManagerModification" SUPPLEMENT_CARD = "CardReplacement" # 补卡完成 SUPPLEMENT_CARD_APPLICATION = "CardReplacementApplication" # 补卡申请中 SUPPLEMENT_SHIFT = "ShiftChange" SUPPLEMENT_TRAVEL = "Travel" SUPPLEMENT_LEAVE = "Leave" SUPPLEMENT_GOOUT = "GoOut" SUPPLEMENT_FIELD = "FieldPunch" # A result of these values is fine and needs no attention. BENIGN_RESULTS: frozenset[str] = frozenset({ RESULT_NORMAL, RESULT_NO_NEED, RESULT_SYSTEM, RESULT_TODO, }) # Supplements that mean an otherwise-abnormal segment has been resolved # (or is pending resolution) by an approved correction — not a problem # the intern still needs to act on, EXCEPT a pending 补卡 application, # which we surface as "补卡申请中" so HR/intern know it isn't final yet. RESOLVED_SUPPLEMENTS: frozenset[str] = frozenset({ SUPPLEMENT_MANAGER, SUPPLEMENT_CARD, SUPPLEMENT_SHIFT, SUPPLEMENT_TRAVEL, SUPPLEMENT_LEAVE, SUPPLEMENT_GOOUT, SUPPLEMENT_FIELD, }) # ── Action-token (one-click, no-login) ───────────────────────────── # Capability JWT purpose for the intern's one-click "confirm" button in # the Feishu card. The token binds (subject=person_id, resource=report_id) # so it can only confirm that one intern's one report. ACTION_TOKEN_PURPOSE_CONFIRM = "attendance_confirm" # ── Platform-setting keys ─────────────────────────────────────────── SETTING_INTERN_DEPARTMENT = "attendance.intern_department" SETTING_CYCLE_START_DAY = "attendance.cycle_start_day" SETTING_REMINDER_ENABLED = "attendance.reminder_enabled" SETTING_REMINDER_MAX = "attendance.reminder_max_per_run" # Defaults — overridable without a code change via platform_settings. DEFAULT_INTERN_DEPARTMENT = "PingCAP Intern" DEFAULT_CYCLE_START_DAY = 21