CSPlane
DOCUMENTAÇÃO BOOTCAMP CSPTECH - ORGANIZAÇÃO CSP PLANE
INTRODUÇÃO
Para a imersão, a CSP Tech sugeriu a elaboração de um cenário empresarial focado na venda, programação e gestão de despesas de viagens corporativas. Nesse contexto, cada colaborador da empresa cliente seria responsável por registrar suas próprias viagens e as despesas associadas, atendendo assim às demandas específicas do negócio.
A despeito do foco em desenvolvimento, durante a imersão tivemos contato com as principais funcionalidades de Salesforce, tanto em relação a parte funcional quanto em relação a parte de desenvolvimento, de modo a adentrar profundamente no ambiente Salesforce e em um cenário de negócios. Para solucionar os desafios propostos, o MVP da CSPlane foi criada, no qual, além de constar a solução de todos os exercícios propostos pela própria CSP Tech, o MVP da CSPlane incorpora as resoluções dos exercícios propostos e foi projetado para ser uma solução abrangente no contexto de gestão de viagens corporativas de modo a atender de maneira integral às necessidades básicas do cenário de negócios proposto.
Esse modelo inicial, além de incorporar as soluções desenvolvidas para os exercícios, também integra ferramentas e recursos avançados para otimizar a experiência do usuário e a eficiência operacional. O time se dedicou a personalizar e aprimorar o ambiente Salesforce, implementado, para tanto, novos objetos e campos customizados, Flows para a realização das tarefas, além de funcionalidades com Apex e testes para tal. Durante a imersão, a exploração da ferramenta Copado Essentials proporcionou uma oportunidade para aprofundar o entendimento sobre deploy em ambiente Salesforce.
Características do CSPlane
Objetos
Na primeira semana, nosso contato principal foi com a parte funcional, no qual estruturamos a organização da CSPlane para desenvolvimento futuro. Além dos Objetos e Campos customizados, fizemos implementações de Flows na organização. Cabe destacar que, em decisão conjunta, o grupo optou por utilizar Camel Case para nomear os futuros API Names da organização.
Principais objetos:
Despesa (Despesa__c)
Objeto para lançamento/reembolso de despesas do dia-a-dia de um colaborador que está em viagem, por exemplo alimentação, translados, etc-:
| Campo | Tipo | Nome da API Salesforce | | --- | --- | --- | | Cliente | Relacionamento Pesquisa com objeto Conta | Cliente__c | | Colaborador | Relacionamento Pesquisa com objeto Contato | Colaborador__c | | Pagamento | Relacionamento Pesquisa com objeto Pagamento | Pagamento__c | | Custo | Moeda | Custo__c | | Data | Data | Data__c | | Descrição | Área de texto (255) | Descricao__c | | Status | Picklist | Status__c |
Viagem (Viagem__c)
Objeto personalizado para registro de viagens dos clientes:
| Campo | Tipo | Nome da API Salesforce | | --- | --- | --- | | Cliente | Relacionamento Mestre-Detalhe | Cliente__c | | Colaborador | Relacionamento Mestre-Detalhe | Colaborador__c | | Pagamento | Relacionamento Pesquisa | Pagamento__c | | Prestador de Serviços | Relacionamento Pesquisa | PrestadoresServicos__c | | Custo | Moeda | Custo__c | | Data/Hora Ida | Data/Hora | DataHoraIda__c | | Data/Hora Volta | Data/Hora | DataHoraVolta__c | | Destino | Texto | Destino__c | | Id Controle Viagem | Fórmula | IdControle_Viagem__c | | Origem | Texto | Origem__c |
Pagamento (Pagamento__c)
Objeto personalizado para registro dos pagamentos realizados pelos clientes:
| Campo | Tipo | Nome da API Salesforce | | --- | --- | --- | | Cliente | Relacionamento Pesquisa | Cliente__c | | Status | Picklist | Status__c | | Valor Total | Fórmula | ValorTotal__c | | Valor Total Despesas | Moeda | ValorTotalDespesas__c | | Valor Total Viagens | Moeda | ValorTotalViagens__c |
Flows
Fluxo “Conta - Atualiza Limite de Credito”
Descrição
Cenário
Conclusão
Na plataforma:
O primeiro fluxo foi realizado no 1º exercício da CSP Tech, logo na primeira semana, se relacionando com a possibilidade de alteração de Limite de Crédito na Conta da empresa:
Um usuário solicita a aprovação de novo limite de crédito para determinada empresa, no qual, o fluxo verifica se já existe tal solicitação para àquela conta ou se trata de nova. A depender do resultado da verificação, é solicitado que se aguarde ou que se escolha o novo limite de crédito:
Tela caso seja verificado que tal solicitação é nova:
Tela caso seja verificado que tal solicitação já existe:
Após verificar que se trata de nova solicitação de crédito, o valor atribuído pelo usuário é capturado e adicionado à variável “NovoLimite”:
Após adicionar à variável “NovoLimite”, o campo “LimiteSolicitado__c” do registro do objeto conta é atualizado com tal limite:
Após, a solicitação é enviada para aprovação, resultando na tela a seguir:
Fluxo “Conta - Atualiza Limite de Credito”
Descrição
Cenário
Conclusão
- Na plataforma:
O primeiro fluxo foi realizado no 1º exercício da CSP Tech, logo na primeira semana, se relacionando com a possibilidade de alteração de Limite de Crédito na Conta da empresa:
Um usuário solicita a aprovação de novo limite de crédito para determinada empresa, no qual, o fluxo verifica se já existe tal solicitação para àquela conta ou se trata de nova. A depender do resultado da verificação, é solicitado que se aguarde ou que se escolha o novo limite de crédito.
Tela caso seja verificado que tal solicitação é nova.
Tela caso seja verificado que tal solicitação já existe.
Após verificar que se trata de nova solicitação de crédito, o valor atribuído pelo usuário é capturado e adicionado à variável “NovoLimite”.
Após adicionar à variável “NovoLimite”, o campo “LimiteSolicitado__c” do registro do objeto conta é atualizado com tal limite.
Após, a solicitação é enviada para aprovação, resultando na tela a seguir:
Tela final.
Fluxo “Despesas - Processo de Aprovação”
Descrição
Cenário
Conclusão
Na plataforma:
O Fluxo acionado por registro se inicia após um registro do Objeto Despesa ser criado.
Após, o ID do registro Despesa é coletado e enviado para o processo de aprovação.
Após, é obtido o ID da notificação personalizada, através de GetRecords, personalizada para enviar no componente de ação.
Após, no Objeto Grupo, é obtida a fila responsável pela futura aprovação ou desaprovação da despesa. Cabe destacar que para esse processo vigorar restou necessário inserir nos Processos de Aprovação, recurso de automação do processo fornecido pelo Salesforce.
Após o resultado acima, é obtido o ID do Grupo que se encontra nessa fila, sendo que logo após um loop é realizado nos IDs obtidos.
Para cada item do loop, é adicionado a variável destinátarios, responsável por coletar tais IDs nos quais a notificação será direcionada.
Após, finalizando o ciclo, as informações coletadas são adicionadas à uma ação de notificação, momento no qual ela é disparada.
Na plataforma
A princípio, o fluxo é acionado quando um registro do objeto Despesa é criado, sendo que logo após uma ação é chamada.
Após, a notificação, a fila da aprovação e os membros do grupo são obtidos através do método get. Isso será importante no futuro, durante o loop
Após, cada membro do grupo obtido acima, é enviado uma notificação da despesa para aprovação, encerrando, desta maneira, o presente fluxo.
Fluxo “Event - Criar compromisso de viagem”
Descrição
Cenário
Conclusão
Na plataforma
No primeiro momento, acionado após a criação ou atualização do registro de Viagem, através do método Get, é obtido todos os registros do objeto Compromisso que se adequem as condições inseridas. Assim foi feito com intuito de verificar quais compromissos encontram-se entre aquela data cadastrada para viagem.
Após, tais registros são submetidos a um loop de interação, de modo a verificar se se trata de atualização (compromisso já existente) ou de novo compromisso. Assim foi realizado devido ao fato de que o Fluxo é acionado tanto na inserção de novos registos quanto atualização de preexistentes.
Após o último registro submetido ao loop citado acima, uma decisão pelo Flow é realizada, de modo a verificar se existe eventos naquela data. Para tanto, utilizada da variável ContadordeEventos, responsável por acordar os registros da viagem obtidos logo no início do Flow.
Caso se verifique que a agenda está livre, sem compromissos na data da viagem, após é submetido a nova decisão, de modo a verificar se trata de atualização do registro de viagem ou nova viagem.
Caso se trata de nova viagem, o Fluxo já parte para a parte final, no qual novo registro será criado no objeto compromisso.
Para tanto, são adicionados as informações da viagem, devidamente obtidas logo no início do Fluxo, são atribuídas ao registro do objeto Compromisso.
Após, através do getRecords, é obtido o modelo de Email, que se encontra cadastrado no “Modelos de Email Classic” do Salesforce que será enviado ao usuário final.
Após, um email é enviado, finalizando o fluxo.
Caso se trate de uma atualização de um registro de viagem preexistente, o fluxo é direcionado para atualização do registro com as informações obtidas ao início do fluxo principal, de modo a atualizar o registro de viagem, notificar a atuaçização, enviar notificação sobre o sucesso da operação, obter os emails necessários e enviar o email informando a alteração do evento.
Caso se verifique que existem registros de compromissos na data da viagem cadastrada, através do método GetRecords é obtido o ID das notificações para posterior envio de impossibilidade de criação do evento.
Após, ocorre a atribuição dos destinátários da notificação, enviando a notificação de agenda indisponível:
Texto do corpo da notificação: A viagem: {!$Record.Name} precisa ser ajustada, já existe um compromisso no intervalo de datas informado.
Fluxo “Event Cria Evento Viagem Apex”
Cenário
Explicação
Conclusão
Na plataforma:
Fluxo acionado pela criação de algum registro do objeto Viagem. Acionando o código Apex abaixo, fazendo com que um evento de viagem seja inserido no calendário:
public class CriaEventoViagem {
@InvocableMethod(label='Cria Evento para Viagem' description='Cria Evento para Viagem' category ='Viagem__c')
public static void criarEventoDataViagem(List<Viagem__c> novasViagens){
List<Event> eventos = new List<Event>();
for(Viagem__c viagem :novasViagens){
Event evento = new Event();
evento.Subject = viagem.Origem__c + ' - ' + viagem.Destino__c;
evento.ActivityDateTime = viagem.DataHoraIda__c;
evento.EndDateTime = viagem.DataHoraVolta__c;
evento.WhatId = viagem.Id;
evento.WhoId = viagem.Colaborador__c;
eventos.add(evento);
}
system.debug('EVENTOS: ' + eventos);
insert eventos;
}
}
Apex
Controller de Pagamentos
Descrição
Cenário
Conclusão
- Na plataforma:
public class PagamentosGestaoController {
//AuraEnabled feito para comunicar o LWC ou o component Aura com o APEX
@AuraEnabled(cacheable=true)
public static List<Account> getClientesAtivos(){
return [SELECT Name
FROM Account
WHERE Status__c = 'Ativa'
AND RecordType.Name = 'Cliente'];
}
@AuraEnabled
public static List<Viagem__c> getViagens(Id contaId, Date dataInicio, Date dataFim){
return [SELECT Origem__c, Destino__c, Custo__c,
DataHoraIda__c, DataHoraVolta__c,
Colaborador__r.Name, Colaborador__r.FirstName
FROM Viagem__c
WHERE
DataHoraIda__c >= :dataInicio
AND DataHoraIda__c <= :dataFim
AND Cliente__c = :contaId
AND Pagamento__c = null
ORDER BY DataHoraIda__c
];
}
@AuraEnabled
public static List<Despesa__c> getDespesas(Id contaId, Date dataInicio, Date dataFim){
return [SELECT Custo__c, DataDespesa__c, Descricao__c, Colaborador__r.Name, Colaborador__r.FirstName
FROM Despesa__c
WHERE
DataDespesa__c >= :dataInicio
AND DataDespesa__c <= :dataFim
AND Cliente__c = :contaId
AND Pagamento__c = null
ORDER BY DataDespesa__c
];
}
@AuraEnabled
public static Pagamento__c criarPagamento(Id contaId, List<Viagem__c> viagens, List<Despesa__c> despesas){
Pagamento__c pagamento = new Pagamento__c();
pagamento.Cliente__c = contaId;
pagamento.Status__c = 'Aguardando Pagamento';
insert pagamento;
Decimal valorTotalCustoViagem = 0;
if(!viagens.isEmpty()){
for(Viagem__c viagem : viagens){
viagem.Pagamento__c = pagamento.Id;
valorTotalCustoViagem += viagem.Custo__c;
}
update viagens;
}
Decimal valorTotalCustoDespesa = 0;
if(!despesas.isEmpty()){
for(Despesa__c despesa : despesas){
despesa.Pagamento__c = pagamento.Id;
valorTotalCustoDespesa += despesa.Custo__c;
}
update despesas;
}
pagamento.ValorTotalViagens__c = valorTotalCustoViagem;
pagamento.ValorTotalDespesas__c = valorTotalCustoDespesa;
update pagamento;
//update new Pagamento__c(Id=pagamento.Id, ValorTotalViagens__c=valorTotalCustoViagem, ValorTotalDespesas__c=valorTotalCustoDespesa);
Formula.recalculateFormulas(new List<Pagamento__c>{pagamento});
System.debug('VALOR TOTAL PAGAMENTO ' + pagamento.ValorTotal__c);
return pagamento;
}
//Não está sendo usado, mas fiz para que vocês possam estudar como retornar uma classe customizada para um LWC ou AURA especifico
public class Pagamento{
@AuraEnabled
public Id pagamentoId;
@AuraEnabled
public Decimal valorTotal;
public Pagamento(Id pagamentoId, Decimal valorTotal){
this.pagamentoId = pagamentoId;
this.valorTotal = valorTotal;
}
}
}
Criar Evento Viagem
Vide“Fluxo “Event Cria Evento Viagem Apex”
Controller do Gerador PDF de Pagamentos
Descrição
Pagamento
c, Viagemc
, Despesac
, e Account
são objetos personalizados em Salesforce. O método construtor ControllerPagamentoPDF()
é responsável por inicializar os dados quando a página é carregada. Ele faz isso recuperando os IDs de pagamento e conta dos parâmetros da página e, em seguida, utiliza esses IDs para buscar os registros correspondentes do banco de dados.Cenário
Conclusão
- Na plataforma:
public class ControllerPagamentoPDF {
public Pagamento__c Pagamento {get; set;}
public Viagem__c Viagem {get; set;}
public Despesa__c Despesa {get; set;}
public String PagamentoId {get;set;}
public Account Account {get; set;}
public String AccountId {get;set;}
public string userName {get;set;}
public String dt {get;set;}
public ControllerPagamentoPDF(){
this.PagamentoId = apexpages.currentpage().getparameters().get(PagamentoId);
if(this.PagamentoId!=null) {
this.PagamentoId = [SELECT Id FROM Pagamento__c WHERE Id =: this.PagamentoId LIMIT 1].Id;
//
this.AccountId = apexpages.currentpage().getparameters().get(AccountId);
if(this.AccountId!=null) {
this.AccountId = [SELECT Id FROM Account WHERE Id =: this.AccountId LIMIT 1].Id;
//
this.userName=userinfo.getName();
this.dt= date.today().format();
}
}
}
}
Visual Force Page
Gerador de pagamento PDF
Descrição
Cenário
Conclusão
- Na plataforma:
<apex:page controller="ControllerPagamentoPDF" applyHtmlTag="false" showHeader="false" renderAs="PDF">
<html>
<head>
<style type="text/css">
.header {
font-family: sans-serif;
padding-bottom: 30px;
width: 100%;
}
.logo {
width: 180px;
height: 90px;
}
.company-name {
font-size: 22pt;
padding-left: 10px;
padding-right: 140px;
}
.title {
font-size: 22pt;
}
.table {
font-family: sans-serif;
font-size: 12px;
width: 100%;
}
.table-header {
color: #fff;
background-color: rgb(3, 175, 243);
}
.table-header th {
padding-left: 5px;
padding-right: 170px;
}
.table-row td {
padding-left: 5px;
height: 30px;
}
.table-row td.width-35 {
width: 35%;
}
.table-row td.width-40 {
width: 40%;
text-align: right;
}
</style>
</head>
<body>
<table class="header">
<tr>
<td><apex:image id="logo2" value="{!$Resource.csplogo}" width="180" height="90"/></td>
<td class="company-name">CSPlane</td>
<td class="title">FATURA</td>
</tr>
</table>
<br></br>
<table class="table">
<tr class="table-header">
<th>Bill To</th>
</tr>
<tr class="table-row">
<td class="width-35"><apex:outputField value="{!Account.Name}"/></td>
<td class="width-40">Created Date</td>
<td class="padding-left:5px;">{!dt}</td>
</tr>
<tr class="table-row">
<td class="width-35"><apex:outputField value="{!Account.BillingStreet}"/></td>
<td class="width-40">Contact Name</td>
<td class="padding-left:5px;">{!Account.Name}</td>
</tr>
<tr class="table-row">
<td class="width-35"><apex:outputField value="{!Account.BillingState}"/></td>
<td class="width-40">Email</td>
<td class="padding-left:5px;">{!Account.Email__c}</td>
</tr>
</table>
</body>
</html>
</apex:page>