segunda-feira, 10 de maio de 2021

[CRM-GT] - Javascript Functions (SDK)

Este artigo tem como objetivo documentar o uso de um importante recurso do meu sistema de gerenciamento com o cliente, o CRM-GT. Eu criei diversas funções Javascript para facilitar as customizações do sistema (client side).

Vamos a elas!





CRMGT.UserConfig

Namespace: CRMGT.UserConfig
Obtenha várias informações sobre o usuário logado no sistema, como:
  • userId - Guid do usuário
  • userName - Nome do usuário
  • userEmail – E-mail do usuário
  • orgName – Nome da Organização do usuário
  • userProfile – Json contendo uma lista com todas as informações do perfil do usuário (ids, nomes dos departamentos/funções nos departamentos)

Segue um exemplo de retorno das propriedades citadas acima no Console do browser:



CRMGT.Env

Namespace: CRMGT.Env
  • Suspende a execução de um código até que o tempo limite seja alcançado (parâmetro passado, em milisegundos)


Namespace: CRMGT.Env
  • Retorna true/false


Namespace: CRMGT.Env
  • Retorna true/false


Namespace: CRMGT.Env
  • Retorna true/false


Namespace: CRMGT.Env
  • Retorna true/false


Namespace: CRMGT.Env
  • Retorna true/false


Namespace: CRMGT.Env
  • Retorna o nome da organização corrente


Namespace: CRMGT.Env
  • Retorna o idioma corrente do site (ptBr, enUs, etc)


Namespace: CRMGT.Env
  • Ativa / Desativa o resize automático de uma página do site


Namespace: CRMGT.Env
  • True/false – Consulta se pode efetuar o resize de uma página do site


Namespace: CRMGT.Env
  • Retorna uma data passada por parâmetro no formato ISO (International Organization for Standardization), levando em consideração o idioma corrente (CRMGT.Env.Language()):
  • Exemplo de retorno: 2019-06-21T15:32:00
  • Exemplo para atualização de um campo no form do tipo [DATETIME]
    CRMGT.Page.Util.SetAttributeValue("resolutionDateTime", CRMGT.Env.FormatDateStringToISO(new Date()));
    
  • Para atualização de um campo do tipo [DATE] somente, passe diretamente a data desejada no formato YYYY-MM-DD
    Exemplo:
    var currDate = new Date();
    var currDateOnly = `${currDate.getFullYear()}-${(currDate.getMonth() + 1).padLeft(2)}-${(currDate.getDate()).padLeft(2)}`;
    CRMGT.Page.Util.SetAttributeValue("dateOnlyTest", currDateOnly);
    

CRMGT.Page.Util
Utilize as diversas funções abaixo para busca e manipulação de dados nos formulários do sistema.

Note que em muitas das funções existe um parâmetro booleano chamado [fromParentRecord].
Ele é útil quando precisamos buscar algum dado da tela [anterior].
Por exemplo, ao editar um registro de contato, no final da tela existe a seção [Anotações e Visão 360]. Nesta seção temos abas (com subGrids) que mostram diversas atividades relacionadas ao contato. Você pode, neste ponto, criar um novo registro de atividade relacionado. Neste momento um popup se abre para o cadastro. É neste ponto que podemos buscar algum dado da tela [pai] (no nosso ex, de contato) para preencher na tela de popup usando então o parâmetro [fromParentRecord como true].


Namespace: CRMGT.Page.Util
Utilize este método para fazer o download de um arquivo Excel a partir de um conteúdo JSON, passado como parâmetro.
Segue um exemplo de utilização:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Exportar para Excel com Data</title>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src='https://crmgt.net/js/CRMGT.js'></script>
  
</head>
<body>

<button onclick="exportarParaExcel()">Exportar Excel</button>

<script>
function exportarParaExcel() {
const dados = [
{ Nome: "João", Data: "26/06/2024" },
{ Nome: "Maria", Data: "27/06/2024 15:45" }
];

CRMGT.Page.Util.ExportJsonToExcel(dados, "relatorio");
}
</script>

</body>
</html> 

Note a importância da referência das bibliotecas [jquery-x.min.js] e [CRMGT.js].

O método converte campos de [data somente] e [data e hora], usando os formatos:
- dd/MM/YYYY (ou MM/dd/YYYY se a data estiver no formato US)
- dd/MM/YYYY HH:mm (ou MM/dd/YYYY HH:mm se a data estiver no formato US)


Namespace: CRMGT.Page.Util
Utilize este método para retornar um ZIP (em forma de download) de todos os anexos relacionados a uma tabela do sistema, para um ou vários registros.
Passe como parâmetro o nome desejado do arquivo ZIP, o nome da tabela para busca dos anexos (schemaName da tabela) e o ID do registro (mais de um registro, separe-os por vírgula).


Namespace: CRMGT.Page.Util
Utilize este método se precisar passar alguma string complexa, com caractéres especiais, acentos, conteúdo html, símbolos, etc, diretamente para ser atualizada no banco de dados do CRM.
Isto pode acontecer, por exemplo, se usarmos o método QueryExecData, passando como parâmetro esta string complexa para alguma QueryExec efetuar um update.
Como esta string vai estar no formato Base64, o local que vai receber este valor precisa convertê-la.
Pensando no exemplo de uma QueryExec, podemos, via comando de banco de dados, converter da seguinte forma:

Exemplo em MySQL:
SET @convertedValue = SELECT CAST(FROM_BASE64(@SuaVarNaBase64) AS CHAR);

Se estiver usando o banco de dados SQL SERVER no produto, esta função (EncodingUTF8Base64) não é necessária. Utilize o comando javascript [window.btoa()] para converter para a base64, e no Sql Server converta da seguinte forma:
DECLARE @convertedValue VARCHAR(MAX) = (SELECT CONVERT(
VARCHAR(MAX),
CAST(N'' AS XML).value('xs:base64Binary(sql:column("base64Value"))', 'varbinary(max)')
)
FROM (SELECT @SuaVarNaBase64 base64Value) t);


Namespace: CRMGT.Page.Util
Todos os campos alterados do formulário são rastreados e salvos quando o usuário clica no botão Salvar.
Se precisar pedir para o sistema ignorar, não rastrear um determinado campo ao salvar, adicione este método no Onload do form, passando o id do campo como parâmetro.


Namespace: CRMGT.Page.Util
Utilize esta função quando precisar copiar todos os campos da tela [pai] quando estiver criando um registro de [subGrid].
A função vai buscar todos os campos [iguais] da tela relacionada na tela pai.

Exemplo, você está editando um registro de [incident].
No final do formulário existe a seção [Anotações e Visão 360].
Uma das abas desta seção é o relacionamento da incident com ela mesma; o nome da aba está geralmente como [Chamados].
Quando você clicar para criar um novo chamado relacionado (botão [criar] do subGrid), neste ponto você pode precisar copiar todos os campos do chamado principal neste chamado relacionado.
A função [CopyFieldsFromParentForm] vai então fazer este mapeamento/cópia.

Se precisar pedir para a função [ignorar algum campo, ou vários], passe o nome dos campos no parâmetro [exceptions], separados por vírgula.


Namespace: CRMGT.Page.Util
Por padrão, ao adicionar um item em um dos sub-Grids no formulário, aparece a mensagem para o usuário salvar o registro principal do form para de fato salvar todos os itens inseridos.
Com esta opção de SDK podemos alterar este comportamento.
Passando o param [value] como true, dizemos para o sistema salvar automaticamente o registro principal a [cada] item inserido no sub-Grid.
Também é opcional se deseja dar um [refresh] no form (param refreshForm).
Note que, se o usuário estiver [criando um novo] registro principal, ao adicionar o [primeiro item] de um sub-Grid, o sistema faz o refresh automaticamente.


Namespace: CRMGT.Page.Util
O valor passado como parâmetro é retornado como um valor criptografado (modelo de criptografia AES).


Namespace: CRMGT.Page.Util
Retorna um Token para acesso ao Conector de Dados** do CRM-GT.
** veja em Funcionalidades -> Conector de Dados

Segue um exemplo de execução e o retorno do token:

Passe as credentiais (clientId e ClientSecret) convertidas na [base64 string], através da função BTOA conforme destacado na imagem acima.
Estas credenciais são criadas pelo [administrador] do sistema e repassada para os desenvolvedores/clientes.
O retorno é um JSON contendo o token, ou o detalhe do erro na tentativa de obtenção do token.


Namespace: CRMGT.Page.Util
Retorna um Token [renovado] para acesso ao conector do CRM-GT.
Utilize o exemplo da função [GetToken] acima para a execução do [RenewToken].


Namespace: CRMGT.Page.Util
Para tratarmos falhas na execução da webAPI, por falha de Token, podemos usar a func [CheckAccessToken]
Então, dentro do callback [success] do ajax, você pode adicionar o seguinte código:
},
success: function (data, textStatus, jqXHR) {
let checkConnection = CRMGT.Page.Util.CheckAccessToken(data, organization, clientId, clientSecret);
if (checkConnection.IsInvalid) {
token = checkConnection.RenewedToken;
ReExecutaSuaFuncaoAqui();
}
Se o Token for inválido, a função traz um novo Token (RenewedToken).
Atualize sua variável de token com a nova e execute novamente sua função conforme indicado (ReExecutaSuaFuncaoAqui()).


Namespace: CRMGT.Page.Util
Retorna o [valor] de um parâmetro da [Query String] da página corrente - ou da janela superior do navegador (window.top).


Namespace: CRMGT.Page.Util
Retorna o contentDocument (DOM) de uma página HTML aberta pelo método ShowDialog (que é explicado em detalhes nesta documentação).


Namespace: CRMGT.Page.Util
Execute este método para dar um Refresh na página.


Namespace: CRMGT.Page.Util
Execute este método para dar um Refresh em uma Lista de Registros.


Namespace: CRMGT.Page.Util
Execute este método para salvar o registro corrente.
Se precisar redirecionar a tela para a lista de registros após o Save, passe [true] para o parâmetro [redirectToList].


Namespace: CRMGT.Page.Util
Execute este método para salvar o registro corrente e preparar a página para um novo cadastro.


Namespace: CRMGT.Page.Util
Execute este método para salvar o registro de um [sub-Grid] relacionado em um registro principal.
Exemplo, podemos pedir para o sistema salvar uma [Linha de Contrato], fechar o popup e salvar o Form Pai (de Contrato)
CRMGT.Page.Util.SaveGridEdit(true);


Namespace: CRMGT.Page.Util
Execute este método para converter corretamente uma string para um valor decimal.
Por padrão, [pt-BR] é o formato usado. Para usar outro formato passe o desejado no parâmetro [valueFormattedAs].
Exemplo:
var pricelist = CRMGT.Page.Util.GetAttribute("pricelist");
var pricelistValue = CRMGT.Page.Util.ParseValueToDecimalString(pricelist.val(), 2);


Namespace: CRMGT.Page.Util
Execute este método para adicionar uma [máscara] formatada no seu campo de valor
Por padrão, [#.##0,00] é a máscara usada. Para usar outra máscara passe o desejado no parâmetro [format].
Exemplo:
CRMGT.Page.Util.FormatDecimalField("totalamount");


Namespace: CRMGT.Page.Util
Método que auxilia na inserção de função Javascript customizada, que será executada ao clicar nos botões [Criar], [Editar] ou [Excluir] da toolbar de uma [Sub-Grid].
Adicione este método no [OnLoad] do formulário.
Temos os seguintes parâmetros:
  • subGridId - representa o [id] do campo criado como tipo [Relacionamento N-N]
    Por exemplo, na tabela de Oportunidade temos, na lista de campos criados, um do tipo [ComplexRelationship] chamado [opportunityproduct], que é um SubGrid de [Produtos da Oportunidade]. Portanto subGridId será "opportunityproduct".
  • btnType - Representa o tipo de botão onde adicionaremos a chamada do JS customizado, onde podemos passar o valor [add], [edit] ou [delete]
  • funcName - O nome da função desejada para ser executada ao clicar no botão

Exemplo, ao clicar no botão [Criar] de um sub-grid de produtos da oportunidade, precisamos executar um método JS chamado "CanCreateNewItem" (que deve estar localizado no [OnLoad] do form).
CRMGT.Page.Util.GridViewButtonsOnClick("opportunityproduct", "add", "CanCreateNewItem()");

Se desejarmos [interromper] a ação do [clique] do botão, podemos usar CRMGT.Page.Util.GridViewButtonResult(false); dentro do método customizado (no exemplo, dentro de "CanCreateNewItem")

Se precisarmos fazer uma pergunta de decisão, onde somente após o clique no botão [sim] é que poderemos abrir a tela popup do botão do sub-grid [criar], adicionamos no código:
  • CRMGT.Page.Util.GridViewButtonConfirmDialogMessage("Sua Mensagem de Confirmação");
  • CRMGT.Page.Util.GridViewButtonAdditionalFuncToRun("customFunc()");
    Quando o usuário clicar no botão de confirmação [sim] você pode executar uma função sua customizada antes de abrir o popup.

Importante: No nosso exemplo, os métodos [GridViewButtonConfirmDialogMessage] e [GridViewButtonAdditionalFuncToRun] deverão ser adicionados dentro da função CanCreateNewItem

Se precisarmos atualizar o conteúdo de um subGrid (refresh), adicionamos no código:
  • CRMGT.Page.Util.ReloadGridView(relationshipTableId, fieldId, parentId, parentIdName);
  • Onde:
  • relationshipTableId - representa o Metadata ID da tabela associada ao subGrid (obtemos este id acessando a tabela interna do sistema chamada Entity, campo [entityId])
  • fieldId - representa o ID do subGrid (este id é obtido em Configurações -> Tabelas do Sistema, aba Campos, do tipo [complexrelationship])
  • parentId - representa o ID da tabela pai onde o subGrid está associado (por exemplo, temos um subGrid de Produtos da Oportunidade associado a tabela pai [Oportunidade]). Geralmente podemos obter este id executando CRMGT.Page.Util.GetEntityRecordId()
  • parentIdName - representa o nome do campo chave da tabela pai onde o subGrid está associado (no exemplo citado acima para o parâmetro parentId, teremos como parentIdName [opportunityid])


Namespace: CRMGT.Page.Util
  • Método para converter uma string em Boolean.


Namespace: CRMGT.Page.Util
  • Executa uma REST API e retorna o resultado da mesma.


Namespace: CRMGT.Page.Util
  • Método para retornar a diferença entre duas datas.
  • No parâmetro [datePart] podemos passar como desejamos a diferença, sendo possível em:
    Day
    Month
    Year
    Hour
    Minute
    Second


Namespace: CRMGT.Page.Util
  • Método para verificar se o valor (ou objeto) passado como parâmetro está vazio ou nulo.


Namespace: CRMGT.Page.Util
  • Use esta função para bloquear um cadastro em duplicidade no sistema.
  • Informe a tabela desejada em os campos para busca no banco de dados.
  • Segue um exemplo de uso, no [OnSave] do formulário:
    if (CRMGT.Page.Util.GetCanSaveRecord()) {
    var _name = $("#name").val();
    var _accountnumber = $("#accountnumber").val();
    
    var jsonFields = [];
    jsonFields.push({ "fieldname":"name", "fieldtype": "varchar",  "fieldvalue": _name });
    jsonFields.push({ "fieldname":"accountnumber", "fieldtype": "varchar", "fieldvalue": _accountnumber });
    
    CRMGT.Page.Util.SetCanSaveRecord(true);
    
    if (CRMGT.Page.Util.HasDuplicateDetection("account", JSON.stringify(jsonFields))) {
    CRMGT.Page.Util.SetCanSaveRecord(false);
    var msg = "Detecção de Duplicidade<br/><br/>";
    msg += "Já existe uma Conta cadastrada com os valores:<br/>";
    msg += "Nome: " + _name + "<br/>";
    msg += "Número da Conta: " + _accountnumber + "<br/>";
    ShowAlert(msg, 500, false, "font-size:18px;");
    }
    }
    
  • Neste exemplo, se existir um registro com o mesmo valor dos campos [name e accountNumber] o registro não é salvo (através do uso do método [SetCanSaveRecord] com o parâmetro "false").


Namespace: CRMGT.Page.Util
  • Método para ser usado no [OnLoad] do formulário.
  • Passando o parâmetro [true], ao salvar um [novo] registro, ou alterar um registro [existente], todos os Workflows associados ao registro serão executados imediatamente.
  • Lembrando que serão executados os workflows correspondentes ao [modo] em que o registro está sendo salvo.
  • Por exemplo, ao entrar em um registro existente, ao salvar, somente serão executados os workflows com a configuração de disparo (trigger) no modo [Atualização].
Se existirem subGrids associados no formulário, o sistema também vai executar os workflows (trigger de criação) relacionados as tabelas destes subGrids. Por exemplo, no formulário de Oportunidade vai existir um subGrid de Produtos da Oportunidade. Portanto os workflows associados na tabela [produto da oportunidade] serão também executados de forma síncrona.


Namespace: CRMGT.Page.Util
  • Retorna o conteúdo de um anexo, como uma string na base64.
  • Passe como parâmetro o ID da AttachmentNote, que é a tabela que guarda os anexos.


Namespace: CRMGT.Page.Util
  • Retorna true|false se existe pelo menos 1 anexo no formulário.


Namespace: CRMGT.Page.Util
  • Retorna true|false se existe pelo menos 1 anexo no componente [Time Line Annotation] do formulário.


Namespace: CRMGT.Page.Util
  • Não permite avançar um passo do BPF se não existir pelo menos 1 anexo no componente [Time Line Annotation] do formulário.
    Parâmetros:
    businessProcessStepId - o GUID do processo desejado (buscar diretamente na tabela BusinessProcessStep)
    message - ex: "Informe um Anexo..."


Namespace: CRMGT.Page.Util
  • Adiciona uma quebra de linha, no formulário, [após] o campo especificado por parâmetro
    Exemplo: Após o campo [endereço] reposicionar os próximos na próxima linha da tela:


Namespace: CRMGT.Page.Util
  • Mantém o campo (passado por parâmetro) como único na linha de campos do formulário
    Exemplo:


Namespace: CRMGT.Page.Util
  • Permite esconder/mostrar uma aba do formulário.
    Passe como [id] a descrição da aba que deseja interagir.


Namespace: CRMGT.Page.Util
  • Permite esconder/mostrar uma aba da área [Visão 360] do formulário de edição.
    Passe como [tabLabel] a descrição da aba que deseja interagir.


Namespace: CRMGT.Page.Util
  • Permite esconder/mostrar um campo do formulário
  • Para esconder um campo na área de [Business Process Flow] habilitada na tela, utilize HideBPFAttribute(id, hide)
  • Para esconder um campo do tipo [richText] passe o id concatenado desta forma: "richTextArea_" + id


Namespace: CRMGT.Page.Util
  • Altera o atributo [required] de um campo no formulário
  • Para campos do tipo [richText] passe o id concatenado desta forma: "richTextArea_" + id


Namespace: CRMGT.Page.Util
  • Desabilita um campo no formulário
  • Para desabilitar um campo na área de [Business Process Flow] habilitada na tela, utilize DisableBPFAttribute(id, disable)


Namespace: CRMGT.Page.Util
  • Desabilita todos os elementos do formulário, incluindo richText e ações nos subGrids.


Namespace: CRMGT.Page.Util
  • Altera o Label de um campo no formulário


Namespace: CRMGT.Page.Util
  • Preenche um lookup, com seu Guid/Texto
  • Para preencher um lookup na área de [Business Process Flow] habilitada na tela, utilize SetBPFLookupValue(lookupId, lookupValue, lookupLabel)
  • Exemplo:
    CRMGT.Page.Util.SetLookupValue("accountid", "4f0e7903-5e54-4a11-b596-816a4546fe54", "Conta LTDA");
    


Namespace: CRMGT.Page.Util
  • Preenche um [Multi]Lookup, com seu Guid/Texto
  • 
    Parâmetro lookupId
    ------------------------
    Nome do campo lookup desejado (schema name)
    
    Parâmetro lookupValue
    ------------------------
    [Name da Tabela do Lookup] + [|] + [Guid da Tabela do Lookup] + [|] + [Guid da Entidade do Lookup] + [,] + [...]
    
    Exemplo: Conta1|13a3ef4f-d0de-40fb-a7a5-7568ae7ba542|585E0EEA-362F-423D-AEB3-131E8A956FC3,
    
    O Guid [585E0EEA-362F-423D-AEB3-131E8A956FC3] representa o id da tabela [account] no Metadado do sistema.
    Exemplo para busca deste id, via FetchData:
    
    var aSearch = [];
    var _entityId = "";
    aSearch.push({ field: "EntityName", type: "varchar", value: "account" });
    var data = CRMGT.Page.Util.FetchData("Entity", "Entityid", aSearch, null);
    if (!CRMGT.Page.Util.IsNullOrEmpty(data)) {
    _entityId = data[0].Entityid;
    }
    
    
    Separe esta combinação de valores usando a [,] para preencher o campo com múltiplos valores
    
    Parâmetro lookupLabel
    ------------------------
    Passe o nome que deverá aparecer no campo de texto do lookup. Para vários, separe-os por [,]
    

    Exemplo de preenchimento de um campo multi-lookup de contas (adicionando no campo 2 contas):
    CRMGT.Page.Util.SetMultiLookupValue("account_481c97b9d13d_ids", "Conta1|c35e3d77-f7af-43ca-b5bd-bfe2ac2697bf|585E0EEA-362F-423D-AEB3-131E8A956FC3,Conta2|d40e3d00-f7af-43ca-b5bd-bfe2ac2600cd|585E0EEA-362F-423D-AEB3-131E8A956FC3", "Conta1, Conta2");
    


Namespace: CRMGT.Page.Util
  • Atribui um método para ser executado no OnChange do lookup
  • Exemplo:
    CRMGT.Page.Util.SetLookupOnChange("accountid", "onChangeAccount([[id]], [[value]])");
    

    Note que no método [onChangeAccount] foram preparados dois parâmetros, [[id]] e [[value]], com colchetes duplos.
    Isto significa que o sistema vai passar o ID e o VALOR do Lookup que foi selecionado para seu método.
    O uso deste mecanismo é [opcional].

  • Para atribuir uma método em um lookup na área de [Business Process Flow] habilitada na tela, utilize SetBPFLookupOnChange(lookupId, fnOnChange)


Namespace: CRMGT.Page.Util
  • Atribui um método para ser executado no OnChange do lookup do tipo TreeView



  • Utilize JQuery para ativar o [change] de campos que são diferentes de lookup.
    Exemplo de ativação para o campo [name]:
    $("#name").unbind("change").change(function () 
    {
    // ADICIONE SEU CÓD AQUI...
    });
    


Namespace: CRMGT.Page.Util
  • Limpa o conteúdo do lookup do tipo TreeView


Namespace: CRMGT.Page.Util
  • Retorna todos os dados de um lookup, sendo:
  • Id, que é o lookupId passado por parâmetro
  • Label, que é o nome que aparece no campo do lookup
  • Value, que é o GUID do lookup
  • Exemplo:
    var accountId = CRMGT.Page.Util.GetLookup("accountid").Value;
    
  • Para retornar os dados de um lookup na área de [Business Process Flow] habilitada na tela, utilize GetBPFLookup(lookupId)


Namespace: CRMGT.Page.Util
  • Permite filtrar o conteúdo do lookup, para restringir os dados que serão mostrados para o usuário
  • Para filtrar o conteúdo do lookup na área de [Business Process Flow] habilitada na tela, utilize FilterBPFLookupValue(lookupId, value)

  • Exemplo 1 (relacionamento 1-N):
    Filtra o lookup para mostrar somente os usuários de um determinado departamento (comercial)
    O filter [type] usado abaixo exemplifica uma busca em uma tabela do tipo [1-N]. Desta forma, devemos informar [1nrelationship], depois o símbolo de pipe [ | ] e por fim o [schema da tabela].
    var aFilter = [];
    var filter = new Object();
    filter.field = "department_481c97b9d13d_ids";
    filter.type = "1nrelationship|department";
    filter.value = "Comercial";
    aFilter.push(filter);
    CRMGT.Page.Util.FilterLookupValue("User_746bf8c2581a_id", aFilter);
    
  • Note que para este tipo de filtro (1-n) o atributo [filter.field] deve contér o nome do campo referente a tabela especificada no primeiro parâmetro da função [FilterLookupValue], que no nosso exemplo é o campo [department_481c97b9d13d_ids] da tabela [user].

    Por padrão um LIKE será usado para buscar os dados, mas podemos escolher a forma de busca através do atributo [filter.operatortype], podendo ser [=, !=, <, >, etc].

    Se precisar informar vários valores para [filter.value], passe da seguinte forma:
    filter.value = ["a","b","c","d","e","f"];
    

  • Exemplo 2 (relacionamento N-N):
    Filtra o lookup para mostrar somente as [Unidades] de uma determinada [Instituição]:
    O filter [type] usado abaixo exemplifica uma busca em uma tabela do tipo [N-N]. Desta forma, devemos informar [nnrelationship], depois o símbolo de pipe [ | ] e por fim o [schema da tabela relacional], que no exemplo é a relação entre unidade e instituicao.
    var instituicaoId = CRMGT.Page.Util.GetLookupObject("instituicao_a52bd16faf87_id").ObjectValue.val();
    var aFilter = [];
    var filter = new Object();
    filter.field = "instituicoesid";
    filter.type = "nnrelationship|unidade_instituicoes";
    filter.value = instituicaoId;
    aFilter.push(filter);
    CRMGT.Page.Util.FilterLookupValue("unidade_874773c8a682_id", aFilter);
    

  • Exemplo 3 (relacionamento 1-1):
    Filtra um lookup de [Atividades do Projeto] baseado no lookup de [Projetos]
    O código abaixo deve ser adicionado no [OnLoad] do formulário
    Os campos de lookup usados no exemplo abaixo (por exemplo [timesheetproject_87b12b2ec835_id]) são criados internamente desta maneira pelo sistema.
    if (CRMGT.Page.Util.FormMode() == "edit") {
    OnChangeProject();
    }
    CRMGT.Page.Util.SetLookupOnChange("timesheetproject_87b12b2ec835_id", "OnChangeProject()");
    
    function OnChangeProject()
    {
    var projectId = CRMGT.Page.Util.GetLookup("timesheetproject_87b12b2ec835_id").Value;
    if (CRMGT.Page.Util.IsNullOrEmpty(projectId)) {
    CRMGT.Page.Util.FilterLookupValue("timesheetprojectactivity_de15ff33049c_id", null);
    return;
    }
    
    var aFilter = [];
    
    var filter1 = new Object();
    filter1.field = "timesheetprojectid";
    filter1.type = "uniqueidentifier";
    filter1.value = projectId;
    filter1.conditiontype = "OR"; /* AND, OR, sendo "AND" o padrao */;
    filter1.operatortype = "="; /* =, !=, LIKE, etc..., sendo "=" o padrao */;
    
    var filter2 = new Object();
    filter2.field = "timesheetprojectid";
    filter2.type = "uniqueidentifier";
    filter2.value = "IS NOT NULL"; /* OU "IS NULL" */
    filter2.conditiontype = "OR";
    
    aFilter.push(filter1);
    aFilter.push(filter2);
    
    CRMGT.Page.Util.FilterLookupValue("timesheetprojectactivity_de15ff33049c_id", aFilter);
    }
    

    OBS: Note que o método [OnChangeProject()] é configurado na function [SetLookupOnChange] e executado também. Isto é importante quando estivermos editando um registro, para que os lookups fiquem corretamente relacionados.

  • Para o filter.type você pode usar os seguintes valores:
    bit
    char
    nchar
    varchar
    nvarchar
    datetime
    int
    decimal
    text
    uniqueidentifier
    1nrelationship|Nome da tabela relacional 1-N
    nnrelationship|Nome da tabela relacional N-N


Namespace: CRMGT.Page.Util
  • Semelhante ao GetLookup, porém, retorna com mais informações para o desenvolvedor, sendo:
  • EntityMetadataId, que é o GUID da [tabela do lookup] no metadado interno do sistema chamado [Entity]
  • ObjectDiv, que representa o elemento DOM na tela
  • ObjectLabel, que é o nome que aparece no campo do lookup
  • ObjectValue, que é o GUID do lookup
  • Exemplo:
    var accountId = CRMGT.Page.Util.GetLookupObject("accountid").ObjectValue.val();
    


Namespace: CRMGT.Page.Util
Utilize esta função para automatizar uma busca de lookup, facilitando a seleção por parte do usuário.
Passando um valor [param value] para um lookup (lookupId) o sistema atribui o valor no lookup, filtra e mostra o resultado aberto na lista do lookup.


Namespace: CRMGT.Page.Util
  • Por padrão campos lookup sempre mostram os registros [ativos] na lista ou em uma pesquisa no campo.
    Com este método podemos ignorar este comportamento, resultando em uma lista de resultado independente do status do registro.
    Exemplo:
    CRMGT.Page.Util.IgnoreOnlyActiveRecordsInLookup("unidade_874773c8a682_id", true)
    


Namespace: CRMGT.Page.Util
  • Retorna, como objeto jQuery, um elemento do DOM especificado.
  • Exemplo 1:
    var _name = CRMGT.Page.Util.GetAttribute("name").val();
    

    Exemplo 2 (para campos do tipo Checkbox):
    var _isChecked = CRMGT.Page.Util.GetAttribute("campo_checkBox").prop("checked");
    

  • Para retornar um elemento na área de [Business Process Flow] habilitada na tela, utilize GetBPFAttribute(id)


Namespace: CRMGT.Page.Util
  • Retorna o texto (ou label) associado a um campo do form


Namespace: CRMGT.Page.Util
  • Atualiza o valor de um elemento do DOM especificado.
  • Para atualizar o valor de um elemento na área de [Business Process Flow] habilitada na tela, utilize SetBPFAttributeValue(id, value)


Namespace: CRMGT.Page.Util
  • Atualiza o valor de um elemento [RichText] especificado.


Namespace: CRMGT.Page.Util
  • Obtém o valor de um elemento [RichText] especificado.


Namespace: CRMGT.Page.Util
  • Retorna true/false - Verifica se o conteúdo de um elemento [RichText] está vazio.


Namespace: CRMGT.Page.Util
  • Habilita/Desabilita o campo [RichText] especificado.


Namespace: CRMGT.Page.Util
  • Retorna um json com os registros da tabela especificada no parâmetro
  • Exemplo 1:
    Faz um SELECT na tabela de [incident], retorna o campo [incidentStatusId]
    onde o campo [originatingrecordid] seja igual a variável [incidentId]:
    var incidentId = CRMGT.Page.Util.GetEntityRecordId();
    var aSearch = [];
    var search = new Object();
    search.field = "originatingrecordid";
    search.type = "uniqueidentifier";
    search.value = incidentId;
    aSearch.push(search);
    var data = CRMGT.Page.Util.FetchData('incident', 'incidentStatusId', aSearch);
    
  • Para o search.type você pode usar os seguintes valores:
    bit
    char
    nchar
    varchar
    nvarchar
    datetime
    int
    decimal
    text
    uniqueidentifier
  • Exemplo 2:
    Usa o FetchData para efetuar uma busca do registro mais atual (instrução [DESC LIMIT 1] do MySQL) e [extrair] parte de um valor do campo [numocorrencia] da tabela [incident]
    var data = CRMGT.Page.Util.FetchData(
    'incident', 
    'SUBSTR(numocorrencia, 1, LOCATE("/", numocorrencia) - 1) + 1 num', 
    null,
    'num DESC LIMIT 1');
    
    var nro = "1/" + (new Date()).getFullYear();
    if (data && data.length > 0 && data[0].num != "") {
    nro = data[0].num + "/" + (new Date()).getFullYear();
    }
    

  • É possível executar uma versão [assíncrona] desta função (recomendado).
    Segue exemplo, onde se obtém o retorno através do resultado do promisse (.then):
    CRMGT.Page.Util.FetchDataAsync('incident', 'incidentStatusId', aSearch).then((data) => {
    /* TRATE AQUI O RETORNO (variável data) */
    });
    


Namespace: CRMGT.Page.Util
  • Retorna um json como resultado da execução da QueryExec passada como parâmetro.
  • Lembre-se que, se você precisar efetuar uma busca de dados em apenas uma tabela, não existe a necessidade de uso do QueryExecData, utilize o FetchData, que faz montagem do SELECT diretamente no código JS.
  • Exemplo:
    OnLoad configurado para executar a QueryExec:

    Depurando a execução e analisando o resultado:
  • Na execução de uma QueryExec de UPDATE, na passagem de parâmetros, informe o atributo [type]. Ele não é obrigatório quando o param é do ipo UniqueIdentifier.
    Exemplo:
    Estes types seguem o padrão já estabelecido no SDK, sendo:
    bit
    char
    nchar
    varchar
    nvarchar
    datetime
    int
    decimal
    text
    uniqueidentifier


Namespace: CRMGT.Page.Util
  • Retorna o nome da tabela do formulário corrente


Namespace: CRMGT.Page.Util
  • Retorna o nome da tabela [pai] do formulário corrente


Namespace: CRMGT.Page.Util
  • Retorna o Guid da tabela do formulário corrente (no metadados do sistema)


Namespace: CRMGT.Page.Util
  • Retorna o Guid da tabela baseado no seu nome de Schema (no metadados do sistema)


Namespace: CRMGT.Page.Util
  • Retorna o Guid do Registro para a tabela corrente


Namespace: CRMGT.Page.Util
  • Retorna o Guid do Registro [pai] da tabela corrente


Namespace: CRMGT.Page.Util
  • Retorna o Guid do Registro para a tabela corrente, no formulário de LISTA de registros


Namespace: CRMGT.Page.Util
  • Retorna os Guids dos Registros [Selecionados] para a tabela corrente, no formulário de LISTA de registros


Namespace: CRMGT.Page.Util
  • Retorna o nome da tabela (schema name) a partir de uma LISTA de registros


Namespace: CRMGT.Page.Util
  • True/false – Verifica se pode salvar um registro corrente no formulário


Namespace: CRMGT.Page.Util
  • Determina se pode salvar um registro corrente no formulário


Namespace: CRMGT.Page.Util
  • Retorna o Modo corrente do formulário:
  • create
  • add
  • edit
  • send


Namespace: CRMGT.Page.Util
  • Atribui um valor para um campo e logo em seguida executa um [Enter] nele.
  • Por padrão, o comando vai usar o evento "keydown" do campo. Você pode especificar um evento diferente no parâmetro [eventName], como "keypress", por exemplo.

Dialogs
É possível utilizar diversos tipos de [dialogs] que fazem parte do Core do produto.
Não utilize estas funções com namespace, apenas faça referência direta ao nome das funções.



  • action - URL para visualização na Dialog
  • _width, _height - valor númerico (em pixels)
  • _title - Título da janela
  • callFunc - Executa uma function quando a janela é Aberta. Passe o nome da function desejada neste parâmetro.
  • closePreviousDialog - true|false - Fecha uma janela aberta (via ShowDialog) se existir
  • overflow - Valores possíveis - visible, hidden, scroll e auto (valor padrão)
  • btns - Adiciona um conjunto de botões customizados.
    Exemplo:
    var btns = {};
    btns["OK"] = function () {
    MyFunc();
    };
    btns["Cancelar"] = function () {
    CloseDialog();
    };
    
  • fnCloseCallback - Executa uma function quando a tela é Fechada.
    Passe o nome da function (ou anônima) desejada neste parâmetro.
    Exemplo:
    ShowDialog("/MyWebSite/Index", 850, 650, null, null, null, null, null, function () {
    // My JS code...
    });
    



  • message - Texto da mensagem
  • btnLabelYes - Label para o botão [yes]
  • btnLabelNo - Label para o botão [no]
  • callbackFunc_Yes - Executa uma function quando o usuário clica no botão [yes]. Passe o nome da function desejada (ou anônima) neste parâmetro.
  • callbackFunc_No - Executa uma function quando o usuário clica no botão [no]. Passe o nome da function desejada (ou anônima) neste parâmetro.
  • centerText - true|false
  • textFontSize - ex: "font-size:19px;"
  • pWidth - ex: 400
  • Exemplo:
    ShowConfirmDialog(
    "Confirma a exclusão?",
    "Sim",
    "Não",
    function () {
    DeleteRecordById(_id);
    },
    function () {
    CloseDialog();
    }	
    );
    



  • message - Texto da mensagem
  • width - largura da janela
  • centerText - true|false
  • textFontSize - Exemplo: font-size:12px; (O valor padrão é de 20px)
  • callbackFunc - Executa uma function quando a janela é Fechada. Passe o nome da function desejada (ou anônima) neste parâmetro.
  • imageType - warning (default), alert, error, success
  • Exemplo:
    ShowAlert("Processo concluído...", 500, false, "font-size:15px;", function () {
    MyFunc();
    }, "success");
    



  • Utilize este componente se desejar efetuar um cadastro de informações na forma de uma [Árvore de Dados]
    Por exemplo, dentro do cadastro de Produtos, você precisa informar os [Setores Industriais] para cada produto.
    Os Setores possuem um cadastro na forma de árvore, onde cada setor possui seu registro [pai].
    Então, para mostrar tudo isto de forma a continuar visualizando a árvore de setores e relacionar com o [Produto], faça o seguinte:
  • Crie um campo do tipo [iFrame] no cadastro de Produtos
  • Informe a seguinte URL no campo [src] do iframe (no OnLoad do formulário)
  • var sqlTreeViewStatement = "SELECT UPPER(IndustrialSectorId) id,";
    sqlTreeViewStatement += " UPPER(PrimaryIndustrialSectorId) parent,";
    sqlTreeViewStatement += " name as text,";
    sqlTreeViewStatement += " (CASE WHEN PrimaryIndustrialSectorId = '#' THEN 'root' ELSE 'child' END) type";
    sqlTreeViewStatement += " FROM IndustrialSector";
    sqlTreeViewStatement += " ORDER BY name, PrimaryIndustrialSectorId";
    
    var iFrameSrc = "/TreeViewCheckBoxForm/Index";
    iFrameSrc += "?sqlTreeViewStatement=" + btoa(sqlTreeViewStatement);
    iFrameSrc += "&parentIdFieldName=product_66dba258e5c4_id";
    iFrameSrc += "&tableNameToStore=productIndustrialSector";
    iFrameSrc += "&tableNameToStoreFieldName=industrialsector_47da919eba2f_id";
    
    $("#industrialsector").attr("src", iFrameSrc);
    
  • Onde:
  • sqlTreeViewStatement - Informe o SELECT para preenchimento da árvore de dados
  • parentIdFieldName - se refere ao campo de lookup de produto na tabela especificada no parâmetro [tableNameToStore]
  • tableNameToStore - tabela de destino que conterá o [id] do [Produto] e do [Setor Industrial]
  • tableNameToStoreFieldName - campo de lookup na tabela especificada no parâmetro [tableNameToStore]

Exemplo da configuração citada acima:



  • Utilize este componente se desejar efetuar um cadastro de informações na forma de uma [Árvore de Dados]
    Por exemplo, se precisar cadastrar informações em uma tabela que posssui um relacionamento com ela mesma.
    Imagine uma tabela de Setores Industriais. Cada registro pode ter um registro [pai], formando desta forma uma árvore de dados.
    Então faça o seguinte:
  • Entre em Configurações -> Áreas
  • Escolha ou crie uma área e adicione um [Item da Área]
  • Como Url informe:
  • /TreeViewRegistration/Index?tableName=[NomedoEsquemaDaTabela]
  • Ex: /TreeViewRegistration/Index?tableName=industrialsector

Exemplo da configuração citada acima:

Note que ao clicar em algum nó da árvore, podemos visualizar mais campos para edição.
Estes campos são lidos do metadados da tabela criada no sistema.





Você pode executar um código JavaScript depois de logar no sistema. Isto é útil se precisar manipular algum dado na tela logo ao entrar, como por exemplo, esconder um item do menu de acordo com o perfil do usuário logado.

O exemplo abaixo faz isto, se o usuário tiver o perfil de [Vendedor], não poderá ver um [item do menu] chamado [Área do Gestor].

Para habilitar este recurso faça o seguinte:
  • Crie um [Web Resource do tipo Script] e dê o nome de [RunScriptWhenLoadingMenu]
  • Adicione o código desejado:
var profile = CRMGT.UserConfig.Profile();
if (!CRMGT.Page.Util.IsNullOrEmpty(profile.userProfile)) {
if (profile.userProfile[0].departmentFunctionName.toLowerCase() == "vendedor") {
var _span = $("span").filter(function () {
return (this.innerText == "Área do Gestor");
});
_span.closest("div").css("display", "none");
}
}



Você pode executar um código JavaScript ao entrar na lista de registros de uma tabela. Isto é útil se precisar manipular algum objeto DOM nesta tela.

Para habilitar este recurso faça o seguinte:
  • Crie um [Web Resource do tipo Script] e dê o nome de [RunScriptWhenLoadingGenFormIndex]
  • Adicione o código desejado.



Você pode executar códigos JavaScript ao entrar no Painel de Gráficos.
Isto é útil se precisar manipular algum objeto DOM nesta tela.

Para habilitar este recurso faça o seguinte:
  • Crie um [Web Resource do tipo Script] e dê o nome de [RunScriptWhenLoadingChartPanelIndex]
  • Adicione o código desejado.



Utilização de Built-In Procedures nas Query Execs

Algumas Stored Procedures foram criadas para facilitar a manipulação de dados do CRM-GT.


Retorna os emails de todos os valores de um MultiLookup (de usuário ou contato).
Exemplo: busca dos participantes (campo multiLookup) da tabela de [AppointmentActivity] e execução da procedure para obter os emails destes participantes.
SET @users = '';
SET @emails = '';

SELECT participants
FROM AppointmentActivity
WHERE AppointmentActivityId = '9BF0C60B-1FDF-48E9-B2F0-33438515A6FD'
INTO @users;
 
CALL GetEmailsFromMultiLookupFieldValue(@users, @emails);
 
SELECT @emails;
 
E o resultado:
gabriel001@gmail.com,mauricio@hotmail.com,gtezini@hotmail.com,


Adiciona um registro de [trigger] de workflow programaticamente.
Exemplo: busca dos participantes (campo multiLookup) da tabela de [AppointmentActivity] e execução da procedure para obter os [nomes] destes participantes.
SET @users = '';
SET @labels = '';

SELECT participants
FROM AppointmentActivity
WHERE AppointmentActivityId = '9BF0C60B-1FDF-48E9-B2F0-33438515A6FD'
INTO @users;
 
CALL MultiLookup_GetLabels(@users, @labels);
 
SELECT @labels;
Resultado:
Gabriel Silva,Jose da Silva,gilberto 


Retorna formatado o [name] do registro originador de uma determinada tabela.
Exemplo: Buscar o registro originador na qual uma [task] foi criada. Como resultado, veremos no exemplo abaixo, que a task foi criada a partir de uma [conta].
SET @originatingRecordInfo = '';
CALL GetOriginatingRecordInfo('task', '0cad354b-311a-42d0-a9a0-6b0e85dbdff0', @originatingRecordInfo);
SELECT @originatingRecordInfo;
Resultado:
Gabriel Silva (account)


Efetua um Split em uma string e adiciona o resultado em uma TempTable.
Exemplo:
SET @value = 'slice1, slice2, slice3, slice4, slice5';

DROP TEMPORARY TABLE IF EXISTS temp;
CREATE TEMPORARY TABLE temp (rownumber INT, val VARCHAR(255));

CALL SplitString(@value, ",", "temp");

SELECT * FROM temp;
Resultado:
1	slice1
2	slice2
3	slice3
4	slice4
5	slice5


Adiciona um registro de [trigger] de workflow programaticamente.
Exemplo: Criar um gatilho para executar todos os workflows de [update] para a tabela de [incident], quando o campo [statuscode] mudar.
InsertIntoWorkflowTrigger('incident', 'update', @incidentId, 'statuscode,modifiedby', null, null, null, null, null);

Este método recebe os seguintes principais params:
1º - nome da Tabela (schema name)
2º - nome do Evento (create/update/delete/assign)
3º - RecordID (por ex, o id de um incident que foi atualizado diretamente no banco)
4º - Se precisar configurar o workflow para que seja executado somente quando campos específicos forem alterados, passe-os aqui, separados por vírgula, todos eles dentro de uma única aspas

Existem mais params, são opcionais (passe null por padrão), sendo:
5º - DeleteCurrentWorkflowTrigger (true/false) - default null
6º - CurrentWorkflowTriggerId (guid) - default null
7º - LastStepNotExecuted (usado internamente pelo sistema) - default null
8º - SpecificWorkflowDesignId (usado internamente pelo sistema) - default null
9º - WorkflowTriggerUserId (ID de um usuário do sistema, representando um usuário que iniciou o trigger de wk) - default null

-Importante:
- Se vc precisar Reprocessar [somente] um workflow específico, passe o Id para o parâmetro SpecificWorkflowDesignId e passe NULL para o 2º parâmetro (nome do evento)
-Por que isto é importante? Lembre-se que se o 2º parâmetro estiver preenchido, então o motor de workflow vai rodar [todos] os workflows do [evento] especificado (para o registro corrente).


Utilização de Built-In Functions nas Query Execs

Algumas Funções foram criadas para facilitar a manipulação de dados do CRM-GT.


Retorna a conversão de segundos para Time.
Exemplo:
SELECT cSec_To_Time(1200)
00:20:00


Retorna a conversão de Time para Segundos.
Exemplo:
SELECT cTime_To_Sec("00:20:00")
1200


Retorna em quais Departamentos/Funções de Depto o usuário pertence
Exemplo:
SELECT GetUserDepartmentFunction('2d4485a8-5317-4537-8545-49ddd6af02b8')
DESIGN & ENGENHARIA (Cliente Portal),DESIGN & ENGENHARIA (VENDEDOR), Comercial / Regional Sul (VENDEDOR), Comercial / Regional Nordeste  (VENDEDOR)


quarta-feira, 29 de janeiro de 2020

PCF - PowerApps Component Framework - XrmToolBox utility


Construir um componente (PCF) para ser reutilizado nas telas do Dynmamics 365 não é tarefa fácil.
Felizmente o developer Danish Naglekar criou uma ferramenta para automação na construção destes componentes.

P.S.: Eu criei um componente para formatar e validar um CPF ou CNPJ, e está disponível no GitHub - PCF - CPF / CNPJ

Abaixo segue um passo a passo de fluxo de tela, para criação de um componente, dentro do XrmToolBox.

1 - Instale e execute o componente

2 - Conecte-se ao CRM e terá a tela do utilitário

3 - Baixe e Instale o PowerApps CLI (powerapps-cli-1.0.msi)

4 - Informe a localização para seu componente e a ferramenta do Visual Studio para a automação do PCF (\Common7\Tools\VsDevCmd.bat)


5 - Informe os detalhes do seu componente e da solução
Obs: não use "." para separar as palavras da "solution name"
6 - Crie seu componente
7 - Crie sua Solution
8 - Vá até o diretório fonte
9 - Entre no subdiretório onde se encontram os arquivos para alteração
10 - E altere os arquivos [ControlManifest.Input.xml] e [index.ts]
No meu exemplo, estou criando um componente TextBox que formata e valida um CPF ou CNPJ.

11 - Compile seu [componente] e [solução]

Passo importante: Adicionar o componente na Solução (botão [add component to solution] depois [build])
12 - E faça o teste do componente
13 - Como passo final, efetue o [deploy] da solution para o CRM

Ref: Implementar componentes usando TypeScript

quinta-feira, 2 de janeiro de 2020

Como os desenvolvedores remotos estão mudando a indústria de software


Este artigo foi originalmente publicado pela indexcode.io

Nos artigos anteriores, expusemos as razões convincentes pelas quais mais e mais equipes de software estão ficando remotas. Trabalhadores virtuais são mais produtivos, mais baratos e mais envolvidos com seu trabalho. As empresas parceiras de desenvolvedores remotos obtêm acesso a um pool global de talentos; e, com a flexibilidade de expandir os fusos horários, sempre há alguém on-line para solucionar problemas ou fornecer suporte ao cliente.

Muitas empresas já estão se beneficiando de equipes de desenvolvedores totalmente remotas. Veja como algumas das empresas mais inovadoras estão aproveitando o trabalho remoto para ficar à frente da concorrência.

InVision

O InVision é uma plataforma de design de produtos totalmente remota desde a sua fundação. Quando o CEO Clark Valberg iniciou a empresa em 2011, ele decidiu economizar em custos e permanecer à frente da curva de inovação, confiando nos funcionários para produzir o melhor trabalho de qualquer lugar. “É sobre resultados, não onde está o seu endereço IP”, disse o diretor de pessoal da InVision ao Business Insider. “Nós nos preocupamos com o que você pode fazer ou alcançar. Se você é capaz de alcançar algo excelente enquanto trabalha horas difíceis, isso é ótimo."

As equipes distribuídas da InVision permitem que a empresa atraia talentosos desenvolvedores de software de todo o mundo. Isso permite que a empresa construa um produto melhor em geral e concorra com gigantes como o Google. No momento da criação do InVision, o Google havia aumentado sua presença em Manhattan; ficar totalmente remoto deu à InVision uma vantagem criativa em como a equipe pensava em seu produto. "Somos uma empresa de software que cria ferramentas para designers", explicou o diretor da empresa. "Definitivamente, nos ajuda a pensar em nosso produto, já que estamos projetando remotamente de qualquer maneira".

Hoje, a InVision conta com cerca de 700 funcionários que consideram o desenvolvimento de produtos através das lentes de seus consumidores-alvo. Os desenvolvedores remotos são capazes de aproveitar o ambiente de trabalho remoto para impulsionar players dominantes como o Google e criar melhores produtos no processo.

Basecamp

O Basecamp é uma ferramenta de gerenciamento de projetos usada por muitas equipes remotas e de desenvolvimento. O fundador Jason Fried argumenta que o trabalho remoto é essencial para o sucesso do Basecamp; ele se tornou um defensor vocal do trabalho remoto, chegando ao ponto de escrever um livro defendendo uma maneira melhor de trabalhar.

Fried acredita que o trabalho remoto permite que o Basecamp acesse os melhores desenvolvedores remotos do mundo, dando à empresa uma vantagem do trabalho tradicional. "Você pode contratar as melhores pessoas do mundo", disse Fried à Forbes. “A verdade é que há muitas pessoas excelentes em todo o lugar, para que todos possamos ter ótimas pessoas. A chance de as melhores pessoas do mundo estarem dentro de um raio de 32 quilômetros do seu escritório não faz muito sentido.”

Da mesma forma, Fried descobriu que o trabalho remoto permite que os funcionários pensem mais profundamente sobre seu trabalho. A natureza de um ambiente tradicional de escritório é tal que as reuniões são agendadas, os emails são recebidos e os funcionários se distraem. Um desenvolvedor de software acaba trabalhando em intervalos de tempo de 15 minutos sem trechos de tempo ininterrupto. “No escritório, não é incomum chegar às 17 horas e perceber que você não fez um trabalho significativo”, diz Fried. O trabalho remoto, por outro lado, permite longos períodos de tempo ininterruptos, necessários para pensar profundamente e considerar os problemas com cuidado. ”

Na Basecamp, a abordagem de Fried era encontrar os melhores talentos e capacitá-los para trabalhar da maneira que os tornasse mais produtivos. Fried já vendeu uma participação na Basecamp para Jeff Bezos por milhões, provando o valor da empresa.

Buffer

Plataforma de gerenciamento de mídia social, a Buffer é uma empresa totalmente remota que se beneficia de um conjunto diversificado de opiniões e origens culturais no desenvolvimento de seu produto. Courtney Seiter, do "Inclusive Catalyst", do Buffer, observou que o Buffer "faz tudo 100% remotamente primeiro para criar esse sentimento de inclusão e igualdade em todos os aspectos". A capacidade de aproveitar experiências, idéias e conhecimentos culturais de todo o mundo ajuda o Buffer a ser mais inovador.

A transparência do buffer torna possível criar um ambiente de trabalho remoto em que os funcionários não são definidos por seu status socioeconômico, gênero ou etnia. A empresa compartilha “práticas e salários de contratação, detalhes de receita, roteiros de produtos e muito mais na página de transparência”. O resultado? O buffer se beneficia dos trabalhadores envolvidos, que trazem toda a sua atenção e identidade para a tarefa em questão.

Como um blogueiro resumiu: "As empresas compostas por uma força de trabalho inclusiva e diversificada que apoia trabalhadores felizes é o futuro do trabalho que todos merecemos." Codificadores e engenheiros contratados remotamente estão tornando o desenvolvimento de software mais inovador, mais produtivo e mais empenhados em ajudar suas empresas a crescer.

Como essas três empresas de comércio eletrônico funcionam remotamente


Este artigo foi originalmente publicado pela indexcode.io

Software, desenvolvimento móvel, SaaS e outras empresas de tecnologia que contratam remotamente são comuns: empresas como Stripe, Buffer e Coffee Meets Bagel são apenas algumas daquelas que se beneficiam do trabalho com os principais desenvolvedores em todo o mundo.

Trabalhadores remotos estão penetrando em praticamente todos os setores, não apenas em tecnologia. Uma pesquisa da Upwork constatou que 63% das empresas agora têm funcionários remotos e 90% das que trabalham remotamente planejam continuar fazendo isso pelo resto de suas carreiras. Até empresas de comércio eletrônico estão mudando para uma cultura remota. Embora possa parecer desarticulado projetar e produzir um produto físico com uma equipe distribuída, esses varejistas, atacadistas e marcas de comércio eletrônico estão aproveitando os benefícios do trabalho remoto. Aqui está como eles fazem isso.

Tortuga

A Tortuga é uma empresa totalmente remota que vende mochilas e outros acessórios para bagagem. Seu fundador, Fred Perrotta, sabia que, como empresa de viagens, o trabalho remoto seria uma parte essencial de seu modelo de negócios. Como resultado, a equipe foi totalmente distribuída desde o início.

Tortuga usa as ferramentas tradicionais de produtividade que muitas empresas remotas utilizam, como o Slack. O maior desafio para trabalhar remotamente ocorre quando a equipe de design precisa provar diferentes materiais e hardware. “Produtos leves, como bolsas, precisam ser manuseados fisicamente. Como é o material? Qual será a forma? Qual é o tamanho da bolsa? Essas são perguntas que não podem ser respondidas remotamente ”, observa Perrotta.

Para mitigar esse desafio, a equipe conta com o correio tradicional. O gerente de produto obtém uma amostra da fábrica. Ela faz suas anotações na amostra, observando o que está funcionando e o que não está. Em seguida, ela envia a amostra para outro membro da equipe. A qualquer momento, há amostras viajando da China, Califórnia, Costa Leste e vice-versa. "Isso atrasa o processo, mas é uma das melhores maneiras de fazê-lo funcionar", diz Perrotta. Tortuga descobriu que, apesar dos custos de correspondência e do investimento de tempo, é mais eficiente do que pagar por um escritório.

Linjer

A Linjer é uma empresa remota direta ao consumidor, especializada em artigos de couro fino - comparáveis ​​a marcas de luxo - por um terço do preço. Seu modelo de negócios permite à empresa capturar economias de custos indiretos e minar seus concorrentes de alto preço. Como Linjer descreve, “Nós vendemos diretamente aos clientes, cortando os custos desnecessários e marcações loucas que tornam os relógios e as bolsas de luxo extraordinariamente caros. Nós transferimos a economia para você, para que você obtenha a mesma qualidade a 1/3 ou 1/4 do custo típico.”

Originalmente, Linjer oferecia 100% de funções de telecomutação com horários alternativos. Esse modelo oferece a capacidade de cobrir vários fusos horários e solucionar problemas de atendimento ao cliente em tempo real. O gerenciamento de estoque e o atendimento ao cliente são duas funções em que o trabalho remoto pode dar às marcas de comércio eletrônico uma vantagem competitiva distinta. Hoje, a empresa está sediada em Hong Kong, com curtumes na Itália, fábricas na Turquia e China e parceiros de design na Suíça. A empresa envia para consumidores em 40 países da América do Norte, Europa, Oceania, Ásia e Oriente Médio.

Stryker

Esperamos que alguns produtos voltados para o consumidor obtenham os benefícios de uma equipe remota. Para empresas como a Tortuga, trabalhar virtualmente lhes permite explorar a mentalidade de seus consumidores. Da mesma forma, empresas de tecnologia e empresas de SaaS obtêm acesso a desenvolvedores e engenheiros que podem trabalhar o tempo todo em qualquer fuso horário. Mas as empresas de comércio eletrônico que tiram vantagem dos trabalhadores virtuais nem sempre são o que você esperaria.

A Forbes observou em um artigo recente que, "uma tendência mais recente ... é que as organizações de assistência médica também entraram na onda do trabalho remoto e contratam ativamente especialistas para funções remotas de meio período e período integral". Stryker é uma empresa B2B estabelecida que vende dispositivos médicos para o setor de tecnologia médica. É um caso interessante de uma grande entidade de estilo corporativo que adotou o trabalho remoto para classificar continuamente entre os melhores locais de trabalho nos EUA. Stryker ganhou dezenas de prêmios, incluindo:

  • As 100 melhores empresas para se trabalhar da Fortune
  • Gallup “Prêmio Grande Local de Trabalho”
  • Ótimo lugar para trabalhar “Os melhores locais de trabalho do mundo”


A empresa possui uma sede em Michigan, além de vários escritórios em todo o mundo. No entanto, a Stryker adotou o trabalho virtual para expandir o alcance de seus mais de 30.000 funcionários globais. A marca recruta profissionais de saúde e associados de vendas por mais de 30 anos de crescimento nas vendas.

Pronto para começar com uma equipe de software virtual? Trabalhe com o Index Code para trazer talentos remotos à sua empresa de comércio eletrônico hoje.

segunda-feira, 30 de dezembro de 2019

Por que sua equipe de software deve ser remota


Este artigo foi originalmente publicado pela indexcode.io

O futuro do trabalho é remoto, e em nenhum setor isso é mais verdadeiro do que em programação e desenvolvimento de software. Uma força de trabalho globalizada permite que as empresas utilizem as habilidades de programadores de todo o mundo, economizando tempo e dinheiro no processo. Muitas empresas, incluindo Stripe, ESPN e Coffee Meets Bagel, têm capitalizado em trabalhar com os principais desenvolvedores, não importa onde morem. Aqui estão alguns dos principais benefícios que as empresas obtêm trabalhando com equipes de software remotas.

Beneficie-se do aumento da produtividade 

Não são apenas as equipes de software: em geral, os funcionários remotos são mais produtivos. Uma pesquisa constatou que 77% dos trabalhadores eram mais produtivos ao trabalhar remotamente; 30% dos entrevistados relataram que "mais em menos tempo do que quando trabalhavam internamente".

Os desenvolvedores remotos de software ganham flexibilidade para trabalhar quando são mais produtivos. Nem todo mundo é mais produtivo entre as 9:00 e as 17:00. As equipes dispersas podem espalhar sua cobertura por 24 horas, concluindo tarefas quando cada pessoa é capaz de se concentrar e realizar seu melhor trabalho. Se houver um problema que precise ser resolvido imediatamente, há uma boa chance de alguém em um fuso horário diferente estar pronto para solucionar problemas. Para as empresas que pagam a cada desenvolvedor por hora, você está pagando por horas trabalhadas - não horas gastas em uma mesa olhando para o Facebook. Isso se traduz em um melhor trabalho pelo seu dinheiro.

Tenha acesso aos melhores talentos em todos os lugares

Quando um gerente de contratação recruta de um conjunto de candidatos em uma área geográfica imediata, sua capacidade de contratar o melhor desenvolvedor é limitada. Expandir sua equipe de software para um conjunto global de candidatos oferece à sua empresa o melhor ajuste possível para o projeto. De acordo com o Relatório de Habilidades para Desenvolvedores de 2019 da Hackerrank , "41% dos empregadores dizem que a falta de talentos é um problema que enfrentam ao contratar desenvolvedores". O recrutamento em todo o mundo permite que uma empresa contrate um engenheiro do Vale do Silício para trabalhar com um especialista em Java da Polônia e um desenvolvedor de software das Filipinas. Escassez de talentos: resolvida.

Economize nos custos dos funcionários 

Dependendo do nível da posição e da sua localização, custa em média US$ 4.000 para recrutar um novo funcionário. Desde a redação da descrição do cargo até a publicação do cargo e a contratação de alguém para entrevistar, os custos de contratação de uma equipe de software completa podem aumentar rapidamente.

As empresas economizam nos custos de realocação e recrutamento e podem contratar menos por talento ao contratar remotamente. Estima-se que uma força de trabalho de engenharia remota totalmente funcional economize entre US $ 11.000 e US $ 50.000 por funcionário, por ano. "O talento que trabalha em casa não exige espaço para escritório, equipamento de computação, café e cobertura de deslocações", observa um blog. Engenheiros, desenvolvedores e programadores estão entre os profissionais mais bem pagos do mundo. Mas, é tudo relativo a onde a pessoa está localizada. As taxas de câmbio tornam possível pagar bem a um trabalhador remoto sem interromper seu orçamento.

Aumentar a moral e a retenção de funcionários

A empresa de pesquisa Gartner descobriu que "até 2020, as organizações que apóiam uma cultura de 'escolha seu próprio estilo de trabalho' aumentarão as taxas de retenção de funcionários em mais de 10%". Permitir que seus funcionários determinem quando e onde eles trabalham reforça a motivação e o envolvimento com o trabalho de suas equipes. Funcionários engajados são funcionários felizes, e funcionários felizes permanecem na empresa por mais tempo.

O trabalho remoto está se tornando a norma, e não a exceção. A contratação de uma equipe remota de software prova o seu departamento de TI no futuro. “Com uma força de trabalho cada vez mais remota, você deve configurar esses sistemas agora para permanecer competitivo no futuro. Não seja a empresa que fica para trás porque você não consegue atrair o talento certo para realizar o trabalho ”, escreve um blog de desenvolvimento.

Elimine os riscos da terceirização

Algumas empresas precisam apenas de assistência de curto prazo, baseada em projetos; outros precisam de uma equipe remota em tempo integral para ajudar a cumprir a missão da empresa. Nesta segunda instância, muitas empresas tentariam usar um atalho, terceirizando seu projeto aos poucos para freelancers ou uma empresa terceirizada. Eles perdem a capacidade de controlar o projeto, nunca constroem uma equipe dedicada e gastam mais tempo e dinheiro em controle de qualidade do que se tivessem contratado uma equipe discreta e examinada de codificadores remotos. Contrate uma equipe de software remota dedicada, coordenada por uma parte, para manter “controle total sobre o fluxo de trabalho e eliminar os riscos associados à terceirização”.

Crie uma equipe de desenvolvimento de software de alto desempenho com os melhores engenheiros do mundo. Comece hoje mesmo com a Index Code.

quinta-feira, 26 de dezembro de 2019

Além do Bitcoin: como os desenvolvedores estão ajudando os bancos tradicionais a se modernizar


Este artigo foi originalmente publicado pela indexcode.io

Bitcoin e blockchain tendem a ganhar manchetes no mundo dos bancos. A criptomoeda é o filho da “tecnologia disruptiva” no setor financeiro tradicionalmente lento. Porém, há outras áreas em que desenvolvedores e engenheiros de software precisam atualizar os negócios normalmente no setor bancário para sobreviver.

Segundo uma pesquisa, 80% dos banqueiros concordaram que sua instituição "precisa concluir uma avaliação nos próximos três anos, mas apenas 15% esperavam que isso levasse a um esforço de modernização". Ameaças à segurança, demanda por banco móvel e desatualização, todos os principais sistemas bancários estão levando os bancos a considerar grandes reformas em seus sistemas de TI. Esses são os maiores desafios de modernização enfrentados pelas instituições financeiras - áreas em que desenvolvedores e equipes remotas de software podem desempenhar um papel significativo para manter os bancos competitivos.

Fazendo atualizações nas "estruturas herdadas"

Na mesma pesquisa, 60% dos banqueiros relataram que pelo menos um de seus principais desafios tecnológicos está diretamente relacionado ao envelhecimento dos principais sistemas. "A manutenção de sistemas legados representa 78% do orçamento de TI de um banco e 70% dos banqueiros sentem que seus processos principais não podem se adaptar rapidamente às mudanças", relata Ripple.

Com o tempo, os bancos resistiram a grandes mudanças em seu sistema bancário principal, o sistema de back-end responsável pelo processamento de transações, atualizações nas contas e manutenção de outros registros financeiros. Os principais sistemas bancários são responsáveis ​​pelo processamento de depósitos, empréstimos e lançamento de créditos, além de atualizar outras ferramentas de relatórios e contabilidade.

À medida que crescem os recursos direcionados ao consumidor, como depósito móvel e transferência ponto a ponto, esses sistemas bancários principais tiveram atualizações ad hoc - mas nenhuma transformação completa. Os principais sistemas de depósito foram construídos na década de 1970, escritos em "velhas e inflexíveis linguagens de programação" como Cobalt e PL/I. A análise da Oracle também descobriu que esses "sistemas principais herdados de décadas são inflexíveis e cada vez que um banco deseja lançar um novo produto deve 'codificar' o sistema, o que pode levar 12 meses ou mais".

Não existe uma solução simples para atualizar o sistema principal de um banco: é um empreendimento tecnológico maciço, mas no qual os bancos devem investir para atender bem seus clientes. Os engenheiros podem ajudar os bancos a desenvolver uma abordagem ágil e centrada no consumidor para o core banking. Existem várias abordagens para resolver o problema dos sistemas centrais arcaicos, e as equipes de software podem realizar mudanças iterativas que evoluem a infraestrutura principal de um banco sem muita interrupção do serviço.

Modernizando a proteção contra fraudes

A prevenção de fraudes continua sendo um dos desafios tecnológicos mais difíceis que os bancos enfrentam, à medida que os cibercriminosos ficam mais sofisticados para atingir os consumidores. Para ilustrar o desafio que os bancos enfrentam em manter as informações da conta do consumidor em segurança, a Kasperky Lab invadiu uma “grande empresa financeira de capital aberto em menos de 15 minutos”.

As abordagens tradicionais adotadas por muitos bancos não funcionam. Os requisitos de autenticação e os processos de verificação falham em evitar fraudes e proporcionam uma experiência negativa ao cliente. Em vez disso, escreve um especialista em segurança, “os bancos devem se concentrar na criação de melhores sistemas e técnicas para coletar e analisar dados internos e externos, desenvolver algoritmos e perfis mais significativos, executar testes de penetração em relação às estratégias atuais, detectar alterações nos padrões de transação e desenvolver soluções mais eficazes”.

Para proteger os consumidores de ataques de malware e fraude, os bancos devem passar de uma abordagem de operações reativa para uma preventiva. Os desenvolvedores podem ajudar os bancos a se preparar modernizando os sistemas que armazenam dados do usuário, movendo informações para uma nuvem criptografada. Diz-se que a ferramenta de IA da IBM, por exemplo, oferece uma análise mais rápida de ameaças e ataques persistentes avançados. Os desenvolvedores devem integrar a tecnologia mais recente nos sistemas de segurança dos bancos para se modernizar.

Abertura de conta digital

Os desenvolvedores desempenharão um papel crítico para ajudar os bancos tradicionais a acompanhar as demandas dos clientes em movimento. A abertura de conta digital é um processo em que desenvolvedores e engenheiros de software podem ter um impacto imediato.

A abertura de conta digital (DAO) é o processo de abertura de uma conta bancária sem nunca entrar no banco. O DAO envolve executar as seguintes etapas:

  • Coletar informações de identificação pessoal de um cliente
  • Avaliar e aprovar (ou rejeitar) um cliente de uma perspectiva de risco/fraude
  • Verifique a identidade do cliente
  • Aceite fundos digital e imediatamente, através de cartão de débito / crédito ou com depósito móvel
  • Sincronize com o sistema bancário principal


Muitos bancos são capazes de permitir que os clientes abram uma conta online através de um navegador da web. No entanto, a abertura de conta otimizada para celular é uma área em que o setor ficou para trás. Existem algumas boas razões para esse processo ser tão difícil. A fraude de aplicativos e leis rigorosas contra a lavagem de dinheiro dificultam o cumprimento dos requisitos regulatórios pelos bancos. Há riscos de segurança significativos : em 2018, os bancos enfrentaram mais de US$ 31 bilhões em perda de fraude global.

Mas os desenvolvedores que ajudam os bancos a se modernizar para fornecer DAO terão um impacto financeiro imediato. Um relatório constatou que 69% dos pesquisados desejam realizar todos os seus serviços bancários por meio de canais online e móveis. A BAI descobriu que cerca de 75% da geração Y e mais de 65% da geração X preferem usar um canal digital para abrir uma conta de depósito. O consumidor principal do futuro espera poder abrir uma conta, obter empréstimos pessoais e transferir fundos de qualquer dispositivo a qualquer momento. Os desenvolvedores devem encontrar uma maneira de construir a infraestrutura para permitir que os bancos ofereçam o DAO.

segunda-feira, 23 de dezembro de 2019

10 ferramentas para ajudar as equipes de desenvolvedores remotos a trabalhar com eficiência


Este artigo foi originalmente publicado pela indexcode.io

Está provado que as equipes remotas trabalham mais produtivamente do que as equipes em um ambiente de escritório. Uma pesquisa constatou que 77% dos trabalhadores eram mais produtivos ao trabalhar remotamente. Outro descobriu que 76% dos trabalhadores eram distraídos com menos frequência do que quando trabalhavam em um escritório.

Os funcionários remotos podem ser mais produtivos, mas ainda há desafios para trabalhar com eficiência em uma equipe distribuída. A coordenação de desenvolvedores em fusos horários pode apresentar uma dor de cabeça logística. As equipes de desenvolvedores remotos precisam das ferramentas e plataformas certas para garantir que possam compartilhar o trabalho com eficiência e capturar todos os benefícios do trabalho remoto produtivo. Essas são as dez melhores ferramentas para ajudar as equipes distribuídas de desenvolvedores a coordenar.

GitHub
O GitHub (e o Git) oferece um local baseado em nuvem para armazenamento, teste, compartilhamento e colaboração em projetos de desenvolvimento web. O GitHub é uma plataforma de teste para os desenvolvedores compartilharem projetos, editarem e construírem o código um do outro. Da revisão de código ao gerenciamento de projetos, às codificações e integrações sociais, o GitHub é a espinha dorsal de qualquer projeto de equipe de desenvolvedor remoto.

Slack
Toda equipe remota precisa de uma plataforma de colaboração e comunicação, e é aí que entra o Slack. Favorecido por equipes remotas de todos os tamanhos, o Slack oferece um espaço para bate-papo, para compartilhar documentos, obter feedback e coordenar projetos de codificação. Desde a solicitação de feedback do teste beta até o envio de projetos mais rapidamente, o Slack facilita uma cultura de equipe remota e um trabalho mais produtivo.

Bitbucket
O Bitbucket, anteriormente Stash, é uma ferramenta projetada para manter os desenvolvedores produtivos ao trabalhar no Git. Os repositórios Git podem ficar pesados ​​rapidamente - e o Bitbucket fornece uma maneira de estruturar repositórios com uma interface fácil e amigável. Como o GitHub, o Bitbucket oferece uma plataforma para planejar projetos, colaborando com código, testes e implantação. Além disso, configure repositórios privados ilimitados gratuitos.

Jira
Jira é uma necessidade absoluta para equipes de desenvolvimento ágil. A ferramenta de planejamento e rastreamento ajuda sua equipe a criar novas histórias de usuários, planejar sprints e atribuir tarefas para manter todos responsáveis ​​por seu trabalho. O rastreamento de erros e os relatórios são esclarecidos com o fluxo de trabalho transparente da plataforma, dando visibilidade a todos, não importa onde eles estejam no mundo. Além disso, o Jira se integra ao Confluence, Bitbucket e centenas de outras ferramentas de desenvolvedor.

Basecamp
O Basecamp é uma ótima alternativa (ou suplemento) ao Jira. A beleza do Basecamp é que ele reúne todas as ferramentas que uma equipe remota usa em um único local. A plataforma promete que um trabalhador remoto “saberá o que fazer, onde estão as coisas e onde encontrar as coisas que precisam”. O Basecamp inclui quadros de mensagens, listas de tarefas, armazenamento de arquivos, bate-papo em grupo e muito mais solução de produtividade.

InVision
Prototipar em uma equipe de desenvolvimento remoto pode ser um desafio, e é aí que o InVision vem em socorro. Este aplicativo foi usado por empresas como Airbnb, Amazon, Lyft e Netflix para colaborar em projetos de design e protótipo. Os quadros interativos e as ferramentas de design integradas à plataforma permitem feedback simplificado, coordenação inteligente e transferência eficiente entre as equipes de desenvolvimento e design. O InVision possui uma interface de usuário elegante e muitas integrações (incluindo Jira, Slack e Basecamp) para ajudar sua equipe a trabalhar da maneira mais eficiente possível.

Screenhero
Screenhero é uma ferramenta de compartilhamento de tela desenvolvida especificamente para codificadores remotos. O bate-papo por voz e vários cursores do mouse facilitam a colaboração e o emparelhamento de código ou correção de erros em conjunto. “Os designers podem iterar mais rapidamente revisando projetos com as partes interessadas ou clientes em tempo real - sem passar arquivos para frente e para trás”, escreve um revisor. Vá além do screencasting para uma maneira mais colaborativa de trabalhar virtualmente.

Google Drive
Toda equipe precisa de uma boa maneira de compartilhar arquivos. O Google Drive oferece 15 GB de armazenamento gratuito na nuvem - é uma quantidade razoável de códigos, imagens, histórias, designs e outros documentos que você pode precisar guardar para seus projetos de desenvolvimento. A flexibilidade da plataforma de armazenamento em nuvem significa que os desenvolvedores remotos podem acessar arquivos em qualquer dispositivo; os arquivos são sincronizados automaticamente e a função de salvamento automático salvou muitos planos de projeto em caso de emergência.

Every Time Zone
Às vezes, mesmo as ferramentas mais simples têm o maior impacto na capacidade de uma equipe virtual de trabalhar com eficiência. Every time zone mostra a que horas onde cada desenvolvedor está localizado. Em vez de esperar por uma resposta de um colega que está offline, um desenvolvedor pode solucionar problemas por conta própria ou seguir para uma tarefa diferente. O agendamento de reuniões e o cumprimento dos prazos se tornam mais fáceis quando você sabe quando cada pessoa está online - especialmente quando sua equipe tem agendas flexíveis. Os gerentes experientes também podem criar equipes remotas que sempre têm alguém on-line, garantindo cobertura constante sem atrasos.

I Done This
Procurando uma ferramenta simples de produtividade? I Done This é uma plataforma simples que fornece check-ins diários e relatórios de progresso para fazer mais de forma efetiva e produtiva. Acompanhe o trabalho dos seus co-desenvolvedores e acompanhe o que está sendo feito, o que está fora do cronograma e por quê. Além disso, é baseado em email, tornando-o uma adição simples às ferramentas que sua equipe já está usando.

Interessado em trabalhar com uma equipe de desenvolvedores remotos? Entre em contato com a Index Code para saber mais.