Skip to content

Commit f83616b

Browse files
authored
Fix project account manager setup
Ensure ERPNext project creation adds the selected account manager to the project roster and contact portal user when possible, while surfacing non-fatal setup warnings.
1 parent f247297 commit f83616b

8 files changed

Lines changed: 376 additions & 37 deletions

File tree

apps/admin_dashboard/src/main.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,8 @@ function App() {
11311131
activity_type: { name?: string }
11321132
cache_refresh_error?: string
11331133
cache_refresh_message?: string
1134+
setup_warnings?: string[]
1135+
setup_warning_message?: string
11341136
}>("/dashboard/api/projects/create", {
11351137
method: "POST",
11361138
headers: { "Content-Type": "application/json" },
@@ -1145,12 +1147,23 @@ function App() {
11451147
)
11461148
: [payload.project, ...current]
11471149
})
1148-
showToast("Created ERP project setup", "ok")
1150+
showToast(
1151+
payload.setup_warnings?.length
1152+
? payload.setup_warning_message ||
1153+
"Created ERP project setup; account manager setup needs follow-up"
1154+
: "Created ERP project setup",
1155+
payload.setup_warnings?.length ? "warning" : "ok",
1156+
)
11491157
openProjectDetail(payload.project.id)
11501158
} else {
1159+
const cacheRefreshMessage =
1160+
payload.cache_refresh_message || "Created ERP project in ERPNext; local sync is pending"
1161+
const setupWarningMessage = payload.setup_warnings?.length
1162+
? payload.setup_warning_message || "Account manager setup needs follow-up"
1163+
: ""
11511164
showToast(
1152-
payload.cache_refresh_message || "Created ERP project in ERPNext; local sync is pending",
1153-
"ok",
1165+
[cacheRefreshMessage, setupWarningMessage].filter(Boolean).join(" "),
1166+
payload.setup_warnings?.length ? "warning" : "ok",
11541167
)
11551168
void loadProjects()
11561169
}

apps/api/src/five08/backend/api.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3696,6 +3696,7 @@ def _create_erpnext_project_setup(
36963696

36973697
cache_refresh_error: str | None = None
36983698
cache_refresh_message: str | None = None
3699+
setup_warnings: list[str] = []
36993700
client = _erpnext_client()
37003701
try:
37013702
activity_type = client.ensure_activity_type(activity_type_name)
@@ -3743,6 +3744,18 @@ def cleanup_created_customer() -> None:
37433744
if erpnext_project_id is None:
37443745
cleanup_created_customer()
37453746
raise ERPNextAPIError("ERPNext Project response is missing an id")
3747+
if account_manager is not None:
3748+
try:
3749+
project_detail = client.add_project_user(
3750+
erpnext_project_id, account_manager
3751+
)
3752+
except ERPNextAPIError:
3753+
logger.exception(
3754+
"ERPNext Project was created but account manager roster setup failed project=%s account_manager=%s",
3755+
erpnext_project_id,
3756+
account_manager,
3757+
)
3758+
setup_warnings.append("account_manager_project_user_failed")
37463759

37473760
address_doc: dict[str, Any] | None = None
37483761
if has_address:
@@ -3778,6 +3791,21 @@ def cleanup_created_customer() -> None:
37783791
phone=_text_or_none(payload.contact_phone),
37793792
mobile_no=_text_or_none(payload.contact_mobile),
37803793
)
3794+
if account_manager is not None and contact_doc is not None:
3795+
contact_doc_id = _text_or_none(contact_doc.get("name"))
3796+
if contact_doc_id is not None:
3797+
try:
3798+
contact_doc = client.set_contact_portal_user(
3799+
contact=contact_doc_id,
3800+
portal_user=account_manager,
3801+
)
3802+
except ERPNextAPIError:
3803+
logger.exception(
3804+
"ERPNext Contact was linked but portal user setup failed contact=%s account_manager=%s",
3805+
contact_doc_id,
3806+
account_manager,
3807+
)
3808+
setup_warnings.append("account_manager_contact_user_failed")
37813809

37823810
primary_address = (
37833811
_text_or_none(address_doc.get("name")) if address_doc is not None else None
@@ -3834,6 +3862,11 @@ def cleanup_created_customer() -> None:
38343862
result["cache_refresh_error"] = cache_refresh_error
38353863
if cache_refresh_message is not None:
38363864
result["cache_refresh_message"] = cache_refresh_message
3865+
if setup_warnings:
3866+
result["setup_warnings"] = setup_warnings
3867+
result["setup_warning_message"] = (
3868+
"Created the project, but account manager setup needs follow-up."
3869+
)
38373870
return result
38383871

38393872

@@ -4525,7 +4558,9 @@ async def dashboard_create_project_handler(request: Request) -> JSONResponse:
45254558
actor_provider, actor_subject = _session_audit_actor(session)
45264559
await _write_auth_audit_event(
45274560
action="erpnext.project_setup_create",
4528-
result=AuditResult.SUCCESS,
4561+
result=AuditResult.ERROR
4562+
if isinstance(result.get("setup_warnings"), list)
4563+
else AuditResult.SUCCESS,
45294564
actor_subject=actor_subject,
45304565
actor_display_name=session.display_name,
45314566
actor_provider=actor_provider,
@@ -4543,6 +4578,7 @@ async def dashboard_create_project_handler(request: Request) -> JSONResponse:
45434578
"primary_contact": contact.get("name")
45444579
if isinstance(contact, dict)
45454580
else None,
4581+
"setup_warnings": result.get("setup_warnings"),
45464582
},
45474583
)
45484584
return JSONResponse(result, status_code=201)

apps/api/src/five08/backend/static/dashboard/.vite/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"index.html": {
3-
"file": "assets/index-BA0o_Cce.js",
3+
"file": "assets/index-D0EL0k11.js",
44
"name": "index",
55
"src": "index.html",
66
"isEntry": true,

apps/api/src/five08/backend/static/dashboard/assets/index-BA0o_Cce.js renamed to apps/api/src/five08/backend/static/dashboard/assets/index-D0EL0k11.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/api/src/five08/backend/static/dashboard/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>508 Operations Dashboard</title>
7-
<script type="module" crossorigin src="/dashboard/assets/index-BA0o_Cce.js"></script>
7+
<script type="module" crossorigin src="/dashboard/assets/index-D0EL0k11.js"></script>
88
<link rel="stylesheet" crossorigin href="/dashboard/assets/index-DaV5VIHY.css">
99
</head>
1010
<body>

packages/shared/src/five08/clients/erpnext.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ def create_contact(
447447
email_id: str | None = None,
448448
phone: str | None = None,
449449
mobile_no: str | None = None,
450+
portal_user: str | None = None,
450451
) -> dict[str, Any]:
451452
"""Create one Contact linked to a Customer."""
452453
normalized_customer = customer.strip()
@@ -475,13 +476,40 @@ def create_contact(
475476
payload.setdefault("phone_nos", []).append(
476477
{"phone": normalized_mobile, "is_primary_mobile_no": 1}
477478
)
479+
normalized_portal_user = (portal_user or "").strip()
480+
if normalized_portal_user:
481+
payload["user"] = normalized_portal_user
478482
return self.create_record("Contact", payload)
479483

484+
def set_contact_portal_user(
485+
self,
486+
*,
487+
contact: str,
488+
portal_user: str,
489+
) -> dict[str, Any]:
490+
"""Set Contact.user when the Contact does not already have one."""
491+
normalized_contact = contact.strip()
492+
normalized_portal_user = portal_user.strip()
493+
if not normalized_contact:
494+
raise ERPNextAPIError("Contact is required")
495+
if not normalized_portal_user:
496+
raise ERPNextAPIError("Portal user is required")
497+
contact_doc = self.get_record("Contact", normalized_contact)
498+
existing_user = str(contact_doc.get("user") or "").strip()
499+
if existing_user:
500+
return contact_doc
501+
return self.update_record(
502+
"Contact",
503+
normalized_contact,
504+
{"user": normalized_portal_user},
505+
)
506+
480507
def link_contact_to_customer(
481508
self,
482509
*,
483510
contact: str,
484511
customer: str,
512+
portal_user: str | None = None,
485513
) -> dict[str, Any]:
486514
"""Ensure an existing Contact has a Customer link."""
487515
normalized_contact = contact.strip()
@@ -499,17 +527,22 @@ def link_contact_to_customer(
499527
and link.get("link_name") == normalized_customer
500528
for link in existing_links
501529
):
530+
links = existing_links
531+
else:
532+
links = [
533+
*existing_links,
534+
{"link_doctype": "Customer", "link_name": normalized_customer},
535+
]
536+
updates: dict[str, Any] = {}
537+
if links != existing_links:
538+
updates["links"] = links
539+
normalized_portal_user = (portal_user or "").strip()
540+
existing_user = str(contact_doc.get("user") or "").strip()
541+
if normalized_portal_user and not existing_user:
542+
updates["user"] = normalized_portal_user
543+
if not updates:
502544
return contact_doc
503-
return self.update_record(
504-
"Contact",
505-
normalized_contact,
506-
{
507-
"links": [
508-
*existing_links,
509-
{"link_doctype": "Customer", "link_name": normalized_customer},
510-
]
511-
},
512-
)
545+
return self.update_record("Contact", normalized_contact, updates)
513546

514547
def set_customer_primary_records(
515548
self,

0 commit comments

Comments
 (0)