apskel-pos-backend/internal/processor/ingredient_unit_converter_processor.go

322 lines
12 KiB
Go
Raw Normal View History

2025-09-12 01:12:11 +07:00
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)
2025-09-12 15:37:19 +07:00
GetUnitsByIngredientID(ctx context.Context, organizationID, ingredientID uuid.UUID) (*models.IngredientUnitsResponse, error)
2025-09-12 01:12:11 +07:00
}
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
}
2025-09-12 15:37:19 +07:00
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
}