"""Logic for Ledger Contract Advance Applied.""" from decimal import ROUND_UP, Decimal from typing import Optional from abacus_common_logic.connectors.database import db from owsresponse import response from ledger.constants.constants import ( LEDGER_CONTRACT_ADVANCE_SUCCESS_MSG, SUBLEDGER_EVENT_MAPPING, ) from ledger.constants.error import ( ERROR_EXCHANGE_RANGE_NOT_PROVIDED, ERROR_LEDGER_ACCOUNT_ENTRY_DOES_NOT_EXIST, ERROR_LEDGER_CONTRACT_ADVANCE_APPLIED_DOES_NOT_EXIST, ERROR_WORKSHEET_WITH_AMOUNTS_IS_REQUIRED, ) from ledger.logic.bulk_ledgers import create_ledger_entry from ledger.models.ledger_contract_advance_applied import LedgerContractAdvanceApplied from ledger.schemas.ledger_contract_advance_applied import ( LedgerContractAdvanceAppliedDetailSchema, ) from ledger.utils.currency import validate_currency from ledger.utils.format_error import validation_error def create_ledger_contract_advance_applied( abacus_event_id: int, account_id: int, contract_id: int, contract_advance_id: int, statement_period_id: int, advance_amount: Decimal, advance_currency_code: str, advance_amount_payee_currency: Decimal, advance_payee_currency_code: str, vat_amount: Optional[Decimal] = None, withholding_tax_amount: Optional[Decimal] = None, amount_after_withholding_and_vat: Optional[Decimal] = None, vat_amount_payee_currency: Optional[Decimal] = None, withholding_tax_amount_payee_currency: Optional[Decimal] = None, amount_after_withholding_and_vat_payee_currency: Optional[Decimal] = None, us_source_income_rate: Optional[Decimal] = None, worksheet_payment_contract_advance_id: Optional[int] = None, exchange_rate: Optional[Decimal] = None, ) -> LedgerContractAdvanceApplied: """Create a ledger_contract_advance_applied. Args: abacus_event_id(int): Id of the related abacus_event account_id(int): Id of the related account contract_id(int): Id of the related contract contract_advance_id(int): Id of the related contract_advance statement_period_id(int): Id of the related statement_period advance_amount(Decimal): currency amount of the advance advance_currency_code(str): Alpha-3 iso currency code. advance_amount_payee_currency(Decimal): the amount of the advance in the account payment currency advance_payee_currency_code(str): Alpha-3 iso currency code. account's payment currency vat_amount(Decimal): VAT amount withholding_tax_amount(Decimal): withholding tax amount amount_after_withholding_and_vat(Decimal): amount after withholding and VAT vat_amount_payee_currency(Decimal): VAT amount in payee currency withholding_tax_amount_payee_currency(Decimal): withholding tax amount in payee currency amount_after_withholding_and_vat_payee_currency(Decimal): amount after withholding and VAT in payee currency us_source_income_rate(Decimal): us_source_income_rate worksheet_payment_contract_advance_id(int): worksheet ID exchange_rate(Decimal): exchange rate Returns: A ledger_contract_advance_applied record. """ record = LedgerContractAdvanceApplied.create( abacus_event_id=abacus_event_id, account_id=account_id, contract_id=contract_id, contract_advance_id=contract_advance_id, statement_period_id=statement_period_id, advance_amount=advance_amount, advance_currency_code=advance_currency_code, advance_amount_payee_currency=advance_amount_payee_currency, advance_payee_currency_code=advance_payee_currency_code, vat_amount=vat_amount, withholding_tax_amount=withholding_tax_amount, amount_after_withholding_and_vat=amount_after_withholding_and_vat, vat_amount_payee_currency=vat_amount_payee_currency, withholding_tax_amount_payee_currency=withholding_tax_amount_payee_currency, amount_after_withholding_and_vat_payee_currency=amount_after_withholding_and_vat_payee_currency, us_source_income_rate=us_source_income_rate, worksheet_payment_contract_advance_id=worksheet_payment_contract_advance_id, exchange_rate=exchange_rate, ) return record def get_advance_amount_payee_currency(advance_amount: Decimal) -> Decimal: """Get rounded advance amount in the account's payee currency. Args: advance_amount(Decimal): currency amount of the advance """ float_precision = Decimal('0.01') rounding = ROUND_UP return advance_amount.quantize(float_precision, rounding) def _validate_ledger_entry( account_payment_currency_code: str, advance_currency_code: str, exchange_rate: float = None, worksheet_payment_contract_advance_id: Optional[int] = None, advance_amount_payee_currency: Optional[Decimal] = None, vat_amount_payee_currency: Optional[Decimal] = None, withholding_tax_amount_payee_currency: Optional[Decimal] = None, amount_after_withholding_and_vat_payee_currency: Optional[Decimal] = None, ) -> bool: """Validate ledger entry.""" validate_currency(advance_currency_code) validate_currency(account_payment_currency_code) if ( advance_currency_code != account_payment_currency_code ) and exchange_rate is None: raise Exception(ERROR_EXCHANGE_RANGE_NOT_PROVIDED) if ( worksheet_payment_contract_advance_id is None or advance_amount_payee_currency is None or vat_amount_payee_currency is None or withholding_tax_amount_payee_currency is None or amount_after_withholding_and_vat_payee_currency is None ): raise Exception(ERROR_WORKSHEET_WITH_AMOUNTS_IS_REQUIRED) return True def create_ledger_entries( abacus_event_id: int, account_id: int, contract_id: int, advance_amount_payee_currency: Decimal, advance_amount_remainder: Decimal, advance_currency_code: str, account_payment_currency_code: str, ): """Create ledger entries for contract advance. This function creates records for ledger_account and ledger_deposit. Args: abacus_event_id(int): Id of the related abacus_event account_id(int): Id of the related account contract_id(int): Id of the related contract advance_amount_payee_currency(Decimal): the amount of the advance in the account payment currency advance_amount_remainder(Decimal): remainder of the currency_amount after rounding advance_currency_code(str): Alpha-3 iso currency code. account_payment_currency_code(str): Alpha-3 iso currency code. account's payment currency """ ledger_account_entry = { 'abacus_event_id': abacus_event_id, 'account_id': account_id, 'contract_id': contract_id, 'currency_amount': (-1 * advance_amount_payee_currency), 'currency_code': account_payment_currency_code, 'model_type': 'account', } create_ledger_entry(ledger_account_entry) if advance_currency_code != account_payment_currency_code: ledger_deposit_entry = { 'abacus_event_id': abacus_event_id, 'account_id': account_id, 'contract_id': contract_id, 'currency_code': account_payment_currency_code, 'model_type': 'deposit', 'rounded_amount': advance_amount_payee_currency, 'remaining_amount': advance_amount_remainder, } create_ledger_entry(ledger_deposit_entry) db.session.commit() def create_contract_advance_ledger_entries( contract_advance_id: int, abacus_event_id: int, account_id: int, contract_id: int, statement_period_id: int, advance_amount: Decimal, advance_currency_code: str, account_payment_currency_code: str, exchange_rate: Optional[Decimal] = None, vat_amount: Optional[Decimal] = None, withholding_tax_amount: Optional[Decimal] = None, amount_after_withholding_and_vat: Optional[Decimal] = None, us_source_income_rate: Optional[Decimal] = None, worksheet_payment_contract_advance_id: Optional[int] = None, advance_amount_payee_currency: Optional[Decimal] = None, vat_amount_payee_currency: Optional[Decimal] = None, withholding_tax_amount_payee_currency: Optional[Decimal] = None, amount_after_withholding_and_vat_payee_currency: Optional[Decimal] = None, ) -> response.Response: """Create the contract_advance ledger entries. This function creates records for ledger_contract_advance_applied, ledger_account, and ledger_deposit. Args: contract_advance_id(int): Id of the related contract_advance abacus_event_id(int): Id of the related abacus_event account_id(int): Id of the related account contract_id(int): Id of the related contract statement_period_id(int): Id of the related statement_period advance_amount(float): currency amount of the advance advance_currency_code(str): Alpha-3 iso currency code. account_payment_currency_code(str): Alpha-3 iso currency code. account's payment currency exchange_rate(float): the rate to convert advance_amount from advance_currency_code to account_payment_currency_code vat_amount(Decimal): VAT amount withholding_tax_amount(Decimal): withholding tax amount amount_after_withholding_and_vat(Decimal): amount after withholding and VAT us_source_income_rate(Decimal): us_source_income_rate worksheet_payment_contract_advance_id(int): worksheet ID advance_amount_payee_currency(Decimal): Advance amount in payee currency vat_amount_payee_currency(Decimal): VAT amount in payee currency withholding_tax_amount_payee_currency(Decimal): withholding tax amount in payee currency amount_after_withholding_and_vat_payee_currency(Decimal): amount after withholding and VAT in payee currency """ try: _validate_ledger_entry( account_payment_currency_code, advance_currency_code, exchange_rate, worksheet_payment_contract_advance_id, advance_amount_payee_currency, vat_amount_payee_currency, withholding_tax_amount_payee_currency, amount_after_withholding_and_vat_payee_currency, ) create_ledger_contract_advance_applied( abacus_event_id, account_id, contract_id, contract_advance_id, statement_period_id, (-1 * advance_amount), advance_currency_code, (-1 * advance_amount_payee_currency), account_payment_currency_code, (-1 * vat_amount) if vat_amount else vat_amount, (-1 * withholding_tax_amount) if withholding_tax_amount else withholding_tax_amount, (-1 * amount_after_withholding_and_vat) if amount_after_withholding_and_vat else amount_after_withholding_and_vat, (-1 * vat_amount_payee_currency) if vat_amount_payee_currency else vat_amount_payee_currency, (-1 * withholding_tax_amount_payee_currency) if withholding_tax_amount_payee_currency else withholding_tax_amount_payee_currency, (-1 * amount_after_withholding_and_vat_payee_currency) if amount_after_withholding_and_vat_payee_currency else amount_after_withholding_and_vat_payee_currency, us_source_income_rate, worksheet_payment_contract_advance_id, exchange_rate, ) except Exception as e: db.session.rollback() return validation_error(str(e)) return response.Response(message=LEDGER_CONTRACT_ADVANCE_SUCCESS_MSG, status=201) def get_by_contract_advance_id(contract_advance_id: int): """Get a LedgerContractAdvanceApplied record by contract_advance_id. Args: contract_advance_id (int): ID of the parent contract_advance """ record = LedgerContractAdvanceApplied.get_by_contract_advance_id( contract_advance_id ) if not record: return response.create_error_response( code='error', status=404, message=ERROR_LEDGER_CONTRACT_ADVANCE_APPLIED_DOES_NOT_EXIST, ) message = LedgerContractAdvanceAppliedDetailSchema().dump(record) return response.Response(message=message, status=200) def get_by_worksheet_payment_contract_advance_id( worksheet_payment_contract_advance_id: int, entry_type: str ) -> response.Response: """Get a LedgerContractAdvanceApplied record by worksheet_payment_contract_advance_id. Args: worksheet_payment_contract_advance_id (int): ID of the parent worksheet entry_type (str): Type of the ledger entry """ if entry_type not in SUBLEDGER_EVENT_MAPPING: return response.create_error_response( code='error', status=404, message=ERROR_LEDGER_ACCOUNT_ENTRY_DOES_NOT_EXIST ) record = LedgerContractAdvanceApplied.get_by_worksheet_payment_contract_advance_id( worksheet_payment_contract_advance_id, SUBLEDGER_EVENT_MAPPING[entry_type] ) if not record: return response.create_error_response( code='error', status=404, message=ERROR_LEDGER_CONTRACT_ADVANCE_APPLIED_DOES_NOT_EXIST, ) message = LedgerContractAdvanceAppliedDetailSchema().dump(record) return response.Response(message=message, status=200)