Gerenciamento de anúncios com base no inventário

Se você comercializa vários produtos on-line, é possível que tenha várias tarefas de gerenciamento de anúncios que precisam ser executadas regularmente para manter sua estratégia de publicidade sincronizada com suas vendas e estratégia de marketing:

  • Você precisa pausar anúncios de um determinado produto quando ele está em falta e retomar a publicidade quando sua empresa repõe o estoque.
  • Você planeja uma venda com desconto para um produto e gostaria de veicular anúncios que exibem informações sobre o desconto e códigos de cupom para os clientes on-line em potencial.
  • Você deseja promover um produto específico para os usuários de dispositivos móveis e definir lances maiores na plataforma para celular se seus clientes estiverem mais propensos a comprar no celular do que no computador.

Estas tarefas normalmente levam bastante tempo quando feitas manualmente, pois:

  • Os dados de inventário normalmente são armazenados em um banco de dados no servidor do seu aplicativo e sofrem alterações ao longo do dia, o que dificulta o controle dos detalhes do inventário e a atualização manual dos anúncios.
  • Como o número de produtos anunciados aumenta, o número de anúncios que você precisa gerenciar cresce de modo que o acompanhamento manual torna-se difícil ou impossível.

O script de gerenciamento de anúncios com base no inventário ajuda a automatizar algumas dessas tarefas.

Casos de uso semelhantes

Mesmo que o script seja escrito para um determinado produto e estoque do inventário, é possível usá-lo de maneira que ele se adeque a vários casos de uso semelhantes:

  • Você oferece vários cursos on-line e deseja controlar sua estratégia de publicidade com base no número de vagas restantes para inscrição.
  • Você administra um grupo de hospitais e clínicas e deseja anunciar o tempo de espera para uma consulta em minutos para cada local.
  • Você promove viagens de turismo e deseja fazer publicidade com base no número de vagas restantes, considerando também se o usuário pesquisa de um dispositivo móvel ou não. Isso é importante porque se presume que um usuário que pesquisa de um dispositivo móvel durante o horário de funcionamento está mais propenso a reservar uma vaga imediatamente. Dessa forma, é recomendável definir um lance maior para esse usuário.

Recursos do script

O script de gerenciamento de anúncios com base no inventário pode realizar as seguintes tarefas:

  • Importe os detalhes do estoque do inventário de um banco de dados remoto ou de um banco de dados Google Cloud SQL para uma planilha do Google.
  • Importe estatísticas do relatório de diversos produtos para a planilha do Google.
  • Com base em regras especificadas na planilha do Google:
    • Ative ou desative grupos de anúncios específicos do produto.
    • Aplique um modificador de lance para celular a um grupo de anúncios específico do produto.
    • Crie anúncios em grupos de anúncios específicos do produto para promover descontos.
    • Atualize anúncios de desconto dinamicamente com as porcentagens de desconto e informações de cupom mais recentes.

Além disso, as fórmulas da planilha do Google podem especificar condições adicionais para definir a porcentagem de desconto e o modificador de lance para celular, além de determinar quando ativar ou desativar o desconto, entre outras ações, com base nos detalhes sobre o estoque do inventário.

Pré-requisitos

O script de gerenciamento de anúncios com base no inventário leva em consideração algumas premissas sobre a estrutura da sua conta e seus produtos:

  • Cada produto deve ter um código único. O script usa o código para identificar o grupo de anúncios associado ao produto, e as informações sincronizadas do inventário importadas do banco de dados remoto para a planilha, além de outras finalidades.
  • Cada produto tem um grupo de anúncios correspondente que contém todos os anúncios desse produto.
  • O grupo de anúncios específico do produto recebeu um rótulo do Google AdWords cujo texto corresponde ao código do produto.
  • As informações sobre o estoque do inventário estão disponíveis em um banco de dados SQL remoto ou um banco de dados Google Cloud SQL. O script tem acesso a esse banco de dados. Veja as instruções de configuração do banco de dados abaixo.

Como funciona

O diagrama a seguir mostra as principais interações entre o script e os vários componentes.

No início, o script importa os detalhes do inventário do banco de dados e as estatísticas dos relatórios do Google AdWords. Em seguida, ele faz upload desses valores na planilha para fazer a correspondência com os códigos de produto dessa planilha. Como resultado desse processa, a planilha reavalia as fórmulas, e as regras de produtos são atualizadas. Depois, o script faz o download das regras de produtos atualizadas da planilha e atualiza os anúncios e os lances com base nessas regras. O feed do personalizador de anúncios é atualizado com os valores dos marcadores de posição do banco de dados e da planilha, para que o anúncio seja veiculado com esses novos valores. Essas interações são explicadas em detalhe abaixo.

Recuperação das informações sobre o estoque do banco de dados

Para recuperar as informações sobre o inventário dos seus produtos, o script de gerenciamento de anúncios com base no inventário se conecta a um banco de dados SQL no seu servidor ou em um banco de dados Google Cloud SQL. Em seguida, ele armazena esses dados em uma planilha do Google. O código do produto é usado para fazer a correspondência dos detalhes do banco de dados com as linhas da planilha do Google.

Para simplificar o processo, o script presume que o banco de dados tem uma tabela denominada itens, com o seguinte esquema:

Nome do campo Tipo de campo Significado
código string O código único de produto.
estoque number Os detalhes sobre o estoque do inventário para o item.

O script executa a consulta a seguir no banco de dados remoto e carrega os resultados na guia Estatísticas da planilha.

Select code, stock from items;

Não há problema se seu banco de dados tiver um esquema diferente. Nesse caso, você simplesmente terá que modificar a consulta de modo que o conjunto de dados da consulta tenha o esquema mencionado acima.

Recuperação das estatísticas de produtos

O script recupera a lista de todos os produtos da planilha, juntamente com os grupos de anúncios associados a eles. Em seguida, ele gera um Relatório de desempenho do grupo de anúncios dos últimos sete dias. Os resultados são carregados na guia Estatísticas da planilha.

Leitura das regras de produtos

O script lê as regras de gerenciamento de anúncios na guia Regras da planilha. Cada linha da planilha é interpretada como uma regra de gerenciamento de anúncios para um determinado produto, identificado pelo código do produto. As linhas têm as seguintes colunas:

Nome da linha Significado Comentários
Código O código do produto. Sua planilha deve conter apenas uma linha por código de produto. Se houver várias linhas para um código de produto, o script criará uma exceção.  
Banco de imagens Estoque do produto. Essas informações são sincronizadas a partir do banco de dados remoto.  
Nome Um nome de fácil utilização para o produto. Para se referir a este campo no texto do anúncio, use a notação {=Discount.Name}
Ativar regra? Selecione Sim para ativar esta regra ou Não para desativá-la.  
Ativar anúncios? Selecione Sim para ativar os anúncios deste produto ou Não para desativá-los.

Especifique uma fórmula do formulário =IF(Stats!C4 > 0, "Yes", "No") nessa célula para pausar ou ativar anúncios com base na disponibilidade do produto.

Ativar descontos? Selecione Sim para ativar anúncios de desconto deste produto ou Não para desativá-los.
Se essa propriedade for definida como Sim, o script buscará anúncios com o rótulo Desconto. Caso não encontre, o script criará um anúncio padrão para os descontos.
Você pode especificar um rótulo diferente para os anúncios de desconto usando a chave de configuração DISCOUNT_LABEL_NAME. Consulte a seção de configuração.
Porcentagem do desconto A porcentagem do desconto. Para se referir a este campo no texto do anúncio, use a notação {=Discount.Discount}
Código do cupom O código do cupom. Para se referir a este campo no texto do anúncio, use a notação {=Discount.Coupon}
Modificar lances para celular? Selecione Sim para ativar um modificador de lance para celular ou Não para ignorar a definição do lance.  
Modificador de lance para celular Definido como um valor entre -100 e +300. Consulte a página de ajuda sobre ajustes de lance para ver mais detalhes.
Última atualização Fornece um timestamp que mostra quando essa regra foi executada pela última vez. Este campo é somente leitura.  

Sincronização das informações de desconto

O script tenta recuperar uma origem do personalizador de anúncios chamada Descontos na sua conta. Se ela estiver faltando, o script tentará criar uma com os seguintes campos:

Nome do campo Tipo de campo Significado
código text O código do produto.
cupom text O código do cupom do produto.
desconto number A porcentagem de desconto para este produto.
nome text Um nome de fácil utilização para o produto.

Em seguida, o script preenche a origem do personalizador de anúncios com os dados da planilha Regras. Ele também recupera o grupo de anúncios alvo de cada produto e define a segmentação de itens individuais do personalizador de anúncios para o grupo de anúncios correspondente.

Execução das regras

Em seguida, dependendo das configurações "Ativar", o script executa as operações solicitadas.

Nome da configuração Ação
Ativar regra? Se definido como "Sim", o script processa esta regra. Se definido como "Não", esta regra é ignorada.
Ativar anúncios? O script tenta recuperar um grupo de anúncios que recebeu um rótulo do Google AdWords com o código do produto como texto. A regra será ignorada se não for possível encontrar um grupo de anúncios correspondente. Se definido como "Sim", o script ativa este grupo de anúncios. Se definido como "Não", o grupo de anúncios é pausado.
Ativar descontos? O script tenta recuperar um grupo de anúncios que recebeu um rótulo do Google AdWords com o código do produto como texto. A regra será ignorada se não for possível encontrar um grupo de anúncios correspondente. O script tenta recuperar todos os anúncios do grupo de anúncios do produto com o rótulo "Descontos". Se nenhum anúncio for encontrado, o script criará um anúncio para os descontos na conta e aplicará o rótulo "Descontos" nesse anúncio. Se essa configuração for definida como "Sim", todos os anúncios com o rótulo "Descontos" serão ativados. Se definido como "Não", os anúncios serão pausados.
Modificar lances para celular? O script tenta recuperar um grupo de anúncios que recebeu um rótulo do Google AdWords com o código do produto como texto. A regra será ignorada se não for possível encontrar um grupo de anúncios correspondente. Se essa configuração for definida como "Sim", os lances do grupo de anúncios não serão modificados. Se definido como "Não", o modificador de lance para celular do grupo de anúncios será atualizado com o valor fornecido na coluna "Modificador de lance para celular".

Configuração do banco de dados

Siga as instruções na nossa guia de JDBC para permitir que os scripts do Google AdWords façam chamadas para o seu banco de dados.

Limites de planilha

Como esse script depende de um novo cálculo de várias fórmulas de planilha por regra do produto, tente limitar as regras a um número razoável (aproximadamente 10.000 regras) por planilha. Se você tiver um número muito maior de produtos para gerenciar, considere a possibilidade de criar várias cópias do seu script, cada uma delas gerenciando um conjunto próprio de produtos por meio de uma planilha separada.

Manutenção da planilha

Existem alguns pontos a serem considerados para a manutenção da planilha:

  1. O script não gerencia a lista dos códigos dos produtos na planilha. É sua responsabilidade gerenciar manualmente a lista dos códigos dos produtos na planilha se você tiver novos produtos ou parar de vender os existentes.
  2. A planilha usa vários intervalos nomeados para ler os dados. Se você adicionar mais linhas à planilha, atualize também os intervalos nomeados de forma adequada.
  3. Não modifique a estrutura da planilha, pois o código depende da estrutura dela para ler os dados. Se você planeja melhorar o script, consulte a seção abaixo.

Perguntas frequentes

E se eu quiser atualizar os detalhes do inventário manualmente?

É recomendável estabelecer uma conexão com o banco de dados para atualizar seus dados de inventário. No entanto, talvez você não tenha acesso a ele ou prefira atualizar os detalhes do inventário manualmente. Se você quiser atualizar os detalhes do inventário manualmente:

  • Comente a chamada para o método syncSpreadsheetWithDatabase na linha 67 no método main() como mostrado abaixo.

    // syncSpreadsheetWithDatabase(spreadsheet, productKeys);

  • Atualize manualmente as informações sobre o inventário na coluna Estoque da planilha Estatísticas.

Como faço para melhorar o script?

Existem vários modos de melhorar o script. Veja abaixo alguns dos cenários mais comuns:

Inclusão de mais estatísticas na guia "Estatísticas"

  1. Modifique o método retrieveReportStatsForProducts para recuperar novas estatísticas.
  2. Modifique o método saveStatsToSpreadsheet para gravar as novas estatísticas na planilha.
  3. Atualize o intervalo nomeado Estatísticas na planilha para que saveStatsToSpreadsheet possa salvar as novas colunas na planilha.

Use uma estatística diferente para controlar uma regra

Modifique a fórmula da coluna (ou célula) correspondente à regra para se referir à nova coluna de estatísticas.

Adicionar uma nova ação

  1. Insira novas colunas na guia Regras para fazer correspondência com a nova regra.
  2. Modifique o intervalo nomeado RulesData na planilha para que as novas colunas sejam contempladas.
  3. Modifique o método getRulesFromSpreadsheet para ler as colunas adicionais.
  4. Modifique o método applyRules para implementar a lógica da nova ação.

Adicionar novos campos dinâmicos para os personalizadores de anúncios

  1. Exclua o feed do personalizador de anúncios alimentação chamado Descontos manualmente na IU do Google AdWords.
  2. Atualize o método createAdCustomizerSource para inserir novas colunas.
  3. Atualize o método updateAdCustomizerItems para inserir novos valores para as colunas atualizadas.

Programação

Programe a execução do script por hora.

Configuração

  • Configure um script com base em planilha usando o código-fonte abaixo. Use esta planilha de modelo.
  • Atualize a variável SPREADSHEET_URL no seu código com o URL da nova planilha.
  • Atualize a configuração DATABASE_CREDENTIALS no seu código para que o script possa se conectar ao seu banco de dados.
  • [Opcional] Atualize a variável CUSTOMIZER_SOURCE_NAME no seu código para especificar o nome do personalizador de anúncios com o objetivo de armazenar as informações de desconto.
  • [Opcional] Atualize a variável DISCOUNT_LABEL_NAME para especificar o rótulo de identificação de anúncios relacionados aos descontos de produtos.

Código-fonte


// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @name Inventory management script
 *
 * @overview The Inventory management script allows you to manage ads
 *     for your products based on rules specified in a spreadsheet, and
 *     inventory details from your database. See
 *     https://developers.google.com/adwords/scripts/docs/solutions/inventory-management
 *     for more details.
 *
 * @author AdWords Scripts Team [adwords-scripts@googlegroups.com]
 *
 * @version 1.1.1
 *
 * @changelog
 * - version 1.1.1
 *   - Added validation for external spreadsheet setup.
 *   - Updated to use report version v201609.
 * - version 1.1
 *   - Fixed validation and logging errors.
 *
 * - version 1.0
 *   - Released initial version.
 */

// The spreadsheet for loading rules. This should be a copy of
// https://goo.gl/X69Tcu.
var SPREADSHEET_URL = 'YOUR_SPREADSHEET_URL';

// Credentials for connecting to the database.
var DATABASE_CREDENTIALS = {
  ip: 'INSERT_DATABASE_IP_ADDRESS_HERE',
  user: 'INSERT_DATABASE_USERNAME_HERE',
  password: 'INSERT_DATABASE_PASSWORD_HERE',
  database: 'INSERT_DATABASE_NAME_HERE'
};

// The ad customizer name for storing discount information.
var CUSTOMIZER_SOURCE_NAME = 'Discount';

// The label for identifying ads related to product discounts.
var DISCOUNT_LABEL_NAME = 'Discount';

/**
 * Configuration to be used for running reports.
 */
var REPORTING_OPTIONS = {
  // Comment out the following line to default to the latest reporting version.
  apiVersion: 'v201705'
};

function main() {
  ensureLabels([DISCOUNT_LABEL_NAME]);
  var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
  var productCodes = loadProductCodes(spreadsheet);

  saveMetadata(spreadsheet);

  // To update inventory details manually, comment out the line below.
  syncSpreadsheetWithDatabase(spreadsheet, productCodes);

  addReportStatsToSpreadsheet(spreadsheet, productCodes);
  var rules = getRulesFromSpreadsheet(spreadsheet);
  syncDiscountFeed(rules);
  applyRules(rules, spreadsheet, productCodes);
}

/**
 * Checks that the account has all provided labels and creates any that are
 * missing. Since labels cannot be created in preview mode, throws an exception
 * if a label is missing.
 *
 * @param {Array.<string>} labelNames An array of label names.
 */
function ensureLabels(labelNames) {
  for (var i = 0; i < labelNames.length; i++) {
    var labelName = labelNames[i];
    var label = AdWordsApp.labels()
      .withCondition('Name = "' + labelName + '"')
      .get();

    if (!label.hasNext()) {
      if (!AdWordsApp.getExecutionInfo().isPreview()) {
        AdWordsApp.createLabel(labelName);
      } else {
        throw 'Label ' + labelName + ' is missing and cannot be created in ' +
            'preview mode. Please run the script or create the label manually.';
      }
    }
  }
}

/**
 * Save metadata information on the spreadsheet.
 *
 * @param {Spreadsheet} spreadsheet The spreadsheet with product information
 *     and rules.
 */
function saveMetadata(spreadsheet) {
  var customerId = spreadsheet.getRangeByName('CustomerId');
  customerId.setValue(AdWordsApp.currentAccount().getCustomerId());

  var lastUpdated = spreadsheet.getRangeByName('LastUpdated');
  lastUpdated.setValue(new Date());
}

/**
 * Sync inventory details from the database to the spreadsheet.
 *
 * @param {Spreadsheet} spreadsheet The spreadsheet with product information
 *     and rules.
 * @param {Array.<string>} productCodes The list of all product codes.
 */
function syncSpreadsheetWithDatabase(spreadsheet, productCodes) {
  var items = fetchInventoryDetailsFromDatabase();
  saveInventoryDetailsToSpreadsheet(items, spreadsheet, productCodes);
}

/**
 * Fetches inventory details from the database to the spreadsheet.
 *
 * @return {Object.<string, number>} A map with key as product code and value
 *     as inventory stock details.
 */
function fetchInventoryDetailsFromDatabase() {
  Logger.log('Retrieving inventory stock details from database...');

  var retval = {};

  var dbUrl = 'jdbc:mysql://' + DATABASE_CREDENTIALS.ip + '/' +
      DATABASE_CREDENTIALS.database;
  var conn = Jdbc.getConnection(dbUrl, DATABASE_CREDENTIALS.user,
      DATABASE_CREDENTIALS.password);

  var stmt = conn.createStatement();
  var results = stmt.executeQuery('Select code, stock from items');

  while (results.next()) {
    var rowString = '';
    var code = results.getString('code');
    var stock = results.getString('stock');
    if (retval[code]) {
      throw 'The inventory database has multiple rows for product with ' +
          'code : ' + code + '. Please fix your database.';
    }
    retval[code] = stock;
  }

  results.close();
  stmt.close();

  Logger.log('Done');

  return retval;
}

/**
 * Saves inventory details from the database to the spreadsheet.
 *
 * @param {Object.<string, number>} items A map with key as the product code,
 *     and value as the inventory stock detail.
 * @param {Spreadsheet} spreadsheet The spreadsheet with product information
 *     and rules.
 * @param {Array.<string>} productCodes The list of all product codes.
 */
function saveInventoryDetailsToSpreadsheet(items, spreadsheet, productCodes) {
  Logger.log('Saving inventory stock details to spreadsheet...');

  var range = spreadsheet.getRangeByName('DbData');
  var data = range.getValues();

  for (var i = 0; i < productCodes.length; i++) {
    var productCode = productCodes[i];
    data[i][0] = items[productCode];
  }
  range.setValues(data);
  Logger.log('Done');
}

/**
 * Adds report stats to the spreadsheet.
 *
 * @param {Spreadsheet} spreadsheet The spreadsheet with product information
 *     and rules.
 * @param {Array.<string>} productCodes The list of all product codes.
 */
function addReportStatsToSpreadsheet(spreadsheet, productCodes) {
  var statsMap = retrieveReportStatsForProducts(productCodes);
  saveStatsToSpreadsheet(statsMap, spreadsheet, productCodes);
}

/**
 * Retrieve report stats for the products.
 *
 * @param {Array.<string>} productCodes The list of all product codes.
 *
 * @return {Object.<string, Object>} A map, with key as the product code, and
 *     value as a stats object for that product.
 */
function retrieveReportStatsForProducts(productCodes) {
  Logger.log('Retrieving report stats...');

  var adGroupMap = {};
  var statsMap = {};

  for (var i = 0; i < productCodes.length; i++) {
    var productCode = productCodes[i];
    var adGroup = getTargetAdGroupForProduct(productCode);
    if (adGroup) {
      adGroupMap[adGroup.getId()] = productCode;
    }
  }

  var report = AdWordsApp.report('Select AdGroupId, Clicks, Impressions, ' +
      'Cost, AverageCpc from ADGROUP_PERFORMANCE_REPORT during ' +
      'LAST_7_DAYS', REPORTING_OPTIONS).rows();

  while (report.hasNext()) {
    var reportRow = report.next();
    var adGroupId = reportRow['AdGroupId'];

    if (adGroupId in adGroupMap) {
      var code = adGroupMap[adGroupId];
      var stats = {
        clicks: reportRow['Clicks'],
        impressions: reportRow['Impressions'],
        cost: reportRow['Cost'],
        averageCpc: reportRow['AverageCpc'],
        adGroupId: adGroupId,
        code: code
      };

      statsMap[code] = stats;
    }
  }

  Logger.log('Done');

  return statsMap;
}

/**
 * Save report stats to the spreadsheet.
 *
 * @param {Object.<string, Object>} statsMap A map, with key as the product
 *    code, and value as a stats object for that product.
 * @param {Spreadsheet} spreadsheet The spreadsheet with product information
 *     and rules.
 * @param {Array.<string>} productCodes The list of all product codes.
 */
function saveStatsToSpreadsheet(statsMap, spreadsheet, productCodes) {
  Logger.log('Saving report stats to spreadsheet...');

  range = spreadsheet.getRangeByName('Stats');
  data = range.getValues();

  for (var i = 0; i < productCodes.length; i++) {
    var code = productCodes[i];
    if (code in statsMap) {
      var stats = statsMap[code];
      data[i][0] = stats.clicks;
      data[i][1] = stats.impressions;
      data[i][2] = stats.cost;
      data[i][3] = stats.averageCpc;
    } else {
      data[i][0] = 0;
      data[i][1] = 0;
      data[i][2] = 0;
      data[i][3] = 0;
    }
  }
  range.setValues(data);

  Logger.log('Done');
}

/**
 * Retrieves the product rules from the spreadsheet.
 *
 * @param {Spreadsheet} spreadsheet The spreadsheet with product information
 *     and rules.
 *
 * @return {Array.<Object>} The list of rules.
 */
function getRulesFromSpreadsheet(spreadsheet) {
  Logger.log('Loading rules from database...');

  var retval = [];
  range = spreadsheet.getRangeByName('RulesData');
  data = range.getValues();

  // Ensure that there's exactly one rule row per product code.
  var codeMap = {};

  for (var i = 0; i < data.length; i++) {
    var code = data[i][0];

    // Skip blank code.
    if (!code) {
      continue;
    }

    if (codeMap[code]) {
      throw 'The rules spreadsheet has multiple rows for product with ' +
          'code : ' + code + '. Only one row per product code is allowed. ' +
          'Please fix your spreadsheet.';
    } else {
      codeMap[code] = true;
    }

    retval.push({
      'code': code,
      'name': data[i][1],
      'enable': data[i][2],
      'runAds': data[i][3],
      'runDiscount': data[i][4],
      'discount': data[i][5],
      'coupon': data[i][6],
      'setMobileBidModifier': data[i][7],
      'mobileBidModifier': data[i][8],
    });
  }

  Logger.log('Done');
  return retval;
}


/**
 * Syncs the discount feed with the spreadsheet.
 *
 * @param {Array.<Object>} rules The list of rules from the spreadsheet.
 */
function syncDiscountFeed(rules) {
  var source = createDiscountCustomizerSourceIfRequired();
  updateAdCustomizerItems(source, rules);
}

/**
 * Creates the ad customizer source for storing discount information if it
 * is missing in the account.
 *
 * @return {AdWordsApp.AdCustomizerSource} The ad customizer source.
 */
function createDiscountCustomizerSourceIfRequired() {
  Logger.log('Retrieving ad customizer source for discounts...');

  var source = getAdCustomizerSource();
  if (!source) {
    source = createAdCustomizerSource();
  }

  Logger.log('Done');

  return source;
}

/**
 * Retrieves an existing ad customizer source for storing discount details.
 *
 * @return {AdWordsApp.AdCustomizerSource?} The ad customizer source if one
 *     exists, null otherwise.
 */
function getAdCustomizerSource() {
  var sources = AdWordsApp.adCustomizerSources().get();
  while (sources.hasNext()) {
    var source = sources.next();
    if (source.getName() == CUSTOMIZER_SOURCE_NAME) {
      return source;
    }
  }
  return null;
}

/**
 * Creates an ad customizer source for storing discount details.
 *
 * @return {AdWordsApp.AdCustomizerSource?} The newly created ad customizer
 *     source.
 */
function createAdCustomizerSource() {
  Logger.log('Creating a new ad customizer source for discounts...');

  if (AdWordsApp.getExecutionInfo().isPreview()) {
    Logger.log('This step won\'t work with preview mode, and your script may ' +
        'fail at a later stage. Try running this script normally.');
  }

  var source = AdWordsApp.newAdCustomizerSourceBuilder()
      .withName(CUSTOMIZER_SOURCE_NAME)
      .addAttribute('code', 'text')
      .addAttribute('coupon', 'text')
      .addAttribute('discount', 'number')
      .addAttribute('name', 'text')
      .build().getResult();

  Logger.log('Done');

  return source;
}

/**
 * Updates the ad customizer items with the latest discount details from the
 *     spreadsheet.
 *
 * @param {AdWordsApp.AdCustomizerSource} source the ad customizer source that
 *     stores discount details.
 * @param {Array.<Object>} rules The list of rules from the spreadsheet.
 */
function updateAdCustomizerItems(source, rules) {
  Logger.log('Syncing the discount ad customizer source with the ' +
      'spreadsheet data...');

  var itemMap = {};

  // Store the existing ad customizer items into a
  // lookup map.
  var items = source.items().get();
  while (items.hasNext()) {
    var item = items.next();
    var productCode = item.getAttributeValue('code');
    itemMap[productCode] = item;
  }

  for (var i = 0; i < rules.length; i++) {
    // For each rule, see if there's an existing customizer
    // item by matching the item code.
    var rule = rules[i];
    var item = itemMap[rule.code];

    if (!item) {
      // This is a new item that was added to the spreadsheet.
      // Create a new customizer item for this code.
      item = source.adCustomizerItemBuilder()
      .withAttributeValue('code', rule.code)
      .withAttributeValue('discount', rule.discount)
      .withAttributeValue('coupon', rule.coupon)
      .withAttributeValue('name', rule.name)
      .build()
      .getResult();
    } else {
      // Update this customizer item with the latest details
      // from the spreadsheet.
      item.setAttributeValue('discount', rule.discount);
      item.setAttributeValue('coupon', rule.coupon);
      item.setAttributeValue('name', rule.name);
    }

    // Load the target ad group for this product.
    var adGroup = getTargetAdGroupForProduct(rule.code);

    if (adGroup) {
      // Target this ad customizer item to only the ad group for this product.
      item.setTargetAdGroup(adGroup.getCampaign().getName(), adGroup.getName());
    } else {
      // The target ad group for the product could not be found. So the
      // corresponding ad customizer item should also be deleted to prevent it
      // from ever serving.
      item.remove();
    }

    // Remove the processed product code from the lookup map.
    delete itemMap[rule.code];
  }

  // Whatever left over in itemMap should be deleted, since these products are
  // missing in the spreadsheet.
  for (productCode in itemMap) {
    Logger.log("Removing item that's not on spreadsheet: %s", productCode);
    itemMap[productCode].remove();
  }

  Logger.log('Done');
}

/**
 * Applies product rules to the account.
 *
 * @param {Array.<Object>} rules The list of rules to be applied.
 * @param {Spreadsheet} spreadsheet The spreadsheet with product information
 *     and rules.
 * @param {Array.<string>} productCodes The list of all product codes.
 */
function applyRules(rules, spreadsheet, productCodes) {
  Logger.log('Applying rules...');

  var timestamps = loadTimestamps(spreadsheet, productCodes);

  for (var i = 0; i < rules.length; i++) {
    var rule = rules[i];
    try {
      if (rule.enable != 'Yes') {
        continue;
      }

      var adGroup = getTargetAdGroupForProduct(rule.code);

      if (adGroup) {
        var discountAds = getDiscountAds(adGroup);

        while (discountAds.hasNext()) {
          var discountAd = discountAds.next();
          // Run discounts or not?

          if (rule.runDiscount == 'Yes' && !discountAd.isEnabled()) {
            Logger.log('  - Enabling discount ads for ' + rule.code);
            discountAd.enable();
          } else if (rule.runDiscount == 'No' && !discountAd.isPaused()) {
            Logger.log('  - Disabling discount ads for ' + rule.code);
            discountAd.pause();
          }
        }

        // Run ads or not?
        if (rule.runAds == 'Yes' && !adGroup.isEnabled()) {
          Logger.log('  - Enabling ad group for ' + rule.code);
          adGroup.enable();
        } else if (rule.runAds == 'No' && adGroup.isEnabled()) {
          Logger.log('  - Disabling ad group for ' + rule.code);
          adGroup.pause();
        }

        // Set mobile bid modifier or not?
        if (rule.setMobileBidModifier == 'Yes' &&
            adGroup.getMobileBidModifier() != rule.mobileBidModifier) {
          Logger.log('  - Setting mobile bid modifier for ' + rule.code);
          adGroup.setMobileBidModifier(rule.mobileBidModifier);
        }

        timestamps[rule.code] = new Date();
      }
    } catch (err) {
      Logger.log('An error occurred when running rule for product ' +
          'code "%s". "%s"', rule.code, err);
    }
  }

  saveTimestampsToDatabase(timestamps, spreadsheet, productCodes);

  Logger.log('Done');
}

/**
 * Gets the ads for discounts from an ad group. A new discount ad is created if
 *     no discount ads are found.
 *
 * @param {AdWordsApp.AdGroup} adGroup The ad group from which ads are
 *    retrieved.
 *
 * @return {AdWordsApp.AdIterator} An iterator for discount ads.
 */
function getDiscountAds(adGroup) {
  var ads = adGroup.ads().withCondition('LabelNames CONTAINS_ANY ["' +
      DISCOUNT_LABEL_NAME + '"]').get();
  if (ads.hasNext()) {
    return ads;
  } else {
    // Create a new discount ad that dynamically update values based
    // on values available in the Discount ad customizer source.
    var ad = adGroup.newTextAdBuilder()
        .withHeadline('{=Discount.name} for sale')
        .withDescription1('Get {=Discount.discount}% off per dozen!')
        .withDescription2('Use promo code {=Discount.coupon}.')
        .withDisplayUrl('example.com/flowers')
        .withFinalUrl('http://example.com/flowers')
        .build().getResult();
    ad.applyLabel(DISCOUNT_LABEL_NAME);
  }
  return adGroup.ads().withCondition('LabelNames CONTAINS_ANY ["' +
      DISCOUNT_LABEL_NAME + '"]').get();
}

/**
 * Gets the target ad group for a product. The script determines target ad
 * group by finding the one ad group that has the product key as a label.
 *
 * @param {string} code The product code.
 *
 * @return {AdWordsApp.AdGroup} The ad group for this product or null if the
 *     ad group cannot be found.
 */
function getTargetAdGroupForProduct(code) {
  var labelIterator = AdWordsApp.labels().withCondition(
      'Name = "' + code + '"').get();
  if (!labelIterator.hasNext()) {
    Logger.log('Label not found for product code : %s. Make sure you ' +
        'create a label of name "%s" and apply the label to exactly one ' +
        'ad group that corresponds to this product.', code, code);
    return null;
  }
  var label = labelIterator.next();

  // A product may have a single adgroup, and that should be labelled
  // with the product code.

  var adGroups = label.adGroups().get();
  if (adGroups.totalNumEntities() == 1) {
    return adGroups.next();
  } else {
    Logger.log('Ad group not found for product code : %s. Make sure you ' +
        'create a label of name "%s" and apply the label to exactly one ' +
        'ad group that corresponds to this product', code, code);
      return null;
  }
}

/**
 * Loads product codes from the spreadsheet.
 *
 * @param {Spreadsheet} spreadsheet The spreadsheet with product information
 *     and rules.
 *
 * @return {Array.<string>} The list of product codes.
 */
function loadProductCodes(spreadsheet) {
  var retval = [];

  var range = spreadsheet.getRangeByName('ProductCodes');
  var data = range.getValues();

  for (var i = 0; i < data.length; i++) {
    if (data[i][0].toString()) {
      retval.push(data[i][0]);
    }
  }
  return retval;
}

/**
 * Loads rule timestamps from the spreadsheet.
 *
 * @param {Spreadsheet} spreadsheet The spreadsheet with product information
 *     and rules.
 *
 * @param {Array.<string>} productCodes The list of all product codes.
 *
 * @return {Object<string, string>} A map with product code as key and last
 *     execution time of the corresponding rule as value.
 */
function loadTimestamps(spreadsheet, productCodes) {
  var retval = {};

  var range = spreadsheet.getRangeByName('LastUpdated');
  var data = range.getValues();

  for (var i = 0; i < productCodes.length; i++) {
    var productCode = productCodes[i];

    if (data.length > i) {
      retval[productCode] = data[i][0];
    } else {
      retval[productCode] = '';
    }
  }
  return retval;
}

/**
 * Saves rule timestamps to the spreadsheet.
 *
 * @param {Object.<string, Date>} items A dictionary, with key as the product
 *     code and value as the timestamp at which the rule for that product was
 *     executed.
 * @param {Spreadsheet} spreadsheet The spreadsheet with product information
 *     and rules.
 * @param {Array.<string>} productCodes The list of all product codes.
 */
function saveTimestampsToDatabase(items, spreadsheet, productCodes) {
  var range = spreadsheet.getRangeByName('LastUpdated');
  var data = range.getValues();
  for (var i = 0; i < productCodes.length; i++) {
    var productCode = productCodes[i];
    data[i][0] = items[productCode];
  }
  range.setValues(data);
}

/**
 * Validates the provided spreadsheet URL to make sure that it's set up
 * properly. Throws a descriptive error message if validation fails.
 *
 * @param {string} spreadsheeturl The URL of the spreadsheet to open.
 * @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
 * @throws {Error} If the spreadsheet URL hasn't been set
 */
function validateAndGetSpreadsheet(spreadsheeturl) {
  if (spreadsheeturl == 'YOUR_SPREADSHEET_URL') {
    throw new Error('Please specify a valid Spreadsheet URL. You can find' +
        ' a link to a template in the associated guide for this script.');
  }
  return SpreadsheetApp.openByUrl(spreadsheeturl);
}

Enviar comentários sobre…

Precisa de ajuda? Acesse nossa página de suporte.