133 lines
3.7 KiB
Go
133 lines
3.7 KiB
Go
package projectinstance
|
||
|
||
import (
|
||
"context"
|
||
|
||
"chat-agent-system/internal/workflowdef"
|
||
)
|
||
|
||
const (
|
||
DocAccessActionView = "view"
|
||
DocAccessActionEdit = "edit"
|
||
DocAccessActionSubmit = "submit"
|
||
DocAccessActionReview = "review"
|
||
DocAccessActionApprove = "approve"
|
||
DocAccessActionRun = "run"
|
||
|
||
AccessCodeDocTaskDisabled = "DOC_TASK_DISABLED"
|
||
AccessCodeNotCurrentStage = "DOC_TASK_NOT_IN_ACTIVE_STAGE"
|
||
AccessCodeForbiddenDuty = "DOC_DUTY_FORBIDDEN"
|
||
AccessCodeDutyNotManaged = "DOC_DUTY_NOT_MANAGED"
|
||
AccessCodeInstanceClosed = "WORKFLOW_INSTANCE_CLOSED"
|
||
)
|
||
|
||
// DocAccessDecision 统一文档任务访问判定结果。
|
||
type DocAccessDecision struct {
|
||
Allowed bool `json:"allowed"`
|
||
Code string `json:"code,omitempty"`
|
||
Message string `json:"message,omitempty"`
|
||
}
|
||
|
||
// CheckDocTaskAccess 对治理实例下的文档任务做判定。
|
||
//
|
||
// GetGoverningInstanceForAccess:从未激活 → view 放行、run 带 WORKFLOW_NOT_ACTIVATED(HTTP 转 202-blocked)、其余拒绝;
|
||
// completed/terminated → InstanceClosed;存储错误 fail-closed。
|
||
// deny_only_submit:run/edit 对项目成员放行;submit/review/approve 按对应单一职责,创建者不隐式越权。
|
||
func (s *Service) CheckDocTaskAccess(
|
||
ctx context.Context,
|
||
projectID int64,
|
||
userID int64,
|
||
taskKey string,
|
||
action string,
|
||
isCreator bool,
|
||
) DocAccessDecision {
|
||
allow := DocAccessDecision{Allowed: true}
|
||
if s == nil || s.store == nil || projectID <= 0 || userID <= 0 || taskKey == "" {
|
||
return allow
|
||
}
|
||
|
||
gov, err := s.store.GetGoverningInstanceForAccess(ctx, projectID)
|
||
if err != nil {
|
||
return DocAccessDecision{Allowed: false, Code: AccessCodeInstanceClosed, Message: "工作流实例不可用"}
|
||
}
|
||
if gov == nil {
|
||
return classifyAccess(DocTaskPlan{Enabled: true}, action, userID, isCreator, false)
|
||
}
|
||
if IsClosedInstanceStatus(gov.Status) {
|
||
return DocAccessDecision{
|
||
Allowed: false,
|
||
Code: AccessCodeInstanceClosed,
|
||
Message: "工作流实例已结束",
|
||
}
|
||
}
|
||
|
||
detail, err := s.store.GetInstanceByID(ctx, gov.ID)
|
||
if err != nil || detail == nil {
|
||
return DocAccessDecision{Allowed: false, Code: AccessCodeInstanceClosed, Message: "工作流实例不可用"}
|
||
}
|
||
|
||
var docInst *DocTaskInstance
|
||
var stageKey string
|
||
for _, st := range detail.Stages {
|
||
for i := range st.DocTasks {
|
||
if workflowdef.SameTaskKey(st.DocTasks[i].TaskKey, taskKey) {
|
||
copyDoc := st.DocTasks[i]
|
||
docInst = ©Doc
|
||
stageKey = st.StageKey
|
||
break
|
||
}
|
||
}
|
||
if docInst != nil {
|
||
break
|
||
}
|
||
}
|
||
if docInst == nil {
|
||
return allow
|
||
}
|
||
|
||
if docInst.Status == "skipped_disabled" {
|
||
return DocAccessDecision{
|
||
Allowed: false,
|
||
Code: AccessCodeDocTaskDisabled,
|
||
Message: "该文档已被创建者禁用",
|
||
}
|
||
}
|
||
|
||
// narrative 快照不按活动阶段拦截编制;职责、禁用和实例关闭检查仍在后面。
|
||
if stageKey != "" && stageKey != detail.ActiveStageKey && !gov.ConfigSnapshot.NarrativeStageGating() {
|
||
if action != DocAccessActionView {
|
||
return DocAccessDecision{
|
||
Allowed: false,
|
||
Code: AccessCodeNotCurrentStage,
|
||
Message: "文档不在当前活动阶段",
|
||
}
|
||
}
|
||
}
|
||
|
||
if action == DocAccessActionView {
|
||
return allow
|
||
}
|
||
|
||
planTask := findDocTaskPlan(gov.ConfigSnapshot, taskKey)
|
||
if planTask == nil || !planTask.Enabled {
|
||
return DocAccessDecision{
|
||
Allowed: false,
|
||
Code: AccessCodeDocTaskDisabled,
|
||
Message: "该文档已被创建者禁用",
|
||
}
|
||
}
|
||
|
||
return classifyAccess(*planTask, action, userID, isCreator, true)
|
||
}
|
||
|
||
func findDocTaskPlan(plan WorkflowPlan, taskKey string) *DocTaskPlan {
|
||
for _, st := range plan.Stages {
|
||
for i := range st.DocTasks {
|
||
if workflowdef.SameTaskKey(st.DocTasks[i].TaskKey, taskKey) {
|
||
return &st.DocTasks[i]
|
||
}
|
||
}
|
||
}
|
||
return nil
|
||
}
|