package commands import ( "context" "errors" "fmt" "time" "autostore/internal/application/interfaces" "autostore/internal/domain/entities" "autostore/internal/domain/specifications" "autostore/internal/domain/value_objects" ) var ( ErrItemCreationFailed = errors.New("failed to create item") ErrInvalidUserID = errors.New("invalid user ID") ) type AddItemCommand struct { itemRepo interfaces.IItemRepository orderService interfaces.IOrderService timeProvider interfaces.ITimeProvider expirationSpec *specifications.ItemExpirationSpec logger interfaces.ILogger } func NewAddItemCommand( itemRepo interfaces.IItemRepository, orderService interfaces.IOrderService, timeProvider interfaces.ITimeProvider, expirationSpec *specifications.ItemExpirationSpec, logger interfaces.ILogger, ) *AddItemCommand { return &AddItemCommand{ itemRepo: itemRepo, orderService: orderService, timeProvider: timeProvider, expirationSpec: expirationSpec, logger: logger, } } func (c *AddItemCommand) Execute(ctx context.Context, name string, expirationDate time.Time, orderURL string, userID string) (string, error) { c.logger.Info(ctx, "Executing AddItemCommand", "name", name, "userID", userID) // Convert string IDs to value objects itemID, err := value_objects.NewRandomItemID() if err != nil { c.logger.Error(ctx, "Failed to generate item ID", "error", err) return "", fmt.Errorf("%w: %v", ErrItemCreationFailed, err) } userIDObj, err := value_objects.NewUserID(userID) if err != nil { c.logger.Error(ctx, "Invalid user ID", "userID", userID, "error", err) return "", fmt.Errorf("%w: %v", ErrInvalidUserID, err) } // Create expiration date value object expirationDateObj, err := value_objects.NewExpirationDate(expirationDate) if err != nil { c.logger.Error(ctx, "Invalid expiration date", "expirationDate", expirationDate, "error", err) return "", fmt.Errorf("%w: %v", ErrItemCreationFailed, err) } // Create item entity item, err := entities.NewItem(itemID, name, expirationDateObj, orderURL, userIDObj) if err != nil { c.logger.Error(ctx, "Failed to create item entity", "error", err) return "", fmt.Errorf("%w: %v", ErrItemCreationFailed, err) } // Get current time and check if item is expired currentTime := c.timeProvider.Now() isExpired := c.expirationSpec.IsExpired(item, currentTime) // Save the item first (even if expired, as per business rule #4) if err := c.itemRepo.Save(ctx, item); err != nil { c.logger.Error(ctx, "Failed to save item", "itemID", itemID.String(), "error", err) return "", fmt.Errorf("%w: %v", ErrItemCreationFailed, err) } if isExpired { c.logger.Info(ctx, "Item is expired, triggering order", "itemID", itemID.String()) // Business rule: When an item expires, a new item of the same type is automatically ordered if err := c.orderService.OrderItem(ctx, item); err != nil { c.logger.Error(ctx, "Failed to order expired item", "itemID", itemID.String(), "error", err) // Don't fail the entire operation if ordering fails, just log it c.logger.Warn(ctx, "Item created but ordering failed", "itemID", itemID.String(), "error", err) } } c.logger.Info(ctx, "Item created successfully", "itemID", itemID.String()) return itemID.String(), nil }