목차
- 📌 프로젝트 소개
- 📰 제작기간 & 팀원 소개
- ⛏ 기술 Stack
- 🏘️ 프로젝트 별 repository
- 🌸 아키텍쳐
- ⚙️ ERD
- 🪡 application.properties
- 🏗️ API 설계
- 📔 API 명세서
- ✔ 주요 기능
- 🖼️ 스크린샷
- 🌋 트러블 슈팅
- 😄 Commit Convention
- 테이블 오더 & 가맹점 & 본사(인트라넷) 연계 프로젝트
- 2024-06-19. ~ 2024-07-12.
이름 | 담당 기능 구현 |
---|---|
김예린 | 인트라넷 - 팀장, 가맹점관리(CRUD), 메뉴관리(CRUD), 매출관리(CRUD), 재고관리(CRUD) |
나소림 | 가맹점 - 가맹점 프로젝트 구조 및 데이터베이스 설계, 재고관리(CRUD), 매출관리(CRUD), 메뉴관리(CRUD), 지점장POS & 관리자용POS 웹 개발 |
문승환 | 인트라넷 - 인사관리(CRUD), 로그인(JWT), 메신저(CRUD) |
박민규 | 인트라넷 - 캘린더관리(CRUD), 일정관리(CRUD), 대시보드(CRUD), 디자인 |
이윤재 | 인트라넷 - 전자결재(CRUD), 대시보드(CRUD), 알림(CRUD) |
임주연 | 가맹점 - 부팀장, 가맹점 프로젝트 구조 및 데이터베이스 설계, 관리자관리, 가맹점관리(CRUD), 테이블관리(CRUD), 주문관리(CRUD), 테이블오더 앱 디자인 및 개발, SWAGGER & README.md 문서화 |
- Notion
- Git
- GitHub
- Slack
- Java 17
- Spring Boot 3.3.1
- Database : Oracle 11g
- Security : Spring Security, JWT
- JPA(Hibernate)
- Maven
- springdoc(Swagger) 2.5.0
- Kotlin 1.9.0
- Jetpack Compose
- Retrofit2, okthttp3, gson
- Database : Room
- JavaScript
- React
- axios
- mui
https://github.com/for-a-day/store-back
https://github.com/for-a-day/store-moblie
https://github.com/for-a-day/store-front
spring.application.name=franchise-back
server.port=9001
# Encoding : UTF-8
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
# DB : Oracle database
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
spring.datasource.username=name
spring.datasource.password=password
# JPA
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
# Thymeleaf
spring.thymeleaf.cache=false
spring.thymeleaf.check-template-location=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
# springdoc(Swagger)
springdoc.packages-to-scan=com.nagane.franchise
springdoc.default-consumes-media-type=application/json;charset=UTF-8
springdoc.default-produces-media-type=application/json;charset=UTF-8
springdoc.swagger-ui.path=/swagger-ui
springdoc.swagger-ui.enabled=true
springdoc.api-docs.path=/api-docs
springdoc.api-docs=true
springdoc.swagger-ui.disable-swagger-default-url=true
springdoc.swagger-ui.display-request-duration=true
springdoc.swagger-ui.operations-sorter=alpha
# front server port
intranet.port=3000
pos.port=3001
# jwt
jwt.secret=if_you_want_then_fill_it
API 목록
API 명칭 | HTTP 메서드 | 엔드포인트 | 설명 |
---|---|---|---|
가맹점 로그인 | POST | /login | 가맹점주가 포스기에 로그인하기 위해 사용하는 API입니다. |
테이블 목록 조회 | GET | /table?storeNo={storeNo} | 해당 가맹점 테이블 목록을 조회합니다. |
테이블 신규 등록 | POST | /table | 해당 가맹점 테이블을 신규 등록합니다. |
현재 주문 목록 조회 | GET | /order?storeNo={storeNo} | 현재 가맹점 내의 모든 진행 중인 주문 목록을 조회합니다. (이미 완료되거나 환불된 주문은 조회 X) |
(테이블 별) 현재 주문 목록 조회 | GET | /table/order?tableNo={tableNo} | 선택한 테이블 모든 진행 중인 주문 목록을 조회합니다. |
테이블 수정 | PUT | /table | 선택한 테이블 정보를 수정합니다. |
현재 테이블 주문 완료 처리 | PUT | /table/clear | 현재 테이블 내 주문 내역(0(주문 완료), 1(음식 나감)만 해당. 환불 제외) 다 2(손님 나감)로 수정합니다. |
테이블 삭제 | DELETE | /table | 선택한 테이블을 삭제합니다. (현재 진행 중인 주문 있을 시, 삭제 불가) |
주문 상태 변경 | PUT | /order/state | 주문의 상태를 변경합니다. |
선택한 주문 상세 정보 조회 | GET | /order?orderNo={orderNo} | 선택한 주문의 단일 상세 정보를 조회합니다. |
결제 목록 조회 (영수증) | GET | /payment?storeNo={storeNo} | 해당 가맹점의 당일 결제 목록을 조회합니다. |
(단일) 결제 반품 | PUT | /payment?orderNo={orderNo} | 단일 결제 항목을 반품합니다. |
가맹점별 재고 목록 조회 | GET | /stock?storeNo={storeNo} | 현재 가맹점의 재고 목록을 조회합니다. |
재고량 수정 | PUT | /stock | 선택한 재고의 수량을 수정합니다. |
재고 생성 | POST | /stock | 신규 재고 품목을 생성합니다. |
재고 삭제 | DELETE | /stock?stockNo={stockNo} | 더 이상 관리할 필요가 없는 재고를 삭제합니다. |
재고 단일 품목 발주 신청 | POST | /purchase-order | 재고 단일 품목 발주를 신청합니다. |
발주 입고 완료 여부 체크 | PUT | /purchase-order | 발주 신청한 재고 품목이 입고됐을 시, 입고 완료 여부 확인을 위한 API입니다. |
발주 삭제 | DELETE | /purchaseorder?purchaseOrderNo={purchaseOrderNo} | 발주한 항목을 취소합니다. |
매출 정보 조회 | GET | /sales?storeId={storeId} | 해당 가맹점의 매출 정보를 조회합니다. |
관리자 로그인 | POST | /admin/login | 가맹점 관리자(이하 관리자)가 관리자 페이지에 로그인 하기 위해 사용하는 API입니다. |
지점 목록 조회 | GET | /admin/store | 가맹점 목록을 조회합니다. |
지점 신규 등록(=지점장 회원 가입) | POST | /admin/store | 가맹점을 신규 등록합니다. |
지점 정보 수정 | PUT | /admin/store | 가맹점 정보를 수정합니다. |
지점 삭제 | DELETE | /admin/store | 계약이 끝났거나 경고 횟수가 일정 수준을 넘어간 가맹점을 비활성화 시킵니다. |
카테고리 신규 등록 | POST | /admin/category | 메뉴 카테고리를 새로 등록합니다. |
카테고리 수정 | PUT | /admin/category | 카테고리 정보를 수정합니다. |
카테고리 삭제 | DELETE | /admin/category | 카테고리를 영구 삭제합니다(메뉴 없을 시) |
(카테고리 별) 메뉴 목록 조회 | GET | /admin/menu/{categoryId} | 카테고리 별로 메뉴 목록을 조회합니다. |
메뉴 신규 등록 | POST | /admin/menu | 관리자가 메뉴를 새로 등록합니다. |
관리자용 메뉴 상세 확인 | GET | /admin/menu?menuNo={menuNo} | 관리자가 단일 메뉴 정보를 확인합니다. |
메뉴 수정 | PUT | /admin/menu | 관리자가 메뉴 정보를 수정합니다. |
메뉴 단종 (DELETE) | DELETE | /admin/menu | 더 이상 판매하지 않는 메뉴를 비활성화 시킵니다. |
테이블 오더 로그인 | POST | /to | 테이블 오더 기기에 로그인합니다. 이때 테이블 번호와 이름도 함께 등록합니다. |
테이블 오더 관리자 모드 로그인 | POST | /to/admin | 테이블 오더 관리자 모드 로그인합니다(실제로는, 입력한 아이디와 비밀번호가 일치하는지 여부만 조회) |
테이블 오더 비활성화 | PUT | /to/admin | 선택한 테이블을 비활성화합니다. |
테이블오더 주문 내역 조회 | GET | /to/order?tableCode={tableCode} | 해당 테이블에서 현재까지 주문한 내역을 조회합니다(테이블 비우기 전(state=2 제외) 항목만 조회) |
테이블 오더 카테고리 목록 조회 | GET | /to/category | 현재 활성화된 카테고리 목록을 조회합니다. |
테이블 오더 특정 카테고리 판매 중 메뉴 목록 조회 | GET | /to/menu/list?storeCode={storeCode}&categoryNo={categoryNo} | 현재 가맹점에서 판매하는 메뉴 목록을 카테고리별로 조회합니다. |
테이블 오더 메뉴 단일(상세) 조회 | GET | /to/menu?storeCode={storeCode}&menuNo={menuNo} | 선택한 메뉴의 상세 정보를 확인합니다. |
테이블 오더 주문 신규 등록 | POST | /to/menu?menuId={menuId} | 테이블 오더에서 신규 주문을 진행합니다. |
신규 발주 목록 조회 | GET | /api/order?storeNo={storeNo} | (본사 측에서) 신규 발조 목록을 조회합니다. |
월별 매출 조회 | GET | /api/sales?year={year}&month={month} | (본사 측에서) request param으로 입력한 연도, 월별 매출을 조회합니다. |
주요 기능 목록
-
🏬 가맹점관리
- 가맹점 등록
- 가맹점 경고 기능
- 폐점한 매장 업데이트 및 조회
-
🍽 메뉴관리
- 메뉴 등록
- 메뉴 판매 상태 변경 및 조회(판매/미판매)
- 필요한 재고 발주하기
-
💻 주문관리
- 가맹점 별 (당일) 주문 내역 확인(영수증)
- 테이블 별 주문 내역 확인
- 손님 식사 완료 여부에 따라 주문 상태 변경
-
📈 매출관리
- 당일 & 이번 달 매출 조회
- 당일 & 이번 달 결제 건수 조회
-
📋 테이블 오더
- 테이블 등록 & 비활성화
- 메뉴 확인 & 장바구니 담기
- 장바구니 담은 메뉴 주문하기
- 해당 테이블 현재 주문 내역 확인
- 홀짝 게임
테이블 오더(모바일) 앱 스크린샷
임주연(ljy)
-
(백엔드) jpa repository 쿼리
- No property 'no' found for type 'Order'; Traversed path: OrderMenu.order 에러
- 원인: Order 엔티티에서 no라는 필드를 찾지 못해서 발생한 문제
- Order 엔티티에서 실제로 사용되는 필드 이름을 사용해야 함
- 해결
- findByOrderNo에서 findByOrder_OrderNo로 수정
- 특이사항
- 해당 OrderMenu는 order_no(오라클 기준 컬럼명)를 fk로 가진 엔티티임, 즉 자식 엔티티에서 부모 엔티티 컬럼(예: pk)를 찾기 위해서는 부모 엔티티_부모 엔티티의 해당 컬럼 이름을 명시하는 식으로 jpa 메서드 명을 지정해야 하는 것으로 보임
-
(백엔드) 스웨거 적용
- 원인: No operations defined in spec! 메시지 표시되며 아무 api도 보이지 않음
- 해결: application.proporties에 springdoc.packages-to-scan에 패키지값 제대로 세팅
-
(백엔드) RequestBody null로 돌아오는 에러
- 원인: 몰라…. ⇒ RequestBody를 spring이 아니라 swagger 걸 import
- 해결: 했으면 좋겠다… ⇒ spring에서 제공해주는 걸로 import 문 변경
-
(백엔드) store 정보 수정 뒤, 신규 레코드 생성했을 때 레코드 pk 번호가 수정 횟수만큼 건너뛰어지는 이슈
- 원인: Builder로 객체 생성해서 해당 객체를 수정용 객체로 사용했는데, jpa의 경우 엔티티를 Builder를 이용해서 객체 생성하면 신규 객체를 생성하는 것이라고 무조건 생각하게 됨
- 해결: 기존 db의 entity 객체 불러온 다음, setter로 수정
-
(안드로이드) hilt 오류
error: [Hilt] Unsupported metadata version. Check that your Kotlin version is >= 1.0: java.lang.IllegalStateException: Unsupported metadata version. Check that your Kotlin version is >= 1.0 at dagger.hilt.processor.internal.kotlin.KotlinMetadata.metadataOf(KotlinMetadata.java:200)
위 에러를 비롯한 매우 다양한 오류들이 출몰
- 원인: kotlin과 hilt의 버전 차이
- 해결: kotlin version 1.9.0, hilt 2.48로 맞춤
- 여담: 총 5시간 걸렸습니다 이젠 살의밖에 남아있지 않은 괴물이 되어버렸습니다…
-
(안드로이드) 주문 정보 가져오기 실패 : Text '2024-07-09T11:09:35.93592' could not be parsed at index 20
- 원인: gson에서 LocalDateTime 형변환 못하는데 해당 형식으로 저장하라고 세팅해서 발생한 오류
- 해결: 일단 string 형태로 room에 저장한 뒤 ui에서 표현할 때는 형변환 util 함수 만들어서 대응 (getTimeDifferenceString)
-
(안드로이드) 가맹점 pos 측에서 테이블 삭제했을 시 & token 만료 시 테이블 오더 앱 대응
- 문제: 가맹점 측 pos기에서 테이블 삭제 시 & token 만료 시 테이블 오더 앱 기기가 계속 작동 가능할 시 치명적인 문제가 되리라고 판단되었음
- 해결: 401 error return됐을 시 room과 sharePreferences 내에 저장해둔 데이터 전부 삭제한 뒤 앱 강제 종료하도록 조치 (AuthInterceptor)
-
(안드로이드) 관리자 로그인 시도 시
서버와의 통신에 실패했습니다. : java.lang.RuntimeException: Unable to create instance of interface retrofit2.Call. Registering an InstanceCreator or a TypeAdapter for this type, or adding a no-args constructor may fix this problem.
에러 발생- 원인: retrofit에서 suspend를 사용할 시, Call이 아닌 Response를 사용해야 하기에 발생한 오류
- 해결: Call ⇒ Response로 고침
-
(안드로이드) 카테고리 정보 가져오기 실패 : Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied
- 상황: 이미 기기 로그인 된 상태로 바로 home screen 접속하면 안 뜨는데 로그인 과정 거쳐서 home screen으로 넘어갈 때 뜸 share 문제인가?
- 원인: 로그인하고 성공 뜬 다음에
sharedPreferences
에 데이터 저장하는데 바로 페이지 넘어가버려서 table id가 null인 상태로 api 요청해서 404 뜸 - 해결: delay 걸어서 바로 페이지 넘어가는 게 아니라, 적당한 시간 뒤에 넘어가도록, 그래서 데이터 저장까지 확보하고 home screen으로 넘어감
나소림(nsr)
- @RequestBody 애노테이션으로 받은 json 데이터가 Null
-
에러 : not-null property references a null or transient value : com.nagane.franchise.menu.domain.Category.categoryName
-
해결 : 어노테이션 import 잘못함
-
- StackOverflowError
-
에러 : [2024-06-30 14:55:08.076] [ERROR] [http-nio-9001-exec-1] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed: java.lang.StackOverflowError] with root cause java.lang.StackOverflowError: null
-
원인 : 카테고리 엔티티 안에 메뉴 에티티 리스트 선언돼있고 메뉴 엔티티에 또 카테고리 엔티티가 선언돼있어서 무한반복으로 카테고리와 메뉴 정보를 불러옴
-
해결 : @ToString 어노테이션에서 특정 컬럼 제거
-
- 엔티티에 기본 생성자가 없다는 오류
- 오류 : No default constructor for entity 'com.nagane.franchise.stoke.domain.Stock'
- 원인 : 엔티티에 기본 생성자가 없어서
- 해결 : @NoArgsConstructor 와 @AllArgsConstructor 어노테이션 붙여줌
- 없는 부모를 값으로 넣으려고 해서 난 오류
- 에러 : could not execute statement [ORA-02291: integrity constraint (NAGANE.FKIY2NYPGS7BM2W5RMLMVQKL1OU) violated - parent key not found ] [insert into purchase_order (order_date,price,quantity,state,stock_no,p_order_no) values (?,?,?,?,?,?)]; SQL [insert into purchase_order (order_date,price,quantity,state,stock_no,p_order_no) values (?,?,?,?,?,?)]; constraint [NAGANE.FKIY2NYPGS7BM2W5RMLMVQKL1OU]
- 해결 : 부모를 만들어주고 실행
- yarn start 안됨
-
에러 : 'react-scripts'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는 배치 파일이 아닙니다.
-
원인 : react-scripts라는 라이브러리(프로그램/명령)을 현재 경로에서 실행시킬수 없는 상황이기 때문
-
해결 : react-scripts모듈을 설치
yarn add global react-scripts
npm install -g react-script
-
- 에러는 아니고 Middleware 경고
- 경고
- DeprecationWarning: 'onAfterSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
- 'onBeforeSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
- 뜻
- onAfterSetupMiddleware 옵션은 더 이상 사용되지 않습니다. 대신 setupMiddlewares 옵션을 사용해야 합니다.
- onBeforeSetupMiddleware 옵션도 더 이상 사용되지 않습니다. 마찬가지로 setupMiddlewares 옵션을 사용해야 합니다.
- 경고
- not null 컬럼인데 null 을 넣는다는 에러
- 에러 : 복사 못함
- 원인 : 카테고리 엔티티에서 state = 1이라고 지정해뒀지만 null 값으로 들어감
- 해결 : state 변수에 @Builder.Default 어노테이션 붙이기
- 직접 쿼리문으로 데이터 삽입 시 읽어오지 못함
- commit 을 안함….
- props 으로 값 안넘어감
- 해결 : const CategoryForm = ({ toggleFormLayouts, changeCategory }) ⇒{} { toggleFormLayouts, changeCategory } 이 부분을 {toggleFormLayouts}, {changeCategory } 이렇게 씀
- CORS 오류
- 에러 : Access to XMLHttpRequest at 'http://localhost:9001/admin/category' from origin 'http://localhost:3001' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
- 이유 : 요청한 경로가 허용되지 않음
- 해결 : WebConfig클래스의 addMapping 함수에 허용 경로를 모든 경로("/**")로 설정
- 요청 데이터가 잘못 됨
- 에러 : org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public org.springframework.http.ResponseEntity<? extends com.nagane.franchise.util.model.response.BaseResponseBody> com.nagane.franchise.store.api.StoreController.deleteStore(com.nagane.franchise.store.dto.store.StoreNoDto)]
- 원인 : 지점 삭제시 서버는 Dto 로 요청을 받는데 프론트는 StoreNo(Long) 값을 넘겨줌
- 해결 : StoreNo 을 객체에 담아서 보냄
-
커밋 메세지 기본 구조
commit -m "[type] Subject" (Type : 앞뒤로 한칸 씩 띄우기) commit -m "[feat] 로그인 API 구현"
- Type
- feat : 새로운 기능과 관련된 것을 의미한다.
- fix : 오류와 같은 것을 수정했을 때 사용한다.
- docs : 문서와 관련하여 수정한 부분이 있을 때 사용한다.
- style : 코드의 변화와 관련없는 포맷이나 세미콜론을 놓친 것과 같은 부분들을 의미한다.
- refactor : 코드의 리팩토링을 의미한다.
- test : test를 추가하거나 수정했을 때를 의미한다.
- chore : build와 관련된 부분, 패키지 매니저 설정 등 여러가지 production code와 무관한 부분들을 의미한다. 말 그대로 자질구레한 일들이다.
- remove : 파일 삭제했을 때를 의미한다.
- rename : 폴더 또는 파일의 이름 수정 및 이동.(chore로 해도 될듯?)
- Type
-
Subject
- 한글버전으로 하기
- ex ) 한글버전 >>>> [feat] loginAPI 구현