def test_create_ejv_payment_request(session, client, jwt, app): """Assert payment request works for EJV accounts.""" token = jwt.create_jwt(get_claims(role=Role.SYSTEM.value), token_header) headers = { 'Authorization': f'Bearer {token}', 'content-type': 'application/json' } # Create account first rv = client.post('/api/v1/accounts', data=json.dumps(get_gov_account_payload(account_id=1234)), headers=headers) auth_account_id = rv.json.get('authAccountId') payment_account: PaymentAccountModel = PaymentAccountModel.find_by_auth_account_id( auth_account_id) dist_code: DistributionCodeModel = DistributionCodeModel.find_by_active_for_account( payment_account.id) assert dist_code assert dist_code.account_id == payment_account.id token = jwt.create_jwt(get_claims(), token_header) headers = { 'Authorization': f'Bearer {token}', 'content-type': 'application/json', 'Account-Id': auth_account_id } rv = client.post('/api/v1/payment-requests', data=json.dumps(get_payment_request()), headers=headers) assert rv.json.get('paymentMethod') == PaymentMethod.EJV.value assert rv.json.get('statusCode') == InvoiceStatus.APPROVED.value
def find_active_by_account_id(account_id: int) -> DistributionCode: """Find active distribution code by account_id.""" current_app.logger.debug(f'<find_active_by_account_id, {account_id}') distribution_code = DistributionCodeModel.find_by_active_for_account( account_id) dist_code_svc = DistributionCode() dist_code_svc._dao = distribution_code # pylint: disable=protected-access current_app.logger.debug('>find_active_by_account_id') return dist_code_svc
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( file_type=EjvFileType.PAYMENT.value, 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) current_app.logger.info('Processing accounts.') 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) pay_account: PaymentAccountModel = PaymentAccountModel.find_by_id( account_id) # If no invoices continue. if not invoices or not pay_account.billable: continue disbursement_desc = f'{pay_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 current_app.logger.info( f'Processing invoices for account_id: {account_id}.') for inv in invoices: # If it's a JV reversal credit and debit is reversed. is_jv_reversal = inv.invoice_status_code == InvoiceStatus.REFUND_REQUESTED.value # If it's reversal, If there is no COMPLETED invoice reference, then no need to reverse it. # Else mark it as CANCELLED, as new invoice reference will be created if is_jv_reversal: if (inv_ref := InvoiceReferenceModel. find_reference_by_invoice_id_and_status( inv.id, InvoiceReferenceStatus.COMPLETED.value) ) is None: continue inv_ref.status_code = InvoiceReferenceStatus.CANCELLED.value line_items = inv.payment_line_items for line in line_items: if line.total == 0: continue # 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) 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 # If it's normal payment then the Line distribution goes as Credit, # else it goes as Debit as we need to debit the fund from BC registry GL. 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' if not is_jv_reversal else 'D') # Debit from GOV ACCOUNT GL line_number += 1 control_total += 1 # If it's normal payment then the Gov account GL goes as Debit, # else it goes as Credit as we need to credit the fund back to ministry. 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 not is_jv_reversal else 'C') if line.service_fees > 0: service_fee_distribution_code: DistributionCodeModel = DistributionCodeModel.find_by_id( line_distribution_code. service_fee_distribution_code_id) 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' if not is_jv_reversal else 'D') # 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' if not is_jv_reversal else 'C') batch_total += total # Skip if we have no total from the invoices. if total > 0: # A JV header for each account. control_total += 1 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 current_app.logger.info( 'Creating ejv invoice link records and setting invoice status.' ) for inv in invoices: current_app.logger.debug( f'Creating EJV Invoice Link for invoice id: {inv.id}') # Create Ejv file link and flush ejv_invoice_link = EjvInvoiceLinkModel( invoice_id=inv.id, ejv_header_id=ejv_header_model.id, disbursement_status_code=DisbursementStatus.UPLOADED.value) db.session.add(ejv_invoice_link) # Set distribution status to invoice # Create invoice reference record current_app.logger.debug( f'Creating Invoice Reference for invoice id: {inv.id}') inv_ref = InvoiceReferenceModel( invoice_id=inv.id, invoice_number=generate_transaction_number(inv.id), reference_number=None, status_code=InvoiceReferenceStatus.ACTIVE.value) db.session.add(inv_ref) db.session.flush( ) # Instead of flushing every entity, flush all at once.
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
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)