initial
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
---
|
||||
name: vitruvio-adicionar-menu
|
||||
description: >
|
||||
Use ONLY when the user explicitly asks to add a menu item or menu entry in a Vitruvio repository.
|
||||
Triggers: "add menu item", "add to menu", "adicionar ao menu", "criar item de menu",
|
||||
"add panel to menu", "adicionar painel no menu", or any explicit request to register
|
||||
something in the vitruvio.json "menu" array.
|
||||
Do NOT trigger for general panel/process/script creation — menu entries are separate.
|
||||
---
|
||||
|
||||
# Add Vitruvio Menu Item
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are adding an entry to the `menu` array in `vitruvio.json`. Menu entries are **never created automatically** — only when the user explicitly requests it. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Show the current menu structure
|
||||
|
||||
Read `vitruvio.json` and print the menu tree as a readable outline. Example format:
|
||||
|
||||
```
|
||||
menu:
|
||||
[0] menu-root "Meu Módulo" (MENU)
|
||||
[0] menu-panel "Meu Painel" (PAINEL → my-panel)
|
||||
[1] menu-report "Meu Relatório" (RELATORIO → my-report)
|
||||
```
|
||||
|
||||
If the `menu` array is empty or absent, say so.
|
||||
|
||||
## Step 3 — Collect item details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing):
|
||||
|
||||
- **Type** — one of:
|
||||
- `PAINEL` — links to a panel (`panelKey`)
|
||||
- `RELATORIO` — links to a report (`reportKey`)
|
||||
- `PROCESSO` — links to a process (`processKey`)
|
||||
- `MENU` — a submenu/folder (no artifact link, has `children`)
|
||||
- **Target artifact key** — the `key` of the panel/report/process to link (skip if type is `MENU`)
|
||||
- **Name** — the label shown in the menu
|
||||
- **Key** — unique key for this menu entry (suggest `menu-<artifactKey>` as default)
|
||||
- **Parent** — root level, or inside which existing `MENU` item? Show the options from Step 2.
|
||||
- **Order** — integer position within its parent (suggest next available based on existing siblings)
|
||||
- **Icon** — only relevant for root-level `MENU` items. Vitruvio uses FontAwesome numeric codes (e.g. `61441` = fa-adjust). For children, always `null`.
|
||||
|
||||
## Step 4 — Build the JSON entry
|
||||
|
||||
### Type: PAINEL
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"icon": null,
|
||||
"order": <order>,
|
||||
"type": "PAINEL",
|
||||
"panelKey": "<panelKey>",
|
||||
"children": []
|
||||
}
|
||||
```
|
||||
|
||||
### Type: RELATORIO
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"icon": null,
|
||||
"order": <order>,
|
||||
"type": "RELATORIO",
|
||||
"reportKey": "<reportKey>",
|
||||
"children": []
|
||||
}
|
||||
```
|
||||
|
||||
### Type: PROCESSO
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"icon": null,
|
||||
"order": <order>,
|
||||
"type": "PROCESSO",
|
||||
"processKey": "<processKey>",
|
||||
"children": []
|
||||
}
|
||||
```
|
||||
|
||||
### Type: MENU (submenu / root folder)
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"icon": <iconCode or null>,
|
||||
"order": <order>,
|
||||
"type": "MENU",
|
||||
"children": []
|
||||
}
|
||||
```
|
||||
|
||||
## Step 5 — Insert into vitruvio.json
|
||||
|
||||
- Read `vitruvio.json`.
|
||||
- If `menu` array does not exist, create it as an empty array first.
|
||||
- If the user chose **root level**: append the entry to the top-level `menu` array.
|
||||
- If the user chose a **parent item**: find the parent entry by key inside `menu` (search recursively if needed) and append to its `children` array.
|
||||
- Check that the chosen `key` is not already used anywhere in the menu tree before inserting.
|
||||
- Write the updated file back preserving formatting (2-space indent).
|
||||
|
||||
## Step 6 — Report
|
||||
|
||||
Tell the user:
|
||||
- Added `<type>` entry `<key>` ("Name") at `<location>` with order `<order>`
|
||||
- Remind them: menu order is relative within siblings — reorder adjacent items if needed
|
||||
- Remind them: `icon` values are FontAwesome numeric codes; use `null` for leaf items
|
||||
@@ -0,0 +1,100 @@
|
||||
# Create Vitruvio Library
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are creating a new static file library inside a Vitruvio repository. Libraries are static file bundles (JS, CSS, images) served by the platform as HTTP resources. They are different from scripts — scripts run server-side on Rhino; libraries are served to clients as-is. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect library details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing from their original request):
|
||||
|
||||
- **Key (sigla)** — kebab-case unique identifier. Used to reference the library and build its endpoint URL. Stable — changing it breaks any code that references it.
|
||||
- **Name** — human-readable label shown in the Vitruvio UI.
|
||||
- **Description** — one sentence about what this library provides (optional).
|
||||
- **Auth mode** — one of:
|
||||
- `PUBLIC` — accessible without authentication (default; use for most JS/CSS bundles)
|
||||
- `STATIC_TOKEN` — token required; use when files must not be publicly accessible
|
||||
- **Mobile enabled?** — `true` if mobile clients need to load these files; `false` otherwise (default: `false`).
|
||||
- **What files will this library contain?** — brief description so you can create a useful starting placeholder (e.g. "custom JS utilities for panels", "CSS theme overrides", "image assets").
|
||||
|
||||
## Step 3 — Create the directory and placeholder file
|
||||
|
||||
```bash
|
||||
mkdir -p libraries/<key>
|
||||
```
|
||||
|
||||
Create a placeholder file appropriate to what the user described:
|
||||
|
||||
- For a **JS library**: `libraries/<key>/<key>.js`
|
||||
- For a **CSS library**: `libraries/<key>/<key>.css`
|
||||
- For an **image/mixed library**: `libraries/<key>/README.md` explaining what belongs here
|
||||
|
||||
### JS placeholder
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Library: <name>
|
||||
* Key: <key>
|
||||
* Description: <description>
|
||||
*
|
||||
* These files are served as static HTTP resources.
|
||||
* Access URL: vBibliotecaService.buildEndpointUrl('<key>', '<key>.js')
|
||||
*/
|
||||
|
||||
// Add your client-side JavaScript here.
|
||||
// This runs in the browser — full ES6+ is supported (unlike server-side Rhino scripts).
|
||||
```
|
||||
|
||||
### CSS placeholder
|
||||
|
||||
```css
|
||||
/**
|
||||
* Library: <name>
|
||||
* Key: <key>
|
||||
* Description: <description>
|
||||
*
|
||||
* These files are served as static HTTP resources.
|
||||
* Access URL: vBibliotecaService.buildEndpointUrl('<key>', '<key>.css')
|
||||
*/
|
||||
|
||||
/* Add your styles here */
|
||||
```
|
||||
|
||||
## Step 4 — Register in vitruvio.json
|
||||
|
||||
Read `vitruvio.json`, find or create the `"libraries"` array, and add:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description>",
|
||||
"type": "LOCAL",
|
||||
"authMode": "<authMode>",
|
||||
"authToken": null,
|
||||
"mobileEnabled": <mobileEnabled>,
|
||||
"files": "libraries/<key>/"
|
||||
}
|
||||
```
|
||||
|
||||
Omit `"description"` if not provided. Preserve the existing file structure and all other entries.
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- Directory created: `libraries/<key>/`
|
||||
- Placeholder file(s) created
|
||||
- Registered in `vitruvio.json` with key `<key>`
|
||||
- How to get the serving URL at runtime:
|
||||
```javascript
|
||||
var url = vBibliotecaService.buildEndpointUrl('<key>', 'filename.js');
|
||||
```
|
||||
- Remind them: files in this directory are served as-is — client-side JS here can use modern ES6+, unlike server-side Rhino scripts
|
||||
@@ -0,0 +1,109 @@
|
||||
# Create Vitruvio Endpoint
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are creating a new REST endpoint inside a Vitruvio repository. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect endpoint details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing from their original request):
|
||||
|
||||
- **Key** — kebab-case unique identifier. Forms the URL path. Changing it later breaks external integrations.
|
||||
- **Name** — human-readable label shown in the Vitruvio UI.
|
||||
- **Description** — one sentence about what this endpoint does.
|
||||
- **HTTP verbs** — which methods to implement: GET, POST, PUT, PATCH, DELETE. Only scaffold the ones actually needed.
|
||||
- **Auth mode** — one of:
|
||||
- `PUBLIC` — no authentication (default)
|
||||
- `STATIC_TOKEN` — token in query param `_x_token_auth` or header `X-WS-TOKEN-AUTH`
|
||||
- `VITRUVIO_WS_USER_AUTH` — Vitruvio bearer token; `loggedUser` is available in the script
|
||||
- `HTTP_BASIC_AUTH` — HTTP basic auth; `loggedUser` is available; roles validated by Vitruvio admin
|
||||
|
||||
## Step 3 — Create the file
|
||||
|
||||
Target path: `endpoints/<key>.js`
|
||||
|
||||
URL after deploy:
|
||||
|
||||
| authMode | URL |
|
||||
|---|---|
|
||||
| `PUBLIC` | `/api/integration/public/<key>` |
|
||||
| `STATIC_TOKEN` | `/api/integration/tokenauth/<key>` |
|
||||
| `VITRUVIO_WS_USER_AUTH` | `/api/integration/bearerauth/<key>` |
|
||||
| `HTTP_BASIC_AUTH` | `/api/integration/bauth/<key>` |
|
||||
|
||||
Template (include only the requested verbs):
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Nome: <name>
|
||||
* Sigla: <key>
|
||||
* Descrição: <description>
|
||||
* Auth: <authMode>
|
||||
*/
|
||||
function WebService() {
|
||||
|
||||
// this.onGet = function(params) { ... } ← GET / DELETE: params has .headers and .query
|
||||
// this.onPost = function(params) { ... } ← POST / PUT / PATCH: params also has .requestBody (string, always JSON.parse before use)
|
||||
|
||||
this.onPost = function(params) {
|
||||
try {
|
||||
var body = JSON.parse(params.requestBody);
|
||||
if (!body.id) throw 'Missing required field: id';
|
||||
|
||||
// implementation here
|
||||
|
||||
return JSON.stringify({ success: true });
|
||||
} catch (e) {
|
||||
return JSON.stringify({ error: e.toString() });
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
module.exports = new WebService();
|
||||
```
|
||||
|
||||
Rules (Rhino ES5 — no exceptions):
|
||||
- No `let`, `const`, arrow functions, template literals, destructuring, spread, `class`, or `import/export`
|
||||
- Use `var` everywhere
|
||||
- Always `JSON.parse(params.requestBody)` before accessing the body — never trust it raw
|
||||
- Always return strings — `JSON.stringify(obj)`, not raw objects
|
||||
- Return `null` or nothing for `204 No Content`; return a string for `200 OK`
|
||||
- Remove unused verb stubs entirely — don't leave placeholder bodies
|
||||
- Never concatenate user input into SQL strings; use named bind params (`:paramName`)
|
||||
- Don't hardcode datasource names or tokens — read from `vConfigService` or a DB config table
|
||||
- Put heavy logic in a separate script loaded via `libService.loadScript`, not inline in the endpoint
|
||||
|
||||
## Step 4 — Register in vitruvio.json
|
||||
|
||||
Read `vitruvio.json`, find or create the `"endpoints"` array, and add:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description>",
|
||||
"language": "javascript",
|
||||
"authMode": "<authMode>",
|
||||
"active": true,
|
||||
"source": "endpoints/<key>.js"
|
||||
}
|
||||
```
|
||||
|
||||
Preserve the existing file structure and all other entries. Write the updated `vitruvio.json` back.
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- File created: `endpoints/<key>.js`
|
||||
- Registered in `vitruvio.json` with key `<key>`
|
||||
- URL once deployed (based on authMode)
|
||||
- Which verbs were scaffolded
|
||||
@@ -0,0 +1,279 @@
|
||||
# Create Vitruvio Desktop Form
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
Desktop forms are Vaadin 8 forms defined in XML and rendered by the Vitruvio engine. This
|
||||
skill creates the **desktop** form. There are two variants that share almost all of their
|
||||
component vocabulary but differ in their root element and how variables flow — see
|
||||
**Differences: panel vs process** below.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Determine variant and target
|
||||
|
||||
Ask (only what is missing):
|
||||
|
||||
- **Panel or process form?** (decides root element / file location — see below)
|
||||
- **Key** — the panel or process key (the folder name).
|
||||
- For a **process**, which `formKey`(s) are needed — they must match the `activiti:formKey`
|
||||
values in `processes/<key>/<key>.bpmn`.
|
||||
- **What should the form show/do?** — fields and behaviour, so you can scaffold something useful.
|
||||
|
||||
## Differences: panel vs process
|
||||
|
||||
| | Panel | Process |
|
||||
|----------------|-------|---------|
|
||||
| File | `panels/<key>/<key>-desktop.xml` | `processes/<key>/<key>-desktop.xml` |
|
||||
| Root element | `<panel-form>` | `<forms>` (with `processKey` attribute optional) |
|
||||
| Namespace | `http://www.davinti.com.br/vitruvio/form/panel` | `http://www.davinti.com.br/vitruvio/form` |
|
||||
| XSD | `vitruvio-panel-form.xsd` | `vitruvio-form.xsd` |
|
||||
| Forms per file | exactly one `<form>` | **one `<form formKey>` per BPMN `activiti:formKey`** |
|
||||
| Variables | none built-in; use `engine.getGlobalVariable` | process variables: `engine.getVariable`/`setVariable`; submitted field `id="X"` in `formKey="A"` → variable `A_X` |
|
||||
| `<library>` | not used | optional: shared `<complex-component id>` reused via `<component-ref refId>` |
|
||||
|
||||
Everything below (components, DBTable, engine API, ES5 rules) is **identical** for both.
|
||||
|
||||
## Step 3 — Scaffold the file
|
||||
|
||||
### Panel variant — `panels/<key>/<key>-desktop.xml`
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/panel"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/panel https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-panel-form.xsd">
|
||||
|
||||
<form formKey="<key>" width="100%" height="100%">
|
||||
<name><name></name>
|
||||
<description><description></description>
|
||||
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
// called once when the panel opens
|
||||
}
|
||||
]]>
|
||||
</initScript>
|
||||
|
||||
<components>
|
||||
<VerticalLayout spacing="true" margin="true" width="100%" height="100%">
|
||||
<!-- add widgets here -->
|
||||
</VerticalLayout>
|
||||
</components>
|
||||
</form>
|
||||
</panel-form>
|
||||
```
|
||||
|
||||
### Process variant — `processes/<key>/<key>-desktop.xml`
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<forms xmlns="http://www.davinti.com.br/vitruvio/form"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-form.xsd">
|
||||
|
||||
<!-- Optional: shared components used across multiple forms -->
|
||||
<library>
|
||||
<!-- <complex-component id="libShared">...</complex-component> -->
|
||||
</library>
|
||||
|
||||
<!-- One <form> per activiti:formKey in the BPMN -->
|
||||
<form formKey="formAbertura" width="100%">
|
||||
<name>Abertura</name>
|
||||
<description>Abertura do processo</description>
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
// called when the form opens
|
||||
}
|
||||
]]>
|
||||
</initScript>
|
||||
<components>
|
||||
<VerticalLayout spacing="true" margin="true" width="100%">
|
||||
<TextField id="descricao" type="string" caption="Descrição" width="100%" required="true" />
|
||||
</VerticalLayout>
|
||||
</components>
|
||||
</form>
|
||||
|
||||
<form formKey="formExecutar" width="100%">
|
||||
<name>Executar</name>
|
||||
<description>Etapa de execução</description>
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
// read a process variable set during a previous step
|
||||
// var valor = engine.getVariable('formAbertura_descricao');
|
||||
}
|
||||
]]>
|
||||
</initScript>
|
||||
<components>
|
||||
<VerticalLayout spacing="true" margin="true" width="100%">
|
||||
<TextField id="resultado" type="string" caption="Resultado" width="100%" required="true" />
|
||||
<ComboBox id="aprovado" type="string" caption="Aprovado?" required="true" allowNullSelection="false">
|
||||
<entry key="1" value="Sim"/>
|
||||
<entry key="0" value="Não"/>
|
||||
</ComboBox>
|
||||
</VerticalLayout>
|
||||
</components>
|
||||
</form>
|
||||
|
||||
</forms>
|
||||
```
|
||||
|
||||
## Components (78 desktop components)
|
||||
|
||||
Before using a component you are unsure about, read its reference:
|
||||
`~/.local/share/vitruvio-platform/docs/components/desktop/<ComponentName>.md`
|
||||
(full list in `docs/components/INDEX.md`).
|
||||
|
||||
### Layout
|
||||
|
||||
| Component | Common attributes |
|
||||
|---|---|
|
||||
| `VerticalLayout` | `spacing`, `margin`, `width`, `height`, `expandRatio` |
|
||||
| `HorizontalLayout` | same as above |
|
||||
| `Panel` | `id`, `caption`, `width`, `height`, `margin` |
|
||||
| `TabLayout` | `id`, `width`, `framed` — contains `<Tab caption="...">` children |
|
||||
|
||||
### Widgets
|
||||
|
||||
| Component | Key attributes |
|
||||
|---|---|
|
||||
| `TextField` | `id`, `type` (`string`/`number`), `caption`, `width`, `required` |
|
||||
| `NumericField` | `id`, `type`, `caption`, `width`, `visible` |
|
||||
| `DateField` | `id`, `type` (`date`/`datetime`), `caption`, `resolution` (`DAY`/`MINUTE`) |
|
||||
| `ComboBox` | `id`, `type`, `caption`, `allowNullSelection` — children: `<entry key="..." value="..."/>` |
|
||||
| `Label` | `id`, `width`, `contentMode` (`HTML`/`TEXT`) — child: `<value>...</value>` |
|
||||
| `ButtonWidget` | `id`, `caption`, `style` (`GREEN`/`RED`/`DEFAULT`), `defaultIcon` — child: `<onClickScript>` |
|
||||
| `RichTextArea` | `id`, `type`, `caption`, `width`, `height` |
|
||||
| `ImageWidget` | `id`, `width`, `height` — child: `<image><base64 extension="png">...</base64></image>` |
|
||||
|
||||
### DBTable (data grid)
|
||||
|
||||
```xml
|
||||
<DBTable id="tbDados" type="string" width="100%" rows="8"
|
||||
exportXLS="true" showRowCount="true" selectable="true">
|
||||
<datasource>
|
||||
<!-- Option A: static query -->
|
||||
<freeQuery connection-key="vitruvio_producao">
|
||||
<![CDATA[
|
||||
SELECT col1, col2
|
||||
FROM my_table
|
||||
WHERE param = ${myParam}
|
||||
]]>
|
||||
</freeQuery>
|
||||
|
||||
<!-- Option B: dynamic query built in JS -->
|
||||
<sqlBuilderDataSource connection-key="vitruvio_producao" language="JavaScript">
|
||||
<![CDATA[
|
||||
function buildSQL(params) {
|
||||
var sql = 'SELECT col1, col2 FROM my_table WHERE 1=1';
|
||||
var val = engine.getField('myFilter').getValue();
|
||||
if (val) {
|
||||
sql += ' AND col1 = ${val}';
|
||||
params.put('val', val);
|
||||
}
|
||||
return sql;
|
||||
}
|
||||
]]>
|
||||
</sqlBuilderDataSource>
|
||||
</datasource>
|
||||
<key-field>CHAVE</key-field>
|
||||
<columns>
|
||||
<column name="COL1" caption="Column 1" expand-ratio="1"/>
|
||||
<column name="COL2" caption="Column 2" expand-ratio="2"/>
|
||||
<generated name="Action" expand-ratio="0.5">
|
||||
<scriptColumnGenerator language="JavaScript">
|
||||
<![CDATA[
|
||||
function Generator() {
|
||||
var com = libService.loadScript('vaadinComponents');
|
||||
this.generate = function(itemId, columnId, item, container) {
|
||||
var btn = com.buttonIcon('action', function() {
|
||||
var id = item.getItemProperty('CHAVE').getValue();
|
||||
// do something
|
||||
}, 'edit');
|
||||
return com.horizontalLayout([btn]);
|
||||
}
|
||||
}
|
||||
var script = new Generator();
|
||||
]]>
|
||||
</scriptColumnGenerator>
|
||||
</generated>
|
||||
</columns>
|
||||
<bind>
|
||||
<parameter value-type="number" defaultValue="0" parameterName="myParam" field-ref="otherTable"/>
|
||||
</bind>
|
||||
</DBTable>
|
||||
```
|
||||
|
||||
**SQL in datasources:** use `${paramName}` for substitution — NOT `:paramName`. Named
|
||||
params (`:paramName`) are only for `queries/*.sql` files.
|
||||
|
||||
## engine API
|
||||
|
||||
```javascript
|
||||
// Fields
|
||||
engine.getField('id').getValue()
|
||||
engine.getField('id').getConvertedValue() // typed value (number, date, etc.)
|
||||
engine.getField('id').setValue(value)
|
||||
engine.getField('id').setEnabled(bool)
|
||||
engine.getField('id').setVisible(bool)
|
||||
engine.getField('id').setRequired(bool)
|
||||
engine.getField('id').setCaption('new caption')
|
||||
engine.getField('id').refresh() // DBTable — re-run its query
|
||||
|
||||
// Widgets / layouts
|
||||
engine.getWidgetController('id').getButton()
|
||||
engine.getLayout('id').getSelectedTab()
|
||||
|
||||
// User
|
||||
engine.getLoggedUser().getLogin()
|
||||
engine.getLoggedUser().getNome()
|
||||
|
||||
// Global variables (survive tab changes within a session)
|
||||
engine.setGlobalVariable('key', value)
|
||||
engine.getGlobalVariable('key')
|
||||
|
||||
// Process forms only — process variables:
|
||||
engine.getVariable('varName')
|
||||
engine.setVariable('varName', value)
|
||||
engine.getProcessDefinitionId()
|
||||
engine.formKey() // current form's formKey
|
||||
engine.getFormName()
|
||||
|
||||
// Open another panel / load a library
|
||||
var vUI = libService.loadScript('vUI');
|
||||
vUI.showPanel('panelKey', { param1: value1 });
|
||||
var lib = libService.loadScript('scriptKey');
|
||||
```
|
||||
|
||||
## Script rules (Rhino ES5)
|
||||
|
||||
Desktop form scripts run on **Rhino ES5** — no `let`/`const`, arrow functions, template
|
||||
literals, destructuring, `class`, `import/export`. Use `var`, string `+` concat,
|
||||
`JSON.parse`/`JSON.stringify`, `importClass(Packages.some.java.Class)` for Java interop.
|
||||
(See the repo `CLAUDE.md` "JavaScript — ES5 / Rhino Engine" section.)
|
||||
|
||||
> Note: this ES5 rule is for **desktop**. Mobile forms use modern JS on the client — see
|
||||
> **vitruvio-criar-form-mobile**.
|
||||
|
||||
## Step 4 — Manifest
|
||||
|
||||
If run **standalone**, set `"forms"."desktop"` to the file path on the existing panel or
|
||||
process entry in `vitruvio.json`. Full entry creation is handled by **vitruvio-criar-painel**
|
||||
/ **vitruvio-criar-processo**.
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- File created/updated and which variant (panel/process).
|
||||
- For processes: each `<form formKey>` must match an `activiti:formKey` in the BPMN, and
|
||||
submitted field `id="X"` in `formKey="A"` becomes process variable `A_X`.
|
||||
- `run()` in `<initScript>` is called every time the form opens.
|
||||
- `${paramName}` for SQL substitution in datasources; `:paramName` only in named query files.
|
||||
@@ -0,0 +1,255 @@
|
||||
# Create Vitruvio Mobile Form
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
Mobile forms are **not** desktop forms with a different skin. They use a different schema,
|
||||
a smaller component set, a different JavaScript runtime, and an explicit server↔client data
|
||||
contract. Read this whole skill before scaffolding — the mistakes here are not the desktop
|
||||
mistakes.
|
||||
|
||||
## The four things that make mobile different
|
||||
|
||||
1. **Different schema.** Root is `<mobile-forms>`, namespace
|
||||
`http://www.davinti.com.br/vitruvio/mobile-form`, XSD `vitruvio-mobile-form.xsd`.
|
||||
2. **Only 18 components** (vs 78 desktop). Do **not** assume a desktop widget exists on
|
||||
mobile. The full list:
|
||||
`CheckBox, ComboBox, GoogleMapsField, HorizontalLayout, ImageLibraryByFieldValue,
|
||||
ImageWidget, Label, MaskedField, MoneyField, NumericField, OptionGroup,
|
||||
ProgressBarWidget, RatingStars, SignaturePadField, TabLayout, TextField, Toggle,
|
||||
VerticalLayout` (plus structural `SubForm`/`ItemList`). Read
|
||||
`~/.local/share/vitruvio-platform/docs/components/mobile/<Component>.md` before using one.
|
||||
3. **Two JavaScript runtimes.**
|
||||
- **Client-side** (`initScript`, `discoveryScript`, validators, component event scripts):
|
||||
**modern React-Native JS** — arrow functions, Promises, `.then()/.catch()` are fine and
|
||||
expected. Most APIs are **async** and return Promises.
|
||||
- **Server-side** (`<ServerSide><Bridge>` `execute(...)` bodies): **Rhino ES5**, same rules
|
||||
as scripts/desktop. This is the only place with access to platform libs and services
|
||||
(`libService`, `runtimeService`, db, etc.).
|
||||
4. **Data is explicit.** Nothing is auto-injected the way desktop process variables are. Every
|
||||
piece of server data the form needs must be declared — via an `<Autoload>` variable, a
|
||||
`<QueryDataSource>` (synced to the device, works offline), or fetched on demand from a
|
||||
named `<Bridge>` with `vCommunicationService.executeOnServer(...)`.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Determine variant and gather the data contract
|
||||
|
||||
Ask (only what is missing):
|
||||
|
||||
- **Panel or process mobile form?** (see **Differences: panel vs process** below)
|
||||
- **Key** — the panel or process key (folder name).
|
||||
- For a **process**: which `formKey`(s) — they must match the `activiti:formKey` in
|
||||
`processes/<key>/<key>.bpmn`.
|
||||
- **Which variables does the form need?** Because nothing is auto-injected, ask explicitly:
|
||||
- process variables to read (each becomes an `<Autoload>` `<variable>` and/or a Bridge call)
|
||||
- reference/lookup data (each becomes a `<QueryDataSource>` backed by a `queries/*.sql`)
|
||||
- any server-only logic / libs needed (each becomes a `<Bridge>`)
|
||||
- **Offline?** Whether the data sources must be available without connectivity (affects
|
||||
`autoSyncOnInit` / `autoSyncOnDiscovery`).
|
||||
|
||||
## Differences: panel vs process
|
||||
|
||||
The schema, component set, ServerSide/Bridge mechanics and JS runtimes below are **identical**
|
||||
for both. Only these differ:
|
||||
|
||||
| | Panel mobile form | Process mobile form |
|
||||
|----------------|-------------------|---------------------|
|
||||
| File | `panels/<key>/<key>-mobile.xml` | `processes/<key>/<key>-mobile.xml` |
|
||||
| Root attribute | `<mobile-forms>` (no `processKey`) | `<mobile-forms processKey="<key>">` |
|
||||
| Forms per file | one `<form>` | **one `<form formKey>` per BPMN `activiti:formKey`** (must match) |
|
||||
| Manifest | set `forms.mobile` **and** `showInMobileList: true` on the panel entry | set `forms.mobile` (and optionally `forms.mobileAlternative`) on the process entry |
|
||||
| Variables | use `engine.getGlobalVariable` / Autoload | process variables are fetched server-side via a Bridge (`runtimeService.getVariable`) and/or declared in `<Autoload>` |
|
||||
|
||||
> Filename: name files by the **artifact key + suffix** — `<key>-mobile.xml` (and
|
||||
> `<key>-desktop.xml`, `<key>.bpmn`). This keeps every form searchable by its key instead of
|
||||
> dozens of identical `form-mobile.xml` tabs. Older content used `form-mobile.xml` /
|
||||
> `form_web_mobile.xml`; the real path is whatever `forms.mobile` points to, so legacy files
|
||||
> still work — but new scaffolds use `<key>-mobile.xml`.
|
||||
|
||||
## Step 3 — Scaffold the file
|
||||
|
||||
### Skeleton (process variant shown; for a panel drop `processKey` and use a single `<form>`)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mobile-forms processKey="<key>"
|
||||
xmlns="http://www.davinti.com.br/vitruvio/mobile-form"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/mobile-form https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-mobile-form.xsd">
|
||||
|
||||
<form formKey="formColeta">
|
||||
<name>Coleta</name>
|
||||
<description>Etapa de coleta no app</description>
|
||||
|
||||
<!-- Runs when the form opens. CLIENT-SIDE: modern JS, async APIs return Promises. -->
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
// Autoloaded variables land in the global scope:
|
||||
engine.getField('nomeLoja').setValue(engine.getGlobalVariable('nomeLoja'));
|
||||
}
|
||||
]]>
|
||||
</initScript>
|
||||
|
||||
<!-- Optional: pull server data when the task is discovered (before the user opens it). -->
|
||||
<discoveryScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
var params = { id: execution.getProcessInstanceId() };
|
||||
vCommunicationService.executeOnServer('bridgeNumeroCarga', params).then(result => {
|
||||
var n = parseInt(result, 10);
|
||||
execution.setVariable('numeroCarga', n).then(ok => {}).catch(err => {
|
||||
console.log('Erro ao setar numeroCarga localmente', err);
|
||||
});
|
||||
}).catch(err => {
|
||||
console.log('Erro ao coletar numeroCarga no server', err);
|
||||
});
|
||||
}
|
||||
]]>
|
||||
</discoveryScript>
|
||||
|
||||
<!-- Block completing the task when a rule is not met. -->
|
||||
<validators>
|
||||
<ScriptValidator execution="COMPLETE" language="JavaScript" id="validadorComplete">
|
||||
<![CDATA[
|
||||
function Validator() {
|
||||
var msg;
|
||||
this.getMessage = function() { return msg; };
|
||||
this.isValid = function() {
|
||||
if (engine.getField('confirma').getValue() == 'Sim') { return true; }
|
||||
msg = 'Confirme a execução antes de finalizar.';
|
||||
return false;
|
||||
};
|
||||
}
|
||||
var validator = new Validator();
|
||||
]]>
|
||||
</ScriptValidator>
|
||||
</validators>
|
||||
|
||||
<components>
|
||||
<VerticalLayout spacing="true" margin="true" width="100%">
|
||||
<TextField id="nomeLoja" type="string" caption="Loja" readOnly="true" />
|
||||
<OptionGroup id="confirma" type="string" caption="Executou?" required="true">
|
||||
<entry key="Sim" value="Sim" />
|
||||
<entry key="Nao" value="Não" />
|
||||
</OptionGroup>
|
||||
<SignaturePadField id="assinatura" caption="Assinatura" />
|
||||
</VerticalLayout>
|
||||
</components>
|
||||
|
||||
<!-- Explicitly declared variables auto-injected into the form's scope on load. -->
|
||||
<Autoload>
|
||||
<variables autoInjectionScope="ENGINE_GLOBAL_SCOPE" autoPersist="true">
|
||||
<variable>nomeLoja</variable>
|
||||
<variable>numeroCarga</variable>
|
||||
</variables>
|
||||
</Autoload>
|
||||
|
||||
<!-- Everything the server provides to this form. -->
|
||||
<ServerSide>
|
||||
<!-- Named queries (queries/*.sql) synced to the device for offline lookups. -->
|
||||
<DataSources>
|
||||
<QueryDataSource key="qry_produtos_carga" autoSyncOnInit="true" autoSyncOnDiscovery="true" refreshInSeconds="60" />
|
||||
</DataSources>
|
||||
|
||||
<!-- Server-side functions. Rhino ES5. Full access to libService / runtimeService / db.
|
||||
Called from the client via vCommunicationService.executeOnServer('id', params). -->
|
||||
<Bridges>
|
||||
<Bridge language="JavaScript" id="bridgeNumeroCarga">
|
||||
<![CDATA[
|
||||
function execute(params) {
|
||||
var numeroCarga = runtimeService.getVariable(params.id, 'numeroCarga');
|
||||
return numeroCarga ? numeroCarga : -1;
|
||||
}
|
||||
]]>
|
||||
</Bridge>
|
||||
</Bridges>
|
||||
</ServerSide>
|
||||
</form>
|
||||
|
||||
</mobile-forms>
|
||||
```
|
||||
|
||||
## How libs and process variables reach the mobile form
|
||||
|
||||
The mobile app cannot call `libService.loadScript(...)` or read process variables directly —
|
||||
those live on the server. The pattern is always **declare a Bridge, call it from the client**:
|
||||
|
||||
```xml
|
||||
<!-- SERVER-SIDE (Rhino ES5): a lib used to build a barcode image -->
|
||||
<Bridge language="JavaScript" id="imagemCodBarras">
|
||||
<![CDATA[
|
||||
function execute(codbarras) {
|
||||
var generator = libService.loadScript('barcode-gen');
|
||||
return ',' + generator.generateEAN13BarcodeImageAsBase64({ value: codbarras });
|
||||
}
|
||||
]]>
|
||||
</Bridge>
|
||||
```
|
||||
|
||||
```javascript
|
||||
// CLIENT-SIDE (modern JS): call the bridge, use the Promise result
|
||||
vCommunicationService.executeOnServer('imagemCodBarras', codigoBarras).then(base64 => {
|
||||
engine.getField('codigoImagem').setValue(base64);
|
||||
}).catch(err => console.log('Erro no bridge imagemCodBarras', err));
|
||||
```
|
||||
|
||||
Rules of thumb:
|
||||
- Anything needing a **platform lib, the database, or a platform service** → put it in a
|
||||
**Bridge** (server, ES5) and call it with `vCommunicationService.executeOnServer`.
|
||||
- **Reference/lookup tables** the form reads repeatedly → a **`QueryDataSource`** backed by a
|
||||
`queries/*.sql` (works offline once synced).
|
||||
- **Process variables** the form needs → either declare them in `<Autoload>` or fetch them
|
||||
in a Bridge via `runtimeService.getVariable(processInstanceId, 'varName')` and store locally
|
||||
with `execution.setVariable(...)`.
|
||||
- Client-side `execution.getVariable(...)` / `execution.setVariable(...)` are **async** and
|
||||
return Promises — use `.then()`.
|
||||
|
||||
## List-based entry: SubForm + ItemList
|
||||
|
||||
For "add many items" screens (collect a list of rows), use a `SubForm` with an `<ItemList>`:
|
||||
|
||||
```xml
|
||||
<SubForm formKey="executarAcao">
|
||||
<name>Executar AÇÃO</name>
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[ function run(apply) { if (apply) { apply(); } } ]]>
|
||||
</initScript>
|
||||
<ItemList addItemButtonCaption="Gravar na Lista" caption="Itens">
|
||||
<property id="OBSERVACAO" caption="Observação" />
|
||||
</ItemList>
|
||||
<components>
|
||||
<VerticalLayout width="100%" spacing="true" margin="true">
|
||||
<!-- fields captured per item -->
|
||||
</VerticalLayout>
|
||||
</components>
|
||||
</SubForm>
|
||||
```
|
||||
|
||||
Validate list completeness with `engine.getSubFormListSizeByStatus(function(size){ ... }, "all")`
|
||||
inside a `COMPLETE` validator.
|
||||
|
||||
## Step 4 — Manifest
|
||||
|
||||
If run **standalone**, update the matching entry in `vitruvio.json`:
|
||||
- **Panel**: set `forms.mobile = "panels/<key>/<key>-mobile.xml"` and `showInMobileList: true`.
|
||||
- **Process**: set `forms.mobile = "processes/<key>/<key>-mobile.xml"`.
|
||||
|
||||
Full entry creation is handled by **vitruvio-criar-painel** / **vitruvio-criar-processo**.
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- File created/updated and variant (panel/process).
|
||||
- Which variables/queries/bridges were declared, and that **only declared data is available**
|
||||
on the device — anything else must be added as an Autoload variable, QueryDataSource, or Bridge.
|
||||
- Reminder: client scripts are modern JS (Promises); Bridge bodies are server-side Rhino ES5.
|
||||
- For processes: each `<form formKey>` must match an `activiti:formKey` in the BPMN.
|
||||
- Suggest reading `docs/components/mobile/` and an example
|
||||
(`~/.local/share/vitruvio-platform/examples/processes/*/form_web_mobile.xml`) for richer screens.
|
||||
@@ -0,0 +1,92 @@
|
||||
# Create Vitruvio Panel (orchestrator)
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
A panel is one or two form files plus a manifest entry:
|
||||
|
||||
| Part | Skill that owns it |
|
||||
|------|--------------------|
|
||||
| `panels/<key>/<key>-desktop.xml` (web form) | **vitruvio-criar-form-desktop** (panel variant) |
|
||||
| `panels/<key>/<key>-mobile.xml` (app form, optional) | **vitruvio-criar-form-mobile** (panel variant) |
|
||||
|
||||
This skill collects the intent once, drives those skills, and registers the panel in
|
||||
`vitruvio.json`. Do the file work by following the referenced skills — do not re-derive
|
||||
their templates here.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect panel details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing):
|
||||
|
||||
- **Key** — PascalCase or snake_case unique identifier. Used in `engine.showPanel(key)` and
|
||||
as the folder name.
|
||||
- **Name** — human-readable label shown in the Vitruvio menu.
|
||||
- **Category** — display category, hierarchical with `/` (e.g. `"Comercial"`,
|
||||
`"Auditoria/Gondola"`).
|
||||
- **Description** — one sentence (optional).
|
||||
- **Open in new window?** — `true`/`false`. Default `true`.
|
||||
- **Show in mobile list?** — `true`/`false`. Default `false`.
|
||||
- **Needs a mobile form?** — if yes, a `<key>-mobile.xml` is created too. Mobile is a different
|
||||
schema with explicit data wiring (the mobile skill will ask for variables/lookups/libs).
|
||||
- **What should the panel do?** — fields/behaviour, so the form scaffold is useful.
|
||||
|
||||
## Step 3 — Scaffold
|
||||
|
||||
```bash
|
||||
vitruvio new panel <key> --name "<name>"
|
||||
```
|
||||
|
||||
This creates `panels/<key>/<key>-desktop.xml` and the `vitruvio.json` entry. Then replace the
|
||||
generated form using the focused skills:
|
||||
|
||||
1. **Desktop form** — follow **vitruvio-criar-form-desktop** (panel variant) to write
|
||||
`panels/<key>/<key>-desktop.xml` from the user's description.
|
||||
2. **Mobile form (if requested)** — follow **vitruvio-criar-form-mobile** (panel variant) to
|
||||
write `panels/<key>/<key>-mobile.xml`. `vitruvio new` does not create it.
|
||||
|
||||
## Step 4 — Update vitruvio.json entry
|
||||
|
||||
`vitruvio new` already added the entry. Full shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description>",
|
||||
"category": "<category>",
|
||||
"displayOrder": 0,
|
||||
"showInPresentation": false,
|
||||
"openInNewWindow": true,
|
||||
"showInMobileList": false,
|
||||
"displayTimeInSeconds": 0,
|
||||
"allowedGroups": [],
|
||||
"allowedUsers": [],
|
||||
"forms": {
|
||||
"desktop": "panels/<key>/<key>-desktop.xml"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Fields `vitruvio new` typically leaves at defaults that need updating:
|
||||
- `"category"` — set to the user's category (default `""`).
|
||||
- `"description"` — add if provided.
|
||||
- `"openInNewWindow"` — set to `false` for same-window.
|
||||
- `"showInMobileList"` — set to `true` for mobile visibility.
|
||||
- If a mobile form was created, add `"mobile": "panels/<key>/<key>-mobile.xml"` inside `"forms"`
|
||||
(and set `showInMobileList: true`).
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- Files created: `<key>-desktop.xml` (and `<key>-mobile.xml` if applicable).
|
||||
- Registered in `vitruvio.json` with key `<key>`.
|
||||
- `run()` in `<initScript>` is called every time the panel opens.
|
||||
- `${paramName}` for SQL substitution in datasource blocks; `:paramName` only in named query files.
|
||||
- For mobile: only explicitly declared data is available on the device — see vitruvio-criar-form-mobile.
|
||||
@@ -0,0 +1,120 @@
|
||||
# Create Vitruvio Patch
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are creating a new Liquibase database migration patch inside a Vitruvio repository. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Inspect existing patches to determine the next changeset ID
|
||||
|
||||
```bash
|
||||
find patches/ -name "*.xml" | xargs grep -h 'id="[0-9]' 2>/dev/null | grep -oP 'id="\K[0-9]+' | sort -n | tail -1
|
||||
```
|
||||
|
||||
The next ID = last found ID + 1. If no patches exist yet, start at 1.
|
||||
|
||||
Also check whether oracle and postgresql subdirectories already exist:
|
||||
|
||||
```bash
|
||||
ls patches/
|
||||
```
|
||||
|
||||
## Step 3 — Collect migration details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing):
|
||||
|
||||
- **What this migration does** — describe the change (e.g. "add column STATUS to table PEDIDO", "create table AUDIT_LOG", "insert config rows").
|
||||
- **Author** — Vitruvio username (e.g. `joao.felix`). Use `git config user.name` if unsure.
|
||||
- **Module key** — used in the filename (e.g. `GO`, `checklist`, `faturamento`). Default: the repo's `metadata.key` from `vitruvio.json`.
|
||||
|
||||
## Step 4 — Create the patch files
|
||||
|
||||
**Both oracle/ and postgresql/ files must always be created and kept in sync.**
|
||||
|
||||
Filename convention: `{YYYYMMDDHHmm}_{MODULE_KEY}.xml` (e.g. `202506011430_checklist.xml`). Use the current date and time.
|
||||
|
||||
```bash
|
||||
mkdir -p patches/oracle patches/postgresql
|
||||
```
|
||||
|
||||
### Absolute rules
|
||||
|
||||
- **Append-only.** Never edit or delete existing `<changeSet>` entries — modifying a checksum that Liquibase already recorded breaks deployment.
|
||||
- **Unique numeric IDs.** Each `<changeSet id="...">` must have a unique ID within the repo. Increment from the last found.
|
||||
- **Always use `<preConditions onFail="MARK_RAN">`** — every changeset must be idempotent and safe to re-run on any DB state.
|
||||
- **Oracle ≠ PostgreSQL.** Write each file for its target DB — data types, sequences, and quoting differ. Never copy-paste blindly.
|
||||
- **One logical change per changeset** — don't batch unrelated changes into a single `<changeSet>`.
|
||||
|
||||
### File skeleton
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd
|
||||
http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
|
||||
|
||||
<changeSet author="<author>" id="<next-id>" objectQuotingStrategy="LEGACY">
|
||||
<preConditions onError="WARN" onFail="MARK_RAN" onSqlOutput="IGNORE">
|
||||
<!-- guard appropriate for the operation — see examples below -->
|
||||
</preConditions>
|
||||
<sql endDelimiter=";" splitStatements="true" stripComments="false">
|
||||
-- SQL for this DB dialect
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
```
|
||||
|
||||
### preConditions reference
|
||||
|
||||
| Operation | Guard to use |
|
||||
|-----------|-------------|
|
||||
| CREATE TABLE | `<not><tableExists tableName="MY_TABLE"/></not>` |
|
||||
| ADD COLUMN | `<not><columnExists tableName="MY_TABLE" columnName="MY_COL"/></not>` |
|
||||
| CREATE INDEX | `<not><indexExists indexName="IDX_NAME"/></not>` |
|
||||
| ADD CONSTRAINT / FK | `<not><foreignKeyConstraintExists foreignKeyName="FK_NAME"/></not>` |
|
||||
| INSERT row (by PK) | `<sqlCheck expectedResult="0">SELECT COUNT(*) FROM MY_TABLE WHERE ID = 1</sqlCheck>` |
|
||||
| DROP TABLE | `<tableExists tableName="MY_TABLE"/>` |
|
||||
| DROP COLUMN | `<columnExists tableName="MY_TABLE" columnName="MY_COL"/>` |
|
||||
|
||||
### Oracle vs PostgreSQL differences to watch
|
||||
|
||||
| | Oracle | PostgreSQL |
|
||||
|---|---|---|
|
||||
| Auto-increment | Separate `CREATE SEQUENCE` + trigger or `DEFAULT seq.NEXTVAL` | `SERIAL` or `GENERATED ALWAYS AS IDENTITY` |
|
||||
| String type | `VARCHAR2(n)` | `VARCHAR(n)` |
|
||||
| Boolean | `NUMBER(1)` | `BOOLEAN` |
|
||||
| Date/time | `DATE`, `TIMESTAMP` | `DATE`, `TIMESTAMP` |
|
||||
| Current timestamp | `SYSDATE` | `CURRENT_TIMESTAMP` |
|
||||
| Quoting | `LEGACY` strategy (unquoted) | Same |
|
||||
|
||||
## Step 5 — Check vitruvio.json patches registration
|
||||
|
||||
The patches directory only needs to be registered once. Check if it is already there:
|
||||
|
||||
```bash
|
||||
grep -A2 '"patches"' vitruvio.json
|
||||
```
|
||||
|
||||
If not registered, add to `vitruvio.json`:
|
||||
|
||||
```json
|
||||
"patches": "patches/"
|
||||
```
|
||||
|
||||
## Step 6 — Report
|
||||
|
||||
Tell the user:
|
||||
- Files created: `patches/oracle/<filename>.xml` and `patches/postgresql/<filename>.xml`
|
||||
- Changeset IDs used
|
||||
- Summary of what each changeset does
|
||||
- Reminder: never edit existing changesets once committed — add new ones instead
|
||||
@@ -0,0 +1,216 @@
|
||||
# Create Vitruvio Process BPMN
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are creating the **BPMN workflow file** of a Vitruvio process. This skill is focused
|
||||
on the `.bpmn` file only — the desktop form is handled by **vitruvio-criar-form-desktop**
|
||||
(process variant) and the mobile form by **vitruvio-criar-form-mobile** (process variant).
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect flow details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing):
|
||||
|
||||
- **Key** — snake_case or camelCase unique identifier. Becomes the BPMN process ID and the
|
||||
folder name. Must match the `key` used for the process in `vitruvio.json`.
|
||||
- **Name** — human-readable label.
|
||||
- **Who can start it?** — group key(s) allowed to open the process (e.g. `gestao`,
|
||||
`admin`). Used in `activiti:candidateStarterGroups`.
|
||||
- **Steps** — the human tasks (user tasks), and which group handles each. A simple linear
|
||||
flow is enough to start.
|
||||
- **Decisions / branches?** — any exclusive gateways with conditions.
|
||||
- **Automatic steps?** — any script tasks running between user tasks.
|
||||
|
||||
## Step 3 — Write the BPMN file: `processes/<key>/<key>.bpmn`
|
||||
|
||||
### Critical rules
|
||||
|
||||
- The `<bpmn2:process id="...">` value is the canonical process identity. The importer
|
||||
reads it from the BPMN, not from vitruvio.json. **It must match the `key`.**
|
||||
- Every node must appear in a `<bpmn2:laneSet>` / `<bpmn2:lane>` **and** in the
|
||||
`<bpmndi:BPMNDi>` section — Vitruvio renders the diagram.
|
||||
- Each `activiti:formKey` on the start event and user tasks must match a
|
||||
`<form formKey="...">` in the desktop form XML (and mobile form, if present).
|
||||
- Process variables from submitted forms are auto-named `{formKey}_{fieldId}`
|
||||
(e.g. `formAbertura_status`). Gateway conditions reference them.
|
||||
- Gateway conditions use `#{variable == 'value'}` (JUEL expression language).
|
||||
- Script tasks call `vScriptService.loadScript('scriptKey', 'javascript')`, **not**
|
||||
`libService`.
|
||||
|
||||
### Minimal skeleton (start → user task → end, single lane)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn2:definitions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
|
||||
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
|
||||
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
|
||||
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
|
||||
xmlns:activiti="http://activiti.org/bpmn"
|
||||
id="sample-diagram"
|
||||
targetNamespace="http://bpmn.io/schema/bpmn"
|
||||
exporter="bpmn-js (https://demo.bpmn.io)"
|
||||
exporterVersion="8.2.0"
|
||||
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
|
||||
|
||||
<bpmn2:collaboration id="Collaboration_<key>">
|
||||
<bpmn2:participant id="processo_<key>" name="<Name>" processRef="<key>" />
|
||||
</bpmn2:collaboration>
|
||||
|
||||
<bpmn2:process id="<key>" name="<Name>" isExecutable="true"
|
||||
activiti:candidateStarterGroups="<starterGroup>">
|
||||
<bpmn2:laneSet>
|
||||
<bpmn2:lane id="lane_execucao" name="Execução">
|
||||
<bpmn2:flowNodeRef>inicio</bpmn2:flowNodeRef>
|
||||
<bpmn2:flowNodeRef>task_executar</bpmn2:flowNodeRef>
|
||||
<bpmn2:flowNodeRef>fim</bpmn2:flowNodeRef>
|
||||
</bpmn2:lane>
|
||||
</bpmn2:laneSet>
|
||||
|
||||
<!-- Start event: activiti:initiator stores the login of who opened the process -->
|
||||
<bpmn2:startEvent id="inicio" name="Início"
|
||||
activiti:formKey="formAbertura"
|
||||
activiti:initiator="iniciador">
|
||||
<bpmn2:outgoing>flow_inicio_task</bpmn2:outgoing>
|
||||
</bpmn2:startEvent>
|
||||
|
||||
<!-- User task: candidateGroups controls who sees it in their inbox -->
|
||||
<bpmn2:userTask id="task_executar" name="Executar"
|
||||
activiti:formKey="formExecutar"
|
||||
activiti:candidateGroups="${vStringUtils.validateRoles(gr_executores)}">
|
||||
<bpmn2:incoming>flow_inicio_task</bpmn2:incoming>
|
||||
<bpmn2:outgoing>flow_task_fim</bpmn2:outgoing>
|
||||
</bpmn2:userTask>
|
||||
|
||||
<!-- Script task example (omit if not needed):
|
||||
<bpmn2:scriptTask id="script_processar" name="Processar" scriptFormat="javascript">
|
||||
<bpmn2:incoming>flow_task_script</bpmn2:incoming>
|
||||
<bpmn2:outgoing>flow_script_fim</bpmn2:outgoing>
|
||||
<bpmn2:script>var f = vScriptService.loadScript('meu_script', 'javascript');
|
||||
f(execution);</bpmn2:script>
|
||||
</bpmn2:scriptTask>
|
||||
-->
|
||||
|
||||
<!-- Exclusive gateway example (omit if not needed):
|
||||
<bpmn2:exclusiveGateway id="gw_decisao" name="Aprovado?">
|
||||
<bpmn2:incoming>flow_task_gw</bpmn2:incoming>
|
||||
<bpmn2:outgoing>flow_sim</bpmn2:outgoing>
|
||||
<bpmn2:outgoing>flow_nao</bpmn2:outgoing>
|
||||
</bpmn2:exclusiveGateway>
|
||||
<bpmn2:sequenceFlow id="flow_sim" name="Sim" sourceRef="gw_decisao" targetRef="fim">
|
||||
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">#{formExecutar_aprovado == '1'}</bpmn2:conditionExpression>
|
||||
</bpmn2:sequenceFlow>
|
||||
<bpmn2:sequenceFlow id="flow_nao" name="Não" sourceRef="gw_decisao" targetRef="task_executar">
|
||||
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">#{formExecutar_aprovado == '0'}</bpmn2:conditionExpression>
|
||||
</bpmn2:sequenceFlow>
|
||||
-->
|
||||
|
||||
<bpmn2:endEvent id="fim" name="Fim">
|
||||
<bpmn2:incoming>flow_task_fim</bpmn2:incoming>
|
||||
</bpmn2:endEvent>
|
||||
|
||||
<bpmn2:sequenceFlow id="flow_inicio_task" sourceRef="inicio" targetRef="task_executar" />
|
||||
<bpmn2:sequenceFlow id="flow_task_fim" sourceRef="task_executar" targetRef="fim" />
|
||||
</bpmn2:process>
|
||||
|
||||
<!-- BPMNDi: visual layout — required for the diagram to render -->
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_<key>">
|
||||
|
||||
<bpmndi:BPMNShape id="Participant_di" bpmnElement="processo_<key>" isHorizontal="true">
|
||||
<dc:Bounds x="100" y="80" width="750" height="180" />
|
||||
</bpmndi:BPMNShape>
|
||||
|
||||
<bpmndi:BPMNShape id="lane_execucao_di" bpmnElement="lane_execucao" isHorizontal="true">
|
||||
<dc:Bounds x="130" y="80" width="720" height="180" />
|
||||
</bpmndi:BPMNShape>
|
||||
|
||||
<bpmndi:BPMNShape id="inicio_di" bpmnElement="inicio">
|
||||
<dc:Bounds x="192" y="152" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="195" y="195" width="30" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
|
||||
<bpmndi:BPMNShape id="task_executar_di" bpmnElement="task_executar">
|
||||
<dc:Bounds x="310" y="130" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
|
||||
<bpmndi:BPMNShape id="fim_di" bpmnElement="fim">
|
||||
<dc:Bounds x="492" y="152" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="497" y="195" width="19" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
|
||||
<bpmndi:BPMNEdge id="flow_inicio_task_di" bpmnElement="flow_inicio_task">
|
||||
<di:waypoint x="228" y="170" />
|
||||
<di:waypoint x="310" y="170" />
|
||||
</bpmndi:BPMNEdge>
|
||||
|
||||
<bpmndi:BPMNEdge id="flow_task_fim_di" bpmnElement="flow_task_fim">
|
||||
<di:waypoint x="410" y="170" />
|
||||
<di:waypoint x="492" y="170" />
|
||||
</bpmndi:BPMNEdge>
|
||||
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn2:definitions>
|
||||
```
|
||||
|
||||
### Multi-lane pattern (when tasks belong to different roles)
|
||||
|
||||
Add each lane inside `<bpmn2:laneSet>`, list the node IDs inside each lane, and adjust
|
||||
the BPMNDi bounds:
|
||||
|
||||
```xml
|
||||
<bpmn2:laneSet>
|
||||
<bpmn2:lane id="lane_gestao" name="Gestão">
|
||||
<bpmn2:flowNodeRef>inicio</bpmn2:flowNodeRef>
|
||||
<bpmn2:flowNodeRef>fim</bpmn2:flowNodeRef>
|
||||
</bpmn2:lane>
|
||||
<bpmn2:lane id="lane_execucao" name="Execução">
|
||||
<bpmn2:flowNodeRef>task_executar</bpmn2:flowNodeRef>
|
||||
</bpmn2:lane>
|
||||
</bpmn2:laneSet>
|
||||
```
|
||||
|
||||
### Script task — script side
|
||||
|
||||
```javascript
|
||||
// In <bpmn2:script> inside a scriptTask:
|
||||
var f = vScriptService.loadScript('meu_script', 'javascript');
|
||||
f(execution);
|
||||
|
||||
// In the script file itself (pattern: process/task script — see vitruvio-criar-script):
|
||||
(function(execution) {
|
||||
var db = libService.loadScript('db');
|
||||
var banco = new db(db.VITRUVIO_DATASOURCE);
|
||||
var status = execution.getVariable('formAbertura_status');
|
||||
// ...
|
||||
})(execution)
|
||||
```
|
||||
|
||||
## Step 4 — Manifest
|
||||
|
||||
If this skill is run **standalone** (the process already exists in `vitruvio.json`),
|
||||
ensure its entry has `"bpmn": "processes/<key>/<key>.bpmn"`. Do not create or modify
|
||||
the rest of the entry here — full registration is handled by **vitruvio-criar-processo**.
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- File created/updated: `processes/<key>/<key>.bpmn`
|
||||
- The process identity is the `<bpmn2:process id>` — it must match the key.
|
||||
- Each `activiti:formKey` must have a matching `<form formKey="...">` in the form XML
|
||||
(create/update it with **vitruvio-criar-form-desktop** / **vitruvio-criar-form-mobile**).
|
||||
- Submitted field `id="X"` in `formKey="formAbertura"` becomes variable `formAbertura_X`.
|
||||
- For complex flows, recommend editing the BPMN in bpmn.io or Camunda Modeler before deploying.
|
||||
@@ -0,0 +1,87 @@
|
||||
# Create Vitruvio Process (orchestrator)
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
A process is made of up to three artifacts plus a manifest entry:
|
||||
|
||||
| Part | Skill that owns it |
|
||||
|------|--------------------|
|
||||
| `processes/<key>/<key>.bpmn` (workflow) | **vitruvio-criar-processo-bpmn** |
|
||||
| `processes/<key>/<key>-desktop.xml` (web form) | **vitruvio-criar-form-desktop** (process variant) |
|
||||
| `processes/<key>/<key>-mobile.xml` (app form, optional) | **vitruvio-criar-form-mobile** (process variant) |
|
||||
|
||||
This skill collects the intent once, drives those skills, and registers the process in
|
||||
`vitruvio.json`. Do the file work by following the referenced skills — do not re-derive
|
||||
their templates here.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect process details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing):
|
||||
|
||||
- **Key** — snake_case or camelCase unique identifier. Becomes the BPMN process ID and the
|
||||
folder name.
|
||||
- **Name** — human-readable label.
|
||||
- **Description** — one sentence (optional).
|
||||
- **Who can start it?** — group key(s) for `activiti:candidateStarterGroups`.
|
||||
- **Steps** — the human tasks and which group handles each. A simple linear flow is enough.
|
||||
- **Needs a script task?** — automatic steps running a script between user tasks.
|
||||
- **Needs a mobile form?** — if yes, a `<key>-mobile.xml` is created too. Remember mobile is a
|
||||
different schema with explicit data wiring — variables, lookups and libs must be named
|
||||
up front (the mobile skill will ask).
|
||||
|
||||
## Step 3 — Scaffold
|
||||
|
||||
```bash
|
||||
vitruvio new process <key> --name "<name>"
|
||||
```
|
||||
|
||||
This creates `processes/<key>/<key>.bpmn`, `processes/<key>/<key>-desktop.xml`, and the
|
||||
`vitruvio.json` entry. Then replace the generated files using the focused skills:
|
||||
|
||||
1. **BPMN** — follow **vitruvio-criar-processo-bpmn** to write `processes/<key>/<key>.bpmn`
|
||||
from the user's steps/branches.
|
||||
2. **Desktop form** — follow **vitruvio-criar-form-desktop** (process variant) to write
|
||||
`processes/<key>/<key>-desktop.xml`, one `<form formKey>` per `activiti:formKey` in the BPMN.
|
||||
3. **Mobile form (if requested)** — follow **vitruvio-criar-form-mobile** (process variant) to
|
||||
write `processes/<key>/<key>-mobile.xml`. `vitruvio new` does not create it.
|
||||
|
||||
## Step 4 — Update vitruvio.json entry
|
||||
|
||||
`vitruvio new` already added the entry. Full shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description>",
|
||||
"bpmn": "processes/<key>/<key>.bpmn",
|
||||
"forms": {
|
||||
"desktop": "processes/<key>/<key>-desktop.xml"
|
||||
},
|
||||
"schedules": []
|
||||
}
|
||||
```
|
||||
|
||||
- Add `"description"` if the user provided one — `vitruvio new` does not set it.
|
||||
- If a mobile form was created, add `"mobile": "processes/<key>/<key>-mobile.xml"` inside `"forms"`.
|
||||
- Add `schedules` only if the process runs on a timer (cron/simple interval).
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- Files created: `<key>.bpmn`, `<key>-desktop.xml` (and `<key>-mobile.xml` if applicable).
|
||||
- Registered in `vitruvio.json` with key `<key>`.
|
||||
- The process identity is the `<bpmn2:process id>` — it must match the key.
|
||||
- Each `activiti:formKey` in the BPMN must have a matching `<form formKey="...">` in **every**
|
||||
form file (desktop and mobile).
|
||||
- Submitted field `id="X"` in `formKey="formAbertura"` becomes process variable `formAbertura_X`
|
||||
(desktop auto-injects these; mobile must declare/fetch them — see vitruvio-criar-form-mobile).
|
||||
- For complex flows, recommend editing the BPMN in bpmn.io or Camunda Modeler before deploying.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Create Vitruvio Query
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are creating a new named SQL query inside a Vitruvio repository. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect query details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing from their original request):
|
||||
|
||||
- **Key** — kebab-case unique identifier. Used to reference this query in reports, DBTable components, and scripts.
|
||||
- **Name** — human-readable label shown in the Vitruvio UI.
|
||||
- **SQL** — the query itself, or enough context to write it.
|
||||
- **Connection** — datasource name (default: `vitruvio_producao`). Ask only if the user mentions a specific datasource.
|
||||
|
||||
## Step 3 — Create the file
|
||||
|
||||
Target path: `queries/<key>.sql`
|
||||
|
||||
Rules:
|
||||
- One `SELECT` per file. No multiple statements, no DDL, no INSERT/UPDATE/DELETE.
|
||||
- Named bind parameters use `:paramName` syntax — never concatenate user input into SQL.
|
||||
- Write ANSI SQL where possible. If DB-specific syntax is unavoidable, note it in a comment.
|
||||
- Keep Oracle and PostgreSQL compatibility in mind — avoid syntax that only works in one.
|
||||
|
||||
```sql
|
||||
SELECT col1,
|
||||
col2
|
||||
FROM my_table
|
||||
WHERE active = 1
|
||||
AND id = :id
|
||||
ORDER BY col1
|
||||
```
|
||||
|
||||
## Step 4 — Register in vitruvio.json
|
||||
|
||||
Read `vitruvio.json`, find or create the `"queries"` array, and add:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"source": "queries/<key>.sql",
|
||||
"connection": "<connection>"
|
||||
}
|
||||
```
|
||||
|
||||
Preserve the existing file structure and all other entries. Write the updated `vitruvio.json` back.
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- File created: `queries/<key>.sql`
|
||||
- Registered in `vitruvio.json` with key `<key>`
|
||||
- How it can be used: as a datasource in a report, in a DBTable component, or loaded in a script via `db.executeNamedQuery('<key>', params)`
|
||||
@@ -0,0 +1,118 @@
|
||||
# Create Vitruvio Report
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are registering a new report inside a Vitruvio repository. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Identify the report type
|
||||
|
||||
Ask the user which type applies (if not already clear from the request):
|
||||
|
||||
| Type | When to use |
|
||||
|------|-------------|
|
||||
| **MODELO_ESTATICO** | A Jasper report whose layout is fully designed in a `.jrxml` file (Jaspersoft Studio). The dev provides or will provide the `.jrxml`. |
|
||||
| **DINAMICO_QUERY_SQL** | A Vitruvio-managed report where data comes from a named query and columns/layout are configured in `vitruvio.json`. The `.jrxml` is still needed but is simpler — Vitruvio drives the structure. |
|
||||
|
||||
## Step 3 — Collect details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing):
|
||||
|
||||
**Both types:**
|
||||
- **Key** — kebab-case or snake_case unique identifier.
|
||||
- **Name** — human-readable label shown in the UI.
|
||||
- **Category** — display category (e.g. `"Compras"`, `"Auditoria/Gondola"`).
|
||||
- **Owner** — Vitruvio username of the responsible person.
|
||||
- **Orientation** — `RETRATO` (portrait) or `PAISAGEM` (landscape). Default: `RETRATO`.
|
||||
- **Allowed groups / users** — who can access this report (can be empty arrays).
|
||||
- **Has parameters?** — does the user fill in parameters before running it? If yes, a params form is needed.
|
||||
|
||||
**DINAMICO_QUERY_SQL only:**
|
||||
- **Query key** — the named query that feeds the report (must be registered in `vitruvio.json`).
|
||||
- **Columns** — list of columns: name (DB column), label, alignment (`LEFT`/`CENTER`/`RIGHT`), width (px), aggregation (`null`, `SUM`, `COUNT`, etc.).
|
||||
|
||||
## Step 4 — Create the files
|
||||
|
||||
### MODELO_ESTATICO
|
||||
|
||||
Files live flat in `reports/`:
|
||||
- `reports/<key>.jrxml` — **do not generate this file**; tell the user to place the Jaspersoft-designed template here. Must target **JasperReports 6.21.2** — do not save with a newer version.
|
||||
- `reports/<key>-params.xml` — only if the report has parameters (follows the same Vaadin XML form schema as panels).
|
||||
|
||||
### DINAMICO_QUERY_SQL
|
||||
|
||||
Files live in a subdirectory:
|
||||
- `reports/<key>/template.jrxml` — **do not generate this file**; tell the user to place the template here.
|
||||
- `reports/<key>/params.xml` — only if the report has parameters.
|
||||
|
||||
## Step 5 — Register in vitruvio.json
|
||||
|
||||
Read `vitruvio.json`, find or create the `"reports"` array, and add the entry.
|
||||
|
||||
### MODELO_ESTATICO entry
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"type": "MODELO_ESTATICO",
|
||||
"category": "<category>",
|
||||
"owner": "<owner>",
|
||||
"template": "reports/<key>.jrxml",
|
||||
"parameterForm": "reports/<key>-params.xml",
|
||||
"orientation": "RETRATO",
|
||||
"allowedGroups": [],
|
||||
"allowedUsers": [],
|
||||
"columns": [],
|
||||
"schedules": []
|
||||
}
|
||||
```
|
||||
|
||||
Omit `"parameterForm"` if no params form.
|
||||
|
||||
### DINAMICO_QUERY_SQL entry
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description>",
|
||||
"type": "DINAMICO_QUERY_SQL",
|
||||
"category": "<category>",
|
||||
"owner": "<owner>",
|
||||
"template": "reports/<key>/template.jrxml",
|
||||
"parameterForm": "reports/<key>/params.xml",
|
||||
"query": "<query-key>",
|
||||
"orientation": "RETRATO",
|
||||
"allowedGroups": [],
|
||||
"allowedUsers": [],
|
||||
"columns": [
|
||||
{
|
||||
"name": "COLUMN_NAME",
|
||||
"label": "Column Label",
|
||||
"align": "LEFT",
|
||||
"width": 100,
|
||||
"aggregation": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Omit `"parameterForm"` if no params form.
|
||||
|
||||
Preserve the existing file structure and all other entries. Write the updated `vitruvio.json` back.
|
||||
|
||||
## Step 6 — Report
|
||||
|
||||
Tell the user:
|
||||
- Entry registered in `vitruvio.json` with key `<key>` and type `<type>`
|
||||
- For MODELO_ESTATICO: remind them to place the `.jrxml` at `reports/<key>.jrxml`, designed in Jaspersoft Studio 6.21.2
|
||||
- For DINAMICO_QUERY_SQL: remind them to place the template at `reports/<key>/template.jrxml`
|
||||
- If a params form is needed: what file to create and that it follows the same Vaadin XML schema as panels
|
||||
@@ -0,0 +1,97 @@
|
||||
# Create Vitruvio Script
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are creating a new script inside a Vitruvio repository. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect script details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing from their original request):
|
||||
|
||||
- **Key (sigla)** — unique identifier used in `libService.loadScript('key')`. Snake_case. Must be unique across all scripts in the repo. If the user already provided a name, suggest a key derived from it.
|
||||
- **Name** — human-readable label shown in the Vitruvio UI.
|
||||
- **Pattern** — which of the two patterns applies:
|
||||
- **Library** — reusable module, loaded by other scripts/endpoints/panels via `libService.loadScript`. Wrap in `({...})`.
|
||||
- **Process/task script** — runs directly from a process task or scheduler. Top-level execution, no export.
|
||||
- **Description** — one sentence about what this script does (optional, but ask if not provided).
|
||||
- **Domain** — `USUARIO` (user-level, default) or `SISTEMA` (system-level).
|
||||
|
||||
## Step 3 — Create the file
|
||||
|
||||
Target path: `scripts/<key>.js`
|
||||
|
||||
### Library template
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Nome: <name>
|
||||
* Sigla: <key>
|
||||
* Descrição: <description>
|
||||
*/
|
||||
({
|
||||
// example function — replace with actual implementation
|
||||
run: function(params) {
|
||||
var db = libService.loadScript('db');
|
||||
var banco = new db(db.VITRUVIO_DATASOURCE);
|
||||
|
||||
// implementation here
|
||||
|
||||
return {};
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Process / task script template
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Nome: <name>
|
||||
* Sigla: <key>
|
||||
* Descrição: <description>
|
||||
*/
|
||||
(function(execution) {
|
||||
var db = libService.loadScript('db');
|
||||
var banco = new db(db.VITRUVIO_DATASOURCE);
|
||||
|
||||
// implementation here
|
||||
})(execution)
|
||||
```
|
||||
|
||||
Rules (Rhino ES5 — no exceptions):
|
||||
- No `let`, `const`, arrow functions, template literals, destructuring, spread, `class`, or `import/export`
|
||||
- Use `var` everywhere
|
||||
- No `require`, `process`, `window`, or Node/browser globals
|
||||
- String concatenation with `+`, not template literals
|
||||
- `JSON.parse` / `JSON.stringify` for serialization
|
||||
|
||||
## Step 4 — Register in vitruvio.json
|
||||
|
||||
Read `vitruvio.json`, find or create the `"scripts"` array, and add:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description>",
|
||||
"language": "javascript",
|
||||
"domain": "<USUARIO|SISTEMA>",
|
||||
"source": "scripts/<key>.js"
|
||||
}
|
||||
```
|
||||
|
||||
Preserve the existing file structure and all other entries. Write the updated `vitruvio.json` back.
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- File created: `scripts/<key>.js`
|
||||
- Registered in `vitruvio.json` with key `<key>`
|
||||
- How to load it from another script or endpoint: `var lib = libService.loadScript('<key>');`
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
name: vitruvio-adicionar-menu
|
||||
description: >
|
||||
Use ONLY when the user explicitly asks to add a menu item or menu entry in a Vitruvio repository.
|
||||
Triggers: "add menu item", "add to menu", "adicionar ao menu", "criar item de menu",
|
||||
"add panel to menu", "adicionar painel no menu", or any explicit request to register
|
||||
something in the vitruvio.json "menu" array.
|
||||
Do NOT trigger for general panel/process/script creation — menu entries are separate.
|
||||
---
|
||||
|
||||
# Add Vitruvio Menu Item
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are adding an entry to the `menu` array in `vitruvio.json`. Menu entries are **never created automatically** — only when the user explicitly requests it. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Show the current menu structure
|
||||
|
||||
Read `vitruvio.json` and print the menu tree as a readable outline. Example format:
|
||||
|
||||
```
|
||||
menu:
|
||||
[0] menu-root "Meu Módulo" (MENU)
|
||||
[0] menu-panel "Meu Painel" (PAINEL → my-panel)
|
||||
[1] menu-report "Meu Relatório" (RELATORIO → my-report)
|
||||
```
|
||||
|
||||
If the `menu` array is empty or absent, say so.
|
||||
|
||||
## Step 3 — Collect item details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing):
|
||||
|
||||
- **Type** — one of:
|
||||
- `PAINEL` — links to a panel (`panelKey`)
|
||||
- `RELATORIO` — links to a report (`reportKey`)
|
||||
- `PROCESSO` — links to a process (`processKey`)
|
||||
- `MENU` — a submenu/folder (no artifact link, has `children`)
|
||||
- **Target artifact key** — the `key` of the panel/report/process to link (skip if type is `MENU`)
|
||||
- **Name** — the label shown in the menu
|
||||
- **Key** — unique key for this menu entry (suggest `menu-<artifactKey>` as default)
|
||||
- **Parent** — root level, or inside which existing `MENU` item? Show the options from Step 2.
|
||||
- **Order** — integer position within its parent (suggest next available based on existing siblings)
|
||||
- **Icon** — only relevant for root-level `MENU` items. Vitruvio uses FontAwesome numeric codes (e.g. `61441` = fa-adjust). For children, always `null`.
|
||||
|
||||
## Step 4 — Build the JSON entry
|
||||
|
||||
### Type: PAINEL
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"icon": null,
|
||||
"order": <order>,
|
||||
"type": "PAINEL",
|
||||
"panelKey": "<panelKey>",
|
||||
"children": []
|
||||
}
|
||||
```
|
||||
|
||||
### Type: RELATORIO
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"icon": null,
|
||||
"order": <order>,
|
||||
"type": "RELATORIO",
|
||||
"reportKey": "<reportKey>",
|
||||
"children": []
|
||||
}
|
||||
```
|
||||
|
||||
### Type: PROCESSO
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"icon": null,
|
||||
"order": <order>,
|
||||
"type": "PROCESSO",
|
||||
"processKey": "<processKey>",
|
||||
"children": []
|
||||
}
|
||||
```
|
||||
|
||||
### Type: MENU (submenu / root folder)
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"icon": <iconCode or null>,
|
||||
"order": <order>,
|
||||
"type": "MENU",
|
||||
"children": []
|
||||
}
|
||||
```
|
||||
|
||||
## Step 5 — Insert into vitruvio.json
|
||||
|
||||
- Read `vitruvio.json`.
|
||||
- If `menu` array does not exist, create it as an empty array first.
|
||||
- If the user chose **root level**: append the entry to the top-level `menu` array.
|
||||
- If the user chose a **parent item**: find the parent entry by key inside `menu` (search recursively if needed) and append to its `children` array.
|
||||
- Check that the chosen `key` is not already used anywhere in the menu tree before inserting.
|
||||
- Write the updated file back preserving formatting (2-space indent).
|
||||
|
||||
## Step 6 — Report
|
||||
|
||||
Tell the user:
|
||||
- Added `<type>` entry `<key>` ("Name") at `<location>` with order `<order>`
|
||||
- Remind them: menu order is relative within siblings — reorder adjacent items if needed
|
||||
- Remind them: `icon` values are FontAwesome numeric codes; use `null` for leaf items
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
name: vitruvio-atualizar-manifesto
|
||||
description: >
|
||||
Use when the user wants to update, rename, or remove an existing artifact registration
|
||||
in vitruvio.json. Triggers: "update the category/description/authMode of", "change the name of",
|
||||
"rename this panel/script/endpoint", "remove this artifact", "unregister", "atualizar manifesto",
|
||||
"mudar categoria", "remover painel", or any request to modify fields of an already-registered artifact.
|
||||
Do NOT trigger for creating new artifacts (use vitruvio-criar-* skills) or adding menu entries (use vitruvio-adicionar-menu).
|
||||
---
|
||||
|
||||
# Update Vitruvio Manifest Entry
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are modifying an existing entry in `vitruvio.json`. No new files — only manifest changes (and optionally file moves/deletions). Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Identify operation and target
|
||||
|
||||
Ask the user (in a single message, only ask what is missing):
|
||||
|
||||
- **Operation** — one of:
|
||||
- **A — Update fields**: change metadata on an existing entry (category, description, authMode, allowedGroups, displayOrder, etc.)
|
||||
- **B — Remove entry**: unregister an artifact from `vitruvio.json`
|
||||
- **C — Rename key**: change the artifact's key (breaking change — see Operation C below)
|
||||
- **Target artifact** — the key of the artifact to modify, and its section (panel, script, endpoint, query, report, library, process).
|
||||
|
||||
Read `vitruvio.json` and show the user the current entry before proceeding.
|
||||
|
||||
---
|
||||
|
||||
## Operation A — Update fields
|
||||
|
||||
Ask what fields to change and their new values. Then edit `vitruvio.json` with the updated values, preserving all other fields exactly.
|
||||
|
||||
### Editable fields by artifact type
|
||||
|
||||
**Panel:** `name`, `description`, `category`, `displayOrder`, `showInPresentation`, `openInNewWindow`, `showInMobileList`, `displayTimeInSeconds`, `allowedGroups`, `allowedUsers`, `forms.mobile`, `forms.mobileAlternative`, `defaultState`, `thumbnail`
|
||||
|
||||
**Script:** `name`, `description`, `domain` (`USUARIO` / `SISTEMA`)
|
||||
|
||||
**Endpoint:** `name`, `description`, `authMode` (`PUBLIC`, `STATIC_TOKEN`, `VITRUVIO_WS_USER_AUTH`, `HTTP_BASIC_AUTH`), `active`
|
||||
|
||||
**Query:** `name`, `connection`
|
||||
|
||||
**Report:** `name`, `description`, `category`, `owner`, `orientation`, `parameterForm`, `allowedGroups`, `allowedUsers`
|
||||
|
||||
**Library:** `name`, `authMode`, `mobileEnabled`
|
||||
|
||||
**Process:** `name`, `description`, `forms.mobile`, `forms.mobileAlternative`
|
||||
|
||||
After editing, report each field that changed (old value → new value).
|
||||
|
||||
---
|
||||
|
||||
## Operation B — Remove entry
|
||||
|
||||
1. Show the full current entry from `vitruvio.json` to the user.
|
||||
2. Ask for explicit confirmation before proceeding — do not remove without a yes.
|
||||
3. Remove the entry from its section array in `vitruvio.json`. If the section array becomes empty, remove the array key entirely.
|
||||
4. Ask the user: **"Do you also want to delete the artifact's files from disk?"** — list which files/directories would be deleted. Do not delete anything without an explicit yes.
|
||||
5. If yes, delete the files.
|
||||
6. Report: entry removed from `vitruvio.json`; files deleted if confirmed.
|
||||
|
||||
---
|
||||
|
||||
## Operation C — Rename key
|
||||
|
||||
> **Warning to show the user before proceeding:**
|
||||
> Renaming a key is a breaking change. Any code that references the old key — `engine.showPanel('<old-key>')`, `libService.loadScript('<old-key>')`, menu entries, process variables, external integrations — will break. Search the repo for the old key before confirming.
|
||||
|
||||
1. Show the warning above and ask the user to confirm they understand.
|
||||
2. Ask for the new key (kebab-case). Check it is not already used in the same section of `vitruvio.json`.
|
||||
3. Update the `key` field in `vitruvio.json`.
|
||||
4. Update any `source`, `bpmn`, `template`, `parameterForm`, `files`, or `forms.*` paths inside the same entry that include the old key in their path.
|
||||
5. Rename the artifact's directory or file on disk:
|
||||
- `panels/<old-key>/` → `panels/<new-key>/`
|
||||
- `scripts/<old-key>.js` → `scripts/<new-key>.js`
|
||||
- `endpoints/<old-key>.js` → `endpoints/<new-key>.js`
|
||||
- `queries/<old-key>.sql` → `queries/<new-key>.sql`
|
||||
- `reports/<old-key>/` → `reports/<new-key>/`
|
||||
- `libraries/<old-key>/` → `libraries/<new-key>/`
|
||||
- `processes/<old-key>/` → `processes/<new-key>/`
|
||||
6. Check if a menu entry references the old key (`panelKey`, `reportKey`, `processKey`) and update it too.
|
||||
7. Report: what was renamed in `vitruvio.json` and on disk. Remind the user to search the codebase for any remaining hardcoded references to the old key:
|
||||
|
||||
```bash
|
||||
grep -r "<old-key>" --include="*.xml" --include="*.js" --include="*.json" .
|
||||
```
|
||||
@@ -0,0 +1,109 @@
|
||||
---
|
||||
name: vitruvio-criar-biblioteca
|
||||
description: >
|
||||
Use when the user wants to create a new static file library in a Vitruvio repository.
|
||||
Triggers: "create library", "new library", "criar biblioteca", "nova biblioteca", "add library",
|
||||
"static files", "JS library", "CSS library", or any request to scaffold a libraries/<key>/ directory
|
||||
and register it in vitruvio.json.
|
||||
---
|
||||
|
||||
# Create Vitruvio Library
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are creating a new static file library inside a Vitruvio repository. Libraries are static file bundles (JS, CSS, images) served by the platform as HTTP resources. They are different from scripts — scripts run server-side on Rhino; libraries are served to clients as-is. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect library details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing from their original request):
|
||||
|
||||
- **Key (sigla)** — kebab-case unique identifier. Used to reference the library and build its endpoint URL. Stable — changing it breaks any code that references it.
|
||||
- **Name** — human-readable label shown in the Vitruvio UI.
|
||||
- **Description** — one sentence about what this library provides (optional).
|
||||
- **Auth mode** — one of:
|
||||
- `PUBLIC` — accessible without authentication (default; use for most JS/CSS bundles)
|
||||
- `STATIC_TOKEN` — token required; use when files must not be publicly accessible
|
||||
- **Mobile enabled?** — `true` if mobile clients need to load these files; `false` otherwise (default: `false`).
|
||||
- **What files will this library contain?** — brief description so you can create a useful starting placeholder (e.g. "custom JS utilities for panels", "CSS theme overrides", "image assets").
|
||||
|
||||
## Step 3 — Scaffold and create files
|
||||
|
||||
```bash
|
||||
vitruvio new library <key> --name "<name>"
|
||||
```
|
||||
|
||||
This creates the `libraries/<key>/` directory and registers it in `vitruvio.json` with `authMode: "PUBLIC"` and `mobileEnabled: false`. Then create a placeholder file appropriate to what the user described:
|
||||
|
||||
- For a **JS library**: `libraries/<key>/<key>.js`
|
||||
- For a **CSS library**: `libraries/<key>/<key>.css`
|
||||
- For an **image/mixed library**: `libraries/<key>/README.md` explaining what belongs here
|
||||
|
||||
### JS placeholder
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Library: <name>
|
||||
* Key: <key>
|
||||
* Description: <description>
|
||||
*
|
||||
* These files are served as static HTTP resources.
|
||||
* Access URL: vBibliotecaService.buildEndpointUrl('<key>', '<key>.js')
|
||||
*/
|
||||
|
||||
// Add your client-side JavaScript here.
|
||||
// This runs in the browser — full ES6+ is supported (unlike server-side Rhino scripts).
|
||||
```
|
||||
|
||||
### CSS placeholder
|
||||
|
||||
```css
|
||||
/**
|
||||
* Library: <name>
|
||||
* Key: <key>
|
||||
* Description: <description>
|
||||
*
|
||||
* These files are served as static HTTP resources.
|
||||
* Access URL: vBibliotecaService.buildEndpointUrl('<key>', '<key>.css')
|
||||
*/
|
||||
|
||||
/* Add your styles here */
|
||||
```
|
||||
|
||||
## Step 4 — Update vitruvio.json entry
|
||||
|
||||
`vitruvio new` already added the library entry. The full entry shape is:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description>",
|
||||
"type": "LOCAL",
|
||||
"authMode": "PUBLIC",
|
||||
"authToken": null,
|
||||
"mobileEnabled": false,
|
||||
"files": "libraries/<key>/"
|
||||
}
|
||||
```
|
||||
|
||||
Update only what differs: set `"authMode"` if not `"PUBLIC"`; set `"mobileEnabled": true` if mobile clients need this library; add `"description"` if provided.
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- Directory created: `libraries/<key>/`
|
||||
- Placeholder file(s) created
|
||||
- Registered in `vitruvio.json` with key `<key>`
|
||||
- How to get the serving URL at runtime:
|
||||
```javascript
|
||||
var url = vBibliotecaService.buildEndpointUrl('<key>', 'filename.js');
|
||||
```
|
||||
- Remind them: files in this directory are served as-is — client-side JS here can use modern ES6+, unlike server-side Rhino scripts
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Nome: Parâmetro de indicadores
|
||||
* Sigla: dashboard_ia
|
||||
* Descrição: Biblioteca compartilhada dos dashboards de indicadores da Diretoria: preferências de usuário (tema), CSS do topbar e toolbar, helpers de tema.
|
||||
*/
|
||||
(function() {
|
||||
var PREF_NS = 'preferences';
|
||||
|
||||
function loadPreferences(login) {
|
||||
try {
|
||||
var raw = vConfigService.getUserConfigAsString(String(login), PREF_NS);
|
||||
if (raw != null && String(raw) !== '') {
|
||||
return JSON.parse(String(raw));
|
||||
}
|
||||
} catch (e) {}
|
||||
return { dark_mode: true };
|
||||
}
|
||||
|
||||
function savePreferences(login, prefs) {
|
||||
try {
|
||||
vConfigService.saveUserConfig(String(login), PREF_NS, JSON.stringify(prefs));
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function getTheme(engine) {
|
||||
return String(engine.getGlobalVariable('theme') || 'dark');
|
||||
}
|
||||
|
||||
function isLight(engine) {
|
||||
return getTheme(engine) === 'light';
|
||||
}
|
||||
|
||||
function applyTheme(themeName, page, btnTheme) {
|
||||
page.getJavaScript().execute(
|
||||
themeName === 'light'
|
||||
? "document.body.classList.add('theme-light-app');"
|
||||
: "document.body.classList.remove('theme-light-app');"
|
||||
);
|
||||
if (btnTheme != null) {
|
||||
btnTheme.setCaption(themeName === 'dark' ? '☽' : '☀');
|
||||
}
|
||||
}
|
||||
|
||||
function addSharedCss(page) {
|
||||
var style =
|
||||
".dash-topbar { background:#0f1923 !important; border-bottom:1px solid #243447; } " +
|
||||
".dash-topbar .v-button { background:transparent !important; border:1px solid transparent !important; color:#556677 !important; border-radius:4px !important; transition:none !important; } " +
|
||||
".dash-topbar .v-button .v-button-caption { color:#556677 !important; } " +
|
||||
".dash-topbar .v-button.dash-nav-active { background:#1a2733 !important; border-color:#4a9edd !important; } " +
|
||||
".dash-topbar .v-button.dash-nav-active .v-button-caption { color:#4a9edd !important; font-weight:bold !important; } " +
|
||||
".dash-topbar .v-caption { color:#8899aa !important; font-size:10px !important; } " +
|
||||
".dash-topbar input.v-textfield, .dash-topbar .v-datefield-textfield, .dash-topbar input.v-filterselect-input { background:#1a2733 !important; color:#ccc !important; border-color:#243447 !important; } " +
|
||||
".dash-topbar .v-datefield-button { background:#1a2733 !important; border-color:#243447 !important; } " +
|
||||
".dash-topbar .v-select-select { background:#1a2733 !important; color:#ccc !important; border-color:#243447 !important; } " +
|
||||
"body.theme-light-app .dash-topbar { background:#f8fafc !important; border-bottom:1px solid #e2e8f0; } " +
|
||||
"body.theme-light-app .dash-topbar .v-button { color:#475569 !important; } " +
|
||||
"body.theme-light-app .dash-topbar .v-button .v-button-caption { color:#475569 !important; } " +
|
||||
"body.theme-light-app .dash-topbar .v-button.dash-nav-active { background:#dbeafe !important; border-color:#2563eb !important; color:#2563eb !important; } " +
|
||||
"body.theme-light-app .dash-topbar .v-button.dash-nav-active .v-button-caption { color:#2563eb !important; } " +
|
||||
"body.theme-light-app .dash-topbar input.v-textfield, body.theme-light-app .dash-topbar .v-datefield-textfield, body.theme-light-app .dash-topbar input.v-filterselect-input { background:#ffffff !important; color:#334155 !important; border-color:#e2e8f0 !important; } " +
|
||||
"body.theme-light-app .dash-topbar .v-datefield-button { background:#ffffff !important; border-color:#e2e8f0 !important; } " +
|
||||
"body.theme-light-app .dash-topbar .v-select-select { background:#ffffff !important; color:#334155 !important; border-color:#e2e8f0 !important; } " +
|
||||
".theme-light.dash-wrapper { background:#f0f2f5; } " +
|
||||
".theme-light .dash-kpi { background:#ffffff; box-shadow:0 1px 3px rgba(0,0,0,.08); } " +
|
||||
".theme-light .dash-kpi-label { color:#64748b; } " +
|
||||
".theme-light .dash-kpi-value { color:#1e293b; } " +
|
||||
".theme-light .dash-chart-box { background:#ffffff; box-shadow:0 1px 3px rgba(0,0,0,.08); } " +
|
||||
".theme-light .dash-chart-title { color:#64748b; } " +
|
||||
".mov-toolbar { display:flex; align-items:center; gap:8px; margin-bottom:8px; } " +
|
||||
".mov-filter-input { background:#1a2733; border:1px solid #243447; color:#ccc; padding:5px 10px; border-radius:4px; font-size:11px; flex:1; outline:none; font-family:Arial; } " +
|
||||
".mov-filter-input:focus { border-color:#4a9edd; } " +
|
||||
".mov-export-btn { background:#1a2733; border:1px solid #243447; color:#8899aa; padding:5px 12px; border-radius:4px; font-size:11px; cursor:pointer; white-space:nowrap; font-family:Arial; } " +
|
||||
".mov-export-btn:hover { border-color:#4a9edd; color:#4a9edd; } " +
|
||||
".theme-light .mov-filter-input { background:#ffffff; border-color:#e2e8f0; color:#334155; } " +
|
||||
".theme-light .mov-filter-input:focus { border-color:#2563eb; } " +
|
||||
".theme-light .mov-export-btn { background:#ffffff; border-color:#e2e8f0; color:#64748b; } " +
|
||||
".theme-light .mov-export-btn:hover { border-color:#2563eb; color:#2563eb; } ";
|
||||
|
||||
page.getStyles().add(style);
|
||||
}
|
||||
|
||||
return {
|
||||
loadPreferences: loadPreferences,
|
||||
savePreferences: savePreferences,
|
||||
getTheme: getTheme,
|
||||
isLight: isLight,
|
||||
applyTheme: applyTheme,
|
||||
addSharedCss: addSharedCss
|
||||
};
|
||||
})()
|
||||
@@ -0,0 +1,784 @@
|
||||
---
|
||||
name: vitruvio-criar-dashboard-mobile
|
||||
description: >
|
||||
Use when creating the MOBILE version of an existing Vitruvio indicator dashboard panel — a
|
||||
DesktopPanel shell (`<key>-mobile.xml`) that delegates to `<key>-desktop.xml`, plus the mobile
|
||||
detection/adaptation added to the desktop form: collapsible filter sidebar, stacked cards,
|
||||
dark/light theme toggle, and developer mobile preview. Triggers: "criar mobile do dashboard",
|
||||
"adicionar versão mobile ao indicador", "mobile preview do painel indicador",
|
||||
"CriarMobileIndicador". Called by vitruvio-criar-indicador-dashboard for the mobile part; can
|
||||
also be invoked directly with the target `<key>-desktop.xml` path. For the desktop part use
|
||||
vitruvio-criar-dashboard-desktop.
|
||||
---
|
||||
|
||||
# Criar Mobile para Painel Indicador
|
||||
|
||||
> Todas as mensagens ao usuário devem ser em Português.
|
||||
|
||||
Você está criando a versão mobile de um painel Vitruvio existente. O padrão usado neste repositório é o **DesktopPanel delegado**: o `<key>-mobile.xml` é um shell mínimo que delega toda a renderização ao `<key>-desktop.xml`, que detecta o contexto mobile e adapta o layout sozinho.
|
||||
|
||||
Receba o caminho do `<key>-desktop.xml` alvo como argumento (ex: `panels/meu-painel/meu-painel-desktop.xml`).
|
||||
|
||||
---
|
||||
|
||||
## LEITURA OBRIGATÓRIA AO INICIAR — CONTEXT.md
|
||||
|
||||
**Esta é a primeira coisa a fazer, antes de abrir qualquer outro arquivo.**
|
||||
|
||||
Derive o diretório do painel a partir do argumento e leia:
|
||||
|
||||
```
|
||||
panels/<panel-key>/CONTEXT.md
|
||||
```
|
||||
|
||||
**Se o CONTEXT.md existir:**
|
||||
- Leia-o completo primeiro. Ele contém os IDs reais dos campos, layouts, funções JS e o estado atual do painel — tudo que você precisa para criar o mobile corretamente sem reler o XML inteiro.
|
||||
- Use a seção **"IDs de layout e campo"** para preencher `forceLayoutsRender` e `forceFieldsRender` no `<key>-mobile.xml`.
|
||||
- Use a seção **"Funções JavaScript principais"** para entender o que já existe e evitar duplicar.
|
||||
- Informe ao usuário: _"Li o contexto do painel. Vou criar o mobile com base nele."_
|
||||
- Ao final, **atualize o CONTEXT.md**: marque a versão mobile como "concluída", adicione o caminho do `<key>-mobile.xml` e registre a modificação no histórico.
|
||||
|
||||
**Se o CONTEXT.md não existir:**
|
||||
- Informe ao usuário que não há arquivo de contexto para este painel.
|
||||
- Leia o `<key>-desktop.xml` completo para extrair os IDs necessários.
|
||||
- Ao final, crie o CONTEXT.md completo (veja o formato na Fase 8 da skill **vitruvio-criar-dashboard-desktop**).
|
||||
|
||||
---
|
||||
|
||||
## Princípios obrigatórios — leia antes de qualquer coisa
|
||||
|
||||
### Nunca invente — pergunte quando tiver dúvida
|
||||
|
||||
- Se não conhecer um componente, atributo ou comportamento da plataforma: **pergunte ao usuário** antes de inventar. Uma pergunta custa menos do que um bug em produção.
|
||||
- Baseie-se sempre no que já existe: leia o painel alvo e outros painéis deste repositório para entender o padrão adotado. O que já funciona aqui é a referência.
|
||||
- Não suponha assinaturas de serviços, nomes de atributos ou comportamentos de componentes que não estejam evidentes no código existente.
|
||||
|
||||
### Se o painel tem filtros acima do conteúdo — mova-os para uma sidebar colapsável
|
||||
|
||||
Se o painel alvo tem filtros (combos, campos de texto, datas) posicionados **acima** do conteúdo principal (em linha no topo), **não deixe assim no mobile**. Filtros acima ocupam espaço valioso e poluem a tela.
|
||||
|
||||
O padrão deste repositório é a **sidebar de filtros colapsável** — os dois painéis de referência
|
||||
canônicos estão empacotados junto com a skill desktop, leia-os antes de aplicar o padrão:
|
||||
- `.claude/skills/vitruvio-criar-dashboard-desktop/dashboard-contrato-desktop.xml`
|
||||
- `.claude/skills/vitruvio-criar-dashboard-desktop/dashboard-tecnicos-desktop.xml`
|
||||
|
||||
O painel usa a global `dashLib` (carregada pelo `run()` desktop via `libService.loadScript('dashboard_ia')`)
|
||||
para tema/preferências — veja `.claude/skills/vitruvio-criar-dashboard-desktop/dashboard_ia.js` para
|
||||
a API disponível. **É material de consulta apenas: nunca crie, copie ou registre um
|
||||
`scripts/dashboard_ia.js` no repositório de destino.**
|
||||
|
||||
O padrão é:
|
||||
|
||||
**Layout estrutural (XML)**:
|
||||
```
|
||||
rootLayout (VerticalLayout, 100% x 100%)
|
||||
├── topBar (HorizontalLayout) — botão "◄ Filtros" + navegação + spacer + tema
|
||||
└── mainArea (HorizontalLayout, expandRatio=1)
|
||||
├── filterBar (VerticalLayout, width=220px) — os filtros em coluna
|
||||
└── contentPanel (VerticalLayout, expandRatio=1) — o conteúdo principal
|
||||
```
|
||||
|
||||
**Botão toggle no topBar**:
|
||||
```xml
|
||||
<ButtonWidget id="btnToggleFiltros" caption="◄ Filtros">
|
||||
<onClickScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
var fb = engine.getLayout('filterBar');
|
||||
if (!fb) return;
|
||||
var filterLayout = fb.getRootComposition();
|
||||
var estaVisivel = filterLayout.isVisible();
|
||||
filterLayout.setVisible(!estaVisivel);
|
||||
var btn = engine.getWidgetController('btnToggleFiltros').getButton();
|
||||
btn.setCaption(estaVisivel ? '► Filtros' : '◄ Filtros');
|
||||
}
|
||||
]]>
|
||||
</onClickScript>
|
||||
</ButtonWidget>
|
||||
```
|
||||
|
||||
**No mobile**: o `filterBar` começa invisível (escondido no bloco `_mobileRender` do `run()`). O mobile tem seu próprio botão de filtros dentro da `filterBar` (`btnPreviewFiltros`) que só aparece no mobile — ele aciona o mesmo toggle, mantendo consistência sem adicionar nova lógica.
|
||||
|
||||
**Botão de tema dentro da filterBar (obrigatório no mobile)**: adicione um `ButtonWidget id="btnThemeMob"` no fundo do `filterBar` (após o spacer Label), com `visible="false"`. No bloco `_mobileRender` do `run()`, torne-o visível. Ele deve chamar a mesma função `doToggleTheme` que o `btnTheme` do topBar — defina essa função no ScriptWidget InitScript, registre-a com `engine.setGlobalVariable('doToggleTheme', doToggleTheme)` no `init()`, e tenha ambos os botões chamando-a via `engine.getGlobalVariable('doToggleTheme')`. A função atualiza a caption de AMBOS os botões de tema (`btnTheme` e `btnThemeMob`), o CSS do filterBar e o HTML gerado.
|
||||
|
||||
```xml
|
||||
<!-- dentro do filterBar, após o spacer Label -->
|
||||
<ButtonWidget id="btnThemeMob" caption="☽" description="Alternar tema" visible="false" width="100%">
|
||||
<onClickScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
var fn = engine.getGlobalVariable('doToggleTheme');
|
||||
if (fn) fn();
|
||||
}
|
||||
]]>
|
||||
</onClickScript>
|
||||
</ButtonWidget>
|
||||
```
|
||||
|
||||
```javascript
|
||||
// No bloco _mobileRender do run():
|
||||
var _btnThemeMob = engine.getWidgetController('btnThemeMob');
|
||||
if (_btnThemeMob) { _btnThemeMob.getButton().setVisible(true); }
|
||||
// Oculta btnTheme do topBar — no mobile apenas btnThemeMob é visível
|
||||
var _btnThemeTb = engine.getWidgetController('btnTheme');
|
||||
if (_btnThemeTb) { _btnThemeTb.getButton().setVisible(false); }
|
||||
|
||||
// doToggleTheme no ScriptWidget InitScript — atualiza os dois botões:
|
||||
function doToggleTheme() {
|
||||
var dashLib = engine.getGlobalVariable('dashLib');
|
||||
var login = engine.getGlobalVariable('userLogin');
|
||||
var current = String(engine.getGlobalVariable('theme') || 'dark');
|
||||
var next = current === 'dark' ? 'light' : 'dark';
|
||||
engine.setGlobalVariable('theme', next);
|
||||
try {
|
||||
var ui = Packages.com.vaadin.ui.UI.getCurrent();
|
||||
if (dashLib && ui) { dashLib.applyTheme(next, ui.getPage(), null); }
|
||||
if (dashLib && login) { dashLib.savePreferences(login, { dark_mode: next === 'dark' }); }
|
||||
} catch(e) {}
|
||||
var bT = engine.getWidgetController('btnTheme');
|
||||
if (bT) { bT.getButton().setCaption(next === 'dark' ? '☽' : '☀'); }
|
||||
var bM = engine.getWidgetController('btnThemeMob');
|
||||
if (bM) { bM.getButton().setCaption(next === 'dark' ? '☽' : '☀'); }
|
||||
var _fb = engine.getLayout('filterBar');
|
||||
if (_fb) {
|
||||
var fbComp = _fb.getRootComposition();
|
||||
if (next === 'light') { fbComp.removeStyleName('dash-filterbar-dark'); fbComp.addStyleName('dash-filterbar-light'); }
|
||||
else { fbComp.removeStyleName('dash-filterbar-light'); fbComp.addStyleName('dash-filterbar-dark'); }
|
||||
}
|
||||
try {
|
||||
Packages.com.vaadin.ui.UI.getCurrent().getPage().getJavaScript().execute(
|
||||
"var el=document.getElementById('meu-painel-main');" +
|
||||
"if(el){if('" + next + "'==='light')el.classList.add('theme-light');else el.classList.remove('theme-light');}"
|
||||
);
|
||||
} catch(e2) {}
|
||||
var fn = engine.getGlobalVariable('renderView');
|
||||
if (fn) fn();
|
||||
}
|
||||
|
||||
// No init():
|
||||
engine.setGlobalVariable('doToggleTheme', doToggleTheme);
|
||||
```
|
||||
|
||||
**Os campos de filtro dentro da filterBar são os do painel novo** — não copie os campos do `dashboard-contratos`. Leia o painel alvo, identifique seus filtros originais (cmbAno, cmbMes, txtCliente, etc.) e coloque-os dentro do `filterBar` como `VerticalLayout` com `spacing="true" margin="true"`.
|
||||
|
||||
**`forceFieldsRender` no `<key>-mobile.xml`**: inclua todos os campos de filtro da sidebar. Sem isso, `engine.getField('cmbAno')` falha no WebView mobile.
|
||||
|
||||
**Campo de pesquisa/busca do desktop**: se o painel desktop tem um campo de texto para pesquisa (ex: filtrar por cliente, número, palavra-chave), ele **obrigatoriamente deve aparecer no mobile também**, dentro da filterBar. Nunca omita a pesquisa no mobile — o usuário mobile precisa dela tanto quanto o desktop. Se a pesquisa estiver inline acima da tabela no desktop, mova-a para dentro da `filterBar` no mobile (ou mantenha nos dois lugares).
|
||||
|
||||
---
|
||||
|
||||
### Siga o padrão do repositório
|
||||
|
||||
Este repositório já tem painéis funcionando. Antes de criar algo novo:
|
||||
|
||||
- Leia pelo menos um `<key>-desktop.xml` existente deste repo para entender como estão estruturados os componentes, como são chamados os serviços, como é o estilo visual
|
||||
- Reutilize classes CSS já definidas (ex: `dash-kpi`, `dash-kpi-sm`, `mov-card`, `mobile-topbar`) em vez de criar novas
|
||||
- Reutilize padrões de `run()` / `init()` / `renderHtml()` que já existem no painel alvo
|
||||
|
||||
### Botões e campos padronizados
|
||||
|
||||
- **Botões de ação** (`ButtonWidget`): use `style="DEFAULT"` para ações neutras, `style="GREEN"` para confirmar/salvar, `style="RED"` para cancelar/excluir. Nunca crie botões com HTML customizado quando um `ButtonWidget` padrão serve.
|
||||
- **Campos de filtro**: `ComboBox` para listas fechadas, `TextField` para texto livre, `DateField` para datas. Sempre com `id` único e `caption` em português.
|
||||
- **Ícones**: use os já presentes no painel alvo. Se precisar de novo ícone, use os codepoints já usados no repositório (ex: `📱` para mobile, `✕` para fechar) — não invente.
|
||||
- **Labels de valor monetário**: sempre use a função de formatação já existente no painel (ex: `formatBRL(valor)`) — não crie nova.
|
||||
|
||||
### JavaScript — ES5 Rhino somente
|
||||
|
||||
Nunca use ES6+. Veja o que é proibido e o que usar no lugar:
|
||||
|
||||
```javascript
|
||||
// PROIBIDO
|
||||
const x = 1; // use: var x = 1;
|
||||
let y = 2; // use: var y = 2;
|
||||
() => {}; // use: function() {}
|
||||
`texto ${var}`; // use: 'texto ' + var
|
||||
const { a } = obj; // use: var a = obj.a;
|
||||
class Foo {} // use: function Foo() {}
|
||||
async/await // use: callbacks
|
||||
import/export // não disponível
|
||||
for (const x of arr) // use: for (var i = 0; i < arr.length; i++)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Passo 1 — Confirmar repositório e ler o painel alvo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
Se `NOT_A_VITRUVIO_REPO`, pare e peça para o usuário entrar no diretório correto.
|
||||
|
||||
Leia o `<key>-desktop.xml` alvo **completo** antes de escrever qualquer linha. Leia também pelo menos um outro painel do repositório para entender os padrões já adotados. Só depois prossiga.
|
||||
|
||||
Leia o `<key>-desktop.xml` alvo para entender:
|
||||
- O `formKey` do painel
|
||||
- Os layouts declarados (`id` de cada `VerticalLayout`, `HorizontalLayout`, `Panel`, etc.)
|
||||
- Os campos de filtro (`ComboBox`, `TextField`, etc.)
|
||||
- A estrutura do `initScript` / `run()` / `init()` do ScriptWidget
|
||||
- O que é renderizado em HTML (ScriptWidget que chama `renderHtml`)
|
||||
|
||||
Leia o `vitruvio.json` para identificar a entrada do painel (`key`, `forms`).
|
||||
|
||||
---
|
||||
|
||||
## Passo 2 — Criar o `<key>-mobile.xml`
|
||||
|
||||
Crie `panels/<key>/<key>-mobile.xml`. Ele é um shell que delega ao desktop:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/mobile/panel"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/mobile/panel
|
||||
https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-mobile-panel-form.xsd">
|
||||
<form formKey="<key>-mobile">
|
||||
<name><NOME DO PAINEL></name>
|
||||
<description><DESCRIÇÃO></description>
|
||||
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
// _mobileRender é setado automaticamente pelo DesktopPanel antes
|
||||
// do painel desktop inicializar — o desktop detecta e adapta o layout.
|
||||
}
|
||||
]]>
|
||||
</initScript>
|
||||
|
||||
<components>
|
||||
<VerticalLayout width="100%" height="100%">
|
||||
<DesktopPanel id="<camelCaseId>" panelKey="<key>"
|
||||
layoutId="rootLayout" external="true"
|
||||
forceLayoutsRender="<lista de layout ids separados por vírgula>"
|
||||
forceFieldsRender="<lista de field ids separados por vírgula>" height="100%" />
|
||||
</VerticalLayout>
|
||||
</components>
|
||||
</form>
|
||||
</panel-form>
|
||||
```
|
||||
|
||||
**`forceLayoutsRender`**: liste todos os layouts do desktop que precisam existir no DOM móvel (ex: `topBar, filterBar, mainArea, contentPanel, panelRoot`). Leia o XML do desktop para identificá-los.
|
||||
|
||||
**`forceFieldsRender`**: liste os campos de filtro que o script usa (ex: `cmbAno, cmbMes, cmbOrdem`). Sem eles, `engine.getField('cmbAno')` falha no mobile.
|
||||
|
||||
**`height="100%"` no `VerticalLayout` e no `DesktopPanel`**: os dois níveis do shell usam `100%` — a altura real vem do container mobile que envolve este `<key>-mobile.xml`. Um valor fixo grande (ex: `3000px`/`700px`) já foi usado aqui para forçar os layouts filhos a calcularem porcentagem, mas isso quebrava a tela (espaço em branco/corte); com `100%` nos dois níveis do shell a altura se propaga corretamente do container pai, sem precisar desse artifício.
|
||||
|
||||
---
|
||||
|
||||
## Passo 3 — Atualizar vitruvio.json
|
||||
|
||||
No `vitruvio.json`, na entrada do painel:
|
||||
- Adicione `"mobile": "panels/<key>/<key>-mobile.xml"` dentro de `"forms"`
|
||||
- Defina `"showInMobileList": true`
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"forms": {
|
||||
"desktop": "panels/<key>/<key>-desktop.xml",
|
||||
"mobile": "panels/<key>/<key>-mobile.xml"
|
||||
},
|
||||
"showInMobileList": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Passo 4 — Adaptar o `<key>-desktop.xml`
|
||||
|
||||
Esta é a parte principal. O desktop precisa detectar o contexto mobile e adaptar o HTML gerado.
|
||||
|
||||
### 4.1 — Bloco de inicialização mobile no `run()`
|
||||
|
||||
Dentro do `run()` (initScript), após o bloco de CSS principal, adicione a detecção mobile:
|
||||
|
||||
```javascript
|
||||
if (engine.isGlobalVariableSet('_mobileRender')) {
|
||||
engine.setGlobalVariable('isMobile', true);
|
||||
var _rootLayout = engine.getLayout('rootLayout');
|
||||
if (_rootLayout) {
|
||||
var _rootLayoutComp = _rootLayout.getRootComposition();
|
||||
// 700px é só o placeholder inicial — o server não sabe a altura da tela do
|
||||
// cliente. O script abaixo troca por um valor proporcional a window.screen.height
|
||||
// assim que a página carrega, ANTES do colapso de renderHtml (ver seção 4.6).
|
||||
_rootLayoutComp.setHeight("700px");
|
||||
_rootLayoutComp.addStyleName('mobile-root-frame');
|
||||
page.getJavaScript().execute(
|
||||
"(function(){" +
|
||||
"var el=document.querySelector('.mobile-root-frame');" +
|
||||
"if(!el){return;}" +
|
||||
"var sh=window.screen.height;" +
|
||||
"var byMargin=sh-220;" +
|
||||
"var byRatio=Math.round(sh*0.80);" +
|
||||
"var h=Math.max(320,Math.min(byMargin,byRatio));" +
|
||||
"el.style.height=h+'px';" +
|
||||
"})();"
|
||||
);
|
||||
}
|
||||
// Aplica estilo de topbar mobile ao topBar se existir
|
||||
var _topBar = engine.getLayout('topBar');
|
||||
if (_topBar) { _topBar.getRootComposition().addStyleName('mobile-topbar'); }
|
||||
// Esconde filterBar no mobile (filtros ficam em outro fluxo ou botão)
|
||||
var _filterBar = engine.getLayout('filterBar');
|
||||
if (_filterBar) { _filterBar.getRootComposition().setVisible(false); }
|
||||
// Exibe btnThemeMob (dentro da filterBar) e oculta btnTheme (topBar)
|
||||
// Sem isso o usuário vê dois botões de tema simultaneamente no mobile
|
||||
var _btnThemeMobR = engine.getWidgetController('btnThemeMob');
|
||||
if (_btnThemeMobR) { _btnThemeMobR.getButton().setVisible(true); }
|
||||
var _btnThemeTbR = engine.getWidgetController('btnTheme');
|
||||
if (_btnThemeTbR) { _btnThemeTbR.getButton().setVisible(false); }
|
||||
// CSS específico mobile adicionado via page.getStyles().add()
|
||||
page.getStyles().add(
|
||||
// Tabelas/grids mobile: altura fixa com scroll interno
|
||||
".mobile-view .mov-scroll { height:520px !important; overflow-y:auto !important;" +
|
||||
" -webkit-overflow-scrolling:touch !important; overscroll-behavior:contain; touch-action:pan-y; } "
|
||||
// Adicione aqui outros CSS mobile específicos deste painel
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
> **Se o painel alvo tem o assistente de IA "Pietro"** (sidebar `chatBar`, veja a seção opcional
|
||||
> 7.11 da skill **vitruvio-criar-dashboard-desktop**): no mesmo bloco `_mobileRender`, oculte
|
||||
> `btnToggleChat` e mostre `btnChatFabMobile` — mesmo tratamento dado a `btnTheme`/`btnThemeMob`
|
||||
> acima. Replique também em `enterMobilePreview`/`exitMobilePreview` (Passo 5.3). Se o painel não
|
||||
> tiver esses IDs, ignore — é um add-on opcional, não parte obrigatória do padrão.
|
||||
|
||||
### 4.2 — Variável `mobileClass` no HTML gerado
|
||||
|
||||
Em toda função que gera HTML (ex: `desenharDashboard`, `renderView`, etc.), adicione:
|
||||
|
||||
```javascript
|
||||
var mobileClass = (engine.isGlobalVariableSet('isMobile') || engine.isGlobalVariableSet('_mobileRender')) ? ' mobile-view' : '';
|
||||
var html = '<div class="dash-wrapper' + mobileClass + '" id="meu-painel-main">';
|
||||
```
|
||||
|
||||
Isso permite que todo o CSS mobile use seletores `.mobile-view .classe` sem afetar o desktop.
|
||||
|
||||
### 4.3 — Layouts horizontais → cards verticais no mobile
|
||||
|
||||
**Regra de ouro**: no mobile NUNCA coloque duas informações lado a lado. Tudo em coluna única.
|
||||
|
||||
**Desktop** (layout horizontal):
|
||||
```javascript
|
||||
html += '<div style="display:flex; gap:16px;">';
|
||||
html += '<div>R$ 100.000</div>';
|
||||
html += '<div>R$ 50.000</div>';
|
||||
html += '</div>';
|
||||
```
|
||||
|
||||
**Mobile** (detectar e converter para vertical):
|
||||
```javascript
|
||||
if (mobileClass) {
|
||||
html += '<div style="display:flex; flex-direction:column; gap:8px;">';
|
||||
html += '<div class="mob-card"><span class="mob-label">Locação</span><span class="mob-value">R$ 100.000</span></div>';
|
||||
html += '<div class="mob-card"><span class="mob-label">Serviço</span><span class="mob-value">R$ 50.000</span></div>';
|
||||
html += '</div>';
|
||||
} else {
|
||||
// layout desktop original
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 — Tabelas (grids) no mobile → cards empilhados
|
||||
|
||||
No desktop: `<table>` com colunas. No mobile: cada linha vira um card com os campos empilhados:
|
||||
|
||||
```javascript
|
||||
if (mobileClass) {
|
||||
html += '<div class="mov-scroll"><div class="mov-cards">';
|
||||
for (var i = 0; i < linhas.length; i++) {
|
||||
var l = linhas[i];
|
||||
html += '<div class="mov-card">';
|
||||
html += '<div class="mov-card-top">';
|
||||
html += ' <span class="mov-card-cliente">' + l.nome + '</span>';
|
||||
html += '</div>';
|
||||
html += '<div class="mov-card-meta">' + l.codigo + ' · ' + l.tipo + '</div>';
|
||||
html += '<div class="mov-card-values">';
|
||||
html += ' <div class="mov-card-val"><span class="mov-card-lbl">Valor</span><span>' + formatBRL(l.valor) + '</span></div>';
|
||||
html += ' <div class="mov-card-val"><span class="mov-card-lbl">Status</span><span>' + l.status + '</span></div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div></div>';
|
||||
} else {
|
||||
// tabela desktop
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 — CSS mobile obrigatório
|
||||
|
||||
No `addCss()` (ou no CSS principal adicionado em `run()`), inclua os seletores mobile:
|
||||
|
||||
> **CRÍTICO:** Todo CSS de scroll mobile **deve estar em `addCss()`** — nunca apenas no bloco `_mobileRender`. Se ficar só em `_mobileRender`, o preview mobile do desenvolvedor não funciona (porque `isMobile=true` mas `_mobileRender` não está setado).
|
||||
|
||||
```javascript
|
||||
// ── Scroll containers no mobile — OBRIGATÓRIO em addCss() ──
|
||||
".mobile-view .mov-scroll { max-height:520px !important; overflow-y:auto !important; -webkit-overflow-scrolling:touch !important; overscroll-behavior:contain; touch-action:pan-y; } " +
|
||||
".mobile-view .tec-mob-scroll { max-height:420px !important; overflow-y:auto !important; -webkit-overflow-scrolling:touch !important; overscroll-behavior:contain; touch-action:pan-y; } " +
|
||||
".mobile-view .hbar-scroll { max-height:280px !important; overflow-y:auto !important; -webkit-overflow-scrolling:touch !important; } " +
|
||||
|
||||
// Tamanhos de fonte mobile — NUNCA menor que o desktop, sempre maior
|
||||
".mobile-view .dash-kpi-sm .dash-kpi-label { font-size:13px !important; } " +
|
||||
".mobile-view .dash-kpi-sm .dash-kpi-value { font-size:17px !important; } " +
|
||||
|
||||
// Cards de movimentação/dados mobile
|
||||
".mov-card { background:#1a2733; border-radius:6px; padding:12px 16px; margin-bottom:8px; } " +
|
||||
".mov-card-cliente { font-weight:bold; color:#fff; font-size:13px; } " +
|
||||
".mov-card-meta { font-size:11px; color:#8899aa; margin-bottom:8px; } " +
|
||||
".mov-card-values { display:flex; flex-wrap:wrap; gap:6px; } " +
|
||||
".mov-card-val { flex:1; min-width:80px; background:#0f1923; border-radius:4px; padding:6px 8px; } " +
|
||||
".mov-card-lbl { display:block; font-size:10px; color:#8899aa; margin-bottom:2px; } " +
|
||||
|
||||
// Mobile: fontes maiores nos cards
|
||||
".mobile-view .mov-card-cliente { font-size:15px !important; } " +
|
||||
".mobile-view .mov-card-meta { font-size:13px !important; } " +
|
||||
".mobile-view .mov-card-lbl { font-size:12px !important; } " +
|
||||
```
|
||||
|
||||
**Classes de scroll padrão:**
|
||||
|
||||
| Classe | Altura | Uso |
|
||||
|---|---|---|
|
||||
| `.mov-scroll` | 520px | Container da lista de movimentação (cards ou tabela) |
|
||||
| `.tec-mob-scroll` | 420px | Container de lista de cards de técnicos/ranking |
|
||||
| `.hbar-scroll` | max 280px | Container de gráfico de barras horizontais (svgHBar) |
|
||||
|
||||
**Padrão de envolvimento obrigatório no mobile:**
|
||||
|
||||
```javascript
|
||||
// ── Movimentação: barra de pesquisa FORA do scroll, cards DENTRO ──
|
||||
html += '<div class="mob-search-bar">...</div>'; // fica fixo no topo
|
||||
html += '<div class="mov-scroll">'; // scroll interno
|
||||
for (var i = 0; i < linhas.length; i++) {
|
||||
html += '<div class="mov-card">...</div>';
|
||||
}
|
||||
if (linhas.length === 0) html += '<div style="padding:28px;text-align:center;color:#556677;">Sem dados</div>';
|
||||
html += '</div>'; // fecha mov-scroll
|
||||
|
||||
// ── Lista de técnicos/ranking: header fora, cards dentro ──
|
||||
html += '<div class="dash-chart-box">...header com botões de sort...</div>';
|
||||
html += '<div class="tec-mob-scroll">';
|
||||
html += '<div id="tec-mob-list" data-sc="total" data-sd="-1">';
|
||||
for (var i = 0; i < tecArr.length; i++) {
|
||||
html += '<div class="tec-mob-card" ...>...</div>';
|
||||
}
|
||||
html += '</div>'; // fecha tec-mob-list
|
||||
html += '</div>'; // fecha tec-mob-scroll
|
||||
|
||||
// ── Gráficos svgHBar: sempre dentro de hbar-scroll ──
|
||||
html += '<div class="dash-chart-box">';
|
||||
html += '<div class="dash-chart-title" style="font-size:13px;">Título</div>';
|
||||
html += '<div class="hbar-scroll">' + svgHBar(dados, '#4a9edd') + '</div>';
|
||||
html += '</div>';
|
||||
```
|
||||
|
||||
**Tamanhos mínimos de fonte mobile:**
|
||||
|
||||
| Tipo de dado | Desktop | Mobile mínimo |
|
||||
|---|---|---|
|
||||
| Label/rótulo | 10–12px | 12–13px |
|
||||
| Valor principal | 13–16px | 16–18px |
|
||||
| Título de seção | 14–16px | 18–20px |
|
||||
| Texto de detalhe | 11–12px | 13–14px |
|
||||
|
||||
### 4.6 — renderHtml: tratar ScriptWidget para mobile e preview
|
||||
|
||||
**Padrão real (confirmado nos dois painéis de referência):** `renderHtml` distingue **preview de
|
||||
dev** (`isMobile` setado, `_mobileRender` não) do resto — só o preview usa altura natural via
|
||||
`base.setHeight(null)`; mobile real (`_mobileRender`) e desktop normal sempre usam `setSizeFull()`.
|
||||
Não existe nenhuma técnica de "colapsar um elemento de `700px`" — isso é uma versão obsoleta. Depois
|
||||
do render, o único ajuste feito por JS é liberar `overflow:visible` nos containers pais intermediários
|
||||
até encontrar o `.mobile-root-frame` (cuja altura já foi fixada uma única vez no bloco `_mobileRender`
|
||||
do `run()`, via `window.screen.height` — ver seção 4.1), para que o scroll externo não "arraste" a
|
||||
`topBar` junto:
|
||||
|
||||
```javascript
|
||||
function renderHtml(html) {
|
||||
var comp = components.html(html);
|
||||
// isPreviewMode: preview de dev (isMobile=true SEM _mobileRender). Mobile WebView real
|
||||
// (_mobileRender=true) e desktop normal usam setSizeFull() — só o preview precisa de altura
|
||||
// natural para o Panel (contentPanel) enxergar o overflow real e rolar.
|
||||
var isPreviewMode = engine.isGlobalVariableSet('isMobile') && !engine.isGlobalVariableSet('_mobileRender');
|
||||
if (isPreviewMode) {
|
||||
try { base.setHeight(null); } catch(e) {}
|
||||
comp.setWidth("100%");
|
||||
} else {
|
||||
comp.setSizeFull();
|
||||
}
|
||||
base.removeAllComponents();
|
||||
base.addComponent(comp);
|
||||
if (!isPreviewMode) { base.setExpandRatio(comp, 1.0); }
|
||||
Packages.com.vaadin.ui.UI.getCurrent().getPage().getJavaScript().execute(
|
||||
"setTimeout(function(){" +
|
||||
"if(typeof meuPainelApplyTheme==='function')meuPainelApplyTheme();" +
|
||||
// NÃO redimensionar .mobile-root-frame para caber o conteúdo aqui — sua altura é fixa
|
||||
// (definida uma única vez no _mobileRender via window.screen.height). Só libera
|
||||
// overflow:visible nos pais intermediários para o scroll externo funcionar sem
|
||||
// "arrastar" a topBar junto.
|
||||
"var el=document.getElementById('meu-painel-main');" +
|
||||
"if(el){" +
|
||||
"var p=el,n=0;" +
|
||||
"while(p&&n<30){p=p.parentElement;n++;" +
|
||||
"if(!p){break;}" +
|
||||
"if(p.classList&&p.classList.contains('mobile-root-frame')){break;}" +
|
||||
"var cs=window.getComputedStyle(p);" +
|
||||
"if(cs&&(cs.overflowY==='auto'||cs.overflowY==='scroll'||cs.overflowY==='hidden')){p.style.overflowY='visible';}" +
|
||||
"if(cs&&(cs.overflow==='auto'||cs.overflow==='scroll'||cs.overflow==='hidden')){p.style.overflow='visible';}" +
|
||||
"}" +
|
||||
"}" +
|
||||
"},400);"
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Passo 5 — Sistema de Preview Mobile (para grupo vi_developer)
|
||||
|
||||
Adicione ao `<key>-desktop.xml` um botão de preview que simula o mobile diretamente no desktop. Isso permite testar sem abrir o WebView mobile.
|
||||
|
||||
### 5.1 — Botão no topBar (XML)
|
||||
|
||||
```xml
|
||||
<ButtonWidget id="btnDevPreview" caption="📱"
|
||||
description="Preview Mobile (apenas desenvolvedores)" visible="false">
|
||||
<onClickScript>
|
||||
function run() { var fn = engine.getGlobalVariable('enterMobilePreview'); if (fn) fn(); }
|
||||
</onClickScript>
|
||||
</ButtonWidget>
|
||||
<ButtonWidget id="btnExitPreview" caption="✕ Sair Preview"
|
||||
description="Voltar ao modo desktop" visible="false">
|
||||
<onClickScript>
|
||||
function run() { var fn = engine.getGlobalVariable('exitMobilePreview'); if (fn) fn(); }
|
||||
</onClickScript>
|
||||
</ButtonWidget>
|
||||
```
|
||||
|
||||
### 5.2 — CSS do preview
|
||||
|
||||
Adicione ao CSS principal:
|
||||
|
||||
```javascript
|
||||
".preview-mobile-frame { margin:0 auto !important; border:2px solid #4a9edd !important;" +
|
||||
" border-radius:12px !important; box-shadow:0 0 40px rgba(74,158,221,0.25) !important; } " +
|
||||
".preview-exit-btn { position:fixed !important; top:10px !important; right:12px !important;" +
|
||||
" z-index:99999 !important; background:#e74c3c !important; border:none !important;" +
|
||||
" border-radius:6px !important; padding:7px 16px !important; font-weight:bold !important;" +
|
||||
" box-shadow:0 2px 12px rgba(0,0,0,0.4) !important; cursor:pointer !important; } " +
|
||||
".preview-exit-btn .v-button-caption { color:#fff !important; font-size:12px !important; } "
|
||||
```
|
||||
|
||||
### 5.3 — Funções enterMobilePreview / exitMobilePreview
|
||||
|
||||
**Padrão real (confirmado nos dois painéis de referência):** o preview usa altura **natural**, não
|
||||
fixa — `base.setHeight(null)` faz o `ScriptWidget` crescer ao tamanho do conteúdo, e o `Panel`
|
||||
(`contentPanel`) que o envolve enxerga o overflow real e rola sozinho. Não há valor fixo (nem
|
||||
`700px` nem `3000px`) nem colapso via JS aqui — essa técnica é só para o `_mobileRender` real
|
||||
(placeholder `700px` recalculado por `window.screen.height`, seção 4.1), que existe porque ali sim
|
||||
há uma tela de celular real para medir. No preview, que roda no navegador desktop do dev, altura
|
||||
natural é suficiente e mais simples.
|
||||
|
||||
Adicione no ScriptWidget, antes do `init()`:
|
||||
|
||||
```javascript
|
||||
function enterMobilePreview() {
|
||||
engine.setGlobalVariable('isMobile', true);
|
||||
// base com height null → cresce ao tamanho do conteúdo → Panel enxerga overflow e rola
|
||||
try { base.setHeight(null); } catch(e) {}
|
||||
var _rl = engine.getLayout('rootLayout');
|
||||
if (_rl) {
|
||||
var _rlComp = _rl.getRootComposition();
|
||||
_rlComp.setWidth("375px");
|
||||
_rlComp.addStyleName('preview-mobile-frame');
|
||||
}
|
||||
var _tBar = engine.getLayout('topBar');
|
||||
if (_tBar) _tBar.getRootComposition().addStyleName('mobile-topbar');
|
||||
// Salva e esconde filterBar
|
||||
var _fBar = engine.getLayout('filterBar');
|
||||
if (_fBar) {
|
||||
var _fBarComp = _fBar.getRootComposition();
|
||||
engine.setGlobalVariable('_prevFilterBarVisible', _fBarComp.isVisible());
|
||||
_fBarComp.setVisible(false);
|
||||
}
|
||||
var _btnDev = engine.getWidgetController('btnDevPreview');
|
||||
if (_btnDev) _btnDev.getButton().setVisible(false);
|
||||
var _btnExit = engine.getWidgetController('btnExitPreview');
|
||||
if (_btnExit) {
|
||||
_btnExit.getButton().setVisible(true);
|
||||
_btnExit.getButton().addStyleName('preview-exit-btn');
|
||||
}
|
||||
// Exibe btnThemeMob e oculta btnTheme — igual ao _mobileRender real
|
||||
var _btnThMob = engine.getWidgetController('btnThemeMob');
|
||||
if (_btnThMob) _btnThMob.getButton().setVisible(true);
|
||||
var _btnThTb = engine.getWidgetController('btnTheme');
|
||||
if (_btnThTb) _btnThTb.getButton().setVisible(false);
|
||||
renderView(); // re-renderiza com mobile-view ativo
|
||||
}
|
||||
|
||||
function exitMobilePreview() {
|
||||
engine.unsetGlobalVariable('isMobile');
|
||||
// Restaura base para height 100% (modo desktop normal)
|
||||
try { base.setHeight("100%"); } catch(e) {}
|
||||
var _rl = engine.getLayout('rootLayout');
|
||||
if (_rl) {
|
||||
var _rlComp = _rl.getRootComposition();
|
||||
_rlComp.setWidth("100%");
|
||||
_rlComp.removeStyleName('preview-mobile-frame');
|
||||
}
|
||||
var _tBar = engine.getLayout('topBar');
|
||||
if (_tBar) _tBar.getRootComposition().removeStyleName('mobile-topbar');
|
||||
// Restaura filterBar — usa loose equality (== ) para coerção de Java Boolean
|
||||
var _fBar = engine.getLayout('filterBar');
|
||||
if (_fBar) {
|
||||
var _wasVisible = engine.getGlobalVariable('_prevFilterBarVisible');
|
||||
_fBar.getRootComposition().setVisible(_wasVisible == true);
|
||||
engine.unsetGlobalVariable('_prevFilterBarVisible');
|
||||
}
|
||||
var _btnExit = engine.getWidgetController('btnExitPreview');
|
||||
if (_btnExit) {
|
||||
_btnExit.getButton().removeStyleName('preview-exit-btn');
|
||||
_btnExit.getButton().setVisible(false);
|
||||
}
|
||||
// Restaura btnTheme do topBar e oculta btnThemeMob ao sair do preview
|
||||
var _btnThMobExit = engine.getWidgetController('btnThemeMob');
|
||||
if (_btnThMobExit) _btnThMobExit.getButton().setVisible(false);
|
||||
var _btnThTbExit = engine.getWidgetController('btnTheme');
|
||||
if (_btnThTbExit) _btnThTbExit.getButton().setVisible(true);
|
||||
// Reexibe botão preview se o usuário for developer
|
||||
var _isDev = engine.getGlobalVariable('isDeveloper');
|
||||
var _btnDev = engine.getWidgetController('btnDevPreview');
|
||||
if (_btnDev) _btnDev.getButton().setVisible(!!_isDev);
|
||||
renderView();
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 — Checar grupo vi_developer e exibir botão no init()
|
||||
|
||||
No `init(mapa)` ou no `run()`, após a inicialização principal:
|
||||
|
||||
```javascript
|
||||
// Verifica acesso de desenvolvedor (vi_developer)
|
||||
try {
|
||||
var _dbLib = libService.loadScript('db');
|
||||
var _banco = new _dbLib(_dbLib.VITRUVIO_DATASOURCE);
|
||||
var _devRow = _banco.queryRow(
|
||||
"SELECT COUNT(*) AS CNT FROM nauth.usuario u " +
|
||||
"INNER JOIN nauth.usuario_grupo ug ON u.usuario_id = ug.usuario_fk " +
|
||||
"INNER JOIN nauth.grupo g ON ug.grupo_fk = g.grupo_id " +
|
||||
"WHERE u.login = '" + engine.getLoggedUser().getLogin() + "' " +
|
||||
"AND g.sigla = 'vi_developer'"
|
||||
);
|
||||
if (_devRow && String(_devRow.CNT) !== '0') {
|
||||
engine.setGlobalVariable('isDeveloper', true);
|
||||
var _btnDev = engine.getWidgetController('btnDevPreview');
|
||||
if (_btnDev) _btnDev.getButton().setVisible(true);
|
||||
}
|
||||
} catch(e) {}
|
||||
// Registra funções de preview como variáveis globais (acessíveis pelo XML dos botões)
|
||||
engine.setGlobalVariable('enterMobilePreview', enterMobilePreview);
|
||||
engine.setGlobalVariable('exitMobilePreview', exitMobilePreview);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Passo 6 — Armadilhas conhecidas (não repita estes erros)
|
||||
|
||||
### Java Boolean vs JS boolean
|
||||
`engine.getGlobalVariable()` retorna `Java Boolean`, não JS primitive:
|
||||
```javascript
|
||||
// ERRADO — Java Boolean.FALSE !== JS false (tipos diferentes, strict equality falha)
|
||||
if (_wasVisible !== false) { ... }
|
||||
|
||||
// CERTO — loose equality funciona com Java Boolean
|
||||
if (_wasVisible == true) { ... }
|
||||
// OU
|
||||
if (!!_wasVisible) { ... }
|
||||
```
|
||||
|
||||
### setExpandRatio antes de addComponent
|
||||
```javascript
|
||||
// ERRADO — comp não é filho de base ainda, Vaadin lança IllegalArgumentException
|
||||
comp.setSizeFull();
|
||||
base.setExpandRatio(comp, 1.0); // ← ERRO: comp não está em base
|
||||
base.addComponent(comp);
|
||||
|
||||
// CERTO — setExpandRatio sempre APÓS addComponent
|
||||
comp.setSizeFull();
|
||||
base.removeAllComponents();
|
||||
base.addComponent(comp);
|
||||
base.setExpandRatio(comp, 1.0); // ← OK: comp já é filho
|
||||
```
|
||||
|
||||
### base.setHeight(null) no preview — por que funciona
|
||||
`enterMobilePreview` põe `base` (o container do `ScriptWidget`) em altura natural com `base.setHeight(null)` — sem isso, o container mantém a altura da viewport inteira e o `Panel` (`contentPanel`) não enxerga o conteúdo real para rolar. No `_mobileRender` real esse mesmo `base.setHeight(null)` **não** é usado — lá o `renderHtml` sempre chama `comp.setSizeFull()`, porque quem controla a altura do frame é o cálculo de `window.screen.height` no `rootLayout` (seção 4.1), não o `base`.
|
||||
|
||||
### inline style sobrescreve class CSS
|
||||
Se o HTML gerado tem `style="font-size:12px"` no elemento, CSS de classe não sobrescreve (exceto com `!important`). Mude o valor diretamente na geração do HTML para contexto mobile:
|
||||
```javascript
|
||||
var fontSize = mobileClass ? '14px' : '12px';
|
||||
html += '<div style="font-size:' + fontSize + ';color:#8899aa;">Texto</div>';
|
||||
```
|
||||
|
||||
### Redimensionar o frame mobile a cada render — não faça isso
|
||||
Uma versão antiga recalculava a altura do `.mobile-root-frame` para `el.getBoundingClientRect().bottom` a cada `renderHtml`. Isso fazia o frame crescer para caber TODO o conteúdo (não só o viewport), transformando `topBar` + conteúdo num único bloco comprido que a página externa rolava inteiro — "arrastando" a `topBar` junto. A altura do `.mobile-root-frame` é definida **uma única vez** no bloco `_mobileRender` do `run()` (via `window.screen.height`, seção 4.1) e deve permanecer fixa; `renderHtml` só libera `overflow:visible` nos pais intermediários (seção 4.6), nunca redimensiona o frame em si.
|
||||
|
||||
---
|
||||
|
||||
## Passo 7 — Checklist de entrega
|
||||
|
||||
Antes de declarar pronto, confirme:
|
||||
|
||||
- [ ] Leu o `<key>-desktop.xml` alvo e ao menos um outro painel do repositório antes de escrever qualquer coisa
|
||||
- [ ] Tudo que foi feito tem referência no código existente do repositório — nada inventado
|
||||
- [ ] Não usou nenhuma feature ES6+ (sem `const`, `let`, arrow functions, template literals, destructuring, `class`, `async/await`)
|
||||
- [ ] Botões usam `style` padrão (`DEFAULT`, `GREEN`, `RED`) — sem HTML customizado para botões
|
||||
- [ ] Reutilizou funções de formatação já existentes no painel (ex: `formatBRL`) — sem duplicar
|
||||
- [ ] Reutilizou classes CSS já existentes — sem criar novas desnecessariamente
|
||||
- [ ] `<key>-mobile.xml` criado com `DesktopPanel` apontando para o `panelKey` correto
|
||||
- [ ] `forceLayoutsRender` lista todos os layouts usados pelo desktop
|
||||
- [ ] `forceFieldsRender` lista todos os campos de filtro acessados via `engine.getField()`
|
||||
- [ ] `vitruvio.json`: `"mobile"` adicionado em `"forms"` e `"showInMobileList": true`
|
||||
- [ ] `run()` do desktop detecta `_mobileRender` e adapta rootLayout + filterBar + CSS
|
||||
- [ ] No `_mobileRender` real: `rootLayout` recebe `setHeight("700px")` + `addStyleName('mobile-root-frame')`, seguido de `page.getJavaScript().execute(...)` calculando a altura final a partir de `window.screen.height` (clamp entre 320px e `min(screen.height-220, screen.height*0.80)`) — o `700px` é só o placeholder inicial, não a altura final do mobile real
|
||||
- [ ] HTML gerado usa `mobileClass` e layouts verticais no mobile
|
||||
- [ ] Tabelas/grids do desktop viram cards empilhados no mobile
|
||||
- [ ] Fontes mobile respeitam os tamanhos mínimos (labels ≥ 12px, valores ≥ 16px)
|
||||
- [ ] `renderHtml` distingue preview (`isMobile` sem `_mobileRender`) de mobile real/desktop: só o preview usa `base.setHeight(null)` + `comp.setWidth("100%")`; os outros dois usam `comp.setSizeFull()` + `base.setExpandRatio(comp, 1.0)`
|
||||
- [ ] `enterMobilePreview` usa `base.setHeight(null)` (altura natural) — não copia o `700px` fixo do `_mobileRender` real, que só faz sentido para tela de celular
|
||||
- [ ] `exitMobilePreview` restaura `base.setHeight("100%")`
|
||||
- [ ] JS pós-render (setTimeout 400ms mínimo) só libera `overflow:visible` nos pais até `.mobile-root-frame` — nunca redimensiona o frame em si
|
||||
- [ ] `setExpandRatio` é chamado APÓS `addComponent`
|
||||
- [ ] Funções `enterMobilePreview` / `exitMobilePreview` registradas como global vars
|
||||
- [ ] Botão `btnDevPreview` visível somente para `vi_developer`
|
||||
- [ ] `btnThemeMob` adicionado como **primeiro filho** do `filterBar`, `visible="false"` no XML, visível no bloco `_mobileRender`
|
||||
- [ ] No bloco `_mobileRender` e em `enterMobilePreview`: `btnTheme` (topBar) oculto via `setVisible(false)` — evita dois botões de tema simultâneos
|
||||
- [ ] No `exitMobilePreview`: `btnThemeMob` oculto e `btnTheme` restaurado via `setVisible(true)`
|
||||
- [ ] `doToggleTheme` definido no ScriptWidget, registrado no `init()`, chamado por `btnTheme` e `btnThemeMob`
|
||||
- [ ] `doToggleTheme` atualiza caption de ambos os botões de tema
|
||||
- [ ] Comparação de Java Boolean usa `==` (loose equality), nunca `===`
|
||||
- [ ] CSS de scroll mobile (`.mov-scroll`, `.tec-mob-scroll`, `.hbar-scroll`) está em `addCss()` — **não apenas** no bloco `_mobileRender`
|
||||
- [ ] Lista de cards de movimentação mobile envolvida em `<div class="mov-scroll">`
|
||||
- [ ] Lista de técnicos/ranking mobile envolvida em `<div class="tec-mob-scroll">`
|
||||
- [ ] Gráficos svgHBar no mobile envolvidos em `<div class="hbar-scroll">`
|
||||
|
||||
---
|
||||
|
||||
## Referência rápida de classes CSS mobile
|
||||
|
||||
| Classe | Uso |
|
||||
|---|---|
|
||||
| `.mobile-view` | Aplicada ao `id` raiz do HTML gerado quando em modo mobile |
|
||||
| `.mobile-topbar` | Estilo do topBar no mobile (botões menores, compactos) |
|
||||
| `.preview-mobile-frame` | Borda azul que indica o frame de preview 375px |
|
||||
| `.preview-exit-btn` | Botão "✕ Sair Preview" flutuante fixed no canto superior direito |
|
||||
| `.mov-scroll` | Container da lista de movimentação (cresce com o conteúdo, scroll acima de 520px) |
|
||||
| `.tec-mob-scroll` | Container de lista de técnicos/ranking (cresce com o conteúdo, scroll acima de 420px) |
|
||||
| `.hbar-scroll` | Container de gráfico svgHBar (max 280px + scroll) |
|
||||
| `.mov-cards` | Container de cards empilhados (substitui a table no mobile) |
|
||||
| `.mov-card` | Card individual de cada linha da tabela |
|
||||
| `.mov-card-cliente` | Nome/título principal do card (font-size ≥ 15px mobile) |
|
||||
| `.mov-card-meta` | Informações secundárias (contrato, tipo, período) |
|
||||
| `.mov-card-values` | Flex wrap com os valores numéricos do card |
|
||||
| `.mov-card-val` | Célula individual de valor dentro do card |
|
||||
| `.mov-card-lbl` | Rótulo do valor (font-size ≥ 12px mobile) |
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/mobile/panel"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/mobile/panel
|
||||
https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-mobile-panel-form.xsd">
|
||||
<form formKey="dashboard-contratos-mobile">
|
||||
<name>Dashboard de Contratos</name>
|
||||
<description>Dashboard de Contratos</description>
|
||||
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
// _mobileRender é setado automaticamente pelo DesktopPanel antes
|
||||
// do painel desktop inicializar — o desktop detecta e adapta o layout.
|
||||
}
|
||||
]]>
|
||||
</initScript>
|
||||
|
||||
<components>
|
||||
<VerticalLayout width="100%" height="100%">
|
||||
<DesktopPanel id="dashContratos" panelKey="dashboard-contratos"
|
||||
layoutId="rootLayout" external="true" height="100%"
|
||||
forceLayoutsRender="topBar, filterBar, mainArea, contentPanel, panelRoot"
|
||||
forceFieldsRender="cmbAno, cmbMes, cmbOrdem" />
|
||||
</VerticalLayout>
|
||||
</components>
|
||||
</form>
|
||||
</panel-form>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/mobile/panel"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/mobile/panel
|
||||
https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-mobile-panel-form.xsd">
|
||||
<form formKey="dashboard-tecnicos-mobile">
|
||||
<name>Dashboard Técnicos</name>
|
||||
<description>Dashboard de produtividade dos técnicos com ranking, SLA e detalhamento por OS.</description>
|
||||
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
// _mobileRender é setado automaticamente pelo DesktopPanel antes
|
||||
// do painel desktop inicializar — o desktop detecta e adapta o layout.
|
||||
}
|
||||
]]>
|
||||
</initScript>
|
||||
|
||||
<components>
|
||||
<VerticalLayout width="100%" height="100%">
|
||||
<DesktopPanel id="dashTecnicosMobile" panelKey="dashboard_tecnicos"
|
||||
layoutId="rootLayout" external="true" height="100%"
|
||||
forceLayoutsRender="topBar, mainArea, filterBar, contentPanel, panelRoot"
|
||||
forceFieldsRender="dtIni, dtFim, nmfSla, cmbExecucao" />
|
||||
</VerticalLayout>
|
||||
</components>
|
||||
</form>
|
||||
</panel-form>
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Nome: Parâmetro de indicadores
|
||||
* Sigla: dashboard_ia
|
||||
* Descrição: Biblioteca compartilhada dos dashboards de indicadores da Diretoria: preferências de usuário (tema), CSS do topbar e toolbar, helpers de tema.
|
||||
*/
|
||||
(function() {
|
||||
var PREF_NS = 'preferences';
|
||||
|
||||
function loadPreferences(login) {
|
||||
try {
|
||||
var raw = vConfigService.getUserConfigAsString(String(login), PREF_NS);
|
||||
if (raw != null && String(raw) !== '') {
|
||||
return JSON.parse(String(raw));
|
||||
}
|
||||
} catch (e) {}
|
||||
return { dark_mode: true };
|
||||
}
|
||||
|
||||
function savePreferences(login, prefs) {
|
||||
try {
|
||||
vConfigService.saveUserConfig(String(login), PREF_NS, JSON.stringify(prefs));
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function getTheme(engine) {
|
||||
return String(engine.getGlobalVariable('theme') || 'dark');
|
||||
}
|
||||
|
||||
function isLight(engine) {
|
||||
return getTheme(engine) === 'light';
|
||||
}
|
||||
|
||||
function applyTheme(themeName, page, btnTheme) {
|
||||
page.getJavaScript().execute(
|
||||
themeName === 'light'
|
||||
? "document.body.classList.add('theme-light-app');"
|
||||
: "document.body.classList.remove('theme-light-app');"
|
||||
);
|
||||
if (btnTheme != null) {
|
||||
btnTheme.setCaption(themeName === 'dark' ? '☽' : '☀');
|
||||
}
|
||||
}
|
||||
|
||||
function addSharedCss(page) {
|
||||
var style =
|
||||
".dash-topbar { background:#0f1923 !important; border-bottom:1px solid #243447; } " +
|
||||
".dash-topbar .v-button { background:transparent !important; border:1px solid transparent !important; color:#556677 !important; border-radius:4px !important; transition:none !important; } " +
|
||||
".dash-topbar .v-button .v-button-caption { color:#556677 !important; } " +
|
||||
".dash-topbar .v-button.dash-nav-active { background:#1a2733 !important; border-color:#4a9edd !important; } " +
|
||||
".dash-topbar .v-button.dash-nav-active .v-button-caption { color:#4a9edd !important; font-weight:bold !important; } " +
|
||||
".dash-topbar .v-caption { color:#8899aa !important; font-size:10px !important; } " +
|
||||
".dash-topbar input.v-textfield, .dash-topbar .v-datefield-textfield, .dash-topbar input.v-filterselect-input { background:#1a2733 !important; color:#ccc !important; border-color:#243447 !important; } " +
|
||||
".dash-topbar .v-datefield-button { background:#1a2733 !important; border-color:#243447 !important; } " +
|
||||
".dash-topbar .v-select-select { background:#1a2733 !important; color:#ccc !important; border-color:#243447 !important; } " +
|
||||
"body.theme-light-app .dash-topbar { background:#f8fafc !important; border-bottom:1px solid #e2e8f0; } " +
|
||||
"body.theme-light-app .dash-topbar .v-button { color:#475569 !important; } " +
|
||||
"body.theme-light-app .dash-topbar .v-button .v-button-caption { color:#475569 !important; } " +
|
||||
"body.theme-light-app .dash-topbar .v-button.dash-nav-active { background:#dbeafe !important; border-color:#2563eb !important; color:#2563eb !important; } " +
|
||||
"body.theme-light-app .dash-topbar .v-button.dash-nav-active .v-button-caption { color:#2563eb !important; } " +
|
||||
"body.theme-light-app .dash-topbar input.v-textfield, body.theme-light-app .dash-topbar .v-datefield-textfield, body.theme-light-app .dash-topbar input.v-filterselect-input { background:#ffffff !important; color:#334155 !important; border-color:#e2e8f0 !important; } " +
|
||||
"body.theme-light-app .dash-topbar .v-datefield-button { background:#ffffff !important; border-color:#e2e8f0 !important; } " +
|
||||
"body.theme-light-app .dash-topbar .v-select-select { background:#ffffff !important; color:#334155 !important; border-color:#e2e8f0 !important; } " +
|
||||
".theme-light.dash-wrapper { background:#f0f2f5; } " +
|
||||
".theme-light .dash-kpi { background:#ffffff; box-shadow:0 1px 3px rgba(0,0,0,.08); } " +
|
||||
".theme-light .dash-kpi-label { color:#64748b; } " +
|
||||
".theme-light .dash-kpi-value { color:#1e293b; } " +
|
||||
".theme-light .dash-chart-box { background:#ffffff; box-shadow:0 1px 3px rgba(0,0,0,.08); } " +
|
||||
".theme-light .dash-chart-title { color:#64748b; } " +
|
||||
".mov-toolbar { display:flex; align-items:center; gap:8px; margin-bottom:8px; } " +
|
||||
".mov-filter-input { background:#1a2733; border:1px solid #243447; color:#ccc; padding:5px 10px; border-radius:4px; font-size:11px; flex:1; outline:none; font-family:Arial; } " +
|
||||
".mov-filter-input:focus { border-color:#4a9edd; } " +
|
||||
".mov-export-btn { background:#1a2733; border:1px solid #243447; color:#8899aa; padding:5px 12px; border-radius:4px; font-size:11px; cursor:pointer; white-space:nowrap; font-family:Arial; } " +
|
||||
".mov-export-btn:hover { border-color:#4a9edd; color:#4a9edd; } " +
|
||||
".theme-light .mov-filter-input { background:#ffffff; border-color:#e2e8f0; color:#334155; } " +
|
||||
".theme-light .mov-filter-input:focus { border-color:#2563eb; } " +
|
||||
".theme-light .mov-export-btn { background:#ffffff; border-color:#e2e8f0; color:#64748b; } " +
|
||||
".theme-light .mov-export-btn:hover { border-color:#2563eb; color:#2563eb; } ";
|
||||
|
||||
page.getStyles().add(style);
|
||||
}
|
||||
|
||||
return {
|
||||
loadPreferences: loadPreferences,
|
||||
savePreferences: savePreferences,
|
||||
getTheme: getTheme,
|
||||
isLight: isLight,
|
||||
applyTheme: applyTheme,
|
||||
addSharedCss: addSharedCss
|
||||
};
|
||||
})()
|
||||
@@ -0,0 +1,123 @@
|
||||
---
|
||||
name: vitruvio-criar-endpoint
|
||||
description: >
|
||||
Use when the user wants to create a new REST endpoint in a Vitruvio repository.
|
||||
Triggers: "create endpoint", "new endpoint", "criar endpoint", "novo endpoint", "add endpoint",
|
||||
"REST", "WebService", "integração", or any request to scaffold an endpoints/*.js file.
|
||||
---
|
||||
|
||||
# Create Vitruvio Endpoint
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are creating a new REST endpoint inside a Vitruvio repository. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect endpoint details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing from their original request):
|
||||
|
||||
- **Key** — kebab-case unique identifier. Forms the URL path. Changing it later breaks external integrations.
|
||||
- **Name** — human-readable label shown in the Vitruvio UI.
|
||||
- **Description** — one sentence about what this endpoint does.
|
||||
- **HTTP verbs** — which methods to implement: GET, POST, PUT, PATCH, DELETE. Only scaffold the ones actually needed.
|
||||
- **Auth mode** — one of:
|
||||
- `PUBLIC` — no authentication (default)
|
||||
- `STATIC_TOKEN` — token in query param `_x_token_auth` or header `X-WS-TOKEN-AUTH`
|
||||
- `VITRUVIO_WS_USER_AUTH` — Vitruvio bearer token; `loggedUser` is available in the script
|
||||
- `HTTP_BASIC_AUTH` — HTTP basic auth; `loggedUser` is available; roles validated by Vitruvio admin
|
||||
|
||||
## Step 3 — Scaffold and create file
|
||||
|
||||
```bash
|
||||
vitruvio new endpoint <key> --name "<name>"
|
||||
```
|
||||
|
||||
This creates `endpoints/<key>.js` and registers it in `vitruvio.json` with `authMode: "PUBLIC"` and `active: true`. Replace the generated file with the template below. If authMode is not PUBLIC, update that field in `vitruvio.json`.
|
||||
|
||||
Target path: `endpoints/<key>.js`
|
||||
|
||||
URL after deploy:
|
||||
|
||||
| authMode | URL |
|
||||
|---|---|
|
||||
| `PUBLIC` | `/api/integration/public/<key>` |
|
||||
| `STATIC_TOKEN` | `/api/integration/tokenauth/<key>` |
|
||||
| `VITRUVIO_WS_USER_AUTH` | `/api/integration/bearerauth/<key>` |
|
||||
| `HTTP_BASIC_AUTH` | `/api/integration/bauth/<key>` |
|
||||
|
||||
Template (include only the requested verbs):
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Nome: <name>
|
||||
* Sigla: <key>
|
||||
* Descrição: <description>
|
||||
* Auth: <authMode>
|
||||
*/
|
||||
function WebService() {
|
||||
|
||||
// this.onGet = function(params) { ... } ← GET / DELETE: params has .headers and .query
|
||||
// this.onPost = function(params) { ... } ← POST / PUT / PATCH: params also has .requestBody (string, always JSON.parse before use)
|
||||
|
||||
this.onPost = function(params) {
|
||||
try {
|
||||
var body = JSON.parse(params.requestBody);
|
||||
if (!body.id) throw 'Missing required field: id';
|
||||
|
||||
// implementation here
|
||||
|
||||
return JSON.stringify({ success: true });
|
||||
} catch (e) {
|
||||
return JSON.stringify({ error: e.toString() });
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
module.exports = new WebService();
|
||||
```
|
||||
|
||||
Rules (Rhino ES5 — no exceptions):
|
||||
- No `let`, `const`, arrow functions, template literals, destructuring, spread, `class`, or `import/export`
|
||||
- Use `var` everywhere
|
||||
- Always `JSON.parse(params.requestBody)` before accessing the body — never trust it raw
|
||||
- Always return strings — `JSON.stringify(obj)`, not raw objects
|
||||
- Return `null` or nothing for `204 No Content`; return a string for `200 OK`
|
||||
- Remove unused verb stubs entirely — don't leave placeholder bodies
|
||||
- Never concatenate user input into SQL strings; use named bind params (`:paramName`)
|
||||
- Don't hardcode datasource names or tokens — read from `vConfigService` or a DB config table
|
||||
- Put heavy logic in a separate script loaded via `libService.loadScript`, not inline in the endpoint
|
||||
|
||||
## Step 4 — Update vitruvio.json entry
|
||||
|
||||
`vitruvio new` already added the endpoint entry. The full entry shape is:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description>",
|
||||
"language": "javascript",
|
||||
"authMode": "PUBLIC",
|
||||
"active": true,
|
||||
"source": "endpoints/<key>.js"
|
||||
}
|
||||
```
|
||||
|
||||
Update only what differs: set `"authMode"` to the correct value if not `"PUBLIC"`; add `"description"` if provided.
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- File created: `endpoints/<key>.js`
|
||||
- Registered in `vitruvio.json` with key `<key>`
|
||||
- URL once deployed (based on authMode)
|
||||
- Which verbs were scaffolded
|
||||
@@ -0,0 +1,290 @@
|
||||
---
|
||||
name: vitruvio-criar-form-desktop
|
||||
description: >
|
||||
Use when the user wants to create or edit the DESKTOP (web) XML form of a Vitruvio panel
|
||||
or process — the Vaadin form rendered on desktop. Triggers: "create desktop form",
|
||||
"criar formulário desktop", "form xml", "desktop form xml", "tela desktop",
|
||||
"add a field to the desktop form", "process desktop form". This is the single home for
|
||||
desktop form knowledge; vitruvio-criar-painel and vitruvio-criar-processo call it for
|
||||
their form part. For the mobile form use vitruvio-criar-form-mobile.
|
||||
---
|
||||
|
||||
# Create Vitruvio Desktop Form
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
Desktop forms are Vaadin 8 forms defined in XML and rendered by the Vitruvio engine. This
|
||||
skill creates the **desktop** form. There are two variants that share almost all of their
|
||||
component vocabulary but differ in their root element and how variables flow — see
|
||||
**Differences: panel vs process** below.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Determine variant and target
|
||||
|
||||
Ask (only what is missing):
|
||||
|
||||
- **Panel or process form?** (decides root element / file location — see below)
|
||||
- **Key** — the panel or process key (the folder name).
|
||||
- For a **process**, which `formKey`(s) are needed — they must match the `activiti:formKey`
|
||||
values in `processes/<key>/<key>.bpmn`.
|
||||
- **What should the form show/do?** — fields and behaviour, so you can scaffold something useful.
|
||||
|
||||
## Differences: panel vs process
|
||||
|
||||
| | Panel | Process |
|
||||
|----------------|-------|---------|
|
||||
| File | `panels/<key>/<key>-desktop.xml` | `processes/<key>/<key>-desktop.xml` |
|
||||
| Root element | `<panel-form>` | `<forms>` (with `processKey` attribute optional) |
|
||||
| Namespace | `http://www.davinti.com.br/vitruvio/form/panel` | `http://www.davinti.com.br/vitruvio/form` |
|
||||
| XSD | `vitruvio-panel-form.xsd` | `vitruvio-form.xsd` |
|
||||
| Forms per file | exactly one `<form>` | **one `<form formKey>` per BPMN `activiti:formKey`** |
|
||||
| Variables | none built-in; use `engine.getGlobalVariable` | process variables: `engine.getVariable`/`setVariable`; submitted field `id="X"` in `formKey="A"` → variable `A_X` |
|
||||
| `<library>` | not used | optional: shared `<complex-component id>` reused via `<component-ref refId>` |
|
||||
|
||||
Everything below (components, DBTable, engine API, ES5 rules) is **identical** for both.
|
||||
|
||||
## Step 3 — Scaffold the file
|
||||
|
||||
### Panel variant — `panels/<key>/<key>-desktop.xml`
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/panel"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/panel https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-panel-form.xsd">
|
||||
|
||||
<form formKey="<key>" width="100%" height="100%">
|
||||
<name><name></name>
|
||||
<description><description></description>
|
||||
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
// called once when the panel opens
|
||||
}
|
||||
]]>
|
||||
</initScript>
|
||||
|
||||
<components>
|
||||
<VerticalLayout spacing="true" margin="true" width="100%" height="100%">
|
||||
<!-- add widgets here -->
|
||||
</VerticalLayout>
|
||||
</components>
|
||||
</form>
|
||||
</panel-form>
|
||||
```
|
||||
|
||||
### Process variant — `processes/<key>/<key>-desktop.xml`
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<forms xmlns="http://www.davinti.com.br/vitruvio/form"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-form.xsd">
|
||||
|
||||
<!-- Optional: shared components used across multiple forms -->
|
||||
<library>
|
||||
<!-- <complex-component id="libShared">...</complex-component> -->
|
||||
</library>
|
||||
|
||||
<!-- One <form> per activiti:formKey in the BPMN -->
|
||||
<form formKey="formAbertura" width="100%">
|
||||
<name>Abertura</name>
|
||||
<description>Abertura do processo</description>
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
// called when the form opens
|
||||
}
|
||||
]]>
|
||||
</initScript>
|
||||
<components>
|
||||
<VerticalLayout spacing="true" margin="true" width="100%">
|
||||
<TextField id="descricao" type="string" caption="Descrição" width="100%" required="true" />
|
||||
</VerticalLayout>
|
||||
</components>
|
||||
</form>
|
||||
|
||||
<form formKey="formExecutar" width="100%">
|
||||
<name>Executar</name>
|
||||
<description>Etapa de execução</description>
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
// read a process variable set during a previous step
|
||||
// var valor = engine.getVariable('formAbertura_descricao');
|
||||
}
|
||||
]]>
|
||||
</initScript>
|
||||
<components>
|
||||
<VerticalLayout spacing="true" margin="true" width="100%">
|
||||
<TextField id="resultado" type="string" caption="Resultado" width="100%" required="true" />
|
||||
<ComboBox id="aprovado" type="string" caption="Aprovado?" required="true" allowNullSelection="false">
|
||||
<entry key="1" value="Sim"/>
|
||||
<entry key="0" value="Não"/>
|
||||
</ComboBox>
|
||||
</VerticalLayout>
|
||||
</components>
|
||||
</form>
|
||||
|
||||
</forms>
|
||||
```
|
||||
|
||||
## Components (78 desktop components)
|
||||
|
||||
Before using a component you are unsure about, read its reference:
|
||||
`~/.local/share/vitruvio-platform/docs/components/desktop/<ComponentName>.md`
|
||||
(full list in `docs/components/INDEX.md`).
|
||||
|
||||
### Layout
|
||||
|
||||
| Component | Common attributes |
|
||||
|---|---|
|
||||
| `VerticalLayout` | `spacing`, `margin`, `width`, `height`, `expandRatio` |
|
||||
| `HorizontalLayout` | same as above |
|
||||
| `Panel` | `id`, `caption`, `width`, `height`, `margin` |
|
||||
| `TabLayout` | `id`, `width`, `framed` — contains `<Tab caption="...">` children |
|
||||
|
||||
### Widgets
|
||||
|
||||
| Component | Key attributes |
|
||||
|---|---|
|
||||
| `TextField` | `id`, `type` (`string`/`number`), `caption`, `width`, `required` |
|
||||
| `NumericField` | `id`, `type`, `caption`, `width`, `visible` |
|
||||
| `DateField` | `id`, `type` (`date`/`datetime`), `caption`, `resolution` (`DAY`/`MINUTE`) |
|
||||
| `ComboBox` | `id`, `type`, `caption`, `allowNullSelection` — children: `<entry key="..." value="..."/>` |
|
||||
| `Label` | `id`, `width`, `contentMode` (`HTML`/`TEXT`) — child: `<value>...</value>` |
|
||||
| `ButtonWidget` | `id`, `caption`, `style` (`GREEN`/`RED`/`DEFAULT`), `defaultIcon` — child: `<onClickScript>` |
|
||||
| `RichTextArea` | `id`, `type`, `caption`, `width`, `height` |
|
||||
| `ImageWidget` | `id`, `width`, `height` — child: `<image><base64 extension="png">...</base64></image>` |
|
||||
|
||||
### DBTable (data grid)
|
||||
|
||||
```xml
|
||||
<DBTable id="tbDados" type="string" width="100%" rows="8"
|
||||
exportXLS="true" showRowCount="true" selectable="true">
|
||||
<datasource>
|
||||
<!-- Option A: static query -->
|
||||
<freeQuery connection-key="vitruvio_producao">
|
||||
<![CDATA[
|
||||
SELECT col1, col2
|
||||
FROM my_table
|
||||
WHERE param = ${myParam}
|
||||
]]>
|
||||
</freeQuery>
|
||||
|
||||
<!-- Option B: dynamic query built in JS -->
|
||||
<sqlBuilderDataSource connection-key="vitruvio_producao" language="JavaScript">
|
||||
<![CDATA[
|
||||
function buildSQL(params) {
|
||||
var sql = 'SELECT col1, col2 FROM my_table WHERE 1=1';
|
||||
var val = engine.getField('myFilter').getValue();
|
||||
if (val) {
|
||||
sql += ' AND col1 = ${val}';
|
||||
params.put('val', val);
|
||||
}
|
||||
return sql;
|
||||
}
|
||||
]]>
|
||||
</sqlBuilderDataSource>
|
||||
</datasource>
|
||||
<key-field>CHAVE</key-field>
|
||||
<columns>
|
||||
<column name="COL1" caption="Column 1" expand-ratio="1"/>
|
||||
<column name="COL2" caption="Column 2" expand-ratio="2"/>
|
||||
<generated name="Action" expand-ratio="0.5">
|
||||
<scriptColumnGenerator language="JavaScript">
|
||||
<![CDATA[
|
||||
function Generator() {
|
||||
var com = libService.loadScript('vaadinComponents');
|
||||
this.generate = function(itemId, columnId, item, container) {
|
||||
var btn = com.buttonIcon('action', function() {
|
||||
var id = item.getItemProperty('CHAVE').getValue();
|
||||
// do something
|
||||
}, 'edit');
|
||||
return com.horizontalLayout([btn]);
|
||||
}
|
||||
}
|
||||
var script = new Generator();
|
||||
]]>
|
||||
</scriptColumnGenerator>
|
||||
</generated>
|
||||
</columns>
|
||||
<bind>
|
||||
<parameter value-type="number" defaultValue="0" parameterName="myParam" field-ref="otherTable"/>
|
||||
</bind>
|
||||
</DBTable>
|
||||
```
|
||||
|
||||
**SQL in datasources:** use `${paramName}` for substitution — NOT `:paramName`. Named
|
||||
params (`:paramName`) are only for `queries/*.sql` files.
|
||||
|
||||
## engine API
|
||||
|
||||
```javascript
|
||||
// Fields
|
||||
engine.getField('id').getValue()
|
||||
engine.getField('id').getConvertedValue() // typed value (number, date, etc.)
|
||||
engine.getField('id').setValue(value)
|
||||
engine.getField('id').setEnabled(bool)
|
||||
engine.getField('id').setVisible(bool)
|
||||
engine.getField('id').setRequired(bool)
|
||||
engine.getField('id').setCaption('new caption')
|
||||
engine.getField('id').refresh() // DBTable — re-run its query
|
||||
|
||||
// Widgets / layouts
|
||||
engine.getWidgetController('id').getButton()
|
||||
engine.getLayout('id').getSelectedTab()
|
||||
|
||||
// User
|
||||
engine.getLoggedUser().getLogin()
|
||||
engine.getLoggedUser().getNome()
|
||||
|
||||
// Global variables (survive tab changes within a session)
|
||||
engine.setGlobalVariable('key', value)
|
||||
engine.getGlobalVariable('key')
|
||||
|
||||
// Process forms only — process variables:
|
||||
engine.getVariable('varName')
|
||||
engine.setVariable('varName', value)
|
||||
engine.getProcessDefinitionId()
|
||||
engine.formKey() // current form's formKey
|
||||
engine.getFormName()
|
||||
|
||||
// Open another panel / load a library
|
||||
var vUI = libService.loadScript('vUI');
|
||||
vUI.showPanel('panelKey', { param1: value1 });
|
||||
var lib = libService.loadScript('scriptKey');
|
||||
```
|
||||
|
||||
## Script rules (Rhino ES5)
|
||||
|
||||
Desktop form scripts run on **Rhino ES5** — no `let`/`const`, arrow functions, template
|
||||
literals, destructuring, `class`, `import/export`. Use `var`, string `+` concat,
|
||||
`JSON.parse`/`JSON.stringify`, `importClass(Packages.some.java.Class)` for Java interop.
|
||||
(See the repo `CLAUDE.md` "JavaScript — ES5 / Rhino Engine" section.)
|
||||
|
||||
> Note: this ES5 rule is for **desktop**. Mobile forms use modern JS on the client — see
|
||||
> **vitruvio-criar-form-mobile**.
|
||||
|
||||
## Step 4 — Manifest
|
||||
|
||||
If run **standalone**, set `"forms"."desktop"` to the file path on the existing panel or
|
||||
process entry in `vitruvio.json`. Full entry creation is handled by **vitruvio-criar-painel**
|
||||
/ **vitruvio-criar-processo**.
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- File created/updated and which variant (panel/process).
|
||||
- For processes: each `<form formKey>` must match an `activiti:formKey` in the BPMN, and
|
||||
submitted field `id="X"` in `formKey="A"` becomes process variable `A_X`.
|
||||
- `run()` in `<initScript>` is called every time the form opens.
|
||||
- `${paramName}` for SQL substitution in datasources; `:paramName` only in named query files.
|
||||
@@ -0,0 +1,267 @@
|
||||
---
|
||||
name: vitruvio-criar-form-mobile
|
||||
description: >
|
||||
Use when the user wants to create or edit the MOBILE form of a Vitruvio panel or process
|
||||
(the React-Native form rendered in the mobile app). Triggers: "create mobile form",
|
||||
"criar formulário mobile", "mobile panel", "painel mobile", "criar painel mobile",
|
||||
"process mobile form", "formulário mobile do processo", "add mobile form", "mobile form xml",
|
||||
"bridge mobile". This is the single home for mobile form knowledge; vitruvio-criar-painel
|
||||
and vitruvio-criar-processo call it for their mobile part. For the desktop form use
|
||||
vitruvio-criar-form-desktop.
|
||||
---
|
||||
|
||||
# Create Vitruvio Mobile Form
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
Mobile forms are **not** desktop forms with a different skin. They use a different schema,
|
||||
a smaller component set, a different JavaScript runtime, and an explicit server↔client data
|
||||
contract. Read this whole skill before scaffolding — the mistakes here are not the desktop
|
||||
mistakes.
|
||||
|
||||
## The four things that make mobile different
|
||||
|
||||
1. **Different schema.** Root is `<mobile-forms>`, namespace
|
||||
`http://www.davinti.com.br/vitruvio/mobile-form`, XSD `vitruvio-mobile-form.xsd`.
|
||||
2. **Only 18 components** (vs 78 desktop). Do **not** assume a desktop widget exists on
|
||||
mobile. The full list:
|
||||
`CheckBox, ComboBox, GoogleMapsField, HorizontalLayout, ImageLibraryByFieldValue,
|
||||
ImageWidget, Label, MaskedField, MoneyField, NumericField, OptionGroup,
|
||||
ProgressBarWidget, RatingStars, SignaturePadField, TabLayout, TextField, Toggle,
|
||||
VerticalLayout` (plus structural `SubForm`/`ItemList`). Read
|
||||
`~/.local/share/vitruvio-platform/docs/components/mobile/<Component>.md` before using one.
|
||||
3. **Two JavaScript runtimes.**
|
||||
- **Client-side** (`initScript`, `discoveryScript`, validators, component event scripts):
|
||||
**modern React-Native JS** — arrow functions, Promises, `.then()/.catch()` are fine and
|
||||
expected. Most APIs are **async** and return Promises.
|
||||
- **Server-side** (`<ServerSide><Bridge>` `execute(...)` bodies): **Rhino ES5**, same rules
|
||||
as scripts/desktop. This is the only place with access to platform libs and services
|
||||
(`libService`, `runtimeService`, db, etc.).
|
||||
4. **Data is explicit.** Nothing is auto-injected the way desktop process variables are. Every
|
||||
piece of server data the form needs must be declared — via an `<Autoload>` variable, a
|
||||
`<QueryDataSource>` (synced to the device, works offline), or fetched on demand from a
|
||||
named `<Bridge>` with `vCommunicationService.executeOnServer(...)`.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Determine variant and gather the data contract
|
||||
|
||||
Ask (only what is missing):
|
||||
|
||||
- **Panel or process mobile form?** (see **Differences: panel vs process** below)
|
||||
- **Key** — the panel or process key (folder name).
|
||||
- For a **process**: which `formKey`(s) — they must match the `activiti:formKey` in
|
||||
`processes/<key>/<key>.bpmn`.
|
||||
- **Which variables does the form need?** Because nothing is auto-injected, ask explicitly:
|
||||
- process variables to read (each becomes an `<Autoload>` `<variable>` and/or a Bridge call)
|
||||
- reference/lookup data (each becomes a `<QueryDataSource>` backed by a `queries/*.sql`)
|
||||
- any server-only logic / libs needed (each becomes a `<Bridge>`)
|
||||
- **Offline?** Whether the data sources must be available without connectivity (affects
|
||||
`autoSyncOnInit` / `autoSyncOnDiscovery`).
|
||||
|
||||
## Differences: panel vs process
|
||||
|
||||
The schema, component set, ServerSide/Bridge mechanics and JS runtimes below are **identical**
|
||||
for both. Only these differ:
|
||||
|
||||
| | Panel mobile form | Process mobile form |
|
||||
|----------------|-------------------|---------------------|
|
||||
| File | `panels/<key>/<key>-mobile.xml` | `processes/<key>/<key>-mobile.xml` |
|
||||
| Root attribute | `<mobile-forms>` (no `processKey`) | `<mobile-forms processKey="<key>">` |
|
||||
| Forms per file | one `<form>` | **one `<form formKey>` per BPMN `activiti:formKey`** (must match) |
|
||||
| Manifest | set `forms.mobile` **and** `showInMobileList: true` on the panel entry | set `forms.mobile` (and optionally `forms.mobileAlternative`) on the process entry |
|
||||
| Variables | use `engine.getGlobalVariable` / Autoload | process variables are fetched server-side via a Bridge (`runtimeService.getVariable`) and/or declared in `<Autoload>` |
|
||||
|
||||
> Filename: name files by the **artifact key + suffix** — `<key>-mobile.xml` (and
|
||||
> `<key>-desktop.xml`, `<key>.bpmn`). This keeps every form searchable by its key instead of
|
||||
> dozens of identical `form-mobile.xml` tabs. Older content used `form-mobile.xml` /
|
||||
> `form_web_mobile.xml`; the real path is whatever `forms.mobile` points to, so legacy files
|
||||
> still work — but new scaffolds use `<key>-mobile.xml`.
|
||||
|
||||
## Step 3 — Scaffold the file
|
||||
|
||||
### Skeleton (process variant shown; for a panel drop `processKey` and use a single `<form>`)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mobile-forms processKey="<key>"
|
||||
xmlns="http://www.davinti.com.br/vitruvio/mobile-form"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/mobile-form https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-mobile-form.xsd">
|
||||
|
||||
<form formKey="formColeta">
|
||||
<name>Coleta</name>
|
||||
<description>Etapa de coleta no app</description>
|
||||
|
||||
<!-- Runs when the form opens. CLIENT-SIDE: modern JS, async APIs return Promises. -->
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
// Autoloaded variables land in the global scope:
|
||||
engine.getField('nomeLoja').setValue(engine.getGlobalVariable('nomeLoja'));
|
||||
}
|
||||
]]>
|
||||
</initScript>
|
||||
|
||||
<!-- Optional: pull server data when the task is discovered (before the user opens it). -->
|
||||
<discoveryScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
var params = { id: execution.getProcessInstanceId() };
|
||||
vCommunicationService.executeOnServer('bridgeNumeroCarga', params).then(result => {
|
||||
var n = parseInt(result, 10);
|
||||
execution.setVariable('numeroCarga', n).then(ok => {}).catch(err => {
|
||||
console.log('Erro ao setar numeroCarga localmente', err);
|
||||
});
|
||||
}).catch(err => {
|
||||
console.log('Erro ao coletar numeroCarga no server', err);
|
||||
});
|
||||
}
|
||||
]]>
|
||||
</discoveryScript>
|
||||
|
||||
<!-- Block completing the task when a rule is not met. -->
|
||||
<validators>
|
||||
<ScriptValidator execution="COMPLETE" language="JavaScript" id="validadorComplete">
|
||||
<![CDATA[
|
||||
function Validator() {
|
||||
var msg;
|
||||
this.getMessage = function() { return msg; };
|
||||
this.isValid = function() {
|
||||
if (engine.getField('confirma').getValue() == 'Sim') { return true; }
|
||||
msg = 'Confirme a execução antes de finalizar.';
|
||||
return false;
|
||||
};
|
||||
}
|
||||
var validator = new Validator();
|
||||
]]>
|
||||
</ScriptValidator>
|
||||
</validators>
|
||||
|
||||
<components>
|
||||
<VerticalLayout spacing="true" margin="true" width="100%">
|
||||
<TextField id="nomeLoja" type="string" caption="Loja" readOnly="true" />
|
||||
<OptionGroup id="confirma" type="string" caption="Executou?" required="true">
|
||||
<entry key="Sim" value="Sim" />
|
||||
<entry key="Nao" value="Não" />
|
||||
</OptionGroup>
|
||||
<SignaturePadField id="assinatura" caption="Assinatura" />
|
||||
</VerticalLayout>
|
||||
</components>
|
||||
|
||||
<!-- Explicitly declared variables auto-injected into the form's scope on load. -->
|
||||
<Autoload>
|
||||
<variables autoInjectionScope="ENGINE_GLOBAL_SCOPE" autoPersist="true">
|
||||
<variable>nomeLoja</variable>
|
||||
<variable>numeroCarga</variable>
|
||||
</variables>
|
||||
</Autoload>
|
||||
|
||||
<!-- Everything the server provides to this form. -->
|
||||
<ServerSide>
|
||||
<!-- Named queries (queries/*.sql) synced to the device for offline lookups. -->
|
||||
<DataSources>
|
||||
<QueryDataSource key="qry_produtos_carga" autoSyncOnInit="true" autoSyncOnDiscovery="true" refreshInSeconds="60" />
|
||||
</DataSources>
|
||||
|
||||
<!-- Server-side functions. Rhino ES5. Full access to libService / runtimeService / db.
|
||||
Called from the client via vCommunicationService.executeOnServer('id', params). -->
|
||||
<Bridges>
|
||||
<Bridge language="JavaScript" id="bridgeNumeroCarga">
|
||||
<![CDATA[
|
||||
function execute(params) {
|
||||
var numeroCarga = runtimeService.getVariable(params.id, 'numeroCarga');
|
||||
return numeroCarga ? numeroCarga : -1;
|
||||
}
|
||||
]]>
|
||||
</Bridge>
|
||||
</Bridges>
|
||||
</ServerSide>
|
||||
</form>
|
||||
|
||||
</mobile-forms>
|
||||
```
|
||||
|
||||
## How libs and process variables reach the mobile form
|
||||
|
||||
The mobile app cannot call `libService.loadScript(...)` or read process variables directly —
|
||||
those live on the server. The pattern is always **declare a Bridge, call it from the client**:
|
||||
|
||||
```xml
|
||||
<!-- SERVER-SIDE (Rhino ES5): a lib used to build a barcode image -->
|
||||
<Bridge language="JavaScript" id="imagemCodBarras">
|
||||
<![CDATA[
|
||||
function execute(codbarras) {
|
||||
var generator = libService.loadScript('barcode-gen');
|
||||
return ',' + generator.generateEAN13BarcodeImageAsBase64({ value: codbarras });
|
||||
}
|
||||
]]>
|
||||
</Bridge>
|
||||
```
|
||||
|
||||
```javascript
|
||||
// CLIENT-SIDE (modern JS): call the bridge, use the Promise result
|
||||
vCommunicationService.executeOnServer('imagemCodBarras', codigoBarras).then(base64 => {
|
||||
engine.getField('codigoImagem').setValue(base64);
|
||||
}).catch(err => console.log('Erro no bridge imagemCodBarras', err));
|
||||
```
|
||||
|
||||
Rules of thumb:
|
||||
- Anything needing a **platform lib, the database, or a platform service** → put it in a
|
||||
**Bridge** (server, ES5) and call it with `vCommunicationService.executeOnServer`.
|
||||
- **Reference/lookup tables** the form reads repeatedly → a **`QueryDataSource`** backed by a
|
||||
`queries/*.sql` (works offline once synced).
|
||||
- **Process variables** the form needs → either declare them in `<Autoload>` or fetch them
|
||||
in a Bridge via `runtimeService.getVariable(processInstanceId, 'varName')` and store locally
|
||||
with `execution.setVariable(...)`.
|
||||
- Client-side `execution.getVariable(...)` / `execution.setVariable(...)` are **async** and
|
||||
return Promises — use `.then()`.
|
||||
|
||||
## List-based entry: SubForm + ItemList
|
||||
|
||||
For "add many items" screens (collect a list of rows), use a `SubForm` with an `<ItemList>`:
|
||||
|
||||
```xml
|
||||
<SubForm formKey="executarAcao">
|
||||
<name>Executar AÇÃO</name>
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[ function run(apply) { if (apply) { apply(); } } ]]>
|
||||
</initScript>
|
||||
<ItemList addItemButtonCaption="Gravar na Lista" caption="Itens">
|
||||
<property id="OBSERVACAO" caption="Observação" />
|
||||
</ItemList>
|
||||
<components>
|
||||
<VerticalLayout width="100%" spacing="true" margin="true">
|
||||
<!-- fields captured per item -->
|
||||
</VerticalLayout>
|
||||
</components>
|
||||
</SubForm>
|
||||
```
|
||||
|
||||
Validate list completeness with `engine.getSubFormListSizeByStatus(function(size){ ... }, "all")`
|
||||
inside a `COMPLETE` validator.
|
||||
|
||||
## Step 4 — Manifest
|
||||
|
||||
If run **standalone**, update the matching entry in `vitruvio.json`:
|
||||
- **Panel**: set `forms.mobile = "panels/<key>/<key>-mobile.xml"` and `showInMobileList: true`.
|
||||
- **Process**: set `forms.mobile = "processes/<key>/<key>-mobile.xml"`.
|
||||
|
||||
Full entry creation is handled by **vitruvio-criar-painel** / **vitruvio-criar-processo**.
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- File created/updated and variant (panel/process).
|
||||
- Which variables/queries/bridges were declared, and that **only declared data is available**
|
||||
on the device — anything else must be added as an Autoload variable, QueryDataSource, or Bridge.
|
||||
- Reminder: client scripts are modern JS (Promises); Bridge bodies are server-side Rhino ES5.
|
||||
- For processes: each `<form formKey>` must match an `activiti:formKey` in the BPMN.
|
||||
- Suggest reading `docs/components/mobile/` and an example
|
||||
(`~/.local/share/vitruvio-platform/examples/processes/*/form_web_mobile.xml`) for richer screens.
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
name: vitruvio-criar-painel
|
||||
description: >
|
||||
Use when the user wants to create a new panel (tela / form) in a Vitruvio repository.
|
||||
Triggers: "create panel", "new panel", "criar painel", "novo painel", "add panel",
|
||||
or any request to scaffold a <key>-desktop.xml inside a panels/ folder.
|
||||
---
|
||||
|
||||
# Create Vitruvio Panel (orchestrator)
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
A panel is one or two form files plus a manifest entry:
|
||||
|
||||
| Part | Skill that owns it |
|
||||
|------|--------------------|
|
||||
| `panels/<key>/<key>-desktop.xml` (web form) | **vitruvio-criar-form-desktop** (panel variant) |
|
||||
| `panels/<key>/<key>-mobile.xml` (app form, optional) | **vitruvio-criar-form-mobile** (panel variant) |
|
||||
|
||||
This skill collects the intent once, drives those skills, and registers the panel in
|
||||
`vitruvio.json`. Do the file work by following the referenced skills — do not re-derive
|
||||
their templates here.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect panel details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing):
|
||||
|
||||
- **Key** — PascalCase or snake_case unique identifier. Used in `engine.showPanel(key)` and
|
||||
as the folder name.
|
||||
- **Name** — human-readable label shown in the Vitruvio menu.
|
||||
- **Category** — display category, hierarchical with `/` (e.g. `"Comercial"`,
|
||||
`"Auditoria/Gondola"`).
|
||||
- **Description** — one sentence (optional).
|
||||
- **Open in new window?** — `true`/`false`. Default `true`.
|
||||
- **Show in mobile list?** — `true`/`false`. Default `false`.
|
||||
- **Needs a mobile form?** — if yes, a `<key>-mobile.xml` is created too. Mobile is a different
|
||||
schema with explicit data wiring (the mobile skill will ask for variables/lookups/libs).
|
||||
- **What should the panel do?** — fields/behaviour, so the form scaffold is useful.
|
||||
|
||||
## Step 3 — Scaffold
|
||||
|
||||
```bash
|
||||
vitruvio new panel <key> --name "<name>"
|
||||
```
|
||||
|
||||
This creates `panels/<key>/<key>-desktop.xml` and the `vitruvio.json` entry. Then replace the
|
||||
generated form using the focused skills:
|
||||
|
||||
1. **Desktop form** — follow **vitruvio-criar-form-desktop** (panel variant) to write
|
||||
`panels/<key>/<key>-desktop.xml` from the user's description.
|
||||
2. **Mobile form (if requested)** — follow **vitruvio-criar-form-mobile** (panel variant) to
|
||||
write `panels/<key>/<key>-mobile.xml`. `vitruvio new` does not create it.
|
||||
|
||||
## Step 4 — Update vitruvio.json entry
|
||||
|
||||
`vitruvio new` already added the entry. Full shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description>",
|
||||
"category": "<category>",
|
||||
"displayOrder": 0,
|
||||
"showInPresentation": false,
|
||||
"openInNewWindow": true,
|
||||
"showInMobileList": false,
|
||||
"displayTimeInSeconds": 0,
|
||||
"allowedGroups": [],
|
||||
"allowedUsers": [],
|
||||
"forms": {
|
||||
"desktop": "panels/<key>/<key>-desktop.xml"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Fields `vitruvio new` typically leaves at defaults that need updating:
|
||||
- `"category"` — set to the user's category (default `""`).
|
||||
- `"description"` — add if provided.
|
||||
- `"openInNewWindow"` — set to `false` for same-window.
|
||||
- `"showInMobileList"` — set to `true` for mobile visibility.
|
||||
- If a mobile form was created, add `"mobile": "panels/<key>/<key>-mobile.xml"` inside `"forms"`
|
||||
(and set `showInMobileList: true`).
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- Files created: `<key>-desktop.xml` (and `<key>-mobile.xml` if applicable).
|
||||
- Registered in `vitruvio.json` with key `<key>`.
|
||||
- `run()` in `<initScript>` is called every time the panel opens.
|
||||
- `${paramName}` for SQL substitution in datasource blocks; `:paramName` only in named query files.
|
||||
- For mobile: only explicitly declared data is available on the device — see vitruvio-criar-form-mobile.
|
||||
@@ -0,0 +1,132 @@
|
||||
---
|
||||
name: vitruvio-criar-patch
|
||||
description: >
|
||||
Use when the user wants to create a new Liquibase database migration patch in a Vitruvio repository.
|
||||
Triggers: "create patch", "new patch", "criar patch", "novo patch", "database migration",
|
||||
"migração de banco", "changeset", "liquibase", or any request to scaffold oracle/postgresql patch XML files.
|
||||
---
|
||||
|
||||
# Create Vitruvio Patch
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are creating a new Liquibase database migration patch inside a Vitruvio repository. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Inspect existing patches
|
||||
|
||||
`vitruvio new patch` auto-detects the next changeset ID by scanning all existing XML files — you don't need to find it manually. For situational awareness you can check:
|
||||
|
||||
```bash
|
||||
find patches/ -name "*.xml" | xargs grep -h 'id="[0-9]' 2>/dev/null | grep -oP 'id="\K[0-9]+' | sort -n | tail -1
|
||||
```
|
||||
|
||||
Next ID = last found + 1. If no patches exist yet, starts at 1. The CLI also creates `patches/oracle/` and `patches/postgresql/` subdirectories if they don't exist yet.
|
||||
|
||||
## Step 3 — Collect migration details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing):
|
||||
|
||||
- **What this migration does** — describe the change (e.g. "add column STATUS to table PEDIDO", "create table AUDIT_LOG", "insert config rows").
|
||||
- **Author** — Vitruvio username (e.g. `joao.felix`). Use `git config user.name` if unsure.
|
||||
- **Module key** — used in the filename (e.g. `GO`, `checklist`, `faturamento`). Default: the repo's `metadata.key` from `vitruvio.json`.
|
||||
|
||||
## Step 4 — Create the patch files
|
||||
|
||||
**Both oracle/ and postgresql/ files must always be created and kept in sync.**
|
||||
|
||||
Filename convention: `{YYYYMMDDHHmm}_{MODULE_KEY}.xml` (e.g. `202506011430_checklist.xml`).
|
||||
|
||||
Use `vitruvio new patch` to scaffold — it handles the filename, ID assignment, and directory creation automatically:
|
||||
|
||||
```bash
|
||||
vitruvio new patch <module-key>
|
||||
```
|
||||
|
||||
Find the created files, then replace the SQL placeholder in both with the actual migration for each DB dialect, and set `author` to the correct Vitruvio username:
|
||||
|
||||
```bash
|
||||
ls -t patches/oracle/ | head -1
|
||||
```
|
||||
|
||||
### Absolute rules
|
||||
|
||||
- **Append-only.** Never edit or delete existing `<changeSet>` entries — modifying a checksum that Liquibase already recorded breaks deployment.
|
||||
- **Unique numeric IDs.** Each `<changeSet id="...">` must have a unique ID within the repo. Increment from the last found.
|
||||
- **Always use `<preConditions onFail="MARK_RAN">`** — every changeset must be idempotent and safe to re-run on any DB state.
|
||||
- **Oracle ≠ PostgreSQL.** Write each file for its target DB — data types, sequences, and quoting differ. Never copy-paste blindly.
|
||||
- **One logical change per changeset** — don't batch unrelated changes into a single `<changeSet>`.
|
||||
|
||||
### File skeleton
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd
|
||||
http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
|
||||
|
||||
<changeSet author="<author>" id="<next-id>" objectQuotingStrategy="LEGACY">
|
||||
<preConditions onError="WARN" onFail="MARK_RAN" onSqlOutput="IGNORE">
|
||||
<!-- guard appropriate for the operation — see examples below -->
|
||||
</preConditions>
|
||||
<sql endDelimiter=";" splitStatements="true" stripComments="false">
|
||||
-- SQL for this DB dialect
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
```
|
||||
|
||||
### preConditions reference
|
||||
|
||||
| Operation | Guard to use |
|
||||
|-----------|-------------|
|
||||
| CREATE TABLE | `<not><tableExists tableName="MY_TABLE"/></not>` |
|
||||
| ADD COLUMN | `<not><columnExists tableName="MY_TABLE" columnName="MY_COL"/></not>` |
|
||||
| CREATE INDEX | `<not><indexExists indexName="IDX_NAME"/></not>` |
|
||||
| ADD CONSTRAINT / FK | `<not><foreignKeyConstraintExists foreignKeyName="FK_NAME"/></not>` |
|
||||
| INSERT row (by PK) | `<sqlCheck expectedResult="0">SELECT COUNT(*) FROM MY_TABLE WHERE ID = 1</sqlCheck>` |
|
||||
| DROP TABLE | `<tableExists tableName="MY_TABLE"/>` |
|
||||
| DROP COLUMN | `<columnExists tableName="MY_TABLE" columnName="MY_COL"/>` |
|
||||
|
||||
### Oracle vs PostgreSQL differences to watch
|
||||
|
||||
| | Oracle | PostgreSQL |
|
||||
|---|---|---|
|
||||
| Auto-increment | Separate `CREATE SEQUENCE` + trigger or `DEFAULT seq.NEXTVAL` | `SERIAL` or `GENERATED ALWAYS AS IDENTITY` |
|
||||
| String type | `VARCHAR2(n)` | `VARCHAR(n)` |
|
||||
| Boolean | `NUMBER(1)` | `BOOLEAN` |
|
||||
| Date/time | `DATE`, `TIMESTAMP` | `DATE`, `TIMESTAMP` |
|
||||
| Current timestamp | `SYSDATE` | `CURRENT_TIMESTAMP` |
|
||||
| Quoting | `LEGACY` strategy (unquoted) | Same |
|
||||
|
||||
## Step 5 — Check vitruvio.json patches registration
|
||||
|
||||
The patches directory only needs to be registered once. Check if it is already there:
|
||||
|
||||
```bash
|
||||
grep -A2 '"patches"' vitruvio.json
|
||||
```
|
||||
|
||||
If not registered, add to `vitruvio.json`:
|
||||
|
||||
```json
|
||||
"patches": "patches/"
|
||||
```
|
||||
|
||||
## Step 6 — Report
|
||||
|
||||
Tell the user:
|
||||
- Files created: `patches/oracle/<filename>.xml` and `patches/postgresql/<filename>.xml`
|
||||
- Changeset IDs used
|
||||
- Summary of what each changeset does
|
||||
- Reminder: never edit existing changesets once committed — add new ones instead
|
||||
@@ -0,0 +1,227 @@
|
||||
---
|
||||
name: vitruvio-criar-processo-bpmn
|
||||
description: >
|
||||
Use when the user wants to create or edit only the BPMN workflow file of a Vitruvio
|
||||
process (the Activiti flow: lanes, tasks, gateways, sequence flows), without touching
|
||||
the forms. Triggers: "create bpmn", "criar bpmn", "novo bpmn", "fluxo do processo",
|
||||
"workflow do processo", "só o bpmn", "process flow", "edit the bpmn".
|
||||
For the full process (BPMN + forms + manifest) use vitruvio-criar-processo, which calls
|
||||
this skill for the BPMN part.
|
||||
---
|
||||
|
||||
# Create Vitruvio Process BPMN
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are creating the **BPMN workflow file** of a Vitruvio process. This skill is focused
|
||||
on the `.bpmn` file only — the desktop form is handled by **vitruvio-criar-form-desktop**
|
||||
(process variant) and the mobile form by **vitruvio-criar-form-mobile** (process variant).
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect flow details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing):
|
||||
|
||||
- **Key** — snake_case or camelCase unique identifier. Becomes the BPMN process ID and the
|
||||
folder name. Must match the `key` used for the process in `vitruvio.json`.
|
||||
- **Name** — human-readable label.
|
||||
- **Who can start it?** — group key(s) allowed to open the process (e.g. `gestao`,
|
||||
`admin`). Used in `activiti:candidateStarterGroups`.
|
||||
- **Steps** — the human tasks (user tasks), and which group handles each. A simple linear
|
||||
flow is enough to start.
|
||||
- **Decisions / branches?** — any exclusive gateways with conditions.
|
||||
- **Automatic steps?** — any script tasks running between user tasks.
|
||||
|
||||
## Step 3 — Write the BPMN file: `processes/<key>/<key>.bpmn`
|
||||
|
||||
### Critical rules
|
||||
|
||||
- The `<bpmn2:process id="...">` value is the canonical process identity. The importer
|
||||
reads it from the BPMN, not from vitruvio.json. **It must match the `key`.**
|
||||
- Every node must appear in a `<bpmn2:laneSet>` / `<bpmn2:lane>` **and** in the
|
||||
`<bpmndi:BPMNDi>` section — Vitruvio renders the diagram.
|
||||
- Each `activiti:formKey` on the start event and user tasks must match a
|
||||
`<form formKey="...">` in the desktop form XML (and mobile form, if present).
|
||||
- Process variables from submitted forms are auto-named `{formKey}_{fieldId}`
|
||||
(e.g. `formAbertura_status`). Gateway conditions reference them.
|
||||
- Gateway conditions use `#{variable == 'value'}` (JUEL expression language).
|
||||
- Script tasks call `vScriptService.loadScript('scriptKey', 'javascript')`, **not**
|
||||
`libService`.
|
||||
|
||||
### Minimal skeleton (start → user task → end, single lane)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn2:definitions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
|
||||
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
|
||||
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
|
||||
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
|
||||
xmlns:activiti="http://activiti.org/bpmn"
|
||||
id="sample-diagram"
|
||||
targetNamespace="http://bpmn.io/schema/bpmn"
|
||||
exporter="bpmn-js (https://demo.bpmn.io)"
|
||||
exporterVersion="8.2.0"
|
||||
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
|
||||
|
||||
<bpmn2:collaboration id="Collaboration_<key>">
|
||||
<bpmn2:participant id="processo_<key>" name="<Name>" processRef="<key>" />
|
||||
</bpmn2:collaboration>
|
||||
|
||||
<bpmn2:process id="<key>" name="<Name>" isExecutable="true"
|
||||
activiti:candidateStarterGroups="<starterGroup>">
|
||||
<bpmn2:laneSet>
|
||||
<bpmn2:lane id="lane_execucao" name="Execução">
|
||||
<bpmn2:flowNodeRef>inicio</bpmn2:flowNodeRef>
|
||||
<bpmn2:flowNodeRef>task_executar</bpmn2:flowNodeRef>
|
||||
<bpmn2:flowNodeRef>fim</bpmn2:flowNodeRef>
|
||||
</bpmn2:lane>
|
||||
</bpmn2:laneSet>
|
||||
|
||||
<!-- Start event: activiti:initiator stores the login of who opened the process -->
|
||||
<bpmn2:startEvent id="inicio" name="Início"
|
||||
activiti:formKey="formAbertura"
|
||||
activiti:initiator="iniciador">
|
||||
<bpmn2:outgoing>flow_inicio_task</bpmn2:outgoing>
|
||||
</bpmn2:startEvent>
|
||||
|
||||
<!-- User task: candidateGroups controls who sees it in their inbox -->
|
||||
<bpmn2:userTask id="task_executar" name="Executar"
|
||||
activiti:formKey="formExecutar"
|
||||
activiti:candidateGroups="${vStringUtils.validateRoles(gr_executores)}">
|
||||
<bpmn2:incoming>flow_inicio_task</bpmn2:incoming>
|
||||
<bpmn2:outgoing>flow_task_fim</bpmn2:outgoing>
|
||||
</bpmn2:userTask>
|
||||
|
||||
<!-- Script task example (omit if not needed):
|
||||
<bpmn2:scriptTask id="script_processar" name="Processar" scriptFormat="javascript">
|
||||
<bpmn2:incoming>flow_task_script</bpmn2:incoming>
|
||||
<bpmn2:outgoing>flow_script_fim</bpmn2:outgoing>
|
||||
<bpmn2:script>var f = vScriptService.loadScript('meu_script', 'javascript');
|
||||
f(execution);</bpmn2:script>
|
||||
</bpmn2:scriptTask>
|
||||
-->
|
||||
|
||||
<!-- Exclusive gateway example (omit if not needed):
|
||||
<bpmn2:exclusiveGateway id="gw_decisao" name="Aprovado?">
|
||||
<bpmn2:incoming>flow_task_gw</bpmn2:incoming>
|
||||
<bpmn2:outgoing>flow_sim</bpmn2:outgoing>
|
||||
<bpmn2:outgoing>flow_nao</bpmn2:outgoing>
|
||||
</bpmn2:exclusiveGateway>
|
||||
<bpmn2:sequenceFlow id="flow_sim" name="Sim" sourceRef="gw_decisao" targetRef="fim">
|
||||
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">#{formExecutar_aprovado == '1'}</bpmn2:conditionExpression>
|
||||
</bpmn2:sequenceFlow>
|
||||
<bpmn2:sequenceFlow id="flow_nao" name="Não" sourceRef="gw_decisao" targetRef="task_executar">
|
||||
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">#{formExecutar_aprovado == '0'}</bpmn2:conditionExpression>
|
||||
</bpmn2:sequenceFlow>
|
||||
-->
|
||||
|
||||
<bpmn2:endEvent id="fim" name="Fim">
|
||||
<bpmn2:incoming>flow_task_fim</bpmn2:incoming>
|
||||
</bpmn2:endEvent>
|
||||
|
||||
<bpmn2:sequenceFlow id="flow_inicio_task" sourceRef="inicio" targetRef="task_executar" />
|
||||
<bpmn2:sequenceFlow id="flow_task_fim" sourceRef="task_executar" targetRef="fim" />
|
||||
</bpmn2:process>
|
||||
|
||||
<!-- BPMNDi: visual layout — required for the diagram to render -->
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_<key>">
|
||||
|
||||
<bpmndi:BPMNShape id="Participant_di" bpmnElement="processo_<key>" isHorizontal="true">
|
||||
<dc:Bounds x="100" y="80" width="750" height="180" />
|
||||
</bpmndi:BPMNShape>
|
||||
|
||||
<bpmndi:BPMNShape id="lane_execucao_di" bpmnElement="lane_execucao" isHorizontal="true">
|
||||
<dc:Bounds x="130" y="80" width="720" height="180" />
|
||||
</bpmndi:BPMNShape>
|
||||
|
||||
<bpmndi:BPMNShape id="inicio_di" bpmnElement="inicio">
|
||||
<dc:Bounds x="192" y="152" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="195" y="195" width="30" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
|
||||
<bpmndi:BPMNShape id="task_executar_di" bpmnElement="task_executar">
|
||||
<dc:Bounds x="310" y="130" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
|
||||
<bpmndi:BPMNShape id="fim_di" bpmnElement="fim">
|
||||
<dc:Bounds x="492" y="152" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="497" y="195" width="19" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
|
||||
<bpmndi:BPMNEdge id="flow_inicio_task_di" bpmnElement="flow_inicio_task">
|
||||
<di:waypoint x="228" y="170" />
|
||||
<di:waypoint x="310" y="170" />
|
||||
</bpmndi:BPMNEdge>
|
||||
|
||||
<bpmndi:BPMNEdge id="flow_task_fim_di" bpmnElement="flow_task_fim">
|
||||
<di:waypoint x="410" y="170" />
|
||||
<di:waypoint x="492" y="170" />
|
||||
</bpmndi:BPMNEdge>
|
||||
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn2:definitions>
|
||||
```
|
||||
|
||||
### Multi-lane pattern (when tasks belong to different roles)
|
||||
|
||||
Add each lane inside `<bpmn2:laneSet>`, list the node IDs inside each lane, and adjust
|
||||
the BPMNDi bounds:
|
||||
|
||||
```xml
|
||||
<bpmn2:laneSet>
|
||||
<bpmn2:lane id="lane_gestao" name="Gestão">
|
||||
<bpmn2:flowNodeRef>inicio</bpmn2:flowNodeRef>
|
||||
<bpmn2:flowNodeRef>fim</bpmn2:flowNodeRef>
|
||||
</bpmn2:lane>
|
||||
<bpmn2:lane id="lane_execucao" name="Execução">
|
||||
<bpmn2:flowNodeRef>task_executar</bpmn2:flowNodeRef>
|
||||
</bpmn2:lane>
|
||||
</bpmn2:laneSet>
|
||||
```
|
||||
|
||||
### Script task — script side
|
||||
|
||||
```javascript
|
||||
// In <bpmn2:script> inside a scriptTask:
|
||||
var f = vScriptService.loadScript('meu_script', 'javascript');
|
||||
f(execution);
|
||||
|
||||
// In the script file itself (pattern: process/task script — see vitruvio-criar-script):
|
||||
(function(execution) {
|
||||
var db = libService.loadScript('db');
|
||||
var banco = new db(db.VITRUVIO_DATASOURCE);
|
||||
var status = execution.getVariable('formAbertura_status');
|
||||
// ...
|
||||
})(execution)
|
||||
```
|
||||
|
||||
## Step 4 — Manifest
|
||||
|
||||
If this skill is run **standalone** (the process already exists in `vitruvio.json`),
|
||||
ensure its entry has `"bpmn": "processes/<key>/<key>.bpmn"`. Do not create or modify
|
||||
the rest of the entry here — full registration is handled by **vitruvio-criar-processo**.
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- File created/updated: `processes/<key>/<key>.bpmn`
|
||||
- The process identity is the `<bpmn2:process id>` — it must match the key.
|
||||
- Each `activiti:formKey` must have a matching `<form formKey="...">` in the form XML
|
||||
(create/update it with **vitruvio-criar-form-desktop** / **vitruvio-criar-form-mobile**).
|
||||
- Submitted field `id="X"` in `formKey="formAbertura"` becomes variable `formAbertura_X`.
|
||||
- For complex flows, recommend editing the BPMN in bpmn.io or Camunda Modeler before deploying.
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
name: vitruvio-criar-processo
|
||||
description: >
|
||||
Use when the user wants to create a new Activiti workflow process in a Vitruvio repository.
|
||||
Triggers: "create process", "new process", "criar processo", "novo processo", "add process",
|
||||
"workflow", "BPMN", or any request to scaffold a BPMN file or process form XML.
|
||||
---
|
||||
|
||||
# Create Vitruvio Process (orchestrator)
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
A process is made of up to three artifacts plus a manifest entry:
|
||||
|
||||
| Part | Skill that owns it |
|
||||
|------|--------------------|
|
||||
| `processes/<key>/<key>.bpmn` (workflow) | **vitruvio-criar-processo-bpmn** |
|
||||
| `processes/<key>/<key>-desktop.xml` (web form) | **vitruvio-criar-form-desktop** (process variant) |
|
||||
| `processes/<key>/<key>-mobile.xml` (app form, optional) | **vitruvio-criar-form-mobile** (process variant) |
|
||||
|
||||
This skill collects the intent once, drives those skills, and registers the process in
|
||||
`vitruvio.json`. Do the file work by following the referenced skills — do not re-derive
|
||||
their templates here.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect process details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing):
|
||||
|
||||
- **Key** — snake_case or camelCase unique identifier. Becomes the BPMN process ID and the
|
||||
folder name.
|
||||
- **Name** — human-readable label.
|
||||
- **Description** — one sentence (optional).
|
||||
- **Who can start it?** — group key(s) for `activiti:candidateStarterGroups`.
|
||||
- **Steps** — the human tasks and which group handles each. A simple linear flow is enough.
|
||||
- **Needs a script task?** — automatic steps running a script between user tasks.
|
||||
- **Needs a mobile form?** — if yes, a `<key>-mobile.xml` is created too. Remember mobile is a
|
||||
different schema with explicit data wiring — variables, lookups and libs must be named
|
||||
up front (the mobile skill will ask).
|
||||
|
||||
## Step 3 — Scaffold
|
||||
|
||||
```bash
|
||||
vitruvio new process <key> --name "<name>"
|
||||
```
|
||||
|
||||
This creates `processes/<key>/<key>.bpmn`, `processes/<key>/<key>-desktop.xml`, and the
|
||||
`vitruvio.json` entry. Then replace the generated files using the focused skills:
|
||||
|
||||
1. **BPMN** — follow **vitruvio-criar-processo-bpmn** to write `processes/<key>/<key>.bpmn`
|
||||
from the user's steps/branches.
|
||||
2. **Desktop form** — follow **vitruvio-criar-form-desktop** (process variant) to write
|
||||
`processes/<key>/<key>-desktop.xml`, one `<form formKey>` per `activiti:formKey` in the BPMN.
|
||||
3. **Mobile form (if requested)** — follow **vitruvio-criar-form-mobile** (process variant) to
|
||||
write `processes/<key>/<key>-mobile.xml`. `vitruvio new` does not create it.
|
||||
|
||||
## Step 4 — Update vitruvio.json entry
|
||||
|
||||
`vitruvio new` already added the entry. Full shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description>",
|
||||
"bpmn": "processes/<key>/<key>.bpmn",
|
||||
"forms": {
|
||||
"desktop": "processes/<key>/<key>-desktop.xml"
|
||||
},
|
||||
"schedules": []
|
||||
}
|
||||
```
|
||||
|
||||
- Add `"description"` if the user provided one — `vitruvio new` does not set it.
|
||||
- If a mobile form was created, add `"mobile": "processes/<key>/<key>-mobile.xml"` inside `"forms"`.
|
||||
- Add `schedules` only if the process runs on a timer (cron/simple interval).
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- Files created: `<key>.bpmn`, `<key>-desktop.xml` (and `<key>-mobile.xml` if applicable).
|
||||
- Registered in `vitruvio.json` with key `<key>`.
|
||||
- The process identity is the `<bpmn2:process id>` — it must match the key.
|
||||
- Each `activiti:formKey` in the BPMN must have a matching `<form formKey="...">` in **every**
|
||||
form file (desktop and mobile).
|
||||
- Submitted field `id="X"` in `formKey="formAbertura"` becomes process variable `formAbertura_X`
|
||||
(desktop auto-injects these; mobile must declare/fetch them — see vitruvio-criar-form-mobile).
|
||||
- For complex flows, recommend editing the BPMN in bpmn.io or Camunda Modeler before deploying.
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
name: vitruvio-criar-query
|
||||
description: >
|
||||
Use when the user wants to create a new named SQL query in a Vitruvio repository.
|
||||
Triggers: "create query", "new query", "criar query", "nova query", "add query", "named query",
|
||||
or any request to scaffold a queries/*.sql file and register it in vitruvio.json.
|
||||
---
|
||||
|
||||
# Create Vitruvio Query
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are creating a new named SQL query inside a Vitruvio repository. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect query details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing from their original request):
|
||||
|
||||
- **Key** — kebab-case unique identifier. Used to reference this query in reports, DBTable components, and scripts.
|
||||
- **Name** — human-readable label shown in the Vitruvio UI.
|
||||
- **SQL** — the query itself, or enough context to write it.
|
||||
- **Connection** — datasource name (default: `vitruvio_producao`). Ask only if the user mentions a specific datasource.
|
||||
|
||||
## Step 3 — Scaffold and create file
|
||||
|
||||
```bash
|
||||
vitruvio new query <key> --name "<name>"
|
||||
```
|
||||
|
||||
This creates `queries/<key>.sql` and registers it in `vitruvio.json` with `connection: "vitruvio_producao"`. Replace the generated SQL with the actual query. If a different datasource is needed, update `"connection"` in `vitruvio.json`.
|
||||
|
||||
Target path: `queries/<key>.sql`
|
||||
|
||||
Rules:
|
||||
- One `SELECT` per file. No multiple statements, no DDL, no INSERT/UPDATE/DELETE.
|
||||
- Named bind parameters use `:paramName` syntax — never concatenate user input into SQL.
|
||||
- Write ANSI SQL where possible. If DB-specific syntax is unavoidable, note it in a comment.
|
||||
- Keep Oracle and PostgreSQL compatibility in mind — avoid syntax that only works in one.
|
||||
|
||||
```sql
|
||||
SELECT col1,
|
||||
col2
|
||||
FROM my_table
|
||||
WHERE active = 1
|
||||
AND id = :id
|
||||
ORDER BY col1
|
||||
```
|
||||
|
||||
## Step 4 — Update vitruvio.json entry
|
||||
|
||||
`vitruvio new` already added the query entry. The full entry shape is:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"source": "queries/<key>.sql",
|
||||
"connection": "vitruvio_producao"
|
||||
}
|
||||
```
|
||||
|
||||
Update `"connection"` only if the user specified a datasource other than `"vitruvio_producao"`.
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- File created: `queries/<key>.sql`
|
||||
- Registered in `vitruvio.json` with key `<key>`
|
||||
- How it can be used: as a datasource in a report, in a DBTable component, or loaded in a script via `db.executeNamedQuery('<key>', params)`
|
||||
@@ -0,0 +1,126 @@
|
||||
---
|
||||
name: vitruvio-criar-relatorio
|
||||
description: >
|
||||
Use when the user wants to register a new report in a Vitruvio repository.
|
||||
Triggers: "create report", "new report", "criar relatório", "novo relatório", "add report",
|
||||
MODELO_ESTATICO, DINAMICO_QUERY_SQL, "jrxml", or any request to scaffold a report entry.
|
||||
---
|
||||
|
||||
# Create Vitruvio Report
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are registering a new report inside a Vitruvio repository. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Identify the report type
|
||||
|
||||
Ask the user which type applies (if not already clear from the request):
|
||||
|
||||
| Type | When to use |
|
||||
|------|-------------|
|
||||
| **MODELO_ESTATICO** | A Jasper report whose layout is fully designed in a `.jrxml` file (Jaspersoft Studio). The dev provides or will provide the `.jrxml`. |
|
||||
| **DINAMICO_QUERY_SQL** | A Vitruvio-managed report where data comes from a named query and columns/layout are configured in `vitruvio.json`. The `.jrxml` is still needed but is simpler — Vitruvio drives the structure. |
|
||||
|
||||
## Step 3 — Collect details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing):
|
||||
|
||||
**Both types:**
|
||||
- **Key** — kebab-case or snake_case unique identifier.
|
||||
- **Name** — human-readable label shown in the UI.
|
||||
- **Category** — display category (e.g. `"Compras"`, `"Auditoria/Gondola"`).
|
||||
- **Owner** — Vitruvio username of the responsible person.
|
||||
- **Orientation** — `RETRATO` (portrait) or `PAISAGEM` (landscape). Default: `RETRATO`.
|
||||
- **Allowed groups / users** — who can access this report (can be empty arrays).
|
||||
- **Has parameters?** — does the user fill in parameters before running it? If yes, a params form is needed.
|
||||
|
||||
**DINAMICO_QUERY_SQL only:**
|
||||
- **Query key** — the named query that feeds the report (must be registered in `vitruvio.json`).
|
||||
- **Columns** — list of columns: name (DB column), label, alignment (`LEFT`/`CENTER`/`RIGHT`), width (px), aggregation (`null`, `SUM`, `COUNT`, etc.).
|
||||
|
||||
## Step 4 — Create the files
|
||||
|
||||
### MODELO_ESTATICO
|
||||
|
||||
Files live flat in `reports/`:
|
||||
- `reports/<key>.jrxml` — **do not generate this file**; tell the user to place the Jaspersoft-designed template here. Must target **JasperReports 6.21.2** — do not save with a newer version.
|
||||
- `reports/<key>-params.xml` — only if the report has parameters (follows the same Vaadin XML form schema as panels).
|
||||
|
||||
### DINAMICO_QUERY_SQL
|
||||
|
||||
Files live in a subdirectory:
|
||||
- `reports/<key>/template.jrxml` — **do not generate this file**; tell the user to place the template here.
|
||||
- `reports/<key>/params.xml` — only if the report has parameters.
|
||||
|
||||
## Step 5 — Register in vitruvio.json
|
||||
|
||||
Read `vitruvio.json`, find or create the `"reports"` array, and add the entry.
|
||||
|
||||
### MODELO_ESTATICO entry
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"type": "MODELO_ESTATICO",
|
||||
"category": "<category>",
|
||||
"owner": "<owner>",
|
||||
"template": "reports/<key>.jrxml",
|
||||
"parameterForm": "reports/<key>-params.xml",
|
||||
"orientation": "RETRATO",
|
||||
"allowedGroups": [],
|
||||
"allowedUsers": [],
|
||||
"columns": [],
|
||||
"schedules": []
|
||||
}
|
||||
```
|
||||
|
||||
Omit `"parameterForm"` if no params form.
|
||||
|
||||
### DINAMICO_QUERY_SQL entry
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description>",
|
||||
"type": "DINAMICO_QUERY_SQL",
|
||||
"category": "<category>",
|
||||
"owner": "<owner>",
|
||||
"template": "reports/<key>/template.jrxml",
|
||||
"parameterForm": "reports/<key>/params.xml",
|
||||
"query": "<query-key>",
|
||||
"orientation": "RETRATO",
|
||||
"allowedGroups": [],
|
||||
"allowedUsers": [],
|
||||
"columns": [
|
||||
{
|
||||
"name": "COLUMN_NAME",
|
||||
"label": "Column Label",
|
||||
"align": "LEFT",
|
||||
"width": 100,
|
||||
"aggregation": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Omit `"parameterForm"` if no params form.
|
||||
|
||||
Preserve the existing file structure and all other entries. Write the updated `vitruvio.json` back.
|
||||
|
||||
## Step 6 — Report
|
||||
|
||||
Tell the user:
|
||||
- Entry registered in `vitruvio.json` with key `<key>` and type `<type>`
|
||||
- For MODELO_ESTATICO: remind them to place the `.jrxml` at `reports/<key>.jrxml`, designed in Jaspersoft Studio 6.21.2
|
||||
- For DINAMICO_QUERY_SQL: remind them to place the template at `reports/<key>/template.jrxml`
|
||||
- If a params form is needed: what file to create and that it follows the same Vaadin XML schema as panels
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
name: vitruvio-criar-script
|
||||
description: >
|
||||
Use when the user wants to create a new JavaScript script in a Vitruvio repository.
|
||||
Triggers: "create script", "new script", "criar script", "novo script", "add script",
|
||||
or any request to scaffold a scripts/*.js file and register it in vitruvio.json.
|
||||
---
|
||||
|
||||
# Create Vitruvio Script
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are creating a new script inside a Vitruvio repository. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect script details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing from their original request):
|
||||
|
||||
- **Key (sigla)** — unique identifier used in `libService.loadScript('key')`. Snake_case. Must be unique across all scripts in the repo. If the user already provided a name, suggest a key derived from it.
|
||||
- **Name** — human-readable label shown in the Vitruvio UI.
|
||||
- **Pattern** — which of the two patterns applies:
|
||||
- **Library** — reusable module, loaded by other scripts/endpoints/panels via `libService.loadScript`. Wrap in `({...})`.
|
||||
- **Process/task script** — runs directly from a process task or scheduler. Top-level execution, no export.
|
||||
- **Description** — one sentence about what this script does (optional, but ask if not provided).
|
||||
- **Domain** — `USUARIO` (user-level, default) or `SISTEMA` (system-level).
|
||||
|
||||
## Step 3 — Scaffold and create file
|
||||
|
||||
```bash
|
||||
vitruvio new script <key> --name "<name>"
|
||||
```
|
||||
|
||||
This creates `scripts/<key>.js` and registers it in `vitruvio.json` with `domain: "USUARIO"`. Replace the generated file with the appropriate template below. If the domain should be `SISTEMA`, update that field in `vitruvio.json`.
|
||||
|
||||
Target path: `scripts/<key>.js`
|
||||
|
||||
### Library template
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Nome: <name>
|
||||
* Sigla: <key>
|
||||
* Descrição: <description>
|
||||
*/
|
||||
({
|
||||
// example function — replace with actual implementation
|
||||
run: function(params) {
|
||||
var db = libService.loadScript('db');
|
||||
var banco = new db(db.VITRUVIO_DATASOURCE);
|
||||
|
||||
// implementation here
|
||||
|
||||
return {};
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Process / task script template
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Nome: <name>
|
||||
* Sigla: <key>
|
||||
* Descrição: <description>
|
||||
*/
|
||||
(function(execution) {
|
||||
var db = libService.loadScript('db');
|
||||
var banco = new db(db.VITRUVIO_DATASOURCE);
|
||||
|
||||
// implementation here
|
||||
})(execution)
|
||||
```
|
||||
|
||||
Rules (Rhino ES5 — no exceptions):
|
||||
- No `let`, `const`, arrow functions, template literals, destructuring, spread, `class`, or `import/export`
|
||||
- Use `var` everywhere
|
||||
- No `require`, `process`, `window`, or Node/browser globals
|
||||
- String concatenation with `+`, not template literals
|
||||
- `JSON.parse` / `JSON.stringify` for serialization
|
||||
|
||||
## Step 4 — Update vitruvio.json entry
|
||||
|
||||
`vitruvio new` already added the script entry. The full entry shape is:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description>",
|
||||
"language": "javascript",
|
||||
"domain": "USUARIO",
|
||||
"source": "scripts/<key>.js"
|
||||
}
|
||||
```
|
||||
|
||||
Update only what differs: change `"domain"` to `"SISTEMA"` if needed; add `"description"` if provided.
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- File created: `scripts/<key>.js`
|
||||
- Registered in `vitruvio.json` with key `<key>`
|
||||
- How to load it from another script or endpoint: `var lib = libService.loadScript('<key>');`
|
||||
@@ -0,0 +1,178 @@
|
||||
---
|
||||
name: vitruvio-registrar-artefato
|
||||
description: >
|
||||
Use when the user has an existing file they want to add to a Vitruvio repository.
|
||||
Triggers: "I have an existing file", "move this to panels/", "register this artifact",
|
||||
"add this already existing", "tenho um arquivo pronto", "mover para o repo",
|
||||
or any request where the source file already exists and needs to be placed in the
|
||||
correct repo directory and registered in vitruvio.json.
|
||||
Do NOT use vitruvio-criar-* skills for this — those scaffold new files and would overwrite the existing one.
|
||||
---
|
||||
|
||||
# Register Existing Artifact
|
||||
|
||||
> All messages shown to the user must be written in Portuguese.
|
||||
|
||||
You are wiring an already-existing file into a Vitruvio repository. No scaffolding — the file exists and must be placed correctly then registered in `vitruvio.json`. Follow these steps in order.
|
||||
|
||||
## Step 1 — Confirm you are inside a Vitruvio repo
|
||||
|
||||
```bash
|
||||
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
|
||||
```
|
||||
|
||||
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
|
||||
|
||||
## Step 2 — Collect details
|
||||
|
||||
Ask the user (in a single message, only ask what is missing from their original request):
|
||||
|
||||
- **Source file path** — where the file currently lives (absolute or relative).
|
||||
- **Artifact type** — one of: `panel`, `script`, `endpoint`, `query`, `report`, `library`, `process`.
|
||||
- **Key** — kebab-case unique identifier for this artifact. Must be unique within its section in `vitruvio.json`. Changing it later is a breaking change.
|
||||
- **Name** — human-readable label shown in the Vitruvio UI.
|
||||
- Type-specific fields (only ask what is relevant):
|
||||
- Panel: `category` (e.g. `"Base de Conhecimento"`), `openInNewWindow` (default `true`), `showInMobileList` (default `false`), mobile form path if one also exists.
|
||||
- Script: `domain` — `USUARIO` (default) or `SISTEMA`.
|
||||
- Endpoint: `authMode` — `PUBLIC` (default), `STATIC_TOKEN`, `VITRUVIO_WS_USER_AUTH`, or `HTTP_BASIC_AUTH`.
|
||||
- Query: `connection` — datasource name (default `vitruvio_producao`).
|
||||
- Report: `type` — `MODELO_ESTATICO` or `DINAMICO_QUERY_SQL`; `query` key; `template` path; `parameterForm` path (optional).
|
||||
- Library: `authMode` — `PUBLIC` (default); `mobileEnabled` (default `false`).
|
||||
- Process: path to the BPMN file and desktop form XML if they are separate files.
|
||||
|
||||
## Step 3 — Place the file(s)
|
||||
|
||||
Create the destination directory if it does not exist, then move the source file to the correct location. **Never call `vitruvio new`** — it would scaffold and overwrite the existing file.
|
||||
|
||||
| Artifact | Destination | Notes |
|
||||
|----------|-------------|-------|
|
||||
| `panel` | `panels/<key>/<key>-desktop.xml` | Rename the source desktop form to `<key>-desktop.xml`. If a mobile form also exists, place it at `panels/<key>/<key>-mobile.xml`. |
|
||||
| `script` | `scripts/<key>.js` | |
|
||||
| `endpoint` | `endpoints/<key>.js` | |
|
||||
| `query` | `queries/<key>.sql` | |
|
||||
| `report` | `reports/<key>/` | Move jrxml and optional params.xml into this directory. |
|
||||
| `library` | `libraries/<key>/` | Move all files into this directory. |
|
||||
| `process` | `processes/<key>/` | Move the .bpmn and form XML(s) into this directory. |
|
||||
|
||||
```bash
|
||||
# Example: panel
|
||||
mkdir -p panels/<key>
|
||||
mv /path/to/source.xml panels/<key>/<key>-desktop.xml
|
||||
```
|
||||
|
||||
## Step 4 — Add entry to vitruvio.json
|
||||
|
||||
Read `vitruvio.json`. If the relevant section array does not exist, create it. Append the entry for the artifact type. Do **not** remove or modify any existing entries.
|
||||
|
||||
### Panel
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description or omit>",
|
||||
"category": "<category>",
|
||||
"displayOrder": 10,
|
||||
"showInPresentation": false,
|
||||
"openInNewWindow": true,
|
||||
"showInMobileList": false,
|
||||
"displayTimeInSeconds": 0,
|
||||
"allowedGroups": [],
|
||||
"allowedUsers": [],
|
||||
"forms": {
|
||||
"desktop": "panels/<key>/<key>-desktop.xml",
|
||||
"mobile": null,
|
||||
"mobileAlternative": null
|
||||
},
|
||||
"defaultState": null,
|
||||
"thumbnail": null
|
||||
}
|
||||
```
|
||||
|
||||
### Script
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description or omit>",
|
||||
"language": "javascript",
|
||||
"domain": "USUARIO",
|
||||
"source": "scripts/<key>.js"
|
||||
}
|
||||
```
|
||||
|
||||
### Endpoint
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description or omit>",
|
||||
"language": "javascript",
|
||||
"authMode": "PUBLIC",
|
||||
"active": true,
|
||||
"source": "endpoints/<key>.js"
|
||||
}
|
||||
```
|
||||
|
||||
### Query
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"connection": "vitruvio_producao",
|
||||
"source": "queries/<key>.sql"
|
||||
}
|
||||
```
|
||||
|
||||
### Report
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description or omit>",
|
||||
"type": "DINAMICO_QUERY_SQL",
|
||||
"category": "<category or omit>",
|
||||
"owner": null,
|
||||
"template": "reports/<key>/template.jrxml",
|
||||
"parameterForm": "reports/<key>/params.xml",
|
||||
"query": "<query-key>",
|
||||
"orientation": "RETRATO",
|
||||
"allowedGroups": [],
|
||||
"allowedUsers": []
|
||||
}
|
||||
```
|
||||
|
||||
### Library
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"type": "LOCAL",
|
||||
"authMode": "PUBLIC",
|
||||
"authToken": null,
|
||||
"mobileEnabled": false,
|
||||
"files": "libraries/<key>/"
|
||||
}
|
||||
```
|
||||
|
||||
### Process
|
||||
```json
|
||||
{
|
||||
"key": "<key>",
|
||||
"name": "<name>",
|
||||
"description": "<description or omit>",
|
||||
"bpmn": "processes/<key>/<key>.bpmn",
|
||||
"forms": {
|
||||
"desktop": "processes/<key>/<key>-desktop.xml",
|
||||
"mobile": null,
|
||||
"mobileAlternative": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 5 — Report
|
||||
|
||||
Tell the user:
|
||||
- Where the file was placed
|
||||
- Registered in `vitruvio.json` under section `<type>` with key `<key>`
|
||||
- Which fields were left at defaults and may need updating (e.g. `category`, `allowedGroups`, `description`)
|
||||
- Reminder: keys are stable identifiers — changing them later is a breaking change
|
||||
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
tests/.env
|
||||
docs/
|
||||
.mcp.json
|
||||
@@ -0,0 +1 @@
|
||||
@davinti:registry=https://git.davinti.com.br/api/packages/davinTI/npm/
|
||||
@@ -0,0 +1,416 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://git.davinti.com.br/davinTI/vitruvio/raw/branch/master/vitruvio-ui/src/main/resources/vitruvio-manifest-schema.json",
|
||||
"title": "vitruvio.json",
|
||||
"description": "Vitruvio Git Content Repository Manifest",
|
||||
"type": "object",
|
||||
"required": ["version", "metadata"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"$schema": { "type": "string" },
|
||||
"_comment": { "type": "string" },
|
||||
"_comment_version": { "type": "string" },
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "Manifest version, e.g. \"1.0.0\""
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"required": ["key"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"key": { "type": "string", "description": "Unique identifier for this content package" },
|
||||
"standardProduct": { "type": "boolean" }
|
||||
}
|
||||
},
|
||||
"panels": { "type": "array", "items": { "$ref": "#/definitions/panel" } },
|
||||
"processes": { "type": "array", "items": { "$ref": "#/definitions/process" } },
|
||||
"scripts": { "type": "array", "items": { "$ref": "#/definitions/script" } },
|
||||
"endpoints": { "type": "array", "items": { "$ref": "#/definitions/endpoint" } },
|
||||
"queries": { "type": "array", "items": { "$ref": "#/definitions/query" } },
|
||||
"reports": { "type": "array", "items": { "$ref": "#/definitions/report" } },
|
||||
"libraries": { "type": "array", "items": { "$ref": "#/definitions/library" } },
|
||||
"groups": { "type": "array", "items": { "$ref": "#/definitions/group" } },
|
||||
"properties": { "type": "array", "items": { "$ref": "#/definitions/property" } },
|
||||
"menu": { "type": "array", "items": { "$ref": "#/definitions/menuItem" } },
|
||||
"permissions": { "type": "array", "items": { "$ref": "#/definitions/permission" } },
|
||||
"patches": {
|
||||
"type": "string",
|
||||
"description": "Relative directory path containing Liquibase changesets, e.g. \"patches/\""
|
||||
}
|
||||
},
|
||||
|
||||
"definitions": {
|
||||
|
||||
"panel": {
|
||||
"type": "object",
|
||||
"required": ["key", "name"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"description": { "type": ["string", "null"] },
|
||||
"category": { "type": "string", "description": "Hierarchical path, e.g. \"Comercial/Vendas\"" },
|
||||
"displayOrder": { "type": "integer", "default": 0 },
|
||||
"showInPresentation": { "type": "boolean", "default": false },
|
||||
"openInNewWindow": { "type": "boolean", "default": false },
|
||||
"showInMobileList": { "type": "boolean", "default": false },
|
||||
"displayTimeInSeconds": { "type": "integer", "default": 0 },
|
||||
"allowedGroups": { "type": "array", "items": { "type": "string" } },
|
||||
"allowedUsers": { "type": "array", "items": { "type": "string" } },
|
||||
"forms": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"desktop": { "type": ["string", "null"], "description": "Relative path to desktop form XML" },
|
||||
"mobile": { "type": ["string", "null"], "description": "Relative path to mobile form XML" },
|
||||
"mobileAlternative": { "type": ["string", "null"], "description": "Relative path to alternative mobile form XML" }
|
||||
}
|
||||
},
|
||||
"defaultState": { "type": ["string", "null"], "description": "Relative path to default state JSON" },
|
||||
"thumbnail": { "type": ["string", "null"], "description": "Relative path to thumbnail image" }
|
||||
}
|
||||
},
|
||||
|
||||
"script": {
|
||||
"type": "object",
|
||||
"required": ["key", "name", "source", "language", "domain"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"description": { "type": ["string", "null"] },
|
||||
"language": { "type": "string", "enum": ["javascript", "JavaScript", "groovy", "Groovy"] },
|
||||
"domain": { "type": "string", "enum": ["USUARIO", "SISTEMA"] },
|
||||
"source": { "type": "string", "description": "Relative path to the script file" },
|
||||
"documentation": { "type": ["string", "null"], "description": "Relative path to optional documentation file" },
|
||||
"schedules": { "type": "array", "items": { "$ref": "#/definitions/scriptSchedule" } }
|
||||
}
|
||||
},
|
||||
|
||||
"scriptSchedule": {
|
||||
"type": "object",
|
||||
"required": ["uuid", "nome", "triggerInfo"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"uuid": { "type": "string", "format": "uuid" },
|
||||
"nome": { "type": "string" },
|
||||
"descricao": { "type": ["string", "null"] },
|
||||
"proprietario": { "type": ["string", "null"] },
|
||||
"execFeriado": { "$ref": "#/definitions/scheduleExecFeriado" },
|
||||
"scheduleScriptParameters": { "type": ["string", "null"] },
|
||||
"controleNotificacao": { "type": "boolean", "default": false },
|
||||
"configuracaoNotificacao": { "type": ["object", "null"] },
|
||||
"triggerInfo": { "$ref": "#/definitions/triggerInfo" },
|
||||
"statusPolicy": { "$ref": "#/definitions/scheduleSyncPolicy" }
|
||||
}
|
||||
},
|
||||
|
||||
"endpoint": {
|
||||
"type": "object",
|
||||
"required": ["key", "name", "source", "language", "authMode"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"description": { "type": ["string", "null"] },
|
||||
"language": { "type": "string", "enum": ["javascript", "JavaScript", "groovy", "Groovy"] },
|
||||
"authMode": { "type": "string", "enum": ["PUBLIC", "STATIC_TOKEN", "VITRUVIO_WS_USER_AUTH", "HTTP_BASIC_AUTH"] },
|
||||
"active": { "type": "boolean", "default": true },
|
||||
"source": { "type": "string", "description": "Relative path to the endpoint script file" }
|
||||
}
|
||||
},
|
||||
|
||||
"query": {
|
||||
"type": "object",
|
||||
"required": ["key", "name", "source"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"connection": { "type": ["string", "null"], "description": "Database connection key" },
|
||||
"source": { "type": "string", "description": "Relative path to the SQL file" }
|
||||
}
|
||||
},
|
||||
|
||||
"report": {
|
||||
"type": "object",
|
||||
"required": ["key", "name", "type", "category"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"description": { "type": ["string", "null"] },
|
||||
"type": { "type": "string", "enum": ["MODELO_ESTATICO", "DINAMICO_QUERY_SQL"] },
|
||||
"category": { "type": "string", "description": "Hierarchical category path" },
|
||||
"owner": { "type": ["string", "null"] },
|
||||
"template": { "type": ["string", "null"], "description": "Relative path to Jasper template" },
|
||||
"parameterForm": { "type": ["string", "null"], "description": "Relative path to parameter form XML" },
|
||||
"query": { "type": ["string", "null"], "description": "Key of the query used by this report" },
|
||||
"orientation": { "type": "string", "enum": ["RETRATO", "PAISAGEM"] },
|
||||
"allowedGroups": { "type": "array", "items": { "type": "string" } },
|
||||
"allowedUsers": { "type": "array", "items": { "type": "string" } },
|
||||
"columns": { "type": "array", "items": { "$ref": "#/definitions/reportColumn" } },
|
||||
"schedules": { "type": "array", "items": { "$ref": "#/definitions/reportSchedule" } }
|
||||
}
|
||||
},
|
||||
|
||||
"reportColumn": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"label": { "type": "string" },
|
||||
"align": { "type": "string", "enum": ["LEFT", "CENTER", "RIGHT"] },
|
||||
"width": { "type": "integer" },
|
||||
"aggregation": { "type": ["string", "null"], "description": "e.g. \"SUM\", \"COUNT\", \"AVG\"" },
|
||||
"groupBy": { "type": "boolean", "default": false },
|
||||
"groupByType": { "type": ["string", "null"], "description": "e.g. \"VALUE_IN_HEADER\", \"VALUE_IN_HEADER_WITH_HEADERS\"" },
|
||||
"pattern": { "type": ["string", "null"] },
|
||||
"separadorDecimal": { "type": ["string", "null"] },
|
||||
"separadorGrupo": { "type": ["string", "null"] }
|
||||
}
|
||||
},
|
||||
|
||||
"reportSchedule": {
|
||||
"type": "object",
|
||||
"required": ["uuid", "nome", "triggerInfo"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"uuid": { "type": "string", "format": "uuid" },
|
||||
"nome": { "type": "string" },
|
||||
"descricao": { "type": ["string", "null"] },
|
||||
"proprietario": { "type": ["string", "null"] },
|
||||
"execFeriado": { "$ref": "#/definitions/scheduleExecFeriado" },
|
||||
"triggerInfo": { "$ref": "#/definitions/triggerInfo" },
|
||||
"usuarios": { "type": "array", "items": { "type": "string" } },
|
||||
"emails": { "type": "array", "items": { "type": "string" } },
|
||||
"grupos": { "type": "array", "items": { "type": "string" } },
|
||||
"formatos": { "type": "array", "items": { "type": "integer" } },
|
||||
"emailHTML": { "type": ["string", "null"] },
|
||||
"emailAssunto": { "type": ["string", "null"] },
|
||||
"preExec": { "type": ["string", "null"] },
|
||||
"posExec": { "type": ["string", "null"] },
|
||||
"embedHtml": { "type": "boolean", "default": false },
|
||||
"embedRecipients": { "type": "boolean", "default": false },
|
||||
"comprimirAnexosZip": { "type": "boolean", "default": false },
|
||||
"ignorarRelatorioSemDados": { "type": "boolean", "default": false },
|
||||
"enviarRelatorioPorEmail": { "type": "boolean", "default": false },
|
||||
"salvarBibliotecaArquivos": { "type": "boolean", "default": false },
|
||||
"biblioteca": { "type": ["string", "null"] },
|
||||
"nomeArquivoPrefixo": { "type": ["string", "null"] },
|
||||
"usarDataComoSufixoComFormato": { "type": ["string", "null"] },
|
||||
"usarDataComoSufixoComFormatoAoAnexar": { "type": ["string", "null"] },
|
||||
"sobrescreverArquivoNaBiblioteca": { "type": "boolean", "default": false },
|
||||
"subdiretorio": { "type": ["string", "null"] },
|
||||
"jsonReportParam": { "type": ["string", "null"] }
|
||||
}
|
||||
},
|
||||
|
||||
"library": {
|
||||
"type": "object",
|
||||
"required": ["key", "name", "type", "authMode", "files"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"type": { "type": "string", "enum": ["LOCAL"] },
|
||||
"authMode": { "type": "string", "enum": ["PUBLIC", "MOBILE", "STATIC_TOKEN"] },
|
||||
"authToken": { "type": ["string", "null"], "description": "Required when authMode is STATIC_TOKEN" },
|
||||
"mobileEnabled": { "type": "boolean", "default": false },
|
||||
"files": { "type": "string", "description": "Relative path to directory containing library files" }
|
||||
}
|
||||
},
|
||||
|
||||
"group": {
|
||||
"type": "object",
|
||||
"required": ["key", "name"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"description": { "type": ["string", "null"] }
|
||||
}
|
||||
},
|
||||
|
||||
"property": {
|
||||
"type": "object",
|
||||
"required": ["key", "type"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": { "type": "string" },
|
||||
"displayName": { "type": "string" },
|
||||
"description": { "type": ["string", "null"] },
|
||||
"type": { "type": "string", "enum": ["STRING", "INTEGER", "BOOLEAN", "DATE"] },
|
||||
"size": { "type": ["integer", "null"] },
|
||||
"precision": { "type": ["integer", "null"] },
|
||||
"format": { "type": ["string", "null"] },
|
||||
"required": { "type": "boolean", "default": false },
|
||||
"password": { "type": "boolean", "default": false },
|
||||
"predefinedValues": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": { "type": "string" },
|
||||
"descricao": { "type": "string" },
|
||||
"ordem": { "type": "integer", "default": 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"menuItem": {
|
||||
"type": "object",
|
||||
"required": ["key", "name", "type"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"icon": { "type": ["integer", "null"] },
|
||||
"order": { "type": "integer", "default": 0 },
|
||||
"type": { "type": "string", "enum": ["MENU", "PAINEL", "RELATORIO", "PROCESSO", "SISTEMA"] },
|
||||
"panelKey": { "type": ["string", "null"], "description": "References panel.key (when type is PANEL)" },
|
||||
"reportKey": { "type": ["string", "null"], "description": "References report.key (when type is REPORT)" },
|
||||
"processKey": { "type": ["string", "null"], "description": "References process.key (when type is PROCESS)" },
|
||||
"children": { "type": "array", "items": { "$ref": "#/definitions/menuItem" } }
|
||||
}
|
||||
},
|
||||
|
||||
"permission": {
|
||||
"type": "object",
|
||||
"required": ["processKey", "group"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"processKey": { "type": "string", "description": "References process.key" },
|
||||
"group": { "type": "string", "description": "References group.key" },
|
||||
"read": { "type": "boolean", "default": false },
|
||||
"writeStages": { "type": "boolean", "default": false },
|
||||
"cancel": { "type": "boolean", "default": false },
|
||||
"delete": { "type": "boolean", "default": false },
|
||||
"stageStatus": { "type": "boolean", "default": false }
|
||||
}
|
||||
},
|
||||
|
||||
"process": {
|
||||
"type": "object",
|
||||
"required": ["key", "name", "bpmn"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"description": { "type": ["string", "null"] },
|
||||
"bpmn": { "type": "string", "description": "Relative path to the BPMN XML file" },
|
||||
"forms": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"desktop": { "type": ["string", "null"] },
|
||||
"mobile": { "type": ["string", "null"] },
|
||||
"mobileAlternative": { "type": ["string", "null"] }
|
||||
}
|
||||
},
|
||||
"schedules": { "type": "array", "items": { "$ref": "#/definitions/processSchedule" } }
|
||||
}
|
||||
},
|
||||
|
||||
"processSchedule": {
|
||||
"type": "object",
|
||||
"required": ["uuid", "nome", "triggerInfo"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"uuid": { "type": "string", "format": "uuid" },
|
||||
"nome": { "type": "string" },
|
||||
"descricao": { "type": ["string", "null"] },
|
||||
"proprietario": { "type": ["string", "null"] },
|
||||
"execFeriado": { "$ref": "#/definitions/scheduleExecFeriado" },
|
||||
"triggerInfo": { "$ref": "#/definitions/triggerInfo" },
|
||||
"type": { "type": ["string", "null"], "enum": ["SIMPLE", "OWNER_LOOP", null] },
|
||||
"controleNotificacao": { "type": "boolean", "default": false },
|
||||
"configuracaoNotificacao": { "type": ["string", "null"] },
|
||||
"metadata": { "$ref": "#/definitions/scheduleNotificationMetadata" },
|
||||
"usuarios": { "type": "array", "items": { "type": "string" } },
|
||||
"grupos": { "type": "array", "items": { "type": "string" } },
|
||||
"emails": { "type": "array", "items": { "type": "string" } },
|
||||
"scheduleParams": { "type": ["string", "null"] },
|
||||
"customParams": { "type": ["string", "null"] }
|
||||
}
|
||||
},
|
||||
|
||||
"scheduleNotificationMetadata": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"assuntoEmail": { "type": ["string", "null"] },
|
||||
"mensagemEmail": { "type": ["string", "null"] },
|
||||
"checkEmail": { "type": ["boolean", "null"] },
|
||||
"checkPush": { "type": ["boolean", "null"] },
|
||||
"usuariosNotificados": { "type": ["string", "null"] },
|
||||
"gruposNotificados": { "type": ["string", "null"] },
|
||||
"emailsAuxiliares": { "type": ["string", "null"] }
|
||||
}
|
||||
},
|
||||
|
||||
"triggerInfo": {
|
||||
"oneOf": [
|
||||
{ "$ref": "#/definitions/simpleTriggerInfo" },
|
||||
{ "$ref": "#/definitions/calendarTriggerInfo" }
|
||||
]
|
||||
},
|
||||
|
||||
"simpleTriggerInfo": {
|
||||
"type": "object",
|
||||
"required": ["type", "repeatEvery", "repeat"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": { "type": "string", "const": "SimpleTriggerInfo" },
|
||||
"triggerName": { "type": ["string", "null"] },
|
||||
"triggerDescription": { "type": ["string", "null"] },
|
||||
"repeat": { "type": "string", "enum": ["SECONDS", "MINUTES", "HOURS", "DAYS", "WEEKS"] },
|
||||
"repeatEvery": { "type": "integer", "minimum": 1 },
|
||||
"repeatCount": { "type": ["integer", "null"] },
|
||||
"startDate": { "type": ["string", "null"], "format": "date-time" },
|
||||
"endDate": { "type": ["string", "null"], "format": "date-time" },
|
||||
"execFeriado": { "$ref": "#/definitions/scheduleExecFeriado" },
|
||||
"minute": { "type": "integer", "minimum": 0, "maximum": 59 },
|
||||
"hour": { "type": "integer", "minimum": 0, "maximum": 23 },
|
||||
"dayOfWeek": { "type": "integer", "minimum": 1, "maximum": 7 }
|
||||
}
|
||||
},
|
||||
|
||||
"calendarTriggerInfo": {
|
||||
"type": "object",
|
||||
"required": ["type"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": { "type": "string", "const": "CalendarTriggerInfo" },
|
||||
"triggerName": { "type": ["string", "null"] },
|
||||
"triggerDescription": { "type": ["string", "null"] },
|
||||
"startDate": { "type": ["string", "null"], "format": "date-time" },
|
||||
"endDate": { "type": ["string", "null"], "format": "date-time" },
|
||||
"execFeriado": { "$ref": "#/definitions/scheduleExecFeriado" },
|
||||
"months": { "type": "array", "items": { "type": "integer", "minimum": 1, "maximum": 12 }, "description": "Empty = every month" },
|
||||
"weekDays": { "type": "array", "items": { "type": "integer", "minimum": 1, "maximum": 7 }, "description": "1=Sunday ... 7=Saturday. Mutually exclusive with monthDaysExpression" },
|
||||
"monthDaysExpression": { "type": ["string", "null"], "description": "e.g. \"1,15\" or \"1-5\" or \"*\". Mutually exclusive with weekDays" },
|
||||
"hourExpression": { "type": ["string", "null"], "description": "e.g. \"8,12,18\" or \"8-17\" or \"*\"" },
|
||||
"minutesExpression": { "type": ["string", "null"], "description": "e.g. \"0,30\" or \"*\"" },
|
||||
"secondsExpression": { "type": ["string", "null"], "description": "e.g. \"0\"" }
|
||||
}
|
||||
},
|
||||
|
||||
"scheduleExecFeriado": {
|
||||
"type": "string",
|
||||
"enum": ["SEMPRE_EXECUTAR_FERIADO", "NUNCA_EXECUTAR_FERIADO", "DEFINIR_EXECUTAR_FERIADO"]
|
||||
},
|
||||
|
||||
"scheduleSyncPolicy": {
|
||||
"type": "string",
|
||||
"description": "Controls the schedule's paused/active state on git sync. Omitted = KEEP_PREVIOUS: restores whatever pause/active state the schedule already had (new schedules are still imported paused). ALWAYS_ACTIVE / ALWAYS_PAUSED force that state on every sync regardless of prior state.",
|
||||
"enum": ["KEEP_PREVIOUS", "ALWAYS_ACTIVE", "ALWAYS_PAUSED"]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
pipeline {
|
||||
agent {
|
||||
docker {
|
||||
image 'node:22-alpine'
|
||||
// Mount the host Docker socket so docker compose works inside the container
|
||||
args '-u root -v /var/run/docker.sock:/var/run/docker.sock'
|
||||
}
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
|
||||
stage('Install') {
|
||||
steps {
|
||||
withCredentials([string(credentialsId: 'gitea-pat', variable: 'GITEA_PAT')]) {
|
||||
sh '''
|
||||
apk add --no-cache docker-cli docker-cli-compose
|
||||
npm config set @davinti:registry https://git.davinti.com.br/api/packages/davinTI/npm/
|
||||
npm config set //git.davinti.com.br/api/packages/davinTI/npm/:_authToken ${GITEA_PAT}
|
||||
npm ci
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh 'npm test -- --passWithNoTests'
|
||||
}
|
||||
}
|
||||
|
||||
stage('E2E') {
|
||||
steps {
|
||||
withCredentials([
|
||||
usernamePassword(credentialsId: 'gitea-docker-creds', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')
|
||||
]) {
|
||||
sh '''
|
||||
echo "$DOCKER_PASS" | docker login git.davinti.com.br -u "$DOCKER_USER" --password-stdin
|
||||
docker compose -f tests/docker-compose.test.yml up -d
|
||||
for i in $(seq 1 36); do
|
||||
wget -q -O /dev/null http://localhost:18080/rest/public/test && break
|
||||
echo "Aguardando Vitruvio... ($i/36)"
|
||||
sleep 5
|
||||
done
|
||||
npm run test:e2e
|
||||
'''
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh 'docker compose -f tests/docker-compose.test.yml down -v || true'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
cleanWs()
|
||||
}
|
||||
success {
|
||||
echo 'Sucesso ✅'
|
||||
}
|
||||
failure {
|
||||
echo 'Build failed.'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
# Repositório de Conteúdo Vitruvio
|
||||
|
||||
Um repositório de conteúdo Vitruvio é um repositório Git que declara artefatos de UI (painéis, processos, scripts, relatórios, etc.) através de um único arquivo de manifesto: `vitruvio.json`.
|
||||
|
||||
## Arquivos obrigatórios
|
||||
|
||||
| Arquivo | Obrigatório | Descrição |
|
||||
|---------|-------------|-----------|
|
||||
| `vitruvio.json` | Sim | Manifesto que declara todos os artefatos |
|
||||
|
||||
Todo o restante (formulários, scripts, BPMNs, etc.) é referenciado via caminhos relativos dentro do manifesto e pode ser organizado livremente.
|
||||
|
||||
---
|
||||
|
||||
## vitruvio.json
|
||||
|
||||
Localizado na **raiz** do repositório. O sistema falhará ao importar o repositório se este arquivo estiver ausente ou inválido.
|
||||
|
||||
### Campos raiz
|
||||
|
||||
| Campo | Tipo | Obrigatório | Descrição |
|
||||
|-------|------|-------------|-----------|
|
||||
| `version` | string | **Sim** | Versão semântica deste manifesto, ex.: `"1.0.0"` |
|
||||
| `metadata` | object | **Sim** | Identidade do módulo (ver abaixo) |
|
||||
| `panels` | array | Não | Painéis de UI |
|
||||
| `processes` | array | Não | Processos BPMN |
|
||||
| `scripts` | array | Não | Scripts compartilhados |
|
||||
| `endpoints` | array | Não | Endpoints REST |
|
||||
| `queries` | array | Não | Queries SQL nomeadas |
|
||||
| `reports` | array | Não | Relatórios (estáticos ou dinâmicos) |
|
||||
| `libraries` | array | Não | Bibliotecas de arquivos estáticos |
|
||||
| `groups` | array | Não | Grupos de usuários para controle de acesso |
|
||||
| `properties` | array | Não | Propriedades configuráveis do módulo |
|
||||
| `menu` | array | Não | Árvore de navegação do menu |
|
||||
| `permissions` | array | Não | Permissões de grupo por processo |
|
||||
| `patches` | string | Não | Caminho para o diretório de changesets do Liquibase, ex.: `"patches/"` |
|
||||
|
||||
### metadata
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "My Module",
|
||||
"key": "my-module",
|
||||
"standardProduct": false
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Obrigatório | Descrição |
|
||||
|-------|-------------|-----------|
|
||||
| `key` | **Sim** | Identificador único do módulo. Deve ser único em toda a plataforma. |
|
||||
| `name` | Não | Nome de exibição legível |
|
||||
| `standardProduct` | Não | Marca como produto padrão da plataforma (padrão: `false`) |
|
||||
|
||||
---
|
||||
|
||||
## Referência de artefatos
|
||||
|
||||
### panels
|
||||
|
||||
Cada entrada de painel corresponde a uma tela de UI.
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `key` | string | Identificador único do painel |
|
||||
| `name` | string | Nome de exibição |
|
||||
| `description` | string | Descrição curta |
|
||||
| `category` | string | Caminho de categoria hierárquica, ex.: `"Comercial/Vendas"` |
|
||||
| `displayOrder` | int | Ordem de exibição dentro da categoria |
|
||||
| `showInPresentation` | boolean | Exibir no modo apresentação/kiosk |
|
||||
| `openInNewWindow` | boolean | Abrir em uma nova janela do navegador |
|
||||
| `showInMobileList` | boolean | Exibir na lista mobile |
|
||||
| `displayTimeInSeconds` | int | Tempo de rotação automática (0 = desativado) |
|
||||
| `allowedGroups` | string[] | Chaves dos grupos com permissão de visualizar este painel |
|
||||
| `allowedUsers` | string[] | Logins dos usuários com permissão de visualizar este painel |
|
||||
| `forms.desktop` | string | Caminho relativo para o XML do formulário desktop |
|
||||
| `forms.mobile` | string | Caminho relativo para o XML do formulário mobile |
|
||||
| `forms.mobileAlternative` | string | Caminho relativo para o XML do formulário mobile alternativo |
|
||||
| `defaultState` | string | Caminho relativo para um arquivo JSON com o estado padrão do painel |
|
||||
| `thumbnail` | string | Caminho relativo para uma imagem de miniatura |
|
||||
|
||||
---
|
||||
|
||||
### processes
|
||||
|
||||
Fluxos de trabalho baseados em BPMN.
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `key` | string | Identificador único do processo |
|
||||
| `name` | string | Nome de exibição |
|
||||
| `description` | string | Descrição curta |
|
||||
| `bpmn` | string | Caminho relativo para o arquivo XML do BPMN |
|
||||
| `forms.desktop` | string | Caminho relativo para o XML do formulário desktop |
|
||||
| `forms.mobile` | string | Caminho relativo para o XML do formulário mobile |
|
||||
| `forms.mobileAlternative` | string | Caminho relativo para o XML do formulário mobile alternativo |
|
||||
| `schedules` | array | Agendamentos de disparo automático (ver abaixo) |
|
||||
|
||||
**Tipos de gatilho para agendamento:**
|
||||
|
||||
```json
|
||||
{ "type": "cron", "expression": "0 0 0 * * ?" }
|
||||
{ "type": "simple", "intervalMs": 60000, "repeatCount": -1 }
|
||||
```
|
||||
|
||||
`repeatCount: -1` significa repetir indefinidamente.
|
||||
|
||||
---
|
||||
|
||||
### scripts
|
||||
|
||||
Scripts server-side reutilizáveis.
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `key` | string | Identificador único do script |
|
||||
| `name` | string | Nome de exibição |
|
||||
| `description` | string | Descrição curta |
|
||||
| `language` | string | Linguagem do script, ex.: `"javascript"` |
|
||||
| `domain` | string | Classificação: `"NEGOCIO"` ou `"SISTEMA"` |
|
||||
| `source` | string | Caminho relativo para o arquivo do script |
|
||||
| `documentation` | string | Caminho relativo para o arquivo de documentação opcional |
|
||||
|
||||
---
|
||||
|
||||
### endpoints
|
||||
|
||||
Endpoints HTTP expostos.
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `key` | string | Identificador único do endpoint |
|
||||
| `name` | string | Nome de exibição |
|
||||
| `description` | string | Descrição curta |
|
||||
| `language` | string | Linguagem do script, ex.: `"javascript"` |
|
||||
| `authMode` | string | `"PUBLIC"`, `"MOBILE"` ou `"STATIC_TOKEN"` |
|
||||
| `active` | boolean | Se o endpoint está ativo |
|
||||
| `source` | string | Caminho relativo para o arquivo do script do endpoint |
|
||||
|
||||
---
|
||||
|
||||
### queries
|
||||
|
||||
Queries SQL nomeadas que podem ser referenciadas por relatórios.
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `key` | string | Identificador único da query |
|
||||
| `name` | string | Nome de exibição |
|
||||
| `connection` | string | Chave da conexão com o banco de dados |
|
||||
| `source` | string | Caminho relativo para o arquivo `.sql` |
|
||||
|
||||
---
|
||||
|
||||
### reports
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `key` | string | Identificador único do relatório |
|
||||
| `name` | string | Nome de exibição |
|
||||
| `description` | string | Descrição curta |
|
||||
| `type` | string | `"MODELO_ESTATICO"` ou `"DINAMICO_QUERY_SQL"` |
|
||||
| `category` | string | Caminho de categoria hierárquica |
|
||||
| `owner` | string | Chave do grupo proprietário do relatório |
|
||||
| `template` | string | Caminho relativo para o template `.jrxml` |
|
||||
| `parameterForm` | string | Caminho relativo para o XML do formulário de parâmetros |
|
||||
| `query` | string | Referencia uma `QueryManifestEntry.key` |
|
||||
| `orientation` | string | `"RETRATO"` ou `"PAISAGEM"` |
|
||||
| `allowedGroups` | string[] | Chaves dos grupos com permissão de executar este relatório |
|
||||
| `allowedUsers` | string[] | Logins dos usuários com permissão de executar este relatório |
|
||||
| `columns` | array | Definições de colunas (label, alinhamento, largura, agregação) |
|
||||
|
||||
**Valores de agregação de coluna:** `"SUM"`, `"COUNT"`, `"AVG"` ou `null`.
|
||||
|
||||
---
|
||||
|
||||
### libraries
|
||||
|
||||
Pacotes de arquivos estáticos servidos para o front-end.
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `key` | string | Identificador único da biblioteca |
|
||||
| `name` | string | Nome de exibição |
|
||||
| `type` | string | `"LOCAL"` |
|
||||
| `authMode` | string | `"PUBLIC"`, `"MOBILE"` ou `"STATIC_TOKEN"` |
|
||||
| `authToken` | string | Obrigatório quando `authMode` for `"STATIC_TOKEN"` |
|
||||
| `mobileEnabled` | boolean | Se a biblioteca é servida para clientes mobile |
|
||||
| `files` | string | Caminho relativo para o diretório contendo os arquivos da biblioteca |
|
||||
|
||||
---
|
||||
|
||||
### groups
|
||||
|
||||
Grupos de usuários utilizados para controle de acesso em painéis, relatórios e permissões.
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `key` | string | Identificador único do grupo |
|
||||
| `name` | string | Nome de exibição |
|
||||
| `description` | string | Descrição curta |
|
||||
| `tag` | string | Tag opcional para filtragem |
|
||||
|
||||
---
|
||||
|
||||
### properties
|
||||
|
||||
Propriedades configuráveis no nível do módulo, editáveis em tempo de execução.
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `key` | string | Chave da propriedade utilizada no código |
|
||||
| `displayName` | string | Label legível |
|
||||
| `description` | string | Descrição curta |
|
||||
| `type` | string | `"STRING"`, `"INTEGER"`, `"BOOLEAN"`, `"DATE"`, etc. |
|
||||
| `size` | int | Tamanho máximo em caracteres (para STRING) |
|
||||
| `precision` | int | Precisão decimal (para tipos numéricos) |
|
||||
| `format` | string | Máscara de formato opcional |
|
||||
| `required` | boolean | Se um valor deve obrigatoriamente ser definido |
|
||||
| `password` | boolean | Se o valor deve ser mascarado na UI |
|
||||
| `predefinedValues` | array | Valores permitidos: `{ "key": "...", "descricao": "...", "ordem": 1 }` |
|
||||
|
||||
---
|
||||
|
||||
### menu
|
||||
|
||||
Árvore de navegação hierárquica. Itens podem ser aninhados usando `children`.
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `key` | string | Identificador único do item de menu |
|
||||
| `name` | string | Label de exibição |
|
||||
| `icon` | int | Código do ícone (definido pela plataforma) |
|
||||
| `order` | int | Ordem de exibição entre os irmãos |
|
||||
| `type` | string | `"PANEL"`, `"REPORT"`, `"PROCESS"` ou `"GROUP"` |
|
||||
| `panelKey` | string | Referencia uma `PanelManifestEntry.key` (quando type for `"PANEL"`) |
|
||||
| `reportKey` | string | Referencia uma `ReportManifestEntry.key` (quando type for `"REPORT"`) |
|
||||
| `processKey` | string | Referencia uma `ProcessManifestEntry.key` (quando type for `"PROCESS"`) |
|
||||
| `children` | array | Itens de menu aninhados (para o tipo `"GROUP"`) |
|
||||
|
||||
---
|
||||
|
||||
### permissions
|
||||
|
||||
Define o que um grupo pode fazer dentro de um processo.
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `processKey` | string | Referencia uma `ProcessManifestEntry.key` |
|
||||
| `group` | string | Referencia uma `GroupManifestEntry.key` |
|
||||
| `read` | boolean | Pode visualizar instâncias do processo |
|
||||
| `writeStages` | boolean | Pode avançar/concluir etapas |
|
||||
| `cancel` | boolean | Pode cancelar instâncias |
|
||||
| `delete` | boolean | Pode excluir instâncias |
|
||||
| `stageStatus` | boolean | Pode alterar o status de etapas |
|
||||
|
||||
---
|
||||
|
||||
## Estrutura de diretórios sugerida
|
||||
|
||||
```
|
||||
repo-root/
|
||||
├── vitruvio.json
|
||||
├── panels/
|
||||
│ └── my-panel/
|
||||
│ ├── my-panel-desktop.xml
|
||||
│ ├── my-panel-mobile.xml
|
||||
│ ├── default-state.json
|
||||
│ └── thumbnail.png
|
||||
├── processes/
|
||||
│ └── my-process/
|
||||
│ ├── my-process.bpmn
|
||||
│ ├── my-process-desktop.xml
|
||||
│ └── my-process-mobile.xml
|
||||
├── scripts/
|
||||
│ └── my-script.js
|
||||
├── endpoints/
|
||||
│ └── my-endpoint.js
|
||||
├── queries/
|
||||
│ └── my-query.sql
|
||||
├── reports/
|
||||
│ └── my-report/
|
||||
│ ├── template.jrxml
|
||||
│ └── params.xml
|
||||
├── libraries/
|
||||
│ └── my-library/
|
||||
└── patches/
|
||||
└── changelog.xml
|
||||
```
|
||||
|
||||
Todos os caminhos no `vitruvio.json` devem ser **relativos à raiz do repositório**.
|
||||
@@ -0,0 +1,252 @@
|
||||
# Endpoints
|
||||
|
||||
Endpoints are REST WebServices written in ES5 JavaScript, executed on the Rhino engine. Each file must export a single `WebService` instance. Vitruvio maps incoming HTTP requests to the corresponding handler method.
|
||||
|
||||
---
|
||||
|
||||
## vitruvio.json Registration
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "my-endpoint",
|
||||
"name": "My Endpoint",
|
||||
"description": "What this endpoint does.",
|
||||
"language": "javascript",
|
||||
"authMode": "PUBLIC",
|
||||
"active": true,
|
||||
"source": "endpoints/my-endpoint.js"
|
||||
}
|
||||
```
|
||||
|
||||
- `key` — kebab-case, stable. Changing it breaks any external integrations pointing to the URL.
|
||||
- `authMode` — one of `"PUBLIC"`, `"STATIC_TOKEN"`, `"VITRUVIO_WS_USER_AUTH"`, `"HTTP_BASIC_AUTH"`. Default to `"PUBLIC"`.
|
||||
- `active` — must be `true` for the endpoint to be reachable. Set to `false` to disable without removing.
|
||||
|
||||
### URL structure
|
||||
|
||||
The `authMode` determines the URL segment used to reach the endpoint:
|
||||
|
||||
| authMode | URL |
|
||||
|---|---|
|
||||
| `PUBLIC` | `/api/integration/public/{key}` |
|
||||
| `STATIC_TOKEN` | `/api/integration/tokenauth/{key}` |
|
||||
| `VITRUVIO_WS_USER_AUTH` | `/api/integration/bearerauth/{key}` |
|
||||
| `HTTP_BASIC_AUTH` | `/api/integration/bauth/{key}` |
|
||||
|
||||
### STATIC_TOKEN authentication
|
||||
|
||||
The token can be sent in either of two ways:
|
||||
- Query parameter: `_x_token_auth=<token>`
|
||||
- Request header: `X-WS-TOKEN-AUTH: <token>`
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```javascript
|
||||
function WebService() {
|
||||
|
||||
this.onGet = function(params) { ... }
|
||||
this.onPost = function(params) { ... }
|
||||
this.onPut = function(params) { ... }
|
||||
this.onPatch = function(params) { ... }
|
||||
this.onDelete = function(params) { ... }
|
||||
}
|
||||
|
||||
module.exports = new WebService();
|
||||
```
|
||||
|
||||
- All methods are **optional**. Only implement the verbs your endpoint actually handles.
|
||||
- Remove unused verb stubs entirely — don't leave placeholder `return JSON.stringify({ prop: value })` bodies in production code.
|
||||
- `module.exports` must be `new WebService()` — Vitruvio instantiates and reads from this exported object.
|
||||
- If a caller sends a verb that is not implemented in the script, Vitruvio throws `EndpointMethodNotImplementedException` — it is not a silent 404.
|
||||
|
||||
---
|
||||
|
||||
## The `params` Object
|
||||
|
||||
Each handler receives a single `params` argument:
|
||||
|
||||
```javascript
|
||||
// GET / DELETE
|
||||
params = {
|
||||
headers: {}, // request headers as key/value
|
||||
query: {} // query string params as key/value e.g. params.query["hub.mode"]
|
||||
}
|
||||
|
||||
// POST / PUT / PATCH
|
||||
params = {
|
||||
requestBody: '', // raw request body string — always JSON.parse() before use
|
||||
headers: {},
|
||||
query: {}
|
||||
}
|
||||
```
|
||||
|
||||
**Always parse the body explicitly:**
|
||||
```javascript
|
||||
this.onPost = function(params) {
|
||||
var body = JSON.parse(params.requestBody);
|
||||
// use body.*
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Return Values
|
||||
|
||||
| Return value | HTTP response |
|
||||
|---|---|
|
||||
| A non-empty string | `200 OK` with that string as body |
|
||||
| `null`, no return, or empty string `""` | `204 No Content` |
|
||||
| `JSON.stringify(obj)` | `200 OK` with JSON body — set Content-Type accordingly |
|
||||
|
||||
Always return strings. Returning a raw object will not serialize correctly.
|
||||
|
||||
```javascript
|
||||
// Correct
|
||||
return JSON.stringify({ status: 'ok', data: result });
|
||||
|
||||
// Wrong — object will not be serialized properly
|
||||
return { status: 'ok' };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
Wrap handler logic in `try/catch` and return a JSON error body. Don't let uncaught exceptions bubble up.
|
||||
|
||||
```javascript
|
||||
this.onPost = function(params) {
|
||||
try {
|
||||
var body = JSON.parse(params.requestBody);
|
||||
if (!body.id) throw "Missing required field: id";
|
||||
// ...
|
||||
return JSON.stringify({ success: true });
|
||||
} catch (e) {
|
||||
return JSON.stringify({ error: e.toString() });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
- Use `e.toString()` when serializing caught exceptions — raw exception objects may not stringify correctly under Rhino.
|
||||
- Validate required fields early and throw descriptive messages.
|
||||
|
||||
---
|
||||
|
||||
## DELETE Convention
|
||||
|
||||
By convention, DELETE requests should not receive a body and should not return data. If nothing is returned, Vitruvio responds with `204 No Content`.
|
||||
|
||||
```javascript
|
||||
this.onDelete = function(params) {
|
||||
// perform deletion
|
||||
// no return needed — 204 is implied
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Loading Scripts and Libraries
|
||||
|
||||
Use `libService.loadScript(key)` to load scripts registered in `vitruvio.json`. The `key` is the script's `key` field in the manifest.
|
||||
|
||||
```javascript
|
||||
// Load inside the handler — safe, always fresh
|
||||
this.onPost = function(params) {
|
||||
var lib = libService.loadScript('my-lib');
|
||||
lib.doSomething();
|
||||
}
|
||||
|
||||
// Load in the constructor — shared across all calls to this endpoint instance
|
||||
function WebService() {
|
||||
var lib = libService.loadScript('my-lib');
|
||||
|
||||
this.onPost = function(params) {
|
||||
lib.doSomething();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Prefer constructor-level loading** when the same library is used in multiple handlers — avoids redundant loads. Use handler-level loading when the script key is dynamic or determined at runtime.
|
||||
|
||||
---
|
||||
|
||||
## Database Access
|
||||
|
||||
Load the `db` library and instantiate with a datasource name:
|
||||
|
||||
```javascript
|
||||
var db = libService.loadScript('db');
|
||||
var banco = new db(db.VITRUVIO_DATASOURCE); // default Vitruvio datasource
|
||||
// or
|
||||
var banco = new db('named-datasource');
|
||||
```
|
||||
|
||||
Common operations:
|
||||
|
||||
```javascript
|
||||
// Single row
|
||||
var row = banco.queryRow("SELECT NAME FROM TB_CONFIG WHERE ID = :id", { id: 1 });
|
||||
|
||||
// Multiple rows
|
||||
var rows = banco.query("SELECT * FROM TB_ITEMS WHERE ACTIVE = 1");
|
||||
|
||||
// Update / Insert
|
||||
banco.update("UPDATE TB_ITEMS SET STATUS = :status WHERE ID = :id", {
|
||||
status: 'DONE',
|
||||
id: itemId
|
||||
});
|
||||
```
|
||||
|
||||
- Prefer named queries from `queries/*.sql` for reusable or complex SQL. Use inline SQL only for simple, endpoint-specific queries.
|
||||
- Use named bind parameters (`:paramName`) — never concatenate user input into SQL strings.
|
||||
|
||||
---
|
||||
|
||||
## Logged User (Authenticated Modes)
|
||||
|
||||
For `VITRUVIO_WS_USER_AUTH` and `HTTP_BASIC_AUTH`, Vitruvio resolves the caller's identity and injects a `loggedUser` object into the script context. This gives the endpoint access to who is making the call.
|
||||
|
||||
`HTTP_BASIC_AUTH` additionally validates that the authenticated user belongs to the roles configured for that endpoint in Vitruvio's admin.
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Webhook with verification (e.g. Meta/WhatsApp)
|
||||
```javascript
|
||||
this.onGet = function(params) {
|
||||
var mode = params.query["hub.mode"];
|
||||
var token = params.query["hub.verify_token"];
|
||||
var challenge = params.query["hub.challenge"];
|
||||
|
||||
if (mode === "subscribe" && token === EXPECTED_TOKEN) {
|
||||
return challenge;
|
||||
}
|
||||
return JSON.stringify({ error: "Forbidden" });
|
||||
};
|
||||
```
|
||||
|
||||
### Delegating to a script
|
||||
```javascript
|
||||
this.onPost = function(params) {
|
||||
try {
|
||||
var body = JSON.parse(params.requestBody);
|
||||
var lib = libService.loadScript('my-processing-script');
|
||||
var result = lib.run(body);
|
||||
return JSON.stringify(result);
|
||||
} catch (e) {
|
||||
return JSON.stringify({ error: e.toString() });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What NOT to do
|
||||
|
||||
- Don't leave placeholder bodies (`return JSON.stringify({ prop: value })`) in unused verb stubs — remove the whole method instead.
|
||||
- Don't return raw exception objects — always call `.toString()` or wrap in a message.
|
||||
- Don't hardcode datasource names or tokens in the script — read them from `vConfigService` or from a DB config table.
|
||||
- Don't put heavy business logic directly in the endpoint file — delegate to a script via `libService.loadScript`.
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
preset: '@davinti/vitruvio-test-utils',
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
testMatch: ['<rootDir>/tests/**/*.e2e.js'],
|
||||
globalSetup: './tests/global-setup.js',
|
||||
testTimeout: 60000,
|
||||
};
|
||||
Generated
+3876
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "projeto-base",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"test:e2e": "jest --config jest.e2e.config.js",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@davinti/vitruvio-core-libs": "latest",
|
||||
"@davinti/vitruvio-test-utils": "latest",
|
||||
"@types/jest": "^29.5.14",
|
||||
"jest": "^29",
|
||||
"pg": "^8.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,673 @@
|
||||
# Panels
|
||||
|
||||
Panels are UI screens defined entirely in XML. Vitruvio parses the XML against its XSD schema and renders Vaadin 7 components through a Java presenter layer — no Java UI code lives here.
|
||||
|
||||
---
|
||||
|
||||
## vitruvio.json Registration
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "my-panel",
|
||||
"name": "My Panel",
|
||||
"description": "Short description.",
|
||||
"category": "Category/Subcategory",
|
||||
"displayOrder": 10,
|
||||
"showInPresentation": false,
|
||||
"openInNewWindow": false,
|
||||
"showInMobileList": false,
|
||||
"displayTimeInSeconds": 0,
|
||||
"allowedGroups": ["group-key"],
|
||||
"allowedUsers": [],
|
||||
"forms": {
|
||||
"desktop": "panels/my-panel/my-panel-desktop.xml",
|
||||
"mobile": "panels/my-panel/my-panel-mobile.xml"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `displayOrder` — use gaps (10, 20, 30…) to allow future insertions.
|
||||
- `forms.mobile` is optional. Omit if there is no mobile form.
|
||||
- `allowedGroups` and `allowedUsers` are additive — a user in any allowed group or listed directly gets access.
|
||||
|
||||
---
|
||||
|
||||
## Desktop Form — File Structure
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/panel"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/panel
|
||||
https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-panel-form.xsd">
|
||||
|
||||
<form formKey="uniqueFormKey" width="100%" height="100%">
|
||||
<name>Human-readable title</name>
|
||||
<description>Short description.</description>
|
||||
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
// called when the panel loads
|
||||
}
|
||||
]]>
|
||||
</initScript>
|
||||
|
||||
<components>
|
||||
<!-- root layout component -->
|
||||
</components>
|
||||
</form>
|
||||
</panel-form>
|
||||
```
|
||||
|
||||
- `formKey` must be unique within the panel. By convention it matches the panel key.
|
||||
- `<initScript>` runs once when the panel is opened. The `run()` function is the entry point.
|
||||
- Everything in `<components>` is the Vaadin component tree.
|
||||
|
||||
---
|
||||
|
||||
## Mobile Form — File Structure
|
||||
|
||||
Mobile forms use a different namespace and XSD, and support a `<ServerSide>` block for server-executed bridge functions.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/mobile/panel"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/mobile/panel
|
||||
https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-mobile-panel-form.xsd">
|
||||
|
||||
<form formKey="uniqueFormKey">
|
||||
<name>Title</name>
|
||||
<description></description>
|
||||
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() { ... }
|
||||
]]>
|
||||
</initScript>
|
||||
|
||||
<ServerSide>
|
||||
<DataSources>
|
||||
<SQLBuilderScriptDataSource id="dsName" connection-key="datasource-key"
|
||||
language="JavaScript" workOnline="true">
|
||||
<![CDATA[
|
||||
function buildSQL(requestParams, sql_params) {
|
||||
return "SELECT ...";
|
||||
}
|
||||
]]>
|
||||
</SQLBuilderScriptDataSource>
|
||||
</DataSources>
|
||||
|
||||
<Bridges>
|
||||
<Bridge id="myBridge" language="JavaScript">
|
||||
<![CDATA[
|
||||
function execute(params) {
|
||||
// runs on the server — full access to libService, db, etc.
|
||||
return { result: ... };
|
||||
}
|
||||
]]>
|
||||
</Bridge>
|
||||
</Bridges>
|
||||
</ServerSide>
|
||||
|
||||
<components>
|
||||
<!-- mobile component tree -->
|
||||
</components>
|
||||
</form>
|
||||
</panel-form>
|
||||
```
|
||||
|
||||
### Calling bridges from mobile client scripts
|
||||
```javascript
|
||||
vCommunicationService.executeOnServer('myBridge', params).then(function(result) {
|
||||
// handle result
|
||||
}).catch(function(error) {
|
||||
console.log('Error: ' + error);
|
||||
});
|
||||
```
|
||||
|
||||
### Mobile timers
|
||||
```javascript
|
||||
engine.startTimer('timerName', function() {
|
||||
// runs every intervalMs
|
||||
}, intervalMs);
|
||||
|
||||
engine.stopTimer('timerName');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The `engine` API (Desktop)
|
||||
|
||||
`engine` is injected into all script contexts inside a panel.
|
||||
|
||||
### Fields
|
||||
```javascript
|
||||
var field = engine.getField('fieldId');
|
||||
field.getValue(); // returns null when empty; for DB-backed fields returns a Java type — use == not === when comparing
|
||||
field.setValue(value);
|
||||
field.clear();
|
||||
field.setEnabled(true/false);
|
||||
field.setVisible(true/false);
|
||||
field.setRequired(true/false);
|
||||
field.focus();
|
||||
field.refresh(); // DB components — re-runs the datasource query
|
||||
```
|
||||
|
||||
### Layouts
|
||||
```javascript
|
||||
var layout = engine.getLayout('layoutId');
|
||||
layout.getRootComposition().setVisible(true/false);
|
||||
layout.getRootComposition().setCaption('New caption');
|
||||
layout.getRootComposition().removeAllComponents();
|
||||
layout.getRootComposition().addComponent(component);
|
||||
layout.getRootComposition().setScrollTop(100000); // scroll to bottom
|
||||
|
||||
// WindowLayout only
|
||||
layout.showWindow();
|
||||
layout.closeWindow();
|
||||
```
|
||||
|
||||
### User & Session
|
||||
```javascript
|
||||
var user = engine.getLoggedUser();
|
||||
user.getLogin(); // username string
|
||||
user.getNome(); // display name (desktop)
|
||||
```
|
||||
|
||||
### Global variables — sharing state across components
|
||||
```javascript
|
||||
// Set in initScript or any handler
|
||||
engine.setGlobalVariable('myHelper', function() { ... });
|
||||
engine.setGlobalVariable('myData', { key: 'value' });
|
||||
|
||||
// Read anywhere in the form
|
||||
engine.getGlobalVariable('myHelper')();
|
||||
var data = engine.getGlobalVariable('myData');
|
||||
|
||||
// Clean up
|
||||
engine.unsetGlobalVariable('myVar');
|
||||
```
|
||||
|
||||
Use global variables to expose reusable functions (e.g. `limparCampos`, `carregarDados`) and shared state across components and events.
|
||||
|
||||
### Form validation
|
||||
```javascript
|
||||
if (engine.isValid()) {
|
||||
// all required fields are filled
|
||||
}
|
||||
```
|
||||
|
||||
### Repeating timers (desktop polling)
|
||||
```javascript
|
||||
var timer = engine.registerRepeatingTimer(3000, function() {
|
||||
// do something every 3s
|
||||
// return true to keep running, false/undefined to stop
|
||||
});
|
||||
engine.setGlobalVariable('myTimer', timer);
|
||||
|
||||
// Later, to stop it:
|
||||
engine.getGlobalVariable('myTimer').setEnabled(false);
|
||||
engine.unsetGlobalVariable('myTimer');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Script Blocks and Events
|
||||
|
||||
### initScript
|
||||
Runs when the panel loads. Define helper functions here and expose them via `setGlobalVariable` so other components can call them.
|
||||
|
||||
```xml
|
||||
<initScript language="JavaScript">
|
||||
<![CDATA[
|
||||
var db = libService.loadScript('db');
|
||||
var banco = new db(db.VITRUVIO_DATASOURCE);
|
||||
|
||||
function carregarDados() { ... }
|
||||
|
||||
function run() {
|
||||
engine.setGlobalVariable('carregarDados', carregarDados);
|
||||
carregarDados();
|
||||
}
|
||||
]]>
|
||||
</initScript>
|
||||
```
|
||||
|
||||
### Button click
|
||||
```xml
|
||||
<ButtonWidget id="btnSalvar" caption="Salvar" defaultIcon="SAVE" style="GREEN">
|
||||
<onClickScript language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
// handle click
|
||||
}
|
||||
]]>
|
||||
</onClickScript>
|
||||
</ButtonWidget>
|
||||
```
|
||||
|
||||
### Field value change
|
||||
```xml
|
||||
<TextField id="txfPesquisa" type="string" caption="Pesquisar" immediate="true">
|
||||
<events>
|
||||
<valueChange>
|
||||
<script language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
engine.getField('myTable').refresh();
|
||||
}
|
||||
]]>
|
||||
</script>
|
||||
</valueChange>
|
||||
</events>
|
||||
</TextField>
|
||||
```
|
||||
|
||||
`immediate="true"` is required for `valueChange` to fire on every keystroke.
|
||||
|
||||
---
|
||||
|
||||
## Layout Components
|
||||
|
||||
### VerticalLayout / HorizontalLayout
|
||||
```xml
|
||||
<VerticalLayout width="100%" height="100%" spacing="true" margin="true"
|
||||
id="myLayout" align="TOP_LEFT" expandRatio="1" visible="true"
|
||||
backgroundColor="#f8b916">
|
||||
```
|
||||
|
||||
**Sizing fields inside `layouts`:** always prefer `width="100%"` combined with `expandRatio` instead of fixed pixel widths. `expandRatio` controls the proportional share of the available space each child receives.
|
||||
```xml
|
||||
<HorizontalLayout width="100%" spacing="true">
|
||||
<NumericField id="nmfId" width="100%" expandRatio="1" ... />
|
||||
<TextField id="txfNome" width="100%" expandRatio="3" ... />
|
||||
<ButtonWidget id="btnAcao" width="100%" expandRatio="1" ... />
|
||||
</HorizontalLayout>
|
||||
```
|
||||
|
||||
### Panel
|
||||
A styled container with an optional caption and background color.
|
||||
```xml
|
||||
<Panel width="100%" height="100%" caption="Section title" backgroundColor="#f0f0f0" margin="true">
|
||||
<VerticalLayout>...</VerticalLayout>
|
||||
</Panel>
|
||||
```
|
||||
|
||||
### CrudPanel
|
||||
The standard page chrome: header bar + section content area.
|
||||
|
||||
> Validators will reject it as a root element. Always wrap it in a Layout element like `VerticalLayout`, `HorizontalLayout`, `TabLayout`, or `AbsoluteLayout` as the root component inside `<components>`.
|
||||
|
||||
```xml
|
||||
<VerticalLayout width="100%" height="100%">
|
||||
<CrudPanel width="100%" height="100%" expandRatio="1">
|
||||
<Header>
|
||||
<Caption>Page Title</Caption>
|
||||
<SubCaption>Subtitle text</SubCaption>
|
||||
</Header>
|
||||
<Section caption="Tab label" subCaption="..." showHeader="false" width="100%" height="100%">
|
||||
<VerticalLayout width="100%" height="100%">
|
||||
<!-- content — Section requires a layout component (VerticalLayout, HorizontalLayout, etc.) as its direct child, not bare components -->
|
||||
</VerticalLayout>
|
||||
</Section>
|
||||
</CrudPanel>
|
||||
</VerticalLayout>
|
||||
```
|
||||
|
||||
### TabLayout
|
||||
```xml
|
||||
<TabLayout width="100%">
|
||||
<Tab caption="Tab 1">
|
||||
<!-- content -->
|
||||
</Tab>
|
||||
<Tab caption="Tab 2">
|
||||
<!-- content -->
|
||||
</Tab>
|
||||
</TabLayout>
|
||||
```
|
||||
|
||||
### ScrollPanel
|
||||
```xml
|
||||
<ScrollPanel id="scrollArea" width="100%" height="100%">
|
||||
<VerticalLayout id="content" width="100%">
|
||||
<!-- scrollable content -->
|
||||
</VerticalLayout>
|
||||
</ScrollPanel>
|
||||
```
|
||||
|
||||
### WindowLayout (modal)
|
||||
```xml
|
||||
<WindowLayout id="myModal" windowHeight="40%" windowWidth="40%"
|
||||
windowResizable="false" windowClosable="false" windowModal="true">
|
||||
<VerticalLayout width="100%" height="100%" margin="true">
|
||||
<!-- modal content -->
|
||||
</VerticalLayout>
|
||||
</WindowLayout>
|
||||
```
|
||||
Open/close via `engine.getLayout('myModal').showWindow()` / `.closeWindow()`.
|
||||
|
||||
---
|
||||
|
||||
## Input Components
|
||||
|
||||
### Common attributes
|
||||
| Attribute | Purpose |
|
||||
| ------------- | ---------------------------------------------- |
|
||||
| `id` | Required for `engine.getField()` access |
|
||||
| `caption` | Label above the component |
|
||||
| `width` | CSS width (`100%`, `200px`) |
|
||||
| `expandRatio` | Flex grow ratio within parent layout |
|
||||
| `visible` | `true`/`false` |
|
||||
| `required` | Marks field as required for `engine.isValid()` |
|
||||
| `enabled` | `true`/`false` |
|
||||
| `description` | Tooltip text |
|
||||
| `immediate` | `true` to fire events on every change |
|
||||
| `maxLength` | Max character count (TextField, TextArea) |
|
||||
|
||||
### TextField / TextArea / NumericField / DateField
|
||||
```xml
|
||||
<TextField id="txfNome" type="string" caption="Nome" width="100%" required="true" maxLength="100" />
|
||||
<TextArea id="txaDescricao" type="string" caption="Descrição" width="100%" />
|
||||
<NumericField id="nmfCodigo" type="number" caption="Código" width="100%" enabled="false" />
|
||||
<DateField id="dtInicio" type="date" caption="Data Início" resolution="DAY" width="100%" />
|
||||
```
|
||||
|
||||
### Label (HTML content)
|
||||
```xml
|
||||
<Label id="lblInfo" contentMode="HTML" align="MIDDLE_CENTER">
|
||||
<value>
|
||||
<![CDATA[
|
||||
<b style="font-size: 16px;">HTML content here</b>
|
||||
]]>
|
||||
</value>
|
||||
</Label>
|
||||
```
|
||||
|
||||
### ButtonWidget
|
||||
```xml
|
||||
<ButtonWidget id="btnAcao" caption="Label" defaultIcon="SAVE"
|
||||
style="GREEN" width="100%" height="30px" expandRatio="1" align="MIDDLE_RIGHT"
|
||||
keyCode="ENTER">
|
||||
```
|
||||
|
||||
`style` values: `BLUE`, `RED`, `GREEN`, `GRAY`
|
||||
`defaultIcon` values: `SAVE`, `ADD`, `EDIT`, `REMOVE`, `TRASH`, `SEARCH`, `BACK`, `SEND`, `ARROW_UP`, `ARROW_DOWN`
|
||||
|
||||
---
|
||||
|
||||
## DB Components
|
||||
|
||||
### DBTable
|
||||
The primary data grid. Fetches data from a SQL datasource.
|
||||
|
||||
```xml
|
||||
<DBTable id="dbtItems" type="number" immediate="true" width="100%"
|
||||
height="100%" expandRatio="1" caption="Items"
|
||||
showRowCount="true" drawRefreshButton="true" selectable="false"
|
||||
rows="10" multivalue="false" exportXLS="true" useGridComponent="true">
|
||||
|
||||
<datasource>
|
||||
<sqlBuilderDataSource connection-key="vitruvio" language="JavaScript">
|
||||
<![CDATA[
|
||||
function buildSQL(params) {
|
||||
// params is a Map<String, Object> — put values here and reference with ${name} in SQL.
|
||||
// Read form fields via engine (guard with if(engine) — datasource may run before form init).
|
||||
var search = engine ? engine.getField('txfPesquisa').getValue() : null;
|
||||
var searchValue = (search && String(search) != '') ? '%' + String(search) + '%' : '%';
|
||||
params.put('search', searchValue);
|
||||
return "SELECT * FROM MY_TABLE WHERE upper(NAME) LIKE upper(${search}) ORDER BY NAME";
|
||||
}
|
||||
]]>
|
||||
</sqlBuilderDataSource>
|
||||
</datasource>
|
||||
|
||||
<key-field>ID</key-field>
|
||||
|
||||
<columns>
|
||||
<column name="ID" caption="Código" expand-ratio=".1" />
|
||||
<column name="NAME" caption="Nome" expand-ratio=".6" />
|
||||
<generated name="edit" caption="Editar" expand-ratio=".1">
|
||||
<!-- see Generated Columns below -->
|
||||
</generated>
|
||||
</columns>
|
||||
|
||||
<bind>
|
||||
<parameter value-type="string" defaultValue="" parameterName="parFilter" field-ref="txfFilter" />
|
||||
</bind>
|
||||
|
||||
<events>
|
||||
<valueChange>
|
||||
<script language="JavaScript">
|
||||
<![CDATA[
|
||||
function run() {
|
||||
var id = engine.getField('dbtItems').getValue();
|
||||
// react to row selection
|
||||
}
|
||||
]]>
|
||||
</script>
|
||||
</valueChange>
|
||||
</events>
|
||||
|
||||
<styleGenerator>
|
||||
<scriptGenerator language="JavaScript">
|
||||
<![CDATA[
|
||||
function getStyle() {
|
||||
return 'background-light-gray';
|
||||
}
|
||||
]]>
|
||||
</scriptGenerator>
|
||||
</styleGenerator>
|
||||
|
||||
</DBTable>
|
||||
```
|
||||
|
||||
**Datasource variants:**
|
||||
- `<sqlBuilderDataSource>` — dynamic SQL built in JavaScript; `engine` is available inside
|
||||
- `<freeQuery connection-key="...">` — static SQL with `${paramName}` tokens bound via `<bind>`
|
||||
- `<form-datasource ref="dsId" />` — reference a `<DataSources>` definition (mobile)
|
||||
|
||||
**Parameter syntax — important distinction:**
|
||||
- `sqlBuilderDataSource` and `freeQuery`: use `${paramName}` in SQL, bound via `params.put()` or `<bind>`
|
||||
- `db` library scripts (inline SQL): use `:paramName` syntax instead
|
||||
|
||||
**Note:** In `sqlBuilderDataSource`, always guard with `if (engine)` before calling `engine.getField(...)` because the datasource may be evaluated before the form is fully initialised.
|
||||
|
||||
### Generated Columns (action buttons in table rows)
|
||||
```javascript
|
||||
var vc = libService.loadScript('vaadinComponents');
|
||||
|
||||
function Generator() {
|
||||
this.generate = function(itemId, columnId, item, container) {
|
||||
var btn = vc.buttonIcon('Editar', function() {
|
||||
var nome = item.getItemProperty('NAME').getValue();
|
||||
engine.getField('txfNome').setValue(nome);
|
||||
}, 'edit');
|
||||
return btn;
|
||||
}
|
||||
}
|
||||
var script = new Generator();
|
||||
```
|
||||
|
||||
For delete with confirmation dialog:
|
||||
```javascript
|
||||
var vc = libService.loadScript('vaadinComponents');
|
||||
importClass(Packages.br.com.davinti.base.vaadin.components.layout.ConfirmationBox);
|
||||
|
||||
function Generator() {
|
||||
this.generate = function(itemId, columnId, item, container) {
|
||||
var btn = vc.buttonIcon('Deletar', function() {
|
||||
var listener = new ConfirmationBox.ConfirmationBoxListener() {
|
||||
dialogEnd: function(context, action) {
|
||||
if (action == ConfirmationBox.Action.YES) {
|
||||
var db = libService.loadScript('db');
|
||||
var banco = new db(db.VITRUVIO_DATASOURCE);
|
||||
banco.update("DELETE FROM MY_TABLE WHERE ID = :id", { id: Number(itemId) });
|
||||
engine.getField('dbtItems').refresh();
|
||||
}
|
||||
}
|
||||
};
|
||||
ConfirmationBox.show(
|
||||
ConfirmationBox.DialogIcon.WARNING,
|
||||
'Confirmar exclusão',
|
||||
'Deseja excluir o item ' + item.getItemProperty('NAME').getValue() + '?',
|
||||
listener,
|
||||
ConfirmationBox.ACTION_YES_CANCEL
|
||||
);
|
||||
}, 'trash');
|
||||
return btn;
|
||||
}
|
||||
}
|
||||
var script = new Generator();
|
||||
```
|
||||
|
||||
### DBTwinColSelect (multi-select from DB)
|
||||
Multi-select widget populated from a query.
|
||||
|
||||
**Reading selected values:** `getValue()` on a `multivalue="true"` DBTwinColSelect returns a Java `Collection`, not a JS array. Use the Java iterator API:
|
||||
```javascript
|
||||
var selected = engine.getField('dbtwItems').getValue();
|
||||
if (selected != null) {
|
||||
var iter = selected.iterator();
|
||||
while (iter.hasNext()) {
|
||||
var item = String(iter.next());
|
||||
// process item
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Setting values programmatically:** use a Java `ArrayList`:
|
||||
```javascript
|
||||
var itens = new java.util.ArrayList();
|
||||
banco.query(sql, params).each(function(row) {
|
||||
itens.add(java.lang.Long.valueOf(row.ID));
|
||||
});
|
||||
engine.getField('dbtwItems').setValue(itens);
|
||||
```
|
||||
|
||||
### DBComboBox
|
||||
Single-select dropdown from a query. Use `.getValue()` / `.setValue(val)` / `.clear()`.
|
||||
|
||||
---
|
||||
|
||||
## Feedback to the User
|
||||
|
||||
### MessageBox (desktop)
|
||||
```javascript
|
||||
importClass(Packages.br.com.davinti.base.vaadin.components.layout.MessageBox);
|
||||
|
||||
MessageBox.show(MessageBox.BoxType.SUCESS, 'Sucesso', 'Saved.');
|
||||
MessageBox.show(MessageBox.BoxType.ERROR, 'Erro', 'Something failed.');
|
||||
MessageBox.show(MessageBox.BoxType.WARNING, 'Atenção', 'Please check...');
|
||||
```
|
||||
|
||||
Note: `BoxType.SUCESS` has no second S — that's the platform's spelling.
|
||||
|
||||
### Tray notification (non-blocking)
|
||||
```javascript
|
||||
var messages = libService.loadScript('messages');
|
||||
var n = messages.notification;
|
||||
n.show({
|
||||
caption: 'Saved',
|
||||
msg: 'Data saved successfully.',
|
||||
delay: 3,
|
||||
type: n.type.tray
|
||||
});
|
||||
```
|
||||
|
||||
### MessageBox (mobile)
|
||||
The mobile API is different:
|
||||
```javascript
|
||||
MessageBox.show('Title', 'Message text');
|
||||
MessageBox.showLoading('Loading...');
|
||||
MessageBox.hideLoading();
|
||||
MessageBox.confirm('Message', 'Title', [
|
||||
{ text: 'Sim', handler: function() { ... } },
|
||||
{ text: 'Não', handler: function() { ... } }
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Loading Scripts and Libraries
|
||||
|
||||
```javascript
|
||||
// In initScript (runs once, available to all handlers via globalVariable)
|
||||
var libIA = libService.loadScript('IA_lib');
|
||||
engine.setGlobalVariable('libIA', libIA);
|
||||
|
||||
// Inside a button or event handler
|
||||
var db = libService.loadScript('db');
|
||||
var banco = new db(db.VITRUVIO_DATASOURCE);
|
||||
```
|
||||
|
||||
The `vaadinComponents` lib is used heavily in generated columns to create dynamic Vaadin components programmatically (buttons, layouts, labels, etc.).
|
||||
|
||||
---
|
||||
|
||||
## Database in Panels
|
||||
|
||||
Same as in endpoints — see the root CLAUDE.md for `db` usage notes. Key extras for panels:
|
||||
|
||||
```javascript
|
||||
banco.getSequenceNextVal('MY_SEQUENCE'); // get next ID before insert
|
||||
banco.transaction(function() {
|
||||
this.update(sql1, params1);
|
||||
this.update(sql2, params2);
|
||||
});
|
||||
banco.isOracle(); // true if Oracle — use for DB-dialect differences (SYSDATE vs CURRENT_TIMESTAMP, etc.)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Field ID Naming Conventions
|
||||
|
||||
Prefix field IDs by component type for readability:
|
||||
|
||||
| Prefix | Component |
|
||||
| ------------- | --------------- |
|
||||
| `txf` | TextField |
|
||||
| `txa` | TextArea |
|
||||
| `ncf` / `nmf` | NumericField |
|
||||
| `dbt` | DBTable |
|
||||
| `dbb` | DBComboBox |
|
||||
| `dbtw` | DBTwinColSelect |
|
||||
| `btn` | ButtonWidget |
|
||||
| `chk` | CheckBox |
|
||||
| `lbl` | Label |
|
||||
| `dt` | DateField |
|
||||
| `cmb` | ComboBox |
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Show/hide navigation between views
|
||||
Use multiple layouts within the same form and toggle visibility:
|
||||
```javascript
|
||||
engine.getLayout('layoutForm').getRootComposition().setVisible(true);
|
||||
engine.getLayout('layoutList').getRootComposition().setVisible(false);
|
||||
engine.getLayout('sectionHeader').getRootComposition().setCaption('New Caption');
|
||||
```
|
||||
|
||||
### CRUD in a single form
|
||||
The recurring pattern across panels:
|
||||
1. **List view** (`layoutHome`) — DBTable + search field + "Add" button
|
||||
2. **Form view** (`layoutAddItem`) — fields + Save + Back buttons, initially `visible="false"`
|
||||
3. **initScript** exposes a `limparCampos` global that clears all form fields
|
||||
4. **Edit** button in DBTable populates fields and switches to form view
|
||||
5. **Save** checks `engine.isValid()`, upserts based on whether a key field has a value, then returns to list
|
||||
6. **Back** clears fields and returns to list
|
||||
|
||||
### Reading system config
|
||||
```javascript
|
||||
var valor = vConfigService.getSystemConfigAsString('CONFIG_KEY');
|
||||
```
|
||||
|
||||
### Getting the logged user in a script
|
||||
```javascript
|
||||
engine.getLoggedUser().getLogin() // desktop
|
||||
engine.getLoggedUser().getNome() // desktop display name
|
||||
engine.getLoggedUser().getName() // mobile display name
|
||||
```
|
||||
@@ -0,0 +1,54 @@
|
||||
# Patches
|
||||
|
||||
Liquibase database migration changelogs. Two subdirectories — one per supported database:
|
||||
|
||||
```
|
||||
patches/
|
||||
oracle/
|
||||
<timestamp>_<KEY>.xml
|
||||
postgresql/
|
||||
<timestamp>_<KEY>.xml
|
||||
```
|
||||
|
||||
The filename convention is `{YYYYMMDDHHmm}_{MODULE_KEY}.xml` (e.g. `202604081221_GO.xml`).
|
||||
|
||||
Both files must always be kept in sync — every changeset must appear in both.
|
||||
|
||||
## Rules
|
||||
|
||||
- **Append-only.** Never edit or delete existing `<changeSet>` entries. Liquibase tracks executed changesets by `id` + `author`; modifying them breaks the checksum and will fail deployment. You can change conditions and order tho.
|
||||
- **Unique IDs.** Each `<changeSet id="...">` must have a unique numeric ID within the file. Increment from the last existing one.
|
||||
- **Use preConditions.** Wrap DDL in `<preConditions onFail="MARK_RAN">` guards so the changelog is idempotent and safe to re-run.
|
||||
- **Oracle vs PostgreSQL syntax differs.** Sequences, data types, and quoting rules are different — write each file for its target DB. Don't copy-paste blindly between them.
|
||||
|
||||
## File skeleton
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd
|
||||
http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
|
||||
|
||||
<changeSet author="your.name" id="401" objectQuotingStrategy="LEGACY">
|
||||
<preConditions onError="WARN" onFail="MARK_RAN" onSqlOutput="IGNORE">
|
||||
<not>
|
||||
<tableExists tableName="my_table" />
|
||||
</not>
|
||||
</preConditions>
|
||||
<sql endDelimiter=";" splitStatements="true" stripComments="false">
|
||||
CREATE TABLE my_table ( ... );
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
```
|
||||
|
||||
## vitruvio.json registration
|
||||
|
||||
```json
|
||||
"patches": [
|
||||
{ "path": "patches" }
|
||||
]
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd
|
||||
http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
|
||||
|
||||
<changeSet author="nome.usuario" id="id_unico_changeset">
|
||||
<preConditions onError="WARN" onFail="MARK_RAN" onSqlOutput="IGNORE">
|
||||
<not>
|
||||
<tableExists tableName="go_setor" />
|
||||
</not>
|
||||
</preConditions>
|
||||
<sql endDelimiter=";" splitStatements="true" stripComments="false">CREATE TABLE tabela ()</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,166 @@
|
||||
# Processes
|
||||
|
||||
BPMN workflows powered by Activiti. Each process lives in its own subdirectory.
|
||||
|
||||
```
|
||||
processes/
|
||||
<process-key>/
|
||||
<process-key>.bpmn
|
||||
<process-key>-desktop.xml
|
||||
<process-key>-mobile.xml # optional when process involves mobile tasks.
|
||||
<process-key>-mobile-alt.xml # optional when the client uses two versions of the mobile with different APIs. User should point the need for that one out.
|
||||
```
|
||||
|
||||
The XML form rules, components, engine API, and JS constraints are the same as panels — see `panels/CLAUDE.md`. This file covers only the differences.
|
||||
|
||||
---
|
||||
|
||||
## vitruvio.json registration
|
||||
|
||||
```json
|
||||
"processes": [
|
||||
{
|
||||
"key": "my-process",
|
||||
"name": "My Process",
|
||||
"bpmn": "processes/my-process/my-process.bpmn",
|
||||
"forms": {
|
||||
"desktop": "processes/my-process/my-process-desktop.xml",
|
||||
"mobile": null,
|
||||
"mobileAlternative": null
|
||||
},
|
||||
"schedules": [
|
||||
{
|
||||
"name": "Daily trigger",
|
||||
"trigger": { "type": "cron", "expression": "0 0 0 * * ?" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
`schedules` is optional. Trigger types: `"cron"` (Quartz expression) or `"simple"` (`intervalMs` + `repeatCount: -1` for indefinite).
|
||||
|
||||
---
|
||||
|
||||
## Form XML — key differences from panels
|
||||
|
||||
The root element is `<forms>` (not `<form>`), with a `processKey` attribute matching the BPMN process id:
|
||||
|
||||
```xml
|
||||
<forms xmlns="http://www.davinti.com.br/vitruvio/form"
|
||||
xmlns:xsi="..."
|
||||
xsi:schemaLocation="..."
|
||||
processKey="my-process">
|
||||
|
||||
<library>
|
||||
<!-- reusable complex-component definitions -->
|
||||
</library>
|
||||
|
||||
<form formKey="formAbertura" width="100%">
|
||||
<!-- components for the start event / opening task -->
|
||||
</form>
|
||||
|
||||
<form formKey="formAprovacao" width="100%">
|
||||
<!-- components for approval task -->
|
||||
</form>
|
||||
|
||||
</forms>
|
||||
```
|
||||
|
||||
### `<library>`
|
||||
|
||||
Define reusable `<complex-component>` blocks here and reference them by id in task forms. Avoids repeating the same layout across multiple task forms.
|
||||
|
||||
### `<form formKey="...">`
|
||||
|
||||
Each `<form>` corresponds to one BPMN user task (via `activiti:formKey`). The keys must match exactly. One process can have many task forms in the same XML file.
|
||||
|
||||
---
|
||||
|
||||
## BPMN — key attributes
|
||||
|
||||
### User tasks
|
||||
```xml
|
||||
<bpmn2:userTask id="Task_Approve" name="Approve"
|
||||
activiti:formKey="formAprovacao"
|
||||
activiti:candidateGroups="group-key"
|
||||
activiti:assignee="${someVar}">
|
||||
```
|
||||
|
||||
- `activiti:formKey` — must match a `<form formKey="...">` in the desktop/mobile XML.
|
||||
- `activiti:candidateGroups` — group key(s) that can claim this task.
|
||||
- `activiti:assignee` — specific user expression (optional, overrides candidate groups).
|
||||
|
||||
### Start event
|
||||
```xml
|
||||
<bpmn2:startEvent id="start" activiti:formKey="formAbertura" activiti:initiator="owner">
|
||||
```
|
||||
|
||||
`activiti:initiator="owner"` stores the login of whoever opened the process into the `owner` process variable.
|
||||
|
||||
### Sequence flow conditions
|
||||
Conditions reference process variables using JUEL syntax:
|
||||
```xml
|
||||
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">
|
||||
#{formAbertura_aprovado=='1'}
|
||||
</bpmn2:conditionExpression>
|
||||
```
|
||||
|
||||
Process variables are set by the form engine automatically following the convention `{formKey}_{fieldId}` when a task is completed. They can also be set explicitly in script tasks via the `execution` object.
|
||||
|
||||
### Script tasks
|
||||
```xml
|
||||
<bpmn2:scriptTask id="myScript" name="Do something" scriptFormat="javascript">
|
||||
<bpmn2:script>
|
||||
var lib = vScriptService.loadScript('my-lib', 'javascript');
|
||||
var obj = new lib.MyClass();
|
||||
obj.doWork(execution);
|
||||
</bpmn2:script>
|
||||
</bpmn2:scriptTask>
|
||||
```
|
||||
|
||||
- Use `vScriptService.loadScript(key, 'javascript')` — **not** `libService.loadScript()`.
|
||||
- `execution` is the Activiti execution context. Use it to read/write process variables:
|
||||
```javascript
|
||||
execution.getVariable('myVar');
|
||||
execution.setVariable('myVar', value);
|
||||
```
|
||||
|
||||
### Task listeners
|
||||
Listeners run on task lifecycle events (`create`, `complete`, `assignment`):
|
||||
```xml
|
||||
<activiti:taskListener class="org.activiti.engine.impl.bpmn.listener.ScriptTaskListener" event="create">
|
||||
<activiti:field name="language"><activiti:string>javascript</activiti:string></activiti:field>
|
||||
<activiti:field name="script">
|
||||
<activiti:string>
|
||||
var lib = vScriptService.loadScript('my-lib', 'javascript');
|
||||
lib.onTaskCreate(task);
|
||||
</activiti:string>
|
||||
</activiti:field>
|
||||
</activiti:taskListener>
|
||||
```
|
||||
|
||||
`task` is available in listeners (not `execution`). Use `task.getExecution()` if you need the execution context.
|
||||
|
||||
---
|
||||
|
||||
## Engine extras in process forms
|
||||
|
||||
These are available in addition to the standard panel engine API:
|
||||
|
||||
| Method | Description |
|
||||
| ------------------------- | ---------------------------------------------------------------------- |
|
||||
| `engine.formKey()` | Returns the `formKey` of the currently active task form |
|
||||
| `engine.isTaskComplete()` | Returns `true` if the task has already been completed (read-only view) |
|
||||
|
||||
Use `engine.formKey()` to share `initScript` logic across multiple task forms with different initialization paths:
|
||||
|
||||
```javascript
|
||||
function run() {
|
||||
if (engine.formKey() == 'formAbertura') {
|
||||
// init for opening form
|
||||
} else if (engine.formKey() == 'formAprovacao') {
|
||||
// init for approval form
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
# Queries
|
||||
|
||||
Named SQL queries registered in `vitruvio.json`. Plain `.sql` files — one query per file.
|
||||
|
||||
Used as datasources for reports, DBTable components, and referenced by key in scripts via the `db` lib.
|
||||
|
||||
## Rules
|
||||
|
||||
- One `SELECT` per file. No multiple statements, no DDL.
|
||||
- Named bind parameters use `:paramName` syntax (same as inline SQL in scripts).
|
||||
- Write ANSI SQL where possible. If DB-specific syntax is unavoidable, keep both Oracle and PostgreSQL in mind — or note the limitation.
|
||||
- Queries should be read-only. For INSERT/UPDATE/DELETE, use inline SQL in scripts.
|
||||
|
||||
## vitruvio.json registration
|
||||
|
||||
```json
|
||||
"queries": [
|
||||
{
|
||||
"key": "my-query",
|
||||
"name": "My Query",
|
||||
"source": "queries/my-query.sql",
|
||||
"connection": "vitruvio_producao"
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -0,0 +1,33 @@
|
||||
# Reports
|
||||
|
||||
Jasper Reports templates. Each report lives directly under `reports/` (flat — no subdirectory per report).
|
||||
|
||||
## Files per report
|
||||
|
||||
| File | Required | Purpose |
|
||||
|------|----------|---------|
|
||||
| `<key>.jrxml` | yes | Jasper report template |
|
||||
| `<key>-params.xml` | no | Vitruvio form shown to the user before running the report (parameter input) |
|
||||
|
||||
The `-params.xml` file follows the same XML/XSD form schema as panel forms — same components, same engine API, same JS rules.
|
||||
|
||||
## Version constraint
|
||||
|
||||
Reports must be compatible with **JasperReports 6.21.2**. Use **Jaspersoft Studio 6.21.2** to design them — do not save with a newer version, as the generated XML will reference a newer schema and fail to compile on the server.
|
||||
|
||||
## vitruvio.json registration
|
||||
|
||||
```json
|
||||
"reports": [
|
||||
{
|
||||
"key": "my-report",
|
||||
"name": "My Report",
|
||||
"queryKey": "my-query",
|
||||
"template": "reports/my-report.jrxml",
|
||||
"paramsForm": "reports/my-report-params.xml"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
- `queryKey` references a key from `queries/` — the report's main datasource.
|
||||
- `paramsForm` is optional; omit if the report takes no parameters.
|
||||
@@ -0,0 +1,66 @@
|
||||
# Scripts
|
||||
|
||||
Reusable JS logic loaded via `libService.loadScript('script-key')`. All scripts run on **Mozilla Rhino (ES5)** — the same engine and constraints as endpoints and panel inline scripts. See root CLAUDE.md for the full ES5 rules.
|
||||
|
||||
## What's NOT available in scripts
|
||||
|
||||
- No `engine` object (that's panel/process-only).
|
||||
- No `params` request object (that's endpoint-only).
|
||||
- No browser or Node globals (`window`, `require`, `process`, etc.).
|
||||
|
||||
Services like `vProcessInstanceService`, `vConfigService`, and libs like `db`, `http` are available and injected the same way as everywhere else.
|
||||
|
||||
## Two patterns
|
||||
|
||||
### Process / task scripts — top-level execution
|
||||
|
||||
Scripts triggered directly by a process task or scheduler. Code runs at the top level, no export needed.
|
||||
|
||||
```javascript
|
||||
var db = libService.loadScript('db');
|
||||
var banco = new db(db.VITRUVIO_DATASOURCE);
|
||||
|
||||
var result = banco.queryForList("SELECT ...", {});
|
||||
// do work...
|
||||
```
|
||||
|
||||
### Library scripts — object literal export
|
||||
|
||||
Reusable libs meant to be loaded by other scripts, endpoints, or panels. Wrap everything in a `({...})` object literal — this is the value returned by `libService.loadScript()`.
|
||||
|
||||
```javascript
|
||||
({
|
||||
versao: 1.0,
|
||||
nome: 'My Library',
|
||||
|
||||
myFunction: function(params) {
|
||||
var db = libService.loadScript('db');
|
||||
var banco = new db(db.VITRUVIO_DATASOURCE);
|
||||
// ...
|
||||
return result;
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
The caller then uses it as:
|
||||
|
||||
```javascript
|
||||
var myLib = libService.loadScript('my-lib');
|
||||
myLib.myFunction({ foo: 'bar' });
|
||||
```
|
||||
|
||||
## vitruvio.json registration
|
||||
|
||||
```json
|
||||
"scripts": [
|
||||
{
|
||||
"key": "my-lib",
|
||||
"name": "My Library",
|
||||
"path": "scripts/my-lib.js",
|
||||
"domain": "USUARIO"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
`domain` is either `"SISTEMA"` (system-level) or `"USUARIO"` (user-level). Default to `"USUARIO"`.
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Credenciais do usuário de teste — opcional, copie para .env e preencha
|
||||
# se quiser usar um usuário fixo. Se deixado em branco, um usuário de teste
|
||||
# é gerado automaticamente a cada execução (veja resolveE2ECredentials).
|
||||
# O arquivo .env está no .gitignore e NUNCA deve ser commitado
|
||||
|
||||
TEST_USER=
|
||||
TEST_PASSWORD=
|
||||
|
||||
# URL do Vitruvio em execução. Padrão: porta do docker-compose de testes.
|
||||
# Aponte para sua instância local se ela já estiver no ar.
|
||||
TEST_VITRUVIO_URL=http://localhost:18080
|
||||
|
||||
# Conexão que o global-setup usa para criar o usuário de teste no banco.
|
||||
TEST_DB_HOST=localhost
|
||||
TEST_DB_PORT=15432
|
||||
TEST_DB_USER=postgres
|
||||
TEST_DB_PASSWORD=postgres
|
||||
TEST_DB_NAME=vitruvio
|
||||
|
||||
# Para apontar o container Vitruvio a um banco externo em vez do postgres
|
||||
# do docker-compose, preencha as variáveis abaixo. Use host.docker.internal
|
||||
# para referenciar o localhost do host de dentro do container.
|
||||
# Suba apenas o Vitruvio e o gitea-mock com:
|
||||
# vitruvio test:start --no-db
|
||||
#
|
||||
# TYPE_DATABASE=postgresql
|
||||
# DRIVER_CLASS_NAME=org.postgresql.Driver
|
||||
# URL_VITRUVIO=jdbc:postgresql://host.docker.internal:5432/meu_banco
|
||||
# URL_NAUTH=jdbc:postgresql://host.docker.internal:5432/meu_banco
|
||||
# USERNAME_VITRUVIO=meu_usuario
|
||||
# PASSWORD_VITRUVIO=minha_senha
|
||||
# USERNAME_NAUTH=meu_usuario
|
||||
# PASSWORD_NAUTH=minha_senha
|
||||
@@ -0,0 +1,76 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_DB: vitruvio
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
TZ: America/Sao_Paulo
|
||||
volumes:
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
ports:
|
||||
- "15432:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d vitruvio"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 10s
|
||||
|
||||
vitruvio:
|
||||
image: git.davinti.com.br/davinti/vitruvio-test:latest
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- git-repos:/repos
|
||||
ports:
|
||||
- "18080:8080"
|
||||
environment:
|
||||
TYPE_DATABASE: ${TYPE_DATABASE:-postgresql}
|
||||
DRIVER_CLASS_NAME: ${DRIVER_CLASS_NAME:-org.postgresql.Driver}
|
||||
URL_VITRUVIO: ${URL_VITRUVIO:-jdbc:postgresql://postgres:5432/vitruvio}
|
||||
URL_NAUTH: ${URL_NAUTH:-jdbc:postgresql://postgres:5432/vitruvio}
|
||||
USERNAME_VITRUVIO: ${USERNAME_VITRUVIO:-postgres}
|
||||
PASSWORD_VITRUVIO: ${PASSWORD_VITRUVIO:-postgres}
|
||||
USERNAME_NAUTH: ${USERNAME_NAUTH:-postgres}
|
||||
PASSWORD_NAUTH: ${PASSWORD_NAUTH:-postgres}
|
||||
VITRUVIO_QUARTZ_ENABLED: "false"
|
||||
VITRUVIO_PATH: ROOT
|
||||
HOST_JMX_PORT: 19020
|
||||
TZ: America/Sao_Paulo
|
||||
DB_POOL_INITIAL_SIZE: 1
|
||||
DB_POOL_MAX_IDLE: 2
|
||||
DB_POOL_MAX_TOTAL: 10
|
||||
DB_POOL_MIN_IDLE: 1
|
||||
EMAIL_HOST: localhost
|
||||
EMAIL_PORT: "25"
|
||||
EMAIL_DEFAULT_FROM: test@test.local
|
||||
EMAIL_DEFAULT_FROM_NAME: Vitruvio Test
|
||||
EMAIL_USERNAME: test
|
||||
EMAIL_PASSWORD: test
|
||||
EMAIL_USE_TLS: "false"
|
||||
|
||||
gitea-mock:
|
||||
image: alpine/git
|
||||
entrypoint:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
git config --global --add safe.directory /project &&
|
||||
git config --global --add safe.directory /project/.git &&
|
||||
mkdir -p /repos &&
|
||||
git clone --bare /project /repos/project.git &&
|
||||
sleep infinity
|
||||
volumes:
|
||||
- ..:/project:ro
|
||||
- git-repos:/repos
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "test -f /repos/project.git/HEAD"]
|
||||
interval: 3s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
start_period: 15s
|
||||
|
||||
volumes:
|
||||
git-repos:
|
||||
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('@davinti/vitruvio-test-utils').globalSetup;
|
||||
@@ -0,0 +1 @@
|
||||
CREATE SCHEMA IF NOT EXISTS nauth;
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES5",
|
||||
"lib": ["ES5"],
|
||||
"module": "None",
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"noEmit": true,
|
||||
"strict": false,
|
||||
"noImplicitAny": false,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"scripts/**/*.js",
|
||||
"endpoints/**/*.js",
|
||||
"panels/**/*.js",
|
||||
"processes/**/*.js",
|
||||
"queries/**/*.js",
|
||||
"reports/**/*.js",
|
||||
"libraries/**/*.js",
|
||||
"types/**/*.d.ts"
|
||||
],
|
||||
"exclude": ["node_modules", "tests"]
|
||||
}
|
||||
Vendored
+85
@@ -0,0 +1,85 @@
|
||||
// Ambient declarations for the Rhino (ES5) script execution environment.
|
||||
// These globals are injected by Vitruvio — they are NOT available in Node.js.
|
||||
// The Jest stubs in vitruvio-test-utils/setup.js mirror these for unit tests.
|
||||
|
||||
// Java interop (Rhino built-ins)
|
||||
declare var java: any;
|
||||
declare var Packages: any;
|
||||
|
||||
// Script output variable — set this at the end of a script to return a value
|
||||
declare var result: string;
|
||||
|
||||
// Print to the script console
|
||||
declare function println(s: any): void;
|
||||
|
||||
declare var vLogger: {
|
||||
info(msg: string): void;
|
||||
warn(msg: string): void;
|
||||
error(msg: string): void;
|
||||
debug(msg: string): void;
|
||||
};
|
||||
|
||||
declare var libService: {
|
||||
loadScript(name: string): any;
|
||||
};
|
||||
|
||||
declare var vProcessInstanceService: {
|
||||
criarInstancia(user: string, processKey: string): any;
|
||||
adicionarAnexo(processInstanceId: string, descricao: string, arquivo: any, file: any): void;
|
||||
salvarFormularioCompletandoTarefa(taskId: string, data: any): void;
|
||||
salvarFormulario(taskId: string, data: any): void;
|
||||
completarTarefa(taskId: string): void;
|
||||
cancelarProcesso(instanceId: number): void;
|
||||
cancelarProcessoDefinitivamente(instanceId: number): void;
|
||||
deletarProcesso(instanceId: number): void;
|
||||
reativarProcesso(instanceId: number): void;
|
||||
getInstanceByBPMNProcessInstanceId(bpmnId: string): any;
|
||||
getInstanceById(id: number): any;
|
||||
getInstanceStatusByBPMNProcessInstanceId(bpmnId: string): any;
|
||||
updateInstanceDescription(instanceId: number, description: string): void;
|
||||
updateInstanceDueDate(instanceId: number, date: any): void;
|
||||
updateInstancePriority(instanceId: number, priority: number): void;
|
||||
atualizarStatus(instanceId: number, status: string): void;
|
||||
definirMarcador(instanceId: number, marker: string): void;
|
||||
definirEmpresa(instanceId: number, companyId: number): void;
|
||||
setTaskAssignee(taskId: string, userId: number): void;
|
||||
claimTask(taskId: string, userId: number): void;
|
||||
};
|
||||
|
||||
declare var vFileService: {
|
||||
createTemporaryFile(): any;
|
||||
createTemporaryDirectory(): any;
|
||||
writeTemporaryFile(content: any): any;
|
||||
persistFile(file: any, metadata: any): any;
|
||||
updateFile(fileId: number, file: any): any;
|
||||
getFile(fileId: number): any;
|
||||
getMetadataById(fileId: number): any;
|
||||
createStream(fileId: number): any;
|
||||
deleteFile(fileId: number): void;
|
||||
openSession(): any;
|
||||
commit(session: any): void;
|
||||
rollback(session: any): void;
|
||||
};
|
||||
|
||||
declare var vEmailService: {
|
||||
send(to: string, subject: string, body: string): void;
|
||||
sendError(subject: string, body: string): void;
|
||||
sendToUser(userId: number, subject: string, body: string): void;
|
||||
};
|
||||
|
||||
declare var vConfigService: {
|
||||
getSystemConfigAsString(key: string): string | null;
|
||||
getSystemConfigAsLong(key: string): number | null;
|
||||
getSystemConfigAsDouble(key: string): number | null;
|
||||
getSystemConfigAsBoolean(key: string): boolean | null;
|
||||
setSystemConfig(key: string, value: any): void;
|
||||
getUserConfig(userId: number, key: string): string | null;
|
||||
setUserConfig(userId: number, key: string, value: any): void;
|
||||
getInstanceConfig(instanceId: number, key: string): string | null;
|
||||
setInstanceConfig(instanceId: number, key: string, value: any): void;
|
||||
};
|
||||
|
||||
declare var vPanelService: {
|
||||
getPanel(key: string): any;
|
||||
getPanelData(key: string, params: any): any;
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"_comment": "vitruvio.json — Arquivo de manifesto para um repositório de conteúdo Vitruvio.",
|
||||
"_comment_version": "'version' e 'metadata.key' são OBRIGATÓRIOS. Todas as outras seções são opcionais.",
|
||||
"$schema": "./.vitruvio/vitruvio-manifest-schema.json",
|
||||
"version": "1.0.0",
|
||||
"metadata": {
|
||||
"name": "Meu Módulo",
|
||||
"key": "my-module",
|
||||
"standardProduct": false
|
||||
},
|
||||
"panels": [
|
||||
{
|
||||
"key": "my-panel",
|
||||
"name": "Meu Painel",
|
||||
"description": "Descrição curta do que este painel faz.",
|
||||
"category": "Categoria/Subcategoria",
|
||||
"displayOrder": 1,
|
||||
"showInPresentation": false,
|
||||
"openInNewWindow": false,
|
||||
"showInMobileList": false,
|
||||
"displayTimeInSeconds": 0,
|
||||
"allowedGroups": [
|
||||
"group-key-1"
|
||||
],
|
||||
"allowedUsers": [],
|
||||
"forms": {
|
||||
"desktop": "panels/my-panel/my-panel-desktop.xml",
|
||||
"mobile": "panels/my-panel/my-panel-mobile.xml",
|
||||
"mobileAlternative": null
|
||||
},
|
||||
"defaultState": "panels/my-panel/default-state.json",
|
||||
"thumbnail": "panels/my-panel/thumbnail.png"
|
||||
}
|
||||
],
|
||||
"patches": "patches/"
|
||||
}
|
||||
Reference in New Issue
Block a user