"""Generate a distribution fee VAT invoice document.""" from os import path from typing import Callable from typing import Generator from lambdacommon.common_config import logger import pandas from pandas import DataFrame from config import FILE_OUTPUT_PATH from src.connectors import snowflake as snowflake_connector from src.connectors.ows_abacus_account import OwsAbacusAccount from src.connectors.ows_royalties import OwsRoyalties from src.documents.utils import create_report_file_pandas from src.documents.utils import sanitize_filename from src.utils.constants import NumberFormat from src.utils.constants import REVENUE_DETAIL_DECIMAL_COLUMNS # Mapping of column headers to database columns _COLUMN_MAPPING = { 'STATEMENT_PERIOD_NAME': 'STATEMENT PERIOD', 'ACCOUNT_ID': 'ACCOUNT ID', 'ACCOUNT_NAME': 'ACCOUNT NAME', 'CONTRACT_ID': 'CONTRACT ID', 'TRANSACTION_DATE': 'TRANSACTION DATE', 'COUNTRYNAME': 'SALE COUNTRY', 'CUSTOMER_NAME': 'STORE', 'SUBDISTRIBUTOR': 'SERVICE DETAIL', 'LABEL_IMPRINT': 'LABEL IMPRINT', 'ARTISTNAME': 'PRODUCT ARTIST', 'RELEASENAME': 'PRODUCT', 'RELEASE_VERSION': 'PRODUCT VERSION', 'PRODUCT_CODE': 'PRODUCT CODE', 'DISPLAY_UPC': 'DISPLAY UPC', 'MANUFACTURER_UPC': 'MANUFACTURER UPC', 'PERFORMERS': 'TRACK ARTIST', 'TRACKNAME': 'TRACK', 'TRACK_VERSION': 'TRACK VERSION', 'ISRC': 'ISRC', 'VIDEO_ID': 'YOUTUBE VIDEO ID', 'TRANSACTIONTYPEDESC': 'TRANSACTION TYPE', 'TRANSACTION_SUBTYPE': 'TRANSACTION SUBTYPE', 'UNIT_PRICE_SALE_CURRENCY': 'UNIT PRICE SALE CURRENCY', 'QUANTITY': 'QUANTITY', 'GROSS_REVENUE_AFTER_WITHHOLDING_TAX_SALE_CURRENCY': 'GROSS REVENUE SALE CURRENCY', 'SALE_CURRENCY_CODE': 'SALE CURRENCY', 'EXCHANGE_RATE': 'CURRENCY CONVERSION RATE', 'GROSS_REVENUE_AFTER_WITHHOLDING_TAX_PAYEE_CURRENCY': 'GROSS REVENUE ACCOUNT CURRENCY', 'ACCOUNT_PAYEE_CURRENCY': 'ACCOUNT CURRENCY', 'ROYALTY_RATE': 'ACCOUNT SHARE', 'NET_SHARE_PAYEE_CURRENCY': 'NET SHARE ACCOUNT CURRENCY', 'PHYS_PPD_USD': 'PHYSICAL PPD USD', 'MECHANICAL_DEDUCTION_AMOUNT_PAYEE_CURRENCY': 'MECHANICAL DEDUCTION (US only)', 'PUBLISHER_ADMIN_FEE_PAYEE_CURRENCY': 'MECHANICAL DEDUCTION ADMIN FEE (US only)', 'ABACUS_SALE_TYPE': 'ROYALTY TYPE', 'ORIGINAL_STATEMENT_PERIOD': 'ORIGINAL STATEMENT PERIOD', } # Columns to exclude when making a report for a subaccount _SUBACCOUNT_COLUMN_EXCLUDE = [ 'ACCOUNT_ID', 'ACCOUNT_NAME', 'CONTRACT_ID', 'UNIT_PRICE_SALE_CURRENCY', 'GROSS_REVENUE_AFTER_WITHHOLDING_TAX_SALE_CURRENCY', 'SALE_CURRENCY_CODE', 'EXCHANGE_RATE', 'GROSS_REVENUE_AFTER_WITHHOLDING_TAX_PAYEE_CURRENCY', 'ROYALTY_RATE', 'NET_SHARE_PAYEE_CURRENCY', 'PHYS_PPD_USD', 'MECHANICAL_DEDUCTION_AMOUNT_PAYEE_CURRENCY', 'PUBLISHER_ADMIN_FEE_PAYEE_CURRENCY', ] def _make_process_row(subaccount: dict | None) -> Callable[[DataFrame], None]: """Make a process_row function using the given mapping. Args: mapping (dict): Column mapping subaccount (dict): Subaccount used to modify the results Returns: function: Function to use for processing a row """ def process_dataframe(df: DataFrame) -> None: """Process a raw fact_sales row into a row for the CSV file. Args: df (DataFrame): Data frame to process. """ if subaccount and len(df) != 0: if subaccount['SUBACCOUNT_SPLIT_TYPE'] == 'Net': df['REVENUE'] = df['NET_SHARE_PAYEE_CURRENCY'] * subaccount['COMMISSIONOVERRIDE'] # noqa: E501 else: df['REVENUE'] = ( df['GROSS_REVENUE_AFTER_WITHHOLDING_TAX_PAYEE_CURRENCY'] * subaccount['COMMISSIONOVERRIDE'] ) # noqa: E501 df.drop(_SUBACCOUNT_COLUMN_EXCLUDE, axis=1, inplace=True) # Round for column in REVENUE_DETAIL_DECIMAL_COLUMNS: if df.__contains__(column): df[column] = pandas.to_numeric(df[column]).round(6) df.rename(columns=lambda c: _COLUMN_MAPPING[c] if c in _COLUMN_MAPPING else c, inplace=True) return process_dataframe def build_document( account_id: int, contract_id: int | None, statement_period_id: int, subaccount_id: int | None, file_type: str, number_format: NumberFormat, ) -> str: """Generate the revenue detail report document. Args: account_id (int): Account to generate the report for. contract_id (int): Contract to generate the report for. statement_period_id (int): Statement period ID. subaccount_id (int): Subaccount ID. file_type (str): File type to generate. number_format (str): Number format to use. data_function: Function to use to get the data. Returns: str: Local path to the generated file. """ subaccount = None account_name = '' if subaccount_id: subaccount = snowflake_connector.get_subaccount(subaccount_id) assert subaccount is not None account_name = sanitize_filename(subaccount['SUBACCOUNTNAME']) else: account = OwsAbacusAccount.get_account(account_id) account_name = sanitize_filename(account['account_name']) account_name = account_name if len(account_name) <= 50 else account_name[:50] statement_period_name = OwsRoyalties.get_statement_period(statement_period_id)[ 'statement_period_name' ] statement_period_name = sanitize_filename(statement_period_name) file_name = ( f'{account_name}_{statement_period_name}_revenue_details.csv' if subaccount else f'{account_name}_{contract_id}_{statement_period_name}_revenue_details.csv' ) destination_path = path.join(FILE_OUTPUT_PATH, file_name) dataframe_generator, total_rows = snowflake_connector.get_distribution_fact_sales( statement_period_id, account_id, contract_id, subaccount_id ) logger.info(f'Got {total_rows} rows from Snowflake') mapping = _COLUMN_MAPPING if subaccount: mapping = { key: value for key, value in mapping.items() if key not in _SUBACCOUNT_COLUMN_EXCLUDE } mapping['REVENUE'] = 'REVENUE' def wrapped_generator() -> Generator[DataFrame, None, None]: if total_rows == 0: logger.info('No data rows found, yielding empty DataFrame with headers') yield pandas.DataFrame(columns=list(mapping.keys())) else: yield from dataframe_generator return create_report_file_pandas( wrapped_generator(), total_rows, _make_process_row(subaccount), destination_path, file_type, number_format, )