Exemplo n.º 1
0
    def _apply_routing_slips_to_pending_invoices(cls, routing_slip: RoutingSlipModel) -> float:
        """Apply the routing slips again, when routing slip is linked to an NSF parent."""
        current_app.logger.info(f'Starting NSF recovery process for {routing_slip.number}')
        routing_slip_payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
            routing_slip.payment_account_id)

        # apply invoice to the active CFS_ACCOUNT which will be the parent routing slip
        active_cfs_account = CfsAccountModel.find_effective_by_account_id(routing_slip_payment_account.id)

        invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \
            .filter(InvoiceModel.routing_slip == routing_slip.number,
                    InvoiceModel.invoice_status_code.in_([InvoiceStatus.CREATED.value, InvoiceStatus.APPROVED.value])) \
            .all()
        current_app.logger.info(f'Found {len(invoices)} to apply receipt')
        applied_amount = 0
        for inv in invoices:
            inv_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
                inv.id, InvoiceReferenceStatus.ACTIVE.value
            )
            cls.apply_routing_slips_to_invoice(
                routing_slip_payment_account, active_cfs_account, routing_slip, inv, inv_ref.invoice_number
            )

            # IF invoice balance is zero, then update records.
            if CFSService.get_invoice(cfs_account=active_cfs_account, inv_number=inv_ref.invoice_number) \
                    .get('amount_due') == 0:
                applied_amount += inv.total
                inv_ref.status_code = InvoiceReferenceStatus.COMPLETED.value
                inv.invoice_status_code = InvoiceStatus.PAID.value

        return applied_amount
Exemplo n.º 2
0
    def apply_routing_slips_to_invoice(cls,  # pylint: disable = too-many-arguments, too-many-locals
                                       routing_slip_payment_account: PaymentAccountModel,
                                       active_cfs_account: CfsAccountModel,
                                       parent_routing_slip: RoutingSlipModel,
                                       invoice: InvoiceModel,
                                       invoice_number: str) -> bool:
        """Apply routing slips (receipts in CFS) to invoice."""
        has_errors = False
        child_routing_slips: List[RoutingSlipModel] = RoutingSlipModel.find_children(parent_routing_slip.number)
        # an invoice has to be applied to multiple receipts (incl. all linked RS); apply till the balance is zero
        for routing_slip in (parent_routing_slip, *child_routing_slips):
            try:
                # apply receipt now
                current_app.logger.debug(f'Apply receipt {routing_slip.number} on invoice {invoice_number} '
                                         f'for routing slip {routing_slip.number}')
                receipt_number = routing_slip.number
                # For linked routing slips, new receipt numbers ends with 'L'
                if routing_slip.status == RoutingSlipStatus.LINKED.value:
                    receipt_number = f'{routing_slip.number}L'

                # If balance of receipt is zero, continue to next receipt.
                receipt_balance_before_apply = float(
                    CFSService.get_receipt(active_cfs_account, receipt_number).get('unapplied_amount')
                )
                current_app.logger.debug(f'Current balance on {receipt_number} = {receipt_balance_before_apply}')
                if receipt_balance_before_apply == 0:
                    continue

                current_app.logger.debug(f'Applying receipt {receipt_number} to {invoice_number}')
                receipt_response = CFSService.apply_receipt(active_cfs_account, receipt_number, invoice_number)

                # Create receipt.
                receipt = Receipt()
                receipt.receipt_number = receipt_response.json().get('receipt_number', None)
                receipt_amount = receipt_balance_before_apply - float(receipt_response.json().get('unapplied_amount'))
                receipt.receipt_amount = receipt_amount
                receipt.invoice_id = invoice.id
                receipt.receipt_date = datetime.now()
                receipt.flush()

                invoice_from_cfs = CFSService.get_invoice(active_cfs_account, invoice_number)
                if invoice_from_cfs.get('amount_due') == 0:
                    break

            except Exception as e:  # NOQA # pylint: disable=broad-except
                capture_message(
                    f'Error on creating Routing Slip invoice: account id={routing_slip_payment_account.id}, '
                    f'routing slip : {routing_slip.id}, ERROR : {str(e)}', level='error')
                current_app.logger.error(e)
                has_errors = True
                continue
        return has_errors
Exemplo n.º 3
0
    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()