"""Logic for payment details.""" from typing import Optional from abacus_common_data.country import Country from flask import current_app, request from owsrequest import flask_request from payee.config import cmp from payee.connectors.kafka import emit_payee_event from payee.connectors.secure_data.dynamodb_client import get_dynamodb_table from payee.connectors.secure_data.manager import SecureDocumentManager from payee.constants.constants import ( COUNTRY_CODE_FIELDS, ORCHARD_ROLES, PAYEE_EVENT_NAMES, ) from payee.constants.error import ERROR_ACCOUNT_ID_NOT_FOUND from payee.logic.exceptions import LogicError from payee.logic.graphql import get_abacus_account_details from payee.logic.secure_document import ( get_audit_fields, save_secure_document_details_version, ) from payee.models.tax_details import TaxDetailsDocument from payee.utils import validations from payee.utils.exception import AbacusAccountDetailsForReceiptFetchException def create_tax_details(sdm_config, **params): """Create tax-details for the specified payee.""" account_payee_id = params.pop('account_payee_id', None) secure_document = save_tax_details( account_payee_id, sdm_config, **get_audit_fields(), **params, ) emit_payee_event( account_payee_id=account_payee_id, action_name=PAYEE_EVENT_NAMES.VAT_COLLECTED, ) message = secure_document.build_response(account_payee_id, secure_document) return message def save_tax_details(account_payee_id, sdm_config: dict, **params): """Save secure tax details for payee.""" sdm = get_secure_document_manager(sdm_config) secure_document = TaxDetailsDocument(params) if secure_document.has_obscured_values: preprocess_data(secure_document, sdm, account_payee_id) preprocess_country_codes(secure_document, COUNTRY_CODE_FIELDS) sdm.save_item(secure_document.owner_type(), account_payee_id, secure_document) return secure_document def preprocess_data( tax_details: TaxDetailsDocument, sdm: SecureDocumentManager, account_payee_id ): """Apply changes to data prior to saving.""" current_item = sdm.get_item( tax_details.owner_type(), account_payee_id, TaxDetailsDocument ) for key, field in tax_details.fields.items(): if not field.has_obscured_value: continue original_value = None if current_item: original_value = current_item.get_value(key) if original_value: field.value = original_value def preprocess_country_codes( tax_details: TaxDetailsDocument, country_code_fields: list ): """ Apply changes to country code fields prior to saving. Make country code explicitly alpha-3 """ for field_name in country_code_fields: if tax_details.field_with_value_exists(field_name): tax_details.set_value( field_name, Country(tax_details.get_value(field_name)).alpha3 ) def get_tax_details(sdm_config, account_payee_id): """Retrieve payment-details for the specified payee.""" document_class = TaxDetailsDocument sdm = get_secure_document_manager(sdm_config) current_item, history = sdm.get_items( document_class.owner_type(), account_payee_id, document_class ) return current_item, history def get_current_tax_details(sdm_config, account_payee_id): """Retrieve payment-details for the specified payee.""" document_class = TaxDetailsDocument sdm = get_secure_document_manager(sdm_config) item = sdm.get_item(document_class.owner_type(), account_payee_id, document_class) if item is None: item = document_class() message = build_tax_details_response(account_payee_id, item) return message def get_tax_details_version(account_payee_id, revision=None): """Retrieve version of payment-details for the specified payee.""" sdm_config = current_app.config.get('SDM_CONFIG') document_class = TaxDetailsDocument sdm = get_secure_document_manager(sdm_config) item = sdm.get_item( document_class.owner_type(), account_payee_id, document_class, revision ) return item def build_tax_details_response(payee_id, current_item): """Format the response given the raw data.""" message = { 'account_payee_id': payee_id, 'vat_number': current_item.get_value('vat_number'), 'local_tax_id': current_item.get_value('local_tax_id'), 'business_name': current_item.get_value('business_name'), 'business_number': current_item.get_value('business_number'), 'country_of_tax_residency_code': current_item.get_value( 'country_of_tax_residency_code' ), 'address': { 'country_code': current_item.get_value('country_code'), 'address_1': current_item.get_value('address_1'), 'address_2': current_item.get_value('address_2') or '', 'city': current_item.get_value('city'), 'province': current_item.get_value('province'), 'zip': current_item.get_value('zip'), }, } return message def get_secure_document_manager(sdm_config): """Return the configured secure document manager.""" table = get_dynamodb_table(sdm_config) return SecureDocumentManager(table, cmp) def get_tax_receipt_details(account_payee_id: int) -> dict: """Get all needed details for PDF tax receipt.""" payee_tax_details = get_current_tax_details( current_app.config.get('SDM_CONFIG'), account_payee_id ) headers = flask_request.get_ows_headers() headers[ORCHARD_ROLES] = str(request.headers.get(ORCHARD_ROLES)) account_id = validations.get_account_id_by_account_payee_id( account_payee_id=account_payee_id, headers=headers ) if not account_id: raise AbacusAccountDetailsForReceiptFetchException( ERROR_ACCOUNT_ID_NOT_FOUND.format(account_payee_id=account_payee_id) ) account_details = get_abacus_account_details(account_id) tax_receipt_details = {**payee_tax_details, **account_details} return tax_receipt_details def has_tax_details(account_payee_id) -> Optional[TaxDetailsDocument]: """Check whether tax details exists for a given payee.""" sdm = get_secure_document_manager(current_app.config.get('SDM_CONFIG')) payee_tax_details = sdm.get_item( TaxDetailsDocument.owner_type(), account_payee_id, TaxDetailsDocument ) return payee_tax_details def dataload_tax_detail(payee_id, revision): """Dataload versioned tax details document.""" document_item = get_tax_details_version(payee_id, revision) if not document_item: return None item_response = TaxDetailsDocument.build_response(payee_id, document_item) return item_response def resync_payment_readiness(account_payee_id): """Produce resync payment readiness event.""" emit_payee_event( account_payee_id=account_payee_id, action_name=PAYEE_EVENT_NAMES.RESYNC_PAYMENT_READINESS, ) return {'account_payee_id': account_payee_id} def delete_record(account_payee_id: int) -> tuple[str, TaxDetailsDocument]: """ Soft delete tax details for payee. Returns revision of the deleted record and the document itself. """ # get current tax details from DynamoDB previous_details_document = has_tax_details(account_payee_id) if previous_details_document is None: raise LogicError('Document was not found') # create new DynamoDB record to get revision_id, # scanning all the records to find the last one could be costly revision_id = save_secure_document_details_version( account_payee_id, TaxDetailsDocument, **previous_details_document.values, ) # delete current version (#0) in DynamoDB sdm_config = current_app.config.get('SDM_CONFIG') sdm = get_secure_document_manager(sdm_config) sdm.delete_current_item( previous_details_document.owner_type(), account_payee_id, previous_details_document.__class__, ) return revision_id, previous_details_document