"""Logic for product copy.""" from flask import g from oto import response from product.constants import error from product.logic import localization as localization_logic from product.logic import product as product_logic from product.logic import upc as upc_logic from product.models import ows_assets from product.models import ows_carveouts from product.models import ows_marketing from product.models import ows_pricing from product.models import ows_product_digital from product.models import ows_product_physical from product.models import ows_track def _rollback_copy(new_product_id, failure_response): delete_response = product_logic.delete_product(new_product_id) if not delete_response: return delete_response return failure_response def _copy_physical_product(product_id, fields, orchard_user_id): """Copy a physical product. Args: product_id (int): The product_id (release_id) of the product to copy. fields (dict): Fields that override original product fields when copying the product. orchard_user_id (str): orchard user id from grass. Returns: response.Response: object with status of product copy. """ pricing_tier = fields.pop('orchard_pricing_tier', None) copy_product_response = ows_product_physical.copy_product( product_id, fields, orchard_user_id) if not copy_product_response: g.ows.log.warning( 'Error {} copying product (ows-product-physical)'.format( copy_product_response.status)) return copy_product_response new_product_id = copy_product_response.message['product_id'] copy_tracks_response = ows_product_physical.copy_product_tracks( product_id, new_product_id) if not copy_tracks_response: g.ows.log.warning( 'Error {} copying product tracks (ows-product-physical)'.format( copy_tracks_response.status)) return _rollback_copy(new_product_id, copy_tracks_response) copy_carveouts_response = ows_carveouts.copy( product_id, new_product_id) if not copy_carveouts_response: g.ows.log.warning( 'Error {} copying product carveouts (ows-carveouts)'.format( copy_carveouts_response.status)) return _rollback_copy(new_product_id, copy_carveouts_response) copy_highlights_response = ows_marketing.copy_product_highlights( product_id, new_product_id) if not copy_highlights_response: g.ows.log.warning( 'Error {} copying product highlights (ows-marketing)'.format( copy_highlights_response.status)) return _rollback_copy(new_product_id, copy_highlights_response) copy_assets_response = ows_assets.copy_product_artwork( product_id, new_product_id, orchard_user_id) if copy_assets_response.status not in [200, 404]: g.ows.log.warning( 'Error {} copying product assets (ows-assets)'.format( copy_assets_response.status)) return _rollback_copy(new_product_id, copy_assets_response) if pricing_tier is not None: pricing_response = ows_pricing.update_pricing_tier( new_product_id, ows_pricing.PHYSICAL_PRICING_FAMILY_ID, pricing_tier) if not pricing_response: return _rollback_copy(new_product_id, pricing_response) return copy_product_response def _copy_digital_product( product_id, fields, orchard_user_id, account_type, account_id): """Copy a digital product. Args: product_id (int): The product_id (release_id) of the product to copy. fields (dict): Fields that override original product fields when copying the product. orchard_user_id (str): orchard user id from grass. account_type (str): the account type - one of subaccount or vendor. account_id (int): the account_id of the above type. Returns: response.Response: object with status of product copy. """ product_code = fields.get('product_code') if account_id and account_type and product_code: product_code_available_response = ( product_logic.product_code_found_for_account( product_code, account_type, account_id)) if product_code_available_response.status != 404: return response.create_error_response( code=error.ERROR_CODE_PRODUCT_CODE_UNAVAILABLE, message=error.ERROR_MESSAGE_PRODUCT_CODE_UNAVAILABLE) upc = fields.get('upc') is_upc_available = upc_logic.upc_available(upc) if not is_upc_available: g.ows.log.warning( 'Error: upc {upc} is unavailable'.format(upc=upc)) return response.create_error_response( code=error.ERROR_CODE_UPC_UNAVAILABLE, message=error.ERROR_MESSAGE_UPC_UNAVAILABLE) copy_product_response = ows_product_digital.copy_digital_product( product_id, fields) if not copy_product_response: g.ows.log.warning( 'Error {} copying product (ows-product-digital)'.format( copy_product_response.status)) return copy_product_response new_product_id = copy_product_response.message['product_id'] # Product Builder currently uses legacy localization, # so copy should use it as well use_legacy_localization = True copy_localization_response = localization_logic.copy_localization( product_id, new_product_id, use_legacy_localization) if not copy_localization_response: g.ows.log.warning( 'Error {} copying product localization (ows-product)'.format( copy_localization_response.status)) # Copy artwork must be before copy tracks, so it will be available # for product page rendering first. copy_assets_response = ows_assets.copy_product_artwork( product_id, new_product_id, orchard_user_id) if not copy_assets_response: g.ows.log.warning( 'Error {} copying product assets (ows-assets)'.format( copy_assets_response.status)) track_list = fields.get('track_list') copy_tracks_response = ows_track.copy_product_tracks( product_id, new_product_id, orchard_user_id, track_list) if not copy_tracks_response: g.ows.log.warning( 'Error {} copying product tracks (ows-track)'.format( copy_tracks_response.status)) copy_pricing_response = ows_pricing.copy_product_pricing( product_id, new_product_id) if not copy_pricing_response: g.ows.log.warning( 'Error {} copying product pricing (ows-pricing)'.format( copy_pricing_response.status)) copy_carveouts_response = ows_carveouts.copy( product_id, new_product_id) if not copy_carveouts_response: g.ows.log.warning( 'Error {} copying product carveouts (ows-carveouts)'.format( copy_carveouts_response.status)) return copy_product_response def copy_product( product_id, fields, orchard_user_id, account_type=None, account_id=None): """Copy a product. Args: product_id (int): The product_id (release_id) of the product to copy. fields (dict): Fields that override original product fields when copying the product. orchard_user_id (str): orchard user id from grass. account_type (str): the account type - one of subaccount or vendor. account_id (int): the account_id of the above type. """ product_response = product_logic.get_product_by_product_id(product_id) if not product_response: return product_response if product_response.message.get('context_type') == 'physical': copy_product_response = _copy_physical_product( product_id, fields, orchard_user_id) else: copy_product_response = _copy_digital_product( product_id, fields, orchard_user_id, account_type, account_id) return copy_product_response def copy_product_flexible( from_product_id, to_context_type, field_overrides, orchard_user_id, account_type=None, account_id=None): """Copy a product. Args: from_product_id (int): The product_id (release_id) of the product to copy. to_context_type (str): digital or physical, product type you are copying to. field_overrides (dict): Fields that override original product fields when copying the product. orchard_user_id (str): orchard user id from grass. account_type (str): the account type - one of subaccount or vendor. account_id (int): the account_id of the above type. """ from_product_response = product_logic.get_product_by_product_id( from_product_id) if not from_product_response: return from_product_response from_context_type = from_product_response.message.get('context_type') from_project_id = from_product_response.message.get('project_id') if from_context_type == 'physical' and to_context_type == 'physical': return _copy_physical_product( from_product_id, field_overrides, orchard_user_id) if from_context_type == 'physical' and to_context_type == 'digital': return _copy_physical_to_digital( from_product_id, from_project_id, account_id, account_type, orchard_user_id, field_overrides) if from_context_type == 'digital' and to_context_type == 'digital': return _copy_digital_product( from_product_id, field_overrides, orchard_user_id, account_type, account_id) return response.Response(status=400, message='Unsupported copy type') def _copy_physical_to_digital( copied_product_id, project_id, account_id, account_type, orchard_user_id, fields): """Copies a physical product to a digital one. 1. checks if upc is available. 2. fetches physical product to copy. 3. Creates digital product. 4. Updates digital product with metadata. 5. Copy tracks. 6. Copy artwork. """ upc = fields.get('upc') is_upc_available = upc_logic.upc_available(upc) if not is_upc_available: g.ows.log.warning( 'Error: upc {upc} is unavailable'.format(upc=upc)) return response.create_error_response( code=error.ERROR_CODE_UPC_UNAVAILABLE, message=error.ERROR_MESSAGE_UPC_UNAVAILABLE) create_response = _create_digital_product_from_physical_product( project_id, account_id, account_type, fields.copy()) if not create_response: return create_response new_product_id = create_response.message['product_id'] physical_product_response = ows_product_physical.get_product_by_product_id( copied_product_id) if not physical_product_response: return physical_product_response update_response = _update_digital_product_from_physical_product( new_product_id, account_id, account_type, physical_product_response.message) if not update_response: return update_response tracks_response = _copy_physical_to_digital_tracks( new_product_id, copied_product_id, physical_product_response.message) if not tracks_response: return tracks_response copy_assets_response = ows_assets.copy_product_artwork( copied_product_id, new_product_id, orchard_user_id) if copy_assets_response.status not in [200, 404]: g.ows.log.warning( 'Error {} copying product assets (ows-assets)'.format( copy_assets_response.status)) return copy_assets_response return response.Response( message={'product_id': new_product_id}, status=200 ) def _copy_physical_to_digital_tracks( new_product_id, copied_product_id, product_attribs): tracks_response = ows_product_physical.get_tracks(copied_product_id) if not tracks_response: return tracks_response p_line = product_attribs['pline'] explicit = product_attribs['explicit'] for track in tracks_response.message['items']: create_track_response = ows_track.create_product_track({ 'product_id': new_product_id, 'track_name': track['track_name'] }) if not create_track_response: return create_track_response track_id = create_track_response.message['tuid'] track_artists = [{'name': artist, 'type': 'performer'} for artist in track['performer']] update_product_track_response = ows_track.update_product_track( track_id, { 'p_info': p_line, 'explicit': explicit, 'artists': track_artists, 'isrc': track['isrc'] }) if not update_product_track_response: return update_product_track_response return response.Response() def _update_digital_product_from_physical_product( new_product_id, account_id, account_type, product_attribs): """Update a digital product from a physical product.""" imprint = product_attribs['label'] genre_id = product_attribs['genre_id'] subgenre_id = product_attribs['subgenre_id'] c_line = product_attribs['cline'] release_date = product_attribs['release_date'] sale_start_date = product_attribs['sale_start_date'] primary_artist = product_attribs['primary_artist'] update_fields = { 'imprint': imprint, 'genre_id': genre_id, 'subgenre_id': subgenre_id, 'c_line': c_line, 'release_date': release_date, 'sale_start_date': sale_start_date, 'product_artists': [{'name': primary_artist, 'role': 'primary_artist'}] } return ows_product_digital.update_digital_product( new_product_id, account_id, account_type, update_fields) def _create_digital_product_from_physical_product( project_id, account_id, account_type, fields): """Create a digital product from a physical product.""" fields['project_id'] = project_id fields['account_id'] = int(account_id) fields['account_type'] = account_type create_product_response = ows_product_digital.create_digital_product( fields) if not create_product_response: g.ows.log.warning( 'Error {} creating product (ows-product-digital)'.format( create_product_response.status)) return create_product_response return create_product_response