# Rule: LookupObject Patterns — Reference, Count, Validation

LookupObjects (LUs) serve three distinct roles in CG Mobile. Choosing the right pattern up front prevents structural rework.

---

## Pattern 1 — Reference Lookup

**Intent:** "Give me the record for pKey X."

The most common LU pattern. A consumer BO or BL method calls `Facade.getLookupAsync("LuProduct", pKey)` and gets back a typed LU instance with descriptor properties (name, category, group, etc.).

**Characteristics:**

-   DS: declarative (`external="false"`), `QueryCondition` filters by `#pKey#` parameter
-   One or more descriptor `<Attribute>` columns mapped in the DS
-   `generateLoadMethod="true"` — framework auto-loads when called
-   `<Methods>`: only `afterLoadAsync` and/or `beforeLoadAsync`

**Real-file anchor:**

-   LU: `src/Product/BO/LuProduct/LuProduct.lookupobject.xml`
-   DS: `src/Product/DS/DsLuProduct_sf.datasource.xml` (if present; follow `DsLuProduct` name in the LU file)

**Template shape:**

```xml
<LookupObject name="LuProduct" generateLoadMethod="true" schemaVersion="1.1">
  <DataSource name="DsLuProduct" />
  <SimpleProperties>
    <SimpleProperty id="true" name="pKey" type="DomPKey" dataSourceProperty="pKey" />
    <SimpleProperty name="shortText" type="DomText" dataSourceProperty="shortText" />
    <SimpleProperty name="category" type="DomPrdCategory" dataSourceProperty="category" />
  </SimpleProperties>
  <Methods>
    <Method name="afterLoadAsync" />
    <Method name="beforeLoadAsync" />
  </Methods>
</LookupObject>
```

---

## Pattern 2 — Count Lookup

**Intent:** "How many X records are linked to this parent Y?"

Used for cockpit card counts (e.g., "how many attachments in this Sales Folder?", "how many tactics linked to this agreement?").

**Characteristics:**

-   DS: declarative with `<DerivedAttribute name="count" value="Count(*)"/>` — no direct column reads
-   `QueryCondition` filters by a parent key parameter
-   LU has a single numeric property bound to the `DerivedAttribute`
-   `generateLoadMethod="true"`

**Real-file anchors:**

-   DS: `src/Sales Folder/DS/DsLuAttachmentcount_sf.datasource.xml`
-   DS: `src/Sales Folder/DS/DsLuSalesFolderCount_sf.datasource.xml`
-   DS: `src/Sales Folder/DS/DsLuTacticCount_sf.datasource.xml`

**DS shape (DsLuAttachmentcount):**

```xml
<DataSource name="DsLuAttachmentcount" backendSystem="sf" businessObjectClass="LuAttachmentcount"
            external="false" editableEntity="SF_File" schemaVersion="2.0" readOnly="true">
  <Attributes>
    <DerivedAttribute name="salesFolderAttachmentCount" value="Count(*)" />
  </Attributes>
  <Entities>
    <Entity name="SF_File" alias="" idAttribute="Id" />
    <Entity name="SF_FileLink" alias="">
      <Join Type="inner">
        <SimpleJoin>
          <Condition leftSideValue="SF_FileLink.FileId" comparator="eq"
                     rightSideType="Attribute" rightSideValue="SF_File.Id" />
        </SimpleJoin>
      </Join>
    </Entity>
  </Entities>
  <QueryCondition><![CDATA[
    SF_FileLink.ParentId = #pKey#
  ]]></QueryCondition>
  <Parameters>
    <Parameter name="pKey" type="TEXT" />
  </Parameters>
</DataSource>
```

---

## Pattern 3 — Validation Lookup

**Intent:** "Is this record/configuration still active or valid?"

A validation LU resolves a record and exposes a status or flag property that BL code checks before proceeding (e.g., confirming a call is in the right status, confirming a contact still exists).

**Characteristics:**

-   Same structural shape as a Reference Lookup
-   Consumer BL reads a status/flag property and branches: `if (!lu.getIsActive()) throw ...`
-   May use `generateLoadMethod="false"` with a custom `loadAsync` that does compound checks

**Real-file anchor:**

-   `src/Call/DS/DsLuCall_sf.datasource.xml` — loads Visit status by pKey; BL checks status before allowing call creation
-   `src/Sync/DS/DsLuExistsSyncTable.datasource.xml` — checks whether a sync table exists (returns 1 if yes, empty if no)

---

## When LU vs BO vs LO

| Use case                                                        | Contract                | Reason                                          |
| --------------------------------------------------------------- | ----------------------- | ----------------------------------------------- |
| Read a catalog record by pKey (product, category, status value) | LU                      | Cached, read-only, fast resolution              |
| Read a user-modified record (visit details, order header)       | BO                      | Single record, supports save/validate lifecycle |
| Display a list of records on a screen or card                   | LO                      | Collection of rows, supports paging and filters |
| Count related records for a cockpit badge                       | LU (count pattern)      | Aggregate query, no row enumeration needed      |
| Validate a business rule against a master record                | LU (validation pattern) | Read-only check, no UI binding needed           |

**Key invariants:**

-   LUs are **never saved** — no `saveAsync`, no `editableEntity` writes
-   LUs have **no child objects** — no `<ObjectLookups>`, `<NestedObjects>`, `<ListObjects>`
-   LUs do **not appear in UI screens** directly — they are resolved by BL code and BO property population
-   A BO can reference an LU via `<ObjectLookup>` — the LU is loaded as a side effect of the BO load

---

## `generateLoadMethod` on LUs

| Value     | When to use                                                                     |
| --------- | ------------------------------------------------------------------------------- |
| `"true"`  | Standard reference and count LUs — the framework generates the `loadAsync` stub |
| `"false"` | Custom-load LUs that implement `loadAsync` in `.bl.js` (Variant 3 in SKILL.md)  |

Even with `generateLoadMethod="true"`, declaring `<Method name="afterLoadAsync"/>` lets you hook in post-load BL (e.g., computing derived properties that aren't in the DS).

---

## Common Property Shape

| Property              | Type                                 | Notes                                        |
| --------------------- | ------------------------------------ | -------------------------------------------- |
| `pKey`                | `DomPKey`                            | Always first; `id="true"`                    |
| `shortText`           | `DomText`                            | Display label, short form                    |
| `text1`, `text2`      | `DomText`                            | Long description fields                      |
| `status` / `isActive` | `DomText` / `DomBool`                | For validation LUs                           |
| count field           | `DomInteger`                         | For count LUs                                |
| Domain-specific       | `DomPrdCategory`, `DomPrdType`, etc. | Use module-specific Dom types when available |

Properties without a backing DS column (computed in `afterLoadAsync`) use `storable="false"` and omit `dataSourceProperty`.
