package service import ( "bugulma/backend/internal/domain" "context" "encoding/json" "time" "github.com/google/uuid" "gorm.io/datatypes" ) // Context keys for extracting user/org info from context type contextKey string const ( userIDKey contextKey = "user_id" orgIDKey contextKey = "org_id" ) // getUserFromContext extracts user ID from context func getUserFromContext(ctx context.Context) string { if userID, ok := ctx.Value(userIDKey).(string); ok { return userID } return "system" // Default fallback } // getOrgFromContext extracts organization ID from context func getOrgFromContext(ctx context.Context) string { if orgID, ok := ctx.Value(orgIDKey).(string); ok { return orgID } return "" // Will be determined from entity } // ResourceFlowService handles business logic for resource flows type ResourceFlowService struct { repo domain.ResourceFlowRepository eventBus domain.EventBus } // NewResourceFlowService creates a new resource flow service func NewResourceFlowService(repo domain.ResourceFlowRepository, eventBus domain.EventBus) *ResourceFlowService { return &ResourceFlowService{ repo: repo, eventBus: eventBus, } } // CreateResourceFlowRequest represents the request to create a resource flow type CreateResourceFlowRequest struct { OrganizationID string SiteID string Direction domain.ResourceDirection Type domain.ResourceType Quality domain.Quality Quantity domain.Quantity TimeProfile domain.TimeProfile EconomicData domain.EconomicData Constraints domain.Constraints ServiceDetails domain.ServiceDetails PrecisionLevel domain.PrecisionLevel SourceType domain.SourceType } // Create creates a new resource flow func (s *ResourceFlowService) Create(ctx context.Context, req CreateResourceFlowRequest) (*domain.ResourceFlow, error) { qualityJSON, _ := json.Marshal(req.Quality) quantityJSON, _ := json.Marshal(req.Quantity) timeProfileJSON, _ := json.Marshal(req.TimeProfile) economicDataJSON, _ := json.Marshal(req.EconomicData) constraintsJSON, _ := json.Marshal(req.Constraints) serviceDetailsJSON, _ := json.Marshal(req.ServiceDetails) rf := &domain.ResourceFlow{ ID: uuid.New().String(), OrganizationID: req.OrganizationID, SiteID: req.SiteID, Direction: req.Direction, Type: req.Type, Quality: datatypes.JSON(qualityJSON), Quantity: datatypes.JSON(quantityJSON), TimeProfile: datatypes.JSON(timeProfileJSON), EconomicData: datatypes.JSON(economicDataJSON), Constraints: datatypes.JSON(constraintsJSON), ServiceDetails: datatypes.JSON(serviceDetailsJSON), PrecisionLevel: req.PrecisionLevel, SourceType: req.SourceType, CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := s.repo.Create(ctx, rf); err != nil { return nil, err } // Publish event userID := getUserFromContext(ctx) if s.eventBus != nil { event := domain.Event{ Type: domain.EventTypeResourceFlowCreated, EntityID: rf.ID, OrgID: rf.OrganizationID, UserID: userID, Timestamp: time.Now(), Payload: map[string]interface{}{ "resource_type": string(rf.Type), "direction": string(rf.Direction), "site_id": rf.SiteID, }, } if err := s.eventBus.Publish(event); err != nil { // Log error but don't fail the operation // Event publishing should not block the main operation // TODO: Add proper logging here } } return rf, nil } // GetByID retrieves a resource flow by ID func (s *ResourceFlowService) GetByID(ctx context.Context, id string) (*domain.ResourceFlow, error) { return s.repo.GetByID(ctx, id) } // GetBySiteID retrieves resource flows at a specific site func (s *ResourceFlowService) GetBySiteID(ctx context.Context, siteID string) ([]*domain.ResourceFlow, error) { return s.repo.GetBySiteID(ctx, siteID) } // GetByOrganizationID retrieves resource flows owned by an organization func (s *ResourceFlowService) GetByOrganizationID(ctx context.Context, organizationID string) ([]*domain.ResourceFlow, error) { return s.repo.GetByOrganizationID(ctx, organizationID) } // GetByTypeAndDirection retrieves resource flows by type and direction func (s *ResourceFlowService) GetByTypeAndDirection(ctx context.Context, resType domain.ResourceType, direction domain.ResourceDirection) ([]*domain.ResourceFlow, error) { return s.repo.GetByTypeAndDirection(ctx, resType, direction) } // Update updates a resource flow func (s *ResourceFlowService) Update(ctx context.Context, rf *domain.ResourceFlow) error { rf.UpdatedAt = time.Now() if err := s.repo.Update(ctx, rf); err != nil { return err } // Publish event userID := getUserFromContext(ctx) if s.eventBus != nil { event := domain.Event{ Type: domain.EventTypeResourceFlowUpdated, EntityID: rf.ID, OrgID: rf.OrganizationID, UserID: userID, Timestamp: time.Now(), Payload: map[string]interface{}{ "resource_type": string(rf.Type), "direction": string(rf.Direction), "site_id": rf.SiteID, }, } if err := s.eventBus.Publish(event); err != nil { // Log error but don't fail the operation } } return nil } // Delete deletes a resource flow func (s *ResourceFlowService) Delete(ctx context.Context, id string) error { // Get the resource flow before deleting to publish event with details rf, err := s.repo.GetByID(ctx, id) if err != nil { return err } if err := s.repo.Delete(ctx, id); err != nil { return err } // Publish event userID := getUserFromContext(ctx) if s.eventBus != nil { event := domain.Event{ Type: domain.EventTypeResourceFlowDeleted, EntityID: id, OrgID: rf.OrganizationID, UserID: userID, Timestamp: time.Now(), Payload: map[string]interface{}{ "resource_type": string(rf.Type), "direction": string(rf.Direction), "site_id": rf.SiteID, }, } if err := s.eventBus.Publish(event); err != nil { // Log error but don't fail the operation } } return nil }