Documentation
¶
Index ¶
- Constants
- Variables
- func IsSuperAdmin(p *security.Principal) bool
- func PayloadOccurredAt(e DomainEvent) timex.DateTime
- func ValidateBusinessIdentifier(id string) error
- type ActionLog
- type ActionType
- type AddAssigneeType
- type ApprovalMethod
- type ApprovalNodeData
- type AssigneeDefinition
- type AssigneeKind
- type AssigneeService
- type AssigneesAddedEvent
- type AssigneesRemovedEvent
- type BaseNodeData
- type BindingMode
- type BusinessBindingHook
- type CCDefinition
- type CCKind
- type CCNodeData
- type CCNotifiedEvent
- type CCRecord
- type CCTiming
- type CallerContext
- type Condition
- type ConditionBranch
- type ConditionEvaluator
- type ConditionGroup
- type ConditionKind
- type ConditionNodeData
- type ConsecutiveApproverAction
- type Delegation
- type DomainEvent
- type EdgeDefinition
- type EmptyAssigneeAction
- type EndNodeData
- type EvaluationContext
- type ExecutionType
- type FieldKind
- type FieldOption
- type Flow
- type FlowCategory
- type FlowCreatedEvent
- type FlowDefinition
- type FlowDeployedEvent
- type FlowEdge
- type FlowFormField
- type FlowInitiator
- type FlowNode
- type FlowNodeAssignee
- type FlowNodeCC
- type FlowPublishedEvent
- type FlowToggledEvent
- type FlowUpdatedEvent
- type FlowVersion
- type FormData
- type FormDefinition
- type FormFieldDefinition
- type FormSnapshot
- type HandleNodeData
- type InitiatorKind
- type Instance
- type InstanceBindingFailedEvent
- type InstanceCompletedEvent
- type InstanceCreatedEvent
- type InstanceLifecycleHook
- type InstanceNoGenerator
- type InstanceResubmittedEvent
- type InstanceReturnedEvent
- type InstanceRolledBackEvent
- type InstanceStatus
- type InstanceWithdrawnEvent
- type NodeAutoPassedEvent
- type NodeData
- type NodeDefinition
- type NodeEnteredEvent
- type NodeKind
- type OperatorInfo
- type PassRule
- type PassRuleContext
- type PassRuleResult
- type PassRuleStrategy
- type Permission
- type Position
- type PrincipalDepartmentResolver
- type PrincipalTenantResolver
- type ResolvedAssignee
- type RollbackDataStrategy
- type RollbackType
- type SameApplicantAction
- type StartNodeData
- type StorageMode
- type Task
- type TaskApprovedEvent
- type TaskCreatedEvent
- type TaskDeadlineWarningEvent
- type TaskHandledEvent
- type TaskNodeData
- type TaskReassignedEvent
- type TaskRejectedEvent
- type TaskStatus
- type TaskTimedOutEvent
- type TaskTransferredEvent
- type TaskUrgedEvent
- type TimeoutAction
- type UrgeRecord
- type UserInfo
- type UserInfoResolver
- type ValidationRule
- type VersionStatus
Constants ¶
const SuperAdminRole = "approval:super_admin"
SuperAdminRole is the role string that grants cross-tenant access to admin queries and operations. Hosts assign this role to platform-level operators that legitimately need to act across tenants (audit teams, billing, etc.). Without it, admin endpoints reject requests that lack a tenant filter — guarding against accidental cross-tenant data exposure.
Variables ¶
var ( ErrUnknownNodeKind = errors.New("unknown node kind") ErrNodeDataUnmarshal = errors.New("node data unmarshal failed") )
var ErrCrossTenantAccess = errors.New("approval: cross-tenant access denied")
ErrCrossTenantAccess is returned when a non-super-admin caller attempts to act on an entity owned by a different tenant. Resource and command handlers use CallerContext.Authorize to surface it consistently.
var ErrInvalidBusinessIdentifier = errors.New("approval: invalid business identifier (must match ^[A-Za-z_][A-Za-z0-9_]{0,62}$)")
ErrInvalidBusinessIdentifier is returned by ValidateBusinessIdentifier for values that do not match a SQL-safe identifier pattern. Hosts that implement BusinessBindingHook should bubble this up (or wrap it) so admin-side flow CRUD surfaces a meaningful error to operators.
var SystemCaller = CallerContext{IsSystemInternal: true}
SystemCaller is the canonical CallerContext for in-process system code (timeout scanner, binding listener, engine-internal sagas, test fixtures that intentionally bypass tenant scoping). Use this instead of the zero value so the intent is explicit at the call site.
Functions ¶
func IsSuperAdmin ¶ added in v0.24.0
IsSuperAdmin reports whether the principal carries the cross-tenant override role. Nil principal returns false.
func PayloadOccurredAt ¶ added in v0.24.0
func PayloadOccurredAt(e DomainEvent) timex.DateTime
PayloadOccurredAt extracts the OccurredTime field from a DomainEvent payload. Every in-tree approval event struct carries this field; publishers use the value to project business time onto Envelope.OccurredAt via event.WithOccurredAt. Returns the zero DateTime for payloads that lack the field (defensive — should never happen for in-tree events).
Exposed as a package-level helper rather than a method on DomainEvent so the interface can stay minimal (EventType only) while still letting transports / behaviors project business time.
func ValidateBusinessIdentifier ¶ added in v0.24.0
ValidateBusinessIdentifier reports whether id is a safe SQL identifier for use as a table or column name in business binding interpolation. Empty / whitespace-only strings pass — the caller decides whether absence is itself an error (see Flow validation paths for the policy).
Types ¶
type ActionLog ¶
type ActionLog struct {
orm.BaseModel `bun:"table:apv_action_log,alias:aal"`
orm.Model
orm.CreationTrackedModel
InstanceID string `json:"instanceId" bun:"instance_id"`
NodeID *string `json:"nodeId" bun:"node_id,nullzero"`
TaskID *string `json:"taskId" bun:"task_id,nullzero"`
Action ActionType `json:"action" bun:"action"`
OperatorID string `json:"operatorId" bun:"operator_id"`
OperatorName string `json:"operatorName" bun:"operator_name"`
OperatorDepartmentID *string `json:"operatorDepartmentId" bun:"operator_department_id,nullzero"`
OperatorDepartmentName *string `json:"operatorDepartmentName" bun:"operator_department_name,nullzero"`
IPAddress *string `json:"ipAddress" bun:"ip_address,nullzero"`
UserAgent *string `json:"userAgent" bun:"user_agent,nullzero"`
Opinion *string `json:"opinion" bun:"opinion,nullzero"`
TransferToID *string `json:"transferToId" bun:"transfer_to_id,nullzero"`
TransferToName *string `json:"transferToName" bun:"transfer_to_name,nullzero"`
RollbackToNodeID *string `json:"rollbackToNodeId" bun:"rollback_to_node_id,nullzero"`
AddAssigneeType *AddAssigneeType `json:"addAssigneeType" bun:"add_assignee_type,nullzero"`
AddedAssigneeIDs []string `json:"addedAssigneeIds" bun:"added_assignee_ids,type:jsonb"`
RemovedAssigneeIDs []string `json:"removedAssigneeIds" bun:"removed_assignee_ids,type:jsonb"`
CCUserIDs []string `json:"ccUserIds" bun:"cc_user_ids,type:jsonb"`
Attachments []string `json:"attachments" bun:"attachments,type:jsonb,nullzero"`
Meta map[string]any `json:"meta" bun:"meta,type:jsonb,nullzero"`
}
ActionLog represents an action log entry.
type ActionType ¶
type ActionType string
ActionType represents the type of action performed by an operator.
const ( ActionSubmit ActionType = "submit" ActionApprove ActionType = "approve" ActionHandle ActionType = "handle" ActionReject ActionType = "reject" ActionTransfer ActionType = "transfer" ActionWithdraw ActionType = "withdraw" ActionCancel ActionType = "cancel" ActionRollback ActionType = "rollback" ActionAddAssignee ActionType = "add_assignee" ActionRemoveAssignee ActionType = "remove_assignee" ActionExecute ActionType = "execute" // System execution action ActionResubmit ActionType = "resubmit" // Resubmit a returned instance ActionReassign ActionType = "reassign" // Admin reassigned task to a different user ActionTerminate ActionType = "terminate" // Admin force-terminated an instance ActionAddCC ActionType = "add_cc" // Participant added CC recipients )
type AddAssigneeType ¶
type AddAssigneeType string
AddAssigneeType represents the type of dynamic assignee addition. It defines how a newly added assignee is positioned relative to the current task.
const ( AddAssigneeBefore AddAssigneeType = "before" // Before: new assignee processes first, original task becomes pending after completion AddAssigneeAfter AddAssigneeType = "after" // After: new assignee processes after the original assignee completes AddAssigneeParallel AddAssigneeType = "parallel" // Parallel: new assignee joins the current parallel group to process together )
func (AddAssigneeType) IsValid ¶
func (t AddAssigneeType) IsValid() bool
IsValid checks if the AddAssigneeType is a valid value.
func (*AddAssigneeType) UnmarshalJSON ¶
func (t *AddAssigneeType) UnmarshalJSON(data []byte) error
UnmarshalJSON validates AddAssigneeType values when decoding JSON payloads.
type ApprovalMethod ¶
type ApprovalMethod string
ApprovalMethod represents the method of approval for a node with multiple assignees. It defines how the approval decision is made when there are multiple approvers.
const ( ApprovalSequential ApprovalMethod = "sequential" // Sequential: approvers process one by one in order, all must approve ApprovalParallel ApprovalMethod = "parallel" // Parallel: approvers process simultaneously, decision based on consensus rules )
type ApprovalNodeData ¶
type ApprovalNodeData struct {
BaseNodeData
TaskNodeData
ApprovalMethod ApprovalMethod `json:"approvalMethod,omitempty"`
PassRule PassRule `json:"passRule,omitempty"`
PassRatio decimal.Decimal `json:"passRatio"`
SameApplicantAction SameApplicantAction `json:"sameApplicantAction,omitempty"`
ConsecutiveApproverAction ConsecutiveApproverAction `json:"consecutiveApproverAction,omitempty"`
RollbackType RollbackType `json:"rollbackType,omitempty"`
RollbackDataStrategy RollbackDataStrategy `json:"rollbackDataStrategy,omitempty"`
RollbackTargetKeys []string `json:"rollbackTargetKeys,omitempty"`
IsRollbackAllowed bool `json:"isRollbackAllowed,omitempty"`
IsAddAssigneeAllowed bool `json:"isAddAssigneeAllowed,omitempty"`
AddAssigneeTypes []AddAssigneeType `json:"addAssigneeTypes,omitempty"`
IsRemoveAssigneeAllowed bool `json:"isRemoveAssigneeAllowed,omitempty"`
IsManualCCAllowed bool `json:"isManualCcAllowed,omitempty"`
}
ApprovalNodeData contains data specific to approval nodes.
func (*ApprovalNodeData) ApplyTo ¶
func (d *ApprovalNodeData) ApplyTo(node *FlowNode)
ApplyTo applies approval node data to a FlowNode.
func (*ApprovalNodeData) Kind ¶
func (*ApprovalNodeData) Kind() NodeKind
Kind returns the node kind.
type AssigneeDefinition ¶
type AssigneeDefinition struct {
Kind AssigneeKind `json:"kind"`
IDs []string `json:"ids,omitempty"`
FormField *string `json:"formField,omitempty"`
SortOrder int `json:"sortOrder"`
}
AssigneeDefinition represents an assignee configuration in the flow definition.
type AssigneeKind ¶
type AssigneeKind string
AssigneeKind represents the kind of assignee.
const ( AssigneeUser AssigneeKind = "user" AssigneeRole AssigneeKind = "role" AssigneeDepartment AssigneeKind = "department" // Department head AssigneeSelf AssigneeKind = "self" // Applicant themselves AssigneeSuperior AssigneeKind = "superior" // Direct superior AssigneeDepartmentLeader AssigneeKind = "department_leader" // Continuous multi-level supervisor AssigneeFormField AssigneeKind = "form_field" // Based on form field )
type AssigneeService ¶
type AssigneeService interface {
// GetSuperior returns the direct superior's user info for the given user.
GetSuperior(ctx context.Context, userID string) (*UserInfo, error)
// GetDepartmentLeaders returns the leader user info for the given department.
GetDepartmentLeaders(ctx context.Context, departmentID string) ([]UserInfo, error)
// GetRoleUsers returns all user info for users that have the given role.
GetRoleUsers(ctx context.Context, roleID string) ([]UserInfo, error)
}
AssigneeService resolves approval assignees from organizational data (implemented by host app).
type AssigneesAddedEvent ¶
type AssigneesAddedEvent struct {
InstanceID string `json:"instanceId"`
TenantID string `json:"tenantId"`
NodeID string `json:"nodeId"`
TaskID string `json:"taskId"`
AddType AddAssigneeType `json:"addType"`
AssigneeIDs []string `json:"assigneeIds"`
AssigneeNames map[string]string `json:"assigneeNames"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
AssigneesAddedEvent fired when assignees are dynamically added.
func NewAssigneesAddedEvent ¶
func NewAssigneesAddedEvent(instanceID, tenantID, nodeID, taskID string, addType AddAssigneeType, assigneeIDs []string, assigneeNames map[string]string) *AssigneesAddedEvent
func (*AssigneesAddedEvent) EventType ¶ added in v0.24.0
func (*AssigneesAddedEvent) EventType() string
type AssigneesRemovedEvent ¶
type AssigneesRemovedEvent struct {
InstanceID string `json:"instanceId"`
TenantID string `json:"tenantId"`
NodeID string `json:"nodeId"`
TaskID string `json:"taskId"`
AssigneeIDs []string `json:"assigneeIds"`
AssigneeNames map[string]string `json:"assigneeNames"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
AssigneesRemovedEvent fired when assignees are dynamically removed.
func NewAssigneesRemovedEvent ¶
func NewAssigneesRemovedEvent(instanceID, tenantID, nodeID, taskID string, assigneeIDs []string, assigneeNames map[string]string) *AssigneesRemovedEvent
func (*AssigneesRemovedEvent) EventType ¶ added in v0.24.0
func (*AssigneesRemovedEvent) EventType() string
type BaseNodeData ¶
type BaseNodeData struct {
Name string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
}
BaseNodeData contains common fields shared across all node data types.
func (BaseNodeData) GetDescription ¶
func (d BaseNodeData) GetDescription() *string
GetDescription returns the node description.
type BindingMode ¶
type BindingMode string
BindingMode represents the mode of binding with business data. It defines how the approval workflow stores and associates form data.
const ( BindingStandalone BindingMode = "standalone" // Standalone: form data is stored in the approval workflow's own table BindingBusiness BindingMode = "business" // Business: links to existing business data table )
type BusinessBindingHook ¶ added in v0.24.0
type BusinessBindingHook interface {
// OnInstanceCreated returns the business primary key (BusinessRecordID)
// to persist on the instance. Returning empty string indicates the host
// has nothing to bind (engine stores nil).
OnInstanceCreated(ctx context.Context, db orm.DB, flow *Flow, instance *Instance) (businessRecordID string, err error)
// WriteBackStatus writes the final approval status back to the
// business table. Called asynchronously from the binding Listener
// after InstanceCompletedEvent fires. Implementations should be
// idempotent — the listener may retry through the outbox.
WriteBackStatus(ctx context.Context, db orm.DB, flow *Flow, instance *Instance, finalStatus InstanceStatus) error
}
BusinessBindingHook bridges the approval engine with the host application's business tables when Flow.BindingMode is BindingBusiness. It is narrowly scoped to the "approval row ↔ business row" plumbing; broader lifecycle extension goes through InstanceLifecycleHook instead.
Two lifecycle moments matter:
OnInstanceCreated runs inside the start_instance transaction so the host can resolve / create the business row and return the primary key that the engine stores in Instance.BusinessRecordID. Returning an error rolls back the entire instance creation.
WriteBackStatus runs asynchronously via the binding Listener (which subscribes to InstanceCompletedEvent) so the host can stamp the final approval decision onto its own business table. A non-nil error does NOT roll back the approval — the workflow has already decided. Instead the listener publishes InstanceBindingFailedEvent so the host can retry (saga / outbox compensation).
Hosts override the default implementation by binding their own BusinessBindingHook into the FX container, typically through vef.SupplyBusinessBindingHook.
type CCDefinition ¶
type CCDefinition struct {
Kind CCKind `json:"kind"`
IDs []string `json:"ids,omitempty"`
FormField *string `json:"formField,omitempty"`
Timing CCTiming `json:"timing,omitempty"`
}
CCDefinition represents a CC recipient in node data.
type CCNodeData ¶
type CCNodeData struct {
BaseNodeData
CCs []CCDefinition `json:"ccs,omitempty"`
IsReadConfirmRequired bool `json:"isReadConfirmRequired,omitempty"`
FieldPermissions map[string]Permission `json:"fieldPermissions,omitempty"`
}
CCNodeData contains data specific to CC nodes.
func (*CCNodeData) ApplyTo ¶
func (d *CCNodeData) ApplyTo(node *FlowNode)
ApplyTo applies CC node data to a FlowNode.
func (*CCNodeData) GetCCs ¶
func (d *CCNodeData) GetCCs() []CCDefinition
GetCCs returns the CC definitions from CCNodeData.
type CCNotifiedEvent ¶
type CCNotifiedEvent struct {
InstanceID string `json:"instanceId"`
TenantID string `json:"tenantId"`
NodeID string `json:"nodeId"`
CCUserIDs []string `json:"ccUserIds"`
CCUserNames map[string]string `json:"ccUserNames"`
IsManual bool `json:"isManual"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
CCNotifiedEvent fired when users are carbon-copied.
func NewCCNotifiedEvent ¶
func (*CCNotifiedEvent) EventType ¶ added in v0.24.0
func (*CCNotifiedEvent) EventType() string
type CCRecord ¶
type CCRecord struct {
orm.BaseModel `bun:"table:apv_cc_record,alias:acr"`
orm.Model
orm.CreationTrackedModel
InstanceID string `json:"instanceId" bun:"instance_id"`
NodeID *string `json:"nodeId" bun:"node_id,nullzero"`
TaskID *string `json:"taskId" bun:"task_id,nullzero"`
CCUserID string `json:"ccUserId" bun:"cc_user_id"`
CCUserName string `json:"ccUserName" bun:"cc_user_name"`
IsManual bool `json:"isManual" bun:"is_manual"`
ReadAt *timex.DateTime `json:"readAt" bun:"read_at,nullzero"`
}
CCRecord represents a CC notification record.
type CallerContext ¶ added in v0.24.0
type CallerContext struct {
// TenantID is the caller's resolved tenant. Empty for super-admin or
// for system-internal callers; must be non-empty for every authenticated
// API request.
TenantID string
// IsSuperAdmin grants cross-tenant access regardless of TenantID.
IsSuperAdmin bool
// IsSystemInternal marks a caller without an HTTP / RPC principal
// (timeout scanner, binding listener, engine-internal saga, test
// fixtures). Authorize passes unconditionally for such callers; resource
// paths must NEVER populate this — it's only legitimate for in-process
// system code that already established scope by other means.
IsSystemInternal bool
}
CallerContext bundles the tenant authority of a single API call. Resource handlers resolve it from the security principal (via PrincipalTenantResolver + IsSuperAdmin) and pass it into commands / queries through their struct fields so handlers can enforce data ownership without re-parsing principal details.
Production code MUST populate exactly one of TenantID / IsSuperAdmin / IsSystemInternal. A zero-value CallerContext is treated as **unauthorized** so a forgotten resolver call surfaces as a deny rather than silently waving the request through — this is the deliberate fail- closed posture introduced after the security audit caught fail-open regressions in the default principal resolver.
func (CallerContext) Allows ¶ added in v0.24.0
func (c CallerContext) Allows(entityTenantID string) bool
Allows is the bool variant of Authorize. Query handlers use it when a failed authorization must mimic "not found" rather than surface an error — the typical multi-tenant pattern that avoids leaking entity existence across tenants.
func (CallerContext) Authorize ¶ added in v0.24.0
func (c CallerContext) Authorize(entityTenantID string) error
Authorize reports whether the caller is allowed to act on an entity owned by entityTenantID. Super-admin callers always pass; system-internal callers pass too (no HTTP principal exists, scope is enforced upstream). Every other caller must have a non-empty TenantID that matches the entity tenant exactly — zero-value contexts are rejected.
func (CallerContext) EffectiveTenantID ¶ added in v0.24.0
func (c CallerContext) EffectiveTenantID(override string) string
EffectiveTenantID returns the tenant filter the caller is actually allowed to query. Non-super-admin callers always operate within their own tenant; their override (if any) is ignored. Super-admin and system-internal callers may pass a specific tenant through override, or empty for cross-tenant visibility. This is the single source of truth for list queries — the resource layer should never read params.TenantID directly.
type Condition ¶
type Condition struct {
Kind ConditionKind `json:"kind"`
Subject string `json:"subject"`
Operator string `json:"operator"`
Value any `json:"value"`
Expression string `json:"expression"`
}
Condition represents a branch condition evaluated by condition nodes.
type ConditionBranch ¶
type ConditionBranch struct {
ID string `json:"id"`
Label string `json:"label"`
ConditionGroups []ConditionGroup `json:"conditionGroups,omitempty"`
IsDefault bool `json:"isDefault,omitempty"`
Priority int `json:"priority"`
}
ConditionBranch represents a branch in a condition node. Each branch has its own condition groups and can be linked to an edge via its ID.
type ConditionEvaluator ¶
type ConditionEvaluator interface {
// Kind returns the condition kind this evaluator handles.
Kind() ConditionKind
// Evaluate evaluates a single condition against the given evaluation context.
Evaluate(ctx context.Context, cond Condition, ec *EvaluationContext) (bool, error)
}
ConditionEvaluator evaluates branch conditions.
type ConditionGroup ¶
type ConditionGroup struct {
Conditions []Condition `json:"conditions"`
}
ConditionGroup represents a group of conditions evaluated with AND logic. Multiple groups in a branch are evaluated with OR logic.
type ConditionKind ¶
type ConditionKind string
ConditionKind represents the kind of condition for condition branches.
const ( ConditionField ConditionKind = "field" // Field-based condition ConditionExpression ConditionKind = "expression" // Expression-based condition )
type ConditionNodeData ¶
type ConditionNodeData struct {
BaseNodeData
Branches []ConditionBranch `json:"branches,omitempty"`
}
ConditionNodeData contains data specific to condition nodes.
func (*ConditionNodeData) ApplyTo ¶
func (d *ConditionNodeData) ApplyTo(node *FlowNode)
ApplyTo applies condition node data to a FlowNode.
func (*ConditionNodeData) Kind ¶
func (*ConditionNodeData) Kind() NodeKind
Kind returns the node kind.
type ConsecutiveApproverAction ¶
type ConsecutiveApproverAction string
ConsecutiveApproverAction represents the action when the same approver appears in consecutive approval nodes and approved in the previous node.
const ( ConsecutiveApproverNone ConsecutiveApproverAction = "none" ConsecutiveApproverAutoPass ConsecutiveApproverAction = "auto_pass" )
type Delegation ¶
type Delegation struct {
orm.BaseModel `bun:"table:apv_delegation,alias:ad"`
orm.FullAuditedModel
DelegatorID string `json:"delegatorId" bun:"delegator_id"`
DelegateeID string `json:"delegateeId" bun:"delegatee_id"`
FlowCategoryID *string `json:"flowCategoryId" bun:"flow_category_id,nullzero"`
FlowID *string `json:"flowId" bun:"flow_id,nullzero"`
StartTime timex.DateTime `json:"startTime" bun:"start_time"`
EndTime timex.DateTime `json:"endTime" bun:"end_time"`
IsActive bool `json:"isActive" bun:"is_active"`
Reason *string `json:"reason" bun:"reason,nullzero"`
}
Delegation represents an approval delegation.
type DomainEvent ¶
type DomainEvent interface {
// EventType returns the unique event identifier (e.g., "approval.instance.created").
EventType() string
}
DomainEvent is the contract every approval domain event satisfies. EventType matches the framework's event.Event surface so domain events can be published through the event Bus without adaptation. Business time is carried as OccurredTime in the concrete payload and projected onto Envelope.OccurredAt via event.WithOccurredAt at publish time.
Tenant scope: every instance/task/node/cc-level event carries TenantID so subscribers can route on tenancy without re-querying. Flow-level events (created/updated/etc.) already include TenantID on the payload.
type EdgeDefinition ¶
type EdgeDefinition struct {
ID string `json:"id"`
Source string `json:"source"`
Target string `json:"target"`
SourceHandle *string `json:"sourceHandle,omitempty"`
Data map[string]any `json:"data,omitempty"`
}
EdgeDefinition represents a connection between nodes.
type EmptyAssigneeAction ¶
type EmptyAssigneeAction string
EmptyAssigneeAction represents the action when no assignee is found.
const ( EmptyAssigneeAutoPass EmptyAssigneeAction = "auto_pass" EmptyAssigneeTransferAdmin EmptyAssigneeAction = "transfer_admin" EmptyAssigneeTransferSuperior EmptyAssigneeAction = "transfer_superior" EmptyAssigneeTransferApplicant EmptyAssigneeAction = "transfer_applicant" EmptyAssigneeTransferSpecified EmptyAssigneeAction = "transfer_specified" )
type EndNodeData ¶
type EndNodeData struct {
BaseNodeData
}
EndNodeData contains data specific to end nodes.
func (*EndNodeData) ApplyTo ¶
func (d *EndNodeData) ApplyTo(node *FlowNode)
ApplyTo applies end node data to a FlowNode.
type EvaluationContext ¶
type EvaluationContext struct {
FormData FormData
ApplicantID string
ApplicantDepartmentID *string
}
EvaluationContext provides context for condition evaluation.
type ExecutionType ¶
type ExecutionType string
ExecutionType represents how a node is executed. It determines whether the node requires manual intervention or can be processed automatically.
const ( ExecutionManual ExecutionType = "manual" // Manual: requires human intervention to process ExecutionAuto ExecutionType = "auto" // Auto: automatically executed by the system ExecutionAutoPass ExecutionType = "auto_pass" // AutoPass: automatically approved when no assignee is found ExecutionAutoReject ExecutionType = "auto_reject" // AutoReject: automatically rejected when no assignee is found )
type FieldOption ¶
FieldOption represents a selectable option for select/radio/checkbox fields.
type Flow ¶
type Flow struct {
orm.BaseModel `bun:"table:apv_flow,alias:af"`
orm.FullAuditedModel
TenantID string `json:"tenantId" bun:"tenant_id"`
CategoryID string `json:"categoryId" bun:"category_id"`
Code string `json:"code" bun:"code"`
Name string `json:"name" bun:"name"`
Icon *string `json:"icon" bun:"icon,nullzero"`
Description *string `json:"description" bun:"description,nullzero"`
BindingMode BindingMode `json:"bindingMode" bun:"binding_mode"`
BusinessTable *string `json:"businessTable" bun:"business_table,nullzero"`
BusinessPkField *string `json:"businessPkField" bun:"business_pk_field,nullzero"`
BusinessTitleField *string `json:"businessTitleField" bun:"business_title_field,nullzero"`
BusinessStatusField *string `json:"businessStatusField" bun:"business_status_field,nullzero"`
AdminUserIDs []string `json:"adminUserIds" bun:"admin_user_ids,type:jsonb"`
IsAllInitiationAllowed bool `json:"isAllInitiationAllowed" bun:"is_all_initiation_allowed"`
InstanceTitleTemplate string `json:"instanceTitleTemplate" bun:"instance_title_template"`
IsActive bool `json:"isActive" bun:"is_active"`
CurrentVersion int `json:"currentVersion" bun:"current_version"`
}
Flow represents a flow definition.
type FlowCategory ¶
type FlowCategory struct {
orm.BaseModel `bun:"table:apv_flow_category,alias:afc"`
orm.FullAuditedModel
TenantID string `json:"tenantId" bun:"tenant_id"`
Code string `json:"code" bun:"code"`
Name string `json:"name" bun:"name"`
Icon *string `json:"icon" bun:"icon,nullzero"`
ParentID *string `json:"parentId" bun:"parent_id,nullzero"`
SortOrder int `json:"sortOrder" bun:"sort_order"`
IsActive bool `json:"isActive" bun:"is_active"`
Remark *string `json:"remark" bun:"remark,nullzero"`
Children []FlowCategory `json:"children,omitempty" bun:"-"`
}
FlowCategory represents a category for grouping flows.
type FlowCreatedEvent ¶ added in v0.24.0
type FlowCreatedEvent struct {
FlowID string `json:"flowId"`
TenantID string `json:"tenantId"`
Code string `json:"code"`
Name string `json:"name"`
CategoryID string `json:"categoryId"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
FlowCreatedEvent fires when a new flow definition is created.
func NewFlowCreatedEvent ¶ added in v0.24.0
func NewFlowCreatedEvent(flowID, tenantID, code, name, categoryID string) *FlowCreatedEvent
func (*FlowCreatedEvent) EventType ¶ added in v0.24.0
func (*FlowCreatedEvent) EventType() string
type FlowDefinition ¶
type FlowDefinition struct {
Nodes []NodeDefinition `json:"nodes"`
Edges []EdgeDefinition `json:"edges"`
}
FlowDefinition represents the structure of a flow definition JSON (React Flow compatible).
type FlowDeployedEvent ¶ added in v0.24.0
type FlowDeployedEvent struct {
FlowID string `json:"flowId"`
TenantID string `json:"tenantId"`
VersionID string `json:"versionId"`
Version int `json:"version"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
FlowDeployedEvent fires when a flow's schema is deployed as a new draft version (before it's published).
func NewFlowDeployedEvent ¶ added in v0.24.0
func NewFlowDeployedEvent(flowID, tenantID, versionID string, version int) *FlowDeployedEvent
func (*FlowDeployedEvent) EventType ¶ added in v0.24.0
func (*FlowDeployedEvent) EventType() string
type FlowEdge ¶
type FlowEdge struct {
orm.BaseModel `bun:"table:apv_flow_edge,alias:afe"`
orm.Model
FlowVersionID string `json:"flowVersionId" bun:"flow_version_id"`
Key string `json:"key" bun:"key,nullzero"`
SourceNodeID string `json:"sourceNodeId" bun:"source_node_id"`
SourceNodeKey string `json:"sourceNodeKey" bun:"source_node_key"`
TargetNodeID string `json:"targetNodeId" bun:"target_node_id"`
TargetNodeKey string `json:"targetNodeKey" bun:"target_node_key"`
SourceHandle *string `json:"sourceHandle" bun:"source_handle,nullzero"`
}
FlowEdge represents a directed edge between two flow nodes.
type FlowFormField ¶
type FlowFormField struct {
orm.BaseModel `bun:"table:apv_flow_form_field,alias:afff"`
orm.Model
FlowVersionID string `json:"flowVersionId" bun:"flow_version_id"`
Name string `json:"name" bun:"name"`
Kind FieldKind `json:"kind" bun:"kind"`
Label string `json:"label" bun:"label"`
Placeholder *string `json:"placeholder" bun:"placeholder,nullzero"`
DefaultValue *string `json:"defaultValue" bun:"default_value,nullzero"`
IsRequired *bool `json:"isRequired" bun:"is_required,nullzero"`
IsReadonly *bool `json:"isReadonly" bun:"is_readonly,nullzero"`
Validation map[string]any `json:"validation" bun:"validation,type:jsonb,nullzero"`
SortOrder int `json:"sortOrder" bun:"sort_order"`
Meta map[string]any `json:"meta" bun:"meta,type:jsonb,nullzero"`
}
FlowFormField represents a flow form field definition.
type FlowInitiator ¶
type FlowInitiator struct {
orm.BaseModel `bun:"table:apv_flow_initiator,alias:afi"`
orm.Model
FlowID string `json:"flowId" bun:"flow_id"`
Kind InitiatorKind `json:"kind" bun:"kind"`
IDs []string `json:"ids" bun:"ids,type:jsonb"`
}
FlowInitiator represents a flow initiator configuration.
type FlowNode ¶
type FlowNode struct {
orm.BaseModel `bun:"table:apv_flow_node,alias:afn"`
orm.FullAuditedModel
FlowVersionID string `json:"flowVersionId" bun:"flow_version_id"`
Key string `json:"key" bun:"key"`
Kind NodeKind `json:"kind" bun:"kind"`
Name string `json:"name" bun:"name"`
Description *string `json:"description" bun:"description,nullzero"`
ExecutionType ExecutionType `json:"executionType" bun:"execution_type"`
ApprovalMethod ApprovalMethod `json:"approvalMethod" bun:"approval_method"`
PassRule PassRule `json:"passRule" bun:"pass_rule"`
PassRatio decimal.Decimal `json:"passRatio" bun:"pass_ratio"`
EmptyAssigneeAction EmptyAssigneeAction `json:"emptyAssigneeAction" bun:"empty_assignee_action"`
FallbackUserIDs []string `json:"fallbackUserIds" bun:"fallback_user_ids,type:jsonb"`
AdminUserIDs []string `json:"adminUserIds" bun:"admin_user_ids,type:jsonb"`
SameApplicantAction SameApplicantAction `json:"sameApplicantAction" bun:"same_applicant_action"`
IsRollbackAllowed bool `json:"isRollbackAllowed" bun:"is_rollback_allowed"`
RollbackType RollbackType `json:"rollbackType" bun:"rollback_type"`
RollbackDataStrategy RollbackDataStrategy `json:"rollbackDataStrategy" bun:"rollback_data_strategy"`
RollbackTargetKeys []string `json:"rollbackTargetKeys" bun:"rollback_target_keys,type:jsonb,nullzero"`
IsAddAssigneeAllowed bool `json:"isAddAssigneeAllowed" bun:"is_add_assignee_allowed"`
AddAssigneeTypes []AddAssigneeType `json:"addAssigneeTypes" bun:"add_assignee_types,type:jsonb"`
IsRemoveAssigneeAllowed bool `json:"isRemoveAssigneeAllowed" bun:"is_remove_assignee_allowed"`
FieldPermissions map[string]Permission `json:"fieldPermissions" bun:"field_permissions,type:jsonb"`
IsManualCCAllowed bool `json:"isManualCcAllowed" bun:"is_manual_cc_allowed"`
IsTransferAllowed bool `json:"isTransferAllowed" bun:"is_transfer_allowed"`
IsOpinionRequired bool `json:"isOpinionRequired" bun:"is_opinion_required"`
TimeoutHours int `json:"timeoutHours" bun:"timeout_hours"`
TimeoutAction TimeoutAction `json:"timeoutAction" bun:"timeout_action"`
TimeoutNotifyBeforeHours int `json:"timeoutNotifyBeforeHours" bun:"timeout_notify_before_hours"`
UrgeCooldownMinutes int `json:"urgeCooldownMinutes" bun:"urge_cooldown_minutes"`
ConsecutiveApproverAction ConsecutiveApproverAction `json:"consecutiveApproverAction" bun:"consecutive_approver_action"`
IsReadConfirmRequired bool `json:"isReadConfirmRequired" bun:"is_read_confirm_required"`
Branches []ConditionBranch `json:"branches" bun:"branches,type:jsonb,nullzero"`
}
FlowNode represents a node within a flow version.
type FlowNodeAssignee ¶
type FlowNodeAssignee struct {
orm.BaseModel `bun:"table:apv_flow_node_assignee,alias:afna"`
orm.Model
NodeID string `json:"nodeId" bun:"node_id"`
Kind AssigneeKind `json:"kind" bun:"kind"`
IDs []string `json:"ids" bun:"ids,type:jsonb"`
FormField *string `json:"formField" bun:"form_field,nullzero"`
SortOrder int `json:"sortOrder" bun:"sort_order"`
}
FlowNodeAssignee represents a node assignee configuration.
type FlowNodeCC ¶
type FlowNodeCC struct {
orm.BaseModel `bun:"table:apv_flow_node_cc,alias:afnc"`
orm.Model
NodeID string `json:"nodeId" bun:"node_id"`
Kind CCKind `json:"kind" bun:"kind"`
IDs []string `json:"ids" bun:"ids,type:jsonb"`
FormField *string `json:"formField" bun:"form_field,nullzero"`
Timing CCTiming `json:"timing" bun:"timing"`
}
FlowNodeCC represents a node CC configuration.
type FlowPublishedEvent ¶
type FlowPublishedEvent struct {
FlowID string `json:"flowId"`
TenantID string `json:"tenantId"`
VersionID string `json:"versionId"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
FlowPublishedEvent fired when a flow version is published.
func NewFlowPublishedEvent ¶
func NewFlowPublishedEvent(flowID, tenantID, versionID string) *FlowPublishedEvent
func (*FlowPublishedEvent) EventType ¶ added in v0.24.0
func (*FlowPublishedEvent) EventType() string
type FlowToggledEvent ¶ added in v0.24.0
type FlowToggledEvent struct {
FlowID string `json:"flowId"`
TenantID string `json:"tenantId"`
IsActive bool `json:"isActive"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
FlowToggledEvent fires when a flow is activated or deactivated.
func NewFlowToggledEvent ¶ added in v0.24.0
func NewFlowToggledEvent(flowID, tenantID string, isActive bool) *FlowToggledEvent
func (*FlowToggledEvent) EventType ¶ added in v0.24.0
func (*FlowToggledEvent) EventType() string
type FlowUpdatedEvent ¶ added in v0.24.0
type FlowUpdatedEvent struct {
FlowID string `json:"flowId"`
TenantID string `json:"tenantId"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
FlowUpdatedEvent fires when a flow's metadata (name, description, admins, initiators, etc.) is updated. Version publication has its own event.
func NewFlowUpdatedEvent ¶ added in v0.24.0
func NewFlowUpdatedEvent(flowID, tenantID string) *FlowUpdatedEvent
func (*FlowUpdatedEvent) EventType ¶ added in v0.24.0
func (*FlowUpdatedEvent) EventType() string
type FlowVersion ¶
type FlowVersion struct {
orm.BaseModel `bun:"table:apv_flow_version,alias:afv"`
orm.FullAuditedModel
FlowID string `json:"flowId" bun:"flow_id"`
Version int `json:"version" bun:"version"`
Status VersionStatus `json:"status" bun:"status"`
Description *string `json:"description" bun:"description,nullzero"`
StorageMode StorageMode `json:"storageMode" bun:"storage_mode"`
FlowSchema *FlowDefinition `json:"flowSchema" bun:"flow_schema,type:jsonb,nullzero"`
FormSchema *FormDefinition `json:"formSchema" bun:"form_schema,type:jsonb,nullzero"`
PublishedAt *timex.DateTime `json:"publishedAt" bun:"published_at,nullzero"`
PublishedBy *string `json:"publishedBy" bun:"published_by,nullzero"`
}
FlowVersion represents a versioned snapshot of a flow definition.
type FormData ¶
FormData wraps a map to provide helper methods for form data operations.
func NewFormData ¶
type FormDefinition ¶
type FormDefinition struct {
Fields []FormFieldDefinition `json:"fields"`
}
FormDefinition represents the form schema definition for a flow version.
type FormFieldDefinition ¶
type FormFieldDefinition struct {
// Key is the unique identifier for this field (used in form data keys).
Key string `json:"key"`
// Kind is the field type (e.g., "input", "textarea", "select", "number", "date", "upload").
Kind FieldKind `json:"kind"`
// Label is the display label.
Label string `json:"label"`
// Placeholder is the input placeholder text.
Placeholder string `json:"placeholder,omitempty"`
// DefaultValue is the default value for this field.
DefaultValue any `json:"defaultValue,omitempty"`
// IsRequired indicates whether this field is required.
IsRequired bool `json:"isRequired,omitempty"`
// Options is the list of selectable options (for select, radio, checkbox, etc.).
Options []FieldOption `json:"options,omitempty"`
// Validation contains validation rules.
Validation *ValidationRule `json:"validation,omitempty"`
// Props contains additional component-specific properties.
Props map[string]any `json:"props,omitempty"`
// SortOrder controls the display order.
SortOrder int `json:"sortOrder"`
}
FormFieldDefinition represents a single form field.
type FormSnapshot ¶
type FormSnapshot struct {
orm.BaseModel `bun:"table:apv_form_snapshot,alias:afs"`
orm.Model
orm.CreationTrackedModel
InstanceID string `json:"instanceId" bun:"instance_id"`
NodeID string `json:"nodeId" bun:"node_id"`
FormData map[string]any `json:"formData" bun:"form_data,type:jsonb"`
}
FormSnapshot represents a form snapshot for rollback strategies.
type HandleNodeData ¶
type HandleNodeData struct {
BaseNodeData
TaskNodeData
}
HandleNodeData contains data specific to handle nodes.
func (*HandleNodeData) ApplyTo ¶
func (d *HandleNodeData) ApplyTo(node *FlowNode)
ApplyTo applies handle node data to a FlowNode. Handle nodes default to sequential execution with PassAny rule, since any handler completing the task is sufficient.
type InitiatorKind ¶
type InitiatorKind string
InitiatorKind represents the kind of initiator.
const ( InitiatorUser InitiatorKind = "user" InitiatorRole InitiatorKind = "role" InitiatorDepartment InitiatorKind = "department" )
type Instance ¶
type Instance struct {
orm.BaseModel `bun:"table:apv_instance,alias:ai"`
orm.FullAuditedModel
TenantID string `json:"tenantId" bun:"tenant_id"`
FlowID string `json:"flowId" bun:"flow_id"`
FlowVersionID string `json:"flowVersionId" bun:"flow_version_id"`
Title string `json:"title" bun:"title"`
InstanceNo string `json:"instanceNo" bun:"instance_no"`
ApplicantID string `json:"applicantId" bun:"applicant_id"`
ApplicantName string `json:"applicantName" bun:"applicant_name"`
ApplicantDepartmentID *string `json:"applicantDepartmentId" bun:"applicant_department_id,nullzero"`
ApplicantDepartmentName *string `json:"applicantDepartmentName" bun:"applicant_department_name,nullzero"`
Status InstanceStatus `json:"status" bun:"status"`
CurrentNodeID *string `json:"currentNodeId" bun:"current_node_id,nullzero"`
FinishedAt *timex.DateTime `json:"finishedAt" bun:"finished_at,nullzero"`
BusinessRecordID *string `json:"businessRecordId" bun:"business_record_id,nullzero"`
FormData map[string]any `json:"formData" bun:"form_data,type:jsonb,nullzero"`
}
Instance represents a flow instance.
type InstanceBindingFailedEvent ¶ added in v0.24.0
type InstanceBindingFailedEvent struct {
InstanceID string `json:"instanceId"`
TenantID string `json:"tenantId"`
FlowID string `json:"flowId"`
FinalStatus InstanceStatus `json:"finalStatus"`
BusinessTable string `json:"businessTable"`
ErrorMessage string `json:"errorMessage"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
InstanceBindingFailedEvent fires when business binding (writing the final status back to the host's business table) fails after the approval itself has already committed. Subscribers retry asynchronously; the approval is not rolled back. Operators can grep these events for stuck bindings.
func NewInstanceBindingFailedEvent ¶ added in v0.24.0
func NewInstanceBindingFailedEvent(instanceID, tenantID, flowID string, finalStatus InstanceStatus, businessTable, errorMessage string) *InstanceBindingFailedEvent
func (*InstanceBindingFailedEvent) EventType ¶ added in v0.24.0
func (*InstanceBindingFailedEvent) EventType() string
type InstanceCompletedEvent ¶
type InstanceCompletedEvent struct {
InstanceID string `json:"instanceId"`
TenantID string `json:"tenantId"`
FinalStatus InstanceStatus `json:"finalStatus"`
FinishedAt timex.DateTime `json:"finishedAt"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
InstanceCompletedEvent fired when instance reaches a final status.
func NewInstanceCompletedEvent ¶
func NewInstanceCompletedEvent(instanceID, tenantID string, finalStatus InstanceStatus) *InstanceCompletedEvent
func (*InstanceCompletedEvent) EventType ¶ added in v0.24.0
func (*InstanceCompletedEvent) EventType() string
type InstanceCreatedEvent ¶
type InstanceCreatedEvent struct {
InstanceID string `json:"instanceId"`
TenantID string `json:"tenantId"`
FlowID string `json:"flowId"`
Title string `json:"title"`
ApplicantID string `json:"applicantId"`
ApplicantName string `json:"applicantName"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
InstanceCreatedEvent fired when a new instance is created.
func NewInstanceCreatedEvent ¶
func NewInstanceCreatedEvent(instanceID, tenantID, flowID, title, applicantID, applicantName string) *InstanceCreatedEvent
func (*InstanceCreatedEvent) EventType ¶ added in v0.24.0
func (*InstanceCreatedEvent) EventType() string
type InstanceLifecycleHook ¶ added in v0.24.0
type InstanceLifecycleHook interface {
// OnInstanceCreated runs after the instance row is persisted and the
// initial action log is written, but before the engine advances to
// the first node. Returning an error rolls back start_instance.
OnInstanceCreated(ctx context.Context, db orm.DB, instance *Instance) error
// OnInstanceCompleted runs after the engine applies the final state
// transition (within the same transaction). Returning an error rolls
// back the completion. For at-most-once / fire-and-forget side effects
// (webhooks, notifications), subscribe to InstanceCompletedEvent
// instead so the outbox can guarantee delivery.
OnInstanceCompleted(ctx context.Context, db orm.DB, instance *Instance, finalStatus InstanceStatus) error
}
InstanceLifecycleHook is a synchronous extension point for host applications that need to react to approval lifecycle moments inside the same transaction as the business change. Unlike event subscriptions — which are asynchronous, retryable through outbox, and fire after commit — hooks run *during* the transaction, so a hook that returns an error aborts the surrounding business operation.
Use hooks for invariants that must hold within the transaction (e.g. allocating a business row, writing a tightly-coupled record). Use event subscriptions for everything else (webhooks, notifications, analytics, async integrations).
Multiple implementations are aggregated via FX group `group:"vef:approval:lifecycle_hooks"` and invoked in registration order; any non-nil error stops further hooks and bubbles back to the caller.
type InstanceNoGenerator ¶
type InstanceNoGenerator interface {
// Generate creates a unique instance number for a flow identified by flowCode.
Generate(ctx context.Context, flowCode string) (string, error)
}
InstanceNoGenerator generates unique instance numbers for flow instances.
type InstanceResubmittedEvent ¶
type InstanceResubmittedEvent struct {
InstanceID string `json:"instanceId"`
TenantID string `json:"tenantId"`
OperatorID string `json:"operatorId"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
InstanceResubmittedEvent fired when the initiator resubmits a returned instance.
func NewInstanceResubmittedEvent ¶
func NewInstanceResubmittedEvent(instanceID, tenantID, operatorID string) *InstanceResubmittedEvent
func (*InstanceResubmittedEvent) EventType ¶ added in v0.24.0
func (*InstanceResubmittedEvent) EventType() string
type InstanceReturnedEvent ¶
type InstanceReturnedEvent struct {
InstanceID string `json:"instanceId"`
TenantID string `json:"tenantId"`
FromNodeID string `json:"fromNodeId"`
ToNodeID string `json:"toNodeId"`
OperatorID string `json:"operatorId"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
InstanceReturnedEvent fired when instance is returned to the initiator.
func NewInstanceReturnedEvent ¶
func NewInstanceReturnedEvent(instanceID, tenantID, fromNodeID, toNodeID, operatorID string) *InstanceReturnedEvent
func (*InstanceReturnedEvent) EventType ¶ added in v0.24.0
func (*InstanceReturnedEvent) EventType() string
type InstanceRolledBackEvent ¶
type InstanceRolledBackEvent struct {
InstanceID string `json:"instanceId"`
TenantID string `json:"tenantId"`
FromNodeID string `json:"fromNodeId"`
ToNodeID string `json:"toNodeId"`
OperatorID string `json:"operatorId"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
InstanceRolledBackEvent fired when instance is rolled back.
func NewInstanceRolledBackEvent ¶
func NewInstanceRolledBackEvent(instanceID, tenantID, fromNodeID, toNodeID, operatorID string) *InstanceRolledBackEvent
func (*InstanceRolledBackEvent) EventType ¶ added in v0.24.0
func (*InstanceRolledBackEvent) EventType() string
type InstanceStatus ¶
type InstanceStatus string
InstanceStatus represents the status of a flow instance.
const ( InstanceRunning InstanceStatus = "running" InstanceApproved InstanceStatus = "approved" InstanceRejected InstanceStatus = "rejected" InstanceWithdrawn InstanceStatus = "withdrawn" InstanceReturned InstanceStatus = "returned" InstanceTerminated InstanceStatus = "terminated" )
func (InstanceStatus) IsFinal ¶
func (s InstanceStatus) IsFinal() bool
func (InstanceStatus) String ¶
func (s InstanceStatus) String() string
type InstanceWithdrawnEvent ¶
type InstanceWithdrawnEvent struct {
InstanceID string `json:"instanceId"`
TenantID string `json:"tenantId"`
OperatorID string `json:"operatorId"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
InstanceWithdrawnEvent fired when applicant withdraws the instance.
func NewInstanceWithdrawnEvent ¶
func NewInstanceWithdrawnEvent(instanceID, tenantID, operatorID string) *InstanceWithdrawnEvent
func (*InstanceWithdrawnEvent) EventType ¶ added in v0.24.0
func (*InstanceWithdrawnEvent) EventType() string
type NodeAutoPassedEvent ¶
type NodeAutoPassedEvent struct {
InstanceID string `json:"instanceId"`
TenantID string `json:"tenantId"`
NodeID string `json:"nodeId"`
Reason string `json:"reason"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
NodeAutoPassedEvent fired when a node is auto-passed.
func NewNodeAutoPassedEvent ¶
func NewNodeAutoPassedEvent(instanceID, tenantID, nodeID, reason string) *NodeAutoPassedEvent
func (*NodeAutoPassedEvent) EventType ¶ added in v0.24.0
func (*NodeAutoPassedEvent) EventType() string
type NodeData ¶
type NodeData interface {
// Kind returns the node kind (start, end, approval, handle, cc, condition).
Kind() NodeKind
// GetName returns the display name of the node.
GetName() string
// GetDescription returns the optional description of the node.
GetDescription() *string
// ApplyTo applies this node data's configuration to the given FlowNode.
ApplyTo(node *FlowNode)
}
NodeData is the interface implemented by all node data types.
type NodeDefinition ¶
type NodeDefinition struct {
ID string `json:"id"`
Kind NodeKind `json:"kind"`
Position Position `json:"position"`
Data json.RawMessage `json:"data,omitempty"`
}
NodeDefinition represents a node in the flow definition.
func (*NodeDefinition) ParseData ¶
func (nd *NodeDefinition) ParseData() (NodeData, error)
ParseData parses Data into the appropriate typed struct based on Kind.
type NodeEnteredEvent ¶
type NodeEnteredEvent struct {
InstanceID string `json:"instanceId"`
TenantID string `json:"tenantId"`
NodeID string `json:"nodeId"`
NodeName string `json:"nodeName"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
NodeEnteredEvent fired when instance enters a new node.
func NewNodeEnteredEvent ¶
func NewNodeEnteredEvent(instanceID, tenantID, nodeID, nodeName string) *NodeEnteredEvent
func (*NodeEnteredEvent) EventType ¶ added in v0.24.0
func (*NodeEnteredEvent) EventType() string
type NodeKind ¶
type NodeKind string
NodeKind represents the kind of a flow node. It defines the different types of nodes that can exist in a workflow.
const ( NodeStart NodeKind = "start" // Start node: the entry point of a workflow NodeApproval NodeKind = "approval" // Approval node: requires approval action from assignees NodeHandle NodeKind = "handle" // Handle node: requires processing/handling action from assignees NodeCondition NodeKind = "condition" // Condition node: branches the flow based on conditions NodeEnd NodeKind = "end" // End node: the terminal point of a workflow NodeCC NodeKind = "cc" // CC node: sends notifications to specified users )
type OperatorInfo ¶
type OperatorInfo struct {
ID string `json:"id"`
Name string `json:"name"`
DepartmentID *string `json:"departmentId,omitempty"`
DepartmentName *string `json:"departmentName,omitempty"`
}
OperatorInfo bundles operator identity for action logging.
func (OperatorInfo) NewActionLog ¶
func (o OperatorInfo) NewActionLog(instanceID string, action ActionType) *ActionLog
NewActionLog creates an ActionLog with the operator fields pre-filled.
type PassRule ¶
type PassRule string
PassRule represents the strategy for passing the node (for Parallel/Or methods).
type PassRuleContext ¶
type PassRuleContext struct {
ApprovedCount int
RejectedCount int
TotalCount int
PassRatio float64
}
PassRuleContext provides context for pass rule evaluation.
type PassRuleResult ¶
type PassRuleResult int
PassRuleResult indicates the outcome of pass rule evaluation.
const ( PassRulePending PassRuleResult = iota // Still waiting for more actions PassRulePassed // Node passed PassRuleRejected // Node rejected )
type PassRuleStrategy ¶
type PassRuleStrategy interface {
// Rule returns the pass rule this strategy handles.
Rule() PassRule
// Evaluate determines the pass/reject/pending outcome based on task approval counts.
Evaluate(ctx PassRuleContext) PassRuleResult
}
PassRuleStrategy evaluates whether a node passes based on task results.
type Permission ¶
type Permission string
Permission represents the permission level.
const ( PermissionVisible Permission = "visible" PermissionEditable Permission = "editable" PermissionHidden Permission = "hidden" PermissionRequired Permission = "required" )
type PrincipalDepartmentResolver ¶ added in v0.20.1
type PrincipalDepartmentResolver interface {
// Resolve extracts the department ID and name from the given security principal.
Resolve(ctx context.Context, principal *security.Principal) (departmentID, departmentName *string, err error)
}
PrincipalDepartmentResolver resolves department info from a security principal. Implemented by host apps since Principal.Details is business-specific.
type PrincipalTenantResolver ¶ added in v0.24.0
type PrincipalTenantResolver interface {
// Resolve returns the tenant identifier for the given principal.
Resolve(ctx context.Context, principal *security.Principal) (string, error)
}
PrincipalTenantResolver extracts the caller's tenant ID from a security principal. Implemented by host applications because Principal.Details is schema-less; the framework cannot know where the host stores tenant affiliation.
Returning an empty string is valid (e.g. system principals, platform super-admins, or anonymous callers) — CallerContext.Authorize then falls back to its zero-value pass-through.
type ResolvedAssignee ¶
type ResolvedAssignee struct {
UserID string
UserName string
DelegatorID *string
DelegatorName *string
}
ResolvedAssignee represents a resolved assignee with optional delegation info.
type RollbackDataStrategy ¶
type RollbackDataStrategy string
RollbackDataStrategy represents the strategy for handling form data during rollback.
const ( RollbackDataClear RollbackDataStrategy = "clear" // Clear form data RollbackDataKeep RollbackDataStrategy = "keep" // Keep history data )
type RollbackType ¶
type RollbackType string
RollbackType represents the type of rollback allowed.
const ( RollbackNone RollbackType = "none" RollbackPrevious RollbackType = "previous" // To previous node RollbackStart RollbackType = "start" // To start node (applicant) RollbackAny RollbackType = "any" // To any node RollbackSpecified RollbackType = "specified" // To specified nodes )
type SameApplicantAction ¶
type SameApplicantAction string
SameApplicantAction represents the action when the assignee is the same as the applicant.
const ( SameApplicantAutoPass SameApplicantAction = "auto_pass" SameApplicantSelfApprove SameApplicantAction = "self_approve" // Default SameApplicantTransferSuperior SameApplicantAction = "transfer_superior" // Transfer to superior )
type StartNodeData ¶
type StartNodeData struct {
BaseNodeData
}
StartNodeData contains data specific to start nodes.
func (*StartNodeData) ApplyTo ¶
func (d *StartNodeData) ApplyTo(node *FlowNode)
ApplyTo applies start node data to a FlowNode.
type StorageMode ¶
type StorageMode string
StorageMode represents the storage mode of form data at the FlowVersion level. It determines the physical storage location and format of form data, and is fixed when a version is published. This is different from BindingMode (Flow-level), which controls how the workflow integrates with business systems.
Usage scenarios:
- JSON mode: Flexible schema, suitable for frequently changing form fields, limited query capabilities
- Table mode: Structured storage, suitable for complex queries and data analysis, requires predefined schema
const ( StorageJSON StorageMode = "json" // JSON: form data stored in apv_instance.form_data (JSONB column) StorageTable StorageMode = "table" // Table: form data stored in dynamically created tables (e.g., apv_form_data_{flow_code}) )
type Task ¶
type Task struct {
orm.BaseModel `bun:"table:apv_task,alias:at"`
orm.FullAuditedModel
TenantID string `json:"tenantId" bun:"tenant_id"`
InstanceID string `json:"instanceId" bun:"instance_id"`
NodeID string `json:"nodeId" bun:"node_id"`
AssigneeID string `json:"assigneeId" bun:"assignee_id"`
AssigneeName string `json:"assigneeName" bun:"assignee_name"`
DelegatorID *string `json:"delegatorId" bun:"delegator_id,nullzero"`
DelegatorName *string `json:"delegatorName" bun:"delegator_name,nullzero"`
SortOrder int `json:"sortOrder" bun:"sort_order"`
Status TaskStatus `json:"status" bun:"status"`
ReadAt *timex.DateTime `json:"readAt" bun:"read_at,nullzero"`
ParentTaskID *string `json:"parentTaskId" bun:"parent_task_id,nullzero"`
AddAssigneeType *AddAssigneeType `json:"addAssigneeType" bun:"add_assignee_type,nullzero"`
Deadline *timex.DateTime `json:"deadline" bun:"deadline,nullzero"`
IsTimeout bool `json:"isTimeout" bun:"is_timeout"`
IsPreWarningSent bool `json:"isPreWarningSent" bun:"is_pre_warning_sent"`
FinishedAt *timex.DateTime `json:"finishedAt" bun:"finished_at,nullzero"`
}
Task represents an approval task.
type TaskApprovedEvent ¶
type TaskApprovedEvent struct {
TaskID string `json:"taskId"`
TenantID string `json:"tenantId"`
InstanceID string `json:"instanceId"`
NodeID string `json:"nodeId"`
OperatorID string `json:"operatorId"`
Opinion *string `json:"opinion,omitempty"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
TaskApprovedEvent fired when a task is approved.
func NewTaskApprovedEvent ¶
func NewTaskApprovedEvent(taskID, tenantID, instanceID, nodeID, operatorID, opinion string) *TaskApprovedEvent
func (*TaskApprovedEvent) EventType ¶ added in v0.24.0
func (*TaskApprovedEvent) EventType() string
type TaskCreatedEvent ¶
type TaskCreatedEvent struct {
TaskID string `json:"taskId"`
TenantID string `json:"tenantId"`
InstanceID string `json:"instanceId"`
NodeID string `json:"nodeId"`
AssigneeID string `json:"assigneeId"`
AssigneeName string `json:"assigneeName"`
Deadline *timex.DateTime `json:"deadline,omitempty"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
TaskCreatedEvent fired when a new task is created.
func NewTaskCreatedEvent ¶
func NewTaskCreatedEvent(taskID, tenantID, instanceID, nodeID, assigneeID, assigneeName string, deadline *timex.DateTime) *TaskCreatedEvent
func (*TaskCreatedEvent) EventType ¶ added in v0.24.0
func (*TaskCreatedEvent) EventType() string
type TaskDeadlineWarningEvent ¶
type TaskDeadlineWarningEvent struct {
TaskID string `json:"taskId"`
TenantID string `json:"tenantId"`
InstanceID string `json:"instanceId"`
NodeID string `json:"nodeId"`
AssigneeID string `json:"assigneeId"`
AssigneeName string `json:"assigneeName"`
Deadline timex.DateTime `json:"deadline"`
HoursLeft int `json:"hoursLeft"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
TaskDeadlineWarningEvent fired when a task is approaching its deadline.
func NewTaskDeadlineWarningEvent ¶
func NewTaskDeadlineWarningEvent(taskID, tenantID, instanceID, nodeID, assigneeID, assigneeName string, deadline timex.DateTime, hoursLeft int) *TaskDeadlineWarningEvent
func (*TaskDeadlineWarningEvent) EventType ¶ added in v0.24.0
func (*TaskDeadlineWarningEvent) EventType() string
type TaskHandledEvent ¶
type TaskHandledEvent struct {
TaskID string `json:"taskId"`
TenantID string `json:"tenantId"`
InstanceID string `json:"instanceId"`
NodeID string `json:"nodeId"`
OperatorID string `json:"operatorId"`
Opinion *string `json:"opinion,omitempty"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
TaskHandledEvent fired when a handle-type task is completed.
func NewTaskHandledEvent ¶
func NewTaskHandledEvent(taskID, tenantID, instanceID, nodeID, operatorID, opinion string) *TaskHandledEvent
func (*TaskHandledEvent) EventType ¶ added in v0.24.0
func (*TaskHandledEvent) EventType() string
type TaskNodeData ¶
type TaskNodeData struct {
Assignees []AssigneeDefinition `json:"assignees,omitempty"`
ExecutionType ExecutionType `json:"executionType,omitempty"`
EmptyAssigneeAction EmptyAssigneeAction `json:"emptyAssigneeAction,omitempty"`
FallbackUserIDs []string `json:"fallbackUserIds,omitempty"`
AdminUserIDs []string `json:"adminUserIds,omitempty"`
IsTransferAllowed bool `json:"isTransferAllowed,omitempty"`
IsOpinionRequired bool `json:"isOpinionRequired,omitempty"`
TimeoutHours int `json:"timeoutHours,omitempty"`
TimeoutAction TimeoutAction `json:"timeoutAction,omitempty"`
TimeoutNotifyBeforeHours int `json:"timeoutNotifyBeforeHours,omitempty"`
UrgeCooldownMinutes int `json:"urgeCooldownMinutes,omitempty"`
CCs []CCDefinition `json:"ccs,omitempty"`
FieldPermissions map[string]Permission `json:"fieldPermissions,omitempty"`
}
TaskNodeData contains fields shared by approval and handle nodes.
func (*TaskNodeData) GetAssignees ¶
func (d *TaskNodeData) GetAssignees() []AssigneeDefinition
GetAssignees returns the assignee definitions from TaskNodeData.
func (*TaskNodeData) GetCCs ¶
func (d *TaskNodeData) GetCCs() []CCDefinition
GetCCs returns the CC definitions from TaskNodeData.
type TaskReassignedEvent ¶
type TaskReassignedEvent struct {
TaskID string `json:"taskId"`
TenantID string `json:"tenantId"`
InstanceID string `json:"instanceId"`
NodeID string `json:"nodeId"`
FromUserID string `json:"fromUserId"`
FromUserName string `json:"fromUserName"`
ToUserID string `json:"toUserId"`
ToUserName string `json:"toUserName"`
Reason *string `json:"reason,omitempty"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
TaskReassignedEvent fired when an admin reassigns a task to a different user.
func NewTaskReassignedEvent ¶
func NewTaskReassignedEvent(taskID, tenantID, instanceID, nodeID, fromUserID, fromUserName, toUserID, toUserName, reason string) *TaskReassignedEvent
func (*TaskReassignedEvent) EventType ¶ added in v0.24.0
func (*TaskReassignedEvent) EventType() string
type TaskRejectedEvent ¶
type TaskRejectedEvent struct {
TaskID string `json:"taskId"`
TenantID string `json:"tenantId"`
InstanceID string `json:"instanceId"`
NodeID string `json:"nodeId"`
OperatorID string `json:"operatorId"`
Opinion *string `json:"opinion,omitempty"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
TaskRejectedEvent fired when a task is rejected.
func NewTaskRejectedEvent ¶
func NewTaskRejectedEvent(taskID, tenantID, instanceID, nodeID, operatorID, opinion string) *TaskRejectedEvent
func (*TaskRejectedEvent) EventType ¶ added in v0.24.0
func (*TaskRejectedEvent) EventType() string
type TaskStatus ¶
type TaskStatus string
TaskStatus represents the status of an approval task.
const ( TaskWaiting TaskStatus = "waiting" TaskPending TaskStatus = "pending" TaskApproved TaskStatus = "approved" TaskRejected TaskStatus = "rejected" TaskHandled TaskStatus = "handled" TaskTransferred TaskStatus = "transferred" TaskRolledBack TaskStatus = "rolled_back" TaskCanceled TaskStatus = "canceled" TaskRemoved TaskStatus = "removed" TaskSkipped TaskStatus = "skipped" )
func (TaskStatus) IsFinal ¶
func (s TaskStatus) IsFinal() bool
func (TaskStatus) String ¶
func (s TaskStatus) String() string
type TaskTimedOutEvent ¶ added in v0.24.0
type TaskTimedOutEvent struct {
TaskID string `json:"taskId"`
TenantID string `json:"tenantId"`
InstanceID string `json:"instanceId"`
NodeID string `json:"nodeId"`
AssigneeID string `json:"assigneeId"`
AssigneeName string `json:"assigneeName"`
Deadline timex.DateTime `json:"deadline"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
TaskTimedOutEvent fired when a task times out.
func NewTaskTimedOutEvent ¶ added in v0.24.0
func NewTaskTimedOutEvent(taskID, tenantID, instanceID, nodeID, assigneeID, assigneeName string, deadline timex.DateTime) *TaskTimedOutEvent
func (*TaskTimedOutEvent) EventType ¶ added in v0.24.0
func (*TaskTimedOutEvent) EventType() string
type TaskTransferredEvent ¶
type TaskTransferredEvent struct {
TaskID string `json:"taskId"`
TenantID string `json:"tenantId"`
InstanceID string `json:"instanceId"`
NodeID string `json:"nodeId"`
FromUserID string `json:"fromUserId"`
FromUserName string `json:"fromUserName"`
ToUserID string `json:"toUserId"`
ToUserName string `json:"toUserName"`
Reason *string `json:"reason,omitempty"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
TaskTransferredEvent fired when a task is transferred.
func NewTaskTransferredEvent ¶
func NewTaskTransferredEvent(taskID, tenantID, instanceID, nodeID, fromUserID, fromUserName, toUserID, toUserName, reason string) *TaskTransferredEvent
func (*TaskTransferredEvent) EventType ¶ added in v0.24.0
func (*TaskTransferredEvent) EventType() string
type TaskUrgedEvent ¶
type TaskUrgedEvent struct {
InstanceID string `json:"instanceId"`
TenantID string `json:"tenantId"`
NodeID string `json:"nodeId"`
TaskID string `json:"taskId"`
UrgerID string `json:"urgerId"`
UrgerName string `json:"urgerName"`
TargetUserID string `json:"targetUserId"`
TargetUserName string `json:"targetUserName"`
Message *string `json:"message,omitempty"`
OccurredTime timex.DateTime `json:"occurredTime"`
}
TaskUrgedEvent fired when a task assignee is urged/reminded.
func NewTaskUrgedEvent ¶
func NewTaskUrgedEvent(instanceID, tenantID, nodeID, taskID, urgerID, urgerName, targetUserID, targetUserName, message string) *TaskUrgedEvent
func (*TaskUrgedEvent) EventType ¶ added in v0.24.0
func (*TaskUrgedEvent) EventType() string
type TimeoutAction ¶
type TimeoutAction string
TimeoutAction represents the action to take when a task times out.
const ( TimeoutActionNone TimeoutAction = "none" // Mark timeout only, no auto action TimeoutActionAutoPass TimeoutAction = "auto_pass" // Automatically approve the task TimeoutActionAutoReject TimeoutAction = "auto_reject" // Automatically reject the task TimeoutActionNotify TimeoutAction = "notify" // Send notification only TimeoutActionTransferAdmin TimeoutAction = "transfer_admin" // Transfer to node admin )
type UrgeRecord ¶
type UrgeRecord struct {
orm.BaseModel `bun:"table:apv_urge_record,alias:aur"`
orm.Model
orm.CreationTrackedModel
InstanceID string `json:"instanceId" bun:"instance_id"`
NodeID string `json:"nodeId" bun:"node_id"`
TaskID *string `json:"taskId" bun:"task_id,nullzero"`
UrgerID string `json:"urgerId" bun:"urger_id"`
UrgerName string `json:"urgerName" bun:"urger_name"`
TargetUserID string `json:"targetUserId" bun:"target_user_id"`
TargetUserName string `json:"targetUserName" bun:"target_user_name"`
Message string `json:"message" bun:"message"`
}
UrgeRecord represents an urge/reminder record.
type UserInfoResolver ¶
type UserInfoResolver interface {
// ResolveUsers returns user info for the given IDs.
// Missing IDs should be returned with empty Name (not omitted).
ResolveUsers(ctx context.Context, userIDs []string) (map[string]UserInfo, error)
}
UserInfoResolver resolves user display names by IDs (implemented by host app).
type ValidationRule ¶
type ValidationRule struct {
MinLength *int `json:"minLength,omitempty"`
MaxLength *int `json:"maxLength,omitempty"`
Min *float64 `json:"min,omitempty"`
Max *float64 `json:"max,omitempty"`
Pattern string `json:"pattern,omitempty"`
Message string `json:"message,omitempty"`
}
ValidationRule contains validation constraints for a form field.
type VersionStatus ¶
type VersionStatus string
VersionStatus represents the status of a flow version.
const ( VersionDraft VersionStatus = "draft" VersionPublished VersionStatus = "published" VersionArchived VersionStatus = "archived" )