The three components of BC Human Resource — AL extension, ASP.NET API, and React roster add-in — how they fit together, trust boundaries, and data flow.
BC Human Resource is delivered as three components that share data through well-defined contracts. You can deploy just the AL extension if all you need is HR inside BC, or layer the API and React control add-in for richer scheduling and self-service.
Lives in BC-Human-Resource-AL/ and ships as a .app file installed on a Business Central tenant. Every piece of HR data lives in BC tables defined here (or in the standard Employee table extended by this app). All business rules — accrual, deduction, installment generation, approval flow, salary breakdown, EOS calculation — run as AL codeunits inside BC.The extension also publishes a set of bound OData actions on codeunits so external systems can read and write HR data without writing AL.This component can be installed stand-alone and provides a complete HR back-office workflow without any other component.
Lives in HR.API/. It is a thin OAuth-protected pass-through: every endpoint takes a tenant-id header, looks up the matching BC environment in its local Clients SQL table, obtains an OAuth 2.0 access token from Entra ID, and forwards the call to one of BC’s bound OData actions.What this layer adds on top of calling BC directly: a stable documented JSON contract, a simpler authentication story for mobile apps, and multi-tenant routing so one deployed API can serve many BC environments.It does not cache HR data, store any HR records locally, or implement its own user management. This component is optional.
Lives in react-roster-shift-board/ and is built into the AL extension at compile time: Vite writes the bundled js/base.js and app.css to ../BC-Human-Resource-AL/scripts/assets/. The AL control add-in then references those files, so when you ship the .app you ship the React bundle inside it.At runtime the React app runs inside an iframe that BC injects into the Visual Roster Board page (70003175). It talks to BC exclusively through the control add-in extensibility bridge — there is no HTTP between the React app and BC, and no dependency on the HR.API for the Roster Board to function.
Always run npm run build in react-roster-shift-board/ before publishing the AL extension — otherwise the iframe will load a stale bundle.
CI should run, in order:
cd react-roster-shift-board && npm ci && npm run buildcd ../BC-Human-Resource-AL && al-compiler ...
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"BC Human Resource","colors":{"primary":"#041f3d","light":"#3dffc0","dark":"#041f3d"},"logo":{"light":"https://mintcdn.com/haconsultancy-deb296c9/Ia0pESaTTMfXk4ts/images/dark-logo.png?fit=max\u0026auto=format\u0026n=Ia0pESaTTMfXk4ts\u0026q=85\u0026s=a9cc74be17f91df9509eeea50b32c25c","dark":"https://mintcdn.com/haconsultancy-deb296c9/Ia0pESaTTMfXk4ts/images/light-logo.png?fit=max\u0026auto=format\u0026n=Ia0pESaTTMfXk4ts\u0026q=85\u0026s=39979798d56d6d881446f94812ea8105"},"favicon":"https://mintcdn.com/haconsultancy-deb296c9/Ia0pESaTTMfXk4ts/images/favicon.png?fit=max\u0026auto=format\u0026n=Ia0pESaTTMfXk4ts\u0026q=85\u0026s=b3341a3ceaf550a11db382e8c063c8e3","appearance":{"strict":false},"background":{"color":{"light":"#ffffff","dark":"#041f3d"}},"footer":{"socials":{}},"styling":{"latex":false},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"haconsultancy-deb296c9","hasAudioNarration":false,"actualSubdomain":"haconsultancy-deb296c9","customDomains":["human-resource-docs.ha-consultancy.com"],"gitSource":{"type":"github","owner":"ha-consultancy","repo":"bc-human-resource-docs","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":""},"entitlements":{"AI_CHAT":{"status":"DISABLED"}},"buildId":"6ae9579ca79c9948d24bdfc3:success","clientVersion":"0.0.4087","searchFilterCounts":[{"language":"default","tag":"Admin \u0026 Compliance","count":16},{"language":"default","tag":"User Guide","count":13},{"language":"default","tag":"Configuration Reference","count":6},{"language":"default","tag":"Daily Use","count":6},{"language":"default","tag":"Get Started","count":4},{"language":"default","tag":"Compliance","count":3},{"language":"default","tag":"Frontend Deployment","count":3},{"language":"default","tag":"Reference","count":3},{"language":"default","tag":"Payroll \u0026 Posting","count":2},{"language":"default","tag":"Architecture","count":1}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"rbac-expansion","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-metadata","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-serve","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"live-agents","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"comment-mode","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[{"filePath":"custom.css","content":"body,html[data-theme=\"light\"] body,html[data-theme=\"light\"] p,html[data-theme=\"light\"] li,html[data-theme=\"light\"] td,html[data-theme=\"light\"] th{color:#1e2f45!important}h1,html[data-theme=\"light\"] h1{color:#041f3d!important}h2,html[data-theme=\"light\"] h2{color:#0a3a6e!important}h3,html[data-theme=\"light\"] h3{color:#0f509a!important}h4,h5,h6,html[data-theme=\"light\"] h4,html[data-theme=\"light\"] h5,html[data-theme=\"light\"] h6{color:#1a6abf!important}html[data-theme=\"light\"] strong,html[data-theme=\"light\"] b{color:#041f3d!important}a,html[data-theme=\"light\"] a{color:#0f509a!important;text-decoration-color:rgba(15, 80, 154, 0.3)}a:hover,html[data-theme=\"light\"] a:hover{color:#041f3d!important;text-decoration-color:#041f3d}html[data-theme=\"light\"] code:not(pre code){color:#0a3a6e!important;background-color:#e8f0fb!important;border-color:#c3d4ee!important}html[data-theme=\"light\"] thead th,html[data-theme=\"light\"] th{color:#041f3d!important;background-color:#eef3fb!important;border-color:#c3d4ee!important}html[data-theme=\"light\"] td{border-color:#d5e1f5!important}html[data-theme=\"light\"] blockquote{border-left-color:#0a3a6e!important;color:#1e2f45!important}html[data-theme=\"light\"] hr{border-color:#c3d4ee!important}html[data-theme=\"light\"] nav a,html[data-theme=\"light\"] [data-sidebar] a{color:#1e2f45!important}html[data-theme=\"light\"] nav a:hover,html[data-theme=\"light\"] [data-sidebar] a:hover{color:#041f3d!important}.dark h1,.dark h2,.dark h3,.dark h4,.dark h5,.dark h6,html[data-theme=\"dark\"] h1,html[data-theme=\"dark\"] h2,html[data-theme=\"dark\"] h3,html[data-theme=\"dark\"] h4,html[data-theme=\"dark\"] h5,html[data-theme=\"dark\"] h6{color:#3dffc0!important}.dark a,html[data-theme=\"dark\"] a{color:#3dffc0!important}.dark a:hover,html[data-theme=\"dark\"] a:hover{color:#ffffff!important}html[data-theme=\"dark\"] code:not(pre code){color:#3dffc0!important;background-color:rgba(61, 255, 192, 0.08)!important;border-color:rgba(61, 255, 192, 0.2)!important}html[data-theme=\"dark\"] thead th,html[data-theme=\"dark\"] th{color:#3dffc0!important;background-color:rgba(61, 255, 192, 0.06)!important}"}],"page":{"pagePath":"architecture","slug":"/architecture","pageMetadata":{"title":"Architecture Overview","description":"The three components of BC Human Resource — AL extension, ASP.NET API, and React roster add-in — how they fit together, trust boundaries, and data flow.","href":"/architecture"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"The three components of BC Human Resource — AL extension, ASP.NET API, and React roster add-in — how they fit together, trust boundaries, and data flow.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={code:`code`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,..._provideComponents(),...props.components},{CodeBlock,Heading,Mermaid,Table,Warning}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Mermaid)_missingMdxReference(`Mermaid`,true);if(!Table)_missingMdxReference(`Table`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsx(_components.p,{children:`BC Human Resource is delivered as three components that share data through well-defined contracts. You can deploy just the AL extension if all you need is HR inside BC, or layer the API and React control add-in for richer scheduling and self-service.`}),`\n`,_jsx(Mermaid,{chart:`graph TB\n subgraph BC[\"☁️ Business Central Tenant — SaaS or on-prem\"]\n subgraph AL[\"BC Human Resource AL Extension (AppSource app)\"]\n T[\"Tables / Page extensions\"]\n P[\"Pages, reports, codeunits\"]\n PS[\"Permission set: Human Resource HAC\"]\n WS[\"Published web services (OData)\"]\n subgraph RB[\"Roster Board page\"]\n RC[\"React control add-in\\\\n(iframe · /scripts/assets)\"]\n end\n end\n end\n\n subgraph HRAPI[\"HR.API — ASP.NET Core 9\"]\n DB[(\"SQL DB\\\\n(Clients table)\")]\n end\n\n subgraph Clients[\"Portal / Mobile clients\"]\n MOB[\"📱 Mobile app or web portal\"]\n end\n\n MOB -->|\"HTTPS · tenant-id header\"| HRAPI\n HRAPI -->|\"BC OData v4 · OAuth 2.0 client credentials\"| AL`}),`\n`,_jsx(Heading,{level:`2`,id:`the-three-layers`,children:`The three layers`}),`\n`,_jsx(Heading,{level:`3`,id:`1-al-extension-—-the-source-of-truth`,children:`1. AL extension — the source of truth`}),`\n`,_jsxs(_components.p,{children:[`Lives in `,_jsx(_components.strong,{children:_jsx(_components.code,{children:`BC-Human-Resource-AL/`})}),` and ships as a `,_jsx(_components.code,{children:`.app`}),` file installed on a Business Central tenant. Every piece of HR data lives in BC tables defined here (or in the standard `,_jsx(_components.code,{children:`Employee`}),` table extended by this app). All business rules — accrual, deduction, installment generation, approval flow, salary breakdown, EOS calculation — run as AL codeunits inside BC.`]}),`\n`,_jsx(_components.p,{children:`The extension also publishes a set of bound OData actions on codeunits so external systems can read and write HR data without writing AL.`}),`\n`,_jsxs(_components.p,{children:[`This component can be installed `,_jsx(_components.strong,{children:`stand-alone`}),` and provides a complete HR back-office workflow without any other component.`]}),`\n`,_jsx(Heading,{level:`3`,id:`2-asp-net-api-—-the-mobile-/-portal-gateway`,children:`2. ASP.NET API — the mobile / portal gateway`}),`\n`,_jsxs(_components.p,{children:[`Lives in `,_jsx(_components.strong,{children:_jsx(_components.code,{children:`HR.API/`})}),`. It is a thin OAuth-protected pass-through: every endpoint takes a `,_jsx(_components.code,{children:`tenant-id`}),` header, looks up the matching BC environment in its local `,_jsx(_components.code,{children:`Clients`}),` SQL table, obtains an OAuth 2.0 access token from Entra ID, and forwards the call to one of BC’s bound OData actions.`]}),`\n`,_jsx(_components.p,{children:`What this layer adds on top of calling BC directly: a stable documented JSON contract, a simpler authentication story for mobile apps, and multi-tenant routing so one deployed API can serve many BC environments.`}),`\n`,_jsxs(_components.p,{children:[`It does `,_jsx(_components.strong,{children:`not`}),` cache HR data, store any HR records locally, or implement its own user management. This component is `,_jsx(_components.strong,{children:`optional`}),`.`]}),`\n`,_jsx(Heading,{level:`3`,id:`3-react-control-add-in-—-the-visual-roster-board`,children:`3. React control add-in — the Visual Roster Board`}),`\n`,_jsxs(_components.p,{children:[`Lives in `,_jsx(_components.strong,{children:_jsx(_components.code,{children:`react-roster-shift-board/`})}),` and is `,_jsx(_components.strong,{children:`built into the AL extension`}),` at compile time: Vite writes the bundled `,_jsx(_components.code,{children:`js/base.js`}),` and `,_jsx(_components.code,{children:`app.css`}),` to `,_jsx(_components.code,{children:`../BC-Human-Resource-AL/scripts/assets/`}),`. The AL control add-in then references those files, so when you ship the `,_jsx(_components.code,{children:`.app`}),` you ship the React bundle inside it.`]}),`\n`,_jsxs(_components.p,{children:[`At runtime the React app runs inside an iframe that BC injects into the `,_jsx(_components.strong,{children:`Visual Roster Board`}),` page (70003175). It talks to BC exclusively through the control add-in extensibility bridge — there is `,_jsx(_components.strong,{children:`no HTTP`}),` between the React app and BC, and `,_jsx(_components.strong,{children:`no dependency`}),` on the HR.API for the Roster Board to function.`]}),`\n`,_jsx(Heading,{level:`2`,id:`data-flow-per-scenario`,children:`Data flow per scenario`}),`\n`,_jsx(Heading,{level:`3`,id:`hr-consultant-building-a-roster`,children:`HR consultant building a roster`}),`\n`,_jsx(Mermaid,{chart:`graph TD\n A[\"👤 HR user\"] --> B[\"BC Roster Board page (70003175)\"]\n B --> C[\"Loads iframe → React app boots\"]\n C --> D[\"React calls GetResources via control add-in bridge\"]\n D --> E[\"AL trigger reads BC tables → returns data to React\"]\n E --> F[\"User drags employee onto a shift slot\"]\n F --> G[\"React calls SaveRosterEntry\"]\n G --> H[\"AL trigger writes to Employee Roster HAC table\"]`}),`\n`,_jsx(Heading,{level:`3`,id:`employee-submitting-a-leave-request-from-a-phone-app`,children:`Employee submitting a leave request from a phone app`}),`\n`,_jsx(Mermaid,{chart:`sequenceDiagram\n participant M as 📱 Mobile app\n participant A as HR.API\n participant E as Entra ID\n participant B as Business Central\n\n M->>A: POST /api/leave/request-leave\u003cbr/>(tenant-id header)\n A->>A: ApiKeyAttribute resolves tenant → BC environment\n A->>E: Client credentials grant\n E-->>A: OAuth bearer token\n A->>B: POST APIManagementLeave_RequestLeave (OData)\n B->>B: Runs validation — same as in-BC leave page\n B->>B: Writes to Leave Journal HAC / Employee Leave HAC\n B-->>A: Response payload\n A-->>M: JSON response`}),`\n`,_jsx(Heading,{level:`2`,id:`trust-boundaries`,children:`Trust boundaries`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Boundary`}),_jsx(_components.th,{children:`What crosses it`}),_jsx(_components.th,{children:`Protected by`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Mobile/portal ↔ HR.API`}),_jsxs(_components.td,{children:[`JSON, `,_jsx(_components.code,{children:`tenant-id`}),` header`]}),_jsxs(_components.td,{children:[`TLS, the `,_jsx(_components.code,{children:`[ApiKey]`}),` filter validating tenant-id against the local `,_jsx(_components.code,{children:`Clients`}),` table`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`HR.API ↔ BC OData`}),_jsx(_components.td,{children:`JSON, OAuth Bearer`}),_jsx(_components.td,{children:`TLS, Entra ID client credentials per-tenant`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`React iframe ↔ AL page`}),_jsx(_components.td,{children:`JSON via control-add-in bridge`}),_jsx(_components.td,{children:`Same origin within BC web client; bridge is provided by Microsoft and is not network-exposed`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`AL extension ↔ BC tables`}),_jsx(_components.td,{children:`Standard BC permission set`}),_jsxs(_components.td,{children:[`The `,_jsx(_components.code,{children:`Human Resource HAC`}),` permission set, plus standard BC role hierarchy`]})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`technology-map`,children:`Technology map`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Technology`}),_jsx(_components.th,{children:`Where`}),_jsx(_components.th,{children:`Why`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`AL`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`BC-Human-Resource-AL/src/`})}),_jsx(_components.td,{children:`Native BC extension language`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`ASP.NET Core 9 / C#`}),_jsxs(_components.td,{children:[_jsx(_components.code,{children:`HR.API/HR.API/`}),`, `,_jsx(_components.code,{children:`HR.API/HR.Core/`})]}),_jsx(_components.td,{children:`Web API host + service library`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Entity Framework Core 9`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`HR.API/HR.Core/Data/`})}),_jsxs(_components.td,{children:[`One DB context, one table (`,_jsx(_components.code,{children:`Clients`}),`)`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`SQL Server`}),_jsx(_components.td,{children:`External`}),_jsx(_components.td,{children:`EF migrations target SQL Server`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`RestSharp`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`HR.API/HR.Core/Services/`})}),_jsx(_components.td,{children:`Outbound HTTP to BC OData`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`React 19 / TypeScript 5 / Vite 7`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`react-roster-shift-board/src/`})}),_jsx(_components.td,{children:`Roster Board UI`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Syncfusion EJ2`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`react-roster-shift-board/`})}),_jsxs(_components.td,{children:[_jsx(_components.code,{children:`ScheduleComponent`}),`, `,_jsx(_components.code,{children:`GanttComponent`}),` — commercial license`]})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`build-time-coupling`,children:`Build-time coupling`}),`\n`,_jsx(_components.p,{children:`The React project’s Vite config writes its build output directly into the AL extension’s scripts directory:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`10`,language:`typescript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`typescript`,children:_jsxs(_components.code,{language:`typescript`,numberOfLines:`10`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// react-roster-shift-board/vite.config.ts`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#C8C8C8`},children:`build`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#C8C8C8`},children:` outDir`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`'../BC-Human-Resource-AL/scripts/assets'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#C8C8C8`},children:` rollupOptions`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#C8C8C8`},children:` output`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#C8C8C8`},children:` entryFileNames`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`'js/base.js'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#C8C8C8`},children:` assetFileNames`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`'[name].[ext]'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` },`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` },`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`}`})}),`\n`]})})}),`\n`,_jsxs(Warning,{children:[`Always run `,_jsx(_components.code,{children:`npm run build`}),` in `,_jsx(_components.code,{children:`react-roster-shift-board/`}),` before publishing the AL extension — otherwise the iframe will load a stale bundle.`]}),`\n`,_jsx(_components.p,{children:`CI should run, in order:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#DCDCAA`},children:`cd`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` react-roster-shift-board`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` \u0026\u0026 `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`npm`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` ci`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` \u0026\u0026 `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`npm`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` run`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` build`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#DCDCAA`},children:`cd`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` ../BC-Human-Resource-AL`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` \u0026\u0026 `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`al-compiler`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` ...`})]}),`\n`]})})})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Architecture Overview","description":"The three components of BC Human Resource — AL extension, ASP.NET API, and React roster add-in — how they fit together, trust boundaries, and data flow.","href":"/architecture"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"The three layers","slug":"the-three-layers","depth":2,"children":[{"title":"1. AL extension — the source of truth","slug":"1-al-extension-—-the-source-of-truth","depth":3,"children":[]},{"title":"2. ASP.NET API — the mobile / portal gateway","slug":"2-asp-net-api-—-the-mobile-/-portal-gateway","depth":3,"children":[]},{"title":"3. React control add-in — the Visual Roster Board","slug":"3-react-control-add-in-—-the-visual-roster-board","depth":3,"children":[]}]},{"title":"Data flow per scenario","slug":"data-flow-per-scenario","depth":2,"children":[{"title":"HR consultant building a roster","slug":"hr-consultant-building-a-roster","depth":3,"children":[]},{"title":"Employee submitting a leave request from a phone app","slug":"employee-submitting-a-leave-request-from-a-phone-app","depth":3,"children":[]}]},{"title":"Trust boundaries","slug":"trust-boundaries","depth":2,"children":[]},{"title":"Technology map","slug":"technology-map","depth":2,"children":[]},{"title":"Build-time coupling","slug":"build-time-coupling","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"haconsultancy-deb296c9","siteName":"BC Human Resource","title":"Architecture Overview - BC Human Resource","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-05-18T18:13:11.314Z","originalFileLocation":"architecture.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"The three components of BC Human Resource — AL extension, ASP.NET API, and React roster add-in — how they fit together, trust boundaries, and data flow.\">\u003cmeta data-mint-head name=\"application-name\" content=\"BC Human Resource\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/haconsultancy-deb296c9/_tsgMVGUofIwVBfx/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/haconsultancy-deb296c9/_tsgMVGUofIwVBfx/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/haconsultancy-deb296c9/_tsgMVGUofIwVBfx/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/haconsultancy-deb296c9/_tsgMVGUofIwVBfx/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/haconsultancy-deb296c9/_tsgMVGUofIwVBfx/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/haconsultancy-deb296c9/_tsgMVGUofIwVBfx/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/haconsultancy-deb296c9/_tsgMVGUofIwVBfx/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/haconsultancy-deb296c9/_tsgMVGUofIwVBfx/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/haconsultancy-deb296c9/_tsgMVGUofIwVBfx/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Architecture Overview - BC Human Resource\">\u003cmeta data-mint-head property=\"og:description\" content=\"The three components of BC Human Resource — AL extension, ASP.NET API, and React roster add-in — how they fit together, trust boundaries, and data flow.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"BC Human Resource\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://human-resource-docs.ha-consultancy.com/architecture\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://haconsultancy-deb296c9.mintlify.site/_mintlify/api/og/v2?division=Architecture\u0026amp;title=Architecture+Overview\u0026amp;description=The+three+components+of+BC+Human+Resource+%E2%80%94+AL+extension%2C+ASP.NET+API%2C+and+React+roster+add-in+%E2%80%94+how+they+fit+together%2C+trust+boundaries%2C+and+data+flow.\u0026amp;theme=49f5e2b753595e83039df319\u0026amp;signature=e1cec0db4729453964d9f89e24fd3a18cb576a6ed37cb89475e09c635d141888\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Architecture Overview - BC Human Resource\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"The three components of BC Human Resource — AL extension, ASP.NET API, and React roster add-in — how they fit together, trust boundaries, and data flow.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://haconsultancy-deb296c9.mintlify.site/_mintlify/api/og/v2?division=Architecture\u0026amp;title=Architecture+Overview\u0026amp;description=The+three+components+of+BC+Human+Resource+%E2%80%94+AL+extension%2C+ASP.NET+API%2C+and+React+roster+add-in+%E2%80%94+how+they+fit+together%2C+trust+boundaries%2C+and+data+flow.\u0026amp;theme=49f5e2b753595e83039df319\u0026amp;signature=e1cec0db4729453964d9f89e24fd3a18cb576a6ed37cb89475e09c635d141888\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/haconsultancy-deb296c9/_tsgMVGUofIwVBfx/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"BC Human Resource\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#041f3d\">\u003clink data-mint-head rel=\"canonical\" href=\"https://human-resource-docs.ha-consultancy.com/architecture\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/architecture.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://human-resource-docs.ha-consultancy.com/#organization\",\"name\":\"BC Human Resource\",\"url\":\"https://human-resource-docs.ha-consultancy.com\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/haconsultancy-deb296c9/Ia0pESaTTMfXk4ts/images/dark-logo.png?fit=max\u0026auto=format\u0026n=Ia0pESaTTMfXk4ts\u0026q=85\u0026s=a9cc74be17f91df9509eeea50b32c25c\"}},{\"@type\":\"WebSite\",\"@id\":\"https://human-resource-docs.ha-consultancy.com#website\",\"name\":\"BC Human Resource\",\"url\":\"https://human-resource-docs.ha-consultancy.com\",\"publisher\":{\"@id\":\"https://human-resource-docs.ha-consultancy.com/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://human-resource-docs.ha-consultancy.com/architecture#webpage\",\"url\":\"https://human-resource-docs.ha-consultancy.com/architecture\",\"name\":\"Architecture Overview\",\"description\":\"The three components of BC Human Resource — AL extension, ASP.NET API, and React roster add-in — how they fit together, trust boundaries, and data flow.\",\"dateModified\":\"2026-05-18T18:13:11.314Z\",\"isPartOf\":{\"@id\":\"https://human-resource-docs.ha-consultancy.com#website\"},\"breadcrumb\":{\"@id\":\"https://human-resource-docs.ha-consultancy.com/architecture#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://human-resource-docs.ha-consultancy.com/architecture#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"BC Human Resource\",\"item\":\"https://human-resource-docs.ha-consultancy.com\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Architecture Overview\",\"item\":\"https://human-resource-docs.ha-consultancy.com/architecture\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://human-resource-docs.ha-consultancy.com/architecture#article\",\"headline\":\"Architecture Overview\",\"name\":\"Architecture Overview\",\"description\":\"The three components of BC Human Resource — AL extension, ASP.NET API, and React roster add-in — how they fit together, trust boundaries, and data flow.\",\"url\":\"https://human-resource-docs.ha-consultancy.com/architecture\",\"mainEntityOfPage\":{\"@id\":\"https://human-resource-docs.ha-consultancy.com/architecture#webpage\"},\"dateModified\":\"2026-05-18T18:13:11.314Z\",\"publisher\":{\"@id\":\"https://human-resource-docs.ha-consultancy.com/#organization\"},\"isPartOf\":{\"@id\":\"https://human-resource-docs.ha-consultancy.com#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"BC Human Resource\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"BC Human Resource","nav":{"global":null,"tabs":[{"tab":"User Guide","icon":"book-open","pages":[{"title":"User Guide","href":"/introduction"}]},{"tab":"Admin \u0026 Compliance","icon":"shield-halved","groups":[{"group":"Configuration Reference","icon":"sliders","pages":[{"title":"HR General Setup","href":"/configuration/hr-general-setup"},{"title":"No. Series","href":"/configuration/no-series"},{"title":"Employee Posting Groups","href":"/configuration/posting-groups"},{"title":"Dimensions","href":"/configuration/dimensions"},{"title":"Workflow Setup","href":"/configuration/workflow-setup"},{"title":"Leave Deduction Rules","description":"Configure per-leave-plan salary deduction rules that reduce specific salary components — basic, transport, housing — when an employee takes unpaid leave.","href":"/configuration/deduction-setup"}]},{"group":"Architecture","icon":"diagram-project","pages":[{"title":"Architecture Overview","href":"/architecture"}]},{"group":"Frontend Deployment","icon":"browser","pages":[{"title":"Frontend Overview","description":"Architecture of the React 19, TypeScript 5, and Vite 7 control add-in that ships the Visual Roster Board inside the BC Human Resource AL extension.","href":"/frontend/overview"},{"title":"BC Integration Contract","href":"/frontend/integration"},{"title":"Roster Board","href":"/frontend/roster-board"}]},{"group":"Compliance","icon":"shield-halved","pages":[{"title":"Permissions","href":"/compliance/permissions"},{"title":"Privacy \u0026 Data Handling","href":"/compliance/privacy"},{"title":"EULA \u0026 Terms","href":"/compliance/eula-and-terms"}]},{"group":"Reference","icon":"book","pages":[{"title":"Glossary","href":"/reference/glossary"},{"title":"Field Prefix Convention","href":"/reference/field-prefix"},{"title":"Object-ID Allocation","href":"/reference/object-id-allocation"}]}]}]},"basePath":"","title":"Architecture Overview - BC Human Resource","buildId":"dpl_FWZhTb2vab28gFGFH4ojka6qEznQ","deploymentId":"dpl_FWZhTb2vab28gFGFH4ojka6qEznQ","dynamics":[],"chunks":[]}