feat: 기본코드 bag 목록과 관리자 CRUD 분리

- /bag/code-kinds, /bag/code-details/{ck_idx} 조회 (LoginAuthFilter, Roles::canManageCodeMaster)
- admin에서는 종류·세부 목록 제거, 등록·수정·삭제만 유지 후 bag으로 리다이렉트
- 사이트 메뉴·기본코드 링크 SQL, CSV 동기화 스크립트·README 보강
- 관리자 대시보드: 발주·판매 테이블 미존재 시 통계 비활성화
- 회원 로그인 잠금(mb_login_fail_count, mb_locked_until) 및 관리자 잠금 해제

Made-with: Cursor
This commit is contained in:
taekyoungc
2026-03-30 15:07:09 +09:00
parent de8f631ca8
commit ab40a90f69
32 changed files with 1026 additions and 704 deletions

513
README.md
View File

@@ -1,486 +1,97 @@
# 종량제 -- 쓰레기봉투 물류시스템 (jongryangje) # 종량제 쓰레기봉투 물류시스템 (jongryangje)
**[종량제 개발목록 (엑셀 다운로드)](https://github.com/wixon-associates/jongryangje/raw/main/assets/종량제_개발목록_20260127.xlsx)** -- 로컬 복제 후에는 [`assets/종량제_개발목록_20260127.xlsx`](./assets/종량제_개발목록_20260127.xlsx) **[종량제 개발목록](https://github.com/wixon-associates/jongryangje/raw/main/assets/종량제_개발목록_20260127.xlsx)** [`assets/종량제_개발목록_20260127.xlsx`](./assets/종량제_개발목록_20260127.xlsx)
지자체/지정판매소 등을 대상으로 하는 **종량제 쓰레기봉투 물류/업무 웹 애플리케이션**입니다. 지자체·지정판매소 등을 대상으로 하는 **종량제 쓰레기봉투 물류·업무 웹 애플리케이션**입니다.
백엔드는 **[CodeIgniter 4](https://codeigniter.com/)** 기반입니다. 백엔드는 **[CodeIgniter 4](https://codeigniter.com/)** 기반입니다.
**저장소:** [wixon-associates/jongryangje](https://github.com/wixon-associates/jongryangje) **저장소:** [wixon-associates/jongryangje](https://github.com/wixon-associates/jongryangje)
| **[구현 화면 스크린샷](./docs/SCREENSHOTS.md)** | **[Notion 진행상황](https://www.notion.so/31b42b987c3780baba32ded04a1d41bb)** | **[서버/배포 가이드](./docs/server.md)** |
### 운영 환경
| 서비스 | URL |
|--------|-----|
| 웹 서비스 | https://trash.wxn.co.kr |
| Gitea (Git) | https://gitea.wxn.co.kr |
| GitHub | https://github.com/wixon-associates/jongryangje |
--- ---
## 기술 스택 ## 요구 사항
| 항목 | 기술 | - **PHP** 8.2 이상 (`composer.json` 기준)
|------|------| - **Composer** 2.x
| Framework | CodeIgniter 4.7+ | - **MySQL / MariaDB** (프로젝트는 `MySQLi` 드라이버 사용)
| Language | PHP 8.2+ (strict types) | - 권장 PHP 확장: `intl`, `mbstring`, MySQL 사용 시 `mysqlnd`
| Database | MySQL / MariaDB (MySQLi) |
| 의존성 관리 | Composer 2.x |
| E2E 테스트 | Playwright (Chromium) |
| 세션 | 파일 기반 (`writable/session/`) |
| 프론트엔드 | Tailwind CSS (CDN), Vanilla JS |
--- ---
## 프로젝트 구조 ## 빠른 시작 (로컬)
``` ### 1) 저장소 복제
app/
├── Config/ # Routes, Database, Roles, Filters, Session 등 (45개)
├── Controllers/ # 28개 컨트롤러
│ ├── Auth.php # 로그인/로그아웃/회원가입
│ ├── Bag.php # 사이트 메뉴 페이지 (10개 메뉴)
│ ├── Home.php # 홈/대시보드
│ └── Admin/ # 관리자 컨트롤러 24개
│ ├── BagOrder.php # 발주 관리
│ ├── BagReceiving.php # 입고 관리
│ ├── BagInventory.php # 재고 현황
│ ├── BagSale.php # 판매/반품 관리
│ ├── BagIssue.php # 무료용 불출 관리
│ ├── ShopOrder.php # 주문 접수 관리
│ ├── BagPrice.php # 봉투 단가 관리
│ ├── PackagingUnit.php # 포장 단위 관리
│ ├── CodeKind.php # 기본코드 종류
│ ├── CodeDetail.php # 세부코드
│ ├── SalesAgency.php # 판매 대행소
│ ├── Manager.php # 담당자
│ ├── Company.php # 업체 (제작/협회/회수)
│ ├── FreeRecipient.php # 무료용 대상자
│ ├── SalesReport.php # 리포트 (판매대장/일계표/수불)
│ ├── User.php # 회원 관리
│ ├── DesignatedShop.php # 지정판매소
│ ├── LocalGovernment.php # 지자체
│ ├── Menu.php # 메뉴 관리
│ ├── PasswordChange.php # 비밀번호 변경
│ └── ...
├── Models/ # 25개 모델
├── Views/ # 88개 뷰 템플릿
│ ├── admin/ # 관리자 뷰 (59개, 엔티티별 하위 디렉토리)
│ ├── bag/ # 사이트 메뉴 뷰 (17개 + 레이아웃)
│ ├── auth/ # 로그인/회원가입 (2개)
│ └── home/ # 대시보드 (1개)
├── Filters/ # AdminAuthFilter (관리자 접근 제어)
├── Helpers/ # admin_helper, pii_encryption_helper
└── Database/ # Migrations, Seeds
public/ # 웹 루트
writable/database/ # SQL 초기화/시드 스크립트 (21개)
e2e/ # Playwright E2E 테스트 (84개 테스트)
assets/ # 기획 문서 (엑셀)
```
---
## 데이터베이스 (25개 테이블)
### 회원/인증
| 테이블 | 용도 |
|--------|------|
| `member` | 회원 (mb_id, mb_level, mb_state, PII 암호화, 로그인 실패 lock) |
| `member_log` | 로그인/로그아웃 감사 로그 (IP, User-Agent) |
| `member_approval_request` | 회원가입 역할 승인 요청 (pending/approved/rejected) |
### 지자체/판매소
| 테이블 | 용도 |
|--------|------|
| `local_government` | 지자체 (테넌트 루트, lg_code 기반) |
| `designated_shop` | 지정판매소 (지자체별, 판매소번호 자동생성) |
### 메뉴 시스템
| 테이블 | 용도 |
|--------|------|
| `menu_type` | 메뉴 유형 (admin, site) |
| `menu` | 메뉴 항목 (트리 구조, 역할별 노출, 지자체별) |
### 기본코드 마스터
| 테이블 | 용도 |
|--------|------|
| `code_kind` | 코드 종류 (A~T, 20종) |
| `code_detail` | 세부코드 (행정구역, 봉투구분, 재질, 용량 등) |
### 단가/포장
| 테이블 | 용도 |
|--------|------|
| `bag_price` | 봉투 단가 (발주/도매/소비자가, 적용기간) |
| `bag_price_history` | 단가 변경 이력 |
| `packaging_unit` | 포장 단위 (박스/팩/낱장) |
| `packaging_unit_history` | 포장 단위 변경 이력 |
### 업체/담당자/대상자
| 테이블 | 용도 |
|--------|------|
| `sales_agency` | 판매 대행소 |
| `company` | 업체 (manufacturer/association/collector) |
| `manager` | 담당자 (소속/직위) |
| `free_recipient` | 무료용 대상자 (생보자/시설/수훈자) |
### 발주/입고/재고
| 테이블 | 용도 |
|--------|------|
| `bag_order` | 발주 (UUID, LOT번호, SHA-256 해시) |
| `bag_order_item` | 발주 품목 (봉투코드별 수량/금액) |
| `bag_receiving` | 입고 (발주 연계, 박스/낱장 수량) |
| `bag_inventory` | 재고 현황 (봉투코드별 현재 재고) |
### 판매/주문/불출
| 테이블 | 용도 |
|--------|------|
| `bag_sale` | 판매/반품 (판매소, 봉투코드, 수량/금액) |
| `shop_order` | 주문 접수 (배달일, 결제/입금/수령 상태) |
| `shop_order_item` | 주문 품목 (박스/팩/낱장 단위) |
| `bag_issue` | 무료용 불출 (연도/분기, 불출처, 상태) |
---
## 역할 체계 (RBAC)
| Level | 역할 | 설명 |
|-------|------|------|
| 4 | Super Admin | 전체 시스템 관리, 작업 지자체 선택 필수 |
| 3 | 지자체관리자 | 소속 지자체 범위 내 관리 |
| 2 | 지정판매소 | 봉투 판매/재고 관리 |
| 1 | 일반 사용자 | 기본 조회 (시민) |
- 역할 상수: `Config\Roles` -- `LEVEL_SUPER_ADMIN(4)`, `LEVEL_LOCAL_ADMIN(3)`, `LEVEL_SHOP(2)`, `LEVEL_CITIZEN(1)`
- `AdminAuthFilter`가 로그인 + 레벨 3/4 + 지자체 선택 여부 검증
## 멀티테넌시
- `local_government.lg_idx`가 테넌트 루트
- 관리자 필터에서 `admin_effective_lg_idx()` 기반 테넌트 분리
- Super Admin은 `/admin/select-local-government`에서 작업 지자체 선택
- 지자체관리자는 소속 `mb_lg_idx` 자동 적용
---
## 라우트 구조
### 공개 페이지
| 경로 | 설명 |
|------|------|
| `/` | 홈 (비로그인: 환영, 로그인: 대시보드) |
| `/login`, `/logout` | 로그인/로그아웃 |
| `/register` | 회원가입 (역할 승인 플로우) |
| `/dashboard/*` | 대시보드 시안 (classic/modern/dense/charts) |
### 사이트 메뉴 (`/bag/*`)
| 경로 | 설명 | 기능 |
|------|------|------|
| `/bag/basic-info` | 기본정보관리 | 코드/단가/포장단위 조회 + 관리 링크 |
| `/bag/purchase-inbound` | 발주 입고 관리 | 발주/입고 목록 + 등록 버튼 |
| `/bag/issue` | 불출 관리 | 불출 목록 + 처리/취소 |
| `/bag/inventory` | 재고 관리 | 봉투별 현재 재고 조회 |
| `/bag/sales` | 판매 관리 | 주문/판매/반품 + 등록 |
| `/bag/sales-stats` | 판매 현황 | 기간별 판매 데이터 |
| `/bag/flow` | 봉투 수불 관리 | 봉투코드별 입출고 수불 요약 |
| `/bag/analytics` | 통계 분석 관리 | Phase 6 예정 |
| `/bag/window` | 창 | Phase 6 예정 |
| `/bag/help` | 도움말 | 시스템 안내 |
### 관리자 (`/admin/*`, adminAuth 필터)
**시스템 관리**
| 경로 | 기능 |
|------|------|
| `/admin` | 관리자 대시보드 |
| `/admin/users/*` | 회원 관리 (CRUD) |
| `/admin/access/login-history` | 로그인 이력 |
| `/admin/access/approvals` | 회원 승인 대기 처리 |
| `/admin/roles` | 역할 목록 |
| `/admin/menus/*` | 메뉴 관리 (트리 CRUD) |
| `/admin/local-governments/*` | 지자체 관리 (CRUD) |
| `/admin/select-local-government` | 작업 지자체 선택 (Super Admin) |
| `/admin/password-change` | 비밀번호 변경 |
| `/admin/designated-shops/*` | 지정판매소 관리 (CRUD) |
**기본정보관리 (Phase 2)**
| 경로 | 기능 |
|------|------|
| `/admin/code-kinds/*` | 기본코드 종류 (CRUD) |
| `/admin/code-details/*` | 세부코드 (CRUD) |
| `/admin/bag-prices/*` | 봉투 단가 (CRUD + 이력) |
| `/admin/packaging-units/*` | 포장 단위 (CRUD + 이력) |
| `/admin/sales-agencies/*` | 판매 대행소 (CRUD) |
| `/admin/managers/*` | 담당자 (CRUD) |
| `/admin/companies/*` | 업체 (CRUD) |
| `/admin/free-recipients/*` | 무료용 대상자 (CRUD) |
**발주/입고/재고 (Phase 3)**
| 경로 | 기능 |
|------|------|
| `/admin/bag-orders/*` | 발주 관리 (등록/상세/취소/삭제) |
| `/admin/bag-receivings/*` | 입고 관리 (등록, 재고 자동 반영) |
| `/admin/bag-inventory` | 재고 현황 조회 |
**판매/주문/불출 (Phase 4)**
| 경로 | 기능 |
|------|------|
| `/admin/shop-orders/*` | 주문 접수 (등록/취소) |
| `/admin/bag-sales/*` | 판매/반품 (등록) |
| `/admin/bag-issues/*` | 무료용 불출 (등록/취소, 재고 연동) |
**리포트 (Phase 5)**
| 경로 | 기능 |
|------|------|
| `/admin/reports/sales-ledger` | 판매 대장 (일자별/기간별) |
| `/admin/reports/daily-summary` | 일계표 (일계 + 월간 누계) |
| `/admin/reports/period-sales` | 기간별 판매현황 |
| `/admin/reports/supply-demand` | 봉투 수불 현황 |
---
## 모델 (25개)
| 모델 | 테이블 | 용도 |
|------|--------|------|
| MemberModel | member | 회원 계정 |
| MemberLogModel | member_log | 로그인 이력 |
| MemberApprovalRequestModel | member_approval_request | 승인 요청 |
| LocalGovernmentModel | local_government | 지자체 |
| DesignatedShopModel | designated_shop | 지정판매소 |
| MenuModel | menu | 메뉴 항목 |
| MenuTypeModel | menu_type | 메뉴 유형 |
| CodeKindModel | code_kind | 코드 종류 |
| CodeDetailModel | code_detail | 세부코드 |
| BagPriceModel | bag_price | 봉투 단가 |
| BagPriceHistoryModel | bag_price_history | 단가 변경 이력 |
| PackagingUnitModel | packaging_unit | 포장 단위 |
| PackagingUnitHistoryModel | packaging_unit_history | 포장 단위 이력 |
| SalesAgencyModel | sales_agency | 판매 대행소 |
| CompanyModel | company | 업체 |
| ManagerModel | manager | 담당자 |
| FreeRecipientModel | free_recipient | 무료 대상자 |
| BagOrderModel | bag_order | 발주 |
| BagOrderItemModel | bag_order_item | 발주 품목 |
| BagReceivingModel | bag_receiving | 입고 |
| BagInventoryModel | bag_inventory | 재고 |
| BagSaleModel | bag_sale | 판매/반품 |
| ShopOrderModel | shop_order | 주문 접수 |
| ShopOrderItemModel | shop_order_item | 주문 품목 |
| BagIssueModel | bag_issue | 무료 불출 |
---
## 보안
| 항목 | 구현 |
|------|------|
| 인증 | 세션 기반 로그인, AdminAuthFilter로 관리자 접근 제어 |
| RBAC | 4단계 역할 (Config\Roles), 메뉴별 역할 노출 |
| PII 암호화 | `pii_encryption_helper` (AES, `ENC:` prefix) - 이메일/전화번호 |
| 비밀번호 | `password_hash()` + `password_verify()` (bcrypt) |
| 로그인 보호 | 5회 실패 시 계정 잠금 (mb_login_fail_count, mb_locked_until) |
| CSRF | CodeIgniter 내장 CSRF 필터 |
| 멀티테넌시 | lg_idx 기반 데이터 격리, 세션에서 테넌트 관리 |
---
## 빠른 시작
### 1) 저장소 복제 및 의존성 설치
```bash ```bash
git clone https://github.com/wixon-associates/jongryangje.git git clone https://github.com/wixon-associates/jongryangje.git
cd jongryangje cd jongryangje
composer install
npm install # Playwright E2E 테스트용
``` ```
### 2) 환경 ### 2) 의존성
```bash
composer install
```
### 3) 환경 설정
루트에 있는 샘플 파일을 복사해 `.env`를 만듭니다.
```bash ```bash
cp env .env cp env .env
``` ```
`.env`에서 설정: `.env`에서 최소한 다음을 설정합니다.
| 항목 | 설명 | | 항목 | 설명 |
|------|------| |------|------|
| `app.baseURL` | 예: `http://localhost:8045/` | | `app.baseURL` | 예: `http://localhost:8080/` (끝에 `/`) |
| `database.default.*` | DB 호스트/DB명/사용자/비밀번호 | | `database.default.*` | DB 호스트·DB명·사용자·비밀번호 |
| `encryption.key` | PII 암호화용 64자리 hex | | `encryption.key` | 개인정보(이메일·연락처) 암호화용. **64자리 hex** (예: `php -r "echo bin2hex(random_bytes(32));"`) |
### 3) 데이터베이스 준비
SQL 스크립트 실행 순서:
| 순서 | 파일 | 용도 | ### 4) 데이터베이스 준비
|------|------|------|
| 1 | `init_jongryangje_dev.sql` | DB/사용자 생성 |
| 2 | `login_tables.sql` | 회원/로그인/지자체/지정판매소 테이블 |
| 3 | `member_approval_request_add.sql` | 승인 요청 테이블 |
| 4 | `member_login_lock_add.sql` | 로그인 잠금 컬럼 |
| 5 | `menu_tables.sql` | 메뉴 시스템 + 시드 |
| 6 | `menu_type_add_site.sql` | 사이트 메뉴 타입 |
| 7 | `local_government_init_daegu.sql` | 대구 8개 구군 지자체 |
| 8 | `code_master_init_daegu.sql` | 기본코드 마스터 (20종) |
| 9 | `bag_price_tables.sql` | 단가 + 단가 이력 테이블 |
| 10 | `packaging_unit_tables.sql` | 포장 단위 + 이력 테이블 |
| 11 | `sales_agency_tables.sql` | 판매 대행소 테이블 |
| 12 | `manager_tables.sql` | 담당자 테이블 |
| 13 | `company_tables.sql` | 업체 테이블 |
| 14 | `free_recipient_tables.sql` | 무료 대상자 테이블 |
| 15 | `order_tables.sql` | 발주/발주품목 테이블 |
| 16 | `sales_tables.sql` | 판매/입고/재고/불출/주문 테이블 |
| 17 | `seed_test_accounts.sql` | 테스터 계정 4개 |
| 18 | `seed_realistic_data.sql` | 실제형 시범 데이터 |
### 4) 개발 서버 실행
```bash
php spark serve --port=8045
```
### 5) 시드 데이터 (선택)
```bash
node e2e/helpers/db-seed.js # 테스터 계정 생성
node e2e/helpers/db-seed-realistic.js # 실제형 시범 데이터
```
---
## E2E 테스트 (Playwright)
```bash
# 전체 테스트
npm test
# headed 모드 (브라우저 표시)
npm run test:headed
# 특정 파일
npx playwright test e2e/auth.spec.js
# 특정 테스트
npx playwright test -g "로그인 페이지"
```
### 테스터 계정 (비밀번호: `test1234!`)
| ID | 역할 | Level |
|----|------|-------|
| `tester_admin` | Super Admin | 4 |
| `tester_local` | 지자체관리자 (중구청) | 3 |
| `tester_shop` | 지정판매소 | 2 |
| `tester_user` | 일반 사용자 | 1 |
### 테스트 파일 (84개 테스트)
| 파일 | 테스트 수 | 대상 |
|------|-----------|------|
| auth.spec.js | 9 | 로그인/로그아웃/회원가입 |
| admin.spec.js | 10 | 관리자 패널 접근 |
| public.spec.js | 4 | 공개 페이지 |
| bag-site.spec.js | 11 | 사이트 메뉴 /bag/* |
| code-management.spec.js | 7 | 기본코드 CRUD |
| bag-price.spec.js | 6 | 봉투 단가 |
| packaging-unit.spec.js | 3 | 포장 단위 |
| phase2-entities.spec.js | 8 | 대행소/담당자/업체/무료대상자 |
| phase2-extra.spec.js | 4 | 지자체 수정/비밀번호/로그인 lock |
| phase3-order.spec.js | 8 | 발주/입고/재고 |
| phase4-sales.spec.js | 10 | 주문/판매/불출 |
| phase5-reports.spec.js | 4 | 리포트 |
---
## 기본코드 체계
A~T 총 20종의 코드 체계 (`code_kind` + `code_detail`):
| 코드 | 코드명 | 코드 | 코드명 |
|------|--------|------|--------|
| A | 도/특별시/광역시 구분 | K | 반품사유 |
| B | 특별시/광역시/시/군코드 | L | 지정판매소 변경사유 |
| C | 구코드 | M | 수불구분 |
| D | 동코드 | N | 동판종류 |
| E | 봉투구분 | O | 봉투명 (상세 봉투코드) |
| F | 봉투재질 | P | 작업권한 |
| G | 용량별 | Q | 예산과목 |
| H | 무상지급 대상 | R | 은행목록 |
| I | 판매형태 | S | 소속 |
| J | 반품형태 | T | 직위 |
---
## 개발 진행 현황
### Phase별 완료 현황
| Phase | 내용 | 상태 |
|-------|------|------|
| Phase 1 | 프로젝트 초기 세팅, 로그인/회원가입, RBAC, 멀티테넌시, 메뉴 관리, PII 암호화 | **완료** |
| Phase 2 | 기본정보관리 (코드/단가/포장/대행소/담당자/업체/무료대상자/지자체수정/비밀번호/로그인lock) | **완료** |
| Phase 3 | 발주/입고/재고 (발주등록/LOT/취소/삭제/현황/입고처리/재고현황) | **완료** |
| Phase 4 | 주문/판매/불출 (주문접수/판매/반품/불출처리/취소) | **완료** |
| Phase 5 | 리포트 (판매대장/일계표/기간별현황/수불현황) | **완료** |
| Phase 6 | 모바일앱 + 고급기능 (바코드/통계/엑셀/인쇄) | 대기 |
### Phase 6 이후 대기 작업
- 지정판매소 다조건 조회 + 엑셀 + 인쇄 + 바코드 출력
- 지정판매소 지도 표시 / 현황 (신규/취소)
- 카카오 주소 검색 API 연동
- 년 판매 현황 (월별/분기별)
- 지정판매소별 판매현황
- 홈택스 세금계산서 엑셀 생성
- 반품/파기 현황, LOT 수불 조회
- 바코드 스캐너 연동 (Electron + serialport)
- 실사 선별/등록/조회
- 페이지네이션/엑셀/인쇄 공통 컴포넌트
- CRUD 로깅 (전체 데이터 변경 이력)
- 2차 인증 적용
- 대시보드 실 데이터 연동
- 모바일앱 (15개 기능)
---
## SQL 스크립트 목록 (writable/database/)
| 파일 | 용도 | | 파일 | 용도 |
|------|------| |------|------|
| `init_jongryangje_dev.sql` | DB/사용자 생성 | | `writable/database/init_jongryangje_dev.sql` | DB·DB 사용자 생성 예시 |
| `login_tables.sql` | member, member_log, local_government, designated_shop | | `writable/database/login_tables.sql` | 회원·로그인·지자체 등 기본 테이블 |
| `member_approval_request_add.sql` | 승인 요청 테이블 | | `writable/database/member_approval_request_add.sql` | 회원가입 역할 승인 요청 테이블 (별도 추가) |
| `member_login_lock_add.sql` | 로그인 실패 잠금 컬럼 | | `writable/database/menu_tables.sql` 등 | 메뉴·시드 관련 SQL |
| `menu_tables.sql` | menu_type, menu + admin/site 시드 | | `writable/database/order_tables.sql` | 발주 (`bag_order`, `bag_order_item` 등) |
| `menu_type_add_site.sql` | 사이트 메뉴 타입 추가 | | `writable/database/sales_tables.sql` | 판매·입고·재고·불출·주문 등 (`bag_sale`, `bag_receiving`, `bag_inventory` …) |
| `menu_add_lg_idx.sql` | 메뉴에 지자체 컬럼 추가 | | `writable/database/code_master_init_daegu.sql` | 기본코드 종류·세부코드 시드 |
| `menu_site_seed_from_csv.sql` | 사이트 네비게이션 시드 | | `writable/database/code_master_sync_from_csv.sql` | 개발목록 CSV와 DB 보강용 (선택) |
| `local_government_init_daegu.sql` | 대구 8개 구군 지자체 |
| `code_master_init_daegu.sql` | 기본코드 20종 + 세부코드 | 개발목록 **기본코드 종류** CSV와 DB를 맞출 때는 `writable/tools/sync_basic_codes_from_csv.py`로 SQL을 생성하거나, 위 `code_master_sync_from_csv.sql`을 참고해 실행할 수 있습니다.
| `bag_price_tables.sql` | bag_price, bag_price_history |
| `packaging_unit_tables.sql` | packaging_unit, packaging_unit_history | ---
| `sales_agency_tables.sql` | sales_agency |
| `manager_tables.sql` | manager | ## 주요 URL (참고)
| `company_tables.sql` | company |
| `free_recipient_tables.sql` | free_recipient | | 경로 | 설명 |
| `order_tables.sql` | bag_order, bag_order_item | |------|------|
| `sales_tables.sql` | bag_sale, bag_receiving, bag_inventory, bag_issue, shop_order, shop_order_item | | `/` | 홈 (비로그인 시 환영 화면 등) |
| `seed_test_accounts.sql` | 테스터 계정 4개 | | `/login`, `/logout` | 로그인·로그아웃 |
| `seed_realistic_data.sql` | 실제형 시범 데이터 (대구 남구청 기준) | | `/register` | 회원가입 (역할 승인 플로우 연동) |
| `fix_double_encoding.sql` | UTF-8 이중인코딩 수정 | | `/dashboard` | 로그인 후 사이트형 메인 흐름 |
| `/dashboard/classic-mock` 등 | UI 시안용 라우트 |
| `/bag/basic-info` | 기본정보 (단가·포장단위 등 링크 허브) |
| `/bag/code-kinds` | **기본코드 종류** 목록 (로그인 사용자 조회; 시민·판매소는 조회만) |
| `/bag/code-details/{ck_idx}` | **기본코드 세부** 목록 (종류별) |
| `/admin` | 관리자 (권한·필터 적용) |
| `/admin/access/approvals` | 회원가입 역할 **승인 대기** 처리 |
### 기본코드 CRUD (관리자)
- **목록 화면은 `/bag/code-kinds`, `/bag/code-details/{ck_idx}`만 사용**합니다. `/admin` 아래에는 종류·세부 **목록 페이지가 없고**, 등록·수정·삭제 등 **CRUD만** `admin` 라우트로 열립니다.
- 예: `/admin/code-kinds/create`, `edit/{id}`, `store`/`update`/`delete`; `/admin/code-details/{ck_idx}/create`, `code-details/edit/{id}` 등 (전체는 `Routes.php` 참고).
- 예전 주소 `/admin/code-details/{ck_idx}`**`/bag/code-details/{ck_idx}`로 리다이렉트**됩니다.
E2E: 기본코드·사이트 메뉴 경로는 `e2e/code-management.spec.js`, `e2e/bag-site.spec.js`를 참고하세요.
정확한 라우트는 `app/Config/Routes.php`를 확인하세요.

View File

@@ -26,6 +26,7 @@ class Filters extends BaseFilters
*/ */
public array $aliases = [ public array $aliases = [
'adminAuth' => \App\Filters\AdminAuthFilter::class, 'adminAuth' => \App\Filters\AdminAuthFilter::class,
'loginAuth' => \App\Filters\LoginAuthFilter::class,
'csrf' => CSRF::class, 'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class, 'toolbar' => DebugToolbar::class,
'honeypot' => Honeypot::class, 'honeypot' => Honeypot::class,

View File

@@ -42,6 +42,14 @@ class Roles extends BaseConfig
return $level === self::LEVEL_SUPER_ADMIN || $level === self::LEVEL_HEADQUARTERS_ADMIN; return $level === self::LEVEL_SUPER_ADMIN || $level === self::LEVEL_HEADQUARTERS_ADMIN;
} }
/**
* 기본코드(종류·세부) 등록·수정·삭제 가능 (지자체·super·본부 관리자)
*/
public static function canManageCodeMaster(int $level): bool
{
return $level === self::LEVEL_LOCAL_ADMIN || self::isSuperAdminEquivalent($level);
}
/** /**
* TOTP 2차 인증 적용 대상 (지자체·super·본부 관리자) * TOTP 2차 인증 적용 대상 (지자체·super·본부 관리자)
*/ */

View File

@@ -17,6 +17,11 @@ $routes->get('bag/waste-suibal-enterprise', 'Home::wasteSuibalEnterprise');
// 사이트 메뉴 (/bag/*) // 사이트 메뉴 (/bag/*)
$routes->get('bag/basic-info', 'Bag::basicInfo'); $routes->get('bag/basic-info', 'Bag::basicInfo');
$routes->get('bag/code-kinds', 'Bag::codeKinds');
$routes->get('bag/code-details/(:num)', 'Bag::codeDetails/$1');
// 옛 주소 호환: 세부 목록만 사이트로 이동
$routes->get('admin/code-details/(:num)', 'Admin\CodeDetail::index/$1');
$routes->get('bag/purchase-inbound', 'Bag::purchaseInbound'); $routes->get('bag/purchase-inbound', 'Bag::purchaseInbound');
$routes->get('bag/issue', 'Bag::issue'); $routes->get('bag/issue', 'Bag::issue');
$routes->get('bag/inventory', 'Bag::inventory'); $routes->get('bag/inventory', 'Bag::inventory');
@@ -63,6 +68,7 @@ $routes->group('admin', ['filter' => 'adminAuth'], static function ($routes): vo
$routes->post('users/store', 'Admin\User::store'); $routes->post('users/store', 'Admin\User::store');
$routes->get('users/edit/(:num)', 'Admin\User::edit/$1'); $routes->get('users/edit/(:num)', 'Admin\User::edit/$1');
$routes->post('users/update/(:num)', 'Admin\User::update/$1'); $routes->post('users/update/(:num)', 'Admin\User::update/$1');
$routes->post('users/unlock-login/(:num)', 'Admin\User::unlockLogin/$1');
$routes->post('users/delete/(:num)', 'Admin\User::delete/$1'); $routes->post('users/delete/(:num)', 'Admin\User::delete/$1');
$routes->get('access/login-history', 'Admin\Access::loginHistory'); $routes->get('access/login-history', 'Admin\Access::loginHistory');
$routes->get('access/approvals', 'Admin\Access::approvals'); $routes->get('access/approvals', 'Admin\Access::approvals');
@@ -88,8 +94,7 @@ $routes->group('admin', ['filter' => 'adminAuth'], static function ($routes): vo
$routes->get('password-change', 'Admin\PasswordChange::index'); $routes->get('password-change', 'Admin\PasswordChange::index');
$routes->post('password-change', 'Admin\PasswordChange::update'); $routes->post('password-change', 'Admin\PasswordChange::update');
// 기본코드 종류 관리 (P2-01) // 기본코드 종류 관리 (P2-01) — 등록·수정·삭제는 관리자 전용
$routes->get('code-kinds', 'Admin\CodeKind::index');
$routes->get('code-kinds/create', 'Admin\CodeKind::create'); $routes->get('code-kinds/create', 'Admin\CodeKind::create');
$routes->post('code-kinds/store', 'Admin\CodeKind::store'); $routes->post('code-kinds/store', 'Admin\CodeKind::store');
$routes->get('code-kinds/edit/(:num)', 'Admin\CodeKind::edit/$1'); $routes->get('code-kinds/edit/(:num)', 'Admin\CodeKind::edit/$1');
@@ -97,7 +102,6 @@ $routes->group('admin', ['filter' => 'adminAuth'], static function ($routes): vo
$routes->post('code-kinds/delete/(:num)', 'Admin\CodeKind::delete/$1'); $routes->post('code-kinds/delete/(:num)', 'Admin\CodeKind::delete/$1');
// 세부코드 관리 (P2-02) // 세부코드 관리 (P2-02)
$routes->get('code-details/(:num)', 'Admin\CodeDetail::index/$1');
$routes->get('code-details/(:num)/create', 'Admin\CodeDetail::create/$1'); $routes->get('code-details/(:num)/create', 'Admin\CodeDetail::create/$1');
$routes->post('code-details/store', 'Admin\CodeDetail::store'); $routes->post('code-details/store', 'Admin\CodeDetail::store');
$routes->get('code-details/edit/(:num)', 'Admin\CodeDetail::edit/$1'); $routes->get('code-details/edit/(:num)', 'Admin\CodeDetail::edit/$1');

View File

@@ -1,10 +1,14 @@
<?php <?php
declare(strict_types=1);
namespace App\Controllers\Admin; namespace App\Controllers\Admin;
use App\Controllers\BaseController; use App\Controllers\BaseController;
use App\Models\CodeKindModel; use App\Models\CodeKindModel;
use App\Models\CodeDetailModel; use App\Models\CodeDetailModel;
use CodeIgniter\HTTP\RedirectResponse;
use Config\Roles;
class CodeDetail extends BaseController class CodeDetail extends BaseController
{ {
@@ -17,31 +21,30 @@ class CodeDetail extends BaseController
$this->detailModel = model(CodeDetailModel::class); $this->detailModel = model(CodeDetailModel::class);
} }
public function index(int $ckIdx) private function redirectIfCannotManageCodeMaster(): ?RedirectResponse
{ {
$kind = $this->kindModel->find($ckIdx); if (! Roles::canManageCodeMaster((int) session()->get('mb_level'))) {
if ($kind === null) { return redirect()->to(site_url('bag/code-kinds'))->with('error', '코드 관리 권한이 없습니다.');
return redirect()->to(site_url('admin/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
} }
$list = $this->detailModel->where('cd_ck_idx', $ckIdx)->orderBy('cd_sort', 'ASC')->paginate(20); return null;
$pager = $this->detailModel->pager; }
return view('admin/layout', [ /** @deprecated 사이트 URL 유지용 — 세부 목록은 /bag/code-details/{ck_idx} */
'title' => '세부코드 관리 — ' . $kind->ck_name . ' (' . $kind->ck_code . ')', public function index(int $ckIdx): RedirectResponse
'content' => view('admin/code_detail/index', [ {
'kind' => $kind, return redirect()->to(site_url('bag/code-details/' . $ckIdx));
'list' => $list,
'pager' => $pager,
]),
]);
} }
public function create(int $ckIdx) public function create(int $ckIdx)
{ {
if ($r = $this->redirectIfCannotManageCodeMaster()) {
return $r;
}
$kind = $this->kindModel->find($ckIdx); $kind = $this->kindModel->find($ckIdx);
if ($kind === null) { if ($kind === null) {
return redirect()->to(site_url('admin/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.'); return redirect()->to(site_url('bag/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
} }
return view('admin/layout', [ return view('admin/layout', [
@@ -52,6 +55,10 @@ class CodeDetail extends BaseController
public function store() public function store()
{ {
if ($r = $this->redirectIfCannotManageCodeMaster()) {
return $r;
}
$rules = [ $rules = [
'cd_ck_idx' => 'required|is_natural_no_zero', 'cd_ck_idx' => 'required|is_natural_no_zero',
'cd_code' => 'required|max_length[50]', 'cd_code' => 'required|max_length[50]',
@@ -74,14 +81,18 @@ class CodeDetail extends BaseController
'cd_regdate' => date('Y-m-d H:i:s'), 'cd_regdate' => date('Y-m-d H:i:s'),
]); ]);
return redirect()->to(site_url('admin/code-details/' . $ckIdx))->with('success', '세부코드가 등록되었습니다.'); return redirect()->to(site_url('bag/code-details/' . $ckIdx))->with('success', '세부코드가 등록되었습니다.');
} }
public function edit(int $id) public function edit(int $id)
{ {
if ($r = $this->redirectIfCannotManageCodeMaster()) {
return $r;
}
$item = $this->detailModel->find($id); $item = $this->detailModel->find($id);
if ($item === null) { if ($item === null) {
return redirect()->to(site_url('admin/code-kinds'))->with('error', '세부코드를 찾을 수 없습니다.'); return redirect()->to(site_url('bag/code-kinds'))->with('error', '세부코드를 찾을 수 없습니다.');
} }
$kind = $this->kindModel->find($item->cd_ck_idx); $kind = $this->kindModel->find($item->cd_ck_idx);
@@ -97,9 +108,13 @@ class CodeDetail extends BaseController
public function update(int $id) public function update(int $id)
{ {
if ($r = $this->redirectIfCannotManageCodeMaster()) {
return $r;
}
$item = $this->detailModel->find($id); $item = $this->detailModel->find($id);
if ($item === null) { if ($item === null) {
return redirect()->to(site_url('admin/code-kinds'))->with('error', '세부코드를 찾을 수 없습니다.'); return redirect()->to(site_url('bag/code-kinds'))->with('error', '세부코드를 찾을 수 없습니다.');
} }
$rules = [ $rules = [
@@ -118,19 +133,23 @@ class CodeDetail extends BaseController
'cd_state' => (int) $this->request->getPost('cd_state'), 'cd_state' => (int) $this->request->getPost('cd_state'),
]); ]);
return redirect()->to(site_url('admin/code-details/' . $item->cd_ck_idx))->with('success', '세부코드가 수정되었습니다.'); return redirect()->to(site_url('bag/code-details/' . $item->cd_ck_idx))->with('success', '세부코드가 수정되었습니다.');
} }
public function delete(int $id) public function delete(int $id)
{ {
if ($r = $this->redirectIfCannotManageCodeMaster()) {
return $r;
}
$item = $this->detailModel->find($id); $item = $this->detailModel->find($id);
if ($item === null) { if ($item === null) {
return redirect()->to(site_url('admin/code-kinds'))->with('error', '세부코드를 찾을 수 없습니다.'); return redirect()->to(site_url('bag/code-kinds'))->with('error', '세부코드를 찾을 수 없습니다.');
} }
$ckIdx = $item->cd_ck_idx; $ckIdx = $item->cd_ck_idx;
$this->detailModel->delete($id); $this->detailModel->delete($id);
return redirect()->to(site_url('admin/code-details/' . $ckIdx))->with('success', '세부코드가 삭제되었습니다.'); return redirect()->to(site_url('bag/code-details/' . $ckIdx))->with('success', '세부코드가 삭제되었습니다.');
} }
} }

View File

@@ -1,10 +1,13 @@
<?php <?php
declare(strict_types=1);
namespace App\Controllers\Admin; namespace App\Controllers\Admin;
use App\Controllers\BaseController; use App\Controllers\BaseController;
use App\Models\CodeKindModel; use App\Models\CodeKindModel;
use App\Models\CodeDetailModel; use App\Models\CodeDetailModel;
use CodeIgniter\HTTP\RedirectResponse;
use Config\Roles; use Config\Roles;
class CodeKind extends BaseController class CodeKind extends BaseController
@@ -16,30 +19,21 @@ class CodeKind extends BaseController
$this->kindModel = model(CodeKindModel::class); $this->kindModel = model(CodeKindModel::class);
} }
public function index() private function redirectIfCannotManageCodeMaster(): ?RedirectResponse
{ {
$list = $this->kindModel->orderBy('ck_code', 'ASC')->paginate(20); if (! Roles::canManageCodeMaster((int) session()->get('mb_level'))) {
$pager = $this->kindModel->pager; return redirect()->to(site_url('bag/code-kinds'))->with('error', '코드 관리 권한이 없습니다.');
// 세부코드 수 매핑
$detailModel = model(CodeDetailModel::class);
$countMap = [];
foreach ($list as $row) {
$countMap[$row->ck_idx] = $detailModel->where('cd_ck_idx', $row->ck_idx)->countAllResults(false);
} }
return view('admin/layout', [ return null;
'title' => '기본코드 종류 관리',
'content' => view('admin/code_kind/index', [
'list' => $list,
'countMap' => $countMap,
'pager' => $pager,
]),
]);
} }
public function create() public function create()
{ {
if ($r = $this->redirectIfCannotManageCodeMaster()) {
return $r;
}
return view('admin/layout', [ return view('admin/layout', [
'title' => '기본코드 종류 등록', 'title' => '기본코드 종류 등록',
'content' => view('admin/code_kind/create'), 'content' => view('admin/code_kind/create'),
@@ -48,6 +42,10 @@ class CodeKind extends BaseController
public function store() public function store()
{ {
if ($r = $this->redirectIfCannotManageCodeMaster()) {
return $r;
}
$rules = [ $rules = [
'ck_code' => 'required|max_length[20]|is_unique[code_kind.ck_code]', 'ck_code' => 'required|max_length[20]|is_unique[code_kind.ck_code]',
'ck_name' => 'required|max_length[100]', 'ck_name' => 'required|max_length[100]',
@@ -64,14 +62,18 @@ class CodeKind extends BaseController
'ck_regdate' => date('Y-m-d H:i:s'), 'ck_regdate' => date('Y-m-d H:i:s'),
]); ]);
return redirect()->to(site_url('admin/code-kinds'))->with('success', '코드 종류가 등록되었습니다.'); return redirect()->to(site_url('bag/code-kinds'))->with('success', '코드 종류가 등록되었습니다.');
} }
public function edit(int $id) public function edit(int $id)
{ {
if ($r = $this->redirectIfCannotManageCodeMaster()) {
return $r;
}
$item = $this->kindModel->find($id); $item = $this->kindModel->find($id);
if ($item === null) { if ($item === null) {
return redirect()->to(site_url('admin/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.'); return redirect()->to(site_url('bag/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
} }
return view('admin/layout', [ return view('admin/layout', [
@@ -82,9 +84,13 @@ class CodeKind extends BaseController
public function update(int $id) public function update(int $id)
{ {
if ($r = $this->redirectIfCannotManageCodeMaster()) {
return $r;
}
$item = $this->kindModel->find($id); $item = $this->kindModel->find($id);
if ($item === null) { if ($item === null) {
return redirect()->to(site_url('admin/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.'); return redirect()->to(site_url('bag/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
} }
$rules = [ $rules = [
@@ -101,24 +107,28 @@ class CodeKind extends BaseController
'ck_state' => (int) $this->request->getPost('ck_state'), 'ck_state' => (int) $this->request->getPost('ck_state'),
]); ]);
return redirect()->to(site_url('admin/code-kinds'))->with('success', '코드 종류가 수정되었습니다.'); return redirect()->to(site_url('bag/code-kinds'))->with('success', '코드 종류가 수정되었습니다.');
} }
public function delete(int $id) public function delete(int $id)
{ {
$item = $this->kindModel->find($id); if ($r = $this->redirectIfCannotManageCodeMaster()) {
if ($item === null) { return $r;
return redirect()->to(site_url('admin/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.'); }
$item = $this->kindModel->find($id);
if ($item === null) {
return redirect()->to(site_url('bag/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
} }
// 세부코드가 있으면 삭제 불가
$detailCount = model(CodeDetailModel::class)->where('cd_ck_idx', $id)->countAllResults(); $detailCount = model(CodeDetailModel::class)->where('cd_ck_idx', $id)->countAllResults();
if ($detailCount > 0) { if ($detailCount > 0) {
return redirect()->to(site_url('admin/code-kinds')) return redirect()->to(site_url('bag/code-kinds'))
->with('error', '세부코드가 ' . $detailCount . '개 존재하여 삭제할 수 없습니다. 먼저 세부코드를 삭제해 주세요.'); ->with('error', '세부코드가 ' . $detailCount . '개 존재하여 삭제할 수 없습니다. 먼저 세부코드를 삭제해 주세요.');
} }
$this->kindModel->delete($id); $this->kindModel->delete($id);
return redirect()->to(site_url('admin/code-kinds'))->with('success', '코드 종류가 삭제되었습니다.');
return redirect()->to(site_url('bag/code-kinds'))->with('success', '코드 종류가 삭제되었습니다.');
} }
} }

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Controllers\Admin; namespace App\Controllers\Admin;
use App\Controllers\BaseController; use App\Controllers\BaseController;
use CodeIgniter\Database\Exceptions\DatabaseException;
class Dashboard extends BaseController class Dashboard extends BaseController
{ {
@@ -22,65 +23,71 @@ class Dashboard extends BaseController
'issue_count_month'=> 0, 'issue_count_month'=> 0,
'recent_orders' => [], 'recent_orders' => [],
'recent_sales' => [], 'recent_sales' => [],
'stats_unavailable'=> false,
]; ];
if ($lgIdx) { if ($lgIdx) {
$db = \Config\Database::connect(); $db = \Config\Database::connect();
// 총 발주 건수/금액 try {
$orderStats = $db->query(" // 총 발주 건수/금액
SELECT COUNT(*) as cnt, $orderStats = $db->query("
COALESCE(SUM(sub.total_amt), 0) as total_amount SELECT COUNT(*) as cnt,
FROM bag_order bo COALESCE(SUM(sub.total_amt), 0) as total_amount
LEFT JOIN ( FROM bag_order bo
SELECT boi_bo_idx, SUM(boi_amount) as total_amt LEFT JOIN (
FROM bag_order_item GROUP BY boi_bo_idx SELECT boi_bo_idx, SUM(boi_amount) as total_amt
) sub ON sub.boi_bo_idx = bo.bo_idx FROM bag_order_item GROUP BY boi_bo_idx
WHERE bo.bo_lg_idx = ? AND bo.bo_status = 'normal' ) sub ON sub.boi_bo_idx = bo.bo_idx
", [$lgIdx])->getRow(); WHERE bo.bo_lg_idx = ? AND bo.bo_status = 'normal'
$stats['order_count'] = (int) ($orderStats->cnt ?? 0); ", [$lgIdx])->getRow();
$stats['order_amount'] = (int) ($orderStats->total_amount ?? 0); $stats['order_count'] = (int) ($orderStats->cnt ?? 0);
$stats['order_amount'] = (int) ($orderStats->total_amount ?? 0);
// 총 판매 건수/금액 // 총 판매 건수/금액
$saleStats = $db->query(" $saleStats = $db->query("
SELECT COUNT(*) as cnt, COALESCE(SUM(bs_amount), 0) as total_amount SELECT COUNT(*) as cnt, COALESCE(SUM(bs_amount), 0) as total_amount
FROM bag_sale FROM bag_sale
WHERE bs_lg_idx = ? AND bs_type = 'sale' WHERE bs_lg_idx = ? AND bs_type = 'sale'
", [$lgIdx])->getRow(); ", [$lgIdx])->getRow();
$stats['sale_count'] = (int) ($saleStats->cnt ?? 0); $stats['sale_count'] = (int) ($saleStats->cnt ?? 0);
$stats['sale_amount'] = (int) ($saleStats->total_amount ?? 0); $stats['sale_amount'] = (int) ($saleStats->total_amount ?? 0);
// 현재 재고 품목 수 // 현재 재고 품목 수
$invCount = $db->query(" $invCount = $db->query("
SELECT COUNT(*) as cnt FROM bag_inventory WHERE bi_lg_idx = ? AND bi_qty > 0 SELECT COUNT(*) as cnt FROM bag_inventory WHERE bi_lg_idx = ? AND bi_qty > 0
", [$lgIdx])->getRow(); ", [$lgIdx])->getRow();
$stats['inventory_count'] = (int) ($invCount->cnt ?? 0); $stats['inventory_count'] = (int) ($invCount->cnt ?? 0);
// 이번 달 불출 건수 // 이번 달 불출 건수
$monthStart = date('Y-m-01'); $monthStart = date('Y-m-01');
$issueCount = $db->query(" $issueCount = $db->query("
SELECT COUNT(*) as cnt FROM bag_issue SELECT COUNT(*) as cnt FROM bag_issue
WHERE bi2_lg_idx = ? AND bi2_status = 'normal' AND bi2_issue_date >= ? WHERE bi2_lg_idx = ? AND bi2_status = 'normal' AND bi2_issue_date >= ?
", [$lgIdx, $monthStart])->getRow(); ", [$lgIdx, $monthStart])->getRow();
$stats['issue_count_month'] = (int) ($issueCount->cnt ?? 0); $stats['issue_count_month'] = (int) ($issueCount->cnt ?? 0);
// 최근 발주 5건 // 최근 발주 5건
$stats['recent_orders'] = $db->query(" $stats['recent_orders'] = $db->query("
SELECT bo_idx, bo_lot_no, bo_order_date, bo_status SELECT bo_idx, bo_lot_no, bo_order_date, bo_status
FROM bag_order FROM bag_order
WHERE bo_lg_idx = ? WHERE bo_lg_idx = ?
ORDER BY bo_order_date DESC, bo_idx DESC ORDER BY bo_order_date DESC, bo_idx DESC
LIMIT 5 LIMIT 5
", [$lgIdx])->getResult(); ", [$lgIdx])->getResult();
// 최근 판매 5건 // 최근 판매 5건
$stats['recent_sales'] = $db->query(" $stats['recent_sales'] = $db->query("
SELECT bs_idx, bs_ds_name, bs_bag_name, bs_qty, bs_amount, bs_sale_date, bs_type SELECT bs_idx, bs_ds_name, bs_bag_name, bs_qty, bs_amount, bs_sale_date, bs_type
FROM bag_sale FROM bag_sale
WHERE bs_lg_idx = ? WHERE bs_lg_idx = ?
ORDER BY bs_sale_date DESC, bs_idx DESC ORDER BY bs_sale_date DESC, bs_idx DESC
LIMIT 5 LIMIT 5
", [$lgIdx])->getResult(); ", [$lgIdx])->getResult();
} catch (DatabaseException $e) {
$stats['stats_unavailable'] = true;
log_message('error', '[Dashboard] 통계 조회 실패(테이블 미생성 등): ' . $e->getMessage());
}
} }
return view('admin/layout', [ return view('admin/layout', [

View File

@@ -177,6 +177,23 @@ class User extends BaseController
return redirect()->to(site_url('admin/users'))->with('success', '회원 정보가 수정되었습니다.'); return redirect()->to(site_url('admin/users'))->with('success', '회원 정보가 수정되었습니다.');
} }
/**
* 로그인 실패 누적 잠금(mb_locked_until) 해제 — 비밀번호는 그대로 두고 재시도만 가능하게 함
*/
public function unlockLogin(int $id)
{
$member = $this->memberModel->find($id);
if (! $member) {
return redirect()->to(site_url('admin/users'))->with('error', '회원을 찾을 수 없습니다.');
}
$this->memberModel->update($id, [
'mb_login_fail_count' => 0,
'mb_locked_until' => null,
]);
return redirect()->back()->with('success', '로그인 잠금이 해제되었습니다.');
}
/** /**
* 현재 로그인한 관리자가 부여 가능한 역할 목록. * 현재 로그인한 관리자가 부여 가능한 역할 목록.
* super/본부만 4·5 부여 가능, 지자체 관리자는 1~3만. * super/본부만 4·5 부여 가능, 지자체 관리자는 1~3만.

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Controllers; namespace App\Controllers;
use CodeIgniter\Database\Exceptions\DatabaseException;
use App\Models\BagInventoryModel; use App\Models\BagInventoryModel;
use App\Models\BagIssueModel; use App\Models\BagIssueModel;
use App\Models\BagOrderModel; use App\Models\BagOrderModel;
@@ -18,6 +19,7 @@ use App\Models\PackagingUnitModel;
use App\Models\SalesAgencyModel; use App\Models\SalesAgencyModel;
use App\Models\ShopOrderModel; use App\Models\ShopOrderModel;
use App\Models\DesignatedShopModel; use App\Models\DesignatedShopModel;
use Config\Roles;
class Bag extends BaseController class Bag extends BaseController
{ {
@@ -44,17 +46,74 @@ class Bag extends BaseController
public function basicInfo(): string public function basicInfo(): string
{ {
$lgIdx = $this->lgIdx(); $lgIdx = $this->lgIdx();
$data = []; $data = [
'bagPrices' => [],
'packagingUnits' => [],
];
if ($lgIdx) { if ($lgIdx) {
$data['codeKinds'] = model(CodeKindModel::class)->orderBy('ck_code', 'ASC')->findAll(); try {
$data['bagPrices'] = model(BagPriceModel::class)->where('bp_lg_idx', $lgIdx)->orderBy('bp_bag_code', 'ASC')->findAll(); $data['bagPrices'] = model(BagPriceModel::class)->where('bp_lg_idx', $lgIdx)->orderBy('bp_bag_code', 'ASC')->findAll();
$data['packagingUnits'] = model(PackagingUnitModel::class)->where('pu_lg_idx', $lgIdx)->orderBy('pu_bag_code', 'ASC')->findAll(); } catch (DatabaseException $e) {
log_message('error', '[basicInfo] bag_price 조회 실패(테이블 미생성 등): ' . $e->getMessage());
}
try {
$data['packagingUnits'] = model(PackagingUnitModel::class)->where('pu_lg_idx', $lgIdx)->orderBy('pu_bag_code', 'ASC')->findAll();
} catch (DatabaseException $e) {
log_message('error', '[basicInfo] packaging_unit 조회 실패: ' . $e->getMessage());
}
} }
return $this->render('기본정보관리', 'bag/basic_info', $data); return $this->render('기본정보관리', 'bag/basic_info', $data);
} }
/**
* 기본코드 종류·세부코드 조회 전용 (사이트 메뉴 기본코드관리)
*/
public function codeKinds(): string
{
$kindModel = model(CodeKindModel::class);
$detailModel = model(CodeDetailModel::class);
$kinds = $kindModel->orderBy('ck_code', 'ASC')->findAll();
$countMap = [];
foreach ($kinds as $row) {
// countAllResults() 기본값(true)으로 매번 빌더 초기화 — false 시 where 누적되어 2번째부터 0건으로 보임
$countMap[$row->ck_idx] = (int) $detailModel->where('cd_ck_idx', $row->ck_idx)->countAllResults();
}
return $this->render('기본코드관리', 'bag/code_kinds', [
'codeKinds' => $kinds,
'countMap' => $countMap,
'canManage' => Roles::canManageCodeMaster((int) session()->get('mb_level')),
]);
}
/**
* 기본코드 세부 목록 (사이트 레이아웃). 등록·수정·삭제 폼은 /admin/code-details/* 유지.
*/
public function codeDetails(int $ckIdx)
{
$kindModel = model(CodeKindModel::class);
$detailModel = model(CodeDetailModel::class);
$kind = $kindModel->find($ckIdx);
if ($kind === null) {
return redirect()->to(site_url('bag/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
}
$list = $detailModel->where('cd_ck_idx', $ckIdx)->orderBy('cd_sort', 'ASC')->orderBy('cd_idx', 'ASC')->paginate(20);
$pager = $detailModel->pager;
$canManage = Roles::canManageCodeMaster((int) session()->get('mb_level'));
$title = ($canManage ? '세부코드 관리' : '세부코드 조회') . ' — ' . $kind->ck_name . ' (' . $kind->ck_code . ')';
return $this->render($title, 'bag/code_details', [
'kind' => $kind,
'list' => $list,
'pager' => $pager,
'canManage' => $canManage,
]);
}
// ────────────────────────────────────────────── // ──────────────────────────────────────────────
// 발주 입고 관리 // 발주 입고 관리
// ────────────────────────────────────────────── // ──────────────────────────────────────────────

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
/**
* 로그인만 필요 (mb_level 무관). 기본코드 조회 등 시민·판매소도 접근 가능한 /admin/* 하위용.
*/
class LoginAuthFilter implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null)
{
if (! session()->get('logged_in')) {
return redirect()->to(site_url('login'))->with('error', '로그인이 필요합니다.');
}
return null;
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
return $response;
}
}

View File

@@ -1,6 +1,6 @@
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel"> <section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<a href="<?= base_url('admin/code-details/' . (int) $kind->ck_idx) ?>" class="text-blue-600 hover:underline text-sm">&larr; <?= esc($kind->ck_name) ?></a> <a href="<?= base_url('bag/code-details/' . (int) $kind->ck_idx) ?>" class="text-blue-600 hover:underline text-sm">&larr; <?= esc($kind->ck_name) ?></a>
<span class="text-gray-400">|</span> <span class="text-gray-400">|</span>
<span class="text-sm font-bold text-gray-700">세부코드 등록</span> <span class="text-sm font-bold text-gray-700">세부코드 등록</span>
</div> </div>
@@ -32,7 +32,7 @@
<div class="flex gap-2 pt-2"> <div class="flex gap-2 pt-2">
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button> <button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
<a href="<?= base_url('admin/code-details/' . (int) $kind->ck_idx) ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a> <a href="<?= base_url('bag/code-details/' . (int) $kind->ck_idx) ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -1,6 +1,6 @@
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel"> <section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<a href="<?= base_url('admin/code-details/' . (int) $kind->ck_idx) ?>" class="text-blue-600 hover:underline text-sm">&larr; <?= esc($kind->ck_name) ?></a> <a href="<?= base_url('bag/code-details/' . (int) $kind->ck_idx) ?>" class="text-blue-600 hover:underline text-sm">&larr; <?= esc($kind->ck_name) ?></a>
<span class="text-gray-400">|</span> <span class="text-gray-400">|</span>
<span class="text-sm font-bold text-gray-700">세부코드 수정</span> <span class="text-sm font-bold text-gray-700">세부코드 수정</span>
</div> </div>
@@ -39,7 +39,7 @@
<div class="flex gap-2 pt-2"> <div class="flex gap-2 pt-2">
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">수정</button> <button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">수정</button>
<a href="<?= base_url('admin/code-details/' . (int) $kind->ck_idx) ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a> <a href="<?= base_url('bag/code-details/' . (int) $kind->ck_idx) ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -1,49 +0,0 @@
<?= view('components/print_header', ['printTitle' => '세부코드 관리 - ' . esc($kind->ck_name)]) ?>
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<div class="flex flex-wrap items-center justify-between gap-y-2">
<div class="flex items-center gap-2">
<a href="<?= base_url('admin/code-kinds') ?>" class="text-blue-600 hover:underline text-sm">&larr; 코드 종류</a>
<span class="text-gray-400">|</span>
<span class="text-sm font-bold text-gray-700">세부코드 — <?= esc($kind->ck_name) ?> (<?= esc($kind->ck_code) ?>)</span>
</div>
<div class="flex items-center gap-2">
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
<a href="<?= base_url('admin/code-details/' . (int) $kind->ck_idx . '/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">세부코드 등록</a>
</div>
</div>
</section>
<div class="border border-gray-300 overflow-auto mt-2">
<table class="w-full data-table">
<thead>
<tr>
<th class="w-16">번호</th>
<th class="w-24">코드</th>
<th>코드명</th>
<th class="w-20">정렬순서</th>
<th class="w-20">상태</th>
<th>등록일</th>
<th class="w-28">작업</th>
</tr>
</thead>
<tbody class="text-right">
<?php foreach ($list as $row): ?>
<tr>
<td class="text-center"><?= esc($row->cd_idx) ?></td>
<td class="text-center font-mono"><?= esc($row->cd_code) ?></td>
<td class="text-left pl-2"><?= esc($row->cd_name) ?></td>
<td class="text-center"><?= (int) $row->cd_sort ?></td>
<td class="text-center"><?= (int) $row->cd_state === 1 ? '사용' : '미사용' ?></td>
<td class="text-left pl-2"><?= esc($row->cd_regdate ?? '') ?></td>
<td class="text-center">
<a href="<?= base_url('admin/code-details/edit/' . (int) $row->cd_idx) ?>" class="text-blue-600 hover:underline text-sm">수정</a>
<form action="<?= base_url('admin/code-details/delete/' . (int) $row->cd_idx) ?>" method="POST" class="inline ml-1" onsubmit="return confirm('이 세부코드를 삭제하시겠습니까?');">
<?= csrf_field() ?>
<button type="submit" class="text-red-600 hover:underline text-sm">삭제</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if (isset($pager)): ?><div class="mt-3"><?= $pager->links() ?></div><?php endif; ?>

View File

@@ -17,7 +17,7 @@
<div class="flex gap-2 pt-2"> <div class="flex gap-2 pt-2">
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button> <button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
<a href="<?= base_url('admin/code-kinds') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a> <a href="<?= base_url('bag/code-kinds') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -25,7 +25,7 @@
<div class="flex gap-2 pt-2"> <div class="flex gap-2 pt-2">
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">수정</button> <button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">수정</button>
<a href="<?= base_url('admin/code-kinds') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a> <a href="<?= base_url('bag/code-kinds') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -1,48 +0,0 @@
<?= view('components/print_header', ['printTitle' => '기본코드 종류 관리']) ?>
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<div class="flex flex-wrap items-center justify-between gap-y-2">
<span class="text-sm font-bold text-gray-700">기본코드 종류 관리</span>
<div class="flex items-center gap-2">
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
<a href="<?= base_url('admin/code-kinds/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">코드 종류 등록</a>
</div>
</div>
</section>
<div class="border border-gray-300 overflow-auto mt-2">
<table class="w-full data-table">
<thead>
<tr>
<th class="w-16">번호</th>
<th class="w-20">코드</th>
<th>코드명</th>
<th class="w-24">세부코드 수</th>
<th class="w-20">상태</th>
<th>등록일</th>
<th class="w-40">작업</th>
</tr>
</thead>
<tbody class="text-right">
<?php foreach ($list as $row): ?>
<tr>
<td class="text-center"><?= esc($row->ck_idx) ?></td>
<td class="text-center font-mono font-bold"><?= esc($row->ck_code) ?></td>
<td class="text-left pl-2"><?= esc($row->ck_name) ?></td>
<td class="text-center">
<a href="<?= base_url('admin/code-details/' . (int) $row->ck_idx) ?>" class="text-blue-600 hover:underline"><?= (int) ($countMap[$row->ck_idx] ?? 0) ?>개</a>
</td>
<td class="text-center"><?= (int) $row->ck_state === 1 ? '사용' : '미사용' ?></td>
<td class="text-left pl-2"><?= esc($row->ck_regdate ?? '') ?></td>
<td class="text-center">
<a href="<?= base_url('admin/code-details/' . (int) $row->ck_idx) ?>" class="text-green-600 hover:underline text-sm mr-1">세부코드</a>
<a href="<?= base_url('admin/code-kinds/edit/' . (int) $row->ck_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">수정</a>
<form action="<?= base_url('admin/code-kinds/delete/' . (int) $row->ck_idx) ?>" method="POST" class="inline" onsubmit="return confirm('이 코드 종류를 삭제하시겠습니까?');">
<?= csrf_field() ?>
<button type="submit" class="text-red-600 hover:underline text-sm">삭제</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if (isset($pager)): ?><div class="mt-3"><?= $pager->links() ?></div><?php endif; ?>

View File

@@ -6,6 +6,15 @@
</div> </div>
<?php else: ?> <?php else: ?>
<?php if (! empty($s['stats_unavailable'])): ?>
<div class="border border-amber-300 bg-amber-50 p-4 text-sm text-amber-900 mb-4">
발주·판매·재고·불출 통계 테이블이 아직 없거나 조회에 실패했습니다. MySQL에서
<code class="text-xs bg-white px-1 rounded">writable/database/order_tables.sql</code>과
<code class="text-xs bg-white px-1 rounded">writable/database/sales_tables.sql</code>을
<code class="text-xs bg-white px-1 rounded">jongryangje_dev</code> DB에 실행해 주세요.
</div>
<?php endif; ?>
<!-- 통계 카드 --> <!-- 통계 카드 -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4"> <div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
<div class="border border-gray-300 p-4 bg-white"> <div class="border border-gray-300 p-4 bg-white">

View File

@@ -1,6 +1,19 @@
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel"> <section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<span class="text-sm font-bold text-gray-700">회원 수정</span> <span class="text-sm font-bold text-gray-700">회원 수정</span>
</section> </section>
<?php
$editLockUntil = $member->mb_locked_until ?? null;
$editLoginLocked = $editLockUntil !== null && $editLockUntil !== '' && strtotime((string) $editLockUntil) > time();
?>
<?php if ($editLoginLocked): ?>
<div class="mt-2 p-3 bg-amber-50 border border-amber-200 text-sm text-amber-900 flex flex-wrap items-center gap-3">
<span>비밀번호 오류로 로그인이 잠겨 있습니다. (~ <?= esc(date('Y-m-d H:i', strtotime((string) $editLockUntil))) ?>)</span>
<form action="<?= base_url('admin/users/unlock-login/' . $member->mb_idx) ?>" method="POST" class="inline" onsubmit="return confirm('로그인 잠금을 해제할까요?');">
<?= csrf_field() ?>
<button type="submit" class="bg-amber-700 text-white px-3 py-1 rounded-sm text-sm hover:opacity-90">잠금 해제</button>
</form>
</div>
<?php endif; ?>
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-xl"> <div class="border border-gray-300 p-4 mt-2 bg-white max-w-xl">
<form action="<?= base_url('admin/users/update/' . $member->mb_idx) ?>" method="POST" class="space-y-4"> <form action="<?= base_url('admin/users/update/' . $member->mb_idx) ?>" method="POST" class="space-y-4">
<?= csrf_field() ?> <?= csrf_field() ?>

View File

@@ -18,6 +18,7 @@
<th>이메일</th> <th>이메일</th>
<th>역할</th> <th>역할</th>
<th>상태</th> <th>상태</th>
<th>로그인 잠금</th>
<th>가입일</th> <th>가입일</th>
<th>관리</th> <th>관리</th>
</tr> </tr>
@@ -42,9 +43,27 @@
} }
?> ?>
</td> </td>
<td class="text-left pl-2 text-sm">
<?php
$until = $row->mb_locked_until ?? null;
$loginLocked = $until !== null && $until !== '' && strtotime((string) $until) > time();
if ($loginLocked) {
echo '잠금~' . esc(date('Y-m-d H:i', strtotime((string) $until)));
} else {
$fail = (int) ($row->mb_login_fail_count ?? 0);
echo $fail > 0 ? '실패 ' . $fail . '회' : '—';
}
?>
</td>
<td class="text-left pl-2"><?= esc($row->mb_regdate ?? '') ?></td> <td class="text-left pl-2"><?= esc($row->mb_regdate ?? '') ?></td>
<td class="text-center"> <td class="text-center">
<?php if ((int) $row->mb_state !== 0): ?> <?php if ((int) $row->mb_state !== 0): ?>
<?php if ($loginLocked): ?>
<form action="<?= base_url('admin/users/unlock-login/' . $row->mb_idx) ?>" method="POST" class="inline mr-1" onsubmit="return confirm('로그인 잠금을 해제할까요?');">
<?= csrf_field() ?>
<button type="submit" class="text-amber-700 hover:underline">잠금해제</button>
</form>
<?php endif; ?>
<a href="<?= base_url('admin/users/edit/' . $row->mb_idx) ?>" class="text-blue-600 hover:underline">수정</a> <a href="<?= base_url('admin/users/edit/' . $row->mb_idx) ?>" class="text-blue-600 hover:underline">수정</a>
<form action="<?= base_url('admin/users/delete/' . $row->mb_idx) ?>" method="POST" class="inline ml-1" onsubmit="return confirm('탈퇴 처리하시겠습니까?');"> <form action="<?= base_url('admin/users/delete/' . $row->mb_idx) ?>" method="POST" class="inline ml-1" onsubmit="return confirm('탈퇴 처리하시겠습니까?');">
<?= csrf_field() ?> <?= csrf_field() ?>

View File

@@ -1,30 +1,8 @@
<div class="space-y-6"> <div class="space-y-6">
<!-- 기본코드 종류 --> <p class="text-sm text-gray-600">
<section> 기본코드 종류·세부코드는 상단 메뉴 <strong class="font-medium text-gray-800">기본정보관리</strong>
<div class="flex items-center justify-between mb-2 border-b pb-1"> <a href="<?= base_url('bag/code-kinds') ?>" class="text-blue-600 hover:underline">기본코드관리</a>에서 확인할 있습니다.
<h3 class="text-base font-bold text-gray-700">기본코드 종류</h3> </p>
<a href="<?= base_url('admin/code-kinds') ?>" class="text-blue-600 hover:underline text-sm">관리 &rarr;</a>
</div>
<table class="data-table">
<thead><tr>
<th class="w-16">번호</th><th>코드</th><th>코드명</th><th>상태</th>
</tr></thead>
<tbody>
<?php if (! empty($codeKinds)): ?>
<?php foreach ($codeKinds as $i => $row): ?>
<tr>
<td class="text-center"><?= $i + 1 ?></td>
<td class="text-center"><?= esc($row->ck_code) ?></td>
<td><?= esc($row->ck_name) ?></td>
<td class="text-center"><?= ($row->ck_status ?? 'active') === 'active' ? '사용' : '미사용' ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="4" class="text-center text-gray-400 py-4">등록된 코드 종류가 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>
</section>
<!-- 봉투 단가 --> <!-- 봉투 단가 -->
<section> <section>

View File

@@ -0,0 +1,65 @@
<?php
/** @var object $kind */
/** @var list<object> $list */
/** @var bool $canManage */
$canManage = ! empty($canManage);
?>
<div class="space-y-3">
<?= view('components/print_header', ['printTitle' => '세부코드 - ' . esc($kind->ck_name)]) ?>
<section class="border border-gray-300 rounded bg-control-panel p-2">
<div class="flex flex-wrap items-center justify-between gap-y-2">
<div class="flex flex-wrap items-center gap-2 text-sm">
<a href="<?= base_url('bag/code-kinds') ?>" class="text-blue-600 hover:underline">&larr; 기본코드 종류</a>
<span class="text-gray-400">|</span>
<span class="font-bold text-gray-700"><?= $canManage ? '세부코드 관리' : '세부코드 조회' ?> — <?= esc($kind->ck_name) ?> (<?= esc($kind->ck_code) ?>)</span>
</div>
<div class="flex items-center gap-2">
<button type="button" onclick="window.print()" class="no-print rounded border border-gray-300 px-3 py-1 text-sm text-gray-600 hover:bg-gray-50">인쇄</button>
<?php if ($canManage): ?>
<a href="<?= base_url('admin/code-details/' . (int) $kind->ck_idx . '/create') ?>" class="rounded border border-transparent bg-[#1c4e80] px-3 py-1.5 text-sm text-white shadow hover:opacity-90">세부코드 등록</a>
<?php endif; ?>
</div>
</div>
</section>
<div class="border border-gray-300 overflow-auto">
<table class="data-table w-full">
<thead>
<tr>
<th class="w-16">번호</th>
<th class="w-24">코드</th>
<th>코드명</th>
<th class="w-20">정렬순서</th>
<th class="w-20">상태</th>
<th>등록일</th>
<?php if ($canManage): ?>
<th class="w-28">작업</th>
<?php endif; ?>
</tr>
</thead>
<tbody>
<?php foreach ($list as $row): ?>
<tr>
<td class="text-center"><?= esc((string) $row->cd_idx) ?></td>
<td class="text-center font-mono"><?= esc($row->cd_code) ?></td>
<td><?= esc($row->cd_name) ?></td>
<td class="text-center"><?= (int) $row->cd_sort ?></td>
<td class="text-center"><?= (int) $row->cd_state === 1 ? '사용' : '미사용' ?></td>
<td class="text-left"><?= esc($row->cd_regdate ?? '') ?></td>
<?php if ($canManage): ?>
<td class="text-center text-sm">
<a href="<?= base_url('admin/code-details/edit/' . (int) $row->cd_idx) ?>" class="text-blue-600 hover:underline">수정</a>
<form action="<?= base_url('admin/code-details/delete/' . (int) $row->cd_idx) ?>" method="POST" class="ml-1 inline" onsubmit="return confirm('이 세부코드를 삭제하시겠습니까?');">
<?= csrf_field() ?>
<button type="submit" class="text-red-600 hover:underline">삭제</button>
</form>
</td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if (isset($pager) && $pager !== null): ?>
<div class="mt-3"><?= $pager->links() ?></div>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,62 @@
<?php
/** @var list<object> $codeKinds */
/** @var array<int,int> $countMap */
/** @var bool $canManage */
$canManage = ! empty($canManage);
$colCount = $canManage ? 7 : 6;
?>
<div class="space-y-4">
<section>
<div class="flex flex-wrap items-center justify-between gap-2 mb-2 border-b pb-1">
<h3 class="text-base font-bold text-gray-700">기본코드 종류</h3>
<div class="flex flex-wrap items-center gap-2 text-xs sm:text-sm">
<?php if ($canManage): ?>
<a href="<?= base_url('admin/code-kinds/create') ?>" class="inline-flex items-center rounded bg-[#1c4e80] px-3 py-1.5 text-white shadow hover:opacity-90">코드 종류 등록</a>
<?php else: ?>
<span class="text-gray-500">세부코드는 행의 링크에서 조회할 수 있습니다.</span>
<?php endif; ?>
</div>
</div>
<table class="data-table">
<thead><tr>
<th class="w-14"><?= $canManage ? 'PK' : '번호' ?></th>
<th class="w-24">코드</th>
<th>코드명</th>
<th class="w-28">세부코드</th>
<th class="w-20">상태</th>
<th class="w-40">등록일</th>
<?php if ($canManage): ?>
<th class="w-44">작업</th>
<?php endif; ?>
</tr></thead>
<tbody>
<?php if (! empty($codeKinds)): ?>
<?php $i = 0; foreach ($codeKinds as $row): $i++; ?>
<tr>
<td class="text-center"><?= $canManage ? esc((string) $row->ck_idx) : (string) $i ?></td>
<td class="text-center font-mono"><?= esc($row->ck_code) ?></td>
<td><?= esc($row->ck_name) ?></td>
<td class="text-center">
<a href="<?= base_url('bag/code-details/' . (int) $row->ck_idx) ?>" class="text-blue-600 hover:underline"><?= (int) ($countMap[$row->ck_idx] ?? 0) ?>개 보기</a>
</td>
<td class="text-center"><?= (int) ($row->ck_state ?? 0) === 1 ? '사용' : '미사용' ?></td>
<td class="text-left"><?= esc($row->ck_regdate ?? '') ?></td>
<?php if ($canManage): ?>
<td class="text-center text-sm">
<a href="<?= base_url('bag/code-details/' . (int) $row->ck_idx) ?>" class="text-green-600 hover:underline mr-1">세부코드</a>
<a href="<?= base_url('admin/code-kinds/edit/' . (int) $row->ck_idx) ?>" class="text-blue-600 hover:underline mr-1">수정</a>
<form action="<?= base_url('admin/code-kinds/delete/' . (int) $row->ck_idx) ?>" method="POST" class="inline" onsubmit="return confirm('이 코드 종류를 삭제하시겠습니까?');">
<?= csrf_field() ?>
<button type="submit" class="text-red-600 hover:underline">삭제</button>
</form>
</td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="<?= (string) $colCount ?>" class="text-center text-gray-400 py-4">등록된 코드 종류가 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>
</section>
</div>

View File

@@ -64,7 +64,7 @@ body { overflow: hidden; }
</head> </head>
<body class="bg-gray-100 text-gray-800 flex flex-col h-screen font-sans antialiased select-none"> <body class="bg-gray-100 text-gray-800 flex flex-col h-screen font-sans antialiased select-none">
<!-- BEGIN: Top Navigation --> <!-- BEGIN: Top Navigation -->
<header class="bg-white border-b border-gray-300 h-12 flex items-center justify-between px-4 shrink-0 z-20"> <header class="relative bg-white border-b border-gray-300 h-12 flex items-center justify-between px-4 shrink-0 z-50">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="w-6 h-6 flex items-center justify-center shrink-0"> <div class="w-6 h-6 flex items-center justify-center shrink-0">
@@ -78,20 +78,49 @@ body { overflow: hidden; }
<nav class="hidden md:flex gap-5 text-sm font-medium text-gray-600"> <nav class="hidden md:flex gap-5 text-sm font-medium text-gray-600">
<?php if (! empty($siteNavTree)): ?> <?php if (! empty($siteNavTree)): ?>
<?php foreach ($siteNavTree as $navItem): ?> <?php foreach ($siteNavTree as $navItem): ?>
<?php $isActive = ($currentPath === trim((string) $navItem->mm_link, '/')); ?> <?php
$navLink = trim((string) $navItem->mm_link, '/');
$isActive = ($currentPath === $navLink);
if (! $isActive && ! empty($navItem->children)) {
foreach ($navItem->children as $ch) {
$childPath = trim((string) $ch->mm_link, '/');
if ($currentPath === $childPath) {
$isActive = true;
break;
}
// 기본코드 세부는 메뉴에 직접 링크 없음 → 기본코드관리(bag/code-kinds)와 동일 메뉴군으로 표시
if ($childPath === 'bag/code-kinds' && str_starts_with($currentPath, 'bag/code-details')) {
$isActive = true;
break;
}
}
}
?>
<div class="relative group"> <div class="relative group">
<a class="<?= $isActive ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" <a class="<?= $isActive ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>"
href="<?= base_url($navItem->mm_link) ?>"> href="<?= base_url($navItem->mm_link) ?>">
<?= esc($navItem->mm_name) ?> <?= esc($navItem->mm_name) ?>
</a> </a>
<?php if (! empty($navItem->children)): ?> <?php if (! empty($navItem->children)): ?>
<div class="absolute left-0 top-full hidden group-hover:block bg-white border border-gray-200 rounded shadow-lg min-w-[10rem] z-30"> <?php /* pt-1: 부모와 패널 사이 호버 끊김 방지. z-50: 제목 바보다 위 */ ?>
<div class="absolute left-0 top-full z-50 hidden pt-1 min-w-[12rem] group-hover:block group-focus-within:block">
<div class="bg-white border border-gray-200 rounded shadow-lg py-1">
<?php foreach ($navItem->children as $child): ?> <?php foreach ($navItem->children as $child): ?>
<a href="<?= base_url($child->mm_link) ?>" <?php
$childLink = trim((string) ($child->mm_link ?? ''));
?>
<?php if ($childLink !== ''): ?>
<a href="<?= base_url($childLink) ?>"
class="block px-3 py-1.5 text-sm text-gray-700 hover:bg-blue-50 whitespace-nowrap"> class="block px-3 py-1.5 text-sm text-gray-700 hover:bg-blue-50 whitespace-nowrap">
<?= esc($child->mm_name) ?> <?= esc($child->mm_name) ?>
</a> </a>
<?php else: ?>
<span class="block px-3 py-1.5 text-sm text-gray-400 cursor-default whitespace-nowrap" title="관리자 메뉴 관리에서 링크를 설정해 주세요">
<?= esc($child->mm_name) ?>
</span>
<?php endif; ?>
<?php endforeach; ?> <?php endforeach; ?>
</div>
</div> </div>
<?php endif; ?> <?php endif; ?>
</div> </div>

View File

@@ -10,7 +10,14 @@ test.describe('사이트 메뉴 (/bag/*) 페이지 접근', () => {
test('기본정보관리', async ({ page }) => { test('기본정보관리', async ({ page }) => {
await page.goto('/bag/basic-info'); await page.goto('/bag/basic-info');
await expect(page).toHaveURL(/\/bag\/basic-info/); await expect(page).toHaveURL(/\/bag\/basic-info/);
await expect(page.locator('text=기본코드 종류')).toBeVisible(); await expect(page.locator('text=봉투 단가')).toBeVisible();
await expect(page.locator('a[href*="bag/code-kinds"]')).toBeVisible();
});
test('기본코드관리 (/bag/code-kinds)', async ({ page }) => {
await page.goto('/bag/code-kinds');
await expect(page).toHaveURL(/\/bag\/code-kinds/);
await expect(page.locator('h3:has-text("기본코드 종류")')).toBeVisible();
}); });
test('발주 입고 관리', async ({ page }) => { test('발주 입고 관리', async ({ page }) => {

View File

@@ -17,23 +17,23 @@ test.describe('P2-01: 기본코드 종류 관리', () => {
await loginAsAdmin(page); await loginAsAdmin(page);
}); });
test('코드 종류 목록 접근', async ({ page }) => { test('코드 종류 목록 접근 (/bag/code-kinds)', async ({ page }) => {
await page.goto('/admin/code-kinds'); await page.goto('/bag/code-kinds');
await expect(page).toHaveURL(/\/admin\/code-kinds/); await expect(page).toHaveURL(/\/bag\/code-kinds/);
await expect(page.locator('td:has-text("도/특별시/광역시 구분")').first()).toBeVisible({ timeout: 10000 }); await expect(page.locator('td:has-text("도/특별시/광역시 구분")').first()).toBeVisible({ timeout: 10000 });
}); });
test('코드 종류 등록 폼 표시', async ({ page }) => { test('코드 종류 등록 폼 표시', async ({ page }) => {
await page.goto('/admin/code-kinds/create'); await page.goto('/admin/code-kinds/create');
await expect(page).toHaveURL(/\/admin\/code-kinds\/create/);
await expect(page.locator('input[name="ck_code"]')).toBeVisible(); await expect(page.locator('input[name="ck_code"]')).toBeVisible();
await expect(page.locator('input[name="ck_name"]')).toBeVisible(); await expect(page.locator('input[name="ck_name"]')).toBeVisible();
}); });
test('코드 종류 수정', async ({ page }) => { test('코드 종류 수정', async ({ page }) => {
// 기존 코드 A의 수정 테스트
await page.goto('/admin/code-kinds/edit/1'); await page.goto('/admin/code-kinds/edit/1');
await expect(page).toHaveURL(/\/admin\/code-kinds\/edit\/1/);
await expect(page.locator('input[name="ck_name"]')).toBeVisible(); await expect(page.locator('input[name="ck_name"]')).toBeVisible();
// 값 확인만 (실제 수정은 하지 않음 - 기존 데이터 보존)
}); });
}); });
@@ -43,29 +43,58 @@ test.describe('P2-02: 세부코드 관리', () => {
await loginAsAdmin(page); await loginAsAdmin(page);
}); });
test('세부코드 목록 접근 (봉투구분 E)', async ({ page }) => { test('세부코드 목록 접근 (봉투구분 E, /bag/code-details)', async ({ page }) => {
await page.goto('/admin/code-details/5'); await page.goto('/bag/code-details/5');
await expect(page).toHaveURL(/\/admin\/code-details\/5/); await expect(page).toHaveURL(/\/bag\/code-details\/5/);
await expect(page.locator('td:has-text("일반용")').first()).toBeVisible({ timeout: 10000 }); await expect(page.locator('td:has-text("일반용")').first()).toBeVisible({ timeout: 10000 });
}); });
test('옛 /admin/code-details 목록 URL은 /bag으로 리다이렉트', async ({ page }) => {
await page.goto('/admin/code-details/5');
await expect(page).toHaveURL(/\/bag\/code-details\/5/);
});
test('세부코드 등록 폼 표시', async ({ page }) => { test('세부코드 등록 폼 표시', async ({ page }) => {
await page.goto('/admin/code-details/5/create'); await page.goto('/admin/code-details/5/create');
await expect(page).toHaveURL(/\/admin\/code-details\/5\/create/);
await expect(page.locator('input[name="cd_code"]')).toBeVisible(); await expect(page.locator('input[name="cd_code"]')).toBeVisible();
await expect(page.locator('input[name="cd_name"]')).toBeVisible(); await expect(page.locator('input[name="cd_name"]')).toBeVisible();
}); });
test('세부코드 수정 폼', async ({ page }) => { test('세부코드 수정 폼', async ({ page }) => {
// 기존 세부코드 35(일반용)의 수정 폼 확인
await page.goto('/admin/code-details/edit/35'); await page.goto('/admin/code-details/edit/35');
await expect(page.locator('input[name="cd_name"]')).toBeVisible(); await expect(page.locator('input[name="cd_name"]')).toBeVisible();
}); });
test('코드 종류에서 세부코드 링크 이동', async ({ page }) => { test('기본코드 종류에서 세부코드 링크 이동', async ({ page }) => {
await page.goto('/admin/code-kinds'); await page.goto('/bag/code-kinds');
// "세부코드" 링크 클릭 await page.locator('a[href*="/bag/code-details/"]').first().click();
const link = page.locator('a:has-text("세부코드")').first(); await expect(page).toHaveURL(/\/bag\/code-details\/\d+/);
await link.click(); });
await expect(page).toHaveURL(/\/admin\/code-details\/\d+/); });
test.describe('기본코드: 시민·판매소 조회 (/bag/code-kinds)', () => {
test('지정판매소는 bag 목록·테이블만, 등록 버튼 없음', async ({ page }) => {
await login(page, 'shop');
await page.goto('/bag/code-kinds');
await expect(page).toHaveURL(/\/bag\/code-kinds/);
await expect(page.locator('h3:has-text("기본코드 종류")')).toBeVisible();
await expect(page.locator('a:has-text("코드 종류 등록")')).toHaveCount(0);
await expect(page.locator('td:has-text("도/특별시/광역시 구분")').first()).toBeVisible({ timeout: 10000 });
});
test('지정판매소는 코드 종류 등록 URL 차단', async ({ page }) => {
await login(page, 'shop');
await page.goto('/admin/code-kinds/create');
await expect(page).not.toHaveURL(/\/admin\/code-kinds\/create/);
});
test('세부코드 목록은 조회만 (등록 버튼 없음)', async ({ page }) => {
await login(page, 'shop');
await page.goto('/bag/code-details/5');
await expect(page).toHaveURL(/\/bag\/code-details\/5/);
await expect(page.locator('text=세부코드 조회')).toBeVisible();
await expect(page.locator('a:has-text("세부코드 등록")')).toHaveCount(0);
await expect(page.locator('td:has-text("일반용")').first()).toBeVisible({ timeout: 10000 });
}); });
}); });

View File

@@ -0,0 +1,232 @@
-- Sync missing rows from 종량제_개발목록_20260127(기본코드 종류).csv
-- Generated by writable/tools/sync_basic_codes_from_csv.py
SET NAMES utf8mb4;
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'A', '도/특별시/광역시 구분', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'A');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'B', '특별시/광역시/시/군코드', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'B');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'C', '구코드', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'C');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'D', '동코드', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'D');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'E', '봉투구분', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'E');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'F', '봉투재질', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'F');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'G', '용량별', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'G');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'H', '무상지급', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'H');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'I', '판매형태', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'I');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'J', '반품형태', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'J');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'K', '반품사유', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'K');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'L', '지정판매소 변경사유', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'L');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'M', '수불구분', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'M');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'N', '동판종류', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'N');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'O', '봉투명', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'O');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'P', '작업권한', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'P');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'Q', '예산과목', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'Q');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'R', '은행목록', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'R');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'S', '소속', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'S');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'T', '직위', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'T');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'U', '배달', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'U');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'V', '구역', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'V');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'W', '봉투명(약어)', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'W');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'X', '봉투구분(대분)', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'X');
INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) SELECT 'Y', '분기', 1, NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = 'Y');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10', '특별시', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'A' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11', '광역시', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'A' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '20', '경기도', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'A' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '20');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '21', '강원도', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'A' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '21');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '22', '충청북도', 50, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'A' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '22');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '23', '충청남도', 60, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'A' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '23');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '24', '전라북도', 70, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'A' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '24');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '25', '전라남도', 80, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'A' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '25');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '26', '경상북도', 90, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'A' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '26');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '27', '경상남도', 100, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'A' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '27');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '28', '제주도', 110, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'A' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '28');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1001', '서울특별시', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'B' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1001');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1101', '부산광역시', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'B' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1101');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1102', '대구광역시', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'B' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1102');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1103', '인천광역시', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'B' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1103');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1104', '대전광역시', 50, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'B' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1104');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1105', '광주광역시', 60, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'B' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1105');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1106', '울산광역시', 70, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'B' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1106');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '110209', '북구', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'C' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '110209');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020901', '검단동', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020901');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020902', '고성동1가', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020902');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020903', '고성동2가', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020903');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020904', '고성동3가', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020904');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020905', '관음동', 50, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020905');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020906', '구암동', 60, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020906');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020907', '국우동', 70, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020907');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020908', '금호동', 80, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020908');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020909', '노곡동', 90, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020909');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020910', '노원동1가', 100, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020910');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020911', '노원동2가', 110, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020911');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020912', '노원동3가', 120, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020912');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020913', '대현동', 130, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020913');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020914', '도남동', 140, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020914');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020915', '동변동', 150, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020915');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020916', '동천동', 160, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020916');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020917', '동호동', 170, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020917');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020918', '매천동', 180, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020918');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020919', '복현동', 190, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020919');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020920', '사수동', 200, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020920');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020921', '산격동', 210, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020921');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020922', '서변동', 220, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020922');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020923', '연경동', 230, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020923');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020924', '읍내동', 240, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020924');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020925', '조야동', 250, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020925');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020926', '칠성동1가', 260, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020926');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020927', '칠성동2가', 270, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020927');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11020928', '침산동', 280, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'D' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11020928');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10', '일반용', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'E' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '20', '공공용', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'E' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '20');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '30', '무료용', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'E' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '30');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '40', '공동주택용', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'E' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '40');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '50', '재사용', 50, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'E' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '50');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '60', '음식물 봉투', 60, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'E' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '60');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '61', '음식물 스티커', 70, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'E' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '61');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '62', '음식물 용기', 80, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'E' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '62');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '70', '대형폐기물 스티커', 90, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'E' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '70');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2', '고밀도', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'F' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '6', 'PP마대', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'F' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '6');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '7', '스티커', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'F' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '7');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '8', '용기', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'F' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '8');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10', '2L', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11', '3L', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '12', '5L', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '12');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '13', '10L', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '13');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '15', '20L', 50, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '15');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '16', '30L', 60, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '16');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '17', '50L', 70, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '17');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '18', '60L', 80, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '18');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '19', '75L', 90, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '19');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '20', '100L', 100, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '20');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '21', '120L', 110, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '21');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '70', '1000원', 120, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '70');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '72', '3000원', 130, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '72');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '74', '5000원', 140, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '74');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '77', '8000원', 150, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '77');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '78', '10000원', 160, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'G' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '78');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1', '시설보호대상자', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'H' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10', '생보자', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'H' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2', '통,반장', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'H' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '3', '대한민국무공수훈자', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'H' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '3');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '4', '사회복지시설', 50, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'H' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '4');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1', '무상지급', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'I' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2', '일반판매', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'I' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '3', '관내판매', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'I' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '3');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '4', '교환판매', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'I' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '4');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1', '일반반품', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'J' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2', '관내반품', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'J' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '3', '반품', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'J' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '3');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1', '봉투훼손', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'K' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2', '지정판매소 폐업', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'K' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '4', '스티커 미사용', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'K' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '4');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1', '지정판매소변경사유1', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'L' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2', '지정판매소변경사유2', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'L' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10', '실사입고', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '11', '신청입고', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '11');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '12', '무료입고', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '12');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '13', '발주입고', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '13');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '16', '반품입고(정상)', 50, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '16');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '17', '반품입고(불용)', 60, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '17');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '18', '이동입고', 70, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '18');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1A', '교환입고', 80, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1A');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1B', '기타입고', 90, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1B');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '20', '실사출고', 100, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '20');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '21', '신청불출', 110, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '21');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '22', '무료불출', 120, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '22');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '23', '일반판매', 130, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '23');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '24', '공공출고', 140, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '24');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '26', '반품출고(정상)', 150, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '26');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '27', '반품출고(불용)', 160, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '27');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '28', '이동출고', 170, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '28');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '29', '파기처리', 180, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '29');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2A', '교환출고', 190, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2A');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2B', '기타출고', 200, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2B');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '99', '시찰', 210, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'M' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '99');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10', '동판 종류1', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'N' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '20', '동판 종류2', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'N' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '20');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10112', '일반용 3L', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10112');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10122', '일반용 5L', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10122');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10132', '일반용 10L', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10132');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10152', '일반용 20L', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10152');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10162', '일반용 30L', 50, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10162');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10172', '일반용 50L', 60, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10172');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10192', '일반용 75L', 70, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10192');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10202', '일반용 100L', 80, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10202');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '20172', '공공용 50L', 90, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '20172');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '40152', '공동주택용 20L', 100, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '40152');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '40182', '공동주택용 60L', 110, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '40182');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '40212', '공동주택용 120L', 120, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '40212');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '50122', '재사용 5L', 130, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '50122');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '60102', '음식물 2L', 140, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '60102');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '60132', '음식물 10L', 150, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '60132');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '61107', '음식물 스티커 2L', 160, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '61107');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '61117', '음식물 스티커 3L', 170, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '61117');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '61127', '음식물 스티커 5L', 180, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '61127');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '61157', '음식물 스티커 20L', 190, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '61157');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '61187', '음식물 스티커 60L', 200, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '61187');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '61217', '음식물 스티커 120L', 210, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '61217');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '62128', '음식물용기 5L', 220, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '62128');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '62158', '음식물용기 20L', 230, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '62158');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '70707', '폐기물 스티커 1,000원', 240, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '70707');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '70727', '폐기물 스티커 3,000원', 250, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '70727');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '70747', '폐기물 스티커 5,000원', 260, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '70747');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '70787', '폐기물 스티커 10,000원', 270, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'O' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '70787');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '111', '등록-다시', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'P' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '111');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1000', '', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'Q' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1000');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1001', '보건및생활환경개선비', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'Q' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1001');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2000', '', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'Q' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2000');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2010', '환경관리', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'Q' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2010');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '4', '국민은행', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'R' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '4');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '7', '수협', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'R' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '7');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '20', '우리은행', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'R' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '20');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '32', '부산은행', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'R' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '32');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '99', '새마을금고', 50, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'R' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '99');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1', '청소과', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'S' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2', '청소행정과', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'S' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '3', '자원순환과', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'S' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '3');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '4', '도시미화과', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'S' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '4');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '5', '영업부', 50, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'S' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '5');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '6', '관리부', 60, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'S' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '6');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '7', '기술부', 70, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'S' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '7');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1', '7급', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'T' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2', '8급', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'T' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '3', '9급', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'T' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '3');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '4', '기능', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'T' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '4');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '5', '계장', 50, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'T' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '5');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '6', '과장', 60, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'T' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '6');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '7', '사장', 70, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'T' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '7');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '8', '상무', 80, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'T' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '8');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1', '', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'U' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2', '', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'U' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '3', '', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'U' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '3');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '4', '', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'U' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '4');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '5', '', 50, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'U' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '5');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '6', '', 60, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'U' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '6');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '7', '', 70, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'U' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '7');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1', '1구역', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'V' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2', '2구역', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'V' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10132', '일반용 10L', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10132');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10152', '일반용 20L', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10152');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10162', '일반용 30L', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10162');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10172', '일반용 50L', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10172');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '10202', '일반용 100L', 50, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '10202');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '20172', '공공용 50L', 60, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '20172');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '40152', '공동주택용스티커 20L', 70, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '40152');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '40182', '공동주택용스티커 60L', 80, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '40182');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '40212', '공동주택용스티커 120L', 90, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '40212');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '60102', '음식물 2L', 100, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '60102');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '60132', '음식물 10L', 110, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '60132');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '61127', '스티커 5L', 120, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '61127');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '61157', '스티커 20L', 130, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '61157');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '61217', '스티커 120L', 140, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '61217');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '62128', '음식물용기 5L', 150, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '62128');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '62158', '음식물용기 20L', 160, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'W' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '62158');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1', '일반용', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'X' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2', '공공용', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'X' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '3', '무료용', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'X' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '3');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '4', '공동주택용', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'X' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '4');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '6', '음식물', 50, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'X' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '6');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '1', '1/4분기', 10, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'Y' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '1');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '2', '2/4분기', 20, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'Y' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '2');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '3', '3/4분기', 30, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'Y' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '3');
INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) SELECT k.ck_idx, '4', '4/4분기', 40, 1, NOW() FROM `code_kind` k WHERE k.ck_code = 'Y' AND NOT EXISTS (SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = '4');

View File

@@ -27,6 +27,8 @@ CREATE TABLE IF NOT EXISTS `member` (
`mb_regdate` DATETIME NOT NULL COMMENT '가입일시', `mb_regdate` DATETIME NOT NULL COMMENT '가입일시',
`mb_latestdate` DATETIME NULL DEFAULT NULL COMMENT '최근 로그인 일시', `mb_latestdate` DATETIME NULL DEFAULT NULL COMMENT '최근 로그인 일시',
`mb_leavedate` DATETIME NULL DEFAULT NULL COMMENT '탈퇴일시', `mb_leavedate` DATETIME NULL DEFAULT NULL COMMENT '탈퇴일시',
`mb_login_fail_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '연속 로그인 실패 횟수(비번 오류)',
`mb_locked_until` DATETIME NULL DEFAULT NULL COMMENT '로그인 잠금 만료 시각(mb_locked_until > NOW() 이면 잠금)',
PRIMARY KEY (`mb_idx`), PRIMARY KEY (`mb_idx`),
UNIQUE KEY `uk_mb_id` (`mb_id`), UNIQUE KEY `uk_mb_id` (`mb_id`),
KEY `idx_mb_state` (`mb_state`), KEY `idx_mb_state` (`mb_state`),

View File

@@ -0,0 +1,9 @@
-- 기본정보관리(bag/basic-info) 하위 "기본 코드 관리" 메뉴에 링크가 비어 있으면 bag/code-kinds 로 설정
UPDATE `menu` m
INNER JOIN `menu` p ON p.mm_idx = m.mm_pidx
AND p.mm_link = 'bag/basic-info'
AND p.mm_pidx = 0
INNER JOIN `menu_type` t ON t.mt_idx = m.mt_idx AND t.mt_code = 'site'
SET m.mm_link = 'bag/code-kinds'
WHERE m.mm_name IN ('기본 코드 관리', '기본코드관리')
AND (m.mm_link IS NULL OR m.mm_link = '');

View File

@@ -0,0 +1,19 @@
-- 기존 DB: site 메뉴 "기본정보관리" 아래 "기본코드관리" 하위 메뉴 추가 (bag/code-kinds)
-- idempotent: 동일 링크가 이미 있으면 추가하지 않음
INSERT INTO `menu` (`mt_idx`, `lg_idx`, `mm_name`, `mm_link`, `mm_pidx`, `mm_dep`, `mm_num`, `mm_cnode`, `mm_level`, `mm_is_view`)
SELECT m.mt_idx, m.lg_idx, '기본코드관리', 'bag/code-kinds', m.mm_idx, 1, 0, 0, '', 'Y'
FROM `menu` m
JOIN `menu_type` t ON t.mt_idx = m.mt_idx AND t.mt_code = 'site'
WHERE m.mm_link = 'bag/basic-info' AND m.mm_pidx = 0
AND NOT EXISTS (
SELECT 1 FROM `menu` c
WHERE c.mt_idx = m.mt_idx AND c.lg_idx = m.lg_idx AND c.mm_link = 'bag/code-kinds'
);
UPDATE `menu` m
JOIN `menu_type` t ON t.mt_idx = m.mt_idx AND t.mt_code = 'site'
SET m.mm_cnode = (
SELECT COUNT(*) FROM `menu` c WHERE c.mm_pidx = m.mm_idx AND c.mt_idx = m.mt_idx AND c.lg_idx = m.lg_idx
)
WHERE m.mm_link = 'bag/basic-info' AND m.mm_pidx = 0;

View File

@@ -56,3 +56,21 @@ INSERT INTO `menu` (`mt_idx`, `lg_idx`, `mm_name`, `mm_link`, `mm_pidx`, `mm_dep
(2, 1, '통계 분석 관리', 'bag/analytics', 0, 0, 7, 0, '', 'Y'), (2, 1, '통계 분석 관리', 'bag/analytics', 0, 0, 7, 0, '', 'Y'),
(2, 1, '', 'bag/window', 0, 0, 8, 0, '', 'Y'), (2, 1, '', 'bag/window', 0, 0, 8, 0, '', 'Y'),
(2, 1, '도움말', 'bag/help', 0, 0, 9, 0, '', 'Y'); (2, 1, '도움말', 'bag/help', 0, 0, 9, 0, '', 'Y');
-- site: 기본정보관리 하위 "기본코드관리" (기존 DB 중복 방지: bag/code-kinds 없을 때만 추가)
INSERT INTO `menu` (`mt_idx`, `lg_idx`, `mm_name`, `mm_link`, `mm_pidx`, `mm_dep`, `mm_num`, `mm_cnode`, `mm_level`, `mm_is_view`)
SELECT m.mt_idx, m.lg_idx, '기본코드관리', 'bag/code-kinds', m.mm_idx, 1, 0, 0, '', 'Y'
FROM `menu` m
JOIN `menu_type` t ON t.mt_idx = m.mt_idx AND t.mt_code = 'site'
WHERE m.lg_idx = 1 AND m.mm_link = 'bag/basic-info' AND m.mm_pidx = 0
AND NOT EXISTS (
SELECT 1 FROM `menu` c WHERE c.mt_idx = m.mt_idx AND c.lg_idx = m.lg_idx AND c.mm_link = 'bag/code-kinds'
)
LIMIT 1;
UPDATE `menu` m
JOIN `menu_type` t ON t.mt_idx = m.mt_idx AND t.mt_code = 'site'
SET m.mm_cnode = (
SELECT COUNT(*) FROM `menu` c WHERE c.mm_pidx = m.mm_idx AND c.mt_idx = m.mt_idx AND c.lg_idx = m.lg_idx
)
WHERE m.lg_idx = 1 AND m.mm_link = 'bag/basic-info' AND m.mm_pidx = 0;

View File

@@ -32,3 +32,21 @@ WHERE NOT EXISTS (
AND m.mm_link = t.mm_link AND m.mm_link = t.mm_link
); );
-- 3) 기본정보관리 하위 "기본코드관리" → bag/code-kinds
INSERT INTO `menu` (`mt_idx`, `lg_idx`, `mm_name`, `mm_link`, `mm_pidx`, `mm_dep`, `mm_num`, `mm_cnode`, `mm_level`, `mm_is_view`)
SELECT m.mt_idx, m.lg_idx, '기본코드관리', 'bag/code-kinds', m.mm_idx, 1, 0, 0, '', 'Y'
FROM `menu` m
JOIN `menu_type` mt ON mt.mt_idx = m.mt_idx AND mt.mt_code = 'site'
WHERE m.lg_idx = 1 AND m.mm_link = 'bag/basic-info' AND m.mm_pidx = 0
AND NOT EXISTS (
SELECT 1 FROM `menu` c WHERE c.mt_idx = m.mt_idx AND c.lg_idx = m.lg_idx AND c.mm_link = 'bag/code-kinds'
)
LIMIT 1;
UPDATE `menu` m
JOIN `menu_type` mt ON mt.mt_idx = m.mt_idx AND mt.mt_code = 'site'
SET m.mm_cnode = (
SELECT COUNT(*) FROM `menu` c WHERE c.mm_pidx = m.mm_idx AND c.mt_idx = m.mt_idx AND c.lg_idx = m.lg_idx
)
WHERE m.lg_idx = 1 AND m.mm_link = 'bag/basic-info' AND m.mm_pidx = 0;

View File

@@ -0,0 +1,145 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Parse 종량제_개발목록_20260127(기본코드 종류).csv → writable/database/code_master_sync_from_csv.sql"""
from __future__ import annotations
import csv
import re
import sys
from pathlib import Path
def read_csv_rows(path: Path) -> list[list[str]]:
raw = path.read_text(encoding="utf-8-sig")
return list(csv.reader(raw.splitlines()))
def extract_pairs(fields: list[str], first_code_idx: int, ncols: int, step: int = 3) -> list[tuple[str, str]]:
pairs: list[tuple[str, str]] = []
for k in range(ncols):
i = first_code_idx + k * step
if i + 1 >= len(fields):
pairs.append(("", ""))
continue
pairs.append((fields[i].strip(), fields[i + 1].strip()))
return pairs
SKIP_NAME = re.compile(r"코드\s*[ABCD]|순번\s*두자리|등록되는구의", re.I)
def valid_detail(cd_code: str, cd_name: str) -> bool:
if not cd_code or not cd_name:
return False
if cd_code in ("세부코드", "코드명"):
return False
if SKIP_NAME.search(cd_name) or SKIP_NAME.search(cd_code):
return False
return True
def dedup_pairs(pairs: list[tuple[str, str]]) -> list[tuple[str, str]]:
seen: set[str] = set()
out: list[tuple[str, str]] = []
for c, n in pairs:
if c in seen:
continue
seen.add(c)
out.append((c, n))
return out
def main() -> int:
root = Path(__file__).resolve().parents[2]
csv_path = root / "docs/종량제 관련 자료/종량제 개발목록/종량제_개발목록_20260127(기본코드 종류).csv"
if not csv_path.exists():
print("CSV not found:", csv_path, file=sys.stderr)
return 1
rows = read_csv_rows(csv_path)
details: dict[str, list[tuple[str, str]]] = {chr(65 + i): [] for i in range(25)}
KIND_NAMES: dict[str, str] = {}
for fields in rows:
if len(fields) >= 2:
a, b = fields[0].strip(), fields[1].strip()
if len(a) == 1 and a.isalpha() and "A" <= a <= "Y" and b and "세부코드" not in b:
KIND_NAMES[a] = b
# 블록 AI: CSV 상 4행째~ ≈ rows[3]부터 동·메모 행 전까지 (rows[3:32])
for fields in rows[3:32]:
if len(fields) < 5:
continue
pairs = extract_pairs(fields, 3, 9, step=3)
for col, let in enumerate("ABCDEFGHI"):
c, n = pairs[col]
if valid_detail(c, n):
details[let].append((c, n))
# 블록 JR: 데이터 rows[37:64]
for fields in rows[37:64]:
if len(fields) < 5:
continue
pairs = extract_pairs(fields, 3, 9, step=3)
for col, let in enumerate("JKLMNOPQR"):
c, n = pairs[col]
if valid_detail(c, n):
details[let].append((c, n))
# 블록 SY: rows[68:]
for fields in rows[68:]:
if len(fields) < 5:
continue
pairs = extract_pairs(fields, 3, 7, step=3)
for col, let in enumerate("STUVWXY"):
c, n = pairs[col]
if valid_detail(c, n):
details[let].append((c, n))
for L in details:
details[L] = dedup_pairs(details[L])
sql: list[str] = [
"-- Sync missing rows from 종량제_개발목록_20260127(기본코드 종류).csv",
"-- Generated by writable/tools/sync_basic_codes_from_csv.py",
"SET NAMES utf8mb4;",
"",
]
for L in "ABCDEFGHIJKLMNOPQRSTUVWXY":
name = KIND_NAMES.get(L, L)
ne = name.replace("'", "''")
sql.append(
f"INSERT INTO `code_kind` (`ck_code`, `ck_name`, `ck_state`, `ck_regdate`) "
f"SELECT '{L}', '{ne}', 1, NOW() FROM DUAL "
f"WHERE NOT EXISTS (SELECT 1 FROM `code_kind` c WHERE c.ck_code = '{L}');"
)
sql.append("")
for L in "ABCDEFGHIJKLMNOPQRSTUVWXY":
sort_i = 0
for c, n in details[L]:
sort_i += 10
ce = c.replace("'", "''")
ne = n.replace("'", "''")
sql.append(
"INSERT INTO `code_detail` (`cd_ck_idx`, `cd_code`, `cd_name`, `cd_sort`, `cd_state`, `cd_regdate`) "
f"SELECT k.ck_idx, '{ce}', '{ne}', {sort_i}, 1, NOW() FROM `code_kind` k "
f"WHERE k.ck_code = '{L}' AND NOT EXISTS ("
"SELECT 1 FROM `code_detail` d WHERE d.cd_ck_idx = k.ck_idx AND d.cd_code = "
f"'{ce}'"
");"
)
out_path = root / "writable/database/code_master_sync_from_csv.sql"
out_path.write_text("\n".join(sql) + "\n", encoding="utf-8")
print("Wrote", out_path)
for L in "ABCDEFGHIJKLMNOPQRSTUVWXY":
print(f" {L}: {len(details[L])} details (unique cd_code)")
return 0
if __name__ == "__main__":
raise SystemExit(main())