@acorex-platform/extension-project-management-servicem8-client 0.0.30 → 0.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { AXP_INTEGRATION_CONNECTION_STATUS, AXP_INTEGRATION_AUTH_METHOD_TYPE } from '@acorex-platform/framework-shared/core';
1
+ import { createRandomUuid, AXP_INTEGRATION_CONNECTION_STATUS, AXP_INTEGRATION_AUTH_METHOD_TYPE } from '@acorex-platform/framework-shared/core';
2
2
  import { AXPSessionService } from '@acorex-platform/framework-client/auth';
3
3
  import { AXMIntegrationConnectionManager, AXP_INTEGRATION_CREDENTIAL_MASK, toSafeIntegrationConnectionDto, isMaskedIntegrationCredential, hasConfiguredIntegrationCredentials } from '@acorex-platform/module-integration-management-client';
4
4
  import { AXPLayoutBuilderService, resolveLayoutBuilderDialogResult } from '@acorex-platform/framework-client/layout/builder';
@@ -33,7 +33,7 @@ class ConnectServiceM8Command {
33
33
  const isReconfigure = !!existing;
34
34
  const existingConfig = (existing?.config ?? {});
35
35
  const connectionKey = existing?.connectionKey
36
- ?? (trim(input.connectionKey) || crypto.randomUUID());
36
+ ?? (trim(input.connectionKey) || createRandomUuid());
37
37
  const title = await this.translationService.translateAsync(isReconfigure
38
38
  ? '@extensions-servicem8:integration.connect.dialog.reconfigure-title'
39
39
  : '@extensions-servicem8:integration.connect.dialog.title');
@@ -225,4 +225,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
225
225
  }] });
226
226
 
227
227
  export { ConnectServiceM8Command };
228
- //# sourceMappingURL=acorex-platform-extension-project-management-servicem8-client-connect-servicem8.command-CkDOwDrg.mjs.map
228
+ //# sourceMappingURL=acorex-platform-extension-project-management-servicem8-client-connect-servicem8.command-3MmHke4f.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acorex-platform-extension-project-management-servicem8-client-connect-servicem8.command-3MmHke4f.mjs","sources":["../../../../libs/extensions/project-management/servicem8/client/src/lib/features/connect/servicem8-oauth.util.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/features/connect/connect-servicem8.command.ts"],"sourcesContent":["//#region ---- ServiceM8 OAuth helpers (client) ----\n\n/** Resolves the demo app path segment used in the OAuth callback URL. */\nexport function resolveServiceM8OAuthAppName(applicationName: string | null | undefined): string {\n const trimmed = String(applicationName ?? '').trim();\n return trimmed || 'demo';\n}\n\n//#endregion\n","//#region ---- Connect ServiceM8 (OAuth authorization code) ----\n\nimport { AXPCommand, type AXPCommandExecuteResultPromise } from '@acorex-platform/framework-shared/runtime';\nimport {\n AXP_INTEGRATION_AUTH_METHOD_TYPE,\n AXP_INTEGRATION_CONNECTION_STATUS,\n type AXPRuntimeCommandResult,\n type AXPIntegrationConnectionScope,\n createRandomUuid,\n} from '@acorex-platform/framework-shared/core';\nimport { AXPSessionService } from '@acorex-platform/framework-client/auth';\nimport {\n AXMIntegrationConnectionManager,\n AXP_INTEGRATION_CREDENTIAL_MASK,\n hasConfiguredIntegrationCredentials,\n isMaskedIntegrationCredential,\n toSafeIntegrationConnectionDto,\n type ConnectCommandResult,\n} from '@acorex-platform/module-integration-management-client';\nimport {\n AXPLayoutBuilderService,\n resolveLayoutBuilderDialogResult,\n type AXPDialogRef,\n type IActionBuilder,\n type IDialogBuilder,\n type IFlexContainerBuilder,\n type IFormFieldBuilder,\n} from '@acorex-platform/framework-client/layout/builder';\nimport { AXTranslationService } from '@acorex/core/translation';\nimport { AXEServiceM8OAuthHttpClient } from '../../api/servicem8-oauth-http.client';\nimport { Injectable, inject } from '@angular/core';\nimport { defaultTo, isEmpty, trim } from 'lodash-es';\nimport {\n AXE_SERVICEM8_API_BASE_URL,\n AXE_SERVICEM8_OAUTH_SCOPES,\n type AXEServiceM8ConnectionConfig,\n} from '@acorex-platform/extension-project-management-servicem8-client/contracts';\nimport type { AXMIntegrationManagementIntegrationConnectionEntityModel } from '@acorex-platform/module-integration-management-client/contracts';\nimport { resolveServiceM8OAuthAppName } from './servicem8-oauth.util';\n\nexport interface ConnectServiceM8CommandInput {\n providerId?: string;\n connectionScope?: AXPIntegrationConnectionScope;\n /** When set, reconfigure / reconnect this existing connection. */\n connectionId?: string;\n /** Optional explicit key; new connections get a generated key when omitted. */\n connectionKey?: string;\n}\n\ninterface AXEServiceM8ConnectFormContext {\n displayName: string;\n clientId: string;\n clientSecret: string;\n}\n\ninterface AXEServiceM8ConnectFormResult {\n displayName: string;\n clientId: string;\n clientSecret: string;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class ConnectServiceM8Command implements AXPCommand<ConnectServiceM8CommandInput, ConnectCommandResult> {\n private readonly connectionManager = inject(AXMIntegrationConnectionManager);\n private readonly layoutBuilder = inject(AXPLayoutBuilderService);\n private readonly translationService = inject(AXTranslationService);\n private readonly sessionService = inject(AXPSessionService);\n private readonly servicem8Http = inject(AXEServiceM8OAuthHttpClient);\n\n async execute(input: ConnectServiceM8CommandInput = {}): AXPCommandExecuteResultPromise<ConnectCommandResult> {\n try {\n const providerId = input.providerId ?? 'servicem8';\n const connectionScope = input.connectionScope ?? 'tenant';\n const existing = await this.resolveExistingConnection(providerId, connectionScope, input);\n const isReconfigure = !!existing;\n const existingConfig = (existing?.config ?? {}) as AXEServiceM8ConnectionConfig;\n const connectionKey =\n existing?.connectionKey\n ?? (trim(input.connectionKey) || createRandomUuid());\n\n const title = await this.translationService.translateAsync(\n isReconfigure\n ? '@extensions-servicem8:integration.connect.dialog.reconfigure-title'\n : '@extensions-servicem8:integration.connect.dialog.title',\n );\n\n const form = await this.openConnectDialog({\n title: title ?? 'Connect ServiceM8',\n context: {\n displayName: existing?.displayName ?? 'ServiceM8',\n clientId: existingConfig.clientId ?? '',\n clientSecret: isReconfigure ? AXP_INTEGRATION_CREDENTIAL_MASK : '',\n },\n existing,\n existingConfig,\n isReconfigure,\n });\n\n if (!form) {\n return { success: true, kind: 'object', data: { outcome: 'cancelled' } };\n }\n\n const credentialsChanged = this.credentialsChanged(existingConfig, form);\n const needsAuthorization = this.needsOAuthAuthorization(existingConfig, credentialsChanged);\n\n const config: AXEServiceM8ConnectionConfig = {\n clientId: form.clientId,\n clientSecret: form.clientSecret,\n scope: AXE_SERVICEM8_OAUTH_SCOPES,\n ...(credentialsChanged\n ? {}\n : {\n accessToken: existingConfig.accessToken,\n refreshToken: existingConfig.refreshToken,\n tokenExpiresAt: existingConfig.tokenExpiresAt,\n oauthTokenJson: existingConfig.oauthTokenJson,\n }),\n };\n\n const connection = await this.connectionManager.upsertConnection({\n providerId,\n authMethodType: AXP_INTEGRATION_AUTH_METHOD_TYPE.OAuth2,\n connectionScope,\n connectionKey,\n displayName: defaultTo(trim(form.displayName), 'ServiceM8'),\n instanceUrl: AXE_SERVICEM8_API_BASE_URL,\n externalAccountName: AXP_INTEGRATION_CREDENTIAL_MASK,\n config: config as Record<string, unknown>,\n status: needsAuthorization\n ? AXP_INTEGRATION_CONNECTION_STATUS.PendingAuthorization\n : AXP_INTEGRATION_CONNECTION_STATUS.Connected,\n isRevoked: false,\n isDisabled: false,\n });\n\n if (needsAuthorization) {\n const appName = resolveServiceM8OAuthAppName(this.sessionService.application?.name);\n const redirectUri = `${window.location.origin}/${appName}/integrations/oauth/callback/${providerId}`;\n const oauth = await this.servicem8Http.startOAuth({\n redirectUri,\n connectionId: connection.id,\n });\n return { success: true, kind: 'object',\n data: { outcome: 'redirect', redirectUrl: oauth.redirectUrl },\n };\n }\n\n const validation = await this.servicem8Http.validateOAuth({ connectionId: connection.id });\n if (!validation.valid) {\n return {\n success: false,\n message: { text: validation.message ?? 'ServiceM8 connection validation failed. Re-authorize ServiceM8.' },\n };\n }\n\n const refreshed = await this.connectionManager.getByIdForCurrentUser(connection.id);\n return { success: true, kind: 'object',\n data: {\n outcome: 'connected',\n connection: toSafeIntegrationConnectionDto(refreshed ?? connection),\n },\n };\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : 'Failed to connect ServiceM8.';\n return { success: false, message: { text: message } };\n }\n }\n\n private async resolveExistingConnection(\n providerId: string,\n connectionScope: AXPIntegrationConnectionScope,\n input: ConnectServiceM8CommandInput,\n ): Promise<AXMIntegrationManagementIntegrationConnectionEntityModel | null> {\n if (input.connectionId) {\n const byId = await this.connectionManager.getByIdForCurrentUser(input.connectionId);\n if (!byId) {\n throw new Error('Connection not found or access denied.');\n }\n return byId;\n }\n\n const connectionKey = trim(input.connectionKey);\n if (!connectionKey) {\n return null;\n }\n\n return this.connectionManager.findByUniqueKey({\n providerId,\n connectionScope,\n connectionKey,\n });\n }\n\n private credentialsChanged(\n existingConfig: AXEServiceM8ConnectionConfig,\n form: AXEServiceM8ConnectFormResult,\n ): boolean {\n if (trim(form.clientId) !== trim(existingConfig.clientId ?? '')) {\n return true;\n }\n if (!isMaskedIntegrationCredential(form.clientSecret)) {\n return true;\n }\n return false;\n }\n\n private needsOAuthAuthorization(\n existingConfig: AXEServiceM8ConnectionConfig,\n credentialsChanged: boolean,\n ): boolean {\n if (credentialsChanged) {\n return true;\n }\n return isEmpty(trim(existingConfig.refreshToken ?? ''));\n }\n\n private async openConnectDialog(input: {\n title: string;\n context: AXEServiceM8ConnectFormContext;\n existing: AXMIntegrationManagementIntegrationConnectionEntityModel | null;\n existingConfig: AXEServiceM8ConnectionConfig;\n isReconfigure: boolean;\n }): Promise<AXEServiceM8ConnectFormResult | null> {\n const result: unknown = await this.layoutBuilder\n .create()\n .dialog<AXEServiceM8ConnectFormResult | null>((dialog: IDialogBuilder<AXEServiceM8ConnectFormResult | null>) => {\n dialog\n .setTitle(input.title)\n .setSize('md')\n .setCloseButton(true)\n .setContext({ ...input.context })\n .content((flex: IFlexContainerBuilder) => {\n flex\n .setDirection('column')\n .setGap('12px')\n .customWidget('text-block-layout', {\n content: '@extensions-servicem8:integration.connect.instructions',\n })\n .formField('@extensions-servicem8:integration.connect.fields.display-name', (field: IFormFieldBuilder) => {\n field.path('displayName');\n field.textBox({\n placeholder: '@extensions-servicem8:integration.connect.placeholders.display-name',\n });\n })\n .formField('@extensions-servicem8:integration.auth.oauth.client-id.label', (field) => {\n field.path('clientId');\n field.textBox({\n placeholder: '@extensions-servicem8:integration.auth.oauth.client-id.placeholder',\n validations: [{ rule: 'required' }],\n });\n })\n .formField('@extensions-servicem8:integration.auth.oauth.client-secret.label', (field) => {\n field.path('clientSecret');\n field.passwordBox({\n placeholder: '@extensions-servicem8:integration.auth.oauth.client-secret.placeholder',\n revealToggle: true,\n validations: input.isReconfigure ? [] : [{ rule: 'required' }],\n });\n });\n })\n .setActions((actions: IActionBuilder) => {\n actions\n .cancel('@general:actions.cancel.title')\n .submit(\n input.isReconfigure && hasConfiguredIntegrationCredentials(input.existing)\n ? '@extensions-servicem8:integration.connect.actions.save'\n : '@extensions-servicem8:integration.connect.actions.save-and-authorize',\n );\n })\n .onAction(async (ref: AXPDialogRef) => {\n if (ref.action() === 'cancel') {\n return null;\n }\n\n const ctx = ref.context() as AXEServiceM8ConnectFormContext;\n const clientId = trim(ctx.clientId);\n if (!clientId) {\n throw new Error('ServiceM8 App ID is required.');\n }\n\n let clientSecret = trim(ctx.clientSecret);\n if (isMaskedIntegrationCredential(clientSecret) && input.existing) {\n clientSecret = trim(input.existingConfig.clientSecret ?? '');\n }\n if (!clientSecret) {\n throw new Error('ServiceM8 App Secret is required.');\n }\n\n return {\n displayName: trim(ctx.displayName) || 'ServiceM8',\n clientId,\n clientSecret,\n };\n });\n })\n .show();\n\n return resolveLayoutBuilderDialogResult<AXEServiceM8ConnectFormResult>(result);\n }\n}\n\n//#endregion\n"],"names":[],"mappings":"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"}
@@ -19,10 +19,12 @@ const RootConfig = {
19
19
  const AXEServiceM8CommandKeys = {
20
20
  ConnectionConnect: 'servicem8-extension.Connection:Connect',
21
21
  ConnectionCompleteOAuth: 'servicem8-extension.Connection:CompleteOAuth',
22
+ ConnectionLifecycle: 'servicem8-extension.Connection:Lifecycle',
22
23
  OAuthStart: 'servicem8-extension.OAuth:Start',
23
24
  OAuthComplete: 'servicem8-extension.OAuth:Complete',
24
25
  OAuthValidate: 'servicem8-extension.OAuth:Validate',
25
26
  ProjectImportFromServiceM8: 'servicem8-extension.Project:ImportFromServiceM8',
27
+ CustomerImportFromServiceM8: 'servicem8-extension.Customer:ImportFromServiceM8',
26
28
  ProjectSync: 'servicem8-extension.Project:Sync',
27
29
  JobsPull: 'servicem8-extension.Jobs:Pull',
28
30
  JobsRefresh: 'servicem8-extension.Jobs:Refresh',
@@ -74,6 +76,9 @@ const AXEServiceM8Permissions = {
74
76
  ProjectImport: {
75
77
  Execute: AXPAdministrationPermissionKeys.ManageIntegrationActions,
76
78
  },
79
+ CustomerImport: {
80
+ Execute: AXPAdministrationPermissionKeys.ManageIntegrationActions,
81
+ },
77
82
  ProjectSync: {
78
83
  Execute: AXPAdministrationPermissionKeys.ManageIntegrationActions,
79
84
  },
@@ -87,7 +92,7 @@ const AXEServiceM8Permissions = {
87
92
  //#region ---- ServiceM8 OAuth API (client ↔ server) ----
88
93
  //#endregion
89
94
 
90
- //#region ---- Remote ServiceM8 job DTO ----
95
+ //#region ---- Remote ServiceM8 DTOs ----
91
96
  //#endregion
92
97
 
93
98
  //#region ---- ServiceM8 API / OAuth constants ----
@@ -98,10 +103,10 @@ const AXE_SERVICEM8_OAUTH_TOKEN_URL = 'https://go.servicem8.com/oauth/access_tok
98
103
  /** REST API base URL stored on connection `instanceUrl`. */
99
104
  const AXE_SERVICEM8_API_BASE_URL = 'https://api.servicem8.com';
100
105
  /**
101
- * OAuth scopes for connect.
102
- * `manage_jobs` is included so push sync can be added later without re-consent.
106
+ * OAuth scopes for connect / authorize.
107
+ * Space-separated; encoded as `scope=` query value by the OAuth start flow.
103
108
  */
104
- const AXE_SERVICEM8_OAUTH_SCOPES = 'read_jobs manage_jobs vendor';
109
+ const AXE_SERVICEM8_OAUTH_SCOPES = 'read_customers read_jobs read_staff read_job_contacts read_job_attachments publish_job_attachments read_customer_contacts';
105
110
  //#endregion
106
111
 
107
112
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"acorex-platform-extension-project-management-servicem8-client-contracts.mjs","sources":["../../../../libs/extensions/project-management/servicem8/contracts/src/root-config.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/commands.keys.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/menu.keys.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/permission.keys.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/connection.types.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/oauth.api.types.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/project-sync.api.types.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/servicem8.constants.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/acorex-platform-extension-project-management-servicem8-client-contracts.ts"],"sourcesContent":["//#region ---- Module Config ----\n\nconst config = {\n i18n: 'extensions-servicem8',\n};\n\nconst MODULE_NAME = 'ServiceM8Extension';\n\nexport const RootConfig = {\n config,\n module: {\n name: MODULE_NAME,\n title: `@${config.i18n}:integration.display-name`,\n icon: 'fa-light fa-briefcase',\n },\n};\n\n//#endregion\n","//#region ---- Command Keys ----\n\nexport const AXEServiceM8CommandKeys = {\n ConnectionConnect: 'servicem8-extension.Connection:Connect',\n ConnectionCompleteOAuth: 'servicem8-extension.Connection:CompleteOAuth',\n OAuthStart: 'servicem8-extension.OAuth:Start',\n OAuthComplete: 'servicem8-extension.OAuth:Complete',\n OAuthValidate: 'servicem8-extension.OAuth:Validate',\n ProjectImportFromServiceM8: 'servicem8-extension.Project:ImportFromServiceM8',\n ProjectSync: 'servicem8-extension.Project:Sync',\n JobsPull: 'servicem8-extension.Jobs:Pull',\n JobsRefresh: 'servicem8-extension.Jobs:Refresh',\n} as const;\n\n//#endregion\n","//#region ---- Menu Keys ----\n\nexport const AXEServiceM8MenuKeys = {\n ServiceM8ExtensionConfig: 'servicem8-extension:Menu:Configuration',\n} as const;\n\n//#endregion\n","//#region ---- Permission Keys ----\n\n/**\n * Administration capabilities used by this extension.\n * Domain capability keys for this module. Root groups live in `AXPCommonPermissionGroupKeys` (common contracts).\n */\nexport const AXPAdministrationPermissionKeys = {\n Access: 'Administration:Permission:Access',\n MaintainUsersAndRoles: 'Administration:Permission:MaintainUsersAndRoles',\n ManageSessions: 'Administration:Permission:ManageSessions',\n MaintainPolicies: 'Administration:Permission:MaintainPolicies',\n ViewIntegrations: 'Administration:Permission:ViewIntegrations',\n MaintainConnections: 'Administration:Permission:MaintainConnections',\n ManageIntegrationActions: 'Administration:Permission:ManageIntegrationActions',\n MaintainWorkflows: 'Administration:Permission:MaintainWorkflows',\n ManageWorkflowActions: 'Administration:Permission:ManageWorkflowActions',\n ViewRules: 'Administration:Permission:ViewRules',\n MaintainRules: 'Administration:Permission:MaintainRules',\n ManageRuleActions: 'Administration:Permission:ManageRuleActions',\n MaintainAi: 'Administration:Permission:MaintainAi',\n UseAiAssist: 'Administration:Permission:UseAiAssist',\n MaintainDashboards: 'Administration:Permission:MaintainDashboards',\n ViewJobs: 'Administration:Permission:ViewJobs',\n ManageJobActions: 'Administration:Permission:ManageJobActions',\n ViewObservability: 'Administration:Permission:ViewObservability',\n MaintainObservability: 'Administration:Permission:MaintainObservability',\n} as const;\n\n/**\n * @deprecated Prefer {@link AXPAdministrationPermissionKeys}.\n * Legacy nested shape kept so existing imports compile during migration.\n */\nexport const AXEServiceM8Permissions = {\n ServiceM8Extension: {\n Management: AXPAdministrationPermissionKeys.MaintainConnections,\n Catalog: {\n View: AXPAdministrationPermissionKeys.ViewIntegrations,\n },\n ProjectImport: {\n Execute: AXPAdministrationPermissionKeys.ManageIntegrationActions,\n },\n ProjectSync: {\n Execute: AXPAdministrationPermissionKeys.ManageIntegrationActions,\n },\n },\n} as const;\n\n//#endregion\n","//#region ---- ServiceM8 tenant connection config ----\n\n/** Stored on IntegrationManagement connection `config` for tenant-scoped ServiceM8. */\nexport interface AXEServiceM8ConnectionConfig {\n /** OAuth2 App ID from the ServiceM8 developer add-on. */\n clientId?: string;\n /** OAuth2 App Secret from the ServiceM8 developer add-on. */\n clientSecret?: string;\n /** Space-separated scopes granted at authorize time. */\n scope?: string;\n /** Current bearer token (authorization-code flow). */\n accessToken?: string;\n /** Refresh token from ServiceM8 token response. */\n refreshToken?: string;\n /** ISO timestamp when `accessToken` expires. */\n tokenExpiresAt?: string;\n /** Full ServiceM8 token JSON response. */\n oauthTokenJson?: string;\n /** Background job pull sync state (server-managed). */\n jobsSync?: AXEServiceM8JobsSyncState;\n}\n\n/** ServiceM8 job pull sync state stored on the tenant connection `config.jobsSync`. */\nexport interface AXEServiceM8JobsSyncState {\n /** ISO timestamp — last successful job pull (incremental sync watermark). */\n lastSyncAt?: string;\n /** ISO timestamp — last full snapshot reconcile (deactivation pass). */\n lastFullReconcileAt?: string;\n}\n\n//#endregion\n","//#region ---- ServiceM8 OAuth API (client ↔ server) ----\n\nexport interface AXEServiceM8OAuthStartRequest {\n redirectUri: string;\n connectionId?: string;\n}\n\nexport interface AXEServiceM8OAuthStartResponse {\n redirectUrl: string;\n state: string;\n}\n\nexport interface AXEServiceM8OAuthCompleteRequest {\n state: string;\n code: string;\n}\n\nexport interface AXEServiceM8OAuthCompleteResponse {\n connectionId: string;\n}\n\nexport interface AXEServiceM8OAuthValidateRequest {\n connectionId?: string;\n}\n\nexport interface AXEServiceM8OAuthValidateResponse {\n valid: boolean;\n message?: string;\n}\n\n//#endregion\n","//#region ---- Remote ServiceM8 job DTO ----\n\nimport type { AXEServiceM8ProjectSyncStrategy } from './sync.types';\n\n/** Subset of ServiceM8 `job.json` fields used for cache / import / sync. */\nexport interface AXEServiceM8RemoteJob {\n uuid: string;\n generated_job_id?: string | null;\n job_description?: string | null;\n job_address?: string | null;\n status?: string | null;\n date?: string | null;\n edit_date?: string | null;\n company_uuid?: string | null;\n company_name?: string | null;\n purchase_order_number?: string | null;\n active?: number | boolean | null;\n billing_address?: string | null;\n work_done_description?: string | null;\n}\n\n//#endregion\n\n//#region ---- API request / response ----\n\nexport interface AXEServiceM8PullJobsRequest {\n /** Maximum ServiceM8 jobs to pull into the cache (default 250, max 1000). */\n maxJobs?: number;\n /**\n * When true, runs a full snapshot (no incremental filter) and reconciles deactivations.\n * When false (default for schedules), pulls jobs modified since the last successful sync.\n */\n force?: boolean;\n}\n\nexport interface AXEServiceM8PullJobsResponse {\n upserted: number;\n deactivated: number;\n /** True when incremental pull found no modified jobs in ServiceM8. */\n skipped?: boolean;\n /** Whether this run used incremental or full snapshot mode. */\n syncMode?: 'incremental' | 'full';\n}\n\nexport interface AXEServiceM8ImportJobRequest {\n remoteRecordId: string;\n projectTypeId: string;\n syncStrategy: AXEServiceM8ProjectSyncStrategy;\n}\n\nexport interface AXEServiceM8ImportJobResponse {\n platformProjectId: string;\n created: boolean;\n}\n\nexport interface AXEServiceM8SyncPlatformProjectRequest {\n platformProjectId: string;\n}\n\n//#endregion\n","//#region ---- ServiceM8 API / OAuth constants ----\n\n/** Public OAuth authorize endpoint (fixed host). */\nexport const AXE_SERVICEM8_OAUTH_AUTHORIZE_URL = 'https://go.servicem8.com/oauth/authorize';\n\n/** Public OAuth token endpoint (authorization_code + refresh_token). */\nexport const AXE_SERVICEM8_OAUTH_TOKEN_URL = 'https://go.servicem8.com/oauth/access_token';\n\n/** REST API base URL stored on connection `instanceUrl`. */\nexport const AXE_SERVICEM8_API_BASE_URL = 'https://api.servicem8.com';\n\n/**\n * OAuth scopes for connect.\n * `manage_jobs` is included so push sync can be added later without re-consent.\n */\nexport const AXE_SERVICEM8_OAUTH_SCOPES = 'read_jobs manage_jobs vendor';\n\n//#endregion\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;AAAA;AAEA,MAAM,MAAM,GAAG;AACb,IAAA,IAAI,EAAE,sBAAsB;CAC7B;AAED,MAAM,WAAW,GAAG,oBAAoB;AAEjC,MAAM,UAAU,GAAG;IACxB,MAAM;AACN,IAAA,MAAM,EAAE;AACN,QAAA,IAAI,EAAE,WAAW;AACjB,QAAA,KAAK,EAAE,CAAA,CAAA,EAAI,MAAM,CAAC,IAAI,CAAA,yBAAA,CAA2B;AACjD,QAAA,IAAI,EAAE,uBAAuB;AAC9B,KAAA;;AAGH;;ACjBA;AAEO,MAAM,uBAAuB,GAAG;AACrC,IAAA,iBAAiB,EAAE,wCAAwC;AAC3D,IAAA,uBAAuB,EAAE,8CAA8C;AACvE,IAAA,UAAU,EAAE,iCAAiC;AAC7C,IAAA,aAAa,EAAE,oCAAoC;AACnD,IAAA,aAAa,EAAE,oCAAoC;AACnD,IAAA,0BAA0B,EAAE,iDAAiD;AAC7E,IAAA,WAAW,EAAE,kCAAkC;AAC/C,IAAA,QAAQ,EAAE,+BAA+B;AACzC,IAAA,WAAW,EAAE,kCAAkC;;AAGjD;;ACdA;AAEO,MAAM,oBAAoB,GAAG;AAClC,IAAA,wBAAwB,EAAE,wCAAwC;;AAGpE;;ACNA;AAEA;;;AAGG;AACI,MAAM,+BAA+B,GAAG;AAC7C,IAAA,MAAM,EAAE,kCAAkC;AAC1C,IAAA,qBAAqB,EAAE,iDAAiD;AACxE,IAAA,cAAc,EAAE,0CAA0C;AAC1D,IAAA,gBAAgB,EAAE,4CAA4C;AAC9D,IAAA,gBAAgB,EAAE,4CAA4C;AAC9D,IAAA,mBAAmB,EAAE,+CAA+C;AACpE,IAAA,wBAAwB,EAAE,oDAAoD;AAC9E,IAAA,iBAAiB,EAAE,6CAA6C;AAChE,IAAA,qBAAqB,EAAE,iDAAiD;AACxE,IAAA,SAAS,EAAE,qCAAqC;AAChD,IAAA,aAAa,EAAE,yCAAyC;AACxD,IAAA,iBAAiB,EAAE,6CAA6C;AAChE,IAAA,UAAU,EAAE,sCAAsC;AAClD,IAAA,WAAW,EAAE,uCAAuC;AACpD,IAAA,kBAAkB,EAAE,8CAA8C;AAClE,IAAA,QAAQ,EAAE,oCAAoC;AAC9C,IAAA,gBAAgB,EAAE,4CAA4C;AAC9D,IAAA,iBAAiB,EAAE,6CAA6C;AAChE,IAAA,qBAAqB,EAAE,iDAAiD;;AAG1E;;;AAGG;AACI,MAAM,uBAAuB,GAAG;AACrC,IAAA,kBAAkB,EAAE;QAClB,UAAU,EAAE,+BAA+B,CAAC,mBAAmB;AAC/D,QAAA,OAAO,EAAE;YACP,IAAI,EAAE,+BAA+B,CAAC,gBAAgB;AACvD,SAAA;AACD,QAAA,aAAa,EAAE;YACb,OAAO,EAAE,+BAA+B,CAAC,wBAAwB;AAClE,SAAA;AACD,QAAA,WAAW,EAAE;YACX,OAAO,EAAE,+BAA+B,CAAC,wBAAwB;AAClE,SAAA;AACF,KAAA;;AAGH;;AC/CA;AA8BA;;AC9BA;AA8BA;;AC9BA;AA2DA;;AC3DA;AAEA;AACO,MAAM,iCAAiC,GAAG;AAEjD;AACO,MAAM,6BAA6B,GAAG;AAE7C;AACO,MAAM,0BAA0B,GAAG;AAE1C;;;AAGG;AACI,MAAM,0BAA0B,GAAG;AAE1C;;ACjBA;;AAEG;;;;"}
1
+ {"version":3,"file":"acorex-platform-extension-project-management-servicem8-client-contracts.mjs","sources":["../../../../libs/extensions/project-management/servicem8/contracts/src/root-config.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/commands.keys.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/menu.keys.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/permission.keys.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/connection.types.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/oauth.api.types.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/project-sync.api.types.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/servicem8.constants.ts","../../../../libs/extensions/project-management/servicem8/contracts/src/acorex-platform-extension-project-management-servicem8-client-contracts.ts"],"sourcesContent":["//#region ---- Module Config ----\n\nconst config = {\n i18n: 'extensions-servicem8',\n};\n\nconst MODULE_NAME = 'ServiceM8Extension';\n\nexport const RootConfig = {\n config,\n module: {\n name: MODULE_NAME,\n title: `@${config.i18n}:integration.display-name`,\n icon: 'fa-light fa-briefcase',\n },\n};\n\n//#endregion\n","//#region ---- Command Keys ----\n\nexport const AXEServiceM8CommandKeys = {\n ConnectionConnect: 'servicem8-extension.Connection:Connect',\n ConnectionCompleteOAuth: 'servicem8-extension.Connection:CompleteOAuth',\n ConnectionLifecycle: 'servicem8-extension.Connection:Lifecycle',\n OAuthStart: 'servicem8-extension.OAuth:Start',\n OAuthComplete: 'servicem8-extension.OAuth:Complete',\n OAuthValidate: 'servicem8-extension.OAuth:Validate',\n ProjectImportFromServiceM8: 'servicem8-extension.Project:ImportFromServiceM8',\n CustomerImportFromServiceM8: 'servicem8-extension.Customer:ImportFromServiceM8',\n ProjectSync: 'servicem8-extension.Project:Sync',\n JobsPull: 'servicem8-extension.Jobs:Pull',\n JobsRefresh: 'servicem8-extension.Jobs:Refresh',\n} as const;\n\n//#endregion\n","//#region ---- Menu Keys ----\n\nexport const AXEServiceM8MenuKeys = {\n ServiceM8ExtensionConfig: 'servicem8-extension:Menu:Configuration',\n} as const;\n\n//#endregion\n","//#region ---- Permission Keys ----\n\n/**\n * Administration capabilities used by this extension.\n * Domain capability keys for this module. Root groups live in `AXPCommonPermissionGroupKeys` (common contracts).\n */\nexport const AXPAdministrationPermissionKeys = {\n Access: 'Administration:Permission:Access',\n MaintainUsersAndRoles: 'Administration:Permission:MaintainUsersAndRoles',\n ManageSessions: 'Administration:Permission:ManageSessions',\n MaintainPolicies: 'Administration:Permission:MaintainPolicies',\n ViewIntegrations: 'Administration:Permission:ViewIntegrations',\n MaintainConnections: 'Administration:Permission:MaintainConnections',\n ManageIntegrationActions: 'Administration:Permission:ManageIntegrationActions',\n MaintainWorkflows: 'Administration:Permission:MaintainWorkflows',\n ManageWorkflowActions: 'Administration:Permission:ManageWorkflowActions',\n ViewRules: 'Administration:Permission:ViewRules',\n MaintainRules: 'Administration:Permission:MaintainRules',\n ManageRuleActions: 'Administration:Permission:ManageRuleActions',\n MaintainAi: 'Administration:Permission:MaintainAi',\n UseAiAssist: 'Administration:Permission:UseAiAssist',\n MaintainDashboards: 'Administration:Permission:MaintainDashboards',\n ViewJobs: 'Administration:Permission:ViewJobs',\n ManageJobActions: 'Administration:Permission:ManageJobActions',\n ViewObservability: 'Administration:Permission:ViewObservability',\n MaintainObservability: 'Administration:Permission:MaintainObservability',\n} as const;\n\n/**\n * @deprecated Prefer {@link AXPAdministrationPermissionKeys}.\n * Legacy nested shape kept so existing imports compile during migration.\n */\nexport const AXEServiceM8Permissions = {\n ServiceM8Extension: {\n Management: AXPAdministrationPermissionKeys.MaintainConnections,\n Catalog: {\n View: AXPAdministrationPermissionKeys.ViewIntegrations,\n },\n ProjectImport: {\n Execute: AXPAdministrationPermissionKeys.ManageIntegrationActions,\n },\n CustomerImport: {\n Execute: AXPAdministrationPermissionKeys.ManageIntegrationActions,\n },\n ProjectSync: {\n Execute: AXPAdministrationPermissionKeys.ManageIntegrationActions,\n },\n },\n} as const;\n\n//#endregion\n","//#region ---- ServiceM8 tenant connection config ----\n\n/** Stored on IntegrationManagement connection `config` for tenant-scoped ServiceM8. */\nexport interface AXEServiceM8ConnectionConfig {\n /** OAuth2 App ID from the ServiceM8 developer add-on. */\n clientId?: string;\n /** OAuth2 App Secret from the ServiceM8 developer add-on. */\n clientSecret?: string;\n /** Space-separated scopes granted at authorize time. */\n scope?: string;\n /** Current bearer token (authorization-code flow). */\n accessToken?: string;\n /** Refresh token from ServiceM8 token response. */\n refreshToken?: string;\n /** ISO timestamp when `accessToken` expires. */\n tokenExpiresAt?: string;\n /** Full ServiceM8 token JSON response. */\n oauthTokenJson?: string;\n /** Background job pull sync state (server-managed). */\n jobsSync?: AXEServiceM8JobsSyncState;\n}\n\n/** ServiceM8 job pull sync state stored on the tenant connection `config.jobsSync`. */\nexport interface AXEServiceM8JobsSyncState {\n /** ISO timestamp — last successful job pull (incremental sync watermark). */\n lastSyncAt?: string;\n /** ISO timestamp — last full snapshot reconcile (deactivation pass). */\n lastFullReconcileAt?: string;\n}\n\n//#endregion\n","//#region ---- ServiceM8 OAuth API (client ↔ server) ----\n\nexport interface AXEServiceM8OAuthStartRequest {\n redirectUri: string;\n connectionId?: string;\n}\n\nexport interface AXEServiceM8OAuthStartResponse {\n redirectUrl: string;\n state: string;\n}\n\nexport interface AXEServiceM8OAuthCompleteRequest {\n state: string;\n code: string;\n}\n\nexport interface AXEServiceM8OAuthCompleteResponse {\n connectionId: string;\n}\n\nexport interface AXEServiceM8OAuthValidateRequest {\n connectionId?: string;\n}\n\nexport interface AXEServiceM8OAuthValidateResponse {\n valid: boolean;\n message?: string;\n}\n\n//#endregion\n","//#region ---- Remote ServiceM8 DTOs ----\n\nimport type { AXEServiceM8ProjectSyncStrategy } from './sync.types';\n\n/** Subset of ServiceM8 `job.json` fields used for cache / import / sync. */\nexport interface AXEServiceM8RemoteJob {\n uuid: string;\n generated_job_id?: string | null;\n job_description?: string | null;\n job_address?: string | null;\n status?: string | null;\n date?: string | null;\n edit_date?: string | null;\n company_uuid?: string | null;\n company_name?: string | null;\n purchase_order_number?: string | null;\n active?: number | boolean | null;\n billing_address?: string | null;\n work_done_description?: string | null;\n}\n\n/**\n * ServiceM8 company (client) — cached as `resourceType=customer`.\n * Includes ServiceM8 address fields plus optional Simpro-shaped aliases when present.\n */\nexport interface AXEServiceM8RemoteCustomer {\n uuid: string;\n name?: string | null;\n address?: string | null;\n address_street?: string | null;\n address_city?: string | null;\n address_state?: string | null;\n address_postcode?: string | null;\n address_country?: string | null;\n fax_number?: string | null;\n FamilyName?: string | null;\n Phone?: string | null;\n Email?: string | null;\n EIN?: string | null;\n CompanyName?: string | null;\n active?: number | boolean | null;\n edit_date?: string | null;\n}\n\n/** ServiceM8 companycontact — cached as `resourceType=site`. */\nexport interface AXEServiceM8RemoteSite {\n uuid: string;\n company_uuid?: string | null;\n first?: string | null;\n last?: string | null;\n phone?: string | null;\n mobile?: string | null;\n email?: string | null;\n type?: string | null;\n active?: number | boolean | null;\n edit_date?: string | null;\n}\n\n//#endregion\n\n//#region ---- API request / response ----\n\nexport interface AXEServiceM8PullJobsRequest {\n /** When set, pulls for this ServiceM8 connection (required for multi-connection schedules). */\n connectionId?: string;\n /** Maximum ServiceM8 jobs to pull into the cache (default 250, max 1000). */\n maxJobs?: number;\n /**\n * When true, runs a full snapshot (no incremental filter) and reconciles deactivations.\n * When false (default for schedules), pulls jobs modified since the last successful sync.\n */\n force?: boolean;\n}\n\nexport interface AXEServiceM8PullJobsResponse {\n upserted: number;\n deactivated: number;\n /** True when incremental pull found no modified jobs in ServiceM8. */\n skipped?: boolean;\n /** Whether this run used incremental or full snapshot mode. */\n syncMode?: 'incremental' | 'full';\n /** Per data-type upsert counts (jobs / customers / sites). */\n byResourceType?: {\n job?: { upserted: number; deactivated: number };\n customer?: { upserted: number; deactivated: number };\n site?: { upserted: number; deactivated: number };\n };\n}\n\nexport interface AXEServiceM8ImportJobRequest {\n remoteRecordId: string;\n projectTypeId: string;\n syncStrategy: AXEServiceM8ProjectSyncStrategy;\n}\n\nexport interface AXEServiceM8ImportJobResponse {\n platformProjectId: string;\n created: boolean;\n}\n\nexport interface AXEServiceM8ImportCustomerRequest {\n remoteRecordId: string;\n}\n\nexport interface AXEServiceM8ImportCustomerResponse {\n platformCustomerId: string;\n created: boolean;\n}\n\nexport interface AXEServiceM8SyncPlatformProjectRequest {\n platformProjectId: string;\n}\n\nexport interface AXEServiceM8ConnectionLifecycleRequest {\n connectionId: string;\n action: 'connected' | 'disconnected' | 'deleted' | 'enabled' | 'disabled';\n}\n\nexport interface AXEServiceM8ConnectionLifecycleResponse {\n ok: true;\n}\n\n//#endregion\n","//#region ---- ServiceM8 API / OAuth constants ----\n\n/** Public OAuth authorize endpoint (fixed host). */\nexport const AXE_SERVICEM8_OAUTH_AUTHORIZE_URL = 'https://go.servicem8.com/oauth/authorize';\n\n/** Public OAuth token endpoint (authorization_code + refresh_token). */\nexport const AXE_SERVICEM8_OAUTH_TOKEN_URL = 'https://go.servicem8.com/oauth/access_token';\n\n/** REST API base URL stored on connection `instanceUrl`. */\nexport const AXE_SERVICEM8_API_BASE_URL = 'https://api.servicem8.com';\n\n/**\n * OAuth scopes for connect / authorize.\n * Space-separated; encoded as `scope=` query value by the OAuth start flow.\n */\nexport const AXE_SERVICEM8_OAUTH_SCOPES =\n 'read_customers read_jobs read_staff read_job_contacts read_job_attachments publish_job_attachments read_customer_contacts';\n\n//#endregion\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;AAAA;AAEA,MAAM,MAAM,GAAG;AACb,IAAA,IAAI,EAAE,sBAAsB;CAC7B;AAED,MAAM,WAAW,GAAG,oBAAoB;AAEjC,MAAM,UAAU,GAAG;IACxB,MAAM;AACN,IAAA,MAAM,EAAE;AACN,QAAA,IAAI,EAAE,WAAW;AACjB,QAAA,KAAK,EAAE,CAAA,CAAA,EAAI,MAAM,CAAC,IAAI,CAAA,yBAAA,CAA2B;AACjD,QAAA,IAAI,EAAE,uBAAuB;AAC9B,KAAA;;AAGH;;ACjBA;AAEO,MAAM,uBAAuB,GAAG;AACrC,IAAA,iBAAiB,EAAE,wCAAwC;AAC3D,IAAA,uBAAuB,EAAE,8CAA8C;AACvE,IAAA,mBAAmB,EAAE,0CAA0C;AAC/D,IAAA,UAAU,EAAE,iCAAiC;AAC7C,IAAA,aAAa,EAAE,oCAAoC;AACnD,IAAA,aAAa,EAAE,oCAAoC;AACnD,IAAA,0BAA0B,EAAE,iDAAiD;AAC7E,IAAA,2BAA2B,EAAE,kDAAkD;AAC/E,IAAA,WAAW,EAAE,kCAAkC;AAC/C,IAAA,QAAQ,EAAE,+BAA+B;AACzC,IAAA,WAAW,EAAE,kCAAkC;;AAGjD;;AChBA;AAEO,MAAM,oBAAoB,GAAG;AAClC,IAAA,wBAAwB,EAAE,wCAAwC;;AAGpE;;ACNA;AAEA;;;AAGG;AACI,MAAM,+BAA+B,GAAG;AAC7C,IAAA,MAAM,EAAE,kCAAkC;AAC1C,IAAA,qBAAqB,EAAE,iDAAiD;AACxE,IAAA,cAAc,EAAE,0CAA0C;AAC1D,IAAA,gBAAgB,EAAE,4CAA4C;AAC9D,IAAA,gBAAgB,EAAE,4CAA4C;AAC9D,IAAA,mBAAmB,EAAE,+CAA+C;AACpE,IAAA,wBAAwB,EAAE,oDAAoD;AAC9E,IAAA,iBAAiB,EAAE,6CAA6C;AAChE,IAAA,qBAAqB,EAAE,iDAAiD;AACxE,IAAA,SAAS,EAAE,qCAAqC;AAChD,IAAA,aAAa,EAAE,yCAAyC;AACxD,IAAA,iBAAiB,EAAE,6CAA6C;AAChE,IAAA,UAAU,EAAE,sCAAsC;AAClD,IAAA,WAAW,EAAE,uCAAuC;AACpD,IAAA,kBAAkB,EAAE,8CAA8C;AAClE,IAAA,QAAQ,EAAE,oCAAoC;AAC9C,IAAA,gBAAgB,EAAE,4CAA4C;AAC9D,IAAA,iBAAiB,EAAE,6CAA6C;AAChE,IAAA,qBAAqB,EAAE,iDAAiD;;AAG1E;;;AAGG;AACI,MAAM,uBAAuB,GAAG;AACrC,IAAA,kBAAkB,EAAE;QAClB,UAAU,EAAE,+BAA+B,CAAC,mBAAmB;AAC/D,QAAA,OAAO,EAAE;YACP,IAAI,EAAE,+BAA+B,CAAC,gBAAgB;AACvD,SAAA;AACD,QAAA,aAAa,EAAE;YACb,OAAO,EAAE,+BAA+B,CAAC,wBAAwB;AAClE,SAAA;AACD,QAAA,cAAc,EAAE;YACd,OAAO,EAAE,+BAA+B,CAAC,wBAAwB;AAClE,SAAA;AACD,QAAA,WAAW,EAAE;YACX,OAAO,EAAE,+BAA+B,CAAC,wBAAwB;AAClE,SAAA;AACF,KAAA;;AAGH;;AClDA;AA8BA;;AC9BA;AA8BA;;AC9BA;AA0HA;;AC1HA;AAEA;AACO,MAAM,iCAAiC,GAAG;AAEjD;AACO,MAAM,6BAA6B,GAAG;AAE7C;AACO,MAAM,0BAA0B,GAAG;AAE1C;;;AAGG;AACI,MAAM,0BAA0B,GACrC;AAEF;;AClBA;;AAEG;;;;"}
@@ -1,17 +1,20 @@
1
1
  import { AXMIntegrationManagementModule } from '@acorex-platform/module-integration-management-client';
2
- import { AXPIntegrationRuntimeService, AXP_INTEGRATION_SECRET_STORE, AXPSettingsService, AXP_INTEGRATION_RUNTIME_PROVIDER, AXP_MENU_PROVIDER, AXP_INTEGRATION_PROVIDER } from '@acorex-platform/framework-client/common';
2
+ import { AXPIntegrationRuntimeService, AXP_INTEGRATION_SECRET_STORE, AXPSettingsService, AXP_INTEGRATION_RUNTIME_PROVIDER, AXP_INTEGRATION_CONNECTION_LIFECYCLE, AXP_MENU_PROVIDER, AXP_INTEGRATION_PROVIDER } from '@acorex-platform/framework-client/common';
3
3
  import { AXP_LOGO_PROVIDER, AXP_MODULE_MANIFEST_PROVIDER, AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER } from '@acorex-platform/framework-client/core';
4
4
  import { AXPEntityService, ensureListActions, actionExists, AXP_ENTITY_MODIFIER } from '@acorex-platform/framework-client/layout/entity';
5
- import { provideCommandSetups } from '@acorex-platform/framework-client/runtime';
5
+ import { AXPQueryService, AXPCommandService, provideCommandSetups } from '@acorex-platform/framework-client/runtime';
6
6
  import * as i0 from '@angular/core';
7
7
  import { inject, Injectable, NgModule } from '@angular/core';
8
- import { AXEServiceM8CommandKeys, RootConfig as RootConfig$1, AXPAdministrationPermissionKeys, AXEServiceM8MenuKeys } from '@acorex-platform/extension-project-management-servicem8-client/contracts';
8
+ import { AXEServiceM8CommandKeys, RootConfig as RootConfig$2, AXPAdministrationPermissionKeys, AXEServiceM8MenuKeys } from '@acorex-platform/extension-project-management-servicem8-client/contracts';
9
9
  import { AXE_PM_INTEGRATION_PROVIDER_SERVICEM8 } from '@acorex-platform/extension-project-management-core-client/contracts';
10
10
  import { RootConfig } from '@acorex-platform/module-project-management-client/contracts';
11
- import { AXPSystemActionType, AXPWidgetsList, AXP_INTEGRATION_AUTH_METHOD_TYPE, AXP_INTEGRATION_CONNECTION_SCOPE, AXP_INTEGRATION_CAPABILITY, AXPRegionalSetting, AXP_INTEGRATION_CONNECTION_STATUS } from '@acorex-platform/framework-shared/core';
11
+ import { AXPSystemActionType, AXPWidgetsList, AXP_INTEGRATION_AUTH_METHOD_TYPE, AXP_INTEGRATION_CONNECTION_SCOPE, AXP_INTEGRATION_CAPABILITY, AXPRegionalSetting, AXP_INTEGRATION_CONNECTION_STATUS, runtimeQueryPayloadData } from '@acorex-platform/framework-shared/core';
12
12
  import { AXPEntityCommandScope } from '@acorex-platform/framework-shared/entity';
13
+ import { AXEServiceM8CommandKeys as AXEServiceM8CommandKeys$1 } from '@acorex-platform/extension-project-management-servicem8-client/contracts';
14
+ import { RootConfig as RootConfig$1 } from '@acorex-platform/module-customer-management-client/contracts';
13
15
  import { AXFormatService } from '@acorex/core/format';
14
16
  import { trim, defaultTo } from 'lodash-es';
17
+ import { AXPBackgroundJobQueryKeys } from '@acorex-platform/framework-shared/background-job';
15
18
  import { AXPCommonMenuKeys } from '@acorex-platform/module-common-client/contracts';
16
19
 
17
20
  const AXE_SERVICEM8_EXPRESSION_SCOPE = 'ServiceM8Extension';
@@ -52,9 +55,9 @@ class AXEServiceM8ProjectLinkExpressionEvaluatorScopeProvider {
52
55
 
53
56
  //#region ---- Imports ----
54
57
  //#endregion
55
- const IMPORT_ORDER = 36;
58
+ const IMPORT_ORDER$1 = 36;
56
59
  const SYNC_ORDER = 37;
57
- function buildImportAction() {
60
+ function buildImportAction$1() {
58
61
  return {
59
62
  name: AXEServiceM8CommandKeys.ProjectImportFromServiceM8,
60
63
  title: '@extensions-servicem8:actions.import-from-servicem8.title',
@@ -63,7 +66,7 @@ function buildImportAction() {
63
66
  type: AXPSystemActionType.Import,
64
67
  icon: 'fa-light fa-briefcase',
65
68
  scope: AXPEntityCommandScope.TypeLevel,
66
- order: IMPORT_ORDER,
69
+ order: IMPORT_ORDER$1,
67
70
  };
68
71
  }
69
72
  function buildSyncAction() {
@@ -97,7 +100,7 @@ const AXEServiceM8ProjectEntityModifier = {
97
100
  const list = (master['list'] ?? { actions: [], views: [] });
98
101
  const listActions = (list['actions'] ?? []);
99
102
  if (!actionExists(listActions, AXEServiceM8CommandKeys.ProjectImportFromServiceM8)) {
100
- listActions.push(buildImportAction());
103
+ listActions.push(buildImportAction$1());
101
104
  }
102
105
  list['actions'] = listActions;
103
106
  master['list'] = list;
@@ -116,6 +119,50 @@ const AXEServiceM8ProjectEntityModifier = {
116
119
  };
117
120
  //#endregion
118
121
 
122
+ //#region ---- Imports ----
123
+ //#endregion
124
+ //#region ----- Constants -----
125
+ const IMPORT_ORDER = 36;
126
+ //#endregion
127
+ //#region ----- Actions -----
128
+ function buildImportAction() {
129
+ return {
130
+ name: AXEServiceM8CommandKeys$1.CustomerImportFromServiceM8,
131
+ title: '@extensions-servicem8:actions.import-from-servicem8.title',
132
+ command: { name: AXEServiceM8CommandKeys$1.CustomerImportFromServiceM8 },
133
+ priority: 'secondary',
134
+ type: AXPSystemActionType.Import,
135
+ icon: 'fa-light fa-briefcase',
136
+ scope: AXPEntityCommandScope.TypeLevel,
137
+ order: IMPORT_ORDER,
138
+ };
139
+ }
140
+ //#endregion
141
+ //#region ----- Modifier -----
142
+ /** Adds ServiceM8 import action to CustomerManagement.Customer list views. */
143
+ const AXEServiceM8CustomerEntityModifier = {
144
+ entityName: RootConfig$1.entities.customer.name,
145
+ order: 50,
146
+ modifier: (ctx) => {
147
+ if (ctx.module.get() !== RootConfig$1.module.name) {
148
+ return;
149
+ }
150
+ ensureListActions(ctx);
151
+ ctx.interfaces.master.list.update((list) => {
152
+ const next = list ?? { actions: [], views: [] };
153
+ const listActions = [...(next.actions ?? [])];
154
+ if (!actionExists(listActions, AXEServiceM8CommandKeys$1.CustomerImportFromServiceM8)) {
155
+ listActions.push(buildImportAction());
156
+ }
157
+ return {
158
+ ...next,
159
+ actions: listActions,
160
+ };
161
+ });
162
+ },
163
+ };
164
+ //#endregion
165
+
119
166
  //#region ---- ServiceM8 logo registry key ----
120
167
  const axpServiceM8LogoKey = 'servicem8';
121
168
  /** Mark artwork (1024×1024); consuming apps copy to `assets/images/brand/integrations/`. */
@@ -126,14 +173,14 @@ const AXE_SERVICEM8_MARK_ASPECT = 1;
126
173
 
127
174
  class AXEServiceM8IntegrationProvider {
128
175
  async provide(context) {
129
- const I18N = `@${RootConfig$1.config.i18n}:integration`;
176
+ const I18N = `@${RootConfig$2.config.i18n}:integration`;
130
177
  context.register({
131
178
  domainId: 'project-management',
132
179
  id: 'servicem8',
133
- module: RootConfig$1.module.name,
180
+ module: RootConfig$2.module.name,
134
181
  displayName: `${I18N}.display-name`,
135
182
  description: `${I18N}.description`,
136
- icon: RootConfig$1.module.icon,
183
+ icon: RootConfig$2.module.icon,
137
184
  logoKey: axpServiceM8LogoKey,
138
185
  capabilities: [AXP_INTEGRATION_CAPABILITY.Sync, AXP_INTEGRATION_CAPABILITY.Import],
139
186
  connectionScopes: [AXP_INTEGRATION_CONNECTION_SCOPE.Tenant],
@@ -175,6 +222,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
175
222
 
176
223
  //#region ---- ServiceM8 integration settings badges ----
177
224
  const SERVICEM8_I18N = '@extensions-servicem8:integration.badges';
225
+ /** Must match `SERVICEM8_PULL_JOBS_COMMAND_KEY` / schedule `command.key`. */
226
+ const SERVICEM8_PULL_JOBS_SCHEDULE_COMMAND_KEY = 'servicem8-extension.jobs.pull';
178
227
  async function formatSyncTimestamp(iso, formatService, settingsService) {
179
228
  const parsed = Date.parse(iso);
180
229
  if (!Number.isFinite(parsed)) {
@@ -185,30 +234,75 @@ async function formatSyncTimestamp(iso, formatService, settingsService) {
185
234
  const format = `${dateFormat} ${timeFormat}`.trim();
186
235
  return formatService.format(new Date(parsed), 'datetime', { format });
187
236
  }
188
- async function resolveServiceM8ConnectionSettingsBadges(ctx, formatService, settingsService) {
237
+ function isFailureNewer(schedule) {
238
+ const failed = schedule.lastFailureAt ? Date.parse(schedule.lastFailureAt) : NaN;
239
+ const succeeded = schedule.lastSuccessAt ? Date.parse(schedule.lastSuccessAt) : NaN;
240
+ if (!Number.isFinite(failed)) {
241
+ return false;
242
+ }
243
+ if (!Number.isFinite(succeeded)) {
244
+ return true;
245
+ }
246
+ return failed >= succeeded;
247
+ }
248
+ async function resolveServiceM8ConnectionSettingsBadges(ctx, formatService, settingsService, schedule) {
189
249
  const config = (ctx.connection.config ?? {});
190
250
  const jobsSync = config.jobsSync;
191
251
  const lastSync = config.jobsSync?.lastSyncAt?.trim() ||
192
252
  (typeof jobsSync?.['lastSuccessfulSyncAt'] === 'string' ? jobsSync['lastSuccessfulSyncAt'].trim() : '');
253
+ const badges = [];
193
254
  if (!lastSync) {
194
- return [
195
- {
196
- labelKey: `${SERVICEM8_I18N}.never-synced`,
197
- color: 'warning',
198
- },
199
- ];
255
+ badges.push({
256
+ labelKey: `${SERVICEM8_I18N}.never-synced`,
257
+ color: 'warning',
258
+ });
200
259
  }
201
- const datetime = await formatSyncTimestamp(lastSync, formatService, settingsService);
202
- if (!datetime) {
203
- return [];
260
+ else {
261
+ const datetime = await formatSyncTimestamp(lastSync, formatService, settingsService);
262
+ if (datetime) {
263
+ badges.push({
264
+ labelKey: `${SERVICEM8_I18N}.last-sync`,
265
+ params: { datetime },
266
+ color: 'success',
267
+ });
268
+ }
204
269
  }
205
- return [
206
- {
207
- labelKey: `${SERVICEM8_I18N}.last-sync`,
208
- params: { datetime },
209
- color: 'success',
210
- },
211
- ];
270
+ if (!schedule) {
271
+ return badges;
272
+ }
273
+ if (schedule.status === 'pause') {
274
+ badges.push({
275
+ labelKey: `${SERVICEM8_I18N}.schedule-paused`,
276
+ color: 'warning',
277
+ });
278
+ }
279
+ else if (schedule.status === 'disabled') {
280
+ badges.push({
281
+ labelKey: `${SERVICEM8_I18N}.schedule-disabled`,
282
+ color: 'warning',
283
+ });
284
+ }
285
+ if (schedule.nextRunAt) {
286
+ const datetime = await formatSyncTimestamp(schedule.nextRunAt, formatService, settingsService);
287
+ if (datetime) {
288
+ badges.push({
289
+ labelKey: `${SERVICEM8_I18N}.next-run`,
290
+ params: { datetime },
291
+ color: 'secondary',
292
+ });
293
+ }
294
+ }
295
+ if (isFailureNewer(schedule) && schedule.lastFailureAt) {
296
+ const datetime = await formatSyncTimestamp(schedule.lastFailureAt, formatService, settingsService);
297
+ if (datetime) {
298
+ badges.push({
299
+ labelKey: `${SERVICEM8_I18N}.last-failure`,
300
+ params: { datetime },
301
+ color: 'danger',
302
+ });
303
+ }
304
+ }
305
+ return badges;
212
306
  }
213
307
  //#endregion
214
308
 
@@ -218,12 +312,13 @@ class AXEServiceM8IntegrationRuntimeProvider {
218
312
  this.secretStore = inject(AXP_INTEGRATION_SECRET_STORE, { optional: true });
219
313
  this.formatService = inject(AXFormatService);
220
314
  this.settingsService = inject(AXPSettingsService);
315
+ this.queryService = inject(AXPQueryService, { optional: true });
221
316
  }
222
317
  async getHooks() {
223
318
  return {
224
319
  validateConnection: (ctx) => this.validateConnection(ctx),
225
320
  createClient: (ctx) => this.createClient(ctx),
226
- getConnectionSettingsBadges: (ctx) => resolveServiceM8ConnectionSettingsBadges(ctx, this.formatService, this.settingsService),
321
+ getConnectionSettingsBadges: async (ctx) => resolveServiceM8ConnectionSettingsBadges(ctx, this.formatService, this.settingsService, await this.loadPullSchedule(ctx.connection.id)),
227
322
  };
228
323
  }
229
324
  async validateConnection(ctx) {
@@ -273,6 +368,21 @@ class AXEServiceM8IntegrationRuntimeProvider {
273
368
  }
274
369
  return { clientId, clientSecret };
275
370
  }
371
+ async loadPullSchedule(connectionId) {
372
+ if (!this.queryService || !connectionId.trim()) {
373
+ return null;
374
+ }
375
+ try {
376
+ const result = await this.queryService.fetch(AXPBackgroundJobQueryKeys.ScheduleGetByScope, {
377
+ commandKey: SERVICEM8_PULL_JOBS_SCHEDULE_COMMAND_KEY,
378
+ connectionId,
379
+ });
380
+ return runtimeQueryPayloadData(result) ?? null;
381
+ }
382
+ catch {
383
+ return null;
384
+ }
385
+ }
276
386
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEServiceM8IntegrationRuntimeProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
277
387
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEServiceM8IntegrationRuntimeProvider }); }
278
388
  }
@@ -285,6 +395,47 @@ const provideServiceM8IntegrationRuntimeProvider = {
285
395
  multi: true,
286
396
  };
287
397
 
398
+ //#region ---- Imports ----
399
+ //#endregion
400
+ //#region ---- Handler ----
401
+ class AXEServiceM8ConnectionLifecycleHandler {
402
+ constructor() {
403
+ this.providerId = 'servicem8';
404
+ this.commandService = inject(AXPCommandService);
405
+ }
406
+ async onDisconnected(connection) {
407
+ await this.runLifecycle(connection.id, 'disconnected');
408
+ }
409
+ async onDeleted(connection) {
410
+ await this.runLifecycle(connection.id, 'deleted');
411
+ }
412
+ async onEnabledChanged(connection, enabled) {
413
+ if (enabled) {
414
+ if (connection.status === AXP_INTEGRATION_CONNECTION_STATUS.Connected) {
415
+ await this.runLifecycle(connection.id, 'enabled');
416
+ }
417
+ return;
418
+ }
419
+ await this.runLifecycle(connection.id, 'disabled');
420
+ }
421
+ async runLifecycle(connectionId, action) {
422
+ await this.commandService.execute(AXEServiceM8CommandKeys$1.ConnectionLifecycle, {
423
+ connectionId,
424
+ action,
425
+ });
426
+ }
427
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEServiceM8ConnectionLifecycleHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
428
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEServiceM8ConnectionLifecycleHandler }); }
429
+ }
430
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEServiceM8ConnectionLifecycleHandler, decorators: [{
431
+ type: Injectable
432
+ }] });
433
+ const provideServiceM8ConnectionLifecycleHandler = {
434
+ provide: AXP_INTEGRATION_CONNECTION_LIFECYCLE,
435
+ useClass: AXEServiceM8ConnectionLifecycleHandler,
436
+ multi: true,
437
+ };
438
+
288
439
  const servicem8LogoProvider = {
289
440
  key: axpServiceM8LogoKey,
290
441
  resolve: async (request) => ({
@@ -302,11 +453,11 @@ const AXE_SERVICEM8_LOGO_PROVIDER = {
302
453
 
303
454
  //#region ---- Manifest ----
304
455
  const ServiceM8ExtensionManifest = {
305
- name: RootConfig$1.module.name,
456
+ name: RootConfig$2.module.name,
306
457
  version: '1.0.0',
307
- title: RootConfig$1.module.title,
308
- icon: RootConfig$1.module.icon,
309
- i18n: RootConfig$1.config.i18n,
458
+ title: RootConfig$2.module.title,
459
+ icon: RootConfig$2.module.icon,
460
+ i18n: RootConfig$2.config.i18n,
310
461
  type: 'integration',
311
462
  domain: 'project-management',
312
463
  dependencies: [],
@@ -317,7 +468,7 @@ const ServiceM8ExtensionManifest = {
317
468
  //#endregion
318
469
  class AXEServiceM8MenuProvider {
319
470
  async provide(context) {
320
- const module = RootConfig$1.module;
471
+ const module = RootConfig$2.module;
321
472
  const policy = {
322
473
  features: [module.name],
323
474
  permissions: [AXPAdministrationPermissionKeys.MaintainConnections],
@@ -354,6 +505,7 @@ class AXEServiceM8ExtensionModule {
354
505
  { provide: AXP_MODULE_MANIFEST_PROVIDER, useValue: ServiceM8ExtensionManifest, multi: true },
355
506
  { provide: AXP_MENU_PROVIDER, useClass: AXEServiceM8MenuProvider, multi: true },
356
507
  { provide: AXP_ENTITY_MODIFIER, useValue: AXEServiceM8ProjectEntityModifier, multi: true },
508
+ { provide: AXP_ENTITY_MODIFIER, useValue: AXEServiceM8CustomerEntityModifier, multi: true },
357
509
  {
358
510
  provide: AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER,
359
511
  useClass: AXEServiceM8ProjectLinkExpressionEvaluatorScopeProvider,
@@ -361,10 +513,11 @@ class AXEServiceM8ExtensionModule {
361
513
  },
362
514
  { provide: AXP_INTEGRATION_PROVIDER, useClass: AXEServiceM8IntegrationProvider, multi: true },
363
515
  provideServiceM8IntegrationRuntimeProvider,
516
+ provideServiceM8ConnectionLifecycleHandler,
364
517
  provideCommandSetups([
365
518
  {
366
519
  key: AXEServiceM8CommandKeys.ConnectionConnect,
367
- command: () => import('./acorex-platform-extension-project-management-servicem8-client-connect-servicem8.command-CkDOwDrg.mjs').then((c) => c.ConnectServiceM8Command),
520
+ command: () => import('./acorex-platform-extension-project-management-servicem8-client-connect-servicem8.command-3MmHke4f.mjs').then((c) => c.ConnectServiceM8Command),
368
521
  },
369
522
  {
370
523
  key: AXEServiceM8CommandKeys.ConnectionCompleteOAuth,
@@ -382,6 +535,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
382
535
  { provide: AXP_MODULE_MANIFEST_PROVIDER, useValue: ServiceM8ExtensionManifest, multi: true },
383
536
  { provide: AXP_MENU_PROVIDER, useClass: AXEServiceM8MenuProvider, multi: true },
384
537
  { provide: AXP_ENTITY_MODIFIER, useValue: AXEServiceM8ProjectEntityModifier, multi: true },
538
+ { provide: AXP_ENTITY_MODIFIER, useValue: AXEServiceM8CustomerEntityModifier, multi: true },
385
539
  {
386
540
  provide: AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER,
387
541
  useClass: AXEServiceM8ProjectLinkExpressionEvaluatorScopeProvider,
@@ -389,10 +543,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
389
543
  },
390
544
  { provide: AXP_INTEGRATION_PROVIDER, useClass: AXEServiceM8IntegrationProvider, multi: true },
391
545
  provideServiceM8IntegrationRuntimeProvider,
546
+ provideServiceM8ConnectionLifecycleHandler,
392
547
  provideCommandSetups([
393
548
  {
394
549
  key: AXEServiceM8CommandKeys.ConnectionConnect,
395
- command: () => import('./acorex-platform-extension-project-management-servicem8-client-connect-servicem8.command-CkDOwDrg.mjs').then((c) => c.ConnectServiceM8Command),
550
+ command: () => import('./acorex-platform-extension-project-management-servicem8-client-connect-servicem8.command-3MmHke4f.mjs').then((c) => c.ConnectServiceM8Command),
396
551
  },
397
552
  {
398
553
  key: AXEServiceM8CommandKeys.ConnectionCompleteOAuth,
@@ -1 +1 @@
1
- {"version":3,"file":"acorex-platform-extension-project-management-servicem8-client.mjs","sources":["../../../../libs/extensions/project-management/servicem8/client/src/lib/features/project-management/servicem8-expression-scope.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/features/project-management/servicem8-project-link-expression-evaluator-scope.provider.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/features/project-management/project-servicem8.entity-modifier.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/brand/servicem8-logo-key.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/integration/servicem8.integration.provider.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/integration/servicem8-integration-settings-badges.util.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/integration/servicem8.integration-runtime.provider.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/brand/servicem8-logo.provider.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/manifest.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/menu.provider.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/servicem8-extension.module.ts","../../../../libs/extensions/project-management/servicem8/client/src/acorex-platform-extension-project-management-servicem8-client.ts"],"sourcesContent":["export const AXE_SERVICEM8_EXPRESSION_SCOPE = 'ServiceM8Extension';\n","//#region ---- Imports ----\n\nimport { AXPExpressionEvaluatorScopeProvider, AXPExpressionEvaluatorScopeProviderContext } from '@acorex-platform/framework-shared/core';\nimport { AXPIntegrationRuntimeService } from '@acorex-platform/framework-client/common';\nimport { AXPEntityService } from '@acorex-platform/framework-client/layout/entity';\nimport { inject } from '@angular/core';\nimport { AXE_PM_INTEGRATION_PROVIDER_SERVICEM8 } from '@acorex-platform/extension-project-management-core-client/contracts';\nimport { RootConfig as PmRootConfig } from '@acorex-platform/module-project-management-client/contracts';\nimport type { AXMProjectManagementProjectEntityModel } from '@acorex-platform/module-project-management-client/contracts';\nimport { AXE_SERVICEM8_EXPRESSION_SCOPE } from './servicem8-expression-scope';\n\n//#endregion\n\nexport class AXEServiceM8ProjectLinkExpressionEvaluatorScopeProvider implements AXPExpressionEvaluatorScopeProvider {\n private readonly entityService = inject(AXPEntityService);\n private readonly integrationRuntime = inject(AXPIntegrationRuntimeService);\n\n async provide(context: AXPExpressionEvaluatorScopeProviderContext): Promise<void> {\n context.addScope(AXE_SERVICEM8_EXPRESSION_SCOPE, {\n hasProjectLink: async (platformProjectIdRaw?: unknown): Promise<boolean> => {\n const platformProjectId = typeof platformProjectIdRaw === 'string' ? platformProjectIdRaw.trim() : '';\n if (!platformProjectId) {\n return false;\n }\n\n const project = (await this.entityService\n .withEntity(PmRootConfig.module.name, PmRootConfig.entities.project.name)\n .data<string, AXMProjectManagementProjectEntityModel>()\n .byKey(platformProjectId)) as AXMProjectManagementProjectEntityModel | null;\n\n return project?.integrationRef?.source === AXE_PM_INTEGRATION_PROVIDER_SERVICEM8;\n },\n hasServiceM8Connection: async (): Promise<boolean> => {\n try {\n await this.integrationRuntime.getConnection({ providerId: 'servicem8', connectionScope: 'tenant' });\n return true;\n } catch {\n return false;\n }\n },\n });\n }\n}\n\n//#endregion\n","//#region ---- Imports ----\n\nimport { AXPSystemActionType } from '@acorex-platform/framework-shared/core';\nimport { AXPEntityAction, AXPEntityCommandScope } from '@acorex-platform/framework-shared/entity';\nimport {\n actionExists,\n AXPEntityModifierProvider,\n ensureListActions,\n} from '@acorex-platform/framework-client/layout/entity';\nimport { AXEServiceM8CommandKeys } from '@acorex-platform/extension-project-management-servicem8-client/contracts';\nimport { RootConfig as PmRootConfig } from '@acorex-platform/module-project-management-client/contracts';\nimport { AXE_SERVICEM8_EXPRESSION_SCOPE } from './servicem8-expression-scope';\n\n//#endregion\n\nconst IMPORT_ORDER = 36;\nconst SYNC_ORDER = 37;\n\nfunction buildImportAction(): AXPEntityAction {\n return {\n name: AXEServiceM8CommandKeys.ProjectImportFromServiceM8,\n title: '@extensions-servicem8:actions.import-from-servicem8.title',\n command: { name: AXEServiceM8CommandKeys.ProjectImportFromServiceM8 },\n priority: 'secondary',\n type: AXPSystemActionType.Import,\n icon: 'fa-light fa-briefcase',\n scope: AXPEntityCommandScope.TypeLevel,\n order: IMPORT_ORDER,\n };\n}\n\nfunction buildSyncAction(): AXPEntityAction {\n return {\n name: AXEServiceM8CommandKeys.ProjectSync,\n title: '@extensions-servicem8:actions.sync.title',\n command: {\n name: AXEServiceM8CommandKeys.ProjectSync,\n options: { platformProjectId: '{{ context.eval(\"id\") }}' },\n },\n priority: 'secondary',\n type: AXPSystemActionType.Sync,\n icon: 'fa-light fa-arrows-rotate',\n scope: AXPEntityCommandScope.Individual,\n order: SYNC_ORDER,\n hidden: `{{ !(await ${AXE_SERVICEM8_EXPRESSION_SCOPE}.hasProjectLink(context.eval(\"id\"))) }}`,\n };\n}\n\n/** Adds ServiceM8 import/sync actions to ProjectManagement.Project views. */\nexport const AXEServiceM8ProjectEntityModifier: AXPEntityModifierProvider = {\n entityName: PmRootConfig.entities.project.name,\n order: 50,\n modifier: (ctx) => {\n if (ctx.module.get() !== PmRootConfig.module.name) {\n return;\n }\n\n ensureListActions(ctx);\n\n ctx.interfaces.update((interfaces) => {\n const next = (interfaces ?? {}) as Record<string, unknown>;\n const master = (next['master'] ?? {}) as Record<string, unknown>;\n const list = (master['list'] ?? { actions: [], views: [] }) as Record<string, unknown>;\n const listActions = (list['actions'] ?? []) as AXPEntityAction[];\n\n if (!actionExists(listActions, AXEServiceM8CommandKeys.ProjectImportFromServiceM8)) {\n listActions.push(buildImportAction());\n }\n list['actions'] = listActions;\n master['list'] = list;\n\n const single = master['single'] as Record<string, unknown> | undefined;\n if (single) {\n const singleActions = (single['actions'] ?? []) as AXPEntityAction[];\n if (!actionExists(singleActions, AXEServiceM8CommandKeys.ProjectSync)) {\n singleActions.push(buildSyncAction());\n }\n single['actions'] = singleActions;\n }\n\n next['master'] = master;\n return next as typeof interfaces;\n });\n },\n};\n\n//#endregion\n","//#region ---- ServiceM8 logo registry key ----\n\nexport const axpServiceM8LogoKey = 'servicem8' as const;\n\n/** Mark artwork (1024×1024); consuming apps copy to `assets/images/brand/integrations/`. */\nexport const AXE_SERVICEM8_MARK_SRC = 'assets/images/brand/integrations/servicem8-mark.png';\n\n/** Source mark is square. */\nexport const AXE_SERVICEM8_MARK_ASPECT = 1;\n\n//#endregion\n","import {\n AXP_INTEGRATION_AUTH_METHOD_TYPE,\n AXP_INTEGRATION_CAPABILITY,\n AXP_INTEGRATION_CONNECTION_SCOPE,\n AXPWidgetsList,\n} from '@acorex-platform/framework-shared/core';\nimport { AXPIntegrationProvider, AXPIntegrationProviderContext } from '@acorex-platform/framework-client/common';\nimport { Injectable } from '@angular/core';\nimport { AXEServiceM8CommandKeys, RootConfig } from '@acorex-platform/extension-project-management-servicem8-client/contracts';\nimport { axpServiceM8LogoKey } from '../brand/servicem8-logo-key';\n\n@Injectable()\nexport class AXEServiceM8IntegrationProvider implements AXPIntegrationProvider {\n async provide(context: AXPIntegrationProviderContext): Promise<void> {\n const I18N = `@${RootConfig.config.i18n}:integration`;\n\n context.register({\n domainId: 'project-management',\n id: 'servicem8',\n module: RootConfig.module.name,\n displayName: `${I18N}.display-name`,\n description: `${I18N}.description`,\n icon: RootConfig.module.icon,\n logoKey: axpServiceM8LogoKey,\n capabilities: [AXP_INTEGRATION_CAPABILITY.Sync, AXP_INTEGRATION_CAPABILITY.Import],\n connectionScopes: [AXP_INTEGRATION_CONNECTION_SCOPE.Tenant],\n allowMultipleConnections: true,\n defaultConnectionKey: 'default',\n connectCommandKey: AXEServiceM8CommandKeys.ConnectionConnect,\n completeOAuthCommandKey: AXEServiceM8CommandKeys.ConnectionCompleteOAuth,\n authMethods: [\n {\n type: AXP_INTEGRATION_AUTH_METHOD_TYPE.OAuth2,\n label: `${I18N}.auth.oauth.label`,\n instructions: `${I18N}.auth.oauth.instructions`,\n usesRedirect: false,\n supportsRefresh: true,\n supportsRevoke: false,\n supportsValidation: true,\n fields: [\n {\n type: AXPWidgetsList.Editors.TextBox,\n path: 'clientId',\n options: { placeholder: `${I18N}.auth.oauth.client-id.placeholder` },\n },\n {\n type: AXPWidgetsList.Editors.PasswordBox,\n path: 'clientSecret',\n options: { placeholder: `${I18N}.auth.oauth.client-secret.placeholder` },\n },\n ],\n },\n ],\n });\n }\n}\n","//#region ---- ServiceM8 integration settings badges ----\n\nimport { AXFormatService } from '@acorex/core/format';\nimport { AXPSettingsService } from '@acorex-platform/framework-client/common';\nimport { AXPRegionalSetting } from '@acorex-platform/framework-shared/core';\nimport type {\n AXPIntegrationConnectionSettingsContext,\n AXPIntegrationSettingsRowBadge,\n} from '@acorex-platform/framework-shared/core';\nimport type { AXEServiceM8ConnectionConfig } from '@acorex-platform/extension-project-management-servicem8-client/contracts';\n\nconst SERVICEM8_I18N = '@extensions-servicem8:integration.badges';\n\nasync function formatSyncTimestamp(\n iso: string,\n formatService: AXFormatService,\n settingsService: AXPSettingsService,\n): Promise<string | undefined> {\n const parsed = Date.parse(iso);\n if (!Number.isFinite(parsed)) {\n return undefined;\n }\n\n const dateFormat = await settingsService.get(AXPRegionalSetting.ShortDate);\n const timeFormat = await settingsService.get(AXPRegionalSetting.ShortTime);\n const format = `${dateFormat} ${timeFormat}`.trim();\n\n return formatService.format(new Date(parsed), 'datetime', { format });\n}\n\nexport async function resolveServiceM8ConnectionSettingsBadges(\n ctx: AXPIntegrationConnectionSettingsContext,\n formatService: AXFormatService,\n settingsService: AXPSettingsService,\n): Promise<AXPIntegrationSettingsRowBadge[]> {\n const config = (ctx.connection.config ?? {}) as AXEServiceM8ConnectionConfig;\n const jobsSync = config.jobsSync as Record<string, unknown> | undefined;\n const lastSync =\n config.jobsSync?.lastSyncAt?.trim() ||\n (typeof jobsSync?.['lastSuccessfulSyncAt'] === 'string' ? jobsSync['lastSuccessfulSyncAt'].trim() : '');\n\n if (!lastSync) {\n return [\n {\n labelKey: `${SERVICEM8_I18N}.never-synced`,\n color: 'warning',\n },\n ];\n }\n\n const datetime = await formatSyncTimestamp(lastSync, formatService, settingsService);\n if (!datetime) {\n return [];\n }\n\n return [\n {\n labelKey: `${SERVICEM8_I18N}.last-sync`,\n params: { datetime },\n color: 'success',\n },\n ];\n}\n\n//#endregion\n","import {\n AXP_INTEGRATION_CONNECTION_STATUS,\n type AXPIntegrationProviderRuntimeHooks,\n type AXPIntegrationRuntimeContext,\n type AXPIntegrationValidationResult,\n} from '@acorex-platform/framework-shared/core';\nimport {\n AXP_INTEGRATION_RUNTIME_PROVIDER,\n AXP_INTEGRATION_SECRET_STORE,\n type AXPIntegrationRuntimeProvider,\n} from '@acorex-platform/framework-client/common';\nimport type { AXEServiceM8ConnectionConfig } from '@acorex-platform/extension-project-management-servicem8-client/contracts';\nimport { Injectable, inject } from '@angular/core';\nimport { AXFormatService } from '@acorex/core/format';\nimport { AXPSettingsService } from '@acorex-platform/framework-client/common';\nimport { defaultTo, trim } from 'lodash-es';\nimport { resolveServiceM8ConnectionSettingsBadges } from './servicem8-integration-settings-badges.util';\n\n@Injectable()\nexport class AXEServiceM8IntegrationRuntimeProvider implements AXPIntegrationRuntimeProvider {\n readonly providerId = 'servicem8';\n\n private readonly secretStore = inject(AXP_INTEGRATION_SECRET_STORE, { optional: true });\n private readonly formatService = inject(AXFormatService);\n private readonly settingsService = inject(AXPSettingsService);\n\n async getHooks(): Promise<AXPIntegrationProviderRuntimeHooks> {\n return {\n validateConnection: (ctx) => this.validateConnection(ctx),\n createClient: (ctx) => this.createClient(ctx),\n getConnectionSettingsBadges: (ctx) =>\n resolveServiceM8ConnectionSettingsBadges(ctx, this.formatService, this.settingsService),\n };\n }\n\n private async validateConnection(ctx: AXPIntegrationRuntimeContext): Promise<AXPIntegrationValidationResult> {\n if (!this.hasClientCredentials(ctx)) {\n return {\n valid: false,\n status: AXP_INTEGRATION_CONNECTION_STATUS.Invalid,\n errorMessage: 'ServiceM8 App ID and App Secret are required.',\n };\n }\n\n return {\n valid: true,\n status: AXP_INTEGRATION_CONNECTION_STATUS.Connected,\n externalAccountName: trim(defaultTo(ctx.instanceUrl, 'ServiceM8')) || 'ServiceM8',\n };\n }\n\n private async createClient(ctx: AXPIntegrationRuntimeContext): Promise<{ clientId: string; baseUrl?: string }> {\n const credentials = await this.resolveClientCredentials(ctx);\n return {\n clientId: credentials.clientId,\n baseUrl: defaultTo(ctx.instanceUrl, undefined),\n };\n }\n\n private hasClientCredentials(ctx: AXPIntegrationRuntimeContext): boolean {\n const config = (ctx.config ?? {}) as AXEServiceM8ConnectionConfig;\n const clientId = trim(defaultTo(config.clientId, ''));\n const clientSecret = trim(defaultTo(config.clientSecret, ''));\n const legacyToken = trim(defaultTo(config.accessToken, ''));\n const refreshToken = trim(defaultTo(config.refreshToken, ''));\n return Boolean((clientId && clientSecret) || legacyToken || refreshToken);\n }\n\n private async resolveClientCredentials(\n ctx: AXPIntegrationRuntimeContext,\n ): Promise<{ clientId: string; clientSecret?: string }> {\n const config = (ctx.config ?? {}) as AXEServiceM8ConnectionConfig;\n const configClientId = trim(defaultTo(config.clientId, ''));\n const configClientSecret = trim(defaultTo(config.clientSecret, ''));\n\n if (configClientId && configClientSecret) {\n return { clientId: configClientId, clientSecret: configClientSecret };\n }\n\n if (!this.secretStore || !trim(defaultTo(ctx.secretRef, ''))) {\n throw new Error('ServiceM8 connection has no client credentials.');\n }\n\n const credentials = await this.secretStore.resolve(ctx.secretRef, ctx);\n const clientId = trim(defaultTo(credentials.token ?? credentials.email, ''));\n const clientSecret = trim(defaultTo(credentials.password ?? credentials.apiToken, ''));\n if (!clientId || !clientSecret) {\n throw new Error('ServiceM8 connection has no usable client credentials.');\n }\n\n return { clientId, clientSecret };\n }\n}\n\nexport const provideServiceM8IntegrationRuntimeProvider = {\n provide: AXP_INTEGRATION_RUNTIME_PROVIDER,\n useClass: AXEServiceM8IntegrationRuntimeProvider,\n multi: true,\n};\n","import {\n AXP_LOGO_PROVIDER,\n type AXPLogoProvider,\n type AXPLogoRequest,\n type AXPLogoView,\n} from '@acorex-platform/framework-client/core';\nimport {\n AXE_SERVICEM8_MARK_ASPECT,\n AXE_SERVICEM8_MARK_SRC,\n axpServiceM8LogoKey,\n} from './servicem8-logo-key';\n\nconst servicem8LogoProvider: AXPLogoProvider = {\n key: axpServiceM8LogoKey,\n resolve: async (request: AXPLogoRequest): Promise<AXPLogoView> => ({\n kind: 'image',\n src: AXE_SERVICEM8_MARK_SRC,\n height: request.size,\n width: Math.round(request.size * AXE_SERVICEM8_MARK_ASPECT),\n }),\n};\n\nexport const AXE_SERVICEM8_LOGO_PROVIDER = {\n provide: AXP_LOGO_PROVIDER,\n useValue: servicem8LogoProvider,\n multi: true,\n} as const;\n","//#region ---- Manifest ----\n\nimport { AXPModuleManifest } from '@acorex-platform/framework-client/core';\nimport { RootConfig } from '@acorex-platform/extension-project-management-servicem8-client/contracts';\n\nexport const ServiceM8ExtensionManifest: AXPModuleManifest = {\n name: RootConfig.module.name,\n version: '1.0.0',\n title: RootConfig.module.title,\n icon: RootConfig.module.icon,\n i18n: RootConfig.config.i18n,\n type: 'integration',\n domain: 'project-management',\n dependencies: [],\n};\n\n//#endregion\n","//#region ---- Imports ----\n\nimport {\n AXPCommonMenuKeys\n} from '@acorex-platform/module-common-client/contracts';\nimport { AXPMenuProvider, AXPMenuProviderContext } from '@acorex-platform/framework-client/common';\nimport { Injectable } from '@angular/core';\nimport {\n AXEServiceM8MenuKeys,\n RootConfig,\n AXPAdministrationPermissionKeys\n} from '@acorex-platform/extension-project-management-servicem8-client/contracts';\n\n//#endregion\n\n@Injectable()\nexport class AXEServiceM8MenuProvider implements AXPMenuProvider {\n async provide(context: AXPMenuProviderContext): Promise<void> {\n const module = RootConfig.module;\n const policy = {\n features: [module.name],\n permissions: [AXPAdministrationPermissionKeys.MaintainConnections],\n };\n\n const configMenu = context.find(AXPCommonMenuKeys.Configuration);\n if (!context.find(AXEServiceM8MenuKeys.ServiceM8ExtensionConfig).exists) {\n configMenu.addItems([\n {\n name: AXEServiceM8MenuKeys.ServiceM8ExtensionConfig,\n text: '@extensions-servicem8:module.menus.configuration.title',\n icon: module.icon,\n type: 'menu',\n priority: 8940,\n policy,\n children: [],\n },\n ]);\n }\n }\n}\n\n//#endregion\n","//#region ---- Imports ----\n\nimport { AXMIntegrationManagementModule } from '@acorex-platform/module-integration-management-client';\nimport { AXP_INTEGRATION_PROVIDER, AXP_MENU_PROVIDER } from '@acorex-platform/framework-client/common';\nimport { AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, AXP_MODULE_MANIFEST_PROVIDER } from '@acorex-platform/framework-client/core';\nimport { AXP_ENTITY_MODIFIER } from '@acorex-platform/framework-client/layout/entity';\nimport { provideCommandSetups } from '@acorex-platform/framework-client/runtime';\nimport { NgModule } from '@angular/core';\nimport { AXEServiceM8CommandKeys } from '@acorex-platform/extension-project-management-servicem8-client/contracts';\nimport { AXEServiceM8ProjectLinkExpressionEvaluatorScopeProvider } from './features/project-management/servicem8-project-link-expression-evaluator-scope.provider';\nimport { AXEServiceM8ProjectEntityModifier } from './features/project-management/project-servicem8.entity-modifier';\nimport { AXEServiceM8IntegrationProvider } from './integration/servicem8.integration.provider';\nimport { provideServiceM8IntegrationRuntimeProvider } from './integration/servicem8.integration-runtime.provider';\nimport { AXE_SERVICEM8_LOGO_PROVIDER } from './brand/servicem8-logo.provider';\nimport { ServiceM8ExtensionManifest } from './manifest';\nimport { AXEServiceM8MenuProvider } from './menu.provider';\n\n//#endregion\n\n@NgModule({\n imports: [AXMIntegrationManagementModule],\n providers: [\n AXE_SERVICEM8_LOGO_PROVIDER,\n { provide: AXP_MODULE_MANIFEST_PROVIDER, useValue: ServiceM8ExtensionManifest, multi: true },\n { provide: AXP_MENU_PROVIDER, useClass: AXEServiceM8MenuProvider, multi: true },\n { provide: AXP_ENTITY_MODIFIER, useValue: AXEServiceM8ProjectEntityModifier, multi: true },\n {\n provide: AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER,\n useClass: AXEServiceM8ProjectLinkExpressionEvaluatorScopeProvider,\n multi: true,\n },\n { provide: AXP_INTEGRATION_PROVIDER, useClass: AXEServiceM8IntegrationProvider, multi: true },\n provideServiceM8IntegrationRuntimeProvider,\n provideCommandSetups([\n {\n key: AXEServiceM8CommandKeys.ConnectionConnect,\n command: () => import('./features/connect/connect-servicem8.command').then((c) => c.ConnectServiceM8Command),\n },\n {\n key: AXEServiceM8CommandKeys.ConnectionCompleteOAuth,\n command: () =>\n import('./features/connect/complete-servicem8-oauth.command').then((c) => c.CompleteServiceM8OAuthCommand),\n },\n ]),\n ],\n})\nexport class AXEServiceM8ExtensionModule {}\n\n//#endregion\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["PmRootConfig","RootConfig"],"mappings":"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"}
1
+ {"version":3,"file":"acorex-platform-extension-project-management-servicem8-client.mjs","sources":["../../../../libs/extensions/project-management/servicem8/client/src/lib/features/project-management/servicem8-expression-scope.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/features/project-management/servicem8-project-link-expression-evaluator-scope.provider.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/features/project-management/project-servicem8.entity-modifier.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/features/customer-management/customer-servicem8.entity-modifier.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/brand/servicem8-logo-key.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/integration/servicem8.integration.provider.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/integration/servicem8-integration-settings-badges.util.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/integration/servicem8.integration-runtime.provider.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/integration/servicem8.connection-lifecycle.handler.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/brand/servicem8-logo.provider.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/manifest.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/menu.provider.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/servicem8-extension.module.ts","../../../../libs/extensions/project-management/servicem8/client/src/acorex-platform-extension-project-management-servicem8-client.ts"],"sourcesContent":["export const AXE_SERVICEM8_EXPRESSION_SCOPE = 'ServiceM8Extension';\n","//#region ---- Imports ----\n\nimport { AXPExpressionEvaluatorScopeProvider, AXPExpressionEvaluatorScopeProviderContext } from '@acorex-platform/framework-shared/core';\nimport { AXPIntegrationRuntimeService } from '@acorex-platform/framework-client/common';\nimport { AXPEntityService } from '@acorex-platform/framework-client/layout/entity';\nimport { inject } from '@angular/core';\nimport { AXE_PM_INTEGRATION_PROVIDER_SERVICEM8 } from '@acorex-platform/extension-project-management-core-client/contracts';\nimport { RootConfig as PmRootConfig } from '@acorex-platform/module-project-management-client/contracts';\nimport type { AXMProjectManagementProjectEntityModel } from '@acorex-platform/module-project-management-client/contracts';\nimport { AXE_SERVICEM8_EXPRESSION_SCOPE } from './servicem8-expression-scope';\n\n//#endregion\n\nexport class AXEServiceM8ProjectLinkExpressionEvaluatorScopeProvider implements AXPExpressionEvaluatorScopeProvider {\n private readonly entityService = inject(AXPEntityService);\n private readonly integrationRuntime = inject(AXPIntegrationRuntimeService);\n\n async provide(context: AXPExpressionEvaluatorScopeProviderContext): Promise<void> {\n context.addScope(AXE_SERVICEM8_EXPRESSION_SCOPE, {\n hasProjectLink: async (platformProjectIdRaw?: unknown): Promise<boolean> => {\n const platformProjectId = typeof platformProjectIdRaw === 'string' ? platformProjectIdRaw.trim() : '';\n if (!platformProjectId) {\n return false;\n }\n\n const project = (await this.entityService\n .withEntity(PmRootConfig.module.name, PmRootConfig.entities.project.name)\n .data<string, AXMProjectManagementProjectEntityModel>()\n .byKey(platformProjectId)) as AXMProjectManagementProjectEntityModel | null;\n\n return project?.integrationRef?.source === AXE_PM_INTEGRATION_PROVIDER_SERVICEM8;\n },\n hasServiceM8Connection: async (): Promise<boolean> => {\n try {\n await this.integrationRuntime.getConnection({ providerId: 'servicem8', connectionScope: 'tenant' });\n return true;\n } catch {\n return false;\n }\n },\n });\n }\n}\n\n//#endregion\n","//#region ---- Imports ----\n\nimport { AXPSystemActionType } from '@acorex-platform/framework-shared/core';\nimport { AXPEntityAction, AXPEntityCommandScope } from '@acorex-platform/framework-shared/entity';\nimport {\n actionExists,\n AXPEntityModifierProvider,\n ensureListActions,\n} from '@acorex-platform/framework-client/layout/entity';\nimport { AXEServiceM8CommandKeys } from '@acorex-platform/extension-project-management-servicem8-client/contracts';\nimport { RootConfig as PmRootConfig } from '@acorex-platform/module-project-management-client/contracts';\nimport { AXE_SERVICEM8_EXPRESSION_SCOPE } from './servicem8-expression-scope';\n\n//#endregion\n\nconst IMPORT_ORDER = 36;\nconst SYNC_ORDER = 37;\n\nfunction buildImportAction(): AXPEntityAction {\n return {\n name: AXEServiceM8CommandKeys.ProjectImportFromServiceM8,\n title: '@extensions-servicem8:actions.import-from-servicem8.title',\n command: { name: AXEServiceM8CommandKeys.ProjectImportFromServiceM8 },\n priority: 'secondary',\n type: AXPSystemActionType.Import,\n icon: 'fa-light fa-briefcase',\n scope: AXPEntityCommandScope.TypeLevel,\n order: IMPORT_ORDER,\n };\n}\n\nfunction buildSyncAction(): AXPEntityAction {\n return {\n name: AXEServiceM8CommandKeys.ProjectSync,\n title: '@extensions-servicem8:actions.sync.title',\n command: {\n name: AXEServiceM8CommandKeys.ProjectSync,\n options: { platformProjectId: '{{ context.eval(\"id\") }}' },\n },\n priority: 'secondary',\n type: AXPSystemActionType.Sync,\n icon: 'fa-light fa-arrows-rotate',\n scope: AXPEntityCommandScope.Individual,\n order: SYNC_ORDER,\n hidden: `{{ !(await ${AXE_SERVICEM8_EXPRESSION_SCOPE}.hasProjectLink(context.eval(\"id\"))) }}`,\n };\n}\n\n/** Adds ServiceM8 import/sync actions to ProjectManagement.Project views. */\nexport const AXEServiceM8ProjectEntityModifier: AXPEntityModifierProvider = {\n entityName: PmRootConfig.entities.project.name,\n order: 50,\n modifier: (ctx) => {\n if (ctx.module.get() !== PmRootConfig.module.name) {\n return;\n }\n\n ensureListActions(ctx);\n\n ctx.interfaces.update((interfaces) => {\n const next = (interfaces ?? {}) as Record<string, unknown>;\n const master = (next['master'] ?? {}) as Record<string, unknown>;\n const list = (master['list'] ?? { actions: [], views: [] }) as Record<string, unknown>;\n const listActions = (list['actions'] ?? []) as AXPEntityAction[];\n\n if (!actionExists(listActions, AXEServiceM8CommandKeys.ProjectImportFromServiceM8)) {\n listActions.push(buildImportAction());\n }\n list['actions'] = listActions;\n master['list'] = list;\n\n const single = master['single'] as Record<string, unknown> | undefined;\n if (single) {\n const singleActions = (single['actions'] ?? []) as AXPEntityAction[];\n if (!actionExists(singleActions, AXEServiceM8CommandKeys.ProjectSync)) {\n singleActions.push(buildSyncAction());\n }\n single['actions'] = singleActions;\n }\n\n next['master'] = master;\n return next as typeof interfaces;\n });\n },\n};\n\n//#endregion\n","//#region ---- Imports ----\n\nimport { AXPSystemActionType } from '@acorex-platform/framework-shared/core';\nimport { AXPEntityAction, AXPEntityCommandScope } from '@acorex-platform/framework-shared/entity';\nimport {\n actionExists,\n AXPEntityModifierProvider,\n ensureListActions,\n} from '@acorex-platform/framework-client/layout/entity';\nimport { AXEServiceM8CommandKeys } from '@acorex-platform/extension-project-management-servicem8-client/contracts';\nimport { RootConfig as CustomerRootConfig } from '@acorex-platform/module-customer-management-client/contracts';\n\n//#endregion\n\n//#region ----- Constants -----\n\nconst IMPORT_ORDER = 36;\n\n//#endregion\n\n//#region ----- Actions -----\n\nfunction buildImportAction(): AXPEntityAction {\n return {\n name: AXEServiceM8CommandKeys.CustomerImportFromServiceM8,\n title: '@extensions-servicem8:actions.import-from-servicem8.title',\n command: { name: AXEServiceM8CommandKeys.CustomerImportFromServiceM8 },\n priority: 'secondary',\n type: AXPSystemActionType.Import,\n icon: 'fa-light fa-briefcase',\n scope: AXPEntityCommandScope.TypeLevel,\n order: IMPORT_ORDER,\n };\n}\n\n//#endregion\n\n//#region ----- Modifier -----\n\n/** Adds ServiceM8 import action to CustomerManagement.Customer list views. */\nexport const AXEServiceM8CustomerEntityModifier: AXPEntityModifierProvider = {\n entityName: CustomerRootConfig.entities.customer.name,\n order: 50,\n modifier: (ctx) => {\n if (ctx.module.get() !== CustomerRootConfig.module.name) {\n return;\n }\n\n ensureListActions(ctx);\n\n ctx.interfaces.master.list.update((list) => {\n const next = list ?? { actions: [], views: [] };\n const listActions = [...(next.actions ?? [])];\n if (!actionExists(listActions, AXEServiceM8CommandKeys.CustomerImportFromServiceM8)) {\n listActions.push(buildImportAction());\n }\n return {\n ...next,\n actions: listActions,\n };\n });\n },\n};\n\n//#endregion\n","//#region ---- ServiceM8 logo registry key ----\n\nexport const axpServiceM8LogoKey = 'servicem8' as const;\n\n/** Mark artwork (1024×1024); consuming apps copy to `assets/images/brand/integrations/`. */\nexport const AXE_SERVICEM8_MARK_SRC = 'assets/images/brand/integrations/servicem8-mark.png';\n\n/** Source mark is square. */\nexport const AXE_SERVICEM8_MARK_ASPECT = 1;\n\n//#endregion\n","import {\n AXP_INTEGRATION_AUTH_METHOD_TYPE,\n AXP_INTEGRATION_CAPABILITY,\n AXP_INTEGRATION_CONNECTION_SCOPE,\n AXPWidgetsList,\n} from '@acorex-platform/framework-shared/core';\nimport { AXPIntegrationProvider, AXPIntegrationProviderContext } from '@acorex-platform/framework-client/common';\nimport { Injectable } from '@angular/core';\nimport { AXEServiceM8CommandKeys, RootConfig } from '@acorex-platform/extension-project-management-servicem8-client/contracts';\nimport { axpServiceM8LogoKey } from '../brand/servicem8-logo-key';\n\n@Injectable()\nexport class AXEServiceM8IntegrationProvider implements AXPIntegrationProvider {\n async provide(context: AXPIntegrationProviderContext): Promise<void> {\n const I18N = `@${RootConfig.config.i18n}:integration`;\n\n context.register({\n domainId: 'project-management',\n id: 'servicem8',\n module: RootConfig.module.name,\n displayName: `${I18N}.display-name`,\n description: `${I18N}.description`,\n icon: RootConfig.module.icon,\n logoKey: axpServiceM8LogoKey,\n capabilities: [AXP_INTEGRATION_CAPABILITY.Sync, AXP_INTEGRATION_CAPABILITY.Import],\n connectionScopes: [AXP_INTEGRATION_CONNECTION_SCOPE.Tenant],\n allowMultipleConnections: true,\n defaultConnectionKey: 'default',\n connectCommandKey: AXEServiceM8CommandKeys.ConnectionConnect,\n completeOAuthCommandKey: AXEServiceM8CommandKeys.ConnectionCompleteOAuth,\n authMethods: [\n {\n type: AXP_INTEGRATION_AUTH_METHOD_TYPE.OAuth2,\n label: `${I18N}.auth.oauth.label`,\n instructions: `${I18N}.auth.oauth.instructions`,\n usesRedirect: false,\n supportsRefresh: true,\n supportsRevoke: false,\n supportsValidation: true,\n fields: [\n {\n type: AXPWidgetsList.Editors.TextBox,\n path: 'clientId',\n options: { placeholder: `${I18N}.auth.oauth.client-id.placeholder` },\n },\n {\n type: AXPWidgetsList.Editors.PasswordBox,\n path: 'clientSecret',\n options: { placeholder: `${I18N}.auth.oauth.client-secret.placeholder` },\n },\n ],\n },\n ],\n });\n }\n}\n","//#region ---- ServiceM8 integration settings badges ----\n\nimport { AXFormatService } from '@acorex/core/format';\nimport { AXPSettingsService } from '@acorex-platform/framework-client/common';\nimport { AXPRegionalSetting } from '@acorex-platform/framework-shared/core';\nimport type {\n AXPIntegrationConnectionSettingsContext,\n AXPIntegrationSettingsRowBadge,\n} from '@acorex-platform/framework-shared/core';\nimport type { AXPBackgroundJobSchedule } from '@acorex-platform/framework-shared/background-job';\nimport type { AXEServiceM8ConnectionConfig } from '@acorex-platform/extension-project-management-servicem8-client/contracts';\n\nconst SERVICEM8_I18N = '@extensions-servicem8:integration.badges';\n\n/** Must match `SERVICEM8_PULL_JOBS_COMMAND_KEY` / schedule `command.key`. */\nexport const SERVICEM8_PULL_JOBS_SCHEDULE_COMMAND_KEY = 'servicem8-extension.jobs.pull';\n\nasync function formatSyncTimestamp(\n iso: string,\n formatService: AXFormatService,\n settingsService: AXPSettingsService,\n): Promise<string | undefined> {\n const parsed = Date.parse(iso);\n if (!Number.isFinite(parsed)) {\n return undefined;\n }\n\n const dateFormat = await settingsService.get(AXPRegionalSetting.ShortDate);\n const timeFormat = await settingsService.get(AXPRegionalSetting.ShortTime);\n const format = `${dateFormat} ${timeFormat}`.trim();\n\n return formatService.format(new Date(parsed), 'datetime', { format });\n}\n\nfunction isFailureNewer(schedule: AXPBackgroundJobSchedule): boolean {\n const failed = schedule.lastFailureAt ? Date.parse(schedule.lastFailureAt) : NaN;\n const succeeded = schedule.lastSuccessAt ? Date.parse(schedule.lastSuccessAt) : NaN;\n if (!Number.isFinite(failed)) {\n return false;\n }\n if (!Number.isFinite(succeeded)) {\n return true;\n }\n return failed >= succeeded;\n}\n\nexport async function resolveServiceM8ConnectionSettingsBadges(\n ctx: AXPIntegrationConnectionSettingsContext,\n formatService: AXFormatService,\n settingsService: AXPSettingsService,\n schedule?: AXPBackgroundJobSchedule | null,\n): Promise<AXPIntegrationSettingsRowBadge[]> {\n const config = (ctx.connection.config ?? {}) as AXEServiceM8ConnectionConfig;\n const jobsSync = config.jobsSync as Record<string, unknown> | undefined;\n const lastSync =\n config.jobsSync?.lastSyncAt?.trim() ||\n (typeof jobsSync?.['lastSuccessfulSyncAt'] === 'string' ? jobsSync['lastSuccessfulSyncAt'].trim() : '');\n\n const badges: AXPIntegrationSettingsRowBadge[] = [];\n\n if (!lastSync) {\n badges.push({\n labelKey: `${SERVICEM8_I18N}.never-synced`,\n color: 'warning',\n });\n } else {\n const datetime = await formatSyncTimestamp(lastSync, formatService, settingsService);\n if (datetime) {\n badges.push({\n labelKey: `${SERVICEM8_I18N}.last-sync`,\n params: { datetime },\n color: 'success',\n });\n }\n }\n\n if (!schedule) {\n return badges;\n }\n\n if (schedule.status === 'pause') {\n badges.push({\n labelKey: `${SERVICEM8_I18N}.schedule-paused`,\n color: 'warning',\n });\n } else if (schedule.status === 'disabled') {\n badges.push({\n labelKey: `${SERVICEM8_I18N}.schedule-disabled`,\n color: 'warning',\n });\n }\n\n if (schedule.nextRunAt) {\n const datetime = await formatSyncTimestamp(schedule.nextRunAt, formatService, settingsService);\n if (datetime) {\n badges.push({\n labelKey: `${SERVICEM8_I18N}.next-run`,\n params: { datetime },\n color: 'secondary',\n });\n }\n }\n\n if (isFailureNewer(schedule) && schedule.lastFailureAt) {\n const datetime = await formatSyncTimestamp(schedule.lastFailureAt, formatService, settingsService);\n if (datetime) {\n badges.push({\n labelKey: `${SERVICEM8_I18N}.last-failure`,\n params: { datetime },\n color: 'danger',\n });\n }\n }\n\n return badges;\n}\n\n//#endregion\n","import {\n AXP_INTEGRATION_CONNECTION_STATUS,\n runtimeQueryPayloadData,\n type AXPIntegrationProviderRuntimeHooks,\n type AXPIntegrationRuntimeContext,\n type AXPIntegrationValidationResult,\n} from '@acorex-platform/framework-shared/core';\nimport {\n AXP_INTEGRATION_RUNTIME_PROVIDER,\n AXP_INTEGRATION_SECRET_STORE,\n type AXPIntegrationRuntimeProvider,\n} from '@acorex-platform/framework-client/common';\nimport type { AXEServiceM8ConnectionConfig } from '@acorex-platform/extension-project-management-servicem8-client/contracts';\nimport { Injectable, inject } from '@angular/core';\nimport { AXFormatService } from '@acorex/core/format';\nimport { AXPSettingsService } from '@acorex-platform/framework-client/common';\nimport { defaultTo, trim } from 'lodash-es';\nimport { AXPQueryService } from '@acorex-platform/framework-client/runtime';\nimport {\n AXPBackgroundJobQueryKeys,\n type AXPBackgroundJobSchedule,\n} from '@acorex-platform/framework-shared/background-job';\nimport {\n resolveServiceM8ConnectionSettingsBadges,\n SERVICEM8_PULL_JOBS_SCHEDULE_COMMAND_KEY,\n} from './servicem8-integration-settings-badges.util';\n\n@Injectable()\nexport class AXEServiceM8IntegrationRuntimeProvider implements AXPIntegrationRuntimeProvider {\n readonly providerId = 'servicem8';\n\n private readonly secretStore = inject(AXP_INTEGRATION_SECRET_STORE, { optional: true });\n private readonly formatService = inject(AXFormatService);\n private readonly settingsService = inject(AXPSettingsService);\n private readonly queryService = inject(AXPQueryService, { optional: true });\n\n async getHooks(): Promise<AXPIntegrationProviderRuntimeHooks> {\n return {\n validateConnection: (ctx) => this.validateConnection(ctx),\n createClient: (ctx) => this.createClient(ctx),\n getConnectionSettingsBadges: async (ctx) =>\n resolveServiceM8ConnectionSettingsBadges(\n ctx,\n this.formatService,\n this.settingsService,\n await this.loadPullSchedule(ctx.connection.id),\n ),\n };\n }\n\n private async validateConnection(ctx: AXPIntegrationRuntimeContext): Promise<AXPIntegrationValidationResult> {\n if (!this.hasClientCredentials(ctx)) {\n return {\n valid: false,\n status: AXP_INTEGRATION_CONNECTION_STATUS.Invalid,\n errorMessage: 'ServiceM8 App ID and App Secret are required.',\n };\n }\n\n return {\n valid: true,\n status: AXP_INTEGRATION_CONNECTION_STATUS.Connected,\n externalAccountName: trim(defaultTo(ctx.instanceUrl, 'ServiceM8')) || 'ServiceM8',\n };\n }\n\n private async createClient(ctx: AXPIntegrationRuntimeContext): Promise<{ clientId: string; baseUrl?: string }> {\n const credentials = await this.resolveClientCredentials(ctx);\n return {\n clientId: credentials.clientId,\n baseUrl: defaultTo(ctx.instanceUrl, undefined),\n };\n }\n\n private hasClientCredentials(ctx: AXPIntegrationRuntimeContext): boolean {\n const config = (ctx.config ?? {}) as AXEServiceM8ConnectionConfig;\n const clientId = trim(defaultTo(config.clientId, ''));\n const clientSecret = trim(defaultTo(config.clientSecret, ''));\n const legacyToken = trim(defaultTo(config.accessToken, ''));\n const refreshToken = trim(defaultTo(config.refreshToken, ''));\n return Boolean((clientId && clientSecret) || legacyToken || refreshToken);\n }\n\n private async resolveClientCredentials(\n ctx: AXPIntegrationRuntimeContext,\n ): Promise<{ clientId: string; clientSecret?: string }> {\n const config = (ctx.config ?? {}) as AXEServiceM8ConnectionConfig;\n const configClientId = trim(defaultTo(config.clientId, ''));\n const configClientSecret = trim(defaultTo(config.clientSecret, ''));\n\n if (configClientId && configClientSecret) {\n return { clientId: configClientId, clientSecret: configClientSecret };\n }\n\n if (!this.secretStore || !trim(defaultTo(ctx.secretRef, ''))) {\n throw new Error('ServiceM8 connection has no client credentials.');\n }\n\n const credentials = await this.secretStore.resolve(ctx.secretRef, ctx);\n const clientId = trim(defaultTo(credentials.token ?? credentials.email, ''));\n const clientSecret = trim(defaultTo(credentials.password ?? credentials.apiToken, ''));\n if (!clientId || !clientSecret) {\n throw new Error('ServiceM8 connection has no usable client credentials.');\n }\n\n return { clientId, clientSecret };\n }\n\n private async loadPullSchedule(connectionId: string): Promise<AXPBackgroundJobSchedule | null> {\n if (!this.queryService || !connectionId.trim()) {\n return null;\n }\n try {\n const result = await this.queryService.fetch<\n { commandKey: string; connectionId?: string },\n AXPBackgroundJobSchedule | null\n >(AXPBackgroundJobQueryKeys.ScheduleGetByScope, {\n commandKey: SERVICEM8_PULL_JOBS_SCHEDULE_COMMAND_KEY,\n connectionId,\n });\n return runtimeQueryPayloadData(result) ?? null;\n } catch {\n return null;\n }\n }\n}\n\nexport const provideServiceM8IntegrationRuntimeProvider = {\n provide: AXP_INTEGRATION_RUNTIME_PROVIDER,\n useClass: AXEServiceM8IntegrationRuntimeProvider,\n multi: true,\n};\n","//#region ---- Imports ----\n\nimport {\n AXP_INTEGRATION_CONNECTION_LIFECYCLE,\n type AXPIntegrationConnectionLifecycleHandler,\n} from '@acorex-platform/framework-client/common';\nimport { AXP_INTEGRATION_CONNECTION_STATUS, type AXPSafeIntegrationConnectionDto } from '@acorex-platform/framework-shared/core';\nimport { AXPCommandService } from '@acorex-platform/framework-client/runtime';\nimport { AXEServiceM8CommandKeys } from '@acorex-platform/extension-project-management-servicem8-client/contracts';\nimport { Injectable, inject } from '@angular/core';\n\n//#endregion\n\n//#region ---- Handler ----\n\n@Injectable()\nexport class AXEServiceM8ConnectionLifecycleHandler implements AXPIntegrationConnectionLifecycleHandler {\n readonly providerId = 'servicem8';\n\n private readonly commandService = inject(AXPCommandService);\n\n async onDisconnected(connection: AXPSafeIntegrationConnectionDto): Promise<void> {\n await this.runLifecycle(connection.id, 'disconnected');\n }\n\n async onDeleted(connection: AXPSafeIntegrationConnectionDto): Promise<void> {\n await this.runLifecycle(connection.id, 'deleted');\n }\n\n async onEnabledChanged(connection: AXPSafeIntegrationConnectionDto, enabled: boolean): Promise<void> {\n if (enabled) {\n if (connection.status === AXP_INTEGRATION_CONNECTION_STATUS.Connected) {\n await this.runLifecycle(connection.id, 'enabled');\n }\n return;\n }\n await this.runLifecycle(connection.id, 'disabled');\n }\n\n private async runLifecycle(\n connectionId: string,\n action: 'connected' | 'disconnected' | 'deleted' | 'enabled' | 'disabled',\n ): Promise<void> {\n await this.commandService.execute(AXEServiceM8CommandKeys.ConnectionLifecycle, {\n connectionId,\n action,\n });\n }\n}\n\nexport const provideServiceM8ConnectionLifecycleHandler = {\n provide: AXP_INTEGRATION_CONNECTION_LIFECYCLE,\n useClass: AXEServiceM8ConnectionLifecycleHandler,\n multi: true,\n};\n\n//#endregion\n","import {\n AXP_LOGO_PROVIDER,\n type AXPLogoProvider,\n type AXPLogoRequest,\n type AXPLogoView,\n} from '@acorex-platform/framework-client/core';\nimport {\n AXE_SERVICEM8_MARK_ASPECT,\n AXE_SERVICEM8_MARK_SRC,\n axpServiceM8LogoKey,\n} from './servicem8-logo-key';\n\nconst servicem8LogoProvider: AXPLogoProvider = {\n key: axpServiceM8LogoKey,\n resolve: async (request: AXPLogoRequest): Promise<AXPLogoView> => ({\n kind: 'image',\n src: AXE_SERVICEM8_MARK_SRC,\n height: request.size,\n width: Math.round(request.size * AXE_SERVICEM8_MARK_ASPECT),\n }),\n};\n\nexport const AXE_SERVICEM8_LOGO_PROVIDER = {\n provide: AXP_LOGO_PROVIDER,\n useValue: servicem8LogoProvider,\n multi: true,\n} as const;\n","//#region ---- Manifest ----\n\nimport { AXPModuleManifest } from '@acorex-platform/framework-client/core';\nimport { RootConfig } from '@acorex-platform/extension-project-management-servicem8-client/contracts';\n\nexport const ServiceM8ExtensionManifest: AXPModuleManifest = {\n name: RootConfig.module.name,\n version: '1.0.0',\n title: RootConfig.module.title,\n icon: RootConfig.module.icon,\n i18n: RootConfig.config.i18n,\n type: 'integration',\n domain: 'project-management',\n dependencies: [],\n};\n\n//#endregion\n","//#region ---- Imports ----\n\nimport {\n AXPCommonMenuKeys\n} from '@acorex-platform/module-common-client/contracts';\nimport { AXPMenuProvider, AXPMenuProviderContext } from '@acorex-platform/framework-client/common';\nimport { Injectable } from '@angular/core';\nimport {\n AXEServiceM8MenuKeys,\n RootConfig,\n AXPAdministrationPermissionKeys\n} from '@acorex-platform/extension-project-management-servicem8-client/contracts';\n\n//#endregion\n\n@Injectable()\nexport class AXEServiceM8MenuProvider implements AXPMenuProvider {\n async provide(context: AXPMenuProviderContext): Promise<void> {\n const module = RootConfig.module;\n const policy = {\n features: [module.name],\n permissions: [AXPAdministrationPermissionKeys.MaintainConnections],\n };\n\n const configMenu = context.find(AXPCommonMenuKeys.Configuration);\n if (!context.find(AXEServiceM8MenuKeys.ServiceM8ExtensionConfig).exists) {\n configMenu.addItems([\n {\n name: AXEServiceM8MenuKeys.ServiceM8ExtensionConfig,\n text: '@extensions-servicem8:module.menus.configuration.title',\n icon: module.icon,\n type: 'menu',\n priority: 8940,\n policy,\n children: [],\n },\n ]);\n }\n }\n}\n\n//#endregion\n","//#region ---- Imports ----\n\nimport { AXMIntegrationManagementModule } from '@acorex-platform/module-integration-management-client';\nimport { AXP_INTEGRATION_PROVIDER, AXP_MENU_PROVIDER } from '@acorex-platform/framework-client/common';\nimport { AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, AXP_MODULE_MANIFEST_PROVIDER } from '@acorex-platform/framework-client/core';\nimport { AXP_ENTITY_MODIFIER } from '@acorex-platform/framework-client/layout/entity';\nimport { provideCommandSetups } from '@acorex-platform/framework-client/runtime';\nimport { NgModule } from '@angular/core';\nimport { AXEServiceM8CommandKeys } from '@acorex-platform/extension-project-management-servicem8-client/contracts';\nimport { AXEServiceM8ProjectLinkExpressionEvaluatorScopeProvider } from './features/project-management/servicem8-project-link-expression-evaluator-scope.provider';\nimport { AXEServiceM8ProjectEntityModifier } from './features/project-management/project-servicem8.entity-modifier';\nimport { AXEServiceM8CustomerEntityModifier } from './features/customer-management/customer-servicem8.entity-modifier';\nimport { AXEServiceM8IntegrationProvider } from './integration/servicem8.integration.provider';\nimport { provideServiceM8IntegrationRuntimeProvider } from './integration/servicem8.integration-runtime.provider';\nimport { provideServiceM8ConnectionLifecycleHandler } from './integration/servicem8.connection-lifecycle.handler';\nimport { AXE_SERVICEM8_LOGO_PROVIDER } from './brand/servicem8-logo.provider';\nimport { ServiceM8ExtensionManifest } from './manifest';\nimport { AXEServiceM8MenuProvider } from './menu.provider';\n\n//#endregion\n\n@NgModule({\n imports: [AXMIntegrationManagementModule],\n providers: [\n AXE_SERVICEM8_LOGO_PROVIDER,\n { provide: AXP_MODULE_MANIFEST_PROVIDER, useValue: ServiceM8ExtensionManifest, multi: true },\n { provide: AXP_MENU_PROVIDER, useClass: AXEServiceM8MenuProvider, multi: true },\n { provide: AXP_ENTITY_MODIFIER, useValue: AXEServiceM8ProjectEntityModifier, multi: true },\n { provide: AXP_ENTITY_MODIFIER, useValue: AXEServiceM8CustomerEntityModifier, multi: true },\n {\n provide: AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER,\n useClass: AXEServiceM8ProjectLinkExpressionEvaluatorScopeProvider,\n multi: true,\n },\n { provide: AXP_INTEGRATION_PROVIDER, useClass: AXEServiceM8IntegrationProvider, multi: true },\n provideServiceM8IntegrationRuntimeProvider,\n provideServiceM8ConnectionLifecycleHandler,\n provideCommandSetups([\n {\n key: AXEServiceM8CommandKeys.ConnectionConnect,\n command: () => import('./features/connect/connect-servicem8.command').then((c) => c.ConnectServiceM8Command),\n },\n {\n key: AXEServiceM8CommandKeys.ConnectionCompleteOAuth,\n command: () =>\n import('./features/connect/complete-servicem8-oauth.command').then((c) => c.CompleteServiceM8OAuthCommand),\n },\n ]),\n ],\n})\nexport class AXEServiceM8ExtensionModule {}\n\n//#endregion\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["PmRootConfig","IMPORT_ORDER","buildImportAction","AXEServiceM8CommandKeys","CustomerRootConfig","RootConfig"],"mappings":";;;;;;;;;;;;;;;;;;;AAAO,MAAM,8BAA8B,GAAG,oBAAoB;;ACAlE;AAWA;MAEa,uDAAuD,CAAA;AAApE,IAAA,WAAA,GAAA;AACmB,QAAA,IAAA,CAAA,aAAa,GAAG,MAAM,CAAC,gBAAgB,CAAC;AACxC,QAAA,IAAA,CAAA,kBAAkB,GAAG,MAAM,CAAC,4BAA4B,CAAC;IA2B5E;IAzBE,MAAM,OAAO,CAAC,OAAmD,EAAA;AAC/D,QAAA,OAAO,CAAC,QAAQ,CAAC,8BAA8B,EAAE;AAC/C,YAAA,cAAc,EAAE,OAAO,oBAA8B,KAAsB;AACzE,gBAAA,MAAM,iBAAiB,GAAG,OAAO,oBAAoB,KAAK,QAAQ,GAAG,oBAAoB,CAAC,IAAI,EAAE,GAAG,EAAE;gBACrG,IAAI,CAAC,iBAAiB,EAAE;AACtB,oBAAA,OAAO,KAAK;gBACd;AAEA,gBAAA,MAAM,OAAO,IAAI,MAAM,IAAI,CAAC;AACzB,qBAAA,UAAU,CAACA,UAAY,CAAC,MAAM,CAAC,IAAI,EAAEA,UAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI;AACvE,qBAAA,IAAI;AACJ,qBAAA,KAAK,CAAC,iBAAiB,CAAC,CAAkD;AAE7E,gBAAA,OAAO,OAAO,EAAE,cAAc,EAAE,MAAM,KAAK,qCAAqC;YAClF,CAAC;YACD,sBAAsB,EAAE,YAA6B;AACnD,gBAAA,IAAI;AACF,oBAAA,MAAM,IAAI,CAAC,kBAAkB,CAAC,aAAa,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC;AACnG,oBAAA,OAAO,IAAI;gBACb;AAAE,gBAAA,MAAM;AACN,oBAAA,OAAO,KAAK;gBACd;YACF,CAAC;AACF,SAAA,CAAC;IACJ;AACD;AAED;;AC5CA;AAaA;AAEA,MAAMC,cAAY,GAAG,EAAE;AACvB,MAAM,UAAU,GAAG,EAAE;AAErB,SAASC,mBAAiB,GAAA;IACxB,OAAO;QACL,IAAI,EAAE,uBAAuB,CAAC,0BAA0B;AACxD,QAAA,KAAK,EAAE,2DAA2D;AAClE,QAAA,OAAO,EAAE,EAAE,IAAI,EAAE,uBAAuB,CAAC,0BAA0B,EAAE;AACrE,QAAA,QAAQ,EAAE,WAAW;QACrB,IAAI,EAAE,mBAAmB,CAAC,MAAM;AAChC,QAAA,IAAI,EAAE,uBAAuB;QAC7B,KAAK,EAAE,qBAAqB,CAAC,SAAS;AACtC,QAAA,KAAK,EAAED,cAAY;KACpB;AACH;AAEA,SAAS,eAAe,GAAA;IACtB,OAAO;QACL,IAAI,EAAE,uBAAuB,CAAC,WAAW;AACzC,QAAA,KAAK,EAAE,0CAA0C;AACjD,QAAA,OAAO,EAAE;YACP,IAAI,EAAE,uBAAuB,CAAC,WAAW;AACzC,YAAA,OAAO,EAAE,EAAE,iBAAiB,EAAE,0BAA0B,EAAE;AAC3D,SAAA;AACD,QAAA,QAAQ,EAAE,WAAW;QACrB,IAAI,EAAE,mBAAmB,CAAC,IAAI;AAC9B,QAAA,IAAI,EAAE,2BAA2B;QACjC,KAAK,EAAE,qBAAqB,CAAC,UAAU;AACvC,QAAA,KAAK,EAAE,UAAU;QACjB,MAAM,EAAE,CAAA,WAAA,EAAc,8BAA8B,CAAA,uCAAA,CAAyC;KAC9F;AACH;AAEA;AACO,MAAM,iCAAiC,GAA8B;AAC1E,IAAA,UAAU,EAAED,UAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI;AAC9C,IAAA,KAAK,EAAE,EAAE;AACT,IAAA,QAAQ,EAAE,CAAC,GAAG,KAAI;AAChB,QAAA,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,KAAKA,UAAY,CAAC,MAAM,CAAC,IAAI,EAAE;YACjD;QACF;QAEA,iBAAiB,CAAC,GAAG,CAAC;QAEtB,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,UAAU,KAAI;AACnC,YAAA,MAAM,IAAI,IAAI,UAAU,IAAI,EAAE,CAA4B;YAC1D,MAAM,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAA4B;AAChE,YAAA,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAA4B;YACtF,MAAM,WAAW,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAsB;YAEhE,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,uBAAuB,CAAC,0BAA0B,CAAC,EAAE;AAClF,gBAAA,WAAW,CAAC,IAAI,CAACE,mBAAiB,EAAE,CAAC;YACvC;AACA,YAAA,IAAI,CAAC,SAAS,CAAC,GAAG,WAAW;AAC7B,YAAA,MAAM,CAAC,MAAM,CAAC,GAAG,IAAI;AAErB,YAAA,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAwC;YACtE,IAAI,MAAM,EAAE;gBACV,MAAM,aAAa,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,CAAsB;gBACpE,IAAI,CAAC,YAAY,CAAC,aAAa,EAAE,uBAAuB,CAAC,WAAW,CAAC,EAAE;AACrE,oBAAA,aAAa,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;gBACvC;AACA,gBAAA,MAAM,CAAC,SAAS,CAAC,GAAG,aAAa;YACnC;AAEA,YAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,MAAM;AACvB,YAAA,OAAO,IAAyB;AAClC,QAAA,CAAC,CAAC;IACJ,CAAC;CACF;AAED;;ACtFA;AAYA;AAEA;AAEA,MAAM,YAAY,GAAG,EAAE;AAEvB;AAEA;AAEA,SAAS,iBAAiB,GAAA;IACxB,OAAO;QACL,IAAI,EAAEC,yBAAuB,CAAC,2BAA2B;AACzD,QAAA,KAAK,EAAE,2DAA2D;AAClE,QAAA,OAAO,EAAE,EAAE,IAAI,EAAEA,yBAAuB,CAAC,2BAA2B,EAAE;AACtE,QAAA,QAAQ,EAAE,WAAW;QACrB,IAAI,EAAE,mBAAmB,CAAC,MAAM;AAChC,QAAA,IAAI,EAAE,uBAAuB;QAC7B,KAAK,EAAE,qBAAqB,CAAC,SAAS;AACtC,QAAA,KAAK,EAAE,YAAY;KACpB;AACH;AAEA;AAEA;AAEA;AACO,MAAM,kCAAkC,GAA8B;AAC3E,IAAA,UAAU,EAAEC,YAAkB,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI;AACrD,IAAA,KAAK,EAAE,EAAE;AACT,IAAA,QAAQ,EAAE,CAAC,GAAG,KAAI;AAChB,QAAA,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,KAAKA,YAAkB,CAAC,MAAM,CAAC,IAAI,EAAE;YACvD;QACF;QAEA,iBAAiB,CAAC,GAAG,CAAC;AAEtB,QAAA,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,KAAI;AACzC,YAAA,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;AAC/C,YAAA,MAAM,WAAW,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;YAC7C,IAAI,CAAC,YAAY,CAAC,WAAW,EAAED,yBAAuB,CAAC,2BAA2B,CAAC,EAAE;AACnF,gBAAA,WAAW,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvC;YACA,OAAO;AACL,gBAAA,GAAG,IAAI;AACP,gBAAA,OAAO,EAAE,WAAW;aACrB;AACH,QAAA,CAAC,CAAC;IACJ,CAAC;CACF;AAED;;AChEA;AAEO,MAAM,mBAAmB,GAAG;AAEnC;AACO,MAAM,sBAAsB,GAAG;AAEtC;AACO,MAAM,yBAAyB,GAAG;AAEzC;;MCEa,+BAA+B,CAAA;IAC1C,MAAM,OAAO,CAAC,OAAsC,EAAA;QAClD,MAAM,IAAI,GAAG,CAAA,CAAA,EAAIE,YAAU,CAAC,MAAM,CAAC,IAAI,CAAA,YAAA,CAAc;QAErD,OAAO,CAAC,QAAQ,CAAC;AACf,YAAA,QAAQ,EAAE,oBAAoB;AAC9B,YAAA,EAAE,EAAE,WAAW;AACf,YAAA,MAAM,EAAEA,YAAU,CAAC,MAAM,CAAC,IAAI;YAC9B,WAAW,EAAE,CAAA,EAAG,IAAI,CAAA,aAAA,CAAe;YACnC,WAAW,EAAE,CAAA,EAAG,IAAI,CAAA,YAAA,CAAc;AAClC,YAAA,IAAI,EAAEA,YAAU,CAAC,MAAM,CAAC,IAAI;AAC5B,YAAA,OAAO,EAAE,mBAAmB;YAC5B,YAAY,EAAE,CAAC,0BAA0B,CAAC,IAAI,EAAE,0BAA0B,CAAC,MAAM,CAAC;AAClF,YAAA,gBAAgB,EAAE,CAAC,gCAAgC,CAAC,MAAM,CAAC;AAC3D,YAAA,wBAAwB,EAAE,IAAI;AAC9B,YAAA,oBAAoB,EAAE,SAAS;YAC/B,iBAAiB,EAAE,uBAAuB,CAAC,iBAAiB;YAC5D,uBAAuB,EAAE,uBAAuB,CAAC,uBAAuB;AACxE,YAAA,WAAW,EAAE;AACX,gBAAA;oBACE,IAAI,EAAE,gCAAgC,CAAC,MAAM;oBAC7C,KAAK,EAAE,CAAA,EAAG,IAAI,CAAA,iBAAA,CAAmB;oBACjC,YAAY,EAAE,CAAA,EAAG,IAAI,CAAA,wBAAA,CAA0B;AAC/C,oBAAA,YAAY,EAAE,KAAK;AACnB,oBAAA,eAAe,EAAE,IAAI;AACrB,oBAAA,cAAc,EAAE,KAAK;AACrB,oBAAA,kBAAkB,EAAE,IAAI;AACxB,oBAAA,MAAM,EAAE;AACN,wBAAA;AACE,4BAAA,IAAI,EAAE,cAAc,CAAC,OAAO,CAAC,OAAO;AACpC,4BAAA,IAAI,EAAE,UAAU;AAChB,4BAAA,OAAO,EAAE,EAAE,WAAW,EAAE,CAAA,EAAG,IAAI,mCAAmC,EAAE;AACrE,yBAAA;AACD,wBAAA;AACE,4BAAA,IAAI,EAAE,cAAc,CAAC,OAAO,CAAC,WAAW;AACxC,4BAAA,IAAI,EAAE,cAAc;AACpB,4BAAA,OAAO,EAAE,EAAE,WAAW,EAAE,CAAA,EAAG,IAAI,uCAAuC,EAAE;AACzE,yBAAA;AACF,qBAAA;AACF,iBAAA;AACF,aAAA;AACF,SAAA,CAAC;IACJ;8GA1CW,+BAA+B,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA,CAAA;kHAA/B,+BAA+B,EAAA,CAAA,CAAA;;2FAA/B,+BAA+B,EAAA,UAAA,EAAA,CAAA;kBAD3C;;;ACXD;AAYA,MAAM,cAAc,GAAG,0CAA0C;AAEjE;AACO,MAAM,wCAAwC,GAAG,+BAA+B;AAEvF,eAAe,mBAAmB,CAChC,GAAW,EACX,aAA8B,EAC9B,eAAmC,EAAA;IAEnC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;IAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE;AAC5B,QAAA,OAAO,SAAS;IAClB;IAEA,MAAM,UAAU,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,kBAAkB,CAAC,SAAS,CAAC;IAC1E,MAAM,UAAU,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,kBAAkB,CAAC,SAAS,CAAC;IAC1E,MAAM,MAAM,GAAG,CAAA,EAAG,UAAU,CAAA,CAAA,EAAI,UAAU,CAAA,CAAE,CAAC,IAAI,EAAE;AAEnD,IAAA,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC;AACvE;AAEA,SAAS,cAAc,CAAC,QAAkC,EAAA;IACxD,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,GAAG,GAAG;IAChF,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,GAAG,GAAG;IACnF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE;AAC5B,QAAA,OAAO,KAAK;IACd;IACA,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;AAC/B,QAAA,OAAO,IAAI;IACb;IACA,OAAO,MAAM,IAAI,SAAS;AAC5B;AAEO,eAAe,wCAAwC,CAC5D,GAA4C,EAC5C,aAA8B,EAC9B,eAAmC,EACnC,QAA0C,EAAA;IAE1C,MAAM,MAAM,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,IAAI,EAAE,CAAiC;AAC5E,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,QAA+C;IACvE,MAAM,QAAQ,GACZ,MAAM,CAAC,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE;SAClC,OAAO,QAAQ,GAAG,sBAAsB,CAAC,KAAK,QAAQ,GAAG,QAAQ,CAAC,sBAAsB,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC;IAEzG,MAAM,MAAM,GAAqC,EAAE;IAEnD,IAAI,CAAC,QAAQ,EAAE;QACb,MAAM,CAAC,IAAI,CAAC;YACV,QAAQ,EAAE,CAAA,EAAG,cAAc,CAAA,aAAA,CAAe;AAC1C,YAAA,KAAK,EAAE,SAAS;AACjB,SAAA,CAAC;IACJ;SAAO;QACL,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,QAAQ,EAAE,aAAa,EAAE,eAAe,CAAC;QACpF,IAAI,QAAQ,EAAE;YACZ,MAAM,CAAC,IAAI,CAAC;gBACV,QAAQ,EAAE,CAAA,EAAG,cAAc,CAAA,UAAA,CAAY;gBACvC,MAAM,EAAE,EAAE,QAAQ,EAAE;AACpB,gBAAA,KAAK,EAAE,SAAS;AACjB,aAAA,CAAC;QACJ;IACF;IAEA,IAAI,CAAC,QAAQ,EAAE;AACb,QAAA,OAAO,MAAM;IACf;AAEA,IAAA,IAAI,QAAQ,CAAC,MAAM,KAAK,OAAO,EAAE;QAC/B,MAAM,CAAC,IAAI,CAAC;YACV,QAAQ,EAAE,CAAA,EAAG,cAAc,CAAA,gBAAA,CAAkB;AAC7C,YAAA,KAAK,EAAE,SAAS;AACjB,SAAA,CAAC;IACJ;AAAO,SAAA,IAAI,QAAQ,CAAC,MAAM,KAAK,UAAU,EAAE;QACzC,MAAM,CAAC,IAAI,CAAC;YACV,QAAQ,EAAE,CAAA,EAAG,cAAc,CAAA,kBAAA,CAAoB;AAC/C,YAAA,KAAK,EAAE,SAAS;AACjB,SAAA,CAAC;IACJ;AAEA,IAAA,IAAI,QAAQ,CAAC,SAAS,EAAE;AACtB,QAAA,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,SAAS,EAAE,aAAa,EAAE,eAAe,CAAC;QAC9F,IAAI,QAAQ,EAAE;YACZ,MAAM,CAAC,IAAI,CAAC;gBACV,QAAQ,EAAE,CAAA,EAAG,cAAc,CAAA,SAAA,CAAW;gBACtC,MAAM,EAAE,EAAE,QAAQ,EAAE;AACpB,gBAAA,KAAK,EAAE,WAAW;AACnB,aAAA,CAAC;QACJ;IACF;IAEA,IAAI,cAAc,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,aAAa,EAAE;AACtD,QAAA,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,aAAa,EAAE,aAAa,EAAE,eAAe,CAAC;QAClG,IAAI,QAAQ,EAAE;YACZ,MAAM,CAAC,IAAI,CAAC;gBACV,QAAQ,EAAE,CAAA,EAAG,cAAc,CAAA,aAAA,CAAe;gBAC1C,MAAM,EAAE,EAAE,QAAQ,EAAE;AACpB,gBAAA,KAAK,EAAE,QAAQ;AAChB,aAAA,CAAC;QACJ;IACF;AAEA,IAAA,OAAO,MAAM;AACf;AAEA;;MCzFa,sCAAsC,CAAA;AADnD,IAAA,WAAA,GAAA;QAEW,IAAA,CAAA,UAAU,GAAG,WAAW;QAEhB,IAAA,CAAA,WAAW,GAAG,MAAM,CAAC,4BAA4B,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AACtE,QAAA,IAAA,CAAA,aAAa,GAAG,MAAM,CAAC,eAAe,CAAC;AACvC,QAAA,IAAA,CAAA,eAAe,GAAG,MAAM,CAAC,kBAAkB,CAAC;QAC5C,IAAA,CAAA,YAAY,GAAG,MAAM,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AA2F5E,IAAA;AAzFC,IAAA,MAAM,QAAQ,GAAA;QACZ,OAAO;YACL,kBAAkB,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC;YACzD,YAAY,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC;AAC7C,YAAA,2BAA2B,EAAE,OAAO,GAAG,KACrC,wCAAwC,CACtC,GAAG,EACH,IAAI,CAAC,aAAa,EAClB,IAAI,CAAC,eAAe,EACpB,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAC/C;SACJ;IACH;IAEQ,MAAM,kBAAkB,CAAC,GAAiC,EAAA;QAChE,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,EAAE;YACnC,OAAO;AACL,gBAAA,KAAK,EAAE,KAAK;gBACZ,MAAM,EAAE,iCAAiC,CAAC,OAAO;AACjD,gBAAA,YAAY,EAAE,+CAA+C;aAC9D;QACH;QAEA,OAAO;AACL,YAAA,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,iCAAiC,CAAC,SAAS;AACnD,YAAA,mBAAmB,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,IAAI,WAAW;SAClF;IACH;IAEQ,MAAM,YAAY,CAAC,GAAiC,EAAA;QAC1D,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC;QAC5D,OAAO;YACL,QAAQ,EAAE,WAAW,CAAC,QAAQ;YAC9B,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC;SAC/C;IACH;AAEQ,IAAA,oBAAoB,CAAC,GAAiC,EAAA;QAC5D,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,EAAE,CAAiC;AACjE,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AACrD,QAAA,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;AAC7D,QAAA,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AAC3D,QAAA,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;AAC7D,QAAA,OAAO,OAAO,CAAC,CAAC,QAAQ,IAAI,YAAY,KAAK,WAAW,IAAI,YAAY,CAAC;IAC3E;IAEQ,MAAM,wBAAwB,CACpC,GAAiC,EAAA;QAEjC,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,EAAE,CAAiC;AACjE,QAAA,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAC3D,QAAA,MAAM,kBAAkB,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;AAEnE,QAAA,IAAI,cAAc,IAAI,kBAAkB,EAAE;YACxC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,kBAAkB,EAAE;QACvE;AAEA,QAAA,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,EAAE;AAC5D,YAAA,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC;QACpE;AAEA,QAAA,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC;AACtE,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,KAAK,IAAI,WAAW,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC5E,QAAA,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,QAAQ,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AACtF,QAAA,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY,EAAE;AAC9B,YAAA,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC;QAC3E;AAEA,QAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE;IACnC;IAEQ,MAAM,gBAAgB,CAAC,YAAoB,EAAA;QACjD,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE;AAC9C,YAAA,OAAO,IAAI;QACb;AACA,QAAA,IAAI;AACF,YAAA,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,CAG1C,yBAAyB,CAAC,kBAAkB,EAAE;AAC9C,gBAAA,UAAU,EAAE,wCAAwC;gBACpD,YAAY;AACb,aAAA,CAAC;AACF,YAAA,OAAO,uBAAuB,CAAC,MAAM,CAAC,IAAI,IAAI;QAChD;AAAE,QAAA,MAAM;AACN,YAAA,OAAO,IAAI;QACb;IACF;8GAhGW,sCAAsC,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA,CAAA;kHAAtC,sCAAsC,EAAA,CAAA,CAAA;;2FAAtC,sCAAsC,EAAA,UAAA,EAAA,CAAA;kBADlD;;AAoGM,MAAM,0CAA0C,GAAG;AACxD,IAAA,OAAO,EAAE,gCAAgC;AACzC,IAAA,QAAQ,EAAE,sCAAsC;AAChD,IAAA,KAAK,EAAE,IAAI;CACZ;;ACnID;AAWA;AAEA;MAGa,sCAAsC,CAAA;AADnD,IAAA,WAAA,GAAA;QAEW,IAAA,CAAA,UAAU,GAAG,WAAW;AAEhB,QAAA,IAAA,CAAA,cAAc,GAAG,MAAM,CAAC,iBAAiB,CAAC;AA6B5D,IAAA;IA3BC,MAAM,cAAc,CAAC,UAA2C,EAAA;QAC9D,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,EAAE,cAAc,CAAC;IACxD;IAEA,MAAM,SAAS,CAAC,UAA2C,EAAA;QACzD,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,CAAC;IACnD;AAEA,IAAA,MAAM,gBAAgB,CAAC,UAA2C,EAAE,OAAgB,EAAA;QAClF,IAAI,OAAO,EAAE;YACX,IAAI,UAAU,CAAC,MAAM,KAAK,iCAAiC,CAAC,SAAS,EAAE;gBACrE,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,CAAC;YACnD;YACA;QACF;QACA,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,EAAE,UAAU,CAAC;IACpD;AAEQ,IAAA,MAAM,YAAY,CACxB,YAAoB,EACpB,MAAyE,EAAA;QAEzE,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAACF,yBAAuB,CAAC,mBAAmB,EAAE;YAC7E,YAAY;YACZ,MAAM;AACP,SAAA,CAAC;IACJ;8GA/BW,sCAAsC,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA,CAAA;kHAAtC,sCAAsC,EAAA,CAAA,CAAA;;2FAAtC,sCAAsC,EAAA,UAAA,EAAA,CAAA;kBADlD;;AAmCM,MAAM,0CAA0C,GAAG;AACxD,IAAA,OAAO,EAAE,oCAAoC;AAC7C,IAAA,QAAQ,EAAE,sCAAsC;AAChD,IAAA,KAAK,EAAE,IAAI;CACZ;;AC1CD,MAAM,qBAAqB,GAAoB;AAC7C,IAAA,GAAG,EAAE,mBAAmB;AACxB,IAAA,OAAO,EAAE,OAAO,OAAuB,MAA4B;AACjE,QAAA,IAAI,EAAE,OAAO;AACb,QAAA,GAAG,EAAE,sBAAsB;QAC3B,MAAM,EAAE,OAAO,CAAC,IAAI;QACpB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,GAAG,yBAAyB,CAAC;KAC5D,CAAC;CACH;AAEM,MAAM,2BAA2B,GAAG;AACzC,IAAA,OAAO,EAAE,iBAAiB;AAC1B,IAAA,QAAQ,EAAE,qBAAqB;AAC/B,IAAA,KAAK,EAAE,IAAI;;;ACzBb;AAKO,MAAM,0BAA0B,GAAsB;AAC3D,IAAA,IAAI,EAAEE,YAAU,CAAC,MAAM,CAAC,IAAI;AAC5B,IAAA,OAAO,EAAE,OAAO;AAChB,IAAA,KAAK,EAAEA,YAAU,CAAC,MAAM,CAAC,KAAK;AAC9B,IAAA,IAAI,EAAEA,YAAU,CAAC,MAAM,CAAC,IAAI;AAC5B,IAAA,IAAI,EAAEA,YAAU,CAAC,MAAM,CAAC,IAAI;AAC5B,IAAA,IAAI,EAAE,aAAa;AACnB,IAAA,MAAM,EAAE,oBAAoB;AAC5B,IAAA,YAAY,EAAE,EAAE;CACjB;AAED;;AChBA;AAaA;MAGa,wBAAwB,CAAA;IACnC,MAAM,OAAO,CAAC,OAA+B,EAAA;AAC3C,QAAA,MAAM,MAAM,GAAGA,YAAU,CAAC,MAAM;AAChC,QAAA,MAAM,MAAM,GAAG;AACb,YAAA,QAAQ,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC;AACvB,YAAA,WAAW,EAAE,CAAC,+BAA+B,CAAC,mBAAmB,CAAC;SACnE;QAED,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,aAAa,CAAC;AAChE,QAAA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,wBAAwB,CAAC,CAAC,MAAM,EAAE;YACvE,UAAU,CAAC,QAAQ,CAAC;AAClB,gBAAA;oBACE,IAAI,EAAE,oBAAoB,CAAC,wBAAwB;AACnD,oBAAA,IAAI,EAAE,wDAAwD;oBAC9D,IAAI,EAAE,MAAM,CAAC,IAAI;AACjB,oBAAA,IAAI,EAAE,MAAM;AACZ,oBAAA,QAAQ,EAAE,IAAI;oBACd,MAAM;AACN,oBAAA,QAAQ,EAAE,EAAE;AACb,iBAAA;AACF,aAAA,CAAC;QACJ;IACF;8GAtBW,wBAAwB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA,CAAA;kHAAxB,wBAAwB,EAAA,CAAA,CAAA;;2FAAxB,wBAAwB,EAAA,UAAA,EAAA,CAAA;kBADpC;;;ACfD;AAmBA;MA+Ba,2BAA2B,CAAA;8GAA3B,2BAA2B,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,QAAA,EAAA,CAAA,CAAA;AAA3B,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,mBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,2BAA2B,YA5B5B,8BAA8B,CAAA,EAAA,CAAA,CAAA;AA4B7B,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,mBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,2BAA2B,EAAA,SAAA,EA3B3B;YACT,2BAA2B;YAC3B,EAAE,OAAO,EAAE,4BAA4B,EAAE,QAAQ,EAAE,0BAA0B,EAAE,KAAK,EAAE,IAAI,EAAE;YAC5F,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,wBAAwB,EAAE,KAAK,EAAE,IAAI,EAAE;YAC/E,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,iCAAiC,EAAE,KAAK,EAAE,IAAI,EAAE;YAC1F,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,kCAAkC,EAAE,KAAK,EAAE,IAAI,EAAE;AAC3F,YAAA;AACE,gBAAA,OAAO,EAAE,uCAAuC;AAChD,gBAAA,QAAQ,EAAE,uDAAuD;AACjE,gBAAA,KAAK,EAAE,IAAI;AACZ,aAAA;YACD,EAAE,OAAO,EAAE,wBAAwB,EAAE,QAAQ,EAAE,+BAA+B,EAAE,KAAK,EAAE,IAAI,EAAE;YAC7F,0CAA0C;YAC1C,0CAA0C;AAC1C,YAAA,oBAAoB,CAAC;AACnB,gBAAA;oBACE,GAAG,EAAE,uBAAuB,CAAC,iBAAiB;AAC9C,oBAAA,OAAO,EAAE,MAAM,OAAO,wGAA8C,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,uBAAuB,CAAC;AAC7G,iBAAA;AACD,gBAAA;oBACE,GAAG,EAAE,uBAAuB,CAAC,uBAAuB;AACpD,oBAAA,OAAO,EAAE,MACP,OAAO,+GAAqD,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,6BAA6B,CAAC;AAC7G,iBAAA;aACF,CAAC;AACH,SAAA,EAAA,OAAA,EAAA,CA1BS,8BAA8B,CAAA,EAAA,CAAA,CAAA;;2FA4B7B,2BAA2B,EAAA,UAAA,EAAA,CAAA;kBA7BvC,QAAQ;AAAC,YAAA,IAAA,EAAA,CAAA;oBACR,OAAO,EAAE,CAAC,8BAA8B,CAAC;AACzC,oBAAA,SAAS,EAAE;wBACT,2BAA2B;wBAC3B,EAAE,OAAO,EAAE,4BAA4B,EAAE,QAAQ,EAAE,0BAA0B,EAAE,KAAK,EAAE,IAAI,EAAE;wBAC5F,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,wBAAwB,EAAE,KAAK,EAAE,IAAI,EAAE;wBAC/E,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,iCAAiC,EAAE,KAAK,EAAE,IAAI,EAAE;wBAC1F,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,kCAAkC,EAAE,KAAK,EAAE,IAAI,EAAE;AAC3F,wBAAA;AACE,4BAAA,OAAO,EAAE,uCAAuC;AAChD,4BAAA,QAAQ,EAAE,uDAAuD;AACjE,4BAAA,KAAK,EAAE,IAAI;AACZ,yBAAA;wBACD,EAAE,OAAO,EAAE,wBAAwB,EAAE,QAAQ,EAAE,+BAA+B,EAAE,KAAK,EAAE,IAAI,EAAE;wBAC7F,0CAA0C;wBAC1C,0CAA0C;AAC1C,wBAAA,oBAAoB,CAAC;AACnB,4BAAA;gCACE,GAAG,EAAE,uBAAuB,CAAC,iBAAiB;AAC9C,gCAAA,OAAO,EAAE,MAAM,OAAO,wGAA8C,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,uBAAuB,CAAC;AAC7G,6BAAA;AACD,4BAAA;gCACE,GAAG,EAAE,uBAAuB,CAAC,uBAAuB;AACpD,gCAAA,OAAO,EAAE,MACP,OAAO,+GAAqD,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,6BAA6B,CAAC;AAC7G,6BAAA;yBACF,CAAC;AACH,qBAAA;AACF,iBAAA;;;ACjDD;;AAEG;;;;"}
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@acorex-platform/extension-project-management-servicem8-client",
3
- "version": "0.0.30",
3
+ "version": "0.0.33",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
- "@acorex-platform/extension-project-management-core-client": "0.0.30",
6
+ "@acorex-platform/extension-project-management-core-client": "0.0.33",
7
7
  "@angular/common": "^21.0.0 || ^22.0.0",
8
8
  "@angular/core": "^21.0.0 || ^22.0.0"
9
9
  },
@@ -15,10 +15,12 @@ declare const RootConfig: {
15
15
  declare const AXEServiceM8CommandKeys: {
16
16
  readonly ConnectionConnect: "servicem8-extension.Connection:Connect";
17
17
  readonly ConnectionCompleteOAuth: "servicem8-extension.Connection:CompleteOAuth";
18
+ readonly ConnectionLifecycle: "servicem8-extension.Connection:Lifecycle";
18
19
  readonly OAuthStart: "servicem8-extension.OAuth:Start";
19
20
  readonly OAuthComplete: "servicem8-extension.OAuth:Complete";
20
21
  readonly OAuthValidate: "servicem8-extension.OAuth:Validate";
21
22
  readonly ProjectImportFromServiceM8: "servicem8-extension.Project:ImportFromServiceM8";
23
+ readonly CustomerImportFromServiceM8: "servicem8-extension.Customer:ImportFromServiceM8";
22
24
  readonly ProjectSync: "servicem8-extension.Project:Sync";
23
25
  readonly JobsPull: "servicem8-extension.Jobs:Pull";
24
26
  readonly JobsRefresh: "servicem8-extension.Jobs:Refresh";
@@ -66,6 +68,9 @@ declare const AXEServiceM8Permissions: {
66
68
  readonly ProjectImport: {
67
69
  readonly Execute: "Administration:Permission:ManageIntegrationActions";
68
70
  };
71
+ readonly CustomerImport: {
72
+ readonly Execute: "Administration:Permission:ManageIntegrationActions";
73
+ };
69
74
  readonly ProjectSync: {
70
75
  readonly Execute: "Administration:Permission:ManageIntegrationActions";
71
76
  };
@@ -138,7 +143,44 @@ interface AXEServiceM8RemoteJob {
138
143
  billing_address?: string | null;
139
144
  work_done_description?: string | null;
140
145
  }
146
+ /**
147
+ * ServiceM8 company (client) — cached as `resourceType=customer`.
148
+ * Includes ServiceM8 address fields plus optional Simpro-shaped aliases when present.
149
+ */
150
+ interface AXEServiceM8RemoteCustomer {
151
+ uuid: string;
152
+ name?: string | null;
153
+ address?: string | null;
154
+ address_street?: string | null;
155
+ address_city?: string | null;
156
+ address_state?: string | null;
157
+ address_postcode?: string | null;
158
+ address_country?: string | null;
159
+ fax_number?: string | null;
160
+ FamilyName?: string | null;
161
+ Phone?: string | null;
162
+ Email?: string | null;
163
+ EIN?: string | null;
164
+ CompanyName?: string | null;
165
+ active?: number | boolean | null;
166
+ edit_date?: string | null;
167
+ }
168
+ /** ServiceM8 companycontact — cached as `resourceType=site`. */
169
+ interface AXEServiceM8RemoteSite {
170
+ uuid: string;
171
+ company_uuid?: string | null;
172
+ first?: string | null;
173
+ last?: string | null;
174
+ phone?: string | null;
175
+ mobile?: string | null;
176
+ email?: string | null;
177
+ type?: string | null;
178
+ active?: number | boolean | null;
179
+ edit_date?: string | null;
180
+ }
141
181
  interface AXEServiceM8PullJobsRequest {
182
+ /** When set, pulls for this ServiceM8 connection (required for multi-connection schedules). */
183
+ connectionId?: string;
142
184
  /** Maximum ServiceM8 jobs to pull into the cache (default 250, max 1000). */
143
185
  maxJobs?: number;
144
186
  /**
@@ -154,6 +196,21 @@ interface AXEServiceM8PullJobsResponse {
154
196
  skipped?: boolean;
155
197
  /** Whether this run used incremental or full snapshot mode. */
156
198
  syncMode?: 'incremental' | 'full';
199
+ /** Per data-type upsert counts (jobs / customers / sites). */
200
+ byResourceType?: {
201
+ job?: {
202
+ upserted: number;
203
+ deactivated: number;
204
+ };
205
+ customer?: {
206
+ upserted: number;
207
+ deactivated: number;
208
+ };
209
+ site?: {
210
+ upserted: number;
211
+ deactivated: number;
212
+ };
213
+ };
157
214
  }
158
215
  interface AXEServiceM8ImportJobRequest {
159
216
  remoteRecordId: string;
@@ -164,9 +221,23 @@ interface AXEServiceM8ImportJobResponse {
164
221
  platformProjectId: string;
165
222
  created: boolean;
166
223
  }
224
+ interface AXEServiceM8ImportCustomerRequest {
225
+ remoteRecordId: string;
226
+ }
227
+ interface AXEServiceM8ImportCustomerResponse {
228
+ platformCustomerId: string;
229
+ created: boolean;
230
+ }
167
231
  interface AXEServiceM8SyncPlatformProjectRequest {
168
232
  platformProjectId: string;
169
233
  }
234
+ interface AXEServiceM8ConnectionLifecycleRequest {
235
+ connectionId: string;
236
+ action: 'connected' | 'disconnected' | 'deleted' | 'enabled' | 'disabled';
237
+ }
238
+ interface AXEServiceM8ConnectionLifecycleResponse {
239
+ ok: true;
240
+ }
170
241
 
171
242
  /** Public OAuth authorize endpoint (fixed host). */
172
243
  declare const AXE_SERVICEM8_OAUTH_AUTHORIZE_URL = "https://go.servicem8.com/oauth/authorize";
@@ -175,10 +246,10 @@ declare const AXE_SERVICEM8_OAUTH_TOKEN_URL = "https://go.servicem8.com/oauth/ac
175
246
  /** REST API base URL stored on connection `instanceUrl`. */
176
247
  declare const AXE_SERVICEM8_API_BASE_URL = "https://api.servicem8.com";
177
248
  /**
178
- * OAuth scopes for connect.
179
- * `manage_jobs` is included so push sync can be added later without re-consent.
249
+ * OAuth scopes for connect / authorize.
250
+ * Space-separated; encoded as `scope=` query value by the OAuth start flow.
180
251
  */
181
- declare const AXE_SERVICEM8_OAUTH_SCOPES = "read_jobs manage_jobs vendor";
252
+ declare const AXE_SERVICEM8_OAUTH_SCOPES = "read_customers read_jobs read_staff read_job_contacts read_job_attachments publish_job_attachments read_customer_contacts";
182
253
 
183
254
  export { AXEServiceM8CommandKeys, AXEServiceM8MenuKeys, AXEServiceM8Permissions, AXE_SERVICEM8_API_BASE_URL, AXE_SERVICEM8_OAUTH_AUTHORIZE_URL, AXE_SERVICEM8_OAUTH_SCOPES, AXE_SERVICEM8_OAUTH_TOKEN_URL, AXPAdministrationPermissionKeys, RootConfig };
184
- export type { AXEServiceM8ConnectionConfig, AXEServiceM8ImportJobRequest, AXEServiceM8ImportJobResponse, AXEServiceM8JobsSyncState, AXEServiceM8OAuthCompleteRequest, AXEServiceM8OAuthCompleteResponse, AXEServiceM8OAuthStartRequest, AXEServiceM8OAuthStartResponse, AXEServiceM8OAuthValidateRequest, AXEServiceM8OAuthValidateResponse, AXEServiceM8PullJobsRequest, AXEServiceM8PullJobsResponse, AXEServiceM8RemoteJob, AXEServiceM8SyncPlatformProjectRequest };
255
+ export type { AXEServiceM8ConnectionConfig, AXEServiceM8ConnectionLifecycleRequest, AXEServiceM8ConnectionLifecycleResponse, AXEServiceM8ImportCustomerRequest, AXEServiceM8ImportCustomerResponse, AXEServiceM8ImportJobRequest, AXEServiceM8ImportJobResponse, AXEServiceM8JobsSyncState, AXEServiceM8OAuthCompleteRequest, AXEServiceM8OAuthCompleteResponse, AXEServiceM8OAuthStartRequest, AXEServiceM8OAuthStartResponse, AXEServiceM8OAuthValidateRequest, AXEServiceM8OAuthValidateResponse, AXEServiceM8PullJobsRequest, AXEServiceM8PullJobsResponse, AXEServiceM8RemoteCustomer, AXEServiceM8RemoteJob, AXEServiceM8RemoteSite, AXEServiceM8SyncPlatformProjectRequest };
@@ -1 +0,0 @@
1
- {"version":3,"file":"acorex-platform-extension-project-management-servicem8-client-connect-servicem8.command-CkDOwDrg.mjs","sources":["../../../../libs/extensions/project-management/servicem8/client/src/lib/features/connect/servicem8-oauth.util.ts","../../../../libs/extensions/project-management/servicem8/client/src/lib/features/connect/connect-servicem8.command.ts"],"sourcesContent":["//#region ---- ServiceM8 OAuth helpers (client) ----\n\n/** Resolves the demo app path segment used in the OAuth callback URL. */\nexport function resolveServiceM8OAuthAppName(applicationName: string | null | undefined): string {\n const trimmed = String(applicationName ?? '').trim();\n return trimmed || 'demo';\n}\n\n//#endregion\n","//#region ---- Connect ServiceM8 (OAuth authorization code) ----\n\nimport { AXPCommand, type AXPCommandExecuteResultPromise } from '@acorex-platform/framework-shared/runtime';\nimport {\n AXP_INTEGRATION_AUTH_METHOD_TYPE,\n AXP_INTEGRATION_CONNECTION_STATUS,\n type AXPRuntimeCommandResult,\n type AXPIntegrationConnectionScope,\n} from '@acorex-platform/framework-shared/core';\nimport { AXPSessionService } from '@acorex-platform/framework-client/auth';\nimport {\n AXMIntegrationConnectionManager,\n AXP_INTEGRATION_CREDENTIAL_MASK,\n hasConfiguredIntegrationCredentials,\n isMaskedIntegrationCredential,\n toSafeIntegrationConnectionDto,\n type ConnectCommandResult,\n} from '@acorex-platform/module-integration-management-client';\nimport {\n AXPLayoutBuilderService,\n resolveLayoutBuilderDialogResult,\n type AXPDialogRef,\n type IActionBuilder,\n type IDialogBuilder,\n type IFlexContainerBuilder,\n type IFormFieldBuilder,\n} from '@acorex-platform/framework-client/layout/builder';\nimport { AXTranslationService } from '@acorex/core/translation';\nimport { AXEServiceM8OAuthHttpClient } from '../../api/servicem8-oauth-http.client';\nimport { Injectable, inject } from '@angular/core';\nimport { defaultTo, isEmpty, trim } from 'lodash-es';\nimport {\n AXE_SERVICEM8_API_BASE_URL,\n AXE_SERVICEM8_OAUTH_SCOPES,\n type AXEServiceM8ConnectionConfig,\n} from '@acorex-platform/extension-project-management-servicem8-client/contracts';\nimport type { AXMIntegrationManagementIntegrationConnectionEntityModel } from '@acorex-platform/module-integration-management-client/contracts';\nimport { resolveServiceM8OAuthAppName } from './servicem8-oauth.util';\n\nexport interface ConnectServiceM8CommandInput {\n providerId?: string;\n connectionScope?: AXPIntegrationConnectionScope;\n /** When set, reconfigure / reconnect this existing connection. */\n connectionId?: string;\n /** Optional explicit key; new connections get a generated key when omitted. */\n connectionKey?: string;\n}\n\ninterface AXEServiceM8ConnectFormContext {\n displayName: string;\n clientId: string;\n clientSecret: string;\n}\n\ninterface AXEServiceM8ConnectFormResult {\n displayName: string;\n clientId: string;\n clientSecret: string;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class ConnectServiceM8Command implements AXPCommand<ConnectServiceM8CommandInput, ConnectCommandResult> {\n private readonly connectionManager = inject(AXMIntegrationConnectionManager);\n private readonly layoutBuilder = inject(AXPLayoutBuilderService);\n private readonly translationService = inject(AXTranslationService);\n private readonly sessionService = inject(AXPSessionService);\n private readonly servicem8Http = inject(AXEServiceM8OAuthHttpClient);\n\n async execute(input: ConnectServiceM8CommandInput = {}): AXPCommandExecuteResultPromise<ConnectCommandResult> {\n try {\n const providerId = input.providerId ?? 'servicem8';\n const connectionScope = input.connectionScope ?? 'tenant';\n const existing = await this.resolveExistingConnection(providerId, connectionScope, input);\n const isReconfigure = !!existing;\n const existingConfig = (existing?.config ?? {}) as AXEServiceM8ConnectionConfig;\n const connectionKey =\n existing?.connectionKey\n ?? (trim(input.connectionKey) || crypto.randomUUID());\n\n const title = await this.translationService.translateAsync(\n isReconfigure\n ? '@extensions-servicem8:integration.connect.dialog.reconfigure-title'\n : '@extensions-servicem8:integration.connect.dialog.title',\n );\n\n const form = await this.openConnectDialog({\n title: title ?? 'Connect ServiceM8',\n context: {\n displayName: existing?.displayName ?? 'ServiceM8',\n clientId: existingConfig.clientId ?? '',\n clientSecret: isReconfigure ? AXP_INTEGRATION_CREDENTIAL_MASK : '',\n },\n existing,\n existingConfig,\n isReconfigure,\n });\n\n if (!form) {\n return { success: true, kind: 'object', data: { outcome: 'cancelled' } };\n }\n\n const credentialsChanged = this.credentialsChanged(existingConfig, form);\n const needsAuthorization = this.needsOAuthAuthorization(existingConfig, credentialsChanged);\n\n const config: AXEServiceM8ConnectionConfig = {\n clientId: form.clientId,\n clientSecret: form.clientSecret,\n scope: AXE_SERVICEM8_OAUTH_SCOPES,\n ...(credentialsChanged\n ? {}\n : {\n accessToken: existingConfig.accessToken,\n refreshToken: existingConfig.refreshToken,\n tokenExpiresAt: existingConfig.tokenExpiresAt,\n oauthTokenJson: existingConfig.oauthTokenJson,\n }),\n };\n\n const connection = await this.connectionManager.upsertConnection({\n providerId,\n authMethodType: AXP_INTEGRATION_AUTH_METHOD_TYPE.OAuth2,\n connectionScope,\n connectionKey,\n displayName: defaultTo(trim(form.displayName), 'ServiceM8'),\n instanceUrl: AXE_SERVICEM8_API_BASE_URL,\n externalAccountName: AXP_INTEGRATION_CREDENTIAL_MASK,\n config: config as Record<string, unknown>,\n status: needsAuthorization\n ? AXP_INTEGRATION_CONNECTION_STATUS.PendingAuthorization\n : AXP_INTEGRATION_CONNECTION_STATUS.Connected,\n isRevoked: false,\n isDisabled: false,\n });\n\n if (needsAuthorization) {\n const appName = resolveServiceM8OAuthAppName(this.sessionService.application?.name);\n const redirectUri = `${window.location.origin}/${appName}/integrations/oauth/callback/${providerId}`;\n const oauth = await this.servicem8Http.startOAuth({\n redirectUri,\n connectionId: connection.id,\n });\n return { success: true, kind: 'object',\n data: { outcome: 'redirect', redirectUrl: oauth.redirectUrl },\n };\n }\n\n const validation = await this.servicem8Http.validateOAuth({ connectionId: connection.id });\n if (!validation.valid) {\n return {\n success: false,\n message: { text: validation.message ?? 'ServiceM8 connection validation failed. Re-authorize ServiceM8.' },\n };\n }\n\n const refreshed = await this.connectionManager.getByIdForCurrentUser(connection.id);\n return { success: true, kind: 'object',\n data: {\n outcome: 'connected',\n connection: toSafeIntegrationConnectionDto(refreshed ?? connection),\n },\n };\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : 'Failed to connect ServiceM8.';\n return { success: false, message: { text: message } };\n }\n }\n\n private async resolveExistingConnection(\n providerId: string,\n connectionScope: AXPIntegrationConnectionScope,\n input: ConnectServiceM8CommandInput,\n ): Promise<AXMIntegrationManagementIntegrationConnectionEntityModel | null> {\n if (input.connectionId) {\n const byId = await this.connectionManager.getByIdForCurrentUser(input.connectionId);\n if (!byId) {\n throw new Error('Connection not found or access denied.');\n }\n return byId;\n }\n\n const connectionKey = trim(input.connectionKey);\n if (!connectionKey) {\n return null;\n }\n\n return this.connectionManager.findByUniqueKey({\n providerId,\n connectionScope,\n connectionKey,\n });\n }\n\n private credentialsChanged(\n existingConfig: AXEServiceM8ConnectionConfig,\n form: AXEServiceM8ConnectFormResult,\n ): boolean {\n if (trim(form.clientId) !== trim(existingConfig.clientId ?? '')) {\n return true;\n }\n if (!isMaskedIntegrationCredential(form.clientSecret)) {\n return true;\n }\n return false;\n }\n\n private needsOAuthAuthorization(\n existingConfig: AXEServiceM8ConnectionConfig,\n credentialsChanged: boolean,\n ): boolean {\n if (credentialsChanged) {\n return true;\n }\n return isEmpty(trim(existingConfig.refreshToken ?? ''));\n }\n\n private async openConnectDialog(input: {\n title: string;\n context: AXEServiceM8ConnectFormContext;\n existing: AXMIntegrationManagementIntegrationConnectionEntityModel | null;\n existingConfig: AXEServiceM8ConnectionConfig;\n isReconfigure: boolean;\n }): Promise<AXEServiceM8ConnectFormResult | null> {\n const result: unknown = await this.layoutBuilder\n .create()\n .dialog<AXEServiceM8ConnectFormResult | null>((dialog: IDialogBuilder<AXEServiceM8ConnectFormResult | null>) => {\n dialog\n .setTitle(input.title)\n .setSize('md')\n .setCloseButton(true)\n .setContext({ ...input.context })\n .content((flex: IFlexContainerBuilder) => {\n flex\n .setDirection('column')\n .setGap('12px')\n .customWidget('text-block-layout', {\n content: '@extensions-servicem8:integration.connect.instructions',\n })\n .formField('@extensions-servicem8:integration.connect.fields.display-name', (field: IFormFieldBuilder) => {\n field.path('displayName');\n field.textBox({\n placeholder: '@extensions-servicem8:integration.connect.placeholders.display-name',\n });\n })\n .formField('@extensions-servicem8:integration.auth.oauth.client-id.label', (field) => {\n field.path('clientId');\n field.textBox({\n placeholder: '@extensions-servicem8:integration.auth.oauth.client-id.placeholder',\n validations: [{ rule: 'required' }],\n });\n })\n .formField('@extensions-servicem8:integration.auth.oauth.client-secret.label', (field) => {\n field.path('clientSecret');\n field.passwordBox({\n placeholder: '@extensions-servicem8:integration.auth.oauth.client-secret.placeholder',\n revealToggle: true,\n validations: input.isReconfigure ? [] : [{ rule: 'required' }],\n });\n });\n })\n .setActions((actions: IActionBuilder) => {\n actions\n .cancel('@general:actions.cancel.title')\n .submit(\n input.isReconfigure && hasConfiguredIntegrationCredentials(input.existing)\n ? '@extensions-servicem8:integration.connect.actions.save'\n : '@extensions-servicem8:integration.connect.actions.save-and-authorize',\n );\n })\n .onAction(async (ref: AXPDialogRef) => {\n if (ref.action() === 'cancel') {\n return null;\n }\n\n const ctx = ref.context() as AXEServiceM8ConnectFormContext;\n const clientId = trim(ctx.clientId);\n if (!clientId) {\n throw new Error('ServiceM8 App ID is required.');\n }\n\n let clientSecret = trim(ctx.clientSecret);\n if (isMaskedIntegrationCredential(clientSecret) && input.existing) {\n clientSecret = trim(input.existingConfig.clientSecret ?? '');\n }\n if (!clientSecret) {\n throw new Error('ServiceM8 App Secret is required.');\n }\n\n return {\n displayName: trim(ctx.displayName) || 'ServiceM8',\n clientId,\n clientSecret,\n };\n });\n })\n .show();\n\n return resolveLayoutBuilderDialogResult<AXEServiceM8ConnectFormResult>(result);\n }\n}\n\n//#endregion\n"],"names":[],"mappings":"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"}