diff --git a/internal/handler/category_handler.go b/internal/handler/category_handler.go index 36ea3c4..21b21b2 100644 --- a/internal/handler/category_handler.go +++ b/internal/handler/category_handler.go @@ -191,7 +191,6 @@ func (h *CategoryHandler) ListCategories(c *gin.Context) { req.OutletID = &outletID } } - validationError, validationErrorCode := h.categoryValidator.ValidateListCategoriesRequest(req) if validationError != nil { logger.FromContext(ctx).WithError(validationError).Error("CategoryHandler::ListCategories -> request validation failed") diff --git a/internal/processor/product_processor.go b/internal/processor/product_processor.go index db8d560..6b1593c 100644 --- a/internal/processor/product_processor.go +++ b/internal/processor/product_processor.go @@ -39,6 +39,7 @@ type ProductRepository interface { ExistsBySKU(ctx context.Context, organizationID uuid.UUID, sku string, excludeID *uuid.UUID) (bool, error) GetByName(ctx context.Context, organizationID uuid.UUID, name string) (*entities.Product, error) ExistsByName(ctx context.Context, organizationID uuid.UUID, name string, excludeID *uuid.UUID) (bool, error) + ExistsByNameInOutlet(ctx context.Context, organizationID uuid.UUID, outletID uuid.UUID, name string, excludeID *uuid.UUID) (bool, error) UpdateActiveStatus(ctx context.Context, id uuid.UUID, isActive bool) error GetLowCostProducts(ctx context.Context, organizationID uuid.UUID, maxCost float64) ([]*entities.Product, error) } @@ -79,12 +80,12 @@ func (p *ProductProcessorImpl) CreateProduct(ctx context.Context, req *models.Cr } } - exists, err := p.productRepo.ExistsByName(ctx, req.OrganizationID, req.Name, nil) + exists, err := p.productRepo.ExistsByNameInOutlet(ctx, req.OrganizationID, req.OutletID, req.Name, nil) if err != nil { return nil, fmt.Errorf("failed to check product name uniqueness: %w", err) } if exists { - return nil, fmt.Errorf("product with name '%s' already exists for this organization", req.Name) + return nil, fmt.Errorf("product with name '%s' already exists for this outlet", req.Name) } productEntity := mappers.CreateProductRequestToEntity(req) @@ -173,12 +174,12 @@ func (p *ProductProcessorImpl) UpdateProduct(ctx context.Context, id uuid.UUID, } if req.Name != nil && *req.Name != existingProduct.Name { - exists, err := p.productRepo.ExistsByName(ctx, existingProduct.OrganizationID, *req.Name, &id) + exists, err := p.productRepo.ExistsByNameInOutlet(ctx, existingProduct.OrganizationID, req.OutletID, *req.Name, &id) if err != nil { return nil, fmt.Errorf("failed to check product name uniqueness: %w", err) } if exists { - return nil, fmt.Errorf("product with name '%s' already exists for this organization", *req.Name) + return nil, fmt.Errorf("product with name '%s' already exists for this outlet", *req.Name) } } diff --git a/internal/repository/category_repository.go b/internal/repository/category_repository.go index a592a49..e60e923 100644 --- a/internal/repository/category_repository.go +++ b/internal/repository/category_repository.go @@ -72,6 +72,9 @@ func (r *CategoryRepositoryImpl) List(ctx context.Context, filters map[string]in case "search": searchValue := "%" + value.(string) + "%" query = query.Where("name ILIKE ? OR description ILIKE ?", searchValue, searchValue) + case "outlet_id": + // Include outlet-specific categories AND global categories (outlet_id IS NULL) + query = query.Where("outlet_id = ? OR outlet_id IS NULL", value) default: query = query.Where(key+" = ?", value) } diff --git a/internal/repository/product_repository.go b/internal/repository/product_repository.go index d5fc5f4..3a4bff6 100644 --- a/internal/repository/product_repository.go +++ b/internal/repository/product_repository.go @@ -178,6 +178,26 @@ func (r *ProductRepositoryImpl) ExistsByName(ctx context.Context, organizationID return count > 0, err } +// ExistsByNameInOutlet checks name uniqueness scoped to a specific outlet via product_outlet_prices. +// Falls back to organization-scoped check when outletID is zero. +func (r *ProductRepositoryImpl) ExistsByNameInOutlet(ctx context.Context, organizationID uuid.UUID, outletID uuid.UUID, name string, excludeID *uuid.UUID) (bool, error) { + if outletID == uuid.Nil { + return r.ExistsByName(ctx, organizationID, name, excludeID) + } + + query := r.db.WithContext(ctx).Model(&entities.Product{}). + Joins("INNER JOIN product_outlet_prices pop ON pop.product_id = products.id AND pop.outlet_id = ?", outletID). + Where("products.organization_id = ? AND products.name = ?", organizationID, name) + + if excludeID != nil { + query = query.Where("products.id != ?", *excludeID) + } + + var count int64 + err := query.Count(&count).Error + return count > 0, err +} + func (r *ProductRepositoryImpl) UpdateActiveStatus(ctx context.Context, id uuid.UUID, isActive bool) error { return r.db.WithContext(ctx).Model(&entities.Product{}). Where("id = ?", id).