Como compilar comprovantes formais de pedidos, capturar assinaturas digitais na tela do tablet e imprimir documentos em impressoras térmicas via Bluetooth sem depender de conexão com a nuvem.
No segmento de bens de consumo embalados (CPG/FMCG), poucas rotinas operacionais são tão críticas e sensíveis ao tempo quanto a entrega direta em loja (Direct Store Delivery – DSD) e as visitas de negociação comercial em campo (Van Sales).
Quando o caminhão de pronta-entrega estaciona na doca de um grande supermercado ou quando um representante de vendas conclui uma negociação de reposição urgente no balcão de uma mercearia, o gerente do estabelecimento exige um comprovante físico ou digital formalizado naquele exato instante:
- A lista discriminada de caixas e unidades entregues por código de barras e lote.
- O espelho de cálculo com preços unitários, descontos negociados e alíquotas de impostos.
- A captura obrigatória da assinatura de aceite do recebedor na tela do dispositivo.
- A emissão de um comprovante impresso na hora ou compartilhado em PDF via e-mail e mensageiros.
No entanto, há um obstáculo geográfico inevitável: as docas de recebimento e os depósitos de varejo frequentemente situam-se em subsolos, galpões metálicos ou zonas com zero sinal de internet móvel (4G/5G).
Nesse cenário desconectado, as abordagens convencionais de geração de documentos utilizadas no Salesforce na nuvem — como renderização de páginas Visualforce com renderAs="pdf" ou soluções pesadas de OmniStudio Document Generation — falham imediatamente, pois exigem conexão contínua com os servidores centrais.
Para suprir essa necessidade operacional inegociável, o Salesforce Consumer Goods Cloud (CGC) Modeler (Mv2) disponibiliza o contrato nativo PrintLayoutV2.
Governada por arquivos com a extensão .printlayoutv2.xml, essa camada declarativa atua como um motor de diagramação de documentos executado diretamente pela casca nativa do aplicativo móvel (iOS e Android). O motor compila os dados residentes no banco de dados relacional SQLite local (app.db3), renderiza vetores de texto, tabelas repetidoras e imagens de assinatura digital, e gera um arquivo PDF completo e autônomo em frações de segundo, sem que um único pacote de dados precise transitar pela internet.
Neste guia técnico, examinamos a estrutura do contrato XML, como preparar e internacionalizar os dados em arquivos de Business Logic (.bl.js), como disparar a esteira no ProcessFlow e as melhores práticas para integrar o PDF com impressoras móveis via Bluetooth e folhas de compartilhamento nativas do sistema operacional.
1. Topologia da Geração de Documentos Offline com PrintLayoutV2
A arquitetura de impressão do Modeler não se apoia em motores de renderização web baseados em Chromium; ela utiliza um pipeline de renderização vetorial compilado em código nativo de baixo nível dentro da casca móvel do CG Cloud:
Pipeline de Geração de Documentos Offline:
1. SQLite Local (app.db3): Contém o pedido (BoOrder) e itens (LoOrderItems)
│
2. Business Logic (.bl.js): Extrai, valida e formata valores monetários e datas
│ └──> Monta o contexto tipado para a esteira de impressão
│
3. ProcessFlow (.processflow.xml): Executa a ação do tipo 'PROCESS' ou 'PRINT'
│ └──> Dispara a captura da assinatura digital na tela
│
4. Contrato .printlayoutv2.xml: Define cabeçalho, colunas de itens, subtotais e rodapé
│ └──> O motor nativo une o layout aos dados locais
│
5. Saída Física / Digital:
├──> Visualizador de PDF nativo embutido no aplicativo
├──> Disparo direto para impressoras térmicas via Bluetooth (Zebra, Epson)
└──> Exportação via Share Sheet do SO (WhatsApp, E-mail corporativo, AirDrop)2. A Anatomia do Contrato XML: .printlayoutv2.xml
Um contrato PrintLayoutV2 define a geometria da página, margens, fontes corporativas e a distribuição das seções visuais em blocos bem definidos:
Seções Estruturais do Contrato:
<Declarations>: Declara os objetos consumidos (BoOrder,LuSalesOrg,Signature,BoOrderPrintTextItems).<ReportLayout>: Define margens de página (pageMargins="[40,40,40,40]").<table name="HeaderTable">: Cabeçalho corporativo contendo logotipo, razão social e dados cadastrais da loja em atendimento.<table name="OrderItemsTable">com<each>: Tabela repetidora iterando sobre a coleção de itens do pedido (loPrintItems), com filtros e ordenação configurados.<table name="TotalTable">: Resumo consolidado de faturamento, com<sum>e total líquido calculado.<table name="SignatureTable">: Caixa delimitada para renderizar o bitmap da assinatura digital colhida na tela do tablet via<img src="{{Declarations::signature1}}" />.- Rodapé: Numeração de páginas, carimbo de data e hora do SQLite e termos legais de recebimento de mercadoria.
Exemplo de Contrato Declarativo: OrderReceiptPrintLayoutV2.printlayoutv2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
Data: 2026-10-12
Objetivo da Customização: PrintLayoutV2 para geração de pedidos e relatórios em PDF 100% offline no CG Cloud Mobile com assinatura digital
-->
<PrintLayout xmlns="https://www.salesforce.com/cgcloud/xsds" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="OrderReceiptPrintLayoutV2">
<Declarations>
<DataDeclaration name="order" type="BoOrder" />
<DataDeclaration name="salesOrg" type="LuSalesOrg" />
<DataDeclaration name="signature1" type="Signature" />
<DataDeclaration name="textItems" type="BoOrderPrintTextItems" />
</Declarations>
<ReportLayout pageMargins="[40,40,40,40]">
<!-- Cabeçalho Corporativo e Dados do Cliente -->
<table name="HeaderTable" tableLayout="noBorders">
<tbody>
<tr>
<td width="260" alignment="left" bold="true">{{Declarations::salesOrg.text}}</td>
<td width="100" alignment="right">{{Labels::DocumentNoId; defaultLabel=Document No.:}}</td>
<td width="130">{{Declarations::order.orderId}}</td>
</tr>
<tr>
<td>{{Labels::CustomerAddressId; defaultLabel=Address:}} {{Declarations::salesOrg.street}} {{Declarations::salesOrg.city}}</td>
<td alignment="right">{{Labels::DeliveryDateId; defaultLabel=Delivery Date:}}</td>
<td>{{Declarations::order.deliveryDate; dateTimeFormat=date}}</td>
</tr>
<tr>
<td>{{Labels::CustomerNumberId; defaultLabel=Customer:}} {{Declarations::order.luDeliveryRecipient.name}}</td>
<td alignment="right">{{Labels::CustomerCommitDateId; defaultLabel=Order Date:}}</td>
<td>{{Declarations::order.commitDate; dateTimeFormat=date}}</td>
</tr>
</tbody>
</table>
<h2 alignment="center">{{Labels::OrderConfirmationId; defaultLabel=Comprovante Oficial de Pedido}}</h2>
<!-- Tabela Repetidora de Itens do Pedido -->
<table tableLayout="lightHorizontalLinesMainItemsOnly" name="OrderItemsTable">
<thead>
<tr>
<th width="75">{{Labels::PrdNumberId; defaultLabel=Código}}</th>
<th width="*">{{Labels::TextId; defaultLabel=Produto}}</th>
<th width="35" alignment="center">{{Labels::UnitId; defaultLabel=Unid.}}</th>
<th width="35" alignment="center">{{Labels::QuantityId; defaultLabel=Qtd}}</th>
<th width="50" alignment="right">{{Labels::UnitPriceId; defaultLabel=Preço Unit.}}</th>
<th width="55" alignment="right">{{Labels::NettoAmountId; defaultLabel=Total Líq.}}</th>
</tr>
</thead>
<tbody>
<each name="itemsEach" value="{{Declarations::order.loPrintItems}}">
<filters>
<filter fieldName="quantity" value="0" operator="GT" compareMode="NUMBER" />
</filters>
<orderCriteria>
<orderCriterion fieldName="prdId" direction="ASC" compareMode="NUMBER" />
</orderCriteria>
<tr>
<td>{{.prdId}}</td>
<td>{{.text1}}</td>
<td alignment="center">{{.quantityLogisticUnit; toggleId=DomPrdLogisticUnit; toggleField=shortText}}</td>
<td alignment="right">{{.quantity}}</td>
<td alignment="right">{{.basePriceReceipt; numberFormat=8.2}}</td>
<td alignment="right">{{.valueReceipt; numberFormat=8.2}}</td>
</tr>
</each>
<tr>
<td colSpan="3" alignment="center" bold="true">{{Labels::TotalId; defaultLabel=Total dos Itens}}</td>
<td alignment="right" bold="true">
<sum table="OrderItemsTable" col="3" />
</td>
<td />
<td alignment="right" bold="true">
<sum table="OrderItemsTable" col="5" numberFormat="8.2" />
</td>
</tr>
</tbody>
</table>
<!-- Tabela de Totais Finais -->
<table tableLayout="noBorders" name="TotalTable">
<tbody>
<tr>
<td width="*" />
<td width="auto" alignment="right" bold="true">{{Labels::TotalAmountId; defaultLabel=Valor Total a Faturar:}}</td>
<td width="auto" alignment="right" bold="true">{{Declarations::order.grossTotalValueReceipt; numberFormat=8.2}} {{Declarations::order.currency}}</td>
</tr>
</tbody>
</table>
<!-- Bloco de Captura de Assinatura Digital do Gerente da Loja -->
<table name="SignatureTable" tableLayout="noBorders">
<tbody>
<tr>
<td width="200" alignment="center">
<img src="{{Declarations::signature1}}" width="160" alignment="center" />
</td>
<td width="*" />
</tr>
<tr>
<td alignment="center" bold="true">{{Declarations::textItems.signature1Name}}</td>
<td />
</tr>
<tr>
<td alignment="center">{{Labels::SignatureReceiptNoticeId; defaultLabel=Assinatura do Responsável no Recebimento}}</td>
<td />
</tr>
</tbody>
</table>
<p>{{Declarations::salesOrg.legalInformation}}</p>
<p>{{Declarations::textItems.endOfPrint}}</p>
</ReportLayout>
</PrintLayout>3. Preparação dos Dados e Internacionalização na Business Logic (.bl.js)
Um motor de impressão móvel não deve conter lógica matemática interna; ele apenas formata dados textuais pré-calculados.
Antes de invocar o layout de impressão, a Business Logic do Business Object mestre (BoOrder) percorre a coleção de itens do pedido, calcula as somas líquidas e formata moedas e datas respeitando o idioma e a moeda local configurados no tablet via Localization.localize().
Crie o arquivo BoOrder.PreparePrintData.bl.js:
/* Data: 2026-10-12; Objetivo da Customização: Preparação e formatação localizada de dados de pedido e itens para o PrintLayoutV2 */
/**
* @function preparePrintData
* @this BoOrder
* @kind businessobject
* @namespace Order
* @param {LoOrderItems} orderItemsList Coleção de itens do pedido.
* @returns {Promise} Promessa resolvida após enriquecer os objetos para o PrintLayoutV2.
*/
function preparePrintData(orderItemsList) {
var me = this;
// 1. Formata textos de cabeçalho do pedido
var customerInfo = "Cliente: " + me.getCustomerName() + " (CNPJ: " + me.getCustomerTaxNumber() + ")";
me.setCustomerFormattedInfo(customerInfo);
var formattedDate = Localization.localize(me.getOrderDate(), "date");
me.setOrderDateText("Data: " + formattedDate);
var generationStamp = "Emitido via CG Cloud Mobile em " + Localization.localize(Utils.createAnsiDateTimeToday(), "dateTime");
me.setGenerationTimestampText(generationStamp);
// 2. Itera sobre o ListObject formatando linhas de tabela
var items = orderItemsList.getAllItems();
var grossSum = 0.0;
var discountSum = 0.0;
items.forEach(function (item) {
var qty = Number(item.getQuantity()) || 0;
var unitPrice = Number(item.getUnitPrice()) || 0.0;
var discount = Number(item.getDiscount()) || 0.0;
var lineTotal = (qty * unitPrice) - discount;
grossSum += (qty * unitPrice);
discountSum += discount;
// Injeta propriedades de texto pré-formatadas nas colunas do ListItem
item.setFormattedQuantity(String(qty));
item.setFormattedUnitPrice(Localization.localize(unitPrice, "currency"));
item.setFormattedLineTotal(Localization.localize(lineTotal, "currency"));
});
var netTotal = grossSum - discountSum;
// 3. Atribui os totalizadores formatados ao BusinessObject mestre
me.setFormattedGrossTotal(Localization.localize(grossSum, "currency"));
me.setFormattedDiscountTotal(Localization.localize(discountSum, "currency"));
me.setFormattedNetTotal(Localization.localize(netTotal, "currency"));
AppLog.info("Dados de impressão preparados com sucesso para o pedido: " + me.getPKey());
return when.resolve(me);
}Por Que Usar
when.resolve(me)? Como estabelecido nos padrões do Modeler, o compilador e o runtime exigem que métodos assíncronos retornem promessas da bibliotecawhencompatíveis com o motor Mv2 para que o encadeamento no ProcessFlow resolva sem bloqueios na UI thread.

4. Orquestrando a Ação de Impressão e Assinatura no ProcessFlow
Com o layout configurado e os dados enriquecidos na memória, a máquina de estados do arquivo de processo (Order_DetailProcess.processflow.xml) comanda a interação com o usuário:
<!-- 1. Prepara as variáveis e formata os dados -->
<Action name="Order_PreparePrintContext" actionType="LOGIC"
call="ProcessContext::CurrentOrder.preparePrintData">
<Parameters>
<Input name="orderItemsList" value="ProcessContext::OrderItemsList" />
</Parameters>
<TransitionTo action="Order_OpenSignatureCaptureDialog" />
</Action>
<!-- 2. Abre a tela modal de captura de assinatura do gerente -->
<Action name="Order_OpenSignatureCaptureDialog" actionType="PROCESS"
process="Core::SignatureCaptureProcess">
<Return name="ProcessContext::CurrentOrder.storeManagerSignatureBlob" />
<TransitionTo action="Order_ExecutePrintLayout" />
</Action>
<!-- 3. Invoca o motor nativo de compilação do PrintLayoutV2 -->
<Action name="Order_ExecutePrintLayout" actionType="PROCESS"
process="Core::PrintProcess">
<Parameters>
<!-- Referência ao contrato de layout criado -->
<Input name="layoutName" value="OrderReceiptPrintLayoutV2" type="Literal" />
<Input name="businessObject" value="ProcessContext::CurrentOrder" />
<Input name="listObject" value="ProcessContext::OrderItemsList" />
<!-- Modo de entrega: PREVIEW_AND_SHARE ou DIRECT_BLUETOOTH -->
<Input name="printMode" value="PREVIEW_AND_SHARE" type="Literal" />
</Parameters>
<TransitionTo action="Order_FinalizeAndCommit" />
</Action>
<!-- 4. Grava a confirmação no banco SQLite local com commit="true" -->
<Action name="Order_FinalizeAndCommit" actionType="LOGIC"
call="ProcessContext::CurrentOrder.markAsReceiptIssued">
<TransitionTo action="Order_ExitView" commit="true" />
</Action>5. Impressão Térmica Bluetooth versus Exportação Digital
Dependendo da dinâmica de campo, a operação adota duas modalidades de entrega:
1. Van Sales e DSD: Impressão Térmica via Bluetooth
Em caminhões de pronta-entrega de bebidas, alimentos e laticínios, os motoristas carregam impressoras térmicas portáteis (como modelos Zebra ZQ520 ou Epson Mobilink) conectadas via Bluetooth com o tablet.
Ao configurar printMode="DIRECT_BLUETOOTH", o shell do CG Cloud Mobile envia comandos de impressão na linguagem da impressora (CPCL ou ESC/POS), imprimindo a bobina física instantaneamente.
2. Promotores e Representantes Comerciais: Compartilhamento Digital
Para equipes de auditoria ou representantes comerciais convencionais que não carregam impressoras na cintura, a casca do aplicativo renderiza o PDF no visualizador nativo e dispara a folha de compartilhamento (Native Share Sheet):
O promotor envia o PDF assinado diretamente para o WhatsApp do proprietário da loja ou para o e-mail do setor de contas a pagar do cliente, arquivando o comprovante digital no ato da reunião.
6. Comparativo Arquitetural: Geração de Relatórios no Salesforce
| Vetor de Engenharia | Nuvem: Visualforce renderAs="pdf" | Nuvem: OmniStudio Document Gen | Mobile Nativo: PrintLayoutV2 |
|---|---|---|---|
| Operação 100% Offline | Impossível. Exige chamada de servidor ativa. | Impossível. Depende de APIs em nuvem. | Total. Roda nativamente no aparelho. |
| Origem dos Dados | Banco relacional da nuvem da Salesforce. | Registros já sincronizados no CRM. | Banco SQLite local (app.db3). |
| Latência de Geração | Média (depende da conexão 4G e fila). | Média (depende de conversão de template). | Instantânea (frações de segundo no chip local). |
| Captura de Assinatura no Ato | Exige sincronização prévia de imagem. | Complexa em cenários desconectados. | Nativa. O traço da tela vai direto ao PDF. |
| Conexão com Impressoras Bluetooth | Inviável sem gateways adicionais. | Inviável. | Nativa através do runtime móvel. |
| Conformidade em Subsolos de Loja | Zero. O promotor fica bloqueado sem sinal. | Zero. | 100% Confiável. Projetado para o PDV. |
7. Diretrizes e Cuidados para Arquitetos de Soluções
Ao desenhar contratos PrintLayoutV2 para projetos corporativos, considere estas regras de boas práticas:
- Evite Imagens Pesadas no Cabeçalho: O logotipo corporativo inserido na declaração
<DataDeclaration name="myImageName" type="Image" ... />deve ser um arquivo leve e pré-otimizado (SVG ou PNG em escala de cinza comprimido). Imagens pesadas aumentam o consumo de memória durante a compilação do PDF no chip do dispositivo. - Tratamento de Quebras de Página em Pedidos Extensos: Se um pedido contiver dezenas ou centenas de itens, o atributo
tableLayout="lightHorizontalLinesMainItemsOnly"com<thead...>garante que as páginas subsequentes repitam os títulos das colunas sem truncar o layout. - Persistência de Assinaturas no SQLite: Nunca confie apenas na memória volátil da tela de assinatura. Garanta que o bitmap capturado pelo
SignatureCaptureProcessseja persistido como campo binário ou blob no SQLite (app.db3) antes de concluir o processo, permitindo que o documento seja reemitido caso o papel da impressora acabe no meio do processo.
Conclusão
A arquitetura do Salesforce Consumer Goods Cloud Modeler prova sua maturidade de engenharia ao garantir que nenhum elo da cadeia de vendas dependa da fragilidade da conectividade externa.
Com o contrato PrintLayoutV2, o aplicativo móvel fecha com excelência a experiência no ponto de venda: da consulta inicial do cliente ao cálculo de preços complexos, chegando à formalização final de um documento PDF assinado e impresso diretamente na doca de descarregamento.
Ao unificar contratos declarativos no XML, internacionalização na Business Logic e aceleração de hardware nativa no dispositivo, arquitetos e desenvolvedores entregam uma solução robusta, garantindo conformidade jurídica e fiscal em cada visita de varejo.
