Files
2026-06-22 16:41:39 +05:30

689 lines
31 KiB
Python

"""
Audit Firm ERP -- UAT seed data for Playwright v2.4.1 IMAP.
Purpose:
- Replace old @tenant-a.test / @tenant-b.test UAT users with real @vavalam.com test emails.
- Create all Playwright role users.
- Create tenants, branches, clients, employees/staff, service catalogue, task templates,
subscriptions/engagements, task instances, notice case, and a local storage node.
Run inside ERP container from project root after migrations:
cd /app
PYTHONPATH=/app UAT_SEED_PASSWORD='Password@123' python scripts/seed_uat_data.py
Safe/idempotent for UAT: re-running updates/reuses existing UAT records.
Do NOT run against live production data.
"""
from __future__ import annotations
import hashlib
import os
import sys
from datetime import date, datetime, timedelta, timezone
PASSWORD = os.getenv("UAT_SEED_PASSWORD", "Password@123")
ACTIVE_FY = os.getenv("ACTIVE_FY", "2025-26")
PREVIOUS_FY = os.getenv("PREVIOUS_FY", "2024-25")
ASSESSMENT_YEAR = os.getenv("ASSESSMENT_YEAR", "2026-27")
LSA_SECRET = os.getenv("UAT_LSA_SECRET", "uat-local-storage-agent-secret")
ROLE_NAME = {
"system_admin": "System Admin",
"firm_admin": "Firm Admin",
"partner": "Partner",
"manager": "Branch Manager",
"staff": "Staff",
"client": "Client",
"consultant": "Consultant",
}
LEGACY_EMAIL_MAP = {
"uat.firmadmin@tenant-a.test": "uat.firmadmin@vavalam.com",
"uat.partner@tenant-a.test": "uat.partner@vavalam.com",
"uat.manager@tenant-a.test": "uat.manager@vavalam.com",
"uat.staff@tenant-a.test": "uat.staff@vavalam.com",
"uat.client@tenant-a.test": "uat.client@vavalam.com",
"uat.consultant@tenant-a.test": "uat.consultant@vavalam.com",
"uat.firmadmin@tenant-b.test": "uat.firmadminb@vavalam.com",
}
def fail(msg: str) -> None:
print(f"\n[seed] ERROR: {msg}", file=sys.stderr)
sys.exit(1)
def main() -> None:
try:
from app.core.db.common import CommonSessionLocal
from app.core.security.passwords import hash_password
from app.modules.core.iam.models import User
from app.modules.core.tenancy.models import Tenant, Branch, FinancialYear
from app.modules.core.rbac.models import Role, UserRole
from app.modules.clients.models import Client
from app.modules.employees.models import Employee
from app.modules.services.models import (
ServiceCategory,
ServiceCatalogue,
ServiceDefaultTaskTemplate,
FirmServiceSelection,
FirmServiceTaskTemplate,
FirmTaskDocumentRequirement,
ServiceDueDateRule,
ClientServiceSubscription,
ClientServiceTaskInstance,
)
from app.modules.documents.models import (
BranchStorageNode,
EngagementDocument,
EngagementDocumentVersion,
DocumentStorageJob,
)
except Exception as exc:
fail(
"Could not import ERP modules. Run this from ERP project root inside the app container. "
f"Underlying import error: {exc!r}"
)
try:
from app.modules.notice_cases.models import NoticeCase
except Exception:
NoticeCase = None
db = CommonSessionLocal()
ids: dict[str, object] = {}
def valid_data(Model, data: dict) -> dict:
valid = {c.name for c in Model.__table__.columns}
return {k: v for k, v in data.items() if k in valid}
def get_or_create(Model, lookup: dict, defaults: dict | None = None):
obj = db.query(Model).filter_by(**lookup).first()
if obj:
if defaults:
for k, v in valid_data(Model, defaults).items():
setattr(obj, k, v)
db.flush()
return obj, False
data = {**lookup, **(defaults or {})}
obj = Model(**valid_data(Model, data))
db.add(obj)
db.flush()
return obj, True
def role_by_name(name: str):
return db.query(Role).filter(Role.name == name).first()
def assign_role(user, role_label: str) -> None:
role_name = ROLE_NAME[role_label]
role = role_by_name(role_name)
if not role:
print(f"[seed] WARNING: role '{role_name}' not found. Run/restart ERP startup seed first.")
return
exists = db.query(UserRole).filter_by(user_id=user.id, role_id=role.id).first()
if not exists:
db.add(UserRole(user_id=user.id, role_id=role.id))
db.flush()
def deactivate_user(email: str) -> None:
user = db.query(User).filter(User.email == email).first()
if user:
user.allow_login = False
user.is_active = False
if hasattr(user, "deleted_at"):
user.deleted_at = datetime.now(timezone.utc)
db.flush()
def ensure_user(email: str, full_name: str, role_labels: list[str], tenant, branch, *, locked=False, allow_login=True):
# If old fake email exists and mapped to this real email, update old record in-place unless real already exists.
for old_email, new_email in LEGACY_EMAIL_MAP.items():
if new_email == email:
old_user = db.query(User).filter(User.email == old_email).first()
real_user = db.query(User).filter(User.email == email).first()
if old_user and not real_user:
old_user.email = email
old_user.full_name = full_name
old_user.tenant_id = tenant.id
old_user.branch_id = branch.id
old_user.password_hash = hash_password(PASSWORD)
old_user.is_active = True
old_user.allow_login = allow_login
old_user.is_locked = locked
old_user.must_change_password = False
db.flush()
for role_label in role_labels:
assign_role(old_user, role_label)
return old_user
if old_user and real_user and old_user.id != real_user.id:
deactivate_user(old_email)
user, _ = get_or_create(
User,
{"email": email},
{
"full_name": full_name,
"password_hash": hash_password(PASSWORD),
"tenant_id": tenant.id,
"branch_id": branch.id,
"is_active": True,
"allow_login": allow_login,
"is_locked": locked,
"must_change_password": False,
"mobile": "9000000000",
},
)
user.password_hash = hash_password(PASSWORD)
user.full_name = full_name
user.tenant_id = tenant.id
user.branch_id = branch.id
user.is_active = True
user.allow_login = allow_login
user.is_locked = locked
user.must_change_password = False
for role_label in role_labels:
assign_role(user, role_label)
db.flush()
return user
def ensure_employee(code: str, user, tenant, branch, designation: str, department="Audit"):
emp, _ = get_or_create(
Employee,
{"tenant_id": tenant.id, "employee_code": code},
{
"branch_id": branch.id,
"user_id": user.id,
"full_name": user.full_name,
"email": user.email,
"mobile": getattr(user, "mobile", None) or "9000000000",
"date_of_joining": date(2025, 4, 1),
"employment_type": "full_time",
"status": "active",
"is_active": True,
"department": department,
"designation": designation,
"pan": f"UAT{code[-3:].zfill(3)}P",
},
)
return emp
def ensure_fy(tenant, year_code: str, ay: str, start: date, end: date, current: bool):
fy, _ = get_or_create(
FinancialYear,
{"tenant_id": tenant.id, "year_code": year_code},
{
"assessment_year": ay,
"start_date": start,
"end_date": end,
"is_current": current,
"is_locked": False,
},
)
return fy
def ensure_service_category(code: str, name: str, sort_order: int):
return get_or_create(
ServiceCategory,
{"code": code},
{"name": name, "sort_order": sort_order, "is_active": True},
)[0]
def ensure_catalogue(code: str, name: str, category_obj, recurrence: str, engagement_type="non_assurance", sort_order=100):
svc, _ = get_or_create(
ServiceCatalogue,
{"service_code": code},
{
"service_name": name,
"category": category_obj.name,
"category_id": category_obj.id,
"recurrence_type": recurrence,
"engagement_type": engagement_type,
"sort_order": sort_order,
"description": f"UAT seed service: {name}",
"applicable_individual": True,
"applicable_proprietorship": True,
"applicable_partnership": True,
"applicable_llp": True,
"applicable_company": True,
"is_active": True,
"is_client_requestable": True,
"is_consultant_requestable": False,
},
)
return svc
def ensure_default_task(svc, seq: int, name: str, role: str, review=False):
return get_or_create(
ServiceDefaultTaskTemplate,
{"service_catalogue_id": svc.id, "sequence_no": seq},
{
"task_name": name,
"description": f"UAT default task: {name}",
"default_role_name": role,
"is_mandatory": True,
"requires_review": review,
"is_active": True,
},
)[0]
def enable_firm_service(tenant, branch, svc, user):
return get_or_create(
FirmServiceSelection,
{"tenant_id": tenant.id, "service_catalogue_id": svc.id},
{
"is_enabled": True,
"default_branch_id": branch.id,
"activated_by_user_id": user.id,
"updated_by_user_id": user.id,
},
)[0]
def ensure_firm_task(tenant, svc, seq: int, name: str, role: str, user, review=False):
task, _ = get_or_create(
FirmServiceTaskTemplate,
{"tenant_id": tenant.id, "service_catalogue_id": svc.id, "sequence_no": seq},
{
"task_name": name,
"description": f"UAT firm task: {name}",
"default_role_name": role,
"is_mandatory": True,
"requires_review": review,
"is_active": True,
"created_by_user_id": user.id,
"updated_by_user_id": user.id,
},
)
return task
def ensure_doc_requirement(tenant, svc, task, name: str, dtype="GENERAL"):
return get_or_create(
FirmTaskDocumentRequirement,
{"tenant_id": tenant.id, "firm_task_template_id": task.id, "document_name": name},
{
"service_catalogue_id": svc.id,
"document_type": dtype,
"is_mandatory": True,
"allowed_file_types": "pdf,xlsx,docx,jpg,png,txt",
"instructions": "UAT seeded document requirement",
"sort_order": task.sequence_no,
"is_active": True,
},
)[0]
def ensure_due_rule(svc, name: str, period_type: str, day: int | None, month: int | None, offset=0):
return get_or_create(
ServiceDueDateRule,
{"service_catalogue_id": svc.id, "rule_name": name},
{
"period_type": period_type,
"due_year_basis": "financial_year_start",
"due_day": day,
"due_month": month,
"due_month_offset": offset,
"sort_order": 100,
"is_active": True,
"remarks": "UAT seed due date rule",
},
)[0]
def ensure_subscription(tenant, branch, client, svc, firm_selection, partner, manager, staff, due_rule=None):
sub, _ = get_or_create(
ClientServiceSubscription,
{"tenant_id": tenant.id, "client_id": client.id, "service_catalogue_id": svc.id, "financial_year": ACTIVE_FY},
{
"branch_id": branch.id,
"firm_service_selection_id": firm_selection.id,
"assigned_partner_user_id": partner.id,
"assigned_manager_user_id": manager.id,
"assigned_staff_user_id": staff.id,
"review_partner_user_id": partner.id,
"assessment_year": ASSESSMENT_YEAR,
"engagement_type": svc.engagement_type,
"due_date_rule_id": due_rule.id if due_rule else None,
"original_due_date": date.today() + timedelta(days=30),
"current_due_date": date.today() + timedelta(days=30),
"due_date_source": "seed",
"start_date": date.today(),
"end_date": date.today() + timedelta(days=365),
"status": "active",
"remarks": "UAT seeded engagement/subscription",
"is_active": True,
"is_locked": False,
"created_by_user_id": partner.id,
"updated_by_user_id": partner.id,
},
)
return sub
def ensure_task_instance(tenant, branch, client, sub, svc, firm_task, assigned_user, days=7):
task, _ = get_or_create(
ClientServiceTaskInstance,
{"subscription_id": sub.id, "firm_task_template_id": firm_task.id, "financial_year": ACTIVE_FY},
{
"tenant_id": tenant.id,
"branch_id": branch.id,
"client_id": client.id,
"service_catalogue_id": svc.id,
"assessment_year": ASSESSMENT_YEAR,
"task_name": firm_task.task_name,
"description": firm_task.description,
"sequence_no": firm_task.sequence_no,
"default_role_name": firm_task.default_role_name,
"assigned_to_user_id": assigned_user.id,
"internal_target_date": date.today() + timedelta(days=days),
"status": "pending",
"priority": "normal",
"is_active": True,
"is_locked": False,
"created_by_user_id": assigned_user.id,
"updated_by_user_id": assigned_user.id,
},
)
return task
try:
# Tenants and branches
tenant_a, _ = get_or_create(Tenant, {"code": "UAT-A"}, {"name": "UAT Tenant A", "is_active": True, "firm_type": "partnership", "contact_email": "support@vavalam.com"})
tenant_b, _ = get_or_create(Tenant, {"code": "UAT-B"}, {"name": "UAT Tenant B", "is_active": True, "firm_type": "partnership", "contact_email": "support@vavalam.com"})
branch_a, _ = get_or_create(Branch, {"tenant_id": tenant_a.id, "code": "UAT-BA"}, {"name": "UAT Branch A", "is_active": True, "is_head_office": True, "timezone": "Asia/Kolkata", "local_storage_path": "D:/UAT/BranchA"})
branch_b, _ = get_or_create(Branch, {"tenant_id": tenant_b.id, "code": "UAT-BB"}, {"name": "UAT Branch B", "is_active": True, "is_head_office": True, "timezone": "Asia/Kolkata", "local_storage_path": "D:/UAT/BranchB"})
ids["TENANT_A_ID"] = tenant_a.id
ids["TENANT_B_ID"] = tenant_b.id
ids["BRANCH_A_ID"] = branch_a.id
ids["BRANCH_B_ID"] = branch_b.id
fy_a = ensure_fy(tenant_a, ACTIVE_FY, ASSESSMENT_YEAR, date(2025, 4, 1), date(2026, 3, 31), True)
ensure_fy(tenant_a, PREVIOUS_FY, "2025-26", date(2024, 4, 1), date(2025, 3, 31), False)
fy_b = ensure_fy(tenant_b, ACTIVE_FY, ASSESSMENT_YEAR, date(2025, 4, 1), date(2026, 3, 31), True)
ensure_fy(tenant_b, PREVIOUS_FY, "2025-26", date(2024, 4, 1), date(2025, 3, 31), False)
ids["ACTIVE_FY_ID"] = fy_a.id
# Users: real IMAP-capable emails
system_admin = ensure_user("uat.admin@vavalam.com", "UAT System Admin", ["system_admin"], tenant_a, branch_a)
firm_admin = ensure_user("uat.firmadmin@vavalam.com", "UAT Firm Admin", ["firm_admin"], tenant_a, branch_a)
partner = ensure_user("uat.partner@vavalam.com", "UAT Partner", ["partner"], tenant_a, branch_a)
manager = ensure_user("uat.manager@vavalam.com", "UAT Manager", ["manager"], tenant_a, branch_a)
staff = ensure_user("uat.staff@vavalam.com", "UAT Staff", ["staff"], tenant_a, branch_a)
staff2 = ensure_user("uat.staff2@vavalam.com", "UAT Staff 2", ["staff"], tenant_a, branch_a)
branch2staff = ensure_user("uat.branch2staff@vavalam.com", "UAT Branch 2 Staff", ["staff"], tenant_b, branch_b)
client_user = ensure_user("uat.client@vavalam.com", "UAT Client User", ["client"], tenant_a, branch_a)
client2_user = ensure_user("uat.client2@vavalam.com", "UAT Client 2 User", ["client"], tenant_b, branch_b)
consultant = ensure_user("uat.consultant@vavalam.com", "UAT Consultant", ["consultant"], tenant_a, branch_a)
invite_user = ensure_user("uat.invite@vavalam.com", "UAT Invite User", ["staff"], tenant_a, branch_a, allow_login=True)
reset_user = ensure_user("uat.reset@vavalam.com", "UAT Reset User", ["staff"], tenant_a, branch_a, allow_login=True)
locked_user = ensure_user("uat.lockeduser@vavalam.com", "UAT Locked User", ["staff"], tenant_a, branch_a, locked=True, allow_login=True)
# Optional but recommended: create one true Tenant B admin for cross-tenant tests.
# Please create this mailbox if OTP is required for Tenant B Admin login.
tenant_b_admin = ensure_user("uat.firmadminb@vavalam.com", "UAT Firm Admin B", ["firm_admin"], tenant_b, branch_b)
for old in LEGACY_EMAIL_MAP:
if not LEGACY_EMAIL_MAP[old].endswith("@vavalam.com"):
deactivate_user(old)
# Employees/staff master records
ensure_employee("UAT-MGR-A", manager, tenant_a, branch_a, "Branch Manager")
ensure_employee("UAT-STF-A", staff, tenant_a, branch_a, "Audit Staff")
ensure_employee("UAT-STF-A2", staff2, tenant_a, branch_a, "Audit Staff")
ensure_employee("UAT-STF-B", branch2staff, tenant_b, branch_b, "Branch Staff")
ensure_employee("UAT-LOCK-A", locked_user, tenant_a, branch_a, "Locked Test User")
# Clients
client_a, _ = get_or_create(
Client,
{"tenant_id": tenant_a.id, "client_code": "UAT-CL-A"},
{
"branch_id": branch_a.id,
"partner_id": partner.id,
"default_review_partner_user_id": partner.id,
"portal_user_id": client_user.id,
"engagement_mode": "hybrid",
"client_name": "UAT Client A Pvt Ltd",
"trade_name": "UAT Client A",
"client_type": "Company",
"pan": "AABCU1111A",
"gstin": "33AABCU1111A1Z5",
"tan": "CHEU12345A",
"cin_llpin": "U12345TN2025PTC111111",
"msme_no": "UDYAM-TN-00-0001111",
"iec_code": "IEC1111111",
"contact_person_name": "UAT Client A Contact",
"mobile": "9000000001",
"email": "uat.client@vavalam.com",
"alternate_email": "support@vavalam.com",
"city": "Chennai",
"state": "Tamil Nadu",
"pincode": "600001",
"country": "India",
"status": "active",
"client_category": "Regular",
"risk_category": "Medium",
"onboarding_date": date.today(),
"gst_applicable": True,
"income_tax_applicable": True,
"tds_applicable": True,
"roc_applicable": True,
"audit_applicable": True,
"is_active": True,
},
)
client_b, _ = get_or_create(
Client,
{"tenant_id": tenant_b.id, "client_code": "UAT-CL-B"},
{
"branch_id": branch_b.id,
"partner_id": tenant_b_admin.id,
"default_review_partner_user_id": tenant_b_admin.id,
"portal_user_id": client2_user.id,
"engagement_mode": "hybrid",
"client_name": "UAT Client B Pvt Ltd",
"trade_name": "UAT Client B",
"client_type": "Company",
"pan": "AABCU2222A",
"gstin": "33AABCU2222A1Z5",
"email": "uat.client2@vavalam.com",
"mobile": "9000000002",
"city": "Chennai",
"state": "Tamil Nadu",
"pincode": "600002",
"country": "India",
"status": "active",
"client_category": "Regular",
"risk_category": "Low",
"onboarding_date": date.today(),
"gst_applicable": True,
"income_tax_applicable": True,
"is_active": True,
},
)
ids["CLIENT_A_ID"] = client_a.id
ids["CLIENT_B_ID"] = client_b.id
# Service catalogue and task templates
gst_cat = ensure_service_category("GST", "GST Compliance", 10)
tax_cat = ensure_service_category("IT", "Income Tax", 20)
audit_cat = ensure_service_category("AUDIT", "Audit & Assurance", 30)
roc_cat = ensure_service_category("ROC", "ROC / MCA", 40)
gst3b = ensure_catalogue("GST-GSTR3B-M", "GSTR-3B Monthly Filing", gst_cat, "monthly", "non_assurance", 10)
gstr1 = ensure_catalogue("GST-GSTR1-M", "GSTR-1 Monthly Filing", gst_cat, "monthly", "non_assurance", 11)
itr = ensure_catalogue("IT-ITR-COMP", "Company Income Tax Return", tax_cat, "yearly", "non_assurance", 20)
audit = ensure_catalogue("AUD-TAX-AUDIT", "Tax Audit", audit_cat, "yearly", "assurance", 30)
roc = ensure_catalogue("ROC-AOC4-MGT7", "AOC-4 and MGT-7 Filing", roc_cat, "yearly", "non_assurance", 40)
ids["SERVICE_A_ID"] = gst3b.id
task_specs = {
gst3b: [(1, "Collect GST data", "Staff", False, "GST_DATA"), (2, "Prepare GSTR-3B working", "Staff", True, "GST_WORKING"), (3, "Partner review and filing", "Partner", True, "GST_RETURN")],
gstr1: [(1, "Collect sales register", "Staff", False, "SALES_REGISTER"), (2, "Prepare GSTR-1", "Staff", True, "GST_RETURN")],
itr: [(1, "Collect financial statements", "Staff", False, "FINANCIALS"), (2, "Prepare computation", "Staff", True, "COMPUTATION"), (3, "Review and file return", "Partner", True, "IT_RETURN")],
audit: [(1, "Plan audit", "Partner", True, "AUDIT_PLAN"), (2, "Execute audit checklist", "Staff", True, "AUDIT_WORKING"), (3, "Issue audit report", "Partner", True, "AUDIT_REPORT")],
roc: [(1, "Collect signed financials", "Staff", False, "SIGNED_FS"), (2, "Prepare forms", "Staff", True, "ROC_FORM"), (3, "File MCA forms", "Partner", True, "ROC_CHALLAN")],
}
firm_tasks_a = {}
firm_tasks_b = {}
for svc, specs in task_specs.items():
enable_firm_service(tenant_a, branch_a, svc, firm_admin)
enable_firm_service(tenant_b, branch_b, svc, tenant_b_admin)
for seq, task_name, role, review, doc_type in specs:
ensure_default_task(svc, seq, task_name, role, review)
fta = ensure_firm_task(tenant_a, svc, seq, task_name, role, firm_admin, review)
ftb = ensure_firm_task(tenant_b, svc, seq, task_name, role, tenant_b_admin, review)
ensure_doc_requirement(tenant_a, svc, fta, f"{doc_type} Document", doc_type)
ensure_doc_requirement(tenant_b, svc, ftb, f"{doc_type} Document", doc_type)
firm_tasks_a[(svc.service_code, seq)] = fta
firm_tasks_b[(svc.service_code, seq)] = ftb
gst3b_rule = ensure_due_rule(gst3b, "GSTR-3B due date", "monthly", 20, None, 1)
gstr1_rule = ensure_due_rule(gstr1, "GSTR-1 due date", "monthly", 11, None, 1)
itr_rule = ensure_due_rule(itr, "Company ITR due date", "yearly", 31, 10, 0)
audit_rule = ensure_due_rule(audit, "Tax audit due date", "yearly", 30, 9, 0)
roc_rule = ensure_due_rule(roc, "ROC annual filing due date", "yearly", 29, 10, 0)
sel_gst_a = enable_firm_service(tenant_a, branch_a, gst3b, firm_admin)
sel_gst_b = enable_firm_service(tenant_b, branch_b, gst3b, tenant_b_admin)
sub_a = ensure_subscription(tenant_a, branch_a, client_a, gst3b, sel_gst_a, partner, manager, staff, gst3b_rule)
sub_b = ensure_subscription(tenant_b, branch_b, client_b, gst3b, sel_gst_b, tenant_b_admin, tenant_b_admin, branch2staff, gst3b_rule)
ids["ENGAGEMENT_A_ID"] = sub_a.id
ids["ENGAGEMENT_B_ID"] = sub_b.id
task_a = ensure_task_instance(tenant_a, branch_a, client_a, sub_a, gst3b, firm_tasks_a[("GST-GSTR3B-M", 1)], staff, 5)
ensure_task_instance(tenant_a, branch_a, client_a, sub_a, gst3b, firm_tasks_a[("GST-GSTR3B-M", 2)], staff, 7)
ensure_task_instance(tenant_a, branch_a, client_a, sub_a, gst3b, firm_tasks_a[("GST-GSTR3B-M", 3)], partner, 9)
ensure_task_instance(tenant_b, branch_b, client_b, sub_b, gst3b, firm_tasks_b[("GST-GSTR3B-M", 1)], branch2staff, 5)
# Notice case
if NoticeCase is not None:
nc, _ = get_or_create(
NoticeCase,
{"reference_no": "UAT-NOTICE-001"},
{
"tenant_id": tenant_a.id,
"branch_id": branch_a.id,
"client_id": client_a.id,
"case_code": "UAT-NC-A-001",
"department": "GST",
"case_type": "Notice",
"title": "UAT GST Notice",
"status": "Open",
"notice_date": date.today(),
"due_date": date.today() + timedelta(days=15),
"issue_summary": "Seed notice case for Playwright tests",
},
)
ids["NOTICE_CASE_A_ID"] = nc.id
# Document storage agent node + sample engagement document/version/job
secret_hash = hashlib.sha256(LSA_SECRET.encode("utf-8")).hexdigest()
node_a, _ = get_or_create(
BranchStorageNode,
{"tenant_id": tenant_a.id, "branch_id": branch_a.id, "node_code": "UAT-LSA-A"},
{
"node_name": "UAT Branch A Local Storage Agent",
"connector_url": None,
"secret_key_hash": secret_hash,
"storage_root_path": "D:/UAT/BranchA/Documents",
"storage_mode": "pull_jobs",
"status": "active",
"is_active": True,
"quota_limit_bytes": 1024 * 1024 * 1024,
"used_storage_bytes": 0,
"subscription_required": False,
"created_by_user_id": firm_admin.id,
},
)
ids["STORAGE_NODE_A_ID"] = node_a.id
doc_a, _ = get_or_create(
EngagementDocument,
{"tenant_id": tenant_a.id, "engagement_id": sub_a.id, "document_code": "UAT-DOC-A-001"},
{
"branch_id": branch_a.id,
"client_id": client_a.id,
"task_instance_id": task_a.id,
"financial_year": ACTIVE_FY,
"assessment_year": ASSESSMENT_YEAR,
"document_type": "GST_DATA",
"title": "UAT GST Working Document",
"description": "Seeded document for UAT/VAPT tests",
"current_version_no": 1,
"status": "active",
"is_deleted": False,
"created_by_user_id": staff.id,
"updated_by_user_id": staff.id,
},
)
sample_hash = hashlib.sha256(b"UAT seeded document content\n").hexdigest()
ver_a, _ = get_or_create(
EngagementDocumentVersion,
{"document_id": doc_a.id, "version_no": 1},
{
"tenant_id": tenant_a.id,
"branch_id": branch_a.id,
"client_id": client_a.id,
"engagement_id": sub_a.id,
"original_filename": "uat-gst-working.txt",
"stored_filename": "uat-gst-working-v1.txt",
"content_type": "text/plain",
"file_size_bytes": len(b"UAT seeded document content\n"),
"file_hash_sha256": sample_hash,
"storage_backend": "LOCAL_YEAR_WISE",
"local_relative_path": "UAT-A/UAT-CL-A/2025-26/uat-gst-working-v1.txt",
"storage_status": "stored",
"remarks": "Seeded UAT version",
"uploaded_by_user_id": staff.id,
},
)
get_or_create(
DocumentStorageJob,
{"storage_node_id": node_a.id, "document_id": doc_a.id, "version_id": ver_a.id},
{
"tenant_id": tenant_a.id,
"branch_id": branch_a.id,
"job_type": "store_version",
"status": "pending",
"priority": 5,
"staging_relative_path": "staging/uat-gst-working-v1.txt",
"target_relative_path": "UAT-A/UAT-CL-A/2025-26/uat-gst-working-v1.txt",
"file_size_bytes": ver_a.file_size_bytes,
"expected_hash_sha256": sample_hash,
"attempts": 0,
"created_by_user_id": staff.id,
},
)
ids["TASK_DOCUMENT_A_ID"] = doc_a.id
db.commit()
print("\nSeed completed. Paste these into the Playwright .env:\n")
for k, v in ids.items():
print(f"{k}={v}")
print("\nUse these login emails in Playwright .env:")
print("SYSTEM_ADMIN_EMAIL=uat.admin@vavalam.com")
print("FIRM_ADMIN_EMAIL=uat.firmadmin@vavalam.com")
print("PARTNER_EMAIL=uat.partner@vavalam.com")
print("MANAGER_EMAIL=uat.manager@vavalam.com")
print("STAFF_EMAIL=uat.staff@vavalam.com")
print("STAFF2_EMAIL=uat.staff2@vavalam.com")
print("BRANCH2_STAFF_EMAIL=uat.branch2staff@vavalam.com")
print("CLIENT_EMAIL=uat.client@vavalam.com")
print("CLIENT2_EMAIL=uat.client2@vavalam.com")
print("CONSULTANT_EMAIL=uat.consultant@vavalam.com")
print("INVITE_TEST_EMAIL=uat.invite@vavalam.com")
print("RESET_TEST_EMAIL=uat.reset@vavalam.com")
print("LOCKED_USER_EMAIL=uat.lockeduser@vavalam.com")
print("TENANT_B_ADMIN_EMAIL=uat.firmadminb@vavalam.com")
print(f"\nSeed password for all UAT ERP users: {PASSWORD}")
print(f"Local storage agent test secret: {LSA_SECRET}")
print("\nNOTE: Create mailbox uat.firmadminb@vavalam.com if OTP login is required for Tenant B Admin tests.")
except Exception:
db.rollback()
raise
finally:
db.close()
if __name__ == "__main__":
main()