"""Application 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: oto.response for more details. """ from flask import g, jsonify, request from oto import response from oto.adaptors.flask import flaskify from owsfeatures import features from owsrequest import access from reporting import config from reporting.api import app from reporting.constants import error, header from reporting.constants.report_map import REPORT_REGISTRY from reporting.logic import account, mode, presets from reporting.logic import physical_reporting as pr from reporting.logic import single_product_overview as spo from reporting.utils.physical_reporting import DecimalEncoder, translate_params @app.route(config.HEALTH_CHECK, methods=['GET']) def health(): """Check the health of the application.""" return jsonify({'status': 'ok'}) @app.route(config.REPORT_RUN_STATUS, methods=['GET']) def report_run(report_token, report_run_token): """Check the status of a report run in Mode Analytics. Returns: flask.Response: a flask response object with the signed_csv_url. """ run_status = mode.report_run_status(report_token, report_run_token) return flaskify(run_status) @app.errorhandler(500) def exception_handler(error): """Default handler when uncaught exception is raised. Note: Exception will also be sent to Sentry if config.SENTRY is set. Returns: flask.Response: A 500 response with JSON 'code' & 'message' payload. """ message = ( 'The server encountered an internal error ' 'and was unable to complete your request.' ) g.log.exception(error) return flaskify(response.create_fatal_response(message)) @app.route('/report//embed', methods=['POST']) def generate_embed_url(report_token): """Generate a signed embed url for the given report id. Returns: flask.Response: JSON with the signed embed url. """ account_params = account.update_params_with_vendor_info(request) if not account_params: return flaskify(account_params) cache_for_day = request.get_json().get('cache_for_day', False) vector_report_cache = request.get_json().get('vector_report_cache', False) signed_embed_url = mode.generate_embed_src( report_token, account_params, cache_for_day, vector_report_cache ) return flaskify(signed_embed_url) @app.route('/report//run', methods=['POST']) def initiate_report_run(report_token): """Initiate a new run of the specified report. Returns: flask.Response: JSON with the report_run_token. """ account_params = account.update_params_with_vendor_info(request) if not account_params: return flaskify(account_params) report_run_token = mode.initiate_report_run(report_token, account_params) return flaskify(report_run_token) @app.route( '/space//report/', methods=['GET'] ) def get_report_token(space_name, report_name): """Return the report token for a space/report name combination. Returns: flask.Response: JSON with the `report_token`. """ report_token_response = mode.get_report_token(space_name, report_name) return flaskify(report_token_response) @app.route('/report/presets', methods=['GET']) def get_report_presets(): """Get report presets for a vendor or subaccount requesting through Grass. Returns: flask.Response: Report presets. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) access_response = access.verify_grass_headers( account_type, account_id, True ) if not access_response: return flaskify(access_response) report_presets = presets.get_report_presets(account_type, int(account_id)) return flaskify(report_presets) @app.route('/report/preset', methods=['POST']) def create_report_preset(): """Create a report preset for a vendor/subaccount requesting through Grass. Returns: flask.Response: Report presets. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) user_id = request.headers.get(header.ORCHARD_USER_ID) access_response = access.verify_grass_headers( account_type, account_id, True ) if not access_response: return flaskify(access_response) preset_dict = request.get_json() preset = presets.create_report_preset( preset_dict, account_type, account_id, user_id ) return flaskify(preset) @app.route('/report/preset/', methods=['DELETE']) def delete_report_presets(preset_id): """Delete report preset for a vendor/subaccount requesting through Grass. Returns: flask.Response: Report presets. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) access_response = access.verify_grass_headers( account_type, account_id, True ) if not access_response: return flaskify(access_response) report_presets = presets.delete_report_preset( preset_id, account_type, account_id ) return flaskify(report_presets) @app.route('/report/', methods=['POST']) @features.load_features def get_data(report_name): """Return the report data. Returns: flask.Response: JSON with the data. """ if report_name not in REPORT_REGISTRY: return flaskify( response.create_error_response( status=400, code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_BAD_REPORT, ) ) if REPORT_REGISTRY[report_name]['grass_headers_required'] is True: params = account.update_params_with_vendor_info(request) if not params: return flaskify(params) # TODO: PHYS-3620 - Remove this function. request_params = translate_params(params) return flaskify( pr.get_report_by_name(report_name, request_params), encoder=DecimalEncoder, ) @app.route('/report//filters', methods=['GET']) def get_filters(report_name): """Return the report data. Returns: flask.Response: JSON with the data. """ if report_name not in REPORT_REGISTRY: return flaskify( response.create_error_response( status=400, code=error.ERROR_CODE_BAD_REQUEST, message=error.ERROR_MESSAGE_BAD_REPORT, ) ) if REPORT_REGISTRY[report_name]['grass_headers_required'] is True: params = account.update_params_with_vendor_info(request) if not params: return flaskify(params) vendor_id = params['param_vendor_id'] subaccount_id = params['param_subaccount_id'] return flaskify( pr.get_report_filters_by_type(report_name, vendor_id, subaccount_id) ) @app.route( '/product//overview/', methods=['GET'], ) def get_product_overview(local_product_cd, territory): """Return overview information for a product. Returns: flask.Response: JSON with data. """ account_type = request.headers.get(header.GRASS_ACCOUNT_TYPE) account_id = request.headers.get(header.GRASS_ACCOUNT_ID) profile_type = request.headers.get(header.ORCHARD_PROFILE_TYPE) profile_id = request.headers.get(header.ORCHARD_PROFILE_ID) if account_type or account_id: access_response = access.verify_grass_headers( account_type, account_id, True ) if not access_response: return flaskify(access_response) product_ownership_response = spo.product_belongs_to_account( local_product_cd, territory, account_type, account_id ) elif profile_type and profile_id: product_ownership_response = spo.product_belongs_to_profile( local_product_cd, territory, profile_type, profile_id ) else: return flaskify( response.create_error_response( status=400, code=error.ERROR_CODE_MISSING_HEADERS, message=error.ERROR_MESSAGE_MISSING_ACCOUNT_OR_PROFILE_HEADERS, ) ) if not product_ownership_response: return flaskify(product_ownership_response) overview = spo.get_overview(local_product_cd, territory) return flaskify(overview) @app.route('/physical-reporting/top-open-orders', methods=['POST']) def physical_reporting_top_open_orders(): """Return top 10 open orders for one or more vendors. Returns: flask.Response: JSON with list of top open orders. """ vendor_ids = request.get_json().get('vendor_ids') if not vendor_ids: return flaskify( response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message='vendor_ids must be a non-empty list', status=400, ) ) subacct_ids = request.get_json().get('subacct_ids') try: return flaskify( response.Response(pr.get_top_open_orders(vendor_ids, subacct_ids)) ) except Exception as e: return flaskify(response.create_fatal_response(str(e))) @app.route('/physical-reporting/top-yesterday-shipments', methods=['POST']) def physical_reporting_top_yesterday_shipments(): """Return top 10 yesterday shipments for one or more vendors. Returns: flask.Response: JSON with list of top yesterday shipments. """ vendor_ids = request.get_json().get('vendor_ids') if not vendor_ids: return flaskify( response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message='vendor_ids must be a non-empty list', status=400, ) ) subacct_ids = request.get_json().get('subacct_ids') try: return flaskify( response.Response( pr.get_top_yesterday_shipments(vendor_ids, subacct_ids) ) ) except Exception as e: return flaskify(response.create_fatal_response(str(e))) @app.route('/physical-reporting/top-mtd-shipments', methods=['POST']) def physical_reporting_top_mtd_shipments(): """Return top 10 MTD shipments for one or more vendors. Returns: flask.Response: JSON with list of top MTD shipments. """ vendor_ids = request.get_json().get('vendor_ids') if not vendor_ids: return flaskify( response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message='vendor_ids must be a non-empty list', status=400, ) ) subacct_ids = request.get_json().get('subacct_ids') try: return flaskify( response.Response( pr.get_top_mtd_shipments(vendor_ids, subacct_ids) ) ) except Exception as e: return flaskify(response.create_fatal_response(str(e))) @app.route('/physical-reporting/label-subaccount-summary', methods=['POST']) def physical_reporting_label_subaccount_summary(): """Return label/subaccount summary totals for one or more vendors. Returns: flask.Response: JSON with list of label/subaccount summary rows. """ vendor_ids = request.get_json().get('vendor_ids') if not vendor_ids: return flaskify( response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message='vendor_ids must be a non-empty list', status=400, ) ) subacct_ids = request.get_json().get('subacct_ids') try: return flaskify( response.Response( pr.get_label_subaccount_summary(vendor_ids, subacct_ids) ) ) except Exception as e: return flaskify(response.create_fatal_response(str(e))) @app.route('/physical-reporting/product-detail', methods=['POST']) def physical_reporting_product_detail(): """Return product-level detail rows for one or more vendors. Returns: flask.Response: JSON with one row per SKU including metadata and shipment/return totals. """ vendor_ids = request.get_json().get('vendor_ids') if not vendor_ids: return flaskify( response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message='vendor_ids must be a non-empty list', status=400, ) ) subacct_ids = request.get_json().get('subacct_ids') try: return flaskify( response.Response(pr.get_product_detail(vendor_ids, subacct_ids)), encoder=DecimalEncoder, ) except Exception as e: return flaskify(response.create_fatal_response(str(e)))