diff --git a/backend/src/main/java/com/example/todoapp/controller/ToBuysController.java b/backend/src/main/java/com/example/todoapp/controller/ToBuysController.java index e263330..35844f7 100644 --- a/backend/src/main/java/com/example/todoapp/controller/ToBuysController.java +++ b/backend/src/main/java/com/example/todoapp/controller/ToBuysController.java @@ -7,6 +7,7 @@ package com.example.todoapp.controller; +import com.example.todoapp.dto.AddByRecipeDTO; import com.example.todoapp.dto.BuyRequestDTO; import com.example.todoapp.dto.DeleteToBuyRequestDTO; import com.example.todoapp.dto.ToBuyResponseDTO; @@ -181,12 +182,10 @@ public class ToBuysController { */ @PostMapping("/addByRecipe") public ResponseEntity> addByRecipe( - @RequestBody Map payload, + @RequestBody AddByRecipeDTO addByRecipeDTO, Authentication authentication) { - Long recipeId = payload.get("recipeId"); - Long servings = payload.get("servings"); - List responseList = toBuysService.addByRecipeId(recipeId, servings,authentication); + List responseList = toBuysService.addByRecipeId(addByRecipeDTO.getRecipeId(), addByRecipeDTO.getServings(), addByRecipeDTO.getDifference(), authentication); //shopのフィールドを削除 List> filteredList = responseList.stream() diff --git a/backend/src/main/java/com/example/todoapp/dto/AddByRecipeDTO.java b/backend/src/main/java/com/example/todoapp/dto/AddByRecipeDTO.java new file mode 100644 index 0000000..5fef152 --- /dev/null +++ b/backend/src/main/java/com/example/todoapp/dto/AddByRecipeDTO.java @@ -0,0 +1,25 @@ +//-------------------------------- +// AddByRecipeDTO.java +// +// +// 更新履歴:2025/06/18 新規作成 +// Copyright(c) 2025 IVIS All rights reserved. +//-------------------------------------------- +package com.example.todoapp.dto; + +import lombok.Data; + +/** + * 料理からの買うものリスト追加データ転送オブジェクト(DTO) + *

+ * このクラスはクライアントとサーバー間で料理情報をやり取りするために使用されます。 + * エンティティとは異なり、必要な情報のみを含み、関連エンティティへの参照ではなくIDのみを保持します。 + *

+ */ + +@Data +public class AddByRecipeDTO { + private Long recipeId; + private Long servings; + private Boolean difference; +} \ No newline at end of file diff --git a/backend/src/main/java/com/example/todoapp/repository/ToBuysRepository.java b/backend/src/main/java/com/example/todoapp/repository/ToBuysRepository.java index 0778f5f..24d464f 100644 --- a/backend/src/main/java/com/example/todoapp/repository/ToBuysRepository.java +++ b/backend/src/main/java/com/example/todoapp/repository/ToBuysRepository.java @@ -47,6 +47,17 @@ public interface ToBuysRepository extends JpaRepository { */ @Query("SELECT t FROM ToBuys t WHERE t.user.id = :userId ORDER BY t.tobuyId ASC") List findByUserIdOrderByTobuyIdAsc(@Param("userId") Long userId); + + /** + * 指定された「買うもの」IDに基づいて「買うもの」での数量を更新 + * + * @param tobuyId 「買うもの」ID + * @param amount 「買うもの」数量 + * @return 更新された行数 + */ + @Modifying + @Query("UPDATE ToBuys t SET t.amount = :amount WHERE t.tobuyId = :tobuyId") + int updateAmountByTobuyId(Long tobuyId, int amount); /** * 指定された「買うもの」IDに基づいて「買うもの」リストを削除 diff --git a/backend/src/main/java/com/example/todoapp/service/ToBuysService.java b/backend/src/main/java/com/example/todoapp/service/ToBuysService.java index 2c0cc2a..ddf5681 100644 --- a/backend/src/main/java/com/example/todoapp/service/ToBuysService.java +++ b/backend/src/main/java/com/example/todoapp/service/ToBuysService.java @@ -63,6 +63,9 @@ public class ToBuysService { @Autowired private RecipeStuffsRepository RecipeStuffsRepository; + @Autowired + private StocksService stocksService; + /** * 購入リストに新しいアイテムを追加する * @@ -171,6 +174,16 @@ public class ToBuysService { return toBuysRepository.findByUserIdOrderByTobuyIdAsc(user.getId()); } + /** + * 指定された購入リストIDに基づいて「数量」を変更する + * + * @param tobuyId 購入リストID + */ + @Transactional + public int updateToBuysAmountByTobuyId(Long tobuyId, int amount) { + return toBuysRepository.updateAmountByTobuyId(tobuyId, amount); + } + /** * 指定された購入リストIDに基づいて「買うもの」を削除する * @@ -207,9 +220,17 @@ public class ToBuysService { stock.setBuyDate(dto.getBuyDate()); stock.setExpDate(dto.getExpDate()); - // 買うものリストから削除 + // まだ買うべき数量を計算 + int remainAmount = Math.max(tobuy.getAmount() - dto.getAmount(), 0); + System.out.println("remainAmount=" + remainAmount); + + // 買うものリストから削除または数量変更 System.out.println("tobuy.getTobuyId()=" + tobuy.getTobuyId()); - deleteToBuysByTobuyId(tobuy.getTobuyId()); + if (remainAmount > 0) { + updateToBuysAmountByTobuyId(tobuy.getTobuyId(), remainAmount); + } else { + deleteToBuysByTobuyId(tobuy.getTobuyId()); + } // データベースに保存 return stocksRepository.save(stock); @@ -222,7 +243,7 @@ public class ToBuysService { * @param authentication 認証情報 * @return 追加された「買うもの」のリスト */ - public List addByRecipeId(Long recipeId, Long servings,Authentication authentication) { + public List addByRecipeId(Long recipeId, Long servings, Boolean difference, Authentication authentication) { // ユーザー情報を取得 String username = authentication.getName(); User user = userRepository.findByUsername(username) @@ -236,13 +257,23 @@ public class ToBuysService { for (RecipeStuffs rs : recipeStuffsList) { Stuffs stuff = rs.getStuff(); - // 材料の数量をサービング数に基づいて計算 + // 材料の数量を在庫数とサービング数に基づいて計算 + int stockAmount = stocksService.calcAmountByStuffId(user.getId(), stuff.getStuffId()); int requiredAmount = rs.getAmount() * (servings != null ? servings.intValue() : 1); + if (difference) { + // 差分を計算 + requiredAmount = requiredAmount - stockAmount; + if (requiredAmount <= 0) { + continue; // 在庫が十分なので追加しない + } + } else { + // 差分を考慮しない場合はrequiredAmountをそのまま使用 + } Optional existingToBuyOpt = toBuysRepository.findByUserAndStuff(user, stuff); ToBuys toBuy; - if (existingToBuyOpt.isPresent()) { + if (existingToBuyOpt.isPresent()) { // 既存の「買うもの」がある場合 toBuy = existingToBuyOpt.get(); toBuy.setAmount(toBuy.getAmount() + requiredAmount); // 既存の数量を更新 } else { diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index fe8bff1..7e1c73f 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -41,15 +41,19 @@ const Layout: React.FC = () => { const getTabIndex = (pathname: string) => { + pathname = pathname.split("/")[1]; + console.log(pathname); switch (pathname) { - case '/stock': + case 'stock': return 0; - case '/tasks': + case 'tasks': return 1; - case '/recipeList': + case 'recipeList': + return 2; + case 'addRecipe': return 2; default: - return 0; + return -1; } }; diff --git a/frontend/src/pages/AddRecipe.tsx b/frontend/src/pages/AddRecipe.tsx index 75678cf..ecff456 100644 --- a/frontend/src/pages/AddRecipe.tsx +++ b/frontend/src/pages/AddRecipe.tsx @@ -18,6 +18,8 @@ import { DialogActions, TextField, Button, + Checkbox, + FormControlLabel, Box, } from '@mui/material'; import { @@ -27,7 +29,7 @@ import { import AddStuffAmountDialog from '../components/AddStuffAmountDialog'; import { StuffAndCategoryAndAmount } from '../types/types'; import EditAmountDialog from '../components/EditAmountDialog'; -import { recipeApi, toBuyApi } from '../services/api'; +import { recipeApi, toBuyApi, stockApi } from '../services/api'; import { useNavigate, useParams } from 'react-router-dom'; import MessageAlert from '../components/MessageAlert'; import { useMessage } from '../components/MessageContext'; @@ -60,6 +62,8 @@ const AddRecipe: React.FC = () => { const [editingItemIdx, setEditingItemIdx] = useState(0); //削除確認ダイアログの表示状態 const [openDeleteDialog, setOpenDeleteDialog] = useState(false); + // チェックボックスが押されたかどうか + const [checked, setChecked] = useState(false); // エラーメッセージ表示 const { showErrorMessage, showSuccessMessage } = useMessage(); @@ -150,8 +154,32 @@ const AddRecipe: React.FC = () => { console.log("yes2"); return false; } - await toBuyApi.addByRecipe(recipeId, numOfPeaple); - showSuccessMessage('レシピが保存されて買うものリストに追加されました!'); + const finalAddResult = await toBuyApi.addByRecipe(recipeId, numOfPeaple, checked); + console.log(finalAddResult) + // const recipeStuffInfo = (await recipeApi.getById(recipeId)).stuffAndAmountArray; + // const recipeStuffId = recipeStuffInfo.map(item => item.stuffId); + // console.log(recipeStuffId); + // const stockStuff = await stockApi.getStocks(); + // const stockedStuffId = stockStuff.filter(stock => recipeStuff.stuffAndAmountArray).map(item => item.id); + // const stockedStuffAmountRecipe = recipeStuff.filter(recipe => (stockStuff.some(stuff => recipe.stuffId === stuff.stuffId))); + // console.log(stockedStuffAmountRecipe) + // const stockedStuffAmountStock = stockStuff.filter(stock => (stockedStuffAmountRecipe.some(stocked => stock.stuffId === stocked.stuffId))); + + // recipeStuff.map(item => { + // await Promise.all(stockStuff.map(async stock => { + // if (item.stuffId == stock.stuffId) + // await toBuyApi.updateToBuy({stuffName: item.stuffName, + // amount: item.amount - stock.amount, + // tobuyId: item.tobuyId, + // stuffId: item.stuffId}); + // })) + // }); + if (finalAddResult.data.length === 0) { + showSuccessMessage('必要な食材が在庫にあったのでリストには追加されませんでした!'); + } + else { + showSuccessMessage('レシピが保存されて買うものリストに追加されました!'); + } navigate('/tasks'); } @@ -337,6 +365,10 @@ const AddRecipe: React.FC = () => { /> + setChecked(e.target.checked)} />} + label={足りない食材のみ登録} + /> diff --git a/frontend/src/pages/RecipeList.tsx b/frontend/src/pages/RecipeList.tsx index f5f6f11..bcdce5a 100644 --- a/frontend/src/pages/RecipeList.tsx +++ b/frontend/src/pages/RecipeList.tsx @@ -102,7 +102,7 @@ const RecipeList: React.FC = () => { fontSize: "32px", left: "50%", transform: 'translateX(-50%)' }} color="primary" - onClick={() => navigate('/AddRecipe')} + onClick={() => navigate('/addRecipe')} > 新しい料理を追加 diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 1bfa9ee..7ec10a3 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -132,11 +132,11 @@ export const toBuyApi = { }, - addByRecipe: async (recipeId: number, servings: number): Promise => { + addByRecipe: async (recipeId: number, servings: number, difference: boolean): Promise => { const response = await fetch(`${API_BASE_URL}/api/tobuy/addByRecipe`, { method: 'POST', headers: getHeaders(), - body: JSON.stringify({ recipeId, servings }), + body: JSON.stringify({ recipeId, servings, difference }), }) if (!response.ok) { throw new Error(TOBUY_ERRORS.CREATE_FAILED);