Files
Agent-System/internal/docgov/engine_review_flow.go
T
WeiCHEN 39e115b4d3 refactor(docgov): split engine.go into use-case files
Move claim/document/persist/proposal/read/review_flow into ≤800-line
siblings without behavior change; short tests green.
2026-09-23 09:54:19 +08:00

194 lines
5.5 KiB
Go

package docgov
import (
"context"
"strings"
"time"
"chat-agent-system/internal/app/uow"
)
// SubmitForReview 编制完成提交审核 (drafting -> reviewing)
func (e *GovernanceEngine) SubmitForReview(ctx context.Context, docID string, actorID int64, actorRole UserRole, actorName string) (*DocumentRecord, error) {
e.mu.Lock()
defer e.mu.Unlock()
doc, exists := e.memDocs[docID]
if !exists {
var err error
doc, err = e.loadDocumentFromDBLocked(ctx, docID)
if err != nil || doc == nil {
return nil, ErrDocumentNotFound
}
}
if doc.HasActiveLease(time.Now()) {
return nil, &DocumentBusyError{
DocID: doc.DocID,
HolderType: doc.LeaseHolderType,
HolderID: doc.LeaseHolderID,
WorkDir: doc.LeaseWorkDir,
ExpiresAt: *doc.LeaseExpiresAt,
Reason: "文档编辑租约占用中,暂不可送审",
}
}
audit, err := e.sm.TransitionSubmitForReview(doc, actorID, actorRole, actorName)
if err != nil {
return nil, err
}
e.memAudits[docID] = append(e.memAudits[docID], audit)
if err := e.syncDocAndAuditToDB(ctx, doc, audit); err != nil {
return nil, err
}
return doc, nil
}
// ReviewApprove 审核通过 (reviewing -> approving)
func (e *GovernanceEngine) ReviewApprove(ctx context.Context, docID string, actorID int64, actorRole UserRole, actorName string, comment string) (*DocumentRecord, error) {
e.mu.Lock()
defer e.mu.Unlock()
doc, exists := e.memDocs[docID]
if !exists {
var err error
doc, err = e.loadDocumentFromDBLocked(ctx, docID)
if err != nil || doc == nil {
return nil, ErrDocumentNotFound
}
}
audit, err := e.sm.TransitionReviewApprove(doc, actorID, actorRole, actorName, comment)
if err != nil {
return nil, err
}
if strings.TrimSpace(comment) != "" {
_ = e.addCommentInternal(docID, actorID, actorName, actorRole, "", CommentSuggestion, comment)
}
e.memAudits[docID] = append(e.memAudits[docID], audit)
if err := e.syncDocAndAuditToDB(ctx, doc, audit); err != nil {
return nil, err
}
return doc, nil
}
// ReviewReject 审核驳回 (reviewing -> drafting, 驳回原因必填)
func (e *GovernanceEngine) ReviewReject(ctx context.Context, docID string, actorID int64, actorRole UserRole, actorName string, rejectReason string) (*DocumentRecord, error) {
e.mu.Lock()
defer e.mu.Unlock()
doc, exists := e.memDocs[docID]
if !exists {
var err error
doc, err = e.loadDocumentFromDBLocked(ctx, docID)
if err != nil || doc == nil {
return nil, ErrDocumentNotFound
}
}
audit, err := e.sm.TransitionReviewReject(doc, actorID, actorRole, actorName, rejectReason)
if err != nil {
return nil, err
}
// 驳回原因自动持久化为批注记录
_ = e.addCommentInternal(docID, actorID, actorName, actorRole, "", CommentRejection, rejectReason)
e.memAudits[docID] = append(e.memAudits[docID], audit)
if err := e.syncDocAndAuditToDB(ctx, doc, audit); err != nil {
return nil, err
}
return doc, nil
}
// ApproverApprove 批准通过 (approving -> approved, 终态归档锁定)
func (e *GovernanceEngine) ApproverApprove(ctx context.Context, docID string, actorID int64, actorRole UserRole, actorName string, comment string) (*DocumentRecord, error) {
e.mu.Lock()
defer e.mu.Unlock()
doc, exists := e.memDocs[docID]
if !exists {
var err error
doc, err = e.loadDocumentFromDBLocked(ctx, docID)
if err != nil || doc == nil {
return nil, ErrDocumentNotFound
}
}
audit, err := e.sm.TransitionApproverApprove(doc, actorID, actorRole, actorName, comment)
if err != nil {
return nil, err
}
if strings.TrimSpace(comment) != "" {
_ = e.addCommentInternal(docID, actorID, actorName, actorRole, "", CommentSuggestion, comment)
}
e.memAudits[docID] = append(e.memAudits[docID], audit)
if err := e.syncDocAndAuditToDB(ctx, doc, audit); err != nil {
return nil, err
}
if _, joined := uow.ScopeFrom(ctx); !joined {
e.mu.Unlock()
e.runApprovedHooks(ctx, doc)
e.mu.Lock()
}
return doc, nil
}
// ApproverReject 批准驳回 (approving -> drafting 或 reviewing, 驳回原因必填)
func (e *GovernanceEngine) ApproverReject(ctx context.Context, docID string, actorID int64, actorRole UserRole, actorName string, rejectReason string, rollbackTo DocumentState) (*DocumentRecord, error) {
e.mu.Lock()
defer e.mu.Unlock()
doc, exists := e.memDocs[docID]
if !exists {
var err error
doc, err = e.loadDocumentFromDBLocked(ctx, docID)
if err != nil || doc == nil {
return nil, ErrDocumentNotFound
}
}
audit, err := e.sm.TransitionApproverReject(doc, actorID, actorRole, actorName, rejectReason, rollbackTo)
if err != nil {
return nil, err
}
_ = e.addCommentInternal(docID, actorID, actorName, actorRole, "", CommentRejection, rejectReason)
e.memAudits[docID] = append(e.memAudits[docID], audit)
if err := e.syncDocAndAuditToDB(ctx, doc, audit); err != nil {
return nil, err
}
return doc, nil
}
// ReopenForRevision moves an approved document back to drafting without recording a rejection.
func (e *GovernanceEngine) ReopenForRevision(ctx context.Context, docID string, actorID int64, actorName, reason string) (*DocumentRecord, error) {
e.mu.Lock()
defer e.mu.Unlock()
doc, exists := e.memDocs[docID]
if !exists {
var err error
doc, err = e.loadDocumentFromDBLocked(ctx, docID)
if err != nil || doc == nil {
return nil, ErrDocumentNotFound
}
}
audit, err := e.sm.TransitionReopenForRevision(doc, actorID, actorName, reason)
if err != nil {
return nil, err
}
e.memAudits[docID] = append(e.memAudits[docID], audit)
if err := e.syncDocAndAuditToDB(ctx, doc, audit); err != nil {
return nil, err
}
return doc, nil
}