250 行
6.5 KiB
TypeScript
250 行
6.5 KiB
TypeScript
import { apiRequestData } from "./http";
|
|
import type { PrecheckResponse, RiskFlag } from "./feeControl";
|
|
|
|
export type TravelRequestStatus = "draft" | "reviewing" | "approved" | "rejected";
|
|
export type TravelTripType = "domestic" | "international";
|
|
export type TravelSegmentRole = "outbound" | "return" | "connection";
|
|
export type TravelTransportType = "flight" | "train" | "car" | "bus" | "other";
|
|
|
|
export type TravelRequestSummary = {
|
|
id: string;
|
|
requestNo: string | null;
|
|
applicantEmployeeId: string;
|
|
submitterId: string | null;
|
|
departmentId: string;
|
|
legalEntityId: string;
|
|
costCenterId: string;
|
|
budgetPoolId: string | null;
|
|
purpose: string;
|
|
tripType: TravelTripType;
|
|
startDate: string | null;
|
|
endDate: string | null;
|
|
estimatedAmount: string;
|
|
projectId: string | null;
|
|
project: {
|
|
id: string;
|
|
projectNo: string;
|
|
name: string;
|
|
} | null;
|
|
status: TravelRequestStatus;
|
|
createdAt: string | null;
|
|
updatedAt: string | null;
|
|
applicant: {
|
|
id: string;
|
|
employeeNo: string;
|
|
name: string;
|
|
} | null;
|
|
submitter: {
|
|
id: string;
|
|
employeeNo: string;
|
|
name: string;
|
|
} | null;
|
|
department: {
|
|
id: string;
|
|
name: string;
|
|
} | null;
|
|
legalEntity: {
|
|
id: string;
|
|
code: string;
|
|
name: string;
|
|
} | null;
|
|
costCenter: {
|
|
id: string;
|
|
code: string;
|
|
name: string;
|
|
legalEntityId: string;
|
|
} | null;
|
|
budgetPool: {
|
|
id: string;
|
|
code: string;
|
|
name: string;
|
|
} | null;
|
|
};
|
|
|
|
export type TravelRequestSegment = {
|
|
id: string;
|
|
sortOrder: number;
|
|
segmentRole: TravelSegmentRole;
|
|
transportType: TravelTransportType;
|
|
fromCity: string;
|
|
toCity: string;
|
|
departAt: string | null;
|
|
arriveAt: string | null;
|
|
createdAt: string | null;
|
|
updatedAt: string | null;
|
|
};
|
|
|
|
export type TravelOrderLink = {
|
|
id: string;
|
|
source: string;
|
|
externalOrderNo: string;
|
|
employeeId: string;
|
|
linkedReimbursementId: string | null;
|
|
travelRequestId: string | null;
|
|
occurredAt: string | null;
|
|
amount: string | null;
|
|
currency: string;
|
|
status: string;
|
|
paymentVoucherNo: string | null;
|
|
paymentDate: string | null;
|
|
paymentChannel: string | null;
|
|
paidAt: string | null;
|
|
paidBy: string | null;
|
|
createdAt: string | null;
|
|
updatedAt: string | null;
|
|
};
|
|
|
|
export type TravelRequestDetail = TravelRequestSummary & {
|
|
segments: TravelRequestSegment[];
|
|
travelOrders: TravelOrderLink[];
|
|
pendingWorkflow: {
|
|
instanceId: string;
|
|
currentStatus: string;
|
|
currentStepId: string;
|
|
task: {
|
|
id: string;
|
|
stepId: string;
|
|
stepName: string;
|
|
assigneeId: string | null;
|
|
assigneeRole: string | null;
|
|
} | null;
|
|
} | null;
|
|
permissions: {
|
|
canEditDraft: boolean;
|
|
canSubmit: boolean;
|
|
canWithdraw: boolean;
|
|
canManage: boolean;
|
|
};
|
|
riskFlags: RiskFlag[];
|
|
};
|
|
|
|
export type TravelRequestListResponse = {
|
|
page: number;
|
|
pageSize: number;
|
|
total: number;
|
|
mine: boolean;
|
|
items: TravelRequestSummary[];
|
|
};
|
|
|
|
export type TravelRequestListQuery = {
|
|
status?: TravelRequestStatus;
|
|
mine?: boolean;
|
|
tripType?: TravelTripType;
|
|
legalEntityId?: string;
|
|
costCenterId?: string;
|
|
page?: number;
|
|
pageSize?: number;
|
|
};
|
|
|
|
export type TravelRequestSegmentPayload = {
|
|
segmentRole: TravelSegmentRole;
|
|
transportType: TravelTransportType;
|
|
fromCity: string;
|
|
toCity: string;
|
|
departAt: string;
|
|
arriveAt: string;
|
|
};
|
|
|
|
export type TravelRequestPayload = {
|
|
purpose: string;
|
|
tripType: TravelTripType;
|
|
startDate: string;
|
|
endDate: string;
|
|
legalEntityId: string;
|
|
costCenterId: string;
|
|
estimatedAmount: string;
|
|
segments: TravelRequestSegmentPayload[];
|
|
};
|
|
|
|
export type TravelRequestSubmitResponse = {
|
|
travelRequestId: string;
|
|
requestNo: string;
|
|
status: "reviewing";
|
|
workflowInstanceId: string;
|
|
currentTaskId: string;
|
|
budgetPoolId: string | null;
|
|
budgetReservation: {
|
|
applied: boolean;
|
|
reservedAmount?: string;
|
|
items?: Array<{ budgetPoolId: string; amount: string }>;
|
|
};
|
|
precheckDecision: "pass" | "warn" | "manual_review";
|
|
openedRiskFlagCount: number;
|
|
};
|
|
|
|
export type TravelRequestWithdrawResponse = {
|
|
travelRequestId: string;
|
|
status: "draft";
|
|
outcome: "withdrawn";
|
|
workflowInstanceId: string | null;
|
|
budgetRelease: {
|
|
applied: boolean;
|
|
items?: Array<{ budgetPoolId: string; amount: string }>;
|
|
};
|
|
};
|
|
|
|
function toQueryString(query: Record<string, string | number | boolean | undefined>) {
|
|
const params = new URLSearchParams();
|
|
for (const [key, value] of Object.entries(query)) {
|
|
if (value == null || value === "") {
|
|
continue;
|
|
}
|
|
params.set(key, typeof value === "boolean" ? (value ? "1" : "0") : String(value));
|
|
}
|
|
const encoded = params.toString();
|
|
return encoded ? `?${encoded}` : "";
|
|
}
|
|
|
|
export async function fetchTravelRequests(query: TravelRequestListQuery = {}) {
|
|
return apiRequestData<TravelRequestListResponse>(
|
|
`/api/travel-requests${toQueryString(query as Record<string, string | number | boolean | undefined>)}`,
|
|
);
|
|
}
|
|
|
|
export async function createTravelRequest(payload: TravelRequestPayload) {
|
|
return apiRequestData<TravelRequestDetail>("/api/travel-requests", {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function fetchTravelRequestDetail(travelRequestId: string) {
|
|
return apiRequestData<TravelRequestDetail>(`/api/travel-requests/${travelRequestId}`);
|
|
}
|
|
|
|
export async function updateTravelRequest(travelRequestId: string, payload: Partial<TravelRequestPayload>) {
|
|
return apiRequestData<TravelRequestDetail>(`/api/travel-requests/${travelRequestId}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function precheckTravelRequest(travelRequestId: string) {
|
|
return apiRequestData<PrecheckResponse>(`/api/travel-requests/${travelRequestId}/precheck`, {
|
|
method: "POST",
|
|
body: JSON.stringify({}),
|
|
});
|
|
}
|
|
|
|
export async function submitTravelRequest(travelRequestId: string) {
|
|
return apiRequestData<TravelRequestSubmitResponse>(`/api/travel-requests/${travelRequestId}/submit`, {
|
|
method: "POST",
|
|
body: JSON.stringify({}),
|
|
});
|
|
}
|
|
|
|
export async function withdrawTravelRequest(travelRequestId: string) {
|
|
return apiRequestData<TravelRequestWithdrawResponse>(`/api/travel-requests/${travelRequestId}/withdraw`, {
|
|
method: "POST",
|
|
body: JSON.stringify({}),
|
|
});
|
|
}
|
|
|
|
|
|
export async function linkTravelRequestProject(travelRequestId: string, projectId: string | null) {
|
|
return apiRequestData<{ projectId: string | null; project: { id: string; projectNo: string; name: string } | null }>(`/api/travel-requests/${travelRequestId}/link-project`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ projectId }),
|
|
});
|
|
}
|