"""Contract Flowthrough logic.""" from abacus_common_logic.connectors.database import db from abacus_common_logic.utils.dates import current_timestamp from abacus_common_logic.utils.users import get_flask_user_id from marshmallow import ValidationError from owsresponse import response from sqlalchemy.exc import IntegrityError, SQLAlchemyError from abacus_contract.constants.constants import CONTRACT_FLOWTHROUGH_STATUSES from abacus_contract.constants.error import ERROR_CONTRACT_FLOWTHROUGH_ALREADY_EXISTS from abacus_contract.models.contract import Contract from abacus_contract.models.contract_flowthrough import ContractFlowthrough from abacus_contract.schemas.contract_flowthrough import ContractFlowthroughDetailSchema from abacus_contract.utils.exception import ObjectAlreadyExistException from abacus_contract.utils.format_error import validation_error def get_contract_flowthrough_by_contract_id(contract_id: int) -> response.Response: """Get a contract_flowthrough by contract_id. Args: contract_id(int): id of the contract Returns: a contract_flowthrough record """ Contract.get_by_id_or_error(contract_id, 404) contract_flowthrough = ContractFlowthrough.get_by_contract_id(contract_id) return response.Response( message=ContractFlowthroughDetailSchema().dump(contract_flowthrough), status=200 ) def get_flowthrough_records_by_contract_ids(authorized_contract_ids: list) -> list: """Flat ContractFlowthroughDetailSchema records for the authorized contract ids (each carries contract_id).""" if not authorized_contract_ids: return [] flowthroughs = ContractFlowthrough.get_by_contract_ids(authorized_contract_ids) return ContractFlowthroughDetailSchema(many=True).dump(flowthroughs) def soft_delete_contract_flowthrough( contract_flowthrough: ContractFlowthrough, ) -> response.Response: """Soft delete a specified contract_flowthrough. Args: contract_flowthrough(ContractFlowthrough): a contract_flowthrough object Returns: response object with status """ try: contract_flowthrough._soft_delete() ContractFlowthrough.commit_changes() except SQLAlchemyError as e: raise e return response.Response(status=204) def update_contract_flowthrough( contract_flowthrough: ContractFlowthrough, **put_request_body: dict ) -> response.Response: """Update specified contract_flowthrough. Args: contract_flowthrough(ContractFlowthrough): a contract_flowthrough object put_request_body(dict): PUT request body - reference_flowthrough_calculation_id (Optional) - flowthrough_rate (Optional) - flowthrough_status (Optional) - has_automatic_shutoff (Optional) [Deprecated] - recoupment_cap (Optional) - calculation_comment (Optional) Returns: updated contract_flowthrough record """ try: existing_flowthrough_status = contract_flowthrough.flowthrough_status new_flowthrough_status = put_request_body.get('flowthrough_status') if ( new_flowthrough_status is not None and new_flowthrough_status != existing_flowthrough_status ): put_request_body.update( { 'previous_flowthrough_status': existing_flowthrough_status, 'status_last_modified_by': get_flask_user_id(), 'status_last_modified': current_timestamp(), } ) contract_flowthrough.update_attributes(**put_request_body) db.session.commit() except ValidationError as e: return validation_error(str(e)) except SQLAlchemyError as e: db.session.rollback() raise e return response.Response( message=ContractFlowthroughDetailSchema().dump(contract_flowthrough), status=200 ) def create_contract_flowthrough( contract_id: int, reference_flowthrough_calculation_id: int, flowthrough_rate: str, has_automatic_shutoff: bool = True, recoupment_cap: float | None = None, calculation_comment: str | None = None, ) -> response.Response: """Create a contract_flowthrough. Args: contract_id (int): id of the contract reference_flowthrough_calculation_id (int): id of the reference_flowthrough_calculation flowthrough_rate (str): percentage of revenue that would be paid has_automatic_shutoff (bool)(Optional): whether to pay flowthrough [Deprecated] recoupment_cap (str)(Optional): recoupment amount calculation_comment (str)(Optional): comment for the "Manual" calculation Returns: a contract_flowthrough record """ Contract.get_by_id_or_error(contract_id, 404) try: _check_whether_contract_flowthrough_exist(contract_id) contract_flowthrough = ContractFlowthrough.build( contract_id=contract_id, reference_flowthrough_calculation_id=reference_flowthrough_calculation_id, flowthrough_rate=flowthrough_rate, has_automatic_shutoff=has_automatic_shutoff, recoupment_cap=recoupment_cap, calculation_comment=calculation_comment, ) db.session.commit() except IntegrityError: db.session.rollback() return response.create_error_response( code='error', status=409, message=ERROR_CONTRACT_FLOWTHROUGH_ALREADY_EXISTS.format( contract_id=contract_id ), ) except SQLAlchemyError as e: db.session.rollback() raise e except ObjectAlreadyExistException as e: return response.create_error_response(code='error', status=409, message=str(e)) return response.Response( message=ContractFlowthroughDetailSchema().dump(contract_flowthrough), status=201 ) def _check_whether_contract_flowthrough_exist(contract_id: int): """Check whether flowthrough is already added to contract. Args: contract_id(int): id of the contract """ existing_contract_flowthrough = ContractFlowthrough.get_by_contract_id(contract_id) if existing_contract_flowthrough is not None: raise ObjectAlreadyExistException( ERROR_CONTRACT_FLOWTHROUGH_ALREADY_EXISTS.format(contract_id=contract_id) ) return False