Every business step beyond create, edit, and deactivate is a metadata action (ActionMeta): approving, sending, converting, recalculating, opening a document. The web app never renders these as inline buttons. It offers them behind a labelled Actions chip (toolbar.actions) and lists them on a sheet that rises from the bottom of the screen. This page explains where actions appear, how webapp/js/action-sheet.js runs them, and how status changes travel through actions. The class itself is listed on metadata model.
In short¶
ActionMeta.scopeisglobal,single_record, orparent(src/cluster_erp/core/metadata.py:418-434). Module metadata declares 210 actions: 188single_record, 21global, and 1parent(counted withgrep -rnoversrc/cluster_erp/modulesforActionMeta(and eachscope=value).- A
single_recordaction is offered only when every condition holds for the row (webapp/js/action-sheet.js:20-34). - An action runs in one of three ways: a
GETendpoint opens the returned file in the viewer, an action without body fields asks for confirmation and posts, and an action with body fields opens a form (webapp/js/action-sheet.js:118-223). - A successful non-
GETaction emits the data-changed signal throughGenApi.action(webapp/js/gen-api.js:164-174).
How it works¶
Where actions appear¶
| Place | Actions offered | Source |
|---|---|---|
| List navigation bar chip Actions | All actions that are not single_record and not parent |
webapp/js/gen-list.js:407, webapp/js/gen-list.js:551-562 |
| List selection bar Actions | single_record actions that apply to the one selected row; disabled for zero or several rows |
webapp/js/gen-list.js:614-640 |
| Record card navigation bar Actions | single_record actions that apply to the open record |
webapp/js/gen-form.js:2387-2393, webapp/js/gen-form.js:2335-2337 |
| Settings screen rows | single_record actions on selected rows and other actions as global entries |
webapp/js/gen-settings.js:165-177, webapp/js/gen-settings.js:225 |
| Detail sub-lists | Actions of the child entity, filtered the same way | webapp/js/gen-detail.js:30 |
| Search results | Every action as a target that opens the screen where it lives | webapp/js/search-index.js:78-127 |
The project's written engineering rules require a labelled entry chip for actions because an icon-only entry was not discovered. The list code states the same reason (webapp/js/gen-list.js:557-559).
The action sheet flow¶
The flowchart follows one action from the chip to the server.
flowchart TD
chip["Actions chip"] --> sheet["openActionSheet"]
sheet -->|"pick"| run["runEntityAction"]
run --> method{"endpoint method"}
method -->|"GET"| blob["apiFetchBlob"]
blob --> viewer["openViewer"]
method -->|"other"| body{"body_fields or body_field_groups"}
body -->|"none"| confirm["confirmAlert"]
confirm -->|"confirmed"| post["GenApi.action"]
body -->|"present"| form["pushView with createGenForm"]
form -->|"Save and valid"| post
post --> signal["emitDataChanged"]
post --> toast["showToast"]The same flow as steps:
openActionSheet({title, subtitle, actions, onPick})builds a modal sheet. Each row shows the action label and, when present, itsdescription(webapp/js/action-sheet.js:55-85). Escape, a tap outside the sheet, and Cancel close it; focus is trapped inside while it is open (webapp/js/action-sheet.js:87-106).runEntityAction(moduleName, entityMeta, action, target, opts)reads the endpoint method (webapp/js/action-sheet.js:118-128).- A
GETaction fetches the response as a blob and opens it in the document viewer. It neither confirms nor emits a signal, and it does not callonDone(webapp/js/action-sheet.js:129-148). See record documents, viewer, and share. - An action without
body_fieldsandbody_field_groupsshows a confirm dialog. The message is the action'sdescriptionwhen declared, elsecommon.confirm_action(webapp/js/action-sheet.js:157-165). It then posts an empty body and shows the server'smessageorcommon.success(webapp/js/action-sheet.js:166-173). - An action with body fields pushes a screen with a generic form built from those fields, the description as an intro line, and a save button labelled with the action name (
webapp/js/action-sheet.js:176-198). Save validates, posts the collected values, pops the screen, and callsonDone(webapp/js/action-sheet.js:202-219).
GenApi.action replaces {id} with the target id, sends the declared method, and emits the data-changed signal for every method except GET (webapp/js/gen-api.js:164-174). Action body fields are pruned by permission and by display switch like form fields (webapp/js/meta.js:66-71, webapp/js/meta.js:99-102).
Conditions¶
actionApplies(action, row) collects condition and every entry of conditions. All must hold. Within one condition, value is a pipe-separated set and negate inverts the test (webapp/js/action-sheet.js:20-34). The same function filters record links on the card (webapp/js/gen-form.js:2364-2368).
Status changes through actions¶
A status field that changes only through actions is not shown as an editable form field; a form condition that depends on it reads the loaded record instead (webapp/js/gen-form.js:703-707). web-client/_e2e_status_actions.mjs pins this for request-for-quotation and sales quotation records: the record is created as a draft and its status moves only through actions.
Why it is built this way¶
- The header comment of
action-sheet.jsstates that a field-carrying action opens a real metadata form instead of posting an empty body (webapp/js/action-sheet.js:5-7). - The comment in
src/cluster_erp/core/metadata.py:430-433states thatdescriptionexists so that a lifecycle step is never a bare verb. - The comment in
webapp/js/action-sheet.js:144-147states that aGETaction must not runonDonebecause the reload would paint over the freshly opened viewer.
Rules and boundaries¶
- Actions with scope
parentare filtered out of lists, and no other file inwebapp/js/offers them (webapp/js/gen-list.js:407; a search for'parent'inwebapp/js/found no other use). - Gated app bundles receive
openActionSheetthrough the platform context and use the same sheet (webapp/js/custom-widgets.js:52-53). - Browser tests:
web-client/_e2e_action_sheet.mjs,web-client/_e2e_status_actions.mjs,web-client/_e2e_soline_bom_action.mjs.
Interaction with other parts¶
- The approvals widget uses the same sheet for approve, reject, defer, open, and withdraw (
webapp/js/widget-approvals.js:1-9). See home widgets. - Approval chains on the server are described on approvals and approval chains.
Limits and open points¶
- The one
parentaction in metadata (src/cluster_erp/modules/crm/rfq_line_materials/metadata.py:50) has no entry point in the web app. - The header comment of
action-sheet.jsstill describes a navigation bar⋯entry (webapp/js/action-sheet.js:3-4). The list renders the labelled Actions chip instead (webapp/js/gen-list.js:557-562).
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| action sheet | openActionSheet |
The modal card that rises from the bottom and lists the applicable actions. | aksiyon kartı | webapp/js/action-sheet.js:56 |
| action scope | ActionMeta.scope |
Whether an action applies to the whole list, one record, or a parent. | aksiyon kapsamı | src/cluster_erp/core/metadata.py:422 |
| form-backed action | runEntityAction |
An action whose body fields are collected on a pushed form before the call. | formlu aksiyon | webapp/js/action-sheet.js:119 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.