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 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 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 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_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 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
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 _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 = [] 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
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)