본문 바로가기

내일 배움 캠프/kiosk

kiosk 4일차 7/28 상품 발주

728x90

0. Directory Structure

kiosk
├─ .prettierrc.cjs
├─ README.md
├─ migrations
├─ package-lock.json
├─ package.json
├─ seeders
└─ src
   ├─ app.js
   ├─ controllers
   │  ├─ itmes.controller.js
   │  └─ order_items.controller.js
   ├─ db
   │  ├─ index.js
   │  ├─ models
   │  │  ├─ items.js
   │  │  └─ order_items.js
   │  ├─ relations
   │  │  ├─ index.js
   │  │  ├─ items.relation.js
   │  │  └─ order_items.relation.js
   │  └─ sequelize.js
   ├─ init.js
   ├─ repositories
   │  ├─ items.repository.js
   │  └─ order_items.repository.js
   ├─ routes
   │  ├─ items.route.js
   │  └─ order_items.route.js
   └─ services
      ├─ items.service.js
      ├─ message.js
      └─ order_items.repository.js

1. 발주 테이블 스키마

1-1. src/db/models/order_items.js

import { Model, DataTypes } from 'sequelize';
import sequelize from '../sequelize.js';

class Order_Items extends Model {}

Order_Items.init(
  {
    id: { type: DataTypes.BIGINT, autoIncrement: true, primaryKey: true },
    item_id: DataTypes.BIGINT,
    amount: { type: DataTypes.BIGINT, defaultValue: 0 },
    state: { type: DataTypes.BIGINT, defaultValue: 0 },
    createdAt: {
      type: DataTypes.DATE,
      defaultValue: DataTypes.NOW,
    },
    updatedAt: {
      type: DataTypes.DATE,
      defaultValue: DataTypes.NOW,
    },
  },
  {
    sequelize,
    modelName: 'Order_Items',
  },
);

export default Order_Items;

1-2. src/db/relations/items.relation.js

import Order_Items from '../models/order_items.js';
import Items from '../models/items.js';

export default () => {
  Items.hasMany(Order_Items, {
    sourceKey: 'id',
    foreignKey: 'item_id',
  });
};

 1-3. src/db/relations/order_items.relation.js

import Order_Items from '../models/order_items.js';
import Items from '../models/items.js';

export default () => {
  Order_Items.belongsTo(Items, {
    targetKey: 'id',
    foreignKey: 'item_id',
  });
};

1-4. src/db/relation/index.js

import ItemsRelations from './items.relation.js';
import Order_ItemsRelations from './order_items.relation.js';

export default {
  ItemsRelations,
  Order_ItemsRelations,
};

1-5. src/db/index.js 수정

import sequelize from './sequelize.js';
import Items from './models/items.js';
import Order_Items from './models/order_items.js';
import Relations from './relations/index.js';

Object.values(Relations).forEach(relationsFunction => {
  relationsFunction();
});

export { sequelize, Items, Order_Items };

1-6. src/db/models/items.js 수정

import { Model, DataTypes } from 'sequelize';
import sequelize from '../sequelize.js';

class Items extends Model {}

Items.init(
  {
    id: { type: DataTypes.BIGINT, autoIncrement: true, primaryKey: true },
    name: DataTypes.STRING,
    option_id: { type: DataTypes.BIGINT, defaultValue: 0 },
    price: DataTypes.BIGINT,
    type: { type: DataTypes.ENUM, values: ['COFFEE', 'JUICE', 'FOOD'] },
    amount: { type: DataTypes.BIGINT, defaultValue: 0 },
    createdAt: {
      type: DataTypes.DATE,
      defaultValue: DataTypes.NOW,
    },
    updatedAt: {
      type: DataTypes.DATE,
      defaultValue: DataTypes.NOW,
    },
  },
  {
    sequelize,
    modelName: 'Items',
  },
);

export default Items;

1-7. init.js :27 sequelize.sync({force : true})를 통해 스키마 만들기

 

2. 상품 발주 API

2-1. order_items.route.js

import { Router } from 'express';
import Order_ItemsController from '../controllers/order_items.controller.js';

const router = Router();

const order_itemsController = new Order_ItemsController();

router.post('/items/:item_id/orders', order_itemsController.makeOrder);

export default router;

2-2. app.js

import express from 'express';
import itemsRouter from './routes/items.route.js';
import order_itemsRouter from './routes/order_items.route.js';

export class ExpressApp {
  app = express();

  constructor() {
    this.setAppSettings();
    this.setAppRouter();
  }

  setAppSettings = () => {
    this.app.use(express.json());
  };
  setAppRouter = () => {
    this.app.use(
      '/api',
      [itemsRouter, order_itemsRouter],
      (error, request, response, next) => {
        response.status(400).json({
          success: false,
          error: error.message,
        });
      },
    );

    this.app.use('/ping', (req, res, next) => {
      return res.status(200).json({ message: 'pong' });
    });
  };
}

2-3. order_items.controller.js

import Order_ItemsService from '../services/order_items.service.js';

class Order_ItemsController {
  order_itemsService = new Order_ItemsService();

  makeOrder = async (req, res) => {
    const { item_id } = req.params;
    const { amount } = req.body;

    const { status, message, order } = await this.order_itemsService.makeOrder(
      item_id,
      amount,
    );
    return res.status(status).json({ message, order });
  };
}
export default Order_ItemsController;

2-4. order_items.service.js

import Messages from './message.js';
import Order_ItemsRepository from '../repositories/order_items.repository.js';
const noid = new Messages('상품 id');
const noamount = new Messages('수량');
class Order_ItemsService {
  order_itemRepository = new Order_ItemsRepository();
  makeOrder = async (item_id, amount) => {
    const messages = new Messages('상품 발주');

    try {
      if (!item_id) {
        return noid.nosubject();
      } else if (!amount) {
        return noamount.nosubject();
      }

      const order = await this.order_itemRepository.makeOrder(item_id, amount);
      if (order) {
        return {
          status: 200,
          message: '상품 발주에 성공하였습니다.',
          order,
        };
      } else {
        return messages.status400();
      }
    } catch (err) {
      console.log(err);
      return messages.status400();
    }
  };
}
export default Order_ItemsService;

2-5. order_items.repository.js

import Order_Items from '../db/models/order_items.js';

class Order_ItemsRepository {
  makeOrder = async (item_id, amount) => {
    const order = await Order_Items.create({
      item_id,
      amount,
    });

    return order;
  };
}
export default Order_ItemsRepository;

3. 발주 상태 수정 API & enum.js 추가

enum.js

const itemTypes = {
  coffee: 'COFFEE',
  juice: 'JUICE',
  food: 'FOOD',
};

const orderItemState = {
  ORDERED: 0,
  PENDING: 1,
  COMPLETED: 2,
  CANCELED: 3,
};

export default { itemTypes, orderItemState };

items.service.js

import Messages from './message.js';
import ItemsRepository from '../repositories/items.repository.js';
import Enum from '../db/models/enum.js';
const noname = new Messages('이름');
const noprice = new Messages('가격');
class ItemsService {
  itemsRepository = new ItemsRepository();
  makeItem = async (name, price, type) => {
    const messages = new Messages('상품 추가');

    try {
      if (!name.length) {
        return noname.nosubject();
      } else if (!price) {
        return noprice.nosubject();
      } else if (Enum.itemTypes[type] == undefined) {
        return {
          status: 400,
          message: '알맞은 타입을 지정해 주세요.',
        };
      }

      const item = await this.itemsRepository.makeItem(
        name,
        price,
        Enum.itemTypes[type],
      );
      if (item.name) {
        return messages.status200();
      } else {
        return messages.status400();
      }
    } catch (err) {
      console.log(err);
      return messages.status400();
    }
  };
  getItemList = async category => {
    try {
      if (category == 'all') {
        const allItemList = await this.itemsRepository.getAllItemList();
        return {
          status: 200,
          message: '전체 상품이 조회되었습니다.',
          list: allItemList,
        };
      } else {
        const itemList = await this.itemsRepository.getItemList(category);
        return {
          status: 200,
          message: `${category} 타입의 상품이 조회되었습니다.`,
          list: itemList,
        };
      }
    } catch (err) {
      return {
        status: 400,
        message: '상품 조회에 실패하였습니다.',
        list: null,
      };
    }
  };
  removeItem = async id => {
    const messages = new Messages('상품 삭제');
    try {
      const checkItem = await this.itemsRepository.checkamount(id);
      if (checkItem.amount > 0) {
        return {
          status: 200,
          message: '현재 수량이 남아있습니다. 삭제하시겠습니까?',
        };
      } else if (checkItem.amount == 0) {
        const removeItem = await this.itemsRepository.removeItem(id);
        if (removeItem) {
          return messages.status200();
        }
      }
    } catch (err) {
      return messages.status400();
    }
  };
  answerRemoveItem = async (id, answer) => {
    const messages = new Messages('상품 삭제');
    try {
      if (answer == '예') {
        const removeItem = await this.itemsRepository.removeItem(id);
        if (removeItem) {
          return messages.status200();
        }
      } else {
        return messages.status400();
      }
    } catch (err) {
      return messages.status400();
    }
  };
  editItem = async (id, name, price) => {
    const messages = new Messages('상품 수정');

    try {
      if (!name.length) {
        return noname.nosubject();
      } else if (!price) {
        return noprice.nosubject();
      } else if (price < 0) {
        return {
          status: 400,
          message: '알맞은 가격을 입력해주세요.',
        };
      }

      const item = await this.itemsRepository.editItem(id, name, price);
      if (item) {
        return messages.status200();
      } else {
        return messages.status400();
      }
    } catch (err) {
      return messages.status400();
    }
  };
}
export default ItemsService;

order_items.controller.js

import Order_ItemsService from '../services/order_items.service.js';

class Order_ItemsController {
  order_itemsService = new Order_ItemsService();

  makeOrder = async (req, res) => {
    const { item_id } = req.params;
    const { amount } = req.body;

    const { status, message, order } = await this.order_itemsService.makeOrder(
      item_id,
      amount,
    );
    return res.status(status).json({ message, order });
  };
  editOrderState = async (req, res) => {
    const { item_id, id } = req.params;
    const { state } = req.body;

    const { status, message } = await this.order_itemsService.editOrderState(
      item_id,
      id,
      state,
    );
    return res.status(status).json({ message });
  };
}
export default Order_ItemsController;

order_items.service.js

import Messages from './message.js';
import Order_ItemsRepository from '../repositories/order_items.repository.js';
import Enum from '../db/models/enum.js';
const noid = new Messages('상품 id');
const noamount = new Messages('수량');
const noorderid = new Messages('발주 id');
const nostate = new Messages('상품 상태');
const noenumstate = new Messages('정확한 상품 상태');
class Order_ItemsService {
  order_itemRepository = new Order_ItemsRepository();
  makeOrder = async (item_id, amount) => {
    const messages = new Messages('상품 발주');

    try {
      if (!item_id) {
        return noid.nosubject();
      } else if (!amount) {
        return noamount.nosubject();
      }

      const order = await this.order_itemRepository.makeOrder(item_id, amount);
      if (order) {
        return {
          status: 200,
          message: '상품 발주에 성공하였습니다.',
          order,
        };
      } else {
        return messages.status400();
      }
    } catch (err) {
      console.log(err);
      return messages.status400();
    }
  };
  editOrderState = async (item_id, id, state) => {
    const messages = new Messages('상품 발주 상태 수정');
    try {
      if (!item_id) {
        return noid.nosubject();
      } else if (!id) {
        return noorderid.nosubject();
      } else if (!state) {
        return nostate.nosubject();
      } else if (Enum.orderItemState[state] == undefined) {
        return noenumstate.nosubject();
      }

      const prevstate = await this.order_itemRepository.stateChecker(
        id,
        item_id,
      );

      if (!prevstate.item_id) {
        return messages.status400;
      }

      if (
        (prevstate.state == 0 && state == 'PENDING') ||
        (prevstate.state == 1 && state == 'CANCELED') ||
        (prevstate.state == 0 && state == 'CANCELED')
      ) {
        const nextstate = await this.order_itemRepository.editOrderState(
          id,
          Enum.orderItemState[state],
        );
        return messages.status200();
      } else if (prevstate.state == 1 && state == 'COMPLETED') {
        const item = await this.order_itemRepository.ItemChecker(item_id);
        const updateamount = item.amount + prevstate.amount;
        const pendingToCompleted =
          await this.order_itemRepository.pendingToCompleted(
            id,
            item_id,
            Enum.orderItemState[state],
            updateamount,
          );
        if (pendingToCompleted.result == 1) {
          return messages.status200();
        }
      } else if (
        (prevstate.state == 2 && state == 'CANCELED') ||
        (prevstate.state == 2 && state == 'PENDING') ||
        (prevstate.state == 2 && state == 'ORDERED')
      ) {
        const item = await this.order_itemRepository.ItemChecker(item_id);
        const updateamount = item.amount - prevstate.amount;
        if (updateamount < 0) {
          return {
            status: 400,
            message: '현재 수량이 발주 수량보다 적어 발주 취소가 불가능합니다.',
          };
        }
        const pendingToCompleted =
          await this.order_itemRepository.pendingToCompleted(
            id,
            item_id,
            Enum.orderItemState[state],
            updateamount,
          );
        if (pendingToCompleted.result == 1) {
          return messages.status200();
        }
      } else {
        messages.status400();
      }
    } catch (err) {
      console.log(err);
      return messages.status400();
    }
  };
}
export default Order_ItemsService;

order_items.repository.js

import Order_Items from '../db/models/order_items.js';
import Items from '../db/models/items.js';
import { Transaction } from 'sequelize';
import sequelize from '../db/sequelize.js';

class Order_ItemsRepository {
  makeOrder = async (item_id, amount) => {
    const order = await Order_Items.create({
      item_id,
      amount,
    });

    return order;
  };
  stateChecker = async (id, item_id) => {
    const prevstate = await Order_Items.findOne({ where: { item_id, id } });
    return prevstate;
  };
  editOrderState = async (id, state) => {
    const nextstate = await Order_Items.update({ state }, { where: { id } });
    return nextstate;
  };

  ItemChecker = async item_id => {
    const item = await Items.findByPk(item_id);
    return item;
  };

  pendingToCompleted = async (id, item_id, state, updateamount) => {
    const t = await sequelize.transaction({
      isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED,
    });
    try {
      const orderUpdate = await Order_Items.update(
        { state },
        { where: { id } },
        { transaction: t },
      );

      const itemUpdate = await Items.update(
        { amount: updateamount },
        { where: { id: item_id } },
        { transaction: t },
      );
      await t.commit();
      return { result: 1, nextstate: orderUpdate };
    } catch (err) {
      console.log(err);
      await t.rollback();
      return { result: 0, nextstate: null };
    }
  };
}
export default Order_ItemsRepository;

order_items.route.js

import { Router } from 'express';
import Order_ItemsController from '../controllers/order_items.controller.js';

const router = Router();

const order_itemsController = new Order_ItemsController();

router.post('/items/:item_id/orders', order_itemsController.makeOrder);
router.patch(
  '/items/:item_id/orders/:id',
  order_itemsController.editOrderState,
);

export default router;