Automates the production manager's manual workflow of checking xTuple WO Schedule + Kit Material Shortage for FA department work orders. Two tabs: WO Shortages (detail per WO + shortage line with customer name, YYYY-MM-DD dates) and Critical Parts (aggregated parts blocking near-term shipments with QOH from MPE warehouse). Nav button added to all pages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8.2 KiB
AGENTS.md — Statistics App (Appsmith)
Project Overview
Internal statistics dashboard built on Appsmith (file-based git sync mode). Displays filtered data from a PostgreSQL / xTuple ERP database (xTuple_GoLive datasource). The app is a collection of JSON widget definitions, SQL query files, and Appsmith configuration — there is no traditional application code.
Live URL: https://appsmith.mpeapp.com/applications/6947cc068872ae1d129983a0/pages/6947cc068872ae1d129983a3
Tech Stack
| Layer | Technology |
|---|---|
| Platform | Appsmith (file format v5, 64-column grid layout) |
| Database | PostgreSQL via xTuple ERP (mpe and public schemas) |
| Datasource | xTuple_GoLive (production) / xTuple_Sandbox (development) |
| Version control | Git (Bitbucket), Appsmith git sync |
Repository Structure
statistics-app/
├── AGENTS.md # This file — keep up to date after every change
├── application.json # Page registry, app metadata
├── metadata.json # Appsmith schema versions
├── theme.json # UI theme (colors, fonts, borders)
├── datasources/
│ └── xTuple_GoLive.json # PostgreSQL connection config (credentials managed by Appsmith server, not stored here)
├── pages/ # One directory per page
│ └── <Page Name>/
│ ├── <Page Name>.json # Page definition (gitSyncId required)
│ ├── widgets/ # Widget JSON files
│ │ ├── Heading.json
│ │ ├── Container1/ # Navigation sidebar widgets
│ │ └── Tabs1/ # Tabbed content (if applicable)
│ └── queries/ # SQL queries
│ └── <query_name>/
│ ├── <query_name>.txt # Raw SQL (source of truth)
│ └── metadata.json # Appsmith action config
└── .cursor/skills/ # AI agent workflows (Cursor-specific)
Pages
| Section | Page | Key Data |
|---|---|---|
| Sales | Capacity Planning (default) | mpe.poormancapacity |
| Sales | Units Shipped | Orders shipped by series / power input, date-filtered |
| Sales | Units Ordered | Orders placed by series / power input, date-filtered |
| Engineering | Pending POs | Purchase orders via mpe.get_prototype_po_dashboard_data() |
| Engineering | Pending Revisions | Revisions via mpe.get_prototype_dashboard_data() |
| Engineering | xGen | 8 tabs: build info, compatibility, firmware, test measurements |
| Operations | Engineering Holds | Work orders with active engineering holds |
| Operations | Job Drawing Status | CAD drawing builds from mpe.inventortools |
| Operations | Unused Items | Items with no inventory transactions for X days (default 365), filterable via input widget |
| Operations | WO Shortages | 2 tabs: WO shortage details (MTS/MTO) for FA dept top-level WOs; Critical Parts aggregation. Uses mpe.itematmdept, mpe.wogrpext, womatl, qtynetable() |
Critical Conventions
gitSyncId (REQUIRED)
Every page JSON and query metadata.json must have a gitSyncId as the first field. Format: "<24-char-hex>_<uuid-v4>". Without it, Appsmith silently deletes the entity on next sync.
Generate:
python3 -c "import uuid; print(uuid.uuid4().hex[:24] + '_' + str(uuid.uuid4()))"
Widget IDs
Every widgetId across all pages must be globally unique. Use short descriptive strings with a 2-3 character page prefix (e.g., ui1btn3jds, eh1heading1).
Navigation
- Every page has identical nav sidebar (
Container1/) with buttons for all pages. - Active page button:
buttonColor={{appsmith.theme.colors.backgroundColor}}(dynamic binding). - Inactive buttons:
buttonColor=#ffffff(static, no dynamic binding). parentIdfor nav widgets = the CanvaswidgetIdinside that page'sContainer1.json(varies per page — always read it).- Current nav sections: Sales (rows 0–16), Engineering (rows 18–34), Operations (rows 36–56).
Canvas Widget IDs (for parentId in Container1 children)
| Page | Canvas widgetId |
|---|---|
| Sales - Capacity Planning | x3pc17vp6q |
| Sales - Units Shipped | wj6fxg5wpg |
| Sales - Units Ordered | wj6fxg5wpg |
| Pending POs | pa1canvas01 |
| Pending Revisions | pr1canvas01 |
| xGen | xg1canvas01 |
| Operations - Job Drawing Status | jd1canvas01 |
| Operations - Engineering Holds | eh1canvas01 |
| Operations - Unused Items | ui1canvas01 |
| Operations - WO Shortages | ws1canvas01 |
Grid Layout
- 64 columns wide, 10px per row.
- Nav container: rows 0–7. Content starts at row 8.
- Nav buttons: 4 rows each, 2-row gap between sections.
Queries
- SQL source of truth:
<query_name>.txt(readable, real newlines). metadata.jsonbodyfield: same SQL but JSON-escaped (\n,\").- Table binding:
{{query_name.data}}. - All production queries use
xTuple_GoLivedatasource. - Database credentials are managed by Appsmith server-side — they are not stored in this repository. The datasource JSON only contains the name and plugin reference. When developing/testing SQL queries locally, use separate read-only credentials provided by the user — never embed them in committed files.
Widget Bindings in SQL (IMPORTANT)
Appsmith uses prepared statements (pluginSpecifiedTemplates[0].value = true). When a query body contains {{Widget.property}}, Appsmith replaces each binding with a typed prepared statement parameter ($1, $2, etc.).
For DATE_PICKER_WIDGET2 (text/date type parameters):
- The
NULLIFpattern works:NULLIF('{{DateWidget.selectedDate}}','')::date - This is safe because the parameter is always typed as
text.
For INPUT_WIDGET_V2 with inputType: "NUMBER" (integer type parameters):
- Do NOT use
NULLIF('{{Widget.text}}','')::int— this fails because Appsmith may type the parameter asinteger, andNULLIF(integer, '')causes a type mismatch error (invalid input syntax for integer: ""). - Instead, use a direct cast:
'{{Widget.text}}'::int - The widget's
defaultTextproperty ensures there is always a value on page load, so NULL/empty handling is unnecessary.
General rule: Match the SQL cast to the widget's output type. When in doubt, test with a hardcoded value first to confirm the base query works, then add the binding.
Common Tasks
Add a new page
Provide: page name (<Section> - <Label>), section, SQL query. The workflow:
- Clone
Sales - Capacity Planningas template - Generate new
gitSyncIdvalues for page and queries - Update page name, slug, heading, and widget IDs
- Register in
application.json - Add nav button on all pages (highlighted on own page, white elsewhere)
- If new section needed, add section label (
Text<N>) on all pages first - Update this AGENTS.md file (add the page to the Pages table, update Canvas Widget IDs table, and note any new conventions)
See .cursor/skills/create-new-page/SKILL.md for full procedure.
Add tabs to a page
Replace standalone table with TABS_WIDGET containing multiple tabs, each with its own query and table. See .cursor/skills/add-tabs-to-page/SKILL.md.
Add a query
Create queries/<name>/ with .txt and metadata.json. Wire to table via tableData: "{{name.data}}". See .cursor/skills/add-query-to-table/SKILL.md.
Launch prep (sandbox → production)
Replace all xTuple_Sandbox references in query metadata.json files with xTuple_GoLive. See .cursor/skills/prepare-for-launch/SKILL.md.
Rules
- After every change to this project, update this AGENTS.md file to reflect the current state — new pages, changed navigation layout, new conventions, etc. This file is the single source of truth for AI agents working in this repo.
- Do not create or modify files in the
docs/directory. - Template page for cloning:
Sales - Capacity Planning. - Slug format: lowercase, spaces to hyphens (e.g.,
operations-unused-items). - Product lines tracked: XR, SL, TS, MS, MT, Harmonic Neutralizer (mapped by item number in SQL CASE statements).
- Database credentials are never stored in this repository. Use the Appsmith-managed
xTuple_GoLivedatasource for all queries.