146 lines
5.0 KiB
Python
146 lines
5.0 KiB
Python
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())
|