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 _create_single_invoice_per_purchase(cls, payment_method: PaymentMethod): """Create one CFS invoice per purchase.""" invoices: List[InvoiceModel] = InvoiceModel.query \ .filter_by(payment_method_code=payment_method.value) \ .filter_by(invoice_status_code=InvoiceStatus.CREATED.value) \ .order_by(InvoiceModel.created_on.asc()).all() current_app.logger.info(f'Found {len(invoices)} to be created in CFS.') for invoice in invoices: # Get cfs account payment_account: PaymentAccountService = PaymentAccountService.find_by_id( invoice.payment_account_id) cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( payment_account.id) # Check for corp type and see if online banking is allowed. if invoice.payment_method_code == PaymentMethod.ONLINE_BANKING.value: corp_type: CorpTypeModel = CorpTypeModel.find_by_code( invoice.corp_type_code) if not corp_type.is_online_banking_allowed: continue # Create a CFS invoice current_app.logger.debug( f'Creating cfs invoice for invoice {invoice.id}') try: invoice_response = CFSService.create_account_invoice( transaction_number=invoice.id, line_items=invoice.payment_line_items, cfs_account=cfs_account) except Exception as e: # NOQA # pylint: disable=broad-except capture_message( f'Error on creating Online Banking invoice: account id={payment_account.id}, ' f'auth account : {payment_account.auth_account_id}, ERROR : {str(e)}', level='error') current_app.logger.error(e) continue # Create invoice reference, payment record and a payment transaction InvoiceReference.create( invoice_id=invoice.id, invoice_number=invoice_response.json().get('invoice_number'), reference_number=invoice_response.json().get( 'pbc_ref_number', None)) # Misc invoice.cfs_account_id = payment_account.cfs_account_id # leave the status as SETTLEMENT_SCHEDULED invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice.save()
def _create_pad_invoices(cls): # pylint: disable=too-many-locals """Create PAD invoices in to CFS system.""" # Find all accounts which have done a transaction with PAD transactions inv_subquery = db.session.query(InvoiceModel.payment_account_id) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \ .filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value).subquery() # Exclude the accounts which are in FREEZE state. pad_accounts: List[PaymentAccountModel] = db.session.query(PaymentAccountModel) \ .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \ .filter(CfsAccountModel.status != CfsAccountStatus.FREEZE.value) \ .filter(PaymentAccountModel.id.in_(inv_subquery)).all() current_app.logger.info( f'Found {len(pad_accounts)} with PAD transactions.') invoice_ref_subquery = db.session.query(InvoiceReferenceModel.invoice_id). \ filter(InvoiceReferenceModel.status_code.in_((InvoiceReferenceStatus.ACTIVE.value,))) for account in pad_accounts: # Find all PAD invoices for this account account_invoices = db.session.query(InvoiceModel) \ .filter(InvoiceModel.payment_account_id == account.id) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \ .filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value) \ .filter(InvoiceModel.id.notin_(invoice_ref_subquery)) \ .order_by(InvoiceModel.created_on.desc()).all() # Get cfs account payment_account: PaymentAccountService = PaymentAccountService.find_by_id( account.id) current_app.logger.debug( f'Found {len(account_invoices)} invoices for account {payment_account.auth_account_id}' ) if len(account_invoices) == 0: continue cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( payment_account.id) if cfs_account is None: # Get the last cfs_account for it, as the account might have got upgraded from PAD to DRAWDOWN. cfs_account: CfsAccountModel = CfsAccountModel.query.\ filter(CfsAccountModel.account_id == payment_account.id).order_by(CfsAccountModel.id.desc()).first() # If the CFS Account status is not ACTIVE or INACTIVE (for above case), raise error and continue if cfs_account.status not in (CfsAccountStatus.ACTIVE.value, CfsAccountStatus.INACTIVE.value): capture_message( f'CFS Account status is not ACTIVE. for account {payment_account.auth_account_id} ' f'is {payment_account.cfs_account_status}', level='error') current_app.logger.error( f'CFS status for account {payment_account.auth_account_id} ' f'is {payment_account.cfs_account_status}') continue # Add all lines together lines = [] invoice_total: float = 0 for invoice in account_invoices: lines.extend(invoice.payment_line_items) invoice_total += invoice.total invoice_number = account_invoices[-1].id try: # Get the first invoice id as the trx number for CFS invoice_response = CFSService.create_account_invoice( transaction_number=invoice_number, line_items=lines, cfs_account=cfs_account) except Exception as e: # NOQA # pylint: disable=broad-except # There is a chance that the error is a timeout from CAS side, # so to make sure we are not missing any data, make a GET call for the invoice we tried to create # and use it if it got created. current_app.logger.info( e ) # INFO is intentional as sentry alerted only after the following try/catch has_invoice_created: bool = False try: # add a 60 seconds delay here as safe bet, as CFS takes time to create the invoice and # since this is a job, delay doesn't cause any performance issue time.sleep(60) invoice_number = generate_transaction_number( str(invoice_number)) invoice_response = CFSService.get_invoice( cfs_account=cfs_account, inv_number=invoice_number) has_invoice_created = invoice_response.json().get( 'invoice_number', None) == invoice_number except Exception as exc: # NOQA # pylint: disable=broad-except,unused-variable # Ignore this error, as it is irrelevant and error on outer level is relevant. pass # If no invoice is created raise an error for sentry if not has_invoice_created: capture_message( f'Error on creating PAD invoice: account id={payment_account.id}, ' f'auth account : {payment_account.auth_account_id}, ERROR : {str(e)}', level='error') current_app.logger.error(e) continue # emit account mailer event mailer.publish_mailer_events('pad.invoiceCreated', payment_account, {'invoice_total': invoice_total}) # Iterate invoice and create invoice reference records for invoice in account_invoices: # Create invoice reference, payment record and a payment transaction InvoiceReference.create( invoice_id=invoice.id, invoice_number=invoice_response.json().get( 'invoice_number'), reference_number=invoice_response.json().get( 'pbc_ref_number', None)) # Misc invoice.cfs_account_id = payment_account.cfs_account_id # no longer set to settlement sceduled # invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice.save()
def _create_rs_invoices(cls): # pylint: disable=too-many-locals """Create RS invoices in to CFS system.""" # Find all pending routing slips. # find all routing slip invoices [cash or cheque] # create invoices in csf # do the receipt apply invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \ .join(RoutingSlipModel, RoutingSlipModel.number == InvoiceModel.routing_slip) \ .join(CfsAccountModel, CfsAccountModel.account_id == RoutingSlipModel.payment_account_id) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.INTERNAL.value) \ .filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value) \ .filter(CfsAccountModel.status.in_([CfsAccountStatus.ACTIVE.value, CfsAccountStatus.FREEZE.value])) \ .filter(InvoiceModel.routing_slip is not None) \ .order_by(InvoiceModel.created_on.asc()).all() current_app.logger.info(f'Found {len(invoices)} to be created in CFS.') for invoice in invoices: # Create a CFS invoice current_app.logger.debug( f'Creating cfs invoice for invoice {invoice.id}') routing_slip = RoutingSlipModel.find_by_number( invoice.routing_slip) # If routing slip is not found in Pay-DB, assume legacy RS and move on to next one. if not routing_slip: continue routing_slip_payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id( routing_slip.payment_account_id) # apply invoice to the active CFS_ACCOUNT which will be the parent routing slip active_cfs_account = CfsAccountModel.find_effective_by_account_id( routing_slip_payment_account.id) invoice_response = CFSService.create_account_invoice( transaction_number=invoice.id, line_items=invoice.payment_line_items, cfs_account=active_cfs_account) invoice_number = invoice_response.json().get( 'invoice_number', None) current_app.logger.info( f'invoice_number {invoice_number} created in CFS.') has_error_in_apply_receipt = RoutingSlipTask.apply_routing_slips_to_invoice( routing_slip_payment_account, active_cfs_account, routing_slip, invoice, invoice_number) if has_error_in_apply_receipt: # move on to next invoice continue invoice_reference: InvoiceReference = InvoiceReference.create( invoice.id, invoice_number, invoice_response.json().get('pbc_ref_number', None)) current_app.logger.debug('>create_invoice') # leave the status as PAID invoice_reference.status_code = InvoiceReferenceStatus.COMPLETED.value invoice.invoice_status_code = InvoiceStatus.PAID.value invoice.paid = invoice.total Payment.create(payment_method=PaymentMethod.INTERNAL.value, payment_system=PaymentSystem.INTERNAL.value, payment_status=PaymentStatus.COMPLETED.value, invoice_number=invoice_reference.invoice_number, invoice_amount=invoice.total, payment_account_id=invoice.payment_account_id) invoice.save()
def _create_pad_invoices(cls): # pylint: disable=too-many-locals """Create PAD invoices in to CFS system.""" # Find all accounts which have done a transaction with PAD transactions inv_subquery = db.session.query(InvoiceModel.payment_account_id) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \ .filter(InvoiceModel.invoice_status_code == PaymentStatus.CREATED.value).subquery() # Exclude the accounts which are in FREEZE state. pad_accounts: List[PaymentAccountModel] = db.session.query(PaymentAccountModel) \ .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \ .filter(CfsAccountModel.status != CfsAccountStatus.FREEZE.value) \ .filter(PaymentAccountModel.id.in_(inv_subquery)).all() current_app.logger.info( f'Found {len(pad_accounts)} with PAD transactions.') for account in pad_accounts: # Find all PAD invoices for this account account_invoices = db.session.query(InvoiceModel) \ .filter(InvoiceModel.payment_account_id == account.id) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \ .filter(InvoiceModel.invoice_status_code == InvoiceStatus.CREATED.value) \ .order_by(InvoiceModel.created_on.desc()).all() # Get cfs account payment_account: PaymentAccountService = PaymentAccountService.find_by_id( account.id) current_app.logger.debug( f'Found {len(account_invoices)} invoices for account {payment_account.auth_account_id}' ) if len(account_invoices) == 0: continue cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( payment_account.id) if cfs_account is None: # Get the last invoice and look up cfs_account for it, as the account might have got upgraded. cfs_account = CfsAccountModel.find_by_id( account_invoices[0].cfs_account_id) # If the CFS Account status is not ACTIVE, raise error and continue if cfs_account.status not in (CfsAccountStatus.ACTIVE.value, CfsAccountStatus.INACTIVE.value): capture_message( f'CFS Account status is not ACTIVE. for account {payment_account.auth_account_id} ' f'is {payment_account.cfs_account_status}', level='error') current_app.logger.error( f'CFS status for account {payment_account.auth_account_id} ' f'is {payment_account.cfs_account_status}') continue # Add all lines together lines = [] invoice_total: float = 0 for invoice in account_invoices: lines.append(*invoice.payment_line_items) invoice_total += invoice.total try: # Get the first invoice id as the trx number for CFS invoice_response = CFSService.create_account_invoice( transaction_number=account_invoices[0].id, line_items=lines, payment_account=cfs_account) except Exception as e: # pylint: disable=broad-except capture_message( f'Error on creating PAD invoice: account id={payment_account.id}, ' f'auth account : {payment_account.auth_account_id}, ERROR : {str(e)}', level='error') current_app.logger.error(e) continue # emit account mailer event mailer.publish_mailer_events('pad.invoiceCreated', payment_account, {'invoice_total': invoice_total}) # Iterate invoice and create invoice reference records for invoice in account_invoices: # Create invoice reference, payment record and a payment transaction InvoiceReference.create( invoice_id=invoice.id, invoice_number=invoice_response.json().get( 'invoice_number'), reference_number=invoice_response.json().get( 'pbc_ref_number', None)) # Misc invoice.cfs_account_id = payment_account.cfs_account_id invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice.save()
def _consolidate_payments(cls, auth_account_id: str, failed_payments: List[PaymentModel]) -> Payment: # If the payment is for consolidating failed payments, # 1. Cancel the invoices in CFS # 2. Update status of invoice_reference to CANCELLED # 3. Create new consolidated invoice in CFS. # 4. Create new invoice reference records. # 5. Create new payment records for the invoice as CREATED. pay_account = PaymentAccountService.find_by_auth_account_id( auth_account_id) consolidated_invoices: List[InvoiceModel] = [] consolidated_line_items: List[PaymentLineItem] = [] invoice_total: float = 0 for failed_payment in failed_payments: # Reverse invoice balance CFSService.reverse_invoice( inv_number=failed_payment.invoice_number) # Find all invoices for this payment. # Add all line items to the array for invoice in InvoiceModel.find_invoices_for_payment( payment_id=failed_payment.id): consolidated_invoices.append(invoice) invoice_total += invoice.total consolidated_line_items.append(*invoice.payment_line_items) # Create consolidated invoice invoice_response = CFSService.create_account_invoice( transaction_number=str(consolidated_invoices[-1].id) + '-C', line_items=consolidated_line_items, payment_account=pay_account) invoice_number: str = invoice_response.json().get('invoice_number') # Mark all invoice references to status CANCELLED, and create a new one for the new invoice number. for invoice in consolidated_invoices: inv_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status( invoice_id=invoice.id, status_code=InvoiceReferenceStatus.ACTIVE.value) inv_ref.status_code = InvoiceReferenceStatus.CANCELLED.value # print(inv_ref) InvoiceReferenceModel( invoice_id=invoice.id, status_code=InvoiceReferenceStatus.ACTIVE.value, invoice_number=invoice_number, reference_number=invoice_response.json().get( 'pbc_ref_number')).flush() payment = Payment.create(payment_method=PaymentMethod.CC.value, payment_system=PaymentSystem.PAYBC.value, invoice_number=invoice_number, invoice_amount=invoice_total, payment_account_id=pay_account.id) # Update all failed payment with consolidated invoice number. for failed_payment in failed_payments: failed_payment.cons_inv_number = invoice_number # If the payment status is CREATED, which means we consolidated one payment which was already consolidated. # Set the status as DELETED if failed_payment.payment_status_code == PaymentStatus.CREATED.value: failed_payment.payment_status_code = PaymentStatus.DELETED.value # commit transaction BaseModel.commit() return payment