Tutorial · 45 min

Construa a sua primeira aplicação

No fim deste tutorial terá um pequeno CRM com três tabelas (company, contact, deal), uma página de lista-e-detalhe para cada, um Business Event que carimba automaticamente o updated_at do deal, e um Script Module que recalcula o valor total dos deals em aberto da empresa sempre que um deal muda. Todos os conceitos de Conceitos centrais aparecem pelo menos uma vez.

Assume que terminou Começar e que tem um ambiente Archestack aberto noutra aba. Tempo total: cerca de 45 minutos se ler com atenção, 25 se ler por alto.

Convenções usadas aqui: nomes de tabela e coluna em snake_case (a plataforma dá a dica desta convenção no campo "Table name" do Schema Designer). Os exemplos de código usam a API Db real, ver Referência → APIs invocáveis a partir de scripts para a superfície completa.

Passo 1 - Desenhar o schema (10 min)

Abra Schema Designer e adicione três tabelas.

Para cada tabela: clique em Add Table na barra de ferramentas, escreva o nome no campo "Table name" do diálogo, clique em Create. A nova tabela aparece no canvas com uma chave primária SERIAL id gerada automaticamente (bloqueada). O painel à direita abre no separador Columns, adicione colunas adicionais a partir da caixa tracejada "Add column" no fundo. Clique em cada coluna para a expandir e ajustar os interruptores (PK / NULL / UQ), o Length e o Default value.

Tabela: company

  • id · SERIAL, PK (auto)
  • name · VARCHAR(200), obrigatório
  • industry · VARCHAR(80), nullable
  • open_deal_value · DECIMAL, default 0 - mantido em sincronização via o script no Passo 5
  • created_at · TIMESTAMPTZ, default now()

Tabela: contact

  • id · SERIAL, PK (auto)
  • first_name, last_name · VARCHAR(100), obrigatório
  • email · VARCHAR(200), obrigatório, UQ ligado
  • company_id · INTEGER, obrigatório - chave estrangeira, configurada a seguir

Tabela: deal

  • id · SERIAL, PK (auto)
  • company_id · INTEGER, obrigatório - chave estrangeira, configurada a seguir
  • title · VARCHAR(200), obrigatório
  • stage · VARCHAR(40), obrigatório, default 'New' - valores: New, Qualified, Proposal, Won, Lost
  • amount · DECIMAL, obrigatório, default 0
  • created_at, updated_at · TIMESTAMPTZ, default now()

Chaves estrangeiras

As chaves estrangeiras vivem no separador Relations do painel à direita. Selecione a tabela contact, mude para Relations, desça até à secção "Add relationship":

  1. FK column (on this table) = company_id
  2. Relation type = One-to-Many
  3. References → table = company
  4. References → column = id
  5. Clique em Add Relationship.

Repita para a tabela deal (o seu company_idcompany.id).

Canvas do Schema Designer com várias tabelas ligadas por linhas FK. A barra de ferramentas no topo tem separadores Canvas/JSON, menu Packages, pesquisa Find table, controlos de zoom, e à direita: + (Add Table), Add Group, Add Text, Save, JSON/código, Deploy (foguetão verde).
Como fica o canvas quando tem algumas tabelas com relações. A barra de ferramentas (topo) tem separadores Canvas / JSON, um filtro Packages, uma pesquisa Find-table, controlos de zoom, e à direita os botões de ícone + (Add Table), Add Group, Add Text, Save, JSON, e o foguetão verde Deploy. As colunas FK mostram um ícone de elo de cadeia junto ao seu tipo, e as relações renderizam como linhas coloridas entre tabelas. (O exemplo mostrado usa tabelas diferentes deste tutorial, o seu setup company / contact / deal terá a mesma forma com duas linhas FK a apontar para company.)

Deploy

Clique em Deploy na barra de ferramentas do Schema Designer. Aterra na página de configuração do deployment. Mude para o separador Generated SQL, deverá ver três statements CREATE TABLE mais as constraints FK. Clique em Deploy no canto superior direito.

O que verá se funcionar: a linha do deployment em Database Deployments → Overview passa de "Executing" para "Succeeded" dentro de alguns segundos. As três tabelas aparecem na lista de tabelas do Object Browser.

Se o deploy falhar em constraints FK: o Generated SQL emite tabelas pela ordem em que foram guardadas. Abra o separador SQL e reordene para que company seja criada antes de contact e deal, depois faça redeploy. Ou clique em Regenerate após reordenar manualmente as tabelas no canvas do Schema Designer.

Passo 2 - Compor as Business Entities (10 min)

Cada página precisa de uma Business Entity a que se ligar. Abra Business Entities e clique em Create para cada uma. Carregue em Run Preview após cada gravação para confirmar que as colunas voltam corretamente.

BE: company

Entity Name company, Master Table company, Label Column name. Guarde.

Clique em Add Join duas vezes para adicionar dois joins agregados:

  • Join a contact: From column company.id, To column contact.company_id. Ative Aggregate Mode. Escolha a coluna id, defina Aggregate Function = COUNT, nomeie-a contact_count.
  • Join a deal: From column company.id, To column deal.company_id. Aggregate Mode ligado. Escolha a coluna id, função COUNT, nomeie-a open_deal_count. (Vamos filtrar para deals "em aberto" via um Business Event no Passo 5; por agora isto conta todos os deals.)

BE: contact

Entity Name contact, Master Table contact, Label Column email. Adicione um Join a company via contact.company_id → company.id, expondo a coluna name como company_name. (Não ative Aggregate Mode aqui, é um join normal.)

BE: deal

Entity Name deal, Master Table deal, Label Column title. Adicione um Join a company via deal.company_id → company.id, expondo a coluna name como company_name.

Verifique: em cada BE, clique em Run Preview. Verá uma grelha vazia (ainda sem dados) com os cabeçalhos de coluna que definiu. Se um cabeçalho de coluna de join está em falta, provavelmente esqueceu-se de marcar a coluna no seletor de colunas do join. Reabra o join, marque a coluna, guarde, corra a pré-visualização novamente.

Passo 3 - Construir as páginas (10 min)

Abra Page Editor. Para cada Business Entity, clique em Create:

  1. Companies - Page Name Companies, Page Route /companies, Business Entity company. O separador Visual gera automaticamente uma lista e um formulário de detalhe. No formulário de detalhe, clique em Add Tab duas vezes para adicionar os separadores Contacts e Deals. Em cada separador, adicione uma secção RelatedGrid ligada à BE contact / deal com o filtro de join definido para company.id = id do registo atual.
  2. Contacts - Page Name Contacts, Page Route /contacts, Business Entity contact. O campo company_id do formulário de detalhe gerado automaticamente aparecerá como número, mude o seu Type para Select e defina o autocomplete Entity para company para que os utilizadores escolham uma empresa pelo nome.
  3. Deals - Page Name Deals, Page Route /deals, Business Entity deal. Mesmo tratamento para company_id (Type Select, Entity company). Para stage, deixe o Type como Text por agora, os valores podem ser impostos via um evento Validate mais tarde se quiser.

Para cada página, vire o interruptor Published no cabeçalho do topo para ON. As páginas aparecem na barra lateral sob uma secção APPLICATION (agrupadas por categoria). Adicione uma Company, depois um Contact ligado a essa Company, depois um Deal, confirme que as relações renderizam corretamente. A página Companies deverá agora mostrar 1 em contact_count.

Página de utilizador final publicada com breadcrumbs (Home / Customers / Record #9), um botão Edit no canto superior direito, vários separadores (Details, Vehicles, Address & Contact, Open service orders, Closed Service orders), um formulário Properties com lápis de edição in-line por campo, e uma grelha de dados relacionada (Vehicles) abaixo do formulário.
Como fica uma página publicada em runtime. O exemplo aqui é uma página Customer de um domínio diferente (DMS), mas a forma é exatamente o que a sua página company produzirá. Nota: a secção APPLICATION da barra lateral aparece assim que publica, as páginas estão agrupadas sob um cabeçalho de categoria (aqui: CUSTOMER PORTAL). O painel de detalhe renderiza breadcrumbs, um botão Edit (canto superior direito), ícones de lápis por campo para edição in-line, separadores adicionais no topo (Details / Vehicles / etc.) e abaixo do formulário uma secção de grelha relacionada que puxa linhas de outra BE filtradas pelo registo atual. O badge do sino ("1") mostra que há uma falha de Event Log não lida.
Se algo parecer vazio: a causa mais comum é esquecer-se de virar o interruptor Published, se estiver OFF, navegar para /companies na barra lateral não mostra nada. Vire-o para ON e recarregue.

Passo 4 - Limpe um campo ao guardar com um Business Event (5 min)

Os Business Events executam um pouco de lógica assim que um registo é escrito. O mais pequeno útil: remover os espaços supérfluos do title de um deal a cada gravação, para que " Acme renewal " fique como "Acme renewal". Vamos montá-lo.

Nunca precisa de uma regra para created_at / updated_at / created_by / updated_by. O Archestack adiciona essas quatro colunas de auditoria a cada tabela e carimba-as em cada insert e update automaticamente, por isso um trigger de "carimbar updated_at" só duplicaria trabalho que a plataforma já faz.
  1. Abra Business EventsCreate.
  2. Rule Name: Normalize deal.title. Ative Enabled.
  3. Business Entity: deal. Triggers: marque Before Update.
  4. Sem condições, dispara em cada update.
  5. Adicione uma ação: escolha Execute Script. O corpo do script da ação:
    string title = Entity.title;
    Entity.title = title?.Trim();
  6. Clique no separador Simulate → escolha qualquer Deal existente → clique em Run Simulation. O painel de output mostra que o script correu com sucesso e a que Entity.title ficou definido. (Não acontece nenhuma escrita real, a simulação corre numa transação revertida.)
  7. Guarde. Teste editando um Deal cujo título tenha espaços no início ou no fim, o título deverá voltar sem espaços a cada gravação.
Porquê Before Update + Execute Script em vez de uma ação que "define um campo"? O Archestack não tem uma ação discreta "Set field". A forma de mutar um registo a partir de um trigger é atribuir a Entity.column_name dentro de um Execute Script num timing Before*. A plataforma persiste o Entity modificado como parte da escrita em curso, sem query extra, sem risco de recursão.

Passo 5 - Recalcular company.open_deal_value com um Script Module (10 min)

Um total calculado como "valor de deals em aberto por empresa" é demasiado dinâmico para uma coluna armazenada ficar correta à mão. Vamos mantê-lo em sincronização com um pequeno script que volta a correr sempre que algum deal de uma empresa é inserido, atualizado ou apagado.

Escrever o script

Abra Script ModulesCreate. Nomeie-o RecalcCompanyOpenDealValue.

Mude para o separador Parameters. Clique em Add. Name company_id, Type int, Required marcado.

De volta ao separador Edit. Corpo:

var openDeals = await Db.From("deal")
    .Where("company_id", "=", company_id)
    .Where("stage", "!=", "Won")
    .Where("stage", "!=", "Lost")
    .ToListAsync();

decimal total = 0;
foreach (var d in openDeals) total += (decimal)(d.amount ?? 0);

await Db.UpdateAsync("company", company_id, new { open_deal_value = total });

return total;

Mude para o separador Test. Escreva um company_id real da sua página Companies no input de parâmetro, clique em Run. O output mostra o valor de retorno (o total) e um indicador de sucesso. Recarregue a página Companies, open_deal_value nessa empresa está agora em sincronização.

Tropeções comuns:
  • Esquecer await em .ToListAsync(), o script compila mas openDeals acaba a guardar um Task, não as linhas. O erro no painel Test mencionará "cannot be enumerated".
  • Usar Db.Query em vez de Db.From, não existe um método Query. Cinja-se a Db.From(table) para queries encadeadas e Db.GetAsync(table, id) para registo único por PK.
  • Tentar .SumAsync(...), não existe. Vá buscar + some em C#, como acima.

Ligá-lo a um Business Event

  1. Abra Business EventsCreate.
  2. Rule Name: Recalc company.open_deal_value on deal change. Enabled ligado.
  3. Business Entity: deal. Triggers: marque After Create, After Update e Before Delete.
  4. Sem condições, dispara em cada alteração.
  5. Adicione uma ação: Execute Script. Corpo:
    var entity = OldEntity != null && OldEntity.company_id != null
        ? OldEntity      // for delete & update, OldEntity has the original company_id
        : Entity;        // for create, only Entity is populated
    
    await Modules.CallAsync("RecalcCompanyOpenDealValue", new Dictionary<string, object?> {
        ["company_id"] = entity.company_id
    });
  6. Guarde. Edite o amount de um Deal e recarregue a página Company, o total mantém-se em sincronização.
Porquê chamá-lo através de Modules em vez de pôr a lógica inline no script deste trigger? Duas razões. Primeiro, o recálculo é reutilizável, também o pode chamar a partir de um Scheduled Event (catch-up noturno) ou diretamente do front-end. Segundo, isola a lógica num único sítio nomeado, mais fácil de testar, mais fácil de encontrar depois.

Passo 6 - Agrupar como package (5 min)

Construiu um mini-CRM real e funcional. Agrupe-o para o poder mover para outro ambiente.

  1. Abra PackagesCreate. Nomeie-o MiniCRM v1.
  2. Adicione as três Pages. Abra o painel de linked-items para ver a cascata: o package vai trazer as BEs que as páginas referenciam, as tabelas de origem que essas BEs leem, mais os dois Business Events e o Script Module que lhes tocam.
  3. Desmarque o que preferir omitir, tipicamente manteria tudo para um tutorial como este.
  4. Clique em Export → faça download do ZIP.

Importar o ZIP num ambiente diferente recria tudo (assumindo que o schema é deployado primeiro). É assim que os parceiros enviam configurações específicas de vertical e como promoveria trabalho de um trial para um ambiente pago mais tarde.

Recapitulação - o que acaba de acontecer

  • Desenhou um schema, fez-lhe deploy como tabelas PostgreSQL reais, e nunca escreveu SQL à mão.
  • Expôs essas tabelas através de Business Entities, escolhendo o que expor, juntando etiquetas para usabilidade, agregando contagens de linhas relacionadas.
  • Compôs três páginas só a partir de configuração, com separadores embutidos e pesquisa.
  • Adicionou um comportamento com um Business Event (auto-stamping) e um cálculo mais complexo com um Script Module, oito linhas de C# que correm sempre que os dados mudam.
  • Agrupou o conjunto todo como um Package, portável entre ambientes.

O mesmo ciclo escala para dezenas de tabelas e centenas de páginas. A Referência cobre o resto da plataforma, branding, sincronização de dados de terceiros, scaffolding assistido por IA, mas a competência central é aquilo que acabou de aprender.

Para onde ir a seguir

  • Gerar faturas em PDF - adicione um template de fatura imprimível aos Deals que acabou de construir. Mesmos dados, novo canal de saída.
  • Scheduled Events - estenda o script de recálculo com um catch-up noturno que corre sem ação do utilizador.
  • AI Assistant - peça-lhe "suporte para marcações de serviço, data, cliente, veículo, estado" e veja-o gerar uma funcionalidade semelhante.