Example #1
0
def test_receipt_find_by_id(session):
    """Assert a invoice is stored.

    Start with a blank database.
    """
    payment_account = factory_payment_account()
    payment = factory_payment()
    payment_account.save()
    payment.save()
    invoice = factory_invoice(payment_id=payment.id,
                              account_id=payment_account.id)
    invoice = invoice.save()
    receipt = Receipt()
    receipt.receipt_amount = 100
    receipt.receipt_date = datetime.now()
    receipt.invoice_id = invoice.id
    receipt.receipt_number = '123451'
    receipt = receipt.save()
    receipt = receipt.find_by_id(receipt.id)
    assert receipt is not None
    receipt = receipt.find_by_invoice_id_and_receipt_number(
        invoice.id, '123451')
    assert receipt is not None
    receipt = receipt.find_by_invoice_id_and_receipt_number(invoice.id, None)
    assert receipt is not None
Example #2
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'))
Example #3
0
    def find_by_id(receipt_id: int):
        """Find by receipt id."""
        receipt_dao = ReceiptModel.find_by_id(receipt_id)

        receipt = Receipt()
        receipt._dao = receipt_dao  # pylint: disable=protected-access

        current_app.logger.debug('>find_by_id')
        return receipt
    def _cancel_rs_invoices(cls):
        """Cancel routing slip invoices in CFS."""
        invoices: List[InvoiceModel] = InvoiceModel.query \
            .filter(InvoiceModel.payment_method_code == PaymentMethod.INTERNAL.value) \
            .filter(InvoiceModel.invoice_status_code == InvoiceStatus.REFUND_REQUESTED.value) \
            .filter(InvoiceModel.routing_slip is not None) \
            .order_by(InvoiceModel.created_on.asc()).all()

        current_app.logger.info(
            f'Found {len(invoices)} to be cancelled in CFS.')
        for invoice in invoices:
            # call unapply rcpts
            # adjust invoice to zero
            current_app.logger.debug(f'Calling the invoice {invoice.id}')
            routing_slip = RoutingSlipModel.find_by_number(
                invoice.routing_slip)
            routing_slip_payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
                routing_slip.payment_account_id)
            cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
                routing_slip_payment_account.id)
            # Find COMPLETED invoice reference; as unapply has to be done only if invoice is created and applied in CFS.
            invoice_reference = InvoiceReferenceModel. \
                find_reference_by_invoice_id_and_status(invoice.id, status_code=InvoiceReferenceStatus.COMPLETED.value)
            if invoice_reference:
                current_app.logger.debug(
                    f'Found invoice reference - {invoice_reference.invoice_number}'
                )
                try:
                    # find receipts against the invoice and unapply
                    # apply receipt now
                    receipts: List[
                        ReceiptModel] = ReceiptModel.find_all_receipts_for_invoice(
                            invoice_id=invoice.id)
                    for receipt in receipts:
                        CFSService.unapply_receipt(
                            cfs_account, receipt.receipt_number,
                            invoice_reference.invoice_number)

                    adjustment_negative_amount = -invoice.total
                    CFSService.adjust_invoice(
                        cfs_account=cfs_account,
                        inv_number=invoice_reference.invoice_number,
                        amount=adjustment_negative_amount)

                except Exception as e:  # NOQA # pylint: disable=broad-except
                    capture_message(
                        f'Error on canelling Routing Slip invoice: invoice id={invoice.id}, '
                        f'routing slip : {routing_slip.id}, ERROR : {str(e)}',
                        level='error')
                    current_app.logger.error(e)
                    # TODO stop execution ? what should be the invoice stats ; should we set it to error or retry?
                    continue

                invoice_reference.status_code = InvoiceReferenceStatus.CANCELLED.value

            invoice.invoice_status_code = InvoiceStatus.REFUNDED.value
            invoice.save()
Example #5
0
def factory_receipt(invoice_id: int,
                    receipt_number: str = 'TEST1234567890',
                    receipt_date: datetime = datetime.now(),
                    receipt_amount: float = 10.0):
    """Return Factory."""
    return Receipt(invoice_id=invoice_id,
                   receipt_number=receipt_number,
                   receipt_date=receipt_date,
                   receipt_amount=receipt_amount)
Example #6
0
    def find_by_invoice_id_and_receipt_number(invoice_id: int, receipt_number: str = None):
        """Find by the combination of invoce id and receipt number."""
        receipt_dao = ReceiptModel.find_by_invoice_id_and_receipt_number(invoice_id, receipt_number)

        receipt = Receipt()
        receipt._dao = receipt_dao  # pylint: disable=protected-access

        current_app.logger.debug('>find_by_invoice_id_and_receipt_number')
        return receipt
Example #7
0
def _process_failed_payments(row):
    """Handle failed payments."""
    # 1. Set the cfs_account status as FREEZE.
    # 2. Call cfs api to Stop further PAD on this account.
    # 3. Reverse the invoice_reference status to ACTIVE, invoice status to SETTLEMENT_SCHED, and delete receipt.
    # 4. Create an NSF invoice for this account.
    # 5. Create invoice reference for the newly created NSF invoice.
    # 6. Adjust invoice in CFS to include NSF fees.
    inv_number = _get_row_value(row, Column.TARGET_TXN_NO)
    # If there is a FAILED payment record for this; it means it's a duplicate event. Ignore it.
    payment: PaymentModel = PaymentModel.find_payment_by_invoice_number_and_status(
        inv_number, PaymentStatus.FAILED.value)
    if payment:
        logger.info('Ignoring duplicate NSF message for invoice : %s ',
                    inv_number)
        return

    # Set CFS Account Status.
    payment_account: PaymentAccountModel = _get_payment_account(row)
    cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
        payment_account.id)
    logger.info('setting payment account id : %s status as FREEZE',
                payment_account.id)
    cfs_account.status = CfsAccountStatus.FREEZE.value
    # Call CFS to stop any further PAD transactions on this account.
    CFSService.suspend_cfs_account(cfs_account)
    # Find the invoice_reference for this invoice and mark it as ACTIVE.
    inv_references: List[InvoiceReferenceModel] = db.session.query(InvoiceReferenceModel). \
        filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.COMPLETED.value). \
        filter(InvoiceReferenceModel.invoice_number == inv_number). \
        all()

    # Update status to ACTIVE, if it was marked COMPLETED
    for inv_reference in inv_references:
        inv_reference.status_code = InvoiceReferenceStatus.ACTIVE.value
        # Find receipt and delete it.
        receipt: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number(
            invoice_id=inv_reference.invoice_id)
        if receipt:
            db.session.delete(receipt)
        # Find invoice and update the status to SETTLEMENT_SCHED
        invoice: InvoiceModel = InvoiceModel.find_by_id(
            identifier=inv_reference.invoice_id)
        invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value
        invoice.paid = 0

    # Create an invoice for NSF for this account
    invoice = _create_nsf_invoice(cfs_account, inv_number, payment_account)
    # Adjust CFS invoice
    CFSService.add_nsf_adjustment(cfs_account=cfs_account,
                                  inv_number=inv_number,
                                  amount=invoice.total)
Example #8
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'))
Example #9
0
def test_receipt(session):
    """Assert a receipt is stored.

    Start with a blank database.
    """
    payment_account = factory_payment_account()
    payment = factory_payment()
    payment_account.save()
    payment.save()
    invoice = factory_invoice(payment_account=payment_account)
    invoice = invoice.save()
    receipt = Receipt()
    receipt.receipt_amount = 100
    receipt.receipt_date = datetime.now()
    receipt.invoice_id = invoice.id
    receipt.receipt_number = '123451'
    receipt = receipt.save()
    assert receipt.id is not None
Example #10
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
Example #11
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()
Example #12
0
def factory_receipt(invoice_id: int, receipt_number: str = '10021'):
    """Return Factory."""
    return Receipt(invoice_id=invoice_id, receipt_number=receipt_number).save()
Example #13
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
Example #14
0
async def test_pad_reversal_reconciliations(session, app, stan_server,
                                            event_loop, client_id, events_stan,
                                            future, mock_publish):
    """Test Reconciliations worker for NSF."""
    # Call back for the subscription
    from reconciliations.worker import cb_subscription_handler

    # 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 payment account
    # 2. Create invoices and related records for a completed payment
    # 3. Create CFS Invoice records
    # 4. Create a CFS settlement file, and verify the records
    cfs_account_number = '1234'
    pay_account: PaymentAccountModel = factory_create_pad_account(
        status=CfsAccountStatus.ACTIVE.value,
        account_number=cfs_account_number)
    invoice1: InvoiceModel = factory_invoice(
        payment_account=pay_account,
        total=100,
        service_fees=10.0,
        payment_method_code=PaymentMethod.PAD.value,
        status_code=InvoiceStatus.PAID.value)
    factory_payment_line_item(invoice_id=invoice1.id,
                              filing_fees=90.0,
                              service_fees=10.0,
                              total=90.0)

    invoice2: InvoiceModel = factory_invoice(
        payment_account=pay_account,
        total=200,
        service_fees=10.0,
        payment_method_code=PaymentMethod.PAD.value,
        status_code=InvoiceStatus.PAID.value)
    factory_payment_line_item(invoice_id=invoice2.id,
                              filing_fees=190.0,
                              service_fees=10.0,
                              total=190.0)

    invoice_number = '1234567890'
    receipt_number = '9999999999'

    factory_invoice_reference(
        invoice_id=invoice1.id,
        invoice_number=invoice_number,
        status_code=InvoiceReferenceStatus.COMPLETED.value)
    factory_invoice_reference(
        invoice_id=invoice2.id,
        invoice_number=invoice_number,
        status_code=InvoiceReferenceStatus.COMPLETED.value)

    receipt_id1 = factory_receipt(invoice_id=invoice1.id,
                                  receipt_number=receipt_number).save().id
    receipt_id2 = factory_receipt(invoice_id=invoice2.id,
                                  receipt_number=receipt_number).save().id

    invoice1_id = invoice1.id
    invoice2_id = invoice2.id
    pay_account_id = pay_account.id

    total = invoice1.total + invoice2.total

    payment = factory_payment(pay_account=pay_account,
                              paid_amount=total,
                              invoice_amount=total,
                              invoice_number=invoice_number,
                              receipt_number=receipt_number,
                              status=PaymentStatus.COMPLETED.value)
    pay_id = payment.id

    # Now publish message saying payment has been reversed.
    # Create a settlement file and publish.
    file_name: str = 'cas_settlement_file.csv'
    # Settlement row
    date = datetime.now().strftime('%d-%b-%y')
    row = [
        RecordType.PADR.value, SourceTransaction.PAD.value, receipt_number,
        100001, date, 0, cfs_account_number, 'INV', invoice_number, total,
        total, Status.NOT_PAID.value
    ]
    create_and_upload_settlement_file(file_name, [row])
    await helper_add_event_to_queue(events_stan, file_name=file_name)

    # The invoice should be in SETTLEMENT_SCHEDULED status and Payment should be FAILED
    updated_invoice1 = InvoiceModel.find_by_id(invoice1_id)
    assert updated_invoice1.invoice_status_code == InvoiceStatus.SETTLEMENT_SCHEDULED.value
    updated_invoice2 = InvoiceModel.find_by_id(invoice2_id)
    assert updated_invoice2.invoice_status_code == InvoiceStatus.SETTLEMENT_SCHEDULED.value

    payment: PaymentModel = PaymentModel.find_by_id(pay_id)
    assert payment.payment_status_code == PaymentStatus.FAILED.value
    assert payment.paid_amount == 0
    assert payment.receipt_number == receipt_number
    assert payment.payment_method_code == PaymentMethod.PAD.value
    assert payment.invoice_number == invoice_number

    cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
        pay_account_id)
    assert cfs_account.status == CfsAccountStatus.FREEZE.value

    # Receipt should be deleted
    assert ReceiptModel.find_by_id(receipt_id1) is None
    assert ReceiptModel.find_by_id(receipt_id2) is None
Example #15
0
async def test_pad_reconciliations_with_credit_memo(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 payment account
    # 2. Create invoices and related records
    # 3. Create CFS Invoice records
    # 4. Mimic some credits on the account
    # 4. Create a CFS settlement file, and verify the records
    cfs_account_number = '1234'
    pay_account: PaymentAccountModel = factory_create_pad_account(
        status=CfsAccountStatus.ACTIVE.value,
        account_number=cfs_account_number)
    invoice1: InvoiceModel = factory_invoice(
        payment_account=pay_account,
        total=100,
        service_fees=10.0,
        payment_method_code=PaymentMethod.PAD.value)
    factory_payment_line_item(invoice_id=invoice1.id,
                              filing_fees=90.0,
                              service_fees=10.0,
                              total=90.0)

    invoice2: InvoiceModel = factory_invoice(
        payment_account=pay_account,
        total=200,
        service_fees=10.0,
        payment_method_code=PaymentMethod.PAD.value)
    factory_payment_line_item(invoice_id=invoice2.id,
                              filing_fees=190.0,
                              service_fees=10.0,
                              total=190.0)

    invoice_number = '1234567890'

    factory_invoice_reference(invoice_id=invoice1.id,
                              invoice_number=invoice_number)
    factory_invoice_reference(invoice_id=invoice2.id,
                              invoice_number=invoice_number)

    invoice1.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value
    invoice2.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value
    invoice1.save()
    invoice2.save()
    invoice1_id = invoice1.id
    invoice2_id = invoice2.id
    total = invoice1.total + invoice2.total

    # Create a settlement file and publish.
    file_name: str = 'cas_settlement_file.csv'
    # Settlement row
    receipt_number = '1234567890'
    credit_memo_number = 'CM123'
    date = datetime.now().strftime('%d-%b-%y')
    credit_amount = 25

    credit_row = [
        RecordType.CMAP.value, SourceTransaction.CREDIT_MEMO.value,
        credit_memo_number, 100002, date, credit_amount, cfs_account_number,
        'INV', invoice_number, total, 0, Status.PAID.value
    ]
    pad_row = [
        RecordType.PAD.value, SourceTransaction.PAD.value, receipt_number,
        100001, date, total - credit_amount, cfs_account_number, 'INV',
        invoice_number, total, 0, Status.PAID.value
    ]
    create_and_upload_settlement_file(file_name, [credit_row, pad_row])
    await helper_add_event_to_queue(events_stan, file_name=file_name)

    # The invoice should be in PAID status and Payment should be completed
    updated_invoice1 = InvoiceModel.find_by_id(invoice1_id)
    assert updated_invoice1.invoice_status_code == InvoiceStatus.PAID.value
    updated_invoice2 = InvoiceModel.find_by_id(invoice2_id)
    assert updated_invoice2.invoice_status_code == InvoiceStatus.PAID.value

    payment: PaymentModel = PaymentModel.find_payment_by_receipt_number(
        receipt_number)
    assert payment.payment_status_code == PaymentStatus.COMPLETED.value
    assert payment.paid_amount == total - credit_amount
    assert payment.receipt_number == receipt_number
    assert payment.payment_method_code == PaymentMethod.PAD.value
    assert payment.invoice_number == invoice_number

    rcpt1: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number(
        invoice1_id, receipt_number)
    rcpt2: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number(
        invoice2_id, receipt_number)
    assert rcpt1
    assert rcpt2
    assert rcpt1.receipt_date == rcpt2.receipt_date
Example #16
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