# 종량제 -- 쓰레기봉투 물류시스템 (jongryangje) **[종량제 개발목록 (엑셀 다운로드)](https://github.com/wixon-associates/jongryangje/raw/main/assets/종량제_개발목록_20260127.xlsx)** -- 로컬 복제 후에는 [`assets/종량제_개발목록_20260127.xlsx`](./assets/종량제_개발목록_20260127.xlsx) 지자체/지정판매소 등을 대상으로 하는 **종량제 쓰레기봉투 물류/업무 웹 애플리케이션**입니다. 백엔드는 **[CodeIgniter 4](https://codeigniter.com/)** 기반입니다. **저장소:** [wixon-associates/jongryangje](https://github.com/wixon-associates/jongryangje) | **[구현 화면 스크린샷](./docs/SCREENSHOTS.md)** | **[Notion 진행상황](https://www.notion.so/31b42b987c3780baba32ded04a1d41bb)** | --- ## 기술 스택 | 항목 | 기술 | |------|------| | Framework | CodeIgniter 4.7+ | | Language | PHP 8.2+ (strict types) | | Database | MySQL / MariaDB (MySQLi) | | 의존성 관리 | Composer 2.x | | E2E 테스트 | Playwright (Chromium) | | 세션 | 파일 기반 (`writable/session/`) | | 프론트엔드 | Tailwind CSS (CDN), Vanilla JS | --- ## 프로젝트 구조 ``` 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 git clone https://github.com/wixon-associates/jongryangje.git cd jongryangje composer install npm install # Playwright E2E 테스트용 ``` ### 2) 환경 설정 ```bash cp env .env ``` `.env`에서 설정: | 항목 | 설명 | |------|------| | `app.baseURL` | 예: `http://localhost:8045/` | | `database.default.*` | DB 호스트/DB명/사용자/비밀번호 | | `encryption.key` | PII 암호화용 64자리 hex | ### 3) 데이터베이스 준비 SQL 스크립트 실행 순서: | 순서 | 파일 | 용도 | |------|------|------| | 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/사용자 생성 | | `login_tables.sql` | member, member_log, local_government, designated_shop | | `member_approval_request_add.sql` | 승인 요청 테이블 | | `member_login_lock_add.sql` | 로그인 실패 잠금 컬럼 | | `menu_tables.sql` | menu_type, menu + admin/site 시드 | | `menu_type_add_site.sql` | 사이트 메뉴 타입 추가 | | `menu_add_lg_idx.sql` | 메뉴에 지자체 컬럼 추가 | | `menu_site_seed_from_csv.sql` | 사이트 네비게이션 시드 | | `local_government_init_daegu.sql` | 대구 8개 구군 지자체 | | `code_master_init_daegu.sql` | 기본코드 20종 + 세부코드 | | `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 | | `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개 | | `seed_realistic_data.sql` | 실제형 시범 데이터 (대구 남구청 기준) | | `fix_double_encoding.sql` | UTF-8 이중인코딩 수정 |