---
name: create-business-object
description: >
    Use when creating or modifying .businessobject.xml files for CG Mobile modeler contracts.
    Trigger phrases: "create a BO", "business object for X", "BoVisit-like contract",
    ".businessobject.xml", "editable BO", "add child LO to a BO".
    Invokes verify-sqlite-schema first for every table/column the BO's DataSource references.
    Prevents runtime crashes from missing DS bindings, wrong child-object wiring, and missing
    lifecycle-method declarations.
---

# Create BusinessObject — CG Mobile Modeler

A BusinessObject (BO) represents a **single record** — one Visit, one Order, one Account. It maps DataSource attributes to typed properties, fires lifecycle hooks on load/create/save/validate, and can own child relationships (lookups, nested objects, child lists). Every BO lives next to its companion DS and a set of `.bl.js` method files.

## When to Use This Skill

| Invoked by      | Phrase                                                                                 |
| --------------- | -------------------------------------------------------------------------------------- |
| User directly   | "create a BO", "business object for X", "BoVisit-like contract", ".businessobject.xml" |
| Workflow skills | `add-cockpit-card`, `add-detail-screen` (for their backing record BO)                  |

NOT for:

-   Pure display lists — use `create-list-object` (those produce `.listobject.xml` + `.listitem.xml`)
-   Reference registries — use `create-lookup-object` (those produce `.lookupobject.xml`)
-   Build errors in an existing BO — use `build-and-simulate`

---

## Prerequisites

**Invoke `verify-sqlite-schema` for every table/column the companion DS references BEFORE writing the BO or DS file. Stop if any column is missing and cannot be remediated.**

```bash
# Quick manual check:
sqlite3 appl/data/app.db3 "PRAGMA table_info(Visit);"
sqlite3 appl/data/app.db3 "PRAGMA table_info(Account);"
```

A BO is only as good as its DS. If the DS references a missing column, the BO will load with `undefined` values at runtime and no build error will flag it.

---

## Mandatory Checklist

-   [ ] Corresponding DS exists — run `ls src/<Module>/DS/ | grep DsBo<Name>` to confirm. If it does NOT exist, invoke the `create-datasource` skill first and return here after it completes.
-   [ ] BO name starts with `Bo<Name>`; folder structure: `BO/Bo<Name>/Bo<Name>.businessobject.xml`
-   [ ] Exactly one `<SimpleProperty>` has `id="true"` (the `pKey` field)
-   [ ] Every property that reads from or writes to the DS has a `dataSourceProperty` attribute matching a DS `<Attribute name="...">`
-   [ ] Every custom method has an XML `<Method name="..."/>` declaration AND a `.bl.js` file under `Mv2/`
-   [ ] `generateLoadMethod="true"` on root if the BO uses the standard load path (all simple BOs)

---

## Failure Modes

### Failure mode 1: Property reads return `undefined`

**Symptom:** `me.getFieldName()` returns `undefined` at runtime. No build error.
**Diagnosis:** The `<SimpleProperty>` is missing `dataSourceProperty`, or its value doesn't match any `<Attribute name="...">` in the DS.
**Fix:** Add or correct `dataSourceProperty="<dsAttrName>"`. The value must exactly match an `<Attribute name="...">` in the referenced DataSource. Check spelling — the mapping is case-sensitive.

```xml
<!-- WRONG — property will always be undefined -->
<SimpleProperty name="status" type="DomText"/>

<!-- CORRECT — binds to DS attribute named "status" -->
<SimpleProperty name="status" type="DomText" dataSourceProperty="status"/>
```

---

### Failure mode 2: Save succeeds but DB row is unchanged

**Symptom:** The save process action completes without error, but the database row is not updated.
**Diagnosis:** Either (a) the DS has `editableEntity=""` (no write target), or (b) `beforeSaveAsync` doesn't call `Facade.saveObjectAsync(me)`.
**Fix (a):** Set `editableEntity="<TableName>"` in the DS, matching one of its `<Entity name="...">`.
**Fix (b):** Implement `beforeSaveAsync` with `var promise = Facade.saveObjectAsync(me); return promise;`.

Both conditions must be true for saves to work. The DS controls WHERE to write; `Facade.saveObjectAsync` triggers the write.

---

### Failure mode 3: No pKey / multiple pKeys

**Symptom:** Build validation error: "exactly one SimpleProperty must carry id=true" (for platform BOs). Or silent identity failures — the framework can't track which record to update.
**Diagnosis:** `id="true"` is absent from all properties, or appears on more than one property.
**Fix:** Ensure exactly one `<SimpleProperty name="pKey" type="DomPKey" id="true" dataSourceProperty="pKey"/>`. All other properties use `id="false"` (or omit `id` entirely — it defaults to false).

---

### Failure mode 4: Child LO always shows empty

**Symptom:** A `<ListObject>` child LO loads but returns zero items even when data exists.
**Diagnosis:** The `listProperty` value doesn't match the parameter name in the child LO's DS.
**Fix:** The wiring rule is `dataSourceProperty` → value passed; `listProperty` → parameter name received. Trace the chain:

```
Parent BO:  <ListObject dataSourceProperty="pKey" listProperty="visitId"/>
Child DS:   <Parameter name="visitId" type="TEXT" />
            <QueryCondition> Task.VisitId = #visitId# </QueryCondition>
```

The `listProperty="visitId"` must exactly match `<Parameter name="visitId">` in the child's DS. A mismatch means the query receives an empty string and returns no rows.

---

### Failure mode 5: Custom method throws "is not a function"

**Symptom:** `me.startVisit is not a function` at runtime, despite BL code existing.
**Diagnosis:** The `<Method name="startVisit"/>` declaration is missing from the BO's `<Methods>` block. The framework only registers methods it knows about from the XML.
**Fix:** Add `<Method name="startVisit"/>` inside `<Methods>`. Both the XML declaration AND the `.bl.js` file must exist.

---

### Failure mode 6: Lifecycle hooks don't fire

**Symptom:** `afterLoadAsync` or `beforeSaveAsync` BL code is never reached, even though the `.bl.js` file exists.
**Diagnosis:** The method is implemented in `.bl.js` but not declared in `<Methods>`. The framework silently skips undeclared methods.
**Fix:** Add the missing declaration:

```xml
<Methods>
  <Method name="beforeSaveAsync"/>
  <Method name="afterLoadAsync"/>
  <!-- ... all other hooks you implement -->
</Methods>
```

---

## Minimal Template (inline preview)

Full template at `templates/read-only-bo.businessobject.xml.template`. First 15 lines:

```xml
<BusinessObject name="Bo<ENTITY>" schemaVersion="1.1" generateLoadMethod="true">
  <DataSource name="DsBo<ENTITY>"/>
  <SimpleProperties>
    <SimpleProperty name="pKey" type="DomPKey" id="true" dataSourceProperty="pKey"/>
    <SimpleProperty name="name" type="DomText" id="false" dataSourceProperty="name"/>
    <SimpleProperty name="status" type="DomText" id="false" dataSourceProperty="status"/>
    <SimpleProperty name="ownerName" type="DomText" id="false" storable="false" dataSourceProperty="ownerName"/>
    <SimpleProperty name="statusIcon" type="DomString" id="false"/>
  </SimpleProperties>
  <ObjectLookups/>
  <NestedObjects/>
  <ListObjects/>
  <Methods>
    <Method name="beforeLoadAsync"/>
    <Method name="afterLoadAsync"/>
```

---

## Common Variants

### Variant 1 — Read-only BO for a display detail screen

Use when: loading a record to display on a detail page, no editing.

Template: `templates/read-only-bo.businessobject.xml.template`

Key differences from editable: no `createAsync`/`saveAsync`/`validateAsync` methods, `generateLoadMethod="true"`.

Real-file anchor: `src/Visit/BO/BoAccount/BoAccount.businessobject.xml` — two properties, no save lifecycle, all wrappers empty.

---

### Variant 2 — Editable BO (full lifecycle)

Use when: users can edit and save the record.

Template: `templates/editable-bo.businessobject.xml.template`

Key additions:

-   Full lifecycle methods declared: `createAsync`, `beforeSaveAsync`, `afterSaveAsync`, `afterDoValidateAsync`
-   `generateLoadMethod="true"` still applies to load
-   DS must have `editableEntity="<Table>"` set
-   `beforeSaveAsync` BL must call `Facade.saveObjectAsync(me)`
-   `afterCreateAsync` BL must call `PKey.next()` and `setObjectStatus(STATE.NEW | STATE.DIRTY)`

Real-file anchor: `src/Visit/BO/BoVisit/BoVisit.businessobject.xml` — 22 SimpleProperties, full lifecycle, custom domain methods (`startVisit`, `endVisit`, `abandonVisit`).

---

### Variant 3 — BO with child ListObject

Use when: the parent record owns a collection of child items (a visit owns tasks, an order owns line items).

Template: `templates/bo-with-listobject.businessobject.xml.template`

Key wiring:

```xml
<ListObjects>
  <ListObject name="loAssessmentTasks"
              objectClass="LoVisitAssessmentTask"
              dataSourceProperty="pKey"
              listProperty="visitId"/>
</ListObjects>
```

`listProperty="visitId"` must match a `<Parameter name="visitId">` in `LoVisitAssessmentTask`'s DS.

Real-file anchor: `src/Visit/BO/BoVisit/BoVisit.businessobject.xml` (line 32 — the `loAssessmentTasks` ListObject entry).

A richer example: `src/Order/BO/BoOrder/BoOrder.businessobject.xml` — eight ObjectLookups, four NestedObjects, twenty-one ListObjects.

---

## Decision Table

| User wants...                                         | Use template                                     | `generateLoadMethod` | Lifecycle methods                        |
| ----------------------------------------------------- | ------------------------------------------------ | -------------------- | ---------------------------------------- |
| Display-only details page                             | `read-only-bo.businessobject.xml.template`       | `true`               | `beforeLoadAsync`, `afterLoadAsync` only |
| Edit form for a record                                | `editable-bo.businessobject.xml.template`        | `true`               | Full lifecycle (create/save/validate)    |
| Parent with child list (visit + tasks, order + items) | `bo-with-listobject.businessobject.xml.template` | `true`               | Full lifecycle + check child LO wiring   |
| Custom multi-DS load                                  | `editable-bo.businessobject.xml.template`        | `false`              | `loadAsync` fully implemented in BL      |

---

## File and Folder Layout

```
src/<Module>/
├── DS/
│   └── DsBo<Name>_sf.datasource.xml       ← companion DS (create first)
└── BO/
    └── Bo<Name>/
        ├── Bo<Name>.businessobject.xml     ← this file
        ├── Bo<Name>.validationmessages.xml ← companion file (not always required)
        └── Mv2/
            ├── Bo<Name>.AfterLoadAsync.bl.js
            ├── Bo<Name>.AfterCreateAsync.bl.js
            ├── Bo<Name>.BeforeSaveAsync.bl.js
            ├── Bo<Name>.AfterSaveAsync.bl.js
            ├── Bo<Name>.AfterDoValidateAsync.bl.js
            └── Bo<Name>.StartVisit.bl.js   ← custom method (example)
```

All `.bl.js` files under `Mv2/` must use `@namespace CUSTOM` in their JSDoc block. See `_shared/namespace.md`.

---

## Real BO structure (BoVisit summary)

From `src/Visit/BO/BoVisit/BoVisit.businessobject.xml`:

```xml
<BusinessObject name="BoVisit" schemaVersion="1.1" generateLoadMethod="true">
  <DataSource name="DsBoVisit"/>
  <SimpleProperties>
    <SimpleProperty name="pKey" type="DomPKey" id="true" dataSourceProperty="pKey"/>
    <SimpleProperty name="status" type="DomVisitStatus" id="false" dataSourceProperty="status"/>
    <SimpleProperty name="visitPriority" type="DomVisitPriority" id="false" dataSourceProperty="visitPriority"/>
    <!-- 19 more properties including DomDateTime, DomDate, DomTime, DomText, DomInteger -->
  </SimpleProperties>
  <ObjectLookups/>
  <NestedObjects/>
  <ListObjects>
    <ListObject name="loAssessmentTasks" objectClass="LoVisitAssessmentTask"
                dataSourceProperty="pKey" listProperty="visitId"/>
  </ListObjects>
  <Methods>
    <!-- full lifecycle + custom: startVisit, endVisit, abandonVisit, reschedule, etc. -->
  </Methods>
  <Validations>
    <Validation name="validateLengthOfFields"/>
  </Validations>
</BusinessObject>
```

Key observations:

-   Root element: `<BusinessObject name="..." schemaVersion="1.1">` — NOT `<BO>` or `<businessObject>`
-   `schemaVersion="1.1"` (BOs stay on 1.1; DataSources use 2.0)
-   Empty wrappers (`<ObjectLookups/>`, `<NestedObjects/>`) must still be present
-   Module-specific domain types (`DomVisitStatus`, `DomVisitPriority`) used instead of plain `DomText`

---

## Escape Hatch

When the above isn't enough, consult in order:

1. `references/simple-properties.md` — DOM types, `dataSourceProperty` binding, `id="true"`, `storable="false"`, property events
2. `references/child-objects.md` — ObjectLookup / NestedObject / ListObject wiring, `loadMode`, key flow tracing
3. `references/lifecycle-methods.md` — all hook names, `generateLoadMethod`, `Facade.saveObjectAsync`, `STATE.NEW|STATE.DIRTY`, `Validations` block
4. `ai-wiki/wiki/business-objects.md` (if available) — full 556-line wiki page with process integration, DateTimeAttribute splits, ACL pattern

Real-file anchors:

-   Simple BO: `src/Visit/BO/BoAccount/BoAccount.businessobject.xml`
-   Full lifecycle: `src/Visit/BO/BoVisit/BoVisit.businessobject.xml`
-   Complex BO (multiple lookups, nesting, lists): `src/Order/BO/BoOrder/BoOrder.businessobject.xml`

---

## Verify

After writing the BO (and its companion DS and BL files), run a build:

```bash
# cd to your workspace root
sf mdl build 2>&1 | tail -20
```

Then load the owning screen in the simulator and watch the browser devtools console:

```bash
sf mdl simulate
# Open devtools → Console tab
# Look for: "undefined" property reads, "is not a function" errors, SQLITE_ERROR
```

A clean build and no devtools errors on record load confirms the BO is correctly wired.
