chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,95 @@
import type { ApproveExpenseReportParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const approveExpenseReportTool: ToolConfig<
ApproveExpenseReportParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_approve_expense_report',
name: 'SAP Concur Approve Expense Report',
description:
'Approve an expense report as a manager (PATCH /expensereports/v4/reports/{reportId}/approve). Required body field: comment.',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID to approve',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Request body — `comment` is required by Concur (e.g., { "comment": "Approved" }). If the report contains rejected expenses, `expenseRejectedComment` is also required. Optional fields: `expectedStepCode`, `expectedStepSequence`, `statusId` (defaults to "A_APPR").',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const reportId = trimRequired(params.reportId, 'reportId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/reports/${encodeURIComponent(reportId)}/approve`,
method: 'PATCH',
body: params.body,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: { type: 'json', description: 'Empty (204 No Content)' },
},
}
@@ -0,0 +1,123 @@
import type { AssociateAttendeesParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const associateAttendeesTool: ToolConfig<AssociateAttendeesParams, SapConcurProxyResponse> =
{
id: 'sap_concur_associate_attendees',
name: 'SAP Concur Associate Attendees',
description:
'Associate attendees with an expense (POST /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/expenses/{expenseId}/attendees).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: TRAVELER or PROXY',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
expenseId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense ID',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Attendee associations payload (e.g., { "attendeeAssociations": [...] })',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
const expenseId = trimRequired(params.expenseId, 'expenseId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}/expenses/${encodeURIComponent(expenseId)}/attendees`,
method: 'POST',
body: params.body,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Concur association response (201 Created with URI)',
properties: {
uri: {
type: 'string',
description: 'Resource URI of the attendee associations collection',
optional: true,
},
},
},
},
}
@@ -0,0 +1,90 @@
import type { CreateCashAdvanceParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const createCashAdvanceTool: ToolConfig<CreateCashAdvanceParams, SapConcurProxyResponse> = {
id: 'sap_concur_create_cash_advance',
name: 'SAP Concur Create Cash Advance',
description: 'Create a cash advance (POST /cashadvance/v4.1/cashadvances).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Cash advance payload',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
...baseProxyBody(params),
path: `/cashadvance/v4.1/cashadvances`,
method: 'POST',
body: params.body,
}),
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Created cash advance payload',
properties: {
cashAdvanceId: {
type: 'string',
description: 'Unique identifier of the created cash advance',
optional: true,
},
},
},
},
}
@@ -0,0 +1,179 @@
import type { CreateExpectedExpenseParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const createExpectedExpenseTool: ToolConfig<
CreateExpectedExpenseParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_create_expected_expense',
name: 'SAP Concur Create Expected Expense',
description:
'Create an expected expense on a travel request (POST /travelrequest/v4/requests/{requestUuid}/expenses).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
requestUuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Travel request UUID',
},
userId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'User UUID acting on the request (required when using a Company JWT, optional otherwise)',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Expected expense payload',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const requestUuid = trimRequired(params.requestUuid, 'requestUuid')
const query: Record<string, string> = {}
if (params.userId?.trim()) query.userId = params.userId.trim()
return {
...baseProxyBody(params),
path: `/travelrequest/v4/requests/${encodeURIComponent(requestUuid)}/expenses`,
method: 'POST',
body: params.body,
...(Object.keys(query).length > 0 ? { query } : {}),
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Created expected expense payload',
properties: {
id: { type: 'string', description: 'Expected expense identifier', optional: true },
href: { type: 'string', description: 'Self-link to the resource', optional: true },
expenseType: {
type: 'json',
description: 'Expense type {id, name}',
optional: true,
},
transactionDate: {
type: 'string',
description: 'Transaction date',
optional: true,
},
transactionAmount: {
type: 'json',
description: 'Transaction amount {value, currencyCode}',
optional: true,
},
postedAmount: {
type: 'json',
description: 'Posted amount {value, currencyCode}',
optional: true,
},
approvedAmount: {
type: 'json',
description: 'Approved amount {value, currencyCode}',
optional: true,
},
remainingAmount: {
type: 'json',
description: 'Remaining amount on the expected expense',
optional: true,
},
businessPurpose: {
type: 'string',
description: 'Business purpose of the expense',
optional: true,
},
location: {
type: 'json',
description:
'Location {id, name, city, countryCode, countrySubDivisionCode, iataCode, locationType}',
optional: true,
},
exchangeRate: {
type: 'json',
description: 'Exchange rate {value, operation}',
optional: true,
},
allocations: {
type: 'json',
description:
'Budget allocations array (allocationId, allocationAmount, approvedAmount, postedAmount, expenseId, percentEdited, systemAllocation, percentage)',
optional: true,
},
tripData: {
type: 'json',
description:
'Trip data {agencyBooked, selfBooked, tripType (ONE_WAY|ROUND_TRIP), legs[{id, returnLeg, startDate, startTime, startLocationDetail, startLocation, endLocation, class {code,value}, travelExceptionReasonCodes}], segmentType {category, code}}',
optional: true,
},
parentRequest: {
type: 'json',
description: 'Parent travel request resource link {href, id}',
optional: true,
},
comments: {
type: 'json',
description: 'Comments sub-resource link {href, id}',
optional: true,
},
},
},
},
}
@@ -0,0 +1,109 @@
import type { CreateExpenseReportParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const createExpenseReportTool: ToolConfig<
CreateExpenseReportParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_create_expense_report',
name: 'SAP Concur Create Expense Report',
description:
'Create an expense report (POST /expensereports/v4/users/{userId}/context/{contextType}/reports — supported contexts: TRAVELER, PROXY). Required body fields: name, policyId.',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID who will own the report',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Access context: TRAVELER (creating own report) or PROXY (creating on behalf of another user)',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Report payload — `name` and `policyId` are required. Optional fields: businessPurpose, comment, customData, countryCode, countrySubDivisionCode, etc.',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports`,
method: 'POST',
body: params.body,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Created expense report (Concur returns 201 with a URI to the new report)',
properties: {
uri: { type: 'string', description: 'URI of the newly created expense report' },
},
},
},
}
@@ -0,0 +1,121 @@
import type { CreateListItemParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const createListItemTool: ToolConfig<CreateListItemParams, SapConcurProxyResponse> = {
id: 'sap_concur_create_list_item',
name: 'SAP Concur Create List Item',
description: 'Create a list item (POST /list/v4/items).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'List item payload. Required: listId, shortCode, value. Optional: parentId or parentCode (mutually exclusive). Note: Concur rejects shortCode/value containing hyphens.',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
...baseProxyBody(params),
path: '/list/v4/items',
method: 'POST',
body: params.body,
}),
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Created list item',
properties: {
id: { type: 'string', description: 'List item UUID', optional: true },
code: { type: 'string', description: 'Long code format for the item', optional: true },
shortCode: { type: 'string', description: 'Short code identifier', optional: true },
value: { type: 'string', description: 'Display value of the item', optional: true },
parentId: {
type: 'string',
description: 'Parent item UUID (omitted for first-level items)',
optional: true,
},
level: {
type: 'number',
description: 'Hierarchy level (1 for root items)',
optional: true,
},
isDeleted: {
type: 'boolean',
description: 'Deletion status across all containing lists',
optional: true,
},
lists: {
type: 'array',
description: 'Lists containing this item',
optional: true,
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'List UUID', optional: true },
hasChildren: {
type: 'boolean',
description: 'Whether this item has children in the list',
optional: true,
},
},
},
},
},
},
},
}
@@ -0,0 +1,115 @@
import type { CreatePurchaseRequestParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const createPurchaseRequestTool: ToolConfig<
CreatePurchaseRequestParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_create_purchase_request',
name: 'SAP Concur Create Purchase Request',
description: 'Create a purchase request (POST /purchaserequest/v4/purchaserequests).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Purchase request payload',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
...baseProxyBody(params),
path: `/purchaserequest/v4/purchaserequests`,
method: 'POST',
body: params.body,
}),
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Created purchase request payload',
properties: {
id: {
type: 'string',
description: 'Identifier of the created purchase request',
optional: true,
},
uri: {
type: 'string',
description: 'Resource URI for the created purchase request',
optional: true,
},
errors: {
type: 'array',
description: 'Validation or processing errors returned by Concur',
optional: true,
items: {
type: 'json',
properties: {
errorCode: { type: 'string', description: 'Error code', optional: true },
errorMessage: { type: 'string', description: 'Error message', optional: true },
dataPath: {
type: 'string',
description: 'Path to the request data which has the error',
optional: true,
},
},
},
},
},
},
},
}
@@ -0,0 +1,110 @@
import type { CreateQuickExpenseParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const createQuickExpenseTool: ToolConfig<CreateQuickExpenseParams, SapConcurProxyResponse> =
{
id: 'sap_concur_create_quick_expense',
name: 'SAP Concur Create Quick Expense',
description:
'Create a quick expense (POST /quickexpense/v4/users/{userId}/context/TRAVELER/quickexpenses).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID who owns the quick expense',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: must be TRAVELER',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Quick expense payload (expenseTypeId, transactionAmount, transactionDate, etc.)',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
return {
...baseProxyBody(params),
path: `/quickexpense/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/quickexpenses`,
method: 'POST',
body: params.body,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Created quick expense response (HTTP 201 Created)',
properties: {
quickExpenseIdUri: {
type: 'string',
description: 'URI of the created quick expense resource',
optional: true,
},
},
},
},
}
@@ -0,0 +1,123 @@
import type {
CreateQuickExpenseWithImageParams,
SapConcurProxyResponse,
} from '@/tools/sap_concur/types'
import { SAP_CONCUR_UPLOAD_URL } from '@/tools/sap_concur/upload_receipt_image'
import {
baseProxyBody,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const createQuickExpenseWithImageTool: ToolConfig<
CreateQuickExpenseWithImageParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_create_quick_expense_with_image',
name: 'SAP Concur Create Quick Expense With Image',
description:
'Create a quick expense with an attached image (POST /quickexpense/v4/users/{userId}/context/{contextType}/quickexpenses/image).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: must be TRAVELER',
},
receipt: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Receipt image (UserFile). Allowed: PDF, PNG, JPEG, TIFF (max 50MB)',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Quick expense payload (transactionAmount, transactionDate, expenseTypeId, vendor, ...)',
},
},
request: {
url: SAP_CONCUR_UPLOAD_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
return {
...baseProxyBody(params),
operation: 'create_quick_expense_with_image',
userId,
contextType,
receipt: params.receipt,
body: params.body,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Created quick expense response (HTTP 201 with attached receipt image)',
properties: {
quickExpenseIdUri: {
type: 'string',
description: 'URI of the created quick expense resource',
optional: true,
},
},
},
},
}
@@ -0,0 +1,119 @@
import type { CreateReportCommentParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const createReportCommentTool: ToolConfig<
CreateReportCommentParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_create_report_comment',
name: 'SAP Concur Create Report Comment',
description:
'Create a comment on a report (POST /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/comments).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: TRAVELER or PROXY',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
comment: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comment text to add',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
const comment = trimRequired(params.comment, 'comment')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}/comments`,
method: 'POST',
body: { comment },
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Created comment response (Concur returns 201 Created with URI)',
properties: {
uri: {
type: 'string',
description: 'Resource URI of the created comment',
optional: true,
},
},
},
},
}
@@ -0,0 +1,264 @@
import type { CreateTravelRequestParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const createTravelRequestTool: ToolConfig<
CreateTravelRequestParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_create_travel_request',
name: 'SAP Concur Create Travel Request',
description: 'Create a travel request (POST /travelrequest/v4/requests).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional Concur user UUID — required when impersonating another user',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Travel request payload (name, purpose, startDate, endDate, requestPolicyId, etc.)',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const query: Record<string, string> = {}
if (params.userId) query.userId = params.userId
return {
...baseProxyBody(params),
path: `/travelrequest/v4/requests`,
method: 'POST',
body: params.body,
query: Object.keys(query).length > 0 ? query : undefined,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Created travel request payload',
properties: {
id: { type: 'string', description: 'Travel request UUID', optional: true },
href: { type: 'string', description: 'Resource hyperlink', optional: true },
requestId: {
type: 'string',
description: 'Public-facing request ID (4-6 alphanumeric characters)',
optional: true,
},
name: { type: 'string', description: 'Request name', optional: true },
businessPurpose: { type: 'string', description: 'Business purpose', optional: true },
comment: { type: 'string', description: 'Last attached comment', optional: true },
creationDate: { type: 'string', description: 'Creation timestamp', optional: true },
lastModified: {
type: 'string',
description: 'Last modification timestamp',
optional: true,
},
submitDate: { type: 'string', description: 'Last submission timestamp', optional: true },
startDate: { type: 'string', description: 'Trip start date (ISO 8601)', optional: true },
endDate: { type: 'string', description: 'Trip end date (ISO 8601)', optional: true },
startTime: { type: 'string', description: 'Trip start time (HH:mm)', optional: true },
endTime: { type: 'string', description: 'Trip end time (HH:mm)', optional: true },
approved: {
type: 'boolean',
description: 'Whether the request is approved',
optional: true,
},
pendingApproval: { type: 'boolean', description: 'Pending approval flag', optional: true },
closed: { type: 'boolean', description: 'Closed flag', optional: true },
everSentBack: { type: 'boolean', description: 'Ever-sent-back flag', optional: true },
canceledPostApproval: {
type: 'boolean',
description: 'Canceled after approval flag',
optional: true,
},
approvalStatus: {
type: 'json',
description: 'Approval status',
optional: true,
properties: {
code: {
type: 'string',
description: 'Status code (NOT_SUBMITTED, SUBMITTED, APPROVED, CANCELED, SENTBACK)',
optional: true,
},
name: { type: 'string', description: 'Localized status name', optional: true },
},
},
owner: {
type: 'json',
description: 'Travel request owner',
optional: true,
properties: {
id: { type: 'string', description: 'User UUID', optional: true },
firstName: { type: 'string', description: 'Owner first name', optional: true },
lastName: { type: 'string', description: 'Owner last name', optional: true },
},
},
approver: {
type: 'json',
description: 'Approver assigned to the request',
optional: true,
properties: {
id: { type: 'string', description: 'User UUID', optional: true },
firstName: { type: 'string', description: 'Approver first name', optional: true },
lastName: { type: 'string', description: 'Approver last name', optional: true },
},
},
policy: {
type: 'json',
description: 'Resource link to the applicable policy',
optional: true,
properties: {
id: { type: 'string', description: 'Policy ID', optional: true },
href: { type: 'string', description: 'Policy hyperlink', optional: true },
},
},
type: {
type: 'json',
description: 'Request type',
optional: true,
properties: {
code: { type: 'string', description: 'Request type code', optional: true },
label: { type: 'string', description: 'Request type label', optional: true },
},
},
mainDestination: {
type: 'json',
description: 'Main destination of the trip',
optional: true,
properties: {
city: { type: 'string', description: 'City', optional: true },
countryCode: { type: 'string', description: 'ISO country code', optional: true },
countrySubDivisionCode: {
type: 'string',
description: 'ISO country sub-division code',
optional: true,
},
name: { type: 'string', description: 'Destination name', optional: true },
},
},
totalApprovedAmount: {
type: 'json',
description: 'Total approved amount',
optional: true,
properties: {
value: { type: 'number', description: 'Amount value', optional: true },
currency: { type: 'string', description: 'Currency code', optional: true },
},
},
totalPostedAmount: {
type: 'json',
description: 'Total posted amount',
optional: true,
properties: {
value: { type: 'number', description: 'Amount value', optional: true },
currency: { type: 'string', description: 'Currency code', optional: true },
},
},
totalRemainingAmount: {
type: 'json',
description: 'Total remaining amount',
optional: true,
properties: {
value: { type: 'number', description: 'Amount value', optional: true },
currency: { type: 'string', description: 'Currency code', optional: true },
},
},
operations: {
type: 'array',
description: 'Available workflow actions',
optional: true,
items: {
type: 'json',
properties: {
rel: { type: 'string', description: 'Operation name', optional: true },
href: { type: 'string', description: 'Operation URL', optional: true },
},
},
},
expenses: {
type: 'array',
description: 'Expected expenses attached to the request',
optional: true,
items: { type: 'json' },
},
highestExceptionLevel: {
type: 'string',
description: 'Highest exception level (NONE, WARNING, ERROR)',
optional: true,
},
travelAgency: {
type: 'json',
description: 'Travel agency reference',
optional: true,
properties: {
id: { type: 'string', description: 'Agency identifier', optional: true },
href: { type: 'string', description: 'Agency URL', optional: true },
template: { type: 'string', description: 'Template URL', optional: true },
},
},
custom1: { type: 'json', description: 'Custom field 1', optional: true },
custom2: { type: 'json', description: 'Custom field 2', optional: true },
custom3: { type: 'json', description: 'Custom field 3', optional: true },
custom4: { type: 'json', description: 'Custom field 4', optional: true },
},
},
},
}
+85
View File
@@ -0,0 +1,85 @@
import type { CreateUserParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
scimUserOutputProperties,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const createUserTool: ToolConfig<CreateUserParams, SapConcurProxyResponse> = {
id: 'sap_concur_create_user',
name: 'SAP Concur Create User',
description: 'Create a new user identity (POST /profile/identity/v4.1/Users).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'SCIM User payload (schemas, userName, name, emails, active, etc.)',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
...baseProxyBody(params),
path: `/profile/identity/v4.1/Users`,
method: 'POST',
body: params.body,
}),
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Created SCIM User payload',
properties: scimUserOutputProperties,
},
},
}
@@ -0,0 +1,100 @@
import type { DeleteExpectedExpenseParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const deleteExpectedExpenseTool: ToolConfig<
DeleteExpectedExpenseParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_delete_expected_expense',
name: 'SAP Concur Delete Expected Expense',
description: 'Delete an expected expense (DELETE /travelrequest/v4/expenses/{expenseUuid}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
expenseUuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expected expense UUID to delete',
},
userId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'User UUID acting on the request (required when using a Company JWT, optional otherwise)',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const expenseUuid = trimRequired(params.expenseUuid, 'expenseUuid')
const query: Record<string, string> = {}
if (params.userId?.trim()) query.userId = params.userId.trim()
return {
...baseProxyBody(params),
path: `/travelrequest/v4/expenses/${encodeURIComponent(expenseUuid)}`,
method: 'DELETE',
...(Object.keys(query).length > 0 ? { query } : {}),
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Returns boolean true on 200 OK when the expected expense is deleted.',
properties: {},
},
},
}
@@ -0,0 +1,96 @@
import type { DeleteExpenseParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const deleteExpenseTool: ToolConfig<DeleteExpenseParams, SapConcurProxyResponse> = {
id: 'sap_concur_delete_expense',
name: 'SAP Concur Delete Expense',
description:
'Delete an expense (DELETE /expensereports/v4/reports/{reportId}/expenses/{expenseId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
expenseId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense ID to delete',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const reportId = trimRequired(params.reportId, 'reportId')
const expenseId = trimRequired(params.expenseId, 'expenseId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/reports/${encodeURIComponent(reportId)}/expenses/${encodeURIComponent(expenseId)}`,
method: 'DELETE',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description:
'Empty body on success (HTTP 204 No Content). Error details when status is non-2xx',
properties: {},
},
},
}
@@ -0,0 +1,86 @@
import type { DeleteExpenseReportParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const deleteExpenseReportTool: ToolConfig<
DeleteExpenseReportParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_delete_expense_report',
name: 'SAP Concur Delete Expense Report',
description: 'Delete an expense report (DELETE /expensereports/v4/reports/{reportId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID to delete',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const reportId = trimRequired(params.reportId, 'reportId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/reports/${encodeURIComponent(reportId)}`,
method: 'DELETE',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: { type: 'json', description: 'Empty (204 No Content)' },
},
}
@@ -0,0 +1,88 @@
import type { DeleteListItemParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const deleteListItemTool: ToolConfig<DeleteListItemParams, SapConcurProxyResponse> = {
id: 'sap_concur_delete_list_item',
name: 'SAP Concur Delete List Item',
description: 'Delete a list item (DELETE /list/v4/items/{itemId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
itemId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'List item UUID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const itemId = trimRequired(params.itemId, 'itemId')
return {
...baseProxyBody(params),
path: `/list/v4/items/${encodeURIComponent(itemId)}`,
method: 'DELETE',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description:
'Empty body on success (HTTP 204 No Content). Error details when status is non-2xx',
properties: {},
},
},
}
@@ -0,0 +1,99 @@
import type { DeleteTravelRequestParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const deleteTravelRequestTool: ToolConfig<
DeleteTravelRequestParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_delete_travel_request',
name: 'SAP Concur Delete Travel Request',
description: 'Delete a travel request (DELETE /travelrequest/v4/requests/{requestUuid}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
requestUuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Travel request UUID to delete',
},
userId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional Concur user UUID — required when impersonating another user',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const requestUuid = trimRequired(params.requestUuid, 'requestUuid')
const query: Record<string, string> = {}
if (params.userId) query.userId = params.userId
return {
...baseProxyBody(params),
path: `/travelrequest/v4/requests/${encodeURIComponent(requestUuid)}`,
method: 'DELETE',
query: Object.keys(query).length > 0 ? query : undefined,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Concur delete response payload (boolean true on 200 OK)',
properties: {},
},
},
}
+87
View File
@@ -0,0 +1,87 @@
import type { DeleteUserParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const deleteUserTool: ToolConfig<DeleteUserParams, SapConcurProxyResponse> = {
id: 'sap_concur_delete_user',
name: 'SAP Concur Delete User',
description: 'Delete a user identity (DELETE /profile/identity/v4.1/Users/{id}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userUuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'User UUID to delete',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userUuid = trimRequired(params.userUuid, 'userUuid')
return {
...baseProxyBody(params),
path: `/profile/identity/v4.1/Users/${encodeURIComponent(userUuid)}`,
method: 'DELETE',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Deletion response — empty body on HTTP 204 No Content',
properties: {},
},
},
}
+156
View File
@@ -0,0 +1,156 @@
import type { GetAllocationParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getAllocationTool: ToolConfig<GetAllocationParams, SapConcurProxyResponse> = {
id: 'sap_concur_get_allocation',
name: 'SAP Concur Get Allocation',
description:
'Get a single allocation (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/allocations/{allocationId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: TRAVELER or PROXY',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
allocationId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Allocation ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
const allocationId = trimRequired(params.allocationId, 'allocationId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}/allocations/${encodeURIComponent(allocationId)}`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Allocation detail payload',
properties: {
allocationId: { type: 'string', description: 'Unique allocation identifier' },
accountCode: { type: 'string', optional: true, description: 'Ledger account code' },
overLimitAccountCode: {
type: 'string',
optional: true,
description: 'Account code applied to amounts over the per-allocation limit',
},
percentage: { type: 'number', description: 'Allocation percentage' },
allocationAmount: {
type: 'json',
description: 'Allocation amount (value, currencyCode)',
properties: {
value: { type: 'number', description: 'Amount value' },
currencyCode: { type: 'string', description: 'ISO 4217 currency code' },
},
},
approvedAmount: {
type: 'json',
description: 'Pro-rated approved amount (value, currencyCode)',
properties: {
value: { type: 'number', description: 'Amount value' },
currencyCode: { type: 'string', description: 'ISO 4217 currency code' },
},
},
claimedAmount: {
type: 'json',
description: 'Requested reimbursement amount (value, currencyCode)',
properties: {
value: { type: 'number', description: 'Amount value' },
currencyCode: { type: 'string', description: 'ISO 4217 currency code' },
},
},
customData: {
type: 'array',
optional: true,
description: 'Custom field values (id, value, isValid)',
},
expenseId: { type: 'string', description: 'Associated expense identifier' },
isSystemAllocation: {
type: 'boolean',
description: 'True when system-managed',
},
isPercentEdited: {
type: 'boolean',
description: 'True when the percentage was manually edited',
},
},
},
},
}
+179
View File
@@ -0,0 +1,179 @@
import type { GetBudgetParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getBudgetTool: ToolConfig<GetBudgetParams, SapConcurProxyResponse> = {
id: 'sap_concur_get_budget',
name: 'SAP Concur Get Budget',
description: 'Get a budget item header by ID (GET /budget/v4/budgetItemHeader/{id}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
budgetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Budget item header ID (syncguid)',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const budgetId = trimRequired(params.budgetId, 'budgetId')
return {
...baseProxyBody(params),
path: `/budget/v4/budgetItemHeader/${encodeURIComponent(budgetId)}`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Budget header detail payload',
properties: {
id: { type: 'string', description: 'Budget item header ID' },
name: { type: 'string', description: 'Admin-facing budget name' },
description: { type: 'string', description: 'User-friendly display name' },
budgetItemStatusType: {
type: 'string',
description: 'Status: OPEN, CLOSED, or REMOVED',
},
budgetType: {
type: 'string',
optional: true,
description: 'Type: PERSONAL_USE, BUDGET, RESTRICTED, or TEAM',
},
periodType: {
type: 'string',
optional: true,
description: 'Period type: YEARLY, QUARTERLY, MONTHLY, or DATE_RANGE',
},
currencyCode: { type: 'string', optional: true, description: 'ISO 4217 currency code' },
isTest: { type: 'boolean', optional: true, description: 'Test budget flag' },
active: { type: 'boolean', optional: true, description: 'Display availability flag' },
owned: { type: 'boolean', optional: true, description: 'Caller ownership flag' },
annualBudget: { type: 'number', optional: true, description: 'Total annual budget amount' },
createdDate: { type: 'string', optional: true, description: 'UTC creation timestamp' },
lastModifiedDate: {
type: 'string',
optional: true,
description: 'UTC modification timestamp',
},
fiscalYear: {
type: 'json',
optional: true,
description: 'Fiscal year reference (id, name, startDate, endDate, status)',
},
budgetAmounts: {
type: 'json',
optional: true,
description:
'Aggregate spend amounts (pendingAmount, spendAmount, unExpensedAmount, availableAmount, adjustedBudgetAmount, consumedPercent, threshold)',
},
owner: {
type: 'json',
optional: true,
description: 'Owner user (externalUserCUUID, employeeUuid, email, employeeId, name)',
},
budgetManagers: {
type: 'array',
optional: true,
description: 'Manager user objects',
items: { type: 'json' },
},
budgetApprovers: {
type: 'array',
optional: true,
description: 'Approver user objects',
items: { type: 'json' },
},
budgetViewers: {
type: 'array',
optional: true,
description: 'Viewer user objects',
items: { type: 'json' },
},
budgetTeamMembers: {
type: 'array',
optional: true,
description: 'Team member entries (budgetPerson, startDate, endDate, active, status)',
items: { type: 'json' },
},
budgetCategory: {
type: 'json',
optional: true,
description: 'Linked category (id, name, description, statusType)',
},
costObjects: {
type: 'array',
optional: true,
description: 'Tracking field values (fieldDefinitionId, code, value, operator)',
items: { type: 'json' },
},
budgetItemDetails: {
type: 'array',
optional: true,
description:
'Per-period detail entries (id, currencyCode, amount, budgetItemDetailStatusType, fiscalPeriod, budgetAmounts)',
items: { type: 'json' },
},
dateRange: {
type: 'json',
optional: true,
description: 'Date range for DATE_RANGE budgets (startDate, endDate)',
},
},
},
},
}
@@ -0,0 +1,191 @@
import type { GetCashAdvanceParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getCashAdvanceTool: ToolConfig<GetCashAdvanceParams, SapConcurProxyResponse> = {
id: 'sap_concur_get_cash_advance',
name: 'SAP Concur Get Cash Advance',
description: 'Get a cash advance (GET /cashadvance/v4.1/cashadvances/{cashAdvanceId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
cashAdvanceId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Cash advance ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const cashAdvanceId = trimRequired(params.cashAdvanceId, 'cashAdvanceId')
return {
...baseProxyBody(params),
path: `/cashadvance/v4.1/cashadvances/${encodeURIComponent(cashAdvanceId)}`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Cash advance detail payload',
properties: {
cashAdvanceId: { type: 'string', description: 'Unique identifier of the cash advance' },
name: { type: 'string', description: 'Cash advance name', optional: true },
purpose: {
type: 'string',
description: 'Purpose for the cash advance',
optional: true,
},
comment: {
type: 'string',
description: 'Comment recorded on the cash advance',
optional: true,
},
accountCode: {
type: 'string',
description: 'Account code linked to the employee',
optional: true,
},
requestDate: {
type: 'string',
description: 'Datetime the cash advance was requested (UTC, YYYY-MM-DD hh:mm:ss)',
optional: true,
},
issuedDate: {
type: 'string',
description: 'Datetime the cash advance was issued (UTC, YYYY-MM-DD hh:mm:ss)',
optional: true,
},
lastModifiedDate: {
type: 'string',
description: 'Datetime the cash advance was last modified (UTC, YYYY-MM-DD hh:mm:ss)',
optional: true,
},
hasReceipts: {
type: 'boolean',
description: 'Whether the cash advance has receipts',
optional: true,
},
reimbursementCurrency: {
type: 'string',
description: 'Reimbursement currency (3-letter ISO 4217 currency code)',
optional: true,
},
amountRequested: {
type: 'json',
description: 'Amount requested for the cash advance',
optional: true,
properties: {
amount: { type: 'string', description: 'Requested amount value', optional: true },
currency: {
type: 'string',
description: '3-letter ISO 4217 currency code',
optional: true,
},
},
},
availableBalance: {
type: 'json',
description: 'Unsubmitted balance for the cash advance',
optional: true,
properties: {
amount: { type: 'string', description: 'Balance amount', optional: true },
currency: {
type: 'string',
description: '3-letter ISO 4217 currency code',
optional: true,
},
},
},
exchangeRate: {
type: 'json',
description: 'Exchange rate that applies to the cash advance',
optional: true,
properties: {
value: { type: 'string', description: 'Exchange rate value', optional: true },
operation: {
type: 'string',
description: 'Exchange rate operation (MULTIPLY)',
optional: true,
},
},
},
approvalStatus: {
type: 'json',
description: 'Approval status of the cash advance',
optional: true,
properties: {
code: { type: 'string', description: 'Status code', optional: true },
name: { type: 'string', description: 'Status display name', optional: true },
},
},
paymentType: {
type: 'json',
description: 'Payment type for the cash advance',
optional: true,
properties: {
paymentCode: { type: 'string', description: 'Payment type code', optional: true },
description: {
type: 'string',
description: 'Payment method description',
optional: true,
},
},
},
},
},
},
}
@@ -0,0 +1,167 @@
import type { GetExpectedExpenseParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getExpectedExpenseTool: ToolConfig<GetExpectedExpenseParams, SapConcurProxyResponse> =
{
id: 'sap_concur_get_expected_expense',
name: 'SAP Concur Get Expected Expense',
description: 'Get an expected expense (GET /travelrequest/v4/expenses/{expenseUuid}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
expenseUuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expected expense UUID',
},
userId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User UUID acting on the request (optional)',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const expenseUuid = trimRequired(params.expenseUuid, 'expenseUuid')
const query: Record<string, string> = {}
if (params.userId?.trim()) query.userId = params.userId.trim()
return {
...baseProxyBody(params),
path: `/travelrequest/v4/expenses/${encodeURIComponent(expenseUuid)}`,
method: 'GET',
...(Object.keys(query).length > 0 ? { query } : {}),
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Expected expense payload',
properties: {
id: { type: 'string', description: 'Expected expense identifier', optional: true },
href: { type: 'string', description: 'Self-link', optional: true },
expenseType: {
type: 'json',
description: 'Expense type {id, name}',
optional: true,
},
transactionDate: {
type: 'string',
description: 'Transaction date',
optional: true,
},
transactionAmount: {
type: 'json',
description: 'Transaction amount {value, currencyCode}',
optional: true,
},
postedAmount: {
type: 'json',
description: 'Posted amount {value, currencyCode}',
optional: true,
},
approvedAmount: {
type: 'json',
description: 'Approved amount {value, currencyCode}',
optional: true,
},
remainingAmount: {
type: 'json',
description: 'Remaining amount on the expected expense',
optional: true,
},
businessPurpose: {
type: 'string',
description: 'Business purpose of the expense',
optional: true,
},
location: {
type: 'json',
description:
'Location {id, name, city, countryCode, countrySubDivisionCode, iataCode, locationType}',
optional: true,
},
exchangeRate: {
type: 'json',
description: 'Exchange rate {value, operation}',
optional: true,
},
allocations: {
type: 'json',
description: 'Budget allocations array',
optional: true,
},
tripData: {
type: 'json',
description:
'Trip data {agencyBooked, selfBooked, tripType (ONE_WAY|ROUND_TRIP), legs[{id, returnLeg, startDate, startTime, startLocationDetail, startLocation, endLocation, class {code,value}, travelExceptionReasonCodes}], segmentType {category, code}}',
optional: true,
},
parentRequest: {
type: 'json',
description: 'Parent travel request resource link {href, id}',
optional: true,
},
comments: {
type: 'json',
description: 'Comments sub-resource link {href, id}',
optional: true,
},
},
},
},
}
+357
View File
@@ -0,0 +1,357 @@
import type { GetExpenseParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getExpenseTool: ToolConfig<GetExpenseParams, SapConcurProxyResponse> = {
id: 'sap_concur_get_expense',
name: 'SAP Concur Get Expense',
description:
'Get a single expense (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/expenses/{expenseId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: TRAVELER, MANAGER, or PROXY',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
expenseId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
const expenseId = trimRequired(params.expenseId, 'expenseId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}/expenses/${encodeURIComponent(expenseId)}`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Expense detail (ReportExpenseDetail) payload',
properties: {
expenseId: { type: 'string', description: 'Expense identifier', optional: true },
allocationSetId: {
type: 'string',
description: 'Identifier of the associated allocation set',
optional: true,
},
allocationState: {
type: 'string',
description: 'FULLY_ALLOCATED, NOT_ALLOCATED, or PARTIALLY_ALLOCATED',
optional: true,
},
expenseType: {
type: 'json',
description: 'Expense type {id, name, code, isDeleted}',
optional: true,
},
paymentType: {
type: 'json',
description: 'Payment type {id, name, code}',
optional: true,
},
expenseSource: {
type: 'string',
description: 'Source of the expense (CASH, CCARD, EBOOKING, etc.)',
optional: true,
},
transactionDate: {
type: 'string',
description: 'Transaction date (YYYY-MM-DD)',
optional: true,
},
budgetAccrualDate: {
type: 'string',
description: 'Budget accrual date',
optional: true,
},
transactionAmount: {
type: 'json',
description: 'Transaction amount {currencyCode, value}',
optional: true,
},
postedAmount: {
type: 'json',
description: 'Posted amount in report currency {currencyCode, value}',
optional: true,
},
claimedAmount: {
type: 'json',
description: 'Non-personal claimed amount {currencyCode, value}',
optional: true,
},
approvedAmount: {
type: 'json',
description: 'Approved amount {currencyCode, value}',
optional: true,
},
approverAdjustedAmount: {
type: 'json',
description: 'Total amount adjusted by the approver',
optional: true,
},
exchangeRate: {
type: 'json',
description: 'Exchange rate {value, operation}',
optional: true,
},
vendor: {
type: 'json',
description: 'Vendor info {id, name, description}',
optional: true,
},
location: {
type: 'json',
description: 'Location {id, name, city, countryCode, countrySubDivisionCode}',
optional: true,
},
businessPurpose: {
type: 'string',
description: 'Business purpose',
optional: true,
},
comment: {
type: 'string',
description: 'Free-form comment associated with the expense',
optional: true,
},
isExpenseBillable: {
type: 'boolean',
description: 'Billable flag',
optional: true,
},
isPersonalExpense: {
type: 'boolean',
description: 'Personal-expense flag',
optional: true,
},
isExpenseRejected: {
type: 'boolean',
description: 'Whether the expense was rejected',
optional: true,
},
isExcludedFromCashAdvanceByUser: {
type: 'boolean',
description: 'Whether the user excluded this from cash advance',
optional: true,
},
isImageRequired: {
type: 'boolean',
description: 'Whether a receipt image is required',
optional: true,
},
isPaperReceiptRequired: {
type: 'boolean',
description: 'Whether a paper receipt is required',
optional: true,
},
isPaperReceiptReceived: {
type: 'boolean',
description: 'Whether a paper receipt was received',
optional: true,
},
isAutoCreated: {
type: 'boolean',
description: 'Auto-creation indicator',
optional: true,
},
hasBlockingExceptions: {
type: 'boolean',
description: 'Whether submission-blocking exceptions exist',
optional: true,
},
hasExceptions: {
type: 'boolean',
description: 'Whether any exceptions exist',
optional: true,
},
hasMissingReceiptDeclaration: {
type: 'boolean',
description: 'Affidavit declaration status',
optional: true,
},
attendeeCount: {
type: 'number',
description: 'Number of attendees',
optional: true,
},
receiptImageId: {
type: 'string',
description: 'Identifier of the attached receipt image',
optional: true,
},
ereceiptImageId: {
type: 'string',
description: 'eReceipt image identifier',
optional: true,
},
receiptType: {
type: 'json',
description: 'Receipt {id, status}',
optional: true,
},
imageCertificationStatus: {
type: 'string',
description: 'Receipt image processing/certification status',
optional: true,
},
ticketNumber: {
type: 'string',
description: 'Associated travel ticket number',
optional: true,
},
travel: {
type: 'json',
description: 'Travel data (airline, car rental, hotel, etc.)',
optional: true,
},
travelAllowance: {
type: 'json',
description: 'Travel allowance association data',
optional: true,
},
mileage: {
type: 'json',
description: 'Mileage details (odometerStart, odometerEnd, totalDistance, ...)',
optional: true,
},
expenseTaxSummary: {
type: 'json',
description: 'Aggregated tax data for the expense',
optional: true,
},
taxRateLocation: {
type: 'string',
description: 'Tax rate location: FOREIGN, HOME, or OUT_OF_PROVINCE',
optional: true,
},
fuelTypeListItem: {
type: 'json',
description: 'Fuel type list item {id, value, isValid}',
optional: true,
},
merchantTaxId: {
type: 'string',
description: 'Merchant tax identifier',
optional: true,
},
customData: {
type: 'json',
description: 'Array of custom field values [{id, value, isValid}]',
optional: true,
},
parentExpenseId: {
type: 'string',
description: 'Identifier of the parent expense (for itemizations)',
optional: true,
},
authorizationRequestExpenseId: {
type: 'string',
description: 'Linked travel-request expected expense identifier',
optional: true,
},
jptRouteId: {
type: 'string',
description: 'Japan Public Transport route id',
optional: true,
},
invoiceId: { type: 'string', description: 'Invoice identifier', optional: true },
governmentInvoiceId: {
type: 'string',
description: 'Government invoice identifier',
optional: true,
},
lastModifiedDate: {
type: 'string',
description: 'Last modified timestamp',
optional: true,
},
expenseSourceIdentifiers: {
type: 'json',
description: 'Source reference identifiers',
optional: true,
},
links: {
type: 'json',
description: 'HATEOAS links for the expense',
optional: true,
},
},
},
},
}
@@ -0,0 +1,292 @@
import type { GetExpenseReportParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getExpenseReportTool: ToolConfig<GetExpenseReportParams, SapConcurProxyResponse> = {
id: 'sap_concur_get_expense_report',
name: 'SAP Concur Get Expense Report',
description:
'Retrieve a single expense report header by id via Expense Report v4 (/expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID who owns the report',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Access context: TRAVELER (own report), MANAGER (report under approval), PROCESSOR, or PROXY',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Concur expense report header (ReportDetails)',
properties: {
reportId: { type: 'string', description: 'Unique report identifier' },
reportNumber: { type: 'string', description: 'Report number', optional: true },
reportFormId: { type: 'string', description: 'Report form ID' },
policyId: { type: 'string', description: 'Policy ID applied to the report' },
policy: { type: 'string', description: 'Policy name' },
name: { type: 'string', description: 'Report name' },
currencyCode: { type: 'string', description: 'ISO currency code' },
currency: { type: 'string', description: 'Currency name', optional: true },
approvalStatus: { type: 'string', description: 'Approval status name' },
approvalStatusId: { type: 'string', description: 'Approval status identifier' },
paymentStatus: { type: 'string', description: 'Payment status name' },
paymentStatusId: { type: 'string', description: 'Payment status identifier' },
ledger: { type: 'string', description: 'Ledger name', optional: true },
ledgerId: { type: 'string', description: 'Ledger identifier', optional: true },
userId: { type: 'string', description: 'Owner user UUID' },
reportDate: { type: 'string', description: 'Report date (YYYY-MM-DD)' },
creationDate: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
submitDate: {
type: 'string',
description: 'Submit timestamp (ISO 8601) or null',
optional: true,
},
startDate: {
type: 'string',
description: 'Report period start (YYYY-MM-DD)',
optional: true,
},
endDate: { type: 'string', description: 'Report period end (YYYY-MM-DD)', optional: true },
approvedAmount: {
type: 'json',
description: 'Amount approved { value, currencyCode }',
optional: true,
},
claimedAmount: {
type: 'json',
description: 'Amount claimed { value, currencyCode }',
optional: true,
},
reportTotal: {
type: 'json',
description: 'Report total { value, currencyCode }',
optional: true,
},
amountDueEmployee: { type: 'json', description: 'Amount due employee', optional: true },
amountDueCompany: { type: 'json', description: 'Amount due company', optional: true },
amountDueCompanyCard: {
type: 'json',
description: 'Amount due company card',
optional: true,
},
amountCompanyPaid: { type: 'json', description: 'Amount company has paid', optional: true },
personalAmount: {
type: 'json',
description: 'Personal portion of the report',
optional: true,
},
paymentConfirmedAmount: {
type: 'json',
description: 'Confirmed payment amount',
optional: true,
},
amountNotApproved: { type: 'json', description: 'Amount not approved', optional: true },
totalAmountPaidEmployee: {
type: 'json',
description: 'Total amount paid to employee',
optional: true,
},
concurAuditStatus: { type: 'string', description: 'Concur audit status', optional: true },
isFinancialIntegrationEnabled: {
type: 'boolean',
description: 'Whether financial integration is enabled',
optional: true,
},
isSubmitted: {
type: 'boolean',
description: 'Whether the report has been submitted',
optional: true,
},
isSentBack: {
type: 'boolean',
description: 'Whether the report has been sent back',
optional: true,
},
isReopened: {
type: 'boolean',
description: 'Whether the report was reopened',
optional: true,
},
isReportEverSentBack: {
type: 'boolean',
description: 'Whether the report was ever sent back',
optional: true,
},
canRecall: {
type: 'boolean',
description: 'Whether the report can be recalled',
optional: true,
},
canAddExpense: {
type: 'boolean',
description: 'Whether expenses can be added to the report',
optional: true,
},
canReopen: {
type: 'boolean',
description: 'Whether the report can be reopened',
optional: true,
},
isReceiptImageRequired: {
type: 'boolean',
description: 'Whether receipt images are required',
optional: true,
},
isReceiptImageAvailable: {
type: 'boolean',
description: 'Whether receipt images are available',
optional: true,
},
isPaperReceiptsReceived: {
type: 'boolean',
description: 'Whether paper receipts were received',
optional: true,
},
isPendingDelegatorReview: {
type: 'boolean',
description: 'Whether pending delegator review',
optional: true,
},
isFundsAndGrantsIntegrationEligible: {
type: 'boolean',
description: 'Funds and grants eligibility',
optional: true,
},
hasReceivedCashAdvanceReturns: {
type: 'boolean',
description: 'Whether cash advance returns received',
optional: true,
},
analyticsGroupId: { type: 'string', description: 'Analytics group ID', optional: true },
hierarchyNodeId: { type: 'string', description: 'Hierarchy node ID', optional: true },
allocationFormId: { type: 'string', description: 'Allocation form ID', optional: true },
countryCode: { type: 'string', description: 'ISO country code', optional: true },
countrySubDivisionCode: {
type: 'string',
description: 'ISO country subdivision code',
optional: true,
},
country: { type: 'string', description: 'Country name', optional: true },
businessPurpose: { type: 'string', description: 'Business purpose', optional: true },
comment: {
type: 'string',
description: 'Header-level comment on the report',
optional: true,
},
reportVersion: { type: 'number', description: 'Report version number', optional: true },
reportType: { type: 'string', description: 'Report type identifier', optional: true },
cardProgramStatementPeriodId: {
type: 'string',
description: 'Card program statement period ID',
optional: true,
},
defaultFieldAccess: {
type: 'string',
description: 'Default field access (HD/RO/RW)',
optional: true,
},
imageStatus: { type: 'string', description: 'Image status', optional: true },
receiptContainerId: { type: 'string', description: 'Receipt container ID', optional: true },
receiptStatus: { type: 'string', description: 'Receipt status', optional: true },
sponsorId: { type: 'string', description: 'Sponsor ID', optional: true },
submitterId: { type: 'string', description: 'Submitter user ID', optional: true },
taxConfigId: { type: 'string', description: 'Tax configuration ID', optional: true },
redirectFund: {
type: 'json',
description: 'Redirect fund object { amount, creditCardId }',
optional: true,
},
customData: {
type: 'array',
description: 'Array of custom data { id, value, isValid, listItemUrl }',
optional: true,
},
employee: {
type: 'json',
description: 'Employee object { employeeId, employeeUuid }',
optional: true,
},
links: { type: 'array', description: 'HATEOAS links', optional: true },
},
},
},
}
@@ -0,0 +1,174 @@
import type { GetItemizationsParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getItemizationsTool: ToolConfig<GetItemizationsParams, SapConcurProxyResponse> = {
id: 'sap_concur_get_itemizations',
name: 'SAP Concur Get Expense Itemizations',
description:
'Get expense itemizations (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/expenses/{expenseId}/itemizations).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: TRAVELER, MANAGER, or PROXY',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
expenseId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
const expenseId = trimRequired(params.expenseId, 'expenseId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}/expenses/${encodeURIComponent(expenseId)}/itemizations`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'array',
description: 'Array of itemizations (ReportExpenseSummary[])',
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'Itemization identifier', optional: true },
expenseId: { type: 'string', description: 'Itemization expense id', optional: true },
allocations: {
type: 'array',
description: 'Allocations applied to the itemization',
optional: true,
},
expenseType: {
type: 'json',
description: 'Expense type {id, name, code, isDeleted}',
optional: true,
},
transactionDate: {
type: 'string',
description: 'Transaction date (YYYY-MM-DD)',
optional: true,
},
transactionAmount: { type: 'json', description: 'Transaction amount', optional: true },
postedAmount: { type: 'json', description: 'Posted amount', optional: true },
approvedAmount: { type: 'json', description: 'Approved amount', optional: true },
claimedAmount: { type: 'json', description: 'Claimed amount', optional: true },
approverAdjustedAmount: {
type: 'json',
description: 'Approver-adjusted amount',
optional: true,
},
paymentType: { type: 'json', description: 'Payment type', optional: true },
vendor: { type: 'json', description: 'Vendor info', optional: true },
location: { type: 'json', description: 'Location info', optional: true },
allocationState: {
type: 'string',
description: 'Allocation state',
optional: true,
},
allocationSetId: {
type: 'string',
description: 'Allocation set identifier',
optional: true,
},
attendeeCount: { type: 'number', description: 'Attendee count', optional: true },
businessPurpose: {
type: 'string',
description: 'Business purpose',
optional: true,
},
hasBlockingExceptions: {
type: 'boolean',
description: 'Has blocking exceptions',
optional: true,
},
hasExceptions: {
type: 'boolean',
description: 'Has exceptions',
optional: true,
},
isPersonalExpense: {
type: 'boolean',
description: 'Personal expense',
optional: true,
},
links: { type: 'array', description: 'HATEOAS links', optional: true },
},
},
},
},
}
+230
View File
@@ -0,0 +1,230 @@
import type { GetItineraryParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
buildListQuery,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getItineraryTool: ToolConfig<GetItineraryParams, SapConcurProxyResponse> = {
id: 'sap_concur_get_itinerary',
name: 'SAP Concur Get Trip',
description: 'Get a single trip/itinerary (GET /api/travel/trip/v1.1/{tripID}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
tripId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Trip ID',
},
useridType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User identifier type (login, xmlsyncid, uuid)',
},
useridValue: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User identifier value (paired with useridType)',
},
systemFormat: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional system format (e.g., GDS) for the response',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const tripId = trimRequired(params.tripId, 'tripId')
const query = buildListQuery({
userid_type: params.useridType,
userid_value: params.useridValue,
systemFormat: params.systemFormat,
})
return {
...baseProxyBody(params),
path: `/api/travel/trip/v1.1/${encodeURIComponent(tripId)}`,
method: 'GET',
...(Object.keys(query).length > 0 ? { query } : {}),
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Trip detail payload (Itinerary v1.1)',
properties: {
ItinLocator: {
type: 'string',
description: 'Concur trip locator (trip ID)',
optional: true,
},
ClientLocator: {
type: 'string',
description: 'Client (booking source) trip locator',
optional: true,
},
ItinSourceName: {
type: 'string',
description: 'Booking source name',
optional: true,
},
BookedVia: {
type: 'string',
description: 'How the trip was booked (e.g. ConcurTravel, Direct)',
optional: true,
},
TripName: {
type: 'string',
description: 'Trip name',
optional: true,
},
Status: {
type: 'string',
description: 'Trip status (e.g. Confirmed, Cancelled)',
optional: true,
},
Description: {
type: 'string',
description: 'Trip description',
optional: true,
},
Comments: {
type: 'string',
description: 'Comments attached to the trip',
optional: true,
},
CancelComments: {
type: 'string',
description: 'Cancellation comments (when applicable)',
optional: true,
},
ProjectName: {
type: 'string',
description: 'Associated project name',
optional: true,
},
StartDateUtc: {
type: 'string',
description: 'Trip start datetime in UTC',
optional: true,
},
EndDateUtc: {
type: 'string',
description: 'Trip end datetime in UTC',
optional: true,
},
StartDateLocal: {
type: 'string',
description: 'Trip start datetime in local time',
optional: true,
},
EndDateLocal: {
type: 'string',
description: 'Trip end datetime in local time',
optional: true,
},
DateCreatedUtc: {
type: 'string',
description: 'Trip creation timestamp (UTC)',
optional: true,
},
DateModifiedUtc: {
type: 'string',
description: 'Trip last-modified timestamp (UTC)',
optional: true,
},
DateBookedLocal: {
type: 'string',
description: 'Booking date in local time',
optional: true,
},
UserLoginId: {
type: 'string',
description: 'Login id of the trip owner',
optional: true,
},
BookedByFirstName: {
type: 'string',
description: 'First name of the booker',
optional: true,
},
BookedByLastName: {
type: 'string',
description: 'Last name of the booker',
optional: true,
},
IsPersonal: {
type: 'boolean',
description: 'Whether the trip is flagged personal',
optional: true,
},
RuleViolations: {
type: 'array',
description: 'Travel rule violations attached to the trip',
optional: true,
items: { type: 'json' },
},
Bookings: {
type: 'array',
description: 'Bookings (air/hotel/car/rail) attached to the trip',
optional: true,
items: { type: 'json' },
},
},
},
},
}
+134
View File
@@ -0,0 +1,134 @@
import type { GetListParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getListTool: ToolConfig<GetListParams, SapConcurProxyResponse> = {
id: 'sap_concur_get_list',
name: 'SAP Concur Get List',
description: 'Get a single custom list (GET /list/v4/lists/{listId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
listId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'List ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const listId = trimRequired(params.listId, 'listId')
return {
...baseProxyBody(params),
path: `/list/v4/lists/${encodeURIComponent(listId)}`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'List detail payload',
properties: {
id: { type: 'string', description: 'Unique identifier (UUID) of the list', optional: true },
value: { type: 'string', description: 'Name of the list', optional: true },
levelCount: {
type: 'number',
description: 'Number of levels in the list',
optional: true,
},
searchCriteria: {
type: 'string',
description: 'Search attribute (TEXT or CODE)',
optional: true,
},
displayFormat: {
type: 'string',
description: 'Display order ((CODE) TEXT or TEXT (CODE))',
optional: true,
},
category: {
type: 'json',
description: 'List category',
optional: true,
properties: {
id: { type: 'string', description: 'Category UUID', optional: true },
type: { type: 'string', description: 'Category type', optional: true },
},
},
isReadOnly: {
type: 'boolean',
description: 'Whether the list is read-only',
optional: true,
},
isDeleted: {
type: 'boolean',
description: 'Whether the list has been deleted',
optional: true,
},
managedBy: {
type: 'string',
description: 'Identifier of the managing application or service',
optional: true,
},
externalThreshold: {
type: 'number',
description: 'Threshold from where the level starts being external',
optional: true,
},
},
},
},
}
+123
View File
@@ -0,0 +1,123 @@
import type { GetListItemParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getListItemTool: ToolConfig<GetListItemParams, SapConcurProxyResponse> = {
id: 'sap_concur_get_list_item',
name: 'SAP Concur Get List Item',
description: 'Get a single list item (GET /list/v4/items/{itemId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
itemId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'List item ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const itemId = trimRequired(params.itemId, 'itemId')
return {
...baseProxyBody(params),
path: `/list/v4/items/${encodeURIComponent(itemId)}`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'List item detail payload',
properties: {
id: { type: 'string', description: 'List item UUID', optional: true },
code: { type: 'string', description: 'Long code format for the item', optional: true },
shortCode: { type: 'string', description: 'Short code identifier', optional: true },
value: { type: 'string', description: 'Display value of the item', optional: true },
parentId: {
type: 'string',
description: 'Parent item UUID (omitted for first-level items)',
optional: true,
},
level: {
type: 'number',
description: 'Hierarchy level (1 for root items)',
optional: true,
},
isDeleted: {
type: 'boolean',
description: 'Deletion status across all containing lists',
optional: true,
},
lists: {
type: 'array',
description: 'Lists containing this item',
optional: true,
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'List UUID', optional: true },
hasChildren: {
type: 'boolean',
description: 'Whether this item has children in the list',
optional: true,
},
},
},
},
},
},
},
}
@@ -0,0 +1,155 @@
import type { GetPurchaseRequestParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getPurchaseRequestTool: ToolConfig<GetPurchaseRequestParams, SapConcurProxyResponse> =
{
id: 'sap_concur_get_purchase_request',
name: 'SAP Concur Get Purchase Request',
description: 'Get a purchase request by ID (GET /purchaserequest/v4/purchaserequests/{id}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
purchaseRequestId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Purchase request ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const purchaseRequestId = trimRequired(params.purchaseRequestId, 'purchaseRequestId')
return {
...baseProxyBody(params),
path: `/purchaserequest/v4/purchaserequests/${encodeURIComponent(purchaseRequestId)}?mode=COMPACT`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Purchase request detail payload',
properties: {
purchaseRequestId: {
type: 'string',
description: 'Unique identifier of the purchase request',
optional: true,
},
purchaseRequestNumber: {
type: 'string',
description: 'Human-readable purchase request number',
optional: true,
},
purchaseRequestQueueStatus: {
type: 'string',
description: 'Queue status of the purchase request',
optional: true,
},
purchaseRequestWorkflowStatus: {
type: 'string',
description: 'Workflow status of the purchase request',
optional: true,
},
purchaseOrders: {
type: 'array',
description: 'Purchase orders generated from the request',
optional: true,
items: {
type: 'json',
properties: {
purchaseOrderNumber: {
type: 'string',
description: 'Purchase order number',
optional: true,
},
},
},
},
purchaseRequestExceptions: {
type: 'array',
description: 'Exceptions raised on the purchase request',
optional: true,
items: {
type: 'json',
properties: {
eventCode: { type: 'string', description: 'Event code', optional: true },
exceptionCode: {
type: 'string',
description: 'Exception code',
optional: true,
},
isCleared: {
type: 'boolean',
description: 'Whether the exception has been cleared',
optional: true,
},
prExceptionId: {
type: 'string',
description: 'Identifier of the exception record',
optional: true,
},
message: {
type: 'string',
description: 'Exception message',
optional: true,
},
},
},
},
},
},
},
}
+120
View File
@@ -0,0 +1,120 @@
import type { GetReceiptParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getReceiptTool: ToolConfig<GetReceiptParams, SapConcurProxyResponse> = {
id: 'sap_concur_get_receipt',
name: 'SAP Concur Get Receipt',
description: 'Get a single receipt by ID (GET /receipts/v4/{receiptId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
receiptId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Receipt ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const receiptId = trimRequired(params.receiptId, 'receiptId')
return {
...baseProxyBody(params),
path: `/receipts/v4/${encodeURIComponent(receiptId)}`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Receipt detail payload',
properties: {
id: { type: 'string', description: 'Receipt identifier', optional: true },
userId: { type: 'string', description: 'Owning user UUID', optional: true },
dateTimeReceived: {
type: 'string',
description: 'Timestamp when the receipt was received (ISO 8601)',
optional: true,
},
receipt: {
type: 'json',
description: 'Parsed receipt JSON object',
optional: true,
},
image: {
type: 'string',
description: 'Receipt image URL or data reference',
optional: true,
},
validationSchema: {
type: 'string',
description: 'Schema used to validate the receipt',
optional: true,
},
self: {
type: 'string',
description: 'URL to this receipt resource',
optional: true,
},
template: {
type: 'string',
description: 'URL template for receipts',
optional: true,
},
},
},
},
}
@@ -0,0 +1,106 @@
import type { GetReceiptStatusParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getReceiptStatusTool: ToolConfig<GetReceiptStatusParams, SapConcurProxyResponse> = {
id: 'sap_concur_get_receipt_status',
name: 'SAP Concur Get Receipt Status',
description: 'Get receipt processing status (GET /receipts/v4/status/{receiptId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
receiptId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Receipt ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const receiptId = trimRequired(params.receiptId, 'receiptId')
return {
...baseProxyBody(params),
path: `/receipts/v4/status/${encodeURIComponent(receiptId)}`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Receipt status payload',
properties: {
status: {
type: 'string',
description: 'Processing status: ACCEPTED, PROCESSING, PROCESSED, or FAILED',
optional: true,
},
logs: {
type: 'array',
description: 'Array of log entries',
optional: true,
items: {
type: 'json',
properties: {
logLevel: { type: 'string', description: 'Log level', optional: true },
message: { type: 'string', description: 'Log message', optional: true },
timestamp: { type: 'string', description: 'Log timestamp', optional: true },
},
},
},
},
},
},
}
@@ -0,0 +1,130 @@
import type { GetRequestCashAdvanceParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getRequestCashAdvanceTool: ToolConfig<
GetRequestCashAdvanceParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_get_request_cash_advance',
name: 'SAP Concur Get Request Cash Advance',
description:
'Get a single cash advance assigned to a travel request (GET /travelrequest/v4/cashadvances/{cashAdvanceUuid}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
cashAdvanceUuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Cash advance UUID (returned as part of a travel request)',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const cashAdvanceUuid = trimRequired(params.cashAdvanceUuid, 'cashAdvanceUuid')
return {
...baseProxyBody(params),
path: `/travelrequest/v4/cashadvances/${encodeURIComponent(cashAdvanceUuid)}`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Cash advance detail',
properties: {
cashAdvanceId: {
type: 'string',
description: 'Unique cash advance identifier',
optional: true,
},
amountRequested: {
type: 'json',
description: 'Requested amount',
optional: true,
properties: {
value: { type: 'number', description: 'Amount value', optional: true },
currency: { type: 'string', description: 'Currency code', optional: true },
amount: { type: 'number', description: 'Amount (alias)', optional: true },
},
},
approvalStatus: {
type: 'json',
description: 'Approval status',
optional: true,
properties: {
code: { type: 'string', description: 'Status code', optional: true },
name: { type: 'string', description: 'Status name', optional: true },
},
},
requestDate: {
type: 'string',
description: 'Request datetime (ISO 8601)',
optional: true,
},
exchangeRate: {
type: 'json',
description: 'Exchange rate',
optional: true,
properties: {
value: { type: 'number', description: 'Rate value', optional: true },
operation: { type: 'string', description: 'Multiply or divide', optional: true },
},
},
},
},
},
}
@@ -0,0 +1,234 @@
import type { GetTravelProfileParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
buildListQuery,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getTravelProfileTool: ToolConfig<GetTravelProfileParams, SapConcurProxyResponse> = {
id: 'sap_concur_get_travel_profile',
name: 'SAP Concur Get Travel Profile',
description:
'Get a travel profile (GET /api/travelprofile/v2.0/profile). Returns the calling user by default; pass userid_type and userid_value to impersonate.',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
useridType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Identifier type: login, xmlsyncid, or uuid',
},
useridValue: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Identifier value (login id, xml sync id, or UUID)',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const query = buildListQuery({
userid_type: params.useridType,
userid_value: params.useridValue,
})
return {
...baseProxyBody(params),
path: '/api/travelprofile/v2.0/profile',
method: 'GET',
query: Object.keys(query).length > 0 ? query : undefined,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description:
'Travel profile payload. Concur returns XML; downstream may parse it to a best-effort JSON object with the documented top-level sections.',
properties: {
General: {
type: 'json',
description:
'General profile info (NamePrefix, FirstName, MiddleName, LastName, NameSuffix, JobTitle, CompanyEmployeeID, EmailAddress, RuleClass, TravelConfigID, etc.)',
optional: true,
},
Telephones: {
type: 'json',
description: 'Telephone numbers (Telephone[] with Type, CountryCode, PhoneNumber, etc.)',
optional: true,
},
Addresses: {
type: 'json',
description: 'Address records (Address[] with Type, Street, City, StateProvince, etc.)',
optional: true,
},
DriversLicenses: {
type: 'array',
description: 'Drivers license records',
optional: true,
items: { type: 'json' },
},
NationalIDs: {
type: 'array',
description: 'National ID records',
optional: true,
items: { type: 'json' },
},
EmailAddresses: {
type: 'json',
description: 'Email addresses (EmailAddress[] with Type, Address, Contact, Verified)',
optional: true,
},
EmergencyContact: {
type: 'json',
description: 'Emergency contact (Name, Relationship, Phones, Address)',
optional: true,
},
Air: {
type: 'json',
description: 'Air travel preferences (HomeAirport, Seat, Meal, AirOther, AirMemberships)',
optional: true,
},
Rail: {
type: 'json',
description: 'Rail preferences (Seat, Coach, Berth, Other, RailMemberships)',
optional: true,
},
Hotel: {
type: 'json',
description:
'Hotel preferences (SmokingCode, RoomType, HotelOther, HotelMemberships, Accessibility flags)',
optional: true,
},
Car: {
type: 'json',
description: 'Car rental preferences (CarSmokingCode, CarType, CarMemberships, etc.)',
optional: true,
},
CustomFields: {
type: 'json',
description: 'Custom-defined fields configured by the company',
optional: true,
},
RatePreferences: {
type: 'json',
description: 'Rate preferences (e.g. AAA, AARP, government, military rates)',
optional: true,
},
DiscountCodes: {
type: 'json',
description: 'Discount codes available to the traveler',
optional: true,
},
HasNoPassport: {
type: 'boolean',
description: 'Whether the traveler has no passport on file',
optional: true,
},
Roles: {
type: 'json',
description: 'Role assignments (TravelManager, Assistant, etc.)',
optional: true,
},
Sponsors: {
type: 'json',
description: 'Sponsor information for guest travelers',
optional: true,
},
TSAInfo: {
type: 'json',
description: 'TSA SecureFlight info (Gender, DateOfBirth, NoMiddleName, etc.)',
optional: true,
},
Passports: {
type: 'json',
description: 'Passport documents (Passport[] with PassportNumber, Country, Expiration)',
optional: true,
},
Visas: {
type: 'json',
description: 'Visa documents (Visa[] with VisaNationality, VisaNumber, etc.)',
optional: true,
},
UnusedTickets: {
type: 'json',
description: 'Unused ticket records',
optional: true,
},
SouthwestUnusedTickets: {
type: 'json',
description: 'Southwest-specific unused ticket records',
optional: true,
},
AdvantageMemberships: {
type: 'json',
description: 'Advantage program memberships',
optional: true,
},
XmlSyncId: {
type: 'string',
description: 'XML sync identifier for the user',
optional: true,
},
LoginId: {
type: 'string',
description: 'Concur login id',
optional: true,
},
ProfileLastModifiedUTC: {
type: 'string',
description: 'UTC timestamp the profile was last modified',
optional: true,
},
},
},
},
}
@@ -0,0 +1,336 @@
import type { GetTravelRequestParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getTravelRequestTool: ToolConfig<GetTravelRequestParams, SapConcurProxyResponse> = {
id: 'sap_concur_get_travel_request',
name: 'SAP Concur Get Travel Request',
description: 'Get a single travel request (GET /travelrequest/v4/requests/{requestUuid}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
requestUuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Travel request UUID',
},
userId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional Concur user UUID — required when impersonating another user',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const requestUuid = trimRequired(params.requestUuid, 'requestUuid')
const query: Record<string, string> = {}
if (params.userId) query.userId = params.userId
return {
...baseProxyBody(params),
path: `/travelrequest/v4/requests/${encodeURIComponent(requestUuid)}`,
method: 'GET',
query: Object.keys(query).length > 0 ? query : undefined,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Travel request detail payload',
properties: {
id: { type: 'string', description: 'Travel request UUID', optional: true },
href: { type: 'string', description: 'Resource hyperlink', optional: true },
requestId: {
type: 'string',
description: 'Public-facing request ID (4-6 alphanumeric characters)',
optional: true,
},
name: { type: 'string', description: 'Request name', optional: true },
businessPurpose: { type: 'string', description: 'Business purpose', optional: true },
comment: { type: 'string', description: 'Last attached comment', optional: true },
creationDate: { type: 'string', description: 'Creation timestamp', optional: true },
lastModified: {
type: 'string',
description: 'Last modification timestamp',
optional: true,
},
submitDate: { type: 'string', description: 'Last submission timestamp', optional: true },
authorizedDate: {
type: 'string',
description: 'Date when approval was completed',
optional: true,
},
approvalLimitDate: {
type: 'string',
description: 'Required approval deadline',
optional: true,
},
startDate: { type: 'string', description: 'Trip start date (ISO 8601)', optional: true },
endDate: { type: 'string', description: 'Trip end date (ISO 8601)', optional: true },
startTime: { type: 'string', description: 'Trip start time (HH:mm)', optional: true },
endTime: { type: 'string', description: 'Trip end time (HH:mm)', optional: true },
pnr: { type: 'string', description: 'Passenger record number', optional: true },
approved: {
type: 'boolean',
description: 'Whether the request is approved',
optional: true,
},
pendingApproval: { type: 'boolean', description: 'Pending approval flag', optional: true },
closed: { type: 'boolean', description: 'Closed flag', optional: true },
everSentBack: { type: 'boolean', description: 'Ever-sent-back flag', optional: true },
canceledPostApproval: {
type: 'boolean',
description: 'Canceled after approval flag',
optional: true,
},
isParentRequest: { type: 'boolean', description: 'Parent request flag', optional: true },
parentRequestId: {
type: 'string',
description: 'Parent budget request ID',
optional: true,
},
allocationFormId: {
type: 'string',
description: 'Allocation form identifier',
optional: true,
},
highestExceptionLevel: {
type: 'string',
description: 'Highest exception level (WARNING, ERROR, NONE)',
optional: true,
},
approvalStatus: {
type: 'json',
description: 'Approval status',
optional: true,
properties: {
code: {
type: 'string',
description: 'Status code (NOT_SUBMITTED, SUBMITTED, APPROVED, CANCELED, SENTBACK)',
optional: true,
},
name: { type: 'string', description: 'Localized status name', optional: true },
},
},
owner: {
type: 'json',
description: 'Travel request owner',
optional: true,
properties: {
id: { type: 'string', description: 'User UUID', optional: true },
firstName: { type: 'string', description: 'Owner first name', optional: true },
lastName: { type: 'string', description: 'Owner last name', optional: true },
},
},
approver: {
type: 'json',
description: 'Approver assigned to the request',
optional: true,
properties: {
id: { type: 'string', description: 'User UUID', optional: true },
firstName: { type: 'string', description: 'Approver first name', optional: true },
lastName: { type: 'string', description: 'Approver last name', optional: true },
},
},
policy: {
type: 'json',
description: 'Resource link to the applicable policy',
optional: true,
properties: {
id: { type: 'string', description: 'Policy ID', optional: true },
href: { type: 'string', description: 'Policy hyperlink', optional: true },
},
},
type: {
type: 'json',
description: 'Request type',
optional: true,
properties: {
code: { type: 'string', description: 'Request type code', optional: true },
label: { type: 'string', description: 'Request type label', optional: true },
},
},
mainDestination: {
type: 'json',
description: 'Main destination of the trip',
optional: true,
properties: {
city: { type: 'string', description: 'City', optional: true },
countryCode: { type: 'string', description: 'ISO country code', optional: true },
countrySubDivisionCode: {
type: 'string',
description: 'ISO country sub-division code',
optional: true,
},
name: { type: 'string', description: 'Destination name', optional: true },
},
},
totalApprovedAmount: {
type: 'json',
description: 'Total approved amount',
optional: true,
properties: {
value: { type: 'number', description: 'Amount value', optional: true },
currency: { type: 'string', description: 'Currency code', optional: true },
},
},
totalPostedAmount: {
type: 'json',
description: 'Total posted amount',
optional: true,
properties: {
value: { type: 'number', description: 'Amount value', optional: true },
currency: { type: 'string', description: 'Currency code', optional: true },
},
},
totalRemainingAmount: {
type: 'json',
description: 'Total remaining amount',
optional: true,
properties: {
value: { type: 'number', description: 'Amount value', optional: true },
currency: { type: 'string', description: 'Currency code', optional: true },
},
},
expenses: {
type: 'array',
description: 'Resource links to expected expenses',
optional: true,
items: { type: 'json' },
},
cashAdvances: {
type: 'json',
description: 'Resource link to cash advances',
optional: true,
properties: {
id: { type: 'string', description: 'Resource ID', optional: true },
href: { type: 'string', description: 'Resource hyperlink', optional: true },
},
},
comments: {
type: 'json',
description: 'Resource link to comments',
optional: true,
properties: {
id: { type: 'string', description: 'Resource ID', optional: true },
href: { type: 'string', description: 'Resource hyperlink', optional: true },
},
},
exceptions: {
type: 'json',
description: 'Resource link to exceptions',
optional: true,
properties: {
id: { type: 'string', description: 'Resource ID', optional: true },
href: { type: 'string', description: 'Resource hyperlink', optional: true },
},
},
travelAgency: {
type: 'json',
description: 'Resource link to travel agency',
optional: true,
properties: {
id: { type: 'string', description: 'Resource ID', optional: true },
href: { type: 'string', description: 'Resource hyperlink', optional: true },
},
},
parentRequest: {
type: 'json',
description: 'Resource link to parent request',
optional: true,
properties: {
id: { type: 'string', description: 'Resource ID', optional: true },
href: { type: 'string', description: 'Resource hyperlink', optional: true },
},
},
eventRequest: {
type: 'json',
description: 'Resource link to parent event request',
optional: true,
properties: {
id: { type: 'string', description: 'Resource ID', optional: true },
href: { type: 'string', description: 'Resource hyperlink', optional: true },
},
},
operations: {
type: 'array',
description: 'Available workflow actions',
optional: true,
items: {
type: 'json',
properties: {
rel: { type: 'string', description: 'Operation name', optional: true },
href: { type: 'string', description: 'Operation URL', optional: true },
},
},
},
expensePolicy: {
type: 'json',
description: 'Expense policy reference',
optional: true,
properties: {
id: { type: 'string', description: 'Policy identifier', optional: true },
href: { type: 'string', description: 'Policy URL', optional: true },
},
},
custom1: { type: 'json', description: 'Custom field 1', optional: true },
custom2: { type: 'json', description: 'Custom field 2', optional: true },
custom3: { type: 'json', description: 'Custom field 3', optional: true },
custom4: { type: 'json', description: 'Custom field 4', optional: true },
},
},
},
}
+105
View File
@@ -0,0 +1,105 @@
import type { GetUserParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
scimUserOutputProperties,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const getUserTool: ToolConfig<GetUserParams, SapConcurProxyResponse> = {
id: 'sap_concur_get_user',
name: 'SAP Concur Get User',
description: 'Get a single user by UUID (GET /profile/identity/v4.1/Users/{id}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userUuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'User UUID',
},
attributes: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated SCIM attributes to include in the response',
},
excludedAttributes: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated SCIM attributes to exclude from the response',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userUuid = trimRequired(params.userUuid, 'userUuid')
const query: Record<string, string> = {}
if (params.attributes?.trim()) query.attributes = params.attributes.trim()
if (params.excludedAttributes?.trim())
query.excludedAttributes = params.excludedAttributes.trim()
return {
...baseProxyBody(params),
path: `/profile/identity/v4.1/Users/${encodeURIComponent(userUuid)}`,
method: 'GET',
...(Object.keys(query).length > 0 ? { query } : {}),
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'SCIM User identity payload',
properties: scimUserOutputProperties,
},
},
}
+70
View File
@@ -0,0 +1,70 @@
export { approveExpenseReportTool } from '@/tools/sap_concur/approve_expense_report'
export { associateAttendeesTool } from '@/tools/sap_concur/associate_attendees'
export { createCashAdvanceTool } from '@/tools/sap_concur/create_cash_advance'
export { createExpectedExpenseTool } from '@/tools/sap_concur/create_expected_expense'
export { createExpenseReportTool } from '@/tools/sap_concur/create_expense_report'
export { createListItemTool } from '@/tools/sap_concur/create_list_item'
export { createPurchaseRequestTool } from '@/tools/sap_concur/create_purchase_request'
export { createQuickExpenseTool } from '@/tools/sap_concur/create_quick_expense'
export { createQuickExpenseWithImageTool } from '@/tools/sap_concur/create_quick_expense_with_image'
export { createReportCommentTool } from '@/tools/sap_concur/create_report_comment'
export { createTravelRequestTool } from '@/tools/sap_concur/create_travel_request'
export { createUserTool } from '@/tools/sap_concur/create_user'
export { deleteExpectedExpenseTool } from '@/tools/sap_concur/delete_expected_expense'
export { deleteExpenseTool } from '@/tools/sap_concur/delete_expense'
export { deleteExpenseReportTool } from '@/tools/sap_concur/delete_expense_report'
export { deleteListItemTool } from '@/tools/sap_concur/delete_list_item'
export { deleteTravelRequestTool } from '@/tools/sap_concur/delete_travel_request'
export { deleteUserTool } from '@/tools/sap_concur/delete_user'
export { getAllocationTool } from '@/tools/sap_concur/get_allocation'
export { getBudgetTool } from '@/tools/sap_concur/get_budget'
export { getCashAdvanceTool } from '@/tools/sap_concur/get_cash_advance'
export { getExpectedExpenseTool } from '@/tools/sap_concur/get_expected_expense'
export { getExpenseTool } from '@/tools/sap_concur/get_expense'
export { getExpenseReportTool } from '@/tools/sap_concur/get_expense_report'
export { getItemizationsTool } from '@/tools/sap_concur/get_itemizations'
export { getItineraryTool } from '@/tools/sap_concur/get_itinerary'
export { getListTool } from '@/tools/sap_concur/get_list'
export { getListItemTool } from '@/tools/sap_concur/get_list_item'
export { getPurchaseRequestTool } from '@/tools/sap_concur/get_purchase_request'
export { getReceiptTool } from '@/tools/sap_concur/get_receipt'
export { getReceiptStatusTool } from '@/tools/sap_concur/get_receipt_status'
export { getRequestCashAdvanceTool } from '@/tools/sap_concur/get_request_cash_advance'
export { getTravelProfileTool } from '@/tools/sap_concur/get_travel_profile'
export { getTravelRequestTool } from '@/tools/sap_concur/get_travel_request'
export { getUserTool } from '@/tools/sap_concur/get_user'
export { issueCashAdvanceTool } from '@/tools/sap_concur/issue_cash_advance'
export { listAllocationsTool } from '@/tools/sap_concur/list_allocations'
export { listAttendeeAssociationsTool } from '@/tools/sap_concur/list_attendee_associations'
export { listBudgetCategoriesTool } from '@/tools/sap_concur/list_budget_categories'
export { listBudgetsTool } from '@/tools/sap_concur/list_budgets'
export { listExceptionsTool } from '@/tools/sap_concur/list_exceptions'
export { listExpectedExpensesTool } from '@/tools/sap_concur/list_expected_expenses'
export { listExpenseReportsTool } from '@/tools/sap_concur/list_expense_reports'
export { listExpensesTool } from '@/tools/sap_concur/list_expenses'
export { listItinerariesTool } from '@/tools/sap_concur/list_itineraries'
export { listListItemsTool } from '@/tools/sap_concur/list_list_items'
export { listListsTool } from '@/tools/sap_concur/list_lists'
export { listReceiptsTool } from '@/tools/sap_concur/list_receipts'
export { listReportCommentsTool } from '@/tools/sap_concur/list_report_comments'
export { listReportsToApproveTool } from '@/tools/sap_concur/list_reports_to_approve'
export { listTravelProfilesSummaryTool } from '@/tools/sap_concur/list_travel_profiles_summary'
export { listTravelRequestCommentsTool } from '@/tools/sap_concur/list_travel_request_comments'
export { listTravelRequestsTool } from '@/tools/sap_concur/list_travel_requests'
export { listUsersTool } from '@/tools/sap_concur/list_users'
export { moveTravelRequestTool } from '@/tools/sap_concur/move_travel_request'
export { recallExpenseReportTool } from '@/tools/sap_concur/recall_expense_report'
export { removeAllAttendeesTool } from '@/tools/sap_concur/remove_all_attendees'
export { searchLocationsTool } from '@/tools/sap_concur/search_locations'
export { searchUsersTool } from '@/tools/sap_concur/search_users'
export { sendBackExpenseReportTool } from '@/tools/sap_concur/send_back_expense_report'
export { submitExpenseReportTool } from '@/tools/sap_concur/submit_expense_report'
export { updateAllocationTool } from '@/tools/sap_concur/update_allocation'
export { updateExpectedExpenseTool } from '@/tools/sap_concur/update_expected_expense'
export { updateExpenseTool } from '@/tools/sap_concur/update_expense'
export { updateExpenseReportTool } from '@/tools/sap_concur/update_expense_report'
export { updateListItemTool } from '@/tools/sap_concur/update_list_item'
export { updateTravelRequestTool } from '@/tools/sap_concur/update_travel_request'
export { updateUserTool } from '@/tools/sap_concur/update_user'
export { uploadExchangeRatesTool } from '@/tools/sap_concur/upload_exchange_rates'
export { uploadReceiptImageTool } from '@/tools/sap_concur/upload_receipt_image'
@@ -0,0 +1,109 @@
import type { IssueCashAdvanceParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const issueCashAdvanceTool: ToolConfig<IssueCashAdvanceParams, SapConcurProxyResponse> = {
id: 'sap_concur_issue_cash_advance',
name: 'SAP Concur Issue Cash Advance',
description: 'Issue a cash advance (POST /cashadvance/v4.1/cashadvances/{cashAdvanceId}/issue).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
cashAdvanceId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Cash advance ID to issue',
},
body: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Optional request body',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const cashAdvanceId = trimRequired(params.cashAdvanceId, 'cashAdvanceId')
return {
...baseProxyBody(params),
path: `/cashadvance/v4.1/cashadvances/${encodeURIComponent(cashAdvanceId)}/issue`,
method: 'POST',
body: params.body ?? {},
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Issue cash advance result payload',
properties: {
issuedDate: {
type: 'string',
description: 'Date the cash advance was issued (YYYY-MM-DD)',
optional: true,
},
status: {
type: 'json',
description: 'Cash advance status after the issue action',
optional: true,
properties: {
code: { type: 'string', description: 'Status code', optional: true },
name: { type: 'string', description: 'Status display name', optional: true },
},
},
},
},
},
}
@@ -0,0 +1,117 @@
import type { ListAllocationsParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listAllocationsTool: ToolConfig<ListAllocationsParams, SapConcurProxyResponse> = {
id: 'sap_concur_list_allocations',
name: 'SAP Concur List Allocations',
description:
'List allocations on an expense (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/expenses/{expenseId}/allocations).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: TRAVELER or PROXY',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
expenseId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
const expenseId = trimRequired(params.expenseId, 'expenseId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}/expenses/${encodeURIComponent(expenseId)}/allocations`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Allocations list payload',
properties: {
items: {
type: 'array',
optional: true,
description:
'Array of allocation objects (allocationId, accountCode, percentage, allocationAmount, approvedAmount, claimedAmount, customData, expenseId, isSystemAllocation, isPercentEdited, overLimitAccountCode)',
items: { type: 'json' },
},
},
},
},
}
@@ -0,0 +1,193 @@
import type {
ListAttendeeAssociationsParams,
SapConcurProxyResponse,
} from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listAttendeeAssociationsTool: ToolConfig<
ListAttendeeAssociationsParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_list_attendee_associations',
name: 'SAP Concur List Attendee Associations',
description:
'List attendees associated with an expense (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/expenses/{expenseId}/attendees).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: TRAVELER or PROXY',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
expenseId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
const expenseId = trimRequired(params.expenseId, 'expenseId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}/expenses/${encodeURIComponent(expenseId)}/attendees`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Attendees list payload',
properties: {
noShowAttendeeCount: {
type: 'number',
description: 'Number of unnamed/no-show attendees',
optional: true,
},
expenseAttendeeList: {
type: 'array',
description: 'Attendees associated with the expense, including amounts',
items: {
type: 'json',
properties: {
attendeeId: { type: 'string', description: 'Unique identifier of the attendee' },
transactionAmount: {
type: 'json',
description: 'Expense portion assigned to this attendee',
properties: {
value: { type: 'number', description: 'Numeric amount' },
currencyCode: { type: 'string', description: 'ISO 4217 currency code' },
},
},
approvedAmount: {
type: 'json',
description: 'Approved amount in report currency',
properties: {
value: { type: 'number', description: 'Numeric amount' },
currencyCode: { type: 'string', description: 'ISO 4217 currency code' },
},
},
isAmountUserEdited: {
type: 'boolean',
description: 'Whether the amount was manually edited',
optional: true,
},
isTraveling: {
type: 'boolean',
description: 'Whether the attendee is traveling (affects tax calculations)',
optional: true,
},
associatedAttendeeCount: {
type: 'number',
description: 'Total attendee count; greater than 1 indicates unnamed attendees',
optional: true,
},
versionNumber: {
type: 'number',
description: 'Version number preserving previous attendee state',
optional: true,
},
customData: {
type: 'array',
description: 'Custom field values for the association',
optional: true,
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'Custom field identifier' },
value: {
type: 'string',
description: 'Custom field value (max 48 characters)',
optional: true,
},
isValid: {
type: 'boolean',
description: 'Whether the value passes validation',
optional: true,
},
listItemUrl: {
type: 'string',
description: 'HATEOAS link for list items',
optional: true,
},
},
},
},
},
},
},
},
},
},
}
@@ -0,0 +1,106 @@
import type { ListBudgetCategoriesParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listBudgetCategoriesTool: ToolConfig<
ListBudgetCategoriesParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_list_budget_categories',
name: 'SAP Concur List Budget Categories',
description: 'List budget categories (GET /budget/v4/budgetCategory).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
...baseProxyBody(params),
path: `/budget/v4/budgetCategory`,
method: 'GET',
}),
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Budget categories collection payload',
properties: {
items: {
type: 'array',
optional: true,
description: 'Array of budget category objects',
items: {
type: 'json',
properties: {
id: { type: 'string', optional: true, description: 'Category ID' },
name: { type: 'string', optional: true, description: 'Admin-facing category name' },
description: { type: 'string', optional: true, description: 'Friendly name' },
statusType: {
type: 'string',
optional: true,
description: 'Status: OPEN or REMOVED',
},
expenseTypes: {
type: 'array',
optional: true,
description:
'Expense types in this category (id, featureTypeCode, expenseTypeCode, name)',
items: { type: 'json' },
},
},
},
},
},
},
},
}
+112
View File
@@ -0,0 +1,112 @@
import type { ListBudgetsParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
buildListQuery,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listBudgetsTool: ToolConfig<ListBudgetsParams, SapConcurProxyResponse> = {
id: 'sap_concur_list_budgets',
name: 'SAP Concur List Budgets',
description: 'List budget item headers (GET /budget/v4/budgetItemHeader).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
adminView: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'When true, returns all budgets the caller can administer (default false)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page offset (Concur returns up to 50 budget headers per page)',
},
responseSchema: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Response schema variant: "COMPACT" returns a smaller payload',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
...baseProxyBody(params),
path: `/budget/v4/budgetItemHeader`,
method: 'GET',
query: buildListQuery({
adminView: params.adminView,
offset: params.offset,
responseSchema: params.responseSchema,
}),
}),
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Budget headers collection payload',
properties: {
items: {
type: 'array',
optional: true,
description:
'Array of budget item header summaries (id, name, description, budgetItemStatusType, budgetType, currencyCode, fiscalYear, budgetAmounts, owner, ...)',
items: { type: 'json' },
},
offset: { type: 'number', optional: true, description: 'Page offset' },
limit: { type: 'number', optional: true, description: 'Page size' },
totalCount: { type: 'number', optional: true, description: 'Total result count' },
},
},
},
}
@@ -0,0 +1,131 @@
import type { ListExceptionsParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listExceptionsTool: ToolConfig<ListExceptionsParams, SapConcurProxyResponse> = {
id: 'sap_concur_list_exceptions',
name: 'SAP Concur List Report Exceptions',
description:
'List exceptions on a report (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/exceptions).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: TRAVELER, MANAGER, or PROXY',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}/exceptions`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'array',
description: 'Array of report header exception entries',
items: {
type: 'json',
properties: {
exceptionCode: { type: 'string', description: 'Unique exception code' },
exceptionVisibility: {
type: 'string',
description: 'Visibility scope: ALL, APPROVER_PROCESSOR, or PROCESSOR',
},
isBlocking: {
type: 'boolean',
description: 'Whether the exception prevents report submission',
},
message: { type: 'string', description: 'Human-readable description of the exception' },
expenseId: {
type: 'string',
description: 'Related expense entry ID',
optional: true,
},
allocationId: {
type: 'string',
description: 'Related allocation ID, if any',
optional: true,
},
parentExpenseId: {
type: 'string',
description: 'Parent expense ID for itemized entries',
optional: true,
},
},
},
},
},
}
@@ -0,0 +1,100 @@
import type { ListExpectedExpensesParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listExpectedExpensesTool: ToolConfig<
ListExpectedExpensesParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_list_expected_expenses',
name: 'SAP Concur List Expected Expenses',
description:
'List expected expenses on a travel request (GET /travelrequest/v4/requests/{requestUuid}/expenses).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
requestUuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Travel request UUID',
},
userId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User UUID acting on the request (optional)',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const requestUuid = trimRequired(params.requestUuid, 'requestUuid')
const query: Record<string, string> = {}
if (params.userId?.trim()) query.userId = params.userId.trim()
return {
...baseProxyBody(params),
path: `/travelrequest/v4/requests/${encodeURIComponent(requestUuid)}/expenses`,
method: 'GET',
...(Object.keys(query).length > 0 ? { query } : {}),
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description:
'Array of expected expense objects. Each entry includes id, href, expenseType {id,name}, transactionDate, transactionAmount, postedAmount, approvedAmount, remainingAmount, businessPurpose, location, exchangeRate, allocations, tripData, parentRequest {href, id}, comments {href, id}.',
},
},
}
@@ -0,0 +1,275 @@
import type { ListExpenseReportsParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listExpenseReportsTool: ToolConfig<ListExpenseReportsParams, SapConcurProxyResponse> =
{
id: 'sap_concur_list_expense_reports',
name: 'SAP Concur List Expense Reports',
description:
'List expense reports (GET /api/v3.0/expense/reports). Returns a v3 envelope with Items and NextPage.',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Concur datacenter base URL (us, us2, eu, eu2, cn, emea — defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
user: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by a specific user (login id or user identifier).',
},
submitDateBefore: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter to reports submitted on or before this date (YYYY-MM-DD)',
},
submitDateAfter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter to reports submitted on or after this date (YYYY-MM-DD)',
},
paidDateBefore: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter to reports paid on or before this date (YYYY-MM-DD)',
},
paidDateAfter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter to reports paid on or after this date (YYYY-MM-DD)',
},
modifiedDateBefore: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter to reports last modified on or before this date (YYYY-MM-DD)',
},
modifiedDateAfter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter to reports last modified on or after this date (YYYY-MM-DD)',
},
createDateBefore: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter to reports created on or before this date (YYYY-MM-DD)',
},
createDateAfter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter to reports created on or after this date (YYYY-MM-DD)',
},
approvalStatusCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by approval status code (e.g. A_NOTF, A_PEND, A_APPR)',
},
paymentStatusCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by payment status code',
},
currencyCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by ISO currency code (e.g. USD, EUR)',
},
approverLoginID: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by approver login ID',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of records per page (default 25, max 100)',
},
offset: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Opaque cursor token returned by a prior call (NextPage).',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const query: Record<string, string | number> = {}
if (params.user) query.user = params.user
if (params.submitDateBefore) query.submitDateBefore = params.submitDateBefore
if (params.submitDateAfter) query.submitDateAfter = params.submitDateAfter
if (params.paidDateBefore) query.paidDateBefore = params.paidDateBefore
if (params.paidDateAfter) query.paidDateAfter = params.paidDateAfter
if (params.modifiedDateBefore) query.modifiedDateBefore = params.modifiedDateBefore
if (params.modifiedDateAfter) query.modifiedDateAfter = params.modifiedDateAfter
if (params.createDateBefore) query.createDateBefore = params.createDateBefore
if (params.createDateAfter) query.createDateAfter = params.createDateAfter
if (params.approvalStatusCode) query.approvalStatusCode = params.approvalStatusCode
if (params.paymentStatusCode) query.paymentStatusCode = params.paymentStatusCode
if (params.currencyCode) query.currencyCode = params.currencyCode
if (params.approverLoginID) query.approverLoginID = params.approverLoginID
if (params.limit !== undefined) query.limit = params.limit
if (params.offset) query.offset = params.offset
return {
...baseProxyBody(params),
path: '/api/v3.0/expense/reports',
method: 'GET',
query,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Concur v3 expense reports envelope',
properties: {
Items: {
type: 'array',
description: 'Array of report header objects',
optional: true,
items: {
type: 'json',
properties: {
ID: { type: 'string', description: 'Report ID', optional: true },
Name: { type: 'string', description: 'Report name', optional: true },
OwnerLoginID: { type: 'string', description: 'Owner login ID', optional: true },
OwnerName: { type: 'string', description: 'Owner display name', optional: true },
Total: { type: 'number', description: 'Report total', optional: true },
TotalApprovedAmount: {
type: 'number',
description: 'Total approved amount',
optional: true,
},
TotalClaimedAmount: {
type: 'number',
description: 'Total claimed amount',
optional: true,
},
AmountDueEmployee: {
type: 'number',
description: 'Amount due employee',
optional: true,
},
CurrencyCode: { type: 'string', description: 'ISO currency code', optional: true },
ApprovalStatusName: {
type: 'string',
description: 'Approval status name',
optional: true,
},
ApprovalStatusCode: {
type: 'string',
description: 'Approval status code',
optional: true,
},
PaymentStatusName: {
type: 'string',
description: 'Payment status name',
optional: true,
},
PaymentStatusCode: {
type: 'string',
description: 'Payment status code',
optional: true,
},
ApproverLoginID: {
type: 'string',
description: 'Approver login ID',
optional: true,
},
ApproverName: {
type: 'string',
description: 'Approver display name',
optional: true,
},
HasException: {
type: 'boolean',
description: 'Whether the report has any exception',
optional: true,
},
ReceiptsReceived: {
type: 'boolean',
description: 'Whether paper receipts were received',
optional: true,
},
CreateDate: { type: 'string', description: 'Creation date', optional: true },
SubmitDate: { type: 'string', description: 'Submit date', optional: true },
LastModifiedDate: {
type: 'string',
description: 'Last modified date',
optional: true,
},
PaidDate: { type: 'string', description: 'Paid date', optional: true },
URI: { type: 'string', description: 'Self URI', optional: true },
},
},
},
NextPage: {
type: 'string',
description: 'URI of the next page (use as offset cursor)',
optional: true,
},
},
},
},
}
+216
View File
@@ -0,0 +1,216 @@
import type { ListExpensesParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listExpensesTool: ToolConfig<ListExpensesParams, SapConcurProxyResponse> = {
id: 'sap_concur_list_expenses',
name: 'SAP Concur List Expenses',
description:
'List expenses on a report (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/expenses).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: TRAVELER, MANAGER, or PROXY',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}/expenses`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'array',
description: 'Array of expense summary entries (ReportExpenseSummary[])',
items: {
type: 'json',
properties: {
expenseId: { type: 'string', description: 'Expense identifier', optional: true },
expenseType: {
type: 'json',
description: 'Expense type {id, name, code, isDeleted}',
optional: true,
},
transactionDate: {
type: 'string',
description: 'Transaction date (YYYY-MM-DD)',
optional: true,
},
transactionAmount: {
type: 'json',
description: 'Transaction amount {currencyCode, value}',
optional: true,
},
postedAmount: { type: 'json', description: 'Posted amount', optional: true },
approvedAmount: { type: 'json', description: 'Approved amount', optional: true },
claimedAmount: { type: 'json', description: 'Claimed amount', optional: true },
approverAdjustedAmount: {
type: 'json',
description: 'Approver-adjusted amount',
optional: true,
},
paymentType: {
type: 'json',
description: 'Payment type {id, name, code}',
optional: true,
},
vendor: { type: 'json', description: 'Vendor info', optional: true },
location: { type: 'json', description: 'Location info', optional: true },
allocationState: {
type: 'string',
description: 'Allocation state',
optional: true,
},
allocationSetId: {
type: 'string',
description: 'Allocation set identifier',
optional: true,
},
attendeeCount: { type: 'number', description: 'Attendee count', optional: true },
businessPurpose: {
type: 'string',
description: 'Business purpose',
optional: true,
},
hasBlockingExceptions: {
type: 'boolean',
description: 'Has submission-blocking exceptions',
optional: true,
},
hasExceptions: {
type: 'boolean',
description: 'Has exceptions',
optional: true,
},
hasMissingReceiptDeclaration: {
type: 'boolean',
description: 'Has missing-receipt declaration',
optional: true,
},
isAutoCreated: { type: 'boolean', description: 'Auto-created', optional: true },
isPersonalExpense: {
type: 'boolean',
description: 'Personal-expense flag',
optional: true,
},
isImageRequired: {
type: 'boolean',
description: 'Receipt image required',
optional: true,
},
isPaperReceiptRequired: {
type: 'boolean',
description: 'Paper receipt required',
optional: true,
},
imageCertificationStatus: {
type: 'string',
description: 'Receipt image certification status',
optional: true,
},
receiptImageId: {
type: 'string',
description: 'Receipt image identifier',
optional: true,
},
ereceiptImageId: {
type: 'string',
description: 'eReceipt image identifier',
optional: true,
},
ticketNumber: {
type: 'string',
description: 'Ticket number',
optional: true,
},
exchangeRate: { type: 'json', description: 'Exchange rate', optional: true },
travelAllowance: {
type: 'json',
description: 'Travel allowance',
optional: true,
},
expenseSourceIdentifiers: {
type: 'json',
description: 'Expense source identifiers',
optional: true,
},
links: { type: 'array', description: 'HATEOAS links', optional: true },
},
},
},
},
}
@@ -0,0 +1,232 @@
import type { ListItinerariesParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
buildListQuery,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listItinerariesTool: ToolConfig<ListItinerariesParams, SapConcurProxyResponse> = {
id: 'sap_concur_list_itineraries',
name: 'SAP Concur List Trips',
description: 'List travel trips/itineraries (GET /api/travel/trip/v1.1).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
startDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter trips starting on/after this date (YYYY-MM-DD)',
},
endDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter trips ending on/before this date (YYYY-MM-DD)',
},
bookingType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by booking type (air, car, hotel, rail, etc.)',
},
useridType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User identifier type (login, xmlsyncid, uuid)',
},
useridValue: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User identifier value (paired with useridType)',
},
itemsPerPage: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Items per page',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: '1-based page number',
},
includeMetadata: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include paging metadata in the response',
},
includeCanceledTrips: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include canceled trips in the result set',
},
createdAfterDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Only trips created after this date (YYYY-MM-DD)',
},
createdBeforeDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Only trips created before this date (YYYY-MM-DD)',
},
lastModifiedDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Only trips modified on/after this date (YYYY-MM-DD)',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const query = buildListQuery({
startDate: params.startDate,
endDate: params.endDate,
bookingType: params.bookingType,
userid_type: params.useridType,
userid_value: params.useridValue,
ItemsPerPage: params.itemsPerPage,
Page: params.page,
includeMetadata: params.includeMetadata,
includeCanceledTrips: params.includeCanceledTrips,
createdAfterDate: params.createdAfterDate,
createdBeforeDate: params.createdBeforeDate,
lastModifiedDate: params.lastModifiedDate,
})
return {
...baseProxyBody(params),
path: `/api/travel/trip/v1.1`,
method: 'GET',
...(Object.keys(query).length > 0 ? { query } : {}),
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Trips list payload (Itinerary v1.1 ConnectResponse)',
properties: {
Metadata: {
type: 'json',
description: 'Paging metadata (when includeMetadata=true)',
optional: true,
properties: {
Paging: {
type: 'json',
description: 'Pagination details',
optional: true,
properties: {
TotalPages: { type: 'number', description: 'Total pages', optional: true },
TotalItems: { type: 'number', description: 'Total items', optional: true },
Page: { type: 'number', description: 'Current page', optional: true },
ItemsPerPage: { type: 'number', description: 'Items per page', optional: true },
PreviousPageURL: {
type: 'string',
description: 'Previous page URL',
optional: true,
},
NextPageURL: { type: 'string', description: 'Next page URL', optional: true },
},
},
},
},
ItineraryInfoList: {
type: 'array',
description: 'List of itinerary summary records',
optional: true,
items: {
type: 'json',
properties: {
ItinLocator: {
type: 'string',
description: 'Trip locator (trip ID)',
optional: true,
},
ClientLocator: { type: 'string', description: 'Client trip locator', optional: true },
ItinSourceName: {
type: 'string',
description: 'Booking source name',
optional: true,
},
BookedVia: { type: 'string', description: 'Booking channel', optional: true },
TripName: { type: 'string', description: 'Trip name', optional: true },
Status: { type: 'string', description: 'Trip status', optional: true },
Description: { type: 'string', description: 'Trip description', optional: true },
StartDateUtc: { type: 'string', description: 'Start (UTC)', optional: true },
EndDateUtc: { type: 'string', description: 'End (UTC)', optional: true },
StartDateLocal: { type: 'string', description: 'Start (local)', optional: true },
EndDateLocal: { type: 'string', description: 'End (local)', optional: true },
DateCreatedUtc: { type: 'string', description: 'Created (UTC)', optional: true },
DateModifiedUtc: { type: 'string', description: 'Modified (UTC)', optional: true },
DateBookedLocal: { type: 'string', description: 'Booked (local)', optional: true },
UserLoginId: { type: 'string', description: 'Trip owner login id', optional: true },
BookedByFirstName: {
type: 'string',
description: 'Booker first name',
optional: true,
},
BookedByLastName: { type: 'string', description: 'Booker last name', optional: true },
IsPersonal: { type: 'boolean', description: 'Personal trip flag', optional: true },
},
},
},
},
},
},
}
@@ -0,0 +1,220 @@
import type { ListListItemsParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
buildListQuery,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listListItemsTool: ToolConfig<ListListItemsParams, SapConcurProxyResponse> = {
id: 'sap_concur_list_list_items',
name: 'SAP Concur List List Items',
description:
'List the top-level items (children) for a custom list (GET /list/v4/lists/{listId}/children).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
listId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'List ID',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number (1-based; page size is fixed at 100)',
},
sortBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort field: value or shortCode',
},
sortDirection: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort direction: asc or desc',
},
hasChildren: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include only items that have children',
},
isDeleted: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include deleted items',
},
shortCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by short code',
},
value: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by display value',
},
shortCodeOrValue: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by short code OR value',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const listId = trimRequired(params.listId, 'listId')
return {
...baseProxyBody(params),
path: `/list/v4/lists/${encodeURIComponent(listId)}/children`,
method: 'GET',
query: buildListQuery({
page: params.page,
sortBy: params.sortBy,
sortDirection: params.sortDirection,
hasChildren: params.hasChildren,
isDeleted: params.isDeleted,
shortCode: params.shortCode,
value: params.value,
shortCodeOrValue: params.shortCodeOrValue,
}),
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Paginated list items collection',
properties: {
content: {
type: 'array',
description: 'List items in the current page',
optional: true,
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'List item UUID', optional: true },
code: {
type: 'string',
description: 'Long code format for the item',
optional: true,
},
shortCode: { type: 'string', description: 'Short code identifier', optional: true },
value: { type: 'string', description: 'Display value of the item', optional: true },
parentId: {
type: 'string',
description: 'Parent item UUID (omitted for first-level items)',
optional: true,
},
level: {
type: 'number',
description: 'Hierarchy level (1 for root items)',
optional: true,
},
isDeleted: {
type: 'boolean',
description: 'Deletion status across all containing lists',
optional: true,
},
lists: {
type: 'array',
description: 'Lists containing this item',
optional: true,
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'List UUID', optional: true },
hasChildren: {
type: 'boolean',
description: 'Whether this item has children in the list',
optional: true,
},
},
},
},
},
},
},
page: {
type: 'json',
description: 'Pagination metadata',
optional: true,
properties: {
number: { type: 'number', description: 'Current page number', optional: true },
size: { type: 'number', description: 'Items per page', optional: true },
totalElements: { type: 'number', description: 'Total item count', optional: true },
totalPages: { type: 'number', description: 'Total page count', optional: true },
},
},
links: {
type: 'array',
description: 'Navigation links (next, previous, first, last)',
optional: true,
items: {
type: 'json',
properties: {
rel: { type: 'string', description: 'Link relation', optional: true },
href: { type: 'string', description: 'Link URL', optional: true },
},
},
},
},
},
},
}
+209
View File
@@ -0,0 +1,209 @@
import type { ListListsParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
buildListQuery,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listListsTool: ToolConfig<ListListsParams, SapConcurProxyResponse> = {
id: 'sap_concur_list_lists',
name: 'SAP Concur List Lists',
description: 'List custom lists (GET /list/v4/lists).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number (1-based; page size is fixed at 100)',
},
sortBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort field: name, levelcount, or listcategory',
},
sortDirection: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort direction: asc or desc',
},
value: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by list name',
},
categoryType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by category type (mapped to category.type query param)',
},
isDeleted: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include deleted lists',
},
levelCount: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Filter by number of levels',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
...baseProxyBody(params),
path: `/list/v4/lists`,
method: 'GET',
query: buildListQuery({
page: params.page,
sortBy: params.sortBy,
sortDirection: params.sortDirection,
value: params.value,
'category.type': params.categoryType,
isDeleted: params.isDeleted,
levelCount: params.levelCount,
}),
}),
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Paginated lists collection',
properties: {
content: {
type: 'array',
description: 'Lists in the current page',
optional: true,
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'List UUID', optional: true },
value: { type: 'string', description: 'Name of the list', optional: true },
levelCount: {
type: 'number',
description: 'Number of levels in the list',
optional: true,
},
searchCriteria: {
type: 'string',
description: 'Search attribute (TEXT or CODE)',
optional: true,
},
displayFormat: {
type: 'string',
description: 'Display order ((CODE) TEXT or TEXT (CODE))',
optional: true,
},
category: {
type: 'json',
description: 'List category',
optional: true,
properties: {
id: { type: 'string', description: 'Category UUID', optional: true },
type: { type: 'string', description: 'Category type', optional: true },
},
},
isReadOnly: {
type: 'boolean',
description: 'Whether the list is read-only',
optional: true,
},
isDeleted: {
type: 'boolean',
description: 'Whether the list has been deleted',
optional: true,
},
managedBy: {
type: 'string',
description: 'Managing application or service identifier',
optional: true,
},
externalThreshold: {
type: 'number',
description: 'Threshold from where the level starts being external',
optional: true,
},
},
},
},
page: {
type: 'json',
description: 'Pagination metadata',
optional: true,
properties: {
number: { type: 'number', description: 'Current page number', optional: true },
size: { type: 'number', description: 'Items per page', optional: true },
totalElements: { type: 'number', description: 'Total item count', optional: true },
totalPages: { type: 'number', description: 'Total page count', optional: true },
},
},
links: {
type: 'array',
description: 'Navigation links (next, previous, first, last)',
optional: true,
items: {
type: 'json',
properties: {
rel: { type: 'string', description: 'Link relation', optional: true },
href: { type: 'string', description: 'Link URL', optional: true },
},
},
},
},
},
},
}
+107
View File
@@ -0,0 +1,107 @@
import type { ListReceiptsParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listReceiptsTool: ToolConfig<ListReceiptsParams, SapConcurProxyResponse> = {
id: 'sap_concur_list_receipts',
name: 'SAP Concur List Receipts',
description: 'List receipts for a user (GET /receipts/v4/users/{userId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
return {
...baseProxyBody(params),
path: `/receipts/v4/users/${encodeURIComponent(userId)}`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'array',
description: 'Array of e-receipt objects',
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'Receipt id', optional: true },
userId: { type: 'string', description: 'Owner user UUID', optional: true },
dateTimeReceived: {
type: 'string',
description: 'Timestamp the receipt was received',
optional: true,
},
receipt: { type: 'json', description: 'Structured receipt data', optional: true },
image: { type: 'string', description: 'Receipt image URL or reference', optional: true },
validationSchema: {
type: 'string',
description: 'Validation schema URI',
optional: true,
},
self: { type: 'string', description: 'Self URL', optional: true },
template: { type: 'string', description: 'Template URL', optional: true },
},
},
},
},
}
@@ -0,0 +1,154 @@
import type { ListReportCommentsParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
buildListQuery,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listReportCommentsTool: ToolConfig<ListReportCommentsParams, SapConcurProxyResponse> =
{
id: 'sap_concur_list_report_comments',
name: 'SAP Concur List Report Comments',
description:
'List comments on a report (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/comments).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: TRAVELER or PROXY',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
includeAllComments: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include comments from all expenses in the report (default false)',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}/comments`,
method: 'GET',
query: buildListQuery({ includeAllComments: params.includeAllComments }),
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'array',
description: 'Array of report comment entries',
items: {
type: 'json',
properties: {
comment: { type: 'string', description: 'Comment text' },
creationDate: {
type: 'string',
description: 'Comment creation timestamp (ISO 8601)',
},
expenseId: { type: 'string', description: 'Related expense entry ID' },
isAuditorComment: {
type: 'boolean',
description: 'Whether the comment was added by an auditor',
},
isLatest: {
type: 'boolean',
description: 'Whether this is the latest comment',
},
createdForEmployeeId: {
type: 'string',
description: 'Employee ID the comment was created for',
},
author: {
type: 'json',
description: 'Comment author',
properties: {
employeeId: { type: 'string', description: 'Employee identifier' },
employeeUuid: { type: 'string', description: 'Employee UUID' },
},
},
createdForEmployee: {
type: 'json',
description: 'Employee the comment was created for',
properties: {
employeeId: { type: 'string', description: 'Employee identifier' },
employeeUuid: { type: 'string', description: 'Employee UUID' },
},
},
stepInstanceId: {
type: 'string',
description: 'Workflow step instance identifier',
optional: true,
},
},
},
},
},
}
@@ -0,0 +1,175 @@
import type { ListReportsToApproveParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
buildListQuery,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listReportsToApproveTool: ToolConfig<
ListReportsToApproveParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_list_reports_to_approve',
name: 'SAP Concur List Reports To Approve',
description:
'List expense reports awaiting approval (GET /expensereports/v4/users/{userId}/context/MANAGER/reportsToApprove).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Manager user UUID',
},
contextType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Access context: must be MANAGER (default)',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Report field name to sort by (e.g., reportDate)',
},
order: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort direction: asc or desc',
},
includeDelegateApprovals: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to include reports the caller can approve as a delegate',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = (params.contextType ?? 'MANAGER').trim() || 'MANAGER'
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reportsToApprove`,
method: 'GET',
query: buildListQuery({
sort: params.sort,
order: params.order,
includeDelegateApprovals: params.includeDelegateApprovals,
}),
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'array',
description: 'Array of reports awaiting approval (ReportToApprove[])',
items: {
type: 'json',
properties: {
reportId: { type: 'string', description: 'Unique report identifier' },
name: { type: 'string', description: 'Report name' },
reportDate: { type: 'string', description: 'Report date (YYYY-MM-DD)', optional: true },
reportNumber: {
type: 'string',
description: 'User-friendly report number',
optional: true,
},
submitDate: {
type: 'string',
description: 'Submission timestamp (ISO 8601 UTC)',
optional: true,
},
approver: {
type: 'json',
description: 'Approver employee { employeeId, employeeUuid }',
optional: true,
},
employee: {
type: 'json',
description: 'Report owner employee { employeeId, employeeUuid }',
optional: true,
},
amountDueEmployee: {
type: 'json',
description: 'Amount due employee { value, currencyCode }',
optional: true,
},
claimedAmount: {
type: 'json',
description: 'Total claimed amount { value, currencyCode }',
optional: true,
},
totalApprovedAmount: {
type: 'json',
description: 'Total approved amount { value, currencyCode }',
optional: true,
},
hasExceptions: {
type: 'boolean',
description: 'Whether the report has exceptions',
optional: true,
},
reportType: {
type: 'string',
description: 'Report creation method identifier',
optional: true,
},
links: { type: 'array', description: 'HATEOAS links', optional: true },
},
},
},
},
}
@@ -0,0 +1,200 @@
import type {
ListTravelProfilesSummaryParams,
SapConcurProxyResponse,
} from '@/tools/sap_concur/types'
import {
baseProxyBody,
buildListQuery,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listTravelProfilesSummaryTool: ToolConfig<
ListTravelProfilesSummaryParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_list_travel_profiles_summary',
name: 'SAP Concur List Travel Profiles Summary',
description:
'List travel profile summaries (GET /api/travelprofile/v2.0/summary). LastModifiedDate is required by Concur.',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
lastModifiedDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Required UTC datetime in YYYY-MM-DDThh:mm:ss format',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: '1-based page number',
},
itemsPerPage: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Items per page (max 200)',
},
travelConfigs: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated travel configuration ids',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const lastModifiedDate = trimRequired(params.lastModifiedDate, 'lastModifiedDate')
const query = buildListQuery({
LastModifiedDate: lastModifiedDate,
Page: params.page,
ItemsPerPage: params.itemsPerPage,
travelConfigs: params.travelConfigs,
})
return {
...baseProxyBody(params),
path: '/api/travelprofile/v2.0/summary',
method: 'GET',
query,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Travel profile summary list payload (Concur returns XML mapped to JSON)',
properties: {
Metadata: {
type: 'json',
description: 'Paging metadata',
optional: true,
properties: {
Paging: {
type: 'json',
description: 'Pagination details',
optional: true,
properties: {
TotalPages: {
type: 'number',
description: 'Total number of pages',
optional: true,
},
TotalItems: {
type: 'number',
description: 'Total number of items',
optional: true,
},
Page: {
type: 'number',
description: 'Current page',
optional: true,
},
ItemsPerPage: {
type: 'number',
description: 'Items per page',
optional: true,
},
PreviousPageURL: {
type: 'string',
description: 'URL to the previous page',
optional: true,
},
NextPageURL: {
type: 'string',
description: 'URL to the next page',
optional: true,
},
},
},
},
},
Data: {
type: 'array',
description: 'Array of travel profile summaries',
optional: true,
items: {
type: 'json',
properties: {
Status: { type: 'string', description: 'Status (Active/Inactive)', optional: true },
LoginID: { type: 'string', description: 'Login identifier', optional: true },
XmlProfileSyncID: {
type: 'string',
description: 'XML profile sync identifier',
optional: true,
},
ProfileLastModifiedUTC: {
type: 'string',
description: 'Last modified timestamp (UTC)',
optional: true,
},
RuleClass: {
type: 'string',
description: 'Travel rule class assigned to the profile',
optional: true,
},
TravelConfigID: {
type: 'string',
description: 'Travel configuration identifier',
optional: true,
},
UUID: { type: 'string', description: 'Profile UUID', optional: true },
EmployeeID: { type: 'string', description: 'Employee ID', optional: true },
CompanyID: { type: 'string', description: 'Company ID', optional: true },
},
},
},
},
},
},
}
@@ -0,0 +1,118 @@
import type {
ListTravelRequestCommentsParams,
SapConcurProxyResponse,
} from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listTravelRequestCommentsTool: ToolConfig<
ListTravelRequestCommentsParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_list_travel_request_comments',
name: 'SAP Concur List Travel Request Comments',
description:
'List comments on a travel request (GET /travelrequest/v4/requests/{requestUuid}/comments).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
requestUuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Travel request UUID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const requestUuid = trimRequired(params.requestUuid, 'requestUuid')
return {
...baseProxyBody(params),
path: `/travelrequest/v4/requests/${encodeURIComponent(requestUuid)}/comments`,
method: 'GET',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'array',
description: 'Array of comment entries',
items: {
type: 'json',
properties: {
author: {
type: 'json',
description: 'Comment author',
optional: true,
properties: {
firstName: { type: 'string', description: 'Author first name', optional: true },
lastName: { type: 'string', description: 'Author last name', optional: true },
},
},
creationDateTime: {
type: 'string',
description: 'Comment creation timestamp (ISO 8601)',
optional: true,
},
isLatest: {
type: 'boolean',
description: 'Whether this is the latest comment',
optional: true,
},
value: { type: 'string', description: 'Comment text', optional: true },
},
},
},
},
}
@@ -0,0 +1,349 @@
import type { ListTravelRequestsParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
buildListQuery,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listTravelRequestsTool: ToolConfig<ListTravelRequestsParams, SapConcurProxyResponse> =
{
id: 'sap_concur_list_travel_requests',
name: 'SAP Concur List Travel Requests',
description: 'List travel requests (GET /travelrequest/v4/requests).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
view: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'View filter (e.g., ALL, ACTIVE, PENDING, TOAPPROVE)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Max number of results per page',
},
start: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page start cursor (offset)',
},
userId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by Concur user UUID',
},
approvedBefore: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 date — return requests approved before this date',
},
approvedAfter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 date — return requests approved after this date',
},
modifiedBefore: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 date — return requests modified before this date',
},
modifiedAfter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 date — return requests modified after this date',
},
sortField: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Field to sort by: startDate, approvalStatus, or requestId (default startDate)',
},
sortOrder: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort order: ASC or DESC (default DESC)',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
...baseProxyBody(params),
path: `/travelrequest/v4/requests`,
method: 'GET',
query: buildListQuery({
view: params.view,
limit: params.limit,
start: params.start,
userId: params.userId,
approvedBefore: params.approvedBefore,
approvedAfter: params.approvedAfter,
modifiedBefore: params.modifiedBefore,
modifiedAfter: params.modifiedAfter,
sortField: params.sortField,
sortOrder: params.sortOrder ? params.sortOrder.toUpperCase() : undefined,
}),
}),
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Travel requests list payload',
properties: {
data: {
type: 'array',
description: 'Array of travel request summaries',
optional: true,
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'Travel request UUID', optional: true },
href: { type: 'string', description: 'Resource hyperlink', optional: true },
requestId: {
type: 'string',
description: 'Public-facing request ID',
optional: true,
},
name: { type: 'string', description: 'Request name', optional: true },
businessPurpose: {
type: 'string',
description: 'Business purpose',
optional: true,
},
comment: { type: 'string', description: 'Last attached comment', optional: true },
creationDate: {
type: 'string',
description: 'Creation timestamp',
optional: true,
},
submitDate: {
type: 'string',
description: 'Last submission timestamp',
optional: true,
},
startDate: {
type: 'string',
description: 'Trip start date (ISO 8601)',
optional: true,
},
endDate: {
type: 'string',
description: 'Trip end date (ISO 8601)',
optional: true,
},
startTime: {
type: 'string',
description: 'Trip start time (HH:mm)',
optional: true,
},
approved: {
type: 'boolean',
description: 'Whether the request is approved',
optional: true,
},
pendingApproval: {
type: 'boolean',
description: 'Pending approval flag',
optional: true,
},
closed: { type: 'boolean', description: 'Closed flag', optional: true },
everSentBack: {
type: 'boolean',
description: 'Ever-sent-back flag',
optional: true,
},
canceledPostApproval: {
type: 'boolean',
description: 'Canceled after approval flag',
optional: true,
},
approvalStatus: {
type: 'json',
description: 'Approval status',
optional: true,
properties: {
code: {
type: 'string',
description:
'Status code (NOT_SUBMITTED, SUBMITTED, APPROVED, CANCELED, SENTBACK)',
optional: true,
},
name: {
type: 'string',
description: 'Localized status name',
optional: true,
},
},
},
owner: {
type: 'json',
description: 'Travel request owner',
optional: true,
properties: {
id: { type: 'string', description: 'User UUID', optional: true },
firstName: {
type: 'string',
description: 'Owner first name',
optional: true,
},
lastName: {
type: 'string',
description: 'Owner last name',
optional: true,
},
},
},
approver: {
type: 'json',
description: 'Approver assigned to the request',
optional: true,
properties: {
id: { type: 'string', description: 'User UUID', optional: true },
firstName: {
type: 'string',
description: 'Approver first name',
optional: true,
},
lastName: {
type: 'string',
description: 'Approver last name',
optional: true,
},
},
},
type: {
type: 'json',
description: 'Request type',
optional: true,
properties: {
code: { type: 'string', description: 'Request type code', optional: true },
label: {
type: 'string',
description: 'Request type label',
optional: true,
},
},
},
totalApprovedAmount: {
type: 'json',
description: 'Total approved amount',
optional: true,
properties: {
value: { type: 'number', description: 'Amount value', optional: true },
currency: {
type: 'string',
description: 'Currency code',
optional: true,
},
},
},
totalPostedAmount: {
type: 'json',
description: 'Total posted amount',
optional: true,
properties: {
value: { type: 'number', description: 'Amount value', optional: true },
currency: {
type: 'string',
description: 'Currency code',
optional: true,
},
},
},
totalRemainingAmount: {
type: 'json',
description: 'Total remaining amount',
optional: true,
properties: {
value: { type: 'number', description: 'Amount value', optional: true },
currency: {
type: 'string',
description: 'Currency code',
optional: true,
},
},
},
expenses: {
type: 'array',
description: 'Resource links to expected expenses',
optional: true,
items: { type: 'json' },
},
},
},
},
operations: {
type: 'array',
description: 'Pagination links (next, prev, first, last)',
optional: true,
items: {
type: 'json',
properties: {
rel: { type: 'string', description: 'Link relation', optional: true },
href: { type: 'string', description: 'Link target', optional: true },
method: { type: 'string', description: 'HTTP method', optional: true },
name: { type: 'string', description: 'Link name', optional: true },
},
},
},
},
},
},
}
+109
View File
@@ -0,0 +1,109 @@
import type { ListUsersParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
buildListQuery,
SAP_CONCUR_PROXY_URL,
scimListResponseOutputProperties,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const listUsersTool: ToolConfig<ListUsersParams, SapConcurProxyResponse> = {
id: 'sap_concur_list_users',
name: 'SAP Concur List Users',
description: 'List Concur user identities (GET /profile/identity/v4.1/Users).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
count: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Max number of users to return (default 100, max 1000)',
},
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'SCIM v4.1 pagination cursor returned by a prior call',
},
attributes: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of attributes to include in the response',
},
excludedAttributes: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of attributes to exclude from the response',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
...baseProxyBody(params),
path: `/profile/identity/v4.1/Users`,
method: 'GET',
query: buildListQuery({
count: params.count,
cursor: params.cursor,
attributes: params.attributes,
excludedAttributes: params.excludedAttributes,
}),
}),
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'SCIM ListResponse with Resources array',
properties: scimListResponseOutputProperties,
},
},
}
@@ -0,0 +1,151 @@
import type { MoveTravelRequestParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const moveTravelRequestTool: ToolConfig<MoveTravelRequestParams, SapConcurProxyResponse> = {
id: 'sap_concur_move_travel_request',
name: 'SAP Concur Move Travel Request',
description:
'Move a travel request through workflow (POST /travelrequest/v4/requests/{requestUuid}/{action}). Valid actions: submit, recall, cancel, approve, sendback, close, reopen.',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
requestUuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Travel request UUID',
},
action: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Workflow action: submit, recall, cancel, approve, sendback, close, reopen',
},
userId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional Concur user UUID — required when impersonating another user',
},
body: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Optional payload (e.g., { "comment": "..." })',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const requestUuid = trimRequired(params.requestUuid, 'requestUuid')
const action = trimRequired(params.action, 'action')
const query: Record<string, string> = {}
if (params.userId) query.userId = params.userId
return {
...baseProxyBody(params),
path: `/travelrequest/v4/requests/${encodeURIComponent(requestUuid)}/${encodeURIComponent(action)}`,
method: 'POST',
body: params.body ?? {},
query: Object.keys(query).length > 0 ? query : undefined,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Workflow transition response payload',
properties: {
id: { type: 'string', description: 'Travel request UUID', optional: true },
href: { type: 'string', description: 'Resource hyperlink', optional: true },
approvalStatus: {
type: 'json',
description: 'Approval status after the workflow transition',
optional: true,
properties: {
code: {
type: 'string',
description: 'Status code (NOT_SUBMITTED, SUBMITTED, APPROVED, CANCELED, SENTBACK)',
optional: true,
},
name: { type: 'string', description: 'Localized status name', optional: true },
},
},
approver: {
type: 'json',
description: 'Approver assigned after the transition',
optional: true,
properties: {
id: { type: 'string', description: 'User UUID', optional: true },
firstName: { type: 'string', description: 'Approver first name', optional: true },
lastName: { type: 'string', description: 'Approver last name', optional: true },
},
},
operations: {
type: 'array',
description: 'Available follow-up workflow actions',
optional: true,
items: {
type: 'json',
properties: {
rel: { type: 'string', description: 'Link relation', optional: true },
href: { type: 'string', description: 'Link target', optional: true },
method: { type: 'string', description: 'HTTP method', optional: true },
name: { type: 'string', description: 'Link name', optional: true },
},
},
},
},
},
},
}
@@ -0,0 +1,109 @@
import type { RecallExpenseReportParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const recallExpenseReportTool: ToolConfig<
RecallExpenseReportParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_recall_expense_report',
name: 'SAP Concur Recall Expense Report',
description:
'Recall a submitted expense report (PATCH /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/recall — supported contexts: TRAVELER, PROXY). No request body is required.',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID who owns the report',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: TRAVELER or PROXY',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID to recall',
},
body: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description:
"Optional body. Concur docs don't define a payload for this action; pass an empty object if uncertain.",
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}/recall`,
method: 'PATCH',
body: params.body ?? {},
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: { type: 'json', description: 'Empty (204 No Content)' },
},
}
@@ -0,0 +1,110 @@
import type { RemoveAllAttendeesParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const removeAllAttendeesTool: ToolConfig<RemoveAllAttendeesParams, SapConcurProxyResponse> =
{
id: 'sap_concur_remove_all_attendees',
name: 'SAP Concur Remove All Attendees',
description:
'Remove all attendees from an expense (DELETE /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/expenses/{expenseId}/attendees).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: TRAVELER or PROXY',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
expenseId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
const expenseId = trimRequired(params.expenseId, 'expenseId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}/expenses/${encodeURIComponent(expenseId)}/attendees`,
method: 'DELETE',
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Empty response body (Concur returns 204 No Content)',
properties: {},
},
},
}
@@ -0,0 +1,227 @@
import type { SapConcurProxyResponse, SearchLocationsParams } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const searchLocationsTool: ToolConfig<SearchLocationsParams, SapConcurProxyResponse> = {
id: 'sap_concur_search_locations',
name: 'SAP Concur Search Locations',
description: 'Search Concur location reference data (GET /localities/v5/locations).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
searchText: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Free-text query (city, airport, landmark, etc.)',
},
locCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'IATA / location code',
},
locationNameId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Concur internal location name ID (UUID)',
},
locationNameKey: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Concur internal numeric location name key',
},
countryCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: '2-letter ISO 3166-1 country code',
},
subdivisionCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-2:2007 country subdivision (e.g. US-WA)',
},
adminRegionId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Administrative region ID',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const query: Record<string, string | number> = {}
if (params.searchText) query.searchText = params.searchText
if (params.locCode) query.locCode = params.locCode
if (params.locationNameId) query.locationNameId = params.locationNameId
if (params.locationNameKey !== undefined && params.locationNameKey !== null)
query.locationNameKey = params.locationNameKey
if (
query.searchText === undefined &&
query.locCode === undefined &&
query.locationNameId === undefined &&
query.locationNameKey === undefined
) {
throw new Error(
'search_locations requires at least one of: searchText, locCode, locationNameId, locationNameKey'
)
}
if (params.countryCode) query.countryCode = params.countryCode
if (params.subdivisionCode) query.subdivisionCode = params.subdivisionCode
if (params.adminRegionId) query.adminRegionId = params.adminRegionId
return {
...baseProxyBody(params),
path: '/localities/v5/locations',
method: 'GET',
query,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Localities v5 search response',
properties: {
locations: {
type: 'array',
description: 'Array of matching Location records',
optional: true,
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'Location ID (UUID)', optional: true },
code: { type: 'string', description: 'IATA / location code', optional: true },
legacyKey: {
type: 'number',
description: 'Legacy numeric location key',
optional: true,
},
timeZoneOffset: {
type: 'string',
description: 'IANA timezone or UTC offset',
optional: true,
},
active: {
type: 'boolean',
description: 'Whether the location is active',
optional: true,
},
point: {
type: 'json',
description: 'Geographic coordinates',
optional: true,
properties: {
latitude: { type: 'number', description: 'Latitude', optional: true },
longitude: { type: 'number', description: 'Longitude', optional: true },
},
},
names: {
type: 'array',
description: 'Localized location names',
optional: true,
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'Name ID', optional: true },
key: { type: 'number', description: 'Numeric name key', optional: true },
locale: { type: 'string', description: 'Locale tag', optional: true },
name: { type: 'string', description: 'Display name', optional: true },
},
},
},
administrativeRegion: {
type: 'json',
description: 'Administrative region (e.g., metro area)',
optional: true,
properties: {
id: { type: 'string', description: 'Region ID', optional: true },
name: { type: 'string', description: 'Region name', optional: true },
},
},
country: {
type: 'json',
description: 'Country reference',
optional: true,
properties: {
id: { type: 'string', description: 'Country ID', optional: true },
code: { type: 'string', description: 'ISO country code', optional: true },
name: { type: 'string', description: 'Country name', optional: true },
},
},
subDivision: {
type: 'json',
description: 'Country subdivision (state/province)',
optional: true,
properties: {
id: { type: 'string', description: 'Subdivision ID', optional: true },
code: { type: 'string', description: 'ISO subdivision code', optional: true },
name: { type: 'string', description: 'Subdivision name', optional: true },
},
},
links: {
type: 'array',
description: 'HATEOAS links',
optional: true,
items: { type: 'json' },
},
},
},
},
},
},
},
}
+87
View File
@@ -0,0 +1,87 @@
import type { SapConcurProxyResponse, SearchUsersParams } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
scimListResponseOutputProperties,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const searchUsersTool: ToolConfig<SearchUsersParams, SapConcurProxyResponse> = {
id: 'sap_concur_search_users',
name: 'SAP Concur Search Users',
description:
'Search users via SCIM .search endpoint (POST /profile/identity/v4.1/Users/.search).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'SCIM search request payload ({ schemas, attributes, filter, count, startIndex })',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
...baseProxyBody(params),
path: `/profile/identity/v4.1/Users/.search`,
method: 'POST',
body: params.body,
}),
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'SCIM search ListResponse',
properties: scimListResponseOutputProperties,
},
},
}
@@ -0,0 +1,95 @@
import type { SapConcurProxyResponse, SendBackExpenseReportParams } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const sendBackExpenseReportTool: ToolConfig<
SendBackExpenseReportParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_send_back_expense_report',
name: 'SAP Concur Send Back Expense Report',
description:
'Send back an expense report to the employee (PATCH /expensereports/v4/reports/{reportId}/sendBack). Required body field: comment.',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID to send back',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Request body — `comment` is required by Concur (e.g., { "comment": "Missing receipt" }). Optional fields: `expectedStepCode`, `expectedStepSequence`.',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const reportId = trimRequired(params.reportId, 'reportId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/reports/${encodeURIComponent(reportId)}/sendBack`,
method: 'PATCH',
body: params.body,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: { type: 'json', description: 'Empty (204 No Content)' },
},
}
@@ -0,0 +1,102 @@
import type { SapConcurProxyResponse, SubmitExpenseReportParams } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const submitExpenseReportTool: ToolConfig<
SubmitExpenseReportParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_submit_expense_report',
name: 'SAP Concur Submit Expense Report',
description:
'Submit an expense report into the workflow via Expense Report v4 (PATCH /expensereports/v4/users/{userId}/reports/{reportId}/submit).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID who owns the report',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID to submit',
},
body: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description:
"Optional body. Concur docs don't define a payload for this action; pass an empty object if uncertain.",
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const reportId = trimRequired(params.reportId, 'reportId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/reports/${encodeURIComponent(reportId)}/submit`,
method: 'PATCH',
body: params.body ?? {},
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: { type: 'json', description: 'Empty (204 No Content)' },
},
}
+530
View File
@@ -0,0 +1,530 @@
import type { ToolResponse } from '@/tools/types'
export type SapConcurDatacenter =
| 'us.api.concursolutions.com'
| 'us2.api.concursolutions.com'
| 'eu.api.concursolutions.com'
| 'eu2.api.concursolutions.com'
| 'cn.api.concursolutions.com'
| 'emea.api.concursolutions.com'
export type SapConcurGrantType = 'client_credentials' | 'password'
export interface SapConcurBaseParams {
datacenter?: SapConcurDatacenter
grantType?: SapConcurGrantType
clientId: string
clientSecret: string
username?: string
password?: string
companyUuid?: string
}
interface ProxyOutput {
status: number
data: unknown
}
export interface SapConcurProxyResponse extends ToolResponse {
output: ProxyOutput
}
export interface ListExpenseReportsParams extends SapConcurBaseParams {
user?: string
submitDateBefore?: string
submitDateAfter?: string
paidDateBefore?: string
paidDateAfter?: string
modifiedDateBefore?: string
modifiedDateAfter?: string
createDateBefore?: string
createDateAfter?: string
approvalStatusCode?: string
paymentStatusCode?: string
currencyCode?: string
approverLoginID?: string
limit?: number
offset?: string
}
export interface GetExpenseReportParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'MANAGER' | 'PROCESSOR' | 'PROXY'
reportId: string
}
export interface ListReportsToApproveParams extends SapConcurBaseParams {
userId: string
contextType?: 'MANAGER'
sort?: string
order?: string
includeDelegateApprovals?: boolean
}
export interface CreateExpenseReportParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'PROXY'
body: Record<string, unknown> | string
}
export interface UpdateExpenseReportParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'PROXY'
reportId: string
body: Record<string, unknown> | string
}
export interface DeleteExpenseReportParams extends SapConcurBaseParams {
reportId: string
}
export interface SubmitExpenseReportParams extends SapConcurBaseParams {
userId: string
reportId: string
body?: Record<string, unknown> | string
}
export interface RecallExpenseReportParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'PROXY'
reportId: string
body?: Record<string, unknown> | string
}
export interface ApproveExpenseReportParams extends SapConcurBaseParams {
reportId: string
body: Record<string, unknown> | string
}
export interface SendBackExpenseReportParams extends SapConcurBaseParams {
reportId: string
body: Record<string, unknown> | string
}
export interface ListExpensesParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'MANAGER'
reportId: string
}
export interface GetExpenseParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'MANAGER' | 'PROXY'
reportId: string
expenseId: string
}
export interface GetItemizationsParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'MANAGER'
reportId: string
expenseId: string
}
export interface UpdateExpenseParams extends SapConcurBaseParams {
reportId: string
expenseId: string
body: Record<string, unknown> | string
}
export interface DeleteExpenseParams extends SapConcurBaseParams {
reportId: string
expenseId: string
}
export interface ListAllocationsParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'MANAGER' | 'PROXY'
reportId: string
expenseId: string
}
export interface GetAllocationParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'MANAGER' | 'PROXY'
reportId: string
allocationId: string
}
export interface UpdateAllocationParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'PROXY'
reportId: string
allocationId: string
body: Record<string, unknown> | string
}
export interface ListAttendeeAssociationsParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'PROXY'
reportId: string
expenseId: string
}
export interface AssociateAttendeesParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'PROXY'
reportId: string
expenseId: string
body: Record<string, unknown> | string
}
export interface RemoveAllAttendeesParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'PROXY'
reportId: string
expenseId: string
}
export interface ListReportCommentsParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'PROXY'
reportId: string
includeAllComments?: boolean
}
export interface CreateReportCommentParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'PROXY'
reportId: string
comment: string
}
export interface ListExceptionsParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER' | 'MANAGER' | 'PROXY'
reportId: string
}
export interface CreateQuickExpenseParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER'
body: Record<string, unknown> | string
}
export interface ListReceiptsParams extends SapConcurBaseParams {
userId: string
}
export interface GetReceiptParams extends SapConcurBaseParams {
receiptId: string
}
export interface GetReceiptStatusParams extends SapConcurBaseParams {
receiptId: string
}
export interface ListTravelRequestsParams extends SapConcurBaseParams {
view?: string
limit?: number
start?: number
userId?: string
approvedBefore?: string
approvedAfter?: string
modifiedBefore?: string
modifiedAfter?: string
sortField?: string
sortOrder?: 'asc' | 'desc'
}
export interface GetTravelRequestParams extends SapConcurBaseParams {
requestUuid: string
userId?: string
}
export interface CreateTravelRequestParams extends SapConcurBaseParams {
userId?: string
body: Record<string, unknown> | string
}
export interface UpdateTravelRequestParams extends SapConcurBaseParams {
requestUuid: string
body: Record<string, unknown> | string
}
export interface DeleteTravelRequestParams extends SapConcurBaseParams {
requestUuid: string
userId?: string
}
export interface MoveTravelRequestParams extends SapConcurBaseParams {
requestUuid: string
action: string
userId?: string
body?: Record<string, unknown> | string
}
export interface ListTravelRequestCommentsParams extends SapConcurBaseParams {
requestUuid: string
}
export interface GetRequestCashAdvanceParams extends SapConcurBaseParams {
cashAdvanceUuid: string
}
export interface CreateExpectedExpenseParams extends SapConcurBaseParams {
requestUuid: string
userId?: string
body: Record<string, unknown> | string
}
export interface ListExpectedExpensesParams extends SapConcurBaseParams {
requestUuid: string
userId?: string
}
export interface GetExpectedExpenseParams extends SapConcurBaseParams {
expenseUuid: string
userId?: string
}
export interface UpdateExpectedExpenseParams extends SapConcurBaseParams {
expenseUuid: string
userId?: string
body: Record<string, unknown> | string
}
export interface DeleteExpectedExpenseParams extends SapConcurBaseParams {
expenseUuid: string
userId?: string
}
export interface GetCashAdvanceParams extends SapConcurBaseParams {
cashAdvanceId: string
}
export interface CreateCashAdvanceParams extends SapConcurBaseParams {
body: Record<string, unknown> | string
}
export interface IssueCashAdvanceParams extends SapConcurBaseParams {
cashAdvanceId: string
body?: Record<string, unknown> | string
}
export interface ListItinerariesParams extends SapConcurBaseParams {
startDate?: string
endDate?: string
bookingType?: string
useridType?: string
useridValue?: string
itemsPerPage?: number
page?: number
includeMetadata?: boolean
includeCanceledTrips?: boolean
createdAfterDate?: string
createdBeforeDate?: string
lastModifiedDate?: string
}
export interface GetItineraryParams extends SapConcurBaseParams {
tripId: string
useridType?: string
useridValue?: string
systemFormat?: string
}
export interface ListUsersParams extends SapConcurBaseParams {
count?: number
cursor?: string
attributes?: string
excludedAttributes?: string
}
export interface SearchUsersParams extends SapConcurBaseParams {
body: Record<string, unknown> | string
}
export interface GetUserParams extends SapConcurBaseParams {
userUuid: string
attributes?: string
excludedAttributes?: string
}
export interface CreateUserParams extends SapConcurBaseParams {
body: Record<string, unknown> | string
}
export interface UpdateUserParams extends SapConcurBaseParams {
userUuid: string
body: Record<string, unknown> | string
}
export interface DeleteUserParams extends SapConcurBaseParams {
userUuid: string
}
export interface GetTravelProfileParams extends SapConcurBaseParams {
loginId?: string
userId?: string
useridType?: 'login' | 'xmlsyncid' | 'uuid'
useridValue?: string
}
export interface ListTravelProfilesSummaryParams extends SapConcurBaseParams {
lastModifiedDate: string
page?: number
itemsPerPage?: number
travelConfigs?: string
}
export interface SearchLocationsParams extends SapConcurBaseParams {
searchText?: string
locCode?: string
locationNameId?: string
locationNameKey?: number
countryCode?: string
subdivisionCode?: string
adminRegionId?: string
}
export interface CreateListItemParams extends SapConcurBaseParams {
body: Record<string, unknown> | string
}
export interface UpdateListItemParams extends SapConcurBaseParams {
itemId: string
body: Record<string, unknown> | string
}
export interface DeleteListItemParams extends SapConcurBaseParams {
itemId: string
}
export interface UserFileLike {
id?: string
key?: string
path?: string
url?: string
name: string
size: number
type?: string
[key: string]: unknown
}
export interface UploadReceiptImageParams extends SapConcurBaseParams {
userId: string
receipt: UserFileLike
forwardId?: string
}
export interface CreateQuickExpenseWithImageParams extends SapConcurBaseParams {
userId: string
contextType: 'TRAVELER'
receipt: UserFileLike
body: Record<string, unknown> | string
}
interface ListInvoicesParams extends SapConcurBaseParams {
limit?: number
offset?: number
modifiedAfter?: string
}
interface GetInvoiceParams extends SapConcurBaseParams {
invoiceId: string
}
interface ListPurchaseOrdersParams extends SapConcurBaseParams {
limit?: number
offset?: number
}
interface GetPurchaseOrderParams extends SapConcurBaseParams {
purchaseOrderId: string
}
interface ListVendorsParams extends SapConcurBaseParams {
limit?: number
offset?: number
vendorCode?: string
}
interface ListPurchaseRequestsParams extends SapConcurBaseParams {
limit?: number
offset?: number
modifiedAfter?: string
}
export interface GetPurchaseRequestParams extends SapConcurBaseParams {
purchaseRequestId: string
}
export interface CreatePurchaseRequestParams extends SapConcurBaseParams {
body: Record<string, unknown> | string
}
interface UpdatePurchaseRequestParams extends SapConcurBaseParams {
purchaseRequestId: string
body: Record<string, unknown> | string
}
export interface ListListsParams extends SapConcurBaseParams {
page?: number
sortBy?: string
sortDirection?: 'asc' | 'desc'
value?: string
categoryType?: string
isDeleted?: boolean
levelCount?: number
}
export interface GetListParams extends SapConcurBaseParams {
listId: string
}
export interface ListListItemsParams extends SapConcurBaseParams {
listId: string
page?: number
sortBy?: 'value' | 'shortCode'
sortDirection?: 'asc' | 'desc'
hasChildren?: boolean
isDeleted?: boolean
shortCode?: string
value?: string
shortCodeOrValue?: string
}
export interface GetListItemParams extends SapConcurBaseParams {
itemId: string
}
export interface ListBudgetsParams extends SapConcurBaseParams {
adminView?: boolean
offset?: number
responseSchema?: 'COMPACT'
}
export interface GetBudgetParams extends SapConcurBaseParams {
budgetId: string
}
interface ListBudgetItemsParams extends SapConcurBaseParams {
budgetId: string
limit?: number
offset?: number
}
export type ListBudgetCategoriesParams = SapConcurBaseParams
interface ListCardTransactionsParams extends SapConcurBaseParams {
limit?: number
offset?: number
cardAccountId?: string
user?: string
modifiedAfter?: string
}
interface GetCardTransactionParams extends SapConcurBaseParams {
cardTransactionId: string
}
export interface UploadExchangeRatesParams extends SapConcurBaseParams {
body: Record<string, unknown> | string
}
interface ListLocalitiesParams extends SapConcurBaseParams {
limit?: number
offset?: number
countryCode?: string
}
@@ -0,0 +1,116 @@
import type { SapConcurProxyResponse, UpdateAllocationParams } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const updateAllocationTool: ToolConfig<UpdateAllocationParams, SapConcurProxyResponse> = {
id: 'sap_concur_update_allocation',
name: 'SAP Concur Update Allocation',
description:
'Update an allocation (PATCH /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/allocations/{allocationId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Access context: TRAVELER or PROXY (write requires expense.report.readwrite)',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
allocationId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Allocation ID to update',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Fields to update on the allocation',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
const allocationId = trimRequired(params.allocationId, 'allocationId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}/allocations/${encodeURIComponent(allocationId)}`,
method: 'PATCH',
body: params.body,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Empty body on success (Concur returns 204 No Content)',
properties: {},
},
},
}
@@ -0,0 +1,177 @@
import type { SapConcurProxyResponse, UpdateExpectedExpenseParams } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const updateExpectedExpenseTool: ToolConfig<
UpdateExpectedExpenseParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_update_expected_expense',
name: 'SAP Concur Update Expected Expense',
description: 'Update an expected expense (PUT /travelrequest/v4/expenses/{expenseUuid}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
expenseUuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expected expense UUID to update',
},
userId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'User UUID acting on the request (required when using a Company JWT, optional otherwise)',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Fields to update on the expected expense',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const expenseUuid = trimRequired(params.expenseUuid, 'expenseUuid')
const query: Record<string, string> = {}
if (params.userId?.trim()) query.userId = params.userId.trim()
return {
...baseProxyBody(params),
path: `/travelrequest/v4/expenses/${encodeURIComponent(expenseUuid)}`,
method: 'PUT',
body: params.body,
...(Object.keys(query).length > 0 ? { query } : {}),
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Updated expected expense payload',
properties: {
id: { type: 'string', description: 'Expected expense identifier', optional: true },
href: { type: 'string', description: 'Self-link', optional: true },
expenseType: {
type: 'json',
description: 'Expense type {id, name}',
optional: true,
},
transactionDate: {
type: 'string',
description: 'Transaction date',
optional: true,
},
transactionAmount: {
type: 'json',
description: 'Transaction amount {value, currencyCode}',
optional: true,
},
postedAmount: {
type: 'json',
description: 'Posted amount {value, currencyCode}',
optional: true,
},
approvedAmount: {
type: 'json',
description: 'Approved amount {value, currencyCode}',
optional: true,
},
remainingAmount: {
type: 'json',
description: 'Remaining amount on the expected expense',
optional: true,
},
businessPurpose: {
type: 'string',
description: 'Business purpose of the expense',
optional: true,
},
location: {
type: 'json',
description:
'Location {id, name, city, countryCode, countrySubDivisionCode, iataCode, locationType}',
optional: true,
},
exchangeRate: {
type: 'json',
description: 'Exchange rate {value, operation}',
optional: true,
},
allocations: {
type: 'json',
description: 'Budget allocations array',
optional: true,
},
tripData: {
type: 'json',
description:
'Trip data {agencyBooked, selfBooked, tripType (ONE_WAY|ROUND_TRIP), legs[{id, returnLeg, startDate, startTime, startLocationDetail, startLocation, endLocation, class {code,value}, travelExceptionReasonCodes}], segmentType {category, code}}',
optional: true,
},
parentRequest: {
type: 'json',
description: 'Parent travel request resource link {href, id}',
optional: true,
},
comments: {
type: 'json',
description: 'Comments sub-resource link {href, id}',
optional: true,
},
},
},
},
}
+104
View File
@@ -0,0 +1,104 @@
import type { SapConcurProxyResponse, UpdateExpenseParams } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const updateExpenseTool: ToolConfig<UpdateExpenseParams, SapConcurProxyResponse> = {
id: 'sap_concur_update_expense',
name: 'SAP Concur Update Expense',
description:
'Update an expense (PATCH /expensereports/v4/reports/{reportId}/expenses/{expenseId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID',
},
expenseId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense ID to update',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'PATCH body. Allowed fields: businessPurpose (string, max 64), customData (CustomData[]), expenseSource (required: EA|MOB|OTHER|SE|TA|TR|UI), isExpenseRejected (boolean), isPaperReceiptReceived (boolean).',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const reportId = trimRequired(params.reportId, 'reportId')
const expenseId = trimRequired(params.expenseId, 'expenseId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/reports/${encodeURIComponent(reportId)}/expenses/${encodeURIComponent(expenseId)}`,
method: 'PATCH',
body: params.body,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description:
'Empty body on success (HTTP 204 No Content). Error details when status is non-2xx',
properties: {},
},
},
}
@@ -0,0 +1,109 @@
import type { SapConcurProxyResponse, UpdateExpenseReportParams } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const updateExpenseReportTool: ToolConfig<
UpdateExpenseReportParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_update_expense_report',
name: 'SAP Concur Update Expense Report',
description:
'Update an unsubmitted expense report (PATCH /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId} — supported contexts: TRAVELER, PROXY). Body fields: businessPurpose, comment, customData, name, etc.',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID who owns the report',
},
contextType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Access context: TRAVELER (own report) or PROXY (editing on behalf of another user)',
},
reportId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Expense report ID to update',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Fields to update on the report',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
const contextType = trimRequired(params.contextType, 'contextType')
const reportId = trimRequired(params.reportId, 'reportId')
return {
...baseProxyBody(params),
path: `/expensereports/v4/users/${encodeURIComponent(userId)}/context/${encodeURIComponent(contextType)}/reports/${encodeURIComponent(reportId)}`,
method: 'PATCH',
body: params.body,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: { type: 'json', description: 'Empty (204 No Content)' },
},
}
@@ -0,0 +1,131 @@
import type { SapConcurProxyResponse, UpdateListItemParams } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const updateListItemTool: ToolConfig<UpdateListItemParams, SapConcurProxyResponse> = {
id: 'sap_concur_update_list_item',
name: 'SAP Concur Update List Item',
description: 'Update a list item (PUT /list/v4/items/{itemId}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
itemId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'List item UUID',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'List item payload. Required: shortCode, value. Other fields in the body are ignored.',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const itemId = trimRequired(params.itemId, 'itemId')
return {
...baseProxyBody(params),
path: `/list/v4/items/${encodeURIComponent(itemId)}`,
method: 'PUT',
body: params.body,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Updated list item',
properties: {
id: { type: 'string', description: 'List item UUID', optional: true },
code: { type: 'string', description: 'Long code format for the item', optional: true },
shortCode: { type: 'string', description: 'Short code identifier', optional: true },
value: { type: 'string', description: 'Display value of the item', optional: true },
parentId: {
type: 'string',
description: 'Parent item UUID (omitted for first-level items)',
optional: true,
},
level: {
type: 'number',
description: 'Hierarchy level (1 for root items)',
optional: true,
},
isDeleted: {
type: 'boolean',
description: 'Deletion status across all containing lists',
optional: true,
},
lists: {
type: 'array',
description: 'Lists containing this item',
optional: true,
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'List UUID', optional: true },
hasChildren: {
type: 'boolean',
description: 'Whether this item has children in the list',
optional: true,
},
},
},
},
},
},
},
}
@@ -0,0 +1,231 @@
import type { SapConcurProxyResponse, UpdateTravelRequestParams } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const updateTravelRequestTool: ToolConfig<
UpdateTravelRequestParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_update_travel_request',
name: 'SAP Concur Update Travel Request',
description: 'Update a travel request (PUT /travelrequest/v4/requests/{requestUuid}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
requestUuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Travel request UUID to update',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Fields to update on the travel request',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const requestUuid = trimRequired(params.requestUuid, 'requestUuid')
return {
...baseProxyBody(params),
path: `/travelrequest/v4/requests/${encodeURIComponent(requestUuid)}`,
method: 'PUT',
body: params.body,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Updated travel request payload',
properties: {
id: { type: 'string', description: 'Travel request UUID', optional: true },
href: { type: 'string', description: 'Resource hyperlink', optional: true },
requestId: {
type: 'string',
description: 'Public-facing request ID (4-6 alphanumeric characters)',
optional: true,
},
name: { type: 'string', description: 'Request name', optional: true },
businessPurpose: { type: 'string', description: 'Business purpose', optional: true },
comment: { type: 'string', description: 'Last attached comment', optional: true },
creationDate: { type: 'string', description: 'Creation timestamp', optional: true },
lastModified: {
type: 'string',
description: 'Last modification timestamp',
optional: true,
},
submitDate: { type: 'string', description: 'Last submission timestamp', optional: true },
startDate: { type: 'string', description: 'Trip start date (ISO 8601)', optional: true },
endDate: { type: 'string', description: 'Trip end date (ISO 8601)', optional: true },
startTime: { type: 'string', description: 'Trip start time (HH:mm)', optional: true },
endTime: { type: 'string', description: 'Trip end time (HH:mm)', optional: true },
approved: {
type: 'boolean',
description: 'Whether the request is approved',
optional: true,
},
pendingApproval: { type: 'boolean', description: 'Pending approval flag', optional: true },
closed: { type: 'boolean', description: 'Closed flag', optional: true },
everSentBack: { type: 'boolean', description: 'Ever-sent-back flag', optional: true },
canceledPostApproval: {
type: 'boolean',
description: 'Canceled after approval flag',
optional: true,
},
approvalStatus: {
type: 'json',
description: 'Approval status',
optional: true,
properties: {
code: {
type: 'string',
description: 'Status code (NOT_SUBMITTED, SUBMITTED, APPROVED, CANCELED, SENTBACK)',
optional: true,
},
name: { type: 'string', description: 'Localized status name', optional: true },
},
},
owner: {
type: 'json',
description: 'Travel request owner',
optional: true,
properties: {
id: { type: 'string', description: 'User UUID', optional: true },
firstName: { type: 'string', description: 'Owner first name', optional: true },
lastName: { type: 'string', description: 'Owner last name', optional: true },
},
},
approver: {
type: 'json',
description: 'Approver assigned to the request',
optional: true,
properties: {
id: { type: 'string', description: 'User UUID', optional: true },
firstName: { type: 'string', description: 'Approver first name', optional: true },
lastName: { type: 'string', description: 'Approver last name', optional: true },
},
},
policy: {
type: 'json',
description: 'Resource link to the applicable policy',
optional: true,
properties: {
id: { type: 'string', description: 'Policy ID', optional: true },
href: { type: 'string', description: 'Policy hyperlink', optional: true },
},
},
type: {
type: 'json',
description: 'Request type',
optional: true,
properties: {
code: { type: 'string', description: 'Request type code', optional: true },
label: { type: 'string', description: 'Request type label', optional: true },
},
},
mainDestination: {
type: 'json',
description: 'Main destination of the trip',
optional: true,
properties: {
city: { type: 'string', description: 'City', optional: true },
countryCode: { type: 'string', description: 'ISO country code', optional: true },
countrySubDivisionCode: {
type: 'string',
description: 'ISO country sub-division code',
optional: true,
},
name: { type: 'string', description: 'Destination name', optional: true },
},
},
totalApprovedAmount: {
type: 'json',
description: 'Total approved amount',
optional: true,
properties: {
value: { type: 'number', description: 'Amount value', optional: true },
currency: { type: 'string', description: 'Currency code', optional: true },
},
},
totalPostedAmount: {
type: 'json',
description: 'Total posted amount',
optional: true,
properties: {
value: { type: 'number', description: 'Amount value', optional: true },
currency: { type: 'string', description: 'Currency code', optional: true },
},
},
totalRemainingAmount: {
type: 'json',
description: 'Total remaining amount',
optional: true,
properties: {
value: { type: 'number', description: 'Amount value', optional: true },
currency: { type: 'string', description: 'Currency code', optional: true },
},
},
operations: {
type: 'array',
description: 'Available workflow actions',
optional: true,
items: { type: 'json' },
},
},
},
},
}
+95
View File
@@ -0,0 +1,95 @@
import type { SapConcurProxyResponse, UpdateUserParams } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
scimUserOutputProperties,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const updateUserTool: ToolConfig<UpdateUserParams, SapConcurProxyResponse> = {
id: 'sap_concur_update_user',
name: 'SAP Concur Update User',
description: 'Patch a user identity (PATCH /profile/identity/v4.1/Users/{id}).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userUuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'User UUID to update',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'SCIM PATCH operations payload ({ schemas, Operations: [...] })',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userUuid = trimRequired(params.userUuid, 'userUuid')
return {
...baseProxyBody(params),
path: `/profile/identity/v4.1/Users/${encodeURIComponent(userUuid)}`,
method: 'PATCH',
body: params.body,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Updated SCIM User payload',
properties: scimUserOutputProperties,
},
},
}
@@ -0,0 +1,106 @@
import type { SapConcurProxyResponse, UploadExchangeRatesParams } from '@/tools/sap_concur/types'
import {
baseProxyBody,
SAP_CONCUR_PROXY_URL,
transformSapConcurProxyResponse,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const uploadExchangeRatesTool: ToolConfig<
UploadExchangeRatesParams,
SapConcurProxyResponse
> = {
id: 'sap_concur_upload_exchange_rates',
name: 'SAP Concur Upload Exchange Rates',
description:
'Bulk upload up to 100 custom exchange rates (POST /exchangerate/v4/rates). Body contains a currency_sets array, each with from_crn_code, to_crn_code, start_date (YYYY-MM-DD), and rate.',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Bulk upload body: { currency_sets: [{ from_crn_code, to_crn_code, start_date: "YYYY-MM-DD", rate }] } (max 100 entries)',
},
},
request: {
url: SAP_CONCUR_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
...baseProxyBody(params),
path: `/exchangerate/v4/rates`,
method: 'POST',
body: params.body,
}),
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description: 'Bulk-upload exchange rate response (Exchange Rate v4)',
properties: {
overallStatus: {
type: 'string',
description: 'Overall result status for the bulk upload (e.g. SUCCESS, FAILURE)',
optional: true,
},
message: {
type: 'string',
description: 'Top-level result message',
optional: true,
},
currencySets: {
type: 'json',
description:
'Per-row results: array of { from_crn_code, to_crn_code, start_date, rate, statusCode, statusMessage }',
optional: true,
},
},
},
},
}
@@ -0,0 +1,119 @@
import type { SapConcurProxyResponse, UploadReceiptImageParams } from '@/tools/sap_concur/types'
import {
baseProxyBody,
transformSapConcurProxyResponse,
trimRequired,
} from '@/tools/sap_concur/utils'
import type { ToolConfig } from '@/tools/types'
export const SAP_CONCUR_UPLOAD_URL = '/api/tools/sap_concur/upload'
export const uploadReceiptImageTool: ToolConfig<UploadReceiptImageParams, SapConcurProxyResponse> =
{
id: 'sap_concur_upload_receipt_image',
name: 'SAP Concur Upload Receipt Image',
description:
'Upload an image-only receipt (POST /receipts/v4/users/{userId}/image-only-receipts).',
version: '1.0.0',
params: {
datacenter: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Concur datacenter base URL (defaults to us.api.concursolutions.com)',
},
grantType: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'OAuth grant type: client_credentials (default) or password',
},
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Concur OAuth client secret',
},
username: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Username (only for password grant)',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password (only for password grant)',
},
companyUuid: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Company UUID for multi-company access tokens',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Concur user UUID who owns the receipt',
},
receipt: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Receipt image file (UserFile reference). Supported formats: PDF, PNG, JPEG, GIF, TIFF',
},
forwardId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Optional client-supplied dedup id (max 40 chars). Sent as the concur-forwardid header.',
},
},
request: {
url: SAP_CONCUR_UPLOAD_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const userId = trimRequired(params.userId, 'userId')
return {
...baseProxyBody(params),
operation: 'upload_receipt_image',
userId,
receipt: params.receipt,
forwardId: params.forwardId,
}
},
},
transformResponse: transformSapConcurProxyResponse,
outputs: {
status: { type: 'number', description: 'HTTP status code returned by Concur' },
data: {
type: 'json',
description:
'Image-only receipt upload response (HTTP 202 Accepted; Location and Link response headers exposed in body)',
properties: {
location: {
type: 'string',
description:
'Location header URL for the new receipt image (e.g. /receipts/v4/images/{receiptId})',
optional: true,
},
link: {
type: 'string',
description: 'Link header URL pointing to /receipts/v4/status/{receiptId}',
optional: true,
},
},
},
},
}
+297
View File
@@ -0,0 +1,297 @@
import type { SapConcurBaseParams, SapConcurProxyResponse } from '@/tools/sap_concur/types'
import type { OutputProperty } from '@/tools/types'
export const scimUserOutputProperties: Record<string, OutputProperty> = {
id: { type: 'string', description: 'User UUID' },
externalId: {
type: 'string',
description: 'External identifier set by the provisioning client',
optional: true,
},
userName: { type: 'string', description: 'Unique username (often email)' },
displayName: { type: 'string', description: 'Display name', optional: true },
nickName: { type: 'string', description: 'Casual or alternate name', optional: true },
title: { type: 'string', description: 'Job title', optional: true },
userType: { type: 'string', description: 'User type (e.g., Employee)', optional: true },
preferredLanguage: { type: 'string', description: 'Preferred language tag', optional: true },
locale: { type: 'string', description: 'Locale (e.g., en-US)', optional: true },
timezone: { type: 'string', description: 'Timezone (e.g., America/Los_Angeles)', optional: true },
active: { type: 'boolean', description: 'Whether the user is active', optional: true },
dateOfBirth: { type: 'string', description: 'Date of birth (YYYY-MM-DD)', optional: true },
name: {
type: 'json',
description: 'Structured name',
optional: true,
properties: {
formatted: { type: 'string', description: 'Formatted full name', optional: true },
familyName: { type: 'string', description: 'Family (last) name', optional: true },
familyNamePrefix: { type: 'string', description: 'Family name prefix', optional: true },
givenName: { type: 'string', description: 'Given (first) name', optional: true },
middleName: { type: 'string', description: 'Middle name', optional: true },
honorificPrefix: { type: 'string', description: 'Honorific prefix', optional: true },
honorificSuffix: { type: 'string', description: 'Honorific suffix', optional: true },
},
},
emails: {
type: 'array',
description: 'Email addresses',
optional: true,
items: {
type: 'json',
properties: {
value: { type: 'string', description: 'Email address' },
type: { type: 'string', description: 'Type (e.g., work, home)', optional: true },
primary: { type: 'boolean', description: 'Primary email flag', optional: true },
display: { type: 'string', description: 'Display label', optional: true },
notifications: {
type: 'boolean',
description: 'Whether email notifications are enabled',
optional: true,
},
verified: { type: 'boolean', description: 'Whether the email is verified', optional: true },
},
},
},
phoneNumbers: {
type: 'array',
description: 'Phone numbers',
optional: true,
items: {
type: 'json',
properties: {
value: { type: 'string', description: 'Phone number' },
type: { type: 'string', description: 'Type (work, mobile, fax, etc.)', optional: true },
primary: { type: 'boolean', description: 'Primary phone flag', optional: true },
display: { type: 'string', description: 'Display label', optional: true },
notifications: {
type: 'boolean',
description: 'Whether SMS notifications are enabled',
optional: true,
},
},
},
},
addresses: {
type: 'array',
description: 'Addresses',
optional: true,
items: {
type: 'json',
properties: {
type: { type: 'string', description: 'Address type (work, home, etc.)', optional: true },
formatted: { type: 'string', description: 'Formatted address', optional: true },
streetAddress: { type: 'string', description: 'Street address', optional: true },
locality: { type: 'string', description: 'City / locality', optional: true },
region: { type: 'string', description: 'State / region', optional: true },
postalCode: { type: 'string', description: 'Postal code', optional: true },
country: { type: 'string', description: 'ISO 3166-1 country code', optional: true },
primary: { type: 'boolean', description: 'Primary address flag', optional: true },
},
},
},
entitlements: {
type: 'array',
description: 'Entitlements granted to the user',
optional: true,
items: { type: 'json' },
},
roles: {
type: 'array',
description: 'Roles assigned to the user',
optional: true,
items: { type: 'json' },
},
schemas: {
type: 'array',
description: 'SCIM schemas the resource conforms to',
optional: true,
items: { type: 'string' },
},
meta: {
type: 'json',
description: 'Resource metadata',
optional: true,
properties: {
created: { type: 'string', description: 'Creation timestamp', optional: true },
lastModified: { type: 'string', description: 'Last modified timestamp', optional: true },
resourceType: { type: 'string', description: 'Resource type (User)', optional: true },
location: { type: 'string', description: 'Resource URL', optional: true },
version: { type: 'string', description: 'ETag version', optional: true },
},
},
emergencyContacts: {
type: 'array',
description: 'Emergency contacts',
optional: true,
items: {
type: 'json',
properties: {
name: { type: 'string', description: 'Contact full name', optional: true },
relationship: { type: 'string', description: 'Relationship to user', optional: true },
emails: { type: 'array', description: 'Emails', optional: true, items: { type: 'json' } },
phones: { type: 'array', description: 'Phones', optional: true, items: { type: 'json' } },
streetAddress: { type: 'string', description: 'Street address', optional: true },
locality: { type: 'string', description: 'City / locality', optional: true },
region: { type: 'string', description: 'State / region', optional: true },
postalCode: { type: 'string', description: 'Postal code', optional: true },
country: { type: 'string', description: 'ISO 3166-1 country code', optional: true },
},
},
},
localeOverrides: {
type: 'json',
description: 'Read-only locale and date/time/number preference overrides',
optional: true,
},
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User': {
type: 'json',
description: 'SCIM Enterprise User extension',
optional: true,
properties: {
employeeNumber: { type: 'string', description: 'Employee number', optional: true },
companyId: { type: 'string', description: 'Concur company identifier', optional: true },
startDate: { type: 'string', description: 'Employment start date', optional: true },
terminationDate: {
type: 'string',
description: 'Employment termination date',
optional: true,
},
leavesOfAbsence: {
type: 'array',
description: 'Leaves of absence',
optional: true,
items: {
type: 'json',
properties: {
startDate: { type: 'string', description: 'Leave start date', optional: true },
endDate: { type: 'string', description: 'Leave end date', optional: true },
type: { type: 'string', description: 'Leave type', optional: true },
},
},
},
costCenter: { type: 'string', description: 'Cost center', optional: true },
organization: { type: 'string', description: 'Organization', optional: true },
division: { type: 'string', description: 'Division', optional: true },
department: { type: 'string', description: 'Department', optional: true },
manager: {
type: 'json',
description: 'Manager reference',
optional: true,
properties: {
value: { type: 'string', description: 'Manager UUID', optional: true },
$ref: { type: 'string', description: 'Manager resource URL', optional: true },
displayName: { type: 'string', description: 'Manager display name', optional: true },
employeeNumber: {
type: 'string',
description: 'Manager employee number',
optional: true,
},
},
},
},
},
'urn:ietf:params:scim:schemas:extension:sap:2.0:User': {
type: 'json',
description: 'SAP SCIM extension',
optional: true,
properties: {
userUuid: { type: 'string', description: 'SAP global user UUID', optional: true },
},
},
'urn:ietf:params:scim:schemas:extension:sap:concur:2.0:User': {
type: 'json',
description: 'SAP Concur SCIM extension (Concur-specific attributes)',
optional: true,
},
}
export const scimListResponseOutputProperties: Record<string, OutputProperty> = {
schemas: {
type: 'array',
description: 'SCIM schemas the response conforms to',
optional: true,
items: { type: 'string' },
},
totalResults: {
type: 'number',
description: 'Total number of results matching the query',
optional: true,
},
itemsPerPage: {
type: 'number',
description: 'Number of results returned in this page',
optional: true,
},
startIndex: {
type: 'number',
description: '1-based index of the first result',
optional: true,
},
cursor: {
type: 'string',
description: 'SCIM v4.1 cursor for the next page of results',
optional: true,
},
Resources: {
type: 'array',
description: 'SCIM User resources',
optional: true,
items: {
type: 'json',
properties: scimUserOutputProperties,
},
},
}
export const SAP_CONCUR_PROXY_URL = '/api/tools/sap_concur/proxy'
export function baseProxyBody(params: SapConcurBaseParams): Record<string, unknown> {
const body: Record<string, unknown> = {
datacenter: params.datacenter ?? 'us.api.concursolutions.com',
grantType: params.grantType ?? 'client_credentials',
clientId: params.clientId,
clientSecret: params.clientSecret,
}
if (params.username) body.username = params.username
if (params.password) body.password = params.password
if (params.companyUuid) body.companyUuid = params.companyUuid
return body
}
export function buildListQuery(
params: Record<string, string | number | boolean | undefined | null>
): Record<string, string | number | boolean> {
const query: Record<string, string | number | boolean> = {}
for (const [key, value] of Object.entries(params)) {
if (value === undefined || value === null) continue
if (typeof value === 'string' && value.trim() === '') continue
query[key] = value
}
return query
}
export async function transformSapConcurProxyResponse(
response: Response
): Promise<SapConcurProxyResponse> {
const data = (await response.json()) as
| { success: true; output: { status: number; data: unknown } }
| { success: false; error?: string; status?: number }
if (!('success' in data) || data.success === false) {
const errMessage = 'error' in data && data.error ? data.error : 'Concur request failed'
throw new Error(errMessage)
}
return {
success: true,
output: {
status: data.output.status,
data: data.output.data,
},
}
}
export function trimRequired(value: string | undefined, name: string): string {
if (!value || !value.trim()) {
throw new Error(`${name} is required`)
}
return value.trim()
}