"""Payee Ledger Processor. Calculates and saves all ledger entries for a payee's adjustments in an accounting run """ from decimal import Decimal, getcontext, ROUND_HALF_EVEN from owsrequest import request import simplejson as json from commit_adjustment.config import APPLICATION_NAME from commit_adjustment.config import ENVIRONMENT from commit_adjustment.logging import app_logger from commit_adjustment.template import get_formatted_query class PayeeLedgerProcessor: """Payee ledger processor.""" CONTRACT_ADJUSTMENTS_SQL = """ SELECT CONTRACT_ID, CURRENCY_CODE, SUM(CURRENCY_AMOUNT) AS AMOUNT FROM $db.$schema.ACCOUNTING_RUN_ADJUSTMENTS WHERE ACCOUNTING_RUN_ID = $accounting_run_id AND PAYEE_ID = $payee_id GROUP BY CONTRACT_ID, CURRENCY_CODE """ NEGATIVE_ERROR = 'Negative adjustment found for payee id: {} run id: {}' def __init__(self, payee_id, event, sf_executor): """Init command.""" self._payee_id = payee_id self._payee_currency_code = '' self._event = event self._entries = [] self._payee_ledger_amount = Decimal('0.00') self._general_ledger_amount = Decimal('0.00000000000000000') # TODO: check if we need to declare a non-zero decimal to use in quantize() self._payee_ledger_precision = Decimal('0.01') self._sf_executor = sf_executor def validate_row(self, row): """Validate a row of adjustments.""" contract_adjustments_amount = row['AMOUNT'] if contract_adjustments_amount < 0: raise Exception(self.NEGATIVE_ERROR.format( self._payee_id, self._event.get('target_id'))) def process(self): """Process the ledger entries for a payee in an accounting run.""" getcontext().prec = 18 sql = self.get_formatted_query() for row in self._sf_executor.fetchall(sql, dict_cursor=True): self.validate_row(row) self.process_row(row) self._entries.append(self.format_payee_ledger_entry()) self._entries.append(self.format_general_ledger_entry()) self.flush_entries() def get_formatted_query(self): """Format the SQL.""" template_parameters = { 'accounting_run_id': str(int(self._event.get('target_id'))), 'payee_id': str(self._payee_id) } return get_formatted_query(self.CONTRACT_ADJUSTMENTS_SQL, template_parameters) def process_row(self, row): """Process a row returned from snowflake.""" self._payee_currency_code = row['CURRENCY_CODE'] rounded_amount = row['AMOUNT'].quantize( self._payee_ledger_precision, rounding=ROUND_HALF_EVEN) remainder = row['AMOUNT'] - rounded_amount self._payee_ledger_amount += rounded_amount self._general_ledger_amount -= row['AMOUNT'] self._entries.append( self.format_deposit_ledger_entry( row['CONTRACT_ID'], rounded_amount, remainder)) def format_deposit_ledger_entry(self, contract_id, currency_amount, remainder): """Format a deposit ledger entry.""" return { 'model_type': 'deposit', 'ledger_type': 'credit', 'abacus_event_id': self._event.get('abacus_event_id'), 'contract_id': contract_id, 'payee_id': self._payee_id, 'currency_code': self._payee_currency_code, 'rounded_amount': currency_amount, 'remaining_amount': remainder } def format_payee_ledger_entry(self): """Format a payee_ledger entry.""" return { 'model_type': 'payee', 'ledger_type': 'credit', 'abacus_event_id': self._event.get('abacus_event_id'), 'payee_id': self._payee_id, 'currency_code': self._payee_currency_code, 'currency_amount': self._payee_ledger_amount } def format_general_ledger_entry(self): """Format a general ledger entry.""" return { 'model_type': 'general', 'ledger_type': 'debit', 'abacus_event_id': self._event.get('abacus_event_id'), 'currency_code': self._payee_currency_code, 'currency_amount': self._general_ledger_amount } def flush_entries(self): """Send entries to ows-ledger.""" response = request.process( APPLICATION_NAME, ENVIRONMENT, 'POST', 'ows-ledger', '/ledger/bulk', data=json.dumps(self._entries)) if response.status_code != 201: ledger_response = response.json() app_logger.error('ledger service error: ', ledger_response) app_logger.error('ledger entries: ', self._entries) raise Exception('Call to ledger service failed') self._entries = []