""" Handlers ======== Requests are redirected to handlers, which are responsible for getting information from the URL and passing it down to the logic layer. The way each layer talks to each other is through Response objects which defines the type status of the data and the data itself. Please note: The Orchard uses the term handlers over views as convention for clarity See: ows_accounting.response for more details. """ import datetime import flask from flask import jsonify from flask import request from owsfeatures import features as python_owsfeatures from owsrequest import flask_request from ows_accounting import config from ows_accounting import response from ows_accounting.api import app from ows_accounting.constants import error from ows_accounting.constants import feature from ows_accounting.constants import header from ows_accounting.logic import accounting_intervals from ows_accounting.logic import attachment from ows_accounting.logic import budget_caps from ows_accounting.logic import currency from ows_accounting.logic import hello from ows_accounting.logic import passthrough from ows_accounting.logic import payment_holds from ows_accounting.logic import payments from ows_accounting.logic import period from ows_accounting.logic import physical_reserves from ows_accounting.logic import reports from ows_accounting.logic import revenue from ows_accounting.logic import transaction_types from ows_accounting.logic import vendor_currency from ows_accounting.utils import request_flask from ows_accounting.validation import access from ows_accounting.validation import json_schema from ows_accounting.validation import json_validators from ows_accounting.validation import validators @app.route('/') def hello_world(): """Hello World with an optional GET param "name". """ name = request.args.get('name', '') return response.flaskify(hello.say_hello(name)) @app.route('/reports', methods=['GET']) @access.verify_grass_header() @access.verify_account_params @json_schema.validate_request( args_schema=json_validators.REPORTS_GET_VALIDATOR) def get_reports(): """Get Reports. Retrieve reports for statement period and account specified by query params. Request Params: periods (string): Comma-delimited set of period ids corresponding to Statement Period. account_id (string, optional): Available as optional param for external clients. account_type (string, optional): Available as optional param for external clients. page_offset (int, optional): corresponds to page number, starting with 0. page_limit (int, optional): max items per page. Returns: Response: Flask response. """ grass_account = request_flask.get_account_from_grass_headers(request) pagination = request_flask.get_pagination(request) validation = validators.validate_periods(request) if not validation: return response.flaskify(validation) return response.flaskify( reports.get_reports( request.args.get('account_id', grass_account.id), request.args.get('account_type', grass_account.type), sorted(request.args.get('periods', '').split(',')), pagination.offset, pagination.limit)) @app.route('/report', methods=['GET']) @access.verify_grass_header() @access.verify_account_params @json_schema.validate_request(args_schema=json_validators.REPORT_GET_VALIDATOR) def get_report(): """Get Report. Retrieve report specified by query params. Request Params: periods (string): Comma-delimited set of period ids corresponding to Statement Period. transaction_types (string): Comma-delimited set of transaction types for requested report. file_type (string): File type of requested report. number_format (string): Number format of requested report. account_id (string, optional): Available as optional param for external clients. account_type (string, optional): Available as optional param for external clients. Returns: Redirect: redirect to url. """ grass_account = request_flask.get_account_from_grass_headers(request) validation = validators.validate_periods(request) if not validation: return response.flaskify(validation) response_object = reports.get_report( request.args.get('account_id', grass_account.id), request.args.get('account_type', grass_account.type), request.args.get('periods', ''), request.args.get('transaction_types', ''), request.args.get('file_type', ''), request.args.get('number_format', '')) if response_object: headers = { 'Access-Control-Allow-Origin': response_object.message, 'Location': response_object.message } return flask.Response( response=response_object.message, status=302, headers=headers) return response.flaskify(response_object) @app.route('/report-link', methods=['GET']) @access.verify_grass_header() @access.verify_account_params @json_schema.validate_request(args_schema=json_validators.REPORT_GET_VALIDATOR) def get_report_link(): """Get Report Link. Retrieve report specified by query params. Request Params: periods (string): Comma-delimited set of period ids corresponding to Statement Period. transaction_types (string): Comma-delimited set of transaction types for requested report. file_type (string): File type of requested report. number_format (string): Number format of requested report. account_id (string, optional): Available as optional param for external clients. account_type (string, optional): Available as optional param for external clients. Returns: Response: Flask response. """ grass_account = request_flask.get_account_from_grass_headers(request) validation = validators.validate_periods(request) if not validation: return response.flaskify(validation) response_object = reports.get_report( request.args.get('account_id', grass_account.id), request.args.get('account_type', grass_account.type), request.args.get('periods', ''), request.args.get('transaction_types', ''), request.args.get('file_type', ''), request.args.get('number_format', '')) if response_object: headers = { 'Access-Control-Allow-Origin': response_object.message, 'Location': response_object.message } return flask.Response( response=response_object.message, status=200, headers=headers) return response.flaskify(response_object) @app.route('/report', methods=['POST']) @access.verify_grass_header(True) @json_schema.validate_request( body_schema=json_validators.REPORT_POST_VALIDATOR) def post_report(): """Post Report Request to create a new report for the statement period and additional provided params. Body Params: periods (string): Comma-delimited set of period ids corresponding to Statement Period. transaction_types (string): Comma-delimited set of transaction types for requested report. Example: 'DA, DT', or 'all' for all transaction types. file_type (string): File type of requested report. Example: 'xls, txt'. number_format (string): Number format of requested report. request_context (map): A JSON Hash containing available attributes from Workstation that describe request context (e.g., user). Returns: Response: Flask response. """ grass_account = request_flask.get_account_from_grass_headers(request) correlation_id = request.headers.get(header.CORRELATION_ID) validation = validators.validate_periods(request) if not validation: return response.flaskify(validation) data = request.get_json() periods = data.get('periods').split(',') transaction_types = data.get('transaction_types').split(',') file_format = data.get('file_type') number_format = data.get('number_format') request_context = data.get('request_context') email = request_context.get('contact_email') vend_contact_id = request_context.get('contact_id') first_name = request_context.get('first_name') last_name = request_context.get('last_name') requested_datetime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") return response.flaskify(reports.add_report( correlation_id, grass_account.id, grass_account.type, periods, transaction_types, file_format, number_format, email, vend_contact_id, first_name, last_name, requested_datetime, feature.DEFAULT_REPORT_VERSION)) @app.route('/transaction_types', methods=['GET']) @python_owsfeatures.load_features @access.verify_grass_header(True) @json_schema.validate_request( args_schema=json_validators.TRANSACTION_TYPES_GET_VALIDATOR) def get_transaction_types(): """Get Transaction Types. Retrieve set of relevant transaction types for the account and requested statement period. Request Params: periods (string): Comma-delimited set of period ids corresponding to Statement Period. Returns: Response: Flask response. """ grass_account = request_flask.get_account_from_grass_headers(request) validation = validators.validate_periods(request) if not validation: return response.flaskify(validation) periods = request.args.get('periods', '').split(',') return response.flaskify( transaction_types.get_transaction_types( grass_account.id, grass_account.type, periods)) @app.route('/revenue', methods=['GET']) @python_owsfeatures.load_features @access.verify_grass_header(True) @json_schema.validate_request( args_schema=json_validators.REVENUE_GET_VALIDATOR) def get_revenue(): """Get Account Revenue. Request Params: recent (int): Number of recent accounting intervals for revenue calculation Returns: Response: Flask response """ grass_account = request_flask.get_account_from_grass_headers(request) recent_intervals = int(request.args.get('recent')) return response.flaskify( revenue.get_revenue( grass_account.type, grass_account.id, recent_intervals)) @app.route('/payments', methods=['GET']) @python_owsfeatures.load_features @access.verify_grass_header(True) @json_schema.validate_request( args_schema=json_validators.PAYMENTS_GET_VALIDATOR) def get_payments(): """Get Account Payments. Request Params: recent (int): Number of recent accounting intervals for payments calculation Returns: Response: Flask response """ grass_account = request_flask.get_account_from_grass_headers(request) recent_intervals = int(request.args.get('recent')) return response.flaskify( payments.get_payments( grass_account.type, grass_account.id, recent_intervals)) @app.route(config.HEALTH_CHECK) def health(): """Check the health of the application. """ return jsonify({'status': 'ok'}) @app.route('/holds/', methods=['PUT']) def edit_hold(hold_id): """Edit a hold by its id. @todo schema validation pending. Args: hold_id (int): id for vendor. Returns: flask.response: Updated holds object. """ user_id = request.headers.get('Orchard-User-Id') put_data = request.get_json() return response.flaskify(payment_holds.update_hold_by_id( hold_id, user_id, put_data.get('status'), put_data.get('description'))) @app.route('/holds/active', methods=['GET']) def get_active_holds(): """Get all active holds for all vendors. Request Params: limit (int): limit number of result records. offset (int): offset start of result records. vendor_ids(str): comma separated list of vendor ids. Returns: flask.response: list of holds. """ limit = request.args.get('limit', type=int) offset = request.args.get('offset', type=int) vendor_ids = request.args.get('vendor_ids') if vendor_ids: if not vendor_ids.replace(',', '').isdigit(): return response.flaskify(response.create_error_response( code=error.ERROR_CODE_INVALID_REQUEST, message='Invalid vendor ids.')) vendor_ids = set(map(int, filter(None, vendor_ids.split(',')))) return response.flaskify(payment_holds.get_all_active_holds( limit, offset, vendor_ids)) @app.route('/holds/', methods=['GET']) def get_holds_by_id(hold_id): """Get a holds by its id. Args: hold_id (int): id for hold. Returns: flask.response: hold obj with logs. """ return response.flaskify(payment_holds.get_holds_by_id(hold_id)) @app.route('/holds/vendor/', methods=['GET']) def get_holds_for_vendor(vendor_id): """Get all holds for a vendor. Args: vendor_id (int): vendor identifier. Returns: flask.response: list of active & inactive holds. """ return response.flaskify(payment_holds.get_holds_by_vendor_id(vendor_id)) @app.route('/holds/vendor/', methods=['POST']) @access.verify_orchard_user_header @json_schema.validate_request( body_schema=json_validators.PAYMENT_HOLD_POST_VALIDATOR) def create_hold_for_vendor(vendor_id): """Create a new hold for a vendor. Args: vendor_id (int): vendor identifier. Returns: flask.response: New holds object. """ user_id = request.headers.get('Orchard-User-Id') put_data = request.get_json() return response.flaskify(payment_holds.create_hold_for_vendor( vendor_id, user_id, put_data.get('status'), put_data.get('description'))) @app.route('/attachments', methods=['GET']) @access.verify_grass_header(True) @json_schema.validate_request( args_schema=json_validators.ATTACHMENTS_GET_VALIDATOR) def get_attachments(): """Get Attachments. Retrieve set of relevant attachments for the account and requested periods. Request Params: periods (string): Comma-delimited set of period ids corresponding to Account type. Returns: Response: Flask response. """ grass_account = request_flask.get_account_from_grass_headers(request) periods = request.args.get('periods').split(',') return response.flaskify( attachment.get_attachments( grass_account.id, grass_account.type, periods)) @app.route('/attachment', methods=['GET']) @access.verify_grass_header(True) @json_schema.validate_request( args_schema=json_validators.ATTACHMENT_GET_VALIDATOR) def get_attachment(): """Get Attachment. Retrieve set of relevant attachments for the account, requested periods and file_name. Request Params: periods (string): Comma-delimited set of period ids corresponding to Account type. file_name (string): File name, e.g. test.csv. Returns: Response: Flask response. """ grass_account = request_flask.get_account_from_grass_headers(request) periods = request.args.get('periods').split(',') file_name = request.args.get('file_name') return response.flaskify( attachment.get_attachment( grass_account.id, grass_account.type, periods, file_name)) @app.route('/accounting_period', methods=['GET']) def get_accounting_period(): """Get Active Accounting Period. Returns: Response: Flask response. """ active_accounting_period = config.ACTIVE_ACCOUNTING_PERIOD return response.flaskify( response.Response({'accounting_period': active_accounting_period})) @app.route('/accounting_period_details/', methods=['GET']) def get_accounting_period_details(period_id): """Get accounting period details for a given period_id. Args: period_id (int): period identifier. Returns: Response: Flask response. """ return response.flaskify(accounting_intervals.get_period_by_id(period_id)) @app.route('/accounting_periods', methods=['GET']) @access.verify_grass_header(True) def get_accounting_periods(): """Get accounting periods for account Returns: Response: Flask response. """ grass_account = request_flask.get_account_from_grass_headers(request) return response.flaskify( period.get_all_periods(grass_account.id, grass_account.type)) @app.route('/accounting-intervals/', methods=['GET']) @access.verify_grass_header(True) def get_accounting_intervals(recent_intervals): """Get accounting intervals. Args: recent_intervals(int): number of intervals to get info Returns: Response: Flask response. example: [ { "type": "quarter", "number": 1, "year": "2020", "currency_id": 4, "periods": [ 253 ] } ] """ grass_account = request_flask.get_account_from_grass_headers(request) return response.flaskify( accounting_intervals.get_intervals_from_contracts( grass_account.type, grass_account.id, recent_intervals)) @app.route('/eq_bonus', methods=['GET']) @access.verify_grass_header(True) def get_eq_bonus(): """Get account EQ bonus.""" grass_account = request_flask.get_account_from_grass_headers(request) period_id = request.args.get('period_id') if grass_account.type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: return response.flaskify(response.Response({})) return response.flaskify(passthrough.get_eq_bonus( grass_account.id, period_id)) @app.route('/eq_bonus_payment', methods=['GET']) @access.verify_grass_header(True) def get_eq_bonus_payment(): """Get account EQ bonus payment details.""" grass_account = request_flask.get_account_from_grass_headers(request) period_id = request.args.get('period_id') if grass_account.type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: return response.flaskify(response.Response([])) return response.flaskify(passthrough.get_eq_payment_details( grass_account.id, period_id)) @app.route( '///average-monthly-net-revenue', methods=['GET']) def get_average_monthly_net_revenue(account_type, account_id): """Get the average monthly net revenue for a vendor/subaccount. Args: account_type (str): The account type (vendor or subaccount). account_id (int): The account id. Returns: Flask.response: Containing the average monthly net revenue. """ grass_account_type, grass_account_id = flask_request.get_grass_headers( request) if grass_account_type == header.GRASS_ACCOUNT_TYPE_VENDOR: validation = flask_request.verify_grass_access( request, vendor=account_id) else: validation = flask_request.verify_grass_access( request, subaccount=account_id) if not validation: return response.flaskify(validation) return response.flaskify( revenue.get_average_monthly_net_revenue(account_type, account_id)) @app.route('/budget-caps', methods=['GET']) def get_budget_caps(): """Get the budget caps for all the labels.""" return response.flaskify(budget_caps.get_budget_caps()) @app.route('/get-physical-reserves', methods=['GET']) def get_physical_reserves(): """Get physical reserves for a requested vendor""" vendor_id = request.args.get('vendor_id') period_id = request.args.get('period_id') if not vendor_id or not period_id: return response.flaskify(response.create_error_response( '400 Bad Request', 'Missing required parameters')) validation = flask_request.verify_grass_access( request, vendor=vendor_id) if not validation: return response.flaskify(validation) return response.flaskify( physical_reserves.get_physical_reserves( vendor_id, period_id)) @app.route('///eq_bonus', methods=['GET']) def get_eq_bonus_for_account(account_type, account_id): """Get account equity bonus. Args: account_type (str): The account type (vendor or subaccount). account_id (int): The account id. Returns: Flask.response: Containing the bonus. """ access_response = flask_request.verify_grass_access( request, vendor=account_id, subaccount=account_id) if not access_response: return response.flaskify(access_response) if account_type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: return response.flaskify(response.Response({})) return response.flaskify(passthrough.get_eq_bonus( account_id, request.args.get('period_id'))) @app.route( '///eq_bonus_payment', methods=['GET']) def get_eq_bonus_payment_for_account(account_type, account_id): """Get account equity bonus payment details. Args: account_type (str): The account type (vendor or subaccount). account_id (int): The account id. Returns: Flask.response: Containing the bonus details. """ access_response = flask_request.verify_grass_access( request, vendor=account_id, subaccount=account_id) if not access_response: return response.flaskify(access_response) if account_type == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT: return response.flaskify(response.Response([])) return response.flaskify(passthrough.get_eq_payment_details( account_id, request.args.get('period_id'))) @access.verify_grass_header(True) @app.route('/vendor-currency', methods=['GET']) def get_vendor_currency(): """Retrieve vendor currency Returns: Flask.response: Containing the bonus details. """ grass_account = request_flask.get_account_from_grass_headers(request) return response.flaskify( vendor_currency.get_currency_by_vendor_id(grass_account)) @app.route('/currency/', methods=['GET']) def get_currency_by_id(currency_id): """Get currency details. Args: currency_id(int): currency_id Returns: Response: Flask response. """ return response.flaskify( currency.get_currency_by_id(currency_id=currency_id))