Skip to content

Commit

Permalink
21519 - Rework partner disbursement job so it works with multiple lin…
Browse files Browse the repository at this point in the history
…k and unlinking (#1637)
  • Loading branch information
seeker25 authored Oct 3, 2024
1 parent 3293520 commit 1102953
Show file tree
Hide file tree
Showing 27 changed files with 1,036 additions and 510 deletions.
10 changes: 5 additions & 5 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/bcgov/sbc-pay.git", subdirectory = "pay-api", branch = "feature/22263"}
pay-api = {git = "https://github.com/seeker25/sbc-pay.git", branch = "21519", subdirectory = "pay-api"}
flask = "^3.0.2"
flask-sqlalchemy = "^3.1.1"
sqlalchemy = "^2.0.28"
Expand Down
57 changes: 49 additions & 8 deletions jobs/payment-jobs/tasks/ap_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"""Task to create AP file for FAS refunds and Disbursement via EFT for non-government orgs without a GL."""

import time
from datetime import date, datetime, timezone
from datetime import date, datetime, timedelta, timezone
from typing import List

from flask import current_app
Expand All @@ -29,15 +29,17 @@
from pay_api.models import EjvHeader as EjvHeaderModel
from pay_api.models import EjvLink as EjvLinkModel
from pay_api.models import Invoice as InvoiceModel
from pay_api.models import Receipt as ReceiptModel
from pay_api.models import Refund as RefundModel
from pay_api.models import RoutingSlip as RoutingSlipModel
from pay_api.models import db
from pay_api.utils.enums import (
DisbursementStatus, EFTShortnameRefundStatus, EjvFileType, EJVLinkType, RoutingSlipStatus)
DisbursementStatus, EFTShortnameRefundStatus, EjvFileType, EJVLinkType, InvoiceStatus, PaymentMethod,
RoutingSlipStatus)
from sqlalchemy import Date, cast

from tasks.common.cgi_ap import CgiAP
from tasks.common.dataclasses import APLine
from tasks.ejv_partner_distribution_task import EjvPartnerDistributionTask


class ApTask(CgiAP):
Expand Down Expand Up @@ -166,13 +168,52 @@ def _create_routing_slip_refund_file(cls): # pylint:disable=too-many-locals, to

cls._create_file_and_upload(ap_content)

@classmethod
def get_invoices_for_disbursement(cls, partner):
"""Return invoices for disbursement. Used by EJV and AP."""
disbursement_date = datetime.now(tz=timezone.utc).replace(tzinfo=None) \
- timedelta(days=current_app.config.get('DISBURSEMENT_DELAY_IN_DAYS'))
invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \
.filter(InvoiceModel.invoice_status_code == InvoiceStatus.PAID.value) \
.filter(
InvoiceModel.payment_method_code.notin_([PaymentMethod.INTERNAL.value,
PaymentMethod.DRAWDOWN.value,
PaymentMethod.EFT.value])) \
.filter((InvoiceModel.disbursement_status_code.is_(None)) |
(InvoiceModel.disbursement_status_code == DisbursementStatus.ERRORED.value)) \
.filter(~InvoiceModel.receipts.any(cast(ReceiptModel.receipt_date, Date) >= disbursement_date.date())) \
.filter(InvoiceModel.corp_type_code == partner.code) \
.all()
current_app.logger.info(invoices)
return invoices

@classmethod
def get_invoices_for_refund_reversal(cls, partner):
"""Return invoices for refund reversal."""
# REFUND_REQUESTED for credit card payments, CREDITED for AR and REFUNDED for other payments.
refund_inv_statuses = (InvoiceStatus.REFUNDED.value, InvoiceStatus.REFUND_REQUESTED.value,
InvoiceStatus.CREDITED.value)

invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \
.filter(InvoiceModel.invoice_status_code.in_(refund_inv_statuses)) \
.filter(
InvoiceModel.payment_method_code.notin_([PaymentMethod.INTERNAL.value,
PaymentMethod.DRAWDOWN.value,
PaymentMethod.EFT.value])) \
.filter(InvoiceModel.disbursement_status_code == DisbursementStatus.COMPLETED.value) \
.filter(InvoiceModel.corp_type_code == partner.code) \
.all()
current_app.logger.info(invoices)
return invoices

@classmethod
def _create_non_gov_disbursement_file(cls): # pylint:disable=too-many-locals
"""Create AP file for disbursement for non government entities without a GL code via EFT and upload to CGI."""
cls.ap_type = EjvFileType.NON_GOV_DISBURSEMENT
bca_partner = CorpTypeModel.find_by_code('BCA')
total_invoices: List[InvoiceModel] = EjvPartnerDistributionTask().get_invoices_for_disbursement(bca_partner) + \
EjvPartnerDistributionTask().get_invoices_for_refund_reversal(bca_partner)
# TODO these two functions need to be reworked when we onboard BCA again.
total_invoices: List[InvoiceModel] = cls.get_invoices_for_disbursement(bca_partner) + \
cls.get_invoices_for_refund_reversal(bca_partner)

current_app.logger.info(f'Found {len(total_invoices)} to disburse.')
if not total_invoices:
Expand Down Expand Up @@ -228,10 +269,10 @@ def _create_non_gov_disbursement_file(cls): # pylint:disable=too-many-locals

@classmethod
def _create_file_and_upload(cls, ap_content):
file_path_with_name, trg_file_path = cls.create_inbox_and_trg_files(ap_content)
cls.upload(ap_content, cls.get_file_name(), file_path_with_name, trg_file_path)
file_path_with_name, trg_file_path, file_name = cls.create_inbox_and_trg_files(ap_content)
cls.upload(ap_content, file_name, file_path_with_name, trg_file_path)
db.session.commit()
# Add a sleep to prevent collision on file name.
# Sleep to prevent collision on file name.
time.sleep(1)

@classmethod
Expand Down
5 changes: 3 additions & 2 deletions jobs/payment-jobs/tasks/common/cgi_ejv.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ def get_trg_suffix(cls):
def create_inbox_and_trg_files(cls, ejv_content):
"""Create inbox and trigger files."""
file_path: str = tempfile.gettempdir()
file_path_with_name = f'{file_path}/{cls.get_file_name()}'
file_name = cls.get_file_name()
file_path_with_name = f'{file_path}/{file_name}'
trg_file_path = f'{file_path_with_name}.{cls.get_trg_suffix()}'
with open(file_path_with_name, 'a+', encoding='utf-8') as jv_file:
jv_file.write(ejv_content)
Expand All @@ -157,4 +158,4 @@ def create_inbox_and_trg_files(cls, ejv_content):
with open(trg_file_path, 'a+', encoding='utf-8') as trg_file:
trg_file.write('')
trg_file.close()
return file_path_with_name, trg_file_path
return file_path_with_name, trg_file_path, file_name
26 changes: 26 additions & 0 deletions jobs/payment-jobs/tasks/common/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,41 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Common dataclasses for tasks, dataclasses allow for cleaner code with autocompletion in vscode."""
from decimal import Decimal

from dataclasses import dataclass
from typing import List, Optional
from dataclass_wizard import JSONWizard
from pay_api.models import DistributionCode as DistributionCodeModel
from pay_api.models import Invoice as InvoiceModel
from pay_api.models import PartnerDisbursements as PartnerDisbursementModel
from pay_api.models import PaymentLineItem as LineItemModel
from pay_api.utils.enums import InvoiceStatus
from tasks.common.enums import PaymentDetailsGlStatus


@dataclass
class DisbursementLineItem:
"""DTO mapping for disbursement line item."""

amount: Decimal
flow_through: str
description_identifier: str
is_reversal: bool
target_type: str
identifier: int


@dataclass
class Disbursement:
"""DTO mapping for disbursement."""

bcreg_distribution_code: DistributionCodeModel
partner_distribution_code: DistributionCodeModel
line_item: DisbursementLineItem
target: InvoiceModel | PartnerDisbursementModel


@dataclass
class RefundData(JSONWizard):
"""Refund data from order status query."""
Expand Down
Loading

0 comments on commit 1102953

Please sign in to comment.