예제 #1
0
def test_routing_slip_find_search(session):
    """Assert a routing slip is stored.

    Start with a blank database.
    """
    payment_account = factory_payment_account()
    payment_account.save()

    rs = factory_routing_slip(number=fake.name(),
                              payment_account_id=payment_account.id)
    rs.save()

    for i in range(20):
        factory_routing_slip(number=fake.name(),
                             payment_account_id=payment_account.id).save()

    routing_slip = RoutingSlip()
    search_dict = {'routingSlipNumber': rs.number}
    res, count = routing_slip.search(search_dict,
                                     page=1,
                                     limit=1,
                                     return_all=True)
    assert count == 1
    assert len(res) == 1, 'searched with routing slip.so only one record'

    res, count = routing_slip.search({}, page=1, limit=1, return_all=True)
    assert count == 21
    assert len(res) == 21, 'retun all true ;so shud return all records'

    res, count = routing_slip.search({}, page=1, limit=1, return_all=False)
    assert count == 1
    assert len(res) == 1, 'return all false'
예제 #2
0
def test_receipt_adjustments(session, rs_status):
    """Test routing slip adjustments."""
    child_rs_number = '1234'
    parent_rs_number = '89799'
    factory_routing_slip_account(number=child_rs_number,
                                 status=CfsAccountStatus.ACTIVE.value)
    factory_routing_slip_account(number=parent_rs_number,
                                 status=CfsAccountStatus.ACTIVE.value,
                                 total=10,
                                 remaining_amount=10)
    child_rs = RoutingSlipModel.find_by_number(child_rs_number)
    parent_rs = RoutingSlipModel.find_by_number(parent_rs_number)
    # Do Link
    child_rs.status = RoutingSlipStatus.LINKED.value
    child_rs.parent_number = parent_rs.number
    child_rs.save()

    parent_rs.status = rs_status

    # Test exception path first.
    with patch('pay_api.services.CFSService.adjust_receipt_to_zero') as mock:
        mock.side_effect = Exception('ERROR!')
        RoutingSlipTask.adjust_routing_slips()

    parent_rs = RoutingSlipModel.find_by_number(parent_rs.number)
    assert parent_rs.remaining_amount == 10

    with patch('pay_api.services.CFSService.adjust_receipt_to_zero'):
        RoutingSlipTask.adjust_routing_slips()

    parent_rs = RoutingSlipModel.find_by_number(parent_rs.number)
    assert parent_rs.remaining_amount == 0
예제 #3
0
    def get_links(cls, rs_number: str) -> Dict[str, any]:
        """Find dependents/links of a routing slips."""
        links: Dict[str, any] = None
        routing_slip: RoutingSlipModel = RoutingSlipModel.find_by_number(rs_number)
        if routing_slip:
            routing_slip_schema = RoutingSlipSchema()
            children = RoutingSlipModel.find_children(rs_number)
            links = {
                'parent': routing_slip_schema.dump(routing_slip.parent),
                'children': routing_slip_schema.dump(children, many=True)
            }

        return links
예제 #4
0
def test_routing_slip_find_creation(session):
    """Assert a routing slip is stored.

    Start with a blank database.
    """
    payment_account = factory_payment_account()
    payment_account.save()

    rs = factory_routing_slip(payment_account_id=payment_account.id)
    rs.save()
    assert rs.id is not None

    routing_slip = RoutingSlip()
    assert routing_slip.find_by_number(rs.number) is not None
예제 #5
0
def test_create_payment_record_with_internal_pay(session, public_user_mock):
    """Assert that the payment records are created."""
    # Create invoice without routing slip.
    payment_response = PaymentService.create_invoice(
        get_routing_slip_payment_request(), get_auth_staff())
    account_model = PaymentAccount.find_by_auth_account_id(get_auth_staff().get('account').get('id'))
    account_id = account_model.id
    assert account_id is not None
    assert payment_response.get('id') is not None

    rs_number = '123456789'
    rs = factory_routing_slip(number=rs_number, payment_account_id=account_id, remaining_amount=50.00)
    rs.save()

    # Create another invoice with a routing slip.
    PaymentService.create_invoice(get_routing_slip_payment_request(), get_auth_staff())
    account_model = PaymentAccount.find_by_auth_account_id(get_auth_staff().get('account').get('id'))

    assert account_id == account_model.id

    rs = RoutingSlipModel.find_by_number(rs_number)
    assert rs.remaining_amount == 0.0
    """12033 - Scenario 1.

    Manual transaction reduces RS to 0.00
    Routing slip status becomes Completed
    """
    assert rs.status == RoutingSlipStatus.COMPLETE.name
예제 #6
0
def test_internal_rs_back_active(session, public_user_mock):
    """12033 - Scenario 2.

    Routing slip is complete and a transaction is cancelled
    the balance is restored - Should move back to Active
    """
    payment_response = PaymentService.create_invoice(
        get_routing_slip_payment_request(), get_auth_staff())
    account_model = PaymentAccount.find_by_auth_account_id(get_auth_staff().get('account').get('id'))
    account_id = account_model.id
    assert account_id is not None
    assert payment_response.get('id') is not None

    rs_number = '123456789'
    rs = factory_routing_slip(number=rs_number, payment_account_id=account_id, remaining_amount=50.00)
    rs.save()

    # Create another invoice with a routing slip.
    invoice = PaymentService.create_invoice(get_routing_slip_payment_request(), get_auth_staff())
    account_model = PaymentAccount.find_by_auth_account_id(get_auth_staff().get('account').get('id'))

    assert account_id == account_model.id

    rs = RoutingSlipModel.find_by_number(rs_number)
    assert rs.remaining_amount == 0.0
    assert rs.status == RoutingSlipStatus.COMPLETE.name

    invoice = Invoice.find_by_id(invoice['id'])
    InternalPayService().process_cfs_refund(invoice)

    assert rs.status == RoutingSlipStatus.ACTIVE.name
예제 #7
0
    def _validate_linking(routing_slip: RoutingSlipModel, parent_rs_slip: RoutingSlipModel) -> None:
        """Validate the linking.

        1). child already has a parent/already linked.
        2). its already a parent.
        3). parent_rs_slip has a parent.ie parent_rs_slip shouldn't already be linked
        4). one of them has transactions
        """
        if RoutingSlip._is_linked_already(routing_slip):
            raise BusinessException(Error.RS_ALREADY_LINKED)

        children = RoutingSlipModel.find_children(routing_slip.number)
        if children and len(children) > 0:
            raise BusinessException(Error.RS_ALREADY_A_PARENT)

        if RoutingSlip._is_linked_already(parent_rs_slip):
            raise BusinessException(Error.RS_PARENT_ALREADY_LINKED)

        # prevent self linking

        if routing_slip.number == parent_rs_slip.number:
            raise BusinessException(Error.RS_CANT_LINK_TO_SAME)

        # has one of these has pending
        if routing_slip.invoices:
            raise BusinessException(Error.RS_CHILD_HAS_TRANSACTIONS)

        # Stop the user from linking NSF. NSF can only be a parent.
        if routing_slip.status == RoutingSlipStatus.NSF.value:
            raise BusinessException(Error.RS_CANT_LINK_NSF)

        RoutingSlip._validate_status(parent_rs_slip, routing_slip)
예제 #8
0
def test_routing_slip_status_to_nsf_attempt(client, jwt, app):
    """12033 - Scenario 4.

    Routing slip in Completed,
    user attempts to move it into another status, can only set to NSF.
    """
    token = jwt.create_jwt(get_claims(roles=[Role.FAS_CREATE.value, Role.FAS_LINK.value,
                                             Role.FAS_SEARCH.value, Role.FAS_EDIT.value]),
                           token_header)
    headers = {'Authorization': f'Bearer {token}', 'content-type': 'application/json'}
    child = get_routing_slip_request('438607657')
    client.post('/api/v1/fas/routing-slips', data=json.dumps(child), headers=headers)

    rs_model = RoutingSlip.find_by_number('438607657')
    rs_model.status = RoutingSlipStatus.COMPLETE.value
    rs_model.commit()

    # Active shouldn't work.
    rv = client.patch(f"/api/v1/fas/routing-slips/{child.get('number')}?action={PatchActions.UPDATE_STATUS.value}",
                      data=json.dumps({'status': RoutingSlipStatus.ACTIVE.value}), headers=headers)
    assert rv.status_code == 400

    # NSF should work.
    rv = client.patch(f"/api/v1/fas/routing-slips/{child.get('number')}?action={PatchActions.UPDATE_STATUS.value}",
                      data=json.dumps({'status': RoutingSlipStatus.NSF.value}), headers=headers)
    assert rv.status_code == 200, 'status changed successfully.'
예제 #9
0
def test_routing_slip_link_attempt(client, jwt, app):
    """12033 - Scenario 3.

    Routing slip is Completed, attempt to be linked.
    Linking shouldn't be allowed and explaining that completed routing
    slip cannot be involved in linking.
    """
    token = jwt.create_jwt(get_claims(roles=[Role.FAS_CREATE.value, Role.FAS_LINK.value, Role.FAS_SEARCH.value]),
                           token_header)
    headers = {'Authorization': f'Bearer {token}', 'content-type': 'application/json'}
    child = get_routing_slip_request('438607657')
    parent1 = get_routing_slip_request('355336710')
    client.post('/api/v1/fas/routing-slips', data=json.dumps(child), headers=headers)
    client.post('/api/v1/fas/routing-slips', data=json.dumps(parent1), headers=headers)

    rs_model = RoutingSlip.find_by_number('438607657')
    rs_model.status = RoutingSlipStatus.COMPLETE.value
    rs_model.commit()

    data = {'childRoutingSlipNumber': f"{child.get('number')}", 'parentRoutingSlipNumber': f"{parent1.get('number')}"}
    rv = client.post('/api/v1/fas/routing-slips/links', data=json.dumps(data), headers=headers)
    assert rv.json.get('type') == 'RS_IN_INVALID_STATUS'
    assert rv.status_code == 400

    # Try the reverse:
    data = {'childRoutingSlipNumber': f"{parent1.get('number')}", 'parentRoutingSlipNumber': f"{child.get('number')}"}
    rv = client.post('/api/v1/fas/routing-slips/links', data=json.dumps(data), headers=headers)
    assert rv.json.get('type') == 'RS_IN_INVALID_STATUS'
    assert rv.status_code == 400
예제 #10
0
def test_routing_slip_usd_creation(session):
    """Assert a routing slip is stored with total_usd column.

    Start with a blank database.
    """
    payment_account = factory_payment_account()
    payment_account.save()

    rs = factory_routing_slip_usd(payment_account_id=payment_account.id,
                                  total_usd=50)
    rs.save()
    assert rs.id is not None
    assert rs.total_usd == 50

    routing_slip = RoutingSlip()
    assert routing_slip.find_by_number(rs.number) is not None
예제 #11
0
    def search(cls, search_filter: Dict, page: int, limit: int, return_all: bool = False):
        """Search for routing slip."""
        routing_slips, total = RoutingSlipModel.search(search_filter, page, limit, return_all)
        data = {
            'total': total,
            'page': page,
            'limit': limit,
            # We need these fields, to populate the UI.
            'items': RoutingSlipSchema(only=('number',
                                             'payments.receipt_number',
                                             'payment_account.name',
                                             'created_name',
                                             'routing_slip_date',
                                             'status',
                                             'invoices.business_identifier',
                                             'payments.cheque_receipt_number',
                                             'remaining_amount',
                                             'total',
                                             'invoices.corp_type_code',
                                             'payments.payment_method_code',
                                             'payments.payment_status_code',
                                             'payment_account.payment_method'
                                             )
                                       ).dump(routing_slips, many=True)
        }

        return data
예제 #12
0
    def _cancel_rs_invoices(cls):
        """Cancel routing slip invoices in CFS."""
        invoices: List[InvoiceModel] = InvoiceModel.query \
            .filter(InvoiceModel.payment_method_code == PaymentMethod.INTERNAL.value) \
            .filter(InvoiceModel.invoice_status_code == InvoiceStatus.REFUND_REQUESTED.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 cancelled in CFS.')
        for invoice in invoices:
            # call unapply rcpts
            # adjust invoice to zero
            current_app.logger.debug(f'Calling the invoice {invoice.id}')
            routing_slip = RoutingSlipModel.find_by_number(
                invoice.routing_slip)
            routing_slip_payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
                routing_slip.payment_account_id)
            cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
                routing_slip_payment_account.id)
            # Find COMPLETED invoice reference; as unapply has to be done only if invoice is created and applied in CFS.
            invoice_reference = InvoiceReferenceModel. \
                find_reference_by_invoice_id_and_status(invoice.id, status_code=InvoiceReferenceStatus.COMPLETED.value)
            if invoice_reference:
                current_app.logger.debug(
                    f'Found invoice reference - {invoice_reference.invoice_number}'
                )
                try:
                    # find receipts against the invoice and unapply
                    # apply receipt now
                    receipts: List[
                        ReceiptModel] = ReceiptModel.find_all_receipts_for_invoice(
                            invoice_id=invoice.id)
                    for receipt in receipts:
                        CFSService.unapply_receipt(
                            cfs_account, receipt.receipt_number,
                            invoice_reference.invoice_number)

                    adjustment_negative_amount = -invoice.total
                    CFSService.adjust_invoice(
                        cfs_account=cfs_account,
                        inv_number=invoice_reference.invoice_number,
                        amount=adjustment_negative_amount)

                except Exception as e:  # NOQA # pylint: disable=broad-except
                    capture_message(
                        f'Error on canelling Routing Slip invoice: invoice id={invoice.id}, '
                        f'routing slip : {routing_slip.id}, ERROR : {str(e)}',
                        level='error')
                    current_app.logger.error(e)
                    # TODO stop execution ? what should be the invoice stats ; should we set it to error or retry?
                    continue

                invoice_reference.status_code = InvoiceReferenceStatus.CANCELLED.value

            invoice.invoice_status_code = InvoiceStatus.REFUNDED.value
            invoice.save()
예제 #13
0
def test_link_rs(session):
    """Test link routing slip."""
    child_rs_number = '1234'
    parent_rs_number = '89799'
    factory_routing_slip_account(number=child_rs_number,
                                 status=CfsAccountStatus.ACTIVE.value)
    factory_routing_slip_account(number=parent_rs_number,
                                 status=CfsAccountStatus.ACTIVE.value)
    child_rs = RoutingSlipModel.find_by_number(child_rs_number)
    parent_rs = RoutingSlipModel.find_by_number(parent_rs_number)
    # Do Link
    child_rs.status = RoutingSlipStatus.LINKED.value
    child_rs.parent_number = parent_rs.number
    child_rs.save()
    payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
        child_rs.payment_account_id)

    cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
        payment_account.id)

    with patch('pay_api.services.CFSService.reverse_rs_receipt_in_cfs'
               ) as mock_cfs_reverse:
        with patch('pay_api.services.CFSService.create_cfs_receipt'
                   ) as mock_create_cfs:
            RoutingSlipTask.link_routing_slips()
            mock_cfs_reverse.assert_called()
            mock_cfs_reverse.assert_called_with(cfs_account, child_rs.number)
            mock_create_cfs.assert_called()

    # child_rs = RoutingSlipModel.find_by_number(child_rs_number)
    # parent_rs = RoutingSlipModel.find_by_number(parent_rs_number)
    # PS This has changed, no longer updating child rs payment account with parent.
    # assert child_rs.payment_account_id == parent_rs.payment_account_id
    cfs_account: CfsAccountModel = CfsAccountModel.find_by_id(cfs_account.id)
    assert cfs_account.status == CfsAccountStatus.INACTIVE.value

    # make sure next invocation doesnt fetch any records
    with patch('pay_api.services.CFSService.reverse_rs_receipt_in_cfs'
               ) as mock_cfs_reverse:
        with patch('pay_api.services.CFSService.create_cfs_receipt'
                   ) as mock_create_cfs:
            RoutingSlipTask.link_routing_slips()
            mock_cfs_reverse.assert_not_called()
            mock_create_cfs.assert_not_called()
예제 #14
0
    def create_daily_reports(cls, date: str, **kwargs):
        """Create and return daily report for the day provided."""
        routing_slips: List[RoutingSlipModel] = RoutingSlipModel.search(
            dict(
                dateFilter=dict(
                    endDate=date,
                    startDate=date,
                    target='created_on'
                )
            ),
            page=1, limit=0, return_all=True
        )[0]

        total: float = 0
        no_of_cash: int = 0
        no_of_cheque: int = 0
        total_cash_usd: float = 0
        total_cheque_usd: float = 0
        total_cash_cad: float = 0
        total_cheque_cad: float = 0
        # TODO Only CAD supported now, so just add up the total.
        for routing_slip in routing_slips:
            total += float(routing_slip.total)
            if routing_slip.payment_account.payment_method == PaymentMethod.CASH.value:
                no_of_cash += 1
                # TODO check if the payment is CAD or USD.
                total_cash_cad += float(routing_slip.total)
                if routing_slip.total_usd is not None:
                    total_cash_usd += float(routing_slip.total_usd)
            else:
                no_of_cheque += 1
                total_cheque_cad += float(routing_slip.total)
                if routing_slip.total_usd is not None:
                    total_cheque_usd += float(routing_slip.total_usd)

        report_dict = dict(
            templateName='routing_slip_report',
            reportName=f'Routing-Slip-Daily-Report-{date}',
            templateVars=dict(
                day=date,
                reportDay=str(get_local_time(datetime.now())),
                total=total,
                numberOfCashReceipts=no_of_cash,
                numberOfChequeReceipts=no_of_cheque,
                totalCashInUsd=total_cash_usd,
                totalChequeInUsd=total_cheque_usd,
                totalCashInCad=total_cash_cad,
                totalChequeInCad=total_cheque_cad
            )
        )

        pdf_response = OAuthService.post(current_app.config.get('REPORT_API_BASE_URL'),
                                         kwargs['user'].bearer_token, AuthHeaderType.BEARER,
                                         ContentType.JSON, report_dict)

        return pdf_response, report_dict.get('reportName')
예제 #15
0
 def find_by_number(cls, rs_number: str) -> Dict[str, any]:
     """Find by routing slip number."""
     routing_slip_dict: Dict[str, any] = None
     routing_slip: RoutingSlipModel = RoutingSlipModel.find_by_number(rs_number)
     if routing_slip:
         routing_slip_schema = RoutingSlipSchema()
         routing_slip_dict = routing_slip_schema.dump(routing_slip)
         routing_slip_dict['allowedStatuses'] = RoutingSlipStatusTransitionService. \
             get_possible_transitions(routing_slip)
     return routing_slip_dict
예제 #16
0
    def do_link(cls, rs_number: str, parent_rs_number: str) -> Dict[str, any]:
        """Link routing slip to parent routing slip."""
        routing_slip: RoutingSlipModel = RoutingSlipModel.find_by_number(rs_number)
        parent_routing_slip: RoutingSlipModel = RoutingSlipModel.find_by_number(parent_rs_number)
        if routing_slip is None or parent_routing_slip is None:
            raise BusinessException(Error.FAS_INVALID_ROUTING_SLIP_NUMBER)

        # do validations if its linkable
        RoutingSlip._validate_linking(routing_slip=routing_slip, parent_rs_slip=parent_routing_slip)

        routing_slip.parent_number = parent_routing_slip.number
        routing_slip.status = RoutingSlipStatus.LINKED.value

        # transfer the amount to parent.
        # we keep the total amount as such and transfer only the remaining amount.
        parent_routing_slip.remaining_amount += routing_slip.remaining_amount
        routing_slip.remaining_amount = 0

        routing_slip.commit()
        return cls.find_by_number(rs_number)
예제 #17
0
def test_routing_slip_refunds(session, monkeypatch):
    """Test Routing slip refund job.

    Steps:
    1) Create a routing slip with remaining_amount and status REFUND_AUTHORIZED
    2) Run the job and assert status
    """
    rs_1 = 'RS0000001'
    factory_routing_slip_account(
        number=rs_1,
        status=CfsAccountStatus.ACTIVE.value,
        total=100,
        remaining_amount=0,
        auth_account_id='1234',
        routing_slip_status=RoutingSlipStatus.REFUND_AUTHORIZED.value,
        refund_amount=100)

    routing_slip = RoutingSlip.find_by_number(rs_1)
    factory_refund(
        routing_slip.id, {
            'name': 'TEST',
            'mailingAddress': {
                'city': 'Victoria',
                'region': 'BC',
                'street': '655 Douglas St',
                'country': 'CA',
                'postalCode': 'V8V 0B6',
                'streetAdditional': ''
            }
        })
    with patch('pysftp.Connection.put') as mock_upload:
        ApRoutingSlipRefundTask.create_ap_file()
        mock_upload.assert_called()

    routing_slip = RoutingSlip.find_by_number(rs_1)
    assert routing_slip.status == RoutingSlipStatus.REFUND_UPLOADED.value

    # Run again and assert nothing is uploaded
    with patch('pysftp.Connection.put') as mock_upload:
        ApRoutingSlipRefundTask.create_ap_file()
        mock_upload.assert_not_called()
예제 #18
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
예제 #19
0
 def create_invoice(self, payment_account: PaymentAccount,
                    line_items: [PaymentLineItem], invoice: Invoice,
                    **kwargs) -> InvoiceReference:
     """Return a static invoice number."""
     routing_slip = None
     is_zero_dollar_invoice = get_quantized(invoice.total) == 0
     invoice_reference: InvoiceReference = None
     if routing_slip_number := invoice.routing_slip:
         current_app.logger.info(
             f'Routing slip number {routing_slip_number}, for invoice {invoice.id}'
         )
         routing_slip = RoutingSlipModel.find_by_number(routing_slip_number)
         InternalPayService._validate_routing_slip(routing_slip, invoice)
예제 #20
0
def factory_routing_slip(number: str = None,
                         payment_account_id=None,
                         status: str = RoutingSlipStatus.ACTIVE.value,
                         total: int = 0,
                         remaining_amount: Decimal = 0.0,
                         routing_slip_date=datetime.now()):
    """Return Factory."""
    routing_slip: RoutingSlip = RoutingSlip(
        number=number or fake.name(),
        payment_account_id=payment_account_id,
        status=status,
        total=total,
        remaining_amount=remaining_amount,
        created_by='test',
        routing_slip_date=routing_slip_date)
    return routing_slip
예제 #21
0
    def find_all_comments_for_a_routingslip(cls, routing_slip_number: str):
        """Find comments for a routing slip."""
        current_app.logger.debug('<Comment.get.service')
        routing_slip: RoutingSlipModel = RoutingSlipModel.find_by_number(
            routing_slip_number)

        if routing_slip is None:
            raise BusinessException(Error.FAS_INVALID_ROUTING_SLIP_NUMBER)

        comments_dao = CommentModel.find_all_comments_for_a_routingslip(
            routing_slip.number)
        comments = CommentSchema().dump(comments_dao, many=True)
        data = {'comments': comments}

        current_app.logger.debug('>Comment.get.service')
        return data
예제 #22
0
    def create(cls, comment_value: str, rs_number: str):
        """Create routing slip comment."""
        current_app.logger.debug('<Comment.create.service')
        routing_slip: RoutingSlipModel = RoutingSlipModel.find_by_number(
            number=rs_number)
        if routing_slip is None:
            raise BusinessException(Error.FAS_INVALID_ROUTING_SLIP_NUMBER)

        # Create a routing slip comment record.
        comment_service = Comment()
        comment_service._dao = CommentModel(comment=comment_value,
                                            routing_slip_number=rs_number)
        comment_service.flush()

        comment_service.commit()
        current_app.logger.debug('>Comment.create.service')
        return comment_service.asdict()
예제 #23
0
 def create_routing_slip_refund(cls, routing_slip_number: str,
                                request: Dict[str, str],
                                **kwargs) -> Dict[str, str]:
     """Create Routing slip refund."""
     current_app.logger.debug('<create Routing slip  refund')
     #
     # check if routing slip exists
     # validate user role -> update status of routing slip
     # check refunds table
     #   if Yes ; update the data [only with whatever is in payload]
     #   if not ; create new entry
     # call cfs
     rs_model = RoutingSlipModel.find_by_number(routing_slip_number)
     if not rs_model:
         raise BusinessException(Error.RS_DOESNT_EXIST)
     reason = get_str_by_path(request, 'reason')
     if (refund_status := get_str_by_path(request, 'status')) is None:
         raise BusinessException(Error.INVALID_REQUEST)
예제 #24
0
    def adjust_routing_slips(cls):
        """Adjust routing slips.

        Steps:
        1. Adjust routing slip receipts for any Write off routing slips.
        2. Adjust routing slip receipts for any Refund approved routing slips.
        """
        current_app.logger.info('<<adjust_routing_slips')
        adjust_statuses = [RoutingSlipStatus.REFUND_AUTHORIZED.value, RoutingSlipStatus.WRITE_OFF_AUTHORIZED.value]
        # For any pending refund/write off balance should be more than $0
        routing_slips = db.session.query(RoutingSlipModel) \
            .filter(RoutingSlipModel.status.in_(adjust_statuses), RoutingSlipModel.remaining_amount > 0).all()
        current_app.logger.info(f'Found {len(routing_slips)} to write off or refund authorized.')
        for routing_slip in routing_slips:
            try:
                # 1.Adjust the routing slip and it's child routing slips for the remaining balance.
                current_app.logger.debug(f'Adjusting routing slip {routing_slip.number}')
                payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(routing_slip.payment_account_id)
                cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id)

                # reverse routing slip receipt
                # Find all child routing slip and reverse it, as all linked routing slips are also considered as NSF.
                child_routing_slips: List[RoutingSlipModel] = RoutingSlipModel.find_children(routing_slip.number)
                for rs in (routing_slip, *child_routing_slips):
                    receipt_number = rs.number
                    is_refund = routing_slip.status == RoutingSlipStatus.REFUND_AUTHORIZED.value
                    if rs.parent_number:
                        receipt_number = f'{receipt_number}L'
                    # Adjust the receipt to zero in CFS
                    CFSService.adjust_receipt_to_zero(cfs_account, receipt_number, is_refund)

                routing_slip.refund_amount = routing_slip.remaining_amount
                routing_slip.remaining_amount = 0
                routing_slip.save()

            except Exception as e:  # NOQA # pylint: disable=broad-except
                capture_message(
                    f'Error on Adjusting Routing Slip for :={routing_slip.number}, '
                    f'routing slip : {routing_slip.id}, ERROR : {str(e)}', level='error')
                current_app.logger.error(e)
                continue
예제 #25
0
async def _process_ap_feedback(group_batches) -> bool:  # pylint:disable=too-many-locals
    """Process AP Feedback contents."""
    has_errors = False
    for group_batch in group_batches:
        ejv_file: Optional[EjvFileModel] = None
        for line in group_batch.splitlines():
            # For all these indexes refer the sharepoint docs refer : https://github.com/bcgov/entity/issues/6226
            is_batch_group: bool = line[2:4] == 'BG'
            is_batch_header: bool = line[2:4] == 'BH'
            is_ap_header: bool = line[2:4] == 'IH'
            if is_batch_group:
                batch_number = int(line[15:24])
                ejv_file = EjvFileModel.find_by_id(batch_number)
            elif is_batch_header:
                return_code = line[7:11]
                return_message = line[11:161]
                ejv_file.disbursement_status_code = _get_disbursement_status(
                    return_code)
                ejv_file.message = return_message
                if ejv_file.disbursement_status_code == DisbursementStatus.ERRORED.value:
                    has_errors = True
            elif is_ap_header:
                routing_slip_number = line[19:69].strip()
                routing_slip: RoutingSlipModel = RoutingSlipModel.find_by_number(
                    routing_slip_number)
                ap_header_return_code = line[414:418]
                ap_header_error_message = line[418:568]
                if _get_disbursement_status(
                        ap_header_return_code
                ) == DisbursementStatus.ERRORED.value:
                    has_errors = True
                    routing_slip.status = RoutingSlipStatus.REFUND_REJECTED.value
                    capture_message(
                        f'Refund failed for {routing_slip_number}, reason : {ap_header_error_message}',
                        level='error')
                else:
                    routing_slip.status = RoutingSlipStatus.REFUND_COMPLETED.value

    db.session.commit()
    return has_errors
예제 #26
0
def create_cfs_account(cfs_account: CfsAccountModel,
                       pay_account: PaymentAccountModel):
    """Create CFS account for routing slip."""
    routing_slip: RoutingSlipModel = RoutingSlipModel.find_by_payment_account_id(
        pay_account.id)
    try:
        # TODO add status check so that LINKED etc can be skipped.
        # for RS , entity/business number=party name ; RS Number=site name
        cfs_account_details: Dict[str, any] = CFSService.create_cfs_account(
            identifier=pay_account.name,
            contact_info={},
            site_name=routing_slip.number,
            is_fas=True)
        cfs_account.cfs_account = cfs_account_details.get('account_number')
        cfs_account.cfs_party = cfs_account_details.get('party_number')
        cfs_account.cfs_site = cfs_account_details.get('site_number')
        cfs_account.status = CfsAccountStatus.ACTIVE.value
        # Create receipt in CFS for the payment.
        CFSService.create_cfs_receipt(
            cfs_account=cfs_account,
            rcpt_number=routing_slip.number,
            rcpt_date=routing_slip.routing_slip_date.strftime('%Y-%m-%d'),
            amount=routing_slip.total,
            payment_method=pay_account.payment_method,
            access_token=CFSService.get_fas_token().json().get('access_token'))
        cfs_account.commit()
        return

    except Exception as e:  # NOQA # pylint: disable=broad-except

        capture_message(
            f'Error on creating Routing Slip CFS Account: account id={pay_account.id}, '
            f'auth account : {pay_account.auth_account_id}, ERROR : {str(e)}',
            level='error')
        current_app.logger.error(e)
        cfs_account.rollback()
        return
예제 #27
0
def factory_routing_slip_account(
        number: str = '1234',
        status: str = CfsAccountStatus.PENDING.value,
        total: int = 0,
        remaining_amount: int = 0,
        routing_slip_date=datetime.now(),
        payment_method=PaymentMethod.CASH.value,
        auth_account_id='1234',
        routing_slip_status=RoutingSlipStatus.ACTIVE.value,
        refund_amount=0):
    """Create routing slip and return payment account with it."""
    payment_account = PaymentAccount(payment_method=payment_method,
                                     name=f'Test {auth_account_id}')
    payment_account.save()

    rs = RoutingSlip(number=number,
                     payment_account_id=payment_account.id,
                     status=routing_slip_status,
                     total=total,
                     remaining_amount=remaining_amount,
                     created_by='test',
                     routing_slip_date=routing_slip_date,
                     refund_amount=refund_amount).save()

    Payment(payment_system_code=PaymentSystem.FAS.value,
            payment_account_id=payment_account.id,
            payment_method_code=PaymentMethod.CASH.value,
            payment_status_code=PaymentStatus.COMPLETED.value,
            receipt_number=number,
            is_routing_slip=True,
            paid_amount=rs.total,
            created_by='TEST')

    CfsAccount(status=status, account_id=payment_account.id).save()

    return payment_account
예제 #28
0
    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()
예제 #29
0
    def process_cfs_refund(self, invoice: InvoiceModel):
        """Process refund in CFS."""
        # TODO handle Routing Slip refund CAS integration here
        if invoice.total == 0:
            raise BusinessException(Error.NO_FEE_REFUND)

        # find if its a routing slip refund
        # if yes -> check existing one or legacy
        # if yes , add money back to rs after refunding

        if (routing_slip_number := invoice.routing_slip) is None:
            raise BusinessException(Error.INVALID_REQUEST)
        if invoice.total == 0:
            raise BusinessException(Error.NO_FEE_REFUND)
        if not (routing_slip :=
                RoutingSlipModel.find_by_number(routing_slip_number)):
            raise BusinessException(Error.ROUTING_SLIP_REFUND)
        current_app.logger.info(
            f'Processing refund for {invoice.id}, on routing slip {routing_slip.number}'
        )
        payment: PaymentModel = PaymentModel.find_payment_for_invoice(
            invoice.id)
        if payment:
            payment.payment_status_code = PaymentStatus.REFUNDED.value
            payment.flush()
        routing_slip.remaining_amount += get_quantized(invoice.total)
        # Move routing slip back to active on refund.
        if routing_slip.status == RoutingSlipStatus.COMPLETE.value:
            routing_slip.status = RoutingSlipStatus.ACTIVE.value
        routing_slip.flush()
        invoice.invoice_status_code = InvoiceStatus.REFUND_REQUESTED.value
예제 #30
0
    def link_routing_slips(cls):
        """Create invoice in CFS.

        Steps:
        1. Find all pending rs with pending status.
        1. Notify mailer
        """
        routing_slips = db.session.query(RoutingSlipModel) \
            .join(PaymentAccountModel, PaymentAccountModel.id == RoutingSlipModel.payment_account_id) \
            .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \
            .filter(RoutingSlipModel.status == RoutingSlipStatus.LINKED.value) \
            .filter(CfsAccountModel.status == CfsAccountStatus.ACTIVE.value).all()

        for routing_slip in routing_slips:
            # 1.reverse the child routing slip
            # 2.create receipt to the parent
            # 3.change the payment account of child to parent
            # 4. change the status

            try:
                current_app.logger.debug(f'Linking Routing Slip: {routing_slip.number}')
                payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
                    routing_slip.payment_account_id)
                cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
                    payment_account.id)

                # reverse routing slip receipt
                CFSService.reverse_rs_receipt_in_cfs(cfs_account, routing_slip.number)
                cfs_account.status = CfsAccountStatus.INACTIVE.value

                # apply receipt to parent cfs account
                parent_rs: RoutingSlipModel = RoutingSlipModel.find_by_number(routing_slip.parent_number)
                parent_payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
                    parent_rs.payment_account_id)
                parent_cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
                    parent_payment_account.id)
                # For linked routing slip receipts, append 'L' to the number to avoid duplicate error
                receipt_number = f'{routing_slip.number}L'
                CFSService.create_cfs_receipt(cfs_account=parent_cfs_account,
                                              rcpt_number=receipt_number,
                                              rcpt_date=routing_slip.routing_slip_date.strftime('%Y-%m-%d'),
                                              amount=routing_slip.total,
                                              payment_method=parent_payment_account.payment_method,
                                              access_token=CFSService.get_fas_token().json().get('access_token'))

                # Add to the list if parent is NSF, to apply the receipts.
                if parent_rs.status == RoutingSlipStatus.NSF.value:
                    total_invoice_amount = cls._apply_routing_slips_to_pending_invoices(parent_rs)
                    current_app.logger.debug(f'Total Invoice Amount : {total_invoice_amount}')
                    # Update the parent routing slip status to ACTIVE
                    parent_rs.status = RoutingSlipStatus.ACTIVE.value
                    # linking routing slip balance is transferred ,so use the total
                    parent_rs.remaining_amount = float(routing_slip.total) - total_invoice_amount

                routing_slip.save()

            except Exception as e:  # NOQA # pylint: disable=broad-except
                capture_message(
                    f'Error on Linking Routing Slip number:={routing_slip.number}, '
                    f'routing slip : {routing_slip.id}, ERROR : {str(e)}', level='error')
                current_app.logger.error(e)
                continue