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.

49 KiB

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 웹 애플리케이션입니다.

빌드 및 개발 명령어

빌드 명령어

# 프로젝트 빌드
./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 는 아래 패턴을 이용

 /**
         * 단속 목록 관리 네임스페이스
         */
        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 (삭제)

페이지네이션 패턴 (중요한 순서)

// 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 사이 값)

계산 단계:

// ===== 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 곱하기

반환 데이터:

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) 패턴

기본 구조:

@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 패턴

표준 구조:

@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 패턴

인터페이스:

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);
}

구현체:

@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 인터페이스 패턴

@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 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 모드 상수

// 화면 모드
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)

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 삭제 여부

public static final String DEL_YN_N = "N";  // 미삭제
public static final String DEL_YN_Y = "Y";  // 삭제됨

6.4 페이징 설정

// PagingVO 기본값
private int page = 1;              // 현재 페이지
private int perPageNum = 10;       // 페이지당 행 수
private String pagingYn = "N";     // 페이징 사용 여부

7. 보안 및 권한

7.1 세션 정보 사용

// 사용자 ID 가져오기
String userId = SessionUtil.getUserId();

// 사용자 정보 가져오기
SessionVO sessionVO = SessionUtil.getSessionVO();
String orgCd = sessionVO.getUser().getOrgCd();  // 조직코드
String userNm = sessionVO.getUser().getUserNm(); // 사용자명

7.2 CSRF 보호

모든 JSP 폼에 CSRF 토큰 포함:

<form>
    <sec:csrfInput/>
    <!-- 폼 필드들 -->
</form>

8. 에러 처리

8.1 MessageException 사용

if (data == null) {
    throw new MessageException("데이터를 찾을 수 없습니다.");
}

8.2 ApiResponseUtil 패턴

// 성공 응답
ApiResponseUtil.success(data, "성공 메시지");
ApiResponseUtil.successWithGrid(list, vo);

// 실패 응답
ApiResponseUtil.error("에러 메시지");

9. 엑셀 다운로드

9.1 엑셀 다운로드 패턴

@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 (엑셀 다운로드)

이 문서는 프로젝트의 전체 구조와 주요 업무 로직을 설명합니다. 새로운 기능 개발 시 이 문서를 참고하여 기존 패턴을 따라주세요.