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

21537 - Shortname Refunds - PATCH / GET methods & Notification #1758

Merged
merged 18 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions pay-api/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ notes=FIXME,XXX,TODO
ignored-modules=flask_sqlalchemy,sqlalchemy,SQLAlchemy,alembic,scoped_session
ignored-classes=scoped_session
min-similarity-lines=15
max-attributes=15
seeker25 marked this conversation as resolved.
Show resolved Hide resolved
disable=C0301,W0511,R0903
good-names=
b,
Expand Down
1 change: 1 addition & 0 deletions pay-api/src/pay_api/dtos/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Init."""
87 changes: 87 additions & 0 deletions pay-api/src/pay_api/dtos/eft_shortname.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""Rationale behind creating these DTOS below.

1. To ensure that the request and response payloads are validated before they are passed to the service layer.
2. To ensure that the request and response payloads are consistent across the application.
3. To ensure that the request and response payloads are consistent with the API documentation.

In the near future, will find a library that generates our API spec based off of these DTOs.
"""
from decimal import Decimal
from attrs import define
from typing import List

from pay_api.utils.converter import Converter


@define
class EFTShortNameGetRequest:
"""EFT Short name search."""

short_name: str = None
short_name_id: int = None
short_name_type: str = None
amount_owing: Decimal = None
statement_id: int = None
state: str = None
page: int = 1
limit: int = 10
account_id: int = None
account_name: str = None
account_branch: str = None
account_id_list: str = None

@classmethod
def from_dict(cls, data: dict):
"""Convert from request args to EFTShortNameSearchDTO."""
dto = Converter(camel_to_snake_case=True).structure(data, EFTShortNameGetRequest)
# In the future, we'll need a cleaner way to handle this.
dto.state = dto.state.split(',') if dto.state else None
dto.account_id_list = dto.account_id_list.split(',') if dto.account_id_list else None
return dto


@define
class EFTShortNameSummaryGetRequest:
"""EFT Short name summary search."""

short_name: str = None
short_name_id: int = None
short_name_type: str = None
credits_remaining: Decimal = None
linked_accounts_count: int = None
payment_received_start_date: str = None
payment_received_end_date: str = None
page: int = 1
limit: int = 10

@classmethod
def from_dict(cls, data: dict):
"""Convert from request args to EFTShortNameSummarySearchDTO."""
dto = Converter(camel_to_snake_case=True).structure(data, EFTShortNameSummaryGetRequest)
return dto


@define
class EFTShortNameRefundPatchRequest:
"""EFT Short name refund DTO."""

comment: str
declined_reason: str
status: str

@classmethod
def from_dict(cls, data: dict):
"""Convert from request json to EFTShortNameRefundDTO."""
return Converter(camel_to_snake_case=True).structure(data, EFTShortNameRefundPatchRequest)


@define
class EFTShortNameRefundGetRequest:
"""EFT Short name refund DTO."""

statuses: List[str]

@classmethod
def from_dict(cls, data: dict):
"""Convert from request json to EFTShortNameRefundDTO."""
EFTShortNameRefundGetRequest(statuses=data.get('status', []))
10 changes: 5 additions & 5 deletions pay-api/src/pay_api/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@
from .eft_credit_invoice_link import EFTCreditInvoiceLink
from .eft_file import EFTFile
from .eft_process_status_code import EFTProcessStatusCode
from .eft_short_names import EFTShortnames, EFTShortnameSchema, EFTShortnameSummarySchema
from .eft_short_names_historical import EFTShortnamesHistorical
from .eft_refund import EFTRefund
from .eft_refund_email_list import EFTRefundEmailList
from .eft_short_name_links import EFTShortnameLinks, EFTShortnameLinkSchema
from .eft_short_names import EFTShortnames, EFTShortnameSchema, EFTShortnameSummarySchema
from .eft_short_names_historical import EFTShortnameHistorySchema, EFTShortNamesHistorical
from .eft_transaction import EFTTransaction, EFTTransactionSchema
from .ejv_file import EjvFile
from .ejv_header import EjvHeader
Expand All @@ -61,13 +62,12 @@
from .refunds_partial import RefundPartialLine, RefundsPartial
from .routing_slip import RoutingSlip, RoutingSlipSchema
from .routing_slip_status_code import RoutingSlipStatusCode, RoutingSlipStatusCodeSchema
from .statement import StatementDTO, Statement, StatementSchema
from .statement import Statement, StatementDTO, StatementSchema
from .statement_invoices import StatementInvoices, StatementInvoicesSchema # noqa: I005
from .statement_recipients import StatementRecipients, StatementRecipientsSchema
from .statement_settings import StatementSettings, StatementSettingsSchema
from .transaction_status_code import TransactionStatusCode, TransactionStatusCodeSchema
from .comment import Comment, CommentSchema
from .eft_refund_email_list import EFTRefundEmailList
from .comment import Comment, CommentSchema # This has to be at the bottom otherwise FeeSchedule errors


event.listen(Engine, 'before_cursor_execute', DBTracing.query_tracing)
4 changes: 4 additions & 0 deletions pay-api/src/pay_api/models/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ def delete(self):
db.session.delete(self)
db.session.commit()

def to_dict(self):
"""Quick and easy way to convert to a dict."""
return {c.name: getattr(self, c.name) for c in self.__table__.columns}

@staticmethod
def rollback():
"""RollBack."""
Expand Down
2 changes: 1 addition & 1 deletion pay-api/src/pay_api/models/cfs_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from .db import db


class CfsAccount(Versioned, BaseModel): # pylint:disable=too-many-instance-attributes
class CfsAccount(Versioned, BaseModel):
"""This class manages all of the base data about PayBC Account."""

__tablename__ = 'cfs_accounts'
Expand Down
2 changes: 1 addition & 1 deletion pay-api/src/pay_api/models/credit.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from .db import db, ma


class Credit(BaseModel): # pylint:disable=too-many-instance-attributes
class Credit(BaseModel):
"""This class manages all of the base data about Credit."""

__tablename__ = 'credits'
Expand Down
2 changes: 1 addition & 1 deletion pay-api/src/pay_api/models/distribution_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def bulk_save_links(cls, links: list):
BaseModel.commit()


class DistributionCode(Audit, Versioned, BaseModel): # pylint:disable=too-many-instance-attributes
class DistributionCode(Audit, Versioned, BaseModel):
"""This class manages all of the base data about distribution code.

Distribution code holds details on the codes for how the collected payment is going to be distributed.
Expand Down
2 changes: 1 addition & 1 deletion pay-api/src/pay_api/models/eft_credit.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from .db import db


class EFTCredit(BaseModel): # pylint:disable=too-many-instance-attributes
class EFTCredit(BaseModel):
"""This class manages all of the base data for EFT credits."""

__tablename__ = 'eft_credits'
Expand Down
11 changes: 10 additions & 1 deletion pay-api/src/pay_api/models/eft_refund.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from .db import db


class EFTRefund(BaseModel): # pylint: disable=too-many-instance-attributes
class EFTRefund(BaseModel):
"""This class manages the file data for EFT Refunds."""

__tablename__ = 'eft_refunds'
Expand Down Expand Up @@ -66,3 +66,12 @@ class EFTRefund(BaseModel): # pylint: disable=too-many-instance-attributes
updated_by = db.Column('updated_by', db.String(100), nullable=True)
updated_by_name = db.Column('updated_by_name', db.String(100), nullable=True)
updated_on = db.Column('updated_on', db.DateTime, nullable=True)


@classmethod
def find_refunds(cls, statuses: List[str]):
query = EFTRefundModel.query
if data.statuses:
query = query.filter(EFTRefundModel.status.in_(data.statuses))
refunds = query.all()
return [refund.to_dict() for refund in refunds]
2 changes: 1 addition & 1 deletion pay-api/src/pay_api/models/eft_short_names_historical.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from .db import db


class EFTShortnamesHistorical(BaseModel): # pylint:disable=too-many-instance-attributes
class EFTShortNamesHistorical(BaseModel):
"""This class manages all EFT Short name historical data."""

__tablename__ = 'eft_short_names_historical'
Expand Down
2 changes: 1 addition & 1 deletion pay-api/src/pay_api/models/refund.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from .db import db, ma


class Refund(BaseModel): # pylint:disable=too-many-instance-attributes
class Refund(BaseModel):
"""This class manages all of the base data about Refunds."""

__tablename__ = 'refunds'
Expand Down
124 changes: 70 additions & 54 deletions pay-api/src/pay_api/resources/v1/eft_short_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,22 @@
from flask import Blueprint, current_app, jsonify, request
from flask_cors import cross_origin

from pay_api.dtos.eft_shortname import EFTShortNameGetRequest, EFTShortNameRefundGetRequest, EFTShortNameRefundPatchRequest, EFTShortNameSummaryGetRequest
from pay_api.exceptions import BusinessException, error_to_response
from pay_api.schemas import utils as schema_utils
from pay_api.services.eft_service import EftService
from pay_api.services.eft_short_names import EFTShortnames as EFTShortnameService
from pay_api.services.eft_refund import EFTRefund as EFTRefundService
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
from pay_api.services.eft_short_name_historical import EFTShortnameHistorical as EFTShortnameHistoryService
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.utils.auth import jwt as _jwt
from pay_api.utils.endpoints_enums import EndpointEnum
from pay_api.utils.enums import Role
from pay_api.utils.errors import Error
from pay_api.utils.util import string_to_date, string_to_decimal, string_to_int


bp = Blueprint('EFT_SHORT_NAMES', __name__, url_prefix=f'{EndpointEnum.API_V1.value}/eft-shortnames')


Expand All @@ -41,36 +43,21 @@
def get_eft_shortnames():
"""Get all eft short name records."""
current_app.logger.info('<get_eft_shortnames')

state = request.args.get('state').split(',') if request.args.get('state', None) else None
page: int = int(request.args.get('page', '1'))
limit: int = int(request.args.get('limit', '10'))
amount_owing = request.args.get('amountOwing', None)
short_name = request.args.get('shortName', None)
short_name_id = request.args.get('shortNameId', None)
short_name_type = request.args.get('shortNameType', None)
statement_id = request.args.get('statementId', None)
account_id = request.args.get('accountId', None)
account_name = request.args.get('accountName', None)
account_branch = request.args.get('accountBranch', None)
account_id_list = request.args.get('accountIdList', None)
account_id_list = account_id_list.split(',') if account_id_list else None

request_data = EFTShortNameGetRequest.from_dict(request.args.to_dict())
response, status = EFTShortnameService.search(EFTShortnamesSearch(
id=short_name_id,
account_id=account_id,
account_id_list=account_id_list,
account_name=account_name,
account_branch=account_branch,
amount_owing=string_to_decimal(amount_owing),
short_name=short_name,
short_name_type=short_name_type,
statement_id=string_to_int(statement_id),
state=state,
page=page,
limit=limit)), HTTPStatus.OK
id=request_data.short_name_id,
account_id=request_data.account_id,
account_id_list=request_data.account_id_list,
account_name=request_data.account_name,
account_branch=request_data.account_branch,
amount_owing=string_to_decimal(request_data.amount_owing),
short_name=request_data.short_name,
short_name_type=request_data.short_name_type,
statement_id=string_to_int(request_data.statement_id),
state=request_data.state,
page=request_data.page,
limit=request_data.limit)), HTTPStatus.OK
current_app.logger.debug('>get_eft_shortnames')

return jsonify(response), status


Expand All @@ -81,26 +68,17 @@ def get_eft_shortnames():
def get_eft_shortname_summaries():
"""Get all eft short name summaries."""
current_app.logger.info('<get_eft_shortname_summaries')
page: int = int(request.args.get('page', '1'))
limit: int = int(request.args.get('limit', '10'))
short_name = request.args.get('shortName', None)
short_name_id = request.args.get('shortNameId', None)
short_name_type = request.args.get('shortNameType', None)
credits_remaining = request.args.get('creditsRemaining', None)
linked_accounts_count = request.args.get('linkedAccountsCount', None)
payment_received_start_date = request.args.get('paymentReceivedStartDate', None)
payment_received_end_date = request.args.get('paymentReceivedEndDate', None)

request_data = EFTShortNameSummaryGetRequest.from_dict(request.args.to_dict())
response, status = EFTShortnameSummariesService.search(EFTShortnamesSearch(
id=string_to_int(short_name_id),
deposit_start_date=string_to_date(payment_received_start_date),
deposit_end_date=string_to_date(payment_received_end_date),
credit_remaining=string_to_decimal(credits_remaining),
linked_accounts_count=string_to_int(linked_accounts_count),
short_name=short_name,
short_name_type=short_name_type,
page=page,
limit=limit)), HTTPStatus.OK
id=string_to_int(request_data.short_name_id),
deposit_start_date=string_to_date(request_data.payment_received_start_date),
deposit_end_date=string_to_date(request_data.payment_received_end_date),
credit_remaining=string_to_decimal(request_data.credits_remaining),
linked_accounts_count=string_to_int(request_data.linked_accounts_count),
short_name=request_data.short_name,
short_name_type=request_data.short_name_type,
page=request_data.page,
limit=request_data.limit)), HTTPStatus.OK

current_app.logger.debug('>get_eft_shortname_summaries')
return jsonify(response), status
Expand Down Expand Up @@ -245,10 +223,22 @@ def post_eft_statement_payment(short_name_id: int):
return jsonify(response), status


@bp.route('/shortname-refund', methods=['GET', 'OPTIONS'])
@cross_origin(origins='*', methods=['GET', 'POST', 'PATCH'])
@_jwt.has_one_of_roles([Role.EFT_REFUND.value, Role.EFT_REFUND_APPROVER.value])
def get_shortname_refunds():
"""Get all short name refunds."""
# TODO unit test spec update
request_data = EFTShortNameRefundGetRequest.from_dict(request.args.to_dict())
current_app.logger.info('<get_shortname_refunds')
refunds = EFTRefundService.get_shortname_refunds(request_data)
current_app.logger.info('<get_shortname_refund')
return jsonify(refunds), HTTPStatus.OK


@bp.route('/shortname-refund', methods=['POST', 'OPTIONS'])
@cross_origin(origins='*', methods=['POST'])
@_jwt.has_one_of_roles(
[Role.SYSTEM.value, Role.EFT_REFUND.value])
@_jwt.has_one_of_roles([Role.EFT_REFUND.value, Role.EFT_REFUND_APPROVER.value])
def post_shortname_refund():
"""Create the Refund for the Shortname."""
current_app.logger.info('<post_shortname_refund')
Expand All @@ -257,10 +247,36 @@ def post_shortname_refund():
if not valid_format:
return error_to_response(Error.INVALID_REQUEST, invalid_params=schema_utils.serialize(errors))
try:
response = EftService.create_shortname_refund(request_json)
response = EFTRefundService.create_shortname_refund(request_json)
status = HTTPStatus.ACCEPTED
except BusinessException as exception:
return exception.response()

current_app.logger.debug('>post_fas_refund')
current_app.logger.debug('>post_shortname_refund')
return jsonify(response), status


@bp.route('/shortname-refund/<int:eft_refund_id>', methods=['PATCH'])
@cross_origin(origins='*')
@_jwt.requires_auth
@_jwt.has_one_of_roles([Role.EFT_REFUND_APPROVER.value])
def patch_shortname_refund(eft_refund_id: int):
"""Patch EFT short name link."""
current_app.logger.info('<patch_eft_shortname_link')
request_json = request.get_json()
valid_format, errors = schema_utils.validate(request_json, 'refund_shortname')
# TODO unit test spec update
if not valid_format:
return error_to_response(Error.INVALID_REQUEST, invalid_params=schema_utils.serialize(errors))
short_name_refund = EFTShortNameRefundPatchRequest.from_dict(request_json)
try:
# Status, Reason, Comment, declined_reason
if EFTRefundService.find_refund_by_id(eft_refund_id):
response, status = EFTRefundService.update_shortname_refund(eft_refund_id, short_name_refund), HTTPStatus.OK
else:
response, status = {}, HTTPStatus.NOT_FOUND
except BusinessException as exception:
return exception.response()

current_app.logger.debug('>patch_eft_shortname_link')
return jsonify(response), status
Loading
Loading