sales unites ordered power level and input voltage query
This commit is contained in:
101
.cursor/skills/add-query-to-table/SKILL.md
Normal file
101
.cursor/skills/add-query-to-table/SKILL.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
name: add-query-to-table
|
||||||
|
description: Adds a new DB query to a page and wires it to a Table widget; documents how parameter/search syntax works (widget bindings, optional date range). Use when the user asks to add a query, connect a query to a table, create a new query, or how parameters/filters work in queries.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Add Query to Table & Parameter Search Syntax
|
||||||
|
|
||||||
|
Use this skill when adding a new query to a page, wiring a query to a table, or when you need the correct syntax for parameterized filters (e.g. date range from widgets).
|
||||||
|
|
||||||
|
## 1. Query structure (single source of truth)
|
||||||
|
|
||||||
|
Each query lives under the page in a folder named after the query:
|
||||||
|
|
||||||
|
- **Path**: `pages/<PageName>/queries/<query_name>/`
|
||||||
|
- **Files**:
|
||||||
|
- **`<query_name>.txt`** — Raw SQL. This is the **source of truth** for the query body. Keep it readable (real newlines, no escaping).
|
||||||
|
- **`metadata.json`** — Appsmith action config. The `body` inside `unpublishedAction.actionConfiguration` must match the SQL in the `.txt` file, stored as a single-line string with `\n` for newlines and `\"` for double quotes.
|
||||||
|
|
||||||
|
**Naming**: Use lowercase with underscores (e.g. `units_ordered_by_series`, `units_shipped_by_power_input`). The query name is the identifier used in bindings (e.g. `{{query_name.data}}`).
|
||||||
|
|
||||||
|
## 2. Metadata.json required fields
|
||||||
|
|
||||||
|
- **`id`**: `"<PageName>_<query_name>"` (e.g. `"Sales - Units Ordered_units_ordered_by_series"`).
|
||||||
|
- **`name`**: `"<query_name>"` (must match the folder and the name used in bindings).
|
||||||
|
- **`unpublishedAction.actionConfiguration.body`**: The exact SQL from `<query_name>.txt`, escaped for JSON (newlines → `\n`, `"` → `\"`).
|
||||||
|
- **`unpublishedAction.datasource`**: Use the same as other queries on the page (e.g. `xTuple_Sandbox`).
|
||||||
|
- **`unpublishedAction.pageId`**: `"<PageName>"`.
|
||||||
|
- **`unpublishedAction.dynamicBindingPathList`**: `[{"key":"body"}]` when the body contains `{{...}}` widget references.
|
||||||
|
- Keep **`pluginId`**: `"postgres-plugin"`, **`pluginType`**: `"DB"`, and existing flags like **`encodeParamsToggle`**, **`paginationType`**, **`pluginSpecifiedTemplates`**, **`timeoutInMillisecond`** consistent with other DB queries in the app.
|
||||||
|
|
||||||
|
When adding a new query, copy an existing query’s `metadata.json` from the same page and change `id`, `name`, and `body` (and ensure `body` stays in sync with the new `.txt` file).
|
||||||
|
|
||||||
|
## 3. Wiring the query to a Table widget
|
||||||
|
|
||||||
|
- In the Table widget JSON (e.g. `widgets/.../Table1.json`), set:
|
||||||
|
- **`tableData`**: `"{{<query_name>.data}}"`
|
||||||
|
- Table columns read from the query result by **key**. The key is the **SELECT alias** from the query (e.g. `"Product"`, `"Qty Ordered"`). In `primaryColumns`, the column **id** can be a safe identifier (e.g. `Qty_Ordered`) while **originalId** / **alias** / **label** match the display name; **computedValue** must use the same key as in the query result, e.g. `currentRow["Qty Ordered"]`.
|
||||||
|
|
||||||
|
So: **query SELECT aliases = keys in the table’s row object**. Keep column keys and any `currentRow["..."]` references in the table in sync with those aliases.
|
||||||
|
|
||||||
|
## 4. Parameter search syntax (widgets in SQL)
|
||||||
|
|
||||||
|
### 4.1 Referencing a widget value
|
||||||
|
|
||||||
|
- **Syntax**: `{{WidgetName.property}}`
|
||||||
|
- **Examples**:
|
||||||
|
- DatePicker date: `{{SeriesDateFrom.selectedDate}}`, `{{PowerDateTo.selectedDate}}`
|
||||||
|
- Other widgets: use the widget’s value property (e.g. `selectedOptionValue`, `text`) as per Appsmith docs.
|
||||||
|
|
||||||
|
Values are injected as strings. For PostgreSQL dates you typically cast in SQL, e.g. `'{{DatePicker1.selectedDate}}'::date`.
|
||||||
|
|
||||||
|
### 4.2 Optional date range (no filter when either date is empty)
|
||||||
|
|
||||||
|
Use this pattern so that:
|
||||||
|
- If **either** date widget is empty → the date condition is **not** applied (all dates allowed).
|
||||||
|
- If **both** are set → filter by `date_column BETWEEN from ::date AND to ::date`.
|
||||||
|
|
||||||
|
**SQL pattern** (replace widget names and column as needed):
|
||||||
|
|
||||||
|
```sql
|
||||||
|
AND (
|
||||||
|
NULLIF('{{DateFromWidget.selectedDate}}','') IS NULL
|
||||||
|
OR NULLIF('{{DateToWidget.selectedDate}}','') IS NULL
|
||||||
|
OR date_column BETWEEN
|
||||||
|
NULLIF('{{DateFromWidget.selectedDate}}','')::date
|
||||||
|
AND NULLIF('{{DateToWidget.selectedDate}}','')::date
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Logic**: `NULLIF('{{...}}','')` turns an empty string into SQL `NULL`. If either widget is empty, one of the first two conditions is true, so the whole `AND (...)` is true and the BETWEEN is not applied. When both are non-empty, the third branch applies the range.
|
||||||
|
- **Widget names**: Use the actual widget names (e.g. `SeriesDateFrom` / `SeriesDateTo` for one tab, `PowerDateFrom` / `PowerDateTo` for another). Ensure those widgets exist on the same page and (if in a tab) the same tab so the query runs with the right context.
|
||||||
|
|
||||||
|
### 4.3 Required parameters (always filter)
|
||||||
|
|
||||||
|
If the filter must always be applied (no “show all” when empty):
|
||||||
|
|
||||||
|
- Use the binding directly and ensure the widget always has a value, or use a default in the widget.
|
||||||
|
- Example: `AND cohead_orderdate BETWEEN '{{DateFrom.selectedDate}}'::date AND '{{DateTo.selectedDate}}'::date` — then empty dates may produce invalid SQL or empty results, so prefer the optional pattern above unless the UI guarantees non-empty values.
|
||||||
|
|
||||||
|
## 5. Checklist when adding a new query
|
||||||
|
|
||||||
|
1. Create `pages/<PageName>/queries/<query_name>/`.
|
||||||
|
2. Add `<query_name>.txt` with the full SQL (source of truth).
|
||||||
|
3. Add `metadata.json` with correct `id`, `name`, `body` (body = SQL from .txt, JSON-escaped), and same datasource/pageId as other page queries.
|
||||||
|
4. If the SQL uses `{{...}}`, set `dynamicBindingPathList` to `[{"key":"body"}]`.
|
||||||
|
5. In the Table widget that should show the data, set `tableData` to `{{<query_name>.data}}`.
|
||||||
|
6. Ensure table column keys / `currentRow["..."]` match the query’s SELECT aliases.
|
||||||
|
|
||||||
|
## 6. Renaming or replacing a query
|
||||||
|
|
||||||
|
- To **rename** (e.g. `units_shipped_by_series` → `units_ordered_by_series`):
|
||||||
|
- Create the new folder and files under the new name.
|
||||||
|
- Update every reference to the old query (e.g. `tableData`: `{{old_name.data}}` → `{{new_name.data}}`).
|
||||||
|
- Remove the old query folder (both `.txt` and `metadata.json`).
|
||||||
|
- When **replacing** the SQL but keeping the same name, update the `.txt` first, then update the `body` in `metadata.json` to match (same content, JSON-escaped).
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- **Optional date range**: NULLIF + IS NULL + OR + BETWEEN as above.
|
||||||
|
- **Table data binding**: `{{<query_name>.data}}`.
|
||||||
|
- **Column keys**: Must match query SELECT aliases (e.g. `"Product"`, `"Qty Ordered"`).
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"gitSyncId":"6978a0503f1cbf3622b556da_a33c1a5c-f987-4855-97da-8817781be960","id":"Sales - Units Ordered_units_ordered_by_power_input","pluginId":"postgres-plugin","pluginType":"DB","unpublishedAction":{"actionConfiguration":{"body":"SELECT \nCASE WHEN item_number IN ('16688','25020') THEN 'XR Series'\nWHEN item_number IN ('22351','24952') THEN 'SL Series'\nWHEN item_number IN ('17861','25021','29243','29752') THEN 'TS Series'\nWHEN item_number IN ('17862') THEN 'MS Series'\nWHEN item_number IN ('17863') THEN 'MT Series'\nWHEN item_number IN ('17873') THEN 'Harmonic Neutralizer'\n\nELSE 'None'\nEND AS \"Product\",\nreportpower.charass_value as \"Power Output\", reportinput.charass_value || ' ' || reportphases.charass_value AS \"Input\", ROUND(SUM(coitem_qtyord),0) as \"Qty Ordered\"\nFROM coitem LEFT OUTER JOIN cohead ON cohead_id=coitem_cohead_id\nLEFT OUTER JOIN itemsite ON coitem_itemsite_id = itemsite_id\nLEFT OUTER JOIN item ON item_id = itemsite_item_id\nLEFT OUTER JOIN charass AS reportpower ON \n(coitem_id = reportpower.charass_target_id \nAND reportpower.charass_target_type = 'SI'\nAND reportpower.charass_char_id = 25)\nLEFT OUTER JOIN charass AS reportinput ON \n(coitem_id = reportinput.charass_target_id \nAND reportinput.charass_target_type = 'SI'\nAND reportinput.charass_char_id = 36)\nLEFT OUTER JOIN charass AS reportphases ON \n(coitem_id = reportphases.charass_target_id \nAND reportphases.charass_target_type = 'SI'\nAND reportphases.charass_char_id = 18)\nWHERE item_number IN ('16688','17861','17862','17863','22351','25020','25144','25147','25021','24952','17873','29243','29752')\nAND (coitem_status <> 'X') \nAND cohead_id NOT IN(SELECT cohead_id from rahead LEFT OUTER JOIN cohead ON rahead_new_cohead_id=cohead_id WHERE cohead_id IS NOT NULL)\nAND coitem_price != 0\nAND (\n\tNULLIF('{{PowerDateFrom.selectedDate}}','') IS NULL\n\tOR NULLIF('{{PowerDateTo.selectedDate}}','') IS NULL\n\tOR cohead_orderdate BETWEEN\n\tNULLIF('{{PowerDateFrom.selectedDate}}','')::date\n\tAND NULLIF('{{PowerDateTo.selectedDate}}','')::date\n)\nAND reportpower.charass_value IS NOT NULL\nGROUP BY reportpower.charass_value, \"Product\", \"Input\"\nORDER BY \"Product\", reportpower.charass_value","encodeParamsToggle":true,"paginationType":"NONE","pluginSpecifiedTemplates":[{"value":true}],"timeoutInMillisecond":10000},"confirmBeforeExecute":false,"datasource":{"id":"xTuple_Sandbox","isAutoGenerated":false,"name":"xTuple_Sandbox","pluginId":"postgres-plugin"},"dynamicBindingPathList":[{"key":"body"}],"name":"units_ordered_by_power_input","pageId":"Sales - Units Ordered","runBehaviour":"AUTOMATIC","userSetOnLoad":false}}
|
||||||
@@ -14,16 +14,16 @@ LEFT OUTER JOIN itemsite ON coitem_itemsite_id = itemsite_id
|
|||||||
LEFT OUTER JOIN item ON item_id = itemsite_item_id
|
LEFT OUTER JOIN item ON item_id = itemsite_item_id
|
||||||
LEFT OUTER JOIN charass AS reportpower ON
|
LEFT OUTER JOIN charass AS reportpower ON
|
||||||
(coitem_id = reportpower.charass_target_id
|
(coitem_id = reportpower.charass_target_id
|
||||||
AND reportpower.charass_target_type = 'SI'
|
AND reportpower.charass_target_type = 'SI'
|
||||||
AND reportpower.charass_char_id = 25)
|
AND reportpower.charass_char_id = 25)
|
||||||
LEFT OUTER JOIN charass AS reportinput ON
|
LEFT OUTER JOIN charass AS reportinput ON
|
||||||
(coitem_id = reportinput.charass_target_id
|
(coitem_id = reportinput.charass_target_id
|
||||||
AND reportinput.charass_target_type = 'SI'
|
AND reportinput.charass_target_type = 'SI'
|
||||||
AND reportinput.charass_char_id = 36)
|
AND reportinput.charass_char_id = 36)
|
||||||
LEFT OUTER JOIN charass AS reportphases ON
|
LEFT OUTER JOIN charass AS reportphases ON
|
||||||
(coitem_id = reportphases.charass_target_id
|
(coitem_id = reportphases.charass_target_id
|
||||||
AND reportphases.charass_target_type = 'SI'
|
AND reportphases.charass_target_type = 'SI'
|
||||||
AND reportphases.charass_char_id = 18)
|
AND reportphases.charass_char_id = 18)
|
||||||
WHERE item_number IN ('16688','17861','17862','17863','22351','25020','25144','25147','25021','24952','17873','29243','29752')
|
WHERE item_number IN ('16688','17861','17862','17863','22351','25020','25144','25147','25021','24952','17873','29243','29752')
|
||||||
AND (coitem_status <> 'X')
|
AND (coitem_status <> 'X')
|
||||||
AND cohead_id NOT IN(SELECT cohead_id from rahead LEFT OUTER JOIN cohead ON rahead_new_cohead_id=cohead_id WHERE cohead_id IS NOT NULL)
|
AND cohead_id NOT IN(SELECT cohead_id from rahead LEFT OUTER JOIN cohead ON rahead_new_cohead_id=cohead_id WHERE cohead_id IS NOT NULL)
|
||||||
@@ -37,4 +37,4 @@ AND (
|
|||||||
)
|
)
|
||||||
AND reportpower.charass_value IS NOT NULL
|
AND reportpower.charass_value IS NOT NULL
|
||||||
GROUP BY reportpower.charass_value, "Product", "Input"
|
GROUP BY reportpower.charass_value, "Product", "Input"
|
||||||
ORDER BY "Product", reportpower.charass_value
|
ORDER BY "Product", reportpower.charass_value
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"gitSyncId": "6978a0503f1cbf3622b556da_a33c1a5c-f987-4855-97da-8817781be960",
|
|
||||||
"id": "Sales - Units Ordered_units_shipped_by_power_input",
|
|
||||||
"pluginId": "postgres-plugin",
|
|
||||||
"pluginType": "DB",
|
|
||||||
"unpublishedAction": {
|
|
||||||
"actionConfiguration": {
|
|
||||||
"body": "SELECT \nCASE WHEN item_number IN ('16688','25020') THEN 'XR Series'\nWHEN item_number IN ('22351','24952') THEN 'SL Series'\nWHEN item_number IN ('17861','25021','29243','29752') THEN 'TS Series'\nWHEN item_number IN ('17862') THEN 'MS Series'\nWHEN item_number IN ('17863') THEN 'MT Series'\nWHEN item_number IN ('17873') THEN 'Harmonic Neutralizer'\n\nELSE 'None'\nEND AS \"Product\",\nreportpower.charass_value as \"Power Output\", reportinput.charass_value || ' ' || reportphases.charass_value AS \"Input\", ROUND(SUM(coitem_qtyord),0) as \"Qty Ordered\"\nFROM coitem LEFT OUTER JOIN cohead ON cohead_id=coitem_cohead_id\nLEFT OUTER JOIN itemsite ON coitem_itemsite_id = itemsite_id\nLEFT OUTER JOIN item ON item_id = itemsite_item_id\nLEFT OUTER JOIN charass AS reportpower ON \n(coitem_id = reportpower.charass_target_id \n AND reportpower.charass_target_type = 'SI'\n AND reportpower.charass_char_id = 25)\nLEFT OUTER JOIN charass AS reportinput ON \n(coitem_id = reportinput.charass_target_id \n AND reportinput.charass_target_type = 'SI'\n AND reportinput.charass_char_id = 36)\nLEFT OUTER JOIN charass AS reportphases ON \n(coitem_id = reportphases.charass_target_id \n AND reportphases.charass_target_type = 'SI'\n AND reportphases.charass_char_id = 18)\nWHERE item_number IN ('16688','17861','17862','17863','22351','25020','25144','25147','25021','24952','17873','29243','29752')\nAND (coitem_status <> 'X') \nAND cohead_id NOT IN(SELECT cohead_id from rahead LEFT OUTER JOIN cohead ON rahead_new_cohead_id=cohead_id WHERE cohead_id IS NOT NULL)\nAND coitem_price != 0\nAND (\n\tNULLIF('{{PowerDateFrom.selectedDate}}','') IS NULL\n\tOR NULLIF('{{PowerDateTo.selectedDate}}','') IS NULL\n\tOR cohead_orderdate BETWEEN\n\tNULLIF('{{PowerDateFrom.selectedDate}}','')::date\n\tAND NULLIF('{{PowerDateTo.selectedDate}}','')::date\n)\nAND reportpower.charass_value IS NOT NULL\nGROUP BY reportpower.charass_value, \"Product\", \"Input\"\nORDER BY \"Product\", reportpower.charass_value",
|
|
||||||
"encodeParamsToggle": true,
|
|
||||||
"paginationType": "NONE",
|
|
||||||
"pluginSpecifiedTemplates": [
|
|
||||||
{
|
|
||||||
"value": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeoutInMillisecond": 10000
|
|
||||||
},
|
|
||||||
"confirmBeforeExecute": false,
|
|
||||||
"datasource": {
|
|
||||||
"id": "xTuple_Sandbox",
|
|
||||||
"isAutoGenerated": false,
|
|
||||||
"name": "xTuple_Sandbox",
|
|
||||||
"pluginId": "postgres-plugin"
|
|
||||||
},
|
|
||||||
"dynamicBindingPathList": [
|
|
||||||
{
|
|
||||||
"key": "body"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "units_shipped_by_power_input",
|
|
||||||
"pageId": "Sales - Units Ordered",
|
|
||||||
"runBehaviour": "AUTOMATIC",
|
|
||||||
"userSetOnLoad": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
"responsiveBehavior": "fill",
|
"responsiveBehavior": "fill",
|
||||||
"rightColumn": 61,
|
"rightColumn": 61,
|
||||||
"searchKey": "",
|
"searchKey": "",
|
||||||
"tableData": "{{units_shipped_by_power_input.data}}",
|
"tableData": "{{units_ordered_by_power_input.data}}",
|
||||||
"textSize": "0.775rem",
|
"textSize": "0.775rem",
|
||||||
"topRow": 7,
|
"topRow": 7,
|
||||||
"totalRecordsCount": 0,
|
"totalRecordsCount": 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user