feat(P20T1-4): 差旅前端入口与审批移动端联动
这个提交包含在:
@@ -0,0 +1,218 @@
|
||||
import { apiRequestData } from "./http";
|
||||
|
||||
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;
|
||||
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;
|
||||
occurredAt: string | null;
|
||||
amount: string | null;
|
||||
currency: string;
|
||||
status: string;
|
||||
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;
|
||||
};
|
||||
};
|
||||
|
||||
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 }>;
|
||||
};
|
||||
};
|
||||
|
||||
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 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({}),
|
||||
});
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户