コード例 #1
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
コード例 #2
0
 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
コード例 #3
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.
コード例 #4
0
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
コード例 #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(
            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)