R95 A5 合同台账权限请求降级
这个提交包含在:
@@ -19,6 +19,8 @@ import { fetchEmployees } from "../api/employees";
|
||||
import { mapApiErrorMessage } from "../api/errorMessages";
|
||||
import { fetchProjects, type ConfidentialityLevel } from "../api/projects";
|
||||
import { isApiError } from "../api/types";
|
||||
import { useAuth } from "../auth/AuthContext";
|
||||
import { hasAnyPermission } from "../auth/permissions";
|
||||
import { PageHeader } from "../components/common/PageHeader";
|
||||
import { StatusBadge } from "../components/common/StatusBadge";
|
||||
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 {
|
||||
contractNo: form.contractNo.trim(),
|
||||
projectId: form.projectId || null,
|
||||
@@ -139,7 +143,7 @@ function buildSavePayload(form: EditorState) {
|
||||
confidentialityLevel: form.confidentialityLevel,
|
||||
archiveLocation: form.archiveLocation.trim(),
|
||||
offlineArchiveNo: form.offlineArchiveNo.trim() || null,
|
||||
ownerEmployeeId: form.ownerEmployeeId || undefined,
|
||||
...(includeOwnerEmployeeId && form.ownerEmployeeId ? { ownerEmployeeId: form.ownerEmployeeId } : {}),
|
||||
status: form.status,
|
||||
};
|
||||
}
|
||||
@@ -170,8 +174,10 @@ function nextPersistedStatus(item: ContractRegistryItem): ContractRegistryPersis
|
||||
|
||||
export function ContractsRegistryPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const { permissions } = useAuth();
|
||||
const [searchParams] = useSearchParams();
|
||||
const initialProjectId = searchParams.get("projectId") ?? "";
|
||||
const canBrowseEmployees = hasAnyPermission(permissions, ["system.users", "report.dashboard.view"]);
|
||||
|
||||
const [filters, setFilters] = useState<FiltersState>({
|
||||
projectId: initialProjectId,
|
||||
@@ -199,15 +205,21 @@ export function ContractsRegistryPage() {
|
||||
queryFn: () => fetchProjects({}),
|
||||
});
|
||||
const employeesQuery = useQuery({
|
||||
enabled: canBrowseEmployees,
|
||||
queryKey: ["employees", "contracts-page"],
|
||||
queryFn: () => fetchEmployees({ page: 1, pageSize: 100, status: "active" }),
|
||||
});
|
||||
|
||||
const projectOptions = projectsQuery.data?.items ?? [];
|
||||
const employeeOptions = employeesQuery.data?.items ?? [];
|
||||
const employeeOptions = canBrowseEmployees ? employeesQuery.data?.items ?? [] : [];
|
||||
const contracts = contractsQuery.data?.items ?? [];
|
||||
const expiringItems = expiringQuery.data?.items ?? [];
|
||||
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 refreshAll = async () => {
|
||||
@@ -224,10 +236,11 @@ export function ContractsRegistryPage() {
|
||||
|
||||
const saveMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
const payload = buildSavePayload(editor, { includeOwnerEmployeeId: canBrowseEmployees });
|
||||
if (editorId) {
|
||||
return patchContractRegistry(editorId, buildSavePayload(editor));
|
||||
return patchContractRegistry(editorId, payload);
|
||||
}
|
||||
return createContractRegistry(buildSavePayload(editor));
|
||||
return createContractRegistry(payload);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await refreshAll();
|
||||
@@ -537,18 +550,27 @@ export function ContractsRegistryPage() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>责任人</Label>
|
||||
<select
|
||||
className="select-std"
|
||||
value={editor.ownerEmployeeId}
|
||||
onChange={(event) => setEditor((current) => ({ ...current, ownerEmployeeId: event.target.value }))}
|
||||
>
|
||||
<option value="">默认当前用户</option>
|
||||
{employeeOptions.map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.name} · {item.employeeNo}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{canBrowseEmployees ? (
|
||||
<select
|
||||
className="select-std"
|
||||
value={editor.ownerEmployeeId}
|
||||
onChange={(event) => setEditor((current) => ({ ...current, ownerEmployeeId: event.target.value }))}
|
||||
>
|
||||
<option value="">默认当前用户</option>
|
||||
{employeeOptions.map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.name} · {item.employeeNo}
|
||||
</option>
|
||||
))}
|
||||
</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>
|
||||
|
||||
<Button
|
||||
|
||||
在新工单中引用
屏蔽一个用户