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
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'))
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()
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)
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
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)
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'))
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
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
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()
def factory_receipt(invoice_id: int, receipt_number: str = '10021'): """Return Factory.""" return Receipt(invoice_id=invoice_id, receipt_number=receipt_number).save()
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
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
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
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