"""Statement Attachment Invoices logic.""" from datetime import datetime from moneyhub import models from moneyhub.config import Config from moneyhub.connectors import sqs from moneyhub.constants.constants import NumberFormat from moneyhub.constants.constants import StatementAttachmentFileType from moneyhub.constants.constants import StatementAttachmentStatus from moneyhub.constants.constants import StatementAttachmentType from moneyhub.constants.constants import SYSTEM_TIMEZONE from moneyhub.constants.constants import VatCategory from moneyhub.constants.features import FEATURE_INVOICE_ACCOUNT_SEQUENCE_NUMBERS from moneyhub.logic.statement_attachment import bulk_create_statement_attachments from moneyhub.logic.statement_attachment import generate_account_invoice_number from moneyhub.logic.statement_attachment import generate_entity_invoice_number from moneyhub.logic.statement_attachment import get_latest_account_invoice_number_map from moneyhub.logic.statement_attachment import get_latest_entity_invoice_number_map from moneyhub.logic.statement_attachment import update_statement_attachment from moneyhub.schemas.statement_attachment import StatementAttachmentDetailSchema from moneyhub.utils.features import is_feature_enabled DISTRIBUTION_FEE_INVOICE = StatementAttachmentType.DISTRIBUTION_FEE_INVOICE SELF_BILLING_INVOICE = StatementAttachmentType.SELF_BILLING_INVOICE CUSTOM_PAYMENT = VatCategory.CUSTOM_PAYMENT GROSS_REVENUE = VatCategory.GROSS_REVENUE DISTRIBUTION_FEE = VatCategory.DISTRIBUTION_FEE CLOSING_BALANCE = VatCategory.CLOSING_BALANCE COMMISSION = VatCategory.COMMISSION def get_invoices_by_payment_entity( payment_entity_id: int, statement_period_id: int): """GET invoice statement attachments for a specified payment entity id and statement period. Args: payment_entity_id (int): The id of a payment entity statement_period_id (int): the id of a statement period Returns: list: list of invoice statement attachments """ return models.StatementAttachment.get_invoices_by_payment_entity( payment_entity_id, statement_period_id) def _add_new_invoices( statement_period_id: int, contracts: list, existing_attachments_keys: list, distribution_fee_contracts: set, self_billing_contracts: set, orchard_identity_id: str, ) -> list: """Add new invoices for specified payment entity and statement period. Args: statement_period_id (int): Period to create the report for contracts (list): list of contracts by payment entity existing_attachments_keys (list): existing attachments keys distribution_fee_contracts (set): set of distro contracts self_billing_contracts (set): set of self billing contracts orchard_identity_id (str): orchard identity id Returns: list: list of new invoice statement attachments """ new_statement_attachments = list() current_year = (datetime.now(SYSTEM_TIMEZONE)).year invoices_sequence_numbers = get_latest_entity_invoice_number_map(current_year) account_invoice_sequence_numbers = get_latest_account_invoice_number_map( current_year, [contract.account_id for contract in contracts] ) for contract in contracts: statement_attachment_key = f'{contract.contract_id}-{DISTRIBUTION_FEE_INVOICE}' company_code = contract.tax_entity_company_code if contract.tax_entity_company_code \ else contract.company_code if (statement_attachment_key not in existing_attachments_keys and contract.contract_id in distribution_fee_contracts): invoice_number = generate_entity_invoice_number( current_year, company_code, DISTRIBUTION_FEE_INVOICE, invoices_sequence_numbers) new_statement_attachments.append(dict( account_id=contract.account_id, contract_id=contract.contract_id, statement_period_id=statement_period_id, statement_attachment_status=StatementAttachmentStatus.IN_PROGRESS, statement_attachment_type=DISTRIBUTION_FEE_INVOICE, file_type=StatementAttachmentFileType.PDF, number_format=NumberFormat.US, invoice_number=invoice_number, created_by=orchard_identity_id )) statement_attachment_key = f'{contract.contract_id}-{SELF_BILLING_INVOICE}' if (statement_attachment_key not in existing_attachments_keys and contract.contract_id in self_billing_contracts): if is_feature_enabled(FEATURE_INVOICE_ACCOUNT_SEQUENCE_NUMBERS): invoice_number = generate_account_invoice_number( current_year, company_code, contract.account_id, account_invoice_sequence_numbers) else: invoice_number = generate_entity_invoice_number( current_year, company_code, SELF_BILLING_INVOICE, invoices_sequence_numbers) new_statement_attachments.append(dict( account_id=contract.account_id, contract_id=contract.contract_id, statement_period_id=statement_period_id, statement_attachment_status=StatementAttachmentStatus.IN_PROGRESS, statement_attachment_type=SELF_BILLING_INVOICE, file_type=StatementAttachmentFileType.PDF, number_format=NumberFormat.US, invoice_number=invoice_number, created_by=orchard_identity_id )) return new_statement_attachments def create_invoice_documents( payment_entity_id: int, statement_period_id: int, orchard_identity_id: str, correlation_id: str | None) -> list[StatementAttachmentDetailSchema]: """Create invoices for specified payment entity and statement period. Args: payment_entity_id (int): The id of a payment entity statement_period_id (int): Period to create the report for orchard_identity_id (str): orchard identity id correlation_id (str): Optional request correlation ID Returns: list: list of invoice statement attachments """ contracts = models.AccountContract.get_contracts_by_payment_entity(payment_entity_id) vat_entries = models.LedgerAccountingRunVat.get_by_statement_period(statement_period_id) ledger_vat_summary_entries = models.LedgerVatSummary.get_by_activity_statement_period( statement_period_id) # Determine which contracts should get what invoice distribution_fee_contracts = [ entry.contract_id for entry in vat_entries if entry.distribution_fee is not None ] summary_distribution_contracts = [ entry.contract_id for entry in ledger_vat_summary_entries if entry.vat_category in [DISTRIBUTION_FEE, COMMISSION] ] distribution_fee_contracts = set(distribution_fee_contracts + summary_distribution_contracts) self_billing_contracts = set( entry.contract_id for entry in ledger_vat_summary_entries if entry.vat_category in [GROSS_REVENUE, CLOSING_BALANCE, CUSTOM_PAYMENT] ) existing_attachments = models.StatementAttachment.get_invoices_by_payment_entity( payment_entity_id, statement_period_id) existing_attachments_keys = [ f'{item.contract_id}-{item.statement_attachment_type}' for item in existing_attachments] new_statement_attachments = _add_new_invoices( statement_period_id, contracts, existing_attachments_keys, distribution_fee_contracts, self_billing_contracts, orchard_identity_id) if len(new_statement_attachments) != 0: new_statement_attachments = bulk_create_statement_attachments(new_statement_attachments) _trigger_invoices_generation(statement_period_id, new_statement_attachments, correlation_id) return existing_attachments + new_statement_attachments def _trigger_invoices_generation( statement_period_id: int, statement_attachments: list, correlation_id: str | None = None ) -> dict: """Trigger invoice generation for the specified period. Args: statement_period_id (int): The id of statement period statement_attachments (list): List of created invoices correlation_id (str): Correlation id from the header """ account_ids = {attachment.account_id for attachment in statement_attachments} if correlation_id is None: correlation_id = '' messages = [ { 'account_id': account_id, 'statement_period_id': statement_period_id, } for account_id in account_ids ] sqs.send_messages( Config.SQS_MH_GENERATE_ATTACHMENTS_QUEUE_NAME, correlation_id, messages) return { 'status': 'OK', 'messages_sent': len(messages), } def retry_invoice_documents( payment_entity_id: int, statement_period_id: int, correlation_id: str | None, ) -> list[StatementAttachmentDetailSchema]: """Regenerate invoices for specified payment entity and statement period. Args: payment_entity_id (int): The id of a payment entity statement_period_id (int): Period to create the report for correlation_id (str): Optional request correlation ID Returns: list: list of invoice statement attachments """ retrying_statement_attachments = list() failed_attachments = models.StatementAttachment.get_failed_invoices_by_payment_entity( payment_entity_id, statement_period_id) if len(failed_attachments) != 0: for item in failed_attachments: params = { 'statement_attachment_status': StatementAttachmentStatus.RETRY, 'failure_reason': None } retrying_statement_attachment = update_statement_attachment( item.statement_attachment_id, **params) retrying_statement_attachments.append(retrying_statement_attachment) _trigger_invoices_generation( statement_period_id, retrying_statement_attachments, correlation_id) return retrying_statement_attachments