Example #1
0
    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
Example #2
0
 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)
Example #3
0
    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)
Example #4
0
    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)
Example #5
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.
Example #6
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
Example #7
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())
    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)
Example #9
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
Example #10
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)