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
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()
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_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
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
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 _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()
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_invoice_reference(invoice_id: int, invoice_number: str = '10021'): """Factory.""" return InvoiceReference(invoice_id=invoice_id, status_code='CREATED', invoice_number=invoice_number)
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_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
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
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)