Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 85 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@ Find more documentation and related information at [SAP Field Service Management
- [Service Management API](#service-management-api)
- [Activity Business Actions](#activity-business-actions)
- [Activity Bulk Business Actions](#activity-bulk-business-actions)
- [Query for objects using CoreSQL](#query-for-objects-using-coresql)
- [CRUD object](#crud-object)
- [Create a new object](#create-a-new-object)
- [Read object by id](#read-object-by-id)
- [Update object (providing full new version)](#update-object-providing-full-new-version)
- [Update object (providing only fields to change)](#update-object-providing-only-fields-to-change)
- [Delete object](#delete-object)
- [lastChanged](#lastchanged)
- [Batch Actions (Transactions)](#batch-actions-transactions)
- [Translation API](#translation-api)
- [Rules API](#rules-api)
- [Optimization API](#optimization-api)
- [Legacy Data Cloud API (Deprecated)](#legacy-data-cloud-api-deprecated)
- [Query for objects using CoreSQL](#query-for-objects-using-coresql)
- [CRUD object](#crud-object)
- [Create a new object](#create-a-new-object)
- [Read object by id](#read-object-by-id)
- [Update object (providing full new version)](#update-object-providing-full-new-version)
- [Update object (providing only fields to change)](#update-object-providing-only-fields-to-change)
- [Delete object](#delete-object)
- [lastChanged](#lastchanged)
- [Batch Actions (Transactions)](#batch-actions-transactions)
- [Support](#support)
- [License](#license)

Expand Down Expand Up @@ -303,6 +307,78 @@ const executionRecords = await client.rulesAPI.getRuleExecutionRecords('rule-id'
});
```

### Optimization API

The Optimization API provides automated scheduling and technician assignment operations:

```typescript
// List available optimization plugins (policies)
const plugins = await client.optimizationAPI.plugins.list();

// Find best matching technicians for an activity
const technicians = await client.optimizationAPI.bestMatchingTechnicians.find('activity-id', {
policy: 'DistanceAndSkills',
start: '2025-01-15T08:00:00Z',
end: '2025-01-15T18:00:00Z',
schedulingOptions: {
maxResults: 10
}
});

// Search for available time slots
const slots = await client.optimizationAPI.jobSlots.search({
activityId: 'activity-id',
slots: [
{ start: '2025-01-15T08:00:00Z', end: '2025-01-15T10:00:00Z' }
],
resources: {
personIds: ['person-1', 'person-2']
},
options: {
maxResults: 10,
defaultDrivingTimeMinutes: 30
},
policy: 'DistanceAndSkills'
});

// Execute re-optimization
const result = await client.optimizationAPI.reOptimization.execute({
activityIds: ['activity-1', 'activity-2'],
optimizationPlugin: 'DistanceAndSkills',
start: '2025-01-15T06:00:00Z',
end: '2025-01-15T18:00:00Z',
simulation: false,
resources: {
includeNonSchedulable: false
}
});

// Get optimization task status
const task = await client.optimizationAPI.tasks.get('task-id');

// Cancel an optimization task
await client.optimizationAPI.tasks.cancel('task-id', {
reason: 'User requested cancellation'
});

// List optimization executions
const executions = await client.optimizationAPI.executions.list({
filter: {
status: 'SUCCESS',
policyName: 'DistanceAndSkills'
},
page: 0,
size: 20
});

// List processed jobs from optimization
const processedJobs = await client.optimizationAPI.processedJobs.list({
taskId: 'task-id',
page: 0,
size: 50
});
```

### Legacy Data Cloud API (Deprecated)

> ⚠️ **Note**: The Data Service API (Data Cloud) is deprecated. For service calls and activities, use the Service Management API instead.
Expand Down
12 changes: 12 additions & 0 deletions src/core-api.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { TranslationApiService } from './core/translations/translations-api.serv
import { ServiceManagementAPIService } from './core/service-management/service-management.service';
import { DataServiceAPIService } from './core/data-service/data-service.service';
import { RulesAPIService } from './core/rules/rules-api.service';
import { OptimizationAPIService } from './core/optimization/optimization-api.service';


export class CoreAPIClient {
Expand All @@ -18,6 +19,7 @@ export class CoreAPIClient {
private _accountApi: AccountAPIService;
private _translationApi: TranslationApiService;
private _rulesApi: RulesAPIService;
private _optimizationApi: OptimizationAPIService;

private _dataServiceAPI: DataServiceAPIService

Expand Down Expand Up @@ -91,6 +93,7 @@ export class CoreAPIClient {
this._translationApi = new TranslationApiService(this._config, _http, this._auth);
this._rulesApi = new RulesAPIService(this._config, _http, this._auth);
this._serviceManagementApi = new ServiceManagementAPIService(this._config, _http, this._auth);
this._optimizationApi = new OptimizationAPIService(this._config, _http, this._auth);
}

/**
Expand Down Expand Up @@ -149,6 +152,15 @@ export class CoreAPIClient {
return this._rulesApi;
}

/**
* Provides access to the Optimization API for automated scheduling and technician assignment.
*
* @returns {OptimizationAPIService} Optimization API instance for optimization operations.
*/
public get optimizationAPI(): OptimizationAPIService {
return this._optimizationApi;
}

/**
* Executes a login using the current client configuration and retrieves an OAuth token.
*
Expand Down
63 changes: 63 additions & 0 deletions src/core/optimization/best-matching-technicians-api.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { URLSearchParams } from '../../polyfills';
import { ClientConfig } from '../client-config.model';
import { HttpService } from '../http/http-service';
import { OAuthService } from '../oauth/oauth.service';
import { RequestOptionsFactory } from '../request-options.factory';
import { BestMatchingTechniciansRequest, BestMatchingTechniciansResponse } from './optimization.model';

/**
* Service for best matching technicians operations.
* Provides ranked technician recommendations for activities.
*/
export class BestMatchingTechniciansAPI {

constructor(
private _config: Readonly<ClientConfig>,
private _http: Readonly<HttpService>,
private _auth: Readonly<OAuthService>
) { }

/**
* Constructs the API URL for best matching technicians operations.
*
* @param {string} path - Path to append to the base URL.
* @returns {string} The complete API URL.
*/
private getApiUrl(jobId: string, path: string = ''): string {
return `${this._config.baseUrl}/optimization/api/v2/jobs/${jobId}/best-matching-technicians${path ? '/' + path : ''}`;
}

/**
* Retrieves a ranked list of best matching technicians for a given job (activity or service call).
*
* The Best Matching Technician endpoint enables customers to automatically find the best matching
* technician for an activity. The process is policy based and creates additional value for the customer
* through the possibility of customized policy that fit specific business needs.
*
* @param {string} jobId - Activity ID or Service Call ID.
* @param {BestMatchingTechniciansRequest} request - Request with policy and optional constraints.
* @returns {Promise<BestMatchingTechniciansResponse | null>} Promise resolving to ranked technicians.
*
* @example
* ```typescript
* const technicians = await client.optimizationAPI.bestMatchingTechnicians.find('ACTIVITY_ID', {
* policy: 'DistanceAndSkills',
* start: '2030-08-12T08:00:00Z',
* end: '2030-08-12T18:00:00Z',
* schedulingOptions: {
* maxResults: 10
* }
* });
* ```
*/
public async find(jobId: string, request: BestMatchingTechniciansRequest): Promise<BestMatchingTechniciansResponse | null> {
const token = await this._auth.ensureToken(this._config);

return this._http.request<BestMatchingTechniciansResponse>(this.getApiUrl(jobId), {
method: 'POST',
headers: RequestOptionsFactory.getRequestHeaders(token, this._config),
body: JSON.stringify(request)
});
}

}
82 changes: 82 additions & 0 deletions src/core/optimization/executions-api.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { URLSearchParams } from '../../polyfills';
import { ClientConfig } from '../client-config.model';
import { HttpService } from '../http/http-service';
import { OAuthService } from '../oauth/oauth.service';
import { RequestOptionsFactory } from '../request-options.factory';
import { ExecutionsFilter, ExecutionsListResponse } from './optimization.model';

/**
* Service for optimization execution operations.
* Provides access to execution history and status.
*/
export class ExecutionsAPI {

constructor(
private _config: Readonly<ClientConfig>,
private _http: Readonly<HttpService>,
private _auth: Readonly<OAuthService>
) { }

/**
* Constructs the API URL for optimization executions operations.
*
* @param {string} path - Path to append to the base URL.
* @returns {string} The complete API URL.
*/
private getApiUrl(path: string = ''): string {
return `${this._config.baseUrl}/optimization/api/v1/executions${path ? '/' + path : ''}`;
}

/**
* Retrieves a list of optimization executions with optional filtering.
*
* Provides a ranked list of Executions with support for filtering by task, policy, status, and time range.
*
* @param {object} params - Optional query parameters for filtering and pagination.
* @param {ExecutionsFilter} params.filter - Filter criteria for executions.
* @param {string} params.search - Search term to filter the executions.
* @param {number} params.page - Page number for pagination.
* @param {number} params.size - Page size for pagination.
* @param {string} params.sort - Sort field and order (e.g., 'enqueuedAt,desc').
* @returns {Promise<ExecutionsListResponse | null>} Promise resolving to paginated executions.
*
* @example
* ```typescript
* const executions = await client.optimizationAPI.executions.list({
* filter: {
* status: 'SUCCESS',
* policyName: 'DistanceAndSkills'
* },
* page: 0,
* size: 20
* });
* ```
*/
public async list(params?: Partial<{
filter: ExecutionsFilter;
search: string;
page: number;
size: number;
sort: string;
}>): Promise<ExecutionsListResponse | null> {
const token = await this._auth.ensureToken(this._config);

// Convert nested filter object to flat query params
const queryParams: any = {};
if (params?.filter) {
Object.assign(queryParams, params.filter);
}
if (params?.search) queryParams.search = params.search;
if (params?.page !== undefined) queryParams.page = params.page;
if (params?.size !== undefined) queryParams.size = params.size;
if (params?.sort) queryParams.sort = params.sort;

const queryString = Object.keys(queryParams).length ? `?${new URLSearchParams(queryParams as any)}` : '';

return this._http.request<ExecutionsListResponse>(this.getApiUrl() + queryString, {
method: 'GET',
headers: RequestOptionsFactory.getRequestHeaders(token, this._config)
});
}

}
68 changes: 68 additions & 0 deletions src/core/optimization/job-slots-api.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { URLSearchParams } from '../../polyfills';
import { ClientConfig } from '../client-config.model';
import { HttpService } from '../http/http-service';
import { OAuthService } from '../oauth/oauth.service';
import { RequestOptionsFactory } from '../request-options.factory';
import { JobSlotsSearchRequest, JobSlotsSearchResponse } from './optimization.model';

/**
* Service for job slot search operations.
* Provides slot finding for booking and scheduling.
*/
export class JobSlotsAPI {

constructor(
private _config: Readonly<ClientConfig>,
private _http: Readonly<HttpService>,
private _auth: Readonly<OAuthService>
) { }

/**
* Constructs the API URL for job slots operations.
*
* @param {string} path - Path to append to the base URL.
* @returns {string} The complete API URL.
*/
private getApiUrl(path: string = ''): string {
return `${this._config.baseUrl}/optimization/api/v3/job-slots/actions${path ? '/' + path : ''}`;
}

/**
* Searches for free slots to perform/book a job.
*
* This endpoint can be used to search for a free slot to perform/book a job.
* Slots may then be used for booking a concrete appointment. This API does NOT
* auto-reserve, book or block slots in any direct way, results are computed on real time data.
*
* @param {JobSlotsSearchRequest} request - The slot search request with job details and constraints.
* @returns {Promise<JobSlotsSearchResponse | null>} Promise resolving to available slots.
*
* @example
* ```typescript
* const slots = await client.optimizationAPI.jobSlots.search({
* activityId: 'ACTIVITY_ID',
* slots: [
* { start: '2030-08-12T08:00:00Z', end: '2030-08-12T10:00:00Z' }
* ],
* resources: {
* personIds: ['PERSON_ID_1', 'PERSON_ID_2']
* },
* options: {
* maxResults: 10,
* defaultDrivingTimeMinutes: 30
* },
* policy: 'DistanceAndSkills'
* });
* ```
*/
public async search(request: JobSlotsSearchRequest): Promise<JobSlotsSearchResponse | null> {
const token = await this._auth.ensureToken(this._config);

return this._http.request<JobSlotsSearchResponse>(this.getApiUrl('search'), {
method: 'POST',
headers: RequestOptionsFactory.getRequestHeaders(token, this._config),
body: JSON.stringify(request)
});
}

}
Loading
Loading