Prepare ERP source for Gitea deployment
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
from sqlalchemy import or_, select
|
||||
from sqlalchemy.orm import Session, selectinload
|
||||
|
||||
from app.modules.clients.models import Client
|
||||
from app.modules.core.iam.models import User
|
||||
from app.modules.core.rbac.models import Role, UserRole
|
||||
from app.modules.core.tenancy.models import Tenant
|
||||
from app.modules.services.models import ClientServiceSubscription, FirmServiceSelection, ServiceCatalogue
|
||||
|
||||
SUBSCRIPTION_STATUSES = [
|
||||
("draft", "Draft"),
|
||||
("active", "Active"),
|
||||
("on_hold", "On Hold"),
|
||||
("completed", "Completed"),
|
||||
("cancelled", "Cancelled"),
|
||||
("inactive", "Inactive"),
|
||||
]
|
||||
|
||||
ASSIGNMENT_ROLE_NAMES = ("Partner", "Branch Manager", "Staff")
|
||||
|
||||
|
||||
def current_financial_year(today: date | None = None) -> str:
|
||||
today = today or date.today()
|
||||
if today.month >= 4:
|
||||
start = today.year
|
||||
else:
|
||||
start = today.year - 1
|
||||
return f"{start}-{str(start + 1)[-2:]}"
|
||||
|
||||
|
||||
def assessment_year_from_financial_year(financial_year: str | None) -> str | None:
|
||||
if not financial_year or "-" not in financial_year:
|
||||
return None
|
||||
start = int(str(financial_year).split("-")[0])
|
||||
return f"{start + 1}-{str(start + 2)[-2:]}"
|
||||
|
||||
|
||||
def normalize_financial_year(value: str | None) -> str:
|
||||
value = (value or "").strip()
|
||||
return value or current_financial_year()
|
||||
|
||||
|
||||
def parse_date(value: str | None) -> date | None:
|
||||
if not value:
|
||||
return None
|
||||
value = value.strip()
|
||||
if not value:
|
||||
return None
|
||||
return date.fromisoformat(value)
|
||||
|
||||
|
||||
def list_subscription_payload(
|
||||
db: Session,
|
||||
*,
|
||||
tenant_id: int,
|
||||
branch_id: int | None = None,
|
||||
financial_year: str | None = None,
|
||||
q: str = "",
|
||||
include_inactive: bool = True,
|
||||
):
|
||||
fy = normalize_financial_year(financial_year)
|
||||
query = (
|
||||
select(ClientServiceSubscription)
|
||||
.options(
|
||||
selectinload(ClientServiceSubscription.client),
|
||||
selectinload(ClientServiceSubscription.catalogue),
|
||||
selectinload(ClientServiceSubscription.assigned_partner),
|
||||
selectinload(ClientServiceSubscription.assigned_manager),
|
||||
selectinload(ClientServiceSubscription.assigned_staff),
|
||||
selectinload(ClientServiceSubscription.review_partner),
|
||||
selectinload(ClientServiceSubscription.due_date_rule),
|
||||
)
|
||||
.where(
|
||||
ClientServiceSubscription.tenant_id == tenant_id,
|
||||
ClientServiceSubscription.financial_year == fy,
|
||||
)
|
||||
)
|
||||
|
||||
if branch_id:
|
||||
query = query.where(ClientServiceSubscription.branch_id == branch_id)
|
||||
|
||||
if not include_inactive:
|
||||
query = query.where(ClientServiceSubscription.is_active.is_(True))
|
||||
|
||||
if q.strip():
|
||||
term = f"%{q.strip()}%"
|
||||
query = (
|
||||
query.join(Client, Client.id == ClientServiceSubscription.client_id)
|
||||
.join(ServiceCatalogue, ServiceCatalogue.id == ClientServiceSubscription.service_catalogue_id)
|
||||
.where(
|
||||
or_(
|
||||
Client.client_name.ilike(term),
|
||||
Client.client_code.ilike(term),
|
||||
ServiceCatalogue.service_code.ilike(term),
|
||||
ServiceCatalogue.service_name.ilike(term),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return db.execute(
|
||||
query.order_by(
|
||||
ClientServiceSubscription.is_locked.asc(),
|
||||
ClientServiceSubscription.is_active.desc(),
|
||||
ClientServiceSubscription.id.desc(),
|
||||
)
|
||||
).scalars().all()
|
||||
|
||||
|
||||
def get_subscription(db: Session, *, subscription_id: int, tenant_id: int) -> ClientServiceSubscription | None:
|
||||
return db.execute(
|
||||
select(ClientServiceSubscription)
|
||||
.options(
|
||||
selectinload(ClientServiceSubscription.client),
|
||||
selectinload(ClientServiceSubscription.catalogue),
|
||||
selectinload(ClientServiceSubscription.assigned_partner),
|
||||
selectinload(ClientServiceSubscription.assigned_manager),
|
||||
selectinload(ClientServiceSubscription.assigned_staff),
|
||||
selectinload(ClientServiceSubscription.review_partner),
|
||||
selectinload(ClientServiceSubscription.due_date_rule),
|
||||
)
|
||||
.where(
|
||||
ClientServiceSubscription.id == subscription_id,
|
||||
ClientServiceSubscription.tenant_id == tenant_id,
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
|
||||
|
||||
def get_existing_subscription(
|
||||
db: Session,
|
||||
*,
|
||||
tenant_id: int,
|
||||
client_id: int,
|
||||
service_catalogue_id: int,
|
||||
financial_year: str | None = None,
|
||||
) -> ClientServiceSubscription | None:
|
||||
return db.execute(
|
||||
select(ClientServiceSubscription).where(
|
||||
ClientServiceSubscription.tenant_id == tenant_id,
|
||||
ClientServiceSubscription.client_id == client_id,
|
||||
ClientServiceSubscription.service_catalogue_id == service_catalogue_id,
|
||||
ClientServiceSubscription.financial_year == normalize_financial_year(financial_year),
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
|
||||
|
||||
def list_clients_for_assignment(db: Session, *, tenant_id: int, branch_id: int | None = None, partner_id: int | None = None):
|
||||
query = select(Client).where(Client.tenant_id == tenant_id)
|
||||
if branch_id:
|
||||
query = query.where(Client.branch_id == branch_id)
|
||||
if partner_id:
|
||||
query = query.where(Client.partner_id == partner_id)
|
||||
return db.execute(query.order_by(Client.client_name.asc())).scalars().all()
|
||||
|
||||
|
||||
def list_enabled_services_for_assignment(db: Session, *, tenant_id: int):
|
||||
return db.execute(
|
||||
select(FirmServiceSelection)
|
||||
.join(ServiceCatalogue, ServiceCatalogue.id == FirmServiceSelection.service_catalogue_id)
|
||||
.options(selectinload(FirmServiceSelection.catalogue))
|
||||
.where(
|
||||
FirmServiceSelection.tenant_id == tenant_id,
|
||||
FirmServiceSelection.is_enabled.is_(True),
|
||||
ServiceCatalogue.is_active.is_(True),
|
||||
)
|
||||
.order_by(ServiceCatalogue.sort_order.asc(), ServiceCatalogue.service_name.asc())
|
||||
).scalars().all()
|
||||
|
||||
|
||||
def get_enabled_firm_service(db: Session, *, tenant_id: int, service_catalogue_id: int) -> FirmServiceSelection | None:
|
||||
return db.execute(
|
||||
select(FirmServiceSelection).where(
|
||||
FirmServiceSelection.tenant_id == tenant_id,
|
||||
FirmServiceSelection.service_catalogue_id == service_catalogue_id,
|
||||
FirmServiceSelection.is_enabled.is_(True),
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
|
||||
|
||||
def list_assignable_users(db: Session, *, tenant_id: int, branch_id: int | None = None, role_names: tuple[str, ...] = ASSIGNMENT_ROLE_NAMES):
|
||||
query = (
|
||||
select(User)
|
||||
.join(UserRole, UserRole.user_id == User.id)
|
||||
.join(Role, Role.id == UserRole.role_id)
|
||||
.where(User.tenant_id == tenant_id, User.is_active.is_(True), Role.name.in_(role_names))
|
||||
)
|
||||
if branch_id:
|
||||
query = query.where(or_(User.branch_id == branch_id, User.branch_id.is_(None)))
|
||||
return db.execute(query.order_by(User.full_name.asc(), User.email.asc()).distinct()).scalars().all()
|
||||
|
||||
|
||||
def tenant_requires_review_partner(db: Session, *, tenant_id: int) -> bool:
|
||||
tenant = db.get(Tenant, tenant_id)
|
||||
firm_type = (getattr(tenant, "firm_type", None) or "partnership").strip().lower() if tenant else "partnership"
|
||||
return firm_type == "partnership"
|
||||
|
||||
|
||||
def review_partner_required_for_engagement(db: Session, *, tenant_id: int, engagement_type: str | None) -> bool:
|
||||
return tenant_requires_review_partner(db, tenant_id=tenant_id) and (engagement_type or "").strip().lower() == "assurance"
|
||||
|
||||
|
||||
def list_review_partners(db: Session, *, tenant_id: int, branch_id: int | None = None):
|
||||
return list_assignable_users(db, tenant_id=tenant_id, branch_id=branch_id, role_names=("Partner",))
|
||||
Reference in New Issue
Block a user