"""Accounting run logic.""" from abacus_common_logic.utils.dates import current_timestamp from owsresponse import response from royalties import models from royalties.connectors.s3 import create_presigned_url, get_s3_client from royalties.constants import error from royalties.constants.constants import ( ACCOUNTING_PERIOD_STATUSES as PERIOD_STATUSES, ACCOUNTING_RUN_STATUSES as STATUSES, ) from royalties.schemas import AccountingRunSchema from royalties.utils.aws import parse_s3_url from royalties.utils.format_error import validation_error detail_schema = AccountingRunSchema() allowed_statuses = { STATUSES.NO_ACTION_TAKEN: ( STATUSES.WAITING_TO_RUN, STATUSES.SKIPPED, STATUSES.ERROR, ), STATUSES.WAITING_TO_RUN: (STATUSES.RUNNING, STATUSES.ERROR), STATUSES.RUNNING: (STATUSES.COMPLETE, STATUSES.ERROR), STATUSES.SKIPPED: (STATUSES.NO_ACTION_TAKEN,), STATUSES.COMPLETE: (STATUSES.COMMITTING, STATUSES.INVALID), STATUSES.COMMITTING: (STATUSES.COMMITTED, STATUSES.ERROR), STATUSES.ERROR: ( STATUSES.COMPLETE, STATUSES.COMMITTED, STATUSES.COMMITTING, STATUSES.RUNNING, ), } def get_run_summary_items_csv(accounting_run_id): """Generate presigned S3 url to download summary csv.""" accounting_run = models.AccountingRun.get_by_id(accounting_run_id) summary_url = accounting_run.summary_export_url if not summary_url: return response.create_error_response( code='error', status=400, message=error.ERROR_NO_SUMMARY_EXPORT_URL.format( accounting_run_id=accounting_run_id ), ) bucket, key = parse_s3_url(accounting_run.summary_export_url) client = get_s3_client() return create_presigned_url(client, bucket, key) def _invalidate_accounting_run(accounting_run): """Clone the invalid accounting run.""" accounting_period = accounting_run.accounting_period accounting_period.accounting_runs.append( models.AccountingRun.build(run_controller=accounting_run.run_controller) ) change_handlers = {STATUSES.INVALID: _invalidate_accounting_run} def update_accounting_run(accounting_run, run_status, **attrs): """Update accounting run status.""" status = accounting_run.accounting_period.accounting_period_status if status == PERIOD_STATUSES.CLOSED: return validation_error(flat_error_message=error.ERROR_CANNOT_UPDATE) current_status = accounting_run.run_status allowed_statuses_values = allowed_statuses.get(current_status, ()) status_updated = run_status != current_status if status_updated and run_status not in allowed_statuses_values: return response.create_error_response( code='error', message=error.ERROR_INVALID_STATUS_CHANGE.format( object_type='Accounting run', current_status=current_status, new_status=run_status, ), status=400, ) change_handler = change_handlers.get(run_status) if status_updated and change_handler: change_handler(accounting_run) if run_status == STATUSES.WAITING_TO_RUN: attrs.update({'start_date': current_timestamp()}) if run_status in [STATUSES.COMPLETE, STATUSES.ERROR]: attrs.update({'end_date': current_timestamp()}) accounting_run.update_attributes(run_status=run_status, **attrs) models.AccountingRun.commit_changes() return response.Response( message=detail_schema.dump( accounting_run.accounting_period.accounting_runs, many=True ), status=200, ) def get_contract_ids_by_accounting_run_id(accounting_run_id): """Get contract IDs associated to an accounting run.""" accounting_run = models.AccountingRun.get_by_id_or_error(accounting_run_id) results = models.RunControllerContract.get_by_run_controller_id( accounting_run.run_controller_id ) return [item.contract_id for item in results]