feat: init support departments (#788)
Co-authored-by: sealday <sealday@gmail.com> Reviewed-on: daoyoucloud/tachybase#788
This commit is contained in:
parent
4bc1eb5ac2
commit
c949bf4141
36
package.json
36
package.json
@ -41,37 +41,37 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pm2": "^5.2.0",
|
"pm2": "^5.3.1",
|
||||||
"rimraf": "^3.0.0"
|
"rimraf": "^3.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/cli": "^2.27.1",
|
"@changesets/cli": "^2.27.1",
|
||||||
"@commitlint/cli": "^16.1.0",
|
"@commitlint/cli": "^16.3.0",
|
||||||
"@commitlint/config-conventional": "^16.0.0",
|
"@commitlint/config-conventional": "^16.2.4",
|
||||||
"@commitlint/prompt-cli": "^16.1.0",
|
"@commitlint/prompt-cli": "^16.3.0",
|
||||||
"@nocobase/build": "workspace:*",
|
"@nocobase/build": "workspace:*",
|
||||||
"@nocobase/test": "workspace:*",
|
"@nocobase/test": "workspace:*",
|
||||||
"@tachybase/cli": "workspace:*",
|
"@tachybase/cli": "workspace:*",
|
||||||
"@tachybase/preset-tachybase": "workspace:*",
|
"@tachybase/preset-tachybase": "workspace:*",
|
||||||
"@types/react": "^18.2.73",
|
"@types/react": "^18.2.79",
|
||||||
"@types/react-dom": "^18.2.23",
|
"@types/react-dom": "^18.2.25",
|
||||||
"auto-changelog": "^2.4.0",
|
"auto-changelog": "^2.4.0",
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.6.8",
|
||||||
"commander": "^9.2.0",
|
"commander": "^9.5.0",
|
||||||
"eslint-plugin-jest-dom": "^5.0.1",
|
"eslint-plugin-jest-dom": "^5.4.0",
|
||||||
"eslint-plugin-testing-library": "^5.11.0",
|
"eslint-plugin-testing-library": "^5.11.1",
|
||||||
"husky": "^9.0.11",
|
"husky": "^9.0.11",
|
||||||
"lint-staged": "^13.2.3",
|
"lint-staged": "^13.3.0",
|
||||||
"prettier": "^3.1.1",
|
"prettier": "^3.2.5",
|
||||||
"prettier-plugin-packagejson": "^2.4.14",
|
"prettier-plugin-packagejson": "^2.5.0",
|
||||||
"prettier-plugin-sql": "^0.17.0",
|
"prettier-plugin-sql": "^0.17.1",
|
||||||
"pretty-format": "^24.0.0",
|
"pretty-format": "^24.9.0",
|
||||||
"pretty-quick": "^3.1.0",
|
"pretty-quick": "^3.3.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"tsx": "^4.7.2",
|
"tsx": "^4.7.2",
|
||||||
"typescript": "5.4.5",
|
"typescript": "5.4.5",
|
||||||
"umi": "^4.1.8"
|
"umi": "^4.1.10"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.15.5",
|
"packageManager": "pnpm@8.15.5",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"ws": "^8.13.0"
|
"ws": "^8.13.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
"@nocobase/plugin-acl": "workspace:*",
|
||||||
"@nocobase/actions": "workspace:*",
|
"@nocobase/actions": "workspace:*",
|
||||||
"@nocobase/client": "workspace:*",
|
"@nocobase/client": "workspace:*",
|
||||||
"@nocobase/database": "workspace:*",
|
"@nocobase/database": "workspace:*",
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
import { useFieldSchema } from '@nocobase/schema';
|
import { useFieldSchema } from '@nocobase/schema';
|
||||||
import { App } from 'antd';
|
import { App } from 'antd';
|
||||||
import { useMatch } from 'react-router-dom';
|
import { useMatch } from 'react-router-dom';
|
||||||
import { useTranslation } from '../locale';
|
import { useTranslation } from '../../locale';
|
||||||
|
|
||||||
export function usePageSettingsProps() {
|
export function usePageSettingsProps() {
|
||||||
const isAdmin = useMatch('/admin/:name');
|
const isAdmin = useMatch('/admin/:name');
|
@ -74,7 +74,8 @@ import {
|
|||||||
useCustomPresets1,
|
useCustomPresets1,
|
||||||
} from './schema-settings/SchemaSettingsDatePresets';
|
} from './schema-settings/SchemaSettingsDatePresets';
|
||||||
import { SchemaSettingsSubmitDataType } from './schema-settings/SchemaSettingsSubmitDataType';
|
import { SchemaSettingsSubmitDataType } from './schema-settings/SchemaSettingsSubmitDataType';
|
||||||
import { EmbedPlugin } from './embed';
|
import { EmbedPlugin } from './features/embed';
|
||||||
|
import { DepartmentsPlugin } from './features/departments';
|
||||||
export { usePDFViewerRef } from './schema-initializer';
|
export { usePDFViewerRef } from './schema-initializer';
|
||||||
export * from './components/custom-components/custom-components';
|
export * from './components/custom-components/custom-components';
|
||||||
|
|
||||||
@ -84,6 +85,7 @@ export class PluginCoreClient extends Plugin {
|
|||||||
async afterAdd() {
|
async afterAdd() {
|
||||||
await this.app.pm.add(GroupBlockPlugin);
|
await this.app.pm.add(GroupBlockPlugin);
|
||||||
await this.app.pm.add(EmbedPlugin);
|
await this.app.pm.add(EmbedPlugin);
|
||||||
|
await this.app.pm.add(DepartmentsPlugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerSettings() {
|
async registerSettings() {
|
||||||
|
@ -1,23 +1,57 @@
|
|||||||
{
|
{
|
||||||
"Add custom component": "Add custom component",
|
"Add custom component": "Add custom component",
|
||||||
|
"Add department": "Add department",
|
||||||
|
"Add departments": "Add departments",
|
||||||
|
"Add members": "Add members",
|
||||||
|
"All users": "All users",
|
||||||
|
"Are you sure you want to remove it?": "Are you sure you want to remove it?",
|
||||||
|
"Are you sure you want to remove these departments?": "Are you sure you want to remove these departments?",
|
||||||
|
"Are you sure you want to remove these members?": "Are you sure you want to remove these members?",
|
||||||
"Association field": "Association field",
|
"Association field": "Association field",
|
||||||
"Chuangxing presets": "Chuangxing presets",
|
"Chuangxing presets": "Chuangxing presets",
|
||||||
"Configure": "Configure",
|
"Configure": "Configure",
|
||||||
"Custom filter": "Custom filter",
|
"Custom filter": "Custom filter",
|
||||||
"Custom option label": "Custom option label",
|
"Custom option label": "Custom option label",
|
||||||
|
"Delete department": "Delete department",
|
||||||
|
"Department name": "Department name",
|
||||||
|
"Department": "Department",
|
||||||
|
"Departments management": "Departments management",
|
||||||
|
"Departments": "Departments",
|
||||||
|
"Edit department": "Edit department",
|
||||||
"Group block": "Group block",
|
"Group block": "Group block",
|
||||||
"Group": "Group",
|
"Group": "Group",
|
||||||
"HomePage Config": "HomePage Config",
|
"HomePage Config": "HomePage Config",
|
||||||
"Last month": "Last month",
|
"Last month": "Last month",
|
||||||
|
"Main department": "Main department",
|
||||||
|
"Main": "Main",
|
||||||
"Mobile UI": "Mobile UI",
|
"Mobile UI": "Mobile UI",
|
||||||
"Navigate to new page": "Navigate to new page",
|
"Navigate to new page": "Navigate to new page",
|
||||||
|
"New department": "New department",
|
||||||
|
"New sub department": "New sub department",
|
||||||
|
"Owner": "Owner",
|
||||||
|
"Owners": "Owners",
|
||||||
"Page style": "Page Style",
|
"Page style": "Page Style",
|
||||||
"Pagination": "Pagination",
|
"Pagination": "Pagination",
|
||||||
"Pick a data entry for viewing and editing": "Pick a data entry for viewing and editing",
|
"Pick a data entry for viewing and editing": "Pick a data entry for viewing and editing",
|
||||||
|
"Please select departments": "Please select departments",
|
||||||
|
"Please select members": "Please select members",
|
||||||
"Please select": "Please select",
|
"Please select": "Please select",
|
||||||
|
"Remove department": "Remove department",
|
||||||
|
"Remove departments": "Remove departments",
|
||||||
|
"Remove member": "Remove member",
|
||||||
|
"Remove members": "Remove members",
|
||||||
|
"Roles management": "Roles management",
|
||||||
|
"Search for departments, users": "Search for departments, users",
|
||||||
|
"Search results": "Search results",
|
||||||
|
"Select submit data type": "Select submit data type",
|
||||||
|
"Set as main department": "Set as main department",
|
||||||
"Signature input": "Signature input",
|
"Signature input": "Signature input",
|
||||||
"Something went wrong. Please contact the developer to resolve the issue.": "Something went wrong. Please contact the developer to resolve the issue.",
|
"Something went wrong. Please contact the developer to resolve the issue.": "Something went wrong. Please contact the developer to resolve the issue.",
|
||||||
|
"Superior department": "Superior department",
|
||||||
"System setting": "System setting",
|
"System setting": "System setting",
|
||||||
|
"The department has members, please remove them first": "The department has members, please remove them first",
|
||||||
|
"The department has sub-departments, please delete them first": "The department has sub-departments, please delete them first",
|
||||||
|
"This field is currently not supported for use in form blocks.": "This field is currently not supported for use in form blocks.",
|
||||||
"This month": "This month",
|
"This month": "This month",
|
||||||
"This year": "This year",
|
"This year": "This year",
|
||||||
"classical": "classical",
|
"classical": "classical",
|
||||||
@ -27,6 +61,5 @@
|
|||||||
"loading...": "loading...",
|
"loading...": "loading...",
|
||||||
"preview block": "preview block",
|
"preview block": "preview block",
|
||||||
"tabs": "tabs",
|
"tabs": "tabs",
|
||||||
"year": "year",
|
"year": "year"
|
||||||
"Select submit data type": "Select submit data type"
|
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,57 @@
|
|||||||
{
|
{
|
||||||
"Add custom component": "添加自定义组件",
|
"Add custom component": "添加自定义组件",
|
||||||
|
"Add department": "添加部门",
|
||||||
|
"Add departments": "添加部门",
|
||||||
|
"Add members": "添加成员",
|
||||||
|
"All users": "所有用户",
|
||||||
|
"Are you sure you want to remove it?": "你确定要移除吗?",
|
||||||
|
"Are you sure you want to remove these departments?": "你确定要移除这些部门吗?",
|
||||||
|
"Are you sure you want to remove these members?": "你确定要移除这些成员吗?",
|
||||||
"Association field": "关联字段",
|
"Association field": "关联字段",
|
||||||
"Chuangxing presets": "创兴预置",
|
"Chuangxing presets": "创兴预置",
|
||||||
"Configure": "配置",
|
"Configure": "配置",
|
||||||
"Custom filter": "自定义筛选",
|
"Custom filter": "自定义筛选",
|
||||||
"Custom option label": "自定义选项标签",
|
"Custom option label": "自定义选项标签",
|
||||||
"Group": "汇总",
|
"Delete department": "删除部门",
|
||||||
|
"Department name": "部门名称",
|
||||||
|
"Department": "部门",
|
||||||
|
"Departments management": "部门管理",
|
||||||
|
"Departments": "部门",
|
||||||
|
"Edit department": "编辑部门",
|
||||||
"Group block": "汇总区块",
|
"Group block": "汇总区块",
|
||||||
|
"Group": "汇总",
|
||||||
"HomePage Config": "主页配置",
|
"HomePage Config": "主页配置",
|
||||||
"Last month": "上月",
|
"Last month": "上月",
|
||||||
|
"Main department": "主属部门",
|
||||||
|
"Main": "主属部门",
|
||||||
"Mobile UI": "移动端页面",
|
"Mobile UI": "移动端页面",
|
||||||
"Navigate to new page": "导航到新页面",
|
"Navigate to new page": "导航到新页面",
|
||||||
|
"New department": "新建部门",
|
||||||
|
"New sub department": "新建子部门",
|
||||||
|
"Owner": "负责人",
|
||||||
|
"Owners": "负责人",
|
||||||
"Page style": "页面版本",
|
"Page style": "页面版本",
|
||||||
"Pagination": "分页",
|
"Pagination": "分页",
|
||||||
"Pick a data entry for viewing and editing": "选择一条数据用于查看与编辑",
|
"Pick a data entry for viewing and editing": "选择一条数据用于查看与编辑",
|
||||||
|
"Please select departments": "请选择部门",
|
||||||
|
"Please select members": "请选择成员",
|
||||||
"Please select": "请选择",
|
"Please select": "请选择",
|
||||||
|
"Remove department": "移除部门",
|
||||||
|
"Remove departments": "移除部门",
|
||||||
|
"Remove member": "移除成员",
|
||||||
|
"Remove members": "移除成员",
|
||||||
|
"Roles management": "角色管理",
|
||||||
|
"Search for departments, users": "搜索部门、用户",
|
||||||
|
"Search results": "搜索结果",
|
||||||
|
"Select submit data type": "增量提交",
|
||||||
|
"Set as main department": "设置为主属部门",
|
||||||
"Signature input": "手写签名",
|
"Signature input": "手写签名",
|
||||||
"Something went wrong. Please contact the developer to resolve the issue.": "系统内部错误,请联系开发者解决",
|
"Something went wrong. Please contact the developer to resolve the issue.": "系统内部错误,请联系开发者解决",
|
||||||
|
"Superior department": "上级部门",
|
||||||
"System setting": "系统设置",
|
"System setting": "系统设置",
|
||||||
|
"The department has members, please remove them first": "部门下有成员,请先移除",
|
||||||
|
"The department has sub-departments, please delete them first": "部门下有子部门,请先删除子部门",
|
||||||
|
"This field is currently not supported for use in form blocks.": "该字段目前不支持在表单区块中使用。",
|
||||||
"This month": "本月",
|
"This month": "本月",
|
||||||
"This year": "今年",
|
"This year": "今年",
|
||||||
"classical": "经典版",
|
"classical": "经典版",
|
||||||
@ -27,6 +61,5 @@
|
|||||||
"loading...": "加载中...",
|
"loading...": "加载中...",
|
||||||
"preview block": "预览区块",
|
"preview block": "预览区块",
|
||||||
"tabs": "多标签",
|
"tabs": "多标签",
|
||||||
"year": "年",
|
"year": "年"
|
||||||
"Select submit data type": "增量提交"
|
|
||||||
}
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
import type { Context, Next } from '@nocobase/actions';
|
||||||
|
|
||||||
|
export const getAppendsOwners = async (ctx: Context, next: Next) => {
|
||||||
|
const { filterByTk, appends } = ctx.action.params;
|
||||||
|
const repo = ctx.db.getRepository('departments');
|
||||||
|
const department = await repo.findOne({
|
||||||
|
filterByTk,
|
||||||
|
appends,
|
||||||
|
});
|
||||||
|
const owners = await department.getOwners();
|
||||||
|
department.setDataValue('owners', owners);
|
||||||
|
ctx.body = department;
|
||||||
|
await next();
|
||||||
|
};
|
||||||
|
export const aggregateSearch = async (ctx: Context, next: Next) => {
|
||||||
|
const { keyword, type, last = 0, limit = 10 } = ctx.action.params.values || {};
|
||||||
|
let users = [];
|
||||||
|
let departments = [];
|
||||||
|
if (!type || type === 'user') {
|
||||||
|
const repo = ctx.db.getRepository('users');
|
||||||
|
users = await repo.find({
|
||||||
|
filter: {
|
||||||
|
id: { $gt: last },
|
||||||
|
$or: [
|
||||||
|
{ username: { $includes: keyword } },
|
||||||
|
{ nickname: { $includes: keyword } },
|
||||||
|
{ phone: { $includes: keyword } },
|
||||||
|
{ email: { $includes: keyword } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!type || type === 'department') {
|
||||||
|
const repo = ctx.db.getRepository('departments');
|
||||||
|
departments = await repo.find({
|
||||||
|
filter: {
|
||||||
|
id: { $gt: last },
|
||||||
|
title: { $includes: keyword },
|
||||||
|
},
|
||||||
|
appends: ['parent(recursively=true)'],
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ctx.body = { users, departments };
|
||||||
|
await next();
|
||||||
|
};
|
||||||
|
export const setOwner = async (ctx: Context, next: Next) => {
|
||||||
|
const { userId, departmentId } = ctx.action.params.values || {};
|
||||||
|
const throughRepo = ctx.db.getRepository('departmentsUsers');
|
||||||
|
await throughRepo.update({
|
||||||
|
filter: {
|
||||||
|
userId,
|
||||||
|
departmentId,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
isOwner: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await next();
|
||||||
|
};
|
||||||
|
export const removeOwner = async (ctx: Context, next: Next) => {
|
||||||
|
const { userId, departmentId } = ctx.action.params.values || {};
|
||||||
|
const throughRepo = ctx.db.getRepository('departmentsUsers');
|
||||||
|
await throughRepo.update({
|
||||||
|
filter: {
|
||||||
|
userId,
|
||||||
|
departmentId,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
isOwner: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await next();
|
||||||
|
};
|
@ -3,3 +3,5 @@ export * from './token-configuration-controller';
|
|||||||
export * from './plugin-version';
|
export * from './plugin-version';
|
||||||
export * from './link-manager-controller';
|
export * from './link-manager-controller';
|
||||||
export * from './system-message-controller';
|
export * from './system-message-controller';
|
||||||
|
export * from './departments';
|
||||||
|
export * from './users';
|
||||||
|
112
packages/plugins/@hera/plugin-core/src/server/actions/users.ts
Normal file
112
packages/plugins/@hera/plugin-core/src/server/actions/users.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { DEFAULT_PAGE, DEFAULT_PER_PAGE, type Context, type Next } from '@nocobase/actions';
|
||||||
|
export const listExcludeDept = async (ctx: Context, next: Next) => {
|
||||||
|
const { departmentId, page = DEFAULT_PAGE, pageSize = DEFAULT_PER_PAGE } = ctx.action.params;
|
||||||
|
const repo = ctx.db.getRepository('users');
|
||||||
|
const members = await repo.find({
|
||||||
|
fields: ['id'],
|
||||||
|
filter: {
|
||||||
|
'departments.id': departmentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const memberIds = members.map((member) => member.id);
|
||||||
|
if (memberIds.length) {
|
||||||
|
ctx.action.mergeParams({
|
||||||
|
filter: {
|
||||||
|
id: {
|
||||||
|
$notIn: memberIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { filter } = ctx.action.params;
|
||||||
|
const [rows, count] = await repo.findAndCount({
|
||||||
|
context: ctx,
|
||||||
|
offset: (page - 1) * pageSize,
|
||||||
|
limit: pageSize,
|
||||||
|
filter,
|
||||||
|
});
|
||||||
|
ctx.body = {
|
||||||
|
count,
|
||||||
|
rows,
|
||||||
|
page: Number(page),
|
||||||
|
pageSize: Number(pageSize),
|
||||||
|
totalPage: Math.ceil(count / pageSize),
|
||||||
|
};
|
||||||
|
await next();
|
||||||
|
};
|
||||||
|
export const setDepartments = async (ctx: Context, next: Next) => {
|
||||||
|
const { values = {} } = ctx.action.params;
|
||||||
|
const { userId, departments = [] } = values;
|
||||||
|
const repo = ctx.db.getRepository('users');
|
||||||
|
const throughRepo = ctx.db.getRepository('departmentsUsers');
|
||||||
|
const user = await repo.findOne({ filterByTk: userId });
|
||||||
|
if (!user) {
|
||||||
|
ctx.throw(400, ctx.t('User does not exist'));
|
||||||
|
}
|
||||||
|
const departmentIds = departments.map((department) => department.id);
|
||||||
|
const main = departments.find((department) => department.isMain);
|
||||||
|
const owners = departments.filter((department) => department.isOwner);
|
||||||
|
await ctx.db.sequelize.transaction(async (t) => {
|
||||||
|
await user.setDepartments(departmentIds, {
|
||||||
|
through: {
|
||||||
|
isMain: false,
|
||||||
|
isOwner: false,
|
||||||
|
},
|
||||||
|
transaction: t,
|
||||||
|
});
|
||||||
|
if (main) {
|
||||||
|
await throughRepo.update({
|
||||||
|
filter: {
|
||||||
|
userId,
|
||||||
|
departmentId: main.id,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
isMain: true,
|
||||||
|
},
|
||||||
|
transaction: t,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (owners.length) {
|
||||||
|
await throughRepo.update({
|
||||||
|
filter: {
|
||||||
|
userId,
|
||||||
|
departmentId: {
|
||||||
|
$in: owners.map((owner) => owner.id),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
isOwner: true,
|
||||||
|
},
|
||||||
|
transaction: t,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await next();
|
||||||
|
};
|
||||||
|
export const setMainDepartment = async (ctx: Context, next: Next) => {
|
||||||
|
const { userId, departmentId } = ctx.action.params.values || {};
|
||||||
|
const throughRepo = ctx.db.getRepository('departmentsUsers');
|
||||||
|
await ctx.db.sequelize.transaction(async (t) => {
|
||||||
|
await throughRepo.update({
|
||||||
|
filter: {
|
||||||
|
userId,
|
||||||
|
isMain: true,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
isMain: false,
|
||||||
|
},
|
||||||
|
transaction: t,
|
||||||
|
});
|
||||||
|
await throughRepo.update({
|
||||||
|
filter: {
|
||||||
|
userId,
|
||||||
|
departmentId,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
isMain: true,
|
||||||
|
},
|
||||||
|
transaction: t,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await next();
|
||||||
|
};
|
@ -0,0 +1,4 @@
|
|||||||
|
import { defineCollection } from '@nocobase/database';
|
||||||
|
export default defineCollection({
|
||||||
|
name: 'departmentsRoles',
|
||||||
|
});
|
@ -0,0 +1,134 @@
|
|||||||
|
import { defineCollection } from '@nocobase/database';
|
||||||
|
export const ownersField = {
|
||||||
|
interface: 'm2m',
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'owners',
|
||||||
|
collectionName: 'departments',
|
||||||
|
target: 'users',
|
||||||
|
through: 'departmentsUsers',
|
||||||
|
foreignKey: 'departmentId',
|
||||||
|
otherKey: 'userId',
|
||||||
|
targetKey: 'id',
|
||||||
|
sourceKey: 'id',
|
||||||
|
throughScope: {
|
||||||
|
isOwner: true,
|
||||||
|
},
|
||||||
|
uiSchema: {
|
||||||
|
type: 'm2m',
|
||||||
|
title: '{{t("Owners")}}',
|
||||||
|
'x-component': 'DepartmentOwnersField',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: true,
|
||||||
|
fieldNames: {
|
||||||
|
label: 'nickname',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export default defineCollection({
|
||||||
|
name: 'departments',
|
||||||
|
title: '{{t("Departments")}}',
|
||||||
|
tree: 'adjacency-list',
|
||||||
|
template: 'tree',
|
||||||
|
shared: true,
|
||||||
|
sortable: true,
|
||||||
|
model: 'DepartmentModel',
|
||||||
|
createdBy: true,
|
||||||
|
updatedBy: true,
|
||||||
|
logging: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'bigInt',
|
||||||
|
name: 'id',
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
interface: 'id',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: '{{t("ID")}}',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'title',
|
||||||
|
interface: 'input',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Department name")}}',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'boolean',
|
||||||
|
name: 'isLeaf',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'parent',
|
||||||
|
target: 'departments',
|
||||||
|
foreignKey: 'parentId',
|
||||||
|
treeParent: true,
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
interface: 'm2o',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'm2o',
|
||||||
|
title: '{{t("Superior department")}}',
|
||||||
|
'x-component': 'AssociationField',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: false,
|
||||||
|
fieldNames: {
|
||||||
|
label: 'title',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'hasMany',
|
||||||
|
name: 'children',
|
||||||
|
target: 'departments',
|
||||||
|
foreignKey: 'parentId',
|
||||||
|
treeChildren: true,
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'members',
|
||||||
|
target: 'users',
|
||||||
|
through: 'departmentsUsers',
|
||||||
|
foreignKey: 'departmentId',
|
||||||
|
otherKey: 'userId',
|
||||||
|
targetKey: 'id',
|
||||||
|
sourceKey: 'id',
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
interface: 'm2m',
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'roles',
|
||||||
|
target: 'roles',
|
||||||
|
through: 'departmentsRoles',
|
||||||
|
foreignKey: 'departmentId',
|
||||||
|
otherKey: 'roleName',
|
||||||
|
targetKey: 'name',
|
||||||
|
sourceKey: 'id',
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'm2m',
|
||||||
|
title: '{{t("Roles")}}',
|
||||||
|
'x-component': 'AssociationField',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: true,
|
||||||
|
fieldNames: {
|
||||||
|
label: 'title',
|
||||||
|
value: 'name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ownersField,
|
||||||
|
],
|
||||||
|
});
|
@ -0,0 +1,20 @@
|
|||||||
|
import { defineCollection } from '@nocobase/database';
|
||||||
|
export default defineCollection({
|
||||||
|
name: 'departmentsUsers',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'boolean',
|
||||||
|
name: 'isOwner',
|
||||||
|
// Weather the user is the owner of the department
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'boolean',
|
||||||
|
name: 'isMain',
|
||||||
|
// Weather this is the main department of the user
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
@ -0,0 +1,17 @@
|
|||||||
|
import { extendCollection } from '@nocobase/database';
|
||||||
|
export default extendCollection({
|
||||||
|
name: 'roles',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'departments',
|
||||||
|
target: 'departments',
|
||||||
|
foreignKey: 'roleName',
|
||||||
|
otherKey: 'departmentId',
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
sourceKey: 'name',
|
||||||
|
targetKey: 'id',
|
||||||
|
through: 'departmentsRoles',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
@ -0,0 +1,58 @@
|
|||||||
|
import { extendCollection } from '@nocobase/database';
|
||||||
|
export const departmentsField = {
|
||||||
|
collectionName: 'users',
|
||||||
|
interface: 'm2m',
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'departments',
|
||||||
|
target: 'departments',
|
||||||
|
foreignKey: 'userId',
|
||||||
|
otherKey: 'departmentId',
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
sourceKey: 'id',
|
||||||
|
targetKey: 'id',
|
||||||
|
through: 'departmentsUsers',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'm2m',
|
||||||
|
title: '{{t("Departments")}}',
|
||||||
|
'x-component': 'UserDepartmentsField',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: true,
|
||||||
|
fieldNames: {
|
||||||
|
label: 'title',
|
||||||
|
value: 'name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export const mainDepartmentField = {
|
||||||
|
collectionName: 'users',
|
||||||
|
interface: 'm2m',
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'mainDepartment',
|
||||||
|
target: 'departments',
|
||||||
|
foreignKey: 'userId',
|
||||||
|
otherKey: 'departmentId',
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
sourceKey: 'id',
|
||||||
|
targetKey: 'id',
|
||||||
|
through: 'departmentsUsers',
|
||||||
|
throughScope: {
|
||||||
|
isMain: true,
|
||||||
|
},
|
||||||
|
uiSchema: {
|
||||||
|
type: 'm2m',
|
||||||
|
title: '{{t("Main department")}}',
|
||||||
|
'x-component': 'UserMainDepartmentField',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: false,
|
||||||
|
fieldNames: {
|
||||||
|
label: 'title',
|
||||||
|
value: 'name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export default extendCollection({
|
||||||
|
name: 'users',
|
||||||
|
fields: [departmentsField, mainDepartmentField],
|
||||||
|
});
|
@ -0,0 +1,117 @@
|
|||||||
|
import type { CollectionRepository } from '@nocobase/plugin-collection-manager';
|
||||||
|
import { Plugin } from '@nocobase/server';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import {
|
||||||
|
aggregateSearch,
|
||||||
|
listExcludeDept,
|
||||||
|
removeOwner,
|
||||||
|
setMainDepartment as setMainDepartmentAction,
|
||||||
|
setOwner,
|
||||||
|
} from '../actions';
|
||||||
|
import { departmentsField, mainDepartmentField } from '../collections/users';
|
||||||
|
import {
|
||||||
|
destroyDepartmentCheck,
|
||||||
|
resetUserDepartmentsCache,
|
||||||
|
setDepartmentOwners,
|
||||||
|
setDepartmentsInfo,
|
||||||
|
setMainDepartment,
|
||||||
|
updateDepartmentIsLeaf,
|
||||||
|
} from '../middlewares';
|
||||||
|
import { DepartmentModel } from '../models/department';
|
||||||
|
|
||||||
|
export class DepartmentsPlugin extends Plugin {
|
||||||
|
beforeLoad() {
|
||||||
|
this.app.db.registerModels({ DepartmentModel });
|
||||||
|
this.app.acl.addFixedParams('collections', 'destroy', () => {
|
||||||
|
return {
|
||||||
|
filter: {
|
||||||
|
'name.$notIn': ['departments', 'departmentsUsers', 'departmentsRoles'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async load() {
|
||||||
|
this.app.resource({
|
||||||
|
name: 'users',
|
||||||
|
actions: {
|
||||||
|
listExcludeDept: listExcludeDept,
|
||||||
|
setMainDepartment: setMainDepartmentAction,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.app.resource({
|
||||||
|
name: 'departments',
|
||||||
|
actions: {
|
||||||
|
aggregateSearch: aggregateSearch,
|
||||||
|
setOwner: setOwner,
|
||||||
|
removeOwner: removeOwner,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.app.acl.allow('users', ['setMainDepartment', 'listExcludeDept'], 'loggedIn');
|
||||||
|
this.app.acl.registerSnippet({
|
||||||
|
name: `pm.${this.name}.*`,
|
||||||
|
actions: [
|
||||||
|
'departments:*',
|
||||||
|
'roles:list',
|
||||||
|
'users:list',
|
||||||
|
'users:listExcludeDept',
|
||||||
|
'users:setMainDepartment',
|
||||||
|
'roles.departments:add',
|
||||||
|
'roles.departments:remove',
|
||||||
|
'users.departments:add',
|
||||||
|
'users.departments:remove',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
this.app.resourcer.use(setDepartmentsInfo, {
|
||||||
|
tag: 'setDepartmentsInfo',
|
||||||
|
before: 'setCurrentRole',
|
||||||
|
after: 'auth',
|
||||||
|
});
|
||||||
|
this.app.resourcer.use(setDepartmentOwners);
|
||||||
|
this.app.resourcer.use(destroyDepartmentCheck);
|
||||||
|
this.app.resourcer.use(updateDepartmentIsLeaf);
|
||||||
|
this.app.resourcer.use(resetUserDepartmentsCache);
|
||||||
|
this.app.resourcer.use(setMainDepartment);
|
||||||
|
this.app.db.on('departmentsUsers.afterSave', async (model) => {
|
||||||
|
const cache = this.app.cache;
|
||||||
|
await cache.del(`departments:${model.get('userId')}`);
|
||||||
|
});
|
||||||
|
this.app.db.on('departmentsUsers.afterDestroy', async (model) => {
|
||||||
|
const cache = this.app.cache;
|
||||||
|
await cache.del(`departments:${model.get('userId')}`);
|
||||||
|
});
|
||||||
|
this.app.on('beforeSignOut', ({ userId }) => {
|
||||||
|
this.app.cache.del(`departments:${userId}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async install(options) {
|
||||||
|
const collectionRepo = this.db.getRepository<CollectionRepository>('collections');
|
||||||
|
if (collectionRepo) {
|
||||||
|
await collectionRepo.db2cm('departments');
|
||||||
|
}
|
||||||
|
const fieldRepo = this.db.getRepository('fields');
|
||||||
|
if (fieldRepo) {
|
||||||
|
const isDepartmentsFieldExists = await fieldRepo.count({
|
||||||
|
filter: {
|
||||||
|
name: 'departments',
|
||||||
|
collectionName: 'users',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!isDepartmentsFieldExists) {
|
||||||
|
await fieldRepo.create({
|
||||||
|
values: departmentsField,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const isMainDepartmentFieldExists = await fieldRepo.count({
|
||||||
|
filter: {
|
||||||
|
name: 'mainDepartment',
|
||||||
|
collectionName: 'users',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!isMainDepartmentFieldExists) {
|
||||||
|
await fieldRepo.create({
|
||||||
|
values: mainDepartmentField,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
import type { Context, Next } from '@nocobase/actions';
|
||||||
|
|
||||||
|
const destroyCheck = async (ctx: Context) => {
|
||||||
|
const { filterByTk } = ctx.action.params;
|
||||||
|
const repo = ctx.db.getRepository('departments');
|
||||||
|
const children = await repo.count({
|
||||||
|
filter: {
|
||||||
|
parentId: filterByTk,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (children) {
|
||||||
|
ctx.throw(400, ctx.t('The department has sub-departments, please delete them first', { ns: 'departments' }));
|
||||||
|
}
|
||||||
|
const members = await ctx.db.getRepository('departmentsUsers').count({
|
||||||
|
filter: {
|
||||||
|
departmentId: filterByTk,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (members) {
|
||||||
|
ctx.throw(400, ctx.t('The department has members, please remove them first', { ns: 'departments' }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const destroyDepartmentCheck = async (ctx: Context, next: Next) => {
|
||||||
|
const { resourceName, actionName } = ctx.action.params;
|
||||||
|
if (resourceName === 'departments' && actionName === 'destroy') {
|
||||||
|
await destroyCheck(ctx);
|
||||||
|
}
|
||||||
|
await next();
|
||||||
|
};
|
@ -0,0 +1,6 @@
|
|||||||
|
export * from './destroy-department-check';
|
||||||
|
export * from './reset-user-departments-cache';
|
||||||
|
export * from './set-department-owners';
|
||||||
|
export * from './update-department-isleaf';
|
||||||
|
export * from './set-departments-roles';
|
||||||
|
export * from './set-main-department';
|
@ -0,0 +1,20 @@
|
|||||||
|
import type { Context, Next } from '@nocobase/actions';
|
||||||
|
|
||||||
|
export const resetUserDepartmentsCache = async (ctx: Context, next: Next) => {
|
||||||
|
await next();
|
||||||
|
const { associatedName, resourceName, associatedIndex, actionName, values } = ctx.action.params;
|
||||||
|
const cache = ctx.app.cache;
|
||||||
|
if (
|
||||||
|
associatedName === 'departments' &&
|
||||||
|
resourceName === 'members' &&
|
||||||
|
['add', 'remove', 'set'].includes(actionName) &&
|
||||||
|
(values == null ? void 0 : values.length)
|
||||||
|
) {
|
||||||
|
for (const memberId of values) {
|
||||||
|
await cache.del(`departments:${memberId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (associatedName === 'users' && resourceName === 'departments' && ['add', 'remove', 'set'].includes(actionName)) {
|
||||||
|
await cache.del(`departments:${associatedIndex}`);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,40 @@
|
|||||||
|
import type { Context, Next } from '@nocobase/actions';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const setOwners = async (ctx: Context, filterByTk, owners) => {
|
||||||
|
const throughRepo = ctx.db.getRepository('departmentsUsers');
|
||||||
|
await ctx.db.sequelize.transaction(async (t) => {
|
||||||
|
await throughRepo.update({
|
||||||
|
filter: {
|
||||||
|
departmentId: filterByTk,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
isOwner: false,
|
||||||
|
},
|
||||||
|
transaction: t,
|
||||||
|
});
|
||||||
|
await throughRepo.update({
|
||||||
|
filter: {
|
||||||
|
departmentId: filterByTk,
|
||||||
|
userId: {
|
||||||
|
$in: owners.map((owner) => owner.id),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
isOwner: true,
|
||||||
|
},
|
||||||
|
transaction: t,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export const setDepartmentOwners = async (ctx: Context, next: Next) => {
|
||||||
|
const { filterByTk, values = {}, resourceName, actionName } = ctx.action.params;
|
||||||
|
const { owners } = values;
|
||||||
|
if (resourceName === 'departments' && actionName === 'update' && owners) {
|
||||||
|
ctx.action.params.values = _.omit(values, ['owners']);
|
||||||
|
await next();
|
||||||
|
await setOwners(ctx, filterByTk, owners);
|
||||||
|
} else {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,38 @@
|
|||||||
|
import type { Context, Next } from '@nocobase/actions';
|
||||||
|
import type { ArrayFieldRepository } from '@nocobase/database';
|
||||||
|
|
||||||
|
export const setDepartmentsInfo = async (ctx: Context, next: Next) => {
|
||||||
|
const currentUser = ctx.state.currentUser;
|
||||||
|
if (!currentUser) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
const cache = ctx.cache;
|
||||||
|
const repo = ctx.db.getRepository<ArrayFieldRepository>('users.departments', currentUser.id);
|
||||||
|
const departments = await cache.wrap(`departments:${currentUser.id}`, () =>
|
||||||
|
repo.find({
|
||||||
|
appends: ['owners', 'roles', 'parent(recursively=true)'],
|
||||||
|
raw: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (!departments.length) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
ctx.state.currentUser.departments = departments;
|
||||||
|
ctx.state.currentUser.mainDeparmtent = departments.find((dept) => dept.isMain);
|
||||||
|
const departmentIds = departments.map((dept) => dept.id);
|
||||||
|
const roleRepo = ctx.db.getRepository('roles');
|
||||||
|
const roles = await roleRepo.find({
|
||||||
|
filter: {
|
||||||
|
'departments.id': {
|
||||||
|
$in: departmentIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!roles.length) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
const rolesMap = new Map();
|
||||||
|
roles.forEach((role) => rolesMap.set(role.name, role));
|
||||||
|
ctx.state.attachRoles = Array.from(rolesMap.values());
|
||||||
|
await next();
|
||||||
|
};
|
@ -0,0 +1,81 @@
|
|||||||
|
import type { Context, Next } from '@nocobase/actions';
|
||||||
|
|
||||||
|
export const setMainDepartment = async (ctx: Context, next: Next) => {
|
||||||
|
await next();
|
||||||
|
const { associatedName, resourceName, associatedIndex, actionName, values } = ctx.action.params;
|
||||||
|
if (associatedName === 'departments' && resourceName === 'members' && (values == null ? void 0 : values.length)) {
|
||||||
|
const throughRepo = ctx.db.getRepository('departmentsUsers');
|
||||||
|
const usersHasMain = await throughRepo.find({
|
||||||
|
filter: {
|
||||||
|
userId: {
|
||||||
|
$in: values,
|
||||||
|
},
|
||||||
|
isMain: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const userIdsHasMain = usersHasMain.map((item) => item.userId);
|
||||||
|
if (actionName === 'add' || actionName === 'set') {
|
||||||
|
await throughRepo.update({
|
||||||
|
filter: {
|
||||||
|
userId: {
|
||||||
|
$in: values.filter((id) => !userIdsHasMain.includes(id)),
|
||||||
|
},
|
||||||
|
departmentId: associatedIndex,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
isMain: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (actionName === 'remove') {
|
||||||
|
const userIdsHasNoMain = values.filter((id) => !userIdsHasMain.includes(id));
|
||||||
|
for (const userId of userIdsHasNoMain) {
|
||||||
|
const firstDept = await throughRepo.findOne({
|
||||||
|
filter: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (firstDept) {
|
||||||
|
await throughRepo.update({
|
||||||
|
filter: {
|
||||||
|
userId,
|
||||||
|
departmentId: firstDept.departmentId,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
isMain: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (associatedName === 'users' && resourceName === 'departments' && ['add', 'remove', 'set'].includes(actionName)) {
|
||||||
|
const throughRepo = ctx.db.getRepository('departmentsUsers');
|
||||||
|
const hasMain = await throughRepo.findOne({
|
||||||
|
filter: {
|
||||||
|
userId: associatedIndex,
|
||||||
|
isMain: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (hasMain) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const firstDept = await throughRepo.findOne({
|
||||||
|
filter: {
|
||||||
|
userId: associatedIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (firstDept) {
|
||||||
|
await throughRepo.update({
|
||||||
|
filter: {
|
||||||
|
userId: associatedIndex,
|
||||||
|
departmentId: firstDept.departmentId,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
isMain: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,60 @@
|
|||||||
|
import type { Context, Next } from '@nocobase/actions';
|
||||||
|
|
||||||
|
const updateIsLeafWhenAddChild = async (repo, parent) => {
|
||||||
|
if (parent && parent.isLeaf !== false) {
|
||||||
|
await repo.update({
|
||||||
|
filter: {
|
||||||
|
id: parent.id,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
isLeaf: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const updateIsLeafWhenChangeChild = async (repo, oldParentId, newParentId) => {
|
||||||
|
if (oldParentId && oldParentId !== newParentId) {
|
||||||
|
const hasChild = await repo.count({
|
||||||
|
filter: {
|
||||||
|
parentId: oldParentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!hasChild) {
|
||||||
|
await repo.update({
|
||||||
|
filter: {
|
||||||
|
id: oldParentId,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
isLeaf: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const updateDepartmentIsLeaf = async (ctx: Context, next: Next) => {
|
||||||
|
const { filterByTk, values = {}, resourceName, actionName } = ctx.action.params;
|
||||||
|
const repo = ctx.db.getRepository('departments');
|
||||||
|
const { parent } = values;
|
||||||
|
if (resourceName === 'departments' && actionName === 'create') {
|
||||||
|
ctx.action.params.values = { ...values, isLeaf: true };
|
||||||
|
await next();
|
||||||
|
await updateIsLeafWhenAddChild(repo, parent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (resourceName === 'departments' && actionName === 'update') {
|
||||||
|
const department = await repo.findOne({ filterByTk });
|
||||||
|
await next();
|
||||||
|
await Promise.all([
|
||||||
|
updateIsLeafWhenChangeChild(repo, department.parentId, parent == null ? void 0 : parent.id),
|
||||||
|
updateIsLeafWhenAddChild(repo, parent),
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (resourceName === 'departments' && actionName === 'destroy') {
|
||||||
|
const department = await repo.findOne({ filterByTk });
|
||||||
|
await next();
|
||||||
|
await updateIsLeafWhenChangeChild(repo, department.parentId, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
};
|
@ -0,0 +1,12 @@
|
|||||||
|
import { Model } from '@nocobase/database';
|
||||||
|
export class DepartmentModel extends Model {
|
||||||
|
getOwners() {
|
||||||
|
return this.getMembers({
|
||||||
|
through: {
|
||||||
|
where: {
|
||||||
|
isOwner: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +1,31 @@
|
|||||||
import { InstallOptions, Plugin } from '@nocobase/server';
|
import Application, { InstallOptions, Plugin, type PluginOptions } from '@nocobase/server';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import CalcField from './fields/calc';
|
import CalcField from './fields/calc';
|
||||||
import { SqlLoader } from './services/sql-loader';
|
import { SqlLoader } from './services/sql-loader';
|
||||||
import { ConnectionManager } from './services/connection-manager';
|
import { ConnectionManager } from './services/connection-manager';
|
||||||
import { isMain } from './utils/multiprocess';
|
|
||||||
import { FontManager } from './services/font-manager';
|
import { FontManager } from './services/font-manager';
|
||||||
import { HomePageService } from './services/home-page-service';
|
import { HomePageService } from './services/home-page-service';
|
||||||
import { WebControllerService as WebService } from './services/web-service';
|
import { WebControllerService as WebService } from './services/web-service';
|
||||||
import './actions';
|
import './actions';
|
||||||
import { Container } from '@nocobase/utils';
|
import { Container } from '@nocobase/utils';
|
||||||
|
import { DepartmentsPlugin } from './features/departments';
|
||||||
|
|
||||||
export class PluginCoreServer extends Plugin {
|
export class PluginCoreServer extends Plugin {
|
||||||
afterAdd() {
|
pluginDepartments: DepartmentsPlugin;
|
||||||
|
constructor(app: Application, options?: PluginOptions) {
|
||||||
|
super(app, options);
|
||||||
|
this.pluginDepartments = new DepartmentsPlugin(app, options);
|
||||||
|
}
|
||||||
|
async afterAdd() {
|
||||||
this.db.registerFieldTypes({
|
this.db.registerFieldTypes({
|
||||||
calc: CalcField,
|
calc: CalcField,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
beforeLoad() {}
|
beforeLoad() {
|
||||||
|
this.pluginDepartments.beforeLoad();
|
||||||
|
}
|
||||||
async load() {
|
async load() {
|
||||||
|
await this.pluginDepartments.load();
|
||||||
Container.reset();
|
Container.reset();
|
||||||
try {
|
try {
|
||||||
Container.set({ id: 'db', value: this.db });
|
Container.set({ id: 'db', value: this.db });
|
||||||
@ -35,7 +43,9 @@ export class PluginCoreServer extends Plugin {
|
|||||||
console.warn(err);
|
console.warn(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async install(options?: InstallOptions) {}
|
async install(options?: InstallOptions) {
|
||||||
|
this.pluginDepartments.install(options);
|
||||||
|
}
|
||||||
async afterEnable() {}
|
async afterEnable() {}
|
||||||
async afterDisable() {}
|
async afterDisable() {}
|
||||||
async remove() {}
|
async remove() {}
|
||||||
|
2002
pnpm-lock.yaml
2002
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user