Merge remote-tracking branch 'origin/develop-backend' into feature-backend-stock-service

feature-backend-stock-service
Masaharu.Kato 4 months ago
commit fa5cdd5056
  1. 7
      backend/src/main/java/com/example/todoapp/controller/ToBuysController.java
  2. 25
      backend/src/main/java/com/example/todoapp/dto/AddByRecipeDTO.java
  3. 11
      backend/src/main/java/com/example/todoapp/repository/ToBuysRepository.java
  4. 41
      backend/src/main/java/com/example/todoapp/service/ToBuysService.java
  5. 12
      frontend/src/components/Layout.tsx
  6. 38
      frontend/src/pages/AddRecipe.tsx
  7. 2
      frontend/src/pages/RecipeList.tsx
  8. 4
      frontend/src/services/api.ts

@ -7,6 +7,7 @@
package com.example.todoapp.controller; package com.example.todoapp.controller;
import com.example.todoapp.dto.AddByRecipeDTO;
import com.example.todoapp.dto.BuyRequestDTO; import com.example.todoapp.dto.BuyRequestDTO;
import com.example.todoapp.dto.DeleteToBuyRequestDTO; import com.example.todoapp.dto.DeleteToBuyRequestDTO;
import com.example.todoapp.dto.ToBuyResponseDTO; import com.example.todoapp.dto.ToBuyResponseDTO;
@ -181,12 +182,10 @@ public class ToBuysController {
*/ */
@PostMapping("/addByRecipe") @PostMapping("/addByRecipe")
public ResponseEntity<Map<String, Object>> addByRecipe( public ResponseEntity<Map<String, Object>> addByRecipe(
@RequestBody Map<String, Long> payload, @RequestBody AddByRecipeDTO addByRecipeDTO,
Authentication authentication) { Authentication authentication) {
Long recipeId = payload.get("recipeId"); List<ToBuyResponseDTO> responseList = toBuysService.addByRecipeId(addByRecipeDTO.getRecipeId(), addByRecipeDTO.getServings(), addByRecipeDTO.getDifference(), authentication);
Long servings = payload.get("servings");
List<ToBuyResponseDTO> responseList = toBuysService.addByRecipeId(recipeId, servings,authentication);
//shopのフィールドを削除 //shopのフィールドを削除
List<Map<String, Object>> filteredList = responseList.stream() List<Map<String, Object>> filteredList = responseList.stream()

@ -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
* <p>
* このクラスはクライアントとサーバー間で料理情報をやり取りするために使用されます
* エンティティとは異なり必要な情報のみを含み関連エンティティへの参照ではなくIDのみを保持します
* </p>
*/
@Data
public class AddByRecipeDTO {
private Long recipeId;
private Long servings;
private Boolean difference;
}

@ -47,6 +47,17 @@ public interface ToBuysRepository extends JpaRepository<ToBuys, Integer> {
*/ */
@Query("SELECT t FROM ToBuys t WHERE t.user.id = :userId ORDER BY t.tobuyId ASC") @Query("SELECT t FROM ToBuys t WHERE t.user.id = :userId ORDER BY t.tobuyId ASC")
List<ToBuys> findByUserIdOrderByTobuyIdAsc(@Param("userId") Long userId); List<ToBuys> 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に基づいて買うものリストを削除 * 指定された買うものIDに基づいて買うものリストを削除

@ -63,6 +63,9 @@ public class ToBuysService {
@Autowired @Autowired
private RecipeStuffsRepository RecipeStuffsRepository; private RecipeStuffsRepository RecipeStuffsRepository;
@Autowired
private StocksService stocksService;
/** /**
* 購入リストに新しいアイテムを追加する * 購入リストに新しいアイテムを追加する
* *
@ -171,6 +174,16 @@ public class ToBuysService {
return toBuysRepository.findByUserIdOrderByTobuyIdAsc(user.getId()); return toBuysRepository.findByUserIdOrderByTobuyIdAsc(user.getId());
} }
/**
* 指定された購入リストIDに基づいて数量を変更する
*
* @param tobuyId 購入リストID
*/
@Transactional
public int updateToBuysAmountByTobuyId(Long tobuyId, int amount) {
return toBuysRepository.updateAmountByTobuyId(tobuyId, amount);
}
/** /**
* 指定された購入リストIDに基づいて買うものを削除する * 指定された購入リストIDに基づいて買うものを削除する
* *
@ -207,9 +220,17 @@ public class ToBuysService {
stock.setBuyDate(dto.getBuyDate()); stock.setBuyDate(dto.getBuyDate());
stock.setExpDate(dto.getExpDate()); 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()); 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); return stocksRepository.save(stock);
@ -222,7 +243,7 @@ public class ToBuysService {
* @param authentication 認証情報 * @param authentication 認証情報
* @return 追加された買うもののリスト * @return 追加された買うもののリスト
*/ */
public List<ToBuyResponseDTO> addByRecipeId(Long recipeId, Long servings,Authentication authentication) { public List<ToBuyResponseDTO> addByRecipeId(Long recipeId, Long servings, Boolean difference, Authentication authentication) {
// ユーザー情報を取得 // ユーザー情報を取得
String username = authentication.getName(); String username = authentication.getName();
User user = userRepository.findByUsername(username) User user = userRepository.findByUsername(username)
@ -236,13 +257,23 @@ public class ToBuysService {
for (RecipeStuffs rs : recipeStuffsList) { for (RecipeStuffs rs : recipeStuffsList) {
Stuffs stuff = rs.getStuff(); Stuffs stuff = rs.getStuff();
// 材料の数量をサービング数に基づいて計算 // 材料の数量を在庫数とサービング数に基づいて計算
int stockAmount = stocksService.calcAmountByStuffId(user.getId(), stuff.getStuffId());
int requiredAmount = rs.getAmount() * (servings != null ? servings.intValue() : 1); int requiredAmount = rs.getAmount() * (servings != null ? servings.intValue() : 1);
if (difference) {
// 差分を計算
requiredAmount = requiredAmount - stockAmount;
if (requiredAmount <= 0) {
continue; // 在庫が十分なので追加しない
}
} else {
// 差分を考慮しない場合はrequiredAmountをそのまま使用
}
Optional<ToBuys> existingToBuyOpt = toBuysRepository.findByUserAndStuff(user, stuff); Optional<ToBuys> existingToBuyOpt = toBuysRepository.findByUserAndStuff(user, stuff);
ToBuys toBuy; ToBuys toBuy;
if (existingToBuyOpt.isPresent()) { if (existingToBuyOpt.isPresent()) { // 既存の「買うもの」がある場合
toBuy = existingToBuyOpt.get(); toBuy = existingToBuyOpt.get();
toBuy.setAmount(toBuy.getAmount() + requiredAmount); // 既存の数量を更新 toBuy.setAmount(toBuy.getAmount() + requiredAmount); // 既存の数量を更新
} else { } else {

@ -41,15 +41,19 @@ const Layout: React.FC = () => {
const getTabIndex = (pathname: string) => { const getTabIndex = (pathname: string) => {
pathname = pathname.split("/")[1];
console.log(pathname);
switch (pathname) { switch (pathname) {
case '/stock': case 'stock':
return 0; return 0;
case '/tasks': case 'tasks':
return 1; return 1;
case '/recipeList': case 'recipeList':
return 2;
case 'addRecipe':
return 2; return 2;
default: default:
return 0; return -1;
} }
}; };

@ -18,6 +18,8 @@ import {
DialogActions, DialogActions,
TextField, TextField,
Button, Button,
Checkbox,
FormControlLabel,
Box, Box,
} from '@mui/material'; } from '@mui/material';
import { import {
@ -27,7 +29,7 @@ import {
import AddStuffAmountDialog from '../components/AddStuffAmountDialog'; import AddStuffAmountDialog from '../components/AddStuffAmountDialog';
import { StuffAndCategoryAndAmount } from '../types/types'; import { StuffAndCategoryAndAmount } from '../types/types';
import EditAmountDialog from '../components/EditAmountDialog'; 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 { useNavigate, useParams } from 'react-router-dom';
import MessageAlert from '../components/MessageAlert'; import MessageAlert from '../components/MessageAlert';
import { useMessage } from '../components/MessageContext'; import { useMessage } from '../components/MessageContext';
@ -60,6 +62,8 @@ const AddRecipe: React.FC = () => {
const [editingItemIdx, setEditingItemIdx] = useState(0); const [editingItemIdx, setEditingItemIdx] = useState(0);
//削除確認ダイアログの表示状態 //削除確認ダイアログの表示状態
const [openDeleteDialog, setOpenDeleteDialog] = useState(false); const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
// チェックボックスが押されたかどうか
const [checked, setChecked] = useState(false);
// エラーメッセージ表示 // エラーメッセージ表示
const { showErrorMessage, showSuccessMessage } = useMessage(); const { showErrorMessage, showSuccessMessage } = useMessage();
@ -150,8 +154,32 @@ const AddRecipe: React.FC = () => {
console.log("yes2"); console.log("yes2");
return false; return false;
} }
await toBuyApi.addByRecipe(recipeId, numOfPeaple); const finalAddResult = await toBuyApi.addByRecipe(recipeId, numOfPeaple, checked);
showSuccessMessage('レシピが保存されて買うものリストに追加されました!'); 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'); navigate('/tasks');
} }
@ -337,6 +365,10 @@ const AddRecipe: React.FC = () => {
/> />
</div> </div>
<FormControlLabel
control={<Checkbox checked={checked} onChange={(e) => setChecked(e.target.checked)} />}
label={<Typography fontSize="clamp(8px, 1.5vw, 14px)"></Typography>}
/>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => setOpenNumOfPeapleDialog(false)}></Button> <Button onClick={() => setOpenNumOfPeapleDialog(false)}></Button>

@ -102,7 +102,7 @@ const RecipeList: React.FC = () => {
fontSize: "32px", left: "50%", transform: 'translateX(-50%)' fontSize: "32px", left: "50%", transform: 'translateX(-50%)'
}} }}
color="primary" color="primary"
onClick={() => navigate('/AddRecipe')} onClick={() => navigate('/addRecipe')}
> >
</Button> </Button>

@ -132,11 +132,11 @@ export const toBuyApi = {
}, },
addByRecipe: async (recipeId: number, servings: number): Promise<any> => { addByRecipe: async (recipeId: number, servings: number, difference: boolean): Promise<any> => {
const response = await fetch(`${API_BASE_URL}/api/tobuy/addByRecipe`, { const response = await fetch(`${API_BASE_URL}/api/tobuy/addByRecipe`, {
method: 'POST', method: 'POST',
headers: getHeaders(), headers: getHeaders(),
body: JSON.stringify({ recipeId, servings }), body: JSON.stringify({ recipeId, servings, difference }),
}) })
if (!response.ok) { if (!response.ok) {
throw new Error(TOBUY_ERRORS.CREATE_FAILED); throw new Error(TOBUY_ERRORS.CREATE_FAILED);

Loading…
Cancel
Save