@multiplatform.one/backoffice 7.20.0 → 7.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/form/DocFormScreen.cjs +27 -4
- package/dist/cjs/form/DocFormScreen.native.js +28 -5
- package/dist/cjs/form/DocFormScreen.native.js.map +1 -1
- package/dist/cjs/list/DoctypeListScreen.cjs +18 -10
- package/dist/cjs/list/DoctypeListScreen.native.js +21 -11
- package/dist/cjs/list/DoctypeListScreen.native.js.map +1 -1
- package/dist/cjs/list/listFields.cjs +1 -1
- package/dist/cjs/list/listFields.native.js +1 -1
- package/dist/cjs/list/listFields.native.js.map +1 -1
- package/dist/cjs/shared/useUserRoles.cjs +6 -1
- package/dist/cjs/shared/useUserRoles.native.js +6 -1
- package/dist/cjs/shared/useUserRoles.native.js.map +1 -1
- package/dist/esm/chrome/BackofficeAwesomebar.native.js.map +1 -1
- package/dist/esm/chrome/CommandPalette.native.js.map +1 -1
- package/dist/esm/chrome/HelpMenu.native.js.map +1 -1
- package/dist/esm/chrome/NotificationsButton.native.js.map +1 -1
- package/dist/esm/chrome/UserMenu.native.js.map +1 -1
- package/dist/esm/chrome/awesomebar-data.native.js.map +1 -1
- package/dist/esm/chrome/command-palette-model.native.js.map +1 -1
- package/dist/esm/chrome/help-about-dialog.native.js.map +1 -1
- package/dist/esm/chrome/help-shortcuts-dialog.native.js.map +1 -1
- package/dist/esm/chrome/notifications-data.native.js.map +1 -1
- package/dist/esm/chrome/notifications-panel.native.js.map +1 -1
- package/dist/esm/chrome/notifications-utils.native.js.map +1 -1
- package/dist/esm/chrome/shortcuts.native.js.map +1 -1
- package/dist/esm/chrome/user-session.native.js.map +1 -1
- package/dist/esm/config/BackofficeProvider.native.js.map +1 -1
- package/dist/esm/config/capabilities.native.js.map +1 -1
- package/dist/esm/config/events.native.js.map +1 -1
- package/dist/esm/config/slots.native.js.map +1 -1
- package/dist/esm/desk/Desk.native.js.map +1 -1
- package/dist/esm/desk/DeskSurface.native.js.map +1 -1
- package/dist/esm/desk/resolveDeskPath.native.js.map +1 -1
- package/dist/esm/desk/scope.native.js.map +1 -1
- package/dist/esm/desk/target.native.js.map +1 -1
- package/dist/esm/form/DocFormScreen.mjs +28 -5
- package/dist/esm/form/DocFormScreen.mjs.map +1 -1
- package/dist/esm/form/DocFormScreen.native.js +29 -6
- package/dist/esm/form/DocFormScreen.native.js.map +1 -1
- package/dist/esm/form/FormConnections.native.js.map +1 -1
- package/dist/esm/form/FormDialogs.native.js.map +1 -1
- package/dist/esm/form/FormErrorBoundary.native.js.map +1 -1
- package/dist/esm/form/FormHeaderCluster.native.js.map +1 -1
- package/dist/esm/form/FormSidebarPanel.native.js.map +1 -1
- package/dist/esm/form/FormTimeline.native.js.map +1 -1
- package/dist/esm/form/FormToolbar.native.js.map +1 -1
- package/dist/esm/form/connections-data.native.js.map +1 -1
- package/dist/esm/form/createPrefill.native.js.map +1 -1
- package/dist/esm/form/dependsOn.native.js.map +1 -1
- package/dist/esm/form/dirtyTracking.native.js.map +1 -1
- package/dist/esm/form/docinfo.native.js.map +1 -1
- package/dist/esm/form/fetchFrom.native.js.map +1 -1
- package/dist/esm/form/fieldPermissions.native.js.map +1 -1
- package/dist/esm/form/formActions.native.js.map +1 -1
- package/dist/esm/form/formLayout.native.js.map +1 -1
- package/dist/esm/form/platformHooks.native.js.map +1 -1
- package/dist/esm/form/sidebar-attachments.native.js.map +1 -1
- package/dist/esm/form/sidebar-dialogs.native.js.map +1 -1
- package/dist/esm/form/sidebar-people.native.js.map +1 -1
- package/dist/esm/form/sidebar-rpc.native.js.map +1 -1
- package/dist/esm/form/sidebar-utils.native.js.map +1 -1
- package/dist/esm/form/timeline-entries.native.js.map +1 -1
- package/dist/esm/form/timeline-utils.native.js.map +1 -1
- package/dist/esm/form/useFormEngine.native.js.map +1 -1
- package/dist/esm/i18n/en.native.js.map +1 -1
- package/dist/esm/i18n/index.native.js.map +1 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/list/AddTagsDialog.native.js.map +1 -1
- package/dist/esm/list/AssignToDialog.native.js.map +1 -1
- package/dist/esm/list/DoctypeListScreen.mjs +19 -11
- package/dist/esm/list/DoctypeListScreen.mjs.map +1 -1
- package/dist/esm/list/DoctypeListScreen.native.js +22 -12
- package/dist/esm/list/DoctypeListScreen.native.js.map +1 -1
- package/dist/esm/list/ListBulkActionsBar.native.js.map +1 -1
- package/dist/esm/list/ListFilterBar.native.js.map +1 -1
- package/dist/esm/list/ListSidebar.native.js.map +1 -1
- package/dist/esm/list/bulk-edit-dialog.native.js.map +1 -1
- package/dist/esm/list/bulk-export.native.js.map +1 -1
- package/dist/esm/list/bulk-submit-cancel.native.js.map +1 -1
- package/dist/esm/list/bulkActions.native.js.map +1 -1
- package/dist/esm/list/filter-chips.native.js.map +1 -1
- package/dist/esm/list/filter-editor.native.js.map +1 -1
- package/dist/esm/list/filter-fields.native.js.map +1 -1
- package/dist/esm/list/filter-saved.native.js.map +1 -1
- package/dist/esm/list/filter-sort.native.js.map +1 -1
- package/dist/esm/list/filter-standard.native.js.map +1 -1
- package/dist/esm/list/filterConversion.native.js.map +1 -1
- package/dist/esm/list/list-tools-sheet.native.js.map +1 -1
- package/dist/esm/list/listColumns.native.js.map +1 -1
- package/dist/esm/list/listFields.mjs +1 -1
- package/dist/esm/list/listFields.mjs.map +1 -1
- package/dist/esm/list/listFields.native.js +1 -1
- package/dist/esm/list/listFields.native.js.map +1 -1
- package/dist/esm/list/listFormat.native.js.map +1 -1
- package/dist/esm/list/listUrlState.native.js.map +1 -1
- package/dist/esm/list/sidebar-section.native.js.map +1 -1
- package/dist/esm/list/sidebar-stats.native.js.map +1 -1
- package/dist/esm/list/statusColors.native.js.map +1 -1
- package/dist/esm/navigation/navigator.native.js.map +1 -1
- package/dist/esm/navigation/paths.native.js.map +1 -1
- package/dist/esm/oneAdapter/index.native.js.map +1 -1
- package/dist/esm/quickEntry/QuickEntryDialog.native.js.map +1 -1
- package/dist/esm/quickEntry/quickEntryPlan.native.js.map +1 -1
- package/dist/esm/report/DoctypeReportView.native.js.map +1 -1
- package/dist/esm/report/QueryReportScreen.native.js.map +1 -1
- package/dist/esm/report/queryReportColumns.native.js.map +1 -1
- package/dist/esm/report/queryReportFilterBar.native.js.map +1 -1
- package/dist/esm/report/queryReportUrlState.native.js.map +1 -1
- package/dist/esm/report/report-grid.native.js.map +1 -1
- package/dist/esm/report/report-toolbar.native.js.map +1 -1
- package/dist/esm/report/reportShapes.native.js.map +1 -1
- package/dist/esm/report/reportViewModel.native.js.map +1 -1
- package/dist/esm/report/savedReports.native.js.map +1 -1
- package/dist/esm/report/useQueryReport.native.js.map +1 -1
- package/dist/esm/shared/deskLinks.native.js.map +1 -1
- package/dist/esm/shared/listNavContext.native.js.map +1 -1
- package/dist/esm/shared/types.native.js.map +1 -1
- package/dist/esm/shared/useDocinfo.native.js.map +1 -1
- package/dist/esm/shared/useDoctypeMeta.native.js.map +1 -1
- package/dist/esm/shared/useFrappeApp.native.js.map +1 -1
- package/dist/esm/shared/useRecentVisit.native.js.map +1 -1
- package/dist/esm/shared/useSessionUserId.native.js.map +1 -1
- package/dist/esm/shared/useSlugTarget.native.js.map +1 -1
- package/dist/esm/shared/useUserRoles.mjs +7 -2
- package/dist/esm/shared/useUserRoles.mjs.map +1 -1
- package/dist/esm/shared/useUserRoles.native.js +7 -2
- package/dist/esm/shared/useUserRoles.native.js.map +1 -1
- package/dist/esm/shell/BackofficeShell.native.js.map +1 -1
- package/dist/esm/shell/HomeRedirect.native.js.map +1 -1
- package/dist/esm/shell/NotFoundScreen.native.js.map +1 -1
- package/dist/esm/shell/SlugScreen.native.js.map +1 -1
- package/dist/esm/shell/WorkspaceSidebar.native.js.map +1 -1
- package/dist/esm/shell/useWorkspaces.native.js.map +1 -1
- package/dist/esm/shell/workspaceIcons.native.js.map +1 -1
- package/dist/esm/views/CalendarView.native.js.map +1 -1
- package/dist/esm/views/DashboardView.native.js.map +1 -1
- package/dist/esm/views/DoctypeViewScreen.native.js.map +1 -1
- package/dist/esm/views/ImageView.native.js.map +1 -1
- package/dist/esm/views/KanbanView.native.js.map +1 -1
- package/dist/esm/views/ReportView.native.js.map +1 -1
- package/dist/esm/views/TreeView.native.js.map +1 -1
- package/dist/esm/views/ViewHeader.native.js.map +1 -1
- package/dist/esm/views/ViewStates.native.js.map +1 -1
- package/dist/esm/views/ViewSwitcherMenu.native.js.map +1 -1
- package/dist/esm/views/calendar-field-selector.native.js.map +1 -1
- package/dist/esm/views/connection.native.js.map +1 -1
- package/dist/esm/views/dashboard-data.native.js.map +1 -1
- package/dist/esm/views/deskViewConfigs.native.js.map +1 -1
- package/dist/esm/views/kanban-board-controls.native.js.map +1 -1
- package/dist/esm/views/kanban-field-picker.native.js.map +1 -1
- package/dist/esm/views/kanbanBoardOps.native.js.map +1 -1
- package/dist/esm/views/report-controls.native.js.map +1 -1
- package/dist/esm/views/viewAvailability.native.js.map +1 -1
- package/dist/esm/views/viewMeta.native.js.map +1 -1
- package/dist/esm/workspace/WorkspaceChartBlock.native.js.map +1 -1
- package/dist/esm/workspace/WorkspaceGrid.native.js.map +1 -1
- package/dist/esm/workspace/WorkspaceLinkCard.native.js.map +1 -1
- package/dist/esm/workspace/WorkspaceLinkDirectory.native.js.map +1 -1
- package/dist/esm/workspace/WorkspaceNumberCard.native.js.map +1 -1
- package/dist/esm/workspace/WorkspaceQuickList.native.js.map +1 -1
- package/dist/esm/workspace/WorkspaceRecents.native.js.map +1 -1
- package/dist/esm/workspace/WorkspaceScreen.native.js.map +1 -1
- package/dist/esm/workspace/WorkspaceShortcut.native.js.map +1 -1
- package/dist/esm/workspace/parseContent.native.js.map +1 -1
- package/dist/esm/workspace/types.native.js.map +1 -1
- package/dist/esm/workspace/useDashboardChart.native.js.map +1 -1
- package/dist/esm/workspace/useWorkspaceDoc.native.js.map +1 -1
- package/dist/jsx/form/DocFormScreen.mjs +28 -5
- package/dist/jsx/form/DocFormScreen.mjs.map +1 -1
- package/dist/jsx/form/DocFormScreen.native.js +28 -5
- package/dist/jsx/form/DocFormScreen.native.js.map +1 -1
- package/dist/jsx/list/DoctypeListScreen.mjs +19 -11
- package/dist/jsx/list/DoctypeListScreen.mjs.map +1 -1
- package/dist/jsx/list/DoctypeListScreen.native.js +21 -11
- package/dist/jsx/list/DoctypeListScreen.native.js.map +1 -1
- package/dist/jsx/list/listFields.mjs +1 -1
- package/dist/jsx/list/listFields.mjs.map +1 -1
- package/dist/jsx/list/listFields.native.js +1 -1
- package/dist/jsx/shared/useUserRoles.mjs +7 -2
- package/dist/jsx/shared/useUserRoles.mjs.map +1 -1
- package/dist/jsx/shared/useUserRoles.native.js +6 -1
- package/dist/jsx/shared/useUserRoles.native.js.map +1 -1
- package/package.json +12 -12
- package/src/form/DocFormScreen.rights.spec.tsx +130 -0
- package/src/form/DocFormScreen.tsx +34 -6
- package/src/list/DoctypeListScreen.rights.spec.tsx +113 -0
- package/src/list/DoctypeListScreen.tsx +76 -52
- package/src/list/listFields.spec.ts +37 -1
- package/src/list/listFields.ts +1 -1
- package/src/shared/useUserRoles.ts +7 -6
- package/types/form/DocFormScreen.d.ts.map +1 -1
- package/types/list/DoctypeListScreen.d.ts.map +1 -1
- package/types/shared/useUserRoles.d.ts +4 -3
- package/types/shared/useUserRoles.d.ts.map +1 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// DocFormScreen's permission fallback (MPO-390). When docinfo carries no
|
|
2
|
+
// usable permissions (a new record, or a token-auth bench answering
|
|
3
|
+
// {"null": 0}) the screen used to union every role's DocPerm rows, so any
|
|
4
|
+
// reader got an editable form and Save. It now resolves the session's roles.
|
|
5
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
6
|
+
import { act, screen, waitFor } from "@testing-library/react";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
8
|
+
import { BackofficeProvider } from "../config/BackofficeProvider";
|
|
9
|
+
import { createMemoryNavigator } from "../navigation/navigator";
|
|
10
|
+
import { DocFormScreen } from "./DocFormScreen";
|
|
11
|
+
|
|
12
|
+
const doctype = "Poke Move";
|
|
13
|
+
|
|
14
|
+
const meta = {
|
|
15
|
+
name: doctype,
|
|
16
|
+
doctype: "DocType",
|
|
17
|
+
title_field: "move_name",
|
|
18
|
+
fields: [{ fieldname: "move_name", fieldtype: "Data", label: "Move Name" }],
|
|
19
|
+
permissions: [
|
|
20
|
+
{ role: "Pokemon Editor", read: 1, write: 1, create: 1, delete: 1 },
|
|
21
|
+
{ role: "Guest", read: 1 },
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const tackle = {
|
|
26
|
+
name: "a1b2c3",
|
|
27
|
+
doctype,
|
|
28
|
+
owner: "Administrator",
|
|
29
|
+
modified: "2026-09-27 00:00:00.000000",
|
|
30
|
+
docstatus: 0,
|
|
31
|
+
move_name: "tackle",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let run = 0;
|
|
35
|
+
let roles: string[] = [];
|
|
36
|
+
let sessionAnswered = false;
|
|
37
|
+
|
|
38
|
+
function json(body: unknown, status = 200) {
|
|
39
|
+
return new Response(JSON.stringify(body), {
|
|
40
|
+
status,
|
|
41
|
+
headers: { "content-type": "application/json" },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
run += 1;
|
|
47
|
+
sessionAnswered = false;
|
|
48
|
+
vi.stubGlobal("fetch", async (input: string | URL | Request) => {
|
|
49
|
+
const url = new URL(String(input instanceof Request ? input.url : input));
|
|
50
|
+
const method = url.pathname.replace(/^\/api\/method\//, "");
|
|
51
|
+
if (method === "frappe.auth.get_logged_user") return json({ message: "user@pokemon.test" });
|
|
52
|
+
if (method === "frappe.core.doctype.user.user.get_roles") {
|
|
53
|
+
sessionAnswered = true;
|
|
54
|
+
return json({ message: roles });
|
|
55
|
+
}
|
|
56
|
+
if (method === "frappe.desk.form.load.getdoctype") return json({ docs: [meta] });
|
|
57
|
+
if (method === "frappe.desk.form.load.getdoc") {
|
|
58
|
+
return json({ docs: [tackle], docinfo: { permissions: { null: 0 } } });
|
|
59
|
+
}
|
|
60
|
+
if (method === "frappe.desk.form.load.get_docinfo") {
|
|
61
|
+
return json({ docinfo: { permissions: { null: 0 } } });
|
|
62
|
+
}
|
|
63
|
+
const resource = `/api/resource/${encodeURIComponent(doctype)}`;
|
|
64
|
+
if (url.pathname === `${resource}/a1b2c3`) return json({ data: tackle });
|
|
65
|
+
if (url.pathname === resource) return json({ data: [tackle] });
|
|
66
|
+
return json({ data: [], message: [] });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
vi.restoreAllMocks();
|
|
72
|
+
vi.unstubAllGlobals();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
function renderForm(name: string) {
|
|
76
|
+
return renderWithProviders(
|
|
77
|
+
<BackofficeProvider
|
|
78
|
+
frappe={{ baseURL: `https://form-rights-${run}.spec.test` }}
|
|
79
|
+
navigation={createMemoryNavigator("/backoffice")}
|
|
80
|
+
>
|
|
81
|
+
<DocFormScreen doctype={doctype} name={name} />
|
|
82
|
+
</BackofficeProvider>,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function saveButton(): HTMLElement | undefined {
|
|
87
|
+
return Array.from(document.querySelectorAll('[role="button"], button')).find(
|
|
88
|
+
(node) => node.textContent?.trim() === "Save",
|
|
89
|
+
) as HTMLElement | undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function moveNameInput(): HTMLInputElement {
|
|
93
|
+
const field = screen.getByLabelText("Move Name", { exact: false });
|
|
94
|
+
return (
|
|
95
|
+
field instanceof HTMLInputElement ? field : field.querySelector("input")
|
|
96
|
+
) as HTMLInputElement;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function settle() {
|
|
100
|
+
await waitFor(() => expect(sessionAnswered).toBe(true), { timeout: 5000 });
|
|
101
|
+
await act(async () => {
|
|
102
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
describe("DocFormScreen rights without docinfo permissions", () => {
|
|
107
|
+
it("lets an editor save a new record", async () => {
|
|
108
|
+
roles = ["Pokemon Editor", "Guest"];
|
|
109
|
+
renderForm("new");
|
|
110
|
+
await waitFor(() => expect(saveButton()).toBeTruthy(), { timeout: 5000 });
|
|
111
|
+
expect(moveNameInput().disabled || moveNameInput().readOnly).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("gives a reader a read-only new record with no Save", async () => {
|
|
115
|
+
roles = ["Guest"];
|
|
116
|
+
renderForm("new");
|
|
117
|
+
await waitFor(() => expect(moveNameInput()).toBeTruthy(), { timeout: 5000 });
|
|
118
|
+
await settle();
|
|
119
|
+
expect(saveButton()).toBeUndefined();
|
|
120
|
+
expect(moveNameInput().disabled || moveNameInput().readOnly).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("gives a reader an existing record read-only", async () => {
|
|
124
|
+
roles = ["Guest"];
|
|
125
|
+
renderForm("a1b2c3");
|
|
126
|
+
await waitFor(() => expect(moveNameInput().value).toBe("tackle"), { timeout: 5000 });
|
|
127
|
+
await settle();
|
|
128
|
+
expect(moveNameInput().disabled || moveNameInput().readOnly).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -30,7 +30,12 @@ import {
|
|
|
30
30
|
type TabsItem,
|
|
31
31
|
} from "@multiplatform.one/components";
|
|
32
32
|
import { Input } from "@multiplatform.one/forms";
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
SchemaForm,
|
|
35
|
+
sessionDocRights,
|
|
36
|
+
useFrappeConfig,
|
|
37
|
+
type DocShareGrant,
|
|
38
|
+
} from "@multiplatform.one/frappe-ui";
|
|
34
39
|
import { useLayoutSizeClass, useResolvedKnobs } from "@multiplatform.one/theme";
|
|
35
40
|
import { ListBulletsIcon } from "@phosphor-icons/react";
|
|
36
41
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -222,10 +227,14 @@ export function DocFormScreen({
|
|
|
222
227
|
}, [modifiedStamp, docName, doctype, reloadDocinfo]);
|
|
223
228
|
|
|
224
229
|
// -----------------------------------------------------------------------
|
|
225
|
-
// Permissions: per-doc flags from docinfo when usable, else the
|
|
226
|
-
//
|
|
227
|
-
//
|
|
230
|
+
// Permissions: per-doc flags from docinfo when usable, else the session's
|
|
231
|
+
// roles against meta's DocPerm rows (sessionDocRights). Fixture mode has no
|
|
232
|
+
// session, so it keeps the union of every role's rows.
|
|
228
233
|
// -----------------------------------------------------------------------
|
|
234
|
+
const { roles, user: sessionUser } = useUserRoles();
|
|
235
|
+
const fixtureMode = Boolean(config?.fixtures);
|
|
236
|
+
const recordOwner =
|
|
237
|
+
!isCreate && typeof engine.baseline?.owner === "string" ? engine.baseline.owner : undefined;
|
|
229
238
|
const perms = useMemo(() => {
|
|
230
239
|
const docPerms = normalizeDocPermissions(
|
|
231
240
|
docinfo?.permissions as Record<string, number> | undefined,
|
|
@@ -242,6 +251,26 @@ export function DocFormScreen({
|
|
|
242
251
|
print: !!docPerms.print,
|
|
243
252
|
};
|
|
244
253
|
}
|
|
254
|
+
if (!fixtureMode) {
|
|
255
|
+
const rights = sessionDocRights({
|
|
256
|
+
fixtures: false,
|
|
257
|
+
meta,
|
|
258
|
+
roles,
|
|
259
|
+
user: sessionUser,
|
|
260
|
+
owner: recordOwner,
|
|
261
|
+
shares: docinfo?.shared as DocShareGrant[] | undefined,
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
write: rights.write,
|
|
265
|
+
create: rights.create,
|
|
266
|
+
delete: rights.delete,
|
|
267
|
+
submit: rights.submit,
|
|
268
|
+
cancel: rights.cancel,
|
|
269
|
+
amend: rights.amend,
|
|
270
|
+
email: rights.email,
|
|
271
|
+
print: rights.print,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
245
274
|
const rules = meta?.permissions ?? [];
|
|
246
275
|
const anyRole = (key: keyof (typeof rules)[number]) =>
|
|
247
276
|
rules.some((rule) => !rule.permlevel && !!rule[key]);
|
|
@@ -255,7 +284,7 @@ export function DocFormScreen({
|
|
|
255
284
|
email: rules.length === 0 || anyRole("email"),
|
|
256
285
|
print: rules.length === 0 || anyRole("print"),
|
|
257
286
|
};
|
|
258
|
-
}, [docinfo?.permissions, meta
|
|
287
|
+
}, [docinfo?.permissions, docinfo?.shared, meta, fixtureMode, roles, sessionUser, recordOwner]);
|
|
259
288
|
|
|
260
289
|
const isSubmittable = !!meta?.is_submittable;
|
|
261
290
|
const formReadOnly =
|
|
@@ -270,7 +299,6 @@ export function DocFormScreen({
|
|
|
270
299
|
// -----------------------------------------------------------------------
|
|
271
300
|
// Permlevel matrix: session roles × meta.permissions (desk perm.js). Roles
|
|
272
301
|
// stay undefined in fixture mode → coarse permlevel>0 read-only fallback.
|
|
273
|
-
const { roles } = useUserRoles();
|
|
274
302
|
const permLevels = useMemo(
|
|
275
303
|
() => buildPermLevelMatrix(meta?.permissions, roles),
|
|
276
304
|
[meta?.permissions, roles],
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// The backoffice list offers New and the write/delete bulk actions only as
|
|
2
|
+
// far as the session's roles grant them on the doctype (MPO-390), measured
|
|
3
|
+
// against a live-shaped bench: getdoctype for meta, the session RPCs for the
|
|
4
|
+
// user and roles, and empty answers for everything else the list asks.
|
|
5
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
6
|
+
import { act, fireEvent, screen, waitFor } from "@testing-library/react";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
8
|
+
import { BackofficeProvider } from "../config/BackofficeProvider";
|
|
9
|
+
import { createMemoryNavigator } from "../navigation/navigator";
|
|
10
|
+
import { DoctypeListScreen } from "./DoctypeListScreen";
|
|
11
|
+
|
|
12
|
+
const doctype = "Poke Move";
|
|
13
|
+
|
|
14
|
+
const meta = {
|
|
15
|
+
name: doctype,
|
|
16
|
+
doctype: "DocType",
|
|
17
|
+
title_field: "move_name",
|
|
18
|
+
fields: [{ fieldname: "move_name", fieldtype: "Data", label: "Move Name", in_list_view: 1 }],
|
|
19
|
+
permissions: [
|
|
20
|
+
{ role: "Pokemon Editor", read: 1, write: 1, create: 1, delete: 1 },
|
|
21
|
+
{ role: "Guest", read: 1 },
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
let run = 0;
|
|
26
|
+
let roles: string[] = [];
|
|
27
|
+
let sessionAnswered = false;
|
|
28
|
+
|
|
29
|
+
function json(body: unknown, status = 200) {
|
|
30
|
+
return new Response(JSON.stringify(body), {
|
|
31
|
+
status,
|
|
32
|
+
headers: { "content-type": "application/json" },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
run += 1;
|
|
38
|
+
sessionAnswered = false;
|
|
39
|
+
vi.stubGlobal("fetch", async (input: string | URL | Request) => {
|
|
40
|
+
const url = new URL(String(input instanceof Request ? input.url : input));
|
|
41
|
+
const method = url.pathname.replace(/^\/api\/method\//, "");
|
|
42
|
+
if (method === "frappe.auth.get_logged_user") return json({ message: "user@pokemon.test" });
|
|
43
|
+
if (method === "frappe.core.doctype.user.user.get_roles") {
|
|
44
|
+
sessionAnswered = true;
|
|
45
|
+
return json({ message: roles });
|
|
46
|
+
}
|
|
47
|
+
if (method === "frappe.desk.form.load.getdoctype") return json({ docs: [meta] });
|
|
48
|
+
if (method === "frappe.client.get_count") return json({ message: 1 });
|
|
49
|
+
if (url.pathname === `/api/resource/${encodeURIComponent(doctype)}`) {
|
|
50
|
+
return json({ data: [{ name: "a1b2c3", doctype, move_name: "tackle" }] });
|
|
51
|
+
}
|
|
52
|
+
return json({ data: [], message: [] });
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
vi.restoreAllMocks();
|
|
58
|
+
vi.unstubAllGlobals();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
function renderList() {
|
|
62
|
+
return renderWithProviders(
|
|
63
|
+
<BackofficeProvider
|
|
64
|
+
frappe={{ baseURL: `https://list-rights-${run}.spec.test` }}
|
|
65
|
+
navigation={createMemoryNavigator("/backoffice")}
|
|
66
|
+
>
|
|
67
|
+
<DoctypeListScreen doctype={doctype} />
|
|
68
|
+
</BackofficeProvider>,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function settle() {
|
|
73
|
+
await waitFor(() => expect(sessionAnswered).toBe(true));
|
|
74
|
+
await act(async () => {
|
|
75
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function selectRow() {
|
|
80
|
+
const checkbox = await waitFor(() => screen.getAllByLabelText(/^Select row a1b2c3$/)[0], {
|
|
81
|
+
timeout: 5000,
|
|
82
|
+
});
|
|
83
|
+
fireEvent.click(checkbox as HTMLElement);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function bulkActionLabels(): string[] {
|
|
87
|
+
return Array.from(document.querySelectorAll('[role="button"], button'), (node) =>
|
|
88
|
+
node.textContent?.trim(),
|
|
89
|
+
).filter((label): label is string => label === "Bulk Edit" || label === "Delete");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
describe("DoctypeListScreen rights", () => {
|
|
93
|
+
it("offers an editor New, Bulk Edit and Delete", async () => {
|
|
94
|
+
roles = ["Pokemon Editor", "Guest"];
|
|
95
|
+
renderList();
|
|
96
|
+
await waitFor(() => expect(screen.getByLabelText(`New ${doctype}`)).toBeTruthy());
|
|
97
|
+
await selectRow();
|
|
98
|
+
await waitFor(() => expect(bulkActionLabels()).toEqual(["Bulk Edit", "Delete"]));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("offers a reader no New, no Bulk Edit and no Delete", async () => {
|
|
102
|
+
roles = ["Guest"];
|
|
103
|
+
renderList();
|
|
104
|
+
await waitFor(() => expect(screen.getByRole("heading", { name: doctype })).toBeTruthy());
|
|
105
|
+
await settle();
|
|
106
|
+
expect(screen.queryByLabelText(`New ${doctype}`)).toBeNull();
|
|
107
|
+
await selectRow();
|
|
108
|
+
await act(async () => {
|
|
109
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
110
|
+
});
|
|
111
|
+
expect(bulkActionLabels()).toEqual([]);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
import {
|
|
34
34
|
FrappeTable,
|
|
35
35
|
docFieldsToFilterFields,
|
|
36
|
+
sessionDocRights,
|
|
36
37
|
useFrappeAggregate,
|
|
37
38
|
useFrappeConfig,
|
|
38
39
|
} from "@multiplatform.one/frappe-ui";
|
|
@@ -222,8 +223,17 @@ export function DoctypeListScreen({
|
|
|
222
223
|
if (metaError && !meta) emitEvent({ type: "error", error: metaError, context: "list-meta" });
|
|
223
224
|
}, [metaError, meta, emitEvent]);
|
|
224
225
|
|
|
225
|
-
//
|
|
226
|
-
|
|
226
|
+
// Session roles × meta.permissions: the doctype-level rights (desk
|
|
227
|
+
// get_role_permissions) gate New and the write/delete bulk actions; fixture
|
|
228
|
+
// mode has no session and withholds nothing.
|
|
229
|
+
const { roles, user: rolesUser } = useUserRoles();
|
|
230
|
+
const rights = useMemo(
|
|
231
|
+
() => sessionDocRights({ fixtures: fixtureMode, meta, roles, user: rolesUser }),
|
|
232
|
+
[fixtureMode, meta, roles, rolesUser],
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// "New" affordances: host prop + the newDoc capability + the create right.
|
|
236
|
+
const showNew = !hideNewButton && capabilities.newDoc.enabled && rights.create;
|
|
227
237
|
|
|
228
238
|
// ONE FILTER SYSTEM (MOD-04): `baseFilters` is the single writable filter
|
|
229
239
|
// state for this screen. Every filter surface — quick standard filters,
|
|
@@ -405,7 +415,6 @@ export function DoctypeListScreen({
|
|
|
405
415
|
// fields above the roles' read level are pruned (desk hides them; the
|
|
406
416
|
// server strips their values anyway). Undefined (fixtures / roles RPC
|
|
407
417
|
// unavailable) prunes nothing — never hides more than desk would.
|
|
408
|
-
const { roles } = useUserRoles();
|
|
409
418
|
const permMatrix = useMemo(
|
|
410
419
|
() => buildPermLevelMatrix(meta?.permissions, roles),
|
|
411
420
|
[meta?.permissions, roles],
|
|
@@ -690,14 +699,19 @@ export function DoctypeListScreen({
|
|
|
690
699
|
},
|
|
691
700
|
] as BulkAction<ListRow>[])
|
|
692
701
|
: []),
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
702
|
+
...(rights.write
|
|
703
|
+
? ([
|
|
704
|
+
{
|
|
705
|
+
id: "edit",
|
|
706
|
+
label: t("list.editTitle", "Bulk Edit"),
|
|
707
|
+
onExecute: (items, clearSelection) => {
|
|
708
|
+
const names = items.map((item) => item.name).filter(Boolean) as string[];
|
|
709
|
+
if (names.length > 0)
|
|
710
|
+
setPendingDialog({ type: "edit", names, clear: clearSelection });
|
|
711
|
+
},
|
|
712
|
+
},
|
|
713
|
+
] as BulkAction<ListRow>[])
|
|
714
|
+
: []),
|
|
701
715
|
{
|
|
702
716
|
id: "export",
|
|
703
717
|
label: t("list.export", "Export CSV"),
|
|
@@ -706,7 +720,7 @@ export function DoctypeListScreen({
|
|
|
706
720
|
if (names.length > 0) await handleBulkExport(names);
|
|
707
721
|
},
|
|
708
722
|
},
|
|
709
|
-
...(isSubmittable
|
|
723
|
+
...(isSubmittable && rights.submit
|
|
710
724
|
? ([
|
|
711
725
|
{
|
|
712
726
|
id: "submit",
|
|
@@ -724,6 +738,10 @@ export function DoctypeListScreen({
|
|
|
724
738
|
clearSelection();
|
|
725
739
|
},
|
|
726
740
|
},
|
|
741
|
+
] as BulkAction<ListRow>[])
|
|
742
|
+
: []),
|
|
743
|
+
...(isSubmittable && rights.cancel
|
|
744
|
+
? ([
|
|
727
745
|
{
|
|
728
746
|
id: "cancel",
|
|
729
747
|
label: t("list.cancel", "Cancel"),
|
|
@@ -742,47 +760,51 @@ export function DoctypeListScreen({
|
|
|
742
760
|
},
|
|
743
761
|
] as BulkAction<ListRow>[])
|
|
744
762
|
: []),
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
if (!app) return;
|
|
757
|
-
const names = items.map((item) => item.name).filter(Boolean) as string[];
|
|
758
|
-
if (names.length === 0) return;
|
|
759
|
-
const result = await bulkDelete(app, doctype, names);
|
|
760
|
-
if (result.failed.length > 0) {
|
|
761
|
-
notify({
|
|
762
|
-
severity: "warning",
|
|
763
|
-
scope: "page",
|
|
764
|
-
body: t("list.deletePartial", {
|
|
765
|
-
count: result.succeeded.length,
|
|
766
|
-
total: names.length,
|
|
767
|
-
names: result.failed.map((failure) => failure.name).join(", "),
|
|
768
|
-
defaultValue: "Deleted {{count}} of {{total}} documents; failed: {{names}}",
|
|
769
|
-
}),
|
|
770
|
-
});
|
|
771
|
-
} else {
|
|
772
|
-
notify({
|
|
773
|
-
severity: "success",
|
|
774
|
-
scope: "page",
|
|
775
|
-
body: t("list.deleteDone", {
|
|
776
|
-
count: result.succeeded.length,
|
|
777
|
-
defaultValue_one: "Deleted {{count}} document",
|
|
778
|
-
defaultValue_other: "Deleted {{count}} documents",
|
|
763
|
+
...(rights.delete
|
|
764
|
+
? ([
|
|
765
|
+
{
|
|
766
|
+
id: "delete",
|
|
767
|
+
label: t("list.delete", "Delete"),
|
|
768
|
+
variant: "destructive",
|
|
769
|
+
requiresConfirmation: true,
|
|
770
|
+
confirmTitle: t("list.deleteTitle", "Delete documents"),
|
|
771
|
+
confirmMessage: t("list.deleteConfirm", {
|
|
772
|
+
defaultValue: "Permanently delete the selected {{doctype}} documents?",
|
|
773
|
+
doctype,
|
|
779
774
|
}),
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
775
|
+
onExecute: async (items, clearSelection) => {
|
|
776
|
+
if (!app) return;
|
|
777
|
+
const names = items.map((item) => item.name).filter(Boolean) as string[];
|
|
778
|
+
if (names.length === 0) return;
|
|
779
|
+
const result = await bulkDelete(app, doctype, names);
|
|
780
|
+
if (result.failed.length > 0) {
|
|
781
|
+
notify({
|
|
782
|
+
severity: "warning",
|
|
783
|
+
scope: "page",
|
|
784
|
+
body: t("list.deletePartial", {
|
|
785
|
+
count: result.succeeded.length,
|
|
786
|
+
total: names.length,
|
|
787
|
+
names: result.failed.map((failure) => failure.name).join(", "),
|
|
788
|
+
defaultValue: "Deleted {{count}} of {{total}} documents; failed: {{names}}",
|
|
789
|
+
}),
|
|
790
|
+
});
|
|
791
|
+
} else {
|
|
792
|
+
notify({
|
|
793
|
+
severity: "success",
|
|
794
|
+
scope: "page",
|
|
795
|
+
body: t("list.deleteDone", {
|
|
796
|
+
count: result.succeeded.length,
|
|
797
|
+
defaultValue_one: "Deleted {{count}} document",
|
|
798
|
+
defaultValue_other: "Deleted {{count}} documents",
|
|
799
|
+
}),
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
clearSelection();
|
|
803
|
+
count.refresh();
|
|
804
|
+
},
|
|
805
|
+
},
|
|
806
|
+
] as BulkAction<ListRow>[])
|
|
807
|
+
: []),
|
|
786
808
|
...hostListActions,
|
|
787
809
|
],
|
|
788
810
|
[
|
|
@@ -796,6 +818,7 @@ export function DoctypeListScreen({
|
|
|
796
818
|
capabilities.assignments.enabled,
|
|
797
819
|
capabilities.tags.enabled,
|
|
798
820
|
hostListActions,
|
|
821
|
+
rights,
|
|
799
822
|
],
|
|
800
823
|
);
|
|
801
824
|
|
|
@@ -1060,6 +1083,7 @@ export function DoctypeListScreen({
|
|
|
1060
1083
|
{...(bulkActionsEnabled ? { bulkActions } : {})}
|
|
1061
1084
|
onRowClick={handleRowClick}
|
|
1062
1085
|
onStateChange={handleStateChange}
|
|
1086
|
+
rights={rights}
|
|
1063
1087
|
layout="list"
|
|
1064
1088
|
/>
|
|
1065
1089
|
)}
|
|
@@ -86,7 +86,7 @@ describe("listFieldsToFetch permlevel pruning", () => {
|
|
|
86
86
|
const matrix = buildPermLevelMatrix(meta.permissions, ["Desk User"]);
|
|
87
87
|
const fields = listFieldsToFetch(meta, matrix);
|
|
88
88
|
expect(fields).toEqual(
|
|
89
|
-
expect.arrayContaining(["name", "modified", "full_name", "enabled", "
|
|
89
|
+
expect.arrayContaining(["name", "modified", "full_name", "enabled", "_comments"]),
|
|
90
90
|
);
|
|
91
91
|
expect(fields).not.toContain("role_profile_name");
|
|
92
92
|
expect(fields).not.toContain("api_secret_hint");
|
|
@@ -99,6 +99,42 @@ describe("listFieldsToFetch permlevel pruning", () => {
|
|
|
99
99
|
});
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
+
// frappe/model/__init__.py default_fields and optional_fields: the only names
|
|
103
|
+
// frappe.desk.reportview.validate_fields lets through without a DocField.
|
|
104
|
+
// Anything else that isn't a field of the doctype answers 417 "Field not
|
|
105
|
+
// permitted in query" for every user without the query bypass (MPO-389).
|
|
106
|
+
const frappeStandardFields = new Set([
|
|
107
|
+
"doctype",
|
|
108
|
+
"name",
|
|
109
|
+
"owner",
|
|
110
|
+
"creation",
|
|
111
|
+
"modified",
|
|
112
|
+
"modified_by",
|
|
113
|
+
"docstatus",
|
|
114
|
+
"idx",
|
|
115
|
+
"_user_tags",
|
|
116
|
+
"_comments",
|
|
117
|
+
"_assign",
|
|
118
|
+
"_liked_by",
|
|
119
|
+
"_seen",
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
describe("listFieldsToFetch asks only for columns Frappe will select", () => {
|
|
123
|
+
it("names nothing but the doctype's fields and Frappe's standard columns", () => {
|
|
124
|
+
const fieldnames = new Set(meta.fields.map((field) => field.fieldname));
|
|
125
|
+
const rejected = listFieldsToFetch(meta, undefined).filter(
|
|
126
|
+
(name) => !fieldnames.has(name) && !frappeStandardFields.has(name),
|
|
127
|
+
);
|
|
128
|
+
expect(rejected).toEqual([]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("reads the comment count from _comments instead of the computed _comment_count", () => {
|
|
132
|
+
const fields = listFieldsToFetch(meta, undefined);
|
|
133
|
+
expect(fields).toContain("_comments");
|
|
134
|
+
expect(fields).not.toContain("_comment_count");
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
102
138
|
describe("listDocumentColumnIds desk list anatomy", () => {
|
|
103
139
|
it("is title, in_list_view values, then the activity cluster — no extra ID/modified", () => {
|
|
104
140
|
expect(listDocumentColumnIds(meta)).toEqual([
|
package/src/list/listFields.ts
CHANGED
|
@@ -24,7 +24,7 @@ export function listFieldsToFetch(meta?: DoctypeMeta, permMatrix?: PermLevelMatr
|
|
|
24
24
|
"_liked_by",
|
|
25
25
|
"_assign",
|
|
26
26
|
"_user_tags",
|
|
27
|
-
"
|
|
27
|
+
"_comments",
|
|
28
28
|
]);
|
|
29
29
|
if (meta?.title_field) fields.add(meta.title_field);
|
|
30
30
|
for (const field of meta?.fields ?? []) {
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Session user's role list for backoffice surfaces, read off the ambient
|
|
3
3
|
* FrappeProvider. The fetch itself lives in frappe-ui's form engine
|
|
4
|
-
* (`
|
|
5
|
-
* source; `roles`
|
|
6
|
-
* the RPC fails.
|
|
4
|
+
* (`useFrappeSession`) so FrappeForm and the desk resolve permlevel and doc
|
|
5
|
+
* rights from one source; `roles` and `user` stay undefined while loading,
|
|
6
|
+
* in fixture mode, or when the RPC fails.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { useFrappeConfig,
|
|
9
|
+
import { useFrappeConfig, useFrappeSession } from "@multiplatform.one/frappe-ui";
|
|
10
10
|
|
|
11
|
-
export function useUserRoles(): { roles?: string[] } {
|
|
11
|
+
export function useUserRoles(): { roles?: string[]; user?: string } {
|
|
12
12
|
const config = useFrappeConfig();
|
|
13
|
-
|
|
13
|
+
const { roles, user } = useFrappeSession(config);
|
|
14
|
+
return { roles, user };
|
|
14
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocFormScreen.d.ts","sourceRoot":"","sources":["../../src/form/DocFormScreen.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DocFormScreen.d.ts","sourceRoot":"","sources":["../../src/form/DocFormScreen.tsx"],"names":[],"mappings":"AA0CA,OAAO,EAML,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AAqCtC,oDAAoD;AACpD,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uEAAuE;IACvE,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,2CAA2C;IAC3C,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,+EAA+E;IAC/E,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACpE,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,wDAAwD;IACxD,eAAe,CAAC,EAAE,sBAAsB,CAAC;CAC1C;AAID,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,SAAS,EACT,eAAe,GAChB,EAAE,kBAAkB,2CA4/BpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DoctypeListScreen.d.ts","sourceRoot":"","sources":["../../src/list/DoctypeListScreen.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DoctypeListScreen.d.ts","sourceRoot":"","sources":["../../src/list/DoctypeListScreen.tsx"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EAEV,eAAe,EAIhB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAOL,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAS1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,KAAK,WAAW,EAAkB,MAAM,0BAA0B,CAAC;AAoB5E,OAAO,EAAgB,KAAK,OAAO,EAAoB,MAAM,eAAe,CAAC;AAY7E,MAAM,WAAW,sBAAsB;IACrC,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAClC;;;OAGG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B;;;OAGG;IACH,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,8EAA8E;IAC9E,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,EAAE,KAAK,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;IACtF,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gFAAgF;IAChF,cAAc,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC9C;AAsBD,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,KAAK,EAAE,cAAc,EACrB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,SAAS,EACT,aAAa,EACb,cAAc,GACf,EAAE,sBAAsB,2CAq8BxB"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Session user's role list for backoffice surfaces, read off the ambient
|
|
3
3
|
* FrappeProvider. The fetch itself lives in frappe-ui's form engine
|
|
4
|
-
* (`
|
|
5
|
-
* source; `roles`
|
|
6
|
-
* the RPC fails.
|
|
4
|
+
* (`useFrappeSession`) so FrappeForm and the desk resolve permlevel and doc
|
|
5
|
+
* rights from one source; `roles` and `user` stay undefined while loading,
|
|
6
|
+
* in fixture mode, or when the RPC fails.
|
|
7
7
|
*/
|
|
8
8
|
export declare function useUserRoles(): {
|
|
9
9
|
roles?: string[];
|
|
10
|
+
user?: string;
|
|
10
11
|
};
|
|
11
12
|
//# sourceMappingURL=useUserRoles.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUserRoles.d.ts","sourceRoot":"","sources":["../../src/shared/useUserRoles.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,wBAAgB,YAAY,IAAI;IAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"useUserRoles.d.ts","sourceRoot":"","sources":["../../src/shared/useUserRoles.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,wBAAgB,YAAY,IAAI;IAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAIlE"}
|