-- ============================================================ -- 007_reports.sql -- 보고서 시스템: 템플릿 + 섹션 조합 기반 보고서 CRUD -- ============================================================ -- 1. 보고서 템플릿 마스터 (5종) CREATE TABLE IF NOT EXISTS REPORT_TMPL ( TMPL_SN SERIAL PRIMARY KEY, TMPL_CD VARCHAR(30) NOT NULL UNIQUE, TMPL_NM VARCHAR(100) NOT NULL, ICON VARCHAR(10), TMPL_DC VARCHAR(500), SORT_ORD SMALLINT DEFAULT 0, USE_YN CHAR(1) DEFAULT 'Y', REG_DTM TIMESTAMPTZ DEFAULT NOW() ); -- 2. 템플릿 섹션 정의 (각 템플릿별 N개 섹션) CREATE TABLE IF NOT EXISTS REPORT_TMPL_SECT ( SECT_SN SERIAL PRIMARY KEY, TMPL_SN INTEGER NOT NULL REFERENCES REPORT_TMPL(TMPL_SN), SECT_CD VARCHAR(50) NOT NULL, SECT_NM VARCHAR(100) NOT NULL, FIELD_DEF JSONB NOT NULL, SORT_ORD SMALLINT DEFAULT 0, USE_YN CHAR(1) DEFAULT 'Y', UNIQUE(TMPL_SN, SECT_CD) ); -- 3. 분석 카테고리 (3종) CREATE TABLE IF NOT EXISTS REPORT_ANALYSIS_CTGR ( CTGR_SN SERIAL PRIMARY KEY, CTGR_CD VARCHAR(30) NOT NULL UNIQUE, CTGR_NM VARCHAR(100) NOT NULL, ICON VARCHAR(10), CTGR_DC VARCHAR(500), COLOR_CD VARCHAR(30), BORDER_COLOR VARCHAR(50), BG_ACTIVE VARCHAR(50), REPORT_NM VARCHAR(100), SORT_ORD SMALLINT DEFAULT 0, USE_YN CHAR(1) DEFAULT 'Y' ); -- 4. 분석 카테고리별 템플릿 목록 CREATE TABLE IF NOT EXISTS REPORT_CTGR_TMPL ( CTGR_TMPL_SN SERIAL PRIMARY KEY, CTGR_SN INTEGER NOT NULL REFERENCES REPORT_ANALYSIS_CTGR(CTGR_SN), ICON VARCHAR(10), LABEL VARCHAR(50) NOT NULL, SORT_ORD SMALLINT DEFAULT 0 ); -- 5. 분석 카테고리별 섹션 CREATE TABLE IF NOT EXISTS REPORT_CTGR_SECT ( SECT_SN SERIAL PRIMARY KEY, CTGR_SN INTEGER NOT NULL REFERENCES REPORT_ANALYSIS_CTGR(CTGR_SN), SECT_CD VARCHAR(50) NOT NULL, SECT_NM VARCHAR(100) NOT NULL, ICON VARCHAR(10), SECT_DC VARCHAR(200), DFLT_YN CHAR(1) DEFAULT 'Y', SORT_ORD SMALLINT DEFAULT 0, UNIQUE(CTGR_SN, SECT_CD) ); -- 6. 보고서 인스턴스 CREATE TABLE IF NOT EXISTS REPORT ( REPORT_SN SERIAL PRIMARY KEY, TMPL_SN INTEGER REFERENCES REPORT_TMPL(TMPL_SN), CTGR_SN INTEGER REFERENCES REPORT_ANALYSIS_CTGR(CTGR_SN), ACDNT_SN INTEGER, TITLE VARCHAR(200) NOT NULL, JRSD_CD VARCHAR(20), STTS_CD VARCHAR(20) DEFAULT 'DRAFT', AUTHOR_ID UUID NOT NULL REFERENCES AUTH_USER(USER_ID), USE_YN CHAR(1) DEFAULT 'Y', REG_DTM TIMESTAMPTZ DEFAULT NOW(), MDFCN_DTM TIMESTAMPTZ, MAP_CAPTURE_IMG TEXT CONSTRAINT CK_REPORT_STATUS CHECK (STTS_CD IN ('DRAFT','IN_PROGRESS','COMPLETED')) ); -- 7. 보고서 섹션 데이터 CREATE TABLE IF NOT EXISTS REPORT_SECT_DATA ( DATA_SN SERIAL PRIMARY KEY, REPORT_SN INTEGER NOT NULL REFERENCES REPORT(REPORT_SN) ON DELETE CASCADE, SECT_CD VARCHAR(50) NOT NULL, INCLUDE_YN CHAR(1) DEFAULT 'Y', SECT_DATA JSONB NOT NULL DEFAULT '{}', SORT_ORD SMALLINT DEFAULT 0, UNIQUE(REPORT_SN, SECT_CD) ); -- 인덱스 CREATE INDEX IF NOT EXISTS IDX_REPORT_TMPL ON REPORT(TMPL_SN); CREATE INDEX IF NOT EXISTS IDX_REPORT_CTGR ON REPORT(CTGR_SN); CREATE INDEX IF NOT EXISTS IDX_REPORT_AUTHOR ON REPORT(AUTHOR_ID); CREATE INDEX IF NOT EXISTS IDX_REPORT_STTS ON REPORT(STTS_CD); CREATE INDEX IF NOT EXISTS IDX_REPORT_REG_DTM ON REPORT(REG_DTM DESC); CREATE INDEX IF NOT EXISTS IDX_REPORT_SECT_REPORT ON REPORT_SECT_DATA(REPORT_SN); -- ============================================================ -- 초기 데이터: 보고서 템플릿 5종 -- ============================================================ INSERT INTO REPORT_TMPL (TMPL_CD, TMPL_NM, ICON, TMPL_DC, SORT_ORD) VALUES ('INITIAL', '초기보고서', '📋', '사고 발생 직후 초기 상황 보고', 1), ('COMMAND', '지휘부 보고', '📊', '지휘부 보고용 요약 문서', 2), ('FORECAST', '예측보고서', '📈', '확산예측 결과 및 민감자원 분석', 3), ('COMPREHENSIVE', '종합보고서', '📑', '전체 사고 대응 종합 보고 문서', 4), ('SPILL', '유출유 보고', '🛢️', '유류오염사고 대응지원 상황도', 5) ON CONFLICT (TMPL_CD) DO NOTHING; -- ============================================================ -- 초기 데이터: 템플릿별 섹션 정의 -- ============================================================ -- 초기보고서 (5 섹션) INSERT INTO REPORT_TMPL_SECT (TMPL_SN, SECT_CD, SECT_NM, FIELD_DEF, SORT_ORD) SELECT t.TMPL_SN, v.SECT_CD, v.SECT_NM, v.FIELD_DEF::jsonb, v.SORT_ORD FROM REPORT_TMPL t, (VALUES ('basic-info', '1. 기본정보', '[{"key":"incident.writeTime","label":"보고일시","type":"text"},{"key":"author","label":"작성자","type":"text"},{"key":"targets","label":"보고대상","type":"checkbox-group","options":["본청","지방청","유관기서"]}]', 1), ('incident-overview','2. 사고개요', '[{"key":"incident.name","label":"사고명","type":"text"},{"key":"incident.occurTime","label":"발생일시","type":"text"},{"key":"incident.location","label":"발생위치","type":"text"},{"key":"incident.shipName","label":"사고선박","type":"text"},{"key":"incident.accidentType","label":"사고유형","type":"text"}]', 2), ('spill-status', '3. 유출현황', '[{"key":"incident.pollutant","label":"유출유종","type":"text"},{"key":"incident.spillAmount","label":"유출량","type":"text"},{"key":"spillPattern","label":"유출형태","type":"text"},{"key":"spreadStatus","label":"확산현황","type":"text"}]', 3), ('initial-response', '4. 초동조치 사항', '[{"key":"initialResponse","label":"","type":"textarea"}]', 4), ('future-plan', '5. 향후 대응계획', '[{"key":"futurePlan","label":"","type":"textarea"}]', 5) ) AS v(SECT_CD, SECT_NM, FIELD_DEF, SORT_ORD) WHERE t.TMPL_CD = 'INITIAL' ON CONFLICT (TMPL_SN, SECT_CD) DO NOTHING; -- 지휘부 보고 (4 섹션) INSERT INTO REPORT_TMPL_SECT (TMPL_SN, SECT_CD, SECT_NM, FIELD_DEF, SORT_ORD) SELECT t.TMPL_SN, v.SECT_CD, v.SECT_NM, v.FIELD_DEF::jsonb, v.SORT_ORD FROM REPORT_TMPL t, (VALUES ('basic-info', '1. 기본정보', '[{"key":"incident.writeTime","label":"보고일시","type":"text"},{"key":"author","label":"작성자","type":"text"}]', 1), ('incident-summary','2. 사고 요약', '[{"key":"incident.name","label":"사고명","type":"text"},{"key":"incident.occurTime","label":"발생일시","type":"text"},{"key":"incident.location","label":"발생위치","type":"text"},{"key":"incident.pollutant","label":"유출유종","type":"text"},{"key":"incident.spillAmount","label":"유출량","type":"text"}]', 2), ('response-status', '3. 대응현황', '[{"key":"responseStatus","label":"","type":"textarea"}]', 3), ('suggestions', '4. 건의사항', '[{"key":"suggestions","label":"","type":"textarea"}]', 4) ) AS v(SECT_CD, SECT_NM, FIELD_DEF, SORT_ORD) WHERE t.TMPL_CD = 'COMMAND' ON CONFLICT (TMPL_SN, SECT_CD) DO NOTHING; -- 예측보고서 (5 섹션) INSERT INTO REPORT_TMPL_SECT (TMPL_SN, SECT_CD, SECT_NM, FIELD_DEF, SORT_ORD) SELECT t.TMPL_SN, v.SECT_CD, v.SECT_NM, v.FIELD_DEF::jsonb, v.SORT_ORD FROM REPORT_TMPL t, (VALUES ('basic-info', '1. 기본정보', '[{"key":"incident.writeTime","label":"보고일시","type":"text"},{"key":"author","label":"작성자","type":"text"}]', 1), ('incident-overview','2. 사고개요', '[{"key":"incident.name","label":"사고명","type":"text"},{"key":"incident.occurTime","label":"발생일시","type":"text"},{"key":"incident.location","label":"발생위치","type":"text"},{"key":"incident.pollutant","label":"유출유종","type":"text"},{"key":"incident.spillAmount","label":"유출량","type":"text"}]', 2), ('weather-summary', '3. 해양기상 현황', '[{"key":"weatherSummary","label":"","type":"textarea"}]', 3), ('spread-result', '4. 확산예측 결과', '[{"key":"spreadResult","label":"","type":"textarea"}]', 4), ('sensitive-impact', '5. 민감자원 영향', '[{"key":"sensitiveImpact","label":"","type":"textarea"}]', 5) ) AS v(SECT_CD, SECT_NM, FIELD_DEF, SORT_ORD) WHERE t.TMPL_CD = 'FORECAST' ON CONFLICT (TMPL_SN, SECT_CD) DO NOTHING; -- 종합보고서 (6 섹션) INSERT INTO REPORT_TMPL_SECT (TMPL_SN, SECT_CD, SECT_NM, FIELD_DEF, SORT_ORD) SELECT t.TMPL_SN, v.SECT_CD, v.SECT_NM, v.FIELD_DEF::jsonb, v.SORT_ORD FROM REPORT_TMPL t, (VALUES ('basic-info', '1. 기본정보', '[{"key":"incident.writeTime","label":"보고일시","type":"text"},{"key":"author","label":"작성자","type":"text"}]', 1), ('incident-overview', '2. 사고개요', '[{"key":"incident.name","label":"사고명","type":"text"},{"key":"incident.occurTime","label":"발생일시","type":"text"},{"key":"incident.location","label":"발생위치","type":"text"},{"key":"incident.shipName","label":"사고선박","type":"text"},{"key":"incident.accidentType","label":"사고유형","type":"text"}]', 2), ('spill-spread', '3. 유출 및 확산현황', '[{"key":"incident.pollutant","label":"유출유종","type":"text"},{"key":"incident.spillAmount","label":"유출량","type":"text"},{"key":"spreadSummary","label":"확산현황","type":"textarea"}]', 3), ('response-detail', '4. 대응현황', '[{"key":"responseDetail","label":"","type":"textarea"}]', 4), ('damage-report', '5. 피해현황', '[{"key":"damageReport","label":"","type":"textarea"}]', 5), ('future-plan-detail','6. 향후계획', '[{"key":"futurePlanDetail","label":"","type":"textarea"}]', 6) ) AS v(SECT_CD, SECT_NM, FIELD_DEF, SORT_ORD) WHERE t.TMPL_CD = 'COMPREHENSIVE' ON CONFLICT (TMPL_SN, SECT_CD) DO NOTHING; -- 유출유 보고 (14 섹션) INSERT INTO REPORT_TMPL_SECT (TMPL_SN, SECT_CD, SECT_NM, FIELD_DEF, SORT_ORD) SELECT t.TMPL_SN, v.SECT_CD, v.SECT_NM, v.FIELD_DEF::jsonb, v.SORT_ORD FROM REPORT_TMPL t, (VALUES ('incident-info', '1. 사고 정보', '[{"key":"incident.name","label":"사고명","type":"text"},{"key":"incident.writeTime","label":"작성시간","type":"text"},{"key":"incident.shipName","label":"선명(시설명)","type":"text"},{"key":"agent","label":"제원","type":"text"},{"key":"incident.location","label":"사고위치","type":"text"},{"key":"incident.lat","label":"좌표(위도)","type":"text"},{"key":"incident.lon","label":"좌표(경도)","type":"text"},{"key":"incident.occurTime","label":"발생시각","type":"text"},{"key":"incident.accidentType","label":"사고유형","type":"text"},{"key":"incident.pollutant","label":"오염물질","type":"text"},{"key":"incident.spillAmount","label":"유출 추정량(㎘)","type":"text"},{"key":"incident.depth","label":"수심","type":"text"},{"key":"incident.seabed","label":"저질","type":"text"}]', 1), ('tide-info', '2. 해양기상정보 - 조석정보', '[{"key":"tideDate","label":"일자","type":"text"},{"key":"tideName","label":"물때","type":"text"},{"key":"lowTide1","label":"저조(1차)","type":"text"},{"key":"highTide1","label":"고조(1차)","type":"text"},{"key":"lowTide2","label":"저조(2차)","type":"text"},{"key":"highTide2","label":"고조(2차)","type":"text"}]', 2), ('weather-info', '2. 해양기상정보 - 기상예보', '[{"key":"weatherTime","label":"기상 예측시간","type":"text"},{"key":"sunrise","label":"일출","type":"text"},{"key":"sunset","label":"일몰","type":"text"},{"key":"windDir","label":"풍향","type":"text"},{"key":"windSpeed","label":"풍속(m/s)","type":"text"},{"key":"currentDir","label":"유향","type":"text"},{"key":"currentSpeed","label":"유속(knot)","type":"text"},{"key":"waveHeight","label":"파고(m)","type":"text"}]', 3), ('spread-detail', '3. 유출유 확산예측 - 시간별 상세정보', '[{"key":"spread3h_weathered","label":"3시간 풍화량(kL)","type":"text"},{"key":"spread3h_seaRemain","label":"3시간 해상잔존량(kL)","type":"text"},{"key":"spread3h_coastAttach","label":"3시간 연안부착량(kL)","type":"text"},{"key":"spread3h_area","label":"3시간 오염해역면적(km²)","type":"text"},{"key":"spread6h_weathered","label":"6시간 풍화량(kL)","type":"text"},{"key":"spread6h_seaRemain","label":"6시간 해상잔존량(kL)","type":"text"},{"key":"spread6h_coastAttach","label":"6시간 연안부착량(kL)","type":"text"},{"key":"spread6h_area","label":"6시간 오염해역면적(km²)","type":"text"}]', 4), ('analysis', '3. 분석', '[{"key":"spreadAnalysis","label":"","type":"textarea"}]', 5), ('aquaculture', '4. 민감자원 - 양식장 분포(10km 내)', '[{"key":"aquaculture","label":"","type":"textarea"}]', 6), ('beaches', '4. 민감자원 - 해수욕장/수산시장/해수취수시설', '[{"key":"beaches","label":"","type":"textarea"}]', 7), ('esi-coast', '4. 해안선(ESI) 분포', '[{"key":"esi1_vertical","label":"ESI 1 수직암반(km)","type":"text"},{"key":"esi2_horizontal","label":"ESI 2 수평암반(km)","type":"text"},{"key":"esi3_finesand","label":"ESI 3 세립질 모래(km)","type":"text"},{"key":"esi4_coarsesand","label":"ESI 4 조립질 모래(km)","type":"text"},{"key":"esi5_sandgravel","label":"ESI 5 모래·자갈(km)","type":"text"},{"key":"esi6a_gravel","label":"ESI 6A 자갈(km)","type":"text"},{"key":"esi6b_riprap","label":"ESI 6B 투과성 사석(km)","type":"text"},{"key":"esi7_semiclosed","label":"ESI 7 반폐쇄성 해안(km)","type":"text"},{"key":"esi8a_mudflat","label":"ESI 8A 갯벌(km)","type":"text"},{"key":"esi8b_saltmarsh","label":"ESI 8B 염습지(km)","type":"text"}]', 8), ('bio-species', '4. 생물종(보호종) / 서식지 분포', '[{"key":"bioSpecies","label":"","type":"textarea"}]', 9), ('sensitivity', '4. 통합민감도 평가', '[{"key":"sens_veryHigh","label":"매우 높음(km²)","type":"text"},{"key":"sens_high","label":"높음(km²)","type":"text"},{"key":"sens_medium","label":"보통(km²)","type":"text"},{"key":"sens_low","label":"낮음(km²)","type":"text"}]', 10), ('defense-strategy', '5. 방제전략 - 방제자원 배치 현황(반경 30km)', '[{"key":"defenseShips","label":"","type":"textarea"}]', 11), ('other-equipment', '5. 기타 장비', '[{"key":"otherEquipment","label":"","type":"textarea"}]', 12), ('oil-recovery', '6. 방제선/자원 동원 결과', '[{"key":"oilRecovery","label":"","type":"textarea"}]', 13), ('result-summary', '6. 동원 방제선 내역', '[{"key":"totalSpill","label":"유출량(kL)","type":"text"},{"key":"totalWeathered","label":"누적풍화량(kL)","type":"text"},{"key":"totalRecovered","label":"누적회수량(kL)","type":"text"},{"key":"totalSeaRemain","label":"누적해상잔존량(kL)","type":"text"},{"key":"totalCoastAttach","label":"누적연안부착량(kL)","type":"text"}]', 14) ) AS v(SECT_CD, SECT_NM, FIELD_DEF, SORT_ORD) WHERE t.TMPL_CD = 'SPILL' ON CONFLICT (TMPL_SN, SECT_CD) DO NOTHING; -- ============================================================ -- 초기 데이터: 분석 카테고리 3종 -- ============================================================ INSERT INTO REPORT_ANALYSIS_CTGR (CTGR_CD, CTGR_NM, ICON, CTGR_DC, COLOR_CD, BORDER_COLOR, BG_ACTIVE, REPORT_NM, SORT_ORD) VALUES ('OIL', '유출유 확산예측', '🛢', 'KOSPS · OpenDrift · POSEIDON', 'var(--cyan)', 'rgba(6,182,212,0.4)', 'rgba(6,182,212,0.08)', '유출유 확산예측 보고서', 1), ('HNS', 'HNS 대기확산', '🧪', 'ALOHA · WRF-Chem', 'var(--orange)', 'rgba(249,115,22,0.4)', 'rgba(249,115,22,0.08)','HNS 대기확산 예측보고서', 2), ('RESCUE', '긴급구난', '🚨', '복원성 · 좌초위험 분석', 'var(--red)', 'rgba(239,68,68,0.4)', 'rgba(239,68,68,0.08)', '긴급구난 상황보고서', 3) ON CONFLICT (CTGR_CD) DO NOTHING; -- 카테고리별 템플릿 목록 INSERT INTO REPORT_CTGR_TMPL (CTGR_SN, ICON, LABEL, SORT_ORD) SELECT c.CTGR_SN, v.ICON, v.LABEL, v.SORT_ORD FROM REPORT_ANALYSIS_CTGR c, (VALUES ('OIL', '🔬', '예측보고서', 1), ('OIL', '📋', '초기보고서', 2), ('OIL', '📊', '지휘부 보고', 3), ('OIL', '📑', '종합보고서', 4), ('HNS', '🧪', 'HNS 예측보고서', 1), ('HNS', '📋', '초기보고서', 2), ('HNS', '📊', '지휘부 보고', 3), ('HNS', '🆘', 'EmS 대응보고', 4), ('RESCUE', '🚨', '긴급구난 상황보고', 1), ('RESCUE', '📋', '초기보고서', 2), ('RESCUE', '📊', '지휘부 보고', 3), ('RESCUE', '📑', '종합보고서', 4) ) AS v(CTGR_CD, ICON, LABEL, SORT_ORD) WHERE c.CTGR_CD = v.CTGR_CD; -- 카테고리별 섹션 INSERT INTO REPORT_CTGR_SECT (CTGR_SN, SECT_CD, SECT_NM, ICON, SECT_DC, DFLT_YN, SORT_ORD) SELECT c.CTGR_SN, v.SECT_CD, v.SECT_NM, v.ICON, v.SECT_DC, v.DFLT_YN, v.SORT_ORD FROM REPORT_ANALYSIS_CTGR c, (VALUES -- OIL 섹션 (6개) ('OIL', 'oil-spread', '유출유 확산예측 결과', '🌊', 'KOSPS/OpenDrift/POSEIDON 예측 결과 및 비교', 'Y', 1), ('OIL', 'oil-pollution', '오염종합상황', '📊', '유출량, 풍화량, 해상잔유량, 오염면적', 'Y', 2), ('OIL', 'oil-sensitive', '민감자원 현황', '🐟', '어장정보, 양식업, 환경생태 자원', 'Y', 3), ('OIL', 'oil-coastal', '해안부착 현황', '🏖', '해안 부착 위치, 시간, 오염 길이', 'Y', 4), ('OIL', 'oil-defense', '방제전략·자원배치', '🛡', '방제방법, 방제자원 배치 계획', 'Y', 5), ('OIL', 'oil-tide', '조석·기상정보', '🌊', '사고 해역 조석·기상 현황', 'Y', 6), -- HNS 섹션 (7개) ('HNS', 'hns-atm', '대기확산 예측 결과', '💨', 'ALOHA/WRF-Chem 모델 확산 결과', 'Y', 1), ('HNS', 'hns-hazard', '위험구역·방제거리', '⚠️', 'ERPGs 기준 위험구역 범위', 'Y', 2), ('HNS', 'hns-substance', 'HNS 물질정보', '🧬', '물질 특성, 독성, UN번호', 'Y', 3), ('HNS', 'hns-ppe', 'PPE·대응장비', '🛡', '개인보호장비, 대응장비 배치', 'Y', 4), ('HNS', 'hns-facility', '영향시설·대피현황', '🏫', '주변 시설, 인구, 대피 계획', 'Y', 5), ('HNS', 'hns-3d', '3D 공간분포', '📐', '수직·수평 농도 분포', 'N', 6), ('HNS', 'hns-weather', '기상·해상 조건', '🌊', '풍향·풍속, 대기안정도', 'Y', 7), -- RESCUE 섹션 (6개) ('RESCUE', 'rescue-safety', '선박 안전성 평가', '🚢', 'GM, 경사각, 트림 분석', 'Y', 1), ('RESCUE', 'rescue-timeline', '사고 유형·경과', '⚡', '사고 유형별 타임라인', 'Y', 2), ('RESCUE', 'rescue-casualty', '인명현황', '👥', '인원 현황, 구조 상태', 'Y', 3), ('RESCUE', 'rescue-resource', '구난 자원 현황', '🛟', '예인선, 헬기, 구난 장비 배치', 'Y', 4), ('RESCUE', 'rescue-grounding', '좌초위험 해역', '🗺', '좌초 위험 구역 분석', 'Y', 5), ('RESCUE', 'rescue-weather', '기상·해상 조건', '🌊', '파고, 풍속, 조류 현황', 'Y', 6) ) AS v(CTGR_CD, SECT_CD, SECT_NM, ICON, SECT_DC, DFLT_YN, SORT_ORD) WHERE c.CTGR_CD = v.CTGR_CD ON CONFLICT (CTGR_SN, SECT_CD) DO NOTHING;