Skip to content

Commit 7c895d2

Browse files
goetchstoneclaude
andcommitted
Offboarding: show the manager's name with the email in the auto-reply
The auto-reply listed the manager as a bare email. Now we resolve their directory name and render the contact as "Jane Smith (jane@example.com)" — name so it reads professionally, email so senders can actually reach them. Falls back to the plain email if the manager isn't in the directory. Only the auto-reply TEXT uses the display form; the delegate / data-transfer / reminder steps still act on the raw manager email. Applies to the live preview, the step preview, and the executed vacation message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 12a4eef commit 7c895d2

3 files changed

Lines changed: 49 additions & 12 deletions

File tree

gamgui/core/lifecycle.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,17 @@ class OffboardStep:
3737

3838
def build_offboard_steps(
3939
user: str, manager: str, subject: str, message: str, days: int, today: date,
40-
notify: str = "", employee_name: str = "",
40+
notify: str = "", employee_name: str = "", manager_contact: str = "",
4141
) -> List[OffboardStep]:
42-
"""Turn the offboard parameters into the ordered step list (the user's exact sequence)."""
42+
"""Turn the offboard parameters into the ordered step list (the user's exact sequence).
43+
44+
``employee_name`` / ``manager_contact`` are the directory-resolved display forms used only in the
45+
auto-reply TEXT; the raw ``manager`` email is still what the delegate/transfer/reminder steps act on.
46+
"""
4347
employee = employee_name or user
44-
subject = fill_autoreply(subject, employee, manager)
45-
message = fill_autoreply(message, employee, manager)
48+
contact = manager_contact or manager
49+
subject = fill_autoreply(subject, employee, contact)
50+
message = fill_autoreply(message, employee, contact)
4651
due = today + timedelta(days=days)
4752
reminder_summary = f"Offboarding {user}: confirm with IT whether to delete the account"
4853
reminder_desc = (

gamgui/web/routes/lifecycle.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ def _days(value: str) -> int:
4040
return 30
4141

4242

43-
async def _employee_name(st, email: str) -> str:
44-
"""The departing user's display name (for the auto-reply), falling back to the email."""
43+
async def _resolve_name(st, email: str) -> str:
44+
"""A user's directory display name, falling back to the email if not found."""
4545
try:
4646
for u in await st.users():
4747
if u.primary_email.lower() == email.lower():
@@ -51,16 +51,31 @@ async def _employee_name(st, email: str) -> str:
5151
return email
5252

5353

54+
# Kept for build_offboard_steps' employee_name (subject uses the name alone, not name+email).
55+
async def _employee_name(st, email: str) -> str:
56+
return await _resolve_name(st, email)
57+
58+
59+
async def _manager_contact(st, email: str) -> str:
60+
"""The manager as a sender-facing contact: 'Jane Smith (jane@x.com)', or just the email."""
61+
email = email.strip()
62+
if not email:
63+
return "[manager]"
64+
name = await _resolve_name(st, email)
65+
return f"{name} ({email})" if name and name.lower() != email.lower() else email
66+
67+
5468
async def _compose_autoreply(st, user: str, manager: str, subject: str, message: str):
5569
"""The filled auto-reply (subject, body) exactly as senders will see it.
5670
57-
Resolves the departing user's display name; falls back to readable placeholders for fields not
58-
entered yet so the live preview always reads sensibly.
71+
Resolves both names from the directory — the departing user (subject uses the name) and the
72+
manager (shown as 'Name (email)' so senders can actually reach them). Falls back to readable
73+
placeholders for fields not entered yet so the live preview always reads sensibly.
5974
"""
6075
user, manager = user.strip(), manager.strip()
61-
employee = (await _employee_name(st, user)) if user else ""
76+
employee = (await _resolve_name(st, user)) if user else ""
6277
employee = employee or user or "[departing user]"
63-
contact = manager or "[manager]"
78+
contact = await _manager_contact(st, manager)
6479
subject = lifecycle.fill_autoreply(subject or lifecycle.DEFAULT_SUBJECT, employee, contact)
6580
message = lifecycle.fill_autoreply(message or lifecycle.DEFAULT_MESSAGE, employee, contact)
6681
return subject, message
@@ -89,7 +104,8 @@ async def offboard_preview(
89104
days_i = _days(days)
90105
steps = lifecycle.build_offboard_steps(
91106
user, manager, subject, message, days_i, date.today(),
92-
notify=notify.strip(), employee_name=await _employee_name(st, user))
107+
notify=notify.strip(), employee_name=await _employee_name(st, user),
108+
manager_contact=await _manager_contact(st, manager))
93109
ar_subject, ar_message = await _compose_autoreply(st, user, manager, subject, message)
94110
return TEMPLATES.TemplateResponse(
95111
request, "_offboard_preview.html",
@@ -148,7 +164,8 @@ async def offboard_run(
148164
return _err(request, "Enter both the departing user and the manager email.")
149165
steps = lifecycle.build_offboard_steps(
150166
user, manager, subject, message, _days(days), date.today(),
151-
notify=notify.strip(), employee_name=await _employee_name(st, user))
167+
notify=notify.strip(), employee_name=await _employee_name(st, user),
168+
manager_contact=await _manager_contact(st, manager))
152169
job = start_job(st.jobs, len(steps))
153170
job.task = asyncio.create_task(_run_offboard(job, conn, steps))
154171
st.invalidate_users() # password/org/etc. changed

tests/test_users_web.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,21 @@ def test_lifecycle_autoreply_live_fills_in_names(client):
568568
assert "{employee}" not in r.text and "{manager}" not in r.text
569569

570570

571+
def test_lifecycle_autoreply_resolves_manager_name(client):
572+
# The manager is shown as "Name (email)" — pulled from the directory — so senders can reach them.
573+
r = client.post("/lifecycle/offboard/autoreply", data={
574+
"user": "alice@example.com", "manager": "carol@example.com", "subject": "", "message": ""})
575+
assert "Carol Clark (carol@example.com)" in r.text
576+
assert "{manager}" not in r.text
577+
578+
579+
def test_lifecycle_autoreply_manager_falls_back_to_email(client):
580+
# Manager not in the directory -> just the email, no crash.
581+
r = client.post("/lifecycle/offboard/autoreply", data={
582+
"user": "alice@example.com", "manager": "external@partner.com", "subject": "", "message": ""})
583+
assert "external@partner.com" in r.text
584+
585+
571586
def test_lifecycle_autoreply_live_uses_placeholders_before_entry(client):
572587
# With nothing entered yet, the preview still reads sensibly (bracketed placeholders, no tokens).
573588
r = client.post("/lifecycle/offboard/autoreply", data={"user": "", "manager": ""})

0 commit comments

Comments
 (0)