Appearance
3. The equipment register entity
The entity
spk-assembly/metadata/entities/oeq_equipment.json (real file):
json
{
"entity": {
"name": "oeq_equipment",
"tableName": "oeq_equipment",
"label": "{\"en\":\"Equipment\"}",
"category": "office-equipment",
"icon": "devices",
"color": "#2563EB",
"pkStrategy": "identity"
},
"fields": [
{ "fieldName": "asset_tag", "label": "\"Asset Tag\"", "dataType": "text", "nullable": false, "required": true, "isUnique": true, "isIndexed": true, "isDisplayField": true, "displayOrder": 1 },
{ "fieldName": "name", "label": "\"Name\"", "dataType": "text", "nullable": false, "required": true, "displayOrder": 2 },
{ "fieldName": "category_id", "label": "\"Category Id\"", "dataType": "long", "nullable": true, "required": false, "isIndexed": true, "displayOrder": 3 },
{ "fieldName": "category_name", "label": "\"Category\"", "dataType": "text", "nullable": true, "required": false, "displayOrder": 4 },
{ "fieldName": "serial_number", "label": "\"Serial Number\"", "dataType": "text", "nullable": true, "required": false, "displayOrder": 5 },
{ "fieldName": "status", "label": "\"Status\"", "dataType": "enum", "nullable": false, "required": true, "isIndexed": true, "defaultValue": "AVAILABLE", "typeParams": "{\"enumValues\":[\"AVAILABLE\",\"ASSIGNED\",\"MAINTENANCE\",\"RETIRED\"]}", "displayOrder": 6 },
{ "fieldName": "purchase_date", "label": "\"Purchase Date\"", "dataType": "date", "nullable": true, "required": false, "displayOrder": 7 },
{ "fieldName": "warranty_expiry_date", "label": "\"Warranty Expiry Date\"", "dataType": "date", "nullable": true, "required": false, "displayOrder": 8 },
{ "fieldName": "notes", "label": "\"Notes\"", "dataType": "text", "nullable": true, "required": false, "displayOrder": 9 }
]
}Design notes
category_id(long) +category_name(text) — we denormalize the category name so the grid can show it without a join. The create dialog writes both. (The alternative — areferenceflag — needs a registered provider; chapter 5 uses that for the employee field, where one exists.)statusenum withAVAILABLEas default.asset_tagisisDisplayFieldandisUnique.
The provider
Every entity you want a grid over needs a data provider. spk-assembly/metadata/provider/oeq-equipment-provider.json (real file):
json
{
"name": "oeq-equipment-provider",
"description": "Data Provider for oeq_equipment (grid + CRUD).",
"definition": {
"kind": "rest",
"connectionRef": "self",
"basePath": "/api/v1/entities/oeq_equipment/records",
"supports": ["search", "get", "create", "update"]
},
"metadata": {}, "modules": []
}The category lookup plumbing
The register page's create dialog will have a category picker. That needs a Data View producing { value, label } pairs and a search Data Service over it.
spk-assembly/metadata/data_view/oeq-category-select-options-view.json (real file) — selects t.id as value and a concatenated category_code - category_name as label.
spk-assembly/metadata/data_service/oeq-category-search.json (real file):
json
{
"name": "oeq-category-search",
"description": "Typeahead category search for the category lookup field.",
"definition": {
"operation": "search",
"source": { "kind": "dataView", "dataViewName": "oeq-category-select-options-view" },
"filters": [ { "field": "t.category_name", "operator": "contains", "value": "${param.search}" } ],
"parameters": [ { "name": "search", "type": "string" } ]
},
"metadata": {}, "modules": []
}The KPI Data Services
The register page shows Total / Available / Assigned. Three count services plus a composite:
oeq-equipment-count-total.json—count, no filteroeq-equipment-count-available.json—count,filters: [{ "field": "status", "operator": "eq", "value": "AVAILABLE" }]oeq-equipment-count-assigned.json—count, filterstatus = ASSIGNEDoeq-equipment-kpis.json—compositewith three stepstotal/available/assigned
Verify
bash
node developer-docs/examples/test-examples.mjsvalidates every one of these files against its schema. After publishing:
bash
$ erp api post "/api/v1/data-services/oeq-equipment-kpis/execute" --body "{}"
{ "results": { "total": { "value": 0 }, "available": { "value": 0 }, "assigned": { "value": 0 } } }That results.<step>.value shape is what the page will bind.