Open member free-lane for chat initiation; auto-claim on author view; harden lease persist/ownership with readable DOC_BUSY; add notification inbox and supplement-impact HTTP decision APIs wired to composition root.
113 lines
3.2 KiB
Go
113 lines
3.2 KiB
Go
package projectinstance
|
|
|
|
const (
|
|
DutyRoleAuthor = "author"
|
|
DutyRoleReviewer = "reviewer"
|
|
DutyRoleApprover = "approver"
|
|
|
|
AccessCodeNotActivated = "WORKFLOW_NOT_ACTIVATED"
|
|
|
|
GovernanceBlockedHint = "当前项目尚未激活工作流,文档任务不会启动。请先在设置中配置并激活至少一份纳入人工审批的文档。"
|
|
)
|
|
|
|
// requiredDutyRole maps a document action to the duty that may perform it.
|
|
// run/edit are member-level (empty = no duty binding); submit/review/approve stay duty-gated.
|
|
func requiredDutyRole(action string) string {
|
|
switch action {
|
|
case DocAccessActionSubmit:
|
|
return DutyRoleAuthor
|
|
case DocAccessActionReview:
|
|
return DutyRoleReviewer
|
|
case DocAccessActionApprove:
|
|
return DutyRoleApprover
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func userHoldsDuty(d DocDuty, userID int64, role string) bool {
|
|
if userID <= 0 || role == "" {
|
|
return false
|
|
}
|
|
switch role {
|
|
case DutyRoleAuthor:
|
|
return d.AuthorUserID == userID
|
|
case DutyRoleReviewer:
|
|
return d.ReviewerUserID == userID
|
|
case DutyRoleApprover:
|
|
return d.ApproverUserID == userID
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// classifyAccess applies the locked duty-action matrix. govLive=false is pre-activation.
|
|
// Callers must already have enforced project membership; run/edit are then member-level
|
|
// under DutyModeFull/DutyModeNone. submit/review/approve remain duty-bound.
|
|
// DutyModePartial still denies all non-view actions to avoid half-configured mis-grants.
|
|
func classifyAccess(dt DocTaskPlan, action string, userID int64, isCreator bool, govLive bool) DocAccessDecision {
|
|
allow := DocAccessDecision{Allowed: true}
|
|
if action == DocAccessActionView {
|
|
return allow
|
|
}
|
|
if !govLive {
|
|
if action == DocAccessActionRun {
|
|
return DocAccessDecision{
|
|
Allowed: true,
|
|
Code: AccessCodeNotActivated,
|
|
Message: GovernanceBlockedHint,
|
|
}
|
|
}
|
|
return DocAccessDecision{
|
|
Allowed: false,
|
|
Code: AccessCodeNotActivated,
|
|
Message: GovernanceBlockedHint,
|
|
}
|
|
}
|
|
if !dt.Enabled {
|
|
return DocAccessDecision{Allowed: false, Code: AccessCodeDocTaskDisabled, Message: "该文档已被创建者禁用"}
|
|
}
|
|
mode, effective, _ := ResolveDocTaskDuty(dt)
|
|
switch mode {
|
|
case DutyModeNone:
|
|
switch action {
|
|
case DocAccessActionRun, DocAccessActionEdit:
|
|
// deny_only_submit: any project member (membership checked upstream) may run/edit.
|
|
_ = isCreator
|
|
_ = userID
|
|
return allow
|
|
case DocAccessActionReview, DocAccessActionApprove, DocAccessActionSubmit:
|
|
return DocAccessDecision{
|
|
Allowed: false,
|
|
Code: AccessCodeDutyNotManaged,
|
|
Message: "该文档未纳入人工审批,不能审核、批准或提交审批",
|
|
}
|
|
}
|
|
case DutyModePartial:
|
|
return DocAccessDecision{
|
|
Allowed: false,
|
|
Code: AccessCodeForbiddenDuty,
|
|
Message: "文档职责未完整指派,不能执行该动作",
|
|
}
|
|
case DutyModeFull:
|
|
role := requiredDutyRole(action)
|
|
if role == "" {
|
|
// run/edit: member-level
|
|
return allow
|
|
}
|
|
if userHoldsDuty(effective, userID, role) {
|
|
return allow
|
|
}
|
|
return DocAccessDecision{
|
|
Allowed: false,
|
|
Code: AccessCodeForbiddenDuty,
|
|
Message: "当前用户不是该文档的对应职责人",
|
|
}
|
|
}
|
|
return DocAccessDecision{
|
|
Allowed: false,
|
|
Code: AccessCodeForbiddenDuty,
|
|
Message: "当前用户不是该文档职责人",
|
|
}
|
|
}
|