package docgov import ( "context" "encoding/json" "fmt" "chat-agent-system/internal/app/uow" ) // nullIfZero maps unset duty holders (0) to SQL NULL so nullable FK columns // (author_id/reviewer_id/approver_id → users.id) accept optional duties. func nullIfZero(id int64) any { if id <= 0 { return nil } return id } func (e *GovernanceEngine) syncDocAndAuditToDB(ctx context.Context, doc *DocumentRecord, audit *AuditLog) error { if e.pool == nil { return nil } exec := func(sql string, args ...any) error { if scope, ok := uow.ScopeFrom(ctx); ok { tx, err := uow.AsPG(scope) if err != nil { return err } _, err = tx.Exec(ctx, sql, args...) return err } _, err := e.pool.Exec(ctx, sql, args...) return err } if err := exec(` UPDATE document_records SET current_state = $1, is_locked = $2, last_rejected_reason = $3, last_rejected_by_role = $4, last_rejected_by_name = $5, last_rejected_at = $6, approved_at = $7, updated_at = $8, review_snapshot_id = NULLIF($9, 0) WHERE doc_id = $10 `, string(doc.CurrentState), doc.IsLocked, doc.LastRejectedReason, doc.LastRejectedByRole, doc.LastRejectedByName, doc.LastRejectedAt, doc.ApprovedAt, doc.UpdatedAt, doc.ReviewSnapshotID, doc.DocID); err != nil { return fmt.Errorf("docgov: sync document: %w", err) } if audit != nil { detJSON, _ := json.Marshal(audit.Details) if err := exec(` INSERT INTO document_audit_logs (doc_id, action, actor_id, actor_name, actor_role, from_state, to_state, details, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) `, audit.DocID, audit.Action, audit.ActorID, audit.ActorName, string(audit.ActorRole), string(audit.FromState), string(audit.ToState), detJSON, audit.CreatedAt); err != nil { return fmt.Errorf("docgov: sync audit: %w", err) } } return nil }