mirror of
https://github.com/getrebuild/rebuild.git
synced 2024-09-20 23:45:55 +08:00
feat: principal for project
This commit is contained in:
parent
be7799cda7
commit
0074a8eb49
|
@ -72,20 +72,27 @@ public class ProjectManager implements ConfigManager {
|
|||
|
||||
if (projects == null) {
|
||||
Object[][] array = Application.createQueryNoFilter(
|
||||
"select configId,projectCode,projectName,iconName,scope,members,extraDefinition from ProjectConfig")
|
||||
"select configId,projectCode,projectName,iconName,scope,members,principal,extraDefinition from ProjectConfig")
|
||||
.array();
|
||||
|
||||
List<ConfigEntry> alist = new ArrayList<>();
|
||||
for (Object[] o : array) {
|
||||
String members = (String) o[5];
|
||||
if (o[6] != null) {
|
||||
members = StringUtils.isBlank(members) ? o[6].toString() : members + "," + o[6];
|
||||
}
|
||||
|
||||
ConfigEntry e = new ConfigEntry()
|
||||
.set("id", o[0])
|
||||
.set("projectCode", o[1])
|
||||
.set("projectName", o[2])
|
||||
.set("iconName", StringUtils.defaultIfBlank((String) o[3], "texture"))
|
||||
.set("scope", o[4])
|
||||
.set("_members", o[5]);
|
||||
.set("_members", members)
|
||||
.set("principal", o[6]);
|
||||
|
||||
String extraDefinition = (String) o[6];
|
||||
// 扩展配置
|
||||
String extraDefinition = (String) o[7];
|
||||
if (JSONUtils.wellFormat(extraDefinition)) {
|
||||
JSONObject extraDefinitionJson = JSON.parseObject(extraDefinition);
|
||||
for (String name : extraDefinitionJson.keySet()) {
|
||||
|
|
|
@ -16,7 +16,6 @@ import com.rebuild.server.Application;
|
|||
import com.rebuild.server.business.feeds.FeedsHelper;
|
||||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import com.rebuild.server.service.OperatingContext;
|
||||
import com.rebuild.server.service.bizz.UserHelper;
|
||||
import com.rebuild.server.service.notification.Message;
|
||||
import com.rebuild.server.service.notification.MessageBuilder;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
@ -57,13 +56,12 @@ public class ProjectCommentService extends BaseTaskService {
|
|||
@Override
|
||||
public int delete(ID commentId) {
|
||||
final ID user = Application.getCurrentUser();
|
||||
final Record beforeRecord = getBeforeRecord(commentId);
|
||||
|
||||
ID createdBy = beforeRecord.getID(EntityHelper.CreatedBy);
|
||||
if (!(UserHelper.isAdmin(user) || user.equals(createdBy))) {
|
||||
if (!ProjectHelper.isManageable(commentId, user)) {
|
||||
throw new PrivilegesException("不能删除他人评论");
|
||||
}
|
||||
|
||||
final Record beforeRecord = getBeforeRecord(commentId);
|
||||
|
||||
int d = super.delete(commentId);
|
||||
|
||||
awareAttachment(OperatingContext.create(
|
||||
|
|
|
@ -10,9 +10,12 @@ package com.rebuild.server.service.project;
|
|||
import cn.devezhao.bizz.security.AccessDeniedException;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.configuration.ConfigEntry;
|
||||
import com.rebuild.server.configuration.ProjectManager;
|
||||
import com.rebuild.server.helper.ConfigurationException;
|
||||
import com.rebuild.server.helper.cache.NoRecordFoundException;
|
||||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import com.rebuild.server.service.bizz.UserHelper;
|
||||
|
||||
/**
|
||||
* @author devezhao
|
||||
|
@ -28,14 +31,9 @@ public class ProjectHelper {
|
|||
* @return
|
||||
*/
|
||||
public static boolean checkReadable(ID taskOrComment, ID user) {
|
||||
// 转为任务 ID
|
||||
if (taskOrComment.getEntityCode() == EntityHelper.ProjectTaskComment) {
|
||||
Object[] o = Application.getQueryFactory().uniqueNoFilter(taskOrComment, "taskId");
|
||||
if (o == null) return false;
|
||||
taskOrComment = (ID) o[0];
|
||||
}
|
||||
|
||||
try {
|
||||
taskOrComment = convert2Task(taskOrComment);
|
||||
|
||||
// 能访问就有读取权限
|
||||
ProjectManager.instance.getProjectByTask(taskOrComment, user);
|
||||
return true;
|
||||
|
@ -43,4 +41,39 @@ public class ProjectHelper {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对任务/评论是否有管理权
|
||||
*
|
||||
* @param taskOrComment
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
public static boolean isManageable(ID taskOrComment, ID user) {
|
||||
if (taskOrComment.getEntityCode() == EntityHelper.ProjectTask) {
|
||||
// 管理员
|
||||
if (UserHelper.isAdmin(user)) return true;
|
||||
|
||||
// 负责人
|
||||
ConfigEntry cfg = ProjectManager.instance.getProjectByTask(convert2Task(taskOrComment), null);
|
||||
if (user.equals(cfg.getID("principal"))) return true;
|
||||
}
|
||||
|
||||
// 创建人
|
||||
Object[] createdBy = Application.getQueryFactory().uniqueNoFilter(taskOrComment, "createdBy");
|
||||
return createdBy != null && createdBy[0].equals(user);
|
||||
}
|
||||
|
||||
// 转为任务 ID
|
||||
private static ID convert2Task(ID taskOrComment) {
|
||||
if (taskOrComment.getEntityCode() == EntityHelper.ProjectTaskComment) {
|
||||
Object[] o = Application.getQueryFactory().uniqueNoFilter(taskOrComment, "taskId");
|
||||
if (o == null) {
|
||||
throw new NoRecordFoundException(taskOrComment);
|
||||
}
|
||||
|
||||
return (ID) o[0];
|
||||
}
|
||||
return taskOrComment;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import com.rebuild.server.configuration.ConfigEntry;
|
|||
import com.rebuild.server.configuration.ProjectManager;
|
||||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import com.rebuild.server.service.OperatingContext;
|
||||
import com.rebuild.server.service.bizz.UserHelper;
|
||||
import com.rebuild.server.service.configuration.ProjectPlanConfigService;
|
||||
import com.rebuild.server.service.notification.Message;
|
||||
import com.rebuild.server.service.notification.MessageBuilder;
|
||||
|
@ -107,15 +106,12 @@ public class ProjectTaskService extends BaseTaskService {
|
|||
@Override
|
||||
public int delete(ID taskId) {
|
||||
final ID user = Application.getCurrentUser();
|
||||
checkMember(user, taskId);
|
||||
|
||||
final Record beforeRecord = getBeforeRecord(taskId);
|
||||
|
||||
ID createdBy = beforeRecord.getID(EntityHelper.CreatedBy);
|
||||
if (!(UserHelper.isAdmin(user) || user.equals(createdBy))) {
|
||||
if (!ProjectHelper.isManageable(taskId, user)) {
|
||||
throw new PrivilegesException("不能删除他人任务");
|
||||
}
|
||||
|
||||
final Record beforeRecord = getBeforeRecord(taskId);
|
||||
|
||||
int d = super.delete(taskId);
|
||||
|
||||
awareAttachment(OperatingContext.create(user, BizzPermission.DELETE, beforeRecord, null));
|
||||
|
|
|
@ -36,7 +36,7 @@ import java.io.IOException;
|
|||
public class ProjectAdminControll extends BasePageControll {
|
||||
|
||||
@RequestMapping("/admin/projects")
|
||||
public ModelAndView pageList() throws IOException {
|
||||
public ModelAndView pageList() {
|
||||
return createModelAndView("/admin/project/project-list.jsp");
|
||||
}
|
||||
|
||||
|
@ -44,29 +44,25 @@ public class ProjectAdminControll extends BasePageControll {
|
|||
public ModelAndView pageEditor(@PathVariable String projectId, HttpServletResponse response) throws IOException {
|
||||
ID projectId2 = ID.isId(projectId) ? ID.valueOf(projectId) : null;
|
||||
if (projectId2 == null) {
|
||||
response.sendError(404, "无效的项目");
|
||||
response.sendError(404, "无效项目");
|
||||
return null;
|
||||
}
|
||||
|
||||
Object[] p = Application.createQuery(
|
||||
"select projectName,scope,members from ProjectConfig where configId = ?")
|
||||
"select projectName,scope,principal,members from ProjectConfig where configId = ?")
|
||||
.setParameter(1, projectId2)
|
||||
.unique();
|
||||
if (p == null) {
|
||||
response.sendError(404, "无效的项目,可能已被管理员删除");
|
||||
return null;
|
||||
}
|
||||
|
||||
ModelAndView mv = createModelAndView("/admin/project/project-editor.jsp");
|
||||
mv.getModelMap().put("projectName", p[0]);
|
||||
mv.getModelMap().put("scope", p[1]);
|
||||
mv.getModelMap().put("members", p[2]);
|
||||
|
||||
mv.getModelMap().put("principal", p[2]);
|
||||
mv.getModelMap().put("members", p[3]);
|
||||
return mv;
|
||||
}
|
||||
|
||||
@RequestMapping("/admin/projects/list")
|
||||
public void listProjects(HttpServletResponse resp) throws IOException {
|
||||
public void listProjects(HttpServletResponse resp) {
|
||||
Object[][] array = Application.createQuery(
|
||||
"select configId,projectName,projectCode,iconName from ProjectConfig order by projectName")
|
||||
.array();
|
||||
|
|
|
@ -7,7 +7,6 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
package com.rebuild.web.project;
|
||||
|
||||
import cn.devezhao.bizz.privileges.PrivilegesException;
|
||||
import cn.devezhao.bizz.security.AccessDeniedException;
|
||||
import cn.devezhao.commons.CalendarUtils;
|
||||
import cn.devezhao.commons.web.ServletUtils;
|
||||
|
@ -20,6 +19,7 @@ import com.rebuild.server.configuration.ConfigEntry;
|
|||
import com.rebuild.server.configuration.ProjectManager;
|
||||
import com.rebuild.server.helper.ConfigurationException;
|
||||
import com.rebuild.server.service.bizz.UserHelper;
|
||||
import com.rebuild.server.service.project.ProjectHelper;
|
||||
import com.rebuild.server.service.query.AdvFilterParser;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import com.rebuild.web.BasePageControll;
|
||||
|
@ -49,29 +49,24 @@ public class ProjectTaskControll extends BasePageControll {
|
|||
@RequestMapping("/project/task/{taskId}")
|
||||
public ModelAndView pageTask(@PathVariable String taskId,
|
||||
HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
ID taskId2 = ID.isId(taskId) ? ID.valueOf(taskId) : null;
|
||||
final ID taskId2 = ID.isId(taskId) ? ID.valueOf(taskId) : null;
|
||||
if (taskId2 == null) {
|
||||
response.sendError(404);
|
||||
return null;
|
||||
}
|
||||
|
||||
final ID user = getRequestUser(request);
|
||||
|
||||
ConfigEntry p;
|
||||
try {
|
||||
p = ProjectManager.instance.getProjectByTask(taskId2, getRequestUser(request));
|
||||
} catch (ConfigurationException ex) {
|
||||
response.sendError(404, ex.getLocalizedMessage());
|
||||
return null;
|
||||
} catch (AccessDeniedException ex) {
|
||||
response.sendError(403, ex.getLocalizedMessage());
|
||||
return null;
|
||||
if (!ProjectHelper.checkReadable(taskId2, user)) {
|
||||
response.sendError(403, "你无权查看该任务");
|
||||
}
|
||||
|
||||
ConfigEntry cfg = ProjectManager.instance.getProjectByTask(taskId2, user);
|
||||
|
||||
ModelAndView mv = createModelAndView("/project/task-view.jsp");
|
||||
mv.getModel().put("id", taskId2.toLiteral());
|
||||
mv.getModel().put("projectIcon", p.getString("iconName"));
|
||||
mv.getModel().put("isMember", p.get("members", Set.class).contains(user));
|
||||
mv.getModel().put("projectIcon", cfg.getString("iconName"));
|
||||
mv.getModel().put("isMember", cfg.get("members", Set.class).contains(user));
|
||||
mv.getModel().put("isManageable", ProjectHelper.isManageable(taskId2, user));
|
||||
return mv;
|
||||
}
|
||||
|
||||
|
@ -131,7 +126,7 @@ public class ProjectTaskControll extends BasePageControll {
|
|||
public void taskDetails(HttpServletRequest request, HttpServletResponse response) {
|
||||
ID taskId = getIdParameterNotNull(request, "task");
|
||||
Object[] task = Application.createQueryNoFilter(
|
||||
"select " + BASE_FIELDS + ",projectId,projectPlanId,description,attachments from ProjectTask where taskId = ?")
|
||||
"select " + BASE_FIELDS + ",projectId,description,attachments from ProjectTask where taskId = ?")
|
||||
.setParameter(1, taskId)
|
||||
.unique();
|
||||
|
||||
|
@ -139,15 +134,15 @@ public class ProjectTaskControll extends BasePageControll {
|
|||
|
||||
// 状态面板
|
||||
details.put("projectId", task[11]);
|
||||
details.put("projectPlanId", task[12]);
|
||||
details.put("description", task[13]);
|
||||
String attachments = (String) task[14];
|
||||
details.put("description", task[12]);
|
||||
String attachments = (String) task[13];
|
||||
details.put("attachments", JSON.parseArray(attachments));
|
||||
|
||||
writeSuccess(response, details);
|
||||
}
|
||||
|
||||
private static final String BASE_FIELDS = "projectId.projectCode,taskNumber,taskId,taskName,createdOn,deadline,executor,status,seq,priority,endTime";
|
||||
private static final String BASE_FIELDS =
|
||||
"projectId.projectCode,taskNumber,taskId,taskName,createdOn,deadline,executor,status,seq,priority,endTime";
|
||||
/**
|
||||
* @param o
|
||||
* @return
|
||||
|
|
|
@ -397,6 +397,7 @@
|
|||
<field name="projectCode" type="string" max-length="10" nullable="false" updatable="false" description="项目代号" />
|
||||
<field name="iconName" type="string" max-length="30" description="图标ICON" />
|
||||
<field name="comments" type="string" max-length="300" description="备注" />
|
||||
<field name="principal" type="reference" ref-entity="User" description="负责人" />
|
||||
<field name="members" type="string" max-length="420" description="项目成员($MemberID)" />
|
||||
<field name="scope" type="small-int" default-value="1" description="可见范围(1=公开 2=成员)"/>
|
||||
<field name="extraDefinition" type="text" max-length="1000" description="扩展配置(JSON Map)" />
|
||||
|
|
|
@ -576,6 +576,7 @@ create table if not exists `project_config` (
|
|||
`PROJECT_CODE` varchar(10) not null comment '项目代号',
|
||||
`ICON_NAME` varchar(30) comment '图标ICON',
|
||||
`COMMENTS` varchar(300) comment '备注',
|
||||
`PRINCIPAL` char(20) comment '负责人',
|
||||
`MEMBERS` varchar(420) comment '项目成员($MemberID)',
|
||||
`SCOPE` smallint(6) default '1' comment '可见范围(1=公开 2=成员)',
|
||||
`EXTRA_DEFINITION` text(1000) comment '扩展配置(JSON Map)',
|
||||
|
@ -710,4 +711,4 @@ insert into `classification` (`DATA_ID`, `NAME`, `DESCRIPTION`, `OPEN_LEVEL`, `I
|
|||
|
||||
-- DB Version (see `db-upgrade.sql`)
|
||||
insert into `system_config` (`CONFIG_ID`, `ITEM`, `VALUE`)
|
||||
values ('021-9000000000000001', 'DBVer', 27);
|
||||
values ('021-9000000000000001', 'DBVer', 28);
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
-- Database upgrade scripts for rebuild 1.x
|
||||
-- Each upgraded starts with `-- #VERSION`
|
||||
|
||||
-- #28
|
||||
alter table `project_config`
|
||||
add column `PRINCIPAL` char(20) comment '负责人';
|
||||
|
||||
-- #27 Project/Kanban (v1.11)
|
||||
-- ************ Entity [ProjectConfig] DDL ************
|
||||
create table if not exists `project_config` (
|
||||
|
|
|
@ -62,6 +62,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-group row pt-0">
|
||||
<label class="col-12 col-lg-3 col-form-label text-lg-right">负责人</label>
|
||||
<div class="col-12 col-lg-9">
|
||||
<div id="principal"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-12 col-lg-3 col-form-label text-lg-right">项目成员</label>
|
||||
<div class="col-12 col-lg-9">
|
||||
<div id="members"></div>
|
||||
|
@ -94,6 +100,7 @@
|
|||
window.__PageConfig = {
|
||||
id: '${projectId}',
|
||||
scope: ${scope},
|
||||
principal: '${principal}',
|
||||
members: '${members}',
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -7,25 +7,30 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
const wpc = window.__PageConfig
|
||||
|
||||
let _members
|
||||
let _planList
|
||||
let _Principal
|
||||
let _Members
|
||||
let _PlanList
|
||||
|
||||
$(document).ready(() => {
|
||||
renderRbcomp(<UserSelector />, 'members',
|
||||
function () {
|
||||
_members = this
|
||||
_initUserComp(wpc.members, this)
|
||||
})
|
||||
renderRbcomp(<UserSelector multiple={false} />, 'principal', function () {
|
||||
_Principal = this
|
||||
_initUserComp(wpc.principal, _Principal)
|
||||
})
|
||||
renderRbcomp(<UserSelector />, 'members', function () {
|
||||
_Members = this
|
||||
_initUserComp(wpc.members, this)
|
||||
})
|
||||
|
||||
if (wpc.scope === 2) $('#scope_2').attr('checked', true)
|
||||
|
||||
renderRbcomp(<PlanList />, 'plans', function () { _planList = this })
|
||||
renderRbcomp(<PlanList />, 'plans', function () { _PlanList = this })
|
||||
$('.J_add-plan').click(() =>
|
||||
renderRbcomp(<PlanEdit projectId={wpc.id} flowNexts={_planList.getPlans()} seq={_planList.getMaxSeq() + 1000} />))
|
||||
renderRbcomp(<PlanEdit projectId={wpc.id} flowNexts={_PlanList.getPlans()} seq={_PlanList.getMaxSeq() + 1000} />))
|
||||
|
||||
const $btn = $('.J_save').click(() => {
|
||||
const _data = {
|
||||
scope: $('#scope_2').prop('checked') ? 2 : 1,
|
||||
members: _members.val().join(','),
|
||||
members: _Members.val().join(','),
|
||||
metadata: { id: wpc.id }
|
||||
}
|
||||
if (!_data.members) return RbHighbar.create('请选择项目成员')
|
||||
|
@ -227,7 +232,7 @@ class PlanEdit extends RbFormHandler {
|
|||
if (res.error_code === 0) {
|
||||
this.hide()
|
||||
RbHighbar.success('面板已保存')
|
||||
_planList.loadPlans()
|
||||
_PlanList.loadPlans()
|
||||
} else {
|
||||
RbHighbar.error(res.error_msg)
|
||||
this.disabled()
|
||||
|
|
|
@ -15,7 +15,7 @@ let __TaskContent
|
|||
let __TaskComment
|
||||
|
||||
$(document).ready(() => {
|
||||
renderRbcomp(<TaskForm id={wpc.taskId} editable={wpc.isMember} />, 'task-contents',
|
||||
renderRbcomp(<TaskForm id={wpc.taskId} editable={wpc.isMember} manageable={wpc.isManageable} />, 'task-contents',
|
||||
function () { __TaskContent = this })
|
||||
if (wpc.isMember) {
|
||||
renderRbcomp(<TaskComment id={wpc.taskId} call={() => __TaskContent.refreshComments()} />, 'task-comment',
|
||||
|
@ -40,7 +40,7 @@ class TaskForm extends React.Component {
|
|||
<div className="col-10">
|
||||
<ValueTaskName taskName={this.state.taskName} $$$parent={this} />
|
||||
</div>
|
||||
{this.props.editable &&
|
||||
{(this.props.editable && this.props.manageable) &&
|
||||
<div className="col-2 text-right">
|
||||
<button className="btn btn-secondary" style={{ minWidth: 80, marginTop: 2 }} data-toggle="dropdown">操作 <i className="icon zmdi zmdi-more-vert"></i></button>
|
||||
<div className="dropdown-menu dropdown-menu-right">
|
||||
|
|
|
@ -28,6 +28,7 @@ window.__PageConfig = {
|
|||
type: 'TaskView',
|
||||
taskId: '${id}',
|
||||
isMember: ${isMember},
|
||||
isManageable: ${isManageable},
|
||||
}
|
||||
</script>
|
||||
<script src="${baseUrl}/assets/lib/jquery.textarea.js"></script>
|
||||
|
|
Loading…
Reference in a new issue