""" Reports Logic ============= Manages the retrieval of previous report executions as well as submission of new report execution requests. """ from ows_accounting import config from ows_accounting import response from ows_accounting.constants import error from ows_accounting.constants import feature from ows_accounting.logic.samples import report_fixtures from ows_accounting.models import accounting_statement_export from ows_accounting.models import contact from ows_accounting.models import workflow_service from ows_accounting.utils import s3 def _get_user_id_type(account_id, account_type): """Get user_id_type partition key. Args: account_id (int): label id or subaccount id. account_type (str): subaccount or label. Returns: str: user_id_type string. """ user_type = 'L' if account_type == 'subaccount': user_type = 'S' return '{user_id}{user_type}'.format( user_id=account_id, user_type=user_type) def _get_user_params(periods, transaction_types, file_format, number_format): """Get user_params code. user_params must be generated the same way as the swf. See: https://github.com/theorchard/swf-accounting-statement-export/blob/ master/processing_accounting/flows/custom_export/tasks.py#L41 Args: periods (list): comma delimited string period ids. transaction_types (str): comma delimited string transaction type codes. file_format (str): file format either 'xls' or 'txt'. number_format (str): locale code. Example: en_US, es_ES. Returns: str: sorted user_params code generated from the values in the dictionary input. """ return '__'.join( sorted([periods, transaction_types, file_format, number_format])) def _get_email(account_id, account_type, vend_contact_id): """Get primary email Args: account_id (int): id of account. account_type (string): 'vendor' or 'subaccount'. vend_contact_id (int): primary key of vend_contact table. Returns: str: email. None if email not found. """ get_email_response = None if vend_contact_id: get_email_response = contact.get_email_by_vend_contact_id( vend_contact_id) else: if account_type == 'subaccount': get_email_response = contact.get_email_by_subaccount_id(account_id) else: get_email_response = contact.get_email_by_vendor_id(account_id) if get_email_response: return get_email_response.message return None def get_reports(account_id, account_type, periods, page_offset, page_limit): """Retrieve list of report executions corresponding to params. Args: account_id (int): id of account. account_type (string): 'vendor' or 'subaccount'. periods (list): a list of string period ids. page_offset (int): current page. page_limit (int): max number of items per page. Returns: Response: paginated list of report executions, each a dict, or error payload. """ report_model = accounting_statement_export.AccountingStatementExport() user_type = 'L' if account_type == 'subaccount': user_type = 'S' excludes_records_with_attr = {'file_type': 'AVRO'} return response.Response( report_model.get_records( periods, account_id, user_type, excludes_records_with_attr, page_offset, page_limit)) def get_report( account_id, account_type, periods, transaction_types, file_format, number_format): """Retrieve a specific report execution corresponding to params. Args: account_id (int): id of account. account_type (string): 'vendor' or 'subaccount'. periods (str): comma-delimited string of accounting period ids. transaction_types (str): comma-delimited string of transaction type codes. file_format (str): file format either 'xls' or 'txt'. number_format (str): locale code. Example: en_US, es_ES. Returns: Response: response object with presigned url in it. Return response with code 404 if s3 object does not exist. """ transaction_types = ','.join(sorted(transaction_types.split(','))) periods = ','.join(sorted(periods.split(','))) model = accounting_statement_export.AccountingStatementExport() user_id_type = _get_user_id_type(account_id, account_type) get_item_response = model.get_item( user_id_type, periods, transaction_types, file_format, number_format) if not get_item_response: return response.create_not_found_response( 's3_path not found for params: {},{},{},{},{}'.format( user_id_type, periods, transaction_types, file_format, number_format)) item = get_item_response.message s3_path = item.get('s3_path') # Check if object exists bucket, key = s3.extract_bucket_path(s3_path) if not s3.object_exists(bucket, key): return response.create_not_found_response( 'S3 object for path: {} not found.'.format(s3_path)) return response.Response( message=s3.get_presigned_url( bucket, key, config.PRESIGNED_URL_EXPIRES)) def add_report( correlation_id, account_id, account_type, periods, transaction_types, file_format, number_format, email, vend_contact_id, first_name, last_name, requested_datetime, report_version=feature.DEFAULT_REPORT_VERSION): """Submit a new report execution. Submits a new report execution with requested params unless previously requested. @todo(pkuong) find out if we should get email from a separated microservice or getting it inside this microservice. Args: correlation_id (str): correlation id generated in the request. account_id (int): id of account. account_type (string): 'vendor' or 'subaccount'. periods (list): a list of string period ids. transaction_types (list): a list of string transaction type codes. file_format (str): file format either 'xls' or 'txt'. number_format (str): locale code. Example: en_US, es_ES. email (str): email address of the contact who generates this report. vend_contact_id (int): primary key of vend_contact table. first_name (str): first name of the report submitter. last_name (str): last name of the report submitter. requested_datetime (str): timestamp at which item is created. report_version (int): version of the report. Returns: Response: a specific report execution. """ if account_type not in ['vendor', 'subaccount']: return response.create_fatal_response( 'Invalid account_type: {}.'.format(account_type)) if account_type == 'vendor': user_type = 'label' else: user_type = 'subaccount' periods = ','.join(sorted(periods)) transaction_types = ','.join(sorted(transaction_types)) user_id_type = _get_user_id_type(account_id, account_type) report_model = accounting_statement_export.AccountingStatementExport() status_resp = report_model.get_item( user_id_type, periods, transaction_types, file_format, number_format) avro_check_resp = report_model.get_avro_item(user_id_type, periods) if not status_resp and avro_check_resp: if not email: email = _get_email(account_id, account_type, vend_contact_id) # Trigger SWF params = dict( user_id=account_id, user_type=user_type, period_ids=periods, transaction_types=transaction_types, file_format=file_format, locale=number_format, client_email=email, report_version=report_version) workflow = workflow_service.WorkflowService( config.DEFAULT_WORKFLOW_SERVICE) workflow_id_resp = workflow.generate_workflow_id( config.WORKFLOW_ID_PREFIX, **params) exe_resp = workflow.execute_workflow( workflow_id_resp.message, correlation_id, **params) if not exe_resp: error_response = response.create_error_response( error.ERROR_CODE_CONFLICT, exe_resp.message, status=409) response.send_to_sentry( error_response, error.ERROR_CODE_CONFLICT) return error_response # Make an entry in dynamodb report_model.put_item( user_id_type, _get_user_params( periods, transaction_types, file_format, number_format), status=config.STATUS_PENDING, requested_by='{} {}'.format(first_name, last_name), requested_datetime=requested_datetime) else: if avro_check_resp.status == 400: return response.Response( message=error.ERROR_MESSAGE_UNAVAILABLE, status=400) elif status_resp.message.get('status') == config.STATUS_GENERATING: return response.Response(status=302) elif status_resp.message.get('status') == config.STATUS_GENERATED: return response.Response(status=304) return response.Response(message=dict(message='200 Created')) def get_sample_reports(): """Sample response until we implement actual report execution retrieval. Returns: dict: sample response containing report item array and pagination block. """ results = { 'items': report_fixtures.reports, 'pagination': report_fixtures.pagination} return results