feat(P17A-3,P17A-4): 实现采购申请接口附件与前端入口
这个提交包含在:
@@ -0,0 +1,243 @@
|
||||
import { apiRequestData } from "./http";
|
||||
|
||||
export type PurchaseRequestStatus = "draft" | "reviewing" | "approved" | "rejected" | "cancelled";
|
||||
|
||||
export type PurchaseRequestSummary = {
|
||||
id: string;
|
||||
requestNo: string | null;
|
||||
applicantId: string;
|
||||
submitterId: string | null;
|
||||
departmentId: string;
|
||||
legalEntityId: string | null;
|
||||
costCenterId: string | null;
|
||||
supplierId: string | null;
|
||||
documentCategoryId: string | null;
|
||||
budgetPoolId: string | null;
|
||||
title: string;
|
||||
reason: string | null;
|
||||
currencyCode: string;
|
||||
totalAmount: string;
|
||||
expectedDeliveryDate: string | null;
|
||||
status: PurchaseRequestStatus;
|
||||
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;
|
||||
supplier: {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
} | null;
|
||||
documentCategory: {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
entityType: string;
|
||||
} | null;
|
||||
budgetPool: {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type PurchaseRequestItem = {
|
||||
id: string;
|
||||
description: string;
|
||||
specification: string | null;
|
||||
quantity: string;
|
||||
unitPrice: string;
|
||||
lineAmount: string;
|
||||
isAssetCandidate: boolean;
|
||||
createdAt: string | null;
|
||||
updatedAt: string | null;
|
||||
};
|
||||
|
||||
export type PurchaseAttachment = {
|
||||
id: string;
|
||||
originalName: string;
|
||||
mimeType: string;
|
||||
fileSize: number;
|
||||
uploadedBy: string;
|
||||
createdAt: string | null;
|
||||
uploader: {
|
||||
id: string;
|
||||
employeeNo: string;
|
||||
name: string;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type PurchaseRequestDetail = PurchaseRequestSummary & {
|
||||
items: PurchaseRequestItem[];
|
||||
attachments: PurchaseAttachment[];
|
||||
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;
|
||||
};
|
||||
};
|
||||
|
||||
export type PurchaseRequestListResponse = {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
mine: boolean;
|
||||
items: PurchaseRequestSummary[];
|
||||
};
|
||||
|
||||
export type PurchaseRequestListQuery = {
|
||||
status?: PurchaseRequestStatus;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
mine?: boolean;
|
||||
supplierId?: string;
|
||||
legalEntityId?: string;
|
||||
costCenterId?: string;
|
||||
};
|
||||
|
||||
export type PurchaseRequestItemPayload = {
|
||||
description: string;
|
||||
specification?: string | null;
|
||||
quantity: string;
|
||||
unitPrice: string;
|
||||
isAssetCandidate?: boolean;
|
||||
};
|
||||
|
||||
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 fetchPurchaseRequests(query: PurchaseRequestListQuery = {}) {
|
||||
return apiRequestData<PurchaseRequestListResponse>(
|
||||
`/api/purchase-requests${toQueryString(query as Record<string, string | number | boolean | undefined>)}`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function createPurchaseRequest() {
|
||||
return apiRequestData<{ purchaseRequest: PurchaseRequestDetail }>("/api/purchase-requests", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchPurchaseRequestDetail(purchaseRequestId: string) {
|
||||
return apiRequestData<PurchaseRequestDetail>(`/api/purchase-requests/${purchaseRequestId}`);
|
||||
}
|
||||
|
||||
export async function updatePurchaseRequest(
|
||||
purchaseRequestId: string,
|
||||
payload: Partial<{
|
||||
title: string;
|
||||
reason: string | null;
|
||||
currencyCode: string;
|
||||
legalEntityId: string | null;
|
||||
costCenterId: string | null;
|
||||
supplierId: string | null;
|
||||
documentCategoryId: string | null;
|
||||
budgetPoolId: string | null;
|
||||
expectedDeliveryDate: string | null;
|
||||
items: PurchaseRequestItemPayload[];
|
||||
}>,
|
||||
) {
|
||||
return apiRequestData<{ purchaseRequest: PurchaseRequestDetail }>(`/api/purchase-requests/${purchaseRequestId}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export async function submitPurchaseRequest(purchaseRequestId: string) {
|
||||
return apiRequestData<{
|
||||
purchaseRequestId: string;
|
||||
requestNo: string;
|
||||
status: "reviewing";
|
||||
workflowInstanceId: string;
|
||||
currentTaskId: string;
|
||||
budgetPoolId: string | null;
|
||||
warnings: string[];
|
||||
}>(`/api/purchase-requests/${purchaseRequestId}/submit`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
}
|
||||
|
||||
export async function withdrawPurchaseRequest(purchaseRequestId: string) {
|
||||
return apiRequestData<{
|
||||
purchaseRequestId: string;
|
||||
status: "draft";
|
||||
workflowInstanceId: string | null;
|
||||
}>(`/api/purchase-requests/${purchaseRequestId}/withdraw`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
}
|
||||
|
||||
export async function uploadPurchaseAttachment(purchaseRequestId: string, file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return apiRequestData<{ attachment: PurchaseAttachment }>(`/api/purchase-requests/${purchaseRequestId}/attachments`, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
}
|
||||
|
||||
export async function deletePurchaseAttachment(purchaseRequestId: string, attachmentId: string) {
|
||||
return apiRequestData<{ outcome: "deleted"; attachmentId: string }>(
|
||||
`/api/purchase-requests/${purchaseRequestId}/attachments/${attachmentId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function issuePurchaseAttachmentSignedUrl(purchaseRequestId: string, attachmentId: string) {
|
||||
return apiRequestData<{ signedUrl: string; expiresAt: string }>(
|
||||
`/api/purchase-requests/${purchaseRequestId}/attachments/${attachmentId}/signed-url`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({}),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -101,6 +101,41 @@ export type WorkflowTaskDetail = {
|
||||
name: string;
|
||||
} | null;
|
||||
} | null;
|
||||
purchaseRequest: {
|
||||
id: string;
|
||||
requestNo: string | null;
|
||||
status: string;
|
||||
title: string;
|
||||
reason: string | null;
|
||||
currencyCode: string;
|
||||
totalAmount: string | null;
|
||||
expectedDeliveryDate: string | null;
|
||||
applicant: {
|
||||
id: string;
|
||||
employeeNo: string;
|
||||
name: string;
|
||||
} | null;
|
||||
supplier: {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
} | null;
|
||||
legalEntity: {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
} | null;
|
||||
costCenter: {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
} | null;
|
||||
documentCategory: {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
} | null;
|
||||
} | null;
|
||||
allowedActions: Array<"approve" | "reject" | "return" | "transfer">;
|
||||
history: WorkflowHistoryItem[];
|
||||
};
|
||||
|
||||
在新工单中引用
屏蔽一个用户