오늘 할 일
(완료)1. 시험보기
(X_안하는 날이었다.) 2. 코드카타
(완료) 3. lv5 코드에 추가하기 (추가 요구 사항)
(완료) 4. 챌린지팀 회의 (주제, 앞으로의 방향성)
1. **메뉴 주문 API**를 여러개의 메뉴가 주문 가능하도록 수정해주세요.
2. **주문 내역 조회 API**가 특정 주문 상태만 필터링 가능하도록 개선해주세요.
3. 등록된 메뉴가 **판매 가능한 갯수**를 가질 수 있도록 수정해주세요.
만약, 판매 가능한 갯수가 존재하지 않는다면, 메뉴 상태를 **매진(`SOLD_OUT`)**으로 변경해주세요.
4. 프로젝트의 아키텍처 패턴을 **Layered Architecture Pattern**으로 수정해주세요.
위의 요구 사항 중 1-3번만 우선 적용했다.
메뉴 주문 api에서 추가한 기능은 1. 트랜잭션기능 2.재고관리 3. 재고가 없으면 status 업데이트.
우선 재고관리 기능을 먼저 만들었다. 재고가 있는지 없는지를 확인해야지 트랜잭션 기능이 잘 굴러가는지 확인할 수 있을 것 같았다.
menu테이블에 stock이라는 필드명을 추가해서 처음 메뉴 등록했을 때 stock 갯수를 받도록 했다.
menu의 stock 재고보다 작거나 같을때만 주문을 받아 menus의 테이블의 stock을 업데이트를 했다.
await에서 prisma대신 tx를 쓴건 트랜잭션때문에 그렇게 적은 것이다.
//재고관리: 입력받은 quantity보다 재고량이 같거나 많으면 업데이트 if (quantity <= menu.stock) { await tx.menus.update({ where: { menuId }, data: { stock: menu.stock - quantity }, }); } else { throw new Error( `menuId ${menu.menuId} 번의 재고 수량을 확인하십시오.`, ); // return res.status(400).json({ message: '재고 수량을 확인하세요.' }); }
그리고 주문을 하면서 재고가 0이 된 메뉴는 menus의 status가 sold_out을 표시해야해서 주문등록하는 트랜잭션 안에 포함시켰다.
if (menu.stock - quantity === 0) { await tx.menus.update({ where: { menuId }, data: { status: 'SOLD_OUT' }, }); }
그리고 트랜잭션을 만들었다. 우선 오류를 잡는 try catch문 전에 transaction을 초기화시켜준다. 그 이유는 아래에 catch문에서 transaction을 사용해야하기때문이다. 트랜잭션을 아래와 같이 사용하면 transaction 안에 트랜잭션을 실행한 결과가 담기게 된다.
그럼 try문 안에서 로직을 타면서 만약 오류가 나게되면 catch으로 가게되는데 그 안에 transaction이 만약 (잘못된)내용물이 있다면 롤백하도록 구성하였다. 없다면 트랜잭션의 원자성을 해치지않기때문에 그냥 에러메시지를 띄운다.
/** 여러개 메뉴 주문 **/ router.post('/orders', authMiddleware, async (req, res, next) => { let transaction; try { const orders = req.body; const { userId, type } = req.user; if (type !== checkType.CUSTOMER) { return res .status(400) .json({ message: '소비자만 사용할 수 있는 API입니다.' }); } //여러개 중 하나라도 오류 뜨면 뒤로 돌려야한다. transaction = await prisma.$transaction(async (tx) => { for (const order of orders) { const validation = await createOrders.validateAsync(order); const { menuId, quantity } = validation; const menu = await tx.menus.findFirst({ where: { menuId }, }); //재고관리: 입력받은 quantity보다 재고량이 같거나 많으면 업데이트 if (quantity <= menu.stock) { await tx.menus.update({ where: { menuId }, data: { stock: menu.stock - quantity }, }); } else { throw new Error( `menuId ${menu.menuId} 번의 재고 수량을 확인하십시오.`, ); // return res.status(400).json({ message: '재고 수량을 확인하세요.' }); } if (menu.stock - quantity === 0) { await tx.menus.update({ where: { menuId }, data: { status: 'SOLD_OUT' }, }); } const totalPrice = await tx.$queryRaw`SELECT SUM(price * ${quantity}) AS totalPrice FROM Menus WHERE menuId = ${menuId}`; await tx.orders.create({ data: { User: { connect: { userId: Number(userId), }, }, Menu: { connect: { menuId: Number(menuId), }, }, quantity, orderedAt: new Date(), totalPrice: totalPrice[0].totalPrice, }, }); } }); return res.status(200).json({ message: '메뉴 주문이 완료되었습니다.' }); } catch (error) { console.log(error); if (transaction) { await prisma.$executeRaw`ROLLBACK`; } next(error); } });
사장님 주문 내역 전체 조회와 별도로 주문 상태에 따라 볼 수 있는 페이지를 만들었다.
기존 사장님 주문 내역 전체 조회 페이지에서 params를 추가해서 구현했다.
const orderStatus = ['PENDING', 'ACCEPTED', 'CANCEL']; /** (특정상태) 사장님 주문 내역 조회 **/ router.get('/orders/owner/:status', authMiddleware, async (req, res, next) => { try { const { status } = req.params; const { type } = req.user; if (type !== checkType.OWNER) { return res .status(400) .json({ message: '사장님만 사용할 수 있는 API입니다.' }); } if (!orderStatus.includes(status.toUpperCase())) { return res.status(400).json({ message: 'PENDING, ACCEPTED, CANCEL 중 상태를 잘 입력해주세요.', }); } const orders = await prisma.orders.findMany({ orderBy: { orderedAt: 'desc' }, where: { status }, select: { UserId: true, User: { select: { nickname: true } }, Menu: { select: { name: true, price: true } }, orderId: true, quantity: true, orderedAt: true, totalPrice: true, status: true, }, }); if (!orders) { return res.status(400).json({ message: '주문이 존재하지않습니다.' }); } return res.status(200).json({ data: orders }); } catch (error) { next(error); } });