feat(08-05-business-detail-routing): 完成采购与资产独立路由
这个提交包含在:
@@ -0,0 +1,200 @@
|
||||
import type { PurchaseRequestStatus } from "../api/purchaseRequests";
|
||||
import type { AssetSensitivityLevel, AssetStatus, AssetType } from "../api/assets";
|
||||
|
||||
const PURCHASE_STATUS_VALUES = new Set<PurchaseRequestStatus>([
|
||||
"draft",
|
||||
"reviewing",
|
||||
"approved",
|
||||
"rejected",
|
||||
"cancelled",
|
||||
]);
|
||||
const ASSET_STATUS_VALUES = new Set<AssetStatus>([
|
||||
"in_stock",
|
||||
"in_use",
|
||||
"under_repair",
|
||||
"retired",
|
||||
"archived",
|
||||
]);
|
||||
const ASSET_TYPE_VALUES = new Set<AssetType>([
|
||||
"fixed_asset",
|
||||
"electronic_document",
|
||||
"electronic_system",
|
||||
"code_repository",
|
||||
"backup_media",
|
||||
]);
|
||||
const ASSET_SENSITIVITY_VALUES = new Set<AssetSensitivityLevel>([
|
||||
"public_candidate",
|
||||
"internal",
|
||||
"confidential",
|
||||
"strictly_confidential",
|
||||
]);
|
||||
|
||||
export const ASSET_CREATE_PATH = "/assets/new";
|
||||
|
||||
function normalizedEntityId(value: string) {
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function optionalSearchValue(searchParams: URLSearchParams, key: string) {
|
||||
return (searchParams.get(key) ?? "").trim();
|
||||
}
|
||||
|
||||
function positivePage(value: string | null) {
|
||||
if (!value || !/^[1-9]\d*$/.test(value)) {
|
||||
return 1;
|
||||
}
|
||||
const parsed = Number(value);
|
||||
return Number.isSafeInteger(parsed) ? parsed : 1;
|
||||
}
|
||||
|
||||
function appendOptional(target: URLSearchParams, key: string, value: string) {
|
||||
if (value) {
|
||||
target.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
export function buildPurchaseRequestDetailPath(purchaseRequestId: string) {
|
||||
const normalized = normalizedEntityId(purchaseRequestId);
|
||||
return normalized ? `/purchase-requests/${encodeURIComponent(normalized)}` : "/purchase-requests";
|
||||
}
|
||||
|
||||
export function buildAssetDetailPath(assetId: string) {
|
||||
const normalized = normalizedEntityId(assetId);
|
||||
return normalized ? `/assets/${encodeURIComponent(normalized)}` : "/assets";
|
||||
}
|
||||
|
||||
export function readPurchaseListFilters(
|
||||
searchParams: URLSearchParams,
|
||||
canViewAll: boolean,
|
||||
): {
|
||||
status: PurchaseRequestStatus | "";
|
||||
mine: boolean;
|
||||
supplierId: string;
|
||||
legalEntityId: string;
|
||||
costCenterId: string;
|
||||
page: number;
|
||||
} {
|
||||
const rawStatus = searchParams.get("status");
|
||||
const status = rawStatus === "all"
|
||||
? ""
|
||||
: rawStatus && PURCHASE_STATUS_VALUES.has(rawStatus as PurchaseRequestStatus)
|
||||
? rawStatus as PurchaseRequestStatus
|
||||
: "draft";
|
||||
const scope = searchParams.get("scope");
|
||||
|
||||
return {
|
||||
status,
|
||||
mine: !(canViewAll && scope === "all"),
|
||||
supplierId: optionalSearchValue(searchParams, "supplierId"),
|
||||
legalEntityId: optionalSearchValue(searchParams, "legalEntityId"),
|
||||
costCenterId: optionalSearchValue(searchParams, "costCenterId"),
|
||||
page: positivePage(searchParams.get("page")),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizePurchaseListSearchParams(searchParams: URLSearchParams) {
|
||||
const normalized = new URLSearchParams();
|
||||
const rawStatus = searchParams.get("status");
|
||||
if (rawStatus === "all" || (rawStatus && PURCHASE_STATUS_VALUES.has(rawStatus as PurchaseRequestStatus))) {
|
||||
normalized.set("status", rawStatus);
|
||||
}
|
||||
const rawScope = searchParams.get("scope");
|
||||
if (rawScope === "own" || rawScope === "all") {
|
||||
normalized.set("scope", rawScope);
|
||||
}
|
||||
appendOptional(normalized, "supplierId", optionalSearchValue(searchParams, "supplierId"));
|
||||
appendOptional(normalized, "legalEntityId", optionalSearchValue(searchParams, "legalEntityId"));
|
||||
appendOptional(normalized, "costCenterId", optionalSearchValue(searchParams, "costCenterId"));
|
||||
const page = positivePage(searchParams.get("page"));
|
||||
if (page > 1) {
|
||||
normalized.set("page", String(page));
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function readAssetListFilters(
|
||||
searchParams: URLSearchParams,
|
||||
canViewAll: boolean,
|
||||
): {
|
||||
q: string;
|
||||
status: AssetStatus | "";
|
||||
assetType: AssetType | "";
|
||||
sensitivity: AssetSensitivityLevel | "";
|
||||
categoryId: string;
|
||||
legalEntityId: string;
|
||||
costCenterId: string;
|
||||
assigneeId: string;
|
||||
mine: boolean;
|
||||
} {
|
||||
const rawStatus = searchParams.get("status");
|
||||
const rawAssetType = searchParams.get("assetType");
|
||||
const rawSensitivity = searchParams.get("sensitivity");
|
||||
const scope = searchParams.get("scope");
|
||||
|
||||
return {
|
||||
q: searchParams.get("q") ?? "",
|
||||
status: rawStatus && ASSET_STATUS_VALUES.has(rawStatus as AssetStatus) ? rawStatus as AssetStatus : "",
|
||||
assetType: rawAssetType && ASSET_TYPE_VALUES.has(rawAssetType as AssetType) ? rawAssetType as AssetType : "",
|
||||
sensitivity: rawSensitivity && ASSET_SENSITIVITY_VALUES.has(rawSensitivity as AssetSensitivityLevel)
|
||||
? rawSensitivity as AssetSensitivityLevel
|
||||
: "",
|
||||
categoryId: optionalSearchValue(searchParams, "categoryId"),
|
||||
legalEntityId: optionalSearchValue(searchParams, "legalEntityId"),
|
||||
costCenterId: optionalSearchValue(searchParams, "costCenterId"),
|
||||
assigneeId: optionalSearchValue(searchParams, "assigneeId"),
|
||||
mine: scope === "own" || !(canViewAll && (scope === "all" || scope == null)),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeAssetListSearchParams(searchParams: URLSearchParams) {
|
||||
const normalized = new URLSearchParams();
|
||||
appendOptional(normalized, "q", optionalSearchValue(searchParams, "q"));
|
||||
const rawStatus = searchParams.get("status");
|
||||
if (rawStatus && ASSET_STATUS_VALUES.has(rawStatus as AssetStatus)) {
|
||||
normalized.set("status", rawStatus);
|
||||
}
|
||||
const rawAssetType = searchParams.get("assetType");
|
||||
if (rawAssetType && ASSET_TYPE_VALUES.has(rawAssetType as AssetType)) {
|
||||
normalized.set("assetType", rawAssetType);
|
||||
}
|
||||
const rawSensitivity = searchParams.get("sensitivity");
|
||||
if (rawSensitivity && ASSET_SENSITIVITY_VALUES.has(rawSensitivity as AssetSensitivityLevel)) {
|
||||
normalized.set("sensitivity", rawSensitivity);
|
||||
}
|
||||
appendOptional(normalized, "categoryId", optionalSearchValue(searchParams, "categoryId"));
|
||||
appendOptional(normalized, "legalEntityId", optionalSearchValue(searchParams, "legalEntityId"));
|
||||
appendOptional(normalized, "costCenterId", optionalSearchValue(searchParams, "costCenterId"));
|
||||
appendOptional(normalized, "assigneeId", optionalSearchValue(searchParams, "assigneeId"));
|
||||
const rawScope = searchParams.get("scope");
|
||||
if (rawScope === "own" || rawScope === "all") {
|
||||
normalized.set("scope", rawScope);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function listSource(pathname: string, searchParams: URLSearchParams) {
|
||||
const search = searchParams.toString();
|
||||
return search ? `${pathname}?${search}` : pathname;
|
||||
}
|
||||
|
||||
export function resolvePurchaseRequestLegacyRedirect(searchParams: URLSearchParams) {
|
||||
const purchaseRequestId = normalizedEntityId(searchParams.get("id") ?? "");
|
||||
if (!purchaseRequestId) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
to: buildPurchaseRequestDetailPath(purchaseRequestId),
|
||||
from: listSource("/purchase-requests", normalizePurchaseListSearchParams(searchParams)),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveAssetLegacyRedirect(searchParams: URLSearchParams) {
|
||||
const assetId = normalizedEntityId(searchParams.get("id") ?? "");
|
||||
if (!assetId) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
to: buildAssetDetailPath(assetId),
|
||||
from: listSource("/assets", normalizeAssetListSearchParams(searchParams)),
|
||||
};
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户