Prepare ERP source for Gitea deployment
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from sqlalchemy import func, or_, select
|
||||
from sqlalchemy.orm import Session, selectinload
|
||||
|
||||
from app.modules.core.tenancy.models import Branch
|
||||
from app.modules.services.models import (
|
||||
FirmServiceSelection,
|
||||
FirmServiceTaskTemplate,
|
||||
ServiceCatalogue,
|
||||
ServiceCategory,
|
||||
ServiceDefaultTaskTemplate,
|
||||
ServiceDueDateRule,
|
||||
)
|
||||
|
||||
RECURRENCE_CHOICES = [
|
||||
("one_time", "One Time"),
|
||||
("monthly", "Monthly"),
|
||||
("quarterly", "Quarterly"),
|
||||
("yearly", "Yearly"),
|
||||
("event_based", "Event Based"),
|
||||
("custom", "Custom"),
|
||||
]
|
||||
|
||||
ENGAGEMENT_TYPE_CHOICES = [
|
||||
("assurance", "Assurance"),
|
||||
("non_assurance", "Non-Assurance"),
|
||||
]
|
||||
|
||||
VALID_ENGAGEMENT_TYPES = {value for value, _label in ENGAGEMENT_TYPE_CHOICES}
|
||||
|
||||
|
||||
def normalize_engagement_type(value: str | None) -> str:
|
||||
value = (value or "").strip().lower().replace("-", "_").replace(" ", "_")
|
||||
if value in {"assurance", "audit", "aud", "certification", "certificate", "attestation"}:
|
||||
return "assurance"
|
||||
if value in {"non_assurance", "nonassurance", "non_audit", "nonaudit", "non", "compliance", "consulting", "consultancy"}:
|
||||
return "non_assurance"
|
||||
return "non_assurance"
|
||||
|
||||
|
||||
def engagement_type_label(value: str | None) -> str:
|
||||
normalized = normalize_engagement_type(value)
|
||||
return "Assurance" if normalized == "assurance" else "Non-Assurance"
|
||||
|
||||
|
||||
def normalize_code(value: str) -> str:
|
||||
value = (value or "").strip().upper()
|
||||
value = re.sub(r"[^A-Z0-9]+", "-", value)
|
||||
value = re.sub(r"-+", "-", value).strip("-")
|
||||
return value
|
||||
|
||||
|
||||
def list_categories(db: Session, *, q: str = ""):
|
||||
query = select(ServiceCategory)
|
||||
if q.strip():
|
||||
term = f"%{q.strip()}%"
|
||||
query = query.where(or_(ServiceCategory.code.ilike(term), ServiceCategory.name.ilike(term)))
|
||||
return db.execute(query.order_by(ServiceCategory.sort_order.asc(), ServiceCategory.name.asc())).scalars().all()
|
||||
|
||||
|
||||
def get_category(db: Session, category_id: int) -> ServiceCategory | None:
|
||||
return db.execute(select(ServiceCategory).where(ServiceCategory.id == category_id)).scalar_one_or_none()
|
||||
|
||||
|
||||
def list_catalogue_payload(db: Session, *, q: str = "", category_id: int | None = None, recurrence_type: str = "", engagement_type: str = "", page: int = 1, per_page: int = 20):
|
||||
query = select(ServiceCatalogue).options(
|
||||
selectinload(ServiceCatalogue.service_category),
|
||||
selectinload(ServiceCatalogue.default_task_templates),
|
||||
selectinload(ServiceCatalogue.due_date_rules),
|
||||
)
|
||||
if q.strip():
|
||||
term = f"%{q.strip()}%"
|
||||
query = query.outerjoin(ServiceCategory, ServiceCategory.id == ServiceCatalogue.category_id).where(
|
||||
or_(
|
||||
ServiceCatalogue.service_code.ilike(term),
|
||||
ServiceCatalogue.service_name.ilike(term),
|
||||
ServiceCatalogue.category.ilike(term),
|
||||
ServiceCategory.name.ilike(term),
|
||||
)
|
||||
)
|
||||
if category_id:
|
||||
query = query.where(ServiceCatalogue.category_id == category_id)
|
||||
if recurrence_type.strip():
|
||||
query = query.where(ServiceCatalogue.recurrence_type == recurrence_type.strip())
|
||||
if engagement_type.strip():
|
||||
query = query.where(ServiceCatalogue.engagement_type == normalize_engagement_type(engagement_type))
|
||||
|
||||
total = db.execute(select(func.count()).select_from(query.subquery())).scalar_one()
|
||||
rows = db.execute(
|
||||
query.order_by(ServiceCatalogue.sort_order.asc(), ServiceCatalogue.service_name.asc())
|
||||
.offset((page - 1) * per_page)
|
||||
.limit(per_page)
|
||||
).scalars().all()
|
||||
|
||||
return {
|
||||
"rows": rows,
|
||||
"q": q,
|
||||
"category_id": category_id,
|
||||
"recurrence_type": recurrence_type,
|
||||
"engagement_type": engagement_type,
|
||||
"page": page,
|
||||
"per_page": per_page,
|
||||
"total": total,
|
||||
"pages": max(1, (total + per_page - 1) // per_page),
|
||||
}
|
||||
|
||||
|
||||
def list_firm_services_payload(db: Session, *, tenant_id: int, q: str = ""):
|
||||
query = (
|
||||
select(FirmServiceSelection, ServiceCatalogue, Branch)
|
||||
.join(ServiceCatalogue, ServiceCatalogue.id == FirmServiceSelection.service_catalogue_id)
|
||||
.outerjoin(Branch, Branch.id == FirmServiceSelection.default_branch_id)
|
||||
.where(
|
||||
FirmServiceSelection.tenant_id == tenant_id,
|
||||
FirmServiceSelection.is_enabled.is_(True),
|
||||
)
|
||||
)
|
||||
if q.strip():
|
||||
term = f"%{q.strip()}%"
|
||||
query = query.outerjoin(ServiceCategory, ServiceCategory.id == ServiceCatalogue.category_id).where(
|
||||
or_(
|
||||
ServiceCatalogue.service_code.ilike(term),
|
||||
ServiceCatalogue.service_name.ilike(term),
|
||||
ServiceCatalogue.category.ilike(term),
|
||||
ServiceCategory.name.ilike(term),
|
||||
)
|
||||
)
|
||||
rows = db.execute(query.order_by(ServiceCatalogue.sort_order.asc(), ServiceCatalogue.service_name.asc())).all()
|
||||
return [
|
||||
{"selection": selection, "catalogue": catalogue, "branch": branch}
|
||||
for selection, catalogue, branch in rows
|
||||
]
|
||||
|
||||
|
||||
def list_disabled_catalogues(db: Session, *, tenant_id: int, q: str = ""):
|
||||
enabled_subq = (
|
||||
select(FirmServiceSelection.service_catalogue_id)
|
||||
.where(
|
||||
FirmServiceSelection.tenant_id == tenant_id,
|
||||
FirmServiceSelection.is_enabled.is_(True),
|
||||
)
|
||||
)
|
||||
|
||||
query = select(ServiceCatalogue).where(~ServiceCatalogue.id.in_(enabled_subq)).options(
|
||||
selectinload(ServiceCatalogue.service_category),
|
||||
selectinload(ServiceCatalogue.default_task_templates),
|
||||
selectinload(ServiceCatalogue.due_date_rules),
|
||||
)
|
||||
if q.strip():
|
||||
term = f"%{q.strip()}%"
|
||||
query = query.outerjoin(ServiceCategory, ServiceCategory.id == ServiceCatalogue.category_id).where(
|
||||
or_(
|
||||
ServiceCatalogue.service_code.ilike(term),
|
||||
ServiceCatalogue.service_name.ilike(term),
|
||||
ServiceCatalogue.category.ilike(term),
|
||||
ServiceCategory.name.ilike(term),
|
||||
)
|
||||
)
|
||||
return db.execute(query.order_by(ServiceCatalogue.sort_order.asc(), ServiceCatalogue.service_name.asc())).scalars().all()
|
||||
|
||||
|
||||
def get_catalogue(db: Session, catalogue_id: int) -> ServiceCatalogue | None:
|
||||
return db.execute(
|
||||
select(ServiceCatalogue)
|
||||
.options(
|
||||
selectinload(ServiceCatalogue.service_category),
|
||||
selectinload(ServiceCatalogue.default_task_templates),
|
||||
)
|
||||
.where(ServiceCatalogue.id == catalogue_id)
|
||||
).scalar_one_or_none()
|
||||
|
||||
|
||||
def get_firm_selection(db: Session, *, tenant_id: int, catalogue_id: int) -> FirmServiceSelection | None:
|
||||
return db.execute(
|
||||
select(FirmServiceSelection).where(
|
||||
FirmServiceSelection.tenant_id == tenant_id,
|
||||
FirmServiceSelection.service_catalogue_id == catalogue_id,
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
|
||||
|
||||
def get_firm_task_templates(db: Session, *, tenant_id: int, catalogue_id: int):
|
||||
return db.execute(
|
||||
select(FirmServiceTaskTemplate)
|
||||
.where(
|
||||
FirmServiceTaskTemplate.tenant_id == tenant_id,
|
||||
FirmServiceTaskTemplate.service_catalogue_id == catalogue_id,
|
||||
)
|
||||
.order_by(FirmServiceTaskTemplate.sequence_no.asc(), FirmServiceTaskTemplate.id.asc())
|
||||
).scalars().all()
|
||||
|
||||
|
||||
def get_default_task_templates(db: Session, *, catalogue_id: int):
|
||||
return db.execute(
|
||||
select(ServiceDefaultTaskTemplate)
|
||||
.where(ServiceDefaultTaskTemplate.service_catalogue_id == catalogue_id)
|
||||
.order_by(ServiceDefaultTaskTemplate.sequence_no.asc(), ServiceDefaultTaskTemplate.id.asc())
|
||||
).scalars().all()
|
||||
|
||||
|
||||
def next_task_sequence(db: Session, *, tenant_id: int, catalogue_id: int) -> int:
|
||||
max_seq = db.execute(
|
||||
select(func.max(FirmServiceTaskTemplate.sequence_no)).where(
|
||||
FirmServiceTaskTemplate.tenant_id == tenant_id,
|
||||
FirmServiceTaskTemplate.service_catalogue_id == catalogue_id,
|
||||
)
|
||||
).scalar_one()
|
||||
return int(max_seq or 0) + 1
|
||||
|
||||
|
||||
def next_default_task_sequence(db: Session, *, catalogue_id: int) -> int:
|
||||
max_seq = db.execute(
|
||||
select(func.max(ServiceDefaultTaskTemplate.sequence_no)).where(
|
||||
ServiceDefaultTaskTemplate.service_catalogue_id == catalogue_id,
|
||||
)
|
||||
).scalar_one()
|
||||
return int(max_seq or 0) + 1
|
||||
|
||||
def get_firm_task_template(
|
||||
db: Session,
|
||||
*,
|
||||
tenant_id: int,
|
||||
catalogue_id: int,
|
||||
task_id: int,
|
||||
) -> FirmServiceTaskTemplate | None:
|
||||
return db.execute(
|
||||
select(FirmServiceTaskTemplate).where(
|
||||
FirmServiceTaskTemplate.id == task_id,
|
||||
FirmServiceTaskTemplate.tenant_id == tenant_id,
|
||||
FirmServiceTaskTemplate.service_catalogue_id == catalogue_id,
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
|
||||
|
||||
def get_default_task_template(
|
||||
db: Session,
|
||||
*,
|
||||
catalogue_id: int,
|
||||
task_id: int,
|
||||
) -> ServiceDefaultTaskTemplate | None:
|
||||
return db.execute(
|
||||
select(ServiceDefaultTaskTemplate).where(
|
||||
ServiceDefaultTaskTemplate.id == task_id,
|
||||
ServiceDefaultTaskTemplate.service_catalogue_id == catalogue_id,
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
Reference in New Issue
Block a user