Skip to content

Commit

Permalink
25334 - EFT Statement payment (#1882)
Browse files Browse the repository at this point in the history
  • Loading branch information
ochiu authored Jan 29, 2025
1 parent 04eae62 commit 6a08ee2
Show file tree
Hide file tree
Showing 20 changed files with 644 additions and 243 deletions.
8 changes: 4 additions & 4 deletions jobs/payment-jobs/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion jobs/payment-jobs/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ readme = "README.md"

[tool.poetry.dependencies]
python = "^3.12"
pay-api = {git = "https://github.com/seeker25/sbc-pay.git", branch = "update_deps", subdirectory = "pay-api"}
pay-api = {git = "https://github.com/ochiu/sbc-pay.git", branch = "25334-EFT-Statement-Payment", subdirectory = "pay-api"}
flask = "^3.0.2"
flask-sqlalchemy = "^3.1.1"
sqlalchemy = "^2.0.28"
Expand Down
28 changes: 28 additions & 0 deletions pay-api/src/pay_api/models/eft_short_name_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,29 @@ def get_short_name_links_count(cls, auth_account_id) -> int:
).count()


@define
class StatementOwingSchema: # pylint: disable=too-few-public-methods
"""Schema used to serialize the EFT Short name link statements owing."""

statement_id: int
amount_owing: Decimal
pending_payments_count: int
pending_payments_amount: Decimal

@classmethod
def from_row(cls, row: dict):
"""From row is used so we don't tightly couple to our database class.
https://www.attrs.org/en/stable/init.html
"""
return cls(
statement_id=row.get("statement_id"),
amount_owing=row.get("amount_owing"),
pending_payments_count=row.get("pending_payments_count"),
pending_payments_amount=row.get("pending_payments_amount"),
)


@define
class EFTShortnameLinkSchema: # pylint: disable=too-few-public-methods
"""Main schema used to serialize the EFT Short name link."""
Expand All @@ -132,13 +155,17 @@ class EFTShortnameLinkSchema: # pylint: disable=too-few-public-methods
updated_by_name: str
updated_on: datetime
has_pending_payment: bool
statements_owing: List[StatementOwingSchema]

@classmethod
def from_row(cls, row: EFTShortnameLinks):
"""From row is used so we don't tightly couple to our database class.
https://www.attrs.org/en/stable/init.html
"""
statements = getattr(row, "statements", [])
statements_owing = [StatementOwingSchema.from_row(statement) for statement in statements or []]

return cls(
id=row.id,
short_name_id=row.eft_short_name_id,
Expand All @@ -152,4 +179,5 @@ def from_row(cls, row: EFTShortnameLinks):
updated_by_name=row.updated_by_name,
updated_on=row.updated_on,
has_pending_payment=bool(getattr(row, "invoice_count", 0)),
statements_owing=statements_owing,
)
9 changes: 9 additions & 0 deletions pay-api/src/pay_api/models/statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ def find_all_payments_and_invoices_for_statement(cls, statement_id: str) -> List

return query.all()

@classmethod
def find_statement_by_account(cls, payment_account_id: int, statement_id: int):
"""Return statement for a payment account."""
return (
cls.query.filter(Statement.payment_account_id == payment_account_id)
.filter(Statement.id == statement_id)
.all()
)


class StatementSchema(ma.SQLAlchemyAutoSchema): # pylint: disable=too-many-ancestors
"""Main schema used to serialize the Statements."""
Expand Down
9 changes: 5 additions & 4 deletions pay-api/src/pay_api/resources/v1/eft_short_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from pay_api.services.eft_refund import EFTRefund as EFTRefundService
from pay_api.services.eft_short_name_historical import EFTShortnameHistorical as EFTShortnameHistoryService
from pay_api.services.eft_short_name_historical import EFTShortnameHistorySearch
from pay_api.services.eft_short_name_links import EFTShortnameLinks as EFTShortnameLinkService
from pay_api.services.eft_short_name_summaries import EFTShortnameSummaries as EFTShortnameSummariesService
from pay_api.services.eft_short_names import EFTShortnames as EFTShortnameService
from pay_api.services.eft_short_names import EFTShortnamesSearch
Expand Down Expand Up @@ -173,7 +174,7 @@ def get_eft_shortname_links(short_name_id: int):
response, status = {}, HTTPStatus.NOT_FOUND
else:
response, status = (
EFTShortnameService.get_shortname_links(short_name_id),
EFTShortnameLinkService.get_shortname_links(short_name_id),
HTTPStatus.OK,
)
except BusinessException as exception:
Expand All @@ -197,7 +198,7 @@ def post_eft_shortname_link(short_name_id: int):
else:
account_id = request_json.get("accountId", None)
response, status = (
EFTShortnameService.create_shortname_link(short_name_id, account_id),
EFTShortnameLinkService.create_shortname_link(short_name_id, account_id),
HTTPStatus.OK,
)
except BusinessException as exception:
Expand All @@ -217,12 +218,12 @@ def patch_eft_shortname_link(short_name_id: int, short_name_link_id: int):
request_json = request.get_json()

try:
link = EFTShortnameService.find_link_by_id(short_name_link_id)
link = EFTShortnameLinkService.find_link_by_id(short_name_link_id)
if not link or link["short_name_id"] != short_name_id:
response, status = {}, HTTPStatus.NOT_FOUND
else:
response, status = (
EFTShortnameService.patch_shortname_link(short_name_link_id, request_json),
EFTShortnameLinkService.patch_shortname_link(short_name_link_id, request_json),
HTTPStatus.OK,
)
except BusinessException as exception:
Expand Down
2 changes: 2 additions & 0 deletions pay-api/src/pay_api/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
from .eft_service import EftService
from .eft_short_name_historical import EFTShortnameHistorical as EFTHistoryService
from .eft_short_name_historical import EFTShortnameHistory, EFTShortnameHistorySearch
from .eft_short_name_links import EFTShortnameLinks as EFTShortNameLinkService
from .eft_short_name_summaries import EFTShortnameSummaries as EFTShortNameSummaryService
from .eft_short_names import EFTShortnames as EFTShortNamesService
from .eft_statements import EFTStatements as EFTStatementService
from .fee_schedule import FeeSchedule
from .flags import Flags
from .gcp_queue import GcpQueue
Expand Down
35 changes: 26 additions & 9 deletions pay-api/src/pay_api/services/eft_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,17 +184,29 @@ def create_receipt(invoice: InvoiceModel, payment: PaymentModel) -> ReceiptModel
return receipt

@staticmethod
def apply_payment_action(short_name_id: int, auth_account_id: str):
def apply_payment_action(short_name_id: int, auth_account_id: str, statement_id: int = None):
"""Apply EFT payments to outstanding payments."""
current_app.logger.debug("<apply_payment_action")
if auth_account_id is None or PaymentAccountModel.find_by_auth_account_id(auth_account_id) is None:
if (auth_account_id is None) or (
payment_account := PaymentAccountModel.find_by_auth_account_id(auth_account_id)
) is None:
raise BusinessException(Error.EFT_PAYMENT_ACTION_ACCOUNT_ID_REQUIRED)

EftService.process_owing_statements(short_name_id, auth_account_id)
if (
statement_id is not None
and StatementModel.find_statement_by_account(payment_account.id, statement_id) is None
):
raise BusinessException(Error.EFT_PAYMENT_ACTION_STATEMENT_ID_INVALID)

EftService.process_owing_statements(
short_name_id=short_name_id, auth_account_id=auth_account_id, statement_id=statement_id
)
current_app.logger.debug(">apply_payment_action")

@staticmethod
def cancel_payment_action(short_name_id: int, auth_account_id: str, invoice_id: int = None):
def cancel_payment_action(
short_name_id: int, auth_account_id: str, invoice_id: int = None, statement_id: int = None
):
"""Cancel EFT pending payments."""
current_app.logger.debug("<cancel_payment_action")
if any(
Expand All @@ -210,6 +222,7 @@ def cancel_payment_action(short_name_id: int, auth_account_id: str, invoice_id:
payment_account_id=payment_account.id,
invoice_id=invoice_id,
statuses=[EFTCreditInvoiceStatus.PENDING.value],
statement_id=statement_id,
)
link_group_ids = set()
for credit_link in credit_links:
Expand Down Expand Up @@ -321,6 +334,7 @@ def _get_shortname_invoice_links(
payment_account_id: int,
statuses: List[str],
invoice_id: int = None,
statement_id: int = None,
) -> List[EFTCreditInvoiceLinkModel]:
"""Get short name credit invoice links by account."""
credit_links_query = (
Expand All @@ -330,10 +344,12 @@ def _get_shortname_invoice_links(
EFTCreditModel.id == EFTCreditInvoiceLinkModel.eft_credit_id,
)
.join(InvoiceModel, InvoiceModel.id == EFTCreditInvoiceLinkModel.invoice_id)
.join(StatementInvoicesModel, StatementInvoicesModel.invoice_id == EFTCreditInvoiceLinkModel.invoice_id)
.filter(InvoiceModel.payment_account_id == payment_account_id)
.filter(EFTCreditModel.short_name_id == short_name_id)
.filter(EFTCreditInvoiceLinkModel.status_code.in_(statuses))
)
credit_links_query = credit_links_query.filter_conditionally(statement_id, StatementInvoicesModel.statement_id)
credit_links_query = credit_links_query.filter_conditionally(invoice_id, InvoiceModel.id)
return credit_links_query.all()

Expand Down Expand Up @@ -488,16 +504,17 @@ def apply_eft_credit(invoice_id: int, short_name_id: int, link_group_id: int, au
PartnerDisbursements.handle_payment(invoice)

@staticmethod
def process_owing_statements(short_name_id: int, auth_account_id: str, is_new_link: bool = False):
def process_owing_statements(
short_name_id: int, auth_account_id: str, is_new_link: bool = False, statement_id: int = None
):
"""Process outstanding statement invoices for an EFT Short name."""
current_app.logger.debug("<process_owing_statements")
shortname_link = EFTShortnameLinksModel.find_active_link(short_name_id, auth_account_id)

if shortname_link is None:
if EFTShortnameLinksModel.find_active_link(short_name_id, auth_account_id) is None:
raise BusinessException(Error.EFT_SHORT_NAME_NOT_LINKED)

credit_balance = EFTCreditModel.get_eft_credit_balance(short_name_id)
summary_dict: dict = StatementService.get_summary(auth_account_id)
summary_dict: dict = StatementService.get_summary(auth_account_id=auth_account_id, statement_id=statement_id)
total_due = summary_dict["total_due"]

if credit_balance < total_due:
Expand All @@ -506,7 +523,7 @@ def process_owing_statements(short_name_id: int, auth_account_id: str, is_new_li
return

statements, _ = StatementService.get_account_statements(
auth_account_id=auth_account_id, page=1, limit=1000, is_owing=True
auth_account_id=auth_account_id, page=1, limit=1000, is_owing=True, statement_id=statement_id
)
link_groups = {}
if statements:
Expand Down
Loading

0 comments on commit 6a08ee2

Please sign in to comment.