124 行
3.6 KiB
TypeScript
124 行
3.6 KiB
TypeScript
import { apiRequestData } from "./http";
|
|
|
|
export type RiskFlag = {
|
|
id: string;
|
|
entityType: string;
|
|
entityId: string;
|
|
riskCode: string;
|
|
level: "low" | "medium" | "high";
|
|
title: string;
|
|
detail: string | null;
|
|
source: string;
|
|
status: "open" | "resolved" | "dismissed";
|
|
payloadJson: Record<string, unknown> | null;
|
|
createdAt: string | null;
|
|
createdBy: string | null;
|
|
resolvedAt: string | null;
|
|
resolvedBy: string | null;
|
|
};
|
|
|
|
export type PrecheckDecision = "pass" | "warn" | "manual_review" | "block";
|
|
|
|
export type ExpenseStandardCheck = {
|
|
itemId: string;
|
|
expenseTypeId: string;
|
|
expenseTypeName: string;
|
|
amount: string;
|
|
standardId: string;
|
|
standardName: string;
|
|
maxAmount: string;
|
|
perUnit: "item" | "day" | "trip";
|
|
controlMode: "warn" | "block";
|
|
scopeType: "global" | "department";
|
|
scopeKey: string | null;
|
|
exceeded: boolean;
|
|
exceededAmount: string;
|
|
};
|
|
|
|
export type PrecheckResponse = {
|
|
entityType: "reimbursement" | "purchase_request" | "travel_request";
|
|
entityId: string;
|
|
decision: PrecheckDecision;
|
|
matchedBudgetControlRule: {
|
|
id: string;
|
|
name: string;
|
|
controlMode: "warn" | "block" | "manual_review";
|
|
} | null;
|
|
budgetHint: Record<string, unknown> | null;
|
|
standardChecks: ExpenseStandardCheck[];
|
|
evidenceSummary?: {
|
|
invoiceCount: number;
|
|
attachmentCount: number;
|
|
items: Array<{
|
|
itemId: string;
|
|
linkedInvoiceCount: number;
|
|
invoiceState: "linked" | "unlinked";
|
|
}>;
|
|
};
|
|
timelinessChecks?: Array<{
|
|
itemId: string;
|
|
expenseDate: string;
|
|
ageDays: number;
|
|
maxAgeDays: 180 | 365;
|
|
overdue: boolean;
|
|
crossCalendarYear: boolean;
|
|
}>;
|
|
assetHint?: { required: boolean; itemCount: number };
|
|
warnings: Array<{ code: string; title: string; detail: string }>;
|
|
blockingReasons: Array<{ code: string; title: string; detail: string }>;
|
|
riskFlags: Array<{ code: string; level: "low" | "medium" | "high"; title: string; detail: string; willOpenOnSubmit: boolean }>;
|
|
nextActions: string[];
|
|
};
|
|
|
|
export type RiskFlagListResponse = {
|
|
items: RiskFlag[];
|
|
};
|
|
|
|
function buildRiskFlagsSearch(query: {
|
|
entityType?: string;
|
|
entityId?: string;
|
|
status?: "open" | "resolved" | "dismissed";
|
|
source?: string;
|
|
}) {
|
|
const params = new URLSearchParams();
|
|
if (query.entityType) params.set("entityType", query.entityType);
|
|
if (query.entityId) params.set("entityId", query.entityId);
|
|
if (query.status) params.set("status", query.status);
|
|
if (query.source) params.set("source", query.source);
|
|
const search = params.toString();
|
|
return search ? `?${search}` : "";
|
|
}
|
|
|
|
export async function precheckEntity(payload: {
|
|
entityType: "reimbursement" | "purchase_request" | "travel_request";
|
|
entityId: string;
|
|
}) {
|
|
return apiRequestData<PrecheckResponse>("/api/fee-control/precheck", {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function fetchRiskFlags(query: {
|
|
entityType?: string;
|
|
entityId?: string;
|
|
status?: "open" | "resolved" | "dismissed";
|
|
source?: string;
|
|
} = {}) {
|
|
return apiRequestData<RiskFlagListResponse>(`/api/risk-flags${buildRiskFlagsSearch(query)}`);
|
|
}
|
|
|
|
export async function resolveRiskFlag(riskFlagId: string, payload: { note?: string | null } = {}) {
|
|
return apiRequestData<{ item: RiskFlag }>(`/api/risk-flags/${riskFlagId}/resolve`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function dismissRiskFlag(riskFlagId: string, payload: { note?: string | null } = {}) {
|
|
return apiRequestData<{ item: RiskFlag }>(`/api/risk-flags/${riskFlagId}/dismiss`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|