You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1393 lines
49 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# CLAUDE.md
이 파일은 이 저장소에서 코드 작업을 할 때 Claude Code (claude.ai/code)에게 제공하는 가이드입니다.
# ⚠️ 최우선 규칙 - 반드시 준수 ⚠️
## 🚨 패턴 통일화 필수 프로세스 (MANDATORY)
**모든 코드 작성 전 반드시 아래 프로세스를 따를 것. 이 프로세스를 건너뛰면 작업을 시작하지 말 것.**
### 1단계: 패턴 분석 (ALWAYS FIRST)
새로운 코드를 작성하기 전, **반드시** 유사한 기존 코드의 패턴을 먼저 분석 (참고대상 CRDN-단속 하위 디렉토리 or 패키지 or xml query):
- **Controller 작성 시**:
- 동일 패키지 내 다른 Controller 파일을 최소 2개 이상 Read로 읽고 분석
- 메서드 구조, 어노테이션 패턴, 파라미터 처리 방식, 응답 구조 확인
- **JSP/JavaScript 작성 시**:
- 동일 디렉토리 내 유사한 JSP 파일을 Read로 읽고 분석
- JavaScript 함수 네이밍, AJAX 호출 패턴, 에러 처리 방식 확인
- HTML 구조, CSS 클래스명, 폼 구조 확인
- **Service/Mapper 작성 시**:
- 동일 패키지의 기존 Service/Mapper 파일 읽고 분석
- 메서드 네이밍, 트랜잭션 처리, 예외 처리 패턴 확인
### 2단계: 패턴 적용 검증 (BEFORE WRITING)
코드를 작성하기 전, 다음을 확인:
1. ✅ 기존 파일을 Read 도구로 읽었는가?
2. ✅ 해당 파일의 코딩 스타일(들여쓰기, 주석, 네이밍)을 파악했는가?
3. ✅ 유사한 기능이 어떻게 구현되어 있는지 확인했는가?
4. ✅ 내가 작성할 코드가 기존 패턴과 100% 일치하는가?
**위 4가지 중 하나라도 NO면 코드를 작성하지 말고, 먼저 패턴 분석으로 돌아갈 것.**
### 3단계: 작성 후 자체 검증 (AFTER WRITING)
코드 작성 후 제출 전, 다음을 점검:
1. ✅ 들여쓰기(탭/스페이스)가 기존 파일과 동일한가?
2. ✅ 변수/함수 네이밍 규칙이 기존과 동일한가?
3. ✅ 주석 스타일(한글/영문, 위치)이 기존과 동일한가?
4. ✅ 에러 처리 방식이 기존과 동일한가?
5. ✅ API 응답 구조가 기존과 동일한가?
6.**[필수] VO 파일을 Read 도구로 다시 읽어서 필드가 정상 추가되었는지 확인했는가?** (Edit 결과를 믿지 말고 반드시 Read로 재확인)
7.**[필수] XML 쿼리를 Read 도구로 다시 읽어서 수정이 정상 반영되었는지 확인했는가?**
### 🔴 절대 금지 사항
- ❌ 기존 코드를 읽지 않고 "추측"으로 작성
- ❌ 기존 파일의 들여쓰기/포맷팅 변경
- ❌ 기존과 다른 스타일의 주석 추가
- ❌ 새로운 패턴이나 "더 나은 방법" 제안 (명시적 요청이 없는 한)
- ❌ 임의로 코드 리팩토링
### 📋 체크리스트 (모든 작업 전 필수 확인)
```
[ ] 1. 유사 기능의 기존 코드를 Read로 읽었는가?
[ ] 2. 기존 코드의 패턴(네이밍, 구조, 스타일)을 분석했는가?
[ ] 3. 내 코드가 기존 패턴과 100% 일치하는가?
[ ] 4. 들여쓰기, 주석, 변수명이 기존과 동일한가?
[ ] 5. [작업 후] VO 파일 변경 시 Read로 재확인했는가?
[ ] 6. [작업 후] XML 쿼리 변경 시 Read로 재확인했는가?
[ ] 7. [작업 후] Service/Mapper 변경 시 Read로 재확인했는가?
```
**이 체크리스트를 모두 완료하지 않으면 코드를 작성하지 말 것.**
**특히 Edit 도구 사용 후에는 반드시 Read 도구로 재확인할 것. Edit 결과를 절대 믿지 말 것.**
## 프로젝트 개요
일산 동구 건축물 위법행위 통합관리시스템 - 공무원이 불법 건축물 관련 위반사항을 관리하기 위한 Spring Boot 웹 애플리케이션입니다.
## 빌드 및 개발 명령어
### 빌드 명령어
```bash
# 프로젝트 빌드
./gradlew build
# 클린 빌드
./gradlew clean build
# 애플리케이션 실행 (기본 포트: 8080)
./gradlew bootRun
# 배포용 WAR 파일 빌드
./gradlew bootWar
```
### 테스트
이 프로젝트에서는 프로젝트 가이드라인에 따라 테스트가 명시적으로 비활성화되어 있습니다. 테스트 파일을 생성하지 마세요.
## 고수준 아키텍처
### 기술 스택
- **JDK**: JDK1.8만 사용
- **프레임워크**: Spring Boot 2.7.18 with eGovFrame 4.3.0 (한국 정부 표준 프레임워크)
- **데이터베이스**: MariaDB with MyBatis 2.3.1 for ORM
- **프론트엔드**: JSP with Apache Tiles 3.0.8 (레이아웃), TOAST UI Grid 4.19.2 (데이터 그리드)
- **빌드 도구**: Gradle
### MVC 아키텍처 패턴
프로젝트는 엄격한 MVC 패턴을 따릅니다:
- **Model**: `model/` 패키지에 `PagingVO`를 확장한 VO 클래스들 포함
- **View**: Apache Tiles 레이아웃을 사용하는 `src/main/webapp/WEB-INF/views/`의 JSP 파일들
- **Controller**: Swagger 어노테이션을 사용하는 Spring MVC 컨트롤러가 있는 `controller/` 패키지들
### 비즈니스 도메인별 패키지 구조
```
go.kr.project/
├── system/ # 시스템 관리 (사용자, 역할, 메뉴, 코드)
├── login/ # 인증
├── common/ # 공통 컴포넌트 및 유틸리티
├── baseData/ # 마스터 데이터 관리 (건축물 지수, 위반 법령)
├── crdn/ # 핵심 비즈니스: 위반 관리
│ ├── crndRegistAndView/ # 위반 등록 및 조회
│ │ ├── main/ # 주요 위반 프로세스
│ │ ├── crdnActInfo/ # 위반 행위 정보
│ │ ├── crdnPstnInfo/ # 위치 정보
│ │ └── crdnOwnrInfo/ # 소유자 정보
│ └── ownac/ # 소유자 조치 관리
└── noti/ # 알림 시스템
```
### 데이터베이스 레이어 (MyBatis)
- **매퍼**: `mapper/` 패키지의 인터페이스 클래스들
- **SQL**: 동일한 패키지 구조를 따르는 `src/main/resources/mybatis/mapper/`의 XML 파일들
- **시퀀스**: 모든 기본 키는 10자리 LPAD 시퀀스 사용: `SELECT LPAD(NEXTVAL(seq_table_id), 10, '0')`
- **네이밍**: camelCase 변환 자동 사용, `DB-DDL/maria/dictionary/`의 컬럼 단어 사전 따름
### 서비스 레이어 패턴
각 비즈니스 도메인은 다음을 포함합니다:
- **Service Interface**: 비즈니스 오퍼레이션 정의
- **ServiceImpl**: 비즈니스 로직 구현, `@RequiredArgsConstructor` 사용하고 매퍼 호출
- **표준 메서드**: 일관된 네이밍을 따르는 `select*`, `insert*`, `update*`, `delete*`
### 프론트엔드 아키텍처
#### 레이아웃 (Apache Tiles)
- **base**: 헤더, 메뉴, 콘텐츠 영역을 가진 메인 관리자 레이아웃
- **login**: 로그인 페이지 레이아웃
- **popup**: 팝업 윈도우 레이아웃 (접미사 `TilesConstants.POPUP`)
#### UI 컴포넌트
- **모달**: JavaScript로 제어되는 `.modalz` CSS 클래스를 가진 JSP에 임베드
- **팝업**: 팝업 레이아웃을 사용하는 별도 JSP 페이지, `window.open()`으로 열림
- **그리드**: 구성을 위한 `XitTuiGridConfig` 헬퍼 클래스를 가진 TOAST UI Grid
#### JavaScript 패턴
- `setTimeout` 사용 피하기 (문제 야기)
- 모든 URL에 `<c:url/>` 태그 사용
- `dataSource.api.readData`를 통한 AJAX로 그리드 데이터 로딩
-
#### CSS, JavaScript 위치
src/main/webapp/resources/xit
src/main/webapp/resources/css
#### TUI Grid 는 아래 패턴을 이용
```javascript
/**
* 단속 목록 관리 네임스페이스
*/
var CrdnRegistAndViewList = {
/**
* 선택된 행 정보
*/
selectedRow: null,
/**
* 그리드 관련 객체
*/
grid: {
/**
* 그리드 인스턴스
*/
instance: null,
```
tui grid 선언 규칙
CrdnRegistAndViewList.grid.instance
해당페이지명명규칙.그리드객체.그리드인스턴스
## 중요한 개발 가이드라인
### 모드 표준화
모든 CRUD 작업은 일관된 모드 값을 사용합니다:
- `mode: C` = Create (등록)
- `mode: U` = Update (수정)
- `mode: V` = View (보기)
- `mode: D` = Delete (삭제)
### 페이지네이션 패턴 (중요한 순서)
```java
// 1. 먼저 총 개수 가져오기
int totalCount = service.selectTotalCount(paramVO);
// 2. 총 개수 설정
paramVO.setTotalCount(totalCount);
// 3. 페이징 활성화 (선택사항)
paramVO.setPagingYn("Y");
```
### 보안 및 세션
- 감사 필드에 `SessionUtil.getUserId()` 사용
- 모든 폼에 CSRF 보호 필요
- 하드코딩된 자격증명이나 민감한 데이터 금지
### 코드 품질 요구사항
- **한글 주석**: 모든 비즈니스 로직은 한글 주석 필수 (중요로직 주석)
- **Swagger**: 모든 컨트롤러는 Swagger 어노테이션 필수 (`@Tag`, `@Operation`, `@ApiResponses`)
- **포맷팅 변경 금지**: 기존 코드 포맷팅이나 공백 수정 절대 금지
### 비즈니스 로직 패턴
위반 관리(`crdn`)와 마스터 데이터(`baseData`)의 경우, 다음의 기존 패턴을 따르세요:
- 뷰 패턴: `src/main/webapp/WEB-INF/views/baseData/``src/main/webapp/WEB-INF/views/crdn/`
- Java 패턴: `src/main/java/go/kr/project/baseData/bldgNewPrcCrtrAmt/``src/main/java/go/kr/project/crdn/`
### 최근 아키텍처 추가: 스마트 CRUD 패턴
`CrdnLevyPrvntcController`는 생성/업데이트 작업을 처리하는 고급 패턴을 보여줍니다:
- **통합 저장 API**: `/saveLevyInfo.ajax`가 insert vs update를 자동으로 결정
- **스냅샷 복원**: 그리드 행 선택이 기존 데이터를 로드하여 이전 계산을 복원
- **스마트 데이터 로딩**: 복잡한 조인을 사용하여 표시명과 계산 비율을 함께 가져옴
- **프론트엔드 상태 관리**: JavaScript가 모드 감지와 폼 채우기를 처리
이 패턴은 데이터를 계산 스냅샷으로 저장하고 나중에 복원해야 하는 유사한 위반 처리 워크플로우에서 따라야 합니다.
---
# 📘 프로젝트 상세 문서
## 1. 전체 프로젝트 구조
### 1.1 Java 패키지 구조 상세
```
go.kr.project/
├── common/ # 공통 기능
│ ├── controller/ # 공통 컨트롤러
│ │ ├── CommonCodeController # 공통코드 조회
│ │ ├── HtmlEditorController # HTML 에디터 파일 관리
│ │ └── SearchAddressController # 주소 검색
│ ├── service/ # 공통 서비스
│ │ ├── CommonCodeService # 공통코드 서비스
│ │ ├── CommonHeaderService # 공통 헤더 서비스
│ │ └── HtmlEditorService # HTML 에디터 서비스
│ ├── mapper/ # 공통 매퍼
│ └── model/ # 공통 모델
│ ├── PagingVO # 페이징 기본 VO (모든 VO의 부모 클래스)
│ ├── FileVO # 파일 정보 VO
│ └── CmmnCodeSearchVO # 공통코드 검색 VO
├── login/ # 로그인 및 인증
│ ├── service/LoginService # 로그인 서비스
│ ├── mapper/LoginMapper # 로그인 매퍼
│ └── model/ # 로그인 모델
│ ├── SessionVO # 세션 정보 VO
│ ├── UserSessionVO # 사용자 세션 VO
│ └── LoginUserVO # 로그인 사용자 VO
├── mypage/ # 마이페이지
│ ├── controller/MypageController
│ ├── service/MypageService
│ └── service/impl/MypageServiceImpl
├── system/ # 시스템 관리
│ ├── user/ # 사용자 관리
│ │ ├── controller/UserController
│ │ ├── service/UserService
│ │ ├── service/impl/UserServiceImpl
│ │ ├── mapper/UserMapper
│ │ └── model/
│ │ ├── SystemUserVO # 시스템 사용자 VO
│ │ └── SystemUserSearchVO # 사용자 검색 VO
│ ├── group/ # 그룹 관리
│ │ ├── controller/GroupController
│ │ ├── service/GroupService
│ │ └── model/GroupVO
│ ├── role/ # 역할 관리
│ │ ├── controller/RoleController
│ │ ├── service/RoleService
│ │ └── model/RoleVO
│ ├── menu/ # 메뉴 관리
│ │ ├── controller/MenuController
│ │ ├── service/MenuService
│ │ └── mapper/MenuMapper
│ ├── code/ # 코드 관리
│ │ ├── controller/CodeController
│ │ ├── service/CodeService
│ │ ├── mapper/CodeMapper
│ │ └── model/
│ │ ├── CodeGroupVO # 코드 그룹 VO
│ │ └── CodeDetailVO # 코드 상세 VO
│ ├── auth/ # 권한 관리
│ │ ├── controller/AuthController
│ │ ├── service/AuthService
│ │ ├── mapper/AuthMapper
│ │ └── model/
│ │ ├── AuthVO
│ │ ├── GroupRoleVO
│ │ ├── RoleMenuVO
│ │ └── MenuProgramVO
│ ├── loginLog/ # 로그인 로그
│ │ ├── controller/LoginLogController
│ │ ├── service/LoginLogService
│ │ └── model/LoginLogVO
│ └── execquery/ # 쿼리 실행 (개발/디버깅용)
│ ├── controller/DbQueryController
│ ├── service/DbQueryService
│ └── config/IsolatedTransactionManager
├── baseData/ # 기초 데이터 관리
│ └── bldgNewPrcCrtrAmt/ # 건축물 신축가격 기준액 관리
│ ├── controller/BldgNewPrcCrtrAmtController (추정)
│ ├── service/BldgNewPrcCrtrAmtService
│ ├── service/impl/BldgNewPrcCrtrAmtServiceImpl
│ ├── mapper/BldgNewPrcCrtrAmtMapper
│ └── model/BldgNewPrcCrtrAmtVO
├── crdn/ # 단속 관리 (핵심 비즈니스 도메인)
│ ├── crndRegistAndView/ # 단속 등록 및 조회
│ │ ├── main/ # 메인 단속 프로세스
│ │ │ ├── controller/
│ │ │ │ ├── CrdnRegistAndViewController # 단속 등록/조회
│ │ │ │ ├── CrdnLevyPrvntcController # 부과예고 관리
│ │ │ │ ├── CrdnRelevyController # 재부과 관리
│ │ │ │ └── CrdnImpltTaskController # 이행 업무 관리
│ │ │ ├── service/
│ │ │ │ ├── CrdnRegistAndViewService
│ │ │ │ ├── CrdnLevyPrvntcService
│ │ │ │ ├── CrdnRelevyService
│ │ │ │ └── CrdnImpltTaskService
│ │ │ ├── service/impl/
│ │ │ │ ├── CrdnRegistAndViewServiceImpl
│ │ │ │ ├── CrdnLevyPrvntcServiceImpl
│ │ │ │ ├── CrdnRelevyServiceImpl
│ │ │ │ └── CrdnImpltTaskServiceImpl
│ │ │ └── model/
│ │ │ ├── CrdnRegistAndViewVO # 단속 기본 정보 VO
│ │ │ ├── CrdnLevyInfoVO # 부과 정보 VO (계산식 포함)
│ │ │ ├── CrdnRelevyVO # 재부과 정보 VO
│ │ │ ├── CrdnImpltTaskVO # 이행 업무 VO
│ │ │ ├── CrdnAdsbmtnRtVO # 가감산율 VO
│ │ │ ├── CrdnCmpttnRt2VO # 산정률2 VO
│ │ │ └── LevyPrvntcActInfoVO # 부과예고 행위정보 VO
│ │ ├── crdnActInfo/ # 행위 정보 관리
│ │ │ ├── controller/CrdnActInfoController
│ │ │ ├── service/
│ │ │ │ ├── CrdnActInfoService
│ │ │ │ └── CrdnPhotoService # 사진 관리
│ │ │ ├── service/impl/
│ │ │ └── model/
│ │ │ ├── CrdnActInfoVO # 행위 정보 VO
│ │ │ └── CrdnVltnLwrgVO # 위반 법령 VO
│ │ ├── crdnPstnInfo/ # 위치 정보 관리
│ │ │ ├── controller/CrdnPstnInfoController
│ │ │ ├── service/CrdnPstnInfoService
│ │ │ └── model/CrdnPstnInfoVO # 위치 정보 VO
│ │ ├── crdnOwnrInfo/ # 소유자 정보 관리
│ │ │ ├── controller/CrdnOwnrInfoController
│ │ │ ├── service/CrdnOwnrInfoService
│ │ │ └── model/CrdnOwnrInfoVO # 소유자 정보 VO
│ │ ├── crdnActrInfo/ # 행위자 정보 관리
│ │ │ ├── controller/CrdnActrInfoController
│ │ │ ├── service/CrdnActrInfoService
│ │ │ └── model/ (추정)
│ │ ├── crdnExmnr/ # 조사자 정보 관리
│ │ │ ├── controller/CrdnExmnrController
│ │ │ ├── service/CrdnExmnrService
│ │ │ ├── mapper/CrdnExmnrMapper
│ │ │ └── model/CrdnExmnrVO
│ │ └── crdnOwnrSelect/ # 소유자 선택
│ │ ├── controller/CrdnOwnrSelectController
│ │ ├── service/CrdnOwnrSelectService
│ │ └── service/impl/CrdnOwnrSelectSelectServiceImpl
│ ├── ownac/ # 소유자 조치 관리
│ │ ├── controller/OwnActRegistAndViewController
│ │ ├── service/OwnActRegistAndViewService
│ │ └── service/impl/OwnActRegistAndViewServiceImpl
│ └── exmnr/ # 조사자 관리
│ ├── controller/ExmnrController
│ ├── service/ExmnrService
│ └── service/impl/ExmnrServiceImpl
├── noti/ # 알림 시스템 (추정)
└── ma30/ # MA30 관련 (추정)
└── model/
├── Ma30FindListVO
└── Ma30FindRlistSearchVO
```
### 1.2 View (JSP) 디렉토리 구조
```
src/main/webapp/WEB-INF/views/
├── common/ # 공통 뷰
│ ├── header.jsp # 헤더
│ ├── footer.jsp # 푸터
│ └── menu.jsp # 메뉴
├── login/ # 로그인
│ └── login.jsp
├── system/ # 시스템 관리
│ ├── user/ # 사용자 관리
│ ├── group/ # 그룹 관리
│ ├── role/ # 역할 관리
│ ├── menu/ # 메뉴 관리
│ ├── code/ # 코드 관리
│ └── loginLog/ # 로그인 로그
├── baseData/ # 기초 데이터
│ └── bldgNewPrcCrtrAmt/ # 건축물 신축가격 기준액
└── crdn/ # 단속 관리
├── crndRegistAndView/
│ └── main/
│ ├── list.jsp # 단속 목록
│ ├── crdnRegistPopup.jsp # 단속 등록/수정 팝업
│ ├── detailView-main.jsp # 단속 상세보기 메인
│ └── crdnLevyPrvntc/
│ ├── levyPrvntcPopup.jsp # 부과예고 팝업
│ └── LevyAddMinusPopup.jsp # 가감산 팝업
└── ownac/ # 소유자 조치
```
### 1.3 MyBatis Mapper XML 구조
```
src/main/resources/mybatis/mapper/
├── common/
│ ├── CommonCodeMapper_maria.xml
│ └── CommonHeaderMapper_maria.xml
├── login/
│ └── LoginMapper_maria.xml
├── system/
│ ├── user/UserMapper_maria.xml
│ ├── group/GroupMapper_maria.xml
│ ├── role/RoleMapper_maria.xml
│ ├── menu/MenuMapper_maria.xml
│ ├── code/CodeMapper_maria.xml
│ ├── auth/AuthMapper_maria.xml
│ └── loginLog/LoginLogMapper_maria.xml
├── baseData/
│ └── bldgNewPrcCrtrAmt/BldgNewPrcCrtrAmtMapper_maria.xml
└── crdn/
├── crndRegistAndView/
│ ├── main/
│ │ ├── CrdnRegistAndViewMapper_maria.xml
│ │ ├── CrdnLevyPrvntcMapper_maria.xml
│ │ ├── CrdnRelevyMapper_maria.xml
│ │ └── CrdnImpltTaskMapper_maria.xml
│ ├── crdnActInfo/
│ │ ├── CrdnActInfoMapper_maria.xml
│ │ └── CrdnPhotoMapper_maria.xml
│ ├── crdnPstnInfo/
│ │ └── CrdnPstnInfoMapper_maria.xml
│ ├── crdnOwnrInfo/
│ │ └── CrdnOwnrInfoMapper_maria.xml
│ ├── crdnActrInfo/
│ │ └── CrdnActrInfoMapper_maria.xml
│ ├── crdnExmnr/
│ │ └── CrdnExmnrMapper_maria.xml
│ └── crdnOwnrSelect/
│ └── CrdnOwnrSelectMapper_maria.xml
├── ownact/
│ └── OwnActRegistAndViewMapper_maria.xml
└── exmnr/
└── ExmnrMapper_maria.xml
```
---
## 2. 주요 업무 로직 상세
### 2.1 단속 관리 프로세스 (crdn 도메인)
#### 2.1.1 단속 등록 및 조회 (CrdnRegistAndViewController)
**주요 API 엔드포인트:**
- `GET /crdn/crndRegistAndView/list.do` - 단속 목록 화면
- `POST /crdn/crndRegistAndView/list.ajax` - 단속 목록 조회 (페이징)
- `GET /crdn/crndRegistAndView/crdnRegistPopup.do` - 단속 등록/수정 팝업
- `GET /crdn/crndRegistAndView/detailView.do` - 단속 상세보기
- `POST /crdn/crndRegistAndView/insert.ajax` - 단속 등록
- `POST /crdn/crndRegistAndView/update.ajax` - 단속 수정
- `POST /crdn/crndRegistAndView/delete.ajax` - 단속 삭제 (논리삭제)
- `GET /crdn/crndRegistAndView/selectOne.ajax` - 단속 상세 조회
- `POST /crdn/crndRegistAndView/updateStatus.ajax` - 단속 상태 업데이트
- `GET /crdn/crndRegistAndView/checkActCmpltCd.ajax` - 조치처리상태 확인
- `POST /crdn/crndRegistAndView/excel.do` - 단속 목록 엑셀 다운로드
**주요 필드 (CrdnRegistAndViewVO):**
- `crdnYr` (단속 연도) + `crdnNo` (단속 번호) = 복합키
- `sggCd` (시군구 코드)
- `crdnPrcsSttsCd` (단속 진행 상태 코드)
- `dsclMthd` (적발 방법)
- `stdgEmdCd` (법정동 읍면동 코드)
- `rgnSeCd` (지역 구분 코드)
**비즈니스 규칙:**
1. 등록 시 세션의 조직코드(orgCd)를 sggCd로 자동 설정
2. 삭제는 논리삭제 (DEL_YN='Y')
3. 페이징 처리 시 반드시 순서 준수:
- 총 개수 조회 → totalCount 설정 → pagingYn='Y' 설정
#### 2.1.2 부과예고 관리 (CrdnLevyPrvntcController)
**주요 API 엔드포인트:**
- `GET /crdn/crndRegistAndView/crdnLevyPrvntc/selectLevyInfoFirstCheck.ajax` - 부과정보 존재 여부 확인
- `GET /crdn/crndRegistAndView/crdnLevyPrvntc/levyPrvntcPopup.do` - 부과예고 팝업
- `GET /crdn/crndRegistAndView/crdnLevyPrvntc/selectIsAllLevyInfoCompleted.ajax` - 부과정보 완료 여부
- `POST /crdn/crndRegistAndView/crdnLevyPrvntc/actlist.ajax` - 행위정보 목록 조회
- `GET /crdn/crndRegistAndView/crdnLevyPrvntc/LevyAddMinusPopup.do` - 가감산 팝업
- `POST /crdn/crndRegistAndView/crdnLevyPrvntc/adsbmtnRtList.ajax` - 가감산율 목록 조회
- `GET /crdn/crndRegistAndView/crdnLevyPrvntc/selectLevyInfoByActInfoId.ajax` - 기존 부과정보 조회
- `POST /crdn/crndRegistAndView/crdnLevyPrvntc/saveLevyInfo.ajax` - 부과정보 저장 (신규/수정 자동 판단)
- `POST /crdn/crndRegistAndView/crdnLevyPrvntc/delLevyInfo.ajax` - 부과정보 삭제
- `POST /crdn/crndRegistAndView/crdnLevyPrvntc/calculateAll.ajax` - **통합 계산 API (핵심 로직)**
**주요 필드 (CrdnLevyInfoVO):**
- 기본키: `levyInfoId`
- 참조키: `crdnYr`, `crdnNo`, `impltTaskSeCd`, `actInfoId`
- 계산 관련 필드:
- `bldgCrtrMprcAmt` (건물 기준 시가액)
- `strctIdx` (구조 지수)
- `usgIdx` (용도 지수)
- `pstnIdx` (위치 지수)
- `elpsYrRdvlrt` (경과 연도 잔가율)
- `bscsCstrnRt` (기초 공사율)
- `vltnArea` (위반 면적)
- `adsbmtnEnfcRt` (가감산 시행률)
- `cmpttnRt` (산정률)
- `cmpttnRt2` (산정률2)
- `bdstTxtnMprc` (건축물 과세 시가)
- `mprcStdAmt` (시가 표준액)
- `cmpttnAmt` (산정액)
- `levyWholAmt` (부과 총액)
**핵심 비즈니스 규칙:**
1. **스마트 저장 패턴**:
- `saveLevyInfo.ajax`는 기존 데이터 존재 여부를 자동으로 확인
- 존재하면 UPDATE, 없으면 INSERT 자동 실행
2. **사전 검증**:
- 부과예고 등록 전 반드시 위치정보, 행위정보가 먼저 등록되어야 함
- `selectLevyInfoFirstCheck.ajax`로 사전 검증 수행
3. **계산 스냅샷 패턴**:
- 계산 시점의 모든 값(지수, 비율 등)을 DB에 저장
- 나중에 조회 시 저장된 값으로 복원하여 일관성 유지
#### 2.1.3 행위 정보 관리 (CrdnActInfoController)
**주요 데이터:**
- 위반 행위의 상세 정보 (행위 유형, 위반 법령 등)
- 사진 정보 (CrdnPhotoService 별도 관리)
#### 2.1.4 위치 정보 관리 (CrdnPstnInfoController)
**주요 데이터:**
- 단속 위치 정보
- 지번, 주소, 좌표 등
#### 2.1.5 소유자 정보 관리 (CrdnOwnrInfoController)
**주요 데이터:**
- 건축물 소유자 정보
- 성명, 연락처, 주소 등
---
## 3. 데이터 흐름 상세
### 3.1 표준 CRUD 데이터 흐름
#### 3.1.1 조회 (SELECT) 흐름
```
[사용자]
[JSP - list.jsp]
↓ (페이지 로드 시 TUI Grid 초기화)
[JavaScript - dataSource.api.readData]
↓ (AJAX POST 요청: /list.ajax)
[Controller - listAjax(@ModelAttribute VO)]
↓ (1. totalCount 조회)
↓ (2. setTotalCount)
↓ (3. setPagingYn("Y"))
↓ (4. Service 호출)
[Service - selectList(VO)]
↓ (비즈니스 로직, 검증)
[Mapper - selectList(VO)]
↓ (MyBatis 호출)
[MyBatis XML - <select id="selectList">]
↓ (SQL 실행)
[MariaDB]
↓ (결과 반환)
[Mapper → Service → Controller]
↓ (ApiResponseUtil.successWithGrid(list, vo))
[ResponseEntity<?> 반환]
↓ (JSON 응답)
[JavaScript - Grid 렌더링]
[사용자에게 표시]
```
**핵심 포인트:**
- PagingVO의 `page`, `perPageNum` 필드로 페이징 처리
- `totalCount` 설정 후 `pagingYn="Y"` 설정 (순서 중요!)
- MyBatis에서 `<if test="pagingYn == 'Y'">LIMIT #{startRow}, #{perPageNum}</if>` 처리
#### 3.1.2 등록 (INSERT) 흐름
```
[사용자]
↓ (등록 버튼 클릭)
[JSP - 팝업 열기]
↓ (window.open() or 모달)
[사용자 입력]
↓ (폼 작성)
[JavaScript - 저장 버튼 클릭]
↓ (유효성 검증)
↓ (AJAX POST: /insert.ajax)
[Controller - insert(@ModelAttribute VO)]
↓ (세션 정보 설정: SessionUtil.getUserId())
↓ (sggCd = SessionUtil.getSessionVO().getUser().getOrgCd())
↓ (rgtr = SessionUtil.getUserId())
[Service - insert(VO)]
↓ (비즈니스 검증)
↓ (트랜잭션 시작 - @Transactional)
[Mapper - insert(VO)]
[MyBatis XML - <insert id="insert">]
↓ (시퀀스 생성: LPAD(NEXTVAL(seq_*), 10, '0'))
↓ (INSERT 실행)
[MariaDB]
↓ (결과 반환: affected rows)
[Service - 트랜잭션 커밋]
[Controller]
↓ (result > 0 확인)
↓ (ApiResponseUtil.success(MessageConstants.Common.SAVE_SUCCESS))
[JavaScript - 성공 처리]
↓ (팝업 닫기, 목록 새로고침)
[사용자에게 성공 메시지]
```
**핵심 포인트:**
- 등록자(rgtr) 필드에 `SessionUtil.getUserId()` 자동 설정
- 시군구코드(sggCd)는 세션의 조직코드로 자동 설정
- 시퀀스는 10자리 LPAD 형식: `LPAD(NEXTVAL(seq_name), 10, '0')`
#### 3.1.3 수정 (UPDATE) 흐름
```
[사용자]
↓ (행 선택 + 수정 버튼)
[JavaScript - 팝업 열기]
↓ (mode=U, PK 파라미터 전달)
[Controller - popup(mode=U, PK)]
↓ (기존 데이터 조회)
[Service - selectOne(PK)]
[Mapper - selectOne]
[DB 조회]
[팝업에 데이터 표시]
[사용자 수정]
↓ (저장 버튼)
[JavaScript - AJAX POST: /update.ajax]
[Controller - update(@ModelAttribute VO)]
↓ (mdfr = SessionUtil.getUserId() 설정)
[Service - update(VO)]
↓ (낙관적 잠금 검증 - 선택적)
[Mapper - update(VO)]
[MyBatis XML - <update>]
↓ (UPDATE SET ... WHERE PK = ? AND DEL_YN = 'N')
[DB 업데이트]
[결과 반환]
[ApiResponseUtil.success(MessageConstants.Common.UPDATE_SUCCESS)]
[팝업 닫기, 목록 새로고침]
```
**핵심 포인트:**
- 수정자(mdfr) 필드에 `SessionUtil.getUserId()` 설정
- WHERE 절에 `DEL_YN = 'N'` 조건 필수
- 수정일시(mdfcnDt)는 DB에서 자동 설정 (DEFAULT CURRENT_TIMESTAMP)
#### 3.1.4 삭제 (DELETE) 흐름 - 논리삭제
```
[사용자]
↓ (행 선택 + 삭제 버튼)
[JavaScript - 확인 창]
↓ (confirm("정말 삭제하시겠습니까?"))
[AJAX POST: /delete.ajax]
[Controller - delete(PK)]
[Service - delete(PK)]
[Mapper - delete(PK)]
[MyBatis XML - <update id="delete">]
↓ (UPDATE SET DEL_YN='Y', DLTR=?, DEL_DT=NOW())
↓ (WHERE PK = ? AND DEL_YN = 'N')
[DB 논리삭제]
[결과 반환]
[ApiResponseUtil.success(MessageConstants.Common.DELETE_SUCCESS)]
[목록 새로고침]
```
**핵심 포인트:**
- 물리삭제(DELETE)가 아닌 논리삭제(UPDATE) 사용
- `DEL_YN='Y'`, `DLTR=사용자ID`, `DEL_DT=현재시간` 설정
- 모든 조회 쿼리에는 `WHERE DEL_YN='N'` 조건 필수
### 3.2 부과예고 특수 흐름 (스마트 CRUD 패턴)
#### 3.2.1 부과정보 저장 흐름 (자동 INSERT/UPDATE 판단)
```
[사용자]
↓ (부과예고 팝업에서 행위정보 행 선택)
[JavaScript - 행 클릭 이벤트]
↓ (actInfoId 획득)
[AJAX GET: /selectLevyInfoByActInfoId.ajax]
↓ (기존 부과정보 존재 여부 확인)
[Controller - selectLevyInfoByActInfoId]
[Service - selectLevyInfoByActInfoId(actInfoId)]
[Mapper - SELECT WHERE actInfoId = ?]
[DB 조회]
[결과 반환]
├── (있음) → 기존 데이터 복원
│ ↓ (JavaScript에 기존 계산값 모두 표시)
│ ↓ (모드 = 수정)
└── (없음) → 신규 입력 모드
↓ (빈 폼 표시)
↓ (모드 = 신규)
[사용자가 값 입력/수정]
↓ (계산 버튼 클릭)
[AJAX POST: /calculateAll.ajax]
↓ (모든 입력값 전송)
[Controller - calculateAll]
↓ (BigDecimal 변환)
↓ (4단계 계산 수행 - 아래 섹션 참조)
[계산 결과 반환]
[JavaScript - 결과 필드에 표시]
[사용자 - 저장 버튼 클릭]
[AJAX POST: /saveLevyInfo.ajax]
[Controller - saveLevyInfo]
↓ (행위정보 존재 확인)
↓ (sggCd 자동 설정)
[Service - saveLevyInfo]
↓ (기존 데이터 재확인)
├── (있음) → updateLevyInfo 호출
│ ↓ (기존 levyInfoId 사용)
│ ↓ (UPDATE 실행)
└── (없음) → insertLevyInfo 호출
↓ (새 levyInfoId 생성)
↓ (INSERT 실행)
[트랜잭션 커밋]
[성공 메시지 반환]
[JavaScript - 행위정보 그리드 새로고침]
↓ (저장된 데이터 표시 - 체크 아이콘 등)
```
**핵심 포인트:**
- 클라이언트는 INSERT/UPDATE 구분 불필요
- 서버에서 자동으로 존재 여부 확인 후 분기
- 계산 결과를 스냅샷으로 저장하여 데이터 일관성 보장
---
## 4. 계산식 및 비즈니스 규칙
### 4.1 부과금 계산 로직 (CrdnLevyPrvntcController.calculateAll)
**위치**: `go.kr.project.crdn.crndRegistAndView.main.controller.CrdnLevyPrvntcController:473`
#### 4.1.1 단계별 계산 공식
**입력 파라미터:**
1. `bldgNewPrcCrtrAmt` (건물 기준 시가액) - BigDecimal
2. `strctIdx` (구조 지수) - BigDecimal
3. `usgIdx` (용도 지수) - BigDecimal
4. `pstnIdx` (위치 지수) - BigDecimal
5. `elpsYrRdvlrt` (경과 년수별 잔가율) - BigDecimal (0 ~ 1 사이 값)
6. `bscsCstrnRt` (기초 공사율) - BigDecimal (0 ~ 1 사이 값)
7. `vltnArea` (위반 면적) - BigDecimal (㎡)
8. `adsbmtnEnfcRt` (가감산 시행령률) - BigDecimal (%)
9. `cmpttnRtRate` (산정률 비율값) - BigDecimal (0 ~ 1 사이 값)
10. `cmpttnRt2Rate` (산정률2 비율값) - BigDecimal (0 ~ 1 사이 값)
**계산 단계:**
```java
// ===== 1단계: 건축물과세시가 계산 =====
// 공식: 건물기준시가액 × 구조지수 × 용도지수 × 위치지수 × 경과년수별잔가율 × 기초공사율
BigDecimal bdstTxtnMprc = bldgNewPrcCrtrAmtDecimal
.multiply(strctIdxDecimal)
.multiply(usgIdxDecimal)
.multiply(pstnIdxDecimal)
.multiply(elpsYrRdvlrtDecimal)
.multiply(bscsCstrnRtDecimal)
.setScale(0, RoundingMode.HALF_UP); // 반올림
// ===== 2단계: 시가표준액 계산 (1,000원 미만 절사) =====
// 공식: 건축물과세시가 ÷ 1000 (소수점 버림) × 1000
BigDecimal mprcStdAmt = bdstTxtnMprc
.divide(new BigDecimal("1000"), 0, RoundingMode.DOWN) // 1000으로 나눈 후 버림
.multiply(new BigDecimal("1000")); // 다시 1000 곱하기
// ===== 3단계: 산정액 계산 =====
// 공식: 시가표준액 × 위반면적 × (가감산시행령률 ÷ 100) × 산정률 × 산정률2
BigDecimal cmpttnAmt = mprcStdAmt
.multiply(vltnAreaDecimal)
.multiply(adsbmtnEnfcRtDecimal.divide(new BigDecimal("100"), 10, RoundingMode.HALF_UP))
.multiply(cmpttnRtRateDecimal)
.multiply(cmpttnRt2RateDecimal)
.setScale(0, RoundingMode.DOWN); // 소수점 버림
// ===== 4단계: 부과총액 계산 (10원 단위 절사) =====
// 공식: 산정액 ÷ 10 (소수점 버림) × 10
BigDecimal levyWholAmt = cmpttnAmt
.divide(new BigDecimal("10"), 0, RoundingMode.DOWN) // 10으로 나눈 후 버림
.multiply(new BigDecimal("10")); // 다시 10 곱하기
```
**반환 데이터:**
```java
Map<String, Object> data = new HashMap<>();
data.put("bdstTxtnMprc", bdstTxtnMprc); // 건축물과세시가 (숫자)
data.put("mprcStdAmt", mprcStdAmt); // 시가표준액 (숫자)
data.put("cmpttnAmt", cmpttnAmt); // 산정액 (숫자)
data.put("levyWholAmt", levyWholAmt); // 부과총액 (숫자)
data.put("bdstTxtnMprcDisplay", "1,234,567 원"); // 표시용 포맷 문자열
data.put("mprcStdAmtDisplay", "1,234,000 원");
data.put("cmpttnAmtDisplay", "987,654 원");
data.put("levyWholAmtDisplay", "987,650 원");
```
#### 4.1.2 계산 예시
**예시 입력값:**
- 건물기준시가액: 1,000,000원
- 구조지수: 1.0
- 용도지수: 1.0
- 위치지수: 1.0
- 경과년수별잔가율: 0.8 (80%)
- 기초공사율: 0.7 (70%)
- 위반면적: 50㎡
- 가감산시행령률: 100%
- 산정률: 0.5 (50%)
- 산정률2: 1.0 (100%)
**계산 과정:**
```
1단계: 건축물과세시가
= 1,000,000 × 1.0 × 1.0 × 1.0 × 0.8 × 0.7
= 560,000원
2단계: 시가표준액 (1,000원 미만 절사)
= 560,000 ÷ 1,000 = 560 (버림)
= 560 × 1,000
= 560,000원
3단계: 산정액
= 560,000 × 50 × (100 ÷ 100) × 0.5 × 1.0
= 560,000 × 50 × 1.0 × 0.5 × 1.0
= 14,000,000원
4단계: 부과총액 (10원 단위 절사)
= 14,000,000 ÷ 10 = 1,400,000 (버림)
= 1,400,000 × 10
= 14,000,000원
```
### 4.2 가감산율 적용 규칙
**가산율 (adtnRt):**
- 위반 행위의 중대성에 따라 부과금 증가
- 예: 재범, 고의성, 피해 규모 등
**감산율 (sbtrRt):**
- 자진 시정, 경미한 위반 등의 사유로 부과금 감소
- 예: 자진 신고, 즉시 시정, 초범 등
**가감산 시행령률 (adsbmtnEnfcRt):**
- 가산율과 감산율을 합산한 최종 비율
- 공식: `adsbmtnEnfcRt = 100 + adtnRt - sbtrRt`
- 예: 가산 10%, 감산 5% → 105%
### 4.3 산정률 (cmpttnRt) 및 산정률2 (cmpttnRt2)
**산정률 (cmpttnRt):**
- 이행강제금의 1차 산정 비율
- 법령에 따라 정해진 비율표에서 선택
- 예: 건축법 위반 유형별로 0.1 ~ 1.0 범위
**산정률2 (cmpttnRt2):**
- 이행강제금의 2차 산정 비율
- 회차별 증가율 적용
- 예:
- 1회차: 1.0 (100%)
- 2회차: 1.5 (150%)
- 3회차: 2.0 (200%)
### 4.4 기초공사율 (bscsCstrnRt) 계산
**기초공사 구분 코드 (bscsCstrnSeCd):**
- 기초공사 있음 (Y): 일반 건축물
- 기초공사 없음 (N): 간이 건축물
- 복층증축 (D): 기존 건축물 위에 증축
**계산 비율:**
- 기초공사Y: 1.0 (100%)
- 기초공사N: 0.7 (70%) - 기본 비율의 70%
- 복층증축: 별도 산정 비율 적용
### 4.5 경과년수별 잔가율 (elpsYrRdvlrt)
**계산 방법:**
- 건축물의 경과 년수에 따라 감가상각
- 행위시작일자 기준으로 경과 년수 계산
- 잔가율표에서 해당 년도의 비율 조회
**예시:**
- 신축 (0년): 1.0 (100%)
- 5년 경과: 0.8 (80%)
- 10년 경과: 0.6 (60%)
- 20년 경과: 0.4 (40%)
---
## 5. 공통 패턴 및 규칙
### 5.1 VO (Value Object) 패턴
**기본 구조:**
```java
@EqualsAndHashCode(callSuper=true) // PagingVO 상속 시
@Data // Getter, Setter, ToString
@Builder // 빌더 패턴
@AllArgsConstructor // 모든 필드 생성자
@NoArgsConstructor // 기본 생성자
@ToString // toString() 메서드
public class ExampleVO extends PagingVO {
// ===== 기본 필드 =====
private String exampleId; // 기본키
private String exampleNm; // 명칭
// ===== 감사 필드 (Audit Fields) =====
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime regDt; // 등록 일시
private String rgtr; // 등록자
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime mdfcnDt; // 수정 일시
private String mdfr; // 수정자
private String delYn; // 삭제 여부
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime delDt; // 삭제 일시
private String dltr; // 삭제자
// ===== 조회용 추가 필드 (조인 결과) =====
private String exampleCdNm; // 코드명 (표시용)
private String displayValue; // 표시용 값
}
```
### 5.2 Controller 패턴
**표준 구조:**
```java
@Controller
@RequestMapping("/domain/subdomain")
@RequiredArgsConstructor // final 필드 생성자 자동 생성
@Slf4j // 로깅
@Tag(name = "한글 태그명", description = "설명")
public class ExampleController {
private final ExampleService service;
// ===== 화면 메서드 =====
@GetMapping("/list.do")
@Operation(summary = "목록 화면", description = "상세 설명")
public String list(Model model) {
// 공통코드 조회하여 model에 추가
return "경로" + TilesConstants.BASE;
}
// ===== AJAX 메서드 =====
@PostMapping("/list.ajax")
@Operation(summary = "목록 조회", description = "상세 설명")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(responseCode = "400", description = "실패")
})
public ResponseEntity<?> listAjax(@ModelAttribute ExampleVO vo) {
int totalCount = service.selectListTotalCount(vo);
vo.setTotalCount(totalCount);
vo.setPagingYn("Y");
List<ExampleVO> list = service.selectList(vo);
return ApiResponseUtil.successWithGrid(list, vo);
}
@PostMapping("/insert.ajax")
public ResponseEntity<?> insert(@ModelAttribute ExampleVO vo) {
vo.setRgtr(SessionUtil.getUserId());
int result = service.insert(vo);
if (result > 0) {
return ApiResponseUtil.success(MessageConstants.Common.SAVE_SUCCESS);
} else {
return ApiResponseUtil.error(MessageConstants.Common.SAVE_ERROR);
}
}
}
```
### 5.3 Service 패턴
**인터페이스:**
```java
public interface ExampleService {
List<ExampleVO> selectList(ExampleVO vo);
int selectListTotalCount(ExampleVO vo);
ExampleVO selectOne(ExampleVO vo);
int insert(ExampleVO vo);
int update(ExampleVO vo);
int delete(ExampleVO vo);
}
```
**구현체:**
```java
@Service
@RequiredArgsConstructor
@Slf4j
public class ExampleServiceImpl implements ExampleService {
private final ExampleMapper mapper;
@Override
public List<ExampleVO> selectList(ExampleVO vo) {
return mapper.selectList(vo);
}
@Override
@Transactional // 쓰기 작업에만 적용
public int insert(ExampleVO vo) {
// 비즈니스 검증 로직
return mapper.insert(vo);
}
}
```
### 5.4 Mapper 인터페이스 패턴
```java
@Mapper
public interface ExampleMapper {
List<ExampleVO> selectList(ExampleVO vo);
int selectListTotalCount(ExampleVO vo);
ExampleVO selectOne(ExampleVO vo);
int insert(ExampleVO vo);
int update(ExampleVO vo);
int delete(ExampleVO vo);
}
```
### 5.5 MyBatis XML 패턴
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="go.kr.project.domain.mapper.ExampleMapper">
<!-- 공통 컬럼 -->
<sql id="exampleColumns">
EXAMPLE_ID,
EXAMPLE_NM,
REG_DT,
RGTR,
MDFCN_DT,
MDFR,
DEL_YN,
DEL_DT,
DLTR
</sql>
<!-- 목록 조회 -->
<select id="selectList" parameterType="ExampleVO" resultType="ExampleVO">
SELECT
<include refid="exampleColumns"/>
FROM TB_EXAMPLE
WHERE DEL_YN = 'N'
<if test="searchKeyword != null and searchKeyword != ''">
AND EXAMPLE_NM LIKE CONCAT('%', #{searchKeyword}, '%')
</if>
ORDER BY REG_DT DESC
<if test="pagingYn == 'Y'">
LIMIT #{startRow}, #{perPageNum}
</if>
</select>
<!-- 총 개수 조회 -->
<select id="selectListTotalCount" parameterType="ExampleVO" resultType="int">
SELECT COUNT(*)
FROM TB_EXAMPLE
WHERE DEL_YN = 'N'
<if test="searchKeyword != null and searchKeyword != ''">
AND EXAMPLE_NM LIKE CONCAT('%', #{searchKeyword}, '%')
</if>
</select>
<!-- 단건 조회 -->
<select id="selectOne" parameterType="ExampleVO" resultType="ExampleVO">
SELECT
<include refid="exampleColumns"/>
FROM TB_EXAMPLE
WHERE EXAMPLE_ID = #{exampleId}
AND DEL_YN = 'N'
</select>
<!-- 등록 -->
<insert id="insert" parameterType="ExampleVO">
INSERT INTO TB_EXAMPLE (
EXAMPLE_ID,
EXAMPLE_NM,
RGTR
) VALUES (
LPAD(NEXTVAL(seq_example_id), 10, '0'),
#{exampleNm},
#{rgtr}
)
</insert>
<!-- 수정 -->
<update id="update" parameterType="ExampleVO">
UPDATE TB_EXAMPLE
SET EXAMPLE_NM = #{exampleNm},
MDFR = #{mdfr}
WHERE EXAMPLE_ID = #{exampleId}
AND DEL_YN = 'N'
</update>
<!-- 삭제 (논리삭제) -->
<update id="delete" parameterType="ExampleVO">
UPDATE TB_EXAMPLE
SET DEL_YN = 'Y',
DLTR = #{dltr},
DEL_DT = NOW()
WHERE EXAMPLE_ID = #{exampleId}
AND DEL_YN = 'N'
</update>
</mapper>
```
---
## 6. 주요 상수 및 설정
### 6.1 모드 상수
```java
// 화면 모드
public static final String MODE_CREATE = "C"; // 등록
public static final String MODE_UPDATE = "U"; // 수정
public static final String MODE_VIEW = "V"; // 보기
public static final String MODE_DELETE = "D"; // 삭제
```
### 6.2 이행업무 구분 코드 (ImpltTaskSeConstants)
```java
public static final String LEVY_PRVNTC = "1"; // 부과예고
public static final String LEVY = "2"; // 부과
public static final String RELEVY = "3"; // 재부과
public static final String IMPLT_TASK = "4"; // 이행강제금
```
### 6.3 삭제 여부
```java
public static final String DEL_YN_N = "N"; // 미삭제
public static final String DEL_YN_Y = "Y"; // 삭제됨
```
### 6.4 페이징 설정
```java
// PagingVO 기본값
private int page = 1; // 현재 페이지
private int perPageNum = 10; // 페이지당 행 수
private String pagingYn = "N"; // 페이징 사용 여부
```
---
## 7. 보안 및 권한
### 7.1 세션 정보 사용
```java
// 사용자 ID 가져오기
String userId = SessionUtil.getUserId();
// 사용자 정보 가져오기
SessionVO sessionVO = SessionUtil.getSessionVO();
String orgCd = sessionVO.getUser().getOrgCd(); // 조직코드
String userNm = sessionVO.getUser().getUserNm(); // 사용자명
```
### 7.2 CSRF 보호
모든 JSP 폼에 CSRF 토큰 포함:
```jsp
<form>
<sec:csrfInput/>
<!-- 폼 필드들 -->
</form>
```
---
## 8. 에러 처리
### 8.1 MessageException 사용
```java
if (data == null) {
throw new MessageException("데이터를 찾을 수 없습니다.");
}
```
### 8.2 ApiResponseUtil 패턴
```java
// 성공 응답
ApiResponseUtil.success(data, "성공 메시지");
ApiResponseUtil.successWithGrid(list, vo);
// 실패 응답
ApiResponseUtil.error("에러 메시지");
```
---
## 9. 엑셀 다운로드
### 9.1 엑셀 다운로드 패턴
```java
@PostMapping("/excel.do")
public void downloadExcel(
@ModelAttribute ExampleVO paramVO,
HttpServletRequest request,
HttpServletResponse response) {
try {
// 페이징 없이 전체 조회
paramVO.setPagingYn("N");
List<ExampleExcelVO> excelList = service.selectListForExcel(paramVO);
// 파일명 생성
String filename = "예시목록_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) +
".xlsx";
// 엑셀 파일 생성 및 다운로드
new SxssfExcelFile(
ExcelSheetData.of(excelList, ExampleExcelVO.class, "예시 목록 " + excelList.size() + "건"),
request,
response,
filename
);
} catch (Exception e) {
log.error("엑셀 다운로드 중 오류 발생", e);
}
}
```
---
## 10. 참고 파일 경로
### 10.1 핵심 비즈니스 로직 파일
**단속 등록/조회:**
- Controller: `src/main/java/go/kr/project/crdn/crndRegistAndView/main/controller/CrdnRegistAndViewController.java`
- Service: `src/main/java/go/kr/project/crdn/crndRegistAndView/main/service/impl/CrdnRegistAndViewServiceImpl.java`
- Mapper XML: `src/main/resources/mybatis/mapper/crdn/crndRegistAndView/main/CrdnRegistAndViewMapper_maria.xml`
- JSP: `src/main/webapp/WEB-INF/views/crdn/crndRegistAndView/main/list.jsp`
**부과예고 관리 (계산 로직 포함):**
- Controller: `src/main/java/go/kr/project/crdn/crndRegistAndView/main/controller/CrdnLevyPrvntcController.java`
- Service: `src/main/java/go/kr/project/crdn/crndRegistAndView/main/service/impl/CrdnLevyPrvntcServiceImpl.java`
- Mapper XML: `src/main/resources/mybatis/mapper/crdn/crndRegistAndView/main/CrdnLevyPrvntcMapper_maria.xml`
- VO: `src/main/java/go/kr/project/crdn/crndRegistAndView/main/model/CrdnLevyInfoVO.java`
- JSP: `src/main/webapp/WEB-INF/views/crdn/crndRegistAndView/main/crdnLevyPrvntc/levyPrvntcPopup.jsp`
### 10.2 공통 컴포넌트
**페이징:**
- `src/main/java/go/kr/project/common/model/PagingVO.java`
**공통코드:**
- `src/main/java/go/kr/project/common/service/CommonCodeService.java`
**유틸리티:**
- `egovframework.util.ApiResponseUtil` (API 응답 생성)
- `egovframework.util.SessionUtil` (세션 정보 조회)
- `egovframework.util.excel.SxssfExcelFile` (엑셀 다운로드)
---
**이 문서는 프로젝트의 전체 구조와 주요 업무 로직을 설명합니다. 새로운 기능 개발 시 이 문서를 참고하여 기존 패턴을 따라주세요.**