---
title: Business Logic (.bl.js) — Layer 6
aliases: [BL, business logic, bl.js, JavaScript, Mv2]
sources:
    [
        sources/sessions/2026-02-18-process-analysis.md,
        sources/sessions/2026-04-20-ui-patterns-and-bl-apis.md,
        sources/sessions/2026-04-21-mfgvisit-build-fixes.md,
    ]
last_updated: 2026-04-21
status: draft
---

# Business Logic (.bl.js) — Layer 6

Business logic files contain JavaScript that executes within the BO/LO lifecycle. They follow strict conventions for file naming, JSDoc annotations, and async patterns.

## File Template (Build-Critical Format)

The modeler build system validates BL files structurally. Missing the auto-generated header block or the insertion markers causes build errors `03112630` / `03112631`. Every BL file MUST follow this exact format:

```javascript
'use strict';

///////////////////////////////////////////////////////////////////////////////////////////////
//                 IMPORTANT - DO NOT MODIFY AUTO-GENERATED CODE OR COMMENTS                 //
//Parts of this file are auto-generated and modifications to those sections will be          //
//overwritten. You are allowed to modify:                                                    //
// - the tags in the jsDoc as described in the corresponding section                         //
// - the function name and its parameters                                                    //
// - the function body between the insertion ranges                                          //
//         "Add your customizing javaScript code below / above"                              //
//                                                                                           //
// NOTE:                                                                                     //
// - If you have created PRE and POST functions, they will be executed in the same order     //
//   as before.                                                                              //
// - If you have created a REPLACE to override core function, only the REPLACE function will //
//   be executed. PRE and POST functions will be executed in the same order as before.       //
//                                                                                           //
// - For new customizations, you can directly modify this file. There is no need to use the  //
//   PRE, POST, and REPLACE functions.                                                       //
//                                                                                           //
///////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Use the following jsDoc tags to describe the BL function. Setting these tags will
 * change the runtime behavior in the mobile app. The values specified in the tags determine
 * the name of the contract file. The filename format is "@this . @function .bl.js".
 * For example, LoVisit.BeforeLoadAsync.bl.js
 * -> function: Name of the businessLogic function.
 * -> this: The LO, BO, or LU object that this function belongs to (and it is part of the filename).
 * -> kind: Type of object this function belongs to. Most common value is "businessobject".
 * -> async: If declared as async then the function should return a promise.
 * -> param: List of parameters the function accepts. Make sure the parameters match the function signature.
 * -> module: Use CORE or CUSTOM. If you are a Salesforce client or an implementation partner, always use CUSTOM to enable a seamless release upgrade.
 * -> maxRuntime: Maximum time this function is allowed to run, takes integer value in ms. If the max time is exceeded, error is logged.
 * -> returns: Type and variable name in which the return value is stored.
 * @function methodName
 * @this Bo{Name}
 * @kind businessobject
 * @async
 * @namespace CUSTOM
 * @param {Object} context
 * @returns promise
 */
function methodName(context) {
    var me = this;
    ///////////////////////////////////////////////////////////////////////////////////////////////
    //                                                                                           //
    //               Add your customizing javaScript code below.                                 //
    //                                                                                           //
    ///////////////////////////////////////////////////////////////////////////////////////////////

    var promise = when.resolve(context);

    ///////////////////////////////////////////////////////////////////////////////////////////////
    //                                                                                           //
    //               Add your customizing javaScript code above.                                 //
    //                                                                                           //
    ///////////////////////////////////////////////////////////////////////////////////////////////

    return promise;
}
```

### Build-Critical Rules

| Rule                  | Detail                                                                                                                              |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Auto-generated header | The 19-line `///` bordered comment block is **required**. Build fails without it.                                                   |
| jsDoc block           | Must include the full descriptive text (filename format, tag descriptions). Short-form `/** @function ... */` is insufficient.      |
| Insertion markers     | Must be 3 lines each: blank `//` line, text line (`Add your customizing...`), blank `//` line. Single-line markers fail validation. |
| `@namespace`          | Use `CUSTOM` for all new code. `CORE` is reserved for product code.                                                                 |

## JSDoc Annotations

| Annotation   | Values                                   | Purpose                               |
| ------------ | ---------------------------------------- | ------------------------------------- |
| `@function`  | Method name                              | Must match XML `<Method>` declaration |
| `@this`      | BO/LO/LU class                           | Which object this belongs to          |
| `@kind`      | `businessobject`, `listobject`, `lookup` | Object type                           |
| `@async`     | (presence)                               | Method returns a promise              |
| `@namespace` | `CORE` or `CUSTOM`                       | **Always use CUSTOM for new code**    |
| `@param`     | Parameter types                          | Input parameters                      |
| `@returns`   | Return type                              | Output                                |

## File Organization

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

## Framework APIs

### BoFactory — Object Creation & Loading

```javascript
// Create a new object (triggers createAsync lifecycle)
BoFactory.createObjectAsync('BoGeoHelper', {});

// Load a single object by params
BoFactory.loadObjectByParamsAsync('LuProduct', {
    params: [{ field: 'pKey', value: productPKey, operator: 'EQ' }],
});

// Load a list object by params
BoFactory.loadListAsync('LoWfeState', {
    params: [{ field: 'wfeWorkflowPKey', value: me.getPKey(), operator: 'EQ' }],
});

// Instantiate from raw data (lookup loaded via Facade)
var lookupData = Facade.getObjectAsync('LuProductUom', jsonQuery);
BoFactory.instantiate('LuProductUom', lookupData);
```

### Facade — Backend Operations

```javascript
// Load list items (returns array, not LO instance)
Facade.getListAsync('LoMyVisit', jsonQuery);

// Load single object data (returns raw JSON)
Facade.getObjectAsync('LuProductUom', jsonQuery);

// Save object to backend
Facade.saveObjectAsync(me);

// Get error details after failed operation
Facade.getErrorDetails();
```

### ApplicationContext — Global State

```javascript
// Get current user (most common)
var user = ApplicationContext.get('user');
user.getPKey(); // User's primary key
user.hasRole('TourUser'); // Check role

// Set context value
ApplicationContext.set('user', me);
```

### PKey — ID Generation

```javascript
// Generate new unique primary key
var newPKey = PKey.next();
me.setPKey(newPKey);
```

## Promise Patterns

### Basic Resolution

```javascript
var promise = when.resolve(context);
return promise;
```

### Chained .then() (Sequential Loading)

```javascript
var promise = BoFactory.loadObjectByParamsAsync('BoSettings', query)
    .then(function (settings) {
        me.setBoSettings(settings);
        return BoFactory.loadObjectByParamsAsync('BoSales', query2);
    })
    .then(function (sales) {
        me.setBoSales(sales);
        return me;
    });
```

### when.all() (Parallel Loading)

```javascript
var promises = [];
promises.push(Facade.saveObjectAsync(me));
promises.push(boAnnotation.saveAsync());
promises.push(loRecentState.saveAsync());

var promise = when.all(promises).then(function () {
    return me;
});
```

### Error Handling

```javascript
promise = MessageBox.displayMessage(title, message, buttons)
    .then(function (input) {
        return result;
    })
    .catch(function () {
        return false;
    });
```

## Collection Operations

```javascript
// Get all items
var items = me.getAllItems();

// Get by primary key
var item = me.getItemByPKey(pKey);

// Get by parameter match
var groups = me.getItemsByParam({ prdMetaType: 'PrdGroup' });

// Get count
var count = me.getCount();

// Filter (shows only matching items)
me.setFilter('phase', 'Initial', 'EQ');
me.resetFilter('phase');

// Sort
me.orderBy({ dateFrom: 'DESC', priority: 'ASC' });

// Add/remove items
me.addItems(itemArray, jsonQuery.params);
me.addListItems([newItemObject]); // Add raw object
me.removeAllItems();
me.removeItem(item);
```

## Utility Functions

### Date Utilities

```javascript
Utils.createAnsiDateToday(); // "2026-04-20"
Utils.createAnsiDateTimeNow(); // "2026-04-20T14:30:00Z"
Utils.createAnsiDateTimeToday(); // "2026-04-20T00:00:00Z"
Utils.createDateToday(); // JS Date object
Utils.convertAnsiDate2Date(str); // ANSI string → JS Date
Utils.convertAnsiDateTime2AnsiDate(dt); // DateTime → Date only
Utils.addDays2AnsiDate(date, days); // Add days to date
Utils.addDays2AnsiFullDate(date, days); // Add days (full format)
Utils.addDays2AnsiDateTime(dt, days); // Add days to datetime
Utils.getMinDate(); // System minimum date
```

### String/Value Utilities

```javascript
Utils.isDefined(value); // Null/undefined check
Utils.isEmptyString(str); // Empty string check
Utils.isPhone(); // Device type check
Utils.getToggleText(domain, value, short); // Domain display text
Utils.convertForDBParam(value, type); // Convert for SQL param
```

### Localization

```javascript
Localization.localize(value, 'time'); // Format time for display
Localization.localize(value, 'date'); // Format date for display
Localization.resolve('MessageKey'); // Get localized message string
```

## Common Patterns

### Computed Properties in afterLoadAsync

```javascript
function afterLoadAsync(result, context) {
    var me = this;
    me.setStatusIcon(me.getStatus() === 'Active' ? 'Active24' : 'Inactive24');
    me.setIsEditEnabled(me.getStatus() !== 'Completed');
    var startTime = me.getActualVisitStartTime();
    if (Utils.isDefined(startTime) && !Utils.isEmptyString(startTime)) {
        me.setActualStartTimeUI(startTime.substring(11, 16));
    }
    return when.resolve(result);
}
```

### ACL Pattern (Protected Property Modification)

```javascript
var aclBo = me.getACL();
aclBo.addRight(AclObjectType.PROPERTY, 'status', AclPermission.EDIT);
me.setStatus('InProgress');
aclBo.removeRight(AclObjectType.PROPERTY, 'status', AclPermission.EDIT);
```

### Card Loading Pattern (Facade.getListAsync)

```javascript
function getTasksForCard(numberOfListItems, cardDate) {
    var me = this;
    var jsonQuery = {};
    jsonQuery.params = [];
    jsonQuery.cond = " AND Task.Status IN ('Active') AND Task.ActivityDate <= #cardDate# ";
    jsonQuery.params.push({ field: 'cardDate', value: Utils.convertForDBParam(cardDate, 'DomDate') });

    me.removeAllItems();
    return Facade.getListAsync('LoMyTask', jsonQuery).then(function (items) {
        me.addItems(items, jsonQuery.params);
        me.cardItemCount = me.getAllItems().length;
        var visible = me.getAllItems().splice(0, numberOfListItems);
        me.removeAllItems();
        me.addItems(visible, jsonQuery.params);
        return me;
    });
}
```

### Validation Rules

```javascript
function afterDoValidateAsync(context) {
    var me = this;
    var messageCollector = context.messageCollector;
    if (Utils.isEmptyString(me.getName())) {
        messageCollector.add({ level: 'error', text: 'Name is required' });
    }
    return when.resolve(context);
}
```

### State Transition (New Item with PKey)

```javascript
var newItem = {
    pKey: PKey.next(),
    parentPKey: me.getPKey(),
    userPKey: ApplicationContext.get('user').getPKey(),
    statePKey: me.getActualStatePKey(),
    done: Utils.createAnsiDateNow(),
    objectStatus: STATE.NEW | STATE.DIRTY,
};
me.getLoRecentState().addListItems([newItem]);
```

### Device-Aware Logic

```javascript
var numberVisible = 5;
if (Utils.isPhone()) {
    numberVisible = 3;
}
```

## Namespace Convention

-   `@namespace CORE` — Framework/product code. **Never modify.**
-   `@namespace CUSTOM` — All new or customized code. Safe for upgrades.

## Validation rules

BusinessLogic has two surfaces the validator cares about: legacy XML `*.BusinessLogic.xml` files (still accepted as input) and the modern `*.bl.js` files (preferred). The migrator silently converts the XML form to JS before validation, so most rules you'll encounter are against the JS file.

### Cross-cutting (every contract)

-   Contract (method) names must be unique workspace-wide when combined with their `@this` container — two BL files claiming the same `@this.@function` fail validation.
-   Files must be readable and match their declared shape.

### Must

-   The `.bl.js` file has a complete JSDoc block at the top before the function declaration — without it the build rejects the file.
-   The JSDoc declares `@kind`, `@this`, and `@function`. `@namespace` must be exactly `core` or `custom` (use `CUSTOM` for all new code).
-   The documented `@param` list matches the actual function signature — missing or extra parameters are errors.
-   `@this` must match a folder somewhere in the contract path — the file is located by walking the tree, so `@this LoMyTask` means the file lives under a `LoMyTask` directory.
-   File name matches the pattern `<this>.<function>.bl.js` (e.g. `BoVisit.AfterLoadAsync.bl.js`); the file extension is always `.bl.js`.
-   The auto-generated start-marker and end-marker comment blocks must be present. All authoring code goes **between** those markers.
-   In the legacy XML form, `<Code @language>` must be `JavaScript` and `@module` must be `CORE` or `CUSTOM`.

### Must not

-   Authored code must not appear **outside** the insertion range (above the start-marker or below the end-marker). The validator rejects edits outside the fences; on round-trip those lines are silently lost.
-   Both a `.xml` and a `.bl.js` file must not exist for the same BL — delete the obsolete XML after migrating.
-   Do not use `new Date()`, do not shadow `me` with another `var me =`, and do not use `eval` (allowed only when `allowEval` is explicitly true in the contract).
-   Do not call deprecated engine methods — the validator flags each one from a known list. Some are warnings, some errors, depending on deprecation phase.
-   Do not reference `Framework.settings.multichannel`, `Framework.settings.notificationPopupForUnsavedChangesEnabled`, `AppManager.TYPES`, or `AppManager.getTypes()` from BL code.
-   Missing `.catch` on a promise chain is warned; so is returning a promise from a non-async declared function.

### Coerced (silently rewritten)

-   Legacy `<BusinessLogic>` XML blocks (both standalone files and those embedded inside BusinessObjects) are converted to `.bl.js` form by the migrator. The conversion injects `"use strict"`, an auto-generated banner, the JSDoc header, `var me = this;`, and start/end-marker comment fences. Any jsDoc tags outside the recognized set (`function`, `this`, `kind`, `async`, `private`, `maxRuntime`, `param`, `returns`) are dropped.
-   On reverse-conversion (JS → JSON for UI editing), `accessibility` is reduced to `PUBLIC` or `PRIVATE` (driven by presence of `@private`), `final` is forced to `false`, and `module` is forced to `CORE`. Any other values in the source are lost.
-   Older `ACN.*` API calls are flagged as deprecated (regex matches) but not rewritten — authors must migrate manually.

Internal schema reference: `rcg-mobile-dev-agent/wiki/contracts/business-logic.md`.

## Cross-References

-   [[business-objects]] — BL files implement BO methods
-   [[list-objects]] — BL files implement LO methods (getTasksForCard, etc.)
-   [[processes]] — Process calls BL via LOGIC actions
-   [[datasource]] — BL builds jsonQuery params for DS queries
-   [[cockpit-cards]] — Card logic patterns (getTasksForCard, getInfoForCard)
