Move claim/document/persist/proposal/read/review_flow into ≤800-line siblings without behavior change; short tests green.
194 lines
5.5 KiB
Go
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
|
|
}
|