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 biblioteca when compatí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 EngenhariaNuvem: Visualforce renderAs="pdf"Nuvem: OmniStudio Document GenMobile Nativo: PrintLayoutV2
Operação 100% OfflineImpossível. Exige chamada de servidor ativa.Impossível. Depende de APIs em nuvem.Total. Roda nativamente no aparelho.
Origem dos DadosBanco relacional da nuvem da Salesforce.Registros já sincronizados no CRM.Banco SQLite local (app.db3).
Latência de GeraçãoMé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 AtoExige 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 BluetoothInviável sem gateways adicionais.Inviável.Nativa através do runtime móvel.
Conformidade em Subsolos de LojaZero. 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:

  1. 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.
  2. 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.
  3. Persistência de Assinaturas no SQLite: Nunca confie apenas na memória volátil da tela de assinatura. Garanta que o bitmap capturado pelo SignatureCaptureProcess seja 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.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *