def save_or_update(distribution_details: Dict, dist_id: int = None): """Save distribution.""" current_app.logger.debug('<save_or_update') dist_code_svc = DistributionCode() if dist_id is not None: dist_code_dao = DistributionCodeModel.find_by_id(dist_id) dist_code_svc._dao = dist_code_dao # pylint: disable=protected-access if distribution_details.get('endDate', None): dist_code_svc.end_date = parser.parse( distribution_details.get('endDate')) if distribution_details.get('startDate', None): dist_code_svc.start_date = parser.parse( distribution_details.get('startDate')) else: dist_code_svc.start_date = date.today() _has_code_changes: bool = dist_code_svc.client != distribution_details.get('client', None) \ or dist_code_svc.responsibility_centre != distribution_details.get('responsibilityCentre', None) \ or dist_code_svc.service_line != distribution_details.get('serviceLine', None) \ or dist_code_svc.project_code != distribution_details.get('projectCode', None) \ or dist_code_svc.service_fee_distribution_code_id != \ distribution_details.get('serviceFeeDistributionCodeId', None) dist_code_svc.client = distribution_details.get('client', None) dist_code_svc.name = distribution_details.get('name', None) dist_code_svc.responsibility_centre = distribution_details.get( 'responsibilityCentre', None) dist_code_svc.service_line = distribution_details.get( 'serviceLine', None) dist_code_svc.stob = distribution_details.get('stob', None) dist_code_svc.project_code = distribution_details.get( 'projectCode', None) dist_code_svc.service_fee_distribution_code_id = distribution_details.get( 'serviceFeeDistributionCodeId', None) dist_code_svc.disbursement_distribution_code_id = distribution_details.get( 'disbursementDistributionCodeId', None) dist_code_svc.account_id = distribution_details.get('accountId', None) if _has_code_changes and dist_id is not None: # Update all invoices which used this distribution for updating revenue account details # If this is a service fee distribution, then find all distribution which uses this and update them. InvoiceModel.update_invoices_for_revenue_updates(dist_id) for dist in DistributionCodeModel.find_by_service_fee_distribution_id( dist_id): InvoiceModel.update_invoices_for_revenue_updates( dist.distribution_code_id) # Reset stop jv for every dave. dist_code_svc.stop_ejv = False dist_code_dao = dist_code_svc.save() distribution_code_schema = DistributionCodeSchema() current_app.logger.debug('>save_or_update') return distribution_code_schema.dump(dist_code_dao, many=False)
def _dao(self, value): self.__dao = value self._distribution_code_id: int = self._dao.distribution_code_id self._name: str = self._dao.name self._client: str = self._dao.client self._responsibility_centre: str = self._dao.responsibility_centre self._service_line: str = self._dao.service_line self._stob: str = self._dao.stob self._project_code: str = self._dao.project_code if self._dao.service_fee_distribution_code_id: _service_fee: DistributionCodeModel = DistributionCodeModel.find_by_id( self._dao.service_fee_distribution_code_id) self._service_fee_name: str = _service_fee.name self._service_fee_client: str = _service_fee.client self._service_fee_responsibility_centre: str = _service_fee.responsibility_centre self._service_fee_line: str = _service_fee.service_line self._service_fee_stob: str = _service_fee.stob self._service_fee_project_code: str = _service_fee.project_code self._start_date: date = self._dao.start_date self._end_date: date = self._dao.end_date self._service_fee_distribution_code_id = self._dao.service_fee_distribution_code_id self._disbursement_distribution_code_id = self._dao.disbursement_distribution_code_id self._stop_ejv: bool = self._dao.stop_ejv self._account_id: int = self._dao.account_id
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 test_get_payment_system_url_service_fees(session, public_user_mock): """Assert that the url returned is correct.""" today = current_local_time().strftime(PAYBC_DATE_FORMAT) payment_account = factory_payment_account() payment = factory_payment() payment_account.save() payment.save() invoice = factory_invoice(payment_account) invoice.save() invoice_ref = factory_invoice_reference(invoice.id).save() fee_schedule = FeeSchedule.find_by_filing_type_and_corp_type('CP', 'OTANN') distribution_code = DistributionCodeModel.find_by_active_for_fee_schedule(fee_schedule.fee_schedule_id) distribution_code_svc = DistributionCode() distribution_code_payload = get_distribution_code_payload() # Set service fee distribution distribution_code_payload.update({'serviceFeeDistributionCodeId': distribution_code.distribution_code_id}) # update the existing gl code with new values distribution_code_svc.save_or_update(distribution_code_payload, distribution_code.distribution_code_id) service_fee = 100 line = factory_payment_line_item(invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id, service_fees=service_fee) line.save() direct_pay_service = DirectPayService() payment_response_url = direct_pay_service.get_payment_system_url_for_invoice(invoice, invoice_ref, 'google.com') url_param_dict = dict(urllib.parse.parse_qsl(urllib.parse.urlsplit(payment_response_url).query)) assert url_param_dict['trnDate'] == today assert url_param_dict['glDate'] == today assert url_param_dict['description'] == 'Direct_Sale' assert url_param_dict['pbcRefNumber'] == current_app.config.get('PAYBC_DIRECT_PAY_REF_NUMBER') assert url_param_dict['trnNumber'] == generate_transaction_number(invoice.id) assert url_param_dict['trnAmount'] == str(invoice.total) assert url_param_dict['paymentMethod'] == 'CC' assert url_param_dict['redirectUri'] == 'google.com' revenue_str = f"1:{distribution_code_payload['client']}." \ f"{distribution_code_payload['responsibilityCentre']}." \ f"{distribution_code_payload['serviceLine']}." \ f"{distribution_code_payload['stob']}." \ f"{distribution_code_payload['projectCode']}." \ f'000000.0000:10.00' revenue_str_service_fee = f"2:{distribution_code_payload['client']}." \ f"{distribution_code_payload['responsibilityCentre']}." \ f"{distribution_code_payload['serviceLine']}." \ f"{distribution_code_payload['stob']}." \ f"{distribution_code_payload['projectCode']}." \ f'000000.0000:{format(service_fee, DECIMAL_PRECISION)}' assert url_param_dict['revenue'] == f'{revenue_str}|{revenue_str_service_fee}' urlstring = f"trnDate={today}&pbcRefNumber={current_app.config.get('PAYBC_DIRECT_PAY_REF_NUMBER')}&" \ f'glDate={today}&description=Direct_Sale&' \ f'trnNumber={generate_transaction_number(invoice.id)}&' \ f'trnAmount={invoice.total}&' \ f'paymentMethod=CC&' \ f'redirectUri=google.com&' \ f'currency=CAD&' \ f'revenue={revenue_str}|' \ f'{revenue_str_service_fee}' expected_hash_str = HashingService.encode(urlstring) assert expected_hash_str == url_param_dict['hashValue']
def find_by_id(identifier: int): """Find distribution code by id.""" current_app.logger.debug(f'<find_by_id, {identifier}') distribution_code = DistributionCodeModel.find_by_id( identifier=identifier) distribution_code_schema = DistributionCodeSchema() current_app.logger.debug('>find_by_id') return distribution_code_schema.dump(distribution_code, many=False)
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 find_all(): """Find all distribution codes valid today.""" current_app.logger.debug('<find_all') data = {'items': []} distribution_codes = DistributionCodeModel.find_all() distribution_code_schema = DistributionCodeSchema() data['items'] = distribution_code_schema.dump(distribution_codes, many=True) current_app.logger.debug('>find_all') return data
def _create_nsf_invoice(cls, cfs_account: CfsAccountModel, rs_number: str, payment_account: PaymentAccountModel) -> InvoiceModel: """Create Invoice, line item and invoice reference records.""" fee_schedule: FeeScheduleModel = FeeScheduleModel.find_by_filing_type_and_corp_type(corp_type_code='BCR', filing_type_code='NSF') invoice = InvoiceModel( bcol_account=payment_account.bcol_account, payment_account_id=payment_account.id, cfs_account_id=cfs_account.id, invoice_status_code=InvoiceStatus.CREATED.value, total=fee_schedule.fee.amount, service_fees=0, paid=0, payment_method_code=PaymentMethod.INTERNAL.value, corp_type_code='BCR', created_on=datetime.now(), created_by='SYSTEM', routing_slip=rs_number ) invoice = invoice.save() distribution: DistributionCodeModel = DistributionCodeModel.find_by_active_for_fee_schedule( fee_schedule.fee_schedule_id) line_item = PaymentLineItemModel( invoice_id=invoice.id, total=invoice.total, fee_schedule_id=fee_schedule.fee_schedule_id, description=fee_schedule.filing_type.description, filing_fees=invoice.total, gst=0, priority_fees=0, pst=0, future_effective_fees=0, line_item_status_code=LineItemStatus.ACTIVE.value, service_fees=0, fee_distribution_id=distribution.distribution_code_id) line_item.save() invoice_response = CFSService.create_account_invoice(transaction_number=invoice.id, line_items=invoice.payment_line_items, cfs_account=cfs_account) invoice_number = invoice_response.json().get('invoice_number', None) current_app.logger.info(f'invoice_number {invoice_number} created in CFS for NSF.') InvoiceReferenceModel( invoice_id=invoice.id, invoice_number=invoice_number, reference_number=invoice_response.json().get('pbc_ref_number', None), status_code=InvoiceReferenceStatus.ACTIVE.value ).save() return invoice
def factory_payment_line_item(invoice_id: str, fee_schedule_id: int, filing_fees: int = 10, total: int = 10, service_fees: int = 0, status: str = LineItemStatus.ACTIVE.value): """Return Factory.""" return PaymentLineItem( invoice_id=invoice_id, fee_schedule_id=fee_schedule_id, filing_fees=filing_fees, total=total, service_fees=service_fees, line_item_status_code=status, fee_distribution_id=DistributionCode.find_by_active_for_fee_schedule(fee_schedule_id).distribution_code_id )
def save_or_update(distribution_details: Dict, dist_id: int = None): """Save distribution.""" current_app.logger.debug('<save_or_update') dist_code_svc = DistributionCode() if dist_id is not None: dist_code_dao = DistributionCodeModel.find_by_id(dist_id) dist_code_svc._dao = dist_code_dao # pylint: disable=protected-access if distribution_details.get('endDate', None): dist_code_svc.end_date = parser.parse( distribution_details.get('endDate')) if distribution_details.get('startDate', None): dist_code_svc.start_date = parser.parse( distribution_details.get('startDate')) else: dist_code_svc.start_date = date.today() dist_code_svc.client = distribution_details.get('client', None) dist_code_svc.responsibility_centre = distribution_details.get( 'responsibilityCentre', None) dist_code_svc.service_line = distribution_details.get( 'serviceLine', None) dist_code_svc.stob = distribution_details.get('stob', None) dist_code_svc.project_code = distribution_details.get( 'projectCode', None) dist_code_svc.service_fee_client = distribution_details.get( 'serviceFeeClient', None) dist_code_svc.service_fee_responsibility_centre = distribution_details.get( 'serviceFeeResponsibilityCentre', None) dist_code_svc.service_fee_line = distribution_details.get( 'serviceFeeLine', None) dist_code_svc.service_fee_stob = distribution_details.get( 'serviceFeeStob', None) dist_code_svc.service_fee_project_code = distribution_details.get( 'serviceFeeProjectCode', None) if dist_id is not None: # Update all invoices which used this distribution for updating revenue account details InvoiceModel.update_invoices_for_revenue_updates(dist_id) dist_code_dao = dist_code_svc.save() distribution_code_schema = DistributionCodeSchema() current_app.logger.debug('>save_or_update') return distribution_code_schema.dump(dist_code_dao, many=False)
def _create_nsf_invoice(cfs_account: CfsAccountModel, inv_number: str, payment_account: PaymentAccountModel) -> InvoiceModel: """Create Invoice, line item and invoice referwnce records.""" fee_schedule: FeeScheduleModel = FeeScheduleModel.find_by_filing_type_and_corp_type( corp_type_code='BCR', filing_type_code='NSF') invoice = InvoiceModel(bcol_account=payment_account.bcol_account, payment_account_id=payment_account.id, cfs_account_id=cfs_account.id, invoice_status_code=InvoiceStatus.CREATED.value, total=fee_schedule.fee.amount, service_fees=0, paid=0, payment_method_code=PaymentMethod.CC.value, corp_type_code='BCR', created_on=datetime.now(), created_by='SYSTEM') invoice = invoice.save() distribution: DistributionCodeModel = DistributionCodeModel.find_by_active_for_fee_schedule( fee_schedule.fee_schedule_id) line_item = PaymentLineItemModel( invoice_id=invoice.id, total=invoice.total, fee_schedule_id=fee_schedule.fee_schedule_id, description=fee_schedule.filing_type.description, filing_fees=invoice.total, gst=0, priority_fees=0, pst=0, future_effective_fees=0, line_item_status_code=LineItemStatus.ACTIVE.value, service_fees=0, fee_distribution_id=distribution.distribution_code_id if distribution else 1) # TODO line_item.save() inv_ref: InvoiceReferenceModel = InvoiceReferenceModel( invoice_id=invoice.id, invoice_number=inv_number, reference_number=InvoiceReferenceModel. find_any_active_reference_by_invoice_number( invoice_number=inv_number).reference_number, status_code=InvoiceReferenceStatus.ACTIVE.value) inv_ref.save() return invoice
def factory_distribution(name: str, client: str = '111', reps_centre: str = '22222', service_line: str = '33333', stob: str = '4444', project_code: str = '5555555', service_fee_dist_id: int = None, disbursement_dist_id: int = None): """Return Factory.""" return DistributionCode( name=name, client=client, responsibility_centre=reps_centre, service_line=service_line, stob=stob, project_code=project_code, service_fee_distribution_code_id=service_fee_dist_id, disbursement_distribution_code_id=disbursement_dist_id, start_date=datetime.today().date(), created_by='test').save()
def factory_create_ejv_account(auth_account_id='1234', client: str = '112', resp_centre: str = '11111', service_line: str = '11111', stob: str = '1111', project_code: str = '1111111'): """Return Factory.""" account = PaymentAccount(auth_account_id=auth_account_id, payment_method=PaymentMethod.EJV.value, name=f'Test {auth_account_id}').save() DistributionCode(name=account.name, client=client, responsibility_centre=resp_centre, service_line=service_line, stob=stob, project_code=project_code, account_id=account.id, start_date=datetime.today().date(), created_by='test').save() return account
def create(invoice_id: int, fee: FeeSchedule, **kwargs): """Create Payment Line Item record.""" current_app.logger.debug('<create') user: UserContext = kwargs['user'] p = PaymentLineItem() p.invoice_id = invoice_id p.total = fee.total_excluding_service_fees p.fee_schedule_id = fee.fee_schedule_id p.description = fee.description p.filing_fees = fee.fee_amount p.gst = fee.gst p.priority_fees = fee.priority_fee p.pst = fee.pst p.future_effective_fees = fee.future_effective_fee p.quantity = fee.quantity if fee.quantity else 1 p.line_item_status_code = LineItemStatus.ACTIVE.value p.waived_fees = fee.waived_fee_amount p.service_fees = fee.service_fees # Set distribution details to line item distribution_code = None if p.total > 0: distribution_code = DistributionCodeModel.find_by_active_for_fee_schedule( fee.fee_schedule_id) p.fee_distribution_id = distribution_code.distribution_code_id if fee.waived_fee_amount > 0: if user.has_role(Role.STAFF.value): p.waived_by = user.user_name else: raise BusinessException(Error.FEE_OVERRIDE_NOT_ALLOWED) p_dao = p.flush() p = PaymentLineItem() p._dao = p_dao # pylint: disable=protected-access # Set distribution model to avoid more queries to DB p.fee_distribution = distribution_code current_app.logger.debug('>create') return p
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)
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.
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_partner(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 today = datetime.now() disbursement_desc = current_app.config.get('CGI_DISBURSEMENT_DESC'). \ format(today.strftime('%B').upper(), f'{today.day:0>2}')[:100] disbursement_desc = f'{disbursement_desc:<100}' # Create a ejv file model record. ejv_file_model: EjvFileModel = EjvFileModel( file_type=EjvFileType.DISBURSEMENT.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 partner list. Each of the partner will go as a JV Header and transactions as JV Details. partners = cls._get_partners_by_batch_type(batch_type) current_app.logger.info(partners) # JV Batch Header batch_header: str = cls.get_batch_header(batch_number, batch_type) for partner in partners: # Find all invoices for the partner to disburse. # This includes invoices which are not PAID and invoices which are refunded. payment_invoices = cls._get_invoices_for_disbursement(partner) refund_reversals = cls._get_invoices_for_refund_reversal(partner) invoices = payment_invoices + refund_reversals # If no invoices continue. if not invoices: continue effective_date: str = cls.get_effective_date() # Construct journal name ejv_header_model: EjvFileModel = EjvHeaderModel( partner_code=partner.code, 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) # To populate JV Header and JV Details, group these invoices by the distribution # and create one JV Header and detail for each. distribution_code_set = set() invoice_id_list = [] for inv in invoices: invoice_id_list.append(inv.id) for line_item in inv.payment_line_items: distribution_code_set.add(line_item.fee_distribution_id) for distribution_code_id in list(distribution_code_set): distribution_code: DistributionCodeModel = DistributionCodeModel.find_by_id( distribution_code_id) credit_distribution_code: DistributionCodeModel = DistributionCodeModel.find_by_id( distribution_code.disbursement_distribution_code_id) if credit_distribution_code.stop_ejv: continue line_items = cls._find_line_items_by_invoice_and_distribution( distribution_code_id, invoice_id_list) total: float = 0 for line in line_items: total += line.total batch_total += total debit_distribution = cls.get_distribution_string( distribution_code) # Debit from BCREG GL credit_distribution = cls.get_distribution_string( credit_distribution_code) # Credit to partner GL # JV Header ejv_content = '{}{}'.format( ejv_content, # pylint:disable=consider-using-f-string cls.get_jv_header(batch_type, cls.get_journal_batch_name(batch_number), journal_name, total)) control_total += 1 line_number: int = 0 for line in line_items: # JV Details line_number += 1 # Flow Through add it as the invoice id. flow_through = f'{line.invoice_id:<110}' # debit_distribution and credit_distribution stays as is for invoices which are not PAID # For reversals, we just need to reverse the debit and credit. is_reversal = InvoiceModel.find_by_id(line.invoice_id).invoice_status_code in \ (InvoiceStatus.REFUNDED.value, InvoiceStatus.REFUND_REQUESTED.value) ejv_content = '{}{}'.format( ejv_content, # pylint:disable=consider-using-f-string cls.get_jv_line(batch_type, credit_distribution, disbursement_desc, effective_date, flow_through, journal_name, line.total, line_number, 'C' if not is_reversal else 'D')) line_number += 1 control_total += 1 # Add a line here for debit too ejv_content = '{}{}'.format( ejv_content, # pylint:disable=consider-using-f-string cls.get_jv_line(batch_type, debit_distribution, disbursement_desc, effective_date, flow_through, journal_name, line.total, line_number, 'D' if not is_reversal else 'C')) control_total += 1 # 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 inv.disbursement_status_code = DisbursementStatus.UPLOADED.value inv.flush() if not ejv_content: db.session.rollback() return # JV Batch Trailer jv_batch_trailer: str = cls.get_batch_trailer(batch_number, batch_total, batch_type, control_total) ejv_content = f'{batch_header}{ejv_content}{jv_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)
def _build_lines(cls, payment_line_items: List[PaymentLineItemModel], negate: bool = False): """Build lines for the invoice.""" # Fetch all distribution codes to reduce DB hits. Introduce caching if needed later distribution_codes: List[DistributionCodeModel] = DistributionCodeModel.find_all() lines_map = defaultdict(dict) # To group all the lines with same GL code together. index: int = 0 for line_item in payment_line_items: if line_item.total == 0: continue # Find the distribution from the above list distribution_code = [dist for dist in distribution_codes if dist.distribution_code_id == line_item.fee_distribution_id][0] \ if line_item.fee_distribution_id else None # Check if a line with same GL code exists, if YES just add up the amount. if NO, create a new line. line = lines_map[distribution_code.distribution_code_id] if not line: index = index + 1 distribution = [dict( dist_line_number=index, amount=cls._get_amount(line_item.total, negate), account=f'{distribution_code.client}.{distribution_code.responsibility_centre}.' f'{distribution_code.service_line}.{distribution_code.stob}.' f'{distribution_code.project_code}.000000.0000' )] if distribution_code else None line = { 'line_number': index, 'line_type': CFS_LINE_TYPE, 'description': line_item.description, 'unit_price': cls._get_amount(line_item.total, negate), 'quantity': 1, # 'attribute1': line_item.invoice_id, # 'attribute2': line_item.id, 'distribution': distribution } else: # Add up the price and distribution line['unit_price'] = line['unit_price'] + cls._get_amount(line_item.total, negate) line['distribution'][0]['amount'] = line['distribution'][0]['amount'] + \ cls._get_amount(line_item.total, negate) lines_map[distribution_code.distribution_code_id] = line if line_item.service_fees > 0: service_fee_distribution: DistributionCodeModel = DistributionCodeModel.find_by_id( distribution_code.service_fee_distribution_code_id) service_line = lines_map[service_fee_distribution.distribution_code_id] if not service_line: index = index + 1 service_line = { 'line_number': index, 'line_type': CFS_LINE_TYPE, 'description': 'Service Fee', 'unit_price': cls._get_amount(line_item.service_fees, negate), 'quantity': 1, # 'attribute1': line_item.invoice_id, # 'attribute2': line_item.id, 'distribution': [ { 'dist_line_number': index, 'amount': cls._get_amount(line_item.service_fees, negate), 'account': f'{service_fee_distribution.client}.' f'{service_fee_distribution.responsibility_centre}.' f'{service_fee_distribution.service_line}.' f'{service_fee_distribution.stob}.' f'{service_fee_distribution.project_code}.000000.0000' } ] } else: # Add up the price and distribution service_line['unit_price'] = service_line['unit_price'] + \ cls._get_amount(line_item.service_fees, negate) service_line['distribution'][0]['amount'] = service_line['distribution'][0]['amount'] + \ cls._get_amount(line_item.service_fees, negate) lines_map[service_fee_distribution.distribution_code_id] = service_line return list(lines_map.values())
def _build_lines(cls, payment_line_items: List[PaymentLineItemModel], negate: bool = False): """Build lines for the invoice.""" # Fetch all distribution codes to reduce DB hits. Introduce caching if needed later distribution_codes: List[ DistributionCodeModel] = DistributionCodeModel.find_all() lines = [] index: int = 0 for line_item in payment_line_items: # Find the distribution from the above list distribution_code = [dist for dist in distribution_codes if dist.distribution_code_id == line_item.fee_distribution_id][0] \ if line_item.fee_distribution_id else None index = index + 1 distribution = [ dict( dist_line_number=index, amount=cls._get_amount(line_item.total, negate), account= f'{distribution_code.client}.{distribution_code.responsibility_centre}.' f'{distribution_code.service_line}.{distribution_code.stob}.' f'{distribution_code.project_code}.000000.0000') ] if distribution_code else None lines.append({ 'line_number': index, 'line_type': CFS_LINE_TYPE, 'description': line_item.description, 'unit_price': cls._get_amount(line_item.total, negate), 'quantity': 1, 'attribute1': line_item.invoice_id, 'attribute2': line_item.id, 'distribution': distribution }) if line_item.service_fees > 0: service_fee_distribution: DistributionCodeModel = DistributionCodeModel.find_by_id( distribution_code.service_fee_distribution_code_id) index = index + 1 lines.append({ 'line_number': index, 'line_type': CFS_LINE_TYPE, 'description': 'Service Fee', 'unit_price': cls._get_amount(line_item.service_fees, negate), 'quantity': 1, 'attribute1': line_item.invoice_id, 'attribute2': line_item.id, 'distribution': [{ 'dist_line_number': index, 'amount': cls._get_amount(line_item.service_fees, negate), 'account': f'{service_fee_distribution.client}.' f'{service_fee_distribution.responsibility_centre}.' f'{service_fee_distribution.service_line}.' f'{service_fee_distribution.stob}.' f'{service_fee_distribution.project_code}.000000.0000' }] }) return lines
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
async def test_succesful_partner_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 payment account # 2. Create invoice and related records # 3. Create CFS Invoice records # 4. Create a CFS settlement file, and verify the records cfs_account_number = '1234' partner_code = 'VS' fee_schedule: FeeScheduleModel = FeeScheduleModel.find_by_filing_type_and_corp_type( corp_type_code=partner_code, filing_type_code='WILLSEARCH') pay_account: PaymentAccountModel = factory_create_pad_account( status=CfsAccountStatus.ACTIVE.value, account_number=cfs_account_number) invoice: InvoiceModel = factory_invoice( payment_account=pay_account, total=100, service_fees=10.0, corp_type_code='VS', payment_method_code=PaymentMethod.ONLINE_BANKING.value, status_code=InvoiceStatus.PAID.value) invoice_id = invoice.id line_item: PaymentLineItemModel = factory_payment_line_item( invoice_id=invoice.id, filing_fees=90.0, service_fees=10.0, total=90.0, fee_schedule_id=fee_schedule.fee_schedule_id) dist_code: DistributionCodeModel = DistributionCodeModel.find_by_id( line_item.fee_distribution_id) # Check if the disbursement distribution is present for this. if not dist_code.disbursement_distribution_code_id: disbursement_distribution_code: DistributionCodeModel = factory_distribution( name='Disbursement') dist_code.disbursement_distribution_code_id = disbursement_distribution_code.distribution_code_id dist_code.save() invoice_number = '1234567890' factory_invoice_reference( invoice_id=invoice.id, invoice_number=invoice_number, status_code=InvoiceReferenceStatus.COMPLETED.value) invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice = invoice.save() # 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).save() ejv_file_id = ejv_file.id ejv_header: EjvHeaderModel = EjvHeaderModel( disbursement_status_code=DisbursementStatus.UPLOADED.value, ejv_file_id=ejv_file.id, partner_code=partner_code, payment_account_id=pay_account.id).save() ejv_header_id = ejv_header.id EjvInvoiceLinkModel( invoice_id=invoice.id, ejv_header_id=ejv_header.id, disbursement_status_code=DisbursementStatus.UPLOADED.value).save() 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 # Now upload a feedback file and check the status. # Just create feedback file to mock the real feedback file. feedback_content = f'..BG...........00000000{ejv_file_id}...\n' \ f'..BH...0000.................................................................................' \ f'.....................................................................CGI\n' \ f'..JH...FI0000000{ejv_header_id}.........................000000000090.00.....................' \ f'............................................................................................' \ f'............................................................................................' \ f'.........0000...............................................................................' \ f'.......................................................................CGI\n' \ f'..JD...FI0000000{ejv_header_id}00001........................................................' \ f'...........000000000090.00D.................................................................' \ f'...................................{invoice_id} ' \ f' 0000........................' \ f'............................................................................................' \ f'..................................CGI\n' \ f'..JD...FI0000000{ejv_header_id}00002........................................................' \ f'...........000000000090.00C.................................................................' \ f'...................................{invoice_id} ' \ f' 0000........................' \ f'............................................................................................' \ f'..................................CGI\n' \ f'..BT.......FI0000000{ejv_header_id}000000000000002000000000090.000000.......................' \ f'............................................................................................' \ f'...................................CGI' 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) # upload_to_minio(file_name=feedback_file_name, value_as_bytes=feedback_content.encode()) 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 invoice = InvoiceModel.find_by_id(invoice_id) assert invoice.disbursement_status_code == DisbursementStatus.COMPLETED.value