Skip to content

fix(validation): scoped duplicate checks for Category and Food#216

Merged
rdodiya merged 5 commits into
rdodiya:gssoc_developfrom
iRahmanG:fix/duplicate_validation
May 25, 2026
Merged

fix(validation): scoped duplicate checks for Category and Food#216
rdodiya merged 5 commits into
rdodiya:gssoc_developfrom
iRahmanG:fix/duplicate_validation

Conversation

@iRahmanG
Copy link
Copy Markdown
Contributor

Summary

This PR introduces corrected duplicate validation logic to ensure data integrity:

  • Category: Added per‑menu duplicate name validation using existsByNameIgnoreCaseAndMenu_MenuId.
  • Food: Added per‑category duplicate name validation using existsByNameIgnoreCaseAndCategory_CategoryId.

Why

The earlier PR (#160) attempted similar changes but contained bugs. This new PR provides a clean, corrected implementation with proper repository methods and service logic.

Notes

  • Other modules (Menu, Order, Restaurant, User) already had scoped validation checks in place.
  • This PR focuses only on the missing pieces (Category and Food).
  • If additional validation is expected, please highlight it so I can address it in a follow‑up.

Close Issue: #156

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds per-scope duplicate name validation for Category (per menu) and Food (per category), replacing the previous global existsByNameIgnoreCase checks. It addresses issue #156 by introducing repository methods that include the parent scope, and adds a required menuId field on CategoryRequestDTO so the category create flow can be scoped.

Changes:

  • Adds existsByNameIgnoreCaseAndMenu_MenuId on CategoryRepository and uses it in CategoryServiceImpl.createCategory; adds required menuId to CategoryRequestDTO.
  • Adds existsByNameIgnoreCaseAndCategory_CategoryId on FoodRepository and uses it in both createFood and updateFood, replacing the global duplicate-name check.
  • Adds debug/warn logging around the new duplicate detection paths.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
RestroHub/src/main/java/com/restroly/qrmenu/category/dto/CategoryRequestDTO.java Adds @NotNull Long menuId so create requests carry the menu scope for duplicate validation.
RestroHub/src/main/java/com/restroly/qrmenu/category/repository/CategoryRepository.java New derived query existsByNameIgnoreCaseAndMenu_MenuId for per-menu duplicate detection.
RestroHub/src/main/java/com/restroly/qrmenu/category/service/CategoryServiceImpl.java Adds per-menu duplicate check in createCategory, throwing DuplicateResourceException.
RestroHub/src/main/java/com/restroly/qrmenu/food/repository/FoodRepository.java New derived query existsByNameIgnoreCaseAndCategory_CategoryId for per-category duplicate detection.
RestroHub/src/main/java/com/restroly/qrmenu/food/service/FoodServiceImpl.java Switches create/update duplicate checks from global name to category-scoped, with updated logs and error messages.
Comments suppressed due to low confidence (1)

RestroHub/src/main/java/com/restroly/qrmenu/category/service/CategoryServiceImpl.java:52

  • The new category is built and saved without any association to the Menu identified by menuId. CategoryDTO.toEntity (called immediately below) does not set the menu field, and there is no menuRepository.findById(menuId) lookup or category.getMenu().add(menu) call here. As a result:
  1. The menuId from the request is used only for the duplicate check and is silently discarded — the persisted category will not actually belong to any menu, so the very next createCategory call with the same name and menuId will again return false from existsByNameIgnoreCaseAndMenu_MenuId(...) and create another duplicate.
  2. A non-existent menuId is never validated against MenuRepository, unlike the existing pattern in BranchServiceImpl.createBranch (RestroHub/src/main/java/com/restroly/qrmenu/branch/service/BranchServiceImpl.java:44-49) and MenuServiceImpl.createMenu (RestroHub/src/main/java/com/restroly/qrmenu/menu/service/MenuServiceImpl.java:46-49).

The create flow should look up the Menu by menuId (throwing ResourceNotFoundException if absent) and attach it to the new Category (e.g. category.getMenu().add(menu) / menu.getCategories().add(category)) before saving, otherwise the scoped duplicate validation cannot work.

		if(categoryRepository.existsByNameIgnoreCaseAndMenu_MenuId(
				requestDTO.getName(), requestDTO.getMenuId())) {
			log.warn("Category with name '{}' already exists in menu '{}'", requestDTO.getName(), requestDTO.getMenuId());
			throw new DuplicateResourceException("Category with name '" + requestDTO.getName() + "' already exists");
		}
		Category category = CategoryDTO.toEntity(
				CategoryDTO.builder()
						.name(requestDTO.getName())
						.description(requestDTO.getDescription())
						.isDelete(false) // default value
						.updatedDate(LocalDateTime.now())
						.build()
		);

		Category savedCategory = categoryRepository.save(category);
		log.info("Category created with ID: {}", savedCategory.getCategoryId());
		return CategoryResponseDTO.fromEntity(savedCategory);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 169 to 177
if (updateDTO.getName() != null &&
!updateDTO.getName().equalsIgnoreCase(existingFood.getName()) &&
foodRepository.existsByNameIgnoreCase(updateDTO.getName())) {
!updateDTO.getName().equalsIgnoreCase(existingFood.getName()) &&
foodRepository.existsByNameIgnoreCaseAndCategory_CategoryId(
updateDTO.getName(), existingFood.getCategory().getCategoryId())) {

log.warn("Attempt to update food with duplicate name: {}", updateDTO.getName());
throw new ResourceAlreadyExistsException(
String.format(FOOD_EXISTS_MSG, updateDTO.getName()));
log.warn("Attempt to update food with duplicate name '{}' in category '{}'",
updateDTO.getName(), existingFood.getCategory().getCategoryId());
throw new ResourceAlreadyExistsException(String.format(FOOD_EXISTS_MSG, updateDTO.getName()));
}
@iRahmanG
Copy link
Copy Markdown
Contributor Author

Hi @rdodiya

I have fixed the changes as per review

  • Category creation now validates menu existence and links the category to the menu.
  • Food update now checks duplicates in the target category (from updateDTO if provided) and excludes the current foodId.

This resolves the duplicate validation issue mentioned in the review.

@rdodiya
Copy link
Copy Markdown
Owner

rdodiya commented May 25, 2026

Hi @iRahmanG,
No need to check duplicate category names within a menu. Instead, validate duplicate category names within the same branch.

While creating categories, we are not assigning them to any menu. Categories are created independently first, and later menus are created which can contain multiple categories belonging to the same branch.

image image

@iRahmanG
Copy link
Copy Markdown
Contributor Author

Hi @rdodiya
I’ve updated the logic as per your feedback.

  • Removed duplicate check within a menu
  • Added duplicate validation at the branch level instead
  • Categories are now created independently and linked to a branch, while menus can later include multiple categories from the same branch

Thanks for your feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants