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
|
|
|
|
|
}
|