def save_or_update(distribution_details: Dict, dist_id: int = None):
        """Save distribution."""
        current_app.logger.debug('<save_or_update')

        dist_code_svc = DistributionCode()
        if dist_id is not None:
            dist_code_dao = DistributionCodeModel.find_by_id(dist_id)
            dist_code_svc._dao = dist_code_dao  # pylint: disable=protected-access

        if distribution_details.get('endDate', None):
            dist_code_svc.end_date = parser.parse(
                distribution_details.get('endDate'))

        if distribution_details.get('startDate', None):
            dist_code_svc.start_date = parser.parse(
                distribution_details.get('startDate'))
        else:
            dist_code_svc.start_date = date.today()

        _has_code_changes: bool = dist_code_svc.client != distribution_details.get('client', None) \
            or dist_code_svc.responsibility_centre != distribution_details.get('responsibilityCentre', None) \
            or dist_code_svc.service_line != distribution_details.get('serviceLine', None) \
            or dist_code_svc.project_code != distribution_details.get('projectCode', None) \
            or dist_code_svc.service_fee_distribution_code_id != \
            distribution_details.get('serviceFeeDistributionCodeId', None)

        dist_code_svc.client = distribution_details.get('client', None)
        dist_code_svc.name = distribution_details.get('name', None)
        dist_code_svc.responsibility_centre = distribution_details.get(
            'responsibilityCentre', None)
        dist_code_svc.service_line = distribution_details.get(
            'serviceLine', None)
        dist_code_svc.stob = distribution_details.get('stob', None)
        dist_code_svc.project_code = distribution_details.get(
            'projectCode', None)
        dist_code_svc.service_fee_distribution_code_id = distribution_details.get(
            'serviceFeeDistributionCodeId', None)
        dist_code_svc.disbursement_distribution_code_id = distribution_details.get(
            'disbursementDistributionCodeId', None)
        dist_code_svc.account_id = distribution_details.get('accountId', None)

        if _has_code_changes and dist_id is not None:
            # Update all invoices which used this distribution for updating revenue account details
            # If this is a service fee distribution, then find all distribution which uses this and update them.
            InvoiceModel.update_invoices_for_revenue_updates(dist_id)
            for dist in DistributionCodeModel.find_by_service_fee_distribution_id(
                    dist_id):
                InvoiceModel.update_invoices_for_revenue_updates(
                    dist.distribution_code_id)

        # Reset stop jv for every dave.
        dist_code_svc.stop_ejv = False
        dist_code_dao = dist_code_svc.save()

        distribution_code_schema = DistributionCodeSchema()
        current_app.logger.debug('>save_or_update')
        return distribution_code_schema.dump(dist_code_dao, many=False)
    def _dao(self, value):
        self.__dao = value

        self._distribution_code_id: int = self._dao.distribution_code_id
        self._name: str = self._dao.name

        self._client: str = self._dao.client
        self._responsibility_centre: str = self._dao.responsibility_centre
        self._service_line: str = self._dao.service_line
        self._stob: str = self._dao.stob
        self._project_code: str = self._dao.project_code

        if self._dao.service_fee_distribution_code_id:
            _service_fee: DistributionCodeModel = DistributionCodeModel.find_by_id(
                self._dao.service_fee_distribution_code_id)

            self._service_fee_name: str = _service_fee.name
            self._service_fee_client: str = _service_fee.client
            self._service_fee_responsibility_centre: str = _service_fee.responsibility_centre
            self._service_fee_line: str = _service_fee.service_line
            self._service_fee_stob: str = _service_fee.stob
            self._service_fee_project_code: str = _service_fee.project_code

        self._start_date: date = self._dao.start_date
        self._end_date: date = self._dao.end_date
        self._service_fee_distribution_code_id = self._dao.service_fee_distribution_code_id
        self._disbursement_distribution_code_id = self._dao.disbursement_distribution_code_id
        self._stop_ejv: bool = self._dao.stop_ejv
        self._account_id: int = self._dao.account_id
Exemple #3
0
def test_create_ejv_payment_request(session, client, jwt, app):
    """Assert payment request works for EJV accounts."""
    token = jwt.create_jwt(get_claims(role=Role.SYSTEM.value), token_header)
    headers = {
        'Authorization': f'Bearer {token}',
        'content-type': 'application/json'
    }
    # Create account first
    rv = client.post('/api/v1/accounts',
                     data=json.dumps(get_gov_account_payload(account_id=1234)),
                     headers=headers)
    auth_account_id = rv.json.get('authAccountId')

    payment_account: PaymentAccountModel = PaymentAccountModel.find_by_auth_account_id(
        auth_account_id)
    dist_code: DistributionCodeModel = DistributionCodeModel.find_by_active_for_account(
        payment_account.id)

    assert dist_code
    assert dist_code.account_id == payment_account.id

    token = jwt.create_jwt(get_claims(), token_header)
    headers = {
        'Authorization': f'Bearer {token}',
        'content-type': 'application/json',
        'Account-Id': auth_account_id
    }

    rv = client.post('/api/v1/payment-requests',
                     data=json.dumps(get_payment_request()),
                     headers=headers)
    assert rv.json.get('paymentMethod') == PaymentMethod.EJV.value
    assert rv.json.get('statusCode') == InvoiceStatus.APPROVED.value
Exemple #4
0
def test_get_payment_system_url_service_fees(session, public_user_mock):
    """Assert that the url returned is correct."""
    today = current_local_time().strftime(PAYBC_DATE_FORMAT)
    payment_account = factory_payment_account()
    payment = factory_payment()
    payment_account.save()
    payment.save()
    invoice = factory_invoice(payment_account)
    invoice.save()
    invoice_ref = factory_invoice_reference(invoice.id).save()
    fee_schedule = FeeSchedule.find_by_filing_type_and_corp_type('CP', 'OTANN')
    distribution_code = DistributionCodeModel.find_by_active_for_fee_schedule(fee_schedule.fee_schedule_id)

    distribution_code_svc = DistributionCode()
    distribution_code_payload = get_distribution_code_payload()
    # Set service fee distribution
    distribution_code_payload.update({'serviceFeeDistributionCodeId': distribution_code.distribution_code_id})
    # update the existing gl code with new values
    distribution_code_svc.save_or_update(distribution_code_payload,
                                         distribution_code.distribution_code_id)

    service_fee = 100
    line = factory_payment_line_item(invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id, service_fees=service_fee)
    line.save()
    direct_pay_service = DirectPayService()
    payment_response_url = direct_pay_service.get_payment_system_url_for_invoice(invoice, invoice_ref, 'google.com')
    url_param_dict = dict(urllib.parse.parse_qsl(urllib.parse.urlsplit(payment_response_url).query))
    assert url_param_dict['trnDate'] == today
    assert url_param_dict['glDate'] == today
    assert url_param_dict['description'] == 'Direct_Sale'
    assert url_param_dict['pbcRefNumber'] == current_app.config.get('PAYBC_DIRECT_PAY_REF_NUMBER')
    assert url_param_dict['trnNumber'] == generate_transaction_number(invoice.id)
    assert url_param_dict['trnAmount'] == str(invoice.total)
    assert url_param_dict['paymentMethod'] == 'CC'
    assert url_param_dict['redirectUri'] == 'google.com'
    revenue_str = f"1:{distribution_code_payload['client']}." \
                  f"{distribution_code_payload['responsibilityCentre']}." \
                  f"{distribution_code_payload['serviceLine']}." \
                  f"{distribution_code_payload['stob']}." \
                  f"{distribution_code_payload['projectCode']}." \
                  f'000000.0000:10.00'
    revenue_str_service_fee = f"2:{distribution_code_payload['client']}." \
                              f"{distribution_code_payload['responsibilityCentre']}." \
                              f"{distribution_code_payload['serviceLine']}." \
                              f"{distribution_code_payload['stob']}." \
                              f"{distribution_code_payload['projectCode']}." \
                              f'000000.0000:{format(service_fee, DECIMAL_PRECISION)}'
    assert url_param_dict['revenue'] == f'{revenue_str}|{revenue_str_service_fee}'
    urlstring = f"trnDate={today}&pbcRefNumber={current_app.config.get('PAYBC_DIRECT_PAY_REF_NUMBER')}&" \
                f'glDate={today}&description=Direct_Sale&' \
                f'trnNumber={generate_transaction_number(invoice.id)}&' \
                f'trnAmount={invoice.total}&' \
                f'paymentMethod=CC&' \
                f'redirectUri=google.com&' \
                f'currency=CAD&' \
                f'revenue={revenue_str}|' \
                f'{revenue_str_service_fee}'
    expected_hash_str = HashingService.encode(urlstring)

    assert expected_hash_str == url_param_dict['hashValue']
 def find_by_id(identifier: int):
     """Find distribution code by id."""
     current_app.logger.debug(f'<find_by_id, {identifier}')
     distribution_code = DistributionCodeModel.find_by_id(
         identifier=identifier)
     distribution_code_schema = DistributionCodeSchema()
     current_app.logger.debug('>find_by_id')
     return distribution_code_schema.dump(distribution_code, many=False)
 def find_active_by_account_id(account_id: int) -> DistributionCode:
     """Find active distribution code by account_id."""
     current_app.logger.debug(f'<find_active_by_account_id, {account_id}')
     distribution_code = DistributionCodeModel.find_by_active_for_account(
         account_id)
     dist_code_svc = DistributionCode()
     dist_code_svc._dao = distribution_code  # pylint: disable=protected-access
     current_app.logger.debug('>find_active_by_account_id')
     return dist_code_svc
 def find_all():
     """Find all distribution codes valid today."""
     current_app.logger.debug('<find_all')
     data = {'items': []}
     distribution_codes = DistributionCodeModel.find_all()
     distribution_code_schema = DistributionCodeSchema()
     data['items'] = distribution_code_schema.dump(distribution_codes,
                                                   many=True)
     current_app.logger.debug('>find_all')
     return data
    def _create_nsf_invoice(cls, cfs_account: CfsAccountModel, rs_number: str,
                            payment_account: PaymentAccountModel) -> InvoiceModel:
        """Create Invoice, line item and invoice reference records."""
        fee_schedule: FeeScheduleModel = FeeScheduleModel.find_by_filing_type_and_corp_type(corp_type_code='BCR',
                                                                                            filing_type_code='NSF')
        invoice = InvoiceModel(
            bcol_account=payment_account.bcol_account,
            payment_account_id=payment_account.id,
            cfs_account_id=cfs_account.id,
            invoice_status_code=InvoiceStatus.CREATED.value,
            total=fee_schedule.fee.amount,
            service_fees=0,
            paid=0,
            payment_method_code=PaymentMethod.INTERNAL.value,
            corp_type_code='BCR',
            created_on=datetime.now(),
            created_by='SYSTEM',
            routing_slip=rs_number
        )
        invoice = invoice.save()
        distribution: DistributionCodeModel = DistributionCodeModel.find_by_active_for_fee_schedule(
            fee_schedule.fee_schedule_id)

        line_item = PaymentLineItemModel(
            invoice_id=invoice.id,
            total=invoice.total,
            fee_schedule_id=fee_schedule.fee_schedule_id,
            description=fee_schedule.filing_type.description,
            filing_fees=invoice.total,
            gst=0,
            priority_fees=0,
            pst=0,
            future_effective_fees=0,
            line_item_status_code=LineItemStatus.ACTIVE.value,
            service_fees=0,
            fee_distribution_id=distribution.distribution_code_id)
        line_item.save()

        invoice_response = CFSService.create_account_invoice(transaction_number=invoice.id,
                                                             line_items=invoice.payment_line_items,
                                                             cfs_account=cfs_account)

        invoice_number = invoice_response.json().get('invoice_number', None)
        current_app.logger.info(f'invoice_number  {invoice_number}  created in CFS for NSF.')

        InvoiceReferenceModel(
            invoice_id=invoice.id,
            invoice_number=invoice_number,
            reference_number=invoice_response.json().get('pbc_ref_number', None),
            status_code=InvoiceReferenceStatus.ACTIVE.value
        ).save()

        return invoice
Exemple #9
0
def factory_payment_line_item(invoice_id: str, fee_schedule_id: int, filing_fees: int = 10, total: int = 10,
                              service_fees: int = 0, status: str = LineItemStatus.ACTIVE.value):
    """Return Factory."""
    return PaymentLineItem(
        invoice_id=invoice_id,
        fee_schedule_id=fee_schedule_id,
        filing_fees=filing_fees,
        total=total,
        service_fees=service_fees,
        line_item_status_code=status,
        fee_distribution_id=DistributionCode.find_by_active_for_fee_schedule(fee_schedule_id).distribution_code_id
    )
    def save_or_update(distribution_details: Dict, dist_id: int = None):
        """Save distribution."""
        current_app.logger.debug('<save_or_update')

        dist_code_svc = DistributionCode()
        if dist_id is not None:
            dist_code_dao = DistributionCodeModel.find_by_id(dist_id)
            dist_code_svc._dao = dist_code_dao  # pylint: disable=protected-access

        if distribution_details.get('endDate', None):
            dist_code_svc.end_date = parser.parse(
                distribution_details.get('endDate'))

        if distribution_details.get('startDate', None):
            dist_code_svc.start_date = parser.parse(
                distribution_details.get('startDate'))
        else:
            dist_code_svc.start_date = date.today()

        dist_code_svc.client = distribution_details.get('client', None)
        dist_code_svc.responsibility_centre = distribution_details.get(
            'responsibilityCentre', None)
        dist_code_svc.service_line = distribution_details.get(
            'serviceLine', None)
        dist_code_svc.stob = distribution_details.get('stob', None)
        dist_code_svc.project_code = distribution_details.get(
            'projectCode', None)

        dist_code_svc.service_fee_client = distribution_details.get(
            'serviceFeeClient', None)
        dist_code_svc.service_fee_responsibility_centre = distribution_details.get(
            'serviceFeeResponsibilityCentre', None)
        dist_code_svc.service_fee_line = distribution_details.get(
            'serviceFeeLine', None)
        dist_code_svc.service_fee_stob = distribution_details.get(
            'serviceFeeStob', None)
        dist_code_svc.service_fee_project_code = distribution_details.get(
            'serviceFeeProjectCode', None)

        if dist_id is not None:
            # Update all invoices which used this distribution for updating revenue account details
            InvoiceModel.update_invoices_for_revenue_updates(dist_id)

        dist_code_dao = dist_code_svc.save()

        distribution_code_schema = DistributionCodeSchema()
        current_app.logger.debug('>save_or_update')
        return distribution_code_schema.dump(dist_code_dao, many=False)
Exemple #11
0
def _create_nsf_invoice(cfs_account: CfsAccountModel, inv_number: str,
                        payment_account: PaymentAccountModel) -> InvoiceModel:
    """Create Invoice, line item and invoice referwnce records."""
    fee_schedule: FeeScheduleModel = FeeScheduleModel.find_by_filing_type_and_corp_type(
        corp_type_code='BCR', filing_type_code='NSF')
    invoice = InvoiceModel(bcol_account=payment_account.bcol_account,
                           payment_account_id=payment_account.id,
                           cfs_account_id=cfs_account.id,
                           invoice_status_code=InvoiceStatus.CREATED.value,
                           total=fee_schedule.fee.amount,
                           service_fees=0,
                           paid=0,
                           payment_method_code=PaymentMethod.CC.value,
                           corp_type_code='BCR',
                           created_on=datetime.now(),
                           created_by='SYSTEM')
    invoice = invoice.save()
    distribution: DistributionCodeModel = DistributionCodeModel.find_by_active_for_fee_schedule(
        fee_schedule.fee_schedule_id)

    line_item = PaymentLineItemModel(
        invoice_id=invoice.id,
        total=invoice.total,
        fee_schedule_id=fee_schedule.fee_schedule_id,
        description=fee_schedule.filing_type.description,
        filing_fees=invoice.total,
        gst=0,
        priority_fees=0,
        pst=0,
        future_effective_fees=0,
        line_item_status_code=LineItemStatus.ACTIVE.value,
        service_fees=0,
        fee_distribution_id=distribution.distribution_code_id
        if distribution else 1)  # TODO
    line_item.save()

    inv_ref: InvoiceReferenceModel = InvoiceReferenceModel(
        invoice_id=invoice.id,
        invoice_number=inv_number,
        reference_number=InvoiceReferenceModel.
        find_any_active_reference_by_invoice_number(
            invoice_number=inv_number).reference_number,
        status_code=InvoiceReferenceStatus.ACTIVE.value)
    inv_ref.save()

    return invoice
Exemple #12
0
def factory_distribution(name: str,
                         client: str = '111',
                         reps_centre: str = '22222',
                         service_line: str = '33333',
                         stob: str = '4444',
                         project_code: str = '5555555',
                         service_fee_dist_id: int = None,
                         disbursement_dist_id: int = None):
    """Return Factory."""
    return DistributionCode(
        name=name,
        client=client,
        responsibility_centre=reps_centre,
        service_line=service_line,
        stob=stob,
        project_code=project_code,
        service_fee_distribution_code_id=service_fee_dist_id,
        disbursement_distribution_code_id=disbursement_dist_id,
        start_date=datetime.today().date(),
        created_by='test').save()
Exemple #13
0
def factory_create_ejv_account(auth_account_id='1234',
                               client: str = '112',
                               resp_centre: str = '11111',
                               service_line: str = '11111',
                               stob: str = '1111',
                               project_code: str = '1111111'):
    """Return Factory."""
    account = PaymentAccount(auth_account_id=auth_account_id,
                             payment_method=PaymentMethod.EJV.value,
                             name=f'Test {auth_account_id}').save()
    DistributionCode(name=account.name,
                     client=client,
                     responsibility_centre=resp_centre,
                     service_line=service_line,
                     stob=stob,
                     project_code=project_code,
                     account_id=account.id,
                     start_date=datetime.today().date(),
                     created_by='test').save()
    return account
    def create(invoice_id: int, fee: FeeSchedule, **kwargs):
        """Create Payment Line Item record."""
        current_app.logger.debug('<create')
        user: UserContext = kwargs['user']
        p = PaymentLineItem()
        p.invoice_id = invoice_id
        p.total = fee.total_excluding_service_fees
        p.fee_schedule_id = fee.fee_schedule_id
        p.description = fee.description
        p.filing_fees = fee.fee_amount
        p.gst = fee.gst
        p.priority_fees = fee.priority_fee
        p.pst = fee.pst
        p.future_effective_fees = fee.future_effective_fee
        p.quantity = fee.quantity if fee.quantity else 1
        p.line_item_status_code = LineItemStatus.ACTIVE.value
        p.waived_fees = fee.waived_fee_amount
        p.service_fees = fee.service_fees

        # Set distribution details to line item
        distribution_code = None
        if p.total > 0:
            distribution_code = DistributionCodeModel.find_by_active_for_fee_schedule(
                fee.fee_schedule_id)
            p.fee_distribution_id = distribution_code.distribution_code_id

        if fee.waived_fee_amount > 0:
            if user.has_role(Role.STAFF.value):
                p.waived_by = user.user_name
            else:
                raise BusinessException(Error.FEE_OVERRIDE_NOT_ALLOWED)

        p_dao = p.flush()

        p = PaymentLineItem()
        p._dao = p_dao  # pylint: disable=protected-access

        # Set distribution model to avoid more queries to DB
        p.fee_distribution = distribution_code
        current_app.logger.debug('>create')
        return p
Exemple #15
0
    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)
Exemple #16
0
    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.
Exemple #17
0
def test_payments_for_gov_accounts(session, monkeypatch):
    """Test payments for gov accounts.

    Steps:
    1) Update a distribution code with client code 112.
    2) Create multiple gov accounts for GA - 112
    3) Create multiple gov accounts for GI - NOT 112
    4) Create some transactions for these accounts
    5) Run the job and assert results.
    """
    monkeypatch.setattr('pysftp.Connection.put', lambda *args, **kwargs: None)

    corp_type = 'BEN'
    filing_type = 'BCINC'

    # Find fee schedule which have service fees.
    fee_schedule: FeeSchedule = FeeSchedule.find_by_filing_type_and_corp_type(corp_type, filing_type)
    # Create a service fee distribution code
    service_fee_dist_code = factory_distribution(name='service fee', client='112', reps_centre='99999',
                                                 service_line='99999',
                                                 stob='9999', project_code='9999999')
    service_fee_dist_code.save()

    dist_code: DistributionCode = DistributionCode.find_by_active_for_fee_schedule(fee_schedule.fee_schedule_id)
    # Update fee dist code to match the requirement.
    dist_code.client = '112'
    dist_code.responsibility_centre = '22222'
    dist_code.service_line = '33333'
    dist_code.stob = '4444'
    dist_code.project_code = '5555555'
    dist_code.service_fee_distribution_code_id = service_fee_dist_code.distribution_code_id
    dist_code.save()

    # GA
    jv_account_1 = factory_create_ejv_account(auth_account_id='1')
    jv_account_2 = factory_create_ejv_account(auth_account_id='2')

    # GI
    jv_account_3 = factory_create_ejv_account(auth_account_id='3', client='111')
    jv_account_4 = factory_create_ejv_account(auth_account_id='4', client='111')

    jv_accounts = [jv_account_1, jv_account_2, jv_account_3, jv_account_4]
    inv_ids = []
    for jv_acc in jv_accounts:
        inv = factory_invoice(payment_account=jv_acc, corp_type_code=corp_type, total=101.5,
                              status_code=InvoiceStatus.APPROVED.value, payment_method_code=None)
        factory_payment_line_item(invoice_id=inv.id,
                                  fee_schedule_id=fee_schedule.fee_schedule_id,
                                  filing_fees=100,
                                  total=100,
                                  service_fees=1.5,
                                  fee_dist_id=dist_code.distribution_code_id)
        inv_ids.append(inv.id)

    EjvPaymentTask.create_ejv_file()

    # Lookup invoice and assert invoice status
    for inv_id in inv_ids:
        invoice_ref = InvoiceReference.find_reference_by_invoice_id_and_status(inv_id,
                                                                               InvoiceReferenceStatus.ACTIVE.value)
        assert invoice_ref

        ejv_inv_link = db.session.query(EjvInvoiceLink).filter(EjvInvoiceLink.invoice_id == inv_id).first()
        assert ejv_inv_link

        ejv_header = db.session.query(EjvHeader).filter(EjvHeader.id == ejv_inv_link.ejv_header_id).first()
        assert ejv_header.disbursement_status_code == DisbursementStatus.UPLOADED.value
        assert ejv_header

        ejv_file: EjvFile = EjvFile.find_by_id(ejv_header.ejv_file_id)
        assert ejv_file
        assert ejv_file.disbursement_status_code == DisbursementStatus.UPLOADED.value
        assert not ejv_file.is_distribution
    def _create_ejv_file_for_partner(cls, batch_type: str):  # pylint:disable=too-many-locals, too-many-statements
        """Create EJV file for the partner and upload."""
        ejv_content: str = ''
        batch_total: float = 0
        control_total: int = 0
        today = datetime.now()
        disbursement_desc = current_app.config.get('CGI_DISBURSEMENT_DESC'). \
            format(today.strftime('%B').upper(), f'{today.day:0>2}')[:100]
        disbursement_desc = f'{disbursement_desc:<100}'

        # Create a ejv file model record.
        ejv_file_model: EjvFileModel = EjvFileModel(
            file_type=EjvFileType.DISBURSEMENT.value,
            file_ref=cls.get_file_name(),
            disbursement_status_code=DisbursementStatus.UPLOADED.value).flush(
            )
        batch_number = cls.get_batch_number(ejv_file_model.id)

        # Get partner list. Each of the partner will go as a JV Header and transactions as JV Details.
        partners = cls._get_partners_by_batch_type(batch_type)
        current_app.logger.info(partners)

        # JV Batch Header
        batch_header: str = cls.get_batch_header(batch_number, batch_type)

        for partner in partners:
            # Find all invoices for the partner to disburse.
            # This includes invoices which are not PAID and invoices which are refunded.
            payment_invoices = cls._get_invoices_for_disbursement(partner)
            refund_reversals = cls._get_invoices_for_refund_reversal(partner)
            invoices = payment_invoices + refund_reversals
            # If no invoices continue.
            if not invoices:
                continue

            effective_date: str = cls.get_effective_date()
            # Construct journal name
            ejv_header_model: EjvFileModel = EjvHeaderModel(
                partner_code=partner.code,
                disbursement_status_code=DisbursementStatus.UPLOADED.value,
                ejv_file_id=ejv_file_model.id).flush()
            journal_name: str = cls.get_journal_name(ejv_header_model.id)

            # To populate JV Header and JV Details, group these invoices by the distribution
            # and create one JV Header and detail for each.
            distribution_code_set = set()
            invoice_id_list = []
            for inv in invoices:
                invoice_id_list.append(inv.id)
                for line_item in inv.payment_line_items:
                    distribution_code_set.add(line_item.fee_distribution_id)

            for distribution_code_id in list(distribution_code_set):
                distribution_code: DistributionCodeModel = DistributionCodeModel.find_by_id(
                    distribution_code_id)
                credit_distribution_code: DistributionCodeModel = DistributionCodeModel.find_by_id(
                    distribution_code.disbursement_distribution_code_id)
                if credit_distribution_code.stop_ejv:
                    continue

                line_items = cls._find_line_items_by_invoice_and_distribution(
                    distribution_code_id, invoice_id_list)

                total: float = 0
                for line in line_items:
                    total += line.total

                batch_total += total

                debit_distribution = cls.get_distribution_string(
                    distribution_code)  # Debit from BCREG GL
                credit_distribution = cls.get_distribution_string(
                    credit_distribution_code)  # Credit to partner GL

                # JV Header
                ejv_content = '{}{}'.format(
                    ejv_content,  # pylint:disable=consider-using-f-string
                    cls.get_jv_header(batch_type,
                                      cls.get_journal_batch_name(batch_number),
                                      journal_name, total))
                control_total += 1

                line_number: int = 0
                for line in line_items:
                    # JV Details
                    line_number += 1
                    # Flow Through add it as the invoice id.
                    flow_through = f'{line.invoice_id:<110}'
                    # debit_distribution and credit_distribution stays as is for invoices which are not PAID
                    # For reversals, we just need to reverse the debit and credit.
                    is_reversal = InvoiceModel.find_by_id(line.invoice_id).invoice_status_code in \
                        (InvoiceStatus.REFUNDED.value, InvoiceStatus.REFUND_REQUESTED.value)

                    ejv_content = '{}{}'.format(
                        ejv_content,  # pylint:disable=consider-using-f-string
                        cls.get_jv_line(batch_type, credit_distribution,
                                        disbursement_desc, effective_date,
                                        flow_through, journal_name, line.total,
                                        line_number,
                                        'C' if not is_reversal else 'D'))
                    line_number += 1
                    control_total += 1

                    # Add a line here for debit too
                    ejv_content = '{}{}'.format(
                        ejv_content,  # pylint:disable=consider-using-f-string
                        cls.get_jv_line(batch_type, debit_distribution,
                                        disbursement_desc, effective_date,
                                        flow_through, journal_name, line.total,
                                        line_number,
                                        'D' if not is_reversal else 'C'))

                    control_total += 1

            # Create ejv invoice link records and set invoice status
            for inv in invoices:
                # Create Ejv file link and flush
                EjvInvoiceLinkModel(
                    invoice_id=inv.id,
                    ejv_header_id=ejv_header_model.id,
                    disbursement_status_code=DisbursementStatus.UPLOADED.value
                ).flush()
                # Set distribution status to invoice
                inv.disbursement_status_code = DisbursementStatus.UPLOADED.value
                inv.flush()

        if not ejv_content:
            db.session.rollback()
            return

        # JV Batch Trailer
        jv_batch_trailer: str = cls.get_batch_trailer(batch_number,
                                                      batch_total, batch_type,
                                                      control_total)

        ejv_content = f'{batch_header}{ejv_content}{jv_batch_trailer}'
        # Create a file add this content.
        file_path_with_name, trg_file_path = cls.create_inbox_and_trg_files(
            ejv_content)

        # Upload file and trg to FTP
        cls.upload(ejv_content, cls.get_file_name(), file_path_with_name,
                   trg_file_path)

        # commit changes to DB
        db.session.commit()

        # Add a sleep to prevent collision on file name.
        time.sleep(1)
Exemple #19
0
    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())
Exemple #20
0
    def _build_lines(cls,
                     payment_line_items: List[PaymentLineItemModel],
                     negate: bool = False):
        """Build lines for the invoice."""
        # Fetch all distribution codes to reduce DB hits. Introduce caching if needed later
        distribution_codes: List[
            DistributionCodeModel] = DistributionCodeModel.find_all()
        lines = []
        index: int = 0
        for line_item in payment_line_items:
            # Find the distribution from the above list
            distribution_code = [dist for dist in distribution_codes if
                                 dist.distribution_code_id == line_item.fee_distribution_id][0] \
                if line_item.fee_distribution_id else None
            index = index + 1
            distribution = [
                dict(
                    dist_line_number=index,
                    amount=cls._get_amount(line_item.total, negate),
                    account=
                    f'{distribution_code.client}.{distribution_code.responsibility_centre}.'
                    f'{distribution_code.service_line}.{distribution_code.stob}.'
                    f'{distribution_code.project_code}.000000.0000')
            ] if distribution_code else None
            lines.append({
                'line_number': index,
                'line_type': CFS_LINE_TYPE,
                'description': line_item.description,
                'unit_price': cls._get_amount(line_item.total, negate),
                'quantity': 1,
                'attribute1': line_item.invoice_id,
                'attribute2': line_item.id,
                'distribution': distribution
            })

            if line_item.service_fees > 0:
                service_fee_distribution: DistributionCodeModel = DistributionCodeModel.find_by_id(
                    distribution_code.service_fee_distribution_code_id)
                index = index + 1
                lines.append({
                    'line_number':
                    index,
                    'line_type':
                    CFS_LINE_TYPE,
                    'description':
                    'Service Fee',
                    'unit_price':
                    cls._get_amount(line_item.service_fees, negate),
                    'quantity':
                    1,
                    'attribute1':
                    line_item.invoice_id,
                    'attribute2':
                    line_item.id,
                    'distribution': [{
                        'dist_line_number':
                        index,
                        'amount':
                        cls._get_amount(line_item.service_fees, negate),
                        'account':
                        f'{service_fee_distribution.client}.'
                        f'{service_fee_distribution.responsibility_centre}.'
                        f'{service_fee_distribution.service_line}.'
                        f'{service_fee_distribution.stob}.'
                        f'{service_fee_distribution.project_code}.000000.0000'
                    }]
                })

        return lines
async def _process_jv_details_feedback(ejv_file, has_errors, line,
                                       receipt_number):  # pylint:disable=too-many-locals
    journal_name: str = line[7:17]  # {ministry}{ejv_header_model.id:0>8}
    ejv_header_model_id = int(journal_name[2:])
    invoice_id = int(line[205:315])
    invoice: InvoiceModel = InvoiceModel.find_by_id(invoice_id)
    invoice_link: EjvInvoiceLinkModel = db.session.query(
        EjvInvoiceLinkModel).filter(
            EjvInvoiceLinkModel.ejv_header_id == ejv_header_model_id).filter(
                EjvInvoiceLinkModel.invoice_id == invoice_id).one_or_none()
    invoice_return_code = line[315:319]
    invoice_return_message = line[319:469]
    # If the JV process failed, then mark the GL code against the invoice to be stopped
    # for further JV process for the credit GL.
    if line[104:105] == 'C' and ejv_file.is_distribution:
        invoice_link.disbursement_status_code = _get_disbursement_status(
            invoice_return_code)
        invoice_link.message = invoice_return_message
        invoice.disbursement_status_code = _get_disbursement_status(
            invoice_return_code)

        if invoice_link.disbursement_status_code == DisbursementStatus.ERRORED.value:
            has_errors = True

        line_items: List[PaymentLineItemModel] = invoice.payment_line_items
        for line_item in line_items:
            # Line debit distribution
            debit_distribution: DistributionCodeModel = DistributionCodeModel \
                .find_by_id(line_item.fee_distribution_id)
            credit_distribution: DistributionCodeModel = DistributionCodeModel \
                .find_by_id(debit_distribution.disbursement_distribution_code_id)
            credit_distribution.stop_ejv = True
    elif line[104:105] == 'D' and not ejv_file.is_distribution:
        # This is for gov account payment JV.
        invoice_link.disbursement_status_code = _get_disbursement_status(
            invoice_return_code)
        invoice_link.message = invoice_return_message
        inv_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
            invoice_id, InvoiceReferenceStatus.ACTIVE.value)

        if invoice_link.disbursement_status_code == DisbursementStatus.ERRORED.value:
            has_errors = True
            # Cancel the invoice reference.
            inv_ref.status_code = InvoiceReferenceStatus.CANCELLED.value
            # Find the distribution code and set the stop_ejv flag to TRUE
            dist_code: DistributionCodeModel = DistributionCodeModel.find_by_active_for_account(
                invoice.payment_account_id)
            dist_code.stop_ejv = True
        elif invoice_link.disbursement_status_code == DisbursementStatus.COMPLETED.value:
            # Set the invoice status as PAID. Mark the invoice reference as COMPLETED, create a receipt
            invoice.invoice_status_code = InvoiceStatus.PAID.value
            if inv_ref:
                inv_ref.status_code = InvoiceReferenceStatus.COMPLETED.value
            # Find receipt and add total to it, as single invoice can be multiple rows in the file
            receipt = ReceiptModel.find_by_invoice_id_and_receipt_number(
                invoice_id=invoice_id, receipt_number=receipt_number)
            if receipt:
                receipt.receipt_amount += float(line[89:104])
            else:
                ReceiptModel(invoice_id=invoice_id,
                             receipt_number=receipt_number,
                             receipt_date=datetime.now(),
                             receipt_amount=float(line[89:104])).flush()
    return has_errors
Exemple #22
0
async def test_failed_payment_ejv_reconciliations(session, app, stan_server,
                                                  event_loop, client_id,
                                                  events_stan, future,
                                                  mock_publish):
    """Test Reconciliations worker."""
    # Call back for the subscription
    from reconciliations.worker import cb_subscription_handler

    # Create a Credit Card Payment
    # register the handler to test it
    await subscribe_to_queue(
        events_stan,
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('subject'),
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('queue'),
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('durable_name'),
        cb_subscription_handler)

    # 1. Create EJV payment accounts
    # 2. Create invoice and related records
    # 3. Create a feedback file and assert status

    corp_type = 'BEN'
    filing_type = 'BCINC'

    # Find fee schedule which have service fees.
    fee_schedule: FeeScheduleModel = FeeScheduleModel.find_by_filing_type_and_corp_type(
        corp_type, filing_type)
    # Create a service fee distribution code
    service_fee_dist_code = factory_distribution(name='service fee',
                                                 client='112',
                                                 reps_centre='99999',
                                                 service_line='99999',
                                                 stob='9999',
                                                 project_code='9999999')
    service_fee_dist_code.save()

    dist_code: DistributionCodeModel = DistributionCodeModel.find_by_active_for_fee_schedule(
        fee_schedule.fee_schedule_id)
    # Update fee dist code to match the requirement.
    dist_code.client = '112'
    dist_code.responsibility_centre = '22222'
    dist_code.service_line = '33333'
    dist_code.stob = '4444'
    dist_code.project_code = '5555555'
    dist_code.service_fee_distribution_code_id = service_fee_dist_code.distribution_code_id
    dist_code.save()

    # GA
    jv_account_1 = factory_create_ejv_account(auth_account_id='1')
    jv_account_2 = factory_create_ejv_account(auth_account_id='2')

    # GI
    jv_account_3 = factory_create_ejv_account(auth_account_id='3',
                                              client='111')
    jv_account_4 = factory_create_ejv_account(auth_account_id='4',
                                              client='111')

    # Now create JV records.
    # Create EJV File model
    file_ref = f'INBOX.{datetime.now()}'
    ejv_file: EjvFileModel = EjvFileModel(
        file_ref=file_ref,
        disbursement_status_code=DisbursementStatus.UPLOADED.value,
        is_distribution=False).save()
    ejv_file_id = ejv_file.id

    feedback_content = f'..BG...........00000000{ejv_file_id}...\n' \
                       f'..BH...0000.................................................................................' \
                       f'.....................................................................CGI\n'

    jv_accounts = [jv_account_1, jv_account_2, jv_account_3, jv_account_4]
    inv_ids = []
    jv_account_ids = []
    inv_total_amount = 101.5
    for jv_acc in jv_accounts:
        jv_account_ids.append(jv_acc.id)
        inv = factory_invoice(payment_account=jv_acc,
                              corp_type_code=corp_type,
                              total=inv_total_amount,
                              status_code=InvoiceStatus.APPROVED.value,
                              payment_method_code=None)
        factory_invoice_reference(inv.id)
        line: PaymentLineItemModel = factory_payment_line_item(
            invoice_id=inv.id,
            fee_schedule_id=fee_schedule.fee_schedule_id,
            filing_fees=100,
            total=100,
            service_fees=1.5,
            fee_dist_id=dist_code.distribution_code_id)
        inv_ids.append(inv.id)
        ejv_header: EjvHeaderModel = EjvHeaderModel(
            disbursement_status_code=DisbursementStatus.UPLOADED.value,
            ejv_file_id=ejv_file.id,
            payment_account_id=jv_acc.id).save()

        EjvInvoiceLinkModel(
            invoice_id=inv.id,
            ejv_header_id=ejv_header.id,
            disbursement_status_code=DisbursementStatus.UPLOADED.value).save()
        inv_total = f'{inv.total:.2f}'.zfill(15)
        pay_line_amount = f'{line.total:.2f}'.zfill(15)
        service_fee_amount = f'{line.service_fees:.2f}'.zfill(15)
        jh_and_jd = f'..JH...FI0000000{ejv_header.id}.........................{inv_total}.....................' \
                    f'............................................................................................' \
                    f'............................................................................................' \
                    f'.........1111ERROR..........................................................................' \
                    f'.......................................................................CGI\n' \
                    f'..JD...FI0000000{ejv_header.id}00001........................................................' \
                    f'...........{pay_line_amount}D.................................................................' \
                    f'...................................{inv.id}                                             ' \
                    f'                                                                1111ERROR...................' \
                    f'............................................................................................' \
                    f'..................................CGI\n' \
                    f'..JD...FI0000000{ejv_header.id}00002........................................................' \
                    f'...........{pay_line_amount}C.................................................................' \
                    f'...................................{inv.id}                                             ' \
                    f'                                                                1111ERROR...................' \
                    f'............................................................................................' \
                    f'..................................CGI\n' \
                    f'..JD...FI0000000{ejv_header.id}00003...........................................................' \
                    f'........{service_fee_amount}D.................................................................' \
                    f'...................................{inv.id}                                             ' \
                    f'                                                                1111ERROR...................' \
                    f'............................................................................................' \
                    f'..................................CGI\n' \
                    f'..JD...FI0000000{ejv_header.id}00004........................................................' \
                    f'...........{service_fee_amount}C..............................................................' \
                    f'......................................{inv.id}                                             ' \
                    f'                                                                1111ERROR...................' \
                    f'............................................................................................' \
                    f'..................................CGI\n'
        feedback_content = feedback_content + jh_and_jd
    feedback_content = feedback_content + f'..BT.......FI0000000{ejv_header.id}000000000000002{inv_total}0000.......' \
                                          f'.........................................................................' \
                                          f'......................................................................CGI'
    ack_file_name = f'ACK.{file_ref}'

    with open(ack_file_name, 'a+') as jv_file:
        jv_file.write('')
        jv_file.close()

    # Now upload the ACK file to minio and publish message.
    upload_to_minio(file_name=ack_file_name, value_as_bytes=str.encode(''))

    await helper_add_ejv_event_to_queue(events_stan, file_name=ack_file_name)

    # Query EJV File and assert the status is changed
    ejv_file = EjvFileModel.find_by_id(ejv_file_id)
    assert ejv_file.disbursement_status_code == DisbursementStatus.ACKNOWLEDGED.value

    feedback_file_name = f'FEEDBACK.{file_ref}'

    with open(feedback_file_name, 'a+') as jv_file:
        jv_file.write(feedback_content)
        jv_file.close()

    # Now upload the ACK file to minio and publish message.
    with open(feedback_file_name, 'rb') as f:
        upload_to_minio(f.read(), feedback_file_name)

    await helper_add_ejv_event_to_queue(events_stan,
                                        file_name=feedback_file_name,
                                        message_type='FEEDBACKReceived')

    # Query EJV File and assert the status is changed
    ejv_file = EjvFileModel.find_by_id(ejv_file_id)
    assert ejv_file.disbursement_status_code == DisbursementStatus.COMPLETED.value
    # Assert invoice and receipt records
    for inv_id in inv_ids:
        invoice: InvoiceModel = InvoiceModel.find_by_id(inv_id)
        assert invoice.disbursement_status_code is None
        assert invoice.invoice_status_code == InvoiceStatus.APPROVED.value  # Will stay as original status
        invoice_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
            inv_id, InvoiceReferenceStatus.COMPLETED.value)
        assert invoice_ref is None  # No completed inv ref
        receipt = ReceiptModel.find_by_invoice_id_and_receipt_number(
            invoice_id=inv_id)
        assert receipt is None  # NO receipt

    # Assert payment records
    for jv_account_id in jv_account_ids:
        account = PaymentAccountModel.find_by_id(jv_account_id)
        payment: PaymentModel = PaymentModel.search_account_payments(
            auth_account_id=account.auth_account_id,
            payment_status=PaymentStatus.COMPLETED.value,
            page=1,
            limit=100)[0]
        assert len(payment) == 0
Exemple #23
0
async def test_succesful_partner_ejv_reconciliations(session, app, stan_server,
                                                     event_loop, client_id,
                                                     events_stan, future,
                                                     mock_publish):
    """Test Reconciliations worker."""
    # Call back for the subscription
    from reconciliations.worker import cb_subscription_handler

    # Create a Credit Card Payment
    # register the handler to test it
    await subscribe_to_queue(
        events_stan,
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('subject'),
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('queue'),
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('durable_name'),
        cb_subscription_handler)

    # 1. Create payment account
    # 2. Create invoice and related records
    # 3. Create CFS Invoice records
    # 4. Create a CFS settlement file, and verify the records
    cfs_account_number = '1234'
    partner_code = 'VS'
    fee_schedule: FeeScheduleModel = FeeScheduleModel.find_by_filing_type_and_corp_type(
        corp_type_code=partner_code, filing_type_code='WILLSEARCH')

    pay_account: PaymentAccountModel = factory_create_pad_account(
        status=CfsAccountStatus.ACTIVE.value,
        account_number=cfs_account_number)
    invoice: InvoiceModel = factory_invoice(
        payment_account=pay_account,
        total=100,
        service_fees=10.0,
        corp_type_code='VS',
        payment_method_code=PaymentMethod.ONLINE_BANKING.value,
        status_code=InvoiceStatus.PAID.value)
    invoice_id = invoice.id
    line_item: PaymentLineItemModel = factory_payment_line_item(
        invoice_id=invoice.id,
        filing_fees=90.0,
        service_fees=10.0,
        total=90.0,
        fee_schedule_id=fee_schedule.fee_schedule_id)
    dist_code: DistributionCodeModel = DistributionCodeModel.find_by_id(
        line_item.fee_distribution_id)
    # Check if the disbursement distribution is present for this.
    if not dist_code.disbursement_distribution_code_id:
        disbursement_distribution_code: DistributionCodeModel = factory_distribution(
            name='Disbursement')
        dist_code.disbursement_distribution_code_id = disbursement_distribution_code.distribution_code_id
        dist_code.save()

    invoice_number = '1234567890'
    factory_invoice_reference(
        invoice_id=invoice.id,
        invoice_number=invoice_number,
        status_code=InvoiceReferenceStatus.COMPLETED.value)
    invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value
    invoice = invoice.save()

    # Now create JV records.
    # Create EJV File model
    file_ref = f'INBOX.{datetime.now()}'
    ejv_file: EjvFileModel = EjvFileModel(
        file_ref=file_ref,
        disbursement_status_code=DisbursementStatus.UPLOADED.value).save()
    ejv_file_id = ejv_file.id

    ejv_header: EjvHeaderModel = EjvHeaderModel(
        disbursement_status_code=DisbursementStatus.UPLOADED.value,
        ejv_file_id=ejv_file.id,
        partner_code=partner_code,
        payment_account_id=pay_account.id).save()
    ejv_header_id = ejv_header.id
    EjvInvoiceLinkModel(
        invoice_id=invoice.id,
        ejv_header_id=ejv_header.id,
        disbursement_status_code=DisbursementStatus.UPLOADED.value).save()

    ack_file_name = f'ACK.{file_ref}'

    with open(ack_file_name, 'a+') as jv_file:
        jv_file.write('')
        jv_file.close()

    # Now upload the ACK file to minio and publish message.
    upload_to_minio(file_name=ack_file_name, value_as_bytes=str.encode(''))

    await helper_add_ejv_event_to_queue(events_stan, file_name=ack_file_name)

    # Query EJV File and assert the status is changed
    ejv_file = EjvFileModel.find_by_id(ejv_file_id)
    assert ejv_file.disbursement_status_code == DisbursementStatus.ACKNOWLEDGED.value

    # Now upload a feedback file and check the status.
    # Just create feedback file to mock the real feedback file.
    feedback_content = f'..BG...........00000000{ejv_file_id}...\n' \
                       f'..BH...0000.................................................................................' \
                       f'.....................................................................CGI\n' \
                       f'..JH...FI0000000{ejv_header_id}.........................000000000090.00.....................' \
                       f'............................................................................................' \
                       f'............................................................................................' \
                       f'.........0000...............................................................................' \
                       f'.......................................................................CGI\n' \
                       f'..JD...FI0000000{ejv_header_id}00001........................................................' \
                       f'...........000000000090.00D.................................................................' \
                       f'...................................{invoice_id}                                             ' \
                       f'                                                                0000........................' \
                       f'............................................................................................' \
                       f'..................................CGI\n' \
                       f'..JD...FI0000000{ejv_header_id}00002........................................................' \
                       f'...........000000000090.00C.................................................................' \
                       f'...................................{invoice_id}                                             ' \
                       f'                                                                0000........................' \
                       f'............................................................................................' \
                       f'..................................CGI\n' \
                       f'..BT.......FI0000000{ejv_header_id}000000000000002000000000090.000000.......................' \
                       f'............................................................................................' \
                       f'...................................CGI'

    feedback_file_name = f'FEEDBACK.{file_ref}'

    with open(feedback_file_name, 'a+') as jv_file:
        jv_file.write(feedback_content)
        jv_file.close()

    # Now upload the ACK file to minio and publish message.
    with open(feedback_file_name, 'rb') as f:
        upload_to_minio(f.read(), feedback_file_name)
    # upload_to_minio(file_name=feedback_file_name, value_as_bytes=feedback_content.encode())

    await helper_add_ejv_event_to_queue(events_stan,
                                        file_name=feedback_file_name,
                                        message_type='FEEDBACKReceived')

    # Query EJV File and assert the status is changed
    ejv_file = EjvFileModel.find_by_id(ejv_file_id)
    assert ejv_file.disbursement_status_code == DisbursementStatus.COMPLETED.value
    invoice = InvoiceModel.find_by_id(invoice_id)
    assert invoice.disbursement_status_code == DisbursementStatus.COMPLETED.value