256 lines
9.6 KiB
Go
256 lines
9.6 KiB
Go
package processor
|
|
|
|
import (
|
|
"apskel-pos-be/internal/contract"
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"apskel-pos-be/internal/entities"
|
|
"apskel-pos-be/internal/repository"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type ProductRecipeProcessor interface {
|
|
Create(ctx context.Context, req *contract.CreateProductRecipeRequest, organizationID uuid.UUID) (*contract.ProductRecipeResponse, error)
|
|
GetByID(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) (*contract.ProductRecipeResponse, error)
|
|
GetByProductID(ctx context.Context, productID uuid.UUID, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error)
|
|
GetByProductAndVariantID(ctx context.Context, productID uuid.UUID, variantID *uuid.UUID, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error)
|
|
GetByIngredientID(ctx context.Context, ingredientID uuid.UUID, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error)
|
|
Update(ctx context.Context, id uuid.UUID, req *contract.UpdateProductRecipeRequest, organizationID uuid.UUID) (*contract.ProductRecipeResponse, error)
|
|
Delete(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) error
|
|
BulkCreate(ctx context.Context, recipes []contract.CreateProductRecipeRequest, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error)
|
|
}
|
|
|
|
type ProductRecipeProcessorImpl struct {
|
|
productRecipeRepo *repository.ProductRecipeRepository
|
|
productRepo ProductRepository
|
|
ingredientRepo IngredientRepository
|
|
}
|
|
|
|
func NewProductRecipeProcessor(productRecipeRepo *repository.ProductRecipeRepository, productRepo ProductRepository, ingredientRepo IngredientRepository) *ProductRecipeProcessorImpl {
|
|
return &ProductRecipeProcessorImpl{
|
|
productRecipeRepo: productRecipeRepo,
|
|
productRepo: productRepo,
|
|
ingredientRepo: ingredientRepo,
|
|
}
|
|
}
|
|
|
|
func (p *ProductRecipeProcessorImpl) Create(ctx context.Context, req *contract.CreateProductRecipeRequest, organizationID uuid.UUID) (*contract.ProductRecipeResponse, error) {
|
|
_, err := p.productRepo.GetByID(ctx, req.ProductID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid product: %w", err)
|
|
}
|
|
|
|
_, err = p.ingredientRepo.GetByID(ctx, req.IngredientID, organizationID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid ingredient: %w", err)
|
|
}
|
|
|
|
entity := &entities.ProductRecipe{
|
|
ID: uuid.New(),
|
|
OrganizationID: organizationID,
|
|
OutletID: req.OutletID,
|
|
ProductID: req.ProductID,
|
|
VariantID: req.VariantID,
|
|
IngredientID: req.IngredientID,
|
|
Quantity: req.Quantity,
|
|
WastePercentage: req.WastePercentage,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
|
|
if err := p.productRecipeRepo.Create(ctx, entity); err != nil {
|
|
return nil, fmt.Errorf("failed to create product recipe: %w", err)
|
|
}
|
|
|
|
createdEntity, err := p.productRecipeRepo.GetByID(ctx, entity.ID, organizationID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get created product recipe: %w", err)
|
|
}
|
|
|
|
return p.entityToResponse(createdEntity), nil
|
|
}
|
|
|
|
func (p *ProductRecipeProcessorImpl) GetByID(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) (*contract.ProductRecipeResponse, error) {
|
|
entity, err := p.productRecipeRepo.GetByID(ctx, id, organizationID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get product recipe: %w", err)
|
|
}
|
|
|
|
return p.entityToResponse(entity), nil
|
|
}
|
|
|
|
func (p *ProductRecipeProcessorImpl) GetByProductID(ctx context.Context, productID uuid.UUID, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error) {
|
|
entities, err := p.productRecipeRepo.GetByProductID(ctx, productID, organizationID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get product recipes by product ID: %w", err)
|
|
}
|
|
|
|
responses := make([]*contract.ProductRecipeResponse, len(entities))
|
|
for i, entity := range entities {
|
|
responses[i] = p.entityToResponse(entity)
|
|
}
|
|
|
|
return responses, nil
|
|
}
|
|
|
|
func (p *ProductRecipeProcessorImpl) GetByProductAndVariantID(ctx context.Context, productID uuid.UUID, variantID *uuid.UUID, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error) {
|
|
entities, err := p.productRecipeRepo.GetByProductAndVariantID(ctx, productID, variantID, organizationID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get product recipes by product and variant ID: %w", err)
|
|
}
|
|
|
|
responses := make([]*contract.ProductRecipeResponse, len(entities))
|
|
for i, entity := range entities {
|
|
responses[i] = p.entityToResponse(entity)
|
|
}
|
|
|
|
return responses, nil
|
|
}
|
|
|
|
func (p *ProductRecipeProcessorImpl) GetByIngredientID(ctx context.Context, ingredientID uuid.UUID, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error) {
|
|
entities, err := p.productRecipeRepo.GetByIngredientID(ctx, ingredientID, organizationID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get product recipes by ingredient ID: %w", err)
|
|
}
|
|
|
|
responses := make([]*contract.ProductRecipeResponse, len(entities))
|
|
for i, entity := range entities {
|
|
responses[i] = p.entityToResponse(entity)
|
|
}
|
|
|
|
return responses, nil
|
|
}
|
|
|
|
func (p *ProductRecipeProcessorImpl) Update(ctx context.Context, id uuid.UUID, req *contract.UpdateProductRecipeRequest, organizationID uuid.UUID) (*contract.ProductRecipeResponse, error) {
|
|
// Get existing entity
|
|
existingEntity, err := p.productRecipeRepo.GetByID(ctx, id, organizationID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("product recipe not found: %w", err)
|
|
}
|
|
|
|
// Update fields
|
|
existingEntity.OutletID = req.OutletID
|
|
existingEntity.VariantID = req.VariantID
|
|
existingEntity.Quantity = req.Quantity
|
|
existingEntity.WastePercentage = req.WastePercentage
|
|
existingEntity.UpdatedAt = time.Now().UTC()
|
|
|
|
if err := p.productRecipeRepo.Update(ctx, existingEntity); err != nil {
|
|
return nil, fmt.Errorf("failed to update product recipe: %w", err)
|
|
}
|
|
|
|
// Get the updated entity with relations
|
|
updatedEntity, err := p.productRecipeRepo.GetByID(ctx, id, organizationID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get updated product recipe: %w", err)
|
|
}
|
|
|
|
return p.entityToResponse(updatedEntity), nil
|
|
}
|
|
|
|
func (p *ProductRecipeProcessorImpl) Delete(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) error {
|
|
if err := p.productRecipeRepo.Delete(ctx, id, organizationID); err != nil {
|
|
return fmt.Errorf("failed to delete product recipe: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *ProductRecipeProcessorImpl) BulkCreate(ctx context.Context, recipes []contract.CreateProductRecipeRequest, organizationID uuid.UUID) ([]*contract.ProductRecipeResponse, error) {
|
|
responses := make([]*contract.ProductRecipeResponse, 0, len(recipes))
|
|
|
|
for _, recipe := range recipes {
|
|
response, err := p.Create(ctx, &recipe, organizationID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create recipe for product %s and ingredient %s: %w", recipe.ProductID, recipe.IngredientID, err)
|
|
}
|
|
responses = append(responses, response)
|
|
}
|
|
|
|
return responses, nil
|
|
}
|
|
|
|
func (p *ProductRecipeProcessorImpl) entityToResponse(entity *entities.ProductRecipe) *contract.ProductRecipeResponse {
|
|
response := &contract.ProductRecipeResponse{
|
|
ID: entity.ID,
|
|
OrganizationID: entity.OrganizationID,
|
|
OutletID: entity.OutletID,
|
|
ProductID: entity.ProductID,
|
|
VariantID: entity.VariantID,
|
|
IngredientID: entity.IngredientID,
|
|
Quantity: entity.Quantity,
|
|
WastePercentage: entity.WastePercentage,
|
|
CreatedAt: entity.CreatedAt,
|
|
UpdatedAt: entity.UpdatedAt,
|
|
}
|
|
|
|
if entity.Product != nil {
|
|
response.Product = &contract.ProductResponse{
|
|
ID: entity.Product.ID,
|
|
OrganizationID: entity.Product.OrganizationID,
|
|
CategoryID: entity.Product.CategoryID,
|
|
SKU: entity.Product.SKU,
|
|
Name: entity.Product.Name,
|
|
Description: entity.Product.Description,
|
|
Price: entity.Product.Price,
|
|
Cost: entity.Product.Cost,
|
|
BusinessType: string(entity.Product.BusinessType),
|
|
ImageURL: entity.Product.ImageURL,
|
|
PrinterType: entity.Product.PrinterType,
|
|
Metadata: entity.Product.Metadata,
|
|
IsActive: entity.Product.IsActive,
|
|
CreatedAt: entity.Product.CreatedAt,
|
|
UpdatedAt: entity.Product.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
if entity.ProductVariant != nil {
|
|
response.ProductVariant = &contract.ProductVariantResponse{
|
|
ID: entity.ProductVariant.ID,
|
|
ProductID: entity.ProductVariant.ProductID,
|
|
Name: entity.ProductVariant.Name,
|
|
PriceModifier: entity.ProductVariant.PriceModifier,
|
|
Cost: entity.ProductVariant.Cost,
|
|
Metadata: entity.ProductVariant.Metadata,
|
|
CreatedAt: entity.ProductVariant.CreatedAt,
|
|
UpdatedAt: entity.ProductVariant.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
if entity.Ingredient != nil {
|
|
response.Ingredient = &contract.ProductRecipeIngredientResponse{
|
|
ID: entity.Ingredient.ID,
|
|
OrganizationID: entity.Ingredient.OrganizationID,
|
|
OutletID: entity.Ingredient.OutletID,
|
|
Name: entity.Ingredient.Name,
|
|
UnitID: entity.Ingredient.UnitID,
|
|
Cost: entity.Ingredient.Cost,
|
|
Stock: entity.Ingredient.Stock,
|
|
IsSemiFinished: entity.Ingredient.IsSemiFinished,
|
|
IsActive: entity.Ingredient.IsActive,
|
|
Metadata: entity.Ingredient.Metadata,
|
|
CreatedAt: entity.Ingredient.CreatedAt,
|
|
UpdatedAt: entity.Ingredient.UpdatedAt,
|
|
}
|
|
|
|
// Add unit if available
|
|
if entity.Ingredient.Unit != nil {
|
|
symbol := ""
|
|
if entity.Ingredient.Unit.Abbreviation != nil {
|
|
symbol = *entity.Ingredient.Unit.Abbreviation
|
|
}
|
|
response.Ingredient.Unit = &contract.ProductRecipeUnitResponse{
|
|
ID: entity.Ingredient.Unit.ID,
|
|
Name: entity.Ingredient.Unit.Name,
|
|
Symbol: symbol,
|
|
CreatedAt: entity.Ingredient.Unit.CreatedAt,
|
|
UpdatedAt: entity.Ingredient.Unit.UpdatedAt,
|
|
}
|
|
}
|
|
}
|
|
|
|
return response
|
|
} |