/** 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 { 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; moduleType?: string; valueFormat?: string; itemFields?: Array>; 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; binding: Record; persistedBusinessData: false; }>; listMyTodos(user: AuthenticatedDingTalkUser, limit: number, search?: string): Promise<{ items: XftTodoItem[]; totalCount: number; identity: Record; diagnostics: Record; }>; listMyHistory( user: AuthenticatedDingTalkUser, scope: ApprovalHistoryScope, limit: number, search: string, range: ApprovalHistoryRange, ): Promise<{ items: XftHistoryItem[]; totalCount: number; diagnostics: Record; }>; getTodoDetail(user: AuthenticatedDingTalkUser, billId: string): Promise>; getHistoryDetail( user: AuthenticatedDingTalkUser, scope: ApprovalHistoryScope, billId: string, ): Promise>; 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; }>; processApprovalBatch( user: AuthenticatedDingTalkUser, action: XftApprovalAction, remark: string, items: XftApprovalBatchItem[], ): Promise<{ action: XftApprovalAction; total: number; succeeded: number; failed: number; results: XftApprovalBatchResultItem[]; diagnostics: Record; }>; listSubmissionTemplates(user: AuthenticatedDingTalkUser, limit: number, search?: string): Promise; getSubmissionSchema(user: AuthenticatedDingTalkUser, template: XftSubmissionTemplate): Promise<{ template: XftSubmissionTemplate; fields: XftSubmissionField[]; inputContract: Record; diagnostics: Record; }>; getSubmissionOptions( user: AuthenticatedDingTalkUser, template: XftSubmissionTemplate, kind: XftSubmissionOptionKind, query?: string, limit?: number, ): Promise<{ kind: XftSubmissionOptionKind; items: XftSubmissionOption[]; total?: number; hasMore?: boolean; diagnostics: Record; }>; validateSubmissionIdentity(user: AuthenticatedDingTalkUser): Promise<{ validated: true; diagnostics: Record; }>; submitApproval( user: AuthenticatedDingTalkUser, template: XftSubmissionTemplate, values: Record, ): Promise<{ billId: string; status: "submitted"; diagnostics: Record }>; recallSubmission( user: AuthenticatedDingTalkUser, billId: string, reason?: string, ): Promise<{ status: "revoked" | "failed" | "outcome_uncertain"; billId: string; message?: string; diagnostics: Record }>; 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; }>; } /** 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()); }