mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat(BE): approval_workflow, adjusment project_flocks, common, and migration
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
package approvals
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
)
|
||||
|
||||
type ApprovalStep uint16
|
||||
|
||||
type ApprovalWorkflowKey string
|
||||
|
||||
func (k ApprovalWorkflowKey) String() string {
|
||||
return string(k)
|
||||
}
|
||||
|
||||
type NextStepCallback func(current ApprovalStep, decision entity.ApprovalAction) (ApprovalStep, bool)
|
||||
|
||||
var (
|
||||
approvalActions = map[entity.ApprovalAction]struct{}{
|
||||
entity.ApprovalActionApproved: {},
|
||||
entity.ApprovalActionRejected: {},
|
||||
entity.ApprovalActionCreated: {},
|
||||
entity.ApprovalActionUpdated: {},
|
||||
}
|
||||
|
||||
approvalWorkflows = make(map[ApprovalWorkflowKey]map[ApprovalStep]string)
|
||||
approvalWorkflowsMu sync.RWMutex
|
||||
)
|
||||
|
||||
// WorkflowConstants prepares the registered workflows for exposure via constants endpoints.
|
||||
func WorkflowConstants() map[string]map[string]string {
|
||||
approvalWorkflowsMu.RLock()
|
||||
defer approvalWorkflowsMu.RUnlock()
|
||||
|
||||
if len(approvalWorkflows) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make(map[string]map[string]string, len(approvalWorkflows))
|
||||
for workflow, steps := range approvalWorkflows {
|
||||
if len(steps) == 0 {
|
||||
continue
|
||||
}
|
||||
stepMap := make(map[string]string, len(steps))
|
||||
for step, label := range steps {
|
||||
stepMap[fmt.Sprintf("%d", step)] = label
|
||||
}
|
||||
result[workflow.String()] = stepMap
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// RegisterWorkflowSteps stores the available steps for a workflow key (usually matching approvable type).
|
||||
func RegisterWorkflowSteps(workflow ApprovalWorkflowKey, steps map[ApprovalStep]string) error {
|
||||
workflowStr := strings.TrimSpace(workflow.String())
|
||||
if workflowStr == "" {
|
||||
return errors.New("workflow key is required")
|
||||
}
|
||||
if len(steps) == 0 {
|
||||
return fmt.Errorf("no steps defined for workflow %q", workflowStr)
|
||||
}
|
||||
|
||||
copied := make(map[ApprovalStep]string, len(steps))
|
||||
for step, label := range steps {
|
||||
if step == 0 {
|
||||
return fmt.Errorf("workflow %q contains step 0 which is not allowed", workflowStr)
|
||||
}
|
||||
trimmed := strings.TrimSpace(label)
|
||||
if trimmed == "" {
|
||||
return fmt.Errorf("workflow %q contains empty label for step %d", workflowStr, step)
|
||||
}
|
||||
copied[step] = trimmed
|
||||
}
|
||||
|
||||
approvalWorkflowsMu.Lock()
|
||||
defer approvalWorkflowsMu.Unlock()
|
||||
approvalWorkflows[ApprovalWorkflowKey(workflowStr)] = copied
|
||||
return nil
|
||||
}
|
||||
|
||||
// WorkflowSteps returns the steps registered for the given workflow key.
|
||||
func WorkflowSteps(workflow ApprovalWorkflowKey) map[ApprovalStep]string {
|
||||
approvalWorkflowsMu.RLock()
|
||||
defer approvalWorkflowsMu.RUnlock()
|
||||
|
||||
workflowStr := strings.TrimSpace(workflow.String())
|
||||
if workflowStr == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
steps, ok := approvalWorkflows[ApprovalWorkflowKey(workflowStr)]
|
||||
if !ok || len(steps) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
copied := make(map[ApprovalStep]string, len(steps))
|
||||
for step, label := range steps {
|
||||
copied[step] = label
|
||||
}
|
||||
return copied
|
||||
}
|
||||
|
||||
// ApprovalStepName fetches the label for the target step inside the workflow.
|
||||
func ApprovalStepName(workflow ApprovalWorkflowKey, step ApprovalStep) (string, bool) {
|
||||
steps := WorkflowSteps(workflow)
|
||||
if len(steps) == 0 {
|
||||
return "", false
|
||||
}
|
||||
label, ok := steps[step]
|
||||
return label, ok
|
||||
}
|
||||
|
||||
// ValidateApprovalStep ensures the workflow contains the provided step.
|
||||
func ValidateApprovalStep(workflow ApprovalWorkflowKey, step ApprovalStep) error {
|
||||
if _, ok := ApprovalStepName(workflow, step); ok {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("invalid approval step %d for workflow %s", step, workflow)
|
||||
}
|
||||
|
||||
// IsValidApprovalAction reports whether the action is supported.
|
||||
func IsValidApprovalAction(action entity.ApprovalAction) bool {
|
||||
_, ok := approvalActions[action]
|
||||
return ok
|
||||
}
|
||||
|
||||
// NewApproval creates an approval record for the given approvable target.
|
||||
func NewApproval(workflow ApprovalWorkflowKey, approvableId uint, step ApprovalStep, action *entity.ApprovalAction, actorId uint, note *string) (*entity.Approval, error) {
|
||||
if approvableId == 0 {
|
||||
return nil, errors.New("approvable id is required")
|
||||
}
|
||||
|
||||
workflowStr := strings.TrimSpace(workflow.String())
|
||||
if workflowStr == "" {
|
||||
return nil, errors.New("approval workflow key is required")
|
||||
}
|
||||
|
||||
key := ApprovalWorkflowKey(workflowStr)
|
||||
|
||||
if err := ValidateApprovalStep(key, step); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var actionPtr *entity.ApprovalAction
|
||||
if action != nil {
|
||||
if !IsValidApprovalAction(*action) {
|
||||
return nil, fmt.Errorf("invalid approval action %q", *action)
|
||||
}
|
||||
actionCopy := *action
|
||||
actionPtr = &actionCopy
|
||||
}
|
||||
|
||||
if actorId == 0 {
|
||||
return nil, errors.New("actor id is required")
|
||||
}
|
||||
|
||||
var notes *string
|
||||
if note != nil {
|
||||
trimmed := strings.TrimSpace(*note)
|
||||
if trimmed != "" {
|
||||
notes = &trimmed
|
||||
}
|
||||
}
|
||||
|
||||
actor := actorId
|
||||
var stepName string
|
||||
if label, ok := ApprovalStepName(key, step); ok {
|
||||
labelCopy := label
|
||||
stepName = labelCopy
|
||||
}
|
||||
|
||||
return &entity.Approval{
|
||||
ApprovableType: workflowStr,
|
||||
ApprovableId: approvableId,
|
||||
StepNumber: uint16(step),
|
||||
StepName: stepName,
|
||||
Action: actionPtr,
|
||||
Notes: notes,
|
||||
ActionBy: &actor,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetApprovalAction updates the approval action, notes, and optionally advances to another step.
|
||||
func SetApprovalAction(approval *entity.Approval, action entity.ApprovalAction, actorId uint, note *string, nextStep NextStepCallback) error {
|
||||
if approval == nil {
|
||||
return errors.New("approval is nil")
|
||||
}
|
||||
if !IsValidApprovalAction(action) {
|
||||
return fmt.Errorf("invalid approval action %q", action)
|
||||
}
|
||||
if actorId == 0 {
|
||||
return errors.New("actor id is required for approval decision")
|
||||
}
|
||||
|
||||
act := action
|
||||
approval.Action = &act
|
||||
approval.ActionBy = &actorId
|
||||
|
||||
if note != nil {
|
||||
trimmed := strings.TrimSpace(*note)
|
||||
if trimmed == "" {
|
||||
approval.Notes = nil
|
||||
} else {
|
||||
approval.Notes = &trimmed
|
||||
}
|
||||
} else {
|
||||
approval.Notes = nil
|
||||
}
|
||||
|
||||
if nextStep != nil {
|
||||
current := ApprovalStep(approval.StepNumber)
|
||||
if proposed, ok := nextStep(current, action); ok {
|
||||
if err := ValidateApprovalStep(ApprovalWorkflowKey(approval.ApprovableType), proposed); err != nil {
|
||||
return err
|
||||
}
|
||||
approval.StepNumber = uint16(proposed)
|
||||
}
|
||||
}
|
||||
|
||||
if label, ok := ApprovalStepName(ApprovalWorkflowKey(approval.ApprovableType), ApprovalStep(approval.StepNumber)); ok {
|
||||
labelCopy := label
|
||||
approval.StepName = labelCopy
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Approve marks the approval as approved by the given actor, applying the optional step callback.
|
||||
func Approve(approval *entity.Approval, actorId uint, note *string, nextStep NextStepCallback) error {
|
||||
return SetApprovalAction(approval, entity.ApprovalActionApproved, actorId, note, nextStep)
|
||||
}
|
||||
|
||||
// Reject marks the approval as rejected by the given actor, applying the optional step callback.
|
||||
func Reject(approval *entity.Approval, actorId uint, note *string, nextStep NextStepCallback) error {
|
||||
return SetApprovalAction(approval, entity.ApprovalActionRejected, actorId, note, nextStep)
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
package utils
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// FlagType & Groups
|
||||
@@ -120,6 +124,22 @@ const (
|
||||
ProjectFlockCategoryLaying ProjectFlockCategory = "LAYING"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Project Flock Approval
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const (
|
||||
ApprovalWorkflowProjectFlock approvalutils.ApprovalWorkflowKey = approvalutils.ApprovalWorkflowKey("PROJECT_FLOCKS")
|
||||
ProjectFlockStepPengajuan approvalutils.ApprovalStep = 1
|
||||
ProjectFlockStepAktif approvalutils.ApprovalStep = 2
|
||||
)
|
||||
|
||||
// projectFlockApprovalSteps keeps the workflow step definitions for project flock approvals.
|
||||
var ProjectFlockApprovalSteps = map[approvalutils.ApprovalStep]string{
|
||||
ProjectFlockStepPengajuan: "Pengajuan",
|
||||
ProjectFlockStepAktif: "Aktif",
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Validators
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user