"""Contract Lifecycle handlers.""" from datetime import date from abacus_common_logic.constants.error import ERROR_ENTITY_DOES_NOT_EXIST from abacus_common_logic.views.create_view import CreateView from abacus_common_logic.views.item_view import ItemView from flask import Blueprint, request from marshmallow import ValidationError from owsrequest import flask_request from owsresponse import response from owsresponse.adaptors.flask import flaskify from abacus_contract.constants.error import ( ERROR_ACCOUNT_NOT_FOUND_FOR_CONTRACT, ERROR_CODE_AUTHORIZATION, ERROR_CODE_NOT_FOUND, ERROR_INVALID_IDS, ) from abacus_contract.logic import ( contract as contract_logic, contract_lifecycle as logic, ) from abacus_contract.models.contract_lifecycle import ContractLifecycle from abacus_contract.schemas.contract_lifecycle import ( ContractLifecycleDetailSchema, ContractLifecyclePostSchema, ) from abacus_contract.utils.authorization import pdp_authorize_many_accounts from abacus_contract.utils.format_error import validation_error from abacus_contract.utils.request import ( get_optional_numeric_list_from_params, validate_pagination_params, ) contract_lifecycle_api = Blueprint('contract_lifecycle_api', __name__) class ContractLifecycleCreateView(CreateView): """Handles contract_lifecycle creation.""" post_schema = ContractLifecyclePostSchema() def post(self, **kwargs): """Create a contract_lifecycle.""" access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) return super().post(**kwargs) def create_handler(self, **params): """Create a contract_lifecycle.""" return logic.create_contract_lifecycle(**params) class ContractLifecycleItemView(ItemView): """View for finding a contract lifecycle by ID.""" model_class = ContractLifecycle object_detail_schema = ContractLifecycleDetailSchema() def get(self, object_id): """Find object by its id or return an error.""" access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) obj = self.model_class.get_by_id(object_id) if not obj or (obj.deleted_at is not None or obj.deleted_by is not None): return flaskify( response.create_not_found_response( message=ERROR_ENTITY_DOES_NOT_EXIST.format( object_type='ContractLifecycle', object_id=object_id ) ) ) body = self.object_detail_schema.dump(obj) return flaskify(response.Response(message=body, status=200)) contract_lifecycle_api.add_url_rule( '/contract-lifecycle//', methods=['GET'], view_func=ContractLifecycleItemView.as_view('contract_lifecycle'), ) contract_lifecycle_api.add_url_rule( '/contract//contract-lifecycle-schedule//contract-lifecycle/', methods=['POST'], view_func=ContractLifecycleCreateView.as_view('create_contract_lifecycle'), ) @contract_lifecycle_api.route( '/contract//contract-lifecycle/', methods=['GET'] ) def get_contract_lifecycle_by_contract_id(contract_id: int): """GET a contract lifecycle for a contract_id. Arg: contract_id(int): id of the contract """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: account_id = contract_logic.get_account_id_by_contract_id(contract_id) if not account_id: return flaskify( response.create_error_response( code=ERROR_CODE_NOT_FOUND, message=ERROR_ACCOUNT_NOT_FOUND_FOR_CONTRACT, status=404, ) ) authorized = pdp_authorize_many_accounts([account_id]) if not authorized: return flaskify( response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, ) ) return flaskify(logic.get_contract_lifecycle_by_contract_id(contract_id)) @contract_lifecycle_api.route( '/contract//contract-lifecycle/', methods=['PUT'] ) def update_contract_lifecycle_by_contract_id(contract_id: int): """PUT a contract lifecycle for a contract_id. Arg: contract_id(int): id of the contract body(object): { lifecycleTermStart: Date } """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) lifecycle = request.json if not lifecycle: return flaskify( response.create_error_response( code=400, message='Lifecycle data is required', status=400, ) ) try: result = logic.update_contract_lifecycle_by_contract_id(contract_id, lifecycle) return flaskify(result) except ValidationError as e: return flaskify( response.create_error_response( code=400, message=str(e), status=400, ) ) @contract_lifecycle_api.route( '/contract/contract-lifecycle/dataloader', methods=['POST'] ) def get_contract_lifecycle_by_dataloaded_contract_id(): """Get a contract lifecycle for dataloaded contract_ids. NOTE: This endpoint uses a POST method to allow for a larger list of query args. """ try: contract_ids = get_optional_numeric_list_from_params() except ValueError: return flaskify(validation_error(ERROR_INVALID_IDS.format(object='Contract'))) contracts = contract_logic.get_contracts_by_ids(contract_ids) account_ids = [contract.get('account_id') for contract in contracts] access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: authorized = pdp_authorize_many_accounts(account_ids) if not authorized: return flaskify( response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=403, ) ) return flaskify(logic.get_contract_lifecycles_by_contract_ids(contract_ids)) @contract_lifecycle_api.route('/contract-lifecycles/dataloader/', methods=['POST']) def get_contract_lifecycles_by_ids(): """Get contract lifecycles by list of ids. NOTE: This endpoint uses a POST method to allow for a larger list of query args. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) try: contract_ids = get_optional_numeric_list_from_params() except ValueError: return flaskify( validation_error(ERROR_INVALID_IDS.format(object='ContractLifecycle')) ) return flaskify(logic.get_contract_lifecycles_by_ids(contract_ids)) @contract_lifecycle_api.route( '/contract-lifecycle-schedule//contract-lifecycle/', methods=['GET'], ) def get_contract_lifecycle_by_schedule_id(contract_lifecycle_schedule_id: int): """Get a contract lifecycle by a contract_lifecycle_schedule_id. Arg: contract_lifecycle_schedule_id(int): id of the contract_lifecycle_schedule """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) return flaskify( logic.get_contract_lifecycle_by_schedule_id(contract_lifecycle_schedule_id) ) @contract_lifecycle_api.route('/contract-lifecycles/renewable/', methods=['GET']) def get_contract_lifecycles_to_be_renewed(): """Get 'active' contract_lifecycle records ready to be renewed.""" access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) limit, offset = validate_pagination_params(**request.args).values() items, total_count = ContractLifecycle.get_by_renewal_effective( limit, offset, date.today() ) message = { 'items': ContractLifecycleDetailSchema(many=True).dump(items), 'total_count': total_count, } return flaskify(response.Response(message=message, status=200)) @contract_lifecycle_api.route('/contract-lifecycles/terminable/', methods=['GET']) def get_contract_lifecycles_to_be_terminated(): """Get the contract_lifecycle records that are to be terminated.""" access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) limit, offset = validate_pagination_params(**request.args).values() items, total_count = ( ContractLifecycle.get_by_termination_effective_or_collection_end( limit, offset, date.today() ) ) message = { 'items': ContractLifecycleDetailSchema(many=True).dump(items), 'total_count': total_count, } return flaskify(response.Response(message=message, status=200)) @contract_lifecycle_api.route('/contract-lifecycles/terminate/', methods=['POST']) def terminate_contract_lifecycles(): """Terminate contract lifecycles. This endpoint will only "terminate" contract lifecycles that are in "to_be_terminated" or "in_collection_period" state. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) try: contract_lifecycle_ids = get_optional_numeric_list_from_params() except ValueError: return flaskify( validation_error(ERROR_INVALID_IDS.format(object='Contract Lifecycle')) ) return flaskify(logic.terminate_contract_lifecycles(contract_lifecycle_ids)) @contract_lifecycle_api.route('/contract-lifecycles/activable', methods=['GET']) def get_contract_lifecycles_to_be_activated(): """Get contract_lifecycle records that are ready to be activated.""" access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) limit, offset = validate_pagination_params(**request.args).values() items, total_count = ContractLifecycle.get_by_activable(limit, offset, date.today()) message = { 'items': ContractLifecycleDetailSchema(many=True).dump(items), 'total_count': total_count, } return flaskify(response.Response(message=message, status=200)) @contract_lifecycle_api.route('/contract-lifecycles/activate', methods=['POST']) def activate_contract_lifecycles(): """Activate contract lifecycles. This endpoint will only "activate" contract lifecycles that are in the "init" state. """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) try: contract_lifecycle_ids = get_optional_numeric_list_from_params() except ValueError: return flaskify( validation_error(ERROR_INVALID_IDS.format(object='Contract Lifecycle')) ) return flaskify(logic.activate_contract_lifecycles(contract_lifecycle_ids)) @contract_lifecycle_api.route('/contract-lifecycles/renew/', methods=['POST']) def renew_contract_lifecycles(): """Renew contract lifecycles. This endpoint will only "renew" contract lifecycles that are in "active" state and renewal_effective <= today """ access_rule_decision = flask_request.verify_rules_access_standalone(request) if not access_rule_decision: return flaskify( response.create_error_response( code=ERROR_CODE_AUTHORIZATION, message='Unauthorized', status=401, ) ) try: contract_lifecycle_ids = get_optional_numeric_list_from_params() except ValueError: return flaskify( validation_error(ERROR_INVALID_IDS.format(object='Contract Lifecycle')) ) return flaskify(logic.renew_contract_lifecycles(contract_lifecycle_ids))