# Business Object Layer

> The `src/<Module>/…` paths used throughout this document are drawn from a
> representative CG Mobile customer workspace. Your workspace may use different
> module names, but the folder structure (`DS/`, `BO/`, `LO/`, `PR/`, `UI/`)
> and naming conventions are identical.

## Overview

Business Objects (BO) abstract DataSources and implement business logic through lifecycle methods. They represent individual entities (like an Account, Visit, or Order) and provide the core business rules and data manipulation capabilities.

## Business Object Fundamentals

### Purpose

Business Objects (BO) serve as the business logic layer that:

-   Abstract DataSource mappings into strongly-typed properties
-   Implement business rules through lifecycle hook methods
-   Provide custom methods for entity-specific operations
-   Encapsulate validation logic
-   Manage relationships to other BOs and ListObjects (LOs)
-   Transform data between persistence and presentation layers

### Directory Structure

```
src/
├── Visit/
│   └── BO/
│       ├── BoAccount/
│       │   ├── BoAccount.businessobject.xml
│       │   └── Mv2/
│       │       ├── CreateAsync/
│       │       ├── LoadAsync/
│       │       ├── SaveAsync/
│       │       ├── Initialize/
│       │       └── DoValidateAsync/
│       └── BoVisit/
│           ├── BoVisit.businessobject.xml
│           └── Mv2/
│               └── [business logic files]
└── [module]/
    └── BO/
        └── [business objects]
```

## Business Object XML Structure

### Root Element

```xml
<BusinessObject
  name="BoAccount"                    <!-- BO identifier -->
  schemaVersion="1.1"                 <!-- Schema version -->
  generateLoadMethod="true"           <!-- Auto-generate load method? -->
>
```

### Key Elements

| Element              | Description                                 | Required |
| -------------------- | ------------------------------------------- | -------- |
| `<DataSource>`       | Reference to datasource for persistence     | Yes      |
| `<SimpleProperties>` | Scalar properties (strings, dates, numbers) | Yes      |
| `<NestedObjects>`    | Child BOs (composition relationship)        | No       |
| `<ObjectLookups>`    | References to other BOs (associations)      | No       |
| `<ListObjects>`      | Child lists/collections                     | No       |
| `<Methods>`          | Lifecycle and custom method declarations    | Yes      |

## Example 1: Simple Business Object - BoAccount

**Location:** `src/Visit/BO/BoAccount/BoAccount.businessobject.xml`
**Purpose:** Represents a Salesforce Account entity
**Complexity:** Minimal (2 properties, template methods only)

### Complete XML

```xml
<BusinessObject name="BoAccount" schemaVersion="1.1">
  <DataSource name="DsBoAccount" />
  <SimpleProperties>
    <SimpleProperty name="pKey" type="DomPKey" id="true" dataSourceProperty="pKey" />
    <SimpleProperty name="name" type="DomText" id="false" dataSourceProperty="name" />
  </SimpleProperties>
  <NestedObjects></NestedObjects>
  <ObjectLookups></ObjectLookups>
  <ListObjects></ListObjects>
  <Methods>
    <Method name="beforeSaveAsync" />
    <Method name="afterSaveAsync" />
    <Method name="beforeLoadAsync" />
    <Method name="afterLoadAsync" />
    <Method name="beforeInitialize" />
    <Method name="afterInitialize" />
    <Method name="beforeDoValidateAsync" />
    <Method name="afterDoValidateAsync" />
    <Method name="beforeCreateAsync" />
    <Method name="afterCreateAsync" />
    <Method name="loadAsync" />
    <Method name="saveAsync" />
    <Method name="createAsync" />
  </Methods>
</BusinessObject>
```

### Analysis

#### DataSource Reference

```xml
<DataSource name="DsBoAccount" />
```

References the datasource defined in:
`src/Visit/DS/DsBoAccount_sf.datasource.xml`

#### Simple Properties

| Property | Type    | ID?   | DS Property | Purpose                     |
| -------- | ------- | ----- | ----------- | --------------------------- |
| pKey     | DomPKey | true  | pKey        | Primary key (Salesforce Id) |
| name     | DomText | false | name        | Account name                |

**Property Attributes:**

-   **name:** Property identifier in BO
-   **type:** Domain type (see Domain Types section)
-   **id:** Is this part of the primary key?
-   **dataSourceProperty:** Maps to DS attribute name

#### Lifecycle Methods

Standard lifecycle methods for CRUD operations:

| Method                | Trigger                      | Async? | Purpose                       |
| --------------------- | ---------------------------- | ------ | ----------------------------- |
| beforeCreateAsync     | Before new entity creation   | Yes    | Pre-create validation/setup   |
| afterCreateAsync      | After creation, before save  | Yes    | Post-create processing        |
| beforeInitialize      | Before object initialization | No     | Property setup                |
| afterInitialize       | After initialization         | No     | Computed property population  |
| beforeLoadAsync       | Before loading from DS       | Yes    | Pre-load setup                |
| afterLoadAsync        | After loading from DS        | Yes    | Post-load transformations     |
| beforeDoValidateAsync | Before validation            | Yes    | Pre-validation setup          |
| afterDoValidateAsync  | After validation             | Yes    | Custom validation rules       |
| beforeSaveAsync       | Before saving to DS          | Yes    | Pre-save transformations      |
| afterSaveAsync        | After saving to DS           | Yes    | Post-save processing          |
| loadAsync             | Main load method             | Yes    | Orchestrates load lifecycle   |
| saveAsync             | Main save method             | Yes    | Orchestrates save lifecycle   |
| createAsync           | Main create method           | Yes    | Orchestrates create lifecycle |

## Example 2: Complex Business Object - BoVisit

**Location:** `src/Visit/BO/BoVisit/BoVisit.businessobject.xml`
**Purpose:** Represents a field visit to a customer location
**Complexity:** High (28+ properties, nested lists, custom methods)

### XML Structure (Excerpt)

```xml
<BusinessObject name="BoVisit" schemaVersion="1.1" generateLoadMethod="true">
  <DataSource name="DsBoVisit"/>
  <SimpleProperties>
    <SimpleProperty name="pKey" type="DomPKey" id="true" dataSourceProperty="pKey"/>
    <SimpleProperty name="actualStartDateTime" type="DomDateTime" dataSourceProperty="ActualVisitStartTime"/>
    <SimpleProperty name="actualEndDateTime" type="DomDateTime" dataSourceProperty="ActualVisitEndTime"/>
    <SimpleProperty name="actualStartDate" type="DomDate"/>
    <SimpleProperty name="actualStartTime" type="DomTime"/>
    <SimpleProperty name="actualStartTimeUI" type="DomText"/>
    <SimpleProperty name="plannedStartDateTime" type="DomDateTime" dataSourceProperty="plannedVisitStartTime"/>
    <SimpleProperty name="plannedEndDateTime" type="DomDateTime" dataSourceProperty="plannedVisitEndTime"/>
    <SimpleProperty name="status" type="DomVisitStatus" dataSourceProperty="status"/>
    <SimpleProperty name="accountId" type="DomPKey" dataSourceProperty="accountId"/>
    <SimpleProperty name="visitPriority" type="DomVisitPriority" dataSourceProperty="visitPriority"/>
    <SimpleProperty name="duration" type="DomInteger" dataSourceProperty="duration"/>
    <!-- ... 16 more properties ... -->
  </SimpleProperties>
  <ListObjects>
    <ListObject name="loAssessmentTasks" objectClass="LoVisitAssessmentTask"
                dataSourceProperty="pKey" listProperty="visitId"/>
  </ListObjects>
  <Methods>
    <!-- Standard lifecycle methods -->
    <Method name="afterCreateAsync"/>
    <Method name="beforeLoadAsync"/>
    <!-- ... -->

    <!-- Custom business methods -->
    <Method name="endVisit"/>
    <Method name="reschedule"/>
    <Method name="getCockpitMenuVisibility"/>
    <Method name="isEndVisitButtonVisible"/>
    <Method name="isStartVisitButtonVisible"/>
    <Method name="updateActualTime"/>
    <Method name="validateInstructionDescriptionNotEmpty"/>
  </Methods>
</BusinessObject>
```

### Analysis

#### Computed Properties

Properties without `dataSourceProperty` are computed/transient:

```xml
<SimpleProperty name="actualStartDate" type="DomDate"/>
<SimpleProperty name="actualStartTime" type="DomTime"/>
<SimpleProperty name="actualStartTimeUI" type="DomText"/>
```

These are derived from `actualStartDateTime` in business logic:

```javascript
me.setActualStartDate(actualStartDateTime);
me.setActualStartTime(Utils.convertTime2Ansi(actualStartDateTime));
me.setActualStartTimeUI(Utils.formatTime(actualStartDateTime));
```

#### List Object Relationships

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

**Meaning:**

-   BO has a child list named `loAssessmentTasks`
-   List contains items of type `LoVisitAssessmentTask`
-   Parent key `pKey` maps to child's `visitId` property
-   Access via: `boVisit.getLoAssessmentTasks()`

#### Custom Business Methods

Beyond standard lifecycle, BOs can have custom methods:

| Method                                                           | Purpose                                 |
| ---------------------------------------------------------------- | --------------------------------------- |
| `endVisit()`                                                     | Complete a visit and record actual time |
| `reschedule(newDateFrom, newTimeFrom, newDateThru, newTimeThru)` | Reschedule visit to new time            |
| `isStartVisitButtonVisible()`                                    | UI control visibility logic             |
| `isEndVisitButtonVisible()`                                      | UI control visibility logic             |
| `getCockpitMenuVisibility()`                                     | Menu item visibility rules              |
| `validateInstructionDescriptionNotEmpty()`                       | Field validation                        |

## Business Logic Implementation

### File Structure

Business logic files are organized by lifecycle phase:

```
BoAccount/Mv2/
├── CreateAsync/
│   ├── BoAccount.BeforeCreateAsync.bl.js
│   └── BoAccount.AfterCreateAsync.bl.js
├── LoadAsync/
│   ├── BoAccount.BeforeLoadAsync.bl.js
│   └── BoAccount.AfterLoadAsync.bl.js
├── SaveAsync/
│   ├── BoAccount.BeforeSaveAsync.bl.js
│   └── BoAccount.AfterSaveAsync.bl.js
├── Initialize/
│   ├── BoAccount.BeforeInitialize.bl.js
│   └── BoAccount.AfterInitialize.bl.js
└── DoValidateAsync/
    ├── BoAccount.BeforeDoValidateAsync.bl.js
    └── BoAccount.AfterDoValidateAsync.bl.js
```

Custom methods are in the Mv2 root:

```
BoVisit/Mv2/
├── BoVisit.Reschedule.bl.js
├── BoVisit.EndVisit.bl.js
└── BoVisit.ValidateInstructionDescriptionNotEmpty.bl.js
```

### Business Logic File Template

All `.bl.js` files follow this structure:

```javascript
'use strict';

/**
 * @function functionName
 * @this BoAccount
 * @kind businessobject
 * @async
 * @namespace CORE
 * @param {Object} context
 * @returns promise
 */
function functionName(context) {
    var me = this;

    // Customization area
    var promise = when.resolve(context);

    // Business logic here

    return promise;
}
```

**Key Components:**

-   **@function:** Method name (matches XML declaration)
-   **@this:** BO class this method belongs to
-   **@kind:** "businessobject" (or "listobject", "lookup")
-   **@async:** Method returns a promise
-   **@namespace:** CORE (product) or CUSTOM (implementation)
-   **@param:** Input parameters
-   **@returns:** Return type

### Example: Simple Template (BeforeLoadAsync)

**File:** `src/Visit/BO/BoAccount/Mv2/LoadAsync/BoAccount.BeforeLoadAsync.bl.js`

```javascript
function beforeLoadAsync(context) {
    var me = this;

    // Template: No custom logic, just pass through
    var promise = when.resolve(context);

    return promise;
}
```

**Usage:**

-   Called before datasource query executes
-   `context` contains load parameters (e.g., pKey)
-   Can modify context before load
-   Must return a promise

### Example: Complex Business Logic (Reschedule)

**File:** `src/Visit/BO/BoVisit/Mv2/BoVisit.Reschedule.bl.js`

```javascript
function reschedule(newDateFrom, newTimeFrom, newDateThru, newTimeThru) {
    var me = this;

    // Validate start date/time
    if (!validateDate(newDateFrom) || !validateTime(newTimeFrom)) {
        logMsg('Input Planned Start Date/Time is invalid');
        return;
    }

    // Parse start datetime
    var timeFrom = Utils.convertAnsiDate2Date(newDateFrom);
    timeFrom.setHours(newTimeFrom.substring(0, 2));
    timeFrom.setMinutes(newTimeFrom.substring(3, 5));

    // Handle end datetime (if provided or calculate from duration)
    var timeThru;
    if (validateDate(newDateThru)) {
        timeThru = Utils.convertAnsiDate2Date(newDateThru);
        timeThru.setHours(newTimeThru.substring(0, 2));
        timeThru.setMinutes(newTimeThru.substring(3, 5));
    } else {
        // Calculate end time from duration
        var timeDifference = me.getCallDuration(
            me.getPlannedStartDate(),
            me.getPlannedStartTime(),
            me.getPlannedEndDate(),
            me.getPlannedEndTime()
        );
        timeThru = new Date(timeFrom);
        timeThru.setMinutes(timeFrom.getMinutes() + timeDifference);
    }

    // Validate: start must be before end
    if (timeThru.toJSON() <= timeFrom.toJSON()) {
        logMsg('Start DateTime should be lesser than End DateTime');
        return;
    }

    // Update BO properties
    me.setPlannedStartDate(timeFrom);
    me.setPlannedStartTime(Utils.convertTime2Ansi(timeFrom));
    me.setPlannedEndDate(timeThru);
    me.setPlannedEndTime(Utils.convertTime2Ansi(timeThru));
}
```

**Business Logic Demonstrated:**

1. **Input Validation:** Check parameters are valid dates/times
2. **Date Parsing:** Convert ANSI strings to JavaScript Date objects
3. **Business Rules:** Calculate end time if not provided, enforce start < end
4. **Property Access:** Use getters/setters (getPlannedStartDate, setPlannedEndDate)
5. **Utility Functions:** Leverage Utils for date/time operations
6. **Error Handling:** Log errors and exit early on validation failures

## Domain Types

Domain types define property data types and behavior:

### Common Domain Types

| Domain Type | Purpose                  | Example Value               |
| ----------- | ------------------------ | --------------------------- |
| DomPKey     | Primary key (18-char ID) | "001O300001TYlYFIA1"        |
| DomText     | Text string              | "Northern Trail Outfitters" |
| DomBool     | Boolean                  | true / false                |
| DomInteger  | Whole number             | 42                          |
| DomDecimal  | Decimal number           | 123.45                      |
| DomCurrency | Money value              | 1299.99                     |
| DomDate     | Date only (no time)      | "2026-02-18"                |
| DomTime     | Time only (no date)      | "14:30"                     |
| DomDateTime | Full timestamp           | "2026-02-18T14:30:00Z"      |
| DomImage    | Image reference          | "image_key"                 |
| DomBinary   | Binary data              | Base64 string               |

### Custom Domain Types

| Domain Type        | Purpose          | Possible Values                                    |
| ------------------ | ---------------- | -------------------------------------------------- |
| DomVisitStatus     | Visit state      | "Planned", "In Progress", "Completed", "Cancelled" |
| DomVisitPriority   | Visit importance | "Low", "Medium", "High", "Critical"                |
| DomOrderStatus     | Order state      | "Draft", "Submitted", "Approved", "Cancelled"      |
| DomProductCategory | Product grouping | "Beverages", "Food", "Hardware"                    |

## Property Mappings: Complete Chain

### BoAccount Property Flow

```
Salesforce Account Object
    |
    Field: Id (ID, 18 chars)
    |
DsBoAccount Attribute
    |
    <Attribute name="pKey" table="Account" column="Id" />
    |
BoAccount SimpleProperty
    |
    <SimpleProperty name="pKey" type="DomPKey" id="true" dataSourceProperty="pKey" />
    |
JavaScript Property Access
    |
    boAccount.getPKey()
    boAccount.setPKey("001O300001TYlYFIA1")
```

### BoVisit Property Flow (Computed)

```
Salesforce Visit__c Object
    |
    Field: Planned_Visit_Start_Time__c (DateTime)
    |
DsBoVisit Attribute
    |
    <Attribute name="plannedVisitStartTime" table="Visit__c" column="Planned_Visit_Start_Time__c" />
    |
BoVisit SimpleProperties (Multiple)
    |
    <SimpleProperty name="plannedStartDateTime" type="DomDateTime" dataSourceProperty="plannedVisitStartTime"/>
    <SimpleProperty name="plannedStartDate" type="DomDate"/>  <!-- Computed -->
    <SimpleProperty name="plannedStartTime" type="DomTime"/>  <!-- Computed -->
    |
Business Logic (AfterLoadAsync)
    |
    me.setPlannedStartDate(plannedStartDateTime);  // Extract date part
    me.setPlannedStartTime(Utils.convertTime2Ansi(plannedStartDateTime));  // Extract time part
    |
JavaScript Property Access
    |
    boVisit.getPlannedStartDateTime()
    boVisit.getPlannedStartDate()  // Computed from DateTime
    boVisit.getPlannedStartTime()  // Computed from DateTime
```

## Business Object Lifecycle

### Create Flow

```
User initiates "New Account"
    |
1. BO.createAsync() called
    |
2. beforeCreateAsync(context)
    - Set default values
    - Initialize properties
    - Prepare context
    |
3. Object instantiation
    |
4. afterCreateAsync(result, context)
    - Post-creation logic
    - Validate initial state
    |
5. beforeInitialize(context)
    - Setup computed properties
    |
6. Initialize properties
    |
7. afterInitialize()
    - Populate computed values
    - Set UI defaults
    |
8. BO ready for user interaction
```

### Load Flow

```
Process requests BO load (e.g., "Load Account by Id")
    |
1. BO.loadAsync({pKey: "001xxx"}) called
    |
2. beforeLoadAsync(context)
    - Modify load parameters
    - Pre-fetch related data
    |
3. DataSource query executes
    - Query SQLite/Salesforce
    - Retrieve matching record
    |
4. Map DS attributes to BO properties
    |
5. afterLoadAsync(result, context)
    - Transform data
    - Populate computed properties
    - Load child ListObjects
    |
6. BO ready with data
```

### Validate Flow

```
User clicks "Save" button
    |
1. BO.doValidateAsync() called
    |
2. beforeDoValidateAsync(context)
    - Pre-validation setup
    |
3. Built-in validation rules
    - Required fields
    - Data type checks
    - Length limits
    |
4. afterDoValidateAsync(context)
    - Custom business rules
    - Cross-field validation
    - External validation calls
    |
5. Validation result
    - Success: proceed to save
    - Failure: display errors
```

### Save Flow

```
Validation successful
    |
1. BO.saveAsync() called
    |
2. beforeSaveAsync(context)
    - Transform data for persistence
    - Calculate derived fields
    - Audit trail logging
    |
3. DataSource write executes
    - Update SQLite (local)
    - Stage for Salesforce sync
    |
4. afterSaveAsync(result, context)
    - Post-save processing
    - Trigger dependent updates
    - Refresh UI state
    |
5. BO persisted
```

## Business Logic Patterns

### Pattern 1: Property Getter/Setter

```javascript
// Get property value
var accountName = me.getName();
var visitId = me.getPKey();

// Set property value
me.setName('New Account Name');
me.setStatus('Completed');
```

### Pattern 2: Computed Properties

```javascript
function afterLoadAsync(result, context) {
    var me = this;

    // Split DateTime into Date and Time components
    var startDateTime = me.getPlannedStartDateTime();
    me.setPlannedStartDate(startDateTime);
    me.setPlannedStartTime(Utils.convertTime2Ansi(startDateTime));

    // Create UI-friendly time string
    me.setPlannedStartTimeUI(Utils.formatTime(startDateTime));

    return when.resolve(result);
}
```

### Pattern 3: Validation Rules

```javascript
function afterDoValidateAsync(context) {
    var me = this;
    var messageCollector = context.messageCollector;

    // Required field validation
    if (Utils.isEmptyString(me.getName())) {
        messageCollector.add({
            level: 'error',
            text: 'Account Name is required',
        });
    }

    // Business rule validation
    if (me.getPlannedEndDate() < me.getPlannedStartDate()) {
        messageCollector.add({
            level: 'error',
            text: 'End date must be after start date',
        });
    }

    return when.resolve(context);
}
```

### Pattern 4: Child List Management

```javascript
function afterLoadAsync(result, context) {
    var me = this;

    // Load child list
    return me
        .getLoAssessmentTasks()
        .loadAsync({
            visitId: me.getPKey(),
        })
        .then(function () {
            // Process loaded tasks
            var tasks = me.getLoAssessmentTasks();
            var incompleteTasks = tasks.getAllItems().filter(function (task) {
                return task.getStatus() !== 'Completed';
            });

            me.setIncompleteTaskCount(incompleteTasks.length);
            return when.resolve(result);
        });
}
```

### Pattern 5: Utility Function Usage

```javascript
function reschedule(newDate, newTime) {
    var me = this;

    // Date/Time utilities
    var dateObj = Utils.convertAnsiDate2Date(newDate);
    var timeStr = Utils.convertTime2Ansi(dateObj);
    var formatted = Utils.formatDateTime(dateObj);

    // Validation utilities
    if (Utils.isDefined(newDate) && !Utils.isEmptyString(newDate)) {
        me.setPlannedStartDate(newDate);
    }

    // Logging utilities
    AppLog.info('Visit rescheduled to: ' + formatted);
}
```

### Pattern 6: Promise Chaining

```javascript
function loadAsync(context) {
    var me = this;

    return me
        .beforeLoadAsync(context)
        .then(function () {
            return me.executeDatasourceLoad();
        })
        .then(function (result) {
            return me.afterLoadAsync(result, context);
        })
        .then(function (result) {
            return me.getLoAssessmentTasks().loadAsync({ visitId: me.getPKey() });
        })
        .then(function () {
            return when.resolve(me);
        });
}
```

## Integration with DataSources

### 1:1 Property Mapping

```xml
<!-- DataSource Definition -->
<Attribute name="name" table="Account" column="Name" />

<!-- Business Object Property -->
<SimpleProperty name="name" type="DomText" dataSourceProperty="name" />
```

**Runtime:**

-   Load: DS attribute "name" -> BO property "name"
-   Save: BO property "name" -> DS attribute "name" -> Salesforce "Name"

### 1:Many Property Mapping

```xml
<!-- DataSource Definition -->
<Attribute name="plannedVisitStartTime" table="Visit__c" column="Planned_Visit_Start_Time__c" />

<!-- Business Object Properties -->
<SimpleProperty name="plannedStartDateTime" type="DomDateTime" dataSourceProperty="plannedVisitStartTime"/>
<SimpleProperty name="plannedStartDate" type="DomDate"/>  <!-- No dataSourceProperty -->
<SimpleProperty name="plannedStartTime" type="DomTime"/>  <!-- No dataSourceProperty -->
```

**Runtime:**

-   Load: DS "plannedVisitStartTime" -> BO "plannedStartDateTime"
-   AfterLoad: Business logic splits into "plannedStartDate" and "plannedStartTime"
-   Save: Business logic recombines -> DS "plannedVisitStartTime" -> Salesforce

## Testing Business Logic

### Jest Test Structure

Tests are located in `test/unitTests/[Module]/[BO]/`

Example: `test/unitTests/Visit/BoAccount/BoAccount.BeforeLoadAsync.test.js`

```javascript
describe('BoAccount', function () {
    describe('beforeLoadAsync', function () {
        it('should pass through context unchanged', function () {
            // Arrange
            var bo = BoFactory.instantiate('BoAccount');
            var context = { pKey: '001xxx' };

            // Act
            return bo.beforeLoadAsync(context).then(function (result) {
                // Assert
                expect(result).toBe(context);
            });
        });
    });
});
```

### Coverage Requirements

-   **Statements:** 70%
-   **Branches:** 65%
-   **Functions:** 70%
-   **Lines:** 70%

## Key Takeaways

1. **Business Objects abstract DataSources** into strongly-typed, object-oriented entities
2. **Lifecycle methods** provide hooks for custom logic at each CRUD stage
3. **Property types** use Domain types (DomPKey, DomText, DomDateTime, etc.)
4. **Computed properties** are derived from persisted properties in business logic
5. **Custom methods** implement entity-specific operations (reschedule, endVisit, etc.)
6. **Child lists** are referenced via ListObject declarations
7. **Business logic files** follow strict naming: `{BO}.{Method}.bl.js`
8. **Promise-based** async operations enable sequential and parallel workflows
9. **Validation logic** resides in DoValidateAsync methods
10. **Namespace matters**: CORE = product, CUSTOM = implementation

## Files Referenced

| File Path                                                            | Purpose                       |
| -------------------------------------------------------------------- | ----------------------------- |
| src/Visit/BO/BoAccount/BoAccount.businessobject.xml                  | Simple BO definition          |
| src/Visit/BO/BoAccount/Mv2/LoadAsync/BoAccount.BeforeLoadAsync.bl.js | Template lifecycle method     |
| src/Visit/BO/BoVisit/BoVisit.businessobject.xml                      | Complex BO definition         |
| src/Visit/BO/BoVisit/Mv2/BoVisit.Reschedule.bl.js                    | Complex custom business logic |

---

_This documentation is maintained by the Modeler CLI plugin and refreshed on workspace upgrade._
