R95 A5 合同台账权限请求降级

这个提交包含在:
2026-05-06 00:30:36 +08:00
父节点 e4e47fea3b
当前提交 8b7e043860
+39 -17
查看文件
@@ -19,6 +19,8 @@ import { fetchEmployees } from "../api/employees";
import { mapApiErrorMessage } from "../api/errorMessages"; import { mapApiErrorMessage } from "../api/errorMessages";
import { fetchProjects, type ConfidentialityLevel } from "../api/projects"; import { fetchProjects, type ConfidentialityLevel } from "../api/projects";
import { isApiError } from "../api/types"; import { isApiError } from "../api/types";
import { useAuth } from "../auth/AuthContext";
import { hasAnyPermission } from "../auth/permissions";
import { PageHeader } from "../components/common/PageHeader"; import { PageHeader } from "../components/common/PageHeader";
import { StatusBadge } from "../components/common/StatusBadge"; import { StatusBadge } from "../components/common/StatusBadge";
import { Button } from "../components/ui/button"; import { Button } from "../components/ui/button";
@@ -124,7 +126,9 @@ function buildFiltersPayload(filters: FiltersState): ContractRegistryFilters {
}; };
} }
function buildSavePayload(form: EditorState) { function buildSavePayload(form: EditorState, options: { includeOwnerEmployeeId?: boolean } = {}) {
const includeOwnerEmployeeId = options.includeOwnerEmployeeId ?? true;
return { return {
contractNo: form.contractNo.trim(), contractNo: form.contractNo.trim(),
projectId: form.projectId || null, projectId: form.projectId || null,
@@ -139,7 +143,7 @@ function buildSavePayload(form: EditorState) {
confidentialityLevel: form.confidentialityLevel, confidentialityLevel: form.confidentialityLevel,
archiveLocation: form.archiveLocation.trim(), archiveLocation: form.archiveLocation.trim(),
offlineArchiveNo: form.offlineArchiveNo.trim() || null, offlineArchiveNo: form.offlineArchiveNo.trim() || null,
ownerEmployeeId: form.ownerEmployeeId || undefined, ...(includeOwnerEmployeeId && form.ownerEmployeeId ? { ownerEmployeeId: form.ownerEmployeeId } : {}),
status: form.status, status: form.status,
}; };
} }
@@ -170,8 +174,10 @@ function nextPersistedStatus(item: ContractRegistryItem): ContractRegistryPersis
export function ContractsRegistryPage() { export function ContractsRegistryPage() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { permissions } = useAuth();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const initialProjectId = searchParams.get("projectId") ?? ""; const initialProjectId = searchParams.get("projectId") ?? "";
const canBrowseEmployees = hasAnyPermission(permissions, ["system.users", "report.dashboard.view"]);
const [filters, setFilters] = useState<FiltersState>({ const [filters, setFilters] = useState<FiltersState>({
projectId: initialProjectId, projectId: initialProjectId,
@@ -199,15 +205,21 @@ export function ContractsRegistryPage() {
queryFn: () => fetchProjects({}), queryFn: () => fetchProjects({}),
}); });
const employeesQuery = useQuery({ const employeesQuery = useQuery({
enabled: canBrowseEmployees,
queryKey: ["employees", "contracts-page"], queryKey: ["employees", "contracts-page"],
queryFn: () => fetchEmployees({ page: 1, pageSize: 100, status: "active" }), queryFn: () => fetchEmployees({ page: 1, pageSize: 100, status: "active" }),
}); });
const projectOptions = projectsQuery.data?.items ?? []; const projectOptions = projectsQuery.data?.items ?? [];
const employeeOptions = employeesQuery.data?.items ?? []; const employeeOptions = canBrowseEmployees ? employeesQuery.data?.items ?? [] : [];
const contracts = contractsQuery.data?.items ?? []; const contracts = contractsQuery.data?.items ?? [];
const expiringItems = expiringQuery.data?.items ?? []; const expiringItems = expiringQuery.data?.items ?? [];
const editingItem = editorId ? contracts.find((item) => item.id === editorId) ?? null : null; const editingItem = editorId ? contracts.find((item) => item.id === editorId) ?? null : null;
const readonlyOwnerLabel = editingItem?.owner
? `${editingItem.owner.name} · ${editingItem.owner.employeeNo}`
: editor.ownerEmployeeId
? "保留现有责任人"
: "默认当前用户";
const amountInvalid = Boolean(editor.amount.trim()) && !AMOUNT_PATTERN.test(editor.amount.trim()); const amountInvalid = Boolean(editor.amount.trim()) && !AMOUNT_PATTERN.test(editor.amount.trim());
const refreshAll = async () => { const refreshAll = async () => {
@@ -224,10 +236,11 @@ export function ContractsRegistryPage() {
const saveMutation = useMutation({ const saveMutation = useMutation({
mutationFn: async () => { mutationFn: async () => {
const payload = buildSavePayload(editor, { includeOwnerEmployeeId: canBrowseEmployees });
if (editorId) { if (editorId) {
return patchContractRegistry(editorId, buildSavePayload(editor)); return patchContractRegistry(editorId, payload);
} }
return createContractRegistry(buildSavePayload(editor)); return createContractRegistry(payload);
}, },
onSuccess: async () => { onSuccess: async () => {
await refreshAll(); await refreshAll();
@@ -537,18 +550,27 @@ export function ContractsRegistryPage() {
<div className="space-y-2"> <div className="space-y-2">
<Label>责任人</Label> <Label>责任人</Label>
<select {canBrowseEmployees ? (
className="select-std" <select
value={editor.ownerEmployeeId} className="select-std"
onChange={(event) => setEditor((current) => ({ ...current, ownerEmployeeId: event.target.value }))} value={editor.ownerEmployeeId}
> onChange={(event) => setEditor((current) => ({ ...current, ownerEmployeeId: event.target.value }))}
<option value="">默认当前用户</option> >
{employeeOptions.map((item) => ( <option value="">默认当前用户</option>
<option key={item.id} value={item.id}> {employeeOptions.map((item) => (
{item.name} · {item.employeeNo} <option key={item.id} value={item.id}>
</option> {item.name} · {item.employeeNo}
))} </option>
</select> ))}
</select>
) : (
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-sm">
<div className="font-medium text-zinc-900">{readonlyOwnerLabel}</div>
<div className="mt-1 text-xs text-zinc-500">
当前账号无人员目录权限,新建时使用当前用户,编辑时保留原责任人。
</div>
</div>
)}
</div> </div>
<Button <Button