Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added APIs for program summary, application & benefit details #9

Merged
merged 1 commit into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/openg2p_portal_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ class Settings(AuthSettings, Settings):
)
auth_api_submit_form: ApiAuthSettings = ApiAuthSettings(enabled=True)
auth_api_update_profile: ApiAuthSettings = ApiAuthSettings(enabled=True)
auth_api_get_program_summary: ApiAuthSettings = ApiAuthSettings(enabled=True)
auth_api_get_application_details: ApiAuthSettings = ApiAuthSettings(enabled=True)
auth_api_get_benefit_details: ApiAuthSettings = ApiAuthSettings(enabled=True)
2 changes: 1 addition & 1 deletion src/openg2p_portal_api/controllers/oauth_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ async def oauth_callback(self, request: Request):
auth=cookie_utils.get_response_cookies(res, "X-Access-Token")[-1],
id_token=cookie_utils.get_response_cookies(res, "X-ID-Token")[-1],
)
await PartnerService.check_and_create_partner(self, userinfo_dict)
await self.partner_service.check_and_create_partner(userinfo_dict)

return res
58 changes: 54 additions & 4 deletions src/openg2p_portal_api/controllers/program_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ..config import Settings
from ..dependencies import JwtBearerAuth
from ..models.credentials import AuthCredentials
from ..models.program import Program
from ..models.program import ApplicationDetails, BenefitDetails, Program, ProgramSummary
from ..services.program_service import ProgramService

_config = Settings.get_config()
Expand All @@ -18,23 +18,44 @@ def __init__(self, **kwargs):
super().__init__(**kwargs)
self._program_service = ProgramService.get_component()

self.router.prefix += "/program"
# self.router.prefix += "/program"
self.router.tags += ["portal"]

self.router.add_api_route(
"",
"/program",
self.get_programs,
responses={200: {"model": List[Program]}},
methods=["GET"],
)

self.router.add_api_route(
"/{programid}",
"/program/{programid}",
self.get_program_by_id,
responses={200: {"model": Program}},
methods=["GET"],
)

self.router.add_api_route(
"/programdetails",
self.get_program_summary,
responses={200: {"model": List[ProgramSummary]}},
methods=["GET"],
)

self.router.add_api_route(
"/applicationdetails",
self.get_application_details,
responses={200: {"model": List[ApplicationDetails]}},
methods=["GET"],
)

self.router.add_api_route(
"/benefitdetails",
self.get_benefit_details,
responses={200: {"model": List[BenefitDetails]}},
methods=["GET"],
)

@property
def program_service(self):
if not self._program_service:
Expand All @@ -61,3 +82,32 @@ async def get_program_by_id(
return await self.program_service.get_program_by_id_service(
programid, auth.partner_id
)

async def get_program_summary(
self, auth: Annotated[AuthCredentials, Depends(JwtBearerAuth())]
):
if not auth.partner_id:
raise UnauthorizedError(
message="Unauthorized. Partner Not Found in Registry."
)
return await self.program_service.get_program_summary_service(auth.partner_id)

async def get_application_details(
self, auth: Annotated[AuthCredentials, Depends(JwtBearerAuth())]
):
if not auth.partner_id:
raise UnauthorizedError(
message="Unauthorized. Partner Not Found in Registry."
)
return await self.program_service.get_application_details_service(
auth.partner_id
)

async def get_benefit_details(
self, auth: Annotated[AuthCredentials, Depends(JwtBearerAuth())]
):
if not auth.partner_id:
raise UnauthorizedError(
message="Unauthorized. Partner Not Found in Registry."
)
return await self.program_service.get_benefit_details_service(auth.partner_id)
18 changes: 18 additions & 0 deletions src/openg2p_portal_api/models/orm/cycle_membership_orm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from typing import List, Optional

from openg2p_fastapi_common.models import BaseORMModel
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship

from .cycle_orm import CycleORM


class CycleMembershipORM(BaseORMModel):
__tablename__ = "g2p_cycle_membership"

id: Mapped[int] = mapped_column(primary_key=True)
partner_id: Mapped[int] = mapped_column()
cycle_id: Mapped[int] = mapped_column(ForeignKey("g2p_cycle.id"))
cycles: Mapped[Optional[List["CycleORM"]]] = relationship(
back_populates="cycle_memberships"
)
22 changes: 22 additions & 0 deletions src/openg2p_portal_api/models/orm/cycle_orm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import List, Optional

from openg2p_fastapi_common.models import BaseORMModel
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship

# from .cycle_membership_orm import CycleMembershipORM
# from .program_orm import ProgramORM


class CycleORM(BaseORMModel):
__tablename__ = "g2p_cycle"

id: Mapped[int] = mapped_column(primary_key=True)
program_id: Mapped[int] = mapped_column(ForeignKey("g2p_program.id"))
program: Mapped[Optional[List["ProgramORM"]]] = relationship(
back_populates="cycles"
)
cycle_memberships: Mapped[Optional[List["CycleMembershipORM"]]] = relationship(
back_populates="cycles"
)
name: Mapped[str] = mapped_column()
13 changes: 13 additions & 0 deletions src/openg2p_portal_api/models/orm/entitlement_orm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from openg2p_fastapi_common.models import BaseORMModel
from sqlalchemy.orm import Mapped, mapped_column


class EntitlementORM(BaseORMModel):
__tablename__ = "g2p_entitlement"

id: Mapped[int] = mapped_column(primary_key=True)
partner_id: Mapped[int] = mapped_column()
cycle_id: Mapped[int] = mapped_column()
state: Mapped[str] = mapped_column()
initial_amount: Mapped[int] = mapped_column()
ern: Mapped[int] = mapped_column()
12 changes: 12 additions & 0 deletions src/openg2p_portal_api/models/orm/payment_orm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from openg2p_fastapi_common.models import BaseORMModel
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column


class PaymentORM(BaseORMModel):
__tablename__ = "g2p_payment"

id: Mapped[int] = mapped_column(primary_key=True)
entitlement_id: Mapped[int] = mapped_column(ForeignKey("g2p_entitlement.id"))
status: Mapped[str] = mapped_column()
amount_paid: Mapped[int] = mapped_column()
4 changes: 3 additions & 1 deletion src/openg2p_portal_api/models/orm/program_membership_orm.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from datetime import datetime
from typing import List, Optional

from openg2p_fastapi_common.context import dbengine
from openg2p_fastapi_common.models import BaseORMModel
from sqlalchemy import ForeignKey, Integer, String, and_, select
from sqlalchemy import DateTime, ForeignKey, Integer, String, and_, select
from sqlalchemy.ext.asyncio import async_sessionmaker
from sqlalchemy.orm import Mapped, mapped_column, relationship

Expand All @@ -16,6 +17,7 @@ class ProgramMembershipORM(BaseORMModel):
partner_id: Mapped[int] = mapped_column(Integer())
program_id: Mapped[int] = mapped_column(ForeignKey("g2p_program.id"))
state: Mapped[str] = mapped_column(String())
create_date: Mapped[datetime] = mapped_column(DateTime())

program = relationship("ProgramORM", back_populates="membership")
program_reg_info: Mapped[Optional[List["ProgramRegistrantInfoORM"]]] = relationship(
Expand Down
131 changes: 130 additions & 1 deletion src/openg2p_portal_api/models/orm/program_orm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

from openg2p_fastapi_common.context import dbengine
from openg2p_fastapi_common.models import BaseORMModelWithId
from sqlalchemy import ForeignKey, String, select
from sqlalchemy import ForeignKey, String, and_, func, select
from sqlalchemy.ext.asyncio import async_sessionmaker
from sqlalchemy.orm import Mapped, mapped_column, relationship, selectinload

from .cycle_membership_orm import CycleMembershipORM
from .cycle_orm import CycleORM
from .entitlement_orm import EntitlementORM
from .formio_builder_orm import FormORM
from .payment_orm import PaymentORM
from .program_membership_orm import ProgramMembershipORM
from .program_registrant_info_orm import ProgramRegistrantInfoORM


class ProgramORM(BaseORMModelWithId):
Expand All @@ -25,6 +30,7 @@ class ProgramORM(BaseORMModelWithId):
ForeignKey("formio_builder.id")
)
form: Mapped[Optional[List["FormORM"]]] = relationship(back_populates="program")
cycles: Mapped[Optional[list["CycleORM"]]] = relationship(back_populates="program")

@classmethod
async def get_all_programs(cls) -> List["ProgramORM"]:
Expand Down Expand Up @@ -80,3 +86,126 @@ async def get_program_form(cls, programid: int = None):
response = result.scalar()

return response

@classmethod
async def get_program_summary(cls, partner_id: int) -> List["ProgramORM"]:
async_session_maker = async_sessionmaker(dbengine.get())
async with async_session_maker() as session:
stmt = (
select(
ProgramORM.name.label("program_name"),
ProgramMembershipORM.state.label("enrollment_status"),
func.coalesce(func.sum(EntitlementORM.initial_amount), 0).label(
"total_funds_awaited"
),
func.coalesce(func.sum(PaymentORM.amount_paid), 0).label(
"total_funds_received"
),
)
.select_from(ProgramMembershipORM)
.outerjoin(ProgramORM, ProgramMembershipORM.program_id == ProgramORM.id)
.outerjoin(
CycleORM, ProgramMembershipORM.program_id == CycleORM.program_id
)
.outerjoin(
CycleMembershipORM,
and_(
ProgramMembershipORM.partner_id
== CycleMembershipORM.partner_id,
CycleORM.id == CycleMembershipORM.cycle_id,
),
)
.outerjoin(
EntitlementORM,
and_(
CycleMembershipORM.partner_id == EntitlementORM.partner_id,
CycleMembershipORM.cycle_id == EntitlementORM.cycle_id,
EntitlementORM.state == "approved",
),
)
.outerjoin(
PaymentORM,
and_(
EntitlementORM.id == PaymentORM.entitlement_id,
PaymentORM.status == "paid",
),
)
.where(ProgramMembershipORM.partner_id == partner_id)
.group_by(ProgramORM.name, ProgramMembershipORM.state)
)
result = await session.execute(stmt)
return result.all()

async def get_application_details(partner_id: int) -> List["ProgramORM"]:
async_session_maker = async_sessionmaker(dbengine.get())
async with async_session_maker() as session:
stmt = (
select(
ProgramORM.name.label("program_name"),
ProgramRegistrantInfoORM.application_id.label("application_id"),
ProgramRegistrantInfoORM.create_date.label("date_applied"),
ProgramRegistrantInfoORM.state.label("application_status"),
)
.select_from(ProgramRegistrantInfoORM)
.outerjoin(
ProgramMembershipORM,
and_(
ProgramMembershipORM.partner_id
== ProgramRegistrantInfoORM.registrant_id,
ProgramMembershipORM.program_id
== ProgramRegistrantInfoORM.program_id,
),
)
.outerjoin(ProgramORM, ProgramMembershipORM.program_id == ProgramORM.id)
)
# print("####################################")
# print(stmt)
result = await session.execute(stmt)
return result.all()

async def get_benefit_details(cls, partner_id: int) -> List["ProgramORM"]:
async_session_maker = async_sessionmaker(dbengine.get())
async with async_session_maker() as session:
stmt = (
select(
ProgramORM.name.label("program_name"),
ProgramMembershipORM.state.label("enrollment_status"),
func.coalesce(EntitlementORM.initial_amount, 0).label(
"funds_awaited"
),
func.coalesce(PaymentORM.amount_paid, 0).label("funds_received"),
EntitlementORM.ern.label("entitlement_reference_number"),
# CycleORM.name.label("cycle_name")
)
.select_from(ProgramMembershipORM)
.outerjoin(ProgramORM, ProgramMembershipORM.program_id == ProgramORM.id)
.outerjoin(
CycleORM, ProgramMembershipORM.program_id == CycleORM.program_id
)
.outerjoin(
CycleMembershipORM,
and_(
ProgramMembershipORM.partner_id
== CycleMembershipORM.partner_id,
CycleORM.id == CycleMembershipORM.cycle_id,
),
)
.outerjoin(
EntitlementORM,
and_(
CycleMembershipORM.partner_id == EntitlementORM.partner_id,
CycleMembershipORM.cycle_id == EntitlementORM.cycle_id,
EntitlementORM.state == "approved",
),
)
.outerjoin(
PaymentORM,
and_(
EntitlementORM.id == PaymentORM.entitlement_id,
PaymentORM.status == "paid",
),
)
.where(ProgramMembershipORM.partner_id == partner_id)
)
result = await session.execute(stmt)
return result.all()
30 changes: 30 additions & 0 deletions src/openg2p_portal_api/models/program.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
from typing import Optional

from pydantic import BaseModel, ConfigDict, Field, validator
Expand Down Expand Up @@ -32,3 +33,32 @@ class Program(ProgramBase):
@validator("is_portal_form_mapped", pre=True, always=True)
def is_program_form_mapped(cls, v, values):
return bool(values.get("self_service_portal_form"))


class ProgramSummary(BaseModel):
model_config = ConfigDict(from_attributes=True)

program_name: Optional[str] = None
enrollment_status: Optional[str] = None
total_funds_awaited: Optional[int] = None
total_funds_received: Optional[int] = None


class ApplicationDetails(BaseModel):
model_config = ConfigDict(from_attributes=True)

program_name: Optional[str] = None
application_id: Optional[int] = None
date_applied: Optional[datetime] = None
application_status: Optional[str] = None


class BenefitDetails(BaseModel):
model_config = ConfigDict(from_attributes=True)

program_name: Optional[str] = None
enrollment_status: Optional[str] = None
funds_awaited: Optional[int] = None
funds_received: Optional[int] = None
entitlement_reference_number: Optional[int] = None
# cycle_name: Optional[str]=None
Loading
Loading