This commit is contained in:
Matheus
2026-09-23 12:29:08 -03:00
commit f5d231ab8f
59 changed files with 16658 additions and 0 deletions
+531
View File
@@ -0,0 +1,531 @@
# Vitruvio Development Guidelines
## What is Vitruvio
Vitruvio is a Java 21 / Spring / Vaadin 7 multi-tenant BPM platform. Developers extend it by creating **content repositories** — git repos that contain panels, processes, scripts, queries, reports, endpoints, and more. Vitruvio reads these repos and renders their content at runtime. This file is one such repository.
Every repository must have a `vitruvio.json` manifest at its root that declares all artifacts. That manifest is the source of truth for what the platform will register.
---
## Repository Structure
Artifacts live under their own top-level directories, matching the paths declared in `vitruvio.json`:
```
vitruvio.json
panels/
<panel-key>/
<panel-key>-desktop.xml
<panel-key>-mobile.xml # optional
default-state.json
thumbnail.png
processes/
<process-key>/
<process-key>.bpmn
<process-key>-desktop.xml
<process-key>-mobile.xml # optional
scripts/
<script-key>.js
<script-key>.md # optional documentation
endpoints/
<endpoint-key>.js
queries/
<query-key>.sql
reports/
<report-key>/
template.jrxml
params.xml
libraries/
<library-key>/
patches/
```
All `key` values in `vitruvio.json` must be **kebab-case**, unique within their artifact type, and stable — changing a key is a breaking change.
---
## vitruvio.json Manifest
`version` and `metadata.key` are the only required fields. All other sections are optional arrays.
### metadata
```json
{
"name": "Human-readable module name",
"key": "module-key", // kebab-case, unique across tenants
"standardProduct": false // true only for widespread modules
}
```
### Artifact sections
Each section is an array. Omit the section entirely if there are no items — don't leave empty arrays unless needed.
Key rules per artifact:
- **panels** — UI screens built from XML forms. `forms.desktop` is the primary form path; mobile is optional.
- **processes** — BPMN workflows. Can declare `schedules` (cron or simple interval triggers). Are composed from a BPMN file for Activiti, a xml file for the web form and one or two xml files for the mobile forms.
- **scripts** — Reusable JS logic. `domain` is either `"SISTEMA"` (system-level) or `"USUARIO"` (user-level). Will generally be USUARIO. For non-trivial logic, see Unit Testing below.
- **endpoints** — REST endpoints. `authMode` is one of `"PUBLIC"`, `"STATIC_TOKEN"`, `"VITRUVIO_WS_USER_AUTH"`, or `"HTTP_BASIC_AUTH"`. Default to `"PUBLIC"`. `active: true` to enable. For non-trivial logic, see Unit Testing below.
- **queries** — Named SQL queries. `connection` references a configured datasource. Use `"vitruvio_producao"` for the default datasource.
- **reports** — Jasper reports. Require a query key, a `.jrxml` template, and optionally a parameter form.
- **libraries** — Static file bundles (JS/CSS/images). `type: "LOCAL"` for files in this repo.
- **groups** — User groups this module manages or depends on.
- **properties** — User-configurable key/value settings.
- **menu** — Navigation tree. See the Menu section below for structure and type values.
- **permissions** — Process-level access control per group. Mostly unused, generally defined in the bpmn file for the process.
- **patches** — Path to a directory containing per-database Liquibase changelogs. Has `oracle/` and `postgresql/` subdirectories, each with its own XML changelog file.
---
## XML Forms (Panels & Processes)
Forms are XML files validated against Vitruvio's XSD schema. Vaadin components are declared in XML and rendered by the platform's Java presenter layer — there is no manual Java UI code in content repos (except if imported in scripts using the Rhino engine).
**Form files are named `<key>-desktop.xml` / `<key>-mobile.xml` and the BPMN `<key>.bpmn`** (artifact key + suffix), so each form is searchable by its key rather than dozens of identical `form-desktop.xml` tabs.
**Desktop vs mobile forms are different schemas.** Desktop (`<key>-desktop.xml`) uses the `<panel-form>` / `<forms>` schemas, the 78-component desktop set, and Rhino ES5 scripts with auto-injected process variables. Mobile (`<key>-mobile.xml`) uses the `<mobile-forms>` schema (namespace `.../vitruvio/mobile-form`), only the 18 mobile components, **modern React-Native JS on the client** (Promises) with **server-side `<ServerSide><Bridge>` blocks in Rhino ES5** for any lib/DB/service access, and **explicitly declared data** (`<Autoload>` variables, `<QueryDataSource>`, bridges via `vCommunicationService.executeOnServer`). Use the focused skills — `vitruvio-criar-processo-bpmn`, `vitruvio-criar-form-desktop`, `vitruvio-criar-form-mobile` (driven by the `vitruvio-criar-painel` / `vitruvio-criar-processo` orchestrators) — and read `docs/components/mobile/` before building a mobile form.
General rules:
- **Always save form/XML files as UTF-8 *without* a BOM.** A leading byte-order mark (`EF BB BF`) — or any whitespace/content before the `<?xml` declaration — makes the platform's XML parser fail with `Content is not allowed in prolog` when the form is opened, even though the file looks fine in an editor. When writing or editing `.xml`/`.bpmn` files, never prepend a BOM or blank line; the declaration must be the very first bytes. Run `vitruvio validate` (which checks for this) before committing.
- Always validate your XML against the XSD before committing. Malformed forms fail on open.
- Keep forms focused. One screen = one form. Split complex layouts into sub-forms or components if the schema allows.
- Use `<script>` blocks with `<![CDATA[...]]>` for inline JavaScript on components (button actions, lifecycle hooks, field validators). For non-trivial logic, delegate to a named script file rather than writing large CDATA blocks inline or repetitive code and import it using `libService.loadScript("lib_name")`.
- `displayOrder` on panels controls the order they appear in listings. Use gaps (10, 20, 30…) to allow future insertions without reshuffling.
---
## JavaScript — ES5 / Rhino Engine
All JavaScript in this platform runs on **Mozilla Rhino**, an ES5 interpreter embedded in the JVM. Modern JS features are not available.
### What you CANNOT use
```javascript
// NO — ES6+ is not supported
const x = 1;
let y = 2;
const fn = () => {};
`template ${literal}`;
const { a, b } = obj;
const [first, ...rest] = arr;
class Foo {}
async function bar() {}
await something();
import x from 'y';
export default x;
for (const item of iterable) {}
Promise.resolve();
Array.from(x);
Object.assign({}, a, b);
```
### What you MUST use instead
```javascript
// YES — ES5 style
var x = 1;
var y = 2;
var fn = function() {};
"template " + variable;
var a = obj.a; var b = obj.b;
// loop with index
for (var i = 0; i < arr.length; i++) {}
// prototype-based, not class
function Foo() {}
Foo.prototype.bar = function() {};
// callbacks, not async/await
doSomething(function(result) { ... });
```
### Additional Rhino caveats
- `JSON.parse` and `JSON.stringify` are available.
- `typeof`, `instanceof`, standard `Array`/`Object`/`String` methods from ES5 work fine.
- No browser globals (`window`, `document`, `fetch`, `XMLHttpRequest`).
- No Node.js globals (`require`, `process`, `Buffer`, `__dirname`).
- Rhino exposes Java interop — avoid it in business scripts unless you know what you're doing.
- Always declare variables with `var`. Undeclared variables become globals and cause hard-to-trace bugs.
- When comparing values that originate from Java (DB query results, form field values, process variables), use `==` / `!=` — Java types do not strict-equal JavaScript primitives. Use `===` / `!==` only when you control both sides and know they are pure JS values.
- `Array.prototype` iteration methods `forEach`, `map`, `filter`, `reduce`, `some`, `every` are available — they are ES5 and work fine in Rhino.
---
## Unit Testing
Tests run under **Jest on Node**, not Rhino — this is the one place in the repo where modern JS
(`const`, arrow functions, destructuring) is fine, because `*.test.js` files never execute on the
platform. The production code they test still must be ES5/Rhino-safe.
**Scope: `scripts/*.js` and `endpoints/*.js` only.** Inline `<script>` CDATA in panel/process XML and
`.sql` queries have no module boundary to hook Jest into and are not unit tested this way.
**When to write a test.** Only for non-trivial logic — a function with a branch, a loop, or a call
into `db`/`http`/a platform service. A script or endpoint that's a straight passthrough doesn't need
one. **Any bug fix always gets a regression test**, written test-first:
1. Write a test asserting the correct behavior.
2. Run it and confirm it **fails** (proves it actually catches the bug).
3. Fix the root cause.
4. Run it and confirm it **passes**.
5. Run the full suite (`npm test`) to check for regressions elsewhere.
Only add the dependency-injection seam below to scripts/endpoints that actually get a test — don't
restructure trivial code preemptively for testability it doesn't need.
### Making a script testable
Wrap the logic in a constructor accepting an optional `deps` object, falling back to the real
platform globals. Guard the CommonJS export with `typeof module` — Rhino's script-loading context
(`libService.loadScript`) never defines `module`, Node always does. See `scripts/exemplo.js` /
`scripts/exemplo.test.js` for the full example.
```javascript
function MinhaLib(deps) {
var banco = deps ? deps.banco : new db(db.VITRUVIO_DATASOURCE);
this.metodo = function() { ... };
}
if (typeof module !== 'undefined') module.exports = MinhaLib;
```
### Making an endpoint testable
Endpoints have a fixed production contract: the platform's Rhino endpoint loader expects
`module.exports` to already be an instance with `onGet`/`onPost`/etc, not a constructor — and Rhino
*does* define `module` there, so the `typeof module` guard doesn't distinguish it from Node. Instead,
guard on `process`, which only exists in Node:
```javascript
function WebService(deps) {
if (deps) {
this.db = deps.db;
this.webApi = deps.webApi;
} else {
this.db = libService.loadScript('db');
this.webApi = libService.loadScript('minha-lib');
}
this.onPost = function(params, headers, res) { ... };
}
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") {
module.exports = WebService;
} else {
module.exports = new WebService();
}
```
When there's more than one dependency, swap the **whole `deps` object** in one `if`/`else` block
rather than per-field fallback (`deps.x || real`). A test that forgets to mock one dependency then
gets `undefined` and fails loudly, instead of silently hitting a real service.
### Mocking
Prefer the shared helpers over hand-rolled stubs:
- `@davinti/vitruvio-core-libs/db/mock` → `MockDb`, `createQueryResult` for anything using `db`.
- `@davinti/vitruvio-test-utils` → `createMockServletResponse`, `createMockLibService` for endpoint
responses and `libService.loadScript` lookups.
- The shared Jest preset (`jest.config.js` → `preset: '@davinti/vitruvio-test-utils'`) already stubs
`println`, `java`, `libService`, `vLogger` as globals — `libService.loadScript` returns `null` for
anything not explicitly mocked, by design, so an unmocked lib dependency fails loudly rather than
silently.
For a platform service with no shared mock yet (most of them — only `db` has one so far), stub the
global inline in the test file:
```javascript
beforeEach(() => {
global.vEmailService = { enviarEmail: jest.fn() };
});
```
If the same stub keeps getting copy-pasted across test files, that's a signal to request it be added
to `@davinti/vitruvio-test-utils` upstream — don't duplicate it locally across many test files.
### Running
`npm test` must pass before scripts/endpoints work is considered done — same standing as
`vitruvio validate` for XML forms.
---
## E2E Testing
E2E tests run against a real Vitruvio instance spun up by `vitruvio test:start`. Use them for
**process workflows** — happy paths that cross multiple artifacts (BPMN → script → DB write) and are
too integrated to unit test meaningfully. Don't E2E test things that unit tests already cover
(pure script logic, single endpoint calls with mocked deps).
### Setup
Nenhuma configuração é necessária — `vitruvio test:start` funciona direto. Um usuário de
teste é gerado automaticamente a cada execução (veja `resolveE2ECredentials` em
`@davinti/vitruvio-test-utils`).
Se quiser usar um usuário fixo em vez do gerado automaticamente:
```bash
cp tests/.env.example tests/.env
# preencha TEST_USER e TEST_PASSWORD — nunca use admin
```
`tests/.env` é gitignored.
### Running
```bash
vitruvio test:start # spin up Vitruvio + Postgres + gitea-mock (Docker)
vitruvio test:sync # push current working tree and import into Vitruvio
npm run test:e2e # run tests in tests/*.e2e.js
vitruvio test:stop # tear down containers and volumes
```
`test:sync` pushes uncommitted changes — you don't need to commit before testing. Re-run it whenever
you change a process, form, script, or vitruvio.json.
### Writing a test
Tests live in `tests/*.e2e.js`. Use `VitruvioClient` from `@davinti/vitruvio-test-utils`:
```javascript
'use strict';
var { VitruvioClient } = require('@davinti/vitruvio-test-utils');
var client = new VitruvioClient();
beforeAll(async function() {
await client.login(process.env.TEST_USER, process.env.TEST_PASSWORD);
});
test('meu-processo: completa tarefa de aprovação', async function() {
var instance = await client.startProcess('meu-processo-key');
var task = await client.waitForTask('Task_aprovacao', { processInstanceId: instance.processInstanceId });
var data = await client.getTaskData(task.id);
await client.completeTask(task.id, Object.assign({}, data, { aprovado: true }));
});
```
**`global-setup.js` runs before every test suite** — it pings Vitruvio and seeds the test user. Your
test file just needs `beforeAll` for `client.login`.
### Prerequisites for a process to be testable
Two things must be true or `startProcess` returns 404:
1. **`forms.mobile` declared in `vitruvio.json`** — the REST API only exposes processes with a mobile
form entry. Point it at the desktop XML if there's no real mobile form:
```json
"forms": { "desktop": "processes/meu-processo/meu-processo-desktop.xml", "mobile": "processes/meu-processo/meu-processo-desktop.xml" }
```
2. **`candidateStarterGroups` must exist in nauth with `tag = 'vi_task_group'`** — Vitruvio filters
starter groups by that tag. Groups imported from `vitruvio.json` get it automatically. If you add a
new group just for a process, pass it in `global-setup.js` so `createTestUser` seeds it:
```javascript
await createTestUser(client, { login: login, password: password, groups: ['meu-grupo'] });
```
If `candidateStarterGroups` is absent from the BPMN, any authenticated user can start — fine for
tests but usually wrong for production.
### `VitruvioClient` API
| Method | What it does |
|--------|-------------|
| `login(user, pass)` | Authenticates and stores the JWT |
| `startProcess(key, data?)` | Starts a process instance; returns `{ processInstanceId, ... }` |
| `getTasks()` | Returns all tasks visible to the logged-in user |
| `waitForTask(taskKey, opts?)` | Polls until a task with that `taskDefinitionKey` appears; `opts`: `{ processInstanceId, timeout, interval }` |
| `waitForProcessEnd(instanceId, opts?)` | Polls until no tasks remain for the given process instance |
| `getTaskData(id)` | Returns the task's current form data |
| `completeTask(id, data?)` | Completes the task with the given form data |
| `callEndpoint(key, opts?)` | Calls a public integration endpoint |
`waitForTask` and `waitForProcessEnd` default to a 30 s timeout with 2 s polling.
### DB seed utilities
Available from `@davinti/vitruvio-test-utils` for use in `global-setup.js`:
**`deleteTestProcessInstances(pg, processKey)`** — deletes all instances of a process key from the
test DB (Activiti runtime + history + all Vitruvio child rows). Call it at the top of the setup
function so each run starts clean and `waitForTask` can't match a task from a previous run:
```javascript
await deleteTestProcessInstances(client, 'meu-processo-key');
await createTestUser(client, { login: login, password: password, groups: ['vi_user'] });
```
**`seedConexao(pg, options)`** — upserts a `conexao` row so scripts using `new db('my-key')` or
queries with `connection: 'my-key'` can resolve the datasource. Idempotent — safe to call on every
test run. Required fields: `siglaId`, `host`, `instancia`. Optional: `nome`, `porta` (default 5432),
`usuario`, `senha`, `plataforma` (default 2 = PostgreSQL), `aliases` (array of alias strings):
```javascript
await seedConexao(client, {
siglaId: 'minha-conexao',
host: process.env.TEST_DB_HOST || 'localhost',
porta: parseInt(process.env.TEST_DB_PORT || '15432'),
instancia: process.env.TEST_DB_NAME || 'vitruvio',
usuario: process.env.TEST_DB_USER || 'postgres',
senha: process.env.TEST_DB_PASSWORD || 'postgres',
});
```
---
## Vitruvio Platform Services
Injected objects available in script and endpoint contexts (e.g. `vEmailService`, `vProcessService`, `libService`, `vFileService`, `vLoginService`…).
**Before calling any service method, read its doc file:**
```
~/.local/share/vitruvio-platform/docs/services/<ServiceName>.md
```
Every injected service has its own file there — method signatures, parameters, return types, and examples. Do not guess method names or signatures; always check the file first.
---
## Developer Libraries
These are widely-used libraries already available in the platform environment:
| Library | Purpose |
| ------- | ------------------------------------------------------------------------------------- |
| `http` | HTTP request utilities (GET, POST, etc.) |
| `db` | Database query helpers — prefer named queries from `queries/` over raw SQL in scripts |
> Also not full list, there are more usable libs.
### `db` usage notes
- `db.query()` always returns a result wrapper object, **never `null`** — even with zero rows, you get a wrapper whose `.each()` simply doesn't invoke the callback. Don't null-check it, just call `.each()` directly:
```javascript
var rows = banco.query("SELECT ...", {});
rows.each(function(row) { ... });
```
`db.queryRow()` is the one that returns `null` when there are no rows — that one you do need to null-check.
- Values from `db.queryRow()` and `db.query()` row properties are Java objects. Use `==` / `!=` when comparing them to JavaScript primitives (follows from the Java interop rule above).
- The default datasource connection key is `"vitruvio_producao"`. Use it for `connection-key` in `sqlBuilderDataSource`/`freeQuery`, for `connection` in `vitruvio.json` queries, and when constructing `new db('vitruvio_producao')` in scripts. The string `"default"` does not resolve to anything and will cause errors.
---
## Menu
The `menu` section of `vitruvio.json` defines the navigation tree shown to users. It is a flat-ish array — nesting is done via `children` on `MENU` items.
```json
"menu": [
{
"key": "my-panel-item",
"name": "My Panel",
"order": 1,
"type": "PAINEL",
"panelKey": "my-panel-key",
"children": []
},
{
"key": "my-group",
"name": "My Group",
"icon": 61946,
"order": 2,
"type": "MENU",
"children": [
{
"key": "my-group/child-panel",
"name": "Child Panel",
"icon": 62030,
"order": 0,
"type": "PAINEL",
"panelKey": "child-panel-key",
"children": []
}
]
}
]
```
### Field reference
| Field | Required | Notes |
|-------|----------|-------|
| `key` | yes | Unique identifier. For children, use `Parent/Child` path convention |
| `name` | yes | Label shown in the UI |
| `type` | yes | `MENU` (group/submenu), `PAINEL` (panel), `RELATORIO` (report), `PROCESSO` (process) |
| `order` | yes | Display order within its level |
| `icon` | no | Numeric codepoint for the item icon |
| `panelKey` | for PAINEL | Must match the `key` of a registered panel |
| `children` | yes | Array of child items. Always include, even as `[]` on leaf items |
### Rules
- `MENU` items are containers only — they do not link to anything themselves, only their `children` do.
- Keys must be unique across the whole menu tree. Use the `Parent/Child` path convention for nested items to avoid collisions.
- `order` controls sorting within a level; gaps (0, 10, 20…) allow future insertions.
- **Never generate menu entries unless the user explicitly asks for it.** Creating a panel, process, or other artifact does not imply adding it to the menu.
---
## Global Platform Reference
A shared directory exists outside this repo with platform-wide resources. Always check it before writing new code.
| OS | Path |
|----|------|
| Linux | `~/.local/share/vitruvio-platform/` |
| Windows | `%LOCALAPPDATA%\vitruvio-platform\` |
### What's where and when to use it
| Folder | Contents | When to look here |
|--------|----------|-------------------|
| `libs/` | Core JS library files (`db`, `http`, `messages`, `vaadinComponents`, etc.) | Before implementing something that a platform lib likely already handles |
| `docs/services/` | One `.md` per injected Java service — method signatures, parameters, return types. Named by injection variable (e.g. `vProcessInstanceService.md`, `vEmailService.md`). | When you need to call a platform service and don't know what methods it exposes |
| `docs/java/` | Full exported JavaDocs (HTML). Package structure: `br/`, `com/`, `org/`. | When you need to `importClass(Packages....)` a Java class and want to know its API |
| `docs/components/desktop/` | 78 Vaadin component reference markdowns — attributes, events, usage examples. See `docs/components/INDEX.md` for the full list. | When working with any component and unsure of its attributes or behaviour. Read `docs/components/desktop/<ComponentName>.md` before using a component you're not certain about. |
| `docs/components/mobile/` | 18 mobile component reference markdowns. | When building mobile forms. |
| `examples/panels/` | Example panel XML forms | Before writing a panel from scratch |
| `examples/processes/` | Example BPMN + process XML forms | Before writing a process from scratch |
| `examples/scripts/` | Example library and task scripts | Before writing a script from scratch |
| `examples/endpoints/` | Example REST endpoints | Before writing an endpoint from scratch |
| `examples/reports/` | Example Jasper report configs | Before writing a report from scratch |
> Examples show how things generally work in the platform — treat them as functional reference, not necessarily best-practice code.
### Services vs. libs — key distinction
- **Services** (`docs/services/`) are Java objects injected directly into every script context. Call them by their variable name — no loading needed. Names follow the `vClassName` convention (e.g. `vProcessInstanceService`, `vEmailService`, `vConfigService`). Exceptions: `libService`, `engine`, `execution`.
- **Libs** (`libs/`) are JS files loaded on demand with `libService.loadScript('key')`. The `db` lib is the primary example — it wraps `ConexaoService` into a friendlier JS API.
---
## General Conventions
- **Named queries versus inline SQL.** Put SQL in `queries/*.sql` and reference by key if they will be reused only. Keeps SQL auditable and reusable across scripts/reports.
- **Group keys are shared contracts.** If a `group-key` is referenced across permission rules and menu items, treat it as a public API — dont change the key.
- **Patches are append-only.** `patches/` has `oracle/` and `postgresql/` subdirectories, each with its own Liquibase XML changelog. Never modify existing changesets — only append new ones. Keep both files in sync.
- **No BOM, ever.** All text artifacts (XML forms, BPMN, scripts, queries, reports) must be plain UTF-8 with no byte-order mark. A BOM breaks XML parsing at runtime and is invisible in most editors — `vitruvio validate` now flags it, but don't introduce it in the first place.
---
## Git Commits
Applies to every commit in this repo. Follow **Conventional Commits**
(`type(scope): summary`, e.g. `feat`, `fix`, `refactor`, `docs`, `chore`), in Brazilian Portuguese or
English. The body must give context on **what changed, why, and how** — not just restate the diff —
and reference the ticket number when one exists.
```
feat(compras): adiciona comprador específico na solicitação de compra
Permite abrir a solicitação para um comprador específico e filtra os
produtos por comprador no painel de precificação.
Ref: ticket 23917
```
## README
Check `README.md` whenever the repo looks near-complete or complete (most artifacts registered,
feature work wrapping up). If it's missing or still the `projeto-base` placeholder, warn the dev and
ask them to describe what the module does — then write/update the README covering: what it manages,
how it works, the most important files and how they interact, and any caveats worth flagging to a
future reader. Only write it once the dev has confirmed the description; don't invent one.