from datetime import date from typing import Optional from sqlalchemy import func, select, text, update from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import joinedload from src.db.models import Vsp from src.domain.schemas import VSPCreate class VSPRepository: def __init__(self, db: AsyncSession): self.db = db async def get(self, vsp_id: int, is_active: bool | None = None, load_org_unit: bool = False) -> Optional[Vsp]: queries = [Vsp.id == vsp_id, Vsp.is_deleted.is_(False)] if is_active is not None: queries.append(Vsp.is_active == is_active) if load_org_unit: return ( (await self.db.execute( select(Vsp).where(*queries).options(joinedload(Vsp.org_unit)).limit(1)) ).scalars().first() ) else: return ( (await self.db.execute(select(Vsp).where(*queries).limit(1))).scalars().first() ) async def get_list( self, registration_number: str | None = None, address: str | None = None, ssp_ids: list[int] | None = None, open_date_start: date | None = None, open_date_end: date | None = None, placement_type: str | None = None, staff_count_min: int | None = None, staff_count_max: int | None = None, is_active: bool | None = True, close_date_is_null: bool | None = None, load_org_unit: bool = False, with_count: bool = False, ) -> list[Vsp] | tuple[int, list[Vsp]]: if with_count: query = select(func.count().over().label("total_count"), Vsp) else: query = select(Vsp) queries = [Vsp.is_deleted.is_(False)] filters = [ ("registration_number", registration_number), ("address", address), ("placement_type", placement_type), ("open_date_start", open_date_start), ("open_date_end", open_date_end), ("staff_count_min", staff_count_min), ("staff_count_max", staff_count_max), ("is_active", is_active), ] for filter_name, filter_value in filters: # print(filter_name, filter_value) match filter_name, filter_value: case ("registration_number", str(value)) if value: queries.append(Vsp.reg_number.ilike(f"%{value}%")) case ("address", str(value)) if value: queries.append(Vsp.address.ilike(f"%{value}%")) case ("placement_type", str(value)) if value: queries.append(Vsp.placement_type.ilike(f"%{value}%")) case ("open_date_start", value) if value is not None: queries.append(Vsp.opened_at >= value) case ("open_date_end", value) if value is not None: queries.append(Vsp.opened_at <= value) case ("staff_count_min", value) if value is not None: queries.append(Vsp.staff_count >= value) case ("staff_count_max", value) if value is not None: queries.append(Vsp.staff_count <= value) case ("is_active", value) if value is not None: queries.append(Vsp.is_active == value) case _: continue if close_date_is_null is True: queries.append(Vsp.closed_at.is_(None)) elif close_date_is_null is False: queries.append(Vsp.closed_at.is_not(None)) if ssp_ids is not None: if not ssp_ids: if with_count: return 0, [] return [] queries.append(Vsp.branch_id.in_(ssp_ids)) query = query.where(*queries) if load_org_unit: query = query.options(joinedload(Vsp.org_unit)) query = query.order_by(Vsp.id) if with_count: result = (await self.db.execute(query)).all() if result: return result[0][0], [res[1] for res in result] else: count_query = select(func.count(Vsp.id)) if query.whereclause is not None: count_query = count_query.where( query.whereclause ) return (await self.db.execute(count_query)).scalar(), [] else: return (await self.db.execute(query)).scalars().all() return (await self.db.execute(query.order_by(Vsp.id))).scalars().all() async def create(self, payload: VSPCreate, created_by: int, load_org_unit: bool = False) -> Vsp: data = payload.model_dump() vsp_id = ( await self.db.execute( text(""" SELECT v3.add_vsp( :p_branch_id, :p_reg_number, :p_address, :p_format, :p_opened_at, :p_placement_type, :p_staff_count, :p_total_area, :p_closed_at, :p_is_active, :p_is_deleted, :p_system_code, :p_created_by, :p_vsp_type, :p_notes, :p_rent_contract_num, :p_rent_end_date ) """), { "p_branch_id": data.get("branch_id"), "p_reg_number": data.get("reg_number"), "p_address": data.get("address"), "p_format": data.get("format"), "p_opened_at": data.get("opened_at"), "p_placement_type": data.get("placement_type"), "p_staff_count": data.get("staff_count"), "p_total_area": data.get("total_area"), "p_closed_at": data.get("closed_at"), "p_is_active": data.get("is_active", True), "p_is_deleted": data.get("is_deleted", False), "p_system_code": data.get("system_code"), "p_created_by": created_by, "p_vsp_type": data.get("vsp_type"), "p_notes": data.get("notes"), "p_rent_contract_num": data.get("rent_contract_num"), "p_rent_end_date": data.get("rent_end_date"), } ) ).scalar() await self.db.flush() if load_org_unit: return ( await self.db.execute( select(Vsp).where(Vsp.id == vsp_id).options(joinedload(Vsp.org_unit)).limit(1) ) ).scalars().first() else: return ( await self.db.execute( select(Vsp).where(Vsp.id == vsp_id).limit(1) ) ).scalars().first() async def update(self, vsp: Vsp, data: dict, updated_by: int, load_org_unit: bool = False) -> Vsp: FIELD_TO_PARAM = { "system_code": "p_system_code", "vsp_type": "p_vsp_type", "reg_number": "p_reg_number", "address": "p_address", "opened_at": "p_opened_at", "closed_at": "p_closed_at", "format": "p_format", "placement_type": "p_placement_type", "staff_count": "p_staff_count", "notes": "p_notes", "total_area": "p_total_area", "rent_contract_num": "p_rent_contract_num", "rent_end_date": "p_rent_end_date", "branch_id": "p_branch_id", "is_active": "p_is_active", "is_deleted": "p_is_deleted", } params = {"p_vsp_id": vsp.id, "p_updated_by": updated_by} for field, param in FIELD_TO_PARAM.items(): params[param] = data.get(field) await self.db.execute( text(""" SELECT v3.upd_vsp( :p_vsp_id, :p_branch_id, :p_reg_number, :p_address, :p_format, :p_opened_at, :p_placement_type, :p_staff_count, :p_total_area, :p_closed_at, :p_is_active, :p_is_deleted, :p_system_code, :p_updated_by, :p_vsp_type, :p_notes, :p_rent_contract_num, :p_rent_end_date ) """), params, ) await self.db.flush() await self.db.refresh(vsp) if load_org_unit: return ( await self.db.execute( select(Vsp).where(Vsp.id == vsp.id).options(joinedload(Vsp.org_unit)).limit(1) ) ).scalars().first() else: return vsp async def deactivate_by_close_date(self, as_of: date) -> int: result = await self.db.execute( update(Vsp) .where( Vsp.is_active.is_(True), Vsp.closed_at.is_not(None), Vsp.closed_at <= as_of, ) .values(is_active=False, updated_at=func.now()) ) await self.db.commit() return result.rowcount or 0 async def logical_delete(self, vsp_id: int, updated_by: int) -> bool: result = ( await self.db.execute( text("SELECT v3.del_vsp(:p_vsp_id, :p_deleted_by)"), {"p_vsp_id": vsp_id, "p_deleted_by": updated_by}, ) ).scalar() await self.db.flush() return result is not None