Пример #1
0
    def _apply_routing_slips_to_pending_invoices(cls, routing_slip: RoutingSlipModel) -> float:
        """Apply the routing slips again, when routing slip is linked to an NSF parent."""
        current_app.logger.info(f'Starting NSF recovery process for {routing_slip.number}')
        routing_slip_payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
            routing_slip.payment_account_id)

        # apply invoice to the active CFS_ACCOUNT which will be the parent routing slip
        active_cfs_account = CfsAccountModel.find_effective_by_account_id(routing_slip_payment_account.id)

        invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \
            .filter(InvoiceModel.routing_slip == routing_slip.number,
                    InvoiceModel.invoice_status_code.in_([InvoiceStatus.CREATED.value, InvoiceStatus.APPROVED.value])) \
            .all()
        current_app.logger.info(f'Found {len(invoices)} to apply receipt')
        applied_amount = 0
        for inv in invoices:
            inv_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
                inv.id, InvoiceReferenceStatus.ACTIVE.value
            )
            cls.apply_routing_slips_to_invoice(
                routing_slip_payment_account, active_cfs_account, routing_slip, inv, inv_ref.invoice_number
            )

            # IF invoice balance is zero, then update records.
            if CFSService.get_invoice(cfs_account=active_cfs_account, inv_number=inv_ref.invoice_number) \
                    .get('amount_due') == 0:
                applied_amount += inv.total
                inv_ref.status_code = InvoiceReferenceStatus.COMPLETED.value
                inv.invoice_status_code = InvoiceStatus.PAID.value

        return applied_amount
Пример #2
0
def factory_invoice_reference(invoice_id: int,
                              invoice_number: str = '10021',
                              status_code=InvoiceReferenceStatus.ACTIVE.value):
    """Return Factory."""
    return InvoiceReference(invoice_id=invoice_id,
                            status_code=status_code,
                            invoice_number=invoice_number).save()
Пример #3
0
 def _publish_to_mailer(cls, invoice):
     """Construct message and send to mailer queue."""
     receipt: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number(
         invoice_id=invoice.id)
     invoice_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
         invoice_id=invoice.id,
         status_code=InvoiceReferenceStatus.COMPLETED.value)
     payment_transaction: PaymentTransactionModel = PaymentTransactionModel.find_recent_completed_by_invoice_id(
         invoice_id=invoice.id)
     q_payload = dict(
         specversion='1.x-wip',
         type='bc.registry.payment.refundRequest',
         source=
         f'https://api.pay.bcregistry.gov.bc.ca/v1/invoices/{invoice.id}',
         id=invoice.id,
         datacontenttype='application/json',
         data=dict(identifier=invoice.business_identifier,
                   orderNumber=receipt.receipt_number,
                   transactionDateTime=get_local_formatted_date_time(
                       payment_transaction.transaction_end_time),
                   transactionAmount=receipt.receipt_amount,
                   transactionId=invoice_ref.invoice_number))
     current_app.logger.debug(
         'Publishing payment refund request to mailer ')
     current_app.logger.debug(q_payload)
     publish_response(
         payload=q_payload,
         client_name=current_app.config.get('NATS_MAILER_CLIENT_NAME'),
         subject=current_app.config.get('NATS_MAILER_SUBJECT'))
Пример #4
0
    def find_completed_reference_by_invoice_id(inv_id: int):
        """Find invoice reference by invoice id."""
        ref_dao = ReferenceModel.find_reference_by_invoice_id_and_status(inv_id, InvoiceReferenceStatus.COMPLETED.value)
        invoice_reference = InvoiceReference()
        invoice_reference._dao = ref_dao  # pylint: disable=protected-access

        current_app.logger.debug('>find_reference_by_invoice_id')
        return invoice_reference
Пример #5
0
    def find_any_active_reference_by_invoice_number(inv_number: str):
        """Find invoice reference by invoice id."""
        ref_dao = ReferenceModel.find_any_active_reference_by_invoice_number(inv_number)
        invoice_reference = InvoiceReference()
        invoice_reference._dao = ref_dao  # pylint: disable=protected-access

        current_app.logger.debug('>find_any_active_reference_by_invoice_number')
        return invoice_reference
Пример #6
0
 def _publish_refund_to_mailer(invoice: InvoiceModel):
     """Construct message and send to mailer queue."""
     from .payment_transaction import publish_response  # pylint:disable=import-outside-toplevel,cyclic-import
     receipt: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number(
         invoice_id=invoice.id)
     invoice_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
         invoice_id=invoice.id,
         status_code=InvoiceReferenceStatus.COMPLETED.value)
     payment_transaction: PaymentTransactionModel = PaymentTransactionModel.find_recent_completed_by_invoice_id(
         invoice_id=invoice.id)
     message_type: str = f'bc.registry.payment.{invoice.payment_method_code.lower()}.refundRequest'
     transaction_date_time = receipt.receipt_date if invoice.payment_method_code == PaymentMethod.DRAWDOWN.value \
         else payment_transaction.transaction_end_time
     filing_description = ''
     for line_item in invoice.payment_line_items:
         if filing_description:
             filing_description += ','
         filing_description += line_item.description
     q_payload = dict(
         specversion='1.x-wip',
         type=message_type,
         source=
         f'https://api.pay.bcregistry.gov.bc.ca/v1/invoices/{invoice.id}',
         id=invoice.id,
         datacontenttype='application/json',
         data=dict(identifier=invoice.business_identifier,
                   orderNumber=receipt.receipt_number,
                   transactionDateTime=get_local_formatted_date_time(
                       transaction_date_time),
                   transactionAmount=receipt.receipt_amount,
                   transactionId=invoice_ref.invoice_number,
                   refundDate=get_local_formatted_date_time(
                       datetime.now(), '%Y%m%d'),
                   filingDescription=filing_description))
     if invoice.payment_method_code == PaymentMethod.DRAWDOWN.value:
         payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
             invoice.payment_account_id)
         q_payload['data'].update(
             dict(bcolAccount=invoice.bcol_account,
                  bcolUser=payment_account.bcol_user_id))
     current_app.logger.debug(
         f'Publishing payment refund request to mailer for {invoice.id} : {q_payload}'
     )
     publish_response(
         payload=q_payload,
         client_name=current_app.config.get('NATS_MAILER_CLIENT_NAME'),
         subject=current_app.config.get('NATS_MAILER_SUBJECT'))
Пример #7
0
    def _refund_and_create_credit_memo(invoice: InvoiceModel):
        # Create credit memo in CFS if the invoice status is PAID.
        # Don't do anything is the status is APPROVED.
        current_app.logger.info(
            f'Creating credit memo for invoice : {invoice.id}, {invoice.invoice_status_code}'
        )
        if invoice.invoice_status_code == InvoiceStatus.APPROVED.value \
                and InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
                    invoice.id, InvoiceReferenceStatus.ACTIVE.value) is None:
            return

        cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
            invoice.payment_account_id)
        line_items: List[PaymentLineItemModel] = []
        for line_item in invoice.payment_line_items:
            line_items.append(PaymentLineItemModel.find_by_id(line_item.id))

        cms_response = CFSService.create_cms(line_items=line_items,
                                             cfs_account=cfs_account)
        # TODO Create a payment record for this to show up on transactions, when the ticket comes.
        # Create a credit with CM identifier as CMs are not reported in payment interface file
        # until invoice is applied.
        CreditModel(cfs_identifier=cms_response.get('credit_memo_number'),
                    is_credit_memo=True,
                    amount=invoice.total,
                    remaining_amount=invoice.total,
                    account_id=invoice.payment_account_id).flush()

        # Add up the credit amount and update payment account table.
        payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
            invoice.payment_account_id)
        payment_account.credit = (payment_account.credit or 0) + invoice.total
        current_app.logger.info(
            f'Updating credit amount to  {payment_account.credit} for account {payment_account.auth_account_id}'
        )
        payment_account.flush()
Пример #8
0
    def process_nsf(cls):
        """Process NSF routing slips.

        Steps:
        1. Find all routing slips with NSF status.
        2. Reverse the receipt for the NSF routing slips.
        3. Add an invoice for NSF fees.
        """
        routing_slips: List[RoutingSlipModel] = db.session.query(RoutingSlipModel) \
            .join(PaymentAccountModel, PaymentAccountModel.id == RoutingSlipModel.payment_account_id) \
            .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \
            .filter(RoutingSlipModel.status == RoutingSlipStatus.NSF.value) \
            .filter(CfsAccountModel.status == CfsAccountStatus.ACTIVE.value).all()

        current_app.logger.info(f'Found {len(routing_slips)} to process NSF.')
        for routing_slip in routing_slips:
            # 1. Reverse the routing slip receipt.
            # 2. Reverse all the child receipts.
            # 3. Change the CFS Account status to FREEZE.
            try:
                current_app.logger.debug(f'Reverse receipt {routing_slip.number}')
                payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(routing_slip.payment_account_id)
                cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id)

                # Find all child routing slip and reverse it, as all linked routing slips are also considered as NSF.
                child_routing_slips: List[RoutingSlipModel] = RoutingSlipModel.find_children(routing_slip.number)
                for rs in (routing_slip, *child_routing_slips):
                    receipt_number = rs.number
                    if rs.parent_number:
                        receipt_number = f'{receipt_number}L'
                    CFSService.reverse_rs_receipt_in_cfs(cfs_account, receipt_number, is_nsf=True)

                    for payment in db.session.query(PaymentModel) \
                            .filter(PaymentModel.receipt_number == receipt_number).all():
                        payment.payment_status_code = PaymentStatus.FAILED.value

                # Update the CFS Account status to FREEZE.
                cfs_account.status = CfsAccountStatus.FREEZE.value

                # Update all invoice status to CREATED.
                invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \
                    .filter(InvoiceModel.routing_slip == routing_slip.number) \
                    .filter(InvoiceModel.invoice_status_code == InvoiceStatus.PAID.value) \
                    .all()
                for inv in invoices:
                    # Reset the statuses
                    inv.invoice_status_code = InvoiceStatus.CREATED.value
                    inv_ref = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
                        inv.id, InvoiceReferenceStatus.COMPLETED.value
                    )
                    inv_ref.status_code = InvoiceReferenceStatus.ACTIVE.value
                    # Delete receipts as receipts are reversed in CFS.
                    for receipt in ReceiptModel.find_all_receipts_for_invoice(inv.id):
                        db.session.delete(receipt)

                inv = cls._create_nsf_invoice(cfs_account, routing_slip.number, payment_account)
                # Reduce the NSF fee from remaining amount.
                routing_slip.remaining_amount = float(routing_slip.remaining_amount) - inv.total
                routing_slip.save()

            except Exception as e:  # NOQA # pylint: disable=broad-except
                capture_message(
                    f'Error on Processing NSF for :={routing_slip.number}, '
                    f'routing slip : {routing_slip.id}, ERROR : {str(e)}', level='error')
                current_app.logger.error(e)
                continue
Пример #9
0
def test_process_nsf(session):
    """Test process NSF."""
    # 1. Link 2 child routing slips with parent.
    # 2. Mark the parent as NSF and run job.
    child_1 = '123456789'
    child_2 = '987654321'
    parent = '111111111'
    factory_routing_slip_account(number=child_1,
                                 status=CfsAccountStatus.ACTIVE.value,
                                 total=10)
    factory_routing_slip_account(number=child_2,
                                 status=CfsAccountStatus.ACTIVE.value,
                                 total=10)
    pay_account = factory_routing_slip_account(
        number=parent, status=CfsAccountStatus.ACTIVE.value, total=10)

    child_1_rs = RoutingSlipModel.find_by_number(child_1)
    child_2_rs = RoutingSlipModel.find_by_number(child_2)
    parent_rs = RoutingSlipModel.find_by_number(parent)

    # Do Link
    for child in (child_2_rs, child_1_rs):
        child.status = RoutingSlipStatus.LINKED.value
        child.parent_number = parent_rs.number
        child.save()

    RoutingSlipTask.link_routing_slips()

    # Now mark the parent as NSF
    parent_rs.remaining_amount = -30
    parent_rs.status = RoutingSlipStatus.NSF.value
    parent_rs.save()

    # Create an invoice record against this routing slip.
    invoice = factory_invoice(payment_account=pay_account,
                              total=30,
                              status_code=InvoiceStatus.PAID.value,
                              payment_method_code=PaymentMethod.INTERNAL.value,
                              routing_slip=parent_rs.number)

    fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type(
        'CP', 'OTANN')
    line = factory_payment_line_item(
        invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id)
    line.save()

    # Create a distribution for NSF -> As this is a manual step once in each env.
    nsf_fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type(
        'BCR', 'NSF')
    distribution = factory_distribution('NSF')
    factory_distribution_link(distribution.distribution_code_id,
                              nsf_fee_schedule.fee_schedule_id)

    # Create invoice
    factory_invoice_reference(
        invoice.id, status_code=InvoiceReferenceStatus.COMPLETED.value)

    # Create receipts for the invoices
    factory_receipt(invoice.id, parent_rs.number)
    factory_receipt(invoice.id, child_1_rs.number)
    factory_receipt(invoice.id, child_2_rs.number)

    with patch('pay_api.services.CFSService.reverse_rs_receipt_in_cfs'
               ) as mock_cfs_reverse:
        RoutingSlipTask.process_nsf()
        mock_cfs_reverse.assert_called()

    # Assert the records.
    invoice: InvoiceModel = InvoiceModel.find_by_id(invoice.id)
    assert invoice.invoice_status_code == InvoiceStatus.CREATED.value
    assert InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
        invoice.id, status_code=InvoiceReferenceStatus.ACTIVE.value)
    assert not ReceiptModel.find_all_receipts_for_invoice(invoice.id)
    assert float(
        RoutingSlipModel.find_by_number(
            parent_rs.number).remaining_amount) == -60  # Including NSF Fee

    with patch('pay_api.services.CFSService.reverse_rs_receipt_in_cfs'
               ) as mock_cfs_reverse_2:
        RoutingSlipTask.process_nsf()
        mock_cfs_reverse_2.assert_not_called()
Пример #10
0
def factory_invoice_reference(invoice_id: int, invoice_number: str = '10021'):
    """Factory."""
    return InvoiceReference(invoice_id=invoice_id,
                            status_code='CREATED',
                            invoice_number=invoice_number)
Пример #11
0
async def _process_jv_details_feedback(ejv_file, has_errors, line,
                                       receipt_number):  # pylint:disable=too-many-locals
    journal_name: str = line[7:17]  # {ministry}{ejv_header_model.id:0>8}
    ejv_header_model_id = int(journal_name[2:])
    invoice_id = int(line[205:315])
    invoice: InvoiceModel = InvoiceModel.find_by_id(invoice_id)
    invoice_link: EjvInvoiceLinkModel = db.session.query(
        EjvInvoiceLinkModel).filter(
            EjvInvoiceLinkModel.ejv_header_id == ejv_header_model_id).filter(
                EjvInvoiceLinkModel.invoice_id == invoice_id).one_or_none()
    invoice_return_code = line[315:319]
    invoice_return_message = line[319:469]
    # If the JV process failed, then mark the GL code against the invoice to be stopped
    # for further JV process for the credit GL.
    if line[104:105] == 'C' and ejv_file.is_distribution:
        invoice_link.disbursement_status_code = _get_disbursement_status(
            invoice_return_code)
        invoice_link.message = invoice_return_message
        invoice.disbursement_status_code = _get_disbursement_status(
            invoice_return_code)

        if invoice_link.disbursement_status_code == DisbursementStatus.ERRORED.value:
            has_errors = True

        line_items: List[PaymentLineItemModel] = invoice.payment_line_items
        for line_item in line_items:
            # Line debit distribution
            debit_distribution: DistributionCodeModel = DistributionCodeModel \
                .find_by_id(line_item.fee_distribution_id)
            credit_distribution: DistributionCodeModel = DistributionCodeModel \
                .find_by_id(debit_distribution.disbursement_distribution_code_id)
            credit_distribution.stop_ejv = True
    elif line[104:105] == 'D' and not ejv_file.is_distribution:
        # This is for gov account payment JV.
        invoice_link.disbursement_status_code = _get_disbursement_status(
            invoice_return_code)
        invoice_link.message = invoice_return_message
        inv_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
            invoice_id, InvoiceReferenceStatus.ACTIVE.value)

        if invoice_link.disbursement_status_code == DisbursementStatus.ERRORED.value:
            has_errors = True
            # Cancel the invoice reference.
            inv_ref.status_code = InvoiceReferenceStatus.CANCELLED.value
            # Find the distribution code and set the stop_ejv flag to TRUE
            dist_code: DistributionCodeModel = DistributionCodeModel.find_by_active_for_account(
                invoice.payment_account_id)
            dist_code.stop_ejv = True
        elif invoice_link.disbursement_status_code == DisbursementStatus.COMPLETED.value:
            # Set the invoice status as PAID. Mark the invoice reference as COMPLETED, create a receipt
            invoice.invoice_status_code = InvoiceStatus.PAID.value
            if inv_ref:
                inv_ref.status_code = InvoiceReferenceStatus.COMPLETED.value
            # Find receipt and add total to it, as single invoice can be multiple rows in the file
            receipt = ReceiptModel.find_by_invoice_id_and_receipt_number(
                invoice_id=invoice_id, receipt_number=receipt_number)
            if receipt:
                receipt.receipt_amount += float(line[89:104])
            else:
                ReceiptModel(invoice_id=invoice_id,
                             receipt_number=receipt_number,
                             receipt_date=datetime.now(),
                             receipt_amount=float(line[89:104])).flush()
    return has_errors
Пример #12
0
async def test_failed_payment_ejv_reconciliations(session, app, stan_server,
                                                  event_loop, client_id,
                                                  events_stan, future,
                                                  mock_publish):
    """Test Reconciliations worker."""
    # Call back for the subscription
    from reconciliations.worker import cb_subscription_handler

    # Create a Credit Card Payment
    # register the handler to test it
    await subscribe_to_queue(
        events_stan,
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('subject'),
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('queue'),
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('durable_name'),
        cb_subscription_handler)

    # 1. Create EJV payment accounts
    # 2. Create invoice and related records
    # 3. Create a feedback file and assert status

    corp_type = 'BEN'
    filing_type = 'BCINC'

    # Find fee schedule which have service fees.
    fee_schedule: FeeScheduleModel = FeeScheduleModel.find_by_filing_type_and_corp_type(
        corp_type, filing_type)
    # Create a service fee distribution code
    service_fee_dist_code = factory_distribution(name='service fee',
                                                 client='112',
                                                 reps_centre='99999',
                                                 service_line='99999',
                                                 stob='9999',
                                                 project_code='9999999')
    service_fee_dist_code.save()

    dist_code: DistributionCodeModel = DistributionCodeModel.find_by_active_for_fee_schedule(
        fee_schedule.fee_schedule_id)
    # Update fee dist code to match the requirement.
    dist_code.client = '112'
    dist_code.responsibility_centre = '22222'
    dist_code.service_line = '33333'
    dist_code.stob = '4444'
    dist_code.project_code = '5555555'
    dist_code.service_fee_distribution_code_id = service_fee_dist_code.distribution_code_id
    dist_code.save()

    # GA
    jv_account_1 = factory_create_ejv_account(auth_account_id='1')
    jv_account_2 = factory_create_ejv_account(auth_account_id='2')

    # GI
    jv_account_3 = factory_create_ejv_account(auth_account_id='3',
                                              client='111')
    jv_account_4 = factory_create_ejv_account(auth_account_id='4',
                                              client='111')

    # Now create JV records.
    # Create EJV File model
    file_ref = f'INBOX.{datetime.now()}'
    ejv_file: EjvFileModel = EjvFileModel(
        file_ref=file_ref,
        disbursement_status_code=DisbursementStatus.UPLOADED.value,
        is_distribution=False).save()
    ejv_file_id = ejv_file.id

    feedback_content = f'..BG...........00000000{ejv_file_id}...\n' \
                       f'..BH...0000.................................................................................' \
                       f'.....................................................................CGI\n'

    jv_accounts = [jv_account_1, jv_account_2, jv_account_3, jv_account_4]
    inv_ids = []
    jv_account_ids = []
    inv_total_amount = 101.5
    for jv_acc in jv_accounts:
        jv_account_ids.append(jv_acc.id)
        inv = factory_invoice(payment_account=jv_acc,
                              corp_type_code=corp_type,
                              total=inv_total_amount,
                              status_code=InvoiceStatus.APPROVED.value,
                              payment_method_code=None)
        factory_invoice_reference(inv.id)
        line: PaymentLineItemModel = factory_payment_line_item(
            invoice_id=inv.id,
            fee_schedule_id=fee_schedule.fee_schedule_id,
            filing_fees=100,
            total=100,
            service_fees=1.5,
            fee_dist_id=dist_code.distribution_code_id)
        inv_ids.append(inv.id)
        ejv_header: EjvHeaderModel = EjvHeaderModel(
            disbursement_status_code=DisbursementStatus.UPLOADED.value,
            ejv_file_id=ejv_file.id,
            payment_account_id=jv_acc.id).save()

        EjvInvoiceLinkModel(
            invoice_id=inv.id,
            ejv_header_id=ejv_header.id,
            disbursement_status_code=DisbursementStatus.UPLOADED.value).save()
        inv_total = f'{inv.total:.2f}'.zfill(15)
        pay_line_amount = f'{line.total:.2f}'.zfill(15)
        service_fee_amount = f'{line.service_fees:.2f}'.zfill(15)
        jh_and_jd = f'..JH...FI0000000{ejv_header.id}.........................{inv_total}.....................' \
                    f'............................................................................................' \
                    f'............................................................................................' \
                    f'.........1111ERROR..........................................................................' \
                    f'.......................................................................CGI\n' \
                    f'..JD...FI0000000{ejv_header.id}00001........................................................' \
                    f'...........{pay_line_amount}D.................................................................' \
                    f'...................................{inv.id}                                             ' \
                    f'                                                                1111ERROR...................' \
                    f'............................................................................................' \
                    f'..................................CGI\n' \
                    f'..JD...FI0000000{ejv_header.id}00002........................................................' \
                    f'...........{pay_line_amount}C.................................................................' \
                    f'...................................{inv.id}                                             ' \
                    f'                                                                1111ERROR...................' \
                    f'............................................................................................' \
                    f'..................................CGI\n' \
                    f'..JD...FI0000000{ejv_header.id}00003...........................................................' \
                    f'........{service_fee_amount}D.................................................................' \
                    f'...................................{inv.id}                                             ' \
                    f'                                                                1111ERROR...................' \
                    f'............................................................................................' \
                    f'..................................CGI\n' \
                    f'..JD...FI0000000{ejv_header.id}00004........................................................' \
                    f'...........{service_fee_amount}C..............................................................' \
                    f'......................................{inv.id}                                             ' \
                    f'                                                                1111ERROR...................' \
                    f'............................................................................................' \
                    f'..................................CGI\n'
        feedback_content = feedback_content + jh_and_jd
    feedback_content = feedback_content + f'..BT.......FI0000000{ejv_header.id}000000000000002{inv_total}0000.......' \
                                          f'.........................................................................' \
                                          f'......................................................................CGI'
    ack_file_name = f'ACK.{file_ref}'

    with open(ack_file_name, 'a+') as jv_file:
        jv_file.write('')
        jv_file.close()

    # Now upload the ACK file to minio and publish message.
    upload_to_minio(file_name=ack_file_name, value_as_bytes=str.encode(''))

    await helper_add_ejv_event_to_queue(events_stan, file_name=ack_file_name)

    # Query EJV File and assert the status is changed
    ejv_file = EjvFileModel.find_by_id(ejv_file_id)
    assert ejv_file.disbursement_status_code == DisbursementStatus.ACKNOWLEDGED.value

    feedback_file_name = f'FEEDBACK.{file_ref}'

    with open(feedback_file_name, 'a+') as jv_file:
        jv_file.write(feedback_content)
        jv_file.close()

    # Now upload the ACK file to minio and publish message.
    with open(feedback_file_name, 'rb') as f:
        upload_to_minio(f.read(), feedback_file_name)

    await helper_add_ejv_event_to_queue(events_stan,
                                        file_name=feedback_file_name,
                                        message_type='FEEDBACKReceived')

    # Query EJV File and assert the status is changed
    ejv_file = EjvFileModel.find_by_id(ejv_file_id)
    assert ejv_file.disbursement_status_code == DisbursementStatus.COMPLETED.value
    # Assert invoice and receipt records
    for inv_id in inv_ids:
        invoice: InvoiceModel = InvoiceModel.find_by_id(inv_id)
        assert invoice.disbursement_status_code is None
        assert invoice.invoice_status_code == InvoiceStatus.APPROVED.value  # Will stay as original status
        invoice_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
            inv_id, InvoiceReferenceStatus.COMPLETED.value)
        assert invoice_ref is None  # No completed inv ref
        receipt = ReceiptModel.find_by_invoice_id_and_receipt_number(
            invoice_id=inv_id)
        assert receipt is None  # NO receipt

    # Assert payment records
    for jv_account_id in jv_account_ids:
        account = PaymentAccountModel.find_by_id(jv_account_id)
        payment: PaymentModel = PaymentModel.search_account_payments(
            auth_account_id=account.auth_account_id,
            payment_status=PaymentStatus.COMPLETED.value,
            page=1,
            limit=100)[0]
        assert len(payment) == 0
Пример #13
0
def test_payments_for_gov_accounts(session, monkeypatch):
    """Test payments for gov accounts.

    Steps:
    1) Update a distribution code with client code 112.
    2) Create multiple gov accounts for GA - 112
    3) Create multiple gov accounts for GI - NOT 112
    4) Create some transactions for these accounts
    5) Run the job and assert results.
    """
    monkeypatch.setattr('pysftp.Connection.put', lambda *args, **kwargs: None)

    corp_type = 'BEN'
    filing_type = 'BCINC'

    # Find fee schedule which have service fees.
    fee_schedule: FeeSchedule = FeeSchedule.find_by_filing_type_and_corp_type(corp_type, filing_type)
    # Create a service fee distribution code
    service_fee_dist_code = factory_distribution(name='service fee', client='112', reps_centre='99999',
                                                 service_line='99999',
                                                 stob='9999', project_code='9999999')
    service_fee_dist_code.save()

    dist_code: DistributionCode = DistributionCode.find_by_active_for_fee_schedule(fee_schedule.fee_schedule_id)
    # Update fee dist code to match the requirement.
    dist_code.client = '112'
    dist_code.responsibility_centre = '22222'
    dist_code.service_line = '33333'
    dist_code.stob = '4444'
    dist_code.project_code = '5555555'
    dist_code.service_fee_distribution_code_id = service_fee_dist_code.distribution_code_id
    dist_code.save()

    # GA
    jv_account_1 = factory_create_ejv_account(auth_account_id='1')
    jv_account_2 = factory_create_ejv_account(auth_account_id='2')

    # GI
    jv_account_3 = factory_create_ejv_account(auth_account_id='3', client='111')
    jv_account_4 = factory_create_ejv_account(auth_account_id='4', client='111')

    jv_accounts = [jv_account_1, jv_account_2, jv_account_3, jv_account_4]
    inv_ids = []
    for jv_acc in jv_accounts:
        inv = factory_invoice(payment_account=jv_acc, corp_type_code=corp_type, total=101.5,
                              status_code=InvoiceStatus.APPROVED.value, payment_method_code=None)
        factory_payment_line_item(invoice_id=inv.id,
                                  fee_schedule_id=fee_schedule.fee_schedule_id,
                                  filing_fees=100,
                                  total=100,
                                  service_fees=1.5,
                                  fee_dist_id=dist_code.distribution_code_id)
        inv_ids.append(inv.id)

    EjvPaymentTask.create_ejv_file()

    # Lookup invoice and assert invoice status
    for inv_id in inv_ids:
        invoice_ref = InvoiceReference.find_reference_by_invoice_id_and_status(inv_id,
                                                                               InvoiceReferenceStatus.ACTIVE.value)
        assert invoice_ref

        ejv_inv_link = db.session.query(EjvInvoiceLink).filter(EjvInvoiceLink.invoice_id == inv_id).first()
        assert ejv_inv_link

        ejv_header = db.session.query(EjvHeader).filter(EjvHeader.id == ejv_inv_link.ejv_header_id).first()
        assert ejv_header.disbursement_status_code == DisbursementStatus.UPLOADED.value
        assert ejv_header

        ejv_file: EjvFile = EjvFile.find_by_id(ejv_header.ejv_file_id)
        assert ejv_file
        assert ejv_file.disbursement_status_code == DisbursementStatus.UPLOADED.value
        assert not ejv_file.is_distribution
Пример #14
0
    def _create_ejv_file_for_gov_account(cls, batch_type: str):  # pylint:disable=too-many-locals, too-many-statements
        """Create EJV file for the partner and upload."""
        ejv_content: str = ''
        batch_total: float = 0
        control_total: int = 0

        # Create a ejv file model record.
        ejv_file_model: EjvFileModel = EjvFileModel(
            is_distribution=False,
            file_ref=cls.get_file_name(),
            disbursement_status_code=DisbursementStatus.UPLOADED.value
        ).flush()
        batch_number = cls.get_batch_number(ejv_file_model.id)

        # Get all invoices which should be part of the batch type.
        account_ids = cls._get_account_ids_for_payment(batch_type)

        # JV Batch Header
        batch_header: str = cls.get_batch_header(batch_number, batch_type)

        for account_id in account_ids:
            account_jv: str = ''
            # Find all invoices for the gov account to pay.
            invoices = cls._get_invoices_for_payment(account_id)
            # If no invoices continue.
            if not invoices:
                continue

            pay_account: PaymentAccountModel = PaymentAccountModel.find_by_id(account_id)
            disbursement_desc = f'{pay_account.auth_account_name[:100]:<100}'
            effective_date: str = cls.get_effective_date()
            # Construct journal name
            ejv_header_model: EjvFileModel = EjvHeaderModel(
                payment_account_id=account_id,
                disbursement_status_code=DisbursementStatus.UPLOADED.value,
                ejv_file_id=ejv_file_model.id
            ).flush()
            journal_name: str = cls.get_journal_name(ejv_header_model.id)
            # Distribution code for the account.
            debit_distribution_code: DistributionCodeModel = DistributionCodeModel.find_by_active_for_account(
                account_id
            )
            debit_distribution = cls.get_distribution_string(debit_distribution_code)  # Debit from GOV account GL

            line_number: int = 0
            total: float = 0
            for inv in invoices:
                line_items = inv.payment_line_items
                control_total += 1

                for line in line_items:
                    # Line can have 2 distribution, 1 for the total and another one for service fees.
                    line_distribution_code: DistributionCodeModel = DistributionCodeModel.find_by_id(
                        line.fee_distribution_id)
                    service_fee_distribution_code: DistributionCodeModel = DistributionCodeModel.find_by_id(
                        line_distribution_code.service_fee_distribution_code_id)
                    if line.total > 0:
                        total += line.total
                        line_distribution = cls.get_distribution_string(line_distribution_code)
                        flow_through = f'{line.invoice_id:<110}'
                        # Credit to BCREG GL
                        line_number += 1
                        control_total += 1
                        account_jv = account_jv + cls.get_jv_line(batch_type, line_distribution, disbursement_desc,
                                                                  effective_date, flow_through, journal_name,
                                                                  line.total,
                                                                  line_number, 'C')

                        # Debit from GOV ACCOUNT GL
                        line_number += 1
                        control_total += 1
                        account_jv = account_jv + cls.get_jv_line(batch_type, debit_distribution, disbursement_desc,
                                                                  effective_date, flow_through, journal_name,
                                                                  line.total,
                                                                  line_number, 'D')
                    if line.service_fees > 0:
                        total += line.service_fees
                        service_fee_distribution = cls.get_distribution_string(service_fee_distribution_code)
                        flow_through = f'{line.invoice_id:<110}'
                        # Credit to BCREG GL
                        line_number += 1
                        control_total += 1
                        account_jv = account_jv + cls.get_jv_line(batch_type, service_fee_distribution,
                                                                  disbursement_desc,
                                                                  effective_date, flow_through, journal_name,
                                                                  line.service_fees,
                                                                  line_number, 'C')

                        # Debit from GOV ACCOUNT GL
                        line_number += 1
                        control_total += 1
                        account_jv = account_jv + cls.get_jv_line(batch_type, debit_distribution, disbursement_desc,
                                                                  effective_date, flow_through, journal_name,
                                                                  line.service_fees,
                                                                  line_number, 'D')
            batch_total += total

            # A JV header for each account.
            account_jv = cls.get_jv_header(batch_type, cls.get_journal_batch_name(batch_number),
                                           journal_name, total) + account_jv
            ejv_content = ejv_content + account_jv

            # Create ejv invoice link records and set invoice status
            for inv in invoices:
                # Create Ejv file link and flush
                EjvInvoiceLinkModel(invoice_id=inv.id, ejv_header_id=ejv_header_model.id,
                                    disbursement_status_code=DisbursementStatus.UPLOADED.value).flush()
                # Set distribution status to invoice
                # Create invoice reference record
                inv_ref = InvoiceReferenceModel(
                    invoice_id=inv.id,
                    invoice_number=generate_transaction_number(inv.id),
                    reference_number=None,
                    status_code=InvoiceReferenceStatus.ACTIVE.value
                )
                inv_ref.flush()

        if not ejv_content:
            db.session.rollback()
            return

        # JV Batch Trailer
        batch_trailer: str = cls.get_batch_trailer(batch_number, batch_total, batch_type, control_total)

        ejv_content = f'{batch_header}{ejv_content}{batch_trailer}'

        # Create a file add this content.
        file_path_with_name, trg_file_path = cls.create_inbox_and_trg_files(ejv_content)

        # Upload file and trg to FTP
        cls.upload(ejv_content, cls.get_file_name(), file_path_with_name, trg_file_path)

        # commit changes to DB
        db.session.commit()

        # Add a sleep to prevent collision on file name.
        time.sleep(1)