Add task role eligibility and safe open engagement sync
This commit is contained in:
@@ -103,14 +103,70 @@ def parse_date_value(value: str | None) -> date | None:
|
||||
return date.fromisoformat(text)
|
||||
|
||||
|
||||
def _default_assignee_for_template(subscription: ClientServiceSubscription, template: FirmServiceTaskTemplate) -> int | None:
|
||||
role = (template.default_role_name or "").strip().lower()
|
||||
if "partner" in role:
|
||||
TASK_EXECUTION_ROLES = ("Partner", "Manager", "Staff")
|
||||
|
||||
|
||||
def normalise_task_execution_role(value: str | None) -> str | None:
|
||||
text = (value or "").strip().lower()
|
||||
aliases = {
|
||||
"partner": "Partner",
|
||||
"engagement partner": "Partner",
|
||||
"manager": "Manager",
|
||||
"branch manager": "Manager",
|
||||
"staff": "Staff",
|
||||
"employee": "Staff",
|
||||
}
|
||||
return aliases.get(text)
|
||||
|
||||
|
||||
def normalise_eligible_task_roles(value: str | None, default_role_name: str | None = None) -> list[str]:
|
||||
roles: list[str] = []
|
||||
for piece in (value or "").replace("|", ",").replace(";", ",").split(","):
|
||||
role = normalise_task_execution_role(piece)
|
||||
if role and role not in roles:
|
||||
roles.append(role)
|
||||
default_role = normalise_task_execution_role(default_role_name)
|
||||
if default_role and default_role not in roles:
|
||||
roles.insert(0, default_role)
|
||||
return roles
|
||||
|
||||
|
||||
def serialise_eligible_task_roles(roles, default_role_name: str | None = None) -> str | None:
|
||||
values: list[str] = []
|
||||
for raw in roles or []:
|
||||
role = normalise_task_execution_role(raw)
|
||||
if role and role not in values:
|
||||
values.append(role)
|
||||
default_role = normalise_task_execution_role(default_role_name)
|
||||
if default_role and default_role not in values:
|
||||
values.insert(0, default_role)
|
||||
return ",".join(values) if values else (default_role or None)
|
||||
|
||||
|
||||
def _assignee_for_role(subscription: ClientServiceSubscription, role: str | None) -> int | None:
|
||||
role = normalise_task_execution_role(role)
|
||||
if role == "Partner":
|
||||
return subscription.assigned_partner_user_id
|
||||
if "manager" in role:
|
||||
if role == "Manager":
|
||||
return subscription.assigned_manager_user_id
|
||||
if "staff" in role or "employee" in role:
|
||||
if role == "Staff":
|
||||
return subscription.assigned_staff_user_id
|
||||
return None
|
||||
|
||||
|
||||
def _default_assignee_for_template(subscription: ClientServiceSubscription, template: FirmServiceTaskTemplate) -> int | None:
|
||||
default_role = normalise_task_execution_role(template.default_role_name)
|
||||
eligible = normalise_eligible_task_roles(getattr(template, "eligible_role_names", None), template.default_role_name)
|
||||
|
||||
assignee = _assignee_for_role(subscription, default_role)
|
||||
if assignee:
|
||||
return assignee
|
||||
|
||||
for role in eligible:
|
||||
assignee = _assignee_for_role(subscription, role)
|
||||
if assignee:
|
||||
return assignee
|
||||
|
||||
return subscription.assigned_staff_user_id or subscription.assigned_manager_user_id or subscription.assigned_partner_user_id
|
||||
|
||||
|
||||
@@ -241,6 +297,7 @@ def generate_tasks_for_subscription(db: Session, *, subscription: ClientServiceS
|
||||
description=template.description,
|
||||
sequence_no=template.sequence_no,
|
||||
default_role_name=template.default_role_name,
|
||||
eligible_role_names=serialise_eligible_task_roles((getattr(template, "eligible_role_names", None) or "").split(","), template.default_role_name),
|
||||
assigned_to_user_id=_default_assignee_for_template(subscription, template),
|
||||
internal_target_date=_default_internal_target_date(subscription, template),
|
||||
status="pending",
|
||||
@@ -295,6 +352,174 @@ def generate_tasks_for_subscription_if_ready(
|
||||
)
|
||||
|
||||
|
||||
def _copy_template_snapshot_to_task(
|
||||
task: ClientServiceTaskInstance,
|
||||
*,
|
||||
template: FirmServiceTaskTemplate,
|
||||
subscription: ClientServiceSubscription,
|
||||
update_assignment: bool,
|
||||
) -> None:
|
||||
task.task_name = template.task_name
|
||||
task.description = template.description
|
||||
task.sequence_no = template.sequence_no
|
||||
task.default_role_name = template.default_role_name
|
||||
task.eligible_role_names = serialise_eligible_task_roles(
|
||||
(getattr(template, "eligible_role_names", None) or "").split(","),
|
||||
template.default_role_name,
|
||||
)
|
||||
task.task_category = getattr(template, "task_category", None)
|
||||
task.response_required = getattr(template, "response_required", False)
|
||||
task.response_type = (getattr(template, "response_type", "NONE") or "NONE").upper()
|
||||
task.evidence_required = getattr(template, "evidence_required", False)
|
||||
task.remarks_required_if_no = getattr(template, "remarks_required_if_no", False)
|
||||
task.is_aqmm_task = getattr(template, "is_aqmm_task", False)
|
||||
task.aqmm_mandatory = getattr(template, "aqmm_mandatory", False)
|
||||
task.aqmm_evidence_required = getattr(template, "aqmm_evidence_required", False)
|
||||
task.aqmm_manager_review_required = getattr(template, "aqmm_manager_review_required", False)
|
||||
task.aqmm_partner_review_required = getattr(template, "aqmm_partner_review_required", False)
|
||||
task.aqmm_review_partner_required = getattr(template, "aqmm_review_partner_required", False)
|
||||
task.aqmm_blocks_final_release = getattr(template, "aqmm_blocks_final_release", False)
|
||||
task.aqmm_reference = getattr(template, "aqmm_reference", None)
|
||||
if update_assignment:
|
||||
task.assigned_to_user_id = _default_assignee_for_template(subscription, template)
|
||||
|
||||
|
||||
def sync_open_engagement_tasks_for_service(
|
||||
db: Session,
|
||||
*,
|
||||
tenant_id: int,
|
||||
catalogue_id: int,
|
||||
user_id: int,
|
||||
include_started_open_tasks: bool = False,
|
||||
) -> dict[str, int]:
|
||||
templates = db.execute(
|
||||
select(FirmServiceTaskTemplate)
|
||||
.where(
|
||||
FirmServiceTaskTemplate.tenant_id == tenant_id,
|
||||
FirmServiceTaskTemplate.service_catalogue_id == catalogue_id,
|
||||
FirmServiceTaskTemplate.is_active.is_(True),
|
||||
)
|
||||
.order_by(FirmServiceTaskTemplate.sequence_no.asc(), FirmServiceTaskTemplate.id.asc())
|
||||
).scalars().all()
|
||||
|
||||
engagements = db.execute(
|
||||
select(ClientServiceSubscription)
|
||||
.where(
|
||||
ClientServiceSubscription.tenant_id == tenant_id,
|
||||
ClientServiceSubscription.service_catalogue_id == catalogue_id,
|
||||
ClientServiceSubscription.is_active.is_(True),
|
||||
ClientServiceSubscription.status == "active",
|
||||
ClientServiceSubscription.is_locked.is_(False),
|
||||
)
|
||||
.order_by(ClientServiceSubscription.id.asc())
|
||||
).scalars().all()
|
||||
|
||||
result = {
|
||||
"engagements": len(engagements),
|
||||
"created": 0,
|
||||
"updated_pending": 0,
|
||||
"updated_started": 0,
|
||||
"preserved_history": 0,
|
||||
}
|
||||
|
||||
for subscription in engagements:
|
||||
existing = db.execute(
|
||||
select(ClientServiceTaskInstance).where(
|
||||
ClientServiceTaskInstance.subscription_id == subscription.id,
|
||||
ClientServiceTaskInstance.financial_year == subscription.financial_year,
|
||||
)
|
||||
).scalars().all()
|
||||
by_template = {
|
||||
int(row.firm_task_template_id): row
|
||||
for row in existing
|
||||
if row.firm_task_template_id is not None
|
||||
}
|
||||
|
||||
for template in templates:
|
||||
task = by_template.get(int(template.id))
|
||||
if task is None:
|
||||
db.add(
|
||||
ClientServiceTaskInstance(
|
||||
tenant_id=subscription.tenant_id,
|
||||
branch_id=subscription.branch_id,
|
||||
subscription_id=subscription.id,
|
||||
client_id=subscription.client_id,
|
||||
service_catalogue_id=subscription.service_catalogue_id,
|
||||
firm_task_template_id=template.id,
|
||||
financial_year=subscription.financial_year,
|
||||
assessment_year=subscription.assessment_year,
|
||||
task_name=template.task_name,
|
||||
description=template.description,
|
||||
sequence_no=template.sequence_no,
|
||||
default_role_name=template.default_role_name,
|
||||
eligible_role_names=serialise_eligible_task_roles(
|
||||
(getattr(template, "eligible_role_names", None) or "").split(","),
|
||||
template.default_role_name,
|
||||
),
|
||||
assigned_to_user_id=_default_assignee_for_template(subscription, template),
|
||||
internal_target_date=_default_internal_target_date(subscription, template),
|
||||
status="pending",
|
||||
priority="normal",
|
||||
task_category=getattr(template, "task_category", None),
|
||||
response_required=getattr(template, "response_required", False),
|
||||
response_type=(getattr(template, "response_type", "NONE") or "NONE").upper(),
|
||||
evidence_required=getattr(template, "evidence_required", False),
|
||||
remarks_required_if_no=getattr(template, "remarks_required_if_no", False),
|
||||
is_aqmm_task=getattr(template, "is_aqmm_task", False),
|
||||
aqmm_mandatory=getattr(template, "aqmm_mandatory", False),
|
||||
aqmm_evidence_required=getattr(template, "aqmm_evidence_required", False),
|
||||
aqmm_manager_review_required=getattr(template, "aqmm_manager_review_required", False),
|
||||
aqmm_partner_review_required=getattr(template, "aqmm_partner_review_required", False),
|
||||
aqmm_review_partner_required=getattr(template, "aqmm_review_partner_required", False),
|
||||
aqmm_blocks_final_release=getattr(template, "aqmm_blocks_final_release", False),
|
||||
aqmm_reference=getattr(template, "aqmm_reference", None),
|
||||
aqmm_status="pending" if getattr(template, "is_aqmm_task", False) else "not_required",
|
||||
aqmm_review_status="pending_review" if (
|
||||
getattr(template, "aqmm_manager_review_required", False)
|
||||
or getattr(template, "aqmm_partner_review_required", False)
|
||||
or getattr(template, "aqmm_review_partner_required", False)
|
||||
) else "not_required",
|
||||
is_active=True,
|
||||
created_by_user_id=user_id,
|
||||
updated_by_user_id=user_id,
|
||||
)
|
||||
)
|
||||
result["created"] += 1
|
||||
continue
|
||||
|
||||
status = (task.status or "pending").strip().lower()
|
||||
never_started = (
|
||||
status == "pending"
|
||||
and task.started_at_utc is None
|
||||
and task.completed_at_utc is None
|
||||
and task.submitted_for_review_at_utc is None
|
||||
and (task.rework_status or "none") == "none"
|
||||
)
|
||||
|
||||
if never_started:
|
||||
_copy_template_snapshot_to_task(
|
||||
task,
|
||||
template=template,
|
||||
subscription=subscription,
|
||||
update_assignment=True,
|
||||
)
|
||||
task.updated_by_user_id = user_id
|
||||
result["updated_pending"] += 1
|
||||
elif include_started_open_tasks and status in {"in_progress", "blocked"} and task.completed_at_utc is None:
|
||||
task.default_role_name = template.default_role_name
|
||||
task.eligible_role_names = serialise_eligible_task_roles(
|
||||
(getattr(template, "eligible_role_names", None) or "").split(","),
|
||||
template.default_role_name,
|
||||
)
|
||||
task.assigned_to_user_id = _default_assignee_for_template(subscription, template)
|
||||
task.updated_by_user_id = user_id
|
||||
result["updated_started"] += 1
|
||||
else:
|
||||
result["preserved_history"] += 1
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def _latest_task_documents(db: Session, task_id: int) -> list[EngagementDocument]:
|
||||
return db.execute(
|
||||
|
||||
Reference in New Issue
Block a user