Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Web app

Explanation

Actions and action sheet

How ActionMeta entries reach users through the labelled Actions chip and the bottom action sheet, and how action-sheet.js runs them.

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.scope is global, single_record, or parent (src/cluster_erp/core/metadata.py:418-434). Module metadata declares 210 actions: 188 single_record, 21 global, and 1 parent (counted with grep -rn over src/cluster_erp/modules for ActionMeta( and each scope= value).
  • A single_record action 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 GET endpoint 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-GET action emits the data-changed signal through GenApi.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:

  1. openActionSheet({title, subtitle, actions, onPick}) builds a modal sheet. Each row shows the action label and, when present, its description (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).
  2. runEntityAction(moduleName, entityMeta, action, target, opts) reads the endpoint method (webapp/js/action-sheet.js:118-128).
  3. A GET action fetches the response as a blob and opens it in the document viewer. It neither confirms nor emits a signal, and it does not call onDone (webapp/js/action-sheet.js:129-148). See record documents, viewer, and share.
  4. An action without body_fields and body_field_groups shows a confirm dialog. The message is the action's description when declared, else common.confirm_action (webapp/js/action-sheet.js:157-165). It then posts an empty body and shows the server's message or common.success (webapp/js/action-sheet.js:166-173).
  5. 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 calls onDone (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.js states 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-433 states that description exists so that a lifecycle step is never a bare verb.
  • The comment in webapp/js/action-sheet.js:144-147 states that a GET action must not run onDone because the reload would paint over the freshly opened viewer.

Rules and boundaries

  • Actions with scope parent are filtered out of lists, and no other file in webapp/js/ offers them (webapp/js/gen-list.js:407; a search for 'parent' in webapp/js/ found no other use).
  • Gated app bundles receive openActionSheet through 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 parent action 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.js still 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

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13