package processor import ( "context" "fmt" "apskel-pos-be/internal/entities" "apskel-pos-be/internal/mappers" "apskel-pos-be/internal/models" "github.com/google/uuid" ) type IngredientUnitConverterRepository interface { Create(ctx context.Context, converter *entities.IngredientUnitConverter) error GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error) GetByIDAndOrganizationID(ctx context.Context, id, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error) Update(ctx context.Context, converter *entities.IngredientUnitConverter) error Delete(ctx context.Context, id, organizationID uuid.UUID) error List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*entities.IngredientUnitConverter, int, error) GetByIngredientAndUnits(ctx context.Context, ingredientID, fromUnitID, toUnitID, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error) GetConvertersForIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*entities.IngredientUnitConverter, error) GetActiveConverters(ctx context.Context, organizationID uuid.UUID) ([]*entities.IngredientUnitConverter, error) ConvertQuantity(ctx context.Context, ingredientID, fromUnitID, toUnitID, organizationID uuid.UUID, quantity float64) (float64, error) } type IngredientUnitConverterProcessor interface { CreateIngredientUnitConverter(ctx context.Context, organizationID, userID uuid.UUID, req *models.CreateIngredientUnitConverterRequest) (*models.IngredientUnitConverterResponse, error) UpdateIngredientUnitConverter(ctx context.Context, id, organizationID, userID uuid.UUID, req *models.UpdateIngredientUnitConverterRequest) (*models.IngredientUnitConverterResponse, error) DeleteIngredientUnitConverter(ctx context.Context, id, organizationID uuid.UUID) error GetIngredientUnitConverterByID(ctx context.Context, id, organizationID uuid.UUID) (*models.IngredientUnitConverterResponse, error) ListIngredientUnitConverters(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.IngredientUnitConverterResponse, int, error) GetConvertersForIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*models.IngredientUnitConverterResponse, error) ConvertUnit(ctx context.Context, organizationID uuid.UUID, req *models.ConvertUnitRequest) (*models.ConvertUnitResponse, error) GetUnitsByIngredientID(ctx context.Context, organizationID, ingredientID uuid.UUID) (*models.IngredientUnitsResponse, error) } type IngredientUnitConverterProcessorImpl struct { converterRepo IngredientUnitConverterRepository ingredientRepo IngredientRepository unitRepo UnitRepository } func NewIngredientUnitConverterProcessorImpl( converterRepo IngredientUnitConverterRepository, ingredientRepo IngredientRepository, unitRepo UnitRepository, ) *IngredientUnitConverterProcessorImpl { return &IngredientUnitConverterProcessorImpl{ converterRepo: converterRepo, ingredientRepo: ingredientRepo, unitRepo: unitRepo, } } func (p *IngredientUnitConverterProcessorImpl) CreateIngredientUnitConverter(ctx context.Context, organizationID, userID uuid.UUID, req *models.CreateIngredientUnitConverterRequest) (*models.IngredientUnitConverterResponse, error) { // Validate ingredient exists _, err := p.ingredientRepo.GetByID(ctx, req.IngredientID, organizationID) if err != nil { return nil, fmt.Errorf("ingredient not found: %w", err) } // Validate units exist _, err = p.unitRepo.GetByID(ctx, req.FromUnitID, organizationID) if err != nil { return nil, fmt.Errorf("from unit not found: %w", err) } _, err = p.unitRepo.GetByID(ctx, req.ToUnitID, organizationID) if err != nil { return nil, fmt.Errorf("to unit not found: %w", err) } // Check if converter already exists existingConverter, err := p.converterRepo.GetByIngredientAndUnits(ctx, req.IngredientID, req.FromUnitID, req.ToUnitID, organizationID) if err == nil && existingConverter != nil { return nil, fmt.Errorf("converter already exists for this ingredient and unit combination") } // Set default values isActive := true if req.IsActive != nil { isActive = *req.IsActive } // Create entity converter := &entities.IngredientUnitConverter{ OrganizationID: organizationID, IngredientID: req.IngredientID, FromUnitID: req.FromUnitID, ToUnitID: req.ToUnitID, ConversionFactor: req.ConversionFactor, IsActive: isActive, CreatedBy: userID, UpdatedBy: userID, } err = p.converterRepo.Create(ctx, converter) if err != nil { return nil, fmt.Errorf("failed to create ingredient unit converter: %w", err) } // Get the created converter with relationships createdConverter, err := p.converterRepo.GetByID(ctx, converter.ID, organizationID) if err != nil { return nil, fmt.Errorf("failed to get created converter: %w", err) } return mappers.IngredientUnitConverterEntityToResponse(createdConverter), nil } func (p *IngredientUnitConverterProcessorImpl) UpdateIngredientUnitConverter(ctx context.Context, id, organizationID, userID uuid.UUID, req *models.UpdateIngredientUnitConverterRequest) (*models.IngredientUnitConverterResponse, error) { // Get existing converter converter, err := p.converterRepo.GetByID(ctx, id, organizationID) if err != nil { return nil, fmt.Errorf("ingredient unit converter not found: %w", err) } // Update fields if provided if req.FromUnitID != nil { // Validate new unit exists _, err = p.unitRepo.GetByID(ctx, *req.FromUnitID, organizationID) if err != nil { return nil, fmt.Errorf("from unit not found: %w", err) } converter.FromUnitID = *req.FromUnitID } if req.ToUnitID != nil { // Validate new unit exists _, err = p.unitRepo.GetByID(ctx, *req.ToUnitID, organizationID) if err != nil { return nil, fmt.Errorf("to unit not found: %w", err) } converter.ToUnitID = *req.ToUnitID } if req.ConversionFactor != nil { converter.ConversionFactor = *req.ConversionFactor } if req.IsActive != nil { converter.IsActive = *req.IsActive } converter.UpdatedBy = userID err = p.converterRepo.Update(ctx, converter) if err != nil { return nil, fmt.Errorf("failed to update ingredient unit converter: %w", err) } // Get the updated converter with relationships updatedConverter, err := p.converterRepo.GetByID(ctx, converter.ID, organizationID) if err != nil { return nil, fmt.Errorf("failed to get updated converter: %w", err) } return mappers.IngredientUnitConverterEntityToResponse(updatedConverter), nil } func (p *IngredientUnitConverterProcessorImpl) DeleteIngredientUnitConverter(ctx context.Context, id, organizationID uuid.UUID) error { // Check if converter exists _, err := p.converterRepo.GetByID(ctx, id, organizationID) if err != nil { return fmt.Errorf("ingredient unit converter not found: %w", err) } err = p.converterRepo.Delete(ctx, id, organizationID) if err != nil { return fmt.Errorf("failed to delete ingredient unit converter: %w", err) } return nil } func (p *IngredientUnitConverterProcessorImpl) GetIngredientUnitConverterByID(ctx context.Context, id, organizationID uuid.UUID) (*models.IngredientUnitConverterResponse, error) { converter, err := p.converterRepo.GetByID(ctx, id, organizationID) if err != nil { return nil, fmt.Errorf("ingredient unit converter not found: %w", err) } return mappers.IngredientUnitConverterEntityToResponse(converter), nil } func (p *IngredientUnitConverterProcessorImpl) ListIngredientUnitConverters(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.IngredientUnitConverterResponse, int, error) { converters, total, err := p.converterRepo.List(ctx, organizationID, filters, page, limit) if err != nil { return nil, 0, fmt.Errorf("failed to list ingredient unit converters: %w", err) } responses := make([]*models.IngredientUnitConverterResponse, len(converters)) for i, converter := range converters { responses[i] = mappers.IngredientUnitConverterEntityToResponse(converter) } return responses, total, nil } func (p *IngredientUnitConverterProcessorImpl) GetConvertersForIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*models.IngredientUnitConverterResponse, error) { converters, err := p.converterRepo.GetConvertersForIngredient(ctx, ingredientID, organizationID) if err != nil { return nil, fmt.Errorf("failed to get converters for ingredient: %w", err) } responses := make([]*models.IngredientUnitConverterResponse, len(converters)) for i, converter := range converters { responses[i] = mappers.IngredientUnitConverterEntityToResponse(converter) } return responses, nil } func (p *IngredientUnitConverterProcessorImpl) ConvertUnit(ctx context.Context, organizationID uuid.UUID, req *models.ConvertUnitRequest) (*models.ConvertUnitResponse, error) { // Get ingredient and units for response ingredient, err := p.ingredientRepo.GetByID(ctx, req.IngredientID, organizationID) if err != nil { return nil, fmt.Errorf("ingredient not found: %w", err) } fromUnit, err := p.unitRepo.GetByID(ctx, req.FromUnitID, organizationID) if err != nil { return nil, fmt.Errorf("from unit not found: %w", err) } toUnit, err := p.unitRepo.GetByID(ctx, req.ToUnitID, organizationID) if err != nil { return nil, fmt.Errorf("to unit not found: %w", err) } // Convert quantity convertedQuantity, err := p.converterRepo.ConvertQuantity(ctx, req.IngredientID, req.FromUnitID, req.ToUnitID, organizationID, req.Quantity) if err != nil { return nil, fmt.Errorf("failed to convert quantity: %w", err) } // Get conversion factor for response converter, err := p.converterRepo.GetByIngredientAndUnits(ctx, req.IngredientID, req.FromUnitID, req.ToUnitID, organizationID) var conversionFactor float64 if err == nil { conversionFactor = converter.ConversionFactor } else { // Try reverse converter reverseConverter, err := p.converterRepo.GetByIngredientAndUnits(ctx, req.IngredientID, req.ToUnitID, req.FromUnitID, organizationID) if err == nil { conversionFactor = 1.0 / reverseConverter.ConversionFactor } } response := &models.ConvertUnitResponse{ FromQuantity: req.Quantity, FromUnit: mappers.MapUnitEntityToResponse(fromUnit), ToQuantity: convertedQuantity, ToUnit: mappers.MapUnitEntityToResponse(toUnit), ConversionFactor: conversionFactor, Ingredient: mappers.MapIngredientEntityToResponse(ingredient), } return response, nil } func (p *IngredientUnitConverterProcessorImpl) GetUnitsByIngredientID(ctx context.Context, organizationID, ingredientID uuid.UUID) (*models.IngredientUnitsResponse, error) { // Get the ingredient with its base unit ingredient, err := p.ingredientRepo.GetByID(ctx, ingredientID, organizationID) if err != nil { return nil, fmt.Errorf("failed to get ingredient: %w", err) } // Get the base unit details baseUnit, err := p.unitRepo.GetByID(ctx, ingredient.UnitID, organizationID) if err != nil { return nil, fmt.Errorf("failed to get base unit: %w", err) } // Start with the base unit units := []*models.UnitResponse{ mappers.MapUnitEntityToResponse(baseUnit), } // Get all converters for this ingredient converters, err := p.converterRepo.GetConvertersForIngredient(ctx, ingredientID, organizationID) if err != nil { return nil, fmt.Errorf("failed to get converters: %w", err) } // Add unique units from converters unitMap := make(map[uuid.UUID]bool) unitMap[baseUnit.ID] = true for _, converter := range converters { if converter.IsActive { // Add FromUnit if not already added if !unitMap[converter.FromUnitID] { fromUnit, err := p.unitRepo.GetByID(ctx, converter.FromUnitID, organizationID) if err == nil { units = append(units, mappers.MapUnitEntityToResponse(fromUnit)) unitMap[converter.FromUnitID] = true } } // Add ToUnit if not already added if !unitMap[converter.ToUnitID] { toUnit, err := p.unitRepo.GetByID(ctx, converter.ToUnitID, organizationID) if err == nil { units = append(units, mappers.MapUnitEntityToResponse(toUnit)) unitMap[converter.ToUnitID] = true } } } } response := &models.IngredientUnitsResponse{ IngredientID: ingredientID, IngredientName: ingredient.Name, BaseUnitID: baseUnit.ID, BaseUnitName: baseUnit.Name, Units: units, } return response, nil }