Prepare ERP source for Gitea deployment

This commit is contained in:
A R R R Associates
2026-06-20 15:01:44 +05:30
commit 5c75eb6bd9
450 changed files with 67698 additions and 0 deletions
+305
View File
@@ -0,0 +1,305 @@
from __future__ import annotations
from io import BytesIO
from typing import Any
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Font
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.modules.services.models import FirmServiceTaskTemplate, ServiceCatalogue
def build_import_template_workbook() -> bytes:
wb = Workbook()
ws_services = wb.active
ws_services.title = "service_catalogue"
service_headers = [
"service_code",
"service_name",
"category",
"description",
"recurrence_type",
"is_active",
"is_client_requestable",
"is_consultant_requestable",
]
ws_services.append(service_headers)
ws_services.append([
"GST-MONTHLY",
"GST Monthly Return Filing",
"GST",
"Monthly GST compliance service",
"MONTHLY",
"TRUE",
"TRUE",
"FALSE",
])
ws_tasks = wb.create_sheet("firm_task_templates")
task_headers = [
"service_code",
"sequence_no",
"task_name",
"description",
"default_role_name",
"sla_days",
"is_mandatory",
"requires_review",
"is_active",
]
ws_tasks.append(task_headers)
ws_tasks.append([
"GST-MONTHLY",
1,
"Collect Purchase and Sales Data",
"Collect source data from client",
"Staff",
3,
"TRUE",
"FALSE",
"TRUE",
])
ws_tasks.append([
"GST-MONTHLY",
2,
"Review and File Return",
"Manager review and final filing",
"Partner",
2,
"TRUE",
"TRUE",
"TRUE",
])
for ws in [ws_services, ws_tasks]:
for cell in ws[1]:
cell.font = Font(bold=True)
for col in ws.columns:
max_len = 0
col_letter = col[0].column_letter
for cell in col:
val = "" if cell.value is None else str(cell.value)
max_len = max(max_len, len(val))
ws.column_dimensions[col_letter].width = min(max(max_len + 2, 14), 40)
out = BytesIO()
wb.save(out)
return out.getvalue()
def _norm_text(value: Any) -> str:
if value is None:
return ""
return str(value).strip()
def _norm_upper(value: Any) -> str:
return _norm_text(value).upper()
def _norm_bool(value: Any, default: bool = False) -> bool:
if value is None or value == "":
return default
if isinstance(value, bool):
return value
return str(value).strip().lower() in {"1", "true", "yes", "y", "on"}
def _norm_int(value: Any, default: int | None = None) -> int | None:
if value is None or value == "":
return default
try:
return int(value)
except Exception:
return default
def _sheet_headers(ws) -> dict[str, int]:
headers = {}
first_row = next(ws.iter_rows(min_row=1, max_row=1, values_only=True), [])
for idx, val in enumerate(first_row):
key = _norm_text(val).lower()
if key:
headers[key] = idx
return headers
def _cell(row: tuple, headers: dict[str, int], key: str) -> Any:
idx = headers.get(key.lower())
if idx is None or idx >= len(row):
return None
return row[idx]
def parse_import_workbook(file_bytes: bytes) -> dict:
wb = load_workbook(BytesIO(file_bytes), data_only=True)
errors: list[str] = []
catalogue_rows: list[dict] = []
task_rows: list[dict] = []
if "service_catalogue" not in wb.sheetnames:
return {"ok": False, "errors": ["Workbook must contain a sheet named 'service_catalogue'."], "catalogue_rows": [], "task_rows": []}
ws_services = wb["service_catalogue"]
headers = _sheet_headers(ws_services)
for h in ["service_code", "service_name"]:
if h not in headers:
errors.append(f"Service catalogue sheet missing required column: {h}")
for row_no, row in enumerate(ws_services.iter_rows(min_row=2, values_only=True), start=2):
service_code = _norm_upper(_cell(row, headers, "service_code"))
service_name = _norm_text(_cell(row, headers, "service_name"))
if not service_code and not service_name:
continue
if not service_code:
errors.append(f"Service catalogue row {row_no}: service_code is required.")
continue
if not service_name:
errors.append(f"Service catalogue row {row_no}: service_name is required.")
continue
catalogue_rows.append({
"service_code": service_code,
"service_name": service_name,
"category": _norm_text(_cell(row, headers, "category")) or None,
"description": _norm_text(_cell(row, headers, "description")) or None,
"recurrence_type": _norm_text(_cell(row, headers, "recurrence_type")) or None,
"is_active": _norm_bool(_cell(row, headers, "is_active"), True),
"is_client_requestable": _norm_bool(_cell(row, headers, "is_client_requestable"), False),
"is_consultant_requestable": _norm_bool(_cell(row, headers, "is_consultant_requestable"), False),
})
if "firm_task_templates" in wb.sheetnames:
ws_tasks = wb["firm_task_templates"]
task_headers = _sheet_headers(ws_tasks)
for h in ["service_code", "sequence_no", "task_name"]:
if h not in task_headers:
errors.append(f"Firm task templates sheet missing required column: {h}")
for row_no, row in enumerate(ws_tasks.iter_rows(min_row=2, values_only=True), start=2):
service_code = _norm_upper(_cell(row, task_headers, "service_code"))
task_name = _norm_text(_cell(row, task_headers, "task_name"))
sequence_no = _norm_int(_cell(row, task_headers, "sequence_no"))
if not service_code and not task_name:
continue
if not service_code:
errors.append(f"Firm task templates row {row_no}: service_code is required.")
continue
if not task_name:
errors.append(f"Firm task templates row {row_no}: task_name is required.")
continue
if sequence_no is None:
errors.append(f"Firm task templates row {row_no}: sequence_no must be numeric.")
continue
task_rows.append({
"service_code": service_code,
"sequence_no": sequence_no,
"task_name": task_name,
"description": _norm_text(_cell(row, task_headers, "description")) or None,
"default_role_name": _norm_text(_cell(row, task_headers, "default_role_name")) or None,
"sla_days": _norm_int(_cell(row, task_headers, "sla_days")),
"is_mandatory": _norm_bool(_cell(row, task_headers, "is_mandatory"), True),
"requires_review": _norm_bool(_cell(row, task_headers, "requires_review"), False),
"is_active": _norm_bool(_cell(row, task_headers, "is_active"), True),
})
service_codes = {row["service_code"] for row in catalogue_rows}
for row in task_rows:
if row["service_code"] not in service_codes:
errors.append(f"Task row for service_code '{row['service_code']}' does not match any service in service_catalogue sheet.")
return {"ok": len(errors) == 0, "errors": errors, "catalogue_rows": catalogue_rows, "task_rows": task_rows}
def apply_import_payload(
db: Session,
*,
tenant_id: int,
branch_id: int | None,
actor_user_id: int,
catalogue_rows: list[dict],
task_rows: list[dict],
) -> dict:
created_catalogue = 0
updated_catalogue = 0
created_tasks = 0
updated_tasks = 0
catalogue_map: dict[str, ServiceCatalogue] = {}
for row in catalogue_rows:
service = db.execute(select(ServiceCatalogue).where(ServiceCatalogue.service_code == row["service_code"])).scalar_one_or_none()
if service is None:
service = ServiceCatalogue(
service_code=row["service_code"],
service_name=row["service_name"],
category=row["category"],
description=row["description"],
recurrence_type=row["recurrence_type"],
is_active=row["is_active"],
is_client_requestable=row["is_client_requestable"],
is_consultant_requestable=row["is_consultant_requestable"],
created_by_user_id=actor_user_id,
updated_by_user_id=actor_user_id,
)
db.add(service)
db.flush()
created_catalogue += 1
else:
service.service_name = row["service_name"]
service.category = row["category"]
service.description = row["description"]
service.recurrence_type = row["recurrence_type"]
service.is_active = row["is_active"]
service.is_client_requestable = row["is_client_requestable"]
service.is_consultant_requestable = row["is_consultant_requestable"]
service.updated_by_user_id = actor_user_id
updated_catalogue += 1
catalogue_map[row["service_code"]] = service
for row in task_rows:
service = catalogue_map[row["service_code"]]
task = db.execute(
select(FirmServiceTaskTemplate).where(
FirmServiceTaskTemplate.tenant_id == tenant_id,
FirmServiceTaskTemplate.service_catalogue_id == service.id,
FirmServiceTaskTemplate.sequence_no == row["sequence_no"],
)
).scalar_one_or_none()
if task is None:
task = FirmServiceTaskTemplate(
tenant_id=tenant_id,
branch_id=branch_id,
service_catalogue_id=service.id,
sequence_no=row["sequence_no"],
task_name=row["task_name"],
description=row["description"],
default_role_name=row["default_role_name"],
sla_days=row["sla_days"],
is_mandatory=row["is_mandatory"],
requires_review=row["requires_review"],
is_active=row["is_active"],
created_by_user_id=actor_user_id,
updated_by_user_id=actor_user_id,
)
db.add(task)
created_tasks += 1
else:
task.task_name = row["task_name"]
task.description = row["description"]
task.default_role_name = row["default_role_name"]
task.sla_days = row["sla_days"]
task.is_mandatory = row["is_mandatory"]
task.requires_review = row["requires_review"]
task.is_active = row["is_active"]
task.branch_id = branch_id
task.updated_by_user_id = actor_user_id
updated_tasks += 1
db.commit()
return {
"created_catalogue": created_catalogue,
"updated_catalogue": updated_catalogue,
"created_tasks": created_tasks,
"updated_tasks": updated_tasks,
}