> ## Documentation Index
> Fetch the complete documentation index at: https://human-resource-docs.ha-consultancy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# BC Integration Contract

> The event and procedure contract between the AL hosting page and the React control add-in — treat this as the source of truth when changing either layer.

This page documents the contract between the AL side (the hosting page) and the React side (the control add-in). Treat it as the source of truth when changing either layer.

## How a control add-in works

1. The AL page declares a `usercontrol(Name; "Control Add-in Name")` field in its layout.
2. The Control Add-in object (`controls/RosterGridHAC.al`) declares the JS files to load, the event names it raises (calls from AL into JS), and the procedures it exposes (calls from JS into AL).
3. BC injects an iframe sourcing the bundle's HTML wrapper. The React app boots inside.
4. The control's `Scripts` property points at `/scripts/assets/js/base.js` — the Vite output.

## The event contract

### Outbound (React → AL)

Every call is `Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(eventName, [parameters])`. The AL trigger with the matching name runs, reads data from BC tables, builds a `JsonArray` or `JsonObject`, and calls a control-add-in procedure (back into JS) with the result.

| Event name          | Triggered by                                             | Purpose                                                        |
| ------------------- | -------------------------------------------------------- | -------------------------------------------------------------- |
| `ControlReady`      | `scripts/startup.js` once the iframe is ready            | Tells AL the JS layer is up; AL then calls `InitControls()`    |
| `GetShiftCodes`     | `getShiftCodesAsync()`                                   | Asks AL for shift configurations marked `Add to Roster = true` |
| `GetRosterEntries`  | `getRosterEntriesAsync(from, to)`                        | Asks AL for roster entries within a date window                |
| `SaveRosterEntry`   | `saveRosterEntryAsync(empNo, date, shiftCode, entryNo?)` | Insert or update an `Employee Roster HAC` row                  |
| `DeleteRosterEntry` | `deleteRosterEntryAsync(entryNo)`                        | Delete the row by entry number                                 |
| `GetResources`      | `getAllResourcesAsync(from, to)`                         | Return the BC `Employee` list                                  |

Gantt-specific events (`GetProjects`, `CheckIfUserCanEdit`, `NewTask`, `CreateTaskPlanningLines`, etc.) are also declared and fire when that screen is re-mounted.

### Inbound (AL → React)

The control add-in declares **procedures**, which BC compiles into JavaScript callbacks that the AL page calls with `CurrPage.Control.<ProcedureName>(<Args>)`. The React app registers each as a `window.<NAME_IN_UPPER_SNAKE>` function in `dataService.ts`.

| Procedure                       | Called from AL              | Purpose                                                |
| ------------------------------- | --------------------------- | ------------------------------------------------------ |
| `InitControls()`                | After `ControlReady` event  | Signals JS to start fetching initial data              |
| `GET_SHIFT_CODES(JsonArray)`    | `GetShiftCodes` trigger     | Hands the shift list back to React                     |
| `GET_ROSTER_ENTRIES(JsonArray)` | `GetRosterEntries` trigger  | Hands the entries back                                 |
| `SAVE_ROSTER_ENTRY(JsonObject)` | `SaveRosterEntry` trigger   | Returns the saved row (with the new entryNo on insert) |
| `DELETE_ROSTER_ENTRY()`         | `DeleteRosterEntry` trigger | Acknowledges deletion                                  |
| `GET_RESOURCES(JsonArray)`      | `GetResources` trigger      | Hands the resource/employee list back                  |

## The startup handshake

```mermaid theme={null}
sequenceDiagram
    participant BC as Business Central
    participant iframe as iframe / startup.js
    participant React as React app (main.tsx)
    participant AL as AL page trigger

    BC->>iframe: Page opens — injects iframe
    iframe->>AL: InvokeExtensibilityMethod("ControlReady")
    AL->>iframe: CurrPage.Control.InitControls()
    iframe->>React: window.InitControls() fires
    React->>React: ReactDOM.createRoot().render()
    React->>AL: AutoLoad mounts → dispatches GetResources, GetShiftCodes, GetRosterEntries
    AL-->>React: Data callbacks (GET_RESOURCES, GET_SHIFT_CODES, GET_ROSTER_ENTRIES)
```

<Tip>If anything in steps 4-7 fails, the iframe stays blank. Debug by opening browser DevTools while inside BC, set a breakpoint in `startup.js`, and watch the console.</Tip>

## Working with the bridge in code

```ts theme={null}
// react-roster-shift-board/src/services/dataService.ts

async function getDataFromBusinessCentralAsync<T>(
  eventName: string,
  callbackName: string,
  parameter: unknown
): Promise<T> {
  if (!isBC) {
    // Dev mode: fetch the mock JSON
    return fetch(`/api/${eventName}.json`).then(r => r.json());
  }
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => reject(new Error('BC timeout')), 120_000);
    (window as any)[callbackName] = (result: T) => {
      clearTimeout(timer);
      resolve(result);
    };
    Microsoft.Dynamics.NAV.InvokeExtensibilityMethod(eventName, [parameter]);
  });
}
```

The 120-second timeout protects against AL never calling back.

## Bundle layout (after `npm run build`)

```text theme={null}
scripts/
├── startup.js              Boots the iframe, fires ControlReady
├── dev.html                Used only during local development
└── assets/
    ├── js/
    │   └── base.js         The full React bundle (single file)
    ├── app.css             Compiled styles
    └── api/                Dev-mode JSON fixtures (used when isBC=false)
        ├── shift-codes.json
        ├── roster-entries.json
        └── resources.json
```

<Warning>The Control Add-in's `Scripts` and `StyleSheets` properties name `js/base.js` and `app.css` verbatim. **Never rename them.**</Warning>

## Updating the contract

When you add a new feature that needs a new event or procedure:

1. Add the event/procedure to `controls/RosterGridHAC.al`.
2. Implement the AL trigger on the hosting page (`Pag70003175`).
3. Add the matching service function in `dataService.ts`.
4. Rebuild the React bundle (`npm run build`).
5. Rebuild the AL `.app`.
6. Republish.

<Warning>Skipping the React rebuild is the single most common cause of "feature works in dev but not in BC."</Warning>

## Why no direct OData or fetch from React

Permission uniformity (AL triggers run under the BC user's permission set), audit (every BC table write goes through the AL trigger and into BC's change log), multi-environment support (the iframe inherits the BC session), and no CORS/auth issues — the iframe and the AL page share an origin within BC's web shell.
