"""Logic for marketing highlights.""" from oto import response from sales_goals.models import digital_projections from sales_goals.models import ows_product_digital, ows_project from sales_goals.utils.dataloader_util import format_for_dataloader from sales_goals.utils.logic_utils import is_workstation_user def get_digital_projections_by_product_id(product_id, orchard_user_id): """Fetch digital projections by product id. Args: product_id (int): Product id. Returns: response.Response: object containing projections if available else not found response. """ response = digital_projections.get_projections_by_product_id( product_id) if not response: return response if is_workstation_user(orchard_user_id): response.message.pop('internal_note', None) return response priorities = ows_product_digital.get_marketing_priorities(product_id) if not priorities: return priorities for projection in response.message['projections']: add_priority(projection, priorities) return response def dataload_digital_product_projections(product_ids): """Fetch bulk digital product projections. Combines the marketing projections from ows-sales-goals with the territory marketing priorities from ows-product-digital. Args: product_ids (list): Product ids. Returns: response.Response: object containing projections if available else not found response. """ projections = digital_projections.dataload_digital_product_projections( product_ids) if not projections: return projections priorities = ows_product_digital.dataload_marketing_priorities(product_ids) if not priorities: return priorities result = projections.message.copy() # Combine global projections and territory priorities for global_projection in result: for projection in global_projection['projections']: add_priority(projection, priorities, match_product_id=True) # For projects with no projection data in the DB, add empty objects found_product_ids = [p['product_id'] for p in result] for product_id in product_ids: if product_id not in found_product_ids: result.append({ 'product_id': product_id, 'internal_note': None, 'projections': None, }) return response.Response( message=format_for_dataloader( result, sorted([p['product_id'] for p in result]), 'product_id' )) def upsert_global_projections(product_id, data, orchard_user_id): """Create or update digital projections by product id. Args: product_id (int): Product id. data (dict): data to persist. orchard_user_id (str): Orchard user Id Returns: response.Response: object containing projections if available else not found response. """ if is_workstation_user(orchard_user_id): data.pop('internal_note', None) return digital_projections.upsert_global_projections( product_id, data, orchard_user_id) def upsert_territory_projections( product_id, data, orchard_user_id, delete=False): """Upsert digital territory projections by product id. Args: product_id (int): Product id. data (dict): data to persist. orchard_user_id (str): Orchard user Id delete (bool): If true delete exisiting projections not present in data Returns: response.Response: object containing projections if available else not found response. """ if delete: new_territories = [p['territory_id'] for p in data if 'priority' in p] result = get_digital_projections_by_product_id( product_id, orchard_user_id ).message existing_priorities = result['projections'] if result else [] for projection in existing_priorities: if projection['territory_id'] not in new_territories: delete_territory_projection(product_id, projection['id']) response = digital_projections.upsert_territory_projections( product_id, data, orchard_user_id) if not response or is_workstation_user(orchard_user_id): return response for projection in data['items']: if 'priority' not in projection: continue priority = projection['priority'] country_id = projection['territory_id'] priority_response = ows_product_digital.set_marketing_priority( product_id, country_id, priority) if not priority_response: return priority_response return response def delete_territory_projection(product_id, projection_id): """Delete territory projection.""" response = digital_projections.delete_territory_projection( product_id, projection_id) if not response: return response ows_product_digital.delete_marketing_priority( product_id, response.message.country_id) return response def add_priority(projection, priorities, match_product_id=False): """Add the correct priority to this projection. The flag match_product_id is used to match priorities by product_id. It is used if there are priorities for multiple products in the input data. This should only happen when processing a dataload request. """ try: priority = [ priority for priority in priorities.message if priority['country_id'] == projection['territory_id'] and ( not match_product_id or priority['release_id'] == projection['product_id'] ) ][0] except IndexError: return projection['priority'] = priority['priority'] def get_territory_priorities_for_project(project_id): """Fetch project marketing priorities by project id. Args: project_id (int): Project id. Returns: response.Response: object containing priorities if available else not found response. """ internal_note_result = digital_projections.get_project_marketing_projection(project_id) result = ows_project.get_project_marketing_priorities(project_id) marketing_priorities = internal_note_result.copy() marketing_priorities.update({'projections': result.message}) return response.Response(message=marketing_priorities) def dataload_project_marketing_priorities(project_ids): """Dataload project marketing priorities by project ids. Combines the global marketing projection from ows-sales-goals with the territory marketing priorities from ows-project-manager. Args: project_ids (list): Project ids. Returns: response.Response: object containing priorities if available else not found response. """ internal_note_response = digital_projections.dataload_project_marketing_projection( project_ids) projection_response = ows_project.dataload_project_marketing_priorities( project_ids) # Group projections by product_id projections = {p['project_id']: [] for p in internal_note_response.message} for projection in projection_response.message: project_id = projection['project_id'] projections[project_id].append(projection) # Combine internal note and projections result = [] for internal_note in internal_note_response.message: project_id = internal_note['project_id'] result.append({ **internal_note, 'projections': projections[project_id], }) # For projects with no projection data in the DB, add empty objects found_project_ids = [p['project_id'] for p in result] for project_id in project_ids: if project_id not in found_project_ids: result.append({ 'project_id': project_id, 'internal_note': None, 'projections': None, }) return response.Response( message=format_for_dataloader( result, sorted([p['project_id'] for p in result]), 'project_id' )) def upsert_project_territory_projections( project_id, data, orchard_user_id, delete=False): """Upsert digital territory projections by project id. Args: project_id (int): Project id. data (dict): data to persist. orchard_user_id (str): Orchard user Id delete (bool): If true delete exisiting projections not present in data Returns: response.Response: object containing projections if available else not found response. """ if delete: new_territories = [p['territory_id'] for p in data if 'priority' in p] result = get_territory_priorities_for_project( project_id ).message existing_priorities = result['projections'] if result else [] for projection in existing_priorities: if projection['territory_id'] not in new_territories: delete_project_territory_projection(project_id, projection['id']) # The product territory projection upsert has this logic here: # response = digital_projections.upsert_territory_projections(...) # It's not needed for project since store-specific projections are deprecated: # https://theorchard.atlassian.net/browse/DIS-624 for projection in data['items']: if 'priority' not in projection: continue priority = projection['priority'] territory_id = projection['territory_id'] priority_response = ows_project.set_project_marketing_priority( project_id, territory_id, priority) if not priority_response: return priority_response clear_project_product_marketing_priorities(project_id) clear_project_product_territory_projections(project_id) return get_territory_priorities_for_project(project_id) def delete_project_territory_projection(project_id, projection_id): """Delete territory projection for project.""" response = ows_project.delete_project_marketing_priority( project_id, projection_id) return response def upsert_global_projections_for_project(project_id, data, orchard_user_id): """Create or update project global projections. Args: project_id (int): Project id. data (dict): data to persist. orchard_user_id (str): Orchard user Id Returns: response.Response: object containing projections if available else not found response. """ return digital_projections.upsert_project_marketing_projection( project_id, data, orchard_user_id) def clear_project_product_territory_projections(project_id): """Remove territory projections for all products of the given project_id. Args: project_id (int): Project id. """ products = ows_project.get_products_for_project(project_id) if not products: return for product in products['items']: product_id = product['product_id'] digital_projections.clear_product_territory_projections(product_id) def clear_project_product_marketing_priorities(project_id): """Remove all product marketing priorities for given project_id. Args: project_id (int): Project id. Returns: response.Response: empty object. """ products = ows_project.get_products_for_project(project_id) if not products: return for product in products['items']: product_id = product['product_id'] ows_product_digital.delete_marketing_priority_for_product(product_id) def clear_project_marketing_priorities(product_id): """Remove project marketing priorities for given product_id. Args: product_id (int): Product id. Returns: response.Response: empty object with status. """ project_id = ows_product_digital.get_project_for_product(product_id) if not project_id: return ows_project.delete_project_marketing_priority_in_bulk(project_id) def set_marketing_priority_for_product(product_id, data, orchard_user_id, delete=False): """Set marketing priority for a product. This function checks if there are project level projections applied to the product before applying product level projections. If project level projections do not exist, the product to be updated is set with the projections from the payload and the function returns. If there are project level projections, those projections are applied at the product level to all other products of the project; The payload marketing priority data is saved for the provided product_id; The project level projections are removed and the function returns. Args: product_id (int): Product id. data (dict): data to persist. orchard_user_id (str): Orchard user Id delete (boolean): If true delete exisiting projections not present in data Returns: response.Response: object containing projections if available else not found response. """ project_id = ows_product_digital.get_project_for_product(product_id) result = ows_project.get_project_marketing_priorities(project_id) if not result.message: return upsert_territory_projections( product_id, data, orchard_user_id, delete) project_marketing_priorities = {'items': result.message} products = ows_project.get_products_for_project(project_id) if not products: return clear_project_marketing_priorities(product_id) projection_response = digital_projections.get_project_marketing_projection(project_id) for product in products['items']: product_id_to_add_priority = product['product_id'] if product_id == product_id_to_add_priority: continue upsert_territory_projections( product_id_to_add_priority, project_marketing_priorities, orchard_user_id, delete) digital_projections.upsert_global_projections( product_id_to_add_priority, projection_response, orchard_user_id) return upsert_territory_projections( product_id, data, orchard_user_id, delete)