package service import ( "context" "eslogad-be/internal/contract" "eslogad-be/internal/entities" "eslogad-be/internal/repository" "github.com/google/uuid" "gorm.io/gorm" ) type ApprovalFlowService interface { CreateApprovalFlow(ctx context.Context, req *contract.ApprovalFlowRequest) (*contract.ApprovalFlowResponse, error) GetApprovalFlow(ctx context.Context, id uuid.UUID) (*contract.ApprovalFlowResponse, error) GetApprovalFlowByDepartment(ctx context.Context, departmentID uuid.UUID) (*contract.ApprovalFlowResponse, error) UpdateApprovalFlow(ctx context.Context, id uuid.UUID, req *contract.ApprovalFlowRequest) (*contract.ApprovalFlowResponse, error) DeleteApprovalFlow(ctx context.Context, id uuid.UUID) error ListApprovalFlows(ctx context.Context, req *contract.ListApprovalFlowsRequest) (*contract.ListApprovalFlowsResponse, error) } type ApprovalFlowServiceImpl struct { db *gorm.DB flowRepo *repository.ApprovalFlowRepository stepRepo *repository.ApprovalFlowStepRepository txManager *repository.TxManager } func NewApprovalFlowService( db *gorm.DB, flowRepo *repository.ApprovalFlowRepository, stepRepo *repository.ApprovalFlowStepRepository, txManager *repository.TxManager, ) *ApprovalFlowServiceImpl { return &ApprovalFlowServiceImpl{ db: db, flowRepo: flowRepo, stepRepo: stepRepo, txManager: txManager, } } func (s *ApprovalFlowServiceImpl) CreateApprovalFlow(ctx context.Context, req *contract.ApprovalFlowRequest) (*contract.ApprovalFlowResponse, error) { flow := &entities.ApprovalFlow{ DepartmentID: req.DepartmentID, Name: req.Name, Description: req.Description, IsActive: req.IsActive, } err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { if err := s.flowRepo.Create(txCtx, flow); err != nil { return err } if len(req.Steps) > 0 { steps := make([]entities.ApprovalFlowStep, len(req.Steps)) for i, stepReq := range req.Steps { if stepReq.ApproverRoleID == nil && stepReq.ApproverUserID == nil { return gorm.ErrInvalidData } steps[i] = entities.ApprovalFlowStep{ FlowID: flow.ID, StepOrder: stepReq.StepOrder, ParallelGroup: stepReq.ParallelGroup, ApproverRoleID: stepReq.ApproverRoleID, ApproverUserID: stepReq.ApproverUserID, Required: stepReq.Required, } } if err := s.stepRepo.CreateBulk(txCtx, steps); err != nil { return err } } return nil }) if err != nil { return nil, err } result, err := s.flowRepo.Get(ctx, flow.ID) if err != nil { return nil, err } return transformApprovalFlowToResponse(result), nil } func (s *ApprovalFlowServiceImpl) GetApprovalFlow(ctx context.Context, id uuid.UUID) (*contract.ApprovalFlowResponse, error) { flow, err := s.flowRepo.Get(ctx, id) if err != nil { return nil, err } return transformApprovalFlowToResponse(flow), nil } func (s *ApprovalFlowServiceImpl) GetApprovalFlowByDepartment(ctx context.Context, departmentID uuid.UUID) (*contract.ApprovalFlowResponse, error) { flow, err := s.flowRepo.GetByDepartment(ctx, departmentID) if err != nil { return nil, err } return transformApprovalFlowToResponse(flow), nil } func (s *ApprovalFlowServiceImpl) UpdateApprovalFlow(ctx context.Context, id uuid.UUID, req *contract.ApprovalFlowRequest) (*contract.ApprovalFlowResponse, error) { flow, err := s.flowRepo.Get(ctx, id) if err != nil { return nil, err } flow.DepartmentID = req.DepartmentID flow.Name = req.Name flow.Description = req.Description flow.IsActive = req.IsActive err = s.txManager.WithTransaction(ctx, func(txCtx context.Context) error { if err := s.flowRepo.Update(txCtx, flow); err != nil { return err } if err := s.stepRepo.DeleteByFlow(txCtx, id); err != nil { return err } if len(req.Steps) > 0 { steps := make([]entities.ApprovalFlowStep, len(req.Steps)) for i, stepReq := range req.Steps { if stepReq.ApproverRoleID == nil && stepReq.ApproverUserID == nil { return gorm.ErrInvalidData } steps[i] = entities.ApprovalFlowStep{ FlowID: flow.ID, StepOrder: stepReq.StepOrder, ParallelGroup: stepReq.ParallelGroup, ApproverRoleID: stepReq.ApproverRoleID, ApproverUserID: stepReq.ApproverUserID, Required: stepReq.Required, } } if err := s.stepRepo.CreateBulk(txCtx, steps); err != nil { return err } } return nil }) if err != nil { return nil, err } result, err := s.flowRepo.Get(ctx, id) if err != nil { return nil, err } return transformApprovalFlowToResponse(result), nil } func (s *ApprovalFlowServiceImpl) DeleteApprovalFlow(ctx context.Context, id uuid.UUID) error { return s.flowRepo.Delete(ctx, id) } func (s *ApprovalFlowServiceImpl) ListApprovalFlows(ctx context.Context, req *contract.ListApprovalFlowsRequest) (*contract.ListApprovalFlowsResponse, error) { filter := repository.ListApprovalFlowsFilter{ DepartmentID: req.DepartmentID, IsActive: req.IsActive, } flows, total, err := s.flowRepo.List(ctx, filter, req.Limit, req.Offset) if err != nil { return nil, err } items := make([]*contract.ApprovalFlowResponse, len(flows)) for i, flow := range flows { items[i] = transformApprovalFlowToResponse(&flow) } return &contract.ListApprovalFlowsResponse{ Items: items, Total: total, }, nil } func transformApprovalFlowToResponse(flow *entities.ApprovalFlow) *contract.ApprovalFlowResponse { resp := &contract.ApprovalFlowResponse{ ID: flow.ID, DepartmentID: flow.DepartmentID, Name: flow.Name, Description: flow.Description, IsActive: flow.IsActive, CreatedAt: flow.CreatedAt, UpdatedAt: flow.UpdatedAt, } if flow.Department != nil { resp.Department = &contract.DepartmentResponse{ ID: flow.Department.ID, Name: flow.Department.Name, } } if len(flow.Steps) > 0 { resp.Steps = make([]contract.ApprovalFlowStepResponse, len(flow.Steps)) for i, step := range flow.Steps { stepResp := contract.ApprovalFlowStepResponse{ ID: step.ID, StepOrder: step.StepOrder, ParallelGroup: step.ParallelGroup, ApproverRoleID: step.ApproverRoleID, ApproverUserID: step.ApproverUserID, Required: step.Required, CreatedAt: step.CreatedAt, UpdatedAt: step.UpdatedAt, } if step.ApproverRole != nil { stepResp.ApproverRole = &contract.RoleResponse{ ID: step.ApproverRole.ID, Name: step.ApproverRole.Name, } } if step.ApproverUser != nil { stepResp.ApproverUser = &contract.UserResponse{ ID: step.ApproverUser.ID, Name: step.ApproverUser.Name, Email: step.ApproverUser.Email, } } resp.Steps[i] = stepResp } } return resp }