Files
dingMCP/src/xft.ts
T

218 lines
6.1 KiB
TypeScript

/** Shared contracts for the official XFT OpenAPI adapter. */
export interface AuthenticatedDingTalkUser {
userId: string;
name?: string;
}
export interface XftTodoItem {
source: "xft";
billId: string;
taskId?: string;
eventSeqId?: string;
title?: string;
applicantName?: string;
applicantDepartment?: string;
applyTime?: string;
amount?: number;
currency?: string;
reason?: string;
allowPass?: boolean;
allowReject?: boolean;
eventType?: string;
}
export type ApprovalHistoryScope = "handled" | "submitted";
export interface ApprovalHistoryRange {
startTime: number;
endTime: number;
}
export interface XftHistoryItem extends Omit<XftTodoItem, "allowPass" | "allowReject"> {
historyScope: ApprovalHistoryScope;
status?: string;
statusName?: string;
completedAt?: string;
}
export type XftApprovalAction = "agree" | "reject" | "return";
export interface XftApprovalBatchItem {
billId: string;
targetNodeId?: string;
}
export type XftApprovalBatchResultItem =
| { billId: string; status: "succeeded"; success: true }
| { billId: string; status: "failed"; success: false; failureConfirmed: true; error: string }
| {
billId: string;
status: "outcome_uncertain";
success: false;
outcomeUncertain: true;
error: string;
};
export type XftSubmissionKind =
| "travel_apply"
| "expense_apply"
| "entertainment_apply"
| "common_apply"
| "use_car_apply"
| "common_reimburse"
| "travel_reimburse";
export interface XftSubmissionTemplate {
eventSeqId: string;
eventNumber: string;
eventName: string;
eventType: string;
kind: XftSubmissionKind;
}
export interface XftSubmissionField {
fieldNumber: string;
name: string;
type: string;
required: boolean;
description?: string;
multiple?: boolean;
options?: XftSubmissionOption[];
enumLabels?: Record<string, string>;
moduleType?: string;
valueFormat?: string;
itemFields?: Array<Record<string, unknown>>;
optionSource?: {
tool: "approval_get_submission_options";
kind: XftSubmissionOptionKind;
};
selectionCapability?: XftSubmissionSelectionCapability;
}
export type XftSubmissionOptionKind = "department" | "city" | "expense_type";
export interface XftSubmissionOption {
code: string;
label: string;
}
export type XftSubmissionSelectionCapability =
| {
status: "supported";
userInput: "label";
submitValue: "code";
exposeInternalValue: false;
}
| {
status: "client_only";
reason: string;
};
export interface XftApprovalProvider {
authStatus(user: AuthenticatedDingTalkUser): Promise<{
authenticated: true;
identity: Record<string, unknown>;
binding: Record<string, unknown>;
persistedBusinessData: false;
}>;
listMyTodos(user: AuthenticatedDingTalkUser, limit: number, search?: string): Promise<{
items: XftTodoItem[];
totalCount: number;
identity: Record<string, unknown>;
diagnostics: Record<string, unknown>;
}>;
listMyHistory(
user: AuthenticatedDingTalkUser,
scope: ApprovalHistoryScope,
limit: number,
search: string,
range: ApprovalHistoryRange,
): Promise<{
items: XftHistoryItem[];
totalCount: number;
diagnostics: Record<string, unknown>;
}>;
getTodoDetail(user: AuthenticatedDingTalkUser, billId: string): Promise<Record<string, unknown>>;
getHistoryDetail(
user: AuthenticatedDingTalkUser,
scope: ApprovalHistoryScope,
billId: string,
): Promise<Record<string, unknown>>;
listReturnNodes(user: AuthenticatedDingTalkUser, billId: string): Promise<{
billId: string;
nodes: Array<{ nodeId: string; nodeName?: string; approverNames?: string[] }>;
requirements: {
commentRequired: boolean;
signatureRequired: boolean;
canSkipPreviouslyApprovedNodes: boolean;
};
diagnostics: Record<string, unknown>;
}>;
processApprovalBatch(
user: AuthenticatedDingTalkUser,
action: XftApprovalAction,
remark: string,
items: XftApprovalBatchItem[],
): Promise<{
action: XftApprovalAction;
total: number;
succeeded: number;
failed: number;
results: XftApprovalBatchResultItem[];
diagnostics: Record<string, unknown>;
}>;
listSubmissionTemplates(user: AuthenticatedDingTalkUser, limit: number, search?: string): Promise<XftSubmissionTemplate[]>;
getSubmissionSchema(user: AuthenticatedDingTalkUser, template: XftSubmissionTemplate): Promise<{
template: XftSubmissionTemplate;
fields: XftSubmissionField[];
inputContract: Record<string, unknown>;
diagnostics: Record<string, unknown>;
}>;
getSubmissionOptions(
user: AuthenticatedDingTalkUser,
template: XftSubmissionTemplate,
kind: XftSubmissionOptionKind,
query?: string,
limit?: number,
): Promise<{
kind: XftSubmissionOptionKind;
items: XftSubmissionOption[];
total?: number;
hasMore?: boolean;
diagnostics: Record<string, unknown>;
}>;
validateSubmissionIdentity(user: AuthenticatedDingTalkUser): Promise<{
validated: true;
diagnostics: Record<string, unknown>;
}>;
submitApproval(
user: AuthenticatedDingTalkUser,
template: XftSubmissionTemplate,
values: Record<string, unknown>,
): Promise<{ billId: string; status: "submitted"; diagnostics: Record<string, unknown> }>;
recallSubmission(
user: AuthenticatedDingTalkUser,
billId: string,
reason?: string,
): Promise<{ status: "revoked" | "failed" | "outcome_uncertain"; billId: string; message?: string; diagnostics: Record<string, unknown> }>;
getMySubmissionStatus(
user: AuthenticatedDingTalkUser,
billId: string,
): Promise<{
billId: string;
status: string;
statusName: string;
approvalStatus?: string;
revoked: boolean;
revokeAllowed: boolean;
flowHistory?: Array<{ nodeName?: string; actions: string[] }>;
diagnostics: Record<string, unknown>;
}>;
}
/** Success codes observed on the documented XFT OpenAPI endpoints. */
export function isXftSuccessCode(code: string | undefined): boolean {
if (code === undefined || code === "") return true;
return ["0", "200", "SUCCESS", "SUC0000", "000000", "0000000"].includes(code.toUpperCase());
}