package service import ( "context" "sort" "eslogad-be/internal/contract" "eslogad-be/internal/entities" "eslogad-be/internal/repository" "eslogad-be/internal/transformer" "github.com/google/uuid" ) type DispositionRouteServiceImpl struct { repo *repository.DispositionRouteRepository } func NewDispositionRouteService(repo *repository.DispositionRouteRepository) *DispositionRouteServiceImpl { return &DispositionRouteServiceImpl{repo: repo} } // CreateOrUpdate handles bulk create or update of disposition routes func (s *DispositionRouteServiceImpl) CreateOrUpdate(ctx context.Context, req *contract.CreateDispositionRouteRequest) (*contract.BulkCreateDispositionRouteResponse, error) { // Set default values isActive := true if req.IsActive != nil { isActive = *req.IsActive } var allowedActions entities.JSONB if req.AllowedActions != nil { allowedActions = entities.JSONB(*req.AllowedActions) } // Perform bulk upsert created, updated, err := s.repo.BulkUpsert(ctx, req.FromDepartmentID, req.ToDepartmentIDs, isActive, allowedActions) if err != nil { return nil, err } // Fetch all routes for the from_department_id to return routes, err := s.repo.ListByFromDept(ctx, req.FromDepartmentID) if err != nil { return nil, err } // Transform to response routeResponses := transformer.DispositionRoutesToContract(routes) return &contract.BulkCreateDispositionRouteResponse{ Created: created, Updated: updated, Routes: routeResponses, }, nil } // Create maintains backward compatibility for single route creation func (s *DispositionRouteServiceImpl) Create(ctx context.Context, req *contract.CreateDispositionRouteRequest) (*contract.DispositionRouteResponse, error) { // If only one to_department_id is provided, create a single route if len(req.ToDepartmentIDs) == 1 { entity := &entities.DispositionRoute{ FromDepartmentID: req.FromDepartmentID, ToDepartmentID: req.ToDepartmentIDs[0], } if req.IsActive != nil { entity.IsActive = *req.IsActive } else { entity.IsActive = true } if req.AllowedActions != nil { entity.AllowedActions = entities.JSONB(*req.AllowedActions) } // Use upsert to handle create or update if err := s.repo.Upsert(ctx, entity); err != nil { return nil, err } // Fetch the created/updated route route, err := s.repo.Get(ctx, entity.ID) if err != nil { // If we can't get by ID (new creation), try to get by from/to combination routes, err := s.repo.ListByFromDept(ctx, req.FromDepartmentID) if err != nil { return nil, err } for _, r := range routes { if r.ToDepartmentID == req.ToDepartmentIDs[0] { resp := transformer.DispositionRoutesToContract([]entities.DispositionRoute{r})[0] return &resp, nil } } } resp := transformer.DispositionRoutesToContract([]entities.DispositionRoute{*route})[0] return &resp, nil } // For multiple to_department_ids, use bulk create/update bulkResp, err := s.CreateOrUpdate(ctx, req) if err != nil { return nil, err } // Return the first route as response for backward compatibility if len(bulkResp.Routes) > 0 { return &bulkResp.Routes[0], nil } return nil, nil } func (s *DispositionRouteServiceImpl) Update(ctx context.Context, id uuid.UUID, req *contract.UpdateDispositionRouteRequest) (*contract.DispositionRouteResponse, error) { entity, err := s.repo.Get(ctx, id) if err != nil { return nil, err } if req.IsActive != nil { entity.IsActive = *req.IsActive } if req.AllowedActions != nil { entity.AllowedActions = entities.JSONB(*req.AllowedActions) } if err := s.repo.Update(ctx, entity); err != nil { return nil, err } resp := transformer.DispositionRoutesToContract([]entities.DispositionRoute{*entity})[0] return &resp, nil } func (s *DispositionRouteServiceImpl) Get(ctx context.Context, id uuid.UUID) (*contract.DispositionRouteResponse, error) { entity, err := s.repo.Get(ctx, id) if err != nil { return nil, err } resp := transformer.DispositionRoutesToContract([]entities.DispositionRoute{*entity})[0] return &resp, nil } func (s *DispositionRouteServiceImpl) ListByFromDept(ctx context.Context, from uuid.UUID) (*contract.ListDispositionRoutesResponse, error) { list, err := s.repo.ListByFromDept(ctx, from) if err != nil { return nil, err } return &contract.ListDispositionRoutesResponse{Routes: transformer.DispositionRoutesToContract(list)}, nil } func (s *DispositionRouteServiceImpl) SetActive(ctx context.Context, id uuid.UUID, active bool) error { return s.repo.SetActive(ctx, id, active) } // ListGrouped returns all disposition routes grouped by from_department_id with clean department structure func (s *DispositionRouteServiceImpl) ListGrouped(ctx context.Context) (*contract.ListDispositionRoutesGroupedResponse, error) { // Get routes with department details routes, err := s.repo.ListAllGroupedWithDepartments(ctx) if err != nil { return nil, err } // Group routes by from_department_id and collect department info type groupedData struct { fromDept contract.DepartmentMapping toDepts []contract.DepartmentMapping toDeptMap map[uuid.UUID]bool // To avoid duplicates } grouped := make(map[uuid.UUID]*groupedData) for _, route := range routes { if _, exists := grouped[route.FromDepartmentID]; !exists { grouped[route.FromDepartmentID] = &groupedData{ fromDept: contract.DepartmentMapping{ ID: route.FromDepartmentID, Name: route.FromDepartment.Name, }, toDepts: []contract.DepartmentMapping{}, toDeptMap: make(map[uuid.UUID]bool), } } // Add to_department if not already added (avoid duplicates) if !grouped[route.FromDepartmentID].toDeptMap[route.ToDepartmentID] { grouped[route.FromDepartmentID].toDepts = append( grouped[route.FromDepartmentID].toDepts, contract.DepartmentMapping{ ID: route.ToDepartmentID, Name: route.ToDepartment.Name, }, ) grouped[route.FromDepartmentID].toDeptMap[route.ToDepartmentID] = true } } // Convert to response format var dispositions []contract.DispositionRouteGroupedItem for _, data := range grouped { dispositions = append(dispositions, contract.DispositionRouteGroupedItem{ FromDepartment: data.fromDept, ToDepartments: data.toDepts, }) } // Sort by from department name for consistent ordering sort.Slice(dispositions, func(i, j int) bool { return dispositions[i].FromDepartment.Name < dispositions[j].FromDepartment.Name }) return &contract.ListDispositionRoutesGroupedResponse{ Dispositions: dispositions, }, nil } // ListAll returns all disposition routes with department details func (s *DispositionRouteServiceImpl) ListAll(ctx context.Context) (*contract.ListDispositionRoutesDetailedResponse, error) { routes, err := s.repo.ListAll(ctx) if err != nil { return nil, err } routeResponses := transformer.DispositionRoutesToContract(routes) return &contract.ListDispositionRoutesDetailedResponse{ Routes: routeResponses, Total: len(routeResponses), }, nil }