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
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:
@@ -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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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)' },
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user