Generate engagement tasks automatically and repair missing tasks
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections import Counter
|
||||
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from app.core.db.common import CommonSessionLocal
|
||||
from app.modules.services.execution import generate_tasks_for_subscription_if_ready
|
||||
from app.modules.services.models import (
|
||||
ClientServiceSubscription,
|
||||
ClientServiceTaskInstance,
|
||||
FirmServiceTaskTemplate,
|
||||
)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Generate missing task instances for existing active engagements. "
|
||||
"Dry-run is the default; pass --apply to commit."
|
||||
)
|
||||
)
|
||||
parser.add_argument("--apply", action="store_true", help="Commit generated tasks.")
|
||||
parser.add_argument("--tenant-id", type=int, default=None, help="Optional tenant filter.")
|
||||
parser.add_argument(
|
||||
"--subscription-id",
|
||||
type=int,
|
||||
action="append",
|
||||
default=[],
|
||||
help="Repair only this engagement/subscription ID. May be repeated.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
existing_task_count = (
|
||||
select(
|
||||
ClientServiceTaskInstance.subscription_id.label("subscription_id"),
|
||||
func.count(ClientServiceTaskInstance.id).label("task_count"),
|
||||
)
|
||||
.group_by(ClientServiceTaskInstance.subscription_id)
|
||||
.subquery()
|
||||
)
|
||||
|
||||
stmt = (
|
||||
select(ClientServiceSubscription)
|
||||
.outerjoin(
|
||||
existing_task_count,
|
||||
existing_task_count.c.subscription_id == ClientServiceSubscription.id,
|
||||
)
|
||||
.where(
|
||||
ClientServiceSubscription.status == "active",
|
||||
ClientServiceSubscription.is_active.is_(True),
|
||||
func.coalesce(existing_task_count.c.task_count, 0) == 0,
|
||||
)
|
||||
.order_by(ClientServiceSubscription.tenant_id, ClientServiceSubscription.id)
|
||||
)
|
||||
|
||||
if args.tenant_id is not None:
|
||||
stmt = stmt.where(ClientServiceSubscription.tenant_id == args.tenant_id)
|
||||
if args.subscription_id:
|
||||
stmt = stmt.where(ClientServiceSubscription.id.in_(args.subscription_id))
|
||||
|
||||
engagements = db.execute(stmt).scalars().all()
|
||||
print(f"Candidate active engagements with zero task instances: {len(engagements)}")
|
||||
|
||||
summary = Counter()
|
||||
total_created = 0
|
||||
|
||||
for engagement in engagements:
|
||||
template_count = db.execute(
|
||||
select(func.count(FirmServiceTaskTemplate.id)).where(
|
||||
FirmServiceTaskTemplate.tenant_id == engagement.tenant_id,
|
||||
FirmServiceTaskTemplate.service_catalogue_id == engagement.service_catalogue_id,
|
||||
FirmServiceTaskTemplate.is_active.is_(True),
|
||||
)
|
||||
).scalar_one()
|
||||
|
||||
actor_user_id = (
|
||||
engagement.updated_by_user_id
|
||||
or engagement.created_by_user_id
|
||||
or engagement.assigned_partner_user_id
|
||||
or engagement.assigned_manager_user_id
|
||||
or engagement.assigned_staff_user_id
|
||||
)
|
||||
|
||||
if not template_count:
|
||||
summary["no_active_templates"] += 1
|
||||
print(
|
||||
f"SKIP engagement={engagement.id}: "
|
||||
"no active Firm Service Task Templates."
|
||||
)
|
||||
continue
|
||||
|
||||
if not actor_user_id:
|
||||
summary["no_actor_user"] += 1
|
||||
print(
|
||||
f"SKIP engagement={engagement.id}: "
|
||||
"no valid existing actor/team user available for audit fields."
|
||||
)
|
||||
continue
|
||||
|
||||
created = generate_tasks_for_subscription_if_ready(
|
||||
db,
|
||||
subscription=engagement,
|
||||
user_id=int(actor_user_id),
|
||||
)
|
||||
if created:
|
||||
total_created += created
|
||||
summary["repaired"] += 1
|
||||
print(
|
||||
f"READY engagement={engagement.id}: "
|
||||
f"{created} task instance(s) generated from {template_count} active template(s)."
|
||||
)
|
||||
else:
|
||||
summary["quality_gate_or_no_change"] += 1
|
||||
print(
|
||||
f"NO CHANGE engagement={engagement.id}: "
|
||||
"AQMM acceptance gate is pending or no task was required."
|
||||
)
|
||||
|
||||
if args.apply:
|
||||
db.commit()
|
||||
print(f"COMMITTED. Total task instances generated: {total_created}")
|
||||
else:
|
||||
db.rollback()
|
||||
print(f"DRY RUN ONLY. Would generate {total_created} task instance(s). No database changes committed.")
|
||||
|
||||
print("Summary:")
|
||||
for key in sorted(summary):
|
||||
print(f" {key}: {summary[key]}")
|
||||
return 0
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user