"""Common logic for post tax details processor.""" import logging import typing from config import app_logger as logger from src.connectors import ows_abacus_account, ows_payee from src.connectors.exceptions import ( OwsAbacusAccountException, OwsPayeeException, ) from src.models import AccountTaxInfo, BasePostTaxDetailsInput from src.processors.base import Processor from src.processors.exceptions import ProcessingError from src.utils import chunks class BasePostTaxDetailsProcessor(Processor): """Processor to post payee`s tax details.""" DEFAULT_BATCH_SIZE = 100 _input_type: typing.Type[BasePostTaxDetailsInput] _data: dict[int, BasePostTaxDetailsInput] _account_id_account_payee_id: dict[int, int] _account_id_account_tax_info: dict[int, AccountTaxInfo] _account_payee_ids_with_tax_details: list[int] def process(self) -> None: """Run main logic.""" self._load_data() self._load_account_payee_ids() self._load_account_tax_info() self._load_account_payee_tax_details() self._validate_data() self._post_data() def _load_data(self) -> None: """Load data from file.""" logger.info('Loading data from csv file') try: raw_data: list[typing.Mapping[str, typing.Any]] = list(self.csv_dict_reader) except Exception as e: raise ProcessingError(f'Unable to read the file: {e}') try: self._data = { item.vendor_id: item for item in self._input_type.list_validate(raw_data) } except Exception as e: raise ProcessingError(f'Invalid file content: {e}') if not self._data: raise ProcessingError('No items in file') logger.info(f'Loaded {len(self._data)} items.') def _load_account_payee_ids(self) -> None: """Load account payee IDs by account IDs.""" self._account_id_account_payee_id = {} for account_ids in chunks(list(self._data.keys()), self.DEFAULT_BATCH_SIZE): self._account_id_account_payee_id.update( ows_abacus_account.get_payees_by_accounts(account_ids) ) def _load_account_tax_info(self) -> None: """Load account tax info by account IDs.""" self._account_id_account_tax_info = {} for account_ids in chunks(list(self._data.keys()), self.DEFAULT_BATCH_SIZE): response = ows_abacus_account.get_account_tax_info_bulk( account_ids, limit=self.DEFAULT_BATCH_SIZE ) for tax_info in response.items: self._account_id_account_tax_info[tax_info.account_id] = tax_info def _load_account_payee_tax_details(self) -> None: """Load account payee tax details.""" self._account_payee_ids_with_tax_details = [] for account_payee_ids in chunks( list(self._account_id_account_payee_id.values()), self.DEFAULT_BATCH_SIZE ): self._account_payee_ids_with_tax_details.extend( [ item.account_payee_id for item in ows_payee.get_tax_details_bulk(account_payee_ids) ] ) def _validate_data(self) -> None: """Validate and enrich data.""" validated_data = {} for account_id, item in self._data.items(): account_payee_id = self._account_id_account_payee_id.get(account_id) if not account_payee_id: self._add_log( 'Payee does not exist, skipped', group_condition='account_payee_not_exist', account_id=account_id, ) continue account_tax_info = self._account_id_account_tax_info.get(account_id) if not account_tax_info: self._add_log( 'Tax info does not exist, skipped', group_condition='tax_info_not_exist', account_id=account_id, account_payee_id=account_payee_id, ) continue tax_details_exist = ( account_payee_id in self._account_payee_ids_with_tax_details ) if tax_details_exist: if item.override: msg_action = 'overridden' log_level = logging.INFO else: msg_action = 'skipped' log_level = logging.WARNING self._add_log( f'Tax details already exist, {msg_action}', log_level, group_condition=f'tax_details_exist_{msg_action}', account_id=account_id, account_payee_id=account_payee_id, ) if not item.override: continue validated_data[account_id] = item.model_copy( update={ 'account_payee_id': account_payee_id, 'account_tax_info_id': account_tax_info.account_tax_info_id, } ) logger.info( f'Prepared {len(validated_data)} items to post of {len(self._data)}' ' loaded, check the report for details.' ) self._data = validated_data def _post_data(self) -> None: """Post data.""" posted_items_qty = 0 for item in self._data.values(): logger.debug(f'Posting data for: {item.vendor_id}') try: ows_payee.save_tax_details( typing.cast(int, item.account_payee_id), item.get_account_payee_tax_details(), ) except OwsPayeeException as e: self._add_log( str(e), group_condition=e.error_code, account_id=item.vendor_id, **(e.additional_data or {}), ) continue try: ows_abacus_account.update_account_tax_info( typing.cast(int, item.account_tax_info_id), item.get_account_tax_info(), ) except OwsAbacusAccountException as e: self._add_log( f'Unable to save account tax info for {item.vendor_id}/{item.account_tax_info_id}: {e}' ) continue posted_items_qty += 1 logger.info( f'Posted {posted_items_qty} items of {len(self._data)}' ' prepared, check the report on details.' )