package repository import ( "apskel-pos-be/internal/entities" "context" "fmt" "github.com/google/uuid" "gorm.io/gorm" ) 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 IngredientUnitConverterRepositoryImpl struct { db *gorm.DB } func NewIngredientUnitConverterRepositoryImpl(db *gorm.DB) IngredientUnitConverterRepository { return &IngredientUnitConverterRepositoryImpl{ db: db, } } func (r *IngredientUnitConverterRepositoryImpl) Create(ctx context.Context, converter *entities.IngredientUnitConverter) error { return r.db.WithContext(ctx).Create(converter).Error } func (r *IngredientUnitConverterRepositoryImpl) GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error) { var converter entities.IngredientUnitConverter err := r.db.WithContext(ctx). Where("id = ? AND organization_id = ?", id, organizationID). Preload("Ingredient"). Preload("FromUnit"). Preload("ToUnit"). Preload("CreatedByUser"). Preload("UpdatedByUser"). First(&converter).Error if err != nil { return nil, err } return &converter, nil } func (r *IngredientUnitConverterRepositoryImpl) GetByIDAndOrganizationID(ctx context.Context, id, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error) { return r.GetByID(ctx, id, organizationID) } func (r *IngredientUnitConverterRepositoryImpl) Update(ctx context.Context, converter *entities.IngredientUnitConverter) error { return r.db.WithContext(ctx).Save(converter).Error } func (r *IngredientUnitConverterRepositoryImpl) Delete(ctx context.Context, id, organizationID uuid.UUID) error { return r.db.WithContext(ctx). Where("id = ? AND organization_id = ?", id, organizationID). Delete(&entities.IngredientUnitConverter{}).Error } func (r *IngredientUnitConverterRepositoryImpl) List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*entities.IngredientUnitConverter, int, error) { var converters []*entities.IngredientUnitConverter var total int64 query := r.db.WithContext(ctx). Model(&entities.IngredientUnitConverter{}). Where("organization_id = ?", organizationID) // Apply filters if ingredientID, ok := filters["ingredient_id"].(uuid.UUID); ok { query = query.Where("ingredient_id = ?", ingredientID) } if fromUnitID, ok := filters["from_unit_id"].(uuid.UUID); ok { query = query.Where("from_unit_id = ?", fromUnitID) } if toUnitID, ok := filters["to_unit_id"].(uuid.UUID); ok { query = query.Where("to_unit_id = ?", toUnitID) } if isActive, ok := filters["is_active"].(bool); ok { query = query.Where("is_active = ?", isActive) } if search, ok := filters["search"].(string); ok && search != "" { query = query.Joins("LEFT JOIN ingredients ON ingredient_unit_converters.ingredient_id = ingredients.id"). Joins("LEFT JOIN units AS from_units ON ingredient_unit_converters.from_unit_id = from_units.id"). Joins("LEFT JOIN units AS to_units ON ingredient_unit_converters.to_unit_id = to_units.id"). Where("ingredients.name ILIKE ? OR from_units.name ILIKE ? OR to_units.name ILIKE ?", "%"+search+"%", "%"+search+"%", "%"+search+"%") } // Get total count if err := query.Count(&total).Error; err != nil { return nil, 0, err } // Apply pagination and get results offset := (page - 1) * limit err := query. Preload("Ingredient"). Preload("FromUnit"). Preload("ToUnit"). Preload("CreatedByUser"). Preload("UpdatedByUser"). Order("created_at DESC"). Offset(offset). Limit(limit). Find(&converters).Error return converters, int(total), err } func (r *IngredientUnitConverterRepositoryImpl) GetByIngredientAndUnits(ctx context.Context, ingredientID, fromUnitID, toUnitID, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error) { var converter entities.IngredientUnitConverter err := r.db.WithContext(ctx). Where("ingredient_id = ? AND from_unit_id = ? AND to_unit_id = ? AND organization_id = ? AND is_active = ?", ingredientID, fromUnitID, toUnitID, organizationID, true). Preload("Ingredient"). Preload("FromUnit"). Preload("ToUnit"). First(&converter).Error if err != nil { return nil, err } return &converter, nil } func (r *IngredientUnitConverterRepositoryImpl) GetConvertersForIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*entities.IngredientUnitConverter, error) { var converters []*entities.IngredientUnitConverter err := r.db.WithContext(ctx). Where("ingredient_id = ? AND organization_id = ? AND is_active = ?", ingredientID, organizationID, true). Preload("FromUnit"). Preload("ToUnit"). Find(&converters).Error return converters, err } func (r *IngredientUnitConverterRepositoryImpl) GetActiveConverters(ctx context.Context, organizationID uuid.UUID) ([]*entities.IngredientUnitConverter, error) { var converters []*entities.IngredientUnitConverter err := r.db.WithContext(ctx). Where("organization_id = ? AND is_active = ?", organizationID, true). Preload("Ingredient"). Preload("FromUnit"). Preload("ToUnit"). Find(&converters).Error return converters, err } func (r *IngredientUnitConverterRepositoryImpl) ConvertQuantity(ctx context.Context, ingredientID, fromUnitID, toUnitID, organizationID uuid.UUID, quantity float64) (float64, error) { // If from and to units are the same, return the same quantity if fromUnitID == toUnitID { return quantity, nil } // Try to find direct converter converter, err := r.GetByIngredientAndUnits(ctx, ingredientID, fromUnitID, toUnitID, organizationID) if err == nil { return quantity * converter.ConversionFactor, nil } // If direct converter not found, try to find reverse converter reverseConverter, err := r.GetByIngredientAndUnits(ctx, ingredientID, toUnitID, fromUnitID, organizationID) if err == nil { return quantity / reverseConverter.ConversionFactor, nil } // If no converter found, return error return 0, fmt.Errorf("no conversion found between units %s and %s for ingredient %s", fromUnitID, toUnitID, ingredientID) }