Documentation
¶
Index ¶
- Constants
- Variables
- type Application
- type ApproverResolver
- type ApproverResolverFunc
- type ReviewLog
- type Service
- func (s *Service) Approve(ctx context.Context, applicationID, operatorID string) (Application, ReviewLog, error)
- func (s *Service) CreateDraft(ctx context.Context, app Application) error
- func (s *Service) GetApplication(ctx context.Context, applicationID string) (Application, error)
- func (s *Service) ListLogs(ctx context.Context, applicationID string) ([]ReviewLog, error)
- func (s *Service) RejectClose(ctx context.Context, applicationID, operatorID, comment string) (Application, ReviewLog, error)
- func (s *Service) Resubmit(ctx context.Context, applicationID string) (Application, ReviewLog, error)
- func (s *Service) Return(ctx context.Context, applicationID, operatorID, comment string) (Application, ReviewLog, error)
- func (s *Service) SaveTemplate(ctx context.Context, tpl WorkflowTemplate) error
- func (s *Service) Submit(ctx context.Context, applicationID string) (Application, ReviewLog, error)
- type Store
- type WorkflowEngine
- func (e *WorkflowEngine) Approve(app Application, template WorkflowTemplate, operatorID string, ...) (Application, ReviewLog, error)
- func (e *WorkflowEngine) CanEditForm(app Application, userID string) bool
- func (e *WorkflowEngine) CanResubmit(app Application, userID string) bool
- func (e *WorkflowEngine) CanReview(app Application, template WorkflowTemplate, userID string) bool
- func (e *WorkflowEngine) CanView(app Application, template WorkflowTemplate, userID string) bool
- func (e *WorkflowEngine) RejectClose(app Application, template WorkflowTemplate, operatorID string, comment string) (Application, ReviewLog, error)
- func (e *WorkflowEngine) ResetToFirstSubmit(app Application, template WorkflowTemplate) (Application, ReviewLog, error)
- func (e *WorkflowEngine) Resubmit(app Application, template WorkflowTemplate) (Application, ReviewLog, error)
- func (e *WorkflowEngine) Return(app Application, template WorkflowTemplate, operatorID string, comment string) (Application, ReviewLog, error)
- func (e *WorkflowEngine) Submit(app Application, template WorkflowTemplate) (Application, ReviewLog, error)
- type WorkflowStage
- type WorkflowTemplate
Constants ¶
const ( StatusDraft = "Draft" StatusInReview = "In_Review" StatusReturned = "Returned" StatusApproved = "Approved" StatusRejectedClosed = "Rejected_Closed" )
Application status values.
const ( ReturnModeStrict = "STRICT" ReturnModeDirect = "DIRECT" )
Return mode values for WorkflowTemplate.
const ( ReviewTypeSingle = "SINGLE" ReviewTypeAll = "ALL" )
Review type values for WorkflowStage.
const ( ActionSubmit = "SUBMIT" ActionApprove = "APPROVE" ActionReturn = "RETURN" ActionRejectClose = "REJECT_CLOSE" )
Action values for ReviewLog.
Variables ¶
var ( ErrInvalidStatus = errors.New("workflow: invalid application status for this operation") ErrNoPermission = errors.New("workflow: operator has no permission to perform this action") ErrCommentRequired = errors.New("workflow: comment is required for this action") ErrTemplateEmpty = errors.New("workflow: template has no stages") ErrTemplateMismatch = errors.New("workflow: template does not match application") ErrAlreadyApproved = errors.New("workflow: operator has already approved in current round") ErrInvalidTemplate = errors.New("workflow: invalid template configuration") ErrStaleReturnStage = errors.New("workflow: stored return-stage index is out of range for the current template") )
Engine errors.
var ErrNotFound = errors.New("workflow: record not found")
ErrNotFound is returned by Store implementations when a record cannot be located.
Functions ¶
This section is empty.
Types ¶
type Application ¶
type Application struct {
ID string
WorkflowID string
OwnerID string
Status string
CurrentStageIndex int
ReturnStageIndex int
}
Application represents a single workflow instance and its mutable state.
type ApproverResolver ¶
type ApproverResolver interface {
Resolve(app Application, stage WorkflowStage) ([]string, error)
}
ApproverResolver returns the user IDs allowed to approve a stage for a specific application. Implementations may consult external state (RBAC, org chart, per-application metadata) so reviewers do not have to be hard-coded in the template.
Stability contract: the result MUST be stable for a given (app, stage) within a single approval round. ALL multi-sign counting compares the number of distinct approvals in the round against len(Resolve(...)), so a list that grows or shrinks mid-round will confuse the engine.
An empty list disables the stage: SINGLE can never advance, ALL auto-advances on the first call. Both are almost always bugs — callers should arrange for Resolve to fail loudly instead of returning [].
type ApproverResolverFunc ¶
type ApproverResolverFunc func(app Application, stage WorkflowStage) ([]string, error)
ApproverResolverFunc adapts a plain function to ApproverResolver.
func (ApproverResolverFunc) Resolve ¶
func (f ApproverResolverFunc) Resolve(app Application, stage WorkflowStage) ([]string, error)
Resolve implements ApproverResolver.
type ReviewLog ¶
type ReviewLog struct {
ID string
ApplicationID string
StageIndex int
OperatorID string
Action string
Comment string
CreatedAt time.Time
}
ReviewLog records a single action performed against an Application.
type Service ¶
type Service struct {
Engine *WorkflowEngine
Store Store
}
Service is a convenience facade that combines a WorkflowEngine with a Store. Each public method loads the application, invokes the engine, then persists the new state and log inside a single transaction.
Authorization boundary: Service is *not* an authentication layer. Submit, Resubmit and SaveTemplate do not take an operator argument and trust the caller to have already verified that the requester is allowed to perform the action (typically: requester == app.OwnerID for Submit/Resubmit, and a workflow admin role for SaveTemplate). Approve / Return / RejectClose do take an operatorID and the engine checks it against the stage's ApproverIDs, but that is a workflow-routing check, not a caller-identity check — wire authentication in the layer above.
func NewService ¶
func NewService(store Store, engine *WorkflowEngine) *Service
NewService wires an Engine (or a default one) with the provided Store.
func (*Service) Approve ¶
func (s *Service) Approve(ctx context.Context, applicationID, operatorID string) (Application, ReviewLog, error)
Approve records a stage approval, advancing the workflow when conditions are met.
func (*Service) CreateDraft ¶
func (s *Service) CreateDraft(ctx context.Context, app Application) error
CreateDraft persists a brand new draft application. It is a thin helper — callers can also write the application themselves via Store.SaveApplication.
func (*Service) GetApplication ¶
GetApplication loads a snapshot of the application state.
func (*Service) RejectClose ¶
func (s *Service) RejectClose(ctx context.Context, applicationID, operatorID, comment string) (Application, ReviewLog, error)
RejectClose terminally closes the application.
func (*Service) Resubmit ¶
func (s *Service) Resubmit(ctx context.Context, applicationID string) (Application, ReviewLog, error)
Resubmit puts a Returned application back into the review queue.
func (*Service) Return ¶
func (s *Service) Return(ctx context.Context, applicationID, operatorID, comment string) (Application, ReviewLog, error)
Return sends the application back to the owner for revision.
func (*Service) SaveTemplate ¶
func (s *Service) SaveTemplate(ctx context.Context, tpl WorkflowTemplate) error
SaveTemplate persists a workflow template definition and, in the same transaction, reflows every in-flight application bound to that template.
Reflow semantics: every application whose Status is In_Review or Returned is reset to the post-first-Submit state (Status=In_Review, CurrentStageIndex=1, ReturnStageIndex=0) and gets a new SUBMIT log appended. Approved / Rejected_Closed applications are terminal and are left untouched.
This is intentionally aggressive — calling SaveTemplate again with the same definition still reflows. Callers who want a no-op update should compare templates upstream and skip the call.
type Store ¶
type Store interface {
// Templates
SaveTemplate(ctx context.Context, t WorkflowTemplate) error
GetTemplate(ctx context.Context, id string) (WorkflowTemplate, error)
// Applications
SaveApplication(ctx context.Context, a Application) error
GetApplication(ctx context.Context, id string) (Application, error)
// ListInFlightApplications returns every application bound to workflowID
// whose Status is In_Review or Returned. Used by Service.SaveTemplate to
// reflow in-flight applications whenever the template is updated.
ListInFlightApplications(ctx context.Context, workflowID string) ([]Application, error)
// Review logs
AppendLog(ctx context.Context, l ReviewLog) error
ListLogs(ctx context.Context, applicationID string) ([]ReviewLog, error)
// Transact runs fn inside a single transaction. The Store handed to fn is
// scoped to that transaction; commits/rollbacks are managed by the impl.
Transact(ctx context.Context, fn func(s Store) error) error
}
Store abstracts the persistence layer for templates, applications and logs.
Implementations must keep ReviewLog ordering stable (insertion order) since the engine treats the latest SUBMIT entry as the "current round" boundary.
type WorkflowEngine ¶
type WorkflowEngine struct {
// IDGenerator produces ReviewLog IDs. Swap for deterministic IDs in tests.
IDGenerator func() string
// Clock returns "now" when writing CreatedAt on ReviewLog entries.
Clock func() time.Time
// Resolver decides which user IDs may approve each stage. Defaults to a
// resolver that returns stage.ApproverIDs as-is. Swap in a custom
// implementation to do role/group/department-based routing without
// changing the template schema.
Resolver ApproverResolver
}
WorkflowEngine is the state-driven core of the library. It is stateless across calls; callers persist Application and ReviewLog values returned by the engine.
func NewEngine ¶
func NewEngine() *WorkflowEngine
NewEngine returns a WorkflowEngine wired with default ID/clock implementations and the static ApproverIDs resolver.
func (*WorkflowEngine) Approve ¶
func (e *WorkflowEngine) Approve(app Application, template WorkflowTemplate, operatorID string, logs []ReviewLog) (Application, ReviewLog, error)
Approve records an approval. With ReviewTypeAll it counts approvals collected in the current round; the stage advances only when the full set has approved.
func (*WorkflowEngine) CanEditForm ¶
func (e *WorkflowEngine) CanEditForm(app Application, userID string) bool
CanEditForm reports whether userID may modify the application form fields. Only the owner, and only while in Draft or Returned, may edit.
func (*WorkflowEngine) CanResubmit ¶
func (e *WorkflowEngine) CanResubmit(app Application, userID string) bool
CanResubmit reports whether userID may re-submit a returned application.
func (*WorkflowEngine) CanReview ¶
func (e *WorkflowEngine) CanReview(app Application, template WorkflowTemplate, userID string) bool
CanReview reports whether userID is one of the approvers for the current stage. It performs the static permission check; the "already approved in current round" check is enforced inside Approve.
func (*WorkflowEngine) CanView ¶
func (e *WorkflowEngine) CanView(app Application, template WorkflowTemplate, userID string) bool
CanView reports whether userID is allowed to read the application. Owner can always view. Approvers in stages reached so far can view as well.
func (*WorkflowEngine) RejectClose ¶
func (e *WorkflowEngine) RejectClose(app Application, template WorkflowTemplate, operatorID string, comment string) (Application, ReviewLog, error)
RejectClose terminally closes the application. Requires a comment.
func (*WorkflowEngine) ResetToFirstSubmit ¶
func (e *WorkflowEngine) ResetToFirstSubmit(app Application, template WorkflowTemplate) (Application, ReviewLog, error)
ResetToFirstSubmit rolls a non-terminal application back to the state it occupied immediately after the very first Submit: Status = In_Review, CurrentStageIndex = 1, ReturnStageIndex = 0. A fresh SUBMIT log is produced so the engine's round-boundary logic invalidates every stale APPROVE.
Service.SaveTemplate calls this on every in-flight application whenever the template definition changes, so reviewers re-approve the new flow.
func (*WorkflowEngine) Resubmit ¶
func (e *WorkflowEngine) Resubmit(app Application, template WorkflowTemplate) (Application, ReviewLog, error)
Resubmit moves a Returned application back into review. STRICT mode restarts from stage 1; DIRECT mode resumes at the original return stage.
func (*WorkflowEngine) Return ¶
func (e *WorkflowEngine) Return(app Application, template WorkflowTemplate, operatorID string, comment string) (Application, ReviewLog, error)
Return puts the application back to the owner for modification. Records the originating stage in ReturnStageIndex for DIRECT-mode resubmission.
func (*WorkflowEngine) Submit ¶
func (e *WorkflowEngine) Submit(app Application, template WorkflowTemplate) (Application, ReviewLog, error)
Submit transitions a Draft application into In_Review at stage 1.
type WorkflowStage ¶
WorkflowStage defines a single approval stage.
type WorkflowTemplate ¶
type WorkflowTemplate struct {
ID string
ReturnMode string
Stages []WorkflowStage
}
WorkflowTemplate defines the static configuration of a workflow.
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
dynamic_resolver
command
Dynamic ApproverResolver example: reviewers are looked up at runtime from per-application context instead of being baked into the template.
|
Dynamic ApproverResolver example: reviewers are looked up at runtime from per-application context instead of being baked into the template. |
|
full_workflow
command
Comprehensive example covering every public flow:
|
Comprehensive example covering every public flow: |
|
quickstart
command
Quickstart example: shortest end-to-end usage.
|
Quickstart example: shortest end-to-end usage. |
|
Package gormstore provides a GORM-backed workflow.Store implementation.
|
Package gormstore provides a GORM-backed workflow.Store implementation. |