feat: principal for project

This commit is contained in:
oahzeved 2020-07-30 22:44:19 +08:00
parent be7799cda7
commit 0074a8eb49
13 changed files with 109 additions and 65 deletions

View file

@ -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()) {

View file

@ -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(

View file

@ -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;
}
}

View file

@ -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));

View file

@ -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();

View file

@ -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

View file

@ -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)" />

View file

@ -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);

View file

@ -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` (

View file

@ -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>

View file

@ -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()

View file

@ -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">

View file

@ -28,6 +28,7 @@ window.__PageConfig = {
type: 'TaskView',
taskId: '${id}',
isMember: ${isMember},
isManageable: ${isManageable},
}
</script>
<script src="${baseUrl}/assets/lib/jquery.textarea.js"></script>