# Rule: DsLu DataSource Patterns for LookupObjects

Every LookupObject requires a companion DataSource named `DsLu<Name>`. This file documents the two DS patterns used with LUs and how to find/verify them.

---

## Naming Convention

| Artifact         | Name pattern | File name                                                                           |
| ---------------- | ------------ | ----------------------------------------------------------------------------------- |
| The LookupObject | `Lu<Name>`   | `Lu<Name>.lookupobject.xml`                                                         |
| Its DataSource   | `DsLu<Name>` | `DsLu<Name>_sf.datasource.xml` (synced) or `DsLu<Name>.datasource.xml` (local-only) |

The LU XML references the DS by name:

```xml
<LookupObject name="LuProductGroup" ...>
  <DataSource name="DsLuProductGroup" />   ← must match DS file's name attribute exactly
```

To find an LU's DS:

```bash
grep -rn "DsLuProductGroup" src/Product/DS/
```

---

## Pattern 1 — Declarative DS (most common)

All real `DsLu*` files found in the codebase are declarative (`external="false"`). They follow the same shape as a `DsBo*` DS: an `<Attributes>` block, an `<Entities>` block, a `<QueryCondition>`, and `<Parameters>`.

**Key differences from a BO DS:**

-   `readOnly="true"` attribute is often set (LU never writes back)
-   `<Parameters>` block always has at least `<Parameter name="pKey" type="TEXT"/>` for reference/validation LUs
-   Count LUs use `<DerivedAttribute name="..." value="Count(*)"/>` instead of column-mapped `<Attribute>` entries

**Real-file: DsLuProductGroup (reference, with join)**

```
src/Product/DS/DsLuProductGroup_sf.datasource.xml
```

```xml
<DataSource name="DsLuProductGroup" backendSystem="sf" businessObjectClass="LuProductGroup"
            distinct="false" readonly="false" external="false"
            editableEntity="Product2" schemaVersion="2.0">
  <Attributes>
    <Attribute name="pKey"      table="Product2" column="Id" />
    <Attribute name="shortText" table="Product2" column="Short_Description_#Language#__c" />
    <Attribute name="text1"     table="Product2" column="Description_1_#Language#__c" />
    <Attribute name="text2"     table="Product2" column="Description_2_#Language#__c" />
    <Attribute name="id"        table="Product2" column="Consumer_Goods_Product_Code__c" />
  </Attributes>
  <Entities>
    <Entity name="Product2" alias="" idAttribute="Id" />
    <Entity name="RecordType" alias="">
      <Join Type="inner">
        <SimpleJoin>
          <Condition leftSideValue="Product2.RecordTypeId" comparator="eq"
                     rightSideType="Attribute" rightSideValue="RecordType.Id" />
          <Condition leftSideValue="RecordType.DeveloperName" comparator="eq"
                     rightSideType="Literal" rightSideValue="'Product_Group'" />
        </SimpleJoin>
      </Join>
    </Entity>
  </Entities>
  <QueryCondition><![CDATA[
    Product2.Id = #pKey#
    AND Product2.Sales_Org__c = '#SalesOrg#'
  ]]></QueryCondition>
  <Parameters>
    <Parameter name="pKey" type="TEXT" />
  </Parameters>
</DataSource>
```

**Real-file: DsLuCall (simple reference, Visit table)**

```
src/Call/DS/DsLuCall_sf.datasource.xml
```

```xml
<DataSource name="DsLuCall" backendSystem="sf" businessObjectClass="LuCall"
            external="false" editableEntity="Visit" schemaVersion="2.0">
  <Attributes>
    <Attribute name="pKey"   table="Visit" column="Id" />
    <Attribute name="status" table="Visit" column="Status" />
  </Attributes>
  <Entities>
    <Entity name="Visit" alias="" idAttribute="Id" />
  </Entities>
  <QueryCondition><![CDATA[
    Visit.Id = #pKey#
  ]]></QueryCondition>
  <Parameters>
    <Parameter name="pKey" type="TEXT" />
  </Parameters>
</DataSource>
```

---

## Pattern 2 — Count DS (DerivedAttribute aggregate)

Used when the LU only needs a numeric aggregate, not row data.

**Real-file: DsLuAttachmentcount (count with joins)**

```
src/Sales Folder/DS/DsLuAttachmentcount_sf.datasource.xml
```

Key elements:

-   `<DerivedAttribute name="salesFolderAttachmentCount" value="Count(*)" />` — no column mapping
-   Two `<Entity>` blocks with an inner join
-   `readOnly="true"` on the DS root
-   Still needs `editableEntity` set to the primary table for framework bookkeeping (even though no writes occur)

---

## How to Verify a DsLu Before Writing the LU

1. Run `verify-sqlite-schema` for every `table` and `column` referenced in the DS.
2. For count DSs, verify the joined tables exist (even without column checks):
    ```bash
    sqlite3 appl/data/app.db3 ".tables" | tr ' ' '\n' | grep -i SF_File
    ```
3. Confirm the DS `name` attribute matches exactly what the LU's `<DataSource name="..."/>` will reference.

---

## No Scripted DsLu Found in Codebase

A search of `src` found no `DsLu*.datasource.xml` files with `external="true"`. All LU companion DSs in the codebase are declarative.

If a future LU requires computed/procedural data, the LU would use `generateLoadMethod="false"` and implement `loadAsync` in BL code that may call `Facade.getListAsync` against a separate scripted DS — but the primary `DsLu<Name>` companion would still be declarative for the pKey-based resolution.

```bash
# Verify this yourself:
find src -name "DsLu*.datasource.xml" \
  | xargs grep -l 'external="true"' 2>/dev/null | head -5
# Expected: no output (all are external="false")
```
