"""Generate a Self Bill VAT invoice document.""" from datetime import datetime from os import path import tempfile import config from config import ALTAFONTE_SAP_IDS from config import Countries from config import ORCHARD_NORWAY_PAYMENT_ENTITY from config import ORCHARD_PAYMENT_ENTITIES from config import ORCHARD_SIGNING_ENTITY_MAPPING from src.connectors.ows_abacus_account import OwsAbacusAccount from src.connectors.ows_moneyhub import OwsMoneyhub from src.connectors.ows_payee import OwsPayee from src.connectors.ows_royalties import OwsRoyalties from src.documents.utils import extract_vat_info from src.documents.utils import extract_wht_info from src.documents.utils import format_address from src.documents.utils import generate_pdf_file from src.documents.utils import generate_pdf_file_playwright from src.documents.utils import process_transactions from src.documents.utils import render_template from src.utils import as_decimal from src.utils.constants import fake_tax_details from src.utils.constants import FeatureFlag from src.utils.constants import VatCategoryType from src.utils.error_handling import LambdaException from src.utils.features import is_feature_enabled def _get_tax_details(is_spanish_entity: bool, account: dict) -> dict: required_tax_fields = ['local_tax_id'] if is_spanish_entity else ['vat_number'] tax_details = ( fake_tax_details if config.ENVIRONMENT == config.DEV_ENVIRONMENT else OwsPayee.get_tax_details(account['account_payee_id'], required_tax_fields) ) tax_number = tax_details['local_tax_id'] if is_spanish_entity else tax_details['vat_number'] address: dict | None = tax_details.get('address') has_invalid_address = not address or all(not v for v in address.values()) business_name = tax_details.get('business_name') if not has_invalid_address and business_name: return {'business_name': business_name, 'tax_number': tax_number, 'address': address} bank_details = OwsPayee.get_bank_details(account['account_payee_id'], ['address']) if not business_name: payee_type = bank_details.get('type', '') individual_name = '' if 'contact' in bank_details: individual_name = '{} {}'.format( bank_details['contact'].get('first_name'), bank_details['contact'].get('last_name') ) business_name_map = { 'INDIVIDUAL': individual_name, 'COMPANY': bank_details['company'].get('name') if 'company' in bank_details else '', } business_name = business_name_map[payee_type] or '' tax_address = bank_details['address'] if has_invalid_address else tax_details.get('address') return {'business_name': business_name, 'tax_number': tax_number, 'address': tax_address} def build_document(statement_attachment: dict) -> str: """Create the document, saving the resulting file in the destination path. Args: statement_attachment (dict): Statement attachment details """ invoice_number = statement_attachment['invoice_number'] account = OwsAbacusAccount.get_account(statement_attachment['account_id']) account_id = statement_attachment['account_id'] contract = OwsRoyalties.get_contract(statement_attachment['contract_id']) payment_term = OwsAbacusAccount.get_account_payment_term(account_id) payment_entity_id = payment_term.get('payment_entity_id') assert payment_entity_id is not None payment_entity = OwsRoyalties.get_reference_payment_entity(payment_entity_id) # Orchard contracts get remapped signing entities for now if payment_entity_id in ORCHARD_PAYMENT_ENTITIES: mapped_signing_entity = ORCHARD_SIGNING_ENTITY_MAPPING[payment_entity_id] signing_entity = OwsRoyalties.get_reference_signing_entity(mapped_signing_entity) else: signing_entity = OwsMoneyhub.get_reference_signing_entity( statement_attachment['contract_id'] ) is_spanish_entity = payment_entity.get('country_of_tax_reporting') == Countries.SPAIN tax_details = _get_tax_details(is_spanish_entity, account) destination_path = path.join(config.FILE_OUTPUT_PATH, f'{invoice_number}.pdf') statement_period = OwsRoyalties.get_statement_period( statement_attachment['statement_period_id'] ) ledger_vat_data = OwsMoneyhub.get_ledger_vat_summary( statement_attachment['account_id'], statement_attachment['contract_id'], statement_attachment['statement_period_id'], [ VatCategoryType.GROSS_REVENUE, VatCategoryType.CLOSING_BALANCE, VatCategoryType.CUSTOM_PAYMENT, ], ) if not ledger_vat_data: raise LambdaException( 'There is no data inside ledger_vat_summary for {account} {contract}'.format( account=statement_attachment['account_id'], contract=statement_attachment['contract_id'], ) ) is_altafonte_networks = signing_entity['company_code'] in ALTAFONTE_SAP_IDS payee_currencies = [item['payee_currency_code'] for item in ledger_vat_data] if not all(currency == payee_currencies[0] for currency in payee_currencies): raise LambdaException('VAT entries contain more than one payee currency') vat_currencies = [item['vat_currency_code'] for item in ledger_vat_data] if not all(currency == vat_currencies[0] for currency in vat_currencies): raise LambdaException('VAT entries contain more than one VAT currency') se_data = { 'se_name': signing_entity['legal_name'], 'se_address': (signing_entity['address'] or '').replace('\n', ' '), 'se_vat_number': signing_entity['vat_number'], } customer_data = { 'customer_tax_number': tax_details['tax_number'], 'customer_name': tax_details['business_name'], 'customer_address': format_address(tax_details['address']), 'account_id': account['account_id'], 'account_name': account['account_name'], 'contract_name': contract['contract_name'], 'contract_id': contract['contract_id'], } invoice_data = { 'invoice_number': invoice_number, 'invoice_date': datetime.fromisoformat(statement_attachment['created_at']).date(), 'invoice_currency': ledger_vat_data[0]['payee_currency_code'], } transactions: list[dict] = [] process_transactions(ledger_vat_data, statement_period, transactions) subtotal_amount = total_amount = as_decimal(0) for tr in transactions: subtotal_amount += tr['amount_ex_tax'] total_amount += subtotal_amount vat_info = extract_vat_info(ledger_vat_data, False) wht_info = extract_wht_info(ledger_vat_data, False) has_wht = is_altafonte_networks and wht_info['has_wht'] total_amount += vat_info['payee_total_vat'] total_amount += wht_info['total_wht_amount_payee_currency'] if has_wht else 0 footer_info: str | None = 'The VAT shown is your output tax due to HMRC.' if signing_entity['company_code'] in ALTAFONTE_SAP_IDS: footer_info = None elif payment_entity_id == ORCHARD_NORWAY_PAYMENT_ENTITY: footer_info = 'The VAT shown is your output tax due to the Norwegian Tax Administration (Skatteetaten).' # noqa: E501 document_data = { **se_data, **customer_data, **invoice_data, **vat_info, **wht_info, 'footer_info': footer_info, 'transactions': transactions, 'subtotal_amount': subtotal_amount, 'total_amount': total_amount, 'has_vat': vat_info['has_vat'], 'has_wht': has_wht, } page_options = { 'margin-top': '15', 'margin-right': '20', 'margin-bottom': '60', 'margin-left': '20', 'footer-spacing': '5', 'javascript-delay': '1000', } if is_feature_enabled(FeatureFlag.NEW_PDF_LIBRARY, account['account_id']): generate_pdf_file_playwright( destination_path, 'self_billing_invoice/body.html', 'self_billing_invoice/footer_playwright.html', document_data, '2.75in', ) else: with tempfile.NamedTemporaryFile(suffix='.html', delete=False) as footer: page_options['--footer-html'] = footer.name footer.write( render_template('self_billing_invoice/footer.html', document_data).encode('utf-8') ) generate_pdf_file( destination_path, 'self_billing_invoice/body.html', document_data, page_options, ) return destination_path