From 0037b90573a2ec4f81f0d08049e401e09380d499 Mon Sep 17 00:00:00 2001 From: b24014 Date: Mon, 2 Feb 2026 14:13:16 +0900 Subject: [PATCH 01/21] DB Signed-off-by: b24014 --- kngil_DB | 2382 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2382 insertions(+) create mode 100644 kngil_DB diff --git a/kngil_DB b/kngil_DB new file mode 100644 index 0000000..40cdfd4 --- /dev/null +++ b/kngil_DB @@ -0,0 +1,2382 @@ +-- +-- PostgreSQL database dump +-- + +\restrict osPaC8Gqjay0KBMwX4hwgDvmjwF5rTGmBMzQBdxAne3SBCLMuCNQu2Xg15dPVeb + +-- Dumped from database version 18.1 +-- Dumped by pg_dump version 18.0 + +-- Started on 2026-02-02 14:06:03 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET transaction_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- TOC entry 6 (class 2615 OID 16413) +-- Name: kngil; Type: SCHEMA; Schema: -; Owner: postgres +-- + +CREATE SCHEMA kngil; + + +ALTER SCHEMA kngil OWNER TO postgres; + +-- +-- TOC entry 262 (class 1255 OID 16574) +-- Name: fn_base_cd(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_base_cd(p_main_cd character varying) RETURNS TABLE(id character varying, text character varying) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + -- CASE 문을 사용하여 조건에 따라 반환할 컬럼을 선택합니다. + (CASE + WHEN b.use_bc = 'BS200100' THEN a.base_cd::VARCHAR + WHEN b.use_bc = 'BS200200' THEN a.sub_cd::VARCHAR + END) AS id, -- 코드1이면 기초코드 아니면 서브코드 표시. + a.sub_nm::VARCHAR AS text -- 코드명 + FROM kngil.code_detail a --공통코드 상세 + JOIN kngil.code_master b ON a.main_cd = b.main_cd --공통코드 마스터 + WHERE a.main_cd = p_main_cd + AND a.use_yn = 'Y' -- 사용여부 + ORDER BY + (CASE WHEN b.sort_bc = 'BS110100' THEN a.sub_nm END) ASC, + (CASE WHEN b.sort_bc = 'BS110200' THEN a.sort_sq END) ASC; +END; +$$; + + +ALTER FUNCTION kngil.fn_base_cd(p_main_cd character varying) OWNER TO postgres; + +-- +-- TOC entry 251 (class 1255 OID 16571) +-- Name: fn_base_nm(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_base_nm(p_base_cd character varying) RETURNS TABLE(name character varying) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + a.sub_nm::VARCHAR AS name -- 코드명 + FROM kngil.code_detail a --공통코드 상세 + JOIN kngil.code_master b ON a.main_cd = b.main_cd --공통코드 마스터 + WHERE a.base_cd = p_base_cd; + +END; +$$; + + +ALTER FUNCTION kngil.fn_base_nm(p_base_cd character varying) OWNER TO postgres; + +-- +-- TOC entry 276 (class 1255 OID 16703) +-- Name: fn_update_buy_area(); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_update_buy_area() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + v_member_id character varying; +BEGIN + -- 1. 이벤트 종류(INSERT, UPDATE, DELETE)에 따라 member_id 추출 + IF (TG_OP = 'DELETE') THEN + v_member_id := OLD.member_id; + ELSE + v_member_id := NEW.member_id; + END IF; + + -- 2. 해당 회원의 당해 연도(end_dt 기준) 합계 적용면적 업데이트 + -- ok_yn = 'Y'인 데이터만 합산 + UPDATE kngil.members + SET buy_area = ( + SELECT COALESCE(SUM(sum_area), 0) + FROM kngil.buy_item + WHERE member_id = v_member_id + AND ok_yn = 'Y' + AND EXTRACT(YEAR FROM end_dt) = EXTRACT(YEAR FROM CURRENT_DATE) + ) + WHERE member_id = v_member_id; + + -- UPDATE 이벤트에서 member_id가 변경된 경우, 이전 member_id의 데이터도 갱신 필요 + IF (TG_OP = 'UPDATE' AND OLD.member_id <> NEW.member_id) THEN + UPDATE kngil.members + SET buy_area = ( + SELECT COALESCE(SUM(sum_area), 0) + FROM kngil.buy_item + WHERE member_id = OLD.member_id + AND ok_yn = 'Y' + AND EXTRACT(YEAR FROM end_dt) = EXTRACT(YEAR FROM CURRENT_DATE) + ) + WHERE member_id = OLD.member_id; + END IF; + + RETURN NULL; -- AFTER 트리거이므로 결과 반환은 필요 없음 +END; +$$; + + +ALTER FUNCTION kngil.fn_update_buy_area() OWNER TO postgres; + +-- +-- TOC entry 250 (class 1255 OID 16541) +-- Name: fn_user_auth(); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_user_auth() RETURNS TABLE(code character varying, name character varying) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + -- 마스터의 use_bc 설정에 따라 base_cd 또는 sub_cd 반환 + (CASE + WHEN b.use_bc = 'BS200100' THEN a.base_cd::VARCHAR + WHEN b.use_bc = 'BS200200' THEN a.sub_cd::VARCHAR + END) AS code, + a.sub_nm::VARCHAR AS name + FROM kngil.code_detail a + JOIN kngil.code_master b ON a.main_cd = b.main_cd + WHERE a.main_cd = 'BS100' -- 권한관리코드 고정 + AND a.use_yn = 'Y' -- 사용여부 'Y' + AND a.m1 = '1' -- 특정 필터 조건 + ORDER BY + (CASE WHEN b.sort_bc = 'BS110100' THEN a.sub_nm END) ASC, + (CASE WHEN b.sort_bc = 'BS110200' THEN a.sort_sq END) ASC; +END; +$$; + + +ALTER FUNCTION kngil.fn_user_auth() OWNER TO postgres; + +-- +-- TOC entry 255 (class 1255 OID 16583) +-- Name: fn_user_id_check(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_user_id_check(p_user_id character varying) RETURNS character varying + LANGUAGE plpgsql + AS $$ +DECLARE + v_user_exists INTEGER; +/* +유저 중복 체크 함수 회원가입 시 중복체크 +*/ +BEGIN + -- [1] 중복 아이디 체크 (대소문자 무시) + SELECT COUNT(*) INTO v_user_exists + FROM kngil.users + WHERE LOWER(user_id) = LOWER(p_user_id); -- 양쪽 모두 소문자로 변환하여 비교 + + IF v_user_exists > 0 THEN + RETURN 'ERROR: 이미 존재하는 아이디입니다.'; + ELSE + RETURN 'SUCCESS: 사용 가능한 아이디입니다.'; + END IF; +END; +$$; + + +ALTER FUNCTION kngil.fn_user_id_check(p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 257 (class 1255 OID 16550) +-- Name: sp_buy_item_d(character varying, integer); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_d(p_member_id character varying, p_sq_no integer) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE +/* + 설 명 : buy_item 테이블 삭제 프로시져 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ + v_ok_yn CHAR(1); +BEGIN + -- 1. 해당 구매 건의 승인 여부 확인 + SELECT ok_yn INTO v_ok_yn + FROM kngil.buy_item + WHERE member_id = p_member_id AND sq_no = p_sq_no; + + IF NOT FOUND THEN + RETURN 'ERROR: 삭제할 구매 정보를 찾을 수 없습니다.'; + END IF; + + -- 2. 승인여부(ok_yn)가 'Y'이면 삭제 불가 + IF v_ok_yn = 'Y' THEN + RETURN 'ERROR: 승인 완료(Y)된 구매 내역은 삭제할 수 없습니다.'; + END IF; + + -- 3. 삭제 실행 + DELETE FROM kngil.buy_item + WHERE member_id = p_member_id AND sq_no = p_sq_no; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_d(p_member_id character varying, p_sq_no integer) OWNER TO postgres; + +-- +-- TOC entry 273 (class 1255 OID 16563) +-- Name: sp_buy_item_history_r(character varying, character varying, date, date); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_history_r(p_member_id character varying DEFAULT ''::character varying, p_member_nm character varying DEFAULT NULL::character varying, p_fbuy_dt date DEFAULT NULL::date, p_tbuy_dt date DEFAULT NULL::date) RETURNS TABLE(member_id character varying, sq_no integer, user_nm character varying, co_nm character varying, bs_no character varying, buy_dt date, itm_cd character varying, itm_nm character varying, area numeric, itm_qty numeric, itm_area numeric, add_area numeric, sum_area numeric, itm_amt numeric, dis_rt numeric, buy_amt numeric, vat_amt numeric, sum_amt numeric, end_dt date, ok_yn character, rmks character varying) + LANGUAGE plpgsql + AS $$ +/* + 설 명 : 상품등록화면에서 buy_item 테이블 내용 조회 member_id , buy_dt 변수 필수 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +BEGIN + RETURN QUERY + SELECT + a.member_id, -- 회원ID + a.sq_no, -- 순번 + b.member_nm::character varying as user_nm, -- 구매자 + b.co_nm, -- 회사명 + b.bs_no, -- 사업자번호 + a.buy_dt, -- 구매일자 + a.itm_cd::character varying, -- 상품코드 + c.itm_nm::character varying as itm_nm, -- 상품명 + c.area::NUMERIC as area, -- 상품면적 + a.itm_qty::NUMERIC, -- 수량 + a.itm_area::NUMERIC, -- 면적 + a.add_area::NUMERIC, -- 추가면적 + a.sum_area::NUMERIC, -- 합계면적 + a.itm_amt::NUMERIC, -- 단가 + a.dis_rt::NUMERIC, -- 할인율 + a.buy_amt::NUMERIC, -- 공급금액 + a.vat_amt::NUMERIC, -- 부가세 + a.sum_amt::NUMERIC, -- 합계금액 + a.end_dt, -- 만료일 + a.ok_yn, -- 승인여부 + a.rmks -- 비고 + FROM kngil.buy_item a -- 구매정보 + left join kngil.members b on a.member_id = b.member_id -- 회원정보 + left join kngil.item c on a.itm_cd = c.itm_cd -- 상품정보 + WHERE 1=1 + -- 회원ID 검색 + AND (a.member_id = p_member_id OR p_member_id = '') + -- 구매일자 기간 검색 + AND (a.buy_dt >= p_fbuy_dt OR p_fbuy_dt IS NULL) + AND (a.buy_dt <= p_tbuy_dt OR p_tbuy_dt IS NULL) + AND ( + p_member_nm IS NULL + OR p_member_nm = '' + OR b.co_nm ILIKE '%' || p_member_nm || '%' + OR b.member_nm ILIKE '%' || p_member_nm || '%' +) + ORDER BY a.member_id,a.sq_no DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_history_r(p_member_id character varying, p_member_nm character varying, p_fbuy_dt date, p_tbuy_dt date) OWNER TO postgres; + +-- +-- TOC entry 275 (class 1255 OID 16575) +-- Name: sp_buy_item_i(character varying, date, character, numeric, numeric, numeric, numeric, numeric, numeric, numeric, numeric, numeric, date, character, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_i(p_member_id character varying, p_buy_dt date, p_itm_cd character, p_itm_qty numeric, p_itm_area numeric, p_add_area numeric, p_sum_area numeric, p_itm_amt numeric, p_dis_rt numeric, p_buy_amt numeric, p_vat_amt numeric, p_sum_amt numeric, p_end_dt date, p_ok_yn character, p_rmks character varying, p_cid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE +/* + 설 명 : 서비스 구매정보 isnert 프로시져 sq_no 계산하여 적용. + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 + p_itm_cd character, -- 상품코드 + p_itm_qty numeric, -- 수량 + p_itm_area numeric, -- 적용면적 + p_add_area numeric, -- 추가면적 + p_sum_area numeric, -- 합계면적 + p_itm_amt numeric, -- 상품단가 + p_dis_rt numeric, -- 할인율 + p_buy_amt numeric, -- 공금금액 + p_vat_amt numeric, -- 부가세 + p_sum_amt numeric, -- 합계금액 + p_end_dt date, -- 만료일자 + p_ok_yn character, -- 승인여부 + p_rmks character varying,-- 비고 +*/ + v_next_sq_no INTEGER; +BEGIN + -- 해당 회원의 다음 순번(sq_no) 계산 + SELECT COALESCE(MAX(sq_no), 0) + 1 INTO v_next_sq_no + FROM kngil.buy_item + WHERE member_id = p_member_id; + + -- 데이터 삽입 + INSERT INTO kngil.buy_item ( + member_id, sq_no, buy_dt, itm_cd, itm_qty, itm_area, + add_area, sum_area, itm_amt, dis_rt, buy_amt, + vat_amt, sum_amt, end_dt, ok_yn, rmks, + cid, cdt, mid, mdt + ) VALUES ( + p_member_id, v_next_sq_no, p_buy_dt, p_itm_cd, p_itm_qty, p_itm_area, + p_add_area, p_sum_area, p_itm_amt, p_dis_rt, p_buy_amt, + p_vat_amt, p_sum_amt, p_end_dt, p_ok_yn, p_rmks, + p_cid, CURRENT_TIMESTAMP, p_cid, CURRENT_TIMESTAMP + ); + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_i(p_member_id character varying, p_buy_dt date, p_itm_cd character, p_itm_qty numeric, p_itm_area numeric, p_add_area numeric, p_sum_area numeric, p_itm_amt numeric, p_dis_rt numeric, p_buy_amt numeric, p_vat_amt numeric, p_sum_amt numeric, p_end_dt date, p_ok_yn character, p_rmks character varying, p_cid character varying) OWNER TO postgres; + +-- +-- TOC entry 274 (class 1255 OID 16702) +-- Name: sp_buy_item_r(character varying, date); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_r(p_member_id character varying DEFAULT ''::character varying, p_buy_dt date DEFAULT NULL::date) RETURNS TABLE(member_id character varying, sq_no integer, buy_dt date, itm_cd character, itm_nm character varying, itm_qty numeric, itm_area numeric, add_area numeric, sum_area numeric, itm_amt numeric, dis_rt numeric, buy_amt numeric, vat_amt numeric, sum_amt numeric, end_dt date, ok_yn character, rmks character varying) + LANGUAGE plpgsql + AS $$ +/* + 설 명 : 상품등록화면에서 buy_item 테이블 내용 조회 member_id , buy_dt 변수 필수 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +BEGIN + RETURN QUERY + SELECT + a.member_id, -- 회원ID + a.sq_no, -- 순번 + a.buy_dt, -- 구매일자 + a.itm_cd, -- 상품코드 + b.itm_nm::character varying, -- 상품명 + a.itm_qty::NUMERIC, -- 수량 + a.itm_area::NUMERIC, -- 면적 + a.add_area::NUMERIC, -- 추가면적 + a.sum_area::NUMERIC, -- 합계면적 + a.itm_amt::NUMERIC, -- 단가 + a.dis_rt::NUMERIC, -- 할인율 + a.buy_amt::NUMERIC, -- 공급금액 + a.vat_amt::NUMERIC, -- 부가세 + a.sum_amt::NUMERIC, -- 합계금액 + a.end_dt, -- 만료일 + a.ok_yn, -- 승인여부 + a.rmks -- 비고 + FROM kngil.buy_item a + left join kngil.item b on a.itm_cd = b.itm_cd -- 상품코드 + WHERE 1=1 + -- 회원ID 검색 (회원ID 필수) + AND a.member_id = p_member_id + -- 구매일자 검색(필수) + AND a.buy_dt = p_buy_dt + ORDER BY a.sq_no DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_r(p_member_id character varying, p_buy_dt date) OWNER TO postgres; + +-- +-- TOC entry 256 (class 1255 OID 16549) +-- Name: sp_buy_item_u(character varying, integer, date, character, numeric, numeric, numeric, numeric, numeric, numeric, numeric, numeric, numeric, date, character, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_u(p_member_id character varying, p_sq_no integer, p_buy_dt date, p_itm_cd character, p_itm_qty numeric, p_itm_area numeric, p_add_area numeric, p_sum_area numeric, p_itm_amt numeric, p_dis_rt numeric, p_buy_amt numeric, p_vat_amt numeric, p_sum_amt numeric, p_end_dt date, p_ok_yn character, p_rmks character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ + +BEGIN + UPDATE kngil.buy_item + SET + buy_dt = p_buy_dt, -- 구매일자 + itm_cd = p_itm_cd, -- 상품코드 + itm_qty = p_itm_qty, -- 수량 + itm_area = p_itm_area, -- 기본면적 + add_area = p_add_area, -- 추가면적 + sum_area = p_sum_area, -- 합계면적 + itm_amt = p_itm_amt, -- 단가 + dis_rt = p_dis_rt, -- 할인율 + buy_amt = p_buy_amt, -- 공급금액 + vat_amt = p_vat_amt, -- 부가세 + sum_amt = p_sum_amt, -- 구매금액 + end_dt = p_end_dt, -- 만료일자 + ok_yn = p_ok_yn, -- 확정 [Y업데이트 시 members 테이블의 구매면적에 합산 N 업데이트시 차감 트리거 적용] + rmks = p_rmks, -- 비고 + mid = p_mid, -- 수정자 ID 반영 + mdt = CURRENT_TIMESTAMP -- 수정일시 자동 기록 + WHERE member_id = p_member_id + AND sq_no = p_sq_no; + + IF NOT FOUND THEN + RETURN 'ERROR: 수정할 구매 정보를 찾을 수 없습니다.'; + END IF; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_u(p_member_id character varying, p_sq_no integer, p_buy_dt date, p_itm_cd character, p_itm_qty numeric, p_itm_area numeric, p_add_area numeric, p_sum_area numeric, p_itm_amt numeric, p_dis_rt numeric, p_buy_amt numeric, p_vat_amt numeric, p_sum_amt numeric, p_end_dt date, p_ok_yn character, p_rmks character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 238 (class 1255 OID 16685) +-- Name: sp_fa_comments_d(integer); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_fa_comments_d(p_id integer) RETURNS boolean + LANGUAGE plpgsql + AS $$ +BEGIN + -- fa_id를 조건으로 데이터를 삭제합니다. + DELETE FROM kngil.fa_comments + WHERE fa_id = p_id; + + -- 삭제된 행이 있으면 true, 없으면 false를 반환합니다. + RETURN FOUND; +END; +$$; + + +ALTER FUNCTION kngil.sp_fa_comments_d(p_id integer) OWNER TO postgres; + +-- +-- TOC entry 271 (class 1255 OID 16684) +-- Name: sp_fa_comments_i(text, text, integer, character, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_fa_comments_i(p_subject text, p_content text, p_sq_no integer, p_use_yn character, p_cid character varying) RETURNS integer + LANGUAGE plpgsql + AS $$ +DECLARE + v_new_id integer; +BEGIN + -- fa_id는 GENERATED ALWAYS이므로 INSERT 문에서 제외하여 DB가 자동 생성하게 함 + INSERT INTO kngil.fa_comments ( + fa_subject, + fa_content, + sq_no, + use_yn, + cid, + cdt + ) VALUES ( + p_subject, + p_content, + p_sq_no, + p_use_yn, + p_cid, + CURRENT_TIMESTAMP + ) + RETURNING fa_id INTO v_new_id; + + RETURN v_new_id; +END; +$$; + + +ALTER FUNCTION kngil.sp_fa_comments_i(p_subject text, p_content text, p_sq_no integer, p_use_yn character, p_cid character varying) OWNER TO postgres; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- TOC entry 235 (class 1259 OID 16671) +-- Name: fa_comments; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.fa_comments ( + fa_id integer NOT NULL, + fa_subject text, + fa_content text, + sq_no integer, + use_yn character(1), + cid character varying(20), + cdt timestamp with time zone, + mid character varying(20), + mdt timestamp with time zone +); + + +ALTER TABLE kngil.fa_comments OWNER TO postgres; + +-- +-- TOC entry 268 (class 1255 OID 16686) +-- Name: sp_fa_comments_r(); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_fa_comments_r() RETURNS SETOF kngil.fa_comments + LANGUAGE plpgsql + AS $$ +BEGIN + -- 사용 여부(use_yn)가 'Y'인 데이터를 순번(sq_no) 오름차순으로 조회합니다. + RETURN QUERY + SELECT * FROM kngil.fa_comments + WHERE use_yn = 'Y' + ORDER BY sq_no ASC, fa_id DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_fa_comments_r() OWNER TO postgres; + +-- +-- TOC entry 272 (class 1255 OID 16683) +-- Name: sp_fa_comments_u(integer, text, text, integer, character, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_fa_comments_u(p_id integer, p_subject text, p_content text, p_sq_no integer, p_use_yn character, p_mid character varying) RETURNS boolean + LANGUAGE plpgsql + AS $$ +BEGIN + -- 고유 ID(fa_id)를 조건으로 데이터를 업데이트합니다. + UPDATE kngil.fa_comments + SET + fa_subject = p_subject, + fa_content = p_content, + sq_no = p_sq_no, + use_yn = p_use_yn, + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE + fa_id = p_id; + + -- 업데이트된 행이 있는지 확인하여 반환합니다. + RETURN FOUND; +END; +$$; + + +ALTER FUNCTION kngil.sp_fa_comments_u(p_id integer, p_subject text, p_content text, p_sq_no integer, p_use_yn character, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 261 (class 1255 OID 16568) +-- Name: sp_item_d(character); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_item_d(p_itm_cd character) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE + v_buy_count INTEGER; +BEGIN + -- 1. 무결성 체크: 이 상품을 구매한 내역(buy_item)이 있는지 확인 + SELECT COUNT(*) INTO v_buy_count + FROM kngil.buy_item + WHERE itm_cd = p_itm_cd; + + IF v_buy_count > 0 THEN + RETURN 'ERROR: 구매 내역이 존재하는 상품은 삭제할 수 없습니다. (사용여부를 N으로 변경하세요)'; + END IF; + + -- 2. 상품 존재 여부 확인 + IF NOT EXISTS (SELECT 1 FROM kngil.item WHERE itm_cd = p_itm_cd) THEN + RETURN 'ERROR: 삭제할 상품 정보를 찾을 수 없습니다.'; + END IF; + + -- 3. 삭제 실행 + DELETE FROM kngil.item + WHERE itm_cd = p_itm_cd; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + -- 오류 발생 시 자동 롤백됩니다. + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_item_d(p_itm_cd character) OWNER TO postgres; + +-- +-- TOC entry 263 (class 1255 OID 16576) +-- Name: sp_item_i(character, character varying, numeric, numeric, character, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_item_i(p_itm_cd character, p_itm_nm character varying, p_area numeric, p_itm_amt numeric, p_use_yn character, p_rmks character varying, p_cid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE + v_count INTEGER; +BEGIN + -- 1. 상품코드(itm_cd) 중복 체크 + SELECT COUNT(*) INTO v_count FROM kngil.item WHERE itm_cd = p_itm_cd; + + IF v_count > 0 THEN + RETURN 'ERROR: 이미 존재하는 상품코드입니다.'; + END IF; + + -- 2. 데이터 삽입 + INSERT INTO kngil.item ( + itm_cd, itm_nm, area, itm_amt, + use_yn, rmks, + cid, cdt, mid, mdt + ) VALUES ( + p_itm_cd, p_itm_nm, p_area, p_itm_amt, + p_use_yn, p_rmks, + p_cid, CURRENT_TIMESTAMP, p_cid, CURRENT_TIMESTAMP + ); + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + -- 트랜잭션 원자성에 의해 오류 발생 시 자동 롤백됩니다. + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_item_i(p_itm_cd character, p_itm_nm character varying, p_area numeric, p_itm_amt numeric, p_use_yn character, p_rmks character varying, p_cid character varying) OWNER TO postgres; + +-- +-- TOC entry 252 (class 1255 OID 16572) +-- Name: sp_item_r(); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_item_r() RETURNS TABLE(itm_cd character varying, itm_nm character varying, area numeric, itm_amt numeric, use_yn character varying, rmks character varying, cid character varying, cdt timestamp without time zone, mid character varying, mdt timestamp without time zone) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + a.itm_cd::VARCHAR, -- bpchar 에러 방지를 위한 명시적 변환 + a.itm_nm::VARCHAR, + a.area::NUMERIC, -- DECIMAL을 NUMERIC으로 맞춤 + a.itm_amt::NUMERIC, + a.use_yn::VARCHAR, + a.rmks::VARCHAR, + a.cid::VARCHAR, + a.cdt, + a.mid::VARCHAR, + a.mdt + FROM kngil.item a + ORDER BY a.itm_cd ASC; +END; +$$; + + +ALTER FUNCTION kngil.sp_item_r() OWNER TO postgres; + +-- +-- TOC entry 260 (class 1255 OID 16567) +-- Name: sp_item_u(character, character varying, numeric, numeric, character, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_item_u(p_itm_cd character, p_itm_nm character varying, p_area numeric, p_itm_amt numeric, p_use_yn character, p_rmks character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +BEGIN + -- 1. 데이터 업데이트 수행 + UPDATE kngil.item + SET + itm_nm = p_itm_nm, -- 상품명 + area = p_area, -- 면적 + itm_amt = p_itm_amt, -- 상품금액 + use_yn = p_use_yn, -- 사용여부 + rmks = p_rmks, -- 비고 + mid = p_mid, -- 수정자 기록 + mdt = CURRENT_TIMESTAMP -- 수정일시 기록 + WHERE itm_cd = p_itm_cd; + + -- 2. 업데이트된 행이 있는지 확인 + IF NOT FOUND THEN + RETURN 'ERROR: 수정할 상품코드를 찾을 수 없습니다.'; + END IF; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_item_u(p_itm_cd character, p_itm_nm character varying, p_area numeric, p_itm_amt numeric, p_use_yn character, p_rmks character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 266 (class 1255 OID 16580) +-- Name: sp_member_i(character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_member_i(p_co_bc character varying, p_member_id character varying, p_user_pw character varying, p_member_nm character varying, p_email character varying, p_tel_no character varying, p_co_nm character varying, p_dept_nm character varying, p_cid character varying) RETURNS character varying + LANGUAGE plpgsql + AS $$ + -- 값을 반환하기 위해 VARCHAR로 수정 + DECLARE v_user_exists INTEGER; + v_itm_area numeric; -- 무료 제공 면적 + v_end_dt timestamp; +/* +회원가입시 사용되는 함수 +*/ +BEGIN + + v_end_dt := (date_trunc('year', CURRENT_TIMESTAMP) + INTERVAL '1 year - 1 day')::timestamp; + + -- [0] 무료제공면적 + SELECT a.area INTO v_itm_area + FROM kngil.item a + WHERE a.itm_cd = 'A0000'; -- 회원가입제공 면적 + + + -- [1] 중복 아이디 체크 (대소문자 무시) + SELECT COUNT(*) INTO v_user_exists + FROM kngil.users + WHERE LOWER(user_id) = LOWER(p_member_id); -- 양쪽 모두 소문자로 변환하여 비교 + + IF v_user_exists > 0 THEN + RETURN 'ERROR: 이미 존재하는 아이디입니다. (대소문자 포함)'; + END IF; + + -- 회원 테이블 저장 + INSERT INTO kngil.members ( + member_id, member_nm, co_bc, bs_no, co_nm, + co_tel, tel_no, email, join_dt, buy_area, + use_area, stat_bc, memo, cid, cdt + ) VALUES ( + p_member_id -- 회원ID + , p_member_nm -- 성명 + , p_co_bc -- 법인구분 case 구문 사용해야 할 수 있음. 변수가 라디오 버튼 + , null -- 사업자번호 + , p_co_nm -- 회사명 + , null -- 회사번호 + , p_tel_no -- 휴대폰 + , p_email -- 이메일 + , CURRENT_DATE -- 가입일자 + , null -- 구매면적 + , null -- 사용면적 + , 'SA100100' -- 회원상태 : 사용중 + , null -- 메모 + , p_cid -- 생성자 + , CURRENT_TIMESTAMP -- 생성일 + ); + + -- 회원가입 시 사용자정보 권한 "메인" 생성 + INSERT INTO kngil.users ( + member_id, user_id, user_pw, user_nm, dept_nm, + posit_nm, tel_no, email, auth_bc, use_yn, + rmks, cid, cdt + ) VALUES ( + p_member_id -- 회원ID + , p_member_id -- 유저ID : 최초 회원의 ID와 동일 + , p_user_pw -- 로그인 PW + , p_member_nm -- 성명 + , p_dept_nm -- 부서 + , null -- 직위 + , p_tel_no -- 전화번호 + , p_email -- 이메일 + , 'BS100300' -- 권한관리 : 메인 + , 'Y' -- 사용여부 Y 고정 + , null -- 비고 + , p_cid -- 생성자 + , CURRENT_TIMESTAMP -- 생성일 + ); + + + + -- 사용면적 제공 (구매이력 테이블에 초기제공상품 정보 등록 금액 0원) + PERFORM kngil.sp_buy_item_i( + p_member_id::character varying, -- 1. p_member_id + CURRENT_DATE::date, -- 2.가입일 + 'A0000'::character varying, -- 3. p_itm_cd + 1::integer, -- 4. p_itm_qty + v_itm_area::numeric, -- 5. p_itm_area + 0::numeric, -- 6. p_add_area + v_itm_area::numeric, -- 7. p_sum_area + 0::numeric, -- 8. p_itm_amt + 0::numeric, -- 9. p_dis_rt + 0::numeric, -- 10. p_buy_amt + 0::numeric, -- 11. p_vat_amt + 0::numeric, -- 12. p_sum_amt + v_end_dt::date, -- 13. p_end_dt + 'Y'::character varying, -- 14. p_ok_yn + '최초 가입 제공'::character varying, -- 15. p_rmks + p_cid::character varying -- 16. p_cid + ); + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_member_i(p_co_bc character varying, p_member_id character varying, p_user_pw character varying, p_member_nm character varying, p_email character varying, p_tel_no character varying, p_co_nm character varying, p_dept_nm character varying, p_cid character varying) OWNER TO postgres; + +-- +-- TOC entry 259 (class 1255 OID 16565) +-- Name: sp_member_sys_r(character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_member_sys_r(p_co_nm character varying DEFAULT NULL::character varying, p_rem_area character varying DEFAULT NULL::character varying, p_stat_bc character varying DEFAULT NULL::character varying) RETURNS TABLE(member_id character varying, user_id character varying, user_nm character varying, tel_no character varying, email character varying, co_nm character varying, bs_no character varying, join_dt date, user_y numeric, buy_area numeric, use_area numeric, rem_area numeric, stat_bc character varying, memo text) + LANGUAGE plpgsql + AS $$ +/* + 설 명 : 큰길회원 list 관리자 화면 조회, 사용자의 권한구분이 "메인" 인 인원 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +BEGIN + RETURN QUERY + SELECT + a.member_id, -- 회원ID + b.user_id, -- 사용자ID + b.user_nm, -- 사용자이름 + b.tel_no, -- 사용자연락처 + b.email, -- 사용자이메일 + a.co_nm, -- 회사명 + a.bs_no, -- 사업자번호 + a.join_dt, -- 가입일자 + (SELECT COUNT(*) + FROM kngil.users x + left join kngil.code_detail y on x.auth_bc = y.base_cd + WHERE x.member_id = a.member_id + and y.m1 = '1' + and x.use_yn='Y')::numeric as user_y, -- 사용자수 + a.buy_area, -- 구매면적 + a.use_area, -- 사용면적 + a.buy_area - a.use_area as rem_area, -- 잔여면적 + a.stat_bc, -- 회원상태 + a.memo + FROM kngil.members a -- 회원정보 + left join kngil.users b on a.member_id = b.member_id and b.auth_bc = 'BS100300' -- 권한구분이 메인 + WHERE 1=1 + AND (a.co_nm LIKE '%' || p_co_nm || '%' OR p_co_nm = '') + AND (p_rem_area = '' OR p_rem_area::NUMERIC >= (a.buy_area - a.use_area)) + and (p_stat_bc ='' OR a.stat_bc = p_stat_bc) + ORDER BY a.join_dt DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_member_sys_r(p_co_nm character varying, p_rem_area character varying, p_stat_bc character varying) OWNER TO postgres; + +-- +-- TOC entry 264 (class 1255 OID 16578) +-- Name: sp_member_sys_u(character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_member_sys_u(p_member_id character varying, p_tel_no character varying, p_email character varying, p_bs_no character varying, p_co_nm character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +BEGIN + -- 1. 회원정보 업데이트 + UPDATE kngil.members + SET tel_no = p_tel_no, + email = p_email, + bs_no = p_bs_no, + co_nm = p_co_nm, + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE member_id = p_member_id; + + -- 2. 유저정보 업데이트 + UPDATE kngil.users + SET tel_no = p_tel_no, + email = p_email, + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE member_id = p_member_id + AND auth_bc = 'BS100300'; -- b.auth_bc 대신 컬럼명 직접 사용 + + -- 결과 확인 (하나라도 수정되었다면 성공) + IF NOT FOUND THEN + RETURN 'ERROR: 수정할 회원 또는 유저 정보를 찾을 수 없습니다.'; + else + RETURN 'SUCCESS'; + END IF; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_member_sys_u(p_member_id character varying, p_tel_no character varying, p_email character varying, p_bs_no character varying, p_co_nm character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 277 (class 1255 OID 16707) +-- Name: sp_use_history(character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_use_history(p_member_id character varying, p_user_nm character varying DEFAULT NULL::character varying, p_dept_nm character varying DEFAULT NULL::character varying) RETURNS TABLE(member_id character varying, use_dt date, user_id character varying, sq_no integer, user_nm character varying, dept_nm character varying, posit_nm character varying, use_yn character varying, use_area numeric, ser_bc character varying, cdt timestamp without time zone) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + b.member_id::character varying, + a.use_dt, -- DATE 타입 + a.user_id::character varying, + a.sq_no, + b.user_nm::character varying, + b.dept_nm::character varying, + b.posit_nm::character varying, + b.use_yn::character varying, + a.use_area, + kngil.fn_base_nm(a.ser_bc)::character varying as ser_bc, + a.cdt + FROM kngil.use_history a + INNER JOIN kngil.users b ON a.user_id = b.user_id + WHERE b.member_id = p_member_id + -- 이름 검색 (값이 있을 때만 LIKE) + AND (NULLIF(p_user_nm, '') IS NULL OR b.user_nm LIKE '%' || p_user_nm || '%') + -- 부서 검색 (값이 있을 때만 LIKE) + AND (NULLIF(p_dept_nm, '') IS NULL OR b.dept_nm LIKE '%' || p_dept_nm || '%') + ORDER BY a.use_dt DESC, a.sq_no DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_use_history(p_member_id character varying, p_user_nm character varying, p_dept_nm character varying) OWNER TO postgres; + +-- +-- TOC entry 270 (class 1255 OID 16637) +-- Name: sp_user_auth_tran(character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_user_auth_tran(p_tuser_id character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE + v_fuser_id character varying; -- 위임 주는 사람 (From, 기존 메인) + v_member_id character varying; -- 위임 받는 사람의 회원ID +BEGIN + -- 1. 위임 받을 사용자 정보 조회 (한 번에 조회) + SELECT member_id INTO v_member_id + FROM kngil.users + WHERE user_id = p_tuser_id; + + -- 사용자가 없는 경우 처리 + IF v_member_id IS NULL THEN + RETURN 'ERROR: 수정할 사용자를 찾을 수 없습니다.'; + END IF; + + -- 2. 해당 멤버의 기존 메인 사용자(BS100300) 찾기 + SELECT user_id INTO v_fuser_id + FROM kngil.users + WHERE member_id = v_member_id + AND auth_bc = 'BS100300' + AND use_yn = 'Y'; + + -- 메인 사용자가 없는 경우 (위임해 줄 대상이 없음) + IF v_fuser_id IS NULL THEN + RETURN 'ERROR: 위임할 메인 사용자를 찾을 수 없습니다.'; + END IF; + + -- 본인에게 위임하는 경우 방지 (필요 시) + IF v_fuser_id = p_tuser_id THEN + RETURN 'ERROR: 본인에게는 권한을 위임할 수 없습니다.'; + END IF; + + -- 3. 권한 위임받는 사람 업데이트 (서브 -> 메인) + UPDATE kngil.users + SET + auth_bc = 'BS100300', + rmks = '권한위임[' || v_fuser_id || ']', -- + 대신 || 사용 + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE user_id = p_tuser_id; + + -- 4. 권한 위임주는 사람 업데이트 (메인 -> 서브) + UPDATE kngil.users + SET + auth_bc = 'BS100400', + rmks = '권한위임[' || p_tuser_id || ']', -- + 대신 || 사용 + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE user_id = v_fuser_id; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_user_auth_tran(p_tuser_id character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 278 (class 1255 OID 16711) +-- Name: sp_user_end(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_user_end(p_user_id character varying) RETURNS void + LANGUAGE plpgsql + AS $$ +DECLARE + v_member_id character varying; + v_auth_bc character varying; +BEGIN + -- 1. 유저 ID로 소속 멤버 ID와 권한 코드를 한 번에 조회 + SELECT a.member_id, a.auth_bc + INTO v_member_id, v_auth_bc + FROM kngil.users a + WHERE a.user_id = p_user_id; + + -- 2. 권한 코드에 따른 조건 분기 + IF v_auth_bc = 'BS100300' THEN + -- [메인 관리자 권한] 해당 멤버 전체 탈퇴 및 소속 유저 전원 중지 + + -- 멤버 테이블 업데이트 (탈퇴 처리) + UPDATE kngil.members + SET stat_bc = 'SA100900', + mid = p_user_id, + mdt = CURRENT_TIMESTAMP + WHERE member_id = v_member_id; + + -- 해당 멤버에 속한 모든 유저 사용 안함 처리 + UPDATE kngil.users + SET use_yn = 'N', + mid = p_user_id, + mdt = CURRENT_TIMESTAMP + WHERE member_id = v_member_id; + + ELSE + -- [일반 권한] 해당 유저 개인만 사용 안함 처리 + + UPDATE kngil.users + SET use_yn = 'N', + mid = p_user_id, + mdt = CURRENT_TIMESTAMP + WHERE user_id = p_user_id; + + END IF; + +END; +$$; + + +ALTER FUNCTION kngil.sp_user_end(p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 254 (class 1255 OID 16546) +-- Name: sp_users_d(character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_d(p_member_id character varying, p_user_id character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE +/* + 설 명 : users 테이블 삭제 프로시져 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ + v_exists INTEGER; +BEGIN + -- 1. 삭제 대상이 존재하는지 먼저 확인 + SELECT COUNT(*) INTO v_exists + FROM kngil.users + WHERE member_id = p_member_id AND user_id = p_user_id; + + IF v_exists = 0 THEN + RETURN 'ERROR: 삭제할 사용자를 찾을 수 없습니다.'; + END IF; + + -- 2. 데이터 삭제 실행 + DELETE FROM kngil.users + WHERE member_id = p_member_id + AND user_id = p_user_id; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + -- 트랜잭션 원자성에 의해 에러 발생 시 자동 롤백됩니다. + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_d(p_member_id character varying, p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 265 (class 1255 OID 16577) +-- Name: sp_users_i(character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_i(p_member_id character varying, p_user_id character varying, p_user_pw character varying, p_user_nm character varying, p_dept_nm character varying, p_posit_nm character varying, p_tel_no character varying, p_email character varying, p_auth_bc character varying, p_use_yn character varying, p_rmks character varying, p_cid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +/* + 설 명 : users table 데이터 생성 프로시져 "회원 관리자 페이지" 사용자 추가 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +DECLARE + v_count INTEGER; + -- 값을 반환하기 위해 VARCHAR로 수정 + DECLARE v_user_exists INTEGER; +BEGIN + + -- [1] 중복 아이디 체크 (대소문자 무시) + SELECT COUNT(*) INTO v_user_exists + FROM kngil.users + WHERE LOWER(user_id) = LOWER(p_user_id); -- 양쪽 모두 소문자로 변환하여 비교 + + IF v_user_exists > 0 THEN + RETURN 'ERROR: 이미 존재하는 아이디입니다.'; + END IF; + + -- 1. 부모 테이블(members)에 member_id가 존재하는지 먼저 체크 + SELECT COUNT(*) INTO v_count FROM kngil.members WHERE member_id = p_member_id; + + IF v_count = 0 THEN + RETURN 'ERROR: 존재하지 않는 회원ID(member_id)입니다.'; + END IF; + + -- 2. 사용자ID(user_id) 중복 체크 + SELECT COUNT(*) INTO v_count FROM kngil.users WHERE user_id = p_user_id; + + IF v_count > 0 THEN + RETURN 'ERROR: 이미 존재하는 사용자ID입니다.'; + END IF; + + -- 3. 데이터 삽입 + INSERT INTO kngil.users ( + member_id, user_id, user_pw, user_nm, + dept_nm, posit_nm, tel_no, email, + auth_bc, use_yn, rmks, + cid, cdt, mid, mdt + ) VALUES ( + p_member_id, p_user_id, p_user_pw, p_user_nm, + p_dept_nm, p_posit_nm, p_tel_no, p_email, + p_auth_bc, p_use_yn, p_rmks, + p_cid, CURRENT_TIMESTAMP, p_cid, CURRENT_TIMESTAMP + ); + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_i(p_member_id character varying, p_user_id character varying, p_user_pw character varying, p_user_nm character varying, p_dept_nm character varying, p_posit_nm character varying, p_tel_no character varying, p_email character varying, p_auth_bc character varying, p_use_yn character varying, p_rmks character varying, p_cid character varying) OWNER TO postgres; + +-- +-- TOC entry 258 (class 1255 OID 16590) +-- Name: sp_users_my_history(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_my_history(p_user_id character varying) RETURNS TABLE(use_dt date, user_nm character varying, use_area numeric, ser_bc character varying, cdt timestamp without time zone) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + h.use_dt, + u.user_nm, + h.use_area, + kngil.fn_base_nm(h.ser_bc) as ser_bc, + h.cdt + FROM kngil.use_history h + JOIN kngil.users u ON h.user_id = u.user_id + WHERE h.user_id = p_user_id + ORDER BY h.use_dt DESC, h.sq_no DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_my_history(p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 269 (class 1255 OID 16633) +-- Name: sp_users_my_r(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_my_r(p_user_id character varying) RETURNS TABLE(co_bc character varying, user_id character varying, user_pw character varying, user_nm character varying, email character varying, tel_no character varying, co_nm character varying, dept_nm character varying, tot_use numeric, year_use numeric) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + b.co_bc -- 법인구분 + , a.user_id -- ID + , a.user_pw -- 비밀번호 + , a.user_nm -- 성명 + , a.email -- 이메일 + , a.tel_no -- 연락처 + , b.co_nm -- 회사명 + , a.dept_nm -- 부서 + , (select sum(x.use_area) from kngil.use_history x where a.user_id = x.user_id)::numeric as tot_use -- 누적 사용량 + , (select sum(x.use_area) from kngil.use_history x where a.user_id = x.user_id and EXTRACT(YEAR FROM use_dt) = EXTRACT(YEAR FROM CURRENT_DATE))::numeric as year_use -- 당년 사용량 + FROM kngil.users a + left JOIN kngil.members b ON a.member_id = b.member_id + WHERE a.user_id = p_user_id; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_my_r(p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 267 (class 1255 OID 16584) +-- Name: sp_users_my_u(character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_my_u(p_user_id character varying, p_user_pw character varying, p_email character varying, p_tel_no character varying, p_dept_nm character varying, p_mid character varying) RETURNS character varying + LANGUAGE plpgsql + AS $$ + -- 값을 반환하기 위해 VARCHAR로 수정 + DECLARE v_user_exists INTEGER; +/* +내정보 수정 화면에서 사용. +*/ + +BEGIN + + -- 데이터 업데이트 + UPDATE kngil.users + SET + user_pw = COALESCE(NULLIF(p_user_pw, ''), user_pw), -- 비밀번호가 빈값이면 기존 유지 + dept_nm = p_dept_nm, -- 부서명 + tel_no = p_tel_no, -- 전화번호 + email = p_email, -- 메일주소 + mid = p_mid, -- 수정자 ID 기록 + mdt = CURRENT_TIMESTAMP -- 수정일시 기록 + WHERE 1=1 + AND user_id = p_user_id; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_my_u(p_user_id character varying, p_user_pw character varying, p_email character varying, p_tel_no character varying, p_dept_nm character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 279 (class 1255 OID 16718) +-- Name: sp_users_r(character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_r(p_member_id character varying DEFAULT ''::character varying, p_user_nm character varying DEFAULT ''::character varying, p_dept_nm character varying DEFAULT ''::character varying, p_use_yn character varying DEFAULT ''::character varying) RETURNS TABLE(member_id character varying, users_tot bigint, users_y bigint, term text, tuse_area numeric, tbuy_area numeric, remain_area numeric, user_id character varying, user_pw character varying, user_nm character varying, tel_no character varying, email character varying, dept_nm character varying, use_area numeric, cdt date, use_yn character varying, auth_bc character varying, rmks character varying, itm_nm character varying) + LANGUAGE plpgsql + AS $$ +/* + 설 명 : 조회 프로시져 members 기반 users 테이블 조인하여 사용. "회원 관리자 페이지" 조회용 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +DECLARE + v_to_dt character varying; -- 유효일자 + v_remain_days integer; -- 잔여일 저장 변수 + v_itm_nm character varying; -- 가입 상품명 +BEGIN + + SELECT + MAX(end_dt)::text + + INTO + v_to_dt + FROM kngil.buy_item a + WHERE a.member_id = p_member_id; -- 유효일자 종료일자만 표시 END_DT 가 가장 늦은 날 기준. + + + SELECT b.itm_nm + INTO v_itm_nm + FROM kngil.buy_item a + LEFT JOIN kngil.item b ON a.itm_cd = b.itm_cd + WHERE a.member_id = p_member_id + AND EXTRACT(YEAR FROM a.end_dt) = EXTRACT(YEAR FROM CURRENT_DATE) + -- 금액이 큰 순서대로 정렬 + ORDER BY a.itm_amt DESC, a.end_dt DESC + LIMIT 1; + + + v_remain_days := COALESCE(v_to_dt::DATE - CURRENT_DATE, 0); + + + + RETURN QUERY + SELECT + -- [회원정보 기반] + a.member_id, + (SELECT COUNT(*) FROM kngil.users u WHERE u.member_id = a.member_id) AS users_tot, -- 발급사용자 수 + (SELECT COUNT(*) FROM kngil.users u WHERE u.member_id = a.member_id and u.use_yn='Y') AS users_y, -- 실사용자 수 + --v_fr_dt||' ~ '||v_to_dt AS term, -- 유효일자 + ' : '||v_to_dt||' ('||v_remain_days||'일)' AS term, -- 유효일자 종료일자만 표시 END_DT 가 가장 늦은 날 기준. + a.use_area AS tuse_area, -- 총 사용면적 + a.buy_area AS tbuy_area, -- 총 구입면적 + (a.buy_area - a.use_area) AS remain_area, -- 잔여면적 (계산필드) + + -- [사용자정보 기반] + b.user_id, + b.user_pw, + b.user_nm, + b.tel_no, + b.email, + b.dept_nm, + -- 사용자별 개별 사용량 (use_history 테이블 참조) -- 당해년도기준으로 할지 전체 누적으로 할지 고민 필요. + COALESCE((SELECT SUM(uh.use_area) FROM kngil.use_history uh WHERE uh.user_id = b.user_id), 0) AS use_area, + b.cdt::DATE as cdt, + b.use_yn, -- 사용여부 + b.auth_bc, -- 권한구분 + b.rmks, -- 유저비고 + v_itm_nm::character varying as itm_nm + FROM kngil.members a + LEFT JOIN kngil.users b ON a.member_id = b.member_id + WHERE 1=1 + AND a.member_id = p_member_id + AND (b.user_nm LIKE '%' || p_user_nm || '%' OR p_user_nm = '') + AND (b.dept_nm LIKE '%' || p_dept_nm || '%' OR p_dept_nm = '') + AND (p_use_yn = '' OR b.use_yn = p_use_yn) + ORDER BY a.member_id DESC, b.user_id ASC; + +END; +$$; + + +ALTER FUNCTION kngil.sp_users_r(p_member_id character varying, p_user_nm character varying, p_dept_nm character varying, p_use_yn character varying) OWNER TO postgres; + +-- +-- TOC entry 253 (class 1255 OID 16545) +-- Name: sp_users_u(character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_u(p_member_id character varying, p_user_id character varying, p_user_pw character varying, p_user_nm character varying, p_dept_nm character varying, p_posit_nm character varying, p_tel_no character varying, p_email character varying, p_auth_bc character varying, p_use_yn character varying, p_rmks character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE + v_exists INTEGER; +BEGIN + -- 1. 해당 사용자가 존재하는지 확인 + SELECT COUNT(*) INTO v_exists + FROM kngil.users + WHERE member_id = p_member_id AND user_id = p_user_id; + + IF v_exists = 0 THEN + RETURN 'ERROR: 수정할 사용자를 찾을 수 없습니다.'; + END IF; + + -- 2. 데이터 업데이트 + UPDATE kngil.users + SET + user_pw = COALESCE(NULLIF(p_user_pw, ''), user_pw), -- 비밀번호가 빈값이면 기존 유지 + user_nm = p_user_nm, -- 이름 + dept_nm = p_dept_nm, -- 부서명 + posit_nm = p_posit_nm, -- 직위 + tel_no = p_tel_no, -- 전화번호 + email = p_email, -- 메일주소 + auth_bc = p_auth_bc, -- 권한구분 + use_yn = p_use_yn, -- 사용여부 + rmks = p_rmks, -- 비고 + mid = p_mid, -- 수정자 ID 기록 + mdt = CURRENT_TIMESTAMP -- 수정일시 기록 + WHERE member_id = p_member_id + AND user_id = p_user_id; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_u(p_member_id character varying, p_user_id character varying, p_user_pw character varying, p_user_nm character varying, p_dept_nm character varying, p_posit_nm character varying, p_tel_no character varying, p_email character varying, p_auth_bc character varying, p_use_yn character varying, p_rmks character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 223 (class 1259 OID 16449) +-- Name: buy_item; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.buy_item ( + member_id character varying(20) NOT NULL, + sq_no integer NOT NULL, + buy_dt date NOT NULL, + itm_cd character(5) NOT NULL, + itm_qty numeric(18,0) NOT NULL, + itm_area numeric(18,0) NOT NULL, + add_area numeric(18,0), + sum_area numeric(18,0), + itm_amt numeric(18,2) NOT NULL, + dis_rt numeric(5,2), + buy_amt numeric(18,2), + vat_amt numeric(18,2), + sum_amt numeric(18,2), + end_dt date, + ok_yn character(1), + rmks character varying(100), + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone +); + + +ALTER TABLE kngil.buy_item OWNER TO postgres; + +-- +-- TOC entry 227 (class 1259 OID 16522) +-- Name: code_detail; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.code_detail ( + main_cd character varying(5) NOT NULL, + sub_cd character varying(5) NOT NULL, + sub_nm character varying(100), + base_cd character varying(10) NOT NULL, + use_yn character(1) NOT NULL, + sort_sq integer, + rmks character varying(100), + m1 character varying(50), + m2 character varying(50), + m3 character varying(50), + m4 character varying(50), + m5 character varying(50), + m6 character varying(50), + m7 character varying(50), + m8 character varying(50), + m9 character varying(50), + m10 character varying(50) +); + + +ALTER TABLE kngil.code_detail OWNER TO postgres; + +-- +-- TOC entry 5044 (class 0 OID 0) +-- Dependencies: 227 +-- Name: TABLE code_detail; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.code_detail IS '공통코드상세'; + + +-- +-- TOC entry 5045 (class 0 OID 0) +-- Dependencies: 227 +-- Name: COLUMN code_detail.sub_nm; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.code_detail.sub_nm IS '서브코드명'; + + +-- +-- TOC entry 226 (class 1259 OID 16497) +-- Name: code_master; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.code_master ( + main_cd character varying(5) NOT NULL, + nain_nm character varying(100), + use_yn character(1), + use_bc character varying(10), + sort_bc character varying(10), + rmks character varying(100), + t1 character varying(50), + t2 character varying(50), + t3 character varying(50), + t4 character varying(50), + t5 character varying(50), + t6 character varying(50), + t7 character varying(50), + t8 character varying(50), + t9 character varying(50), + t10 character varying(50) +); + + +ALTER TABLE kngil.code_master OWNER TO postgres; + +-- +-- TOC entry 5046 (class 0 OID 0) +-- Dependencies: 226 +-- Name: TABLE code_master; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.code_master IS '공통코드마스터'; + + +-- +-- TOC entry 5047 (class 0 OID 0) +-- Dependencies: 226 +-- Name: COLUMN code_master.main_cd; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.code_master.main_cd IS '메인코드'; + + +-- +-- TOC entry 5048 (class 0 OID 0) +-- Dependencies: 226 +-- Name: COLUMN code_master.nain_nm; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.code_master.nain_nm IS '코드명'; + + +-- +-- TOC entry 236 (class 1259 OID 16679) +-- Name: fa_comments_fa_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.fa_comments ALTER COLUMN fa_id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME kngil.fa_comments_fa_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 221 (class 1259 OID 16427) +-- Name: item; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.item ( + itm_cd character varying(5) NOT NULL, + itm_nm character varying(50), + area numeric(18,0), + itm_amt numeric(18,2), + use_yn character(1), + rmks character varying(100), + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone +); + + +ALTER TABLE kngil.item OWNER TO postgres; + +-- +-- TOC entry 5049 (class 0 OID 0) +-- Dependencies: 221 +-- Name: TABLE item; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.item IS '서비스 상품 관리 테이블'; + + +-- +-- TOC entry 225 (class 1259 OID 16484) +-- Name: login_history; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.login_history ( + user_id character varying(20) NOT NULL, + sq_no integer NOT NULL, + public_ip character varying(45), + local_ip character varying(45), + login_tm timestamp without time zone +); + + +ALTER TABLE kngil.login_history OWNER TO postgres; + +-- +-- TOC entry 220 (class 1259 OID 16414) +-- Name: members; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.members ( + member_id character varying(20) CONSTRAINT member_member_id_not_null NOT NULL, + member_nm character varying(50) CONSTRAINT member_member_nm_not_null NOT NULL, + co_bc character varying(10) CONSTRAINT member_co_bc_not_null NOT NULL, + bs_no character varying(15), + co_nm character varying(50) CONSTRAINT member_co_nm_not_null NOT NULL, + co_tel character varying(13), + tel_no character varying(13), + email character varying(50), + join_dt date CONSTRAINT member_join_dt_not_null NOT NULL, + end_dt date, + buy_area numeric(18,0), + use_area numeric(18,0), + stat_bc character varying(10) CONSTRAINT member_stat_bc_not_null NOT NULL, + memo text, + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone +); + + +ALTER TABLE kngil.members OWNER TO postgres; + +-- +-- TOC entry 5050 (class 0 OID 0) +-- Dependencies: 220 +-- Name: TABLE members; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.members IS '회원 관리 테이블'; + + +-- +-- TOC entry 5051 (class 0 OID 0) +-- Dependencies: 220 +-- Name: COLUMN members.member_id; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.members.member_id IS '회원ID'; + + +-- +-- TOC entry 228 (class 1259 OID 16594) +-- Name: qa_attachments; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.qa_attachments ( + post_id integer, + ori_name character varying, + save_path character varying, + file_size integer, + uploaded_at timestamp with time zone, + id bigint NOT NULL +); + + +ALTER TABLE kngil.qa_attachments OWNER TO postgres; + +-- +-- TOC entry 5052 (class 0 OID 0) +-- Dependencies: 228 +-- Name: TABLE qa_attachments; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.qa_attachments IS 'Q&A 첨부파일 테이블'; + + +-- +-- TOC entry 237 (class 1259 OID 16687) +-- Name: qa_attachments_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.qa_attachments ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME kngil.qa_attachments_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 234 (class 1259 OID 16627) +-- Name: qa_comment_images; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.qa_comment_images ( + id integer NOT NULL, + comment_id integer, + file_name character varying(255), + file_path character varying(255), + thumb_path character varying(255), + file_size integer, + uploaded_at timestamp with time zone +); + + +ALTER TABLE kngil.qa_comment_images OWNER TO postgres; + +-- +-- TOC entry 5053 (class 0 OID 0) +-- Dependencies: 234 +-- Name: TABLE qa_comment_images; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.qa_comment_images IS '뎃글 이미지'; + + +-- +-- TOC entry 233 (class 1259 OID 16626) +-- Name: qa_comment_images_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.qa_comment_images ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME kngil.qa_comment_images_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 232 (class 1259 OID 16618) +-- Name: qa_comments; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.qa_comments ( + comment_id integer NOT NULL, + post_id integer, + commenter character varying(255), + content text, + cdt_dt timestamp with time zone, + user_nm character varying(100), + mdt_dt timestamp with time zone +); + + +ALTER TABLE kngil.qa_comments OWNER TO postgres; + +-- +-- TOC entry 5054 (class 0 OID 0) +-- Dependencies: 232 +-- Name: TABLE qa_comments; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.qa_comments IS '뎃글'; + + +-- +-- TOC entry 231 (class 1259 OID 16617) +-- Name: qa_comments_comment_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.qa_comments ALTER COLUMN comment_id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME kngil.qa_comments_comment_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 230 (class 1259 OID 16603) +-- Name: qa_posts; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.qa_posts ( + post_id integer NOT NULL, + tel_no character varying(20), + user_id character varying(64), + user_nm character varying(100), + category character varying(20), + co_nm character varying(100), + dept_nm character varying(100), + title character varying(255), + content text, + attachment character varying(500), + stat_bc character varying(50), + complete_form character(1) DEFAULT 0, + cdt_dt timestamp with time zone, + mid_dt timestamp with time zone, + is_secret character(1) DEFAULT 'N'::bpchar, + is_read_admin character(1) DEFAULT 0 +); + + +ALTER TABLE kngil.qa_posts OWNER TO postgres; + +-- +-- TOC entry 5055 (class 0 OID 0) +-- Dependencies: 230 +-- Name: TABLE qa_posts; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.qa_posts IS 'Q&A 내용등록'; + + +-- +-- TOC entry 5056 (class 0 OID 0) +-- Dependencies: 230 +-- Name: COLUMN qa_posts.co_nm; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.qa_posts.co_nm IS '회사명 +'; + + +-- +-- TOC entry 229 (class 1259 OID 16602) +-- Name: qa_posts_post_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.qa_posts ALTER COLUMN post_id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME kngil.qa_posts_post_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 224 (class 1259 OID 16471) +-- Name: use_history; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.use_history ( + user_id character varying(20) NOT NULL, + sq_no integer NOT NULL, + use_dt date, + use_area numeric(18,0), + ser_bc character varying(10), + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone +); + + +ALTER TABLE kngil.use_history OWNER TO postgres; + +-- +-- TOC entry 222 (class 1259 OID 16433) +-- Name: users; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.users ( + member_id character varying(20) CONSTRAINT user_member_id_not_null NOT NULL, + user_id character varying(20) CONSTRAINT user_user_id_not_null NOT NULL, + user_pw character varying(255), + user_nm character varying(50) CONSTRAINT user_user_nm_not_null NOT NULL, + dept_nm character varying(50), + posit_nm character varying(50), + tel_no character varying(13) CONSTRAINT user_tel_no_not_null NOT NULL, + email character varying(50), + auth_bc character varying(10) CONSTRAINT user_auth_bc_not_null NOT NULL, + use_yn character varying(10) CONSTRAINT user_use_yn_not_null NOT NULL, + rmks character varying(100), + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone, + oidc_sub character varying(255) +); + + +ALTER TABLE kngil.users OWNER TO postgres; + +-- +-- TOC entry 5057 (class 0 OID 0) +-- Dependencies: 222 +-- Name: TABLE users; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.users IS '사용자관리 테이블'; + + +-- +-- TOC entry 5024 (class 0 OID 16449) +-- Dependencies: 223 +-- Data for Name: buy_item; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.buy_item (member_id, sq_no, buy_dt, itm_cd, itm_qty, itm_area, add_area, sum_area, itm_amt, dis_rt, buy_amt, vat_amt, sum_amt, end_dt, ok_yn, rmks, cid, cdt, mid, mdt) FROM stdin; +B260001 1 2026-01-14 BSV01 1 10000 500 10500 5000000.00 0.00 5000000.00 500000.00 5500000.00 2026-12-31 N test m24031 2026-01-14 18:48:27.938147 m24031 2026-01-14 18:48:27.938147 +sdisdi 2 2026-01-28 BSV01 3 0 100 100 5000000.00 10.00 13500000.00 1350000.00 14850000.00 \N N ADMIN 2026-01-29 14:02:04.478554 ADMIN 2026-01-29 16:57:37.297659 +sdisdi 1 2026-01-28 실버 2 0 0 0 5000000.00 10.00 9000000.00 900000.00 9900000.00 \N N ADMIN 2026-01-29 13:20:06.016857 ADMIN 2026-01-29 16:57:37.297659 +sdisdi 4 2026-01-28 BSV01 1 10000 0 10000 5000000.00 5.00 5000000.00 500000.00 5500000.00 2026-12-31 N ADMIN 2026-01-29 16:57:37.297659 ADMIN 2026-01-29 16:57:37.297659 +B260001 4 2026-01-16 DDA01 2 10000 0 10000 10000000.00 20.00 8000000.00 800000.00 8800000.00 2026-01-29 Y test1 ADMIN 2026-01-19 09:29:15.302285 ADMIN 2026-01-19 09:29:26.957085 +B260001 6 2026-01-19 DDA01 2 100000 0 100000 10000000.00 25.00 15000000.00 1500000.00 16500000.00 2026-01-29 Y ADMIN 2026-01-19 09:53:04.703453 ADMIN 2026-01-19 11:18:22.708297 +B260001 5 2026-01-19 DDA01 4 10000 0 10000 10000000.00 10.00 36000000.00 3600000.00 39600000.00 2026-01-30 Y ADMIN 2026-01-19 09:33:44.805899 ADMIN 2026-01-19 11:18:22.708297 +B260001 3 2026-01-16 DDA01 1 10000 0 10000 10000000.00 50.00 5000000.00 500000.00 5500000.00 2026-01-26 Y test ADMIN 2026-01-16 17:43:34.138282 ADMIN 2026-01-19 09:29:26.957085 +B260001 2 2026-01-16 DDA01 1 10000 0 10000 10000000.00 10.00 9000000.00 900000.00 9900000.00 2026-01-29 Y test ADMIN 2026-01-16 17:29:57.797917 ADMIN 2026-01-19 09:29:26.957085 +B260001 7 2026-01-19 BSV01 1 10000 0 10000 5000000.00 20.00 5000000.00 500000.00 5500000.00 2026-02-06 N ADMIN 2026-01-19 11:18:09.714842 ADMIN 2026-01-19 11:18:22.708297 +sdisdi 5 2026-01-29 CGD01 2 20000 500 20500 10000000.00 25.00 15000000.00 1500000.00 16500000.00 2026-12-30 N ADMIN 2026-01-29 17:10:39.39493 ADMIN 2026-01-29 17:12:56.807569 +sdisdi 3 2026-01-29 BSV01 1 10000 0 10000 5000000.00 5.00 4750000.00 475000.00 5225000.00 2026-12-31 N ADMIN 2026-01-29 15:31:42.295678 ADMIN 2026-01-29 17:12:56.807569 +tester001 1 2025-04-12 BSV01 1 10000 \N 10000 5000000.00 0.00 5000000.00 500000.00 5500000.00 2025-12-31 Y \N \N \N \N \N +tester001 2 2026-01-01 BSV01 1 10000 300 10300 5000000.00 0.00 5000000.00 500000.00 5500000.00 2026-12-31 Y \N \N \N \N \N +tester001 3 2026-01-01 BSV01 1 10000 \N 10000 5000000.00 0.00 5000000.00 500000.00 5500000.00 2026-12-31 Y \N \N \N \N \N +tester001 4 2026-01-01 BSV01 1 10000 \N 10000 5000000.00 0.00 5000000.00 500000.00 5500000.00 2026-12-31 Y \N \N \N \N \N +sdi9429 1 2026-01-29 골드 1 20000 0 20000 10000000.00 0.00 10000000.00 1000000.00 11000000.00 \N Y ADMIN 2026-01-29 13:05:15.730538 ADMIN 2026-01-29 13:14:38.877759 +sdisdi 6 2026-01-29 ZET01 3000 1 0 1 0.00 0.00 0.00 0.00 0.00 2026-12-31 N ADMIN 2026-01-29 17:12:56.807569 ADMIN 2026-01-29 17:12:56.807569 +am24031 1 2026-01-30 A0000 1 3000 0 3000 0.00 0.00 0.00 0.00 0.00 2026-12-31 Y 최초 가입 제공 am24031 2026-01-30 13:25:57.005379 am24031 2026-01-30 13:25:57.005379 +\. + + +-- +-- TOC entry 5028 (class 0 OID 16522) +-- Dependencies: 227 +-- Data for Name: code_detail; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.code_detail (main_cd, sub_cd, sub_nm, base_cd, use_yn, sort_sq, rmks, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10) FROM stdin; +BS100 300 메인 BS100300 Y 3 \N 1 \N \N \N \N \N \N \N \N \N +BS110 100 오름차순 BS110100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +BS110 200 정의 BS110200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +BS200 100 코드1 BS200100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +BS200 200 코드2 BS200200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +BS210 Y Y BS210Y Y 1 \N \N \N \N \N \N \N \N \N \N \N +BS210 N N BS210N Y 2 \N \N \N \N \N \N \N \N \N \N \N +BS220 1 사용 BS2201 Y 1 \N \N \N \N \N \N \N \N \N \N \N +BS220 0 미사용 BS2200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +SA100 100 사용 SA100100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +SA100 200 미사용 SA100200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +BS100 100 개발자 BS100100 Y 1 \N \N 1 \N \N \N \N \N \N \N \N +BS100 200 관리자 BS100200 Y 2 \N \N 1 \N \N \N \N \N \N \N \N +BS100 400 서브 BS100400 Y 4 \N 1 \N \N \N \N \N \N \N \N \N +BS100 500 일반 BS100500 Y 5 \N 1 \N \N \N \N \N \N \N \N \N +SA150 200 개인 SA150200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +SA150 100 기업 SA150100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +SA200 100 상하수도 기초현황 보고서 SA200100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +SA200 200 도시계획 기초현황 보고서 SA200200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +SA200 300 기초현황 DATA 파일 SA200300 Y 3 \N \N \N \N \N \N \N \N \N \N \N +\. + + +-- +-- TOC entry 5027 (class 0 OID 16497) +-- Dependencies: 226 +-- Data for Name: code_master; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.code_master (main_cd, nain_nm, use_yn, use_bc, sort_bc, rmks, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) FROM stdin; +BS110 정렬기준 Y BS200100 BS110100 \N \N \N \N \N \N \N \N \N \N \N +BS200 사용구분\n Y BS200100 BS110100 \N \N \N \N \N \N \N \N \N \N \N +BS210 Y/N 구분 Y BS200200 BS110100 \N \N \N \N \N \N \N \N \N \N \N +BS220 사용여부 Y BS200200 BS110100 \N \N \N \N \N \N \N \N \N \N \N +SA100 회원상태 Y BS200100 BS110100 \N \N \N \N \N \N \N \N \N \N \N +SA200 서비스구분 Y BS200100 BS110100 \N \N \N \N \N \N \N \N \N \N \N +BS100 권한관리 Y BS200100 BS110100 \N 사용자 관리자 \N \N \N \N \N \N \N \N +SA150 법인구분 Y BS200100 BS110200 \N \N \N \N \N \N \N \N \N \N \N +\. + + +-- +-- TOC entry 5036 (class 0 OID 16671) +-- Dependencies: 235 +-- Data for Name: fa_comments; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.fa_comments (fa_id, fa_subject, fa_content, sq_no, use_yn, cid, cdt, mid, mdt) FROM stdin; +3 계정은 어떻게 생성하나요? 회원가입 메뉴를 통해 계정을 생성할 수 있습니다. 2 Y \N \N \N \N +1 KNGIL은 어떤 서비스인가요? KNGIL은 교량 BIM 기반 설계·관리 플랫폼입니다. hahaha 1 Y \N \N \N \N +13 이것은질문 이것은
답글 0 Y m24031 2026-01-29 14:12:20.625501+09 m24031 2026-01-29 14:26:04.837518+09 +14 ㅇㅇㅇㅇ343 ㅇㅁㅇㄹㅇㅇㄹㅇㄹㅇㄹ 0 Y m24031 2026-02-02 11:22:02.315133+09 m24031 2026-02-02 11:22:07.740956+09 +\. + + +-- +-- TOC entry 5022 (class 0 OID 16427) +-- Dependencies: 221 +-- Data for Name: item; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.item (itm_cd, itm_nm, area, itm_amt, use_yn, rmks, cid, cdt, mid, mdt) FROM stdin; +CGD01 골드 20000 10000000.00 Y test m24031 2026-01-14 17:55:27.062562 m24031 2026-01-14 17:55:27.062562 +ZET01 서비스 1 0.00 Y test m24031 2026-01-14 17:56:47.427489 m24031 2026-01-14 17:56:47.427489 +BSV01 실버 10000 5000000.00 Y test m24031 2026-01-14 17:54:53.937187 m24031 2026-01-14 17:58:17.831158 +BBBB TEST23 4 2.00 Y m24031 2026-01-23 14:28:12.793976 m24031 2026-01-23 15:23:19.231161 +DDA01 다이아 30000 30000000.00 Y testㅇㅇㅇ m24031 2026-01-14 17:56:22.996253 m24031 2026-01-29 13:50:16.978103 +A0000 회원가입 제공 3000 0.00 Y m24031 2026-01-30 10:01:26.15998 m24031 2026-01-30 10:01:26.15998 +\. + + +-- +-- TOC entry 5026 (class 0 OID 16484) +-- Dependencies: 225 +-- Data for Name: login_history; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.login_history (user_id, sq_no, public_ip, local_ip, login_tm) FROM stdin; +\. + + +-- +-- TOC entry 5021 (class 0 OID 16414) +-- Dependencies: 220 +-- Data for Name: members; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.members (member_id, member_nm, co_bc, bs_no, co_nm, co_tel, tel_no, email, join_dt, end_dt, buy_area, use_area, stat_bc, memo, cid, cdt, mid, mdt) FROM stdin; +b25027 김수현 CB100100 223-33-44445 한맥기술 \N 010-5645-5153 b25027@hanmaceng.co.kr 2026-01-29 \N \N \N SA100100 \N b25027 2026-01-29 11:25:14.184682 b24014 2026-01-29 16:21:12.842875 +B260001 바론 관리자 SA150100 222222222 바론 컨설턴트 \N 010-1111-1111 sdi@sdi.com 2026-01-01 \N 140000 1200 SA100100 \N \N \N SYSTEM 2026-01-21 15:45:40.131991 +tester001 테스터10 SA150100 \N 기업1 \N 010-1111-1111 111@gmail.com 2026-01-20 \N 3000 \N SA100100 \N m24031 2026-01-20 19:50:23.029203 \N \N +ctest004 c테스터33 SA150200 \N 기업3 \N 010-3333-3333 333@gmail.com 2026-01-20 \N 1000 \N SA100100 \N m24031 2026-01-20 19:57:42.806965 \N \N +sdi9429 송대일 CB100100 \N 바론 \N 010-8627-0921 sdi9429@naver.com 2026-01-22 \N 1000 \N SA100100 \N sdi9429 2026-01-22 12:56:43.651431 \N \N +tester003 테스터33 SA150200 222222222 기업3 \N 010-3333-3333 333@gmail.com 2026-01-20 \N 2000 \N SA100100 \N m24031 2026-01-20 19:52:19.322838 m24031 2026-01-23 16:50:12.264163 +tester002 테스터10 SA150100 222222 기업1 \N 010-1111-1111 111@gmail.com 2026-01-20 \N 2000 \N SA100100 \N m24031 2026-01-20 19:51:18.809202 m24031 2026-01-27 10:44:31.258757 +sdisdi 송대일 CB100100 \N 바론11 \N 010-8627-0923 sdi9429@naver.com 2026-01-22 \N 0 \N SA100100 \N sdisdi 2026-01-22 13:12:29.376769 m24031 2026-01-27 10:44:47.776389 +Km24031 권오재 CB100100 \N 한맥기술 \N 010-9114-3944 koj111@naver.com 2026-01-30 \N \N \N SA100100 \N Km24031 2026-01-30 09:02:15.195496 \N \N +am24031 권오재A CB100100 \N 한맥 \N 010-2222-2222 ddd@gmail.com 2026-01-30 \N 3000 \N SA100100 \N am24031 2026-01-30 13:25:57.005379 \N \N +\. + + +-- +-- TOC entry 5029 (class 0 OID 16594) +-- Dependencies: 228 +-- Data for Name: qa_attachments; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.qa_attachments (post_id, ori_name, save_path, file_size, uploaded_at, id) FROM stdin; +7 traffic_image.jpg /kngil/uploads/qa/1769509499_417330b6f49e.jpg 123753 2026-01-27 19:24:59.134487+09 1 +8 traffic_image.jpg /kngil/uploads/qa/1769513369_959a050cb972.jpg 123753 2026-01-27 20:29:29.973998+09 2 +\. + + +-- +-- TOC entry 5035 (class 0 OID 16627) +-- Dependencies: 234 +-- Data for Name: qa_comment_images; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.qa_comment_images (id, comment_id, file_name, file_path, thumb_path, file_size, uploaded_at) FROM stdin; +1 2 og-main-thumb.JPG /kngil/uploads/comment/1769511961_dda23bd3.jpg /kngil/uploads/comment/1769511961_dda23bd3.jpg 112930 2026-01-27 20:06:01.050825+09 +\. + + +-- +-- TOC entry 5033 (class 0 OID 16618) +-- Dependencies: 232 +-- Data for Name: qa_comments; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.qa_comments (comment_id, post_id, commenter, content, cdt_dt, user_nm, mdt_dt) FROM stdin; +2 7 b24014 test 2026-01-27 20:06:01.050825+09 송대일1 \N +\. + + +-- +-- TOC entry 5031 (class 0 OID 16603) +-- Dependencies: 230 +-- Data for Name: qa_posts; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.qa_posts (post_id, tel_no, user_id, user_nm, category, co_nm, dept_nm, title, content, attachment, stat_bc, complete_form, cdt_dt, mid_dt, is_secret, is_read_admin) FROM stdin; +7 \N b24014 송대일1 오류문의 \N \N 테스트

1234

\N review 0 2026-01-27 19:24:59.131617+09 2026-01-27 20:14:21.993651+09 0 Y +8 \N b24014 송대일1 오류문의 \N \N 첨부파일 업로드

123

\N deep 0 2026-01-27 20:29:29.970856+09 2026-01-27 20:32:21.107049+09 0 Y +10 010-8627-0921 b24014 송대일1 오류문의 바론 컨설턴트 총괄기획실 test

1qasdweqwe

\N review 0 2026-01-28 10:43:24.655249+09 2026-01-28 11:00:56.361487+09 Y Y +1 오류문의 1111

3333

\N wait 0 2026-01-27 14:09:56.639903+09 \N Y 0 +2 \N b24014 송대일1 오류문의 \N \N 1234

3333

\N wait 0 2026-01-27 15:56:13.097696+09 \N 0 N +5 \N b24014 송대일1 오류문의 \N \N 123

456

\N wait 0 2026-01-27 16:40:55.238147+09 \N 0 N +9 \N b24014 송대일1 오류문의 \N \N 최종테스트

11111333

\N wait 0 2026-01-28 10:09:07.943555+09 \N 0 Y +6 \N b24014 송대일1 오류문의 \N \N 첨부파일 업로드

123

\N wait 0 2026-01-27 19:17:08.118218+09 \N 0 Y +4 \N b24014 송대일1 개선문의 \N \N tests

tests

\N wait 0 2026-01-27 16:00:43.415892+09 \N 0 Y +12 010-8627-0921 b24014 송대일1 일반문의 바론 컨설턴트 총괄기획실 테스트 작성글

ㅅㅅㅅㅅㅅㅅㅅ

\N wait 0 2026-01-30 16:10:49.845849+09 2026-02-02 09:32:40.063076+09 N Y +11 010-8627-0921 b24014 송대일1 오류문의 바론 컨설턴트 총괄기획실 1111

3123123

\N wait 0 2026-01-30 16:10:30.821517+09 2026-02-02 09:32:54.632593+09 N Y +13 010-8627-0921 b24014 송대일1 오류문의 바론 컨설턴트 총괄기획실 테스트 중입니다

3333444

\N wait 0 2026-02-02 09:33:22.636799+09 \N N Y +14 010-8627-0921 b24014 송대일1 공지사항 바론 컨설턴트 총괄기획실 공지 테스트

1234

\N wait 0 2026-02-02 09:40:50.464071+09 \N N Y +\. + + +-- +-- TOC entry 5025 (class 0 OID 16471) +-- Dependencies: 224 +-- Data for Name: use_history; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.use_history (user_id, sq_no, use_dt, use_area, ser_bc, cid, cdt, mid, mdt) FROM stdin; +m24031 1 2026-01-18 100 SA200100 \N \N \N \N +test001 1 2026-01-18 200 SA200100 \N \N \N \N +test001 2 2026-01-19 200 SA200100 \N \N \N \N +m24031 2 2026-01-19 200 SA200300 \N \N \N \N +m24031 3 2026-01-19 300 SA200200 \N \N \N \N +m24031 4 2026-01-19 100 SA200300 \N \N \N \N +m24031 5 2026-01-19 20000 SA200300 \N \N \N \N +m24031 6 2026-01-19 1000 SA200200 \N \N \N \N +m24031 7 2025-01-19 20000 SA200100 \N \N \N \N +\. + + +-- +-- TOC entry 5023 (class 0 OID 16433) +-- Dependencies: 222 +-- Data for Name: users; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.users (member_id, user_id, user_pw, user_nm, dept_nm, posit_nm, tel_no, email, auth_bc, use_yn, rmks, cid, cdt, mid, mdt, oidc_sub) FROM stdin; +B260001 test003 test003 테스터3 기술개발센터 수석연구원 010-1111-3333 test@test.com BS100400 N test 입니다. m24031 2026-01-14 18:39:43.030024 m24031 2026-01-14 18:42:22.488198 \N +sdisdi song12124 test1234!!@ 테스터4 erp 1012346770 test@test.co.kr BS100500 Y song12124 2026-01-30 11:12:14.172743 song12124 2026-01-30 11:12:14.172743 \N +tester002 tester002 tester002 류호성 전산실 \N 010-3371-5649 111@gmail.com BS100100 Y \N m24031 2026-01-20 19:51:18.809202 m24031 2026-01-27 10:44:31.258757 \N +sdisdi song12125 test1234!!@ 테스터5 erp 1012346771 test@test.co.kr BS100500 Y song12125 2026-01-30 11:12:14.184522 song12125 2026-01-30 11:12:14.184522 \N +ctest004 ctest004 ctest004 권오재2 전산실 \N 010-9114-3943 333@gmail.com BS100300 N \N m24031 2026-01-20 19:57:42.806965 \N \N \N +B260001 sdi1108 sdi1108 어드민 ERP팀ㅁ 010-1111-1112 test@test1.com BS100400 Y \N 2026-01-15 20:08:48.731648 m24031 2026-01-29 13:29:52.388359 \N +sdisdi song12126 test1234!!@ 테스터6 erp 1012346772 test@test.co.kr BS100500 Y song12126 2026-01-30 11:18:04.916446 song12126 2026-01-30 11:18:04.916446 \N +sdisdi song12127 test1234!!@ 테스터7 erp 1012346773 test@test.co.kr BS100500 Y song12127 2026-01-30 11:18:04.921195 song12127 2026-01-30 11:18:04.921195 \N +sdisdi song12128 test1234!!@ 테스터8 erp 1012346774 test@test.co.kr BS100500 Y song12128 2026-01-30 12:53:06.276828 song12128 2026-01-30 12:53:06.276828 \N +sdisdi song12129 test1234!!@ 테스터9 erp 1012346775 test@test.co.kr BS100500 Y song12129 2026-01-30 12:53:06.284774 song12129 2026-01-30 12:53:06.284774 \N +sdisdi song12130 test1234!!@ 테스터10 erp 1012346776 test@test.co.kr BS100500 Y song12130 2026-01-30 13:01:11.566978 song12130 2026-01-30 13:01:11.566978 \N +sdisdi song12131 test1234!!@ 테스터11 erp 1012346777 test@test.co.kr BS100500 Y song12131 2026-01-30 13:01:11.57227 song12131 2026-01-30 13:01:11.57227 \N +am24031 am24031 !rnjsdhwo729 권오재A ERP 기획 \N 010-2222-2222 ddd@gmail.com BS100300 Y \N am24031 2026-01-30 13:25:57.005379 \N \N \N +b25027 b25027 a1357125!@23 김수현 디자인기획팀 \N 010-5645-5153 b25027@hanmaceng.co.kr BS100300 Y \N b25027 2026-01-29 11:25:14.184682 b24014 2026-01-29 16:21:12.842875 \N +B260001 test005 b23008 염승호 총괄기획실 수석 연구원 010-8835-0501 df BS100100 Y m24031 2026-01-29 13:30:30.456963 m24031 2026-01-29 13:30:30.456963 \N +B260001 test002 test002 테스터2 기술개발센터 010-1111-2222 test@test.com BS100400 Y m24031 2026-01-14 18:39:03.432801 m24031 2026-01-29 20:31:27.681975 \N +tester003 btest001 btest001 테스터33 도로부 BS100500 Y test m24031 2026-01-20 19:56:32.467743 m24031 2026-01-20 19:56:32.467743 \N +tester003 btest002 btest002 b테스터22 도로부 BS100500 Y test m24031 2026-01-20 19:56:48.750462 m24031 2026-01-20 19:56:48.750462 \N +B260001 m24031 m24031 권오재 총괄기획실 선임 연구원 010-9114-3943 m24031@hanmaceng.co.kr BS100100 Y \N \N \N \N \N \N +Km24031 Km24031 !rnjsdhwo729 권오재 ERP기획팀 \N 010-9114-3944 koj111@naver.com BS100300 Y \N Km24031 2026-01-30 09:02:15.195496 \N \N \N +sdi9429 sdi9429 test1111 송대일 총괄기획실 \N 010-8627-0922 sdi9429@naver.com BS100300 Y \N sdi9429 2026-01-22 12:56:43.651431 \N \N \N +B260001 b24014 test001 송대일1 총괄기획실 010-8627-0921 b24014@hanmaceng.co.kr\n BS100100 Y 123 \N \N SYSTEM 2026-01-19 16:44:25.814475 \N +tester003 tester003 test001 테스터33 전산실 \N 010-3333-3333 333@gmail.com BS100300 Y \N m24031 2026-01-20 19:52:19.322838 m24031 2026-01-23 16:50:12.264163 \N +B260001 test001 test001 회사관리자 총괄기획실1 010-3189-1514 sdi1111@sdi.com BS100300 Y 권한위임[sdi1108] \N \N m24031 2026-01-22 17:11:58.275998 \N +B260001 test0005 test0005 테스트55 총괄기획실1 010-4158-5840 b24000@hanmaceng.co.kr BS100400 Y 비고 입력 테스트 SYSTEM 2026-01-19 19:39:10.476205 SYSTEM 2026-01-19 19:39:10.476205 \N +tester001 tester001 test001 테스터10 전산실 \N 010-9523-0055 111@gmail.com BS100300 Y \N m24031 2026-01-20 19:50:23.029203 \N \N \N +sdisdi sdisdi song1108! 송대일 총괄기획실 \N 010-8627-0923 sdi9429@naver.com BS100300 Y \N sdisdi 2026-01-22 13:12:29.376769 m24031 2026-01-27 10:44:47.776389 \N +sdisdi song1212 test1234!!@ 테스터 erp 01012345689 test@test.co.kr BS100500 N song1212 2026-01-30 09:58:45.274688 sdisdi 2026-01-30 10:02:41.963234 \N +sdisdi song12121 test1234!!@ 테스터1 erp 1012346767 test@test.co.kr BS100400 N song12121 2026-01-30 10:05:59.726345 song12121 2026-01-30 10:05:59.726345 \N +sdisdi song12122 test1234!!@ 테스터2 erp 1012346768 test@test.co.kr BS100500 Y song12122 2026-01-30 10:46:04.719717 song12122 2026-01-30 10:46:04.719717 \N +sdisdi song12123 test1234!!@ 테스터3 erp 1012346769 test@test.co.kr BS100500 Y song12123 2026-01-30 10:46:04.729306 song12123 2026-01-30 10:46:04.729306 \N +B260001 sdi111 000000 test123 ert33 010-8623-6564 test@test.com BS100500 Y b24014 2026-01-30 13:02:45.327993 b24014 2026-01-30 14:08:03.710466 \N +B260001 sdisssss 1234asd!! test1232 erpp 010-8686-2323 test@test.com BS100500 Y b24014 2026-01-30 13:40:50.09753 b24014 2026-01-30 14:08:14.578146 \N +B260001 test1010 test1010 test12 erp 010-8888-2222 test@test1.co.kr BS100500 Y b24014 2026-01-30 13:56:43.807999 b24014 2026-01-30 14:18:12.484107 \N +am24031 kwon001 kwon001 권1 dd 010-1111-2111 kwon001 BS100500 Y kwon001 2026-01-30 16:05:35.819863 kwon001 2026-01-30 16:05:35.819863 \N +am24031 kwon002 kwon002 권2 aa 010-1111-2112 kwon002 BS100500 Y kwon002 2026-01-30 16:05:35.846448 kwon002 2026-01-30 16:05:35.846448 \N +\. + + +-- +-- TOC entry 5058 (class 0 OID 0) +-- Dependencies: 236 +-- Name: fa_comments_fa_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.fa_comments_fa_id_seq', 14, true); + + +-- +-- TOC entry 5059 (class 0 OID 0) +-- Dependencies: 237 +-- Name: qa_attachments_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.qa_attachments_id_seq', 2, true); + + +-- +-- TOC entry 5060 (class 0 OID 0) +-- Dependencies: 233 +-- Name: qa_comment_images_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.qa_comment_images_id_seq', 1, true); + + +-- +-- TOC entry 5061 (class 0 OID 0) +-- Dependencies: 231 +-- Name: qa_comments_comment_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.qa_comments_comment_id_seq', 2, true); + + +-- +-- TOC entry 5062 (class 0 OID 0) +-- Dependencies: 229 +-- Name: qa_posts_post_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.qa_posts_post_id_seq', 14, true); + + +-- +-- TOC entry 4867 (class 2606 OID 16678) +-- Name: fa_comments fa_comments_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.fa_comments + ADD CONSTRAINT fa_comments_pkey PRIMARY KEY (fa_id); + + +-- +-- TOC entry 4855 (class 2606 OID 16643) +-- Name: login_history login_history_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.login_history + ADD CONSTRAINT login_history_pkey PRIMARY KEY (user_id, sq_no); + + +-- +-- TOC entry 4851 (class 2606 OID 16460) +-- Name: buy_item pk_buy_item; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.buy_item + ADD CONSTRAINT pk_buy_item PRIMARY KEY (member_id, sq_no); + + +-- +-- TOC entry 4859 (class 2606 OID 16532) +-- Name: code_detail pk_code_detail; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.code_detail + ADD CONSTRAINT pk_code_detail PRIMARY KEY (main_cd, sub_cd); + + +-- +-- TOC entry 4857 (class 2606 OID 16504) +-- Name: code_master pk_code_master; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.code_master + ADD CONSTRAINT pk_code_master PRIMARY KEY (main_cd); + + +-- +-- TOC entry 4845 (class 2606 OID 16555) +-- Name: item pk_item; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.item + ADD CONSTRAINT pk_item PRIMARY KEY (itm_cd); + + +-- +-- TOC entry 4843 (class 2606 OID 16426) +-- Name: members pk_members; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.members + ADD CONSTRAINT pk_members PRIMARY KEY (member_id); + + +-- +-- TOC entry 4861 (class 2606 OID 16690) +-- Name: qa_attachments qa_attachments_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.qa_attachments + ADD CONSTRAINT qa_attachments_pkey PRIMARY KEY (id); + + +-- +-- TOC entry 4865 (class 2606 OID 16625) +-- Name: qa_comments qa_comments_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.qa_comments + ADD CONSTRAINT qa_comments_pkey PRIMARY KEY (comment_id); + + +-- +-- TOC entry 4863 (class 2606 OID 16616) +-- Name: qa_posts qa_posts_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.qa_posts + ADD CONSTRAINT qa_posts_pkey PRIMARY KEY (post_id); + + +-- +-- TOC entry 4853 (class 2606 OID 16641) +-- Name: use_history use_history_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.use_history + ADD CONSTRAINT use_history_pkey PRIMARY KEY (user_id, sq_no); + + +-- +-- TOC entry 4847 (class 2606 OID 16721) +-- Name: users users_oidc_sub_key; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.users + ADD CONSTRAINT users_oidc_sub_key UNIQUE (oidc_sub); + + +-- +-- TOC entry 4849 (class 2606 OID 16645) +-- Name: users users_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.users + ADD CONSTRAINT users_pkey PRIMARY KEY (user_id); + + +-- +-- TOC entry 4873 (class 2620 OID 16705) +-- Name: buy_item trg_buy_item_changed; Type: TRIGGER; Schema: kngil; Owner: postgres +-- + +CREATE TRIGGER trg_buy_item_changed AFTER INSERT OR DELETE OR UPDATE ON kngil.buy_item FOR EACH ROW EXECUTE FUNCTION kngil.fn_update_buy_area(); + + +-- +-- TOC entry 4872 (class 2606 OID 16533) +-- Name: code_detail fk_code_master_to_code_detail; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.code_detail + ADD CONSTRAINT fk_code_master_to_code_detail FOREIGN KEY (main_cd) REFERENCES kngil.code_master(main_cd) ON DELETE CASCADE; + + +-- +-- TOC entry 4868 (class 2606 OID 16656) +-- Name: users fk_member_to_user; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.users + ADD CONSTRAINT fk_member_to_user FOREIGN KEY (member_id) REFERENCES kngil.members(member_id) NOT VALID; + + +-- +-- TOC entry 4869 (class 2606 OID 16461) +-- Name: buy_item fk_members_to_buy_item; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.buy_item + ADD CONSTRAINT fk_members_to_buy_item FOREIGN KEY (member_id) REFERENCES kngil.members(member_id); + + +-- +-- TOC entry 4871 (class 2606 OID 16651) +-- Name: login_history fk_user_to_login_history; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.login_history + ADD CONSTRAINT fk_user_to_login_history FOREIGN KEY (user_id) REFERENCES kngil.users(user_id) NOT VALID; + + +-- +-- TOC entry 4870 (class 2606 OID 16646) +-- Name: use_history fk_user_to_use_history; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.use_history + ADD CONSTRAINT fk_user_to_use_history FOREIGN KEY (user_id) REFERENCES kngil.users(user_id) NOT VALID; + + +-- Completed on 2026-02-02 14:06:04 + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict osPaC8Gqjay0KBMwX4hwgDvmjwF5rTGmBMzQBdxAne3SBCLMuCNQu2Xg15dPVeb + From bf86b1d1e7ed1625d817c4ecadb7a5df4c726a46 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Mon, 2 Feb 2026 18:52:20 +0900 Subject: [PATCH 02/21] Add docker entrypoint and env loader --- .env.sample | 15 + .gitignore | 2 + Dockerfile | 14 + README.md | 47 + docker-compose.yml | 35 + docker/apache.conf | 6 + docker/entrypoint.sh | 8 + docker/initdb/01_kngil_DB.sql | 2381 +++++++++++++++++ kngil/auth/oidc-login.php | 25 +- kngil/bbs/db_conn.php | 15 +- kngil/bbs/env.php | 59 + kngil/bbs/oidc_config.php | 22 +- kngil/bbs/sales_results.php | 8 + kngil/vendor/autoload.php | 22 - kngil/vendor/composer/ClassLoader.php | 579 ---- kngil/vendor/composer/InstalledVersions.php | 396 --- kngil/vendor/composer/LICENSE | 21 - kngil/vendor/composer/autoload_classmap.php | 12 - kngil/vendor/composer/autoload_files.php | 10 - kngil/vendor/composer/autoload_namespaces.php | 9 - kngil/vendor/composer/autoload_psr4.php | 11 - kngil/vendor/composer/autoload_real.php | 50 - kngil/vendor/composer/autoload_static.php | 50 - kngil/vendor/composer/installed.json | 289 -- kngil/vendor/composer/installed.php | 59 - kngil/vendor/composer/platform_check.php | 25 - kngil/vendor/jumbojett/openid-connect-php | 1 - kngil/vendor/paragonie/constant_time_encoding | 1 - kngil/vendor/paragonie/random_compat | 1 - kngil/vendor/phpseclib/phpseclib | 1 - 30 files changed, 2627 insertions(+), 1547 deletions(-) create mode 100644 .env.sample create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 docker/apache.conf create mode 100644 docker/entrypoint.sh create mode 100644 docker/initdb/01_kngil_DB.sql create mode 100644 kngil/bbs/env.php delete mode 100644 kngil/vendor/autoload.php delete mode 100644 kngil/vendor/composer/ClassLoader.php delete mode 100644 kngil/vendor/composer/InstalledVersions.php delete mode 100644 kngil/vendor/composer/LICENSE delete mode 100644 kngil/vendor/composer/autoload_classmap.php delete mode 100644 kngil/vendor/composer/autoload_files.php delete mode 100644 kngil/vendor/composer/autoload_namespaces.php delete mode 100644 kngil/vendor/composer/autoload_psr4.php delete mode 100644 kngil/vendor/composer/autoload_real.php delete mode 100644 kngil/vendor/composer/autoload_static.php delete mode 100644 kngil/vendor/composer/installed.json delete mode 100644 kngil/vendor/composer/installed.php delete mode 100644 kngil/vendor/composer/platform_check.php delete mode 160000 kngil/vendor/jumbojett/openid-connect-php delete mode 160000 kngil/vendor/paragonie/constant_time_encoding delete mode 160000 kngil/vendor/paragonie/random_compat delete mode 160000 kngil/vendor/phpseclib/phpseclib diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..78e16e8 --- /dev/null +++ b/.env.sample @@ -0,0 +1,15 @@ +# DB 접속 정보 +DB_HOST=db +DB_PORT=5432 +DB_NAME=kngil +DB_USER=postgres +DB_PASS=postgres + +# 참고: kngil_DB 덤프의 소유자가 postgres이므로 DB_USER 변경 시 초기 복원 실패 가능 + +# OIDC 설정 +OIDC_ISSUER= +OIDC_CLIENT_ID= +OIDC_CLIENT_SECRET= +OIDC_REDIRECT_URL= +OIDC_SCOPES=openid profile email diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b5fa9f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +/kngil/vendor/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0d57789 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM php:8.2-apache + +RUN apt-get update \ + && apt-get install -y --no-install-recommends git unzip libpq-dev \ + && docker-php-ext-install pdo_pgsql \ + && a2enmod rewrite \ + && rm -rf /var/lib/apt/lists/* + +COPY docker/apache.conf /etc/apache2/conf-available/kngil.conf +RUN a2enconf kngil + +COPY docker/entrypoint.sh /usr/local/bin/kngil-entrypoint.sh +RUN chmod +x /usr/local/bin/kngil-entrypoint.sh +ENTRYPOINT ["/usr/local/bin/kngil-entrypoint.sh"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..e626864 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# KNGIL 로컬 실행 (Docker Compose) + +## 빠른 시작 +```bash +docker compose up --build +``` +- 접속: `http://localhost:8080` + +## 환경변수 +`docker-compose.yml`에서 기본값을 사용하며, 필요 시 `.env`로 덮어쓸 수 있습니다. + +- `DB_HOST` (기본값: `db`) +- `DB_PORT` (기본값: `5432`) +- `DB_NAME` (기본값: `kngil`) +- `DB_USER` (기본값: `postgres`) +- `DB_PASS` (기본값: `postgres`) + +> 참고: `kngil_DB` 덤프가 `postgres` 소유자를 사용하므로, `DB_USER`를 변경하면 초기 복원에 실패할 수 있습니다. + +### OIDC (선택) +로그인에서 OIDC를 사용하려면 아래 환경변수를 설정하세요. + +- `OIDC_ISSUER` +- `OIDC_CLIENT_ID` +- `OIDC_CLIENT_SECRET` +- `OIDC_REDIRECT_URL` +- `OIDC_SCOPES` (예: `openid profile email`) + +## DB 초기화 +- `kngil_DB` 덤프는 **처음 실행 시** 자동으로 로드됩니다. +- 이미 생성된 볼륨이 있으면 재적용되지 않습니다. 다시 초기화하려면 아래를 실행하세요. + +```bash +docker compose down -v +``` + +## 덤프 호환성 +- `docker/initdb/01_kngil_DB.sql`는 PostgreSQL 16에서도 동작하도록 `transaction_timeout` 설정을 제거한 버전입니다. +- 원본 덤프는 `kngil_DB`에 그대로 보관됩니다. + +## 기능 비활성화 +- MySQL 의존 기능은 현재 제외했습니다. +- `kngil/bbs/sales_results.php`는 410 응답으로 비활성 처리되어 있습니다. + +## PostgreSQL 이미지 버전 +- 기본값은 `postgres:18`입니다. +- 이미지 풀 실패 시 `docker-compose.yml`의 태그를 사용 가능한 버전으로 변경하세요. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..60cbc39 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,35 @@ +services: + web: + build: . + ports: + - "8080:80" + volumes: + - ./:/var/www/html + environment: + DB_HOST: ${DB_HOST:-db} + DB_PORT: ${DB_PORT:-5432} + DB_NAME: ${DB_NAME:-kngil} + DB_USER: ${DB_USER:-postgres} + DB_PASS: ${DB_PASS:-postgres} + OIDC_ISSUER: ${OIDC_ISSUER:-} + OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-} + OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-} + OIDC_REDIRECT_URL: ${OIDC_REDIRECT_URL:-} + OIDC_SCOPES: ${OIDC_SCOPES:-} + depends_on: + - db + + db: + image: postgres:16 + # ports: + # - "5432:5432" + environment: + POSTGRES_DB: ${DB_NAME:-kngil} + POSTGRES_USER: ${DB_USER:-postgres} + POSTGRES_PASSWORD: ${DB_PASS:-postgres} + volumes: + - db_data:/var/lib/postgresql/data + - ./docker/initdb/01_kngil_DB.sql:/docker-entrypoint-initdb.d/01_kngil_DB.sql:ro + +volumes: + db_data: diff --git a/docker/apache.conf b/docker/apache.conf new file mode 100644 index 0000000..ede4140 --- /dev/null +++ b/docker/apache.conf @@ -0,0 +1,6 @@ +ServerName localhost + + + AllowOverride All + Require all granted + diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..9b8837f --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -e + +if [ ! -f /var/www/html/kngil/vendor/autoload.php ]; then + php /var/www/html/kngil/composer.phar install --working-dir=/var/www/html/kngil --no-interaction --prefer-dist +fi + +exec apache2-foreground diff --git a/docker/initdb/01_kngil_DB.sql b/docker/initdb/01_kngil_DB.sql new file mode 100644 index 0000000..33ebe4f --- /dev/null +++ b/docker/initdb/01_kngil_DB.sql @@ -0,0 +1,2381 @@ +-- +-- PostgreSQL database dump +-- + +\restrict osPaC8Gqjay0KBMwX4hwgDvmjwF5rTGmBMzQBdxAne3SBCLMuCNQu2Xg15dPVeb + +-- Dumped from database version 18.1 +-- Dumped by pg_dump version 18.0 + +-- Started on 2026-02-02 14:06:03 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- TOC entry 6 (class 2615 OID 16413) +-- Name: kngil; Type: SCHEMA; Schema: -; Owner: postgres +-- + +CREATE SCHEMA kngil; + + +ALTER SCHEMA kngil OWNER TO postgres; + +-- +-- TOC entry 262 (class 1255 OID 16574) +-- Name: fn_base_cd(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_base_cd(p_main_cd character varying) RETURNS TABLE(id character varying, text character varying) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + -- CASE 문을 사용하여 조건에 따라 반환할 컬럼을 선택합니다. + (CASE + WHEN b.use_bc = 'BS200100' THEN a.base_cd::VARCHAR + WHEN b.use_bc = 'BS200200' THEN a.sub_cd::VARCHAR + END) AS id, -- 코드1이면 기초코드 아니면 서브코드 표시. + a.sub_nm::VARCHAR AS text -- 코드명 + FROM kngil.code_detail a --공통코드 상세 + JOIN kngil.code_master b ON a.main_cd = b.main_cd --공통코드 마스터 + WHERE a.main_cd = p_main_cd + AND a.use_yn = 'Y' -- 사용여부 + ORDER BY + (CASE WHEN b.sort_bc = 'BS110100' THEN a.sub_nm END) ASC, + (CASE WHEN b.sort_bc = 'BS110200' THEN a.sort_sq END) ASC; +END; +$$; + + +ALTER FUNCTION kngil.fn_base_cd(p_main_cd character varying) OWNER TO postgres; + +-- +-- TOC entry 251 (class 1255 OID 16571) +-- Name: fn_base_nm(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_base_nm(p_base_cd character varying) RETURNS TABLE(name character varying) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + a.sub_nm::VARCHAR AS name -- 코드명 + FROM kngil.code_detail a --공통코드 상세 + JOIN kngil.code_master b ON a.main_cd = b.main_cd --공통코드 마스터 + WHERE a.base_cd = p_base_cd; + +END; +$$; + + +ALTER FUNCTION kngil.fn_base_nm(p_base_cd character varying) OWNER TO postgres; + +-- +-- TOC entry 276 (class 1255 OID 16703) +-- Name: fn_update_buy_area(); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_update_buy_area() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + v_member_id character varying; +BEGIN + -- 1. 이벤트 종류(INSERT, UPDATE, DELETE)에 따라 member_id 추출 + IF (TG_OP = 'DELETE') THEN + v_member_id := OLD.member_id; + ELSE + v_member_id := NEW.member_id; + END IF; + + -- 2. 해당 회원의 당해 연도(end_dt 기준) 합계 적용면적 업데이트 + -- ok_yn = 'Y'인 데이터만 합산 + UPDATE kngil.members + SET buy_area = ( + SELECT COALESCE(SUM(sum_area), 0) + FROM kngil.buy_item + WHERE member_id = v_member_id + AND ok_yn = 'Y' + AND EXTRACT(YEAR FROM end_dt) = EXTRACT(YEAR FROM CURRENT_DATE) + ) + WHERE member_id = v_member_id; + + -- UPDATE 이벤트에서 member_id가 변경된 경우, 이전 member_id의 데이터도 갱신 필요 + IF (TG_OP = 'UPDATE' AND OLD.member_id <> NEW.member_id) THEN + UPDATE kngil.members + SET buy_area = ( + SELECT COALESCE(SUM(sum_area), 0) + FROM kngil.buy_item + WHERE member_id = OLD.member_id + AND ok_yn = 'Y' + AND EXTRACT(YEAR FROM end_dt) = EXTRACT(YEAR FROM CURRENT_DATE) + ) + WHERE member_id = OLD.member_id; + END IF; + + RETURN NULL; -- AFTER 트리거이므로 결과 반환은 필요 없음 +END; +$$; + + +ALTER FUNCTION kngil.fn_update_buy_area() OWNER TO postgres; + +-- +-- TOC entry 250 (class 1255 OID 16541) +-- Name: fn_user_auth(); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_user_auth() RETURNS TABLE(code character varying, name character varying) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + -- 마스터의 use_bc 설정에 따라 base_cd 또는 sub_cd 반환 + (CASE + WHEN b.use_bc = 'BS200100' THEN a.base_cd::VARCHAR + WHEN b.use_bc = 'BS200200' THEN a.sub_cd::VARCHAR + END) AS code, + a.sub_nm::VARCHAR AS name + FROM kngil.code_detail a + JOIN kngil.code_master b ON a.main_cd = b.main_cd + WHERE a.main_cd = 'BS100' -- 권한관리코드 고정 + AND a.use_yn = 'Y' -- 사용여부 'Y' + AND a.m1 = '1' -- 특정 필터 조건 + ORDER BY + (CASE WHEN b.sort_bc = 'BS110100' THEN a.sub_nm END) ASC, + (CASE WHEN b.sort_bc = 'BS110200' THEN a.sort_sq END) ASC; +END; +$$; + + +ALTER FUNCTION kngil.fn_user_auth() OWNER TO postgres; + +-- +-- TOC entry 255 (class 1255 OID 16583) +-- Name: fn_user_id_check(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_user_id_check(p_user_id character varying) RETURNS character varying + LANGUAGE plpgsql + AS $$ +DECLARE + v_user_exists INTEGER; +/* +유저 중복 체크 함수 회원가입 시 중복체크 +*/ +BEGIN + -- [1] 중복 아이디 체크 (대소문자 무시) + SELECT COUNT(*) INTO v_user_exists + FROM kngil.users + WHERE LOWER(user_id) = LOWER(p_user_id); -- 양쪽 모두 소문자로 변환하여 비교 + + IF v_user_exists > 0 THEN + RETURN 'ERROR: 이미 존재하는 아이디입니다.'; + ELSE + RETURN 'SUCCESS: 사용 가능한 아이디입니다.'; + END IF; +END; +$$; + + +ALTER FUNCTION kngil.fn_user_id_check(p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 257 (class 1255 OID 16550) +-- Name: sp_buy_item_d(character varying, integer); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_d(p_member_id character varying, p_sq_no integer) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE +/* + 설 명 : buy_item 테이블 삭제 프로시져 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ + v_ok_yn CHAR(1); +BEGIN + -- 1. 해당 구매 건의 승인 여부 확인 + SELECT ok_yn INTO v_ok_yn + FROM kngil.buy_item + WHERE member_id = p_member_id AND sq_no = p_sq_no; + + IF NOT FOUND THEN + RETURN 'ERROR: 삭제할 구매 정보를 찾을 수 없습니다.'; + END IF; + + -- 2. 승인여부(ok_yn)가 'Y'이면 삭제 불가 + IF v_ok_yn = 'Y' THEN + RETURN 'ERROR: 승인 완료(Y)된 구매 내역은 삭제할 수 없습니다.'; + END IF; + + -- 3. 삭제 실행 + DELETE FROM kngil.buy_item + WHERE member_id = p_member_id AND sq_no = p_sq_no; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_d(p_member_id character varying, p_sq_no integer) OWNER TO postgres; + +-- +-- TOC entry 273 (class 1255 OID 16563) +-- Name: sp_buy_item_history_r(character varying, character varying, date, date); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_history_r(p_member_id character varying DEFAULT ''::character varying, p_member_nm character varying DEFAULT NULL::character varying, p_fbuy_dt date DEFAULT NULL::date, p_tbuy_dt date DEFAULT NULL::date) RETURNS TABLE(member_id character varying, sq_no integer, user_nm character varying, co_nm character varying, bs_no character varying, buy_dt date, itm_cd character varying, itm_nm character varying, area numeric, itm_qty numeric, itm_area numeric, add_area numeric, sum_area numeric, itm_amt numeric, dis_rt numeric, buy_amt numeric, vat_amt numeric, sum_amt numeric, end_dt date, ok_yn character, rmks character varying) + LANGUAGE plpgsql + AS $$ +/* + 설 명 : 상품등록화면에서 buy_item 테이블 내용 조회 member_id , buy_dt 변수 필수 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +BEGIN + RETURN QUERY + SELECT + a.member_id, -- 회원ID + a.sq_no, -- 순번 + b.member_nm::character varying as user_nm, -- 구매자 + b.co_nm, -- 회사명 + b.bs_no, -- 사업자번호 + a.buy_dt, -- 구매일자 + a.itm_cd::character varying, -- 상품코드 + c.itm_nm::character varying as itm_nm, -- 상품명 + c.area::NUMERIC as area, -- 상품면적 + a.itm_qty::NUMERIC, -- 수량 + a.itm_area::NUMERIC, -- 면적 + a.add_area::NUMERIC, -- 추가면적 + a.sum_area::NUMERIC, -- 합계면적 + a.itm_amt::NUMERIC, -- 단가 + a.dis_rt::NUMERIC, -- 할인율 + a.buy_amt::NUMERIC, -- 공급금액 + a.vat_amt::NUMERIC, -- 부가세 + a.sum_amt::NUMERIC, -- 합계금액 + a.end_dt, -- 만료일 + a.ok_yn, -- 승인여부 + a.rmks -- 비고 + FROM kngil.buy_item a -- 구매정보 + left join kngil.members b on a.member_id = b.member_id -- 회원정보 + left join kngil.item c on a.itm_cd = c.itm_cd -- 상품정보 + WHERE 1=1 + -- 회원ID 검색 + AND (a.member_id = p_member_id OR p_member_id = '') + -- 구매일자 기간 검색 + AND (a.buy_dt >= p_fbuy_dt OR p_fbuy_dt IS NULL) + AND (a.buy_dt <= p_tbuy_dt OR p_tbuy_dt IS NULL) + AND ( + p_member_nm IS NULL + OR p_member_nm = '' + OR b.co_nm ILIKE '%' || p_member_nm || '%' + OR b.member_nm ILIKE '%' || p_member_nm || '%' +) + ORDER BY a.member_id,a.sq_no DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_history_r(p_member_id character varying, p_member_nm character varying, p_fbuy_dt date, p_tbuy_dt date) OWNER TO postgres; + +-- +-- TOC entry 275 (class 1255 OID 16575) +-- Name: sp_buy_item_i(character varying, date, character, numeric, numeric, numeric, numeric, numeric, numeric, numeric, numeric, numeric, date, character, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_i(p_member_id character varying, p_buy_dt date, p_itm_cd character, p_itm_qty numeric, p_itm_area numeric, p_add_area numeric, p_sum_area numeric, p_itm_amt numeric, p_dis_rt numeric, p_buy_amt numeric, p_vat_amt numeric, p_sum_amt numeric, p_end_dt date, p_ok_yn character, p_rmks character varying, p_cid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE +/* + 설 명 : 서비스 구매정보 isnert 프로시져 sq_no 계산하여 적용. + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 + p_itm_cd character, -- 상품코드 + p_itm_qty numeric, -- 수량 + p_itm_area numeric, -- 적용면적 + p_add_area numeric, -- 추가면적 + p_sum_area numeric, -- 합계면적 + p_itm_amt numeric, -- 상품단가 + p_dis_rt numeric, -- 할인율 + p_buy_amt numeric, -- 공금금액 + p_vat_amt numeric, -- 부가세 + p_sum_amt numeric, -- 합계금액 + p_end_dt date, -- 만료일자 + p_ok_yn character, -- 승인여부 + p_rmks character varying,-- 비고 +*/ + v_next_sq_no INTEGER; +BEGIN + -- 해당 회원의 다음 순번(sq_no) 계산 + SELECT COALESCE(MAX(sq_no), 0) + 1 INTO v_next_sq_no + FROM kngil.buy_item + WHERE member_id = p_member_id; + + -- 데이터 삽입 + INSERT INTO kngil.buy_item ( + member_id, sq_no, buy_dt, itm_cd, itm_qty, itm_area, + add_area, sum_area, itm_amt, dis_rt, buy_amt, + vat_amt, sum_amt, end_dt, ok_yn, rmks, + cid, cdt, mid, mdt + ) VALUES ( + p_member_id, v_next_sq_no, p_buy_dt, p_itm_cd, p_itm_qty, p_itm_area, + p_add_area, p_sum_area, p_itm_amt, p_dis_rt, p_buy_amt, + p_vat_amt, p_sum_amt, p_end_dt, p_ok_yn, p_rmks, + p_cid, CURRENT_TIMESTAMP, p_cid, CURRENT_TIMESTAMP + ); + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_i(p_member_id character varying, p_buy_dt date, p_itm_cd character, p_itm_qty numeric, p_itm_area numeric, p_add_area numeric, p_sum_area numeric, p_itm_amt numeric, p_dis_rt numeric, p_buy_amt numeric, p_vat_amt numeric, p_sum_amt numeric, p_end_dt date, p_ok_yn character, p_rmks character varying, p_cid character varying) OWNER TO postgres; + +-- +-- TOC entry 274 (class 1255 OID 16702) +-- Name: sp_buy_item_r(character varying, date); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_r(p_member_id character varying DEFAULT ''::character varying, p_buy_dt date DEFAULT NULL::date) RETURNS TABLE(member_id character varying, sq_no integer, buy_dt date, itm_cd character, itm_nm character varying, itm_qty numeric, itm_area numeric, add_area numeric, sum_area numeric, itm_amt numeric, dis_rt numeric, buy_amt numeric, vat_amt numeric, sum_amt numeric, end_dt date, ok_yn character, rmks character varying) + LANGUAGE plpgsql + AS $$ +/* + 설 명 : 상품등록화면에서 buy_item 테이블 내용 조회 member_id , buy_dt 변수 필수 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +BEGIN + RETURN QUERY + SELECT + a.member_id, -- 회원ID + a.sq_no, -- 순번 + a.buy_dt, -- 구매일자 + a.itm_cd, -- 상품코드 + b.itm_nm::character varying, -- 상품명 + a.itm_qty::NUMERIC, -- 수량 + a.itm_area::NUMERIC, -- 면적 + a.add_area::NUMERIC, -- 추가면적 + a.sum_area::NUMERIC, -- 합계면적 + a.itm_amt::NUMERIC, -- 단가 + a.dis_rt::NUMERIC, -- 할인율 + a.buy_amt::NUMERIC, -- 공급금액 + a.vat_amt::NUMERIC, -- 부가세 + a.sum_amt::NUMERIC, -- 합계금액 + a.end_dt, -- 만료일 + a.ok_yn, -- 승인여부 + a.rmks -- 비고 + FROM kngil.buy_item a + left join kngil.item b on a.itm_cd = b.itm_cd -- 상품코드 + WHERE 1=1 + -- 회원ID 검색 (회원ID 필수) + AND a.member_id = p_member_id + -- 구매일자 검색(필수) + AND a.buy_dt = p_buy_dt + ORDER BY a.sq_no DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_r(p_member_id character varying, p_buy_dt date) OWNER TO postgres; + +-- +-- TOC entry 256 (class 1255 OID 16549) +-- Name: sp_buy_item_u(character varying, integer, date, character, numeric, numeric, numeric, numeric, numeric, numeric, numeric, numeric, numeric, date, character, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_u(p_member_id character varying, p_sq_no integer, p_buy_dt date, p_itm_cd character, p_itm_qty numeric, p_itm_area numeric, p_add_area numeric, p_sum_area numeric, p_itm_amt numeric, p_dis_rt numeric, p_buy_amt numeric, p_vat_amt numeric, p_sum_amt numeric, p_end_dt date, p_ok_yn character, p_rmks character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ + +BEGIN + UPDATE kngil.buy_item + SET + buy_dt = p_buy_dt, -- 구매일자 + itm_cd = p_itm_cd, -- 상품코드 + itm_qty = p_itm_qty, -- 수량 + itm_area = p_itm_area, -- 기본면적 + add_area = p_add_area, -- 추가면적 + sum_area = p_sum_area, -- 합계면적 + itm_amt = p_itm_amt, -- 단가 + dis_rt = p_dis_rt, -- 할인율 + buy_amt = p_buy_amt, -- 공급금액 + vat_amt = p_vat_amt, -- 부가세 + sum_amt = p_sum_amt, -- 구매금액 + end_dt = p_end_dt, -- 만료일자 + ok_yn = p_ok_yn, -- 확정 [Y업데이트 시 members 테이블의 구매면적에 합산 N 업데이트시 차감 트리거 적용] + rmks = p_rmks, -- 비고 + mid = p_mid, -- 수정자 ID 반영 + mdt = CURRENT_TIMESTAMP -- 수정일시 자동 기록 + WHERE member_id = p_member_id + AND sq_no = p_sq_no; + + IF NOT FOUND THEN + RETURN 'ERROR: 수정할 구매 정보를 찾을 수 없습니다.'; + END IF; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_u(p_member_id character varying, p_sq_no integer, p_buy_dt date, p_itm_cd character, p_itm_qty numeric, p_itm_area numeric, p_add_area numeric, p_sum_area numeric, p_itm_amt numeric, p_dis_rt numeric, p_buy_amt numeric, p_vat_amt numeric, p_sum_amt numeric, p_end_dt date, p_ok_yn character, p_rmks character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 238 (class 1255 OID 16685) +-- Name: sp_fa_comments_d(integer); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_fa_comments_d(p_id integer) RETURNS boolean + LANGUAGE plpgsql + AS $$ +BEGIN + -- fa_id를 조건으로 데이터를 삭제합니다. + DELETE FROM kngil.fa_comments + WHERE fa_id = p_id; + + -- 삭제된 행이 있으면 true, 없으면 false를 반환합니다. + RETURN FOUND; +END; +$$; + + +ALTER FUNCTION kngil.sp_fa_comments_d(p_id integer) OWNER TO postgres; + +-- +-- TOC entry 271 (class 1255 OID 16684) +-- Name: sp_fa_comments_i(text, text, integer, character, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_fa_comments_i(p_subject text, p_content text, p_sq_no integer, p_use_yn character, p_cid character varying) RETURNS integer + LANGUAGE plpgsql + AS $$ +DECLARE + v_new_id integer; +BEGIN + -- fa_id는 GENERATED ALWAYS이므로 INSERT 문에서 제외하여 DB가 자동 생성하게 함 + INSERT INTO kngil.fa_comments ( + fa_subject, + fa_content, + sq_no, + use_yn, + cid, + cdt + ) VALUES ( + p_subject, + p_content, + p_sq_no, + p_use_yn, + p_cid, + CURRENT_TIMESTAMP + ) + RETURNING fa_id INTO v_new_id; + + RETURN v_new_id; +END; +$$; + + +ALTER FUNCTION kngil.sp_fa_comments_i(p_subject text, p_content text, p_sq_no integer, p_use_yn character, p_cid character varying) OWNER TO postgres; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- TOC entry 235 (class 1259 OID 16671) +-- Name: fa_comments; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.fa_comments ( + fa_id integer NOT NULL, + fa_subject text, + fa_content text, + sq_no integer, + use_yn character(1), + cid character varying(20), + cdt timestamp with time zone, + mid character varying(20), + mdt timestamp with time zone +); + + +ALTER TABLE kngil.fa_comments OWNER TO postgres; + +-- +-- TOC entry 268 (class 1255 OID 16686) +-- Name: sp_fa_comments_r(); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_fa_comments_r() RETURNS SETOF kngil.fa_comments + LANGUAGE plpgsql + AS $$ +BEGIN + -- 사용 여부(use_yn)가 'Y'인 데이터를 순번(sq_no) 오름차순으로 조회합니다. + RETURN QUERY + SELECT * FROM kngil.fa_comments + WHERE use_yn = 'Y' + ORDER BY sq_no ASC, fa_id DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_fa_comments_r() OWNER TO postgres; + +-- +-- TOC entry 272 (class 1255 OID 16683) +-- Name: sp_fa_comments_u(integer, text, text, integer, character, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_fa_comments_u(p_id integer, p_subject text, p_content text, p_sq_no integer, p_use_yn character, p_mid character varying) RETURNS boolean + LANGUAGE plpgsql + AS $$ +BEGIN + -- 고유 ID(fa_id)를 조건으로 데이터를 업데이트합니다. + UPDATE kngil.fa_comments + SET + fa_subject = p_subject, + fa_content = p_content, + sq_no = p_sq_no, + use_yn = p_use_yn, + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE + fa_id = p_id; + + -- 업데이트된 행이 있는지 확인하여 반환합니다. + RETURN FOUND; +END; +$$; + + +ALTER FUNCTION kngil.sp_fa_comments_u(p_id integer, p_subject text, p_content text, p_sq_no integer, p_use_yn character, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 261 (class 1255 OID 16568) +-- Name: sp_item_d(character); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_item_d(p_itm_cd character) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE + v_buy_count INTEGER; +BEGIN + -- 1. 무결성 체크: 이 상품을 구매한 내역(buy_item)이 있는지 확인 + SELECT COUNT(*) INTO v_buy_count + FROM kngil.buy_item + WHERE itm_cd = p_itm_cd; + + IF v_buy_count > 0 THEN + RETURN 'ERROR: 구매 내역이 존재하는 상품은 삭제할 수 없습니다. (사용여부를 N으로 변경하세요)'; + END IF; + + -- 2. 상품 존재 여부 확인 + IF NOT EXISTS (SELECT 1 FROM kngil.item WHERE itm_cd = p_itm_cd) THEN + RETURN 'ERROR: 삭제할 상품 정보를 찾을 수 없습니다.'; + END IF; + + -- 3. 삭제 실행 + DELETE FROM kngil.item + WHERE itm_cd = p_itm_cd; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + -- 오류 발생 시 자동 롤백됩니다. + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_item_d(p_itm_cd character) OWNER TO postgres; + +-- +-- TOC entry 263 (class 1255 OID 16576) +-- Name: sp_item_i(character, character varying, numeric, numeric, character, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_item_i(p_itm_cd character, p_itm_nm character varying, p_area numeric, p_itm_amt numeric, p_use_yn character, p_rmks character varying, p_cid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE + v_count INTEGER; +BEGIN + -- 1. 상품코드(itm_cd) 중복 체크 + SELECT COUNT(*) INTO v_count FROM kngil.item WHERE itm_cd = p_itm_cd; + + IF v_count > 0 THEN + RETURN 'ERROR: 이미 존재하는 상품코드입니다.'; + END IF; + + -- 2. 데이터 삽입 + INSERT INTO kngil.item ( + itm_cd, itm_nm, area, itm_amt, + use_yn, rmks, + cid, cdt, mid, mdt + ) VALUES ( + p_itm_cd, p_itm_nm, p_area, p_itm_amt, + p_use_yn, p_rmks, + p_cid, CURRENT_TIMESTAMP, p_cid, CURRENT_TIMESTAMP + ); + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + -- 트랜잭션 원자성에 의해 오류 발생 시 자동 롤백됩니다. + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_item_i(p_itm_cd character, p_itm_nm character varying, p_area numeric, p_itm_amt numeric, p_use_yn character, p_rmks character varying, p_cid character varying) OWNER TO postgres; + +-- +-- TOC entry 252 (class 1255 OID 16572) +-- Name: sp_item_r(); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_item_r() RETURNS TABLE(itm_cd character varying, itm_nm character varying, area numeric, itm_amt numeric, use_yn character varying, rmks character varying, cid character varying, cdt timestamp without time zone, mid character varying, mdt timestamp without time zone) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + a.itm_cd::VARCHAR, -- bpchar 에러 방지를 위한 명시적 변환 + a.itm_nm::VARCHAR, + a.area::NUMERIC, -- DECIMAL을 NUMERIC으로 맞춤 + a.itm_amt::NUMERIC, + a.use_yn::VARCHAR, + a.rmks::VARCHAR, + a.cid::VARCHAR, + a.cdt, + a.mid::VARCHAR, + a.mdt + FROM kngil.item a + ORDER BY a.itm_cd ASC; +END; +$$; + + +ALTER FUNCTION kngil.sp_item_r() OWNER TO postgres; + +-- +-- TOC entry 260 (class 1255 OID 16567) +-- Name: sp_item_u(character, character varying, numeric, numeric, character, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_item_u(p_itm_cd character, p_itm_nm character varying, p_area numeric, p_itm_amt numeric, p_use_yn character, p_rmks character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +BEGIN + -- 1. 데이터 업데이트 수행 + UPDATE kngil.item + SET + itm_nm = p_itm_nm, -- 상품명 + area = p_area, -- 면적 + itm_amt = p_itm_amt, -- 상품금액 + use_yn = p_use_yn, -- 사용여부 + rmks = p_rmks, -- 비고 + mid = p_mid, -- 수정자 기록 + mdt = CURRENT_TIMESTAMP -- 수정일시 기록 + WHERE itm_cd = p_itm_cd; + + -- 2. 업데이트된 행이 있는지 확인 + IF NOT FOUND THEN + RETURN 'ERROR: 수정할 상품코드를 찾을 수 없습니다.'; + END IF; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_item_u(p_itm_cd character, p_itm_nm character varying, p_area numeric, p_itm_amt numeric, p_use_yn character, p_rmks character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 266 (class 1255 OID 16580) +-- Name: sp_member_i(character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_member_i(p_co_bc character varying, p_member_id character varying, p_user_pw character varying, p_member_nm character varying, p_email character varying, p_tel_no character varying, p_co_nm character varying, p_dept_nm character varying, p_cid character varying) RETURNS character varying + LANGUAGE plpgsql + AS $$ + -- 값을 반환하기 위해 VARCHAR로 수정 + DECLARE v_user_exists INTEGER; + v_itm_area numeric; -- 무료 제공 면적 + v_end_dt timestamp; +/* +회원가입시 사용되는 함수 +*/ +BEGIN + + v_end_dt := (date_trunc('year', CURRENT_TIMESTAMP) + INTERVAL '1 year - 1 day')::timestamp; + + -- [0] 무료제공면적 + SELECT a.area INTO v_itm_area + FROM kngil.item a + WHERE a.itm_cd = 'A0000'; -- 회원가입제공 면적 + + + -- [1] 중복 아이디 체크 (대소문자 무시) + SELECT COUNT(*) INTO v_user_exists + FROM kngil.users + WHERE LOWER(user_id) = LOWER(p_member_id); -- 양쪽 모두 소문자로 변환하여 비교 + + IF v_user_exists > 0 THEN + RETURN 'ERROR: 이미 존재하는 아이디입니다. (대소문자 포함)'; + END IF; + + -- 회원 테이블 저장 + INSERT INTO kngil.members ( + member_id, member_nm, co_bc, bs_no, co_nm, + co_tel, tel_no, email, join_dt, buy_area, + use_area, stat_bc, memo, cid, cdt + ) VALUES ( + p_member_id -- 회원ID + , p_member_nm -- 성명 + , p_co_bc -- 법인구분 case 구문 사용해야 할 수 있음. 변수가 라디오 버튼 + , null -- 사업자번호 + , p_co_nm -- 회사명 + , null -- 회사번호 + , p_tel_no -- 휴대폰 + , p_email -- 이메일 + , CURRENT_DATE -- 가입일자 + , null -- 구매면적 + , null -- 사용면적 + , 'SA100100' -- 회원상태 : 사용중 + , null -- 메모 + , p_cid -- 생성자 + , CURRENT_TIMESTAMP -- 생성일 + ); + + -- 회원가입 시 사용자정보 권한 "메인" 생성 + INSERT INTO kngil.users ( + member_id, user_id, user_pw, user_nm, dept_nm, + posit_nm, tel_no, email, auth_bc, use_yn, + rmks, cid, cdt + ) VALUES ( + p_member_id -- 회원ID + , p_member_id -- 유저ID : 최초 회원의 ID와 동일 + , p_user_pw -- 로그인 PW + , p_member_nm -- 성명 + , p_dept_nm -- 부서 + , null -- 직위 + , p_tel_no -- 전화번호 + , p_email -- 이메일 + , 'BS100300' -- 권한관리 : 메인 + , 'Y' -- 사용여부 Y 고정 + , null -- 비고 + , p_cid -- 생성자 + , CURRENT_TIMESTAMP -- 생성일 + ); + + + + -- 사용면적 제공 (구매이력 테이블에 초기제공상품 정보 등록 금액 0원) + PERFORM kngil.sp_buy_item_i( + p_member_id::character varying, -- 1. p_member_id + CURRENT_DATE::date, -- 2.가입일 + 'A0000'::character varying, -- 3. p_itm_cd + 1::integer, -- 4. p_itm_qty + v_itm_area::numeric, -- 5. p_itm_area + 0::numeric, -- 6. p_add_area + v_itm_area::numeric, -- 7. p_sum_area + 0::numeric, -- 8. p_itm_amt + 0::numeric, -- 9. p_dis_rt + 0::numeric, -- 10. p_buy_amt + 0::numeric, -- 11. p_vat_amt + 0::numeric, -- 12. p_sum_amt + v_end_dt::date, -- 13. p_end_dt + 'Y'::character varying, -- 14. p_ok_yn + '최초 가입 제공'::character varying, -- 15. p_rmks + p_cid::character varying -- 16. p_cid + ); + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_member_i(p_co_bc character varying, p_member_id character varying, p_user_pw character varying, p_member_nm character varying, p_email character varying, p_tel_no character varying, p_co_nm character varying, p_dept_nm character varying, p_cid character varying) OWNER TO postgres; + +-- +-- TOC entry 259 (class 1255 OID 16565) +-- Name: sp_member_sys_r(character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_member_sys_r(p_co_nm character varying DEFAULT NULL::character varying, p_rem_area character varying DEFAULT NULL::character varying, p_stat_bc character varying DEFAULT NULL::character varying) RETURNS TABLE(member_id character varying, user_id character varying, user_nm character varying, tel_no character varying, email character varying, co_nm character varying, bs_no character varying, join_dt date, user_y numeric, buy_area numeric, use_area numeric, rem_area numeric, stat_bc character varying, memo text) + LANGUAGE plpgsql + AS $$ +/* + 설 명 : 큰길회원 list 관리자 화면 조회, 사용자의 권한구분이 "메인" 인 인원 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +BEGIN + RETURN QUERY + SELECT + a.member_id, -- 회원ID + b.user_id, -- 사용자ID + b.user_nm, -- 사용자이름 + b.tel_no, -- 사용자연락처 + b.email, -- 사용자이메일 + a.co_nm, -- 회사명 + a.bs_no, -- 사업자번호 + a.join_dt, -- 가입일자 + (SELECT COUNT(*) + FROM kngil.users x + left join kngil.code_detail y on x.auth_bc = y.base_cd + WHERE x.member_id = a.member_id + and y.m1 = '1' + and x.use_yn='Y')::numeric as user_y, -- 사용자수 + a.buy_area, -- 구매면적 + a.use_area, -- 사용면적 + a.buy_area - a.use_area as rem_area, -- 잔여면적 + a.stat_bc, -- 회원상태 + a.memo + FROM kngil.members a -- 회원정보 + left join kngil.users b on a.member_id = b.member_id and b.auth_bc = 'BS100300' -- 권한구분이 메인 + WHERE 1=1 + AND (a.co_nm LIKE '%' || p_co_nm || '%' OR p_co_nm = '') + AND (p_rem_area = '' OR p_rem_area::NUMERIC >= (a.buy_area - a.use_area)) + and (p_stat_bc ='' OR a.stat_bc = p_stat_bc) + ORDER BY a.join_dt DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_member_sys_r(p_co_nm character varying, p_rem_area character varying, p_stat_bc character varying) OWNER TO postgres; + +-- +-- TOC entry 264 (class 1255 OID 16578) +-- Name: sp_member_sys_u(character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_member_sys_u(p_member_id character varying, p_tel_no character varying, p_email character varying, p_bs_no character varying, p_co_nm character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +BEGIN + -- 1. 회원정보 업데이트 + UPDATE kngil.members + SET tel_no = p_tel_no, + email = p_email, + bs_no = p_bs_no, + co_nm = p_co_nm, + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE member_id = p_member_id; + + -- 2. 유저정보 업데이트 + UPDATE kngil.users + SET tel_no = p_tel_no, + email = p_email, + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE member_id = p_member_id + AND auth_bc = 'BS100300'; -- b.auth_bc 대신 컬럼명 직접 사용 + + -- 결과 확인 (하나라도 수정되었다면 성공) + IF NOT FOUND THEN + RETURN 'ERROR: 수정할 회원 또는 유저 정보를 찾을 수 없습니다.'; + else + RETURN 'SUCCESS'; + END IF; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_member_sys_u(p_member_id character varying, p_tel_no character varying, p_email character varying, p_bs_no character varying, p_co_nm character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 277 (class 1255 OID 16707) +-- Name: sp_use_history(character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_use_history(p_member_id character varying, p_user_nm character varying DEFAULT NULL::character varying, p_dept_nm character varying DEFAULT NULL::character varying) RETURNS TABLE(member_id character varying, use_dt date, user_id character varying, sq_no integer, user_nm character varying, dept_nm character varying, posit_nm character varying, use_yn character varying, use_area numeric, ser_bc character varying, cdt timestamp without time zone) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + b.member_id::character varying, + a.use_dt, -- DATE 타입 + a.user_id::character varying, + a.sq_no, + b.user_nm::character varying, + b.dept_nm::character varying, + b.posit_nm::character varying, + b.use_yn::character varying, + a.use_area, + kngil.fn_base_nm(a.ser_bc)::character varying as ser_bc, + a.cdt + FROM kngil.use_history a + INNER JOIN kngil.users b ON a.user_id = b.user_id + WHERE b.member_id = p_member_id + -- 이름 검색 (값이 있을 때만 LIKE) + AND (NULLIF(p_user_nm, '') IS NULL OR b.user_nm LIKE '%' || p_user_nm || '%') + -- 부서 검색 (값이 있을 때만 LIKE) + AND (NULLIF(p_dept_nm, '') IS NULL OR b.dept_nm LIKE '%' || p_dept_nm || '%') + ORDER BY a.use_dt DESC, a.sq_no DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_use_history(p_member_id character varying, p_user_nm character varying, p_dept_nm character varying) OWNER TO postgres; + +-- +-- TOC entry 270 (class 1255 OID 16637) +-- Name: sp_user_auth_tran(character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_user_auth_tran(p_tuser_id character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE + v_fuser_id character varying; -- 위임 주는 사람 (From, 기존 메인) + v_member_id character varying; -- 위임 받는 사람의 회원ID +BEGIN + -- 1. 위임 받을 사용자 정보 조회 (한 번에 조회) + SELECT member_id INTO v_member_id + FROM kngil.users + WHERE user_id = p_tuser_id; + + -- 사용자가 없는 경우 처리 + IF v_member_id IS NULL THEN + RETURN 'ERROR: 수정할 사용자를 찾을 수 없습니다.'; + END IF; + + -- 2. 해당 멤버의 기존 메인 사용자(BS100300) 찾기 + SELECT user_id INTO v_fuser_id + FROM kngil.users + WHERE member_id = v_member_id + AND auth_bc = 'BS100300' + AND use_yn = 'Y'; + + -- 메인 사용자가 없는 경우 (위임해 줄 대상이 없음) + IF v_fuser_id IS NULL THEN + RETURN 'ERROR: 위임할 메인 사용자를 찾을 수 없습니다.'; + END IF; + + -- 본인에게 위임하는 경우 방지 (필요 시) + IF v_fuser_id = p_tuser_id THEN + RETURN 'ERROR: 본인에게는 권한을 위임할 수 없습니다.'; + END IF; + + -- 3. 권한 위임받는 사람 업데이트 (서브 -> 메인) + UPDATE kngil.users + SET + auth_bc = 'BS100300', + rmks = '권한위임[' || v_fuser_id || ']', -- + 대신 || 사용 + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE user_id = p_tuser_id; + + -- 4. 권한 위임주는 사람 업데이트 (메인 -> 서브) + UPDATE kngil.users + SET + auth_bc = 'BS100400', + rmks = '권한위임[' || p_tuser_id || ']', -- + 대신 || 사용 + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE user_id = v_fuser_id; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_user_auth_tran(p_tuser_id character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 278 (class 1255 OID 16711) +-- Name: sp_user_end(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_user_end(p_user_id character varying) RETURNS void + LANGUAGE plpgsql + AS $$ +DECLARE + v_member_id character varying; + v_auth_bc character varying; +BEGIN + -- 1. 유저 ID로 소속 멤버 ID와 권한 코드를 한 번에 조회 + SELECT a.member_id, a.auth_bc + INTO v_member_id, v_auth_bc + FROM kngil.users a + WHERE a.user_id = p_user_id; + + -- 2. 권한 코드에 따른 조건 분기 + IF v_auth_bc = 'BS100300' THEN + -- [메인 관리자 권한] 해당 멤버 전체 탈퇴 및 소속 유저 전원 중지 + + -- 멤버 테이블 업데이트 (탈퇴 처리) + UPDATE kngil.members + SET stat_bc = 'SA100900', + mid = p_user_id, + mdt = CURRENT_TIMESTAMP + WHERE member_id = v_member_id; + + -- 해당 멤버에 속한 모든 유저 사용 안함 처리 + UPDATE kngil.users + SET use_yn = 'N', + mid = p_user_id, + mdt = CURRENT_TIMESTAMP + WHERE member_id = v_member_id; + + ELSE + -- [일반 권한] 해당 유저 개인만 사용 안함 처리 + + UPDATE kngil.users + SET use_yn = 'N', + mid = p_user_id, + mdt = CURRENT_TIMESTAMP + WHERE user_id = p_user_id; + + END IF; + +END; +$$; + + +ALTER FUNCTION kngil.sp_user_end(p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 254 (class 1255 OID 16546) +-- Name: sp_users_d(character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_d(p_member_id character varying, p_user_id character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE +/* + 설 명 : users 테이블 삭제 프로시져 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ + v_exists INTEGER; +BEGIN + -- 1. 삭제 대상이 존재하는지 먼저 확인 + SELECT COUNT(*) INTO v_exists + FROM kngil.users + WHERE member_id = p_member_id AND user_id = p_user_id; + + IF v_exists = 0 THEN + RETURN 'ERROR: 삭제할 사용자를 찾을 수 없습니다.'; + END IF; + + -- 2. 데이터 삭제 실행 + DELETE FROM kngil.users + WHERE member_id = p_member_id + AND user_id = p_user_id; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + -- 트랜잭션 원자성에 의해 에러 발생 시 자동 롤백됩니다. + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_d(p_member_id character varying, p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 265 (class 1255 OID 16577) +-- Name: sp_users_i(character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_i(p_member_id character varying, p_user_id character varying, p_user_pw character varying, p_user_nm character varying, p_dept_nm character varying, p_posit_nm character varying, p_tel_no character varying, p_email character varying, p_auth_bc character varying, p_use_yn character varying, p_rmks character varying, p_cid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +/* + 설 명 : users table 데이터 생성 프로시져 "회원 관리자 페이지" 사용자 추가 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +DECLARE + v_count INTEGER; + -- 값을 반환하기 위해 VARCHAR로 수정 + DECLARE v_user_exists INTEGER; +BEGIN + + -- [1] 중복 아이디 체크 (대소문자 무시) + SELECT COUNT(*) INTO v_user_exists + FROM kngil.users + WHERE LOWER(user_id) = LOWER(p_user_id); -- 양쪽 모두 소문자로 변환하여 비교 + + IF v_user_exists > 0 THEN + RETURN 'ERROR: 이미 존재하는 아이디입니다.'; + END IF; + + -- 1. 부모 테이블(members)에 member_id가 존재하는지 먼저 체크 + SELECT COUNT(*) INTO v_count FROM kngil.members WHERE member_id = p_member_id; + + IF v_count = 0 THEN + RETURN 'ERROR: 존재하지 않는 회원ID(member_id)입니다.'; + END IF; + + -- 2. 사용자ID(user_id) 중복 체크 + SELECT COUNT(*) INTO v_count FROM kngil.users WHERE user_id = p_user_id; + + IF v_count > 0 THEN + RETURN 'ERROR: 이미 존재하는 사용자ID입니다.'; + END IF; + + -- 3. 데이터 삽입 + INSERT INTO kngil.users ( + member_id, user_id, user_pw, user_nm, + dept_nm, posit_nm, tel_no, email, + auth_bc, use_yn, rmks, + cid, cdt, mid, mdt + ) VALUES ( + p_member_id, p_user_id, p_user_pw, p_user_nm, + p_dept_nm, p_posit_nm, p_tel_no, p_email, + p_auth_bc, p_use_yn, p_rmks, + p_cid, CURRENT_TIMESTAMP, p_cid, CURRENT_TIMESTAMP + ); + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_i(p_member_id character varying, p_user_id character varying, p_user_pw character varying, p_user_nm character varying, p_dept_nm character varying, p_posit_nm character varying, p_tel_no character varying, p_email character varying, p_auth_bc character varying, p_use_yn character varying, p_rmks character varying, p_cid character varying) OWNER TO postgres; + +-- +-- TOC entry 258 (class 1255 OID 16590) +-- Name: sp_users_my_history(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_my_history(p_user_id character varying) RETURNS TABLE(use_dt date, user_nm character varying, use_area numeric, ser_bc character varying, cdt timestamp without time zone) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + h.use_dt, + u.user_nm, + h.use_area, + kngil.fn_base_nm(h.ser_bc) as ser_bc, + h.cdt + FROM kngil.use_history h + JOIN kngil.users u ON h.user_id = u.user_id + WHERE h.user_id = p_user_id + ORDER BY h.use_dt DESC, h.sq_no DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_my_history(p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 269 (class 1255 OID 16633) +-- Name: sp_users_my_r(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_my_r(p_user_id character varying) RETURNS TABLE(co_bc character varying, user_id character varying, user_pw character varying, user_nm character varying, email character varying, tel_no character varying, co_nm character varying, dept_nm character varying, tot_use numeric, year_use numeric) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + b.co_bc -- 법인구분 + , a.user_id -- ID + , a.user_pw -- 비밀번호 + , a.user_nm -- 성명 + , a.email -- 이메일 + , a.tel_no -- 연락처 + , b.co_nm -- 회사명 + , a.dept_nm -- 부서 + , (select sum(x.use_area) from kngil.use_history x where a.user_id = x.user_id)::numeric as tot_use -- 누적 사용량 + , (select sum(x.use_area) from kngil.use_history x where a.user_id = x.user_id and EXTRACT(YEAR FROM use_dt) = EXTRACT(YEAR FROM CURRENT_DATE))::numeric as year_use -- 당년 사용량 + FROM kngil.users a + left JOIN kngil.members b ON a.member_id = b.member_id + WHERE a.user_id = p_user_id; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_my_r(p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 267 (class 1255 OID 16584) +-- Name: sp_users_my_u(character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_my_u(p_user_id character varying, p_user_pw character varying, p_email character varying, p_tel_no character varying, p_dept_nm character varying, p_mid character varying) RETURNS character varying + LANGUAGE plpgsql + AS $$ + -- 값을 반환하기 위해 VARCHAR로 수정 + DECLARE v_user_exists INTEGER; +/* +내정보 수정 화면에서 사용. +*/ + +BEGIN + + -- 데이터 업데이트 + UPDATE kngil.users + SET + user_pw = COALESCE(NULLIF(p_user_pw, ''), user_pw), -- 비밀번호가 빈값이면 기존 유지 + dept_nm = p_dept_nm, -- 부서명 + tel_no = p_tel_no, -- 전화번호 + email = p_email, -- 메일주소 + mid = p_mid, -- 수정자 ID 기록 + mdt = CURRENT_TIMESTAMP -- 수정일시 기록 + WHERE 1=1 + AND user_id = p_user_id; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_my_u(p_user_id character varying, p_user_pw character varying, p_email character varying, p_tel_no character varying, p_dept_nm character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 279 (class 1255 OID 16718) +-- Name: sp_users_r(character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_r(p_member_id character varying DEFAULT ''::character varying, p_user_nm character varying DEFAULT ''::character varying, p_dept_nm character varying DEFAULT ''::character varying, p_use_yn character varying DEFAULT ''::character varying) RETURNS TABLE(member_id character varying, users_tot bigint, users_y bigint, term text, tuse_area numeric, tbuy_area numeric, remain_area numeric, user_id character varying, user_pw character varying, user_nm character varying, tel_no character varying, email character varying, dept_nm character varying, use_area numeric, cdt date, use_yn character varying, auth_bc character varying, rmks character varying, itm_nm character varying) + LANGUAGE plpgsql + AS $$ +/* + 설 명 : 조회 프로시져 members 기반 users 테이블 조인하여 사용. "회원 관리자 페이지" 조회용 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +DECLARE + v_to_dt character varying; -- 유효일자 + v_remain_days integer; -- 잔여일 저장 변수 + v_itm_nm character varying; -- 가입 상품명 +BEGIN + + SELECT + MAX(end_dt)::text + + INTO + v_to_dt + FROM kngil.buy_item a + WHERE a.member_id = p_member_id; -- 유효일자 종료일자만 표시 END_DT 가 가장 늦은 날 기준. + + + SELECT b.itm_nm + INTO v_itm_nm + FROM kngil.buy_item a + LEFT JOIN kngil.item b ON a.itm_cd = b.itm_cd + WHERE a.member_id = p_member_id + AND EXTRACT(YEAR FROM a.end_dt) = EXTRACT(YEAR FROM CURRENT_DATE) + -- 금액이 큰 순서대로 정렬 + ORDER BY a.itm_amt DESC, a.end_dt DESC + LIMIT 1; + + + v_remain_days := COALESCE(v_to_dt::DATE - CURRENT_DATE, 0); + + + + RETURN QUERY + SELECT + -- [회원정보 기반] + a.member_id, + (SELECT COUNT(*) FROM kngil.users u WHERE u.member_id = a.member_id) AS users_tot, -- 발급사용자 수 + (SELECT COUNT(*) FROM kngil.users u WHERE u.member_id = a.member_id and u.use_yn='Y') AS users_y, -- 실사용자 수 + --v_fr_dt||' ~ '||v_to_dt AS term, -- 유효일자 + ' : '||v_to_dt||' ('||v_remain_days||'일)' AS term, -- 유효일자 종료일자만 표시 END_DT 가 가장 늦은 날 기준. + a.use_area AS tuse_area, -- 총 사용면적 + a.buy_area AS tbuy_area, -- 총 구입면적 + (a.buy_area - a.use_area) AS remain_area, -- 잔여면적 (계산필드) + + -- [사용자정보 기반] + b.user_id, + b.user_pw, + b.user_nm, + b.tel_no, + b.email, + b.dept_nm, + -- 사용자별 개별 사용량 (use_history 테이블 참조) -- 당해년도기준으로 할지 전체 누적으로 할지 고민 필요. + COALESCE((SELECT SUM(uh.use_area) FROM kngil.use_history uh WHERE uh.user_id = b.user_id), 0) AS use_area, + b.cdt::DATE as cdt, + b.use_yn, -- 사용여부 + b.auth_bc, -- 권한구분 + b.rmks, -- 유저비고 + v_itm_nm::character varying as itm_nm + FROM kngil.members a + LEFT JOIN kngil.users b ON a.member_id = b.member_id + WHERE 1=1 + AND a.member_id = p_member_id + AND (b.user_nm LIKE '%' || p_user_nm || '%' OR p_user_nm = '') + AND (b.dept_nm LIKE '%' || p_dept_nm || '%' OR p_dept_nm = '') + AND (p_use_yn = '' OR b.use_yn = p_use_yn) + ORDER BY a.member_id DESC, b.user_id ASC; + +END; +$$; + + +ALTER FUNCTION kngil.sp_users_r(p_member_id character varying, p_user_nm character varying, p_dept_nm character varying, p_use_yn character varying) OWNER TO postgres; + +-- +-- TOC entry 253 (class 1255 OID 16545) +-- Name: sp_users_u(character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_u(p_member_id character varying, p_user_id character varying, p_user_pw character varying, p_user_nm character varying, p_dept_nm character varying, p_posit_nm character varying, p_tel_no character varying, p_email character varying, p_auth_bc character varying, p_use_yn character varying, p_rmks character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE + v_exists INTEGER; +BEGIN + -- 1. 해당 사용자가 존재하는지 확인 + SELECT COUNT(*) INTO v_exists + FROM kngil.users + WHERE member_id = p_member_id AND user_id = p_user_id; + + IF v_exists = 0 THEN + RETURN 'ERROR: 수정할 사용자를 찾을 수 없습니다.'; + END IF; + + -- 2. 데이터 업데이트 + UPDATE kngil.users + SET + user_pw = COALESCE(NULLIF(p_user_pw, ''), user_pw), -- 비밀번호가 빈값이면 기존 유지 + user_nm = p_user_nm, -- 이름 + dept_nm = p_dept_nm, -- 부서명 + posit_nm = p_posit_nm, -- 직위 + tel_no = p_tel_no, -- 전화번호 + email = p_email, -- 메일주소 + auth_bc = p_auth_bc, -- 권한구분 + use_yn = p_use_yn, -- 사용여부 + rmks = p_rmks, -- 비고 + mid = p_mid, -- 수정자 ID 기록 + mdt = CURRENT_TIMESTAMP -- 수정일시 기록 + WHERE member_id = p_member_id + AND user_id = p_user_id; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_u(p_member_id character varying, p_user_id character varying, p_user_pw character varying, p_user_nm character varying, p_dept_nm character varying, p_posit_nm character varying, p_tel_no character varying, p_email character varying, p_auth_bc character varying, p_use_yn character varying, p_rmks character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 223 (class 1259 OID 16449) +-- Name: buy_item; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.buy_item ( + member_id character varying(20) NOT NULL, + sq_no integer NOT NULL, + buy_dt date NOT NULL, + itm_cd character(5) NOT NULL, + itm_qty numeric(18,0) NOT NULL, + itm_area numeric(18,0) NOT NULL, + add_area numeric(18,0), + sum_area numeric(18,0), + itm_amt numeric(18,2) NOT NULL, + dis_rt numeric(5,2), + buy_amt numeric(18,2), + vat_amt numeric(18,2), + sum_amt numeric(18,2), + end_dt date, + ok_yn character(1), + rmks character varying(100), + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone +); + + +ALTER TABLE kngil.buy_item OWNER TO postgres; + +-- +-- TOC entry 227 (class 1259 OID 16522) +-- Name: code_detail; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.code_detail ( + main_cd character varying(5) NOT NULL, + sub_cd character varying(5) NOT NULL, + sub_nm character varying(100), + base_cd character varying(10) NOT NULL, + use_yn character(1) NOT NULL, + sort_sq integer, + rmks character varying(100), + m1 character varying(50), + m2 character varying(50), + m3 character varying(50), + m4 character varying(50), + m5 character varying(50), + m6 character varying(50), + m7 character varying(50), + m8 character varying(50), + m9 character varying(50), + m10 character varying(50) +); + + +ALTER TABLE kngil.code_detail OWNER TO postgres; + +-- +-- TOC entry 5044 (class 0 OID 0) +-- Dependencies: 227 +-- Name: TABLE code_detail; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.code_detail IS '공통코드상세'; + + +-- +-- TOC entry 5045 (class 0 OID 0) +-- Dependencies: 227 +-- Name: COLUMN code_detail.sub_nm; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.code_detail.sub_nm IS '서브코드명'; + + +-- +-- TOC entry 226 (class 1259 OID 16497) +-- Name: code_master; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.code_master ( + main_cd character varying(5) NOT NULL, + nain_nm character varying(100), + use_yn character(1), + use_bc character varying(10), + sort_bc character varying(10), + rmks character varying(100), + t1 character varying(50), + t2 character varying(50), + t3 character varying(50), + t4 character varying(50), + t5 character varying(50), + t6 character varying(50), + t7 character varying(50), + t8 character varying(50), + t9 character varying(50), + t10 character varying(50) +); + + +ALTER TABLE kngil.code_master OWNER TO postgres; + +-- +-- TOC entry 5046 (class 0 OID 0) +-- Dependencies: 226 +-- Name: TABLE code_master; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.code_master IS '공통코드마스터'; + + +-- +-- TOC entry 5047 (class 0 OID 0) +-- Dependencies: 226 +-- Name: COLUMN code_master.main_cd; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.code_master.main_cd IS '메인코드'; + + +-- +-- TOC entry 5048 (class 0 OID 0) +-- Dependencies: 226 +-- Name: COLUMN code_master.nain_nm; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.code_master.nain_nm IS '코드명'; + + +-- +-- TOC entry 236 (class 1259 OID 16679) +-- Name: fa_comments_fa_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.fa_comments ALTER COLUMN fa_id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME kngil.fa_comments_fa_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 221 (class 1259 OID 16427) +-- Name: item; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.item ( + itm_cd character varying(5) NOT NULL, + itm_nm character varying(50), + area numeric(18,0), + itm_amt numeric(18,2), + use_yn character(1), + rmks character varying(100), + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone +); + + +ALTER TABLE kngil.item OWNER TO postgres; + +-- +-- TOC entry 5049 (class 0 OID 0) +-- Dependencies: 221 +-- Name: TABLE item; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.item IS '서비스 상품 관리 테이블'; + + +-- +-- TOC entry 225 (class 1259 OID 16484) +-- Name: login_history; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.login_history ( + user_id character varying(20) NOT NULL, + sq_no integer NOT NULL, + public_ip character varying(45), + local_ip character varying(45), + login_tm timestamp without time zone +); + + +ALTER TABLE kngil.login_history OWNER TO postgres; + +-- +-- TOC entry 220 (class 1259 OID 16414) +-- Name: members; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.members ( + member_id character varying(20) CONSTRAINT member_member_id_not_null NOT NULL, + member_nm character varying(50) CONSTRAINT member_member_nm_not_null NOT NULL, + co_bc character varying(10) CONSTRAINT member_co_bc_not_null NOT NULL, + bs_no character varying(15), + co_nm character varying(50) CONSTRAINT member_co_nm_not_null NOT NULL, + co_tel character varying(13), + tel_no character varying(13), + email character varying(50), + join_dt date CONSTRAINT member_join_dt_not_null NOT NULL, + end_dt date, + buy_area numeric(18,0), + use_area numeric(18,0), + stat_bc character varying(10) CONSTRAINT member_stat_bc_not_null NOT NULL, + memo text, + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone +); + + +ALTER TABLE kngil.members OWNER TO postgres; + +-- +-- TOC entry 5050 (class 0 OID 0) +-- Dependencies: 220 +-- Name: TABLE members; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.members IS '회원 관리 테이블'; + + +-- +-- TOC entry 5051 (class 0 OID 0) +-- Dependencies: 220 +-- Name: COLUMN members.member_id; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.members.member_id IS '회원ID'; + + +-- +-- TOC entry 228 (class 1259 OID 16594) +-- Name: qa_attachments; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.qa_attachments ( + post_id integer, + ori_name character varying, + save_path character varying, + file_size integer, + uploaded_at timestamp with time zone, + id bigint NOT NULL +); + + +ALTER TABLE kngil.qa_attachments OWNER TO postgres; + +-- +-- TOC entry 5052 (class 0 OID 0) +-- Dependencies: 228 +-- Name: TABLE qa_attachments; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.qa_attachments IS 'Q&A 첨부파일 테이블'; + + +-- +-- TOC entry 237 (class 1259 OID 16687) +-- Name: qa_attachments_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.qa_attachments ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME kngil.qa_attachments_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 234 (class 1259 OID 16627) +-- Name: qa_comment_images; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.qa_comment_images ( + id integer NOT NULL, + comment_id integer, + file_name character varying(255), + file_path character varying(255), + thumb_path character varying(255), + file_size integer, + uploaded_at timestamp with time zone +); + + +ALTER TABLE kngil.qa_comment_images OWNER TO postgres; + +-- +-- TOC entry 5053 (class 0 OID 0) +-- Dependencies: 234 +-- Name: TABLE qa_comment_images; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.qa_comment_images IS '뎃글 이미지'; + + +-- +-- TOC entry 233 (class 1259 OID 16626) +-- Name: qa_comment_images_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.qa_comment_images ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME kngil.qa_comment_images_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 232 (class 1259 OID 16618) +-- Name: qa_comments; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.qa_comments ( + comment_id integer NOT NULL, + post_id integer, + commenter character varying(255), + content text, + cdt_dt timestamp with time zone, + user_nm character varying(100), + mdt_dt timestamp with time zone +); + + +ALTER TABLE kngil.qa_comments OWNER TO postgres; + +-- +-- TOC entry 5054 (class 0 OID 0) +-- Dependencies: 232 +-- Name: TABLE qa_comments; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.qa_comments IS '뎃글'; + + +-- +-- TOC entry 231 (class 1259 OID 16617) +-- Name: qa_comments_comment_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.qa_comments ALTER COLUMN comment_id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME kngil.qa_comments_comment_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 230 (class 1259 OID 16603) +-- Name: qa_posts; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.qa_posts ( + post_id integer NOT NULL, + tel_no character varying(20), + user_id character varying(64), + user_nm character varying(100), + category character varying(20), + co_nm character varying(100), + dept_nm character varying(100), + title character varying(255), + content text, + attachment character varying(500), + stat_bc character varying(50), + complete_form character(1) DEFAULT 0, + cdt_dt timestamp with time zone, + mid_dt timestamp with time zone, + is_secret character(1) DEFAULT 'N'::bpchar, + is_read_admin character(1) DEFAULT 0 +); + + +ALTER TABLE kngil.qa_posts OWNER TO postgres; + +-- +-- TOC entry 5055 (class 0 OID 0) +-- Dependencies: 230 +-- Name: TABLE qa_posts; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.qa_posts IS 'Q&A 내용등록'; + + +-- +-- TOC entry 5056 (class 0 OID 0) +-- Dependencies: 230 +-- Name: COLUMN qa_posts.co_nm; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.qa_posts.co_nm IS '회사명 +'; + + +-- +-- TOC entry 229 (class 1259 OID 16602) +-- Name: qa_posts_post_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.qa_posts ALTER COLUMN post_id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME kngil.qa_posts_post_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 224 (class 1259 OID 16471) +-- Name: use_history; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.use_history ( + user_id character varying(20) NOT NULL, + sq_no integer NOT NULL, + use_dt date, + use_area numeric(18,0), + ser_bc character varying(10), + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone +); + + +ALTER TABLE kngil.use_history OWNER TO postgres; + +-- +-- TOC entry 222 (class 1259 OID 16433) +-- Name: users; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.users ( + member_id character varying(20) CONSTRAINT user_member_id_not_null NOT NULL, + user_id character varying(20) CONSTRAINT user_user_id_not_null NOT NULL, + user_pw character varying(255), + user_nm character varying(50) CONSTRAINT user_user_nm_not_null NOT NULL, + dept_nm character varying(50), + posit_nm character varying(50), + tel_no character varying(13) CONSTRAINT user_tel_no_not_null NOT NULL, + email character varying(50), + auth_bc character varying(10) CONSTRAINT user_auth_bc_not_null NOT NULL, + use_yn character varying(10) CONSTRAINT user_use_yn_not_null NOT NULL, + rmks character varying(100), + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone, + oidc_sub character varying(255) +); + + +ALTER TABLE kngil.users OWNER TO postgres; + +-- +-- TOC entry 5057 (class 0 OID 0) +-- Dependencies: 222 +-- Name: TABLE users; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.users IS '사용자관리 테이블'; + + +-- +-- TOC entry 5024 (class 0 OID 16449) +-- Dependencies: 223 +-- Data for Name: buy_item; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.buy_item (member_id, sq_no, buy_dt, itm_cd, itm_qty, itm_area, add_area, sum_area, itm_amt, dis_rt, buy_amt, vat_amt, sum_amt, end_dt, ok_yn, rmks, cid, cdt, mid, mdt) FROM stdin; +B260001 1 2026-01-14 BSV01 1 10000 500 10500 5000000.00 0.00 5000000.00 500000.00 5500000.00 2026-12-31 N test m24031 2026-01-14 18:48:27.938147 m24031 2026-01-14 18:48:27.938147 +sdisdi 2 2026-01-28 BSV01 3 0 100 100 5000000.00 10.00 13500000.00 1350000.00 14850000.00 \N N ADMIN 2026-01-29 14:02:04.478554 ADMIN 2026-01-29 16:57:37.297659 +sdisdi 1 2026-01-28 실버 2 0 0 0 5000000.00 10.00 9000000.00 900000.00 9900000.00 \N N ADMIN 2026-01-29 13:20:06.016857 ADMIN 2026-01-29 16:57:37.297659 +sdisdi 4 2026-01-28 BSV01 1 10000 0 10000 5000000.00 5.00 5000000.00 500000.00 5500000.00 2026-12-31 N ADMIN 2026-01-29 16:57:37.297659 ADMIN 2026-01-29 16:57:37.297659 +B260001 4 2026-01-16 DDA01 2 10000 0 10000 10000000.00 20.00 8000000.00 800000.00 8800000.00 2026-01-29 Y test1 ADMIN 2026-01-19 09:29:15.302285 ADMIN 2026-01-19 09:29:26.957085 +B260001 6 2026-01-19 DDA01 2 100000 0 100000 10000000.00 25.00 15000000.00 1500000.00 16500000.00 2026-01-29 Y ADMIN 2026-01-19 09:53:04.703453 ADMIN 2026-01-19 11:18:22.708297 +B260001 5 2026-01-19 DDA01 4 10000 0 10000 10000000.00 10.00 36000000.00 3600000.00 39600000.00 2026-01-30 Y ADMIN 2026-01-19 09:33:44.805899 ADMIN 2026-01-19 11:18:22.708297 +B260001 3 2026-01-16 DDA01 1 10000 0 10000 10000000.00 50.00 5000000.00 500000.00 5500000.00 2026-01-26 Y test ADMIN 2026-01-16 17:43:34.138282 ADMIN 2026-01-19 09:29:26.957085 +B260001 2 2026-01-16 DDA01 1 10000 0 10000 10000000.00 10.00 9000000.00 900000.00 9900000.00 2026-01-29 Y test ADMIN 2026-01-16 17:29:57.797917 ADMIN 2026-01-19 09:29:26.957085 +B260001 7 2026-01-19 BSV01 1 10000 0 10000 5000000.00 20.00 5000000.00 500000.00 5500000.00 2026-02-06 N ADMIN 2026-01-19 11:18:09.714842 ADMIN 2026-01-19 11:18:22.708297 +sdisdi 5 2026-01-29 CGD01 2 20000 500 20500 10000000.00 25.00 15000000.00 1500000.00 16500000.00 2026-12-30 N ADMIN 2026-01-29 17:10:39.39493 ADMIN 2026-01-29 17:12:56.807569 +sdisdi 3 2026-01-29 BSV01 1 10000 0 10000 5000000.00 5.00 4750000.00 475000.00 5225000.00 2026-12-31 N ADMIN 2026-01-29 15:31:42.295678 ADMIN 2026-01-29 17:12:56.807569 +tester001 1 2025-04-12 BSV01 1 10000 \N 10000 5000000.00 0.00 5000000.00 500000.00 5500000.00 2025-12-31 Y \N \N \N \N \N +tester001 2 2026-01-01 BSV01 1 10000 300 10300 5000000.00 0.00 5000000.00 500000.00 5500000.00 2026-12-31 Y \N \N \N \N \N +tester001 3 2026-01-01 BSV01 1 10000 \N 10000 5000000.00 0.00 5000000.00 500000.00 5500000.00 2026-12-31 Y \N \N \N \N \N +tester001 4 2026-01-01 BSV01 1 10000 \N 10000 5000000.00 0.00 5000000.00 500000.00 5500000.00 2026-12-31 Y \N \N \N \N \N +sdi9429 1 2026-01-29 골드 1 20000 0 20000 10000000.00 0.00 10000000.00 1000000.00 11000000.00 \N Y ADMIN 2026-01-29 13:05:15.730538 ADMIN 2026-01-29 13:14:38.877759 +sdisdi 6 2026-01-29 ZET01 3000 1 0 1 0.00 0.00 0.00 0.00 0.00 2026-12-31 N ADMIN 2026-01-29 17:12:56.807569 ADMIN 2026-01-29 17:12:56.807569 +am24031 1 2026-01-30 A0000 1 3000 0 3000 0.00 0.00 0.00 0.00 0.00 2026-12-31 Y 최초 가입 제공 am24031 2026-01-30 13:25:57.005379 am24031 2026-01-30 13:25:57.005379 +\. + + +-- +-- TOC entry 5028 (class 0 OID 16522) +-- Dependencies: 227 +-- Data for Name: code_detail; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.code_detail (main_cd, sub_cd, sub_nm, base_cd, use_yn, sort_sq, rmks, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10) FROM stdin; +BS100 300 메인 BS100300 Y 3 \N 1 \N \N \N \N \N \N \N \N \N +BS110 100 오름차순 BS110100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +BS110 200 정의 BS110200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +BS200 100 코드1 BS200100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +BS200 200 코드2 BS200200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +BS210 Y Y BS210Y Y 1 \N \N \N \N \N \N \N \N \N \N \N +BS210 N N BS210N Y 2 \N \N \N \N \N \N \N \N \N \N \N +BS220 1 사용 BS2201 Y 1 \N \N \N \N \N \N \N \N \N \N \N +BS220 0 미사용 BS2200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +SA100 100 사용 SA100100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +SA100 200 미사용 SA100200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +BS100 100 개발자 BS100100 Y 1 \N \N 1 \N \N \N \N \N \N \N \N +BS100 200 관리자 BS100200 Y 2 \N \N 1 \N \N \N \N \N \N \N \N +BS100 400 서브 BS100400 Y 4 \N 1 \N \N \N \N \N \N \N \N \N +BS100 500 일반 BS100500 Y 5 \N 1 \N \N \N \N \N \N \N \N \N +SA150 200 개인 SA150200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +SA150 100 기업 SA150100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +SA200 100 상하수도 기초현황 보고서 SA200100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +SA200 200 도시계획 기초현황 보고서 SA200200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +SA200 300 기초현황 DATA 파일 SA200300 Y 3 \N \N \N \N \N \N \N \N \N \N \N +\. + + +-- +-- TOC entry 5027 (class 0 OID 16497) +-- Dependencies: 226 +-- Data for Name: code_master; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.code_master (main_cd, nain_nm, use_yn, use_bc, sort_bc, rmks, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) FROM stdin; +BS110 정렬기준 Y BS200100 BS110100 \N \N \N \N \N \N \N \N \N \N \N +BS200 사용구분\n Y BS200100 BS110100 \N \N \N \N \N \N \N \N \N \N \N +BS210 Y/N 구분 Y BS200200 BS110100 \N \N \N \N \N \N \N \N \N \N \N +BS220 사용여부 Y BS200200 BS110100 \N \N \N \N \N \N \N \N \N \N \N +SA100 회원상태 Y BS200100 BS110100 \N \N \N \N \N \N \N \N \N \N \N +SA200 서비스구분 Y BS200100 BS110100 \N \N \N \N \N \N \N \N \N \N \N +BS100 권한관리 Y BS200100 BS110100 \N 사용자 관리자 \N \N \N \N \N \N \N \N +SA150 법인구분 Y BS200100 BS110200 \N \N \N \N \N \N \N \N \N \N \N +\. + + +-- +-- TOC entry 5036 (class 0 OID 16671) +-- Dependencies: 235 +-- Data for Name: fa_comments; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.fa_comments (fa_id, fa_subject, fa_content, sq_no, use_yn, cid, cdt, mid, mdt) FROM stdin; +3 계정은 어떻게 생성하나요? 회원가입 메뉴를 통해 계정을 생성할 수 있습니다. 2 Y \N \N \N \N +1 KNGIL은 어떤 서비스인가요? KNGIL은 교량 BIM 기반 설계·관리 플랫폼입니다. hahaha 1 Y \N \N \N \N +13 이것은질문 이것은
답글 0 Y m24031 2026-01-29 14:12:20.625501+09 m24031 2026-01-29 14:26:04.837518+09 +14 ㅇㅇㅇㅇ343 ㅇㅁㅇㄹㅇㅇㄹㅇㄹㅇㄹ 0 Y m24031 2026-02-02 11:22:02.315133+09 m24031 2026-02-02 11:22:07.740956+09 +\. + + +-- +-- TOC entry 5022 (class 0 OID 16427) +-- Dependencies: 221 +-- Data for Name: item; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.item (itm_cd, itm_nm, area, itm_amt, use_yn, rmks, cid, cdt, mid, mdt) FROM stdin; +CGD01 골드 20000 10000000.00 Y test m24031 2026-01-14 17:55:27.062562 m24031 2026-01-14 17:55:27.062562 +ZET01 서비스 1 0.00 Y test m24031 2026-01-14 17:56:47.427489 m24031 2026-01-14 17:56:47.427489 +BSV01 실버 10000 5000000.00 Y test m24031 2026-01-14 17:54:53.937187 m24031 2026-01-14 17:58:17.831158 +BBBB TEST23 4 2.00 Y m24031 2026-01-23 14:28:12.793976 m24031 2026-01-23 15:23:19.231161 +DDA01 다이아 30000 30000000.00 Y testㅇㅇㅇ m24031 2026-01-14 17:56:22.996253 m24031 2026-01-29 13:50:16.978103 +A0000 회원가입 제공 3000 0.00 Y m24031 2026-01-30 10:01:26.15998 m24031 2026-01-30 10:01:26.15998 +\. + + +-- +-- TOC entry 5026 (class 0 OID 16484) +-- Dependencies: 225 +-- Data for Name: login_history; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.login_history (user_id, sq_no, public_ip, local_ip, login_tm) FROM stdin; +\. + + +-- +-- TOC entry 5021 (class 0 OID 16414) +-- Dependencies: 220 +-- Data for Name: members; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.members (member_id, member_nm, co_bc, bs_no, co_nm, co_tel, tel_no, email, join_dt, end_dt, buy_area, use_area, stat_bc, memo, cid, cdt, mid, mdt) FROM stdin; +b25027 김수현 CB100100 223-33-44445 한맥기술 \N 010-5645-5153 b25027@hanmaceng.co.kr 2026-01-29 \N \N \N SA100100 \N b25027 2026-01-29 11:25:14.184682 b24014 2026-01-29 16:21:12.842875 +B260001 바론 관리자 SA150100 222222222 바론 컨설턴트 \N 010-1111-1111 sdi@sdi.com 2026-01-01 \N 140000 1200 SA100100 \N \N \N SYSTEM 2026-01-21 15:45:40.131991 +tester001 테스터10 SA150100 \N 기업1 \N 010-1111-1111 111@gmail.com 2026-01-20 \N 3000 \N SA100100 \N m24031 2026-01-20 19:50:23.029203 \N \N +ctest004 c테스터33 SA150200 \N 기업3 \N 010-3333-3333 333@gmail.com 2026-01-20 \N 1000 \N SA100100 \N m24031 2026-01-20 19:57:42.806965 \N \N +sdi9429 송대일 CB100100 \N 바론 \N 010-8627-0921 sdi9429@naver.com 2026-01-22 \N 1000 \N SA100100 \N sdi9429 2026-01-22 12:56:43.651431 \N \N +tester003 테스터33 SA150200 222222222 기업3 \N 010-3333-3333 333@gmail.com 2026-01-20 \N 2000 \N SA100100 \N m24031 2026-01-20 19:52:19.322838 m24031 2026-01-23 16:50:12.264163 +tester002 테스터10 SA150100 222222 기업1 \N 010-1111-1111 111@gmail.com 2026-01-20 \N 2000 \N SA100100 \N m24031 2026-01-20 19:51:18.809202 m24031 2026-01-27 10:44:31.258757 +sdisdi 송대일 CB100100 \N 바론11 \N 010-8627-0923 sdi9429@naver.com 2026-01-22 \N 0 \N SA100100 \N sdisdi 2026-01-22 13:12:29.376769 m24031 2026-01-27 10:44:47.776389 +Km24031 권오재 CB100100 \N 한맥기술 \N 010-9114-3944 koj111@naver.com 2026-01-30 \N \N \N SA100100 \N Km24031 2026-01-30 09:02:15.195496 \N \N +am24031 권오재A CB100100 \N 한맥 \N 010-2222-2222 ddd@gmail.com 2026-01-30 \N 3000 \N SA100100 \N am24031 2026-01-30 13:25:57.005379 \N \N +\. + + +-- +-- TOC entry 5029 (class 0 OID 16594) +-- Dependencies: 228 +-- Data for Name: qa_attachments; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.qa_attachments (post_id, ori_name, save_path, file_size, uploaded_at, id) FROM stdin; +7 traffic_image.jpg /kngil/uploads/qa/1769509499_417330b6f49e.jpg 123753 2026-01-27 19:24:59.134487+09 1 +8 traffic_image.jpg /kngil/uploads/qa/1769513369_959a050cb972.jpg 123753 2026-01-27 20:29:29.973998+09 2 +\. + + +-- +-- TOC entry 5035 (class 0 OID 16627) +-- Dependencies: 234 +-- Data for Name: qa_comment_images; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.qa_comment_images (id, comment_id, file_name, file_path, thumb_path, file_size, uploaded_at) FROM stdin; +1 2 og-main-thumb.JPG /kngil/uploads/comment/1769511961_dda23bd3.jpg /kngil/uploads/comment/1769511961_dda23bd3.jpg 112930 2026-01-27 20:06:01.050825+09 +\. + + +-- +-- TOC entry 5033 (class 0 OID 16618) +-- Dependencies: 232 +-- Data for Name: qa_comments; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.qa_comments (comment_id, post_id, commenter, content, cdt_dt, user_nm, mdt_dt) FROM stdin; +2 7 b24014 test 2026-01-27 20:06:01.050825+09 송대일1 \N +\. + + +-- +-- TOC entry 5031 (class 0 OID 16603) +-- Dependencies: 230 +-- Data for Name: qa_posts; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.qa_posts (post_id, tel_no, user_id, user_nm, category, co_nm, dept_nm, title, content, attachment, stat_bc, complete_form, cdt_dt, mid_dt, is_secret, is_read_admin) FROM stdin; +7 \N b24014 송대일1 오류문의 \N \N 테스트

1234

\N review 0 2026-01-27 19:24:59.131617+09 2026-01-27 20:14:21.993651+09 0 Y +8 \N b24014 송대일1 오류문의 \N \N 첨부파일 업로드

123

\N deep 0 2026-01-27 20:29:29.970856+09 2026-01-27 20:32:21.107049+09 0 Y +10 010-8627-0921 b24014 송대일1 오류문의 바론 컨설턴트 총괄기획실 test

1qasdweqwe

\N review 0 2026-01-28 10:43:24.655249+09 2026-01-28 11:00:56.361487+09 Y Y +1 오류문의 1111

3333

\N wait 0 2026-01-27 14:09:56.639903+09 \N Y 0 +2 \N b24014 송대일1 오류문의 \N \N 1234

3333

\N wait 0 2026-01-27 15:56:13.097696+09 \N 0 N +5 \N b24014 송대일1 오류문의 \N \N 123

456

\N wait 0 2026-01-27 16:40:55.238147+09 \N 0 N +9 \N b24014 송대일1 오류문의 \N \N 최종테스트

11111333

\N wait 0 2026-01-28 10:09:07.943555+09 \N 0 Y +6 \N b24014 송대일1 오류문의 \N \N 첨부파일 업로드

123

\N wait 0 2026-01-27 19:17:08.118218+09 \N 0 Y +4 \N b24014 송대일1 개선문의 \N \N tests

tests

\N wait 0 2026-01-27 16:00:43.415892+09 \N 0 Y +12 010-8627-0921 b24014 송대일1 일반문의 바론 컨설턴트 총괄기획실 테스트 작성글

ㅅㅅㅅㅅㅅㅅㅅ

\N wait 0 2026-01-30 16:10:49.845849+09 2026-02-02 09:32:40.063076+09 N Y +11 010-8627-0921 b24014 송대일1 오류문의 바론 컨설턴트 총괄기획실 1111

3123123

\N wait 0 2026-01-30 16:10:30.821517+09 2026-02-02 09:32:54.632593+09 N Y +13 010-8627-0921 b24014 송대일1 오류문의 바론 컨설턴트 총괄기획실 테스트 중입니다

3333444

\N wait 0 2026-02-02 09:33:22.636799+09 \N N Y +14 010-8627-0921 b24014 송대일1 공지사항 바론 컨설턴트 총괄기획실 공지 테스트

1234

\N wait 0 2026-02-02 09:40:50.464071+09 \N N Y +\. + + +-- +-- TOC entry 5025 (class 0 OID 16471) +-- Dependencies: 224 +-- Data for Name: use_history; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.use_history (user_id, sq_no, use_dt, use_area, ser_bc, cid, cdt, mid, mdt) FROM stdin; +m24031 1 2026-01-18 100 SA200100 \N \N \N \N +test001 1 2026-01-18 200 SA200100 \N \N \N \N +test001 2 2026-01-19 200 SA200100 \N \N \N \N +m24031 2 2026-01-19 200 SA200300 \N \N \N \N +m24031 3 2026-01-19 300 SA200200 \N \N \N \N +m24031 4 2026-01-19 100 SA200300 \N \N \N \N +m24031 5 2026-01-19 20000 SA200300 \N \N \N \N +m24031 6 2026-01-19 1000 SA200200 \N \N \N \N +m24031 7 2025-01-19 20000 SA200100 \N \N \N \N +\. + + +-- +-- TOC entry 5023 (class 0 OID 16433) +-- Dependencies: 222 +-- Data for Name: users; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.users (member_id, user_id, user_pw, user_nm, dept_nm, posit_nm, tel_no, email, auth_bc, use_yn, rmks, cid, cdt, mid, mdt, oidc_sub) FROM stdin; +B260001 test003 test003 테스터3 기술개발센터 수석연구원 010-1111-3333 test@test.com BS100400 N test 입니다. m24031 2026-01-14 18:39:43.030024 m24031 2026-01-14 18:42:22.488198 \N +sdisdi song12124 test1234!!@ 테스터4 erp 1012346770 test@test.co.kr BS100500 Y song12124 2026-01-30 11:12:14.172743 song12124 2026-01-30 11:12:14.172743 \N +tester002 tester002 tester002 류호성 전산실 \N 010-3371-5649 111@gmail.com BS100100 Y \N m24031 2026-01-20 19:51:18.809202 m24031 2026-01-27 10:44:31.258757 \N +sdisdi song12125 test1234!!@ 테스터5 erp 1012346771 test@test.co.kr BS100500 Y song12125 2026-01-30 11:12:14.184522 song12125 2026-01-30 11:12:14.184522 \N +ctest004 ctest004 ctest004 권오재2 전산실 \N 010-9114-3943 333@gmail.com BS100300 N \N m24031 2026-01-20 19:57:42.806965 \N \N \N +B260001 sdi1108 sdi1108 어드민 ERP팀ㅁ 010-1111-1112 test@test1.com BS100400 Y \N 2026-01-15 20:08:48.731648 m24031 2026-01-29 13:29:52.388359 \N +sdisdi song12126 test1234!!@ 테스터6 erp 1012346772 test@test.co.kr BS100500 Y song12126 2026-01-30 11:18:04.916446 song12126 2026-01-30 11:18:04.916446 \N +sdisdi song12127 test1234!!@ 테스터7 erp 1012346773 test@test.co.kr BS100500 Y song12127 2026-01-30 11:18:04.921195 song12127 2026-01-30 11:18:04.921195 \N +sdisdi song12128 test1234!!@ 테스터8 erp 1012346774 test@test.co.kr BS100500 Y song12128 2026-01-30 12:53:06.276828 song12128 2026-01-30 12:53:06.276828 \N +sdisdi song12129 test1234!!@ 테스터9 erp 1012346775 test@test.co.kr BS100500 Y song12129 2026-01-30 12:53:06.284774 song12129 2026-01-30 12:53:06.284774 \N +sdisdi song12130 test1234!!@ 테스터10 erp 1012346776 test@test.co.kr BS100500 Y song12130 2026-01-30 13:01:11.566978 song12130 2026-01-30 13:01:11.566978 \N +sdisdi song12131 test1234!!@ 테스터11 erp 1012346777 test@test.co.kr BS100500 Y song12131 2026-01-30 13:01:11.57227 song12131 2026-01-30 13:01:11.57227 \N +am24031 am24031 !rnjsdhwo729 권오재A ERP 기획 \N 010-2222-2222 ddd@gmail.com BS100300 Y \N am24031 2026-01-30 13:25:57.005379 \N \N \N +b25027 b25027 a1357125!@23 김수현 디자인기획팀 \N 010-5645-5153 b25027@hanmaceng.co.kr BS100300 Y \N b25027 2026-01-29 11:25:14.184682 b24014 2026-01-29 16:21:12.842875 \N +B260001 test005 b23008 염승호 총괄기획실 수석 연구원 010-8835-0501 df BS100100 Y m24031 2026-01-29 13:30:30.456963 m24031 2026-01-29 13:30:30.456963 \N +B260001 test002 test002 테스터2 기술개발센터 010-1111-2222 test@test.com BS100400 Y m24031 2026-01-14 18:39:03.432801 m24031 2026-01-29 20:31:27.681975 \N +tester003 btest001 btest001 테스터33 도로부 BS100500 Y test m24031 2026-01-20 19:56:32.467743 m24031 2026-01-20 19:56:32.467743 \N +tester003 btest002 btest002 b테스터22 도로부 BS100500 Y test m24031 2026-01-20 19:56:48.750462 m24031 2026-01-20 19:56:48.750462 \N +B260001 m24031 m24031 권오재 총괄기획실 선임 연구원 010-9114-3943 m24031@hanmaceng.co.kr BS100100 Y \N \N \N \N \N \N +Km24031 Km24031 !rnjsdhwo729 권오재 ERP기획팀 \N 010-9114-3944 koj111@naver.com BS100300 Y \N Km24031 2026-01-30 09:02:15.195496 \N \N \N +sdi9429 sdi9429 test1111 송대일 총괄기획실 \N 010-8627-0922 sdi9429@naver.com BS100300 Y \N sdi9429 2026-01-22 12:56:43.651431 \N \N \N +B260001 b24014 test001 송대일1 총괄기획실 010-8627-0921 b24014@hanmaceng.co.kr\n BS100100 Y 123 \N \N SYSTEM 2026-01-19 16:44:25.814475 \N +tester003 tester003 test001 테스터33 전산실 \N 010-3333-3333 333@gmail.com BS100300 Y \N m24031 2026-01-20 19:52:19.322838 m24031 2026-01-23 16:50:12.264163 \N +B260001 test001 test001 회사관리자 총괄기획실1 010-3189-1514 sdi1111@sdi.com BS100300 Y 권한위임[sdi1108] \N \N m24031 2026-01-22 17:11:58.275998 \N +B260001 test0005 test0005 테스트55 총괄기획실1 010-4158-5840 b24000@hanmaceng.co.kr BS100400 Y 비고 입력 테스트 SYSTEM 2026-01-19 19:39:10.476205 SYSTEM 2026-01-19 19:39:10.476205 \N +tester001 tester001 test001 테스터10 전산실 \N 010-9523-0055 111@gmail.com BS100300 Y \N m24031 2026-01-20 19:50:23.029203 \N \N \N +sdisdi sdisdi song1108! 송대일 총괄기획실 \N 010-8627-0923 sdi9429@naver.com BS100300 Y \N sdisdi 2026-01-22 13:12:29.376769 m24031 2026-01-27 10:44:47.776389 \N +sdisdi song1212 test1234!!@ 테스터 erp 01012345689 test@test.co.kr BS100500 N song1212 2026-01-30 09:58:45.274688 sdisdi 2026-01-30 10:02:41.963234 \N +sdisdi song12121 test1234!!@ 테스터1 erp 1012346767 test@test.co.kr BS100400 N song12121 2026-01-30 10:05:59.726345 song12121 2026-01-30 10:05:59.726345 \N +sdisdi song12122 test1234!!@ 테스터2 erp 1012346768 test@test.co.kr BS100500 Y song12122 2026-01-30 10:46:04.719717 song12122 2026-01-30 10:46:04.719717 \N +sdisdi song12123 test1234!!@ 테스터3 erp 1012346769 test@test.co.kr BS100500 Y song12123 2026-01-30 10:46:04.729306 song12123 2026-01-30 10:46:04.729306 \N +B260001 sdi111 000000 test123 ert33 010-8623-6564 test@test.com BS100500 Y b24014 2026-01-30 13:02:45.327993 b24014 2026-01-30 14:08:03.710466 \N +B260001 sdisssss 1234asd!! test1232 erpp 010-8686-2323 test@test.com BS100500 Y b24014 2026-01-30 13:40:50.09753 b24014 2026-01-30 14:08:14.578146 \N +B260001 test1010 test1010 test12 erp 010-8888-2222 test@test1.co.kr BS100500 Y b24014 2026-01-30 13:56:43.807999 b24014 2026-01-30 14:18:12.484107 \N +am24031 kwon001 kwon001 권1 dd 010-1111-2111 kwon001 BS100500 Y kwon001 2026-01-30 16:05:35.819863 kwon001 2026-01-30 16:05:35.819863 \N +am24031 kwon002 kwon002 권2 aa 010-1111-2112 kwon002 BS100500 Y kwon002 2026-01-30 16:05:35.846448 kwon002 2026-01-30 16:05:35.846448 \N +\. + + +-- +-- TOC entry 5058 (class 0 OID 0) +-- Dependencies: 236 +-- Name: fa_comments_fa_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.fa_comments_fa_id_seq', 14, true); + + +-- +-- TOC entry 5059 (class 0 OID 0) +-- Dependencies: 237 +-- Name: qa_attachments_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.qa_attachments_id_seq', 2, true); + + +-- +-- TOC entry 5060 (class 0 OID 0) +-- Dependencies: 233 +-- Name: qa_comment_images_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.qa_comment_images_id_seq', 1, true); + + +-- +-- TOC entry 5061 (class 0 OID 0) +-- Dependencies: 231 +-- Name: qa_comments_comment_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.qa_comments_comment_id_seq', 2, true); + + +-- +-- TOC entry 5062 (class 0 OID 0) +-- Dependencies: 229 +-- Name: qa_posts_post_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.qa_posts_post_id_seq', 14, true); + + +-- +-- TOC entry 4867 (class 2606 OID 16678) +-- Name: fa_comments fa_comments_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.fa_comments + ADD CONSTRAINT fa_comments_pkey PRIMARY KEY (fa_id); + + +-- +-- TOC entry 4855 (class 2606 OID 16643) +-- Name: login_history login_history_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.login_history + ADD CONSTRAINT login_history_pkey PRIMARY KEY (user_id, sq_no); + + +-- +-- TOC entry 4851 (class 2606 OID 16460) +-- Name: buy_item pk_buy_item; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.buy_item + ADD CONSTRAINT pk_buy_item PRIMARY KEY (member_id, sq_no); + + +-- +-- TOC entry 4859 (class 2606 OID 16532) +-- Name: code_detail pk_code_detail; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.code_detail + ADD CONSTRAINT pk_code_detail PRIMARY KEY (main_cd, sub_cd); + + +-- +-- TOC entry 4857 (class 2606 OID 16504) +-- Name: code_master pk_code_master; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.code_master + ADD CONSTRAINT pk_code_master PRIMARY KEY (main_cd); + + +-- +-- TOC entry 4845 (class 2606 OID 16555) +-- Name: item pk_item; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.item + ADD CONSTRAINT pk_item PRIMARY KEY (itm_cd); + + +-- +-- TOC entry 4843 (class 2606 OID 16426) +-- Name: members pk_members; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.members + ADD CONSTRAINT pk_members PRIMARY KEY (member_id); + + +-- +-- TOC entry 4861 (class 2606 OID 16690) +-- Name: qa_attachments qa_attachments_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.qa_attachments + ADD CONSTRAINT qa_attachments_pkey PRIMARY KEY (id); + + +-- +-- TOC entry 4865 (class 2606 OID 16625) +-- Name: qa_comments qa_comments_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.qa_comments + ADD CONSTRAINT qa_comments_pkey PRIMARY KEY (comment_id); + + +-- +-- TOC entry 4863 (class 2606 OID 16616) +-- Name: qa_posts qa_posts_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.qa_posts + ADD CONSTRAINT qa_posts_pkey PRIMARY KEY (post_id); + + +-- +-- TOC entry 4853 (class 2606 OID 16641) +-- Name: use_history use_history_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.use_history + ADD CONSTRAINT use_history_pkey PRIMARY KEY (user_id, sq_no); + + +-- +-- TOC entry 4847 (class 2606 OID 16721) +-- Name: users users_oidc_sub_key; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.users + ADD CONSTRAINT users_oidc_sub_key UNIQUE (oidc_sub); + + +-- +-- TOC entry 4849 (class 2606 OID 16645) +-- Name: users users_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.users + ADD CONSTRAINT users_pkey PRIMARY KEY (user_id); + + +-- +-- TOC entry 4873 (class 2620 OID 16705) +-- Name: buy_item trg_buy_item_changed; Type: TRIGGER; Schema: kngil; Owner: postgres +-- + +CREATE TRIGGER trg_buy_item_changed AFTER INSERT OR DELETE OR UPDATE ON kngil.buy_item FOR EACH ROW EXECUTE FUNCTION kngil.fn_update_buy_area(); + + +-- +-- TOC entry 4872 (class 2606 OID 16533) +-- Name: code_detail fk_code_master_to_code_detail; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.code_detail + ADD CONSTRAINT fk_code_master_to_code_detail FOREIGN KEY (main_cd) REFERENCES kngil.code_master(main_cd) ON DELETE CASCADE; + + +-- +-- TOC entry 4868 (class 2606 OID 16656) +-- Name: users fk_member_to_user; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.users + ADD CONSTRAINT fk_member_to_user FOREIGN KEY (member_id) REFERENCES kngil.members(member_id) NOT VALID; + + +-- +-- TOC entry 4869 (class 2606 OID 16461) +-- Name: buy_item fk_members_to_buy_item; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.buy_item + ADD CONSTRAINT fk_members_to_buy_item FOREIGN KEY (member_id) REFERENCES kngil.members(member_id); + + +-- +-- TOC entry 4871 (class 2606 OID 16651) +-- Name: login_history fk_user_to_login_history; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.login_history + ADD CONSTRAINT fk_user_to_login_history FOREIGN KEY (user_id) REFERENCES kngil.users(user_id) NOT VALID; + + +-- +-- TOC entry 4870 (class 2606 OID 16646) +-- Name: use_history fk_user_to_use_history; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.use_history + ADD CONSTRAINT fk_user_to_use_history FOREIGN KEY (user_id) REFERENCES kngil.users(user_id) NOT VALID; + + +-- Completed on 2026-02-02 14:06:04 + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict osPaC8Gqjay0KBMwX4hwgDvmjwF5rTGmBMzQBdxAne3SBCLMuCNQu2Xg15dPVeb + diff --git a/kngil/auth/oidc-login.php b/kngil/auth/oidc-login.php index beb8bab..1cd80dc 100644 --- a/kngil/auth/oidc-login.php +++ b/kngil/auth/oidc-login.php @@ -5,6 +5,21 @@ $config = require_once dirname(__DIR__) . '/bbs/oidc_config.php'; use Jumbojett\OpenIDConnectClient; +$requiredKeys = ['issuer', 'client_id', 'client_secret', 'redirect_url']; +$missingKeys = []; +foreach ($requiredKeys as $key) { + if (empty($config[$key])) { + $missingKeys[] = $key; + } +} + +if (!empty($missingKeys)) { + http_response_code(500); + header('Content-Type: text/plain; charset=utf-8'); + echo 'OIDC 설정 누락: ' . implode(', ', $missingKeys); + exit; +} + $oidc = new OpenIDConnectClient( $config['issuer'], $config['client_id'], @@ -17,4 +32,12 @@ $oidc->addScope($config['scopes']); // 필요한 경우 PKCE 활성화 // $oidc->setCodeChallengeMethod('S256'); -$oidc->authenticate(); +try { + $oidc->authenticate(); +} catch (Throwable $e) { + error_log($e->getMessage()); + http_response_code(500); + header('Content-Type: text/plain; charset=utf-8'); + echo 'OIDC 인증 중 오류가 발생했습니다.'; + exit; +} diff --git a/kngil/bbs/db_conn.php b/kngil/bbs/db_conn.php index 571dcf8..74b6021 100644 --- a/kngil/bbs/db_conn.php +++ b/kngil/bbs/db_conn.php @@ -1,10 +1,17 @@ PDO::ERRMODE_EXCEPTION ]); // echo "PostgreSQL 연결 성공 🎉"; diff --git a/kngil/bbs/env.php b/kngil/bbs/env.php new file mode 100644 index 0000000..2df1ac5 --- /dev/null +++ b/kngil/bbs/env.php @@ -0,0 +1,59 @@ + 'https://api.descope.com/v1/apps/P2x26KgEwOu0xIwgNZutJjIZc1zz', // 예: https://idp.example.com/auth/realms/master - 'client_id' => 'UDJ4MjZLZ0V3T3UweEl3Z05adXRKaklaYzF6ejpUUEEzOTVtSmx5MXhiczFwZWxrUHdDVFlvU2hiYXc=', - 'client_secret' => 'uTjiKweHYUINalroA1LVu9OacbEEMPtPbfFITfHu3r5', - 'redirect_url' => "https://kngil.hmac.kr/kngil/auth/oidc-callback.php", - 'scopes' => ['openid'], + 'issuer' => $issuer, // 예: https://idp.example.com/auth/realms/master + 'client_id' => $clientId, + 'client_secret' => $clientSecret, + 'redirect_url' => $redirectUrl, + 'scopes' => $scopes, ]; diff --git a/kngil/bbs/sales_results.php b/kngil/bbs/sales_results.php index 701ac04..bdffcdd 100644 --- a/kngil/bbs/sales_results.php +++ b/kngil/bbs/sales_results.php @@ -3,6 +3,14 @@ ini_set('display_errors', 1); error_reporting(E_ALL); header("Content-Type: application/json; charset=utf-8"); +// 기능 비활성화 (PostgreSQL만 사용) +http_response_code(410); +echo json_encode([ + "status" => "disabled", + "message" => "해당 기능은 현재 비활성화되어 있습니다." +]); +exit; + /* ----------------------------------------------------- 🔵 DB 연결 ----------------------------------------------------- */ diff --git a/kngil/vendor/autoload.php b/kngil/vendor/autoload.php deleted file mode 100644 index e2a1b22..0000000 --- a/kngil/vendor/autoload.php +++ /dev/null @@ -1,22 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Autoload; - -/** - * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. - * - * $loader = new \Composer\Autoload\ClassLoader(); - * - * // register classes with namespaces - * $loader->add('Symfony\Component', __DIR__.'/component'); - * $loader->add('Symfony', __DIR__.'/framework'); - * - * // activate the autoloader - * $loader->register(); - * - * // to enable searching the include path (eg. for PEAR packages) - * $loader->setUseIncludePath(true); - * - * In this example, if you try to use a class in the Symfony\Component - * namespace or one of its children (Symfony\Component\Console for instance), - * the autoloader will first look for the class under the component/ - * directory, and it will then fallback to the framework/ directory if not - * found before giving up. - * - * This class is loosely based on the Symfony UniversalClassLoader. - * - * @author Fabien Potencier - * @author Jordi Boggiano - * @see https://www.php-fig.org/psr/psr-0/ - * @see https://www.php-fig.org/psr/psr-4/ - */ -class ClassLoader -{ - /** @var \Closure(string):void */ - private static $includeFile; - - /** @var string|null */ - private $vendorDir; - - // PSR-4 - /** - * @var array> - */ - private $prefixLengthsPsr4 = array(); - /** - * @var array> - */ - private $prefixDirsPsr4 = array(); - /** - * @var list - */ - private $fallbackDirsPsr4 = array(); - - // PSR-0 - /** - * List of PSR-0 prefixes - * - * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) - * - * @var array>> - */ - private $prefixesPsr0 = array(); - /** - * @var list - */ - private $fallbackDirsPsr0 = array(); - - /** @var bool */ - private $useIncludePath = false; - - /** - * @var array - */ - private $classMap = array(); - - /** @var bool */ - private $classMapAuthoritative = false; - - /** - * @var array - */ - private $missingClasses = array(); - - /** @var string|null */ - private $apcuPrefix; - - /** - * @var array - */ - private static $registeredLoaders = array(); - - /** - * @param string|null $vendorDir - */ - public function __construct($vendorDir = null) - { - $this->vendorDir = $vendorDir; - self::initializeIncludeClosure(); - } - - /** - * @return array> - */ - public function getPrefixes() - { - if (!empty($this->prefixesPsr0)) { - return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); - } - - return array(); - } - - /** - * @return array> - */ - public function getPrefixesPsr4() - { - return $this->prefixDirsPsr4; - } - - /** - * @return list - */ - public function getFallbackDirs() - { - return $this->fallbackDirsPsr0; - } - - /** - * @return list - */ - public function getFallbackDirsPsr4() - { - return $this->fallbackDirsPsr4; - } - - /** - * @return array Array of classname => path - */ - public function getClassMap() - { - return $this->classMap; - } - - /** - * @param array $classMap Class to filename map - * - * @return void - */ - public function addClassMap(array $classMap) - { - if ($this->classMap) { - $this->classMap = array_merge($this->classMap, $classMap); - } else { - $this->classMap = $classMap; - } - } - - /** - * Registers a set of PSR-0 directories for a given prefix, either - * appending or prepending to the ones previously set for this prefix. - * - * @param string $prefix The prefix - * @param list|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories - * - * @return void - */ - public function add($prefix, $paths, $prepend = false) - { - $paths = (array) $paths; - if (!$prefix) { - if ($prepend) { - $this->fallbackDirsPsr0 = array_merge( - $paths, - $this->fallbackDirsPsr0 - ); - } else { - $this->fallbackDirsPsr0 = array_merge( - $this->fallbackDirsPsr0, - $paths - ); - } - - return; - } - - $first = $prefix[0]; - if (!isset($this->prefixesPsr0[$first][$prefix])) { - $this->prefixesPsr0[$first][$prefix] = $paths; - - return; - } - if ($prepend) { - $this->prefixesPsr0[$first][$prefix] = array_merge( - $paths, - $this->prefixesPsr0[$first][$prefix] - ); - } else { - $this->prefixesPsr0[$first][$prefix] = array_merge( - $this->prefixesPsr0[$first][$prefix], - $paths - ); - } - } - - /** - * Registers a set of PSR-4 directories for a given namespace, either - * appending or prepending to the ones previously set for this namespace. - * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param list|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories - * - * @throws \InvalidArgumentException - * - * @return void - */ - public function addPsr4($prefix, $paths, $prepend = false) - { - $paths = (array) $paths; - if (!$prefix) { - // Register directories for the root namespace. - if ($prepend) { - $this->fallbackDirsPsr4 = array_merge( - $paths, - $this->fallbackDirsPsr4 - ); - } else { - $this->fallbackDirsPsr4 = array_merge( - $this->fallbackDirsPsr4, - $paths - ); - } - } elseif (!isset($this->prefixDirsPsr4[$prefix])) { - // Register directories for a new namespace. - $length = strlen($prefix); - if ('\\' !== $prefix[$length - 1]) { - throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); - } - $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; - $this->prefixDirsPsr4[$prefix] = $paths; - } elseif ($prepend) { - // Prepend directories for an already registered namespace. - $this->prefixDirsPsr4[$prefix] = array_merge( - $paths, - $this->prefixDirsPsr4[$prefix] - ); - } else { - // Append directories for an already registered namespace. - $this->prefixDirsPsr4[$prefix] = array_merge( - $this->prefixDirsPsr4[$prefix], - $paths - ); - } - } - - /** - * Registers a set of PSR-0 directories for a given prefix, - * replacing any others previously set for this prefix. - * - * @param string $prefix The prefix - * @param list|string $paths The PSR-0 base directories - * - * @return void - */ - public function set($prefix, $paths) - { - if (!$prefix) { - $this->fallbackDirsPsr0 = (array) $paths; - } else { - $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; - } - } - - /** - * Registers a set of PSR-4 directories for a given namespace, - * replacing any others previously set for this namespace. - * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param list|string $paths The PSR-4 base directories - * - * @throws \InvalidArgumentException - * - * @return void - */ - public function setPsr4($prefix, $paths) - { - if (!$prefix) { - $this->fallbackDirsPsr4 = (array) $paths; - } else { - $length = strlen($prefix); - if ('\\' !== $prefix[$length - 1]) { - throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); - } - $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; - $this->prefixDirsPsr4[$prefix] = (array) $paths; - } - } - - /** - * Turns on searching the include path for class files. - * - * @param bool $useIncludePath - * - * @return void - */ - public function setUseIncludePath($useIncludePath) - { - $this->useIncludePath = $useIncludePath; - } - - /** - * Can be used to check if the autoloader uses the include path to check - * for classes. - * - * @return bool - */ - public function getUseIncludePath() - { - return $this->useIncludePath; - } - - /** - * Turns off searching the prefix and fallback directories for classes - * that have not been registered with the class map. - * - * @param bool $classMapAuthoritative - * - * @return void - */ - public function setClassMapAuthoritative($classMapAuthoritative) - { - $this->classMapAuthoritative = $classMapAuthoritative; - } - - /** - * Should class lookup fail if not found in the current class map? - * - * @return bool - */ - public function isClassMapAuthoritative() - { - return $this->classMapAuthoritative; - } - - /** - * APCu prefix to use to cache found/not-found classes, if the extension is enabled. - * - * @param string|null $apcuPrefix - * - * @return void - */ - public function setApcuPrefix($apcuPrefix) - { - $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; - } - - /** - * The APCu prefix in use, or null if APCu caching is not enabled. - * - * @return string|null - */ - public function getApcuPrefix() - { - return $this->apcuPrefix; - } - - /** - * Registers this instance as an autoloader. - * - * @param bool $prepend Whether to prepend the autoloader or not - * - * @return void - */ - public function register($prepend = false) - { - spl_autoload_register(array($this, 'loadClass'), true, $prepend); - - if (null === $this->vendorDir) { - return; - } - - if ($prepend) { - self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; - } else { - unset(self::$registeredLoaders[$this->vendorDir]); - self::$registeredLoaders[$this->vendorDir] = $this; - } - } - - /** - * Unregisters this instance as an autoloader. - * - * @return void - */ - public function unregister() - { - spl_autoload_unregister(array($this, 'loadClass')); - - if (null !== $this->vendorDir) { - unset(self::$registeredLoaders[$this->vendorDir]); - } - } - - /** - * Loads the given class or interface. - * - * @param string $class The name of the class - * @return true|null True if loaded, null otherwise - */ - public function loadClass($class) - { - if ($file = $this->findFile($class)) { - $includeFile = self::$includeFile; - $includeFile($file); - - return true; - } - - return null; - } - - /** - * Finds the path to the file where the class is defined. - * - * @param string $class The name of the class - * - * @return string|false The path if found, false otherwise - */ - public function findFile($class) - { - // class map lookup - if (isset($this->classMap[$class])) { - return $this->classMap[$class]; - } - if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { - return false; - } - if (null !== $this->apcuPrefix) { - $file = apcu_fetch($this->apcuPrefix.$class, $hit); - if ($hit) { - return $file; - } - } - - $file = $this->findFileWithExtension($class, '.php'); - - // Search for Hack files if we are running on HHVM - if (false === $file && defined('HHVM_VERSION')) { - $file = $this->findFileWithExtension($class, '.hh'); - } - - if (null !== $this->apcuPrefix) { - apcu_add($this->apcuPrefix.$class, $file); - } - - if (false === $file) { - // Remember that this class does not exist. - $this->missingClasses[$class] = true; - } - - return $file; - } - - /** - * Returns the currently registered loaders keyed by their corresponding vendor directories. - * - * @return array - */ - public static function getRegisteredLoaders() - { - return self::$registeredLoaders; - } - - /** - * @param string $class - * @param string $ext - * @return string|false - */ - private function findFileWithExtension($class, $ext) - { - // PSR-4 lookup - $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; - - $first = $class[0]; - if (isset($this->prefixLengthsPsr4[$first])) { - $subPath = $class; - while (false !== $lastPos = strrpos($subPath, '\\')) { - $subPath = substr($subPath, 0, $lastPos); - $search = $subPath . '\\'; - if (isset($this->prefixDirsPsr4[$search])) { - $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); - foreach ($this->prefixDirsPsr4[$search] as $dir) { - if (file_exists($file = $dir . $pathEnd)) { - return $file; - } - } - } - } - } - - // PSR-4 fallback dirs - foreach ($this->fallbackDirsPsr4 as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { - return $file; - } - } - - // PSR-0 lookup - if (false !== $pos = strrpos($class, '\\')) { - // namespaced class name - $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) - . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); - } else { - // PEAR-like class name - $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; - } - - if (isset($this->prefixesPsr0[$first])) { - foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { - if (0 === strpos($class, $prefix)) { - foreach ($dirs as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { - return $file; - } - } - } - } - } - - // PSR-0 fallback dirs - foreach ($this->fallbackDirsPsr0 as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { - return $file; - } - } - - // PSR-0 include paths. - if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { - return $file; - } - - return false; - } - - /** - * @return void - */ - private static function initializeIncludeClosure() - { - if (self::$includeFile !== null) { - return; - } - - /** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - * - * @param string $file - * @return void - */ - self::$includeFile = \Closure::bind(static function($file) { - include $file; - }, null, null); - } -} diff --git a/kngil/vendor/composer/InstalledVersions.php b/kngil/vendor/composer/InstalledVersions.php deleted file mode 100644 index 2052022..0000000 --- a/kngil/vendor/composer/InstalledVersions.php +++ /dev/null @@ -1,396 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer; - -use Composer\Autoload\ClassLoader; -use Composer\Semver\VersionParser; - -/** - * This class is copied in every Composer installed project and available to all - * - * See also https://getcomposer.org/doc/07-runtime.md#installed-versions - * - * To require its presence, you can require `composer-runtime-api ^2.0` - * - * @final - */ -class InstalledVersions -{ - /** - * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to - * @internal - */ - private static $selfDir = null; - - /** - * @var mixed[]|null - * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null - */ - private static $installed; - - /** - * @var bool - */ - private static $installedIsLocalDir; - - /** - * @var bool|null - */ - private static $canGetVendors; - - /** - * @var array[] - * @psalm-var array}> - */ - private static $installedByVendor = array(); - - /** - * Returns a list of all package names which are present, either by being installed, replaced or provided - * - * @return string[] - * @psalm-return list - */ - public static function getInstalledPackages() - { - $packages = array(); - foreach (self::getInstalled() as $installed) { - $packages[] = array_keys($installed['versions']); - } - - if (1 === \count($packages)) { - return $packages[0]; - } - - return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); - } - - /** - * Returns a list of all package names with a specific type e.g. 'library' - * - * @param string $type - * @return string[] - * @psalm-return list - */ - public static function getInstalledPackagesByType($type) - { - $packagesByType = array(); - - foreach (self::getInstalled() as $installed) { - foreach ($installed['versions'] as $name => $package) { - if (isset($package['type']) && $package['type'] === $type) { - $packagesByType[] = $name; - } - } - } - - return $packagesByType; - } - - /** - * Checks whether the given package is installed - * - * This also returns true if the package name is provided or replaced by another package - * - * @param string $packageName - * @param bool $includeDevRequirements - * @return bool - */ - public static function isInstalled($packageName, $includeDevRequirements = true) - { - foreach (self::getInstalled() as $installed) { - if (isset($installed['versions'][$packageName])) { - return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; - } - } - - return false; - } - - /** - * Checks whether the given package satisfies a version constraint - * - * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: - * - * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') - * - * @param VersionParser $parser Install composer/semver to have access to this class and functionality - * @param string $packageName - * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package - * @return bool - */ - public static function satisfies(VersionParser $parser, $packageName, $constraint) - { - $constraint = $parser->parseConstraints((string) $constraint); - $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); - - return $provided->matches($constraint); - } - - /** - * Returns a version constraint representing all the range(s) which are installed for a given package - * - * It is easier to use this via isInstalled() with the $constraint argument if you need to check - * whether a given version of a package is installed, and not just whether it exists - * - * @param string $packageName - * @return string Version constraint usable with composer/semver - */ - public static function getVersionRanges($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - $ranges = array(); - if (isset($installed['versions'][$packageName]['pretty_version'])) { - $ranges[] = $installed['versions'][$packageName]['pretty_version']; - } - if (array_key_exists('aliases', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); - } - if (array_key_exists('replaced', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); - } - if (array_key_exists('provided', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); - } - - return implode(' || ', $ranges); - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present - */ - public static function getVersion($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['version'])) { - return null; - } - - return $installed['versions'][$packageName]['version']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present - */ - public static function getPrettyVersion($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['pretty_version'])) { - return null; - } - - return $installed['versions'][$packageName]['pretty_version']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference - */ - public static function getReference($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['reference'])) { - return null; - } - - return $installed['versions'][$packageName]['reference']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. - */ - public static function getInstallPath($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @return array - * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} - */ - public static function getRootPackage() - { - $installed = self::getInstalled(); - - return $installed[0]['root']; - } - - /** - * Returns the raw installed.php data for custom implementations - * - * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. - * @return array[] - * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} - */ - public static function getRawData() - { - @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); - - if (null === self::$installed) { - // only require the installed.php file if this file is loaded from its dumped location, - // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 - if (substr(__DIR__, -8, 1) !== 'C') { - self::$installed = include __DIR__ . '/installed.php'; - } else { - self::$installed = array(); - } - } - - return self::$installed; - } - - /** - * Returns the raw data of all installed.php which are currently loaded for custom implementations - * - * @return array[] - * @psalm-return list}> - */ - public static function getAllRawData() - { - return self::getInstalled(); - } - - /** - * Lets you reload the static array from another file - * - * This is only useful for complex integrations in which a project needs to use - * this class but then also needs to execute another project's autoloader in process, - * and wants to ensure both projects have access to their version of installed.php. - * - * A typical case would be PHPUnit, where it would need to make sure it reads all - * the data it needs from this class, then call reload() with - * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure - * the project in which it runs can then also use this class safely, without - * interference between PHPUnit's dependencies and the project's dependencies. - * - * @param array[] $data A vendor/composer/installed.php data set - * @return void - * - * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data - */ - public static function reload($data) - { - self::$installed = $data; - self::$installedByVendor = array(); - - // when using reload, we disable the duplicate protection to ensure that self::$installed data is - // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, - // so we have to assume it does not, and that may result in duplicate data being returned when listing - // all installed packages for example - self::$installedIsLocalDir = false; - } - - /** - * @return string - */ - private static function getSelfDir() - { - if (self::$selfDir === null) { - self::$selfDir = strtr(__DIR__, '\\', '/'); - } - - return self::$selfDir; - } - - /** - * @return array[] - * @psalm-return list}> - */ - private static function getInstalled() - { - if (null === self::$canGetVendors) { - self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); - } - - $installed = array(); - $copiedLocalDir = false; - - if (self::$canGetVendors) { - $selfDir = self::getSelfDir(); - foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { - $vendorDir = strtr($vendorDir, '\\', '/'); - if (isset(self::$installedByVendor[$vendorDir])) { - $installed[] = self::$installedByVendor[$vendorDir]; - } elseif (is_file($vendorDir.'/composer/installed.php')) { - /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ - $required = require $vendorDir.'/composer/installed.php'; - self::$installedByVendor[$vendorDir] = $required; - $installed[] = $required; - if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { - self::$installed = $required; - self::$installedIsLocalDir = true; - } - } - if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { - $copiedLocalDir = true; - } - } - } - - if (null === self::$installed) { - // only require the installed.php file if this file is loaded from its dumped location, - // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 - if (substr(__DIR__, -8, 1) !== 'C') { - /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ - $required = require __DIR__ . '/installed.php'; - self::$installed = $required; - } else { - self::$installed = array(); - } - } - - if (self::$installed !== array() && !$copiedLocalDir) { - $installed[] = self::$installed; - } - - return $installed; - } -} diff --git a/kngil/vendor/composer/LICENSE b/kngil/vendor/composer/LICENSE deleted file mode 100644 index f27399a..0000000 --- a/kngil/vendor/composer/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - -Copyright (c) Nils Adermann, Jordi Boggiano - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - diff --git a/kngil/vendor/composer/autoload_classmap.php b/kngil/vendor/composer/autoload_classmap.php deleted file mode 100644 index d3bfaaf..0000000 --- a/kngil/vendor/composer/autoload_classmap.php +++ /dev/null @@ -1,12 +0,0 @@ - $vendorDir . '/composer/InstalledVersions.php', - 'Jumbojett\\OpenIDConnectClient' => $vendorDir . '/jumbojett/openid-connect-php/src/OpenIDConnectClient.php', - 'Jumbojett\\OpenIDConnectClientException' => $vendorDir . '/jumbojett/openid-connect-php/src/OpenIDConnectClient.php', -); diff --git a/kngil/vendor/composer/autoload_files.php b/kngil/vendor/composer/autoload_files.php deleted file mode 100644 index eec29ec..0000000 --- a/kngil/vendor/composer/autoload_files.php +++ /dev/null @@ -1,10 +0,0 @@ - $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php', -); diff --git a/kngil/vendor/composer/autoload_namespaces.php b/kngil/vendor/composer/autoload_namespaces.php deleted file mode 100644 index 15a2ff3..0000000 --- a/kngil/vendor/composer/autoload_namespaces.php +++ /dev/null @@ -1,9 +0,0 @@ - array($vendorDir . '/phpseclib/phpseclib/phpseclib'), - 'ParagonIE\\ConstantTime\\' => array($vendorDir . '/paragonie/constant_time_encoding/src'), -); diff --git a/kngil/vendor/composer/autoload_real.php b/kngil/vendor/composer/autoload_real.php deleted file mode 100644 index b743cac..0000000 --- a/kngil/vendor/composer/autoload_real.php +++ /dev/null @@ -1,50 +0,0 @@ -register(true); - - $filesToLoad = \Composer\Autoload\ComposerStaticInit7af56487bc504c75f0291ee804b479b9::$files; - $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { - if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; - - require $file; - } - }, null, null); - foreach ($filesToLoad as $fileIdentifier => $file) { - $requireFile($fileIdentifier, $file); - } - - return $loader; - } -} diff --git a/kngil/vendor/composer/autoload_static.php b/kngil/vendor/composer/autoload_static.php deleted file mode 100644 index 57f82da..0000000 --- a/kngil/vendor/composer/autoload_static.php +++ /dev/null @@ -1,50 +0,0 @@ - __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php', - ); - - public static $prefixLengthsPsr4 = array ( - 'p' => - array ( - 'phpseclib3\\' => 11, - ), - 'P' => - array ( - 'ParagonIE\\ConstantTime\\' => 23, - ), - ); - - public static $prefixDirsPsr4 = array ( - 'phpseclib3\\' => - array ( - 0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib', - ), - 'ParagonIE\\ConstantTime\\' => - array ( - 0 => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src', - ), - ); - - public static $classMap = array ( - 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', - 'Jumbojett\\OpenIDConnectClient' => __DIR__ . '/..' . '/jumbojett/openid-connect-php/src/OpenIDConnectClient.php', - 'Jumbojett\\OpenIDConnectClientException' => __DIR__ . '/..' . '/jumbojett/openid-connect-php/src/OpenIDConnectClient.php', - ); - - public static function getInitializer(ClassLoader $loader) - { - return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit7af56487bc504c75f0291ee804b479b9::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit7af56487bc504c75f0291ee804b479b9::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInit7af56487bc504c75f0291ee804b479b9::$classMap; - - }, null, ClassLoader::class); - } -} diff --git a/kngil/vendor/composer/installed.json b/kngil/vendor/composer/installed.json deleted file mode 100644 index 9892237..0000000 --- a/kngil/vendor/composer/installed.json +++ /dev/null @@ -1,289 +0,0 @@ -{ - "packages": [ - { - "name": "jumbojett/openid-connect-php", - "version": "v1.0.2", - "version_normalized": "1.0.2.0", - "source": { - "type": "git", - "url": "https://github.com/jumbojett/OpenID-Connect-PHP.git", - "reference": "f327e7eb0626d55ddb6abc7b7c9e6ad3af4e5d51" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jumbojett/OpenID-Connect-PHP/zipball/f327e7eb0626d55ddb6abc7b7c9e6ad3af4e5d51", - "reference": "f327e7eb0626d55ddb6abc7b7c9e6ad3af4e5d51", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "ext-json": "*", - "php": ">=7.0", - "phpseclib/phpseclib": "^3.0.7" - }, - "require-dev": { - "phpunit/phpunit": "<10", - "roave/security-advisories": "dev-latest", - "yoast/phpunit-polyfills": "^2.0" - }, - "time": "2024-09-13T07:08:11+00:00", - "type": "library", - "installation-source": "source", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "description": "Bare-bones OpenID Connect client", - "support": { - "issues": "https://github.com/jumbojett/OpenID-Connect-PHP/issues", - "source": "https://github.com/jumbojett/OpenID-Connect-PHP/tree/v1.0.2" - }, - "install-path": "../jumbojett/openid-connect-php" - }, - { - "name": "paragonie/constant_time_encoding", - "version": "v3.1.3", - "version_normalized": "3.1.3.0", - "source": { - "type": "git", - "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", - "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", - "shasum": "" - }, - "require": { - "php": "^8" - }, - "require-dev": { - "infection/infection": "^0", - "nikic/php-fuzzer": "^0", - "phpunit/phpunit": "^9|^10|^11", - "vimeo/psalm": "^4|^5|^6" - }, - "time": "2025-09-24T15:06:41+00:00", - "type": "library", - "installation-source": "source", - "autoload": { - "psr-4": { - "ParagonIE\\ConstantTime\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com", - "role": "Maintainer" - }, - { - "name": "Steve 'Sc00bz' Thomas", - "email": "steve@tobtu.com", - "homepage": "https://www.tobtu.com", - "role": "Original Developer" - } - ], - "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", - "keywords": [ - "base16", - "base32", - "base32_decode", - "base32_encode", - "base64", - "base64_decode", - "base64_encode", - "bin2hex", - "encoding", - "hex", - "hex2bin", - "rfc4648" - ], - "support": { - "email": "info@paragonie.com", - "issues": "https://github.com/paragonie/constant_time_encoding/issues", - "source": "https://github.com/paragonie/constant_time_encoding" - }, - "install-path": "../paragonie/constant_time_encoding" - }, - { - "name": "paragonie/random_compat", - "version": "v9.99.100", - "version_normalized": "9.99.100.0", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", - "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", - "shasum": "" - }, - "require": { - "php": ">= 7" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*", - "vimeo/psalm": "^1" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - }, - "time": "2020-10-15T08:29:30+00:00", - "type": "library", - "installation-source": "source", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" - ], - "support": { - "email": "info@paragonie.com", - "issues": "https://github.com/paragonie/random_compat/issues", - "source": "https://github.com/paragonie/random_compat" - }, - "install-path": "../paragonie/random_compat" - }, - { - "name": "phpseclib/phpseclib", - "version": "3.0.49", - "version_normalized": "3.0.49.0", - "source": { - "type": "git", - "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/6233a1e12584754e6b5daa69fe1289b47775c1b9", - "reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9", - "shasum": "" - }, - "require": { - "paragonie/constant_time_encoding": "^1|^2|^3", - "paragonie/random_compat": "^1.4|^2.0|^9.99.99", - "php": ">=5.6.1" - }, - "require-dev": { - "phpunit/phpunit": "*" - }, - "suggest": { - "ext-dom": "Install the DOM extension to load XML formatted public keys.", - "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", - "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", - "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", - "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." - }, - "time": "2026-01-27T09:17:28+00:00", - "type": "library", - "installation-source": "source", - "autoload": { - "files": [ - "phpseclib/bootstrap.php" - ], - "psr-4": { - "phpseclib3\\": "phpseclib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jim Wigginton", - "email": "terrafrost@php.net", - "role": "Lead Developer" - }, - { - "name": "Patrick Monnerat", - "email": "pm@datasphere.ch", - "role": "Developer" - }, - { - "name": "Andreas Fischer", - "email": "bantu@phpbb.com", - "role": "Developer" - }, - { - "name": "Hans-Jürgen Petrich", - "email": "petrich@tronic-media.com", - "role": "Developer" - }, - { - "name": "Graham Campbell", - "email": "graham@alt-three.com", - "role": "Developer" - } - ], - "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", - "homepage": "http://phpseclib.sourceforge.net", - "keywords": [ - "BigInteger", - "aes", - "asn.1", - "asn1", - "blowfish", - "crypto", - "cryptography", - "encryption", - "rsa", - "security", - "sftp", - "signature", - "signing", - "ssh", - "twofish", - "x.509", - "x509" - ], - "support": { - "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.49" - }, - "funding": [ - { - "url": "https://github.com/terrafrost", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpseclib", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", - "type": "tidelift" - } - ], - "install-path": "../phpseclib/phpseclib" - } - ], - "dev": true, - "dev-package-names": [] -} diff --git a/kngil/vendor/composer/installed.php b/kngil/vendor/composer/installed.php deleted file mode 100644 index f3a9e61..0000000 --- a/kngil/vendor/composer/installed.php +++ /dev/null @@ -1,59 +0,0 @@ - array( - 'name' => 'kngil/oidc', - 'pretty_version' => 'dev-main', - 'version' => 'dev-main', - 'reference' => '21b6332c9c3d52f223e0e57d2b5c7ff633469256', - 'type' => 'project', - 'install_path' => __DIR__ . '/../../', - 'aliases' => array(), - 'dev' => true, - ), - 'versions' => array( - 'jumbojett/openid-connect-php' => array( - 'pretty_version' => 'v1.0.2', - 'version' => '1.0.2.0', - 'reference' => 'f327e7eb0626d55ddb6abc7b7c9e6ad3af4e5d51', - 'type' => 'library', - 'install_path' => __DIR__ . '/../jumbojett/openid-connect-php', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'kngil/oidc' => array( - 'pretty_version' => 'dev-main', - 'version' => 'dev-main', - 'reference' => '21b6332c9c3d52f223e0e57d2b5c7ff633469256', - 'type' => 'project', - 'install_path' => __DIR__ . '/../../', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'paragonie/constant_time_encoding' => array( - 'pretty_version' => 'v3.1.3', - 'version' => '3.1.3.0', - 'reference' => 'd5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77', - 'type' => 'library', - 'install_path' => __DIR__ . '/../paragonie/constant_time_encoding', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'paragonie/random_compat' => array( - 'pretty_version' => 'v9.99.100', - 'version' => '9.99.100.0', - 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a', - 'type' => 'library', - 'install_path' => __DIR__ . '/../paragonie/random_compat', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'phpseclib/phpseclib' => array( - 'pretty_version' => '3.0.49', - 'version' => '3.0.49.0', - 'reference' => '6233a1e12584754e6b5daa69fe1289b47775c1b9', - 'type' => 'library', - 'install_path' => __DIR__ . '/../phpseclib/phpseclib', - 'aliases' => array(), - 'dev_requirement' => false, - ), - ), -); diff --git a/kngil/vendor/composer/platform_check.php b/kngil/vendor/composer/platform_check.php deleted file mode 100644 index a70ba47..0000000 --- a/kngil/vendor/composer/platform_check.php +++ /dev/null @@ -1,25 +0,0 @@ -= 80000)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.0". You are running ' . PHP_VERSION . '.'; -} - -if ($issues) { - if (!headers_sent()) { - header('HTTP/1.1 500 Internal Server Error'); - } - if (!ini_get('display_errors')) { - if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { - fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); - } elseif (!headers_sent()) { - echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; - } - } - throw new \RuntimeException( - 'Composer detected issues in your platform: ' . implode(' ', $issues) - ); -} diff --git a/kngil/vendor/jumbojett/openid-connect-php b/kngil/vendor/jumbojett/openid-connect-php deleted file mode 160000 index f327e7e..0000000 --- a/kngil/vendor/jumbojett/openid-connect-php +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f327e7eb0626d55ddb6abc7b7c9e6ad3af4e5d51 diff --git a/kngil/vendor/paragonie/constant_time_encoding b/kngil/vendor/paragonie/constant_time_encoding deleted file mode 160000 index d5b01a3..0000000 --- a/kngil/vendor/paragonie/constant_time_encoding +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77 diff --git a/kngil/vendor/paragonie/random_compat b/kngil/vendor/paragonie/random_compat deleted file mode 160000 index 996434e..0000000 --- a/kngil/vendor/paragonie/random_compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 996434e5492cb4c3edcb9168db6fbb1359ef965a diff --git a/kngil/vendor/phpseclib/phpseclib b/kngil/vendor/phpseclib/phpseclib deleted file mode 160000 index 6233a1e..0000000 --- a/kngil/vendor/phpseclib/phpseclib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6233a1e12584754e6b5daa69fe1289b47775c1b9 From 410b2b7b488bf67ccbd37dbf440dbe093ed960e8 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 12:40:02 +0900 Subject: [PATCH 03/21] Normalize admin routes and docker config --- .htaccess | 23 ++++ README.md | 10 +- docker-compose.yml | 11 +- docker/postgres/pg_hba.conf | 5 + index.php | 2 +- kngil/auth/oidc-callback.php | 242 ++++++++++++++++++++++++++++++++-- kngil/bbs/adm_guard.php | 4 +- kngil/css/common.css | 6 +- kngil/js/adm.js | 6 +- kngil/js/adm_comp copy.js | 16 +-- kngil/js/adm_comp.js | 18 +-- kngil/js/adm_faq_popup.js | 8 +- kngil/js/adm_product_popup.js | 8 +- kngil/js/adm_purch_popup.js | 2 +- kngil/js/adm_service copy.js | 2 +- kngil/js/adm_service.js | 8 +- kngil/js/adm_use_history.js | 2 +- kngil/js/common.js | 42 +++++- kngil/js/faq/faq_common.js | 4 + kngil/js/login.js | 17 --- kngil/js/qa/qa_common.js | 4 + kngil/log/join.log | 0 kngil/skin/_header.php | 121 +++++++++++++---- kngil/skin/faq_list.skin.php | 2 - kngil/skin/index.php | 7 +- kngil/skin/pop_login.php | 119 ----------------- kngil/skin/qa_detail.skin.php | 3 - kngil/skin/qa_list.skin.php | 2 - kngil/skin/qa_list.skin_.php | 2 - kngil/skin/qa_write.skin.php | 2 - 30 files changed, 467 insertions(+), 231 deletions(-) create mode 100644 .htaccess create mode 100644 docker/postgres/pg_hba.conf mode change 100644 => 100755 kngil/log/join.log delete mode 100644 kngil/skin/pop_login.php diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..17c9d95 --- /dev/null +++ b/.htaccess @@ -0,0 +1,23 @@ +RewriteEngine On + +# Skip existing files and directories. +RewriteCond %{REQUEST_FILENAME} -f [OR] +RewriteCond %{REQUEST_FILENAME} -d +RewriteRule ^ - [L] + +# Admin UI +RewriteRule ^admin/?$ /kngil/skin/adm.php [L] +RewriteRule ^admin/company/?$ /kngil/skin/adm_comp.php [L] + +# Admin APIs +RewriteRule ^admin/api/super/?$ /kngil/bbs/adm.php [QSA,L] +RewriteRule ^admin/api/company/?$ /kngil/bbs/adm_comp.php [QSA,L] +RewriteRule ^admin/api/service/?$ /kngil/bbs/adm_service.php [QSA,L] +RewriteRule ^admin/api/purchase-history/?$ /kngil/bbs/adm_purch_popup.php [QSA,L] +RewriteRule ^admin/api/use-history/?$ /kngil/bbs/adm_use_history.php [QSA,L] +RewriteRule ^admin/api/product/?$ /kngil/bbs/adm_product_popup.php [QSA,L] +RewriteRule ^admin/api/product/save/?$ /kngil/bbs/adm_product_popup_save.php [QSA,L] +RewriteRule ^admin/api/product/delete/?$ /kngil/bbs/adm_product_popup_delete.php [QSA,L] +RewriteRule ^admin/api/faq/?$ /kngil/bbs/adm_faq_popup.php [QSA,L] +RewriteRule ^admin/api/faq/save/?$ /kngil/bbs/adm_faq_popup_save.php [QSA,L] +RewriteRule ^admin/api/faq/delete/?$ /kngil/bbs/adm_faq_popup_delete.php [QSA,L] diff --git a/README.md b/README.md index e626864..6db302e 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,19 @@ ## 빠른 시작 ```bash -docker compose up --build +docker compose up -d --build ``` - 접속: `http://localhost:8080` +```bash +docker compose down +``` ## 환경변수 `docker-compose.yml`에서 기본값을 사용하며, 필요 시 `.env`로 덮어쓸 수 있습니다. - `DB_HOST` (기본값: `db`) -- `DB_PORT` (기본값: `5432`) +- `DB_PORT` (기본값: `5432`) - 웹 컨테이너가 DB에 접속할 때 사용하는 포트 +- `DB_HOST_PORT` (기본값: `5432`) - 외부에서 포트포워딩으로 접속할 때 사용하는 호스트 포트 - `DB_NAME` (기본값: `kngil`) - `DB_USER` (기본값: `postgres`) - `DB_PASS` (기본값: `postgres`) @@ -43,5 +47,5 @@ docker compose down -v - `kngil/bbs/sales_results.php`는 410 응답으로 비활성 처리되어 있습니다. ## PostgreSQL 이미지 버전 -- 기본값은 `postgres:18`입니다. +- 기본값은 `postgres:16`입니다. - 이미지 풀 실패 시 `docker-compose.yml`의 태그를 사용 가능한 버전으로 변경하세요. diff --git a/docker-compose.yml b/docker-compose.yml index 60cbc39..f4d9872 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,8 +21,14 @@ services: db: image: postgres:16 - # ports: - # - "5432:5432" + ports: + - "0.0.0.0:${DB_HOST_PORT:-5432}:5432" + command: + - "postgres" + - "-c" + - "listen_addresses=*" + - "-c" + - "hba_file=/etc/postgresql/pg_hba.conf" environment: POSTGRES_DB: ${DB_NAME:-kngil} POSTGRES_USER: ${DB_USER:-postgres} @@ -30,6 +36,7 @@ services: volumes: - db_data:/var/lib/postgresql/data - ./docker/initdb/01_kngil_DB.sql:/docker-entrypoint-initdb.d/01_kngil_DB.sql:ro + - ./docker/postgres/pg_hba.conf:/etc/postgresql/pg_hba.conf:ro volumes: db_data: diff --git a/docker/postgres/pg_hba.conf b/docker/postgres/pg_hba.conf new file mode 100644 index 0000000..fa5505e --- /dev/null +++ b/docker/postgres/pg_hba.conf @@ -0,0 +1,5 @@ +# +# Allow TCP connections. Narrow the address range in production. +# +host all all 0.0.0.0/0 scram-sha-256 +host all all ::/0 scram-sha-256 diff --git a/index.php b/index.php index 3fe7bac..4df456f 100644 --- a/index.php +++ b/index.php @@ -7,7 +7,7 @@ declare(strict_types=1); // 1. 기본 상수 // --------------------------------- define('ROOT', __DIR__); -define('SKIN_PATH', ROOT.'/skin'); +define('SKIN_PATH', ROOT.'/kngil/skin'); // --------------------------------- // 2. 페이지 결정 diff --git a/kngil/auth/oidc-callback.php b/kngil/auth/oidc-callback.php index 60934e6..eacdee0 100644 --- a/kngil/auth/oidc-callback.php +++ b/kngil/auth/oidc-callback.php @@ -1,12 +1,17 @@ setRedirectURL($config['redirect_url']); try { + $stmt = $pdo->query("SELECT to_regclass('kngil.users') AS reg"); + $reg = $stmt ? $stmt->fetchColumn() : null; + if (!$reg) { + $stmt = $pdo->query("SELECT to_regclass('public.users') AS reg"); + $reg = $stmt ? $stmt->fetchColumn() : null; + if ($reg) { + $usersTable = 'public.users'; + $membersTable = 'public.members'; + } else { + throw new Exception( + "사용자 테이블을 찾을 수 없습니다. DB 초기화가 필요합니다. " + . "docker compose down -v 후 다시 실행하거나, " + . "DB_NAME/DB_USER/DB_PASS 설정을 확인하세요." + ); + } + } + + $memberReg = $pdo->query("SELECT to_regclass('{$membersTable}') AS reg"); + $memberReg = $memberReg ? $memberReg->fetchColumn() : null; + if (!$memberReg) { + $altMembersTable = $membersTable === 'kngil.members' ? 'public.members' : 'kngil.members'; + $memberReg = $pdo->query("SELECT to_regclass('{$altMembersTable}') AS reg"); + $memberReg = $memberReg ? $memberReg->fetchColumn() : null; + if ($memberReg) { + $membersTable = $altMembersTable; + } else { + throw new Exception("회원 테이블을 찾을 수 없습니다. DB 초기화가 필요합니다."); + } + } + + $pdo->exec("ALTER TABLE {$usersTable} ADD COLUMN IF NOT EXISTS oidc_sub VARCHAR(255) UNIQUE"); + if (!$oidc->authenticate()) { throw new Exception("Authentication failed"); } $userInfo = $oidc->requestUserInfo(); + $idToken = $oidc->getIdToken(); + $accessToken = $oidc->getAccessToken(); + $jwtClaims = []; + if (!empty($idToken)) { + $parts = explode('.', $idToken); + if (count($parts) >= 2) { + $payload = strtr($parts[1], '-_', '+/'); + $padding = 4 - (strlen($payload) % 4); + if ($padding < 4) { + $payload .= str_repeat('=', $padding); + } + $decoded = json_decode(base64_decode($payload), true); + if (is_array($decoded)) { + $jwtClaims = $decoded; + } + } + } + + // 디버그용: ID 토큰 확보 여부 로그 출력 (파일) + $logDir = dirname(__DIR__) . '/log'; + if (!is_dir($logDir)) { + @mkdir($logDir, 0775, true); + } + $logPath = $logDir . '/oidc_debug.log'; + if (!is_writable($logDir)) { + $logPath = '/tmp/oidc_debug.log'; + error_log('[OIDC_DEBUG] log_dir_not_writable, fallback=/tmp/oidc_debug.log'); + } + $tokenInfo = empty($idToken) ? 'MISSING' : ('PRESENT len=' . strlen($idToken)); + $claimKeys = empty($jwtClaims) ? 'none' : implode(',', array_keys($jwtClaims)); + $logLine = sprintf( + "[%s] host=%s uri=%s sid=%s id_token=%s claims=%s\n", + date('c'), + $_SERVER['HTTP_HOST'] ?? '-', + $_SERVER['REQUEST_URI'] ?? '-', + session_id(), + $tokenInfo, + $claimKeys + ); + $writeOk = @file_put_contents($logPath, $logLine, FILE_APPEND); + if ($writeOk === false) { + error_log('[OIDC_DEBUG] log_write_failed path=' . $logPath); + } + // 디버그용: userInfo/claims 전체 덤프 (토큰 제외) + $dump = [ + 'userInfo' => $userInfo, + 'jwtClaims' => $jwtClaims + ]; + $dumpLine = sprintf( + "[%s] oidc_dump=%s\n", + date('c'), + json_encode($dump, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) + ); + $dumpOk = @file_put_contents($logPath, $dumpLine, FILE_APPEND); + if ($dumpOk === false) { + error_log('[OIDC_DEBUG] dump_write_failed path=' . $logPath); + } + // 도커 로그로도 출력 + error_log('[OIDC_DEBUG] ' . $dumpLine); // $userInfo 에 포함된 데이터 예시: sub, email, name, preferred_username 등 $email = $userInfo->email ?? null; $sub = $userInfo->sub ?? null; // IDP 고유 식별자 - $name = $userInfo->name ?? ($userInfo->preferred_username ?? 'Unknown'); + $preferred = $userInfo->preferred_username ?? null; + $name = $userInfo->name ?? null; + if (!$email && isset($jwtClaims['email'])) { + $email = $jwtClaims['email']; + } + if (!$name && isset($jwtClaims['name'])) { + $name = $jwtClaims['name']; + } + if (!$name && $preferred) { + $name = $preferred; + } + if (!$name && $email) { + $name = $email; + } + if (!$name && $sub) { + $seed = strtolower(preg_replace('/[^a-z0-9]/', '', (string)$sub)); + $name = 'oidc_' . substr($seed, 0, 10); + } if (!$email && !$sub) { throw new Exception("IDP provided insufficient user information."); @@ -33,7 +146,7 @@ try { // 1. 사용자 매핑 (sub 또는 email 기준) $stmt = $pdo->prepare(" - SELECT * FROM kngil.users + SELECT * FROM {$usersTable} WHERE (oidc_sub = :sub OR LOWER(email) = LOWER(:email)) AND use_yn = 'Y' LIMIT 1 @@ -42,14 +155,99 @@ try { $user = $stmt->fetch(PDO::FETCH_ASSOC); if (!$user) { - // [정책 선택] 새 사용자 자동 생성 또는 로그인 거부 - // 여기서는 예시로 로그인 거부 처리 - throw new Exception("등록되지 않은 사용자입니다. 관리자에게 문의하세요. (IDP: $email)"); + $defaultMemberId = getenv('OIDC_DEFAULT_MEMBER_ID') ?: ''; + if ($defaultMemberId !== '') { + $checkMember = $pdo->prepare("SELECT 1 FROM {$membersTable} WHERE member_id = :member_id LIMIT 1"); + $checkMember->execute([':member_id' => $defaultMemberId]); + if (!$checkMember->fetchColumn()) { + throw new Exception("OIDC_DEFAULT_MEMBER_ID가 members에 존재하지 않습니다: {$defaultMemberId}"); + } + } else { + $memberStmt = $pdo->query("SELECT member_id FROM {$membersTable} ORDER BY member_id ASC LIMIT 1"); + $defaultMemberId = $memberStmt ? $memberStmt->fetchColumn() : ''; + if (!$defaultMemberId) { + throw new Exception("기본 member_id를 찾을 수 없습니다. OIDC_DEFAULT_MEMBER_ID를 설정하세요."); + } + } + + $defaultAuth = getenv('OIDC_DEFAULT_AUTH_BC') ?: 'BS100500'; + + $baseId = $userInfo->preferred_username ?? ($email ? explode('@', $email)[0] : ''); + $baseId = strtolower(preg_replace('/[^a-z0-9]/', '', $baseId)); + if ($baseId === '') { + $seed = strtolower(preg_replace('/[^a-z0-9]/', '', (string)($sub ?? 'oidc'))); + $baseId = 'oidc' . substr($seed, 0, 10); + } + $baseId = substr($baseId, 0, 16); + $userId = $baseId; + + $existsStmt = $pdo->prepare("SELECT 1 FROM {$usersTable} WHERE LOWER(user_id) = LOWER(:user_id) LIMIT 1"); + $suffix = 1; + while (true) { + $existsStmt->execute([':user_id' => $userId]); + if (!$existsStmt->fetchColumn()) { + break; + } + $tail = sprintf('%02d', $suffix); + $userId = substr($baseId, 0, 20 - strlen($tail)) . $tail; + $suffix++; + if ($suffix > 99) { + $userId = 'oidc' . bin2hex(random_bytes(4)); + $userId = substr($userId, 0, 20); + } + } + + $userNm = $name ?: ($email ?: $userId); + $rawPhone = $userInfo->phone_number ?? ''; + $digits = preg_replace('/\D/', '', $rawPhone); + if (strlen($digits) === 11) { + $telNo = substr($digits, 0, 3) . '-' . substr($digits, 3, 4) . '-' . substr($digits, 7, 4); + } elseif (strlen($digits) === 10) { + $telNo = substr($digits, 0, 3) . '-' . substr($digits, 3, 3) . '-' . substr($digits, 6, 4); + } else { + $telNo = '000-0000-0000'; + } + + $insert = $pdo->prepare(" + INSERT INTO {$usersTable} ( + member_id, user_id, user_pw, user_nm, + dept_nm, posit_nm, tel_no, email, + auth_bc, use_yn, rmks, + cid, cdt, mid, mdt, oidc_sub + ) VALUES ( + :member_id, :user_id, NULL, :user_nm, + :dept_nm, :posit_nm, :tel_no, :email, + :auth_bc, 'Y', :rmks, + :cid, CURRENT_TIMESTAMP, :mid, CURRENT_TIMESTAMP, :oidc_sub + ) + "); + $insert->execute([ + ':member_id' => $defaultMemberId, + ':user_id' => $userId, + ':user_nm' => $userNm, + ':dept_nm' => $userInfo->department ?? null, + ':posit_nm' => $userInfo->title ?? null, + ':tel_no' => $telNo, + ':email' => $email, + ':auth_bc' => $defaultAuth, + ':rmks' => 'OIDC auto-registered', + ':cid' => $userId, + ':mid' => $userId, + ':oidc_sub' => $sub + ]); + + $stmt = $pdo->prepare(" + SELECT * FROM {$usersTable} + WHERE LOWER(user_id) = LOWER(:user_id) + LIMIT 1 + "); + $stmt->execute([':user_id' => $userId]); + $user = $stmt->fetch(PDO::FETCH_ASSOC); } // 2. oidc_sub 업데이트 (최초 연동 시) if (empty($user['oidc_sub']) && $sub) { - $upd = $pdo->prepare("UPDATE kngil.users SET oidc_sub = :sub WHERE user_id = :id"); + $upd = $pdo->prepare("UPDATE {$usersTable} SET oidc_sub = :sub WHERE user_id = :id"); $upd->execute([':sub' => $sub, ':id' => $user['user_id']]); } @@ -63,20 +261,42 @@ try { 'dept_nm' => $user['dept_nm'] ?? null, 'tel_no' => $user['tel_no'] ?? null, 'email' => $user['email'] ?? null, + 'idp_name' => $name ?: null, + 'idp_email' => $email ?? null, + 'idp_id_token' => $idToken ?? null, + 'idp_access_token' => $accessToken ?? null, + 'idp_claims' => $jwtClaims ?? null, 'oidc_mode' => true // OIDC 로그인을 나타내는 플래그 ]; - // 로그인 완료 후 부모 창에 알리고 종료 + session_write_close(); + + // 로그인 완료 후 부모 창에 알리고 종료 (팝업이 아닐 경우 메인으로 이동) ?> + res.json()) // .then(d => { // if (d.status !== 'success') { @@ -243,7 +243,7 @@ function formatBizNo(value) { 상단 회사 목록 로드 ---------------------------------------- */ function loadCompanies() { - fetch('/kngil/bbs/adm.php') + fetch('/admin/api/super') .then(res => res.json()) .then(json => { if (!json.records) return @@ -340,7 +340,7 @@ export function bindSaveButton() { return } - fetch('/kngil/bbs/adm.php?action=save', { + fetch('/admin/api/super?action=save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ diff --git a/kngil/js/adm_comp copy.js b/kngil/js/adm_comp copy.js index b4e9589..6b224a7 100644 --- a/kngil/js/adm_comp copy.js +++ b/kngil/js/adm_comp copy.js @@ -9,7 +9,7 @@ function destroyGrid(name) { } function loadBaseCode(mainCd) { - return fetch(`/kngil/bbs/adm_comp.php?action=base_code&main_cd=${mainCd}`) + return fetch(`/admin/api/company?action=base_code&main_cd=${mainCd}`) .then(res => res.json()) .then(json => { if (json.status !== 'success') { @@ -144,7 +144,7 @@ export async function createUserGrid(boxId, options = {}) { } function loadUsers() { - fetch('/kngil/bbs/adm_comp.php') + fetch('/admin/api/company') .then(res => res.text()) // 🔥 먼저 text로 확인 .then(text => { try { @@ -168,7 +168,7 @@ export function loadUsersByMember(member_id) { return } - fetch('/kngil/bbs/adm_comp.php') + fetch('/admin/api/company') .then(res => res.json()) .then(json => { g.clear() @@ -198,7 +198,7 @@ export function setUserGridMode(mode = 'view') { export function loadData({ loadSummary = true } = {}) { - fetch('/kngil/bbs/adm_comp.php') + fetch('/admin/api/company') .then(res => res.json()) .then(async d => { @@ -323,7 +323,7 @@ document.getElementById('btnSave_comp')?.addEventListener('click', () => { console.log('INSERTS', inserts) console.log('UPDATES', updates) - fetch('/kngil/bbs/adm_comp.php', { + fetch('/admin/api/company', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -408,7 +408,7 @@ document.getElementById('btnDelete')?.addEventListener('click', () => { w2confirm(`선택한 ${ids.length}명의 사용자를 삭제하시겠습니까?`) .yes(() => { - fetch('/kngil/bbs/adm_comp.php', { + fetch('/admin/api/company', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -436,7 +436,7 @@ document.getElementById('btnDelete')?.addEventListener('click', () => { }) function loadTotalArea(memberId) { - return fetch(`/kngil/bbs/adm_comp.php?action=total_area&member_id=${memberId}`) + return fetch(`/admin/api/company?action=total_area&member_id=${memberId}`) .then(res => res.json()) .then(json => { if (json.status !== 'success') { @@ -467,7 +467,7 @@ function doSearch() { } // ⚠️ type === 'id' 는 DB로 안 보냄 - fetch(`/kngil/bbs/adm_comp.php?action=list` + fetch(`/admin/api/company?action=list` + `&user_nm=${encodeURIComponent(p_user_nm)}` + `&dept_nm=${encodeURIComponent(p_dept_nm)}` + `&use_yn=${useYn}` diff --git a/kngil/js/adm_comp.js b/kngil/js/adm_comp.js index c7eadd8..90991a7 100644 --- a/kngil/js/adm_comp.js +++ b/kngil/js/adm_comp.js @@ -21,7 +21,7 @@ function destroyGrid(name) { } function loadBaseCode(mainCd) { - return fetch(`/kngil/bbs/adm_comp.php?action=base_code&main_cd=${mainCd}`) + return fetch(`/admin/api/company?action=base_code&main_cd=${mainCd}`) .then(res => res.json()) .then(json => { if (json.status !== 'success') { @@ -251,7 +251,7 @@ export function loadUsersByMember(memberId) { return } - fetch(`/kngil/bbs/adm_comp.php?action=list&member_id=${memberId}`) + fetch(`/admin/api/company?action=list&member_id=${memberId}`) .then(res => res.json()) .then(d => { @@ -285,7 +285,7 @@ export function setUserGridMode(mode = 'view') { } export function loadData({ loadSummary = true } = {}) { - fetch('/kngil/bbs/adm_comp.php?action=list') + fetch('/admin/api/company?action=list') .then(res => res.json()) .then(async d => { @@ -404,7 +404,7 @@ document.getElementById('btnSave_comp')?.addEventListener('click', () => { console.log('INSERTS', inserts) console.log('UPDATES', updates) - fetch('/kngil/bbs/adm_comp.php', { + fetch('/admin/api/company', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -489,7 +489,7 @@ document.getElementById('btnDelete')?.addEventListener('click', () => { w2confirm(`선택한 ${ids.length}명의 사용자를 삭제하시겠습니까?`) .yes(() => { - fetch('/kngil/bbs/adm_comp.php', { + fetch('/admin/api/company', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -517,7 +517,7 @@ document.getElementById('btnDelete')?.addEventListener('click', () => { }) function loadTotalArea(memberId) { - return fetch(`/kngil/bbs/adm_comp.php?action=total_area`) + return fetch(`/admin/api/company?action=total_area`) .then(res => res.json()) .then(json => { if (json.status !== 'success') { @@ -548,7 +548,7 @@ function doSearch() { } // ⚠️ type === 'id' 는 DB로 안 보냄 - fetch(`/kngil/bbs/adm_comp.php?action=list` + fetch(`/admin/api/company?action=list` + `&user_nm=${encodeURIComponent(p_user_nm)}` + `&dept_nm=${encodeURIComponent(p_dept_nm)}` + `&use_yn=${useYn}` @@ -670,7 +670,7 @@ function openBulkCreatePopup(memberId) { function runBulkCreate(memberId, csvUrl) { - fetch('/kngil/bbs/adm_comp.php', { + fetch('/admin/api/company', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -771,7 +771,7 @@ function loadDataByMemberId(memberId) { return; } - fetch(`/kngil/bbs/adm_comp.php?action=list&member_id=${encodeURIComponent(memberId)}`) + fetch(`/admin/api/company?action=list&member_id=${encodeURIComponent(memberId)}`) .then(res => res.json()) .then(async d => { diff --git a/kngil/js/adm_faq_popup.js b/kngil/js/adm_faq_popup.js index fc59dc2..a2b8a8f 100644 --- a/kngil/js/adm_faq_popup.js +++ b/kngil/js/adm_faq_popup.js @@ -11,7 +11,7 @@ function destroyGrid(name) { } function loadBaseCode(mainCd) { - return fetch(`/kngil/bbs/adm_comp.php?action=base_code&main_cd=${mainCd}`) + return fetch(`/admin/api/company?action=base_code&main_cd=${mainCd}`) .then(res => res.json()) .then(json => { if (json.status !== 'success') { @@ -100,7 +100,7 @@ export function openfaqPopup() { // 3. 브라우저 기본 확인창 사용 (가장 확실함) if (confirm(`선택한 ${ids.length}개의 상품을 삭제하시겠습니까?`)) { - fetch('/kngil/bbs/adm_faq_popup_delete.php', { + fetch('/admin/api/faq/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'delete', ids: ids }) @@ -169,7 +169,7 @@ export function openfaqPopup() { console.log('INSERTS', inserts) console.log('UPDATES', updates) - fetch('/kngil/bbs/adm_faq_popup_save.php', { + fetch('/admin/api/faq/save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -270,7 +270,7 @@ async function loadfaqData() { try { w2ui.faqGrid.lock('조회 중...', true); - const response = await fetch('/kngil/bbs/adm_faq_popup.php'); // PHP 파일 호출 + const response = await fetch('/admin/api/faq'); // PHP 파일 호출 const data = await response.json(); w2ui.faqGrid.clear(); diff --git a/kngil/js/adm_product_popup.js b/kngil/js/adm_product_popup.js index 8e522fc..b91bec1 100644 --- a/kngil/js/adm_product_popup.js +++ b/kngil/js/adm_product_popup.js @@ -11,7 +11,7 @@ function destroyGrid(name) { } function loadBaseCode(mainCd) { - return fetch(`/kngil/bbs/adm_comp.php?action=base_code&main_cd=${mainCd}`) + return fetch(`/admin/api/company?action=base_code&main_cd=${mainCd}`) .then(res => res.json()) .then(json => { if (json.status !== 'success') { @@ -101,7 +101,7 @@ export function openProductPopup() { // 3. 브라우저 기본 확인창 사용 (가장 확실함) if (confirm(`선택한 ${ids.length}개의 상품을 삭제하시겠습니까?`)) { - fetch('/kngil/bbs/adm_product_popup_delete.php', { + fetch('/admin/api/product/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'delete', ids: ids }) @@ -171,7 +171,7 @@ export function openProductPopup() { console.log('INSERTS', inserts) console.log('UPDATES', updates) - fetch('/kngil/bbs/adm_product_popup_save.php', { + fetch('/admin/api/product/save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -265,7 +265,7 @@ async function loadProductData() { try { w2ui.productGrid.lock('조회 중...', true); - const response = await fetch('/kngil/bbs/adm_product_popup.php'); // PHP 파일 호출 + const response = await fetch('/admin/api/product'); // PHP 파일 호출 const data = await response.json(); w2ui.productGrid.clear(); diff --git a/kngil/js/adm_purch_popup.js b/kngil/js/adm_purch_popup.js index 2a44fc3..b6d770d 100644 --- a/kngil/js/adm_purch_popup.js +++ b/kngil/js/adm_purch_popup.js @@ -117,7 +117,7 @@ async function loadPurchaseHistoryData(memberId) { searchParams.append('fbuy_dt', ''); searchParams.append('tbuy_dt', ''); - const response = await fetch('/kngil/bbs/adm_purch_popup.php', { + const response = await fetch('/admin/api/purchase-history', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: searchParams diff --git a/kngil/js/adm_service copy.js b/kngil/js/adm_service copy.js index 5b778a6..b9d10f0 100644 --- a/kngil/js/adm_service copy.js +++ b/kngil/js/adm_service copy.js @@ -173,7 +173,7 @@ function addServiceFromProduct(p) { ------------------------------------------------- */ function loadExistingPurchase(memberId, buyDate) { - fetch(`/kngil/bbs/adm_service.php?member_id=${memberId}&buy_date=${buyDate}`) + fetch(`/admin/api/service?member_id=${memberId}&buy_date=${buyDate}`) .then(res => res.json()) .then(json => { diff --git a/kngil/js/adm_service.js b/kngil/js/adm_service.js index 71a4c05..f041d47 100644 --- a/kngil/js/adm_service.js +++ b/kngil/js/adm_service.js @@ -288,7 +288,7 @@ function createProductList() { new w2grid({ name: 'productList', box: '#productList', - url: '/kngil/bbs/adm_product_popup.php', + url: '/admin/api/product', columns: [ { field: 'itm_nm', text: '상품명', size: '120px' }, { @@ -413,7 +413,7 @@ function deleteServiceImmediately(row) { sq_no: row.sq_no }) - fetch('/kngil/bbs/adm_service.php', { + fetch('/admin/api/service', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -446,7 +446,7 @@ function isServiceItem(r) { ------------------------------------------------- */ function loadExistingPurchase(memberId, buyDate) { - fetch('/kngil/bbs/adm_service.php', { + fetch('/admin/api/service', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -606,7 +606,7 @@ function saveService(ctx) { _deleted: r._deleted || false })) - fetch('/kngil/bbs/adm_service.php', { + fetch('/admin/api/service', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ diff --git a/kngil/js/adm_use_history.js b/kngil/js/adm_use_history.js index 206ae04..c15d64c 100644 --- a/kngil/js/adm_use_history.js +++ b/kngil/js/adm_use_history.js @@ -131,7 +131,7 @@ async function loadUseHistoryData(memberId = ''){ searchParams.append('user_nm', sUnm); searchParams.append('dept_nm', sDnm); - const response = await fetch('/kngil/bbs/adm_use_history.php', { + const response = await fetch('/admin/api/use-history', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: searchParams diff --git a/kngil/js/common.js b/kngil/js/common.js index 846129c..ddd10a6 100644 --- a/kngil/js/common.js +++ b/kngil/js/common.js @@ -4286,4 +4286,44 @@ return lottie; */ var Swiper=function(){"use strict";function e(e){return null!==e&&"object"==typeof e&&"constructor"in e&&e.constructor===Object}function t(s,a){void 0===s&&(s={}),void 0===a&&(a={});const i=["__proto__","constructor","prototype"];Object.keys(a).filter((e=>i.indexOf(e)<0)).forEach((i=>{void 0===s[i]?s[i]=a[i]:e(a[i])&&e(s[i])&&Object.keys(a[i]).length>0&&t(s[i],a[i])}))}const s={body:{},addEventListener(){},removeEventListener(){},activeElement:{blur(){},nodeName:""},querySelector:()=>null,querySelectorAll:()=>[],getElementById:()=>null,createEvent:()=>({initEvent(){}}),createElement:()=>({children:[],childNodes:[],style:{},setAttribute(){},getElementsByTagName:()=>[]}),createElementNS:()=>({}),importNode:()=>null,location:{hash:"",host:"",hostname:"",href:"",origin:"",pathname:"",protocol:"",search:""}};function a(){const e="undefined"!=typeof document?document:{};return t(e,s),e}const i={document:s,navigator:{userAgent:""},location:{hash:"",host:"",hostname:"",href:"",origin:"",pathname:"",protocol:"",search:""},history:{replaceState(){},pushState(){},go(){},back(){}},CustomEvent:function(){return this},addEventListener(){},removeEventListener(){},getComputedStyle:()=>({getPropertyValue:()=>""}),Image(){},Date(){},screen:{},setTimeout(){},clearTimeout(){},matchMedia:()=>({}),requestAnimationFrame:e=>"undefined"==typeof setTimeout?(e(),null):setTimeout(e,0),cancelAnimationFrame(e){"undefined"!=typeof setTimeout&&clearTimeout(e)}};function r(){const e="undefined"!=typeof window?window:{};return t(e,i),e}function n(e){return void 0===e&&(e=""),e.trim().split(" ").filter((e=>!!e.trim()))}function l(e,t){return void 0===t&&(t=0),setTimeout(e,t)}function o(){return Date.now()}function d(e,t){void 0===t&&(t="x");const s=r();let a,i,n;const l=function(e){const t=r();let s;return t.getComputedStyle&&(s=t.getComputedStyle(e,null)),!s&&e.currentStyle&&(s=e.currentStyle),s||(s=e.style),s}(e);return s.WebKitCSSMatrix?(i=l.transform||l.webkitTransform,i.split(",").length>6&&(i=i.split(", ").map((e=>e.replace(",","."))).join(", ")),n=new s.WebKitCSSMatrix("none"===i?"":i)):(n=l.MozTransform||l.OTransform||l.MsTransform||l.msTransform||l.transform||l.getPropertyValue("transform").replace("translate(","matrix(1, 0, 0, 1,"),a=n.toString().split(",")),"x"===t&&(i=s.WebKitCSSMatrix?n.m41:16===a.length?parseFloat(a[12]):parseFloat(a[4])),"y"===t&&(i=s.WebKitCSSMatrix?n.m42:16===a.length?parseFloat(a[13]):parseFloat(a[5])),i||0}function c(e){return"object"==typeof e&&null!==e&&e.constructor&&"Object"===Object.prototype.toString.call(e).slice(8,-1)}function p(){const e=Object(arguments.length<=0?void 0:arguments[0]),t=["__proto__","constructor","prototype"];for(let a=1;at.indexOf(e)<0));for(let t=0,a=s.length;tn?"next":"prev",p=(e,t)=>"next"===c&&e>=t||"prev"===c&&e<=t,u=()=>{l=(new Date).getTime(),null===o&&(o=l);const e=Math.max(Math.min((l-o)/d,1),0),r=.5-Math.cos(e*Math.PI)/2;let c=n+r*(s-n);if(p(c,s)&&(c=s),t.wrapperEl.scrollTo({[a]:c}),p(c,s))return t.wrapperEl.style.overflow="hidden",t.wrapperEl.style.scrollSnapType="",setTimeout((()=>{t.wrapperEl.style.overflow="",t.wrapperEl.scrollTo({[a]:c})})),void i.cancelAnimationFrame(t.cssModeFrameID);t.cssModeFrameID=i.requestAnimationFrame(u)};u()}function h(e){return e.querySelector(".swiper-slide-transform")||e.shadowRoot&&e.shadowRoot.querySelector(".swiper-slide-transform")||e}function f(e,t){void 0===t&&(t="");const s=r(),a=[...e.children];return s.HTMLSlotElement&&e instanceof HTMLSlotElement&&a.push(...e.assignedElements()),t?a.filter((e=>e.matches(t))):a}function g(e){try{return void console.warn(e)}catch(e){}}function v(e,t){void 0===t&&(t=[]);const s=document.createElement(e);return s.classList.add(...Array.isArray(t)?t:n(t)),s}function w(e){const t=r(),s=a(),i=e.getBoundingClientRect(),n=s.body,l=e.clientTop||n.clientTop||0,o=e.clientLeft||n.clientLeft||0,d=e===t?t.scrollY:e.scrollTop,c=e===t?t.scrollX:e.scrollLeft;return{top:i.top+d-l,left:i.left+c-o}}function b(e,t){return r().getComputedStyle(e,null).getPropertyValue(t)}function y(e){let t,s=e;if(s){for(t=0;null!==(s=s.previousSibling);)1===s.nodeType&&(t+=1);return t}}function E(e,t){const s=[];let a=e.parentElement;for(;a;)t?a.matches(t)&&s.push(a):s.push(a),a=a.parentElement;return s}function x(e,t){t&&e.addEventListener("transitionend",(function s(a){a.target===e&&(t.call(e,a),e.removeEventListener("transitionend",s))}))}function S(e,t,s){const a=r();return s?e["width"===t?"offsetWidth":"offsetHeight"]+parseFloat(a.getComputedStyle(e,null).getPropertyValue("width"===t?"margin-right":"margin-top"))+parseFloat(a.getComputedStyle(e,null).getPropertyValue("width"===t?"margin-left":"margin-bottom")):e.offsetWidth}function T(e){return(Array.isArray(e)?e:[e]).filter((e=>!!e))}function M(e){return t=>Math.abs(t)>0&&e.browser&&e.browser.need3dFix&&Math.abs(t)%90==0?t+.001:t}let C,P,L;function I(){return C||(C=function(){const e=r(),t=a();return{smoothScroll:t.documentElement&&t.documentElement.style&&"scrollBehavior"in t.documentElement.style,touch:!!("ontouchstart"in e||e.DocumentTouch&&t instanceof e.DocumentTouch)}}()),C}function z(e){return void 0===e&&(e={}),P||(P=function(e){let{userAgent:t}=void 0===e?{}:e;const s=I(),a=r(),i=a.navigator.platform,n=t||a.navigator.userAgent,l={ios:!1,android:!1},o=a.screen.width,d=a.screen.height,c=n.match(/(Android);?[\s\/]+([\d.]+)?/);let p=n.match(/(iPad).*OS\s([\d_]+)/);const u=n.match(/(iPod)(.*OS\s([\d_]+))?/),m=!p&&n.match(/(iPhone\sOS|iOS)\s([\d_]+)/),h="Win32"===i;let f="MacIntel"===i;return!p&&f&&s.touch&&["1024x1366","1366x1024","834x1194","1194x834","834x1112","1112x834","768x1024","1024x768","820x1180","1180x820","810x1080","1080x810"].indexOf(`${o}x${d}`)>=0&&(p=n.match(/(Version)\/([\d.]+)/),p||(p=[0,1,"13_0_0"]),f=!1),c&&!h&&(l.os="android",l.android=!0),(p||m||u)&&(l.os="ios",l.ios=!0),l}(e)),P}function A(){return L||(L=function(){const e=r(),t=z();let s=!1;function a(){const t=e.navigator.userAgent.toLowerCase();return t.indexOf("safari")>=0&&t.indexOf("chrome")<0&&t.indexOf("android")<0}if(a()){const t=String(e.navigator.userAgent);if(t.includes("Version/")){const[e,a]=t.split("Version/")[1].split(" ")[0].split(".").map((e=>Number(e)));s=e<16||16===e&&a<2}}const i=/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(e.navigator.userAgent),n=a();return{isSafari:s||n,needPerspectiveFix:s,need3dFix:n||i&&t.ios,isWebView:i}}()),L}var $={on(e,t,s){const a=this;if(!a.eventsListeners||a.destroyed)return a;if("function"!=typeof t)return a;const i=s?"unshift":"push";return e.split(" ").forEach((e=>{a.eventsListeners[e]||(a.eventsListeners[e]=[]),a.eventsListeners[e][i](t)})),a},once(e,t,s){const a=this;if(!a.eventsListeners||a.destroyed)return a;if("function"!=typeof t)return a;function i(){a.off(e,i),i.__emitterProxy&&delete i.__emitterProxy;for(var s=arguments.length,r=new Array(s),n=0;n=0&&t.eventsAnyListeners.splice(s,1),t},off(e,t){const s=this;return!s.eventsListeners||s.destroyed?s:s.eventsListeners?(e.split(" ").forEach((e=>{void 0===t?s.eventsListeners[e]=[]:s.eventsListeners[e]&&s.eventsListeners[e].forEach(((a,i)=>{(a===t||a.__emitterProxy&&a.__emitterProxy===t)&&s.eventsListeners[e].splice(i,1)}))})),s):s},emit(){const e=this;if(!e.eventsListeners||e.destroyed)return e;if(!e.eventsListeners)return e;let t,s,a;for(var i=arguments.length,r=new Array(i),n=0;n{e.eventsAnyListeners&&e.eventsAnyListeners.length&&e.eventsAnyListeners.forEach((e=>{e.apply(a,[t,...s])})),e.eventsListeners&&e.eventsListeners[t]&&e.eventsListeners[t].forEach((e=>{e.apply(a,s)}))})),e}};const k=(e,t,s)=>{t&&!e.classList.contains(s)?e.classList.add(s):!t&&e.classList.contains(s)&&e.classList.remove(s)};const O=(e,t,s)=>{t&&!e.classList.contains(s)?e.classList.add(s):!t&&e.classList.contains(s)&&e.classList.remove(s)};const D=(e,t)=>{if(!e||e.destroyed||!e.params)return;const s=t.closest(e.isElement?"swiper-slide":`.${e.params.slideClass}`);if(s){let t=s.querySelector(`.${e.params.lazyPreloaderClass}`);!t&&e.isElement&&(s.shadowRoot?t=s.shadowRoot.querySelector(`.${e.params.lazyPreloaderClass}`):requestAnimationFrame((()=>{s.shadowRoot&&(t=s.shadowRoot.querySelector(`.${e.params.lazyPreloaderClass}`),t&&t.remove())}))),t&&t.remove()}},G=(e,t)=>{if(!e.slides[t])return;const s=e.slides[t].querySelector('[loading="lazy"]');s&&s.removeAttribute("loading")},X=e=>{if(!e||e.destroyed||!e.params)return;let t=e.params.lazyPreloadPrevNext;const s=e.slides.length;if(!s||!t||t<0)return;t=Math.min(t,s);const a="auto"===e.params.slidesPerView?e.slidesPerViewDynamic():Math.ceil(e.params.slidesPerView),i=e.activeIndex;if(e.params.grid&&e.params.grid.rows>1){const s=i,r=[s-t];return r.push(...Array.from({length:t}).map(((e,t)=>s+a+t))),void e.slides.forEach(((t,s)=>{r.includes(t.column)&&G(e,s)}))}const r=i+a-1;if(e.params.rewind||e.params.loop)for(let a=i-t;a<=r+t;a+=1){const t=(a%s+s)%s;(tr)&&G(e,t)}else for(let a=Math.max(i-t,0);a<=Math.min(r+t,s-1);a+=1)a!==i&&(a>r||a=0?x=parseFloat(x.replace("%",""))/100*r:"string"==typeof x&&(x=parseFloat(x)),e.virtualSize=-x,c.forEach((e=>{n?e.style.marginLeft="":e.style.marginRight="",e.style.marginBottom="",e.style.marginTop=""})),s.centeredSlides&&s.cssMode&&(u(a,"--swiper-centered-offset-before",""),u(a,"--swiper-centered-offset-after",""));const P=s.grid&&s.grid.rows>1&&e.grid;let L;P?e.grid.initSlides(c):e.grid&&e.grid.unsetSlides();const I="auto"===s.slidesPerView&&s.breakpoints&&Object.keys(s.breakpoints).filter((e=>void 0!==s.breakpoints[e].slidesPerView)).length>0;for(let a=0;a1&&m.push(e.virtualSize-r)}if(o&&s.loop){const t=g[0]+x;if(s.slidesPerGroup>1){const a=Math.ceil((e.virtual.slidesBefore+e.virtual.slidesAfter)/s.slidesPerGroup),i=t*s.slidesPerGroup;for(let e=0;e!(s.cssMode&&!s.loop)||t!==c.length-1)).forEach((e=>{e.style[t]=`${x}px`}))}if(s.centeredSlides&&s.centeredSlidesBounds){let e=0;g.forEach((t=>{e+=t+(x||0)})),e-=x;const t=e>r?e-r:0;m=m.map((e=>e<=0?-v:e>t?t+w:e))}if(s.centerInsufficientSlides){let e=0;g.forEach((t=>{e+=t+(x||0)})),e-=x;const t=(s.slidesOffsetBefore||0)+(s.slidesOffsetAfter||0);if(e+t{m[t]=e-s})),h.forEach(((e,t)=>{h[t]=e+s}))}}if(Object.assign(e,{slides:c,snapGrid:m,slidesGrid:h,slidesSizesGrid:g}),s.centeredSlides&&s.cssMode&&!s.centeredSlidesBounds){u(a,"--swiper-centered-offset-before",-m[0]+"px"),u(a,"--swiper-centered-offset-after",e.size/2-g[g.length-1]/2+"px");const t=-e.snapGrid[0],s=-e.slidesGrid[0];e.snapGrid=e.snapGrid.map((e=>e+t)),e.slidesGrid=e.slidesGrid.map((e=>e+s))}if(p!==d&&e.emit("slidesLengthChange"),m.length!==y&&(e.params.watchOverflow&&e.checkOverflow(),e.emit("snapGridLengthChange")),h.length!==E&&e.emit("slidesGridLengthChange"),s.watchSlidesProgress&&e.updateSlidesOffset(),e.emit("slidesUpdated"),!(o||s.cssMode||"slide"!==s.effect&&"fade"!==s.effect)){const t=`${s.containerModifierClass}backface-hidden`,a=e.el.classList.contains(t);p<=s.maxBackfaceHiddenSlides?a||e.el.classList.add(t):a&&e.el.classList.remove(t)}},updateAutoHeight:function(e){const t=this,s=[],a=t.virtual&&t.params.virtual.enabled;let i,r=0;"number"==typeof e?t.setTransition(e):!0===e&&t.setTransition(t.params.speed);const n=e=>a?t.slides[t.getSlideIndexByData(e)]:t.slides[e];if("auto"!==t.params.slidesPerView&&t.params.slidesPerView>1)if(t.params.centeredSlides)(t.visibleSlides||[]).forEach((e=>{s.push(e)}));else for(i=0;it.slides.length&&!a)break;s.push(n(e))}else s.push(n(t.activeIndex));for(i=0;ir?e:r}(r||0===r)&&(t.wrapperEl.style.height=`${r}px`)},updateSlidesOffset:function(){const e=this,t=e.slides,s=e.isElement?e.isHorizontal()?e.wrapperEl.offsetLeft:e.wrapperEl.offsetTop:0;for(let a=0;a=0?l=parseFloat(l.replace("%",""))/100*t.size:"string"==typeof l&&(l=parseFloat(l));for(let e=0;e=0&&u<=t.size-t.slidesSizesGrid[e],f=u>=0&&u1&&m<=t.size||u<=0&&m>=t.size;f&&(t.visibleSlides.push(o),t.visibleSlidesIndexes.push(e)),k(o,f,s.slideVisibleClass),k(o,h,s.slideFullyVisibleClass),o.progress=i?-c:c,o.originalProgress=i?-p:p}},updateProgress:function(e){const t=this;if(void 0===e){const s=t.rtlTranslate?-1:1;e=t&&t.translate&&t.translate*s||0}const s=t.params,a=t.maxTranslate()-t.minTranslate();let{progress:i,isBeginning:r,isEnd:n,progressLoop:l}=t;const o=r,d=n;if(0===a)i=0,r=!0,n=!0;else{i=(e-t.minTranslate())/a;const s=Math.abs(e-t.minTranslate())<1,l=Math.abs(e-t.maxTranslate())<1;r=s||i<=0,n=l||i>=1,s&&(i=0),l&&(i=1)}if(s.loop){const s=t.getSlideIndexByData(0),a=t.getSlideIndexByData(t.slides.length-1),i=t.slidesGrid[s],r=t.slidesGrid[a],n=t.slidesGrid[t.slidesGrid.length-1],o=Math.abs(e);l=o>=i?(o-i)/n:(o+n-r)/n,l>1&&(l-=1)}Object.assign(t,{progress:i,progressLoop:l,isBeginning:r,isEnd:n}),(s.watchSlidesProgress||s.centeredSlides&&s.autoHeight)&&t.updateSlidesProgress(e),r&&!o&&t.emit("reachBeginning toEdge"),n&&!d&&t.emit("reachEnd toEdge"),(o&&!r||d&&!n)&&t.emit("fromEdge"),t.emit("progress",i)},updateSlidesClasses:function(){const e=this,{slides:t,params:s,slidesEl:a,activeIndex:i}=e,r=e.virtual&&s.virtual.enabled,n=e.grid&&s.grid&&s.grid.rows>1,l=e=>f(a,`.${s.slideClass}${e}, swiper-slide${e}`)[0];let o,d,c;if(r)if(s.loop){let t=i-e.virtual.slidesBefore;t<0&&(t=e.virtual.slides.length+t),t>=e.virtual.slides.length&&(t-=e.virtual.slides.length),o=l(`[data-swiper-slide-index="${t}"]`)}else o=l(`[data-swiper-slide-index="${i}"]`);else n?(o=t.find((e=>e.column===i)),c=t.find((e=>e.column===i+1)),d=t.find((e=>e.column===i-1))):o=t[i];o&&(n||(c=function(e,t){const s=[];for(;e.nextElementSibling;){const a=e.nextElementSibling;t?a.matches(t)&&s.push(a):s.push(a),e=a}return s}(o,`.${s.slideClass}, swiper-slide`)[0],s.loop&&!c&&(c=t[0]),d=function(e,t){const s=[];for(;e.previousElementSibling;){const a=e.previousElementSibling;t?a.matches(t)&&s.push(a):s.push(a),e=a}return s}(o,`.${s.slideClass}, swiper-slide`)[0],s.loop&&0===!d&&(d=t[t.length-1]))),t.forEach((e=>{O(e,e===o,s.slideActiveClass),O(e,e===c,s.slideNextClass),O(e,e===d,s.slidePrevClass)})),e.emitSlidesClasses()},updateActiveIndex:function(e){const t=this,s=t.rtlTranslate?t.translate:-t.translate,{snapGrid:a,params:i,activeIndex:r,realIndex:n,snapIndex:l}=t;let o,d=e;const c=e=>{let s=e-t.virtual.slidesBefore;return s<0&&(s=t.virtual.slides.length+s),s>=t.virtual.slides.length&&(s-=t.virtual.slides.length),s};if(void 0===d&&(d=function(e){const{slidesGrid:t,params:s}=e,a=e.rtlTranslate?e.translate:-e.translate;let i;for(let e=0;e=t[e]&&a=t[e]&&a=t[e]&&(i=e);return s.normalizeSlideIndex&&(i<0||void 0===i)&&(i=0),i}(t)),a.indexOf(s)>=0)o=a.indexOf(s);else{const e=Math.min(i.slidesPerGroupSkip,d);o=e+Math.floor((d-e)/i.slidesPerGroup)}if(o>=a.length&&(o=a.length-1),d===r&&!t.params.loop)return void(o!==l&&(t.snapIndex=o,t.emit("snapIndexChange")));if(d===r&&t.params.loop&&t.virtual&&t.params.virtual.enabled)return void(t.realIndex=c(d));const p=t.grid&&i.grid&&i.grid.rows>1;let u;if(t.virtual&&i.virtual.enabled&&i.loop)u=c(d);else if(p){const e=t.slides.find((e=>e.column===d));let s=parseInt(e.getAttribute("data-swiper-slide-index"),10);Number.isNaN(s)&&(s=Math.max(t.slides.indexOf(e),0)),u=Math.floor(s/i.grid.rows)}else if(t.slides[d]){const e=t.slides[d].getAttribute("data-swiper-slide-index");u=e?parseInt(e,10):d}else u=d;Object.assign(t,{previousSnapIndex:l,snapIndex:o,previousRealIndex:n,realIndex:u,previousIndex:r,activeIndex:d}),t.initialized&&X(t),t.emit("activeIndexChange"),t.emit("snapIndexChange"),(t.initialized||t.params.runCallbacksOnInit)&&(n!==u&&t.emit("realIndexChange"),t.emit("slideChange"))},updateClickedSlide:function(e,t){const s=this,a=s.params;let i=e.closest(`.${a.slideClass}, swiper-slide`);!i&&s.isElement&&t&&t.length>1&&t.includes(e)&&[...t.slice(t.indexOf(e)+1,t.length)].forEach((e=>{!i&&e.matches&&e.matches(`.${a.slideClass}, swiper-slide`)&&(i=e)}));let r,n=!1;if(i)for(let e=0;eo?o:a&&en?"next":r=o.length&&(v=o.length-1);const w=-o[v];if(l.normalizeSlideIndex)for(let e=0;e=s&&t=s&&t=s&&(n=e)}if(r.initialized&&n!==p){if(!r.allowSlideNext&&(u?w>r.translate&&w>r.minTranslate():wr.translate&&w>r.maxTranslate()&&(p||0)!==n)return!1}let b;n!==(c||0)&&s&&r.emit("beforeSlideChangeStart"),r.updateProgress(w),b=n>p?"next":n0?(r._cssModeVirtualInitialSet=!0,requestAnimationFrame((()=>{h[e?"scrollLeft":"scrollTop"]=s}))):h[e?"scrollLeft":"scrollTop"]=s,y&&requestAnimationFrame((()=>{r.wrapperEl.style.scrollSnapType="",r._immediateVirtual=!1}));else{if(!r.support.smoothScroll)return m({swiper:r,targetPosition:s,side:e?"left":"top"}),!0;h.scrollTo({[e?"left":"top"]:s,behavior:"smooth"})}return!0}const E=A().isSafari;return y&&!i&&E&&r.isElement&&r.virtual.update(!1,!1,n),r.setTransition(t),r.setTranslate(w),r.updateActiveIndex(n),r.updateSlidesClasses(),r.emit("beforeTransitionStart",t,a),r.transitionStart(s,b),0===t?r.transitionEnd(s,b):r.animating||(r.animating=!0,r.onSlideToWrapperTransitionEnd||(r.onSlideToWrapperTransitionEnd=function(e){r&&!r.destroyed&&e.target===this&&(r.wrapperEl.removeEventListener("transitionend",r.onSlideToWrapperTransitionEnd),r.onSlideToWrapperTransitionEnd=null,delete r.onSlideToWrapperTransitionEnd,r.transitionEnd(s,b))}),r.wrapperEl.addEventListener("transitionend",r.onSlideToWrapperTransitionEnd)),!0},slideToLoop:function(e,t,s,a){if(void 0===e&&(e=0),void 0===s&&(s=!0),"string"==typeof e){e=parseInt(e,10)}const i=this;if(i.destroyed)return;void 0===t&&(t=i.params.speed);const r=i.grid&&i.params.grid&&i.params.grid.rows>1;let n=e;if(i.params.loop)if(i.virtual&&i.params.virtual.enabled)n+=i.virtual.slidesBefore;else{let e;if(r){const t=n*i.params.grid.rows;e=i.slides.find((e=>1*e.getAttribute("data-swiper-slide-index")===t)).column}else e=i.getSlideIndexByData(n);const t=r?Math.ceil(i.slides.length/i.params.grid.rows):i.slides.length,{centeredSlides:s}=i.params;let l=i.params.slidesPerView;"auto"===l?l=i.slidesPerViewDynamic():(l=Math.ceil(parseFloat(i.params.slidesPerView,10)),s&&l%2==0&&(l+=1));let o=t-e1*t.getAttribute("data-swiper-slide-index")===e)).column}else n=i.getSlideIndexByData(n)}return requestAnimationFrame((()=>{i.slideTo(n,t,s,a)})),i},slideNext:function(e,t,s){void 0===t&&(t=!0);const a=this,{enabled:i,params:r,animating:n}=a;if(!i||a.destroyed)return a;void 0===e&&(e=a.params.speed);let l=r.slidesPerGroup;"auto"===r.slidesPerView&&1===r.slidesPerGroup&&r.slidesPerGroupAuto&&(l=Math.max(a.slidesPerViewDynamic("current",!0),1));const o=a.activeIndex{a.slideTo(a.activeIndex+o,e,t,s)})),!0}return r.rewind&&a.isEnd?a.slideTo(0,e,t,s):a.slideTo(a.activeIndex+o,e,t,s)},slidePrev:function(e,t,s){void 0===t&&(t=!0);const a=this,{params:i,snapGrid:r,slidesGrid:n,rtlTranslate:l,enabled:o,animating:d}=a;if(!o||a.destroyed)return a;void 0===e&&(e=a.params.speed);const c=a.virtual&&i.virtual.enabled;if(i.loop){if(d&&!c&&i.loopPreventsSliding)return!1;a.loopFix({direction:"prev"}),a._clientLeft=a.wrapperEl.clientLeft}function p(e){return e<0?-Math.floor(Math.abs(e)):Math.floor(e)}const u=p(l?a.translate:-a.translate),m=r.map((e=>p(e))),h=i.freeMode&&i.freeMode.enabled;let f=r[m.indexOf(u)-1];if(void 0===f&&(i.cssMode||h)){let e;r.forEach(((t,s)=>{u>=t&&(e=s)})),void 0!==e&&(f=h?r[e]:r[e>0?e-1:e])}let g=0;if(void 0!==f&&(g=n.indexOf(f),g<0&&(g=a.activeIndex-1),"auto"===i.slidesPerView&&1===i.slidesPerGroup&&i.slidesPerGroupAuto&&(g=g-a.slidesPerViewDynamic("previous",!0)+1,g=Math.max(g,0))),i.rewind&&a.isBeginning){const i=a.params.virtual&&a.params.virtual.enabled&&a.virtual?a.virtual.slides.length-1:a.slides.length-1;return a.slideTo(i,e,t,s)}return i.loop&&0===a.activeIndex&&i.cssMode?(requestAnimationFrame((()=>{a.slideTo(g,e,t,s)})),!0):a.slideTo(g,e,t,s)},slideReset:function(e,t,s){void 0===t&&(t=!0);const a=this;if(!a.destroyed)return void 0===e&&(e=a.params.speed),a.slideTo(a.activeIndex,e,t,s)},slideToClosest:function(e,t,s,a){void 0===t&&(t=!0),void 0===a&&(a=.5);const i=this;if(i.destroyed)return;void 0===e&&(e=i.params.speed);let r=i.activeIndex;const n=Math.min(i.params.slidesPerGroupSkip,r),l=n+Math.floor((r-n)/i.params.slidesPerGroup),o=i.rtlTranslate?i.translate:-i.translate;if(o>=i.snapGrid[l]){const e=i.snapGrid[l];o-e>(i.snapGrid[l+1]-e)*a&&(r+=i.params.slidesPerGroup)}else{const e=i.snapGrid[l-1];o-e<=(i.snapGrid[l]-e)*a&&(r-=i.params.slidesPerGroup)}return r=Math.max(r,0),r=Math.min(r,i.slidesGrid.length-1),i.slideTo(r,e,t,s)},slideToClickedSlide:function(){const e=this;if(e.destroyed)return;const{params:t,slidesEl:s}=e,a="auto"===t.slidesPerView?e.slidesPerViewDynamic():t.slidesPerView;let i,r=e.clickedIndex;const n=e.isElement?"swiper-slide":`.${t.slideClass}`;if(t.loop){if(e.animating)return;i=parseInt(e.clickedSlide.getAttribute("data-swiper-slide-index"),10),t.centeredSlides?re.slides.length-e.loopedSlides+a/2?(e.loopFix(),r=e.getSlideIndex(f(s,`${n}[data-swiper-slide-index="${i}"]`)[0]),l((()=>{e.slideTo(r)}))):e.slideTo(r):r>e.slides.length-a?(e.loopFix(),r=e.getSlideIndex(f(s,`${n}[data-swiper-slide-index="${i}"]`)[0]),l((()=>{e.slideTo(r)}))):e.slideTo(r)}else e.slideTo(r)}};var R={loopCreate:function(e){const t=this,{params:s,slidesEl:a}=t;if(!s.loop||t.virtual&&t.params.virtual.enabled)return;const i=()=>{f(a,`.${s.slideClass}, swiper-slide`).forEach(((e,t)=>{e.setAttribute("data-swiper-slide-index",t)}))},r=t.grid&&s.grid&&s.grid.rows>1,n=s.slidesPerGroup*(r?s.grid.rows:1),l=t.slides.length%n!=0,o=r&&t.slides.length%s.grid.rows!=0,d=e=>{for(let a=0;a1;d.lengthe.classList.contains(m.slideActiveClass)))):x=r;const S="next"===a||!a,T="prev"===a||!a;let M=0,C=0;const P=b?Math.ceil(d.length/m.grid.rows):d.length,L=(b?d[r].column:r)+(h&&void 0===i?-f/2+.5:0);if(L=0;t-=1)d[t].column===e&&y.push(t)}else y.push(P-t-1)}}else if(L+f>P-w){C=Math.max(L-(P-2*w),v);for(let e=0;e{e.column===t&&E.push(s)})):E.push(t)}}if(o.__preventObserver__=!0,requestAnimationFrame((()=>{o.__preventObserver__=!1})),T&&y.forEach((e=>{d[e].swiperLoopMoveDOM=!0,u.prepend(d[e]),d[e].swiperLoopMoveDOM=!1})),S&&E.forEach((e=>{d[e].swiperLoopMoveDOM=!0,u.append(d[e]),d[e].swiperLoopMoveDOM=!1})),o.recalcSlides(),"auto"===m.slidesPerView?o.updateSlides():b&&(y.length>0&&T||E.length>0&&S)&&o.slides.forEach(((e,t)=>{o.grid.updateSlide(t,e,o.slides)})),m.watchSlidesProgress&&o.updateSlidesOffset(),s)if(y.length>0&&T){if(void 0===t){const e=o.slidesGrid[x],t=o.slidesGrid[x+M]-e;l?o.setTranslate(o.translate-t):(o.slideTo(x+Math.ceil(M),0,!1,!0),i&&(o.touchEventsData.startTranslate=o.touchEventsData.startTranslate-t,o.touchEventsData.currentTranslate=o.touchEventsData.currentTranslate-t))}else if(i){const e=b?y.length/m.grid.rows:y.length;o.slideTo(o.activeIndex+e,0,!1,!0),o.touchEventsData.currentTranslate=o.translate}}else if(E.length>0&&S)if(void 0===t){const e=o.slidesGrid[x],t=o.slidesGrid[x-C]-e;l?o.setTranslate(o.translate-t):(o.slideTo(x-C,0,!1,!0),i&&(o.touchEventsData.startTranslate=o.touchEventsData.startTranslate-t,o.touchEventsData.currentTranslate=o.touchEventsData.currentTranslate-t))}else{const e=b?E.length/m.grid.rows:E.length;o.slideTo(o.activeIndex-e,0,!1,!0)}if(o.allowSlidePrev=c,o.allowSlideNext=p,o.controller&&o.controller.control&&!n){const e={slideRealIndex:t,direction:a,setTranslate:i,activeSlideIndex:r,byController:!0};Array.isArray(o.controller.control)?o.controller.control.forEach((t=>{!t.destroyed&&t.params.loop&&t.loopFix({...e,slideTo:t.params.slidesPerView===m.slidesPerView&&s})})):o.controller.control instanceof o.constructor&&o.controller.control.params.loop&&o.controller.control.loopFix({...e,slideTo:o.controller.control.params.slidesPerView===m.slidesPerView&&s})}o.emit("loopFix")},loopDestroy:function(){const e=this,{params:t,slidesEl:s}=e;if(!t.loop||!s||e.virtual&&e.params.virtual.enabled)return;e.recalcSlides();const a=[];e.slides.forEach((e=>{const t=void 0===e.swiperSlideIndex?1*e.getAttribute("data-swiper-slide-index"):e.swiperSlideIndex;a[t]=e})),e.slides.forEach((e=>{e.removeAttribute("data-swiper-slide-index")})),a.forEach((e=>{s.append(e)})),e.recalcSlides(),e.slideTo(e.realIndex,0)}};function q(e,t,s){const a=r(),{params:i}=e,n=i.edgeSwipeDetection,l=i.edgeSwipeThreshold;return!n||!(s<=l||s>=a.innerWidth-l)||"prevent"===n&&(t.preventDefault(),!0)}function _(e){const t=this,s=a();let i=e;i.originalEvent&&(i=i.originalEvent);const n=t.touchEventsData;if("pointerdown"===i.type){if(null!==n.pointerId&&n.pointerId!==i.pointerId)return;n.pointerId=i.pointerId}else"touchstart"===i.type&&1===i.targetTouches.length&&(n.touchId=i.targetTouches[0].identifier);if("touchstart"===i.type)return void q(t,i,i.targetTouches[0].pageX);const{params:l,touches:d,enabled:c}=t;if(!c)return;if(!l.simulateTouch&&"mouse"===i.pointerType)return;if(t.animating&&l.preventInteractionOnTransition)return;!t.animating&&l.cssMode&&l.loop&&t.loopFix();let p=i.target;if("wrapper"===l.touchEventsTarget&&!function(e,t){const s=r();let a=t.contains(e);!a&&s.HTMLSlotElement&&t instanceof HTMLSlotElement&&(a=[...t.assignedElements()].includes(e),a||(a=function(e,t){const s=[t];for(;s.length>0;){const t=s.shift();if(e===t)return!0;s.push(...t.children,...t.shadowRoot?t.shadowRoot.children:[],...t.assignedElements?t.assignedElements():[])}}(e,t)));return a}(p,t.wrapperEl))return;if("which"in i&&3===i.which)return;if("button"in i&&i.button>0)return;if(n.isTouched&&n.isMoved)return;const u=!!l.noSwipingClass&&""!==l.noSwipingClass,m=i.composedPath?i.composedPath():i.path;u&&i.target&&i.target.shadowRoot&&m&&(p=m[0]);const h=l.noSwipingSelector?l.noSwipingSelector:`.${l.noSwipingClass}`,f=!(!i.target||!i.target.shadowRoot);if(l.noSwiping&&(f?function(e,t){return void 0===t&&(t=this),function t(s){if(!s||s===a()||s===r())return null;s.assignedSlot&&(s=s.assignedSlot);const i=s.closest(e);return i||s.getRootNode?i||t(s.getRootNode().host):null}(t)}(h,p):p.closest(h)))return void(t.allowClick=!0);if(l.swipeHandler&&!p.closest(l.swipeHandler))return;d.currentX=i.pageX,d.currentY=i.pageY;const g=d.currentX,v=d.currentY;if(!q(t,i,g))return;Object.assign(n,{isTouched:!0,isMoved:!1,allowTouchCallbacks:!0,isScrolling:void 0,startMoving:void 0}),d.startX=g,d.startY=v,n.touchStartTime=o(),t.allowClick=!0,t.updateSize(),t.swipeDirection=void 0,l.threshold>0&&(n.allowThresholdMove=!1);let w=!0;p.matches(n.focusableElements)&&(w=!1,"SELECT"===p.nodeName&&(n.isTouched=!1)),s.activeElement&&s.activeElement.matches(n.focusableElements)&&s.activeElement!==p&&("mouse"===i.pointerType||"mouse"!==i.pointerType&&!p.matches(n.focusableElements))&&s.activeElement.blur();const b=w&&t.allowTouchMove&&l.touchStartPreventDefault;!l.touchStartForcePreventDefault&&!b||p.isContentEditable||i.preventDefault(),l.freeMode&&l.freeMode.enabled&&t.freeMode&&t.animating&&!l.cssMode&&t.freeMode.onTouchStart(),t.emit("touchStart",i)}function F(e){const t=a(),s=this,i=s.touchEventsData,{params:r,touches:n,rtlTranslate:l,enabled:d}=s;if(!d)return;if(!r.simulateTouch&&"mouse"===e.pointerType)return;let c,p=e;if(p.originalEvent&&(p=p.originalEvent),"pointermove"===p.type){if(null!==i.touchId)return;if(p.pointerId!==i.pointerId)return}if("touchmove"===p.type){if(c=[...p.changedTouches].find((e=>e.identifier===i.touchId)),!c||c.identifier!==i.touchId)return}else c=p;if(!i.isTouched)return void(i.startMoving&&i.isScrolling&&s.emit("touchMoveOpposite",p));const u=c.pageX,m=c.pageY;if(p.preventedByNestedSwiper)return n.startX=u,void(n.startY=m);if(!s.allowTouchMove)return p.target.matches(i.focusableElements)||(s.allowClick=!1),void(i.isTouched&&(Object.assign(n,{startX:u,startY:m,currentX:u,currentY:m}),i.touchStartTime=o()));if(r.touchReleaseOnEdges&&!r.loop)if(s.isVertical()){if(mn.startY&&s.translate>=s.minTranslate())return i.isTouched=!1,void(i.isMoved=!1)}else if(un.startX&&s.translate>=s.minTranslate())return;if(t.activeElement&&t.activeElement.matches(i.focusableElements)&&t.activeElement!==p.target&&"mouse"!==p.pointerType&&t.activeElement.blur(),t.activeElement&&p.target===t.activeElement&&p.target.matches(i.focusableElements))return i.isMoved=!0,void(s.allowClick=!1);i.allowTouchCallbacks&&s.emit("touchMove",p),n.previousX=n.currentX,n.previousY=n.currentY,n.currentX=u,n.currentY=m;const h=n.currentX-n.startX,f=n.currentY-n.startY;if(s.params.threshold&&Math.sqrt(h**2+f**2)=25&&(e=180*Math.atan2(Math.abs(f),Math.abs(h))/Math.PI,i.isScrolling=s.isHorizontal()?e>r.touchAngle:90-e>r.touchAngle)}if(i.isScrolling&&s.emit("touchMoveOpposite",p),void 0===i.startMoving&&(n.currentX===n.startX&&n.currentY===n.startY||(i.startMoving=!0)),i.isScrolling||"touchmove"===p.type&&i.preventTouchMoveFromPointerMove)return void(i.isTouched=!1);if(!i.startMoving)return;s.allowClick=!1,!r.cssMode&&p.cancelable&&p.preventDefault(),r.touchMoveStopPropagation&&!r.nested&&p.stopPropagation();let g=s.isHorizontal()?h:f,v=s.isHorizontal()?n.currentX-n.previousX:n.currentY-n.previousY;r.oneWayMovement&&(g=Math.abs(g)*(l?1:-1),v=Math.abs(v)*(l?1:-1)),n.diff=g,g*=r.touchRatio,l&&(g=-g,v=-v);const w=s.touchesDirection;s.swipeDirection=g>0?"prev":"next",s.touchesDirection=v>0?"prev":"next";const b=s.params.loop&&!r.cssMode,y="next"===s.touchesDirection&&s.allowSlideNext||"prev"===s.touchesDirection&&s.allowSlidePrev;if(!i.isMoved){if(b&&y&&s.loopFix({direction:s.swipeDirection}),i.startTranslate=s.getTranslate(),s.setTransition(0),s.animating){const e=new window.CustomEvent("transitionend",{bubbles:!0,cancelable:!0,detail:{bySwiperTouchMove:!0}});s.wrapperEl.dispatchEvent(e)}i.allowMomentumBounce=!1,!r.grabCursor||!0!==s.allowSlideNext&&!0!==s.allowSlidePrev||s.setGrabCursor(!0),s.emit("sliderFirstMove",p)}if((new Date).getTime(),!1!==r._loopSwapReset&&i.isMoved&&i.allowThresholdMove&&w!==s.touchesDirection&&b&&y&&Math.abs(g)>=1)return Object.assign(n,{startX:u,startY:m,currentX:u,currentY:m,startTranslate:i.currentTranslate}),i.loopSwapReset=!0,void(i.startTranslate=i.currentTranslate);s.emit("sliderMove",p),i.isMoved=!0,i.currentTranslate=g+i.startTranslate;let E=!0,x=r.resistanceRatio;if(r.touchReleaseOnEdges&&(x=0),g>0?(b&&y&&i.allowThresholdMove&&i.currentTranslate>(r.centeredSlides?s.minTranslate()-s.slidesSizesGrid[s.activeIndex+1]-("auto"!==r.slidesPerView&&s.slides.length-r.slidesPerView>=2?s.slidesSizesGrid[s.activeIndex+1]+s.params.spaceBetween:0)-s.params.spaceBetween:s.minTranslate())&&s.loopFix({direction:"prev",setTranslate:!0,activeSlideIndex:0}),i.currentTranslate>s.minTranslate()&&(E=!1,r.resistance&&(i.currentTranslate=s.minTranslate()-1+(-s.minTranslate()+i.startTranslate+g)**x))):g<0&&(b&&y&&i.allowThresholdMove&&i.currentTranslate<(r.centeredSlides?s.maxTranslate()+s.slidesSizesGrid[s.slidesSizesGrid.length-1]+s.params.spaceBetween+("auto"!==r.slidesPerView&&s.slides.length-r.slidesPerView>=2?s.slidesSizesGrid[s.slidesSizesGrid.length-1]+s.params.spaceBetween:0):s.maxTranslate())&&s.loopFix({direction:"next",setTranslate:!0,activeSlideIndex:s.slides.length-("auto"===r.slidesPerView?s.slidesPerViewDynamic():Math.ceil(parseFloat(r.slidesPerView,10)))}),i.currentTranslatei.startTranslate&&(i.currentTranslate=i.startTranslate),s.allowSlidePrev||s.allowSlideNext||(i.currentTranslate=i.startTranslate),r.threshold>0){if(!(Math.abs(g)>r.threshold||i.allowThresholdMove))return void(i.currentTranslate=i.startTranslate);if(!i.allowThresholdMove)return i.allowThresholdMove=!0,n.startX=n.currentX,n.startY=n.currentY,i.currentTranslate=i.startTranslate,void(n.diff=s.isHorizontal()?n.currentX-n.startX:n.currentY-n.startY)}r.followFinger&&!r.cssMode&&((r.freeMode&&r.freeMode.enabled&&s.freeMode||r.watchSlidesProgress)&&(s.updateActiveIndex(),s.updateSlidesClasses()),r.freeMode&&r.freeMode.enabled&&s.freeMode&&s.freeMode.onTouchMove(),s.updateProgress(i.currentTranslate),s.setTranslate(i.currentTranslate))}function V(e){const t=this,s=t.touchEventsData;let a,i=e;i.originalEvent&&(i=i.originalEvent);if("touchend"===i.type||"touchcancel"===i.type){if(a=[...i.changedTouches].find((e=>e.identifier===s.touchId)),!a||a.identifier!==s.touchId)return}else{if(null!==s.touchId)return;if(i.pointerId!==s.pointerId)return;a=i}if(["pointercancel","pointerout","pointerleave","contextmenu"].includes(i.type)){if(!(["pointercancel","contextmenu"].includes(i.type)&&(t.browser.isSafari||t.browser.isWebView)))return}s.pointerId=null,s.touchId=null;const{params:r,touches:n,rtlTranslate:d,slidesGrid:c,enabled:p}=t;if(!p)return;if(!r.simulateTouch&&"mouse"===i.pointerType)return;if(s.allowTouchCallbacks&&t.emit("touchEnd",i),s.allowTouchCallbacks=!1,!s.isTouched)return s.isMoved&&r.grabCursor&&t.setGrabCursor(!1),s.isMoved=!1,void(s.startMoving=!1);r.grabCursor&&s.isMoved&&s.isTouched&&(!0===t.allowSlideNext||!0===t.allowSlidePrev)&&t.setGrabCursor(!1);const u=o(),m=u-s.touchStartTime;if(t.allowClick){const e=i.path||i.composedPath&&i.composedPath();t.updateClickedSlide(e&&e[0]||i.target,e),t.emit("tap click",i),m<300&&u-s.lastClickTime<300&&t.emit("doubleTap doubleClick",i)}if(s.lastClickTime=o(),l((()=>{t.destroyed||(t.allowClick=!0)})),!s.isTouched||!s.isMoved||!t.swipeDirection||0===n.diff&&!s.loopSwapReset||s.currentTranslate===s.startTranslate&&!s.loopSwapReset)return s.isTouched=!1,s.isMoved=!1,void(s.startMoving=!1);let h;if(s.isTouched=!1,s.isMoved=!1,s.startMoving=!1,h=r.followFinger?d?t.translate:-t.translate:-s.currentTranslate,r.cssMode)return;if(r.freeMode&&r.freeMode.enabled)return void t.freeMode.onTouchEnd({currentPos:h});const f=h>=-t.maxTranslate()&&!t.params.loop;let g=0,v=t.slidesSizesGrid[0];for(let e=0;e=c[e]&&h=c[e])&&(g=e,v=c[c.length-1]-c[c.length-2])}let w=null,b=null;r.rewind&&(t.isBeginning?b=r.virtual&&r.virtual.enabled&&t.virtual?t.virtual.slides.length-1:t.slides.length-1:t.isEnd&&(w=0));const y=(h-c[g])/v,E=gr.longSwipesMs){if(!r.longSwipes)return void t.slideTo(t.activeIndex);"next"===t.swipeDirection&&(y>=r.longSwipesRatio?t.slideTo(r.rewind&&t.isEnd?w:g+E):t.slideTo(g)),"prev"===t.swipeDirection&&(y>1-r.longSwipesRatio?t.slideTo(g+E):null!==b&&y<0&&Math.abs(y)>r.longSwipesRatio?t.slideTo(b):t.slideTo(g))}else{if(!r.shortSwipes)return void t.slideTo(t.activeIndex);t.navigation&&(i.target===t.navigation.nextEl||i.target===t.navigation.prevEl)?i.target===t.navigation.nextEl?t.slideTo(g+E):t.slideTo(g):("next"===t.swipeDirection&&t.slideTo(null!==w?w:g+E),"prev"===t.swipeDirection&&t.slideTo(null!==b?b:g))}}function W(){const e=this,{params:t,el:s}=e;if(s&&0===s.offsetWidth)return;t.breakpoints&&e.setBreakpoint();const{allowSlideNext:a,allowSlidePrev:i,snapGrid:r}=e,n=e.virtual&&e.params.virtual.enabled;e.allowSlideNext=!0,e.allowSlidePrev=!0,e.updateSize(),e.updateSlides(),e.updateSlidesClasses();const l=n&&t.loop;!("auto"===t.slidesPerView||t.slidesPerView>1)||!e.isEnd||e.isBeginning||e.params.centeredSlides||l?e.params.loop&&!n?e.slideToLoop(e.realIndex,0,!1,!0):e.slideTo(e.activeIndex,0,!1,!0):e.slideTo(e.slides.length-1,0,!1,!0),e.autoplay&&e.autoplay.running&&e.autoplay.paused&&(clearTimeout(e.autoplay.resizeTimeout),e.autoplay.resizeTimeout=setTimeout((()=>{e.autoplay&&e.autoplay.running&&e.autoplay.paused&&e.autoplay.resume()}),500)),e.allowSlidePrev=i,e.allowSlideNext=a,e.params.watchOverflow&&r!==e.snapGrid&&e.checkOverflow()}function j(e){const t=this;t.enabled&&(t.allowClick||(t.params.preventClicks&&e.preventDefault(),t.params.preventClicksPropagation&&t.animating&&(e.stopPropagation(),e.stopImmediatePropagation())))}function U(){const e=this,{wrapperEl:t,rtlTranslate:s,enabled:a}=e;if(!a)return;let i;e.previousTranslate=e.translate,e.isHorizontal()?e.translate=-t.scrollLeft:e.translate=-t.scrollTop,0===e.translate&&(e.translate=0),e.updateActiveIndex(),e.updateSlidesClasses();const r=e.maxTranslate()-e.minTranslate();i=0===r?0:(e.translate-e.minTranslate())/r,i!==e.progress&&e.updateProgress(s?-e.translate:e.translate),e.emit("setTranslate",e.translate,!1)}function K(e){const t=this;D(t,e.target),t.params.cssMode||"auto"!==t.params.slidesPerView&&!t.params.autoHeight||t.update()}function Z(){const e=this;e.documentTouchHandlerProceeded||(e.documentTouchHandlerProceeded=!0,e.params.touchReleaseOnEdges&&(e.el.style.touchAction="auto"))}const Q=(e,t)=>{const s=a(),{params:i,el:r,wrapperEl:n,device:l}=e,o=!!i.nested,d="on"===t?"addEventListener":"removeEventListener",c=t;r&&"string"!=typeof r&&(s[d]("touchstart",e.onDocumentTouchStart,{passive:!1,capture:o}),r[d]("touchstart",e.onTouchStart,{passive:!1}),r[d]("pointerdown",e.onTouchStart,{passive:!1}),s[d]("touchmove",e.onTouchMove,{passive:!1,capture:o}),s[d]("pointermove",e.onTouchMove,{passive:!1,capture:o}),s[d]("touchend",e.onTouchEnd,{passive:!0}),s[d]("pointerup",e.onTouchEnd,{passive:!0}),s[d]("pointercancel",e.onTouchEnd,{passive:!0}),s[d]("touchcancel",e.onTouchEnd,{passive:!0}),s[d]("pointerout",e.onTouchEnd,{passive:!0}),s[d]("pointerleave",e.onTouchEnd,{passive:!0}),s[d]("contextmenu",e.onTouchEnd,{passive:!0}),(i.preventClicks||i.preventClicksPropagation)&&r[d]("click",e.onClick,!0),i.cssMode&&n[d]("scroll",e.onScroll),i.updateOnWindowResize?e[c](l.ios||l.android?"resize orientationchange observerUpdate":"resize observerUpdate",W,!0):e[c]("observerUpdate",W,!0),r[d]("load",e.onLoad,{capture:!0}))};const J=(e,t)=>e.grid&&t.grid&&t.grid.rows>1;var ee={init:!0,direction:"horizontal",oneWayMovement:!1,swiperElementNodeName:"SWIPER-CONTAINER",touchEventsTarget:"wrapper",initialSlide:0,speed:300,cssMode:!1,updateOnWindowResize:!0,resizeObserver:!0,nested:!1,createElements:!1,eventsPrefix:"swiper",enabled:!0,focusableElements:"input, select, option, textarea, button, video, label",width:null,height:null,preventInteractionOnTransition:!1,userAgent:null,url:null,edgeSwipeDetection:!1,edgeSwipeThreshold:20,autoHeight:!1,setWrapperSize:!1,virtualTranslate:!1,effect:"slide",breakpoints:void 0,breakpointsBase:"window",spaceBetween:0,slidesPerView:1,slidesPerGroup:1,slidesPerGroupSkip:0,slidesPerGroupAuto:!1,centeredSlides:!1,centeredSlidesBounds:!1,slidesOffsetBefore:0,slidesOffsetAfter:0,normalizeSlideIndex:!0,centerInsufficientSlides:!1,watchOverflow:!0,roundLengths:!1,touchRatio:1,touchAngle:45,simulateTouch:!0,shortSwipes:!0,longSwipes:!0,longSwipesRatio:.5,longSwipesMs:300,followFinger:!0,allowTouchMove:!0,threshold:5,touchMoveStopPropagation:!1,touchStartPreventDefault:!0,touchStartForcePreventDefault:!1,touchReleaseOnEdges:!1,uniqueNavElements:!0,resistance:!0,resistanceRatio:.85,watchSlidesProgress:!1,grabCursor:!1,preventClicks:!0,preventClicksPropagation:!0,slideToClickedSlide:!1,loop:!1,loopAddBlankSlides:!0,loopAdditionalSlides:0,loopPreventsSliding:!0,rewind:!1,allowSlidePrev:!0,allowSlideNext:!0,swipeHandler:null,noSwiping:!0,noSwipingClass:"swiper-no-swiping",noSwipingSelector:null,passiveListeners:!0,maxBackfaceHiddenSlides:10,containerModifierClass:"swiper-",slideClass:"swiper-slide",slideBlankClass:"swiper-slide-blank",slideActiveClass:"swiper-slide-active",slideVisibleClass:"swiper-slide-visible",slideFullyVisibleClass:"swiper-slide-fully-visible",slideNextClass:"swiper-slide-next",slidePrevClass:"swiper-slide-prev",wrapperClass:"swiper-wrapper",lazyPreloaderClass:"swiper-lazy-preloader",lazyPreloadPrevNext:0,runCallbacksOnInit:!0,_emitClasses:!1};function te(e,t){return function(s){void 0===s&&(s={});const a=Object.keys(s)[0],i=s[a];"object"==typeof i&&null!==i?(!0===e[a]&&(e[a]={enabled:!0}),"navigation"===a&&e[a]&&e[a].enabled&&!e[a].prevEl&&!e[a].nextEl&&(e[a].auto=!0),["pagination","scrollbar"].indexOf(a)>=0&&e[a]&&e[a].enabled&&!e[a].el&&(e[a].auto=!0),a in e&&"enabled"in i?("object"!=typeof e[a]||"enabled"in e[a]||(e[a].enabled=!0),e[a]||(e[a]={enabled:!1}),p(t,s)):p(t,s)):p(t,s)}}const se={eventsEmitter:$,update:H,translate:Y,transition:{setTransition:function(e,t){const s=this;s.params.cssMode||(s.wrapperEl.style.transitionDuration=`${e}ms`,s.wrapperEl.style.transitionDelay=0===e?"0ms":""),s.emit("setTransition",e,t)},transitionStart:function(e,t){void 0===e&&(e=!0);const s=this,{params:a}=s;a.cssMode||(a.autoHeight&&s.updateAutoHeight(),B({swiper:s,runCallbacks:e,direction:t,step:"Start"}))},transitionEnd:function(e,t){void 0===e&&(e=!0);const s=this,{params:a}=s;s.animating=!1,a.cssMode||(s.setTransition(0),B({swiper:s,runCallbacks:e,direction:t,step:"End"}))}},slide:N,loop:R,grabCursor:{setGrabCursor:function(e){const t=this;if(!t.params.simulateTouch||t.params.watchOverflow&&t.isLocked||t.params.cssMode)return;const s="container"===t.params.touchEventsTarget?t.el:t.wrapperEl;t.isElement&&(t.__preventObserver__=!0),s.style.cursor="move",s.style.cursor=e?"grabbing":"grab",t.isElement&&requestAnimationFrame((()=>{t.__preventObserver__=!1}))},unsetGrabCursor:function(){const e=this;e.params.watchOverflow&&e.isLocked||e.params.cssMode||(e.isElement&&(e.__preventObserver__=!0),e["container"===e.params.touchEventsTarget?"el":"wrapperEl"].style.cursor="",e.isElement&&requestAnimationFrame((()=>{e.__preventObserver__=!1})))}},events:{attachEvents:function(){const e=this,{params:t}=e;e.onTouchStart=_.bind(e),e.onTouchMove=F.bind(e),e.onTouchEnd=V.bind(e),e.onDocumentTouchStart=Z.bind(e),t.cssMode&&(e.onScroll=U.bind(e)),e.onClick=j.bind(e),e.onLoad=K.bind(e),Q(e,"on")},detachEvents:function(){Q(this,"off")}},breakpoints:{setBreakpoint:function(){const e=this,{realIndex:t,initialized:s,params:i,el:r}=e,n=i.breakpoints;if(!n||n&&0===Object.keys(n).length)return;const l=a(),o="window"!==i.breakpointsBase&&i.breakpointsBase?"container":i.breakpointsBase,d=["window","container"].includes(i.breakpointsBase)||!i.breakpointsBase?e.el:l.querySelector(i.breakpointsBase),c=e.getBreakpoint(n,o,d);if(!c||e.currentBreakpoint===c)return;const u=(c in n?n[c]:void 0)||e.originalParams,m=J(e,i),h=J(e,u),f=e.params.grabCursor,g=u.grabCursor,v=i.enabled;m&&!h?(r.classList.remove(`${i.containerModifierClass}grid`,`${i.containerModifierClass}grid-column`),e.emitContainerClasses()):!m&&h&&(r.classList.add(`${i.containerModifierClass}grid`),(u.grid.fill&&"column"===u.grid.fill||!u.grid.fill&&"column"===i.grid.fill)&&r.classList.add(`${i.containerModifierClass}grid-column`),e.emitContainerClasses()),f&&!g?e.unsetGrabCursor():!f&&g&&e.setGrabCursor(),["navigation","pagination","scrollbar"].forEach((t=>{if(void 0===u[t])return;const s=i[t]&&i[t].enabled,a=u[t]&&u[t].enabled;s&&!a&&e[t].disable(),!s&&a&&e[t].enable()}));const w=u.direction&&u.direction!==i.direction,b=i.loop&&(u.slidesPerView!==i.slidesPerView||w),y=i.loop;w&&s&&e.changeDirection(),p(e.params,u);const E=e.params.enabled,x=e.params.loop;Object.assign(e,{allowTouchMove:e.params.allowTouchMove,allowSlideNext:e.params.allowSlideNext,allowSlidePrev:e.params.allowSlidePrev}),v&&!E?e.disable():!v&&E&&e.enable(),e.currentBreakpoint=c,e.emit("_beforeBreakpoint",u),s&&(b?(e.loopDestroy(),e.loopCreate(t),e.updateSlides()):!y&&x?(e.loopCreate(t),e.updateSlides()):y&&!x&&e.loopDestroy()),e.emit("breakpoint",u)},getBreakpoint:function(e,t,s){if(void 0===t&&(t="window"),!e||"container"===t&&!s)return;let a=!1;const i=r(),n="window"===t?i.innerHeight:s.clientHeight,l=Object.keys(e).map((e=>{if("string"==typeof e&&0===e.indexOf("@")){const t=parseFloat(e.substr(1));return{value:n*t,point:e}}return{value:e,point:e}}));l.sort(((e,t)=>parseInt(e.value,10)-parseInt(t.value,10)));for(let e=0;es}else e.isLocked=1===e.snapGrid.length;!0===s.allowSlideNext&&(e.allowSlideNext=!e.isLocked),!0===s.allowSlidePrev&&(e.allowSlidePrev=!e.isLocked),t&&t!==e.isLocked&&(e.isEnd=!1),t!==e.isLocked&&e.emit(e.isLocked?"lock":"unlock")}},classes:{addClasses:function(){const e=this,{classNames:t,params:s,rtl:a,el:i,device:r}=e,n=function(e,t){const s=[];return e.forEach((e=>{"object"==typeof e?Object.keys(e).forEach((a=>{e[a]&&s.push(t+a)})):"string"==typeof e&&s.push(t+e)})),s}(["initialized",s.direction,{"free-mode":e.params.freeMode&&s.freeMode.enabled},{autoheight:s.autoHeight},{rtl:a},{grid:s.grid&&s.grid.rows>1},{"grid-column":s.grid&&s.grid.rows>1&&"column"===s.grid.fill},{android:r.android},{ios:r.ios},{"css-mode":s.cssMode},{centered:s.cssMode&&s.centeredSlides},{"watch-progress":s.watchSlidesProgress}],s.containerModifierClass);t.push(...n),i.classList.add(...t),e.emitContainerClasses()},removeClasses:function(){const{el:e,classNames:t}=this;e&&"string"!=typeof e&&(e.classList.remove(...t),this.emitContainerClasses())}}},ae={};class ie{constructor(){let e,t;for(var s=arguments.length,i=new Array(s),r=0;r1){const e=[];return n.querySelectorAll(t.el).forEach((s=>{const a=p({},t,{el:s});e.push(new ie(a))})),e}const l=this;l.__swiper__=!0,l.support=I(),l.device=z({userAgent:t.userAgent}),l.browser=A(),l.eventsListeners={},l.eventsAnyListeners=[],l.modules=[...l.__modules__],t.modules&&Array.isArray(t.modules)&&l.modules.push(...t.modules);const o={};l.modules.forEach((e=>{e({params:t,swiper:l,extendParams:te(t,o),on:l.on.bind(l),once:l.once.bind(l),off:l.off.bind(l),emit:l.emit.bind(l)})}));const d=p({},ee,o);return l.params=p({},d,ae,t),l.originalParams=p({},l.params),l.passedParams=p({},t),l.params&&l.params.on&&Object.keys(l.params.on).forEach((e=>{l.on(e,l.params.on[e])})),l.params&&l.params.onAny&&l.onAny(l.params.onAny),Object.assign(l,{enabled:l.params.enabled,el:e,classNames:[],slides:[],slidesGrid:[],snapGrid:[],slidesSizesGrid:[],isHorizontal:()=>"horizontal"===l.params.direction,isVertical:()=>"vertical"===l.params.direction,activeIndex:0,realIndex:0,isBeginning:!0,isEnd:!1,translate:0,previousTranslate:0,progress:0,velocity:0,animating:!1,cssOverflowAdjustment(){return Math.trunc(this.translate/2**23)*2**23},allowSlideNext:l.params.allowSlideNext,allowSlidePrev:l.params.allowSlidePrev,touchEventsData:{isTouched:void 0,isMoved:void 0,allowTouchCallbacks:void 0,touchStartTime:void 0,isScrolling:void 0,currentTranslate:void 0,startTranslate:void 0,allowThresholdMove:void 0,focusableElements:l.params.focusableElements,lastClickTime:0,clickTimeout:void 0,velocities:[],allowMomentumBounce:void 0,startMoving:void 0,pointerId:null,touchId:null},allowClick:!0,allowTouchMove:l.params.allowTouchMove,touches:{startX:0,startY:0,currentX:0,currentY:0,diff:0},imagesToLoad:[],imagesLoaded:0}),l.emit("_swiper"),l.params.init&&l.init(),l}getDirectionLabel(e){return this.isHorizontal()?e:{width:"height","margin-top":"margin-left","margin-bottom ":"margin-right","margin-left":"margin-top","margin-right":"margin-bottom","padding-left":"padding-top","padding-right":"padding-bottom",marginRight:"marginBottom"}[e]}getSlideIndex(e){const{slidesEl:t,params:s}=this,a=y(f(t,`.${s.slideClass}, swiper-slide`)[0]);return y(e)-a}getSlideIndexByData(e){return this.getSlideIndex(this.slides.find((t=>1*t.getAttribute("data-swiper-slide-index")===e)))}recalcSlides(){const{slidesEl:e,params:t}=this;this.slides=f(e,`.${t.slideClass}, swiper-slide`)}enable(){const e=this;e.enabled||(e.enabled=!0,e.params.grabCursor&&e.setGrabCursor(),e.emit("enable"))}disable(){const e=this;e.enabled&&(e.enabled=!1,e.params.grabCursor&&e.unsetGrabCursor(),e.emit("disable"))}setProgress(e,t){const s=this;e=Math.min(Math.max(e,0),1);const a=s.minTranslate(),i=(s.maxTranslate()-a)*e+a;s.translateTo(i,void 0===t?0:t),s.updateActiveIndex(),s.updateSlidesClasses()}emitContainerClasses(){const e=this;if(!e.params._emitClasses||!e.el)return;const t=e.el.className.split(" ").filter((t=>0===t.indexOf("swiper")||0===t.indexOf(e.params.containerModifierClass)));e.emit("_containerClasses",t.join(" "))}getSlideClasses(e){const t=this;return t.destroyed?"":e.className.split(" ").filter((e=>0===e.indexOf("swiper-slide")||0===e.indexOf(t.params.slideClass))).join(" ")}emitSlidesClasses(){const e=this;if(!e.params._emitClasses||!e.el)return;const t=[];e.slides.forEach((s=>{const a=e.getSlideClasses(s);t.push({slideEl:s,classNames:a}),e.emit("_slideClass",s,a)})),e.emit("_slideClasses",t)}slidesPerViewDynamic(e,t){void 0===e&&(e="current"),void 0===t&&(t=!1);const{params:s,slides:a,slidesGrid:i,slidesSizesGrid:r,size:n,activeIndex:l}=this;let o=1;if("number"==typeof s.slidesPerView)return s.slidesPerView;if(s.centeredSlides){let e,t=a[l]?Math.ceil(a[l].swiperSlideSize):0;for(let s=l+1;sn&&(e=!0));for(let s=l-1;s>=0;s-=1)a[s]&&!e&&(t+=a[s].swiperSlideSize,o+=1,t>n&&(e=!0))}else if("current"===e)for(let e=l+1;e=0;e-=1){i[l]-i[e]{t.complete&&D(e,t)})),e.updateSize(),e.updateSlides(),e.updateProgress(),e.updateSlidesClasses(),s.freeMode&&s.freeMode.enabled&&!s.cssMode)a(),s.autoHeight&&e.updateAutoHeight();else{if(("auto"===s.slidesPerView||s.slidesPerView>1)&&e.isEnd&&!s.centeredSlides){const t=e.virtual&&s.virtual.enabled?e.virtual.slides:e.slides;i=e.slideTo(t.length-1,0,!1,!0)}else i=e.slideTo(e.activeIndex,0,!1,!0);i||a()}s.watchOverflow&&t!==e.snapGrid&&e.checkOverflow(),e.emit("update")}changeDirection(e,t){void 0===t&&(t=!0);const s=this,a=s.params.direction;return e||(e="horizontal"===a?"vertical":"horizontal"),e===a||"horizontal"!==e&&"vertical"!==e||(s.el.classList.remove(`${s.params.containerModifierClass}${a}`),s.el.classList.add(`${s.params.containerModifierClass}${e}`),s.emitContainerClasses(),s.params.direction=e,s.slides.forEach((t=>{"vertical"===e?t.style.width="":t.style.height=""})),s.emit("changeDirection"),t&&s.update()),s}changeLanguageDirection(e){const t=this;t.rtl&&"rtl"===e||!t.rtl&&"ltr"===e||(t.rtl="rtl"===e,t.rtlTranslate="horizontal"===t.params.direction&&t.rtl,t.rtl?(t.el.classList.add(`${t.params.containerModifierClass}rtl`),t.el.dir="rtl"):(t.el.classList.remove(`${t.params.containerModifierClass}rtl`),t.el.dir="ltr"),t.update())}mount(e){const t=this;if(t.mounted)return!0;let s=e||t.params.el;if("string"==typeof s&&(s=document.querySelector(s)),!s)return!1;s.swiper=t,s.parentNode&&s.parentNode.host&&s.parentNode.host.nodeName===t.params.swiperElementNodeName.toUpperCase()&&(t.isElement=!0);const a=()=>`.${(t.params.wrapperClass||"").trim().split(" ").join(".")}`;let i=(()=>{if(s&&s.shadowRoot&&s.shadowRoot.querySelector){return s.shadowRoot.querySelector(a())}return f(s,a())[0]})();return!i&&t.params.createElements&&(i=v("div",t.params.wrapperClass),s.append(i),f(s,`.${t.params.slideClass}`).forEach((e=>{i.append(e)}))),Object.assign(t,{el:s,wrapperEl:i,slidesEl:t.isElement&&!s.parentNode.host.slideSlots?s.parentNode.host:i,hostEl:t.isElement?s.parentNode.host:s,mounted:!0,rtl:"rtl"===s.dir.toLowerCase()||"rtl"===b(s,"direction"),rtlTranslate:"horizontal"===t.params.direction&&("rtl"===s.dir.toLowerCase()||"rtl"===b(s,"direction")),wrongRTL:"-webkit-box"===b(i,"display")}),!0}init(e){const t=this;if(t.initialized)return t;if(!1===t.mount(e))return t;t.emit("beforeInit"),t.params.breakpoints&&t.setBreakpoint(),t.addClasses(),t.updateSize(),t.updateSlides(),t.params.watchOverflow&&t.checkOverflow(),t.params.grabCursor&&t.enabled&&t.setGrabCursor(),t.params.loop&&t.virtual&&t.params.virtual.enabled?t.slideTo(t.params.initialSlide+t.virtual.slidesBefore,0,t.params.runCallbacksOnInit,!1,!0):t.slideTo(t.params.initialSlide,0,t.params.runCallbacksOnInit,!1,!0),t.params.loop&&t.loopCreate(),t.attachEvents();const s=[...t.el.querySelectorAll('[loading="lazy"]')];return t.isElement&&s.push(...t.hostEl.querySelectorAll('[loading="lazy"]')),s.forEach((e=>{e.complete?D(t,e):e.addEventListener("load",(e=>{D(t,e.target)}))})),X(t),t.initialized=!0,X(t),t.emit("init"),t.emit("afterInit"),t}destroy(e,t){void 0===e&&(e=!0),void 0===t&&(t=!0);const s=this,{params:a,el:i,wrapperEl:r,slides:n}=s;return void 0===s.params||s.destroyed||(s.emit("beforeDestroy"),s.initialized=!1,s.detachEvents(),a.loop&&s.loopDestroy(),t&&(s.removeClasses(),i&&"string"!=typeof i&&i.removeAttribute("style"),r&&r.removeAttribute("style"),n&&n.length&&n.forEach((e=>{e.classList.remove(a.slideVisibleClass,a.slideFullyVisibleClass,a.slideActiveClass,a.slideNextClass,a.slidePrevClass),e.removeAttribute("style"),e.removeAttribute("data-swiper-slide-index")}))),s.emit("destroy"),Object.keys(s.eventsListeners).forEach((e=>{s.off(e)})),!1!==e&&(s.el&&"string"!=typeof s.el&&(s.el.swiper=null),function(e){const t=e;Object.keys(t).forEach((e=>{try{t[e]=null}catch(e){}try{delete t[e]}catch(e){}}))}(s)),s.destroyed=!0),null}static extendDefaults(e){p(ae,e)}static get extendedDefaults(){return ae}static get defaults(){return ee}static installModule(e){ie.prototype.__modules__||(ie.prototype.__modules__=[]);const t=ie.prototype.__modules__;"function"==typeof e&&t.indexOf(e)<0&&t.push(e)}static use(e){return Array.isArray(e)?(e.forEach((e=>ie.installModule(e))),ie):(ie.installModule(e),ie)}}function re(e,t,s,a){return e.params.createElements&&Object.keys(a).forEach((i=>{if(!s[i]&&!0===s.auto){let r=f(e.el,`.${a[i]}`)[0];r||(r=v("div",a[i]),r.className=a[i],e.el.append(r)),s[i]=r,t[i]=r}})),s}function ne(e){return void 0===e&&(e=""),`.${e.trim().replace(/([\.:!+\/])/g,"\\$1").replace(/ /g,".")}`}function le(e){const t=this,{params:s,slidesEl:a}=t;s.loop&&t.loopDestroy();const i=e=>{if("string"==typeof e){const t=document.createElement("div");t.innerHTML=e,a.append(t.children[0]),t.innerHTML=""}else a.append(e)};if("object"==typeof e&&"length"in e)for(let t=0;t{if("string"==typeof e){const t=document.createElement("div");t.innerHTML=e,i.prepend(t.children[0]),t.innerHTML=""}else i.prepend(e)};if("object"==typeof e&&"length"in e){for(let t=0;t=l)return void s.appendSlide(t);let o=n>e?n+1:n;const d=[];for(let t=l-1;t>=e;t-=1){const e=s.slides[t];e.remove(),d.unshift(e)}if("object"==typeof t&&"length"in t){for(let e=0;ee?n+t.length:n}else r.append(t);for(let e=0;e{if(s.params.effect!==t)return;s.classNames.push(`${s.params.containerModifierClass}${t}`),l&&l()&&s.classNames.push(`${s.params.containerModifierClass}3d`);const e=n?n():{};Object.assign(s.params,e),Object.assign(s.originalParams,e)})),a("setTranslate",(()=>{s.params.effect===t&&i()})),a("setTransition",((e,a)=>{s.params.effect===t&&r(a)})),a("transitionEnd",(()=>{if(s.params.effect===t&&o){if(!d||!d().slideShadows)return;s.slides.forEach((e=>{e.querySelectorAll(".swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left").forEach((e=>e.remove()))})),o()}})),a("virtualUpdate",(()=>{s.params.effect===t&&(s.slides.length||(c=!0),requestAnimationFrame((()=>{c&&s.slides&&s.slides.length&&(i(),c=!1)})))}))}function me(e,t){const s=h(t);return s!==t&&(s.style.backfaceVisibility="hidden",s.style["-webkit-backface-visibility"]="hidden"),s}function he(e){let{swiper:t,duration:s,transformElements:a,allSlides:i}=e;const{activeIndex:r}=t;if(t.params.virtualTranslate&&0!==s){let e,s=!1;e=i?a:a.filter((e=>{const s=e.classList.contains("swiper-slide-transform")?(e=>{if(!e.parentElement)return t.slides.find((t=>t.shadowRoot&&t.shadowRoot===e.parentNode));return e.parentElement})(e):e;return t.getSlideIndex(s)===r})),e.forEach((e=>{x(e,(()=>{if(s)return;if(!t||t.destroyed)return;s=!0,t.animating=!1;const e=new window.CustomEvent("transitionend",{bubbles:!0,cancelable:!0});t.wrapperEl.dispatchEvent(e)}))}))}}function fe(e,t,s){const a=`swiper-slide-shadow${s?`-${s}`:""}${e?` swiper-slide-shadow-${e}`:""}`,i=h(t);let r=i.querySelector(`.${a.split(" ").join(".")}`);return r||(r=v("div",a.split(" ")),i.append(r)),r}Object.keys(se).forEach((e=>{Object.keys(se[e]).forEach((t=>{ie.prototype[t]=se[e][t]}))})),ie.use([function(e){let{swiper:t,on:s,emit:a}=e;const i=r();let n=null,l=null;const o=()=>{t&&!t.destroyed&&t.initialized&&(a("beforeResize"),a("resize"))},d=()=>{t&&!t.destroyed&&t.initialized&&a("orientationchange")};s("init",(()=>{t.params.resizeObserver&&void 0!==i.ResizeObserver?t&&!t.destroyed&&t.initialized&&(n=new ResizeObserver((e=>{l=i.requestAnimationFrame((()=>{const{width:s,height:a}=t;let i=s,r=a;e.forEach((e=>{let{contentBoxSize:s,contentRect:a,target:n}=e;n&&n!==t.el||(i=a?a.width:(s[0]||s).inlineSize,r=a?a.height:(s[0]||s).blockSize)})),i===s&&r===a||o()}))})),n.observe(t.el)):(i.addEventListener("resize",o),i.addEventListener("orientationchange",d))})),s("destroy",(()=>{l&&i.cancelAnimationFrame(l),n&&n.unobserve&&t.el&&(n.unobserve(t.el),n=null),i.removeEventListener("resize",o),i.removeEventListener("orientationchange",d)}))},function(e){let{swiper:t,extendParams:s,on:a,emit:i}=e;const n=[],l=r(),o=function(e,s){void 0===s&&(s={});const a=new(l.MutationObserver||l.WebkitMutationObserver)((e=>{if(t.__preventObserver__)return;if(1===e.length)return void i("observerUpdate",e[0]);const s=function(){i("observerUpdate",e[0])};l.requestAnimationFrame?l.requestAnimationFrame(s):l.setTimeout(s,0)}));a.observe(e,{attributes:void 0===s.attributes||s.attributes,childList:t.isElement||(void 0===s.childList||s).childList,characterData:void 0===s.characterData||s.characterData}),n.push(a)};s({observer:!1,observeParents:!1,observeSlideChildren:!1}),a("init",(()=>{if(t.params.observer){if(t.params.observeParents){const e=E(t.hostEl);for(let t=0;t{n.forEach((e=>{e.disconnect()})),n.splice(0,n.length)}))}]);const ge=[function(e){let t,{swiper:s,extendParams:i,on:r,emit:n}=e;i({virtual:{enabled:!1,slides:[],cache:!0,renderSlide:null,renderExternal:null,renderExternalUpdate:!0,addSlidesBefore:0,addSlidesAfter:0}});const l=a();s.virtual={cache:{},from:void 0,to:void 0,slides:[],offset:0,slidesGrid:[]};const o=l.createElement("div");function d(e,t){const a=s.params.virtual;if(a.cache&&s.virtual.cache[t])return s.virtual.cache[t];let i;return a.renderSlide?(i=a.renderSlide.call(s,e,t),"string"==typeof i&&(o.innerHTML=i,i=o.children[0])):i=s.isElement?v("swiper-slide"):v("div",s.params.slideClass),i.setAttribute("data-swiper-slide-index",t),a.renderSlide||(i.innerHTML=e),a.cache&&(s.virtual.cache[t]=i),i}function c(e,t,a){const{slidesPerView:i,slidesPerGroup:r,centeredSlides:l,loop:o,initialSlide:c}=s.params;if(t&&!o&&c>0)return;const{addSlidesBefore:p,addSlidesAfter:u}=s.params.virtual,{from:m,to:h,slides:g,slidesGrid:v,offset:w}=s.virtual;s.params.cssMode||s.updateActiveIndex();const b=void 0===a?s.activeIndex||0:a;let y,E,x;y=s.rtlTranslate?"right":s.isHorizontal()?"left":"top",l?(E=Math.floor(i/2)+r+u,x=Math.floor(i/2)+r+p):(E=i+(r-1)+u,x=(o?i:r)+p);let S=b-x,T=b+E;o||(S=Math.max(S,0),T=Math.min(T,g.length-1));let M=(s.slidesGrid[S]||0)-(s.slidesGrid[0]||0);function C(){s.updateSlides(),s.updateProgress(),s.updateSlidesClasses(),n("virtualUpdate")}if(o&&b>=x?(S-=x,l||(M+=s.slidesGrid[0])):o&&b{e.style[y]=M-Math.abs(s.cssOverflowAdjustment())+"px"})),s.updateProgress(),void n("virtualUpdate");if(s.params.virtual.renderExternal)return s.params.virtual.renderExternal.call(s,{offset:M,from:S,to:T,slides:function(){const e=[];for(let t=S;t<=T;t+=1)e.push(g[t]);return e}()}),void(s.params.virtual.renderExternalUpdate?C():n("virtualUpdate"));const P=[],L=[],I=e=>{let t=e;return e<0?t=g.length+e:t>=g.length&&(t-=g.length),t};if(e)s.slides.filter((e=>e.matches(`.${s.params.slideClass}, swiper-slide`))).forEach((e=>{e.remove()}));else for(let e=m;e<=h;e+=1)if(eT){const t=I(e);s.slides.filter((e=>e.matches(`.${s.params.slideClass}[data-swiper-slide-index="${t}"], swiper-slide[data-swiper-slide-index="${t}"]`))).forEach((e=>{e.remove()}))}const z=o?-g.length:0,A=o?2*g.length:g.length;for(let t=z;t=S&&t<=T){const s=I(t);void 0===h||e?L.push(s):(t>h&&L.push(s),t{s.slidesEl.append(d(g[e],e))})),o)for(let e=P.length-1;e>=0;e-=1){const t=P[e];s.slidesEl.prepend(d(g[t],t))}else P.sort(((e,t)=>t-e)),P.forEach((e=>{s.slidesEl.prepend(d(g[e],e))}));f(s.slidesEl,".swiper-slide, swiper-slide").forEach((e=>{e.style[y]=M-Math.abs(s.cssOverflowAdjustment())+"px"})),C()}r("beforeInit",(()=>{if(!s.params.virtual.enabled)return;let e;if(void 0===s.passedParams.virtual.slides){const t=[...s.slidesEl.children].filter((e=>e.matches(`.${s.params.slideClass}, swiper-slide`)));t&&t.length&&(s.virtual.slides=[...t],e=!0,t.forEach(((e,t)=>{e.setAttribute("data-swiper-slide-index",t),s.virtual.cache[t]=e,e.remove()})))}e||(s.virtual.slides=s.params.virtual.slides),s.classNames.push(`${s.params.containerModifierClass}virtual`),s.params.watchSlidesProgress=!0,s.originalParams.watchSlidesProgress=!0,c(!1,!0)})),r("setTranslate",(()=>{s.params.virtual.enabled&&(s.params.cssMode&&!s._immediateVirtual?(clearTimeout(t),t=setTimeout((()=>{c()}),100)):c())})),r("init update resize",(()=>{s.params.virtual.enabled&&s.params.cssMode&&u(s.wrapperEl,"--swiper-virtual-size",`${s.virtualSize}px`)})),Object.assign(s.virtual,{appendSlide:function(e){if("object"==typeof e&&"length"in e)for(let t=0;t{const a=e[s],r=a.getAttribute("data-swiper-slide-index");r&&a.setAttribute("data-swiper-slide-index",parseInt(r,10)+i),t[parseInt(s,10)+i]=a})),s.virtual.cache=t}c(!0),s.slideTo(a,0)},removeSlide:function(e){if(null==e)return;let t=s.activeIndex;if(Array.isArray(e))for(let a=e.length-1;a>=0;a-=1)s.params.virtual.cache&&(delete s.virtual.cache[e[a]],Object.keys(s.virtual.cache).forEach((t=>{t>e&&(s.virtual.cache[t-1]=s.virtual.cache[t],s.virtual.cache[t-1].setAttribute("data-swiper-slide-index",t-1),delete s.virtual.cache[t])}))),s.virtual.slides.splice(e[a],1),e[a]{t>e&&(s.virtual.cache[t-1]=s.virtual.cache[t],s.virtual.cache[t-1].setAttribute("data-swiper-slide-index",t-1),delete s.virtual.cache[t])}))),s.virtual.slides.splice(e,1),e0&&0===E(t.el,`.${t.params.slideActiveClass}`).length)return;const a=t.el,i=a.clientWidth,r=a.clientHeight,n=o.innerWidth,l=o.innerHeight,d=w(a);s&&(d.left-=a.scrollLeft);const c=[[d.left,d.top],[d.left+i,d.top],[d.left,d.top+r],[d.left+i,d.top+r]];for(let t=0;t=0&&s[0]<=n&&s[1]>=0&&s[1]<=l){if(0===s[0]&&0===s[1])continue;e=!0}}if(!e)return}t.isHorizontal()?((d||c||p||u)&&(a.preventDefault?a.preventDefault():a.returnValue=!1),((c||u)&&!s||(d||p)&&s)&&t.slideNext(),((d||p)&&!s||(c||u)&&s)&&t.slidePrev()):((d||c||m||h)&&(a.preventDefault?a.preventDefault():a.returnValue=!1),(c||h)&&t.slideNext(),(d||m)&&t.slidePrev()),n("keyPress",i)}}function c(){t.keyboard.enabled||(l.addEventListener("keydown",d),t.keyboard.enabled=!0)}function p(){t.keyboard.enabled&&(l.removeEventListener("keydown",d),t.keyboard.enabled=!1)}t.keyboard={enabled:!1},s({keyboard:{enabled:!1,onlyInViewport:!0,pageUpDown:!0}}),i("init",(()=>{t.params.keyboard.enabled&&c()})),i("destroy",(()=>{t.keyboard.enabled&&p()})),Object.assign(t.keyboard,{enable:c,disable:p})},function(e){let{swiper:t,extendParams:s,on:a,emit:i}=e;const n=r();let d;s({mousewheel:{enabled:!1,releaseOnEdges:!1,invert:!1,forceToAxis:!1,sensitivity:1,eventsTarget:"container",thresholdDelta:null,thresholdTime:null,noMousewheelClass:"swiper-no-mousewheel"}}),t.mousewheel={enabled:!1};let c,p=o();const u=[];function m(){t.enabled&&(t.mouseEntered=!0)}function h(){t.enabled&&(t.mouseEntered=!1)}function f(e){return!(t.params.mousewheel.thresholdDelta&&e.delta=6&&o()-p<60||(e.direction<0?t.isEnd&&!t.params.loop||t.animating||(t.slideNext(),i("scroll",e.raw)):t.isBeginning&&!t.params.loop||t.animating||(t.slidePrev(),i("scroll",e.raw)),p=(new n.Date).getTime(),!1)))}function g(e){let s=e,a=!0;if(!t.enabled)return;if(e.target.closest(`.${t.params.mousewheel.noMousewheelClass}`))return;const r=t.params.mousewheel;t.params.cssMode&&s.preventDefault();let n=t.el;"container"!==t.params.mousewheel.eventsTarget&&(n=document.querySelector(t.params.mousewheel.eventsTarget));const p=n&&n.contains(s.target);if(!t.mouseEntered&&!p&&!r.releaseOnEdges)return!0;s.originalEvent&&(s=s.originalEvent);let m=0;const h=t.rtlTranslate?-1:1,g=function(e){let t=0,s=0,a=0,i=0;return"detail"in e&&(s=e.detail),"wheelDelta"in e&&(s=-e.wheelDelta/120),"wheelDeltaY"in e&&(s=-e.wheelDeltaY/120),"wheelDeltaX"in e&&(t=-e.wheelDeltaX/120),"axis"in e&&e.axis===e.HORIZONTAL_AXIS&&(t=s,s=0),a=10*t,i=10*s,"deltaY"in e&&(i=e.deltaY),"deltaX"in e&&(a=e.deltaX),e.shiftKey&&!a&&(a=i,i=0),(a||i)&&e.deltaMode&&(1===e.deltaMode?(a*=40,i*=40):(a*=800,i*=800)),a&&!t&&(t=a<1?-1:1),i&&!s&&(s=i<1?-1:1),{spinX:t,spinY:s,pixelX:a,pixelY:i}}(s);if(r.forceToAxis)if(t.isHorizontal()){if(!(Math.abs(g.pixelX)>Math.abs(g.pixelY)))return!0;m=-g.pixelX*h}else{if(!(Math.abs(g.pixelY)>Math.abs(g.pixelX)))return!0;m=-g.pixelY}else m=Math.abs(g.pixelX)>Math.abs(g.pixelY)?-g.pixelX*h:-g.pixelY;if(0===m)return!0;r.invert&&(m=-m);let v=t.getTranslate()+m*r.sensitivity;if(v>=t.minTranslate()&&(v=t.minTranslate()),v<=t.maxTranslate()&&(v=t.maxTranslate()),a=!!t.params.loop||!(v===t.minTranslate()||v===t.maxTranslate()),a&&t.params.nested&&s.stopPropagation(),t.params.freeMode&&t.params.freeMode.enabled){const e={time:o(),delta:Math.abs(m),direction:Math.sign(m)},a=c&&e.time=t.minTranslate()&&(n=t.minTranslate()),n<=t.maxTranslate()&&(n=t.maxTranslate()),t.setTransition(0),t.setTranslate(n),t.updateProgress(),t.updateActiveIndex(),t.updateSlidesClasses(),(!o&&t.isBeginning||!p&&t.isEnd)&&t.updateSlidesClasses(),t.params.loop&&t.loopFix({direction:e.direction<0?"next":"prev",byMousewheel:!0}),t.params.freeMode.sticky){clearTimeout(d),d=void 0,u.length>=15&&u.shift();const s=u.length?u[u.length-1]:void 0,a=u[0];if(u.push(e),s&&(e.delta>s.delta||e.direction!==s.direction))u.splice(0);else if(u.length>=15&&e.time-a.time<500&&a.delta-e.delta>=1&&e.delta<=6){const s=m>0?.8:.2;c=e,u.splice(0),d=l((()=>{!t.destroyed&&t.params&&t.slideToClosest(t.params.speed,!0,void 0,s)}),0)}d||(d=l((()=>{if(t.destroyed||!t.params)return;c=e,u.splice(0),t.slideToClosest(t.params.speed,!0,void 0,.5)}),500))}if(a||i("scroll",s),t.params.autoplay&&t.params.autoplay.disableOnInteraction&&t.autoplay.stop(),r.releaseOnEdges&&(n===t.minTranslate()||n===t.maxTranslate()))return!0}}else{const s={time:o(),delta:Math.abs(m),direction:Math.sign(m),raw:e};u.length>=2&&u.shift();const a=u.length?u[u.length-1]:void 0;if(u.push(s),a?(s.direction!==a.direction||s.delta>a.delta||s.time>a.time+150)&&f(s):f(s),function(e){const s=t.params.mousewheel;if(e.direction<0){if(t.isEnd&&!t.params.loop&&s.releaseOnEdges)return!0}else if(t.isBeginning&&!t.params.loop&&s.releaseOnEdges)return!0;return!1}(s))return!0}return s.preventDefault?s.preventDefault():s.returnValue=!1,!1}function v(e){let s=t.el;"container"!==t.params.mousewheel.eventsTarget&&(s=document.querySelector(t.params.mousewheel.eventsTarget)),s[e]("mouseenter",m),s[e]("mouseleave",h),s[e]("wheel",g)}function w(){return t.params.cssMode?(t.wrapperEl.removeEventListener("wheel",g),!0):!t.mousewheel.enabled&&(v("addEventListener"),t.mousewheel.enabled=!0,!0)}function b(){return t.params.cssMode?(t.wrapperEl.addEventListener(event,g),!0):!!t.mousewheel.enabled&&(v("removeEventListener"),t.mousewheel.enabled=!1,!0)}a("init",(()=>{!t.params.mousewheel.enabled&&t.params.cssMode&&b(),t.params.mousewheel.enabled&&w()})),a("destroy",(()=>{t.params.cssMode&&w(),t.mousewheel.enabled&&b()})),Object.assign(t.mousewheel,{enable:w,disable:b})},function(e){let{swiper:t,extendParams:s,on:a,emit:i}=e;function r(e){let s;return e&&"string"==typeof e&&t.isElement&&(s=t.el.querySelector(e)||t.hostEl.querySelector(e),s)?s:(e&&("string"==typeof e&&(s=[...document.querySelectorAll(e)]),t.params.uniqueNavElements&&"string"==typeof e&&s&&s.length>1&&1===t.el.querySelectorAll(e).length?s=t.el.querySelector(e):s&&1===s.length&&(s=s[0])),e&&!s?e:s)}function n(e,s){const a=t.params.navigation;(e=T(e)).forEach((e=>{e&&(e.classList[s?"add":"remove"](...a.disabledClass.split(" ")),"BUTTON"===e.tagName&&(e.disabled=s),t.params.watchOverflow&&t.enabled&&e.classList[t.isLocked?"add":"remove"](a.lockClass))}))}function l(){const{nextEl:e,prevEl:s}=t.navigation;if(t.params.loop)return n(s,!1),void n(e,!1);n(s,t.isBeginning&&!t.params.rewind),n(e,t.isEnd&&!t.params.rewind)}function o(e){e.preventDefault(),(!t.isBeginning||t.params.loop||t.params.rewind)&&(t.slidePrev(),i("navigationPrev"))}function d(e){e.preventDefault(),(!t.isEnd||t.params.loop||t.params.rewind)&&(t.slideNext(),i("navigationNext"))}function c(){const e=t.params.navigation;if(t.params.navigation=re(t,t.originalParams.navigation,t.params.navigation,{nextEl:"swiper-button-next",prevEl:"swiper-button-prev"}),!e.nextEl&&!e.prevEl)return;let s=r(e.nextEl),a=r(e.prevEl);Object.assign(t.navigation,{nextEl:s,prevEl:a}),s=T(s),a=T(a);const i=(s,a)=>{s&&s.addEventListener("click","next"===a?d:o),!t.enabled&&s&&s.classList.add(...e.lockClass.split(" "))};s.forEach((e=>i(e,"next"))),a.forEach((e=>i(e,"prev")))}function p(){let{nextEl:e,prevEl:s}=t.navigation;e=T(e),s=T(s);const a=(e,s)=>{e.removeEventListener("click","next"===s?d:o),e.classList.remove(...t.params.navigation.disabledClass.split(" "))};e.forEach((e=>a(e,"next"))),s.forEach((e=>a(e,"prev")))}s({navigation:{nextEl:null,prevEl:null,hideOnClick:!1,disabledClass:"swiper-button-disabled",hiddenClass:"swiper-button-hidden",lockClass:"swiper-button-lock",navigationDisabledClass:"swiper-navigation-disabled"}}),t.navigation={nextEl:null,prevEl:null},a("init",(()=>{!1===t.params.navigation.enabled?u():(c(),l())})),a("toEdge fromEdge lock unlock",(()=>{l()})),a("destroy",(()=>{p()})),a("enable disable",(()=>{let{nextEl:e,prevEl:s}=t.navigation;e=T(e),s=T(s),t.enabled?l():[...e,...s].filter((e=>!!e)).forEach((e=>e.classList.add(t.params.navigation.lockClass)))})),a("click",((e,s)=>{let{nextEl:a,prevEl:r}=t.navigation;a=T(a),r=T(r);const n=s.target;let l=r.includes(n)||a.includes(n);if(t.isElement&&!l){const e=s.path||s.composedPath&&s.composedPath();e&&(l=e.find((e=>a.includes(e)||r.includes(e))))}if(t.params.navigation.hideOnClick&&!l){if(t.pagination&&t.params.pagination&&t.params.pagination.clickable&&(t.pagination.el===n||t.pagination.el.contains(n)))return;let e;a.length?e=a[0].classList.contains(t.params.navigation.hiddenClass):r.length&&(e=r[0].classList.contains(t.params.navigation.hiddenClass)),i(!0===e?"navigationShow":"navigationHide"),[...a,...r].filter((e=>!!e)).forEach((e=>e.classList.toggle(t.params.navigation.hiddenClass)))}}));const u=()=>{t.el.classList.add(...t.params.navigation.navigationDisabledClass.split(" ")),p()};Object.assign(t.navigation,{enable:()=>{t.el.classList.remove(...t.params.navigation.navigationDisabledClass.split(" ")),c(),l()},disable:u,update:l,init:c,destroy:p})},function(e){let{swiper:t,extendParams:s,on:a,emit:i}=e;const r="swiper-pagination";let n;s({pagination:{el:null,bulletElement:"span",clickable:!1,hideOnClick:!1,renderBullet:null,renderProgressbar:null,renderFraction:null,renderCustom:null,progressbarOpposite:!1,type:"bullets",dynamicBullets:!1,dynamicMainBullets:1,formatFractionCurrent:e=>e,formatFractionTotal:e=>e,bulletClass:`${r}-bullet`,bulletActiveClass:`${r}-bullet-active`,modifierClass:`${r}-`,currentClass:`${r}-current`,totalClass:`${r}-total`,hiddenClass:`${r}-hidden`,progressbarFillClass:`${r}-progressbar-fill`,progressbarOppositeClass:`${r}-progressbar-opposite`,clickableClass:`${r}-clickable`,lockClass:`${r}-lock`,horizontalClass:`${r}-horizontal`,verticalClass:`${r}-vertical`,paginationDisabledClass:`${r}-disabled`}}),t.pagination={el:null,bullets:[]};let l=0;function o(){return!t.params.pagination.el||!t.pagination.el||Array.isArray(t.pagination.el)&&0===t.pagination.el.length}function d(e,s){const{bulletActiveClass:a}=t.params.pagination;e&&(e=e[("prev"===s?"previous":"next")+"ElementSibling"])&&(e.classList.add(`${a}-${s}`),(e=e[("prev"===s?"previous":"next")+"ElementSibling"])&&e.classList.add(`${a}-${s}-${s}`))}function c(e){const s=e.target.closest(ne(t.params.pagination.bulletClass));if(!s)return;e.preventDefault();const a=y(s)*t.params.slidesPerGroup;if(t.params.loop){if(t.realIndex===a)return;const e=(i=t.realIndex,r=a,n=t.slides.length,(r%=n)==1+(i%=n)?"next":r===i-1?"previous":void 0);"next"===e?t.slideNext():"previous"===e?t.slidePrev():t.slideToLoop(a)}else t.slideTo(a);var i,r,n}function p(){const e=t.rtl,s=t.params.pagination;if(o())return;let a,r,c=t.pagination.el;c=T(c);const p=t.virtual&&t.params.virtual.enabled?t.virtual.slides.length:t.slides.length,u=t.params.loop?Math.ceil(p/t.params.slidesPerGroup):t.snapGrid.length;if(t.params.loop?(r=t.previousRealIndex||0,a=t.params.slidesPerGroup>1?Math.floor(t.realIndex/t.params.slidesPerGroup):t.realIndex):void 0!==t.snapIndex?(a=t.snapIndex,r=t.previousSnapIndex):(r=t.previousIndex||0,a=t.activeIndex||0),"bullets"===s.type&&t.pagination.bullets&&t.pagination.bullets.length>0){const i=t.pagination.bullets;let o,p,u;if(s.dynamicBullets&&(n=S(i[0],t.isHorizontal()?"width":"height",!0),c.forEach((e=>{e.style[t.isHorizontal()?"width":"height"]=n*(s.dynamicMainBullets+4)+"px"})),s.dynamicMainBullets>1&&void 0!==r&&(l+=a-(r||0),l>s.dynamicMainBullets-1?l=s.dynamicMainBullets-1:l<0&&(l=0)),o=Math.max(a-l,0),p=o+(Math.min(i.length,s.dynamicMainBullets)-1),u=(p+o)/2),i.forEach((e=>{const t=[...["","-next","-next-next","-prev","-prev-prev","-main"].map((e=>`${s.bulletActiveClass}${e}`))].map((e=>"string"==typeof e&&e.includes(" ")?e.split(" "):e)).flat();e.classList.remove(...t)})),c.length>1)i.forEach((e=>{const i=y(e);i===a?e.classList.add(...s.bulletActiveClass.split(" ")):t.isElement&&e.setAttribute("part","bullet"),s.dynamicBullets&&(i>=o&&i<=p&&e.classList.add(...`${s.bulletActiveClass}-main`.split(" ")),i===o&&d(e,"prev"),i===p&&d(e,"next"))}));else{const e=i[a];if(e&&e.classList.add(...s.bulletActiveClass.split(" ")),t.isElement&&i.forEach(((e,t)=>{e.setAttribute("part",t===a?"bullet-active":"bullet")})),s.dynamicBullets){const e=i[o],t=i[p];for(let e=o;e<=p;e+=1)i[e]&&i[e].classList.add(...`${s.bulletActiveClass}-main`.split(" "));d(e,"prev"),d(t,"next")}}if(s.dynamicBullets){const a=Math.min(i.length,s.dynamicMainBullets+4),r=(n*a-n)/2-u*n,l=e?"right":"left";i.forEach((e=>{e.style[t.isHorizontal()?l:"top"]=`${r}px`}))}}c.forEach(((e,r)=>{if("fraction"===s.type&&(e.querySelectorAll(ne(s.currentClass)).forEach((e=>{e.textContent=s.formatFractionCurrent(a+1)})),e.querySelectorAll(ne(s.totalClass)).forEach((e=>{e.textContent=s.formatFractionTotal(u)}))),"progressbar"===s.type){let i;i=s.progressbarOpposite?t.isHorizontal()?"vertical":"horizontal":t.isHorizontal()?"horizontal":"vertical";const r=(a+1)/u;let n=1,l=1;"horizontal"===i?n=r:l=r,e.querySelectorAll(ne(s.progressbarFillClass)).forEach((e=>{e.style.transform=`translate3d(0,0,0) scaleX(${n}) scaleY(${l})`,e.style.transitionDuration=`${t.params.speed}ms`}))}"custom"===s.type&&s.renderCustom?(e.innerHTML=s.renderCustom(t,a+1,u),0===r&&i("paginationRender",e)):(0===r&&i("paginationRender",e),i("paginationUpdate",e)),t.params.watchOverflow&&t.enabled&&e.classList[t.isLocked?"add":"remove"](s.lockClass)}))}function u(){const e=t.params.pagination;if(o())return;const s=t.virtual&&t.params.virtual.enabled?t.virtual.slides.length:t.grid&&t.params.grid.rows>1?t.slides.length/Math.ceil(t.params.grid.rows):t.slides.length;let a=t.pagination.el;a=T(a);let r="";if("bullets"===e.type){let a=t.params.loop?Math.ceil(s/t.params.slidesPerGroup):t.snapGrid.length;t.params.freeMode&&t.params.freeMode.enabled&&a>s&&(a=s);for(let s=0;s`}"fraction"===e.type&&(r=e.renderFraction?e.renderFraction.call(t,e.currentClass,e.totalClass):` / `),"progressbar"===e.type&&(r=e.renderProgressbar?e.renderProgressbar.call(t,e.progressbarFillClass):``),t.pagination.bullets=[],a.forEach((s=>{"custom"!==e.type&&(s.innerHTML=r||""),"bullets"===e.type&&t.pagination.bullets.push(...s.querySelectorAll(ne(e.bulletClass)))})),"custom"!==e.type&&i("paginationRender",a[0])}function m(){t.params.pagination=re(t,t.originalParams.pagination,t.params.pagination,{el:"swiper-pagination"});const e=t.params.pagination;if(!e.el)return;let s;"string"==typeof e.el&&t.isElement&&(s=t.el.querySelector(e.el)),s||"string"!=typeof e.el||(s=[...document.querySelectorAll(e.el)]),s||(s=e.el),s&&0!==s.length&&(t.params.uniqueNavElements&&"string"==typeof e.el&&Array.isArray(s)&&s.length>1&&(s=[...t.el.querySelectorAll(e.el)],s.length>1&&(s=s.find((e=>E(e,".swiper")[0]===t.el)))),Array.isArray(s)&&1===s.length&&(s=s[0]),Object.assign(t.pagination,{el:s}),s=T(s),s.forEach((s=>{"bullets"===e.type&&e.clickable&&s.classList.add(...(e.clickableClass||"").split(" ")),s.classList.add(e.modifierClass+e.type),s.classList.add(t.isHorizontal()?e.horizontalClass:e.verticalClass),"bullets"===e.type&&e.dynamicBullets&&(s.classList.add(`${e.modifierClass}${e.type}-dynamic`),l=0,e.dynamicMainBullets<1&&(e.dynamicMainBullets=1)),"progressbar"===e.type&&e.progressbarOpposite&&s.classList.add(e.progressbarOppositeClass),e.clickable&&s.addEventListener("click",c),t.enabled||s.classList.add(e.lockClass)})))}function h(){const e=t.params.pagination;if(o())return;let s=t.pagination.el;s&&(s=T(s),s.forEach((s=>{s.classList.remove(e.hiddenClass),s.classList.remove(e.modifierClass+e.type),s.classList.remove(t.isHorizontal()?e.horizontalClass:e.verticalClass),e.clickable&&(s.classList.remove(...(e.clickableClass||"").split(" ")),s.removeEventListener("click",c))}))),t.pagination.bullets&&t.pagination.bullets.forEach((t=>t.classList.remove(...e.bulletActiveClass.split(" "))))}a("changeDirection",(()=>{if(!t.pagination||!t.pagination.el)return;const e=t.params.pagination;let{el:s}=t.pagination;s=T(s),s.forEach((s=>{s.classList.remove(e.horizontalClass,e.verticalClass),s.classList.add(t.isHorizontal()?e.horizontalClass:e.verticalClass)}))})),a("init",(()=>{!1===t.params.pagination.enabled?f():(m(),u(),p())})),a("activeIndexChange",(()=>{void 0===t.snapIndex&&p()})),a("snapIndexChange",(()=>{p()})),a("snapGridLengthChange",(()=>{u(),p()})),a("destroy",(()=>{h()})),a("enable disable",(()=>{let{el:e}=t.pagination;e&&(e=T(e),e.forEach((e=>e.classList[t.enabled?"remove":"add"](t.params.pagination.lockClass))))})),a("lock unlock",(()=>{p()})),a("click",((e,s)=>{const a=s.target,r=T(t.pagination.el);if(t.params.pagination.el&&t.params.pagination.hideOnClick&&r&&r.length>0&&!a.classList.contains(t.params.pagination.bulletClass)){if(t.navigation&&(t.navigation.nextEl&&a===t.navigation.nextEl||t.navigation.prevEl&&a===t.navigation.prevEl))return;const e=r[0].classList.contains(t.params.pagination.hiddenClass);i(!0===e?"paginationShow":"paginationHide"),r.forEach((e=>e.classList.toggle(t.params.pagination.hiddenClass)))}}));const f=()=>{t.el.classList.add(t.params.pagination.paginationDisabledClass);let{el:e}=t.pagination;e&&(e=T(e),e.forEach((e=>e.classList.add(t.params.pagination.paginationDisabledClass)))),h()};Object.assign(t.pagination,{enable:()=>{t.el.classList.remove(t.params.pagination.paginationDisabledClass);let{el:e}=t.pagination;e&&(e=T(e),e.forEach((e=>e.classList.remove(t.params.pagination.paginationDisabledClass)))),m(),u(),p()},disable:f,render:u,update:p,init:m,destroy:h})},function(e){let{swiper:t,extendParams:s,on:i,emit:r}=e;const o=a();let d,c,p,u,m=!1,h=null,f=null;function g(){if(!t.params.scrollbar.el||!t.scrollbar.el)return;const{scrollbar:e,rtlTranslate:s}=t,{dragEl:a,el:i}=e,r=t.params.scrollbar,n=t.params.loop?t.progressLoop:t.progress;let l=c,o=(p-c)*n;s?(o=-o,o>0?(l=c-o,o=0):-o+c>p&&(l=p+o)):o<0?(l=c+o,o=0):o+c>p&&(l=p-o),t.isHorizontal()?(a.style.transform=`translate3d(${o}px, 0, 0)`,a.style.width=`${l}px`):(a.style.transform=`translate3d(0px, ${o}px, 0)`,a.style.height=`${l}px`),r.hide&&(clearTimeout(h),i.style.opacity=1,h=setTimeout((()=>{i.style.opacity=0,i.style.transitionDuration="400ms"}),1e3))}function b(){if(!t.params.scrollbar.el||!t.scrollbar.el)return;const{scrollbar:e}=t,{dragEl:s,el:a}=e;s.style.width="",s.style.height="",p=t.isHorizontal()?a.offsetWidth:a.offsetHeight,u=t.size/(t.virtualSize+t.params.slidesOffsetBefore-(t.params.centeredSlides?t.snapGrid[0]:0)),c="auto"===t.params.scrollbar.dragSize?p*u:parseInt(t.params.scrollbar.dragSize,10),t.isHorizontal()?s.style.width=`${c}px`:s.style.height=`${c}px`,a.style.display=u>=1?"none":"",t.params.scrollbar.hide&&(a.style.opacity=0),t.params.watchOverflow&&t.enabled&&e.el.classList[t.isLocked?"add":"remove"](t.params.scrollbar.lockClass)}function y(e){return t.isHorizontal()?e.clientX:e.clientY}function E(e){const{scrollbar:s,rtlTranslate:a}=t,{el:i}=s;let r;r=(y(e)-w(i)[t.isHorizontal()?"left":"top"]-(null!==d?d:c/2))/(p-c),r=Math.max(Math.min(r,1),0),a&&(r=1-r);const n=t.minTranslate()+(t.maxTranslate()-t.minTranslate())*r;t.updateProgress(n),t.setTranslate(n),t.updateActiveIndex(),t.updateSlidesClasses()}function x(e){const s=t.params.scrollbar,{scrollbar:a,wrapperEl:i}=t,{el:n,dragEl:l}=a;m=!0,d=e.target===l?y(e)-e.target.getBoundingClientRect()[t.isHorizontal()?"left":"top"]:null,e.preventDefault(),e.stopPropagation(),i.style.transitionDuration="100ms",l.style.transitionDuration="100ms",E(e),clearTimeout(f),n.style.transitionDuration="0ms",s.hide&&(n.style.opacity=1),t.params.cssMode&&(t.wrapperEl.style["scroll-snap-type"]="none"),r("scrollbarDragStart",e)}function S(e){const{scrollbar:s,wrapperEl:a}=t,{el:i,dragEl:n}=s;m&&(e.preventDefault&&e.cancelable?e.preventDefault():e.returnValue=!1,E(e),a.style.transitionDuration="0ms",i.style.transitionDuration="0ms",n.style.transitionDuration="0ms",r("scrollbarDragMove",e))}function M(e){const s=t.params.scrollbar,{scrollbar:a,wrapperEl:i}=t,{el:n}=a;m&&(m=!1,t.params.cssMode&&(t.wrapperEl.style["scroll-snap-type"]="",i.style.transitionDuration=""),s.hide&&(clearTimeout(f),f=l((()=>{n.style.opacity=0,n.style.transitionDuration="400ms"}),1e3)),r("scrollbarDragEnd",e),s.snapOnRelease&&t.slideToClosest())}function C(e){const{scrollbar:s,params:a}=t,i=s.el;if(!i)return;const r=i,n=!!a.passiveListeners&&{passive:!1,capture:!1},l=!!a.passiveListeners&&{passive:!0,capture:!1};if(!r)return;const d="on"===e?"addEventListener":"removeEventListener";r[d]("pointerdown",x,n),o[d]("pointermove",S,n),o[d]("pointerup",M,l)}function P(){const{scrollbar:e,el:s}=t;t.params.scrollbar=re(t,t.originalParams.scrollbar,t.params.scrollbar,{el:"swiper-scrollbar"});const a=t.params.scrollbar;if(!a.el)return;let i,r;if("string"==typeof a.el&&t.isElement&&(i=t.el.querySelector(a.el)),i||"string"!=typeof a.el)i||(i=a.el);else if(i=o.querySelectorAll(a.el),!i.length)return;t.params.uniqueNavElements&&"string"==typeof a.el&&i.length>1&&1===s.querySelectorAll(a.el).length&&(i=s.querySelector(a.el)),i.length>0&&(i=i[0]),i.classList.add(t.isHorizontal()?a.horizontalClass:a.verticalClass),i&&(r=i.querySelector(ne(t.params.scrollbar.dragClass)),r||(r=v("div",t.params.scrollbar.dragClass),i.append(r))),Object.assign(e,{el:i,dragEl:r}),a.draggable&&t.params.scrollbar.el&&t.scrollbar.el&&C("on"),i&&i.classList[t.enabled?"remove":"add"](...n(t.params.scrollbar.lockClass))}function L(){const e=t.params.scrollbar,s=t.scrollbar.el;s&&s.classList.remove(...n(t.isHorizontal()?e.horizontalClass:e.verticalClass)),t.params.scrollbar.el&&t.scrollbar.el&&C("off")}s({scrollbar:{el:null,dragSize:"auto",hide:!1,draggable:!1,snapOnRelease:!0,lockClass:"swiper-scrollbar-lock",dragClass:"swiper-scrollbar-drag",scrollbarDisabledClass:"swiper-scrollbar-disabled",horizontalClass:"swiper-scrollbar-horizontal",verticalClass:"swiper-scrollbar-vertical"}}),t.scrollbar={el:null,dragEl:null},i("changeDirection",(()=>{if(!t.scrollbar||!t.scrollbar.el)return;const e=t.params.scrollbar;let{el:s}=t.scrollbar;s=T(s),s.forEach((s=>{s.classList.remove(e.horizontalClass,e.verticalClass),s.classList.add(t.isHorizontal()?e.horizontalClass:e.verticalClass)}))})),i("init",(()=>{!1===t.params.scrollbar.enabled?I():(P(),b(),g())})),i("update resize observerUpdate lock unlock changeDirection",(()=>{b()})),i("setTranslate",(()=>{g()})),i("setTransition",((e,s)=>{!function(e){t.params.scrollbar.el&&t.scrollbar.el&&(t.scrollbar.dragEl.style.transitionDuration=`${e}ms`)}(s)})),i("enable disable",(()=>{const{el:e}=t.scrollbar;e&&e.classList[t.enabled?"remove":"add"](...n(t.params.scrollbar.lockClass))})),i("destroy",(()=>{L()}));const I=()=>{t.el.classList.add(...n(t.params.scrollbar.scrollbarDisabledClass)),t.scrollbar.el&&t.scrollbar.el.classList.add(...n(t.params.scrollbar.scrollbarDisabledClass)),L()};Object.assign(t.scrollbar,{enable:()=>{t.el.classList.remove(...n(t.params.scrollbar.scrollbarDisabledClass)),t.scrollbar.el&&t.scrollbar.el.classList.remove(...n(t.params.scrollbar.scrollbarDisabledClass)),P(),b(),g()},disable:I,updateSize:b,setTranslate:g,init:P,destroy:L})},function(e){let{swiper:t,extendParams:s,on:a}=e;s({parallax:{enabled:!1}});const i="[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y], [data-swiper-parallax-opacity], [data-swiper-parallax-scale]",r=(e,s)=>{const{rtl:a}=t,i=a?-1:1,r=e.getAttribute("data-swiper-parallax")||"0";let n=e.getAttribute("data-swiper-parallax-x"),l=e.getAttribute("data-swiper-parallax-y");const o=e.getAttribute("data-swiper-parallax-scale"),d=e.getAttribute("data-swiper-parallax-opacity"),c=e.getAttribute("data-swiper-parallax-rotate");if(n||l?(n=n||"0",l=l||"0"):t.isHorizontal()?(n=r,l="0"):(l=r,n="0"),n=n.indexOf("%")>=0?parseInt(n,10)*s*i+"%":n*s*i+"px",l=l.indexOf("%")>=0?parseInt(l,10)*s+"%":l*s+"px",null!=d){const t=d-(d-1)*(1-Math.abs(s));e.style.opacity=t}let p=`translate3d(${n}, ${l}, 0px)`;if(null!=o){p+=` scale(${o-(o-1)*(1-Math.abs(s))})`}if(c&&null!=c){p+=` rotate(${c*s*-1}deg)`}e.style.transform=p},n=()=>{const{el:e,slides:s,progress:a,snapGrid:n,isElement:l}=t,o=f(e,i);t.isElement&&o.push(...f(t.hostEl,i)),o.forEach((e=>{r(e,a)})),s.forEach(((e,s)=>{let l=e.progress;t.params.slidesPerGroup>1&&"auto"!==t.params.slidesPerView&&(l+=Math.ceil(s/2)-a*(n.length-1)),l=Math.min(Math.max(l,-1),1),e.querySelectorAll(`${i}, [data-swiper-parallax-rotate]`).forEach((e=>{r(e,l)}))}))};a("beforeInit",(()=>{t.params.parallax.enabled&&(t.params.watchSlidesProgress=!0,t.originalParams.watchSlidesProgress=!0)})),a("init",(()=>{t.params.parallax.enabled&&n()})),a("setTranslate",(()=>{t.params.parallax.enabled&&n()})),a("setTransition",((e,s)=>{t.params.parallax.enabled&&function(e){void 0===e&&(e=t.params.speed);const{el:s,hostEl:a}=t,r=[...s.querySelectorAll(i)];t.isElement&&r.push(...a.querySelectorAll(i)),r.forEach((t=>{let s=parseInt(t.getAttribute("data-swiper-parallax-duration"),10)||e;0===e&&(s=0),t.style.transitionDuration=`${s}ms`}))}(s)}))},function(e){let{swiper:t,extendParams:s,on:a,emit:i}=e;const n=r();s({zoom:{enabled:!1,limitToOriginalSize:!1,maxRatio:3,minRatio:1,panOnMouseMove:!1,toggle:!0,containerClass:"swiper-zoom-container",zoomedSlideClass:"swiper-slide-zoomed"}}),t.zoom={enabled:!1};let l=1,o=!1,c=!1,p={x:0,y:0};const u=-3;let m,h;const g=[],v={originX:0,originY:0,slideEl:void 0,slideWidth:void 0,slideHeight:void 0,imageEl:void 0,imageWrapEl:void 0,maxRatio:3},b={isTouched:void 0,isMoved:void 0,currentX:void 0,currentY:void 0,minX:void 0,minY:void 0,maxX:void 0,maxY:void 0,width:void 0,height:void 0,startX:void 0,startY:void 0,touchesStart:{},touchesCurrent:{}},y={x:void 0,y:void 0,prevPositionX:void 0,prevPositionY:void 0,prevTime:void 0};let x,S=1;function T(){if(g.length<2)return 1;const e=g[0].pageX,t=g[0].pageY,s=g[1].pageX,a=g[1].pageY;return Math.sqrt((s-e)**2+(a-t)**2)}function M(){const e=t.params.zoom,s=v.imageWrapEl.getAttribute("data-swiper-zoom")||e.maxRatio;if(e.limitToOriginalSize&&v.imageEl&&v.imageEl.naturalWidth){const e=v.imageEl.naturalWidth/v.imageEl.offsetWidth;return Math.min(e,s)}return s}function C(e){const s=t.isElement?"swiper-slide":`.${t.params.slideClass}`;return!!e.target.matches(s)||t.slides.filter((t=>t.contains(e.target))).length>0}function P(e){const s=`.${t.params.zoom.containerClass}`;return!!e.target.matches(s)||[...t.hostEl.querySelectorAll(s)].filter((t=>t.contains(e.target))).length>0}function L(e){if("mouse"===e.pointerType&&g.splice(0,g.length),!C(e))return;const s=t.params.zoom;if(m=!1,h=!1,g.push(e),!(g.length<2)){if(m=!0,v.scaleStart=T(),!v.slideEl){v.slideEl=e.target.closest(`.${t.params.slideClass}, swiper-slide`),v.slideEl||(v.slideEl=t.slides[t.activeIndex]);let a=v.slideEl.querySelector(`.${s.containerClass}`);if(a&&(a=a.querySelectorAll("picture, img, svg, canvas, .swiper-zoom-target")[0]),v.imageEl=a,v.imageWrapEl=a?E(v.imageEl,`.${s.containerClass}`)[0]:void 0,!v.imageWrapEl)return void(v.imageEl=void 0);v.maxRatio=M()}if(v.imageEl){const[e,t]=function(){if(g.length<2)return{x:null,y:null};const e=v.imageEl.getBoundingClientRect();return[(g[0].pageX+(g[1].pageX-g[0].pageX)/2-e.x-n.scrollX)/l,(g[0].pageY+(g[1].pageY-g[0].pageY)/2-e.y-n.scrollY)/l]}();v.originX=e,v.originY=t,v.imageEl.style.transitionDuration="0ms"}o=!0}}function I(e){if(!C(e))return;const s=t.params.zoom,a=t.zoom,i=g.findIndex((t=>t.pointerId===e.pointerId));i>=0&&(g[i]=e),g.length<2||(h=!0,v.scaleMove=T(),v.imageEl&&(a.scale=v.scaleMove/v.scaleStart*l,a.scale>v.maxRatio&&(a.scale=v.maxRatio-1+(a.scale-v.maxRatio+1)**.5),a.scalet.pointerId===e.pointerId));i>=0&&g.splice(i,1),m&&h&&(m=!1,h=!1,v.imageEl&&(a.scale=Math.max(Math.min(a.scale,v.maxRatio),s.minRatio),v.imageEl.style.transitionDuration=`${t.params.speed}ms`,v.imageEl.style.transform=`translate3d(0,0,0) scale(${a.scale})`,l=a.scale,o=!1,a.scale>1&&v.slideEl?v.slideEl.classList.add(`${s.zoomedSlideClass}`):a.scale<=1&&v.slideEl&&v.slideEl.classList.remove(`${s.zoomedSlideClass}`),1===a.scale&&(v.originX=0,v.originY=0,v.slideEl=void 0)))}function A(){t.touchEventsData.preventTouchMoveFromPointerMove=!1}function $(e){const s="mouse"===e.pointerType&&t.params.zoom.panOnMouseMove;if(!C(e)||!P(e))return;const a=t.zoom;if(!v.imageEl)return;if(!b.isTouched||!v.slideEl)return void(s&&O(e));if(s)return void O(e);b.isMoved||(b.width=v.imageEl.offsetWidth||v.imageEl.clientWidth,b.height=v.imageEl.offsetHeight||v.imageEl.clientHeight,b.startX=d(v.imageWrapEl,"x")||0,b.startY=d(v.imageWrapEl,"y")||0,v.slideWidth=v.slideEl.offsetWidth,v.slideHeight=v.slideEl.offsetHeight,v.imageWrapEl.style.transitionDuration="0ms");const i=b.width*a.scale,r=b.height*a.scale;b.minX=Math.min(v.slideWidth/2-i/2,0),b.maxX=-b.minX,b.minY=Math.min(v.slideHeight/2-r/2,0),b.maxY=-b.minY,b.touchesCurrent.x=g.length>0?g[0].pageX:e.pageX,b.touchesCurrent.y=g.length>0?g[0].pageY:e.pageY;if(Math.max(Math.abs(b.touchesCurrent.x-b.touchesStart.x),Math.abs(b.touchesCurrent.y-b.touchesStart.y))>5&&(t.allowClick=!1),!b.isMoved&&!o){if(t.isHorizontal()&&(Math.floor(b.minX)===Math.floor(b.startX)&&b.touchesCurrent.xb.touchesStart.x))return b.isTouched=!1,void A();if(!t.isHorizontal()&&(Math.floor(b.minY)===Math.floor(b.startY)&&b.touchesCurrent.yb.touchesStart.y))return b.isTouched=!1,void A()}e.cancelable&&e.preventDefault(),e.stopPropagation(),clearTimeout(x),t.touchEventsData.preventTouchMoveFromPointerMove=!0,x=setTimeout((()=>{t.destroyed||A()})),b.isMoved=!0;const n=(a.scale-l)/(v.maxRatio-t.params.zoom.minRatio),{originX:c,originY:p}=v;b.currentX=b.touchesCurrent.x-b.touchesStart.x+b.startX+n*(b.width-2*c),b.currentY=b.touchesCurrent.y-b.touchesStart.y+b.startY+n*(b.height-2*p),b.currentXb.maxX&&(b.currentX=b.maxX-1+(b.currentX-b.maxX+1)**.8),b.currentYb.maxY&&(b.currentY=b.maxY-1+(b.currentY-b.maxY+1)**.8),y.prevPositionX||(y.prevPositionX=b.touchesCurrent.x),y.prevPositionY||(y.prevPositionY=b.touchesCurrent.y),y.prevTime||(y.prevTime=Date.now()),y.x=(b.touchesCurrent.x-y.prevPositionX)/(Date.now()-y.prevTime)/2,y.y=(b.touchesCurrent.y-y.prevPositionY)/(Date.now()-y.prevTime)/2,Math.abs(b.touchesCurrent.x-y.prevPositionX)<2&&(y.x=0),Math.abs(b.touchesCurrent.y-y.prevPositionY)<2&&(y.y=0),y.prevPositionX=b.touchesCurrent.x,y.prevPositionY=b.touchesCurrent.y,y.prevTime=Date.now(),v.imageWrapEl.style.transform=`translate3d(${b.currentX}px, ${b.currentY}px,0)`}function k(){const e=t.zoom;v.slideEl&&t.activeIndex!==t.slides.indexOf(v.slideEl)&&(v.imageEl&&(v.imageEl.style.transform="translate3d(0,0,0) scale(1)"),v.imageWrapEl&&(v.imageWrapEl.style.transform="translate3d(0,0,0)"),v.slideEl.classList.remove(`${t.params.zoom.zoomedSlideClass}`),e.scale=1,l=1,v.slideEl=void 0,v.imageEl=void 0,v.imageWrapEl=void 0,v.originX=0,v.originY=0)}function O(e){if(l<=1||!v.imageWrapEl)return;if(!C(e)||!P(e))return;const t=n.getComputedStyle(v.imageWrapEl).transform,s=new n.DOMMatrix(t);if(!c)return c=!0,p.x=e.clientX,p.y=e.clientY,b.startX=s.e,b.startY=s.f,b.width=v.imageEl.offsetWidth||v.imageEl.clientWidth,b.height=v.imageEl.offsetHeight||v.imageEl.clientHeight,v.slideWidth=v.slideEl.offsetWidth,void(v.slideHeight=v.slideEl.offsetHeight);const a=(e.clientX-p.x)*u,i=(e.clientY-p.y)*u,r=b.width*l,o=b.height*l,d=v.slideWidth,m=v.slideHeight,h=Math.min(d/2-r/2,0),f=-h,g=Math.min(m/2-o/2,0),w=-g,y=Math.max(Math.min(b.startX+a,f),h),E=Math.max(Math.min(b.startY+i,w),g);v.imageWrapEl.style.transitionDuration="0ms",v.imageWrapEl.style.transform=`translate3d(${y}px, ${E}px, 0)`,p.x=e.clientX,p.y=e.clientY,b.startX=y,b.startY=E,b.currentX=y,b.currentY=E}function D(e){const s=t.zoom,a=t.params.zoom;if(!v.slideEl){e&&e.target&&(v.slideEl=e.target.closest(`.${t.params.slideClass}, swiper-slide`)),v.slideEl||(t.params.virtual&&t.params.virtual.enabled&&t.virtual?v.slideEl=f(t.slidesEl,`.${t.params.slideActiveClass}`)[0]:v.slideEl=t.slides[t.activeIndex]);let s=v.slideEl.querySelector(`.${a.containerClass}`);s&&(s=s.querySelectorAll("picture, img, svg, canvas, .swiper-zoom-target")[0]),v.imageEl=s,v.imageWrapEl=s?E(v.imageEl,`.${a.containerClass}`)[0]:void 0}if(!v.imageEl||!v.imageWrapEl)return;let i,r,o,d,c,p,u,m,h,g,y,x,S,T,C,P,L,I;t.params.cssMode&&(t.wrapperEl.style.overflow="hidden",t.wrapperEl.style.touchAction="none"),v.slideEl.classList.add(`${a.zoomedSlideClass}`),void 0===b.touchesStart.x&&e?(i=e.pageX,r=e.pageY):(i=b.touchesStart.x,r=b.touchesStart.y);const z=l,A="number"==typeof e?e:null;1===l&&A&&(i=void 0,r=void 0,b.touchesStart.x=void 0,b.touchesStart.y=void 0);const $=M();s.scale=A||$,l=A||$,!e||1===l&&A?(u=0,m=0):(L=v.slideEl.offsetWidth,I=v.slideEl.offsetHeight,o=w(v.slideEl).left+n.scrollX,d=w(v.slideEl).top+n.scrollY,c=o+L/2-i,p=d+I/2-r,h=v.imageEl.offsetWidth||v.imageEl.clientWidth,g=v.imageEl.offsetHeight||v.imageEl.clientHeight,y=h*s.scale,x=g*s.scale,S=Math.min(L/2-y/2,0),T=Math.min(I/2-x/2,0),C=-S,P=-T,z>0&&A&&"number"==typeof b.currentX&&"number"==typeof b.currentY?(u=b.currentX*s.scale/z,m=b.currentY*s.scale/z):(u=c*s.scale,m=p*s.scale),uC&&(u=C),mP&&(m=P)),A&&1===s.scale&&(v.originX=0,v.originY=0),b.currentX=u,b.currentY=m,v.imageWrapEl.style.transitionDuration="300ms",v.imageWrapEl.style.transform=`translate3d(${u}px, ${m}px,0)`,v.imageEl.style.transitionDuration="300ms",v.imageEl.style.transform=`translate3d(0,0,0) scale(${s.scale})`}function G(){const e=t.zoom,s=t.params.zoom;if(!v.slideEl){t.params.virtual&&t.params.virtual.enabled&&t.virtual?v.slideEl=f(t.slidesEl,`.${t.params.slideActiveClass}`)[0]:v.slideEl=t.slides[t.activeIndex];let e=v.slideEl.querySelector(`.${s.containerClass}`);e&&(e=e.querySelectorAll("picture, img, svg, canvas, .swiper-zoom-target")[0]),v.imageEl=e,v.imageWrapEl=e?E(v.imageEl,`.${s.containerClass}`)[0]:void 0}v.imageEl&&v.imageWrapEl&&(t.params.cssMode&&(t.wrapperEl.style.overflow="",t.wrapperEl.style.touchAction=""),e.scale=1,l=1,b.currentX=void 0,b.currentY=void 0,b.touchesStart.x=void 0,b.touchesStart.y=void 0,v.imageWrapEl.style.transitionDuration="300ms",v.imageWrapEl.style.transform="translate3d(0,0,0)",v.imageEl.style.transitionDuration="300ms",v.imageEl.style.transform="translate3d(0,0,0) scale(1)",v.slideEl.classList.remove(`${s.zoomedSlideClass}`),v.slideEl=void 0,v.originX=0,v.originY=0,t.params.zoom.panOnMouseMove&&(p={x:0,y:0},c&&(c=!1,b.startX=0,b.startY=0)))}function X(e){const s=t.zoom;s.scale&&1!==s.scale?G():D(e)}function H(){return{passiveListener:!!t.params.passiveListeners&&{passive:!0,capture:!1},activeListenerWithCapture:!t.params.passiveListeners||{passive:!1,capture:!0}}}function Y(){const e=t.zoom;if(e.enabled)return;e.enabled=!0;const{passiveListener:s,activeListenerWithCapture:a}=H();t.wrapperEl.addEventListener("pointerdown",L,s),t.wrapperEl.addEventListener("pointermove",I,a),["pointerup","pointercancel","pointerout"].forEach((e=>{t.wrapperEl.addEventListener(e,z,s)})),t.wrapperEl.addEventListener("pointermove",$,a)}function B(){const e=t.zoom;if(!e.enabled)return;e.enabled=!1;const{passiveListener:s,activeListenerWithCapture:a}=H();t.wrapperEl.removeEventListener("pointerdown",L,s),t.wrapperEl.removeEventListener("pointermove",I,a),["pointerup","pointercancel","pointerout"].forEach((e=>{t.wrapperEl.removeEventListener(e,z,s)})),t.wrapperEl.removeEventListener("pointermove",$,a)}Object.defineProperty(t.zoom,"scale",{get:()=>S,set(e){if(S!==e){const t=v.imageEl,s=v.slideEl;i("zoomChange",e,t,s)}S=e}}),a("init",(()=>{t.params.zoom.enabled&&Y()})),a("destroy",(()=>{B()})),a("touchStart",((e,s)=>{t.zoom.enabled&&function(e){const s=t.device;if(!v.imageEl)return;if(b.isTouched)return;s.android&&e.cancelable&&e.preventDefault(),b.isTouched=!0;const a=g.length>0?g[0]:e;b.touchesStart.x=a.pageX,b.touchesStart.y=a.pageY}(s)})),a("touchEnd",((e,s)=>{t.zoom.enabled&&function(){const e=t.zoom;if(g.length=0,!v.imageEl)return;if(!b.isTouched||!b.isMoved)return b.isTouched=!1,void(b.isMoved=!1);b.isTouched=!1,b.isMoved=!1;let s=300,a=300;const i=y.x*s,r=b.currentX+i,n=y.y*a,l=b.currentY+n;0!==y.x&&(s=Math.abs((r-b.currentX)/y.x)),0!==y.y&&(a=Math.abs((l-b.currentY)/y.y));const o=Math.max(s,a);b.currentX=r,b.currentY=l;const d=b.width*e.scale,c=b.height*e.scale;b.minX=Math.min(v.slideWidth/2-d/2,0),b.maxX=-b.minX,b.minY=Math.min(v.slideHeight/2-c/2,0),b.maxY=-b.minY,b.currentX=Math.max(Math.min(b.currentX,b.maxX),b.minX),b.currentY=Math.max(Math.min(b.currentY,b.maxY),b.minY),v.imageWrapEl.style.transitionDuration=`${o}ms`,v.imageWrapEl.style.transform=`translate3d(${b.currentX}px, ${b.currentY}px,0)`}()})),a("doubleTap",((e,s)=>{!t.animating&&t.params.zoom.enabled&&t.zoom.enabled&&t.params.zoom.toggle&&X(s)})),a("transitionEnd",(()=>{t.zoom.enabled&&t.params.zoom.enabled&&k()})),a("slideChange",(()=>{t.zoom.enabled&&t.params.zoom.enabled&&t.params.cssMode&&k()})),Object.assign(t.zoom,{enable:Y,disable:B,in:D,out:G,toggle:X})},function(e){let{swiper:t,extendParams:s,on:a}=e;function i(e,t){const s=function(){let e,t,s;return(a,i)=>{for(t=-1,e=a.length;e-t>1;)s=e+t>>1,a[s]<=i?t=s:e=s;return e}}();let a,i;return this.x=e,this.y=t,this.lastIndex=e.length-1,this.interpolate=function(e){return e?(i=s(this.x,e),a=i-1,(e-this.x[a])*(this.y[i]-this.y[a])/(this.x[i]-this.x[a])+this.y[a]):0},this}function r(){t.controller.control&&t.controller.spline&&(t.controller.spline=void 0,delete t.controller.spline)}s({controller:{control:void 0,inverse:!1,by:"slide"}}),t.controller={control:void 0},a("beforeInit",(()=>{if("undefined"!=typeof window&&("string"==typeof t.params.controller.control||t.params.controller.control instanceof HTMLElement)){("string"==typeof t.params.controller.control?[...document.querySelectorAll(t.params.controller.control)]:[t.params.controller.control]).forEach((e=>{if(t.controller.control||(t.controller.control=[]),e&&e.swiper)t.controller.control.push(e.swiper);else if(e){const s=`${t.params.eventsPrefix}init`,a=i=>{t.controller.control.push(i.detail[0]),t.update(),e.removeEventListener(s,a)};e.addEventListener(s,a)}}))}else t.controller.control=t.params.controller.control})),a("update",(()=>{r()})),a("resize",(()=>{r()})),a("observerUpdate",(()=>{r()})),a("setTranslate",((e,s,a)=>{t.controller.control&&!t.controller.control.destroyed&&t.controller.setTranslate(s,a)})),a("setTransition",((e,s,a)=>{t.controller.control&&!t.controller.control.destroyed&&t.controller.setTransition(s,a)})),Object.assign(t.controller,{setTranslate:function(e,s){const a=t.controller.control;let r,n;const l=t.constructor;function o(e){if(e.destroyed)return;const s=t.rtlTranslate?-t.translate:t.translate;"slide"===t.params.controller.by&&(!function(e){t.controller.spline=t.params.loop?new i(t.slidesGrid,e.slidesGrid):new i(t.snapGrid,e.snapGrid)}(e),n=-t.controller.spline.interpolate(-s)),n&&"container"!==t.params.controller.by||(r=(e.maxTranslate()-e.minTranslate())/(t.maxTranslate()-t.minTranslate()),!Number.isNaN(r)&&Number.isFinite(r)||(r=1),n=(s-t.minTranslate())*r+e.minTranslate()),t.params.controller.inverse&&(n=e.maxTranslate()-n),e.updateProgress(n),e.setTranslate(n,t),e.updateActiveIndex(),e.updateSlidesClasses()}if(Array.isArray(a))for(let e=0;e{s.updateAutoHeight()})),x(s.wrapperEl,(()=>{i&&s.transitionEnd()}))))}if(Array.isArray(i))for(r=0;r{e.setAttribute("tabIndex","0")}))}function p(e){(e=T(e)).forEach((e=>{e.setAttribute("tabIndex","-1")}))}function u(e,t){(e=T(e)).forEach((e=>{e.setAttribute("role",t)}))}function m(e,t){(e=T(e)).forEach((e=>{e.setAttribute("aria-roledescription",t)}))}function h(e,t){(e=T(e)).forEach((e=>{e.setAttribute("aria-label",t)}))}function f(e){(e=T(e)).forEach((e=>{e.setAttribute("aria-disabled",!0)}))}function g(e){(e=T(e)).forEach((e=>{e.setAttribute("aria-disabled",!1)}))}function w(e){if(13!==e.keyCode&&32!==e.keyCode)return;const s=t.params.a11y,a=e.target;if(!t.pagination||!t.pagination.el||a!==t.pagination.el&&!t.pagination.el.contains(e.target)||e.target.matches(ne(t.params.pagination.bulletClass))){if(t.navigation&&t.navigation.prevEl&&t.navigation.nextEl){const e=T(t.navigation.prevEl);T(t.navigation.nextEl).includes(a)&&(t.isEnd&&!t.params.loop||t.slideNext(),t.isEnd?d(s.lastSlideMessage):d(s.nextSlideMessage)),e.includes(a)&&(t.isBeginning&&!t.params.loop||t.slidePrev(),t.isBeginning?d(s.firstSlideMessage):d(s.prevSlideMessage))}t.pagination&&a.matches(ne(t.params.pagination.bulletClass))&&a.click()}}function b(){return t.pagination&&t.pagination.bullets&&t.pagination.bullets.length}function E(){return b()&&t.params.pagination.clickable}const x=(e,t,s)=>{c(e),"BUTTON"!==e.tagName&&(u(e,"button"),e.addEventListener("keydown",w)),h(e,s),function(e,t){(e=T(e)).forEach((e=>{e.setAttribute("aria-controls",t)}))}(e,t)},S=e=>{n&&n!==e.target&&!n.contains(e.target)&&(r=!0),t.a11y.clicked=!0},M=()=>{r=!1,requestAnimationFrame((()=>{requestAnimationFrame((()=>{t.destroyed||(t.a11y.clicked=!1)}))}))},C=e=>{o=(new Date).getTime()},P=e=>{if(t.a11y.clicked||!t.params.a11y.scrollOnFocus)return;if((new Date).getTime()-o<100)return;const s=e.target.closest(`.${t.params.slideClass}, swiper-slide`);if(!s||!t.slides.includes(s))return;n=s;const a=t.slides.indexOf(s)===t.activeIndex,i=t.params.watchSlidesProgress&&t.visibleSlides&&t.visibleSlides.includes(s);a||i||e.sourceCapabilities&&e.sourceCapabilities.firesTouchEvents||(t.isHorizontal()?t.el.scrollLeft=0:t.el.scrollTop=0,requestAnimationFrame((()=>{r||(t.params.loop?t.slideToLoop(parseInt(s.getAttribute("data-swiper-slide-index")),0):t.slideTo(t.slides.indexOf(s),0),r=!1)})))},L=()=>{const e=t.params.a11y;e.itemRoleDescriptionMessage&&m(t.slides,e.itemRoleDescriptionMessage),e.slideRole&&u(t.slides,e.slideRole);const s=t.slides.length;e.slideLabelMessage&&t.slides.forEach(((a,i)=>{const r=t.params.loop?parseInt(a.getAttribute("data-swiper-slide-index"),10):i;h(a,e.slideLabelMessage.replace(/\{\{index\}\}/,r+1).replace(/\{\{slidesLength\}\}/,s))}))},I=()=>{const e=t.params.a11y;t.el.append(l);const s=t.el;e.containerRoleDescriptionMessage&&m(s,e.containerRoleDescriptionMessage),e.containerMessage&&h(s,e.containerMessage),e.containerRole&&u(s,e.containerRole);const i=t.wrapperEl,r=e.id||i.getAttribute("id")||`swiper-wrapper-${n=16,void 0===n&&(n=16),"x".repeat(n).replace(/x/g,(()=>Math.round(16*Math.random()).toString(16)))}`;var n;const o=t.params.autoplay&&t.params.autoplay.enabled?"off":"polite";var d;d=r,T(i).forEach((e=>{e.setAttribute("id",d)})),function(e,t){(e=T(e)).forEach((e=>{e.setAttribute("aria-live",t)}))}(i,o),L();let{nextEl:c,prevEl:p}=t.navigation?t.navigation:{};if(c=T(c),p=T(p),c&&c.forEach((t=>x(t,r,e.nextSlideMessage))),p&&p.forEach((t=>x(t,r,e.prevSlideMessage))),E()){T(t.pagination.el).forEach((e=>{e.addEventListener("keydown",w)}))}a().addEventListener("visibilitychange",C),t.el.addEventListener("focus",P,!0),t.el.addEventListener("focus",P,!0),t.el.addEventListener("pointerdown",S,!0),t.el.addEventListener("pointerup",M,!0)};i("beforeInit",(()=>{l=v("span",t.params.a11y.notificationClass),l.setAttribute("aria-live","assertive"),l.setAttribute("aria-atomic","true")})),i("afterInit",(()=>{t.params.a11y.enabled&&I()})),i("slidesLengthChange snapGridLengthChange slidesGridLengthChange",(()=>{t.params.a11y.enabled&&L()})),i("fromEdge toEdge afterInit lock unlock",(()=>{t.params.a11y.enabled&&function(){if(t.params.loop||t.params.rewind||!t.navigation)return;const{nextEl:e,prevEl:s}=t.navigation;s&&(t.isBeginning?(f(s),p(s)):(g(s),c(s))),e&&(t.isEnd?(f(e),p(e)):(g(e),c(e)))}()})),i("paginationUpdate",(()=>{t.params.a11y.enabled&&function(){const e=t.params.a11y;b()&&t.pagination.bullets.forEach((s=>{t.params.pagination.clickable&&(c(s),t.params.pagination.renderBullet||(u(s,"button"),h(s,e.paginationBulletMessage.replace(/\{\{index\}\}/,y(s)+1)))),s.matches(ne(t.params.pagination.bulletActiveClass))?s.setAttribute("aria-current","true"):s.removeAttribute("aria-current")}))}()})),i("destroy",(()=>{t.params.a11y.enabled&&function(){l&&l.remove();let{nextEl:e,prevEl:s}=t.navigation?t.navigation:{};e=T(e),s=T(s),e&&e.forEach((e=>e.removeEventListener("keydown",w))),s&&s.forEach((e=>e.removeEventListener("keydown",w))),E()&&T(t.pagination.el).forEach((e=>{e.removeEventListener("keydown",w)}));a().removeEventListener("visibilitychange",C),t.el&&"string"!=typeof t.el&&(t.el.removeEventListener("focus",P,!0),t.el.removeEventListener("pointerdown",S,!0),t.el.removeEventListener("pointerup",M,!0))}()}))},function(e){let{swiper:t,extendParams:s,on:a}=e;s({history:{enabled:!1,root:"",replaceState:!1,key:"slides",keepQuery:!1}});let i=!1,n={};const l=e=>e.toString().replace(/\s+/g,"-").replace(/[^\w-]+/g,"").replace(/--+/g,"-").replace(/^-+/,"").replace(/-+$/,""),o=e=>{const t=r();let s;s=e?new URL(e):t.location;const a=s.pathname.slice(1).split("/").filter((e=>""!==e)),i=a.length;return{key:a[i-2],value:a[i-1]}},d=(e,s)=>{const a=r();if(!i||!t.params.history.enabled)return;let n;n=t.params.url?new URL(t.params.url):a.location;const o=t.virtual&&t.params.virtual.enabled?t.slidesEl.querySelector(`[data-swiper-slide-index="${s}"]`):t.slides[s];let d=l(o.getAttribute("data-history"));if(t.params.history.root.length>0){let s=t.params.history.root;"/"===s[s.length-1]&&(s=s.slice(0,s.length-1)),d=`${s}/${e?`${e}/`:""}${d}`}else n.pathname.includes(e)||(d=`${e?`${e}/`:""}${d}`);t.params.history.keepQuery&&(d+=n.search);const c=a.history.state;c&&c.value===d||(t.params.history.replaceState?a.history.replaceState({value:d},null,d):a.history.pushState({value:d},null,d))},c=(e,s,a)=>{if(s)for(let i=0,r=t.slides.length;i{n=o(t.params.url),c(t.params.speed,n.value,!1)};a("init",(()=>{t.params.history.enabled&&(()=>{const e=r();if(t.params.history){if(!e.history||!e.history.pushState)return t.params.history.enabled=!1,void(t.params.hashNavigation.enabled=!0);i=!0,n=o(t.params.url),n.key||n.value?(c(0,n.value,t.params.runCallbacksOnInit),t.params.history.replaceState||e.addEventListener("popstate",p)):t.params.history.replaceState||e.addEventListener("popstate",p)}})()})),a("destroy",(()=>{t.params.history.enabled&&(()=>{const e=r();t.params.history.replaceState||e.removeEventListener("popstate",p)})()})),a("transitionEnd _freeModeNoMomentumRelease",(()=>{i&&d(t.params.history.key,t.activeIndex)})),a("slideChange",(()=>{i&&t.params.cssMode&&d(t.params.history.key,t.activeIndex)}))},function(e){let{swiper:t,extendParams:s,emit:i,on:n}=e,l=!1;const o=a(),d=r();s({hashNavigation:{enabled:!1,replaceState:!1,watchState:!1,getSlideIndex(e,s){if(t.virtual&&t.params.virtual.enabled){const e=t.slides.find((e=>e.getAttribute("data-hash")===s));if(!e)return 0;return parseInt(e.getAttribute("data-swiper-slide-index"),10)}return t.getSlideIndex(f(t.slidesEl,`.${t.params.slideClass}[data-hash="${s}"], swiper-slide[data-hash="${s}"]`)[0])}}});const c=()=>{i("hashChange");const e=o.location.hash.replace("#",""),s=t.virtual&&t.params.virtual.enabled?t.slidesEl.querySelector(`[data-swiper-slide-index="${t.activeIndex}"]`):t.slides[t.activeIndex];if(e!==(s?s.getAttribute("data-hash"):"")){const s=t.params.hashNavigation.getSlideIndex(t,e);if(void 0===s||Number.isNaN(s))return;t.slideTo(s)}},p=()=>{if(!l||!t.params.hashNavigation.enabled)return;const e=t.virtual&&t.params.virtual.enabled?t.slidesEl.querySelector(`[data-swiper-slide-index="${t.activeIndex}"]`):t.slides[t.activeIndex],s=e?e.getAttribute("data-hash")||e.getAttribute("data-history"):"";t.params.hashNavigation.replaceState&&d.history&&d.history.replaceState?(d.history.replaceState(null,null,`#${s}`||""),i("hashSet")):(o.location.hash=s||"",i("hashSet"))};n("init",(()=>{t.params.hashNavigation.enabled&&(()=>{if(!t.params.hashNavigation.enabled||t.params.history&&t.params.history.enabled)return;l=!0;const e=o.location.hash.replace("#","");if(e){const s=0,a=t.params.hashNavigation.getSlideIndex(t,e);t.slideTo(a||0,s,t.params.runCallbacksOnInit,!0)}t.params.hashNavigation.watchState&&d.addEventListener("hashchange",c)})()})),n("destroy",(()=>{t.params.hashNavigation.enabled&&t.params.hashNavigation.watchState&&d.removeEventListener("hashchange",c)})),n("transitionEnd _freeModeNoMomentumRelease",(()=>{l&&p()})),n("slideChange",(()=>{l&&t.params.cssMode&&p()}))},function(e){let t,s,{swiper:i,extendParams:r,on:n,emit:l,params:o}=e;i.autoplay={running:!1,paused:!1,timeLeft:0},r({autoplay:{enabled:!1,delay:3e3,waitForTransition:!0,disableOnInteraction:!1,stopOnLastSlide:!1,reverseDirection:!1,pauseOnMouseEnter:!1}});let d,c,p,u,m,h,f,g,v=o&&o.autoplay?o.autoplay.delay:3e3,w=o&&o.autoplay?o.autoplay.delay:3e3,b=(new Date).getTime();function y(e){i&&!i.destroyed&&i.wrapperEl&&e.target===i.wrapperEl&&(i.wrapperEl.removeEventListener("transitionend",y),g||e.detail&&e.detail.bySwiperTouchMove||C())}const E=()=>{if(i.destroyed||!i.autoplay.running)return;i.autoplay.paused?c=!0:c&&(w=d,c=!1);const e=i.autoplay.paused?d:b+w-(new Date).getTime();i.autoplay.timeLeft=e,l("autoplayTimeLeft",e,e/v),s=requestAnimationFrame((()=>{E()}))},x=e=>{if(i.destroyed||!i.autoplay.running)return;cancelAnimationFrame(s),E();let a=void 0===e?i.params.autoplay.delay:e;v=i.params.autoplay.delay,w=i.params.autoplay.delay;const r=(()=>{let e;if(e=i.virtual&&i.params.virtual.enabled?i.slides.find((e=>e.classList.contains("swiper-slide-active"))):i.slides[i.activeIndex],!e)return;return parseInt(e.getAttribute("data-swiper-autoplay"),10)})();!Number.isNaN(r)&&r>0&&void 0===e&&(a=r,v=r,w=r),d=a;const n=i.params.speed,o=()=>{i&&!i.destroyed&&(i.params.autoplay.reverseDirection?!i.isBeginning||i.params.loop||i.params.rewind?(i.slidePrev(n,!0,!0),l("autoplay")):i.params.autoplay.stopOnLastSlide||(i.slideTo(i.slides.length-1,n,!0,!0),l("autoplay")):!i.isEnd||i.params.loop||i.params.rewind?(i.slideNext(n,!0,!0),l("autoplay")):i.params.autoplay.stopOnLastSlide||(i.slideTo(0,n,!0,!0),l("autoplay")),i.params.cssMode&&(b=(new Date).getTime(),requestAnimationFrame((()=>{x()}))))};return a>0?(clearTimeout(t),t=setTimeout((()=>{o()}),a)):requestAnimationFrame((()=>{o()})),a},S=()=>{b=(new Date).getTime(),i.autoplay.running=!0,x(),l("autoplayStart")},T=()=>{i.autoplay.running=!1,clearTimeout(t),cancelAnimationFrame(s),l("autoplayStop")},M=(e,s)=>{if(i.destroyed||!i.autoplay.running)return;clearTimeout(t),e||(f=!0);const a=()=>{l("autoplayPause"),i.params.autoplay.waitForTransition?i.wrapperEl.addEventListener("transitionend",y):C()};if(i.autoplay.paused=!0,s)return h&&(d=i.params.autoplay.delay),h=!1,void a();const r=d||i.params.autoplay.delay;d=r-((new Date).getTime()-b),i.isEnd&&d<0&&!i.params.loop||(d<0&&(d=0),a())},C=()=>{i.isEnd&&d<0&&!i.params.loop||i.destroyed||!i.autoplay.running||(b=(new Date).getTime(),f?(f=!1,x(d)):x(),i.autoplay.paused=!1,l("autoplayResume"))},P=()=>{if(i.destroyed||!i.autoplay.running)return;const e=a();"hidden"===e.visibilityState&&(f=!0,M(!0)),"visible"===e.visibilityState&&C()},L=e=>{"mouse"===e.pointerType&&(f=!0,g=!0,i.animating||i.autoplay.paused||M(!0))},I=e=>{"mouse"===e.pointerType&&(g=!1,i.autoplay.paused&&C())};n("init",(()=>{i.params.autoplay.enabled&&(i.params.autoplay.pauseOnMouseEnter&&(i.el.addEventListener("pointerenter",L),i.el.addEventListener("pointerleave",I)),a().addEventListener("visibilitychange",P),S())})),n("destroy",(()=>{i.el&&"string"!=typeof i.el&&(i.el.removeEventListener("pointerenter",L),i.el.removeEventListener("pointerleave",I)),a().removeEventListener("visibilitychange",P),i.autoplay.running&&T()})),n("_freeModeStaticRelease",(()=>{(u||f)&&C()})),n("_freeModeNoMomentumRelease",(()=>{i.params.autoplay.disableOnInteraction?T():M(!0,!0)})),n("beforeTransitionStart",((e,t,s)=>{!i.destroyed&&i.autoplay.running&&(s||!i.params.autoplay.disableOnInteraction?M(!0,!0):T())})),n("sliderFirstMove",(()=>{!i.destroyed&&i.autoplay.running&&(i.params.autoplay.disableOnInteraction?T():(p=!0,u=!1,f=!1,m=setTimeout((()=>{f=!0,u=!0,M(!0)}),200)))})),n("touchEnd",(()=>{if(!i.destroyed&&i.autoplay.running&&p){if(clearTimeout(m),clearTimeout(t),i.params.autoplay.disableOnInteraction)return u=!1,void(p=!1);u&&i.params.cssMode&&C(),u=!1,p=!1}})),n("slideChange",(()=>{!i.destroyed&&i.autoplay.running&&(h=!0)})),Object.assign(i.autoplay,{start:S,stop:T,pause:M,resume:C})},function(e){let{swiper:t,extendParams:s,on:i}=e;s({thumbs:{swiper:null,multipleActiveThumbs:!0,autoScrollOffset:0,slideThumbActiveClass:"swiper-slide-thumb-active",thumbsContainerClass:"swiper-thumbs"}});let r=!1,n=!1;function l(){const e=t.thumbs.swiper;if(!e||e.destroyed)return;const s=e.clickedIndex,a=e.clickedSlide;if(a&&a.classList.contains(t.params.thumbs.slideThumbActiveClass))return;if(null==s)return;let i;i=e.params.loop?parseInt(e.clickedSlide.getAttribute("data-swiper-slide-index"),10):s,t.params.loop?t.slideToLoop(i):t.slideTo(i)}function o(){const{thumbs:e}=t.params;if(r)return!1;r=!0;const s=t.constructor;if(e.swiper instanceof s){if(e.swiper.destroyed)return r=!1,!1;t.thumbs.swiper=e.swiper,Object.assign(t.thumbs.swiper.originalParams,{watchSlidesProgress:!0,slideToClickedSlide:!1}),Object.assign(t.thumbs.swiper.params,{watchSlidesProgress:!0,slideToClickedSlide:!1}),t.thumbs.swiper.update()}else if(c(e.swiper)){const a=Object.assign({},e.swiper);Object.assign(a,{watchSlidesProgress:!0,slideToClickedSlide:!1}),t.thumbs.swiper=new s(a),n=!0}return t.thumbs.swiper.el.classList.add(t.params.thumbs.thumbsContainerClass),t.thumbs.swiper.on("tap",l),!0}function d(e){const s=t.thumbs.swiper;if(!s||s.destroyed)return;const a="auto"===s.params.slidesPerView?s.slidesPerViewDynamic():s.params.slidesPerView;let i=1;const r=t.params.thumbs.slideThumbActiveClass;if(t.params.slidesPerView>1&&!t.params.centeredSlides&&(i=t.params.slidesPerView),t.params.thumbs.multipleActiveThumbs||(i=1),i=Math.floor(i),s.slides.forEach((e=>e.classList.remove(r))),s.params.loop||s.params.virtual&&s.params.virtual.enabled)for(let e=0;e{e.classList.add(r)}));else for(let e=0;ee.getAttribute("data-swiper-slide-index")===`${t.realIndex}`));r=s.slides.indexOf(e),o=t.activeIndex>t.previousIndex?"next":"prev"}else r=t.realIndex,o=r>t.previousIndex?"next":"prev";l&&(r+="next"===o?n:-1*n),s.visibleSlidesIndexes&&s.visibleSlidesIndexes.indexOf(r)<0&&(s.params.centeredSlides?r=r>i?r-Math.floor(a/2)+1:r+Math.floor(a/2)-1:r>i&&s.params.slidesPerGroup,s.slideTo(r,e?0:void 0))}}t.thumbs={swiper:null},i("beforeInit",(()=>{const{thumbs:e}=t.params;if(e&&e.swiper)if("string"==typeof e.swiper||e.swiper instanceof HTMLElement){const s=a(),i=()=>{const a="string"==typeof e.swiper?s.querySelector(e.swiper):e.swiper;if(a&&a.swiper)e.swiper=a.swiper,o(),d(!0);else if(a){const s=`${t.params.eventsPrefix}init`,i=r=>{e.swiper=r.detail[0],a.removeEventListener(s,i),o(),d(!0),e.swiper.update(),t.update()};a.addEventListener(s,i)}return a},r=()=>{if(t.destroyed)return;i()||requestAnimationFrame(r)};requestAnimationFrame(r)}else o(),d(!0)})),i("slideChange update resize observerUpdate",(()=>{d()})),i("setTransition",((e,s)=>{const a=t.thumbs.swiper;a&&!a.destroyed&&a.setTransition(s)})),i("beforeDestroy",(()=>{const e=t.thumbs.swiper;e&&!e.destroyed&&n&&e.destroy()})),Object.assign(t.thumbs,{init:o,update:d})},function(e){let{swiper:t,extendParams:s,emit:a,once:i}=e;s({freeMode:{enabled:!1,momentum:!0,momentumRatio:1,momentumBounce:!0,momentumBounceRatio:1,momentumVelocityRatio:1,sticky:!1,minimumVelocity:.02}}),Object.assign(t,{freeMode:{onTouchStart:function(){if(t.params.cssMode)return;const e=t.getTranslate();t.setTranslate(e),t.setTransition(0),t.touchEventsData.velocities.length=0,t.freeMode.onTouchEnd({currentPos:t.rtl?t.translate:-t.translate})},onTouchMove:function(){if(t.params.cssMode)return;const{touchEventsData:e,touches:s}=t;0===e.velocities.length&&e.velocities.push({position:s[t.isHorizontal()?"startX":"startY"],time:e.touchStartTime}),e.velocities.push({position:s[t.isHorizontal()?"currentX":"currentY"],time:o()})},onTouchEnd:function(e){let{currentPos:s}=e;if(t.params.cssMode)return;const{params:r,wrapperEl:n,rtlTranslate:l,snapGrid:d,touchEventsData:c}=t,p=o()-c.touchStartTime;if(s<-t.minTranslate())t.slideTo(t.activeIndex);else if(s>-t.maxTranslate())t.slides.length1){const e=c.velocities.pop(),s=c.velocities.pop(),a=e.position-s.position,i=e.time-s.time;t.velocity=a/i,t.velocity/=2,Math.abs(t.velocity)150||o()-e.time>300)&&(t.velocity=0)}else t.velocity=0;t.velocity*=r.freeMode.momentumVelocityRatio,c.velocities.length=0;let e=1e3*r.freeMode.momentumRatio;const s=t.velocity*e;let p=t.translate+s;l&&(p=-p);let u,m=!1;const h=20*Math.abs(t.velocity)*r.freeMode.momentumBounceRatio;let f;if(pt.minTranslate())r.freeMode.momentumBounce?(p-t.minTranslate()>h&&(p=t.minTranslate()+h),u=t.minTranslate(),m=!0,c.allowMomentumBounce=!0):p=t.minTranslate(),r.loop&&r.centeredSlides&&(f=!0);else if(r.freeMode.sticky){let e;for(let t=0;t-p){e=t;break}p=Math.abs(d[e]-p){t.loopFix()})),0!==t.velocity){if(e=l?Math.abs((-p-t.translate)/t.velocity):Math.abs((p-t.translate)/t.velocity),r.freeMode.sticky){const s=Math.abs((l?-p:p)-t.translate),a=t.slidesSizesGrid[t.activeIndex];e=s{t&&!t.destroyed&&c.allowMomentumBounce&&(a("momentumBounce"),t.setTransition(r.speed),setTimeout((()=>{t.setTranslate(u),x(n,(()=>{t&&!t.destroyed&&t.transitionEnd()}))}),0))}))):t.velocity?(a("_freeModeNoMomentumRelease"),t.updateProgress(p),t.setTransition(e),t.setTranslate(p),t.transitionStart(!0,t.swipeDirection),t.animating||(t.animating=!0,x(n,(()=>{t&&!t.destroyed&&t.transitionEnd()})))):t.updateProgress(p),t.updateActiveIndex(),t.updateSlidesClasses()}else{if(r.freeMode.sticky)return void t.slideToClosest();r.freeMode&&a("_freeModeNoMomentumRelease")}(!r.freeMode.momentum||p>=r.longSwipesMs)&&(a("_freeModeStaticRelease"),t.updateProgress(),t.updateActiveIndex(),t.updateSlidesClasses())}}}})},function(e){let t,s,a,i,{swiper:r,extendParams:n,on:l}=e;n({grid:{rows:1,fill:"column"}});const o=()=>{let e=r.params.spaceBetween;return"string"==typeof e&&e.indexOf("%")>=0?e=parseFloat(e.replace("%",""))/100*r.size:"string"==typeof e&&(e=parseFloat(e)),e};l("init",(()=>{i=r.params.grid&&r.params.grid.rows>1})),l("update",(()=>{const{params:e,el:t}=r,s=e.grid&&e.grid.rows>1;i&&!s?(t.classList.remove(`${e.containerModifierClass}grid`,`${e.containerModifierClass}grid-column`),a=1,r.emitContainerClasses()):!i&&s&&(t.classList.add(`${e.containerModifierClass}grid`),"column"===e.grid.fill&&t.classList.add(`${e.containerModifierClass}grid-column`),r.emitContainerClasses()),i=s})),r.grid={initSlides:e=>{const{slidesPerView:i}=r.params,{rows:n,fill:l}=r.params.grid,o=r.virtual&&r.params.virtual.enabled?r.virtual.slides.length:e.length;a=Math.floor(o/n),t=Math.floor(o/n)===o/n?o:Math.ceil(o/n)*n,"auto"!==i&&"row"===l&&(t=Math.max(t,i*n)),s=t/n},unsetSlides:()=>{r.slides&&r.slides.forEach((e=>{e.swiperSlideGridSet&&(e.style.height="",e.style[r.getDirectionLabel("margin-top")]="")}))},updateSlide:(e,i,n)=>{const{slidesPerGroup:l}=r.params,d=o(),{rows:c,fill:p}=r.params.grid,u=r.virtual&&r.params.virtual.enabled?r.virtual.slides.length:n.length;let m,h,f;if("row"===p&&l>1){const s=Math.floor(e/(l*c)),a=e-c*l*s,r=0===s?l:Math.min(Math.ceil((u-s*c*l)/c),l);f=Math.floor(a/r),h=a-f*r+s*l,m=h+f*t/c,i.style.order=m}else"column"===p?(h=Math.floor(e/c),f=e-h*c,(h>a||h===a&&f===c-1)&&(f+=1,f>=c&&(f=0,h+=1))):(f=Math.floor(e/s),h=e-f*s);i.row=f,i.column=h,i.style.height=`calc((100% - ${(c-1)*d}px) / ${c})`,i.style[r.getDirectionLabel("margin-top")]=0!==f?d&&`${d}px`:"",i.swiperSlideGridSet=!0},updateWrapperSize:(e,s)=>{const{centeredSlides:a,roundLengths:i}=r.params,n=o(),{rows:l}=r.params.grid;if(r.virtualSize=(e+n)*t,r.virtualSize=Math.ceil(r.virtualSize/l)-n,r.params.cssMode||(r.wrapperEl.style[r.getDirectionLabel("width")]=`${r.virtualSize+n}px`),a){const e=[];for(let t=0;t{const{slides:e}=t;t.params.fadeEffect;for(let s=0;s{const s=t.slides.map((e=>h(e)));s.forEach((t=>{t.style.transitionDuration=`${e}ms`})),he({swiper:t,duration:e,transformElements:s,allSlides:!0})},overwriteParams:()=>({slidesPerView:1,slidesPerGroup:1,watchSlidesProgress:!0,spaceBetween:0,virtualTranslate:!t.params.cssMode})})},function(e){let{swiper:t,extendParams:s,on:a}=e;s({cubeEffect:{slideShadows:!0,shadow:!0,shadowOffset:20,shadowScale:.94}});const i=(e,t,s)=>{let a=s?e.querySelector(".swiper-slide-shadow-left"):e.querySelector(".swiper-slide-shadow-top"),i=s?e.querySelector(".swiper-slide-shadow-right"):e.querySelector(".swiper-slide-shadow-bottom");a||(a=v("div",("swiper-slide-shadow-cube swiper-slide-shadow-"+(s?"left":"top")).split(" ")),e.append(a)),i||(i=v("div",("swiper-slide-shadow-cube swiper-slide-shadow-"+(s?"right":"bottom")).split(" ")),e.append(i)),a&&(a.style.opacity=Math.max(-t,0)),i&&(i.style.opacity=Math.max(t,0))};ue({effect:"cube",swiper:t,on:a,setTranslate:()=>{const{el:e,wrapperEl:s,slides:a,width:r,height:n,rtlTranslate:l,size:o,browser:d}=t,c=M(t),p=t.params.cubeEffect,u=t.isHorizontal(),m=t.virtual&&t.params.virtual.enabled;let h,f=0;p.shadow&&(u?(h=t.wrapperEl.querySelector(".swiper-cube-shadow"),h||(h=v("div","swiper-cube-shadow"),t.wrapperEl.append(h)),h.style.height=`${r}px`):(h=e.querySelector(".swiper-cube-shadow"),h||(h=v("div","swiper-cube-shadow"),e.append(h))));for(let e=0;e-1&&(f=90*s+90*d,l&&(f=90*-s-90*d)),t.style.transform=w,p.slideShadows&&i(t,d,u)}if(s.style.transformOrigin=`50% 50% -${o/2}px`,s.style["-webkit-transform-origin"]=`50% 50% -${o/2}px`,p.shadow)if(u)h.style.transform=`translate3d(0px, ${r/2+p.shadowOffset}px, ${-r/2}px) rotateX(89.99deg) rotateZ(0deg) scale(${p.shadowScale})`;else{const e=Math.abs(f)-90*Math.floor(Math.abs(f)/90),t=1.5-(Math.sin(2*e*Math.PI/360)/2+Math.cos(2*e*Math.PI/360)/2),s=p.shadowScale,a=p.shadowScale/t,i=p.shadowOffset;h.style.transform=`scale3d(${s}, 1, ${a}) translate3d(0px, ${n/2+i}px, ${-n/2/a}px) rotateX(-89.99deg)`}const g=(d.isSafari||d.isWebView)&&d.needPerspectiveFix?-o/2:0;s.style.transform=`translate3d(0px,0,${g}px) rotateX(${c(t.isHorizontal()?0:f)}deg) rotateY(${c(t.isHorizontal()?-f:0)}deg)`,s.style.setProperty("--swiper-cube-translate-z",`${g}px`)},setTransition:e=>{const{el:s,slides:a}=t;if(a.forEach((t=>{t.style.transitionDuration=`${e}ms`,t.querySelectorAll(".swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left").forEach((t=>{t.style.transitionDuration=`${e}ms`}))})),t.params.cubeEffect.shadow&&!t.isHorizontal()){const t=s.querySelector(".swiper-cube-shadow");t&&(t.style.transitionDuration=`${e}ms`)}},recreateShadows:()=>{const e=t.isHorizontal();t.slides.forEach((t=>{const s=Math.max(Math.min(t.progress,1),-1);i(t,s,e)}))},getEffectParams:()=>t.params.cubeEffect,perspective:()=>!0,overwriteParams:()=>({slidesPerView:1,slidesPerGroup:1,watchSlidesProgress:!0,resistanceRatio:0,spaceBetween:0,centeredSlides:!1,virtualTranslate:!0})})},function(e){let{swiper:t,extendParams:s,on:a}=e;s({flipEffect:{slideShadows:!0,limitRotation:!0}});const i=(e,s)=>{let a=t.isHorizontal()?e.querySelector(".swiper-slide-shadow-left"):e.querySelector(".swiper-slide-shadow-top"),i=t.isHorizontal()?e.querySelector(".swiper-slide-shadow-right"):e.querySelector(".swiper-slide-shadow-bottom");a||(a=fe("flip",e,t.isHorizontal()?"left":"top")),i||(i=fe("flip",e,t.isHorizontal()?"right":"bottom")),a&&(a.style.opacity=Math.max(-s,0)),i&&(i.style.opacity=Math.max(s,0))};ue({effect:"flip",swiper:t,on:a,setTranslate:()=>{const{slides:e,rtlTranslate:s}=t,a=t.params.flipEffect,r=M(t);for(let n=0;n{const s=t.slides.map((e=>h(e)));s.forEach((t=>{t.style.transitionDuration=`${e}ms`,t.querySelectorAll(".swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left").forEach((t=>{t.style.transitionDuration=`${e}ms`}))})),he({swiper:t,duration:e,transformElements:s})},recreateShadows:()=>{t.params.flipEffect,t.slides.forEach((e=>{let s=e.progress;t.params.flipEffect.limitRotation&&(s=Math.max(Math.min(e.progress,1),-1)),i(e,s)}))},getEffectParams:()=>t.params.flipEffect,perspective:()=>!0,overwriteParams:()=>({slidesPerView:1,slidesPerGroup:1,watchSlidesProgress:!0,spaceBetween:0,virtualTranslate:!t.params.cssMode})})},function(e){let{swiper:t,extendParams:s,on:a}=e;s({coverflowEffect:{rotate:50,stretch:0,depth:100,scale:1,modifier:1,slideShadows:!0}}),ue({effect:"coverflow",swiper:t,on:a,setTranslate:()=>{const{width:e,height:s,slides:a,slidesSizesGrid:i}=t,r=t.params.coverflowEffect,n=t.isHorizontal(),l=t.translate,o=n?e/2-l:s/2-l,d=n?r.rotate:-r.rotate,c=r.depth,p=M(t);for(let e=0,t=a.length;e0?u:0),s&&(s.style.opacity=-u>0?-u:0)}}},setTransition:e=>{t.slides.map((e=>h(e))).forEach((t=>{t.style.transitionDuration=`${e}ms`,t.querySelectorAll(".swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left").forEach((t=>{t.style.transitionDuration=`${e}ms`}))}))},perspective:()=>!0,overwriteParams:()=>({watchSlidesProgress:!0})})},function(e){let{swiper:t,extendParams:s,on:a}=e;s({creativeEffect:{limitProgress:1,shadowPerProgress:!1,progressMultiplier:1,perspective:!0,prev:{translate:[0,0,0],rotate:[0,0,0],opacity:1,scale:1},next:{translate:[0,0,0],rotate:[0,0,0],opacity:1,scale:1}}});const i=e=>"string"==typeof e?e:`${e}px`;ue({effect:"creative",swiper:t,on:a,setTranslate:()=>{const{slides:e,wrapperEl:s,slidesSizesGrid:a}=t,r=t.params.creativeEffect,{progressMultiplier:n}=r,l=t.params.centeredSlides,o=M(t);if(l){const e=a[0]/2-t.params.slidesOffsetBefore||0;s.style.transform=`translateX(calc(50% - ${e}px))`}for(let s=0;s0&&(g=r.prev,f=!0),m.forEach(((e,t)=>{m[t]=`calc(${e}px + (${i(g.translate[t])} * ${Math.abs(c*n)}))`})),h.forEach(((e,t)=>{let s=g.rotate[t]*Math.abs(c*n);h[t]=s})),a.style.zIndex=-Math.abs(Math.round(d))+e.length;const v=m.join(", "),w=`rotateX(${o(h[0])}deg) rotateY(${o(h[1])}deg) rotateZ(${o(h[2])}deg)`,b=p<0?`scale(${1+(1-g.scale)*p*n})`:`scale(${1-(1-g.scale)*p*n})`,y=p<0?1+(1-g.opacity)*p*n:1-(1-g.opacity)*p*n,E=`translate3d(${v}) ${w} ${b}`;if(f&&g.shadow||!f){let e=a.querySelector(".swiper-slide-shadow");if(!e&&g.shadow&&(e=fe("creative",a)),e){const t=r.shadowPerProgress?c*(1/r.limitProgress):c;e.style.opacity=Math.min(Math.max(Math.abs(t),0),1)}}const x=me(0,a);x.style.transform=E,x.style.opacity=y,g.origin&&(x.style.transformOrigin=g.origin)}},setTransition:e=>{const s=t.slides.map((e=>h(e)));s.forEach((t=>{t.style.transitionDuration=`${e}ms`,t.querySelectorAll(".swiper-slide-shadow").forEach((t=>{t.style.transitionDuration=`${e}ms`}))})),he({swiper:t,duration:e,transformElements:s,allSlides:!0})},perspective:()=>t.params.creativeEffect.perspective,overwriteParams:()=>({watchSlidesProgress:!0,virtualTranslate:!t.params.cssMode})})},function(e){let{swiper:t,extendParams:s,on:a}=e;s({cardsEffect:{slideShadows:!0,rotate:!0,perSlideRotate:2,perSlideOffset:8}}),ue({effect:"cards",swiper:t,on:a,setTranslate:()=>{const{slides:e,activeIndex:s,rtlTranslate:a}=t,i=t.params.cardsEffect,{startTranslate:r,isTouched:n}=t.touchEventsData,l=a?-t.translate:t.translate;for(let o=0;o0&&p<1&&(n||t.params.cssMode)&&l-1&&(n||t.params.cssMode)&&l>r;if(y||E){const e=(1-Math.abs((Math.abs(p)-.5)/.5))**.5;v+=-28*p*e,g+=-.5*e,w+=96*e,h=-25*e*Math.abs(p)+"%"}if(m=p<0?`calc(${m}px ${a?"-":"+"} (${w*Math.abs(p)}%))`:p>0?`calc(${m}px ${a?"-":"+"} (-${w*Math.abs(p)}%))`:`${m}px`,!t.isHorizontal()){const e=h;h=m,m=e}const x=p<0?""+(1+(1-g)*p):""+(1-(1-g)*p),S=`\n translate3d(${m}, ${h}, ${f}px)\n rotateZ(${i.rotate?a?-v:v:0}deg)\n scale(${x})\n `;if(i.slideShadows){let e=d.querySelector(".swiper-slide-shadow");e||(e=fe("cards",d)),e&&(e.style.opacity=Math.min(Math.max((Math.abs(p)-.5)/.5,0),1))}d.style.zIndex=-Math.abs(Math.round(c))+e.length;me(0,d).style.transform=S}},setTransition:e=>{const s=t.slides.map((e=>h(e)));s.forEach((t=>{t.style.transitionDuration=`${e}ms`,t.querySelectorAll(".swiper-slide-shadow").forEach((t=>{t.style.transitionDuration=`${e}ms`}))})),he({swiper:t,duration:e,transformElements:s})},perspective:()=>!0,overwriteParams:()=>({_loopSwapReset:!1,watchSlidesProgress:!0,loopAdditionalSlides:3,centeredSlides:!0,virtualTranslate:!t.params.cssMode})})}];return ie.use(ge),ie}(); -//# sourceMappingURL=swiper-bundle.min.js.map \ No newline at end of file +//# sourceMappingURL=swiper-bundle.min.js.map +// OIDC 통합 로그인 버튼 처리 +(function() { + 'use strict'; + + function openOidcLogin() { + const width = 500; + const height = 600; + const left = (window.screen.width / 2) - (width / 2); + const top = (window.screen.height / 2) - (height / 2); + + window.open( + '/kngil/auth/oidc-login.php', + 'oidc_login_popup', + `width=${width},height=${height},left=${left},top=${top},scrollbars=yes` + ); + } + + function bindOidcButtons() { + const buttons = document.querySelectorAll('[data-oidc-login]'); + if (!buttons.length) return; + buttons.forEach((button) => { + button.addEventListener('click', openOidcLogin); + }); + } + + window.openOidcLogin = openOidcLogin; + window.login = openOidcLogin; + + window.addEventListener('message', (event) => { + if (event.data && event.data.type === 'OIDC_LOGIN_SUCCESS') { + window.location.reload(); + } + }); + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', bindOidcButtons); + } else { + bindOidcButtons(); + } +})(); diff --git a/kngil/js/faq/faq_common.js b/kngil/js/faq/faq_common.js index 54090ce..b8dda33 100644 --- a/kngil/js/faq/faq_common.js +++ b/kngil/js/faq/faq_common.js @@ -894,6 +894,10 @@ function join() { } function login() { + if (typeof window.openOidcLogin === "function") { + window.openOidcLogin(); + return; + } $(".popup_wrap").hide(); //새로고침 없이 다시 팝업창 열었을때 자동 입력된 id, pw 제거 $("#login_id").val(""); diff --git a/kngil/js/login.js b/kngil/js/login.js index 318e79c..af1553b 100644 --- a/kngil/js/login.js +++ b/kngil/js/login.js @@ -35,23 +35,6 @@ if (form) { }) } -// OIDC 로그인 처리 -const btnOidc = document.getElementById('btn_oidc_login') -if (btnOidc) { - btnOidc.addEventListener('click', () => { - const width = 500; - const height = 600; - const left = (window.screen.width / 2) - (width / 2); - const top = (window.screen.height / 2) - (height / 2); - - window.open( - '/kngil/auth/oidc-login.php', - 'oidc_login_popup', - `width=${width},height=${height},left=${left},top=${top},scrollbars=yes` - ); - }); -} - // 팝업으로부터의 메시지 수신 (로그인 성공 시 새로고침) window.addEventListener('message', (event) => { // 보안을 위해 실제 서비스에서는 event.origin을 체크하는 것이 좋습니다. diff --git a/kngil/js/qa/qa_common.js b/kngil/js/qa/qa_common.js index 54090ce..b8dda33 100644 --- a/kngil/js/qa/qa_common.js +++ b/kngil/js/qa/qa_common.js @@ -894,6 +894,10 @@ function join() { } function login() { + if (typeof window.openOidcLogin === "function") { + window.openOidcLogin(); + return; + } $(".popup_wrap").hide(); //새로고침 없이 다시 팝업창 열었을때 자동 입력된 id, pw 제거 $("#login_id").val(""); diff --git a/kngil/log/join.log b/kngil/log/join.log old mode 100644 new mode 100755 diff --git a/kngil/skin/_header.php b/kngil/skin/_header.php index feee73e..76422e8 100644 --- a/kngil/skin/_header.php +++ b/kngil/skin/_header.php @@ -5,6 +5,22 @@ if (session_status() === PHP_SESSION_NONE) { $isLogin = isset($_SESSION['login']); $auth = $_SESSION['login']['auth_bc'] ?? ''; +$loginName = $_SESSION['login']['idp_name'] ?? ($_SESSION['login']['user_nm'] ?? ''); +$loginEmail = $_SESSION['login']['idp_email'] ?? ($_SESSION['login']['email'] ?? ''); +$displayName = $loginName ?: ($loginEmail ?: ''); +$displayEmail = $loginEmail; +if ($displayName === 'Unknown') { + $displayName = ''; +} +if ($displayEmail === 'Unknown') { + $displayEmail = ''; +} +if ($displayName === $displayEmail) { + $displayEmail = ''; +} +if ($displayName === '') { + $displayName = '사용자'; +} // 권한 그룹 $isSuperAdmin = in_array($auth, ['BS100100', 'BS100200']); @@ -30,6 +46,53 @@ $isCompanyAdmin = in_array($auth, ['BS100100', 'BS100200', 'BS100300', 'BS100400 .icon-btn:hover img { opacity: 1; } + .auth-status { + display: inline-flex; + align-items: center; + margin-right: 10px; + } + + .btn-oidc-top { + --color-primary: #f95523; + --color-primary-border: #ca3f14; + background: var(--color-primary, #f95523); + border: 1px solid var(--color-primary-border, #ca3f14); + color: #fff; + padding: 8px 12px; + border-radius: 4px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08); + } + + .btn-oidc-top:hover { + opacity: 0.85; + } + + .auth-user { + color: #fff; + font-size: 13px; + font-weight: 600; + white-space: nowrap; + } + .btn-logout-top { + --color-secondary: #3a3a3a; + --color-secondary-border: #2a2a2a; + background: var(--color-secondary, #3a3a3a); + border: 1px solid var(--color-secondary-border, #2a2a2a); + color: #fff; + padding: 8px 10px; + border-radius: 4px; + font-size: 12px; + font-weight: 600; + margin-left: 8px; + cursor: pointer; + } + + .btn-logout-top:hover { + opacity: 0.85; + } /* 툴팁 래퍼 */ .tooltip { position: relative; @@ -85,16 +148,29 @@ $isCompanyAdmin = in_array($auth, ['BS100100', 'BS100200', 'BS100300', 'BS100400

- KNGIL + KNGIL

+
+ + + + + () + + + 로그아웃 + + + +
- 통합 회원관리 통합 회원관리 @@ -103,7 +179,7 @@ $isCompanyAdmin = in_array($auth, ['BS100100', 'BS100200', 'BS100300', 'BS100400 - 회사 관리자 회사 관리자 @@ -112,30 +188,23 @@ $isCompanyAdmin = in_array($auth, ['BS100100', 'BS100200', 'BS100300', 'BS100400 - - +
- - - -
- -
- diff --git a/kngil/skin/qa_detail.skin.php b/kngil/skin/qa_detail.skin.php index 3287239..0f0a0d5 100644 --- a/kngil/skin/qa_detail.skin.php +++ b/kngil/skin/qa_detail.skin.php @@ -38,7 +38,6 @@ - - diff --git a/kngil/skin/qa_list.skin.php b/kngil/skin/qa_list.skin.php index f79dcb1..42ce3f0 100644 --- a/kngil/skin/qa_list.skin.php +++ b/kngil/skin/qa_list.skin.php @@ -33,7 +33,6 @@ - - - From 5a8320bb78ec6db3851a25b7294b2e707811fae8 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 12:47:38 +0900 Subject: [PATCH 04/21] Add composer install prompt on startup --- docker/entrypoint.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 9b8837f..f9603d2 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -2,6 +2,7 @@ set -e if [ ! -f /var/www/html/kngil/vendor/autoload.php ]; then + echo "[초기화] composer install을 실행합니다..." php /var/www/html/kngil/composer.phar install --working-dir=/var/www/html/kngil --no-interaction --prefer-dist fi From d920cd5249b49c56a1b4955ff83c2fbc0c406ce9 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 13:00:15 +0900 Subject: [PATCH 05/21] Open IDP profile on MyPage --- .env.sample | 1 + README.md | 1 + docker-compose.yml | 1 + kngil/js/faq/faq_common.js | 6 ++++++ kngil/js/mypage.js | 20 ++++++++++++++++++++ kngil/js/qa/qa_common.js | 6 ++++++ kngil/skin/_header.php | 5 +++++ 7 files changed, 40 insertions(+) diff --git a/.env.sample b/.env.sample index 78e16e8..b914814 100644 --- a/.env.sample +++ b/.env.sample @@ -13,3 +13,4 @@ OIDC_CLIENT_ID= OIDC_CLIENT_SECRET= OIDC_REDIRECT_URL= OIDC_SCOPES=openid profile email +IDP_SERVICE_URL= diff --git a/README.md b/README.md index 6db302e..23b4325 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ docker compose down - `OIDC_CLIENT_SECRET` - `OIDC_REDIRECT_URL` - `OIDC_SCOPES` (예: `openid profile email`) +- `IDP_SERVICE_URL` (예: `https://idp.example.com`) ## DB 초기화 - `kngil_DB` 덤프는 **처음 실행 시** 자동으로 로드됩니다. diff --git a/docker-compose.yml b/docker-compose.yml index f4d9872..4685060 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,7 @@ services: OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-} OIDC_REDIRECT_URL: ${OIDC_REDIRECT_URL:-} OIDC_SCOPES: ${OIDC_SCOPES:-} + IDP_SERVICE_URL: ${IDP_SERVICE_URL:-} depends_on: - db diff --git a/kngil/js/faq/faq_common.js b/kngil/js/faq/faq_common.js index b8dda33..12d1539 100644 --- a/kngil/js/faq/faq_common.js +++ b/kngil/js/faq/faq_common.js @@ -910,6 +910,12 @@ function login() { } function mypage01() { + const raw = (window.IDP_SERVICE_URL || "").trim(); + if (raw) { + const url = raw.replace(/\/+$/, "") + "/profile"; + window.open(url, "idp_profile", "noopener,noreferrer"); + return; + } $(".popup_wrap").hide(); $(".btn_close").show(); $("#pop_mypage01").show(0, function () { diff --git a/kngil/js/mypage.js b/kngil/js/mypage.js index 11770fc..16f0c56 100644 --- a/kngil/js/mypage.js +++ b/kngil/js/mypage.js @@ -20,7 +20,27 @@ function hide(id) { 마이페이지 1단계 (비밀번호 인증) - 헤더에서 호출 ========================= */ +function getIdpProfileUrl() { + const raw = (window.IDP_SERVICE_URL || '').trim() + if (!raw) { + return '' + } + return raw.replace(/\/+$/, '') + '/profile' +} + +function openIdpProfile() { + const url = getIdpProfileUrl() + if (!url) { + return false + } + window.open(url, 'idp_profile', 'noopener,noreferrer') + return true +} + window.mypage01 = function () { + if (openIdpProfile()) { + return + } if (!window.IS_LOGIN) { if (typeof window.login === 'function') { window.login() diff --git a/kngil/js/qa/qa_common.js b/kngil/js/qa/qa_common.js index b8dda33..12d1539 100644 --- a/kngil/js/qa/qa_common.js +++ b/kngil/js/qa/qa_common.js @@ -910,6 +910,12 @@ function login() { } function mypage01() { + const raw = (window.IDP_SERVICE_URL || "").trim(); + if (raw) { + const url = raw.replace(/\/+$/, "") + "/profile"; + window.open(url, "idp_profile", "noopener,noreferrer"); + return; + } $(".popup_wrap").hide(); $(".btn_close").show(); $("#pop_mypage01").show(0, function () { diff --git a/kngil/skin/_header.php b/kngil/skin/_header.php index 76422e8..9f19fff 100644 --- a/kngil/skin/_header.php +++ b/kngil/skin/_header.php @@ -3,8 +3,12 @@ if (session_status() === PHP_SESSION_NONE) { session_start(); } +require_once __DIR__ . '/../bbs/env.php'; +kngil_load_env_once(dirname(__DIR__, 2) . '/.env'); + $isLogin = isset($_SESSION['login']); $auth = $_SESSION['login']['auth_bc'] ?? ''; +$idpServiceUrl = getenv('IDP_SERVICE_URL') ?: ''; $loginName = $_SESSION['login']['idp_name'] ?? ($_SESSION['login']['user_nm'] ?? ''); $loginEmail = $_SESSION['login']['idp_email'] ?? ($_SESSION['login']['email'] ?? ''); $displayName = $loginName ?: ($loginEmail ?: ''); @@ -141,6 +145,7 @@ $isCompanyAdmin = in_array($auth, ['BS100100', 'BS100200', 'BS100300', 'BS100400 From 0feda014359d0df5046ce304c744642f3acc7cc5 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 14:02:51 +0900 Subject: [PATCH 06/21] Point admin home link to root --- kngil/skin/adm.php | 2 +- kngil/skin/adm_comp.php | 2 +- kngil/skin/adm_comp1.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/kngil/skin/adm.php b/kngil/skin/adm.php index 5bb721d..6404ce9 100644 --- a/kngil/skin/adm.php +++ b/kngil/skin/adm.php @@ -13,7 +13,7 @@ - + - + - + Date: Wed, 4 Feb 2026 14:40:34 +0900 Subject: [PATCH 07/21] Add deploy workflow --- .gitea/workflows/deploy.yml | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .gitea/workflows/deploy.yml diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..d815f4f --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,47 @@ +name: Deploy (main) + +on: + push: + branches: [ "main" ] + workflow_dispatch: + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to registry + uses: docker/login-action@v3 + with: + registry: ${{ secrets.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: | + ${{ secrets.REGISTRY }}/${{ secrets.IMAGE_NAME }}:latest + ${{ secrets.REGISTRY }}/${{ secrets.IMAGE_NAME }}:${{ github.sha }} + + - name: Deploy via SSH + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USER }} + key: ${{ secrets.SSH_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd ${{ secrets.DEPLOY_PATH }} + docker compose --env-file .env.production pull + docker compose --env-file .env.production up -d From c5c3e30e78b70ae31fb7efae72d619744f10bf36 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 14:42:29 +0900 Subject: [PATCH 08/21] Load .env for deploy --- .gitea/workflows/deploy.yml | 81 ++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index d815f4f..daab41d 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,47 +1,56 @@ name: Deploy (main) on: - push: - branches: [ "main" ] - workflow_dispatch: + push: + branches: ["main"] + workflow_dispatch: jobs: - build-and-deploy: - runs-on: ubuntu-latest - permissions: - contents: read + build-and-deploy: + runs-on: ubuntu-latest + permissions: + contents: read - steps: - - name: Checkout - uses: actions/checkout@v4 + steps: + - name: Checkout + uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Login to registry - uses: docker/login-action@v3 - with: - registry: ${{ secrets.REGISTRY }} - username: ${{ secrets.REGISTRY_USER }} - password: ${{ secrets.REGISTRY_PASSWORD }} + - name: Login to registry + uses: docker/login-action@v3 + with: + registry: ${{ secrets.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: | - ${{ secrets.REGISTRY }}/${{ secrets.IMAGE_NAME }}:latest - ${{ secrets.REGISTRY }}/${{ secrets.IMAGE_NAME }}:${{ github.sha }} + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: | + ${{ secrets.REGISTRY }}/${{ secrets.IMAGE_NAME }}:latest + ${{ secrets.REGISTRY }}/${{ secrets.IMAGE_NAME }}:${{ github.sha }} - - name: Deploy via SSH - uses: appleboy/ssh-action@v1.0.3 - with: - host: ${{ secrets.SSH_HOST }} - username: ${{ secrets.SSH_USER }} - key: ${{ secrets.SSH_KEY }} - port: ${{ secrets.SSH_PORT }} - script: | + - name: Deploy via SSH + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USER }} + key: ${{ secrets.SSH_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | cd ${{ secrets.DEPLOY_PATH }} - docker compose --env-file .env.production pull - docker compose --env-file .env.production up -d + cat << 'EOF' > .env + ${{ secrets.DEPLOY_ENV_FILE }} + EOF + + # Export variables from .env file + set -a + source .env + set +a + + docker compose --env-file .env pull + docker compose --env-file .env up -d From 02f40e9cc3cf88f308b7169762d674c047e88a5d Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 14:54:43 +0900 Subject: [PATCH 09/21] Split build-on-server and registry deploy workflows --- .gitea/workflows/deploy-registry.yml | 54 ++++++++++++++++++++++ .gitea/workflows/deploy.yml | 67 ++++++++++------------------ 2 files changed, 78 insertions(+), 43 deletions(-) create mode 100644 .gitea/workflows/deploy-registry.yml diff --git a/.gitea/workflows/deploy-registry.yml b/.gitea/workflows/deploy-registry.yml new file mode 100644 index 0000000..459fac5 --- /dev/null +++ b/.gitea/workflows/deploy-registry.yml @@ -0,0 +1,54 @@ +name: Deploy (registry) + +on: + workflow_dispatch: + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to registry + uses: docker/login-action@v3 + with: + registry: ${{ vars.HARBOR_ENDPOINT }} + username: ${{ vars.HARBOR_ROBOT_ACCOUNT }} + password: ${{ secrets.HARBOR_ROBOT_KEY }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: | + ${{ vars.HARBOR_ENDPOINT }}/${{ vars.IMAGE_NAME }}:latest + ${{ vars.HARBOR_ENDPOINT }}/${{ vars.IMAGE_NAME }}:${{ github.sha }} + + - name: Deploy via SSH + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ vars.SSH_HOST }} + username: ${{ vars.SSH_USER }} + key: ${{ secrets.SSH_KEY }} + port: ${{ vars.SSH_PORT }} + script: | + cd ${{ secrets.DEPLOY_PATH }} + cat << 'ENVEOF' > .env + ${{ secrets.DEPLOY_ENV_FILE }} + ENVEOF + + # Export variables from .env file + set -a + source .env + set +a + + docker compose --env-file .env pull + docker compose --env-file .env up -d diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index daab41d..633a86a 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,56 +1,37 @@ -name: Deploy (main) +name: Deploy (build on server) on: - push: - branches: ["main"] - workflow_dispatch: + push: + branches: [ "main" ] + workflow_dispatch: jobs: - build-and-deploy: - runs-on: ubuntu-latest - permissions: - contents: read + deploy: + runs-on: ubuntu-latest + permissions: + contents: read - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to registry - uses: docker/login-action@v3 - with: - registry: ${{ secrets.REGISTRY }} - username: ${{ secrets.REGISTRY_USER }} - password: ${{ secrets.REGISTRY_PASSWORD }} - - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: | - ${{ secrets.REGISTRY }}/${{ secrets.IMAGE_NAME }}:latest - ${{ secrets.REGISTRY }}/${{ secrets.IMAGE_NAME }}:${{ github.sha }} - - - name: Deploy via SSH - uses: appleboy/ssh-action@v1.0.3 - with: - host: ${{ secrets.SSH_HOST }} - username: ${{ secrets.SSH_USER }} - key: ${{ secrets.SSH_KEY }} - port: ${{ secrets.SSH_PORT }} - script: | + steps: + - name: Deploy via SSH + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ vars.SSH_HOST }} + username: ${{ vars.SSH_USER }} + key: ${{ secrets.SSH_KEY }} + port: ${{ vars.SSH_PORT }} + script: | cd ${{ secrets.DEPLOY_PATH }} - cat << 'EOF' > .env + cat << 'ENVEOF' > .env ${{ secrets.DEPLOY_ENV_FILE }} - EOF + ENVEOF # Export variables from .env file set -a source .env set +a - docker compose --env-file .env pull - docker compose --env-file .env up -d + git fetch origin main + git checkout main + git pull --ff-only + + docker compose --env-file .env up -d --build From c55f469f977a15e1b2dd9868b1c5b946ed30107e Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 14:56:56 +0900 Subject: [PATCH 10/21] Validate .env creation in deploy workflow --- .gitea/workflows/deploy.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 633a86a..7ecfa03 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -20,11 +20,18 @@ jobs: key: ${{ secrets.SSH_KEY }} port: ${{ vars.SSH_PORT }} script: | + set -e cd ${{ secrets.DEPLOY_PATH }} cat << 'ENVEOF' > .env ${{ secrets.DEPLOY_ENV_FILE }} ENVEOF + # .env가 비어 있으면 중단 + if [ ! -s .env ]; then + echo ".env가 비어 있습니다. DEPLOY_ENV_FILE 설정을 확인하세요." + exit 1 + fi + # Export variables from .env file set -a source .env From 6abd80b47391c5b1f53026d004b5f82cc09ac1e7 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 14:58:31 +0900 Subject: [PATCH 11/21] Generate .env from Gitea vars for deploy --- .gitea/workflows/deploy.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 7ecfa03..3ba993e 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -23,7 +23,18 @@ jobs: set -e cd ${{ secrets.DEPLOY_PATH }} cat << 'ENVEOF' > .env - ${{ secrets.DEPLOY_ENV_FILE }} + DB_HOST=${{ vars.DB_HOST }} + DB_PORT=${{ vars.DB_PORT }} + DB_HOST_PORT=${{ vars.DB_HOST_PORT }} + DB_NAME=${{ vars.DB_NAME }} + DB_USER=${{ vars.DB_USER }} + DB_PASS=${{ secrets.DB_PASS }} + OIDC_ISSUER=${{ vars.OIDC_ISSUER }} + OIDC_CLIENT_ID=${{ vars.OIDC_CLIENT_ID }} + OIDC_CLIENT_SECRET=${{ secrets.OIDC_CLIENT_SECRET }} + OIDC_REDIRECT_URL=${{ vars.OIDC_REDIRECT_URL }} + OIDC_SCOPES=${{ vars.OIDC_SCOPES }} + IDP_SERVICE_URL=${{ vars.IDP_SERVICE_URL }} ENVEOF # .env가 비어 있으면 중단 From 437b9348ceffe3625135daa9d6648ec66f2c14ce Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 15:00:46 +0900 Subject: [PATCH 12/21] =?UTF-8?q?action=20=EC=9E=90=EB=8F=99=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/deploy.yml | 83 +++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 3ba993e..a19962a 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,55 +1,48 @@ name: Deploy (build on server) on: - push: - branches: [ "main" ] - workflow_dispatch: + workflow_dispatch: jobs: - deploy: - runs-on: ubuntu-latest - permissions: - contents: read + deploy: + runs-on: ubuntu-latest + permissions: + contents: read - steps: - - name: Deploy via SSH - uses: appleboy/ssh-action@v1.0.3 - with: - host: ${{ vars.SSH_HOST }} - username: ${{ vars.SSH_USER }} - key: ${{ secrets.SSH_KEY }} - port: ${{ vars.SSH_PORT }} - script: | - set -e - cd ${{ secrets.DEPLOY_PATH }} - cat << 'ENVEOF' > .env - DB_HOST=${{ vars.DB_HOST }} - DB_PORT=${{ vars.DB_PORT }} - DB_HOST_PORT=${{ vars.DB_HOST_PORT }} - DB_NAME=${{ vars.DB_NAME }} - DB_USER=${{ vars.DB_USER }} - DB_PASS=${{ secrets.DB_PASS }} - OIDC_ISSUER=${{ vars.OIDC_ISSUER }} - OIDC_CLIENT_ID=${{ vars.OIDC_CLIENT_ID }} - OIDC_CLIENT_SECRET=${{ secrets.OIDC_CLIENT_SECRET }} - OIDC_REDIRECT_URL=${{ vars.OIDC_REDIRECT_URL }} - OIDC_SCOPES=${{ vars.OIDC_SCOPES }} - IDP_SERVICE_URL=${{ vars.IDP_SERVICE_URL }} - ENVEOF + steps: + - name: Deploy via SSH + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ vars.SSH_HOST }} + username: ${{ vars.SSH_USER }} + key: ${{ secrets.SSH_KEY }} + port: ${{ vars.SSH_PORT }} + script: | + set -e + cd ${{ secrets.DEPLOY_PATH }} + cat << 'ENVEOF' > .env + DB_HOST=${{ vars.DB_HOST }} + DB_PORT=${{ vars.DB_PORT }} + DB_HOST_PORT=${{ vars.DB_HOST_PORT }} + DB_NAME=${{ vars.DB_NAME }} + DB_USER=${{ vars.DB_USER }} + DB_PASS=${{ secrets.DB_PASS }} + OIDC_ISSUER=${{ vars.OIDC_ISSUER }} + OIDC_CLIENT_ID=${{ vars.OIDC_CLIENT_ID }} + OIDC_CLIENT_SECRET=${{ secrets.OIDC_CLIENT_SECRET }} + OIDC_REDIRECT_URL=${{ vars.OIDC_REDIRECT_URL }} + OIDC_SCOPES=${{ vars.OIDC_SCOPES }} + IDP_SERVICE_URL=${{ vars.IDP_SERVICE_URL }} + ENVEOF - # .env가 비어 있으면 중단 - if [ ! -s .env ]; then - echo ".env가 비어 있습니다. DEPLOY_ENV_FILE 설정을 확인하세요." - exit 1 - fi - # Export variables from .env file - set -a - source .env - set +a + # Export variables from .env file + set -a + source .env + set +a - git fetch origin main - git checkout main - git pull --ff-only + git fetch origin main + git checkout main + git pull --ff-only - docker compose --env-file .env up -d --build + docker compose --env-file .env up -d --build From dd7adfb96fe17271cd7bc294c150bed000fbf265 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 15:15:51 +0900 Subject: [PATCH 13/21] Set origin to SSH in deploy --- .gitea/workflows/deploy.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index a19962a..be810f3 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -19,8 +19,10 @@ jobs: port: ${{ vars.SSH_PORT }} script: | set -e - cd ${{ secrets.DEPLOY_PATH }} + cd ${{ vars.DEPLOY_PATH }} + git remote set-url origin git@gitea.hmac.kr:b24014/kngil_home.git cat << 'ENVEOF' > .env + WEB_HOST_PORT=${{ vars.WEB_HOST_PORT }} DB_HOST=${{ vars.DB_HOST }} DB_PORT=${{ vars.DB_PORT }} DB_HOST_PORT=${{ vars.DB_HOST_PORT }} From d23cfb7c7d3f6fd2f6808886026d5fb56d9fd2a2 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 15:19:40 +0900 Subject: [PATCH 14/21] Guard deploy when repo is not initialized --- .gitea/workflows/deploy.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index be810f3..88f1046 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -20,7 +20,12 @@ jobs: script: | set -e cd ${{ vars.DEPLOY_PATH }} - git remote set-url origin git@gitea.hmac.kr:b24014/kngil_home.git + if [ ! -d .git ]; then + git init + git remote add origin git@gitea.hmac.kr:b24014/kngil_home.git + else + git remote set-url origin git@gitea.hmac.kr:b24014/kngil_home.git + fi cat << 'ENVEOF' > .env WEB_HOST_PORT=${{ vars.WEB_HOST_PORT }} DB_HOST=${{ vars.DB_HOST }} @@ -44,7 +49,7 @@ jobs: set +a git fetch origin main - git checkout main + git checkout -B main origin/main git pull --ff-only docker compose --env-file .env up -d --build From c4a4bdaec3755f7f30b35dcb1ca266d6ae8d9db5 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 15:40:09 +0900 Subject: [PATCH 15/21] =?UTF-8?q?=EB=94=94=ED=94=8C=EB=A1=9C=EC=9D=B4=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=20=EA=B2=BD=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/deploy.yml | 13 ++++++------- docker-compose.yml | 4 +++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 88f1046..5b7c6b7 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -20,12 +20,12 @@ jobs: script: | set -e cd ${{ vars.DEPLOY_PATH }} - if [ ! -d .git ]; then - git init - git remote add origin git@gitea.hmac.kr:b24014/kngil_home.git - else - git remote set-url origin git@gitea.hmac.kr:b24014/kngil_home.git - fi + # if [ ! -d .git ]; then + # git init + # git remote add origin ssh://git@127.0.0.1:222/b24014/kngil_home.git + # else + # git remote set-url origin ssh://git@127.0.0.1:222/b24014/kngil_home.git + # fi cat << 'ENVEOF' > .env WEB_HOST_PORT=${{ vars.WEB_HOST_PORT }} DB_HOST=${{ vars.DB_HOST }} @@ -42,7 +42,6 @@ jobs: IDP_SERVICE_URL=${{ vars.IDP_SERVICE_URL }} ENVEOF - # Export variables from .env file set -a source .env diff --git a/docker-compose.yml b/docker-compose.yml index 4685060..7e91d19 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ services: web: build: . ports: - - "8080:80" + - "${WEB_HOST_PORT:-8080}:80" volumes: - ./:/var/www/html environment: @@ -17,6 +17,7 @@ services: OIDC_REDIRECT_URL: ${OIDC_REDIRECT_URL:-} OIDC_SCOPES: ${OIDC_SCOPES:-} IDP_SERVICE_URL: ${IDP_SERVICE_URL:-} + restart: always depends_on: - db @@ -38,6 +39,7 @@ services: - db_data:/var/lib/postgresql/data - ./docker/initdb/01_kngil_DB.sql:/docker-entrypoint-initdb.d/01_kngil_DB.sql:ro - ./docker/postgres/pg_hba.conf:/etc/postgresql/pg_hba.conf:ro + restart: always volumes: db_data: From 600190dd8033f1f8a7357823c4a5a5dda5f7be07 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 15:43:07 +0900 Subject: [PATCH 16/21] Quote OIDC_SCOPES in deploy env --- .gitea/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 5b7c6b7..acd141e 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -38,7 +38,7 @@ jobs: OIDC_CLIENT_ID=${{ vars.OIDC_CLIENT_ID }} OIDC_CLIENT_SECRET=${{ secrets.OIDC_CLIENT_SECRET }} OIDC_REDIRECT_URL=${{ vars.OIDC_REDIRECT_URL }} - OIDC_SCOPES=${{ vars.OIDC_SCOPES }} + OIDC_SCOPES=\"${{ vars.OIDC_SCOPES }}\" IDP_SERVICE_URL=${{ vars.IDP_SERVICE_URL }} ENVEOF From 7bcf1c57118d1367e9f72b73e9492c4fa88fd6d5 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 15:45:10 +0900 Subject: [PATCH 17/21] =?UTF-8?q?=EC=8A=A4=EC=BD=94=ED=94=84=20=EA=B3=B5?= =?UTF-8?q?=EB=B0=B1=EC=B2=98=EB=A6=AC=20=EB=A1=A4=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index acd141e..2f3760f 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -38,7 +38,7 @@ jobs: OIDC_CLIENT_ID=${{ vars.OIDC_CLIENT_ID }} OIDC_CLIENT_SECRET=${{ secrets.OIDC_CLIENT_SECRET }} OIDC_REDIRECT_URL=${{ vars.OIDC_REDIRECT_URL }} - OIDC_SCOPES=\"${{ vars.OIDC_SCOPES }}\" + OIDC_SCOPES="${{ vars.OIDC_SCOPES }} IDP_SERVICE_URL=${{ vars.IDP_SERVICE_URL }} ENVEOF From 3f81939e806d1fbdd13c4c059649079ac7e06b21 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 15:46:45 +0900 Subject: [PATCH 18/21] =?UTF-8?q?=EC=98=A4=ED=83=80=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 2f3760f..5b7c6b7 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -38,7 +38,7 @@ jobs: OIDC_CLIENT_ID=${{ vars.OIDC_CLIENT_ID }} OIDC_CLIENT_SECRET=${{ secrets.OIDC_CLIENT_SECRET }} OIDC_REDIRECT_URL=${{ vars.OIDC_REDIRECT_URL }} - OIDC_SCOPES="${{ vars.OIDC_SCOPES }} + OIDC_SCOPES=${{ vars.OIDC_SCOPES }} IDP_SERVICE_URL=${{ vars.IDP_SERVICE_URL }} ENVEOF From 2a118a24f7746ace2447addeabb789becb1e7709 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 17:23:12 +0900 Subject: [PATCH 19/21] =?UTF-8?q?PG=EC=A0=91=EC=86=8D=EC=A0=9C=ED=95=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/postgres/pg_hba.conf | 12 +- kngil_plain.sql | 2366 +++++++++++++++++++++++++++++++++++ 2 files changed, 2375 insertions(+), 3 deletions(-) create mode 100644 kngil_plain.sql diff --git a/docker/postgres/pg_hba.conf b/docker/postgres/pg_hba.conf index fa5505e..2ccacfb 100644 --- a/docker/postgres/pg_hba.conf +++ b/docker/postgres/pg_hba.conf @@ -1,5 +1,11 @@ # -# Allow TCP connections. Narrow the address range in production. +# 로컬 소켓 접속 허용(초기화/관리용). 운영에서는 정책에 맞게 조정하세요. # -host all all 0.0.0.0/0 scram-sha-256 -host all all ::/0 scram-sha-256 +local all all trust +host all all 127.0.0.1/32 scram-sha-256 +host all all ::1/128 scram-sha-256 +# +# 외부 TCP 접속 허용. 운영에서는 허용 대역을 제한하세요. +# +host all all 0.0.0.0/0 scram-sha-256 +host all all ::/0 scram-sha-256 diff --git a/kngil_plain.sql b/kngil_plain.sql new file mode 100644 index 0000000..bdc8477 --- /dev/null +++ b/kngil_plain.sql @@ -0,0 +1,2366 @@ +-- +-- PostgreSQL database dump +-- + + +-- Dumped from database version 18.1 +-- Dumped by pg_dump version 18.0 + +-- Started on 2026-02-02 14:06:03 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- TOC entry 6 (class 2615 OID 16413) +-- Name: kngil; Type: SCHEMA; Schema: -; Owner: postgres +-- + +CREATE SCHEMA kngil; + + +ALTER SCHEMA kngil OWNER TO postgres; + +-- +-- TOC entry 262 (class 1255 OID 16574) +-- Name: fn_base_cd(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_base_cd(p_main_cd character varying) RETURNS TABLE(id character varying, text character varying) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + -- CASE 문을 사용하여 조건에 따라 반환할 컬럼을 선택합니다. + (CASE + WHEN b.use_bc = 'BS200100' THEN a.base_cd::VARCHAR + WHEN b.use_bc = 'BS200200' THEN a.sub_cd::VARCHAR + END) AS id, -- 코드1이면 기초코드 아니면 서브코드 표시. + a.sub_nm::VARCHAR AS text -- 코드명 + FROM kngil.code_detail a --공통코드 상세 + JOIN kngil.code_master b ON a.main_cd = b.main_cd --공통코드 마스터 + WHERE a.main_cd = p_main_cd + AND a.use_yn = 'Y' -- 사용여부 + ORDER BY + (CASE WHEN b.sort_bc = 'BS110100' THEN a.sub_nm END) ASC, + (CASE WHEN b.sort_bc = 'BS110200' THEN a.sort_sq END) ASC; +END; +$$; + + +ALTER FUNCTION kngil.fn_base_cd(p_main_cd character varying) OWNER TO postgres; + +-- +-- TOC entry 251 (class 1255 OID 16571) +-- Name: fn_base_nm(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_base_nm(p_base_cd character varying) RETURNS TABLE(name character varying) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + a.sub_nm::VARCHAR AS name -- 코드명 + FROM kngil.code_detail a --공통코드 상세 + JOIN kngil.code_master b ON a.main_cd = b.main_cd --공통코드 마스터 + WHERE a.base_cd = p_base_cd; + +END; +$$; + + +ALTER FUNCTION kngil.fn_base_nm(p_base_cd character varying) OWNER TO postgres; + +-- +-- TOC entry 276 (class 1255 OID 16703) +-- Name: fn_update_buy_area(); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_update_buy_area() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + v_member_id character varying; +BEGIN + -- 1. 이벤트 종류(INSERT, UPDATE, DELETE)에 따라 member_id 추출 + IF (TG_OP = 'DELETE') THEN + v_member_id := OLD.member_id; + ELSE + v_member_id := NEW.member_id; + END IF; + + -- 2. 해당 회원의 당해 연도(end_dt 기준) 합계 적용면적 업데이트 + -- ok_yn = 'Y'인 데이터만 합산 + UPDATE kngil.members + SET buy_area = ( + SELECT COALESCE(SUM(sum_area), 0) + FROM kngil.buy_item + WHERE member_id = v_member_id + AND ok_yn = 'Y' + AND EXTRACT(YEAR FROM end_dt) = EXTRACT(YEAR FROM CURRENT_DATE) + ) + WHERE member_id = v_member_id; + + -- UPDATE 이벤트에서 member_id가 변경된 경우, 이전 member_id의 데이터도 갱신 필요 + IF (TG_OP = 'UPDATE' AND OLD.member_id <> NEW.member_id) THEN + UPDATE kngil.members + SET buy_area = ( + SELECT COALESCE(SUM(sum_area), 0) + FROM kngil.buy_item + WHERE member_id = OLD.member_id + AND ok_yn = 'Y' + AND EXTRACT(YEAR FROM end_dt) = EXTRACT(YEAR FROM CURRENT_DATE) + ) + WHERE member_id = OLD.member_id; + END IF; + + RETURN NULL; -- AFTER 트리거이므로 결과 반환은 필요 없음 +END; +$$; + + +ALTER FUNCTION kngil.fn_update_buy_area() OWNER TO postgres; + +-- +-- TOC entry 250 (class 1255 OID 16541) +-- Name: fn_user_auth(); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_user_auth() RETURNS TABLE(code character varying, name character varying) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + -- 마스터의 use_bc 설정에 따라 base_cd 또는 sub_cd 반환 + (CASE + WHEN b.use_bc = 'BS200100' THEN a.base_cd::VARCHAR + WHEN b.use_bc = 'BS200200' THEN a.sub_cd::VARCHAR + END) AS code, + a.sub_nm::VARCHAR AS name + FROM kngil.code_detail a + JOIN kngil.code_master b ON a.main_cd = b.main_cd + WHERE a.main_cd = 'BS100' -- 권한관리코드 고정 + AND a.use_yn = 'Y' -- 사용여부 'Y' + AND a.m1 = '1' -- 특정 필터 조건 + ORDER BY + (CASE WHEN b.sort_bc = 'BS110100' THEN a.sub_nm END) ASC, + (CASE WHEN b.sort_bc = 'BS110200' THEN a.sort_sq END) ASC; +END; +$$; + + +ALTER FUNCTION kngil.fn_user_auth() OWNER TO postgres; + +-- +-- TOC entry 255 (class 1255 OID 16583) +-- Name: fn_user_id_check(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.fn_user_id_check(p_user_id character varying) RETURNS character varying + LANGUAGE plpgsql + AS $$ +DECLARE + v_user_exists INTEGER; +/* +유저 중복 체크 함수 회원가입 시 중복체크 +*/ +BEGIN + -- [1] 중복 아이디 체크 (대소문자 무시) + SELECT COUNT(*) INTO v_user_exists + FROM kngil.users + WHERE LOWER(user_id) = LOWER(p_user_id); -- 양쪽 모두 소문자로 변환하여 비교 + + IF v_user_exists > 0 THEN + RETURN 'ERROR: 이미 존재하는 아이디입니다.'; + ELSE + RETURN 'SUCCESS: 사용 가능한 아이디입니다.'; + END IF; +END; +$$; + + +ALTER FUNCTION kngil.fn_user_id_check(p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 257 (class 1255 OID 16550) +-- Name: sp_buy_item_d(character varying, integer); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_d(p_member_id character varying, p_sq_no integer) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE +/* + 설 명 : buy_item 테이블 삭제 프로시져 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ + v_ok_yn CHAR(1); +BEGIN + -- 1. 해당 구매 건의 승인 여부 확인 + SELECT ok_yn INTO v_ok_yn + FROM kngil.buy_item + WHERE member_id = p_member_id AND sq_no = p_sq_no; + + IF NOT FOUND THEN + RETURN 'ERROR: 삭제할 구매 정보를 찾을 수 없습니다.'; + END IF; + + -- 2. 승인여부(ok_yn)가 'Y'이면 삭제 불가 + IF v_ok_yn = 'Y' THEN + RETURN 'ERROR: 승인 완료(Y)된 구매 내역은 삭제할 수 없습니다.'; + END IF; + + -- 3. 삭제 실행 + DELETE FROM kngil.buy_item + WHERE member_id = p_member_id AND sq_no = p_sq_no; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_d(p_member_id character varying, p_sq_no integer) OWNER TO postgres; + +-- +-- TOC entry 273 (class 1255 OID 16563) +-- Name: sp_buy_item_history_r(character varying, character varying, date, date); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_history_r(p_member_id character varying DEFAULT ''::character varying, p_member_nm character varying DEFAULT NULL::character varying, p_fbuy_dt date DEFAULT NULL::date, p_tbuy_dt date DEFAULT NULL::date) RETURNS TABLE(member_id character varying, sq_no integer, user_nm character varying, co_nm character varying, bs_no character varying, buy_dt date, itm_cd character varying, itm_nm character varying, area numeric, itm_qty numeric, itm_area numeric, add_area numeric, sum_area numeric, itm_amt numeric, dis_rt numeric, buy_amt numeric, vat_amt numeric, sum_amt numeric, end_dt date, ok_yn character, rmks character varying) + LANGUAGE plpgsql + AS $$ +/* + 설 명 : 상품등록화면에서 buy_item 테이블 내용 조회 member_id , buy_dt 변수 필수 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +BEGIN + RETURN QUERY + SELECT + a.member_id, -- 회원ID + a.sq_no, -- 순번 + b.member_nm::character varying as user_nm, -- 구매자 + b.co_nm, -- 회사명 + b.bs_no, -- 사업자번호 + a.buy_dt, -- 구매일자 + a.itm_cd::character varying, -- 상품코드 + c.itm_nm::character varying as itm_nm, -- 상품명 + c.area::NUMERIC as area, -- 상품면적 + a.itm_qty::NUMERIC, -- 수량 + a.itm_area::NUMERIC, -- 면적 + a.add_area::NUMERIC, -- 추가면적 + a.sum_area::NUMERIC, -- 합계면적 + a.itm_amt::NUMERIC, -- 단가 + a.dis_rt::NUMERIC, -- 할인율 + a.buy_amt::NUMERIC, -- 공급금액 + a.vat_amt::NUMERIC, -- 부가세 + a.sum_amt::NUMERIC, -- 합계금액 + a.end_dt, -- 만료일 + a.ok_yn, -- 승인여부 + a.rmks -- 비고 + FROM kngil.buy_item a -- 구매정보 + left join kngil.members b on a.member_id = b.member_id -- 회원정보 + left join kngil.item c on a.itm_cd = c.itm_cd -- 상품정보 + WHERE 1=1 + -- 회원ID 검색 + AND (a.member_id = p_member_id OR p_member_id = '') + -- 구매일자 기간 검색 + AND (a.buy_dt >= p_fbuy_dt OR p_fbuy_dt IS NULL) + AND (a.buy_dt <= p_tbuy_dt OR p_tbuy_dt IS NULL) + AND ( + p_member_nm IS NULL + OR p_member_nm = '' + OR b.co_nm ILIKE '%' || p_member_nm || '%' + OR b.member_nm ILIKE '%' || p_member_nm || '%' +) + ORDER BY a.member_id,a.sq_no DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_history_r(p_member_id character varying, p_member_nm character varying, p_fbuy_dt date, p_tbuy_dt date) OWNER TO postgres; + +-- +-- TOC entry 275 (class 1255 OID 16575) +-- Name: sp_buy_item_i(character varying, date, character, numeric, numeric, numeric, numeric, numeric, numeric, numeric, numeric, numeric, date, character, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_i(p_member_id character varying, p_buy_dt date, p_itm_cd character, p_itm_qty numeric, p_itm_area numeric, p_add_area numeric, p_sum_area numeric, p_itm_amt numeric, p_dis_rt numeric, p_buy_amt numeric, p_vat_amt numeric, p_sum_amt numeric, p_end_dt date, p_ok_yn character, p_rmks character varying, p_cid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE +/* + 설 명 : 서비스 구매정보 isnert 프로시져 sq_no 계산하여 적용. + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 + p_itm_cd character, -- 상품코드 + p_itm_qty numeric, -- 수량 + p_itm_area numeric, -- 적용면적 + p_add_area numeric, -- 추가면적 + p_sum_area numeric, -- 합계면적 + p_itm_amt numeric, -- 상품단가 + p_dis_rt numeric, -- 할인율 + p_buy_amt numeric, -- 공금금액 + p_vat_amt numeric, -- 부가세 + p_sum_amt numeric, -- 합계금액 + p_end_dt date, -- 만료일자 + p_ok_yn character, -- 승인여부 + p_rmks character varying,-- 비고 +*/ + v_next_sq_no INTEGER; +BEGIN + -- 해당 회원의 다음 순번(sq_no) 계산 + SELECT COALESCE(MAX(sq_no), 0) + 1 INTO v_next_sq_no + FROM kngil.buy_item + WHERE member_id = p_member_id; + + -- 데이터 삽입 + INSERT INTO kngil.buy_item ( + member_id, sq_no, buy_dt, itm_cd, itm_qty, itm_area, + add_area, sum_area, itm_amt, dis_rt, buy_amt, + vat_amt, sum_amt, end_dt, ok_yn, rmks, + cid, cdt, mid, mdt + ) VALUES ( + p_member_id, v_next_sq_no, p_buy_dt, p_itm_cd, p_itm_qty, p_itm_area, + p_add_area, p_sum_area, p_itm_amt, p_dis_rt, p_buy_amt, + p_vat_amt, p_sum_amt, p_end_dt, p_ok_yn, p_rmks, + p_cid, CURRENT_TIMESTAMP, p_cid, CURRENT_TIMESTAMP + ); + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_i(p_member_id character varying, p_buy_dt date, p_itm_cd character, p_itm_qty numeric, p_itm_area numeric, p_add_area numeric, p_sum_area numeric, p_itm_amt numeric, p_dis_rt numeric, p_buy_amt numeric, p_vat_amt numeric, p_sum_amt numeric, p_end_dt date, p_ok_yn character, p_rmks character varying, p_cid character varying) OWNER TO postgres; + +-- +-- TOC entry 274 (class 1255 OID 16702) +-- Name: sp_buy_item_r(character varying, date); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_r(p_member_id character varying DEFAULT ''::character varying, p_buy_dt date DEFAULT NULL::date) RETURNS TABLE(member_id character varying, sq_no integer, buy_dt date, itm_cd character, itm_nm character varying, itm_qty numeric, itm_area numeric, add_area numeric, sum_area numeric, itm_amt numeric, dis_rt numeric, buy_amt numeric, vat_amt numeric, sum_amt numeric, end_dt date, ok_yn character, rmks character varying) + LANGUAGE plpgsql + AS $$ +/* + 설 명 : 상품등록화면에서 buy_item 테이블 내용 조회 member_id , buy_dt 변수 필수 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +BEGIN + RETURN QUERY + SELECT + a.member_id, -- 회원ID + a.sq_no, -- 순번 + a.buy_dt, -- 구매일자 + a.itm_cd, -- 상품코드 + b.itm_nm::character varying, -- 상품명 + a.itm_qty::NUMERIC, -- 수량 + a.itm_area::NUMERIC, -- 면적 + a.add_area::NUMERIC, -- 추가면적 + a.sum_area::NUMERIC, -- 합계면적 + a.itm_amt::NUMERIC, -- 단가 + a.dis_rt::NUMERIC, -- 할인율 + a.buy_amt::NUMERIC, -- 공급금액 + a.vat_amt::NUMERIC, -- 부가세 + a.sum_amt::NUMERIC, -- 합계금액 + a.end_dt, -- 만료일 + a.ok_yn, -- 승인여부 + a.rmks -- 비고 + FROM kngil.buy_item a + left join kngil.item b on a.itm_cd = b.itm_cd -- 상품코드 + WHERE 1=1 + -- 회원ID 검색 (회원ID 필수) + AND a.member_id = p_member_id + -- 구매일자 검색(필수) + AND a.buy_dt = p_buy_dt + ORDER BY a.sq_no DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_r(p_member_id character varying, p_buy_dt date) OWNER TO postgres; + +-- +-- TOC entry 256 (class 1255 OID 16549) +-- Name: sp_buy_item_u(character varying, integer, date, character, numeric, numeric, numeric, numeric, numeric, numeric, numeric, numeric, numeric, date, character, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_buy_item_u(p_member_id character varying, p_sq_no integer, p_buy_dt date, p_itm_cd character, p_itm_qty numeric, p_itm_area numeric, p_add_area numeric, p_sum_area numeric, p_itm_amt numeric, p_dis_rt numeric, p_buy_amt numeric, p_vat_amt numeric, p_sum_amt numeric, p_end_dt date, p_ok_yn character, p_rmks character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ + +BEGIN + UPDATE kngil.buy_item + SET + buy_dt = p_buy_dt, -- 구매일자 + itm_cd = p_itm_cd, -- 상품코드 + itm_qty = p_itm_qty, -- 수량 + itm_area = p_itm_area, -- 기본면적 + add_area = p_add_area, -- 추가면적 + sum_area = p_sum_area, -- 합계면적 + itm_amt = p_itm_amt, -- 단가 + dis_rt = p_dis_rt, -- 할인율 + buy_amt = p_buy_amt, -- 공급금액 + vat_amt = p_vat_amt, -- 부가세 + sum_amt = p_sum_amt, -- 구매금액 + end_dt = p_end_dt, -- 만료일자 + ok_yn = p_ok_yn, -- 확정 [Y업데이트 시 members 테이블의 구매면적에 합산 N 업데이트시 차감 트리거 적용] + rmks = p_rmks, -- 비고 + mid = p_mid, -- 수정자 ID 반영 + mdt = CURRENT_TIMESTAMP -- 수정일시 자동 기록 + WHERE member_id = p_member_id + AND sq_no = p_sq_no; + + IF NOT FOUND THEN + RETURN 'ERROR: 수정할 구매 정보를 찾을 수 없습니다.'; + END IF; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_buy_item_u(p_member_id character varying, p_sq_no integer, p_buy_dt date, p_itm_cd character, p_itm_qty numeric, p_itm_area numeric, p_add_area numeric, p_sum_area numeric, p_itm_amt numeric, p_dis_rt numeric, p_buy_amt numeric, p_vat_amt numeric, p_sum_amt numeric, p_end_dt date, p_ok_yn character, p_rmks character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 238 (class 1255 OID 16685) +-- Name: sp_fa_comments_d(integer); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_fa_comments_d(p_id integer) RETURNS boolean + LANGUAGE plpgsql + AS $$ +BEGIN + -- fa_id를 조건으로 데이터를 삭제합니다. + DELETE FROM kngil.fa_comments + WHERE fa_id = p_id; + + -- 삭제된 행이 있으면 true, 없으면 false를 반환합니다. + RETURN FOUND; +END; +$$; + + +ALTER FUNCTION kngil.sp_fa_comments_d(p_id integer) OWNER TO postgres; + +-- +-- TOC entry 271 (class 1255 OID 16684) +-- Name: sp_fa_comments_i(text, text, integer, character, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_fa_comments_i(p_subject text, p_content text, p_sq_no integer, p_use_yn character, p_cid character varying) RETURNS integer + LANGUAGE plpgsql + AS $$ +DECLARE + v_new_id integer; +BEGIN + -- fa_id는 GENERATED ALWAYS이므로 INSERT 문에서 제외하여 DB가 자동 생성하게 함 + INSERT INTO kngil.fa_comments ( + fa_subject, + fa_content, + sq_no, + use_yn, + cid, + cdt + ) VALUES ( + p_subject, + p_content, + p_sq_no, + p_use_yn, + p_cid, + CURRENT_TIMESTAMP + ) + RETURNING fa_id INTO v_new_id; + + RETURN v_new_id; +END; +$$; + + +ALTER FUNCTION kngil.sp_fa_comments_i(p_subject text, p_content text, p_sq_no integer, p_use_yn character, p_cid character varying) OWNER TO postgres; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- TOC entry 235 (class 1259 OID 16671) +-- Name: fa_comments; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.fa_comments ( + fa_id integer NOT NULL, + fa_subject text, + fa_content text, + sq_no integer, + use_yn character(1), + cid character varying(20), + cdt timestamp with time zone, + mid character varying(20), + mdt timestamp with time zone +); + + +ALTER TABLE kngil.fa_comments OWNER TO postgres; + +-- +-- TOC entry 268 (class 1255 OID 16686) +-- Name: sp_fa_comments_r(); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_fa_comments_r() RETURNS SETOF kngil.fa_comments + LANGUAGE plpgsql + AS $$ +BEGIN + -- 사용 여부(use_yn)가 'Y'인 데이터를 순번(sq_no) 오름차순으로 조회합니다. + RETURN QUERY + SELECT * FROM kngil.fa_comments + WHERE use_yn = 'Y' + ORDER BY sq_no ASC, fa_id DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_fa_comments_r() OWNER TO postgres; + +-- +-- TOC entry 272 (class 1255 OID 16683) +-- Name: sp_fa_comments_u(integer, text, text, integer, character, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_fa_comments_u(p_id integer, p_subject text, p_content text, p_sq_no integer, p_use_yn character, p_mid character varying) RETURNS boolean + LANGUAGE plpgsql + AS $$ +BEGIN + -- 고유 ID(fa_id)를 조건으로 데이터를 업데이트합니다. + UPDATE kngil.fa_comments + SET + fa_subject = p_subject, + fa_content = p_content, + sq_no = p_sq_no, + use_yn = p_use_yn, + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE + fa_id = p_id; + + -- 업데이트된 행이 있는지 확인하여 반환합니다. + RETURN FOUND; +END; +$$; + + +ALTER FUNCTION kngil.sp_fa_comments_u(p_id integer, p_subject text, p_content text, p_sq_no integer, p_use_yn character, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 261 (class 1255 OID 16568) +-- Name: sp_item_d(character); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_item_d(p_itm_cd character) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE + v_buy_count INTEGER; +BEGIN + -- 1. 무결성 체크: 이 상품을 구매한 내역(buy_item)이 있는지 확인 + SELECT COUNT(*) INTO v_buy_count + FROM kngil.buy_item + WHERE itm_cd = p_itm_cd; + + IF v_buy_count > 0 THEN + RETURN 'ERROR: 구매 내역이 존재하는 상품은 삭제할 수 없습니다. (사용여부를 N으로 변경하세요)'; + END IF; + + -- 2. 상품 존재 여부 확인 + IF NOT EXISTS (SELECT 1 FROM kngil.item WHERE itm_cd = p_itm_cd) THEN + RETURN 'ERROR: 삭제할 상품 정보를 찾을 수 없습니다.'; + END IF; + + -- 3. 삭제 실행 + DELETE FROM kngil.item + WHERE itm_cd = p_itm_cd; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + -- 오류 발생 시 자동 롤백됩니다. + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_item_d(p_itm_cd character) OWNER TO postgres; + +-- +-- TOC entry 263 (class 1255 OID 16576) +-- Name: sp_item_i(character, character varying, numeric, numeric, character, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_item_i(p_itm_cd character, p_itm_nm character varying, p_area numeric, p_itm_amt numeric, p_use_yn character, p_rmks character varying, p_cid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE + v_count INTEGER; +BEGIN + -- 1. 상품코드(itm_cd) 중복 체크 + SELECT COUNT(*) INTO v_count FROM kngil.item WHERE itm_cd = p_itm_cd; + + IF v_count > 0 THEN + RETURN 'ERROR: 이미 존재하는 상품코드입니다.'; + END IF; + + -- 2. 데이터 삽입 + INSERT INTO kngil.item ( + itm_cd, itm_nm, area, itm_amt, + use_yn, rmks, + cid, cdt, mid, mdt + ) VALUES ( + p_itm_cd, p_itm_nm, p_area, p_itm_amt, + p_use_yn, p_rmks, + p_cid, CURRENT_TIMESTAMP, p_cid, CURRENT_TIMESTAMP + ); + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + -- 트랜잭션 원자성에 의해 오류 발생 시 자동 롤백됩니다. + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_item_i(p_itm_cd character, p_itm_nm character varying, p_area numeric, p_itm_amt numeric, p_use_yn character, p_rmks character varying, p_cid character varying) OWNER TO postgres; + +-- +-- TOC entry 252 (class 1255 OID 16572) +-- Name: sp_item_r(); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_item_r() RETURNS TABLE(itm_cd character varying, itm_nm character varying, area numeric, itm_amt numeric, use_yn character varying, rmks character varying, cid character varying, cdt timestamp without time zone, mid character varying, mdt timestamp without time zone) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + a.itm_cd::VARCHAR, -- bpchar 에러 방지를 위한 명시적 변환 + a.itm_nm::VARCHAR, + a.area::NUMERIC, -- DECIMAL을 NUMERIC으로 맞춤 + a.itm_amt::NUMERIC, + a.use_yn::VARCHAR, + a.rmks::VARCHAR, + a.cid::VARCHAR, + a.cdt, + a.mid::VARCHAR, + a.mdt + FROM kngil.item a + ORDER BY a.itm_cd ASC; +END; +$$; + + +ALTER FUNCTION kngil.sp_item_r() OWNER TO postgres; + +-- +-- TOC entry 260 (class 1255 OID 16567) +-- Name: sp_item_u(character, character varying, numeric, numeric, character, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_item_u(p_itm_cd character, p_itm_nm character varying, p_area numeric, p_itm_amt numeric, p_use_yn character, p_rmks character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +BEGIN + -- 1. 데이터 업데이트 수행 + UPDATE kngil.item + SET + itm_nm = p_itm_nm, -- 상품명 + area = p_area, -- 면적 + itm_amt = p_itm_amt, -- 상품금액 + use_yn = p_use_yn, -- 사용여부 + rmks = p_rmks, -- 비고 + mid = p_mid, -- 수정자 기록 + mdt = CURRENT_TIMESTAMP -- 수정일시 기록 + WHERE itm_cd = p_itm_cd; + + -- 2. 업데이트된 행이 있는지 확인 + IF NOT FOUND THEN + RETURN 'ERROR: 수정할 상품코드를 찾을 수 없습니다.'; + END IF; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_item_u(p_itm_cd character, p_itm_nm character varying, p_area numeric, p_itm_amt numeric, p_use_yn character, p_rmks character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 266 (class 1255 OID 16580) +-- Name: sp_member_i(character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_member_i(p_co_bc character varying, p_member_id character varying, p_user_pw character varying, p_member_nm character varying, p_email character varying, p_tel_no character varying, p_co_nm character varying, p_dept_nm character varying, p_cid character varying) RETURNS character varying + LANGUAGE plpgsql + AS $$ + -- 값을 반환하기 위해 VARCHAR로 수정 + DECLARE v_user_exists INTEGER; + v_itm_area numeric; -- 무료 제공 면적 + v_end_dt timestamp; +/* +회원가입시 사용되는 함수 +*/ +BEGIN + + v_end_dt := (date_trunc('year', CURRENT_TIMESTAMP) + INTERVAL '1 year - 1 day')::timestamp; + + -- [0] 무료제공면적 + SELECT a.area INTO v_itm_area + FROM kngil.item a + WHERE a.itm_cd = 'A0000'; -- 회원가입제공 면적 + + + -- [1] 중복 아이디 체크 (대소문자 무시) + SELECT COUNT(*) INTO v_user_exists + FROM kngil.users + WHERE LOWER(user_id) = LOWER(p_member_id); -- 양쪽 모두 소문자로 변환하여 비교 + + IF v_user_exists > 0 THEN + RETURN 'ERROR: 이미 존재하는 아이디입니다. (대소문자 포함)'; + END IF; + + -- 회원 테이블 저장 + INSERT INTO kngil.members ( + member_id, member_nm, co_bc, bs_no, co_nm, + co_tel, tel_no, email, join_dt, buy_area, + use_area, stat_bc, memo, cid, cdt + ) VALUES ( + p_member_id -- 회원ID + , p_member_nm -- 성명 + , p_co_bc -- 법인구분 case 구문 사용해야 할 수 있음. 변수가 라디오 버튼 + , null -- 사업자번호 + , p_co_nm -- 회사명 + , null -- 회사번호 + , p_tel_no -- 휴대폰 + , p_email -- 이메일 + , CURRENT_DATE -- 가입일자 + , null -- 구매면적 + , null -- 사용면적 + , 'SA100100' -- 회원상태 : 사용중 + , null -- 메모 + , p_cid -- 생성자 + , CURRENT_TIMESTAMP -- 생성일 + ); + + -- 회원가입 시 사용자정보 권한 "메인" 생성 + INSERT INTO kngil.users ( + member_id, user_id, user_pw, user_nm, dept_nm, + posit_nm, tel_no, email, auth_bc, use_yn, + rmks, cid, cdt + ) VALUES ( + p_member_id -- 회원ID + , p_member_id -- 유저ID : 최초 회원의 ID와 동일 + , p_user_pw -- 로그인 PW + , p_member_nm -- 성명 + , p_dept_nm -- 부서 + , null -- 직위 + , p_tel_no -- 전화번호 + , p_email -- 이메일 + , 'BS100300' -- 권한관리 : 메인 + , 'Y' -- 사용여부 Y 고정 + , null -- 비고 + , p_cid -- 생성자 + , CURRENT_TIMESTAMP -- 생성일 + ); + + + + -- 사용면적 제공 (구매이력 테이블에 초기제공상품 정보 등록 금액 0원) + PERFORM kngil.sp_buy_item_i( + p_member_id::character varying, -- 1. p_member_id + CURRENT_DATE::date, -- 2.가입일 + 'A0000'::character varying, -- 3. p_itm_cd + 1::integer, -- 4. p_itm_qty + v_itm_area::numeric, -- 5. p_itm_area + 0::numeric, -- 6. p_add_area + v_itm_area::numeric, -- 7. p_sum_area + 0::numeric, -- 8. p_itm_amt + 0::numeric, -- 9. p_dis_rt + 0::numeric, -- 10. p_buy_amt + 0::numeric, -- 11. p_vat_amt + 0::numeric, -- 12. p_sum_amt + v_end_dt::date, -- 13. p_end_dt + 'Y'::character varying, -- 14. p_ok_yn + '최초 가입 제공'::character varying, -- 15. p_rmks + p_cid::character varying -- 16. p_cid + ); + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_member_i(p_co_bc character varying, p_member_id character varying, p_user_pw character varying, p_member_nm character varying, p_email character varying, p_tel_no character varying, p_co_nm character varying, p_dept_nm character varying, p_cid character varying) OWNER TO postgres; + +-- +-- TOC entry 259 (class 1255 OID 16565) +-- Name: sp_member_sys_r(character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_member_sys_r(p_co_nm character varying DEFAULT NULL::character varying, p_rem_area character varying DEFAULT NULL::character varying, p_stat_bc character varying DEFAULT NULL::character varying) RETURNS TABLE(member_id character varying, user_id character varying, user_nm character varying, tel_no character varying, email character varying, co_nm character varying, bs_no character varying, join_dt date, user_y numeric, buy_area numeric, use_area numeric, rem_area numeric, stat_bc character varying, memo text) + LANGUAGE plpgsql + AS $$ +/* + 설 명 : 큰길회원 list 관리자 화면 조회, 사용자의 권한구분이 "메인" 인 인원 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +BEGIN + RETURN QUERY + SELECT + a.member_id, -- 회원ID + b.user_id, -- 사용자ID + b.user_nm, -- 사용자이름 + b.tel_no, -- 사용자연락처 + b.email, -- 사용자이메일 + a.co_nm, -- 회사명 + a.bs_no, -- 사업자번호 + a.join_dt, -- 가입일자 + (SELECT COUNT(*) + FROM kngil.users x + left join kngil.code_detail y on x.auth_bc = y.base_cd + WHERE x.member_id = a.member_id + and y.m1 = '1' + and x.use_yn='Y')::numeric as user_y, -- 사용자수 + a.buy_area, -- 구매면적 + a.use_area, -- 사용면적 + a.buy_area - a.use_area as rem_area, -- 잔여면적 + a.stat_bc, -- 회원상태 + a.memo + FROM kngil.members a -- 회원정보 + left join kngil.users b on a.member_id = b.member_id and b.auth_bc = 'BS100300' -- 권한구분이 메인 + WHERE 1=1 + AND (a.co_nm LIKE '%' || p_co_nm || '%' OR p_co_nm = '') + AND (p_rem_area = '' OR p_rem_area::NUMERIC >= (a.buy_area - a.use_area)) + and (p_stat_bc ='' OR a.stat_bc = p_stat_bc) + ORDER BY a.join_dt DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_member_sys_r(p_co_nm character varying, p_rem_area character varying, p_stat_bc character varying) OWNER TO postgres; + +-- +-- TOC entry 264 (class 1255 OID 16578) +-- Name: sp_member_sys_u(character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_member_sys_u(p_member_id character varying, p_tel_no character varying, p_email character varying, p_bs_no character varying, p_co_nm character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +BEGIN + -- 1. 회원정보 업데이트 + UPDATE kngil.members + SET tel_no = p_tel_no, + email = p_email, + bs_no = p_bs_no, + co_nm = p_co_nm, + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE member_id = p_member_id; + + -- 2. 유저정보 업데이트 + UPDATE kngil.users + SET tel_no = p_tel_no, + email = p_email, + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE member_id = p_member_id + AND auth_bc = 'BS100300'; -- b.auth_bc 대신 컬럼명 직접 사용 + + -- 결과 확인 (하나라도 수정되었다면 성공) + IF NOT FOUND THEN + RETURN 'ERROR: 수정할 회원 또는 유저 정보를 찾을 수 없습니다.'; + else + RETURN 'SUCCESS'; + END IF; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_member_sys_u(p_member_id character varying, p_tel_no character varying, p_email character varying, p_bs_no character varying, p_co_nm character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 277 (class 1255 OID 16707) +-- Name: sp_use_history(character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_use_history(p_member_id character varying, p_user_nm character varying DEFAULT NULL::character varying, p_dept_nm character varying DEFAULT NULL::character varying) RETURNS TABLE(member_id character varying, use_dt date, user_id character varying, sq_no integer, user_nm character varying, dept_nm character varying, posit_nm character varying, use_yn character varying, use_area numeric, ser_bc character varying, cdt timestamp without time zone) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + b.member_id::character varying, + a.use_dt, -- DATE 타입 + a.user_id::character varying, + a.sq_no, + b.user_nm::character varying, + b.dept_nm::character varying, + b.posit_nm::character varying, + b.use_yn::character varying, + a.use_area, + kngil.fn_base_nm(a.ser_bc)::character varying as ser_bc, + a.cdt + FROM kngil.use_history a + INNER JOIN kngil.users b ON a.user_id = b.user_id + WHERE b.member_id = p_member_id + -- 이름 검색 (값이 있을 때만 LIKE) + AND (NULLIF(p_user_nm, '') IS NULL OR b.user_nm LIKE '%' || p_user_nm || '%') + -- 부서 검색 (값이 있을 때만 LIKE) + AND (NULLIF(p_dept_nm, '') IS NULL OR b.dept_nm LIKE '%' || p_dept_nm || '%') + ORDER BY a.use_dt DESC, a.sq_no DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_use_history(p_member_id character varying, p_user_nm character varying, p_dept_nm character varying) OWNER TO postgres; + +-- +-- TOC entry 270 (class 1255 OID 16637) +-- Name: sp_user_auth_tran(character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_user_auth_tran(p_tuser_id character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE + v_fuser_id character varying; -- 위임 주는 사람 (From, 기존 메인) + v_member_id character varying; -- 위임 받는 사람의 회원ID +BEGIN + -- 1. 위임 받을 사용자 정보 조회 (한 번에 조회) + SELECT member_id INTO v_member_id + FROM kngil.users + WHERE user_id = p_tuser_id; + + -- 사용자가 없는 경우 처리 + IF v_member_id IS NULL THEN + RETURN 'ERROR: 수정할 사용자를 찾을 수 없습니다.'; + END IF; + + -- 2. 해당 멤버의 기존 메인 사용자(BS100300) 찾기 + SELECT user_id INTO v_fuser_id + FROM kngil.users + WHERE member_id = v_member_id + AND auth_bc = 'BS100300' + AND use_yn = 'Y'; + + -- 메인 사용자가 없는 경우 (위임해 줄 대상이 없음) + IF v_fuser_id IS NULL THEN + RETURN 'ERROR: 위임할 메인 사용자를 찾을 수 없습니다.'; + END IF; + + -- 본인에게 위임하는 경우 방지 (필요 시) + IF v_fuser_id = p_tuser_id THEN + RETURN 'ERROR: 본인에게는 권한을 위임할 수 없습니다.'; + END IF; + + -- 3. 권한 위임받는 사람 업데이트 (서브 -> 메인) + UPDATE kngil.users + SET + auth_bc = 'BS100300', + rmks = '권한위임[' || v_fuser_id || ']', -- + 대신 || 사용 + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE user_id = p_tuser_id; + + -- 4. 권한 위임주는 사람 업데이트 (메인 -> 서브) + UPDATE kngil.users + SET + auth_bc = 'BS100400', + rmks = '권한위임[' || p_tuser_id || ']', -- + 대신 || 사용 + mid = p_mid, + mdt = CURRENT_TIMESTAMP + WHERE user_id = v_fuser_id; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_user_auth_tran(p_tuser_id character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 278 (class 1255 OID 16711) +-- Name: sp_user_end(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_user_end(p_user_id character varying) RETURNS void + LANGUAGE plpgsql + AS $$ +DECLARE + v_member_id character varying; + v_auth_bc character varying; +BEGIN + -- 1. 유저 ID로 소속 멤버 ID와 권한 코드를 한 번에 조회 + SELECT a.member_id, a.auth_bc + INTO v_member_id, v_auth_bc + FROM kngil.users a + WHERE a.user_id = p_user_id; + + -- 2. 권한 코드에 따른 조건 분기 + IF v_auth_bc = 'BS100300' THEN + -- [메인 관리자 권한] 해당 멤버 전체 탈퇴 및 소속 유저 전원 중지 + + -- 멤버 테이블 업데이트 (탈퇴 처리) + UPDATE kngil.members + SET stat_bc = 'SA100900', + mid = p_user_id, + mdt = CURRENT_TIMESTAMP + WHERE member_id = v_member_id; + + -- 해당 멤버에 속한 모든 유저 사용 안함 처리 + UPDATE kngil.users + SET use_yn = 'N', + mid = p_user_id, + mdt = CURRENT_TIMESTAMP + WHERE member_id = v_member_id; + + ELSE + -- [일반 권한] 해당 유저 개인만 사용 안함 처리 + + UPDATE kngil.users + SET use_yn = 'N', + mid = p_user_id, + mdt = CURRENT_TIMESTAMP + WHERE user_id = p_user_id; + + END IF; + +END; +$$; + + +ALTER FUNCTION kngil.sp_user_end(p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 254 (class 1255 OID 16546) +-- Name: sp_users_d(character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_d(p_member_id character varying, p_user_id character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE +/* + 설 명 : users 테이블 삭제 프로시져 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ + v_exists INTEGER; +BEGIN + -- 1. 삭제 대상이 존재하는지 먼저 확인 + SELECT COUNT(*) INTO v_exists + FROM kngil.users + WHERE member_id = p_member_id AND user_id = p_user_id; + + IF v_exists = 0 THEN + RETURN 'ERROR: 삭제할 사용자를 찾을 수 없습니다.'; + END IF; + + -- 2. 데이터 삭제 실행 + DELETE FROM kngil.users + WHERE member_id = p_member_id + AND user_id = p_user_id; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + -- 트랜잭션 원자성에 의해 에러 발생 시 자동 롤백됩니다. + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_d(p_member_id character varying, p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 265 (class 1255 OID 16577) +-- Name: sp_users_i(character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_i(p_member_id character varying, p_user_id character varying, p_user_pw character varying, p_user_nm character varying, p_dept_nm character varying, p_posit_nm character varying, p_tel_no character varying, p_email character varying, p_auth_bc character varying, p_use_yn character varying, p_rmks character varying, p_cid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +/* + 설 명 : users table 데이터 생성 프로시져 "회원 관리자 페이지" 사용자 추가 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +DECLARE + v_count INTEGER; + -- 값을 반환하기 위해 VARCHAR로 수정 + DECLARE v_user_exists INTEGER; +BEGIN + + -- [1] 중복 아이디 체크 (대소문자 무시) + SELECT COUNT(*) INTO v_user_exists + FROM kngil.users + WHERE LOWER(user_id) = LOWER(p_user_id); -- 양쪽 모두 소문자로 변환하여 비교 + + IF v_user_exists > 0 THEN + RETURN 'ERROR: 이미 존재하는 아이디입니다.'; + END IF; + + -- 1. 부모 테이블(members)에 member_id가 존재하는지 먼저 체크 + SELECT COUNT(*) INTO v_count FROM kngil.members WHERE member_id = p_member_id; + + IF v_count = 0 THEN + RETURN 'ERROR: 존재하지 않는 회원ID(member_id)입니다.'; + END IF; + + -- 2. 사용자ID(user_id) 중복 체크 + SELECT COUNT(*) INTO v_count FROM kngil.users WHERE user_id = p_user_id; + + IF v_count > 0 THEN + RETURN 'ERROR: 이미 존재하는 사용자ID입니다.'; + END IF; + + -- 3. 데이터 삽입 + INSERT INTO kngil.users ( + member_id, user_id, user_pw, user_nm, + dept_nm, posit_nm, tel_no, email, + auth_bc, use_yn, rmks, + cid, cdt, mid, mdt + ) VALUES ( + p_member_id, p_user_id, p_user_pw, p_user_nm, + p_dept_nm, p_posit_nm, p_tel_no, p_email, + p_auth_bc, p_use_yn, p_rmks, + p_cid, CURRENT_TIMESTAMP, p_cid, CURRENT_TIMESTAMP + ); + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_i(p_member_id character varying, p_user_id character varying, p_user_pw character varying, p_user_nm character varying, p_dept_nm character varying, p_posit_nm character varying, p_tel_no character varying, p_email character varying, p_auth_bc character varying, p_use_yn character varying, p_rmks character varying, p_cid character varying) OWNER TO postgres; + +-- +-- TOC entry 258 (class 1255 OID 16590) +-- Name: sp_users_my_history(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_my_history(p_user_id character varying) RETURNS TABLE(use_dt date, user_nm character varying, use_area numeric, ser_bc character varying, cdt timestamp without time zone) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + h.use_dt, + u.user_nm, + h.use_area, + kngil.fn_base_nm(h.ser_bc) as ser_bc, + h.cdt + FROM kngil.use_history h + JOIN kngil.users u ON h.user_id = u.user_id + WHERE h.user_id = p_user_id + ORDER BY h.use_dt DESC, h.sq_no DESC; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_my_history(p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 269 (class 1255 OID 16633) +-- Name: sp_users_my_r(character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_my_r(p_user_id character varying) RETURNS TABLE(co_bc character varying, user_id character varying, user_pw character varying, user_nm character varying, email character varying, tel_no character varying, co_nm character varying, dept_nm character varying, tot_use numeric, year_use numeric) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY + SELECT + b.co_bc -- 법인구분 + , a.user_id -- ID + , a.user_pw -- 비밀번호 + , a.user_nm -- 성명 + , a.email -- 이메일 + , a.tel_no -- 연락처 + , b.co_nm -- 회사명 + , a.dept_nm -- 부서 + , (select sum(x.use_area) from kngil.use_history x where a.user_id = x.user_id)::numeric as tot_use -- 누적 사용량 + , (select sum(x.use_area) from kngil.use_history x where a.user_id = x.user_id and EXTRACT(YEAR FROM use_dt) = EXTRACT(YEAR FROM CURRENT_DATE))::numeric as year_use -- 당년 사용량 + FROM kngil.users a + left JOIN kngil.members b ON a.member_id = b.member_id + WHERE a.user_id = p_user_id; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_my_r(p_user_id character varying) OWNER TO postgres; + +-- +-- TOC entry 267 (class 1255 OID 16584) +-- Name: sp_users_my_u(character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_my_u(p_user_id character varying, p_user_pw character varying, p_email character varying, p_tel_no character varying, p_dept_nm character varying, p_mid character varying) RETURNS character varying + LANGUAGE plpgsql + AS $$ + -- 값을 반환하기 위해 VARCHAR로 수정 + DECLARE v_user_exists INTEGER; +/* +내정보 수정 화면에서 사용. +*/ + +BEGIN + + -- 데이터 업데이트 + UPDATE kngil.users + SET + user_pw = COALESCE(NULLIF(p_user_pw, ''), user_pw), -- 비밀번호가 빈값이면 기존 유지 + dept_nm = p_dept_nm, -- 부서명 + tel_no = p_tel_no, -- 전화번호 + email = p_email, -- 메일주소 + mid = p_mid, -- 수정자 ID 기록 + mdt = CURRENT_TIMESTAMP -- 수정일시 기록 + WHERE 1=1 + AND user_id = p_user_id; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_my_u(p_user_id character varying, p_user_pw character varying, p_email character varying, p_tel_no character varying, p_dept_nm character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 279 (class 1255 OID 16718) +-- Name: sp_users_r(character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_r(p_member_id character varying DEFAULT ''::character varying, p_user_nm character varying DEFAULT ''::character varying, p_dept_nm character varying DEFAULT ''::character varying, p_use_yn character varying DEFAULT ''::character varying) RETURNS TABLE(member_id character varying, users_tot bigint, users_y bigint, term text, tuse_area numeric, tbuy_area numeric, remain_area numeric, user_id character varying, user_pw character varying, user_nm character varying, tel_no character varying, email character varying, dept_nm character varying, use_area numeric, cdt date, use_yn character varying, auth_bc character varying, rmks character varying, itm_nm character varying) + LANGUAGE plpgsql + AS $$ +/* + 설 명 : 조회 프로시져 members 기반 users 테이블 조인하여 사용. "회원 관리자 페이지" 조회용 + 작성자 : 권오재 + 작성일 : 2026-01-14 + 비 고 : 최초작성 +*/ +DECLARE + v_to_dt character varying; -- 유효일자 + v_remain_days integer; -- 잔여일 저장 변수 + v_itm_nm character varying; -- 가입 상품명 +BEGIN + + SELECT + MAX(end_dt)::text + + INTO + v_to_dt + FROM kngil.buy_item a + WHERE a.member_id = p_member_id; -- 유효일자 종료일자만 표시 END_DT 가 가장 늦은 날 기준. + + + SELECT b.itm_nm + INTO v_itm_nm + FROM kngil.buy_item a + LEFT JOIN kngil.item b ON a.itm_cd = b.itm_cd + WHERE a.member_id = p_member_id + AND EXTRACT(YEAR FROM a.end_dt) = EXTRACT(YEAR FROM CURRENT_DATE) + -- 금액이 큰 순서대로 정렬 + ORDER BY a.itm_amt DESC, a.end_dt DESC + LIMIT 1; + + + v_remain_days := COALESCE(v_to_dt::DATE - CURRENT_DATE, 0); + + + + RETURN QUERY + SELECT + -- [회원정보 기반] + a.member_id, + (SELECT COUNT(*) FROM kngil.users u WHERE u.member_id = a.member_id) AS users_tot, -- 발급사용자 수 + (SELECT COUNT(*) FROM kngil.users u WHERE u.member_id = a.member_id and u.use_yn='Y') AS users_y, -- 실사용자 수 + --v_fr_dt||' ~ '||v_to_dt AS term, -- 유효일자 + ' : '||v_to_dt||' ('||v_remain_days||'일)' AS term, -- 유효일자 종료일자만 표시 END_DT 가 가장 늦은 날 기준. + a.use_area AS tuse_area, -- 총 사용면적 + a.buy_area AS tbuy_area, -- 총 구입면적 + (a.buy_area - a.use_area) AS remain_area, -- 잔여면적 (계산필드) + + -- [사용자정보 기반] + b.user_id, + b.user_pw, + b.user_nm, + b.tel_no, + b.email, + b.dept_nm, + -- 사용자별 개별 사용량 (use_history 테이블 참조) -- 당해년도기준으로 할지 전체 누적으로 할지 고민 필요. + COALESCE((SELECT SUM(uh.use_area) FROM kngil.use_history uh WHERE uh.user_id = b.user_id), 0) AS use_area, + b.cdt::DATE as cdt, + b.use_yn, -- 사용여부 + b.auth_bc, -- 권한구분 + b.rmks, -- 유저비고 + v_itm_nm::character varying as itm_nm + FROM kngil.members a + LEFT JOIN kngil.users b ON a.member_id = b.member_id + WHERE 1=1 + AND a.member_id = p_member_id + AND (b.user_nm LIKE '%' || p_user_nm || '%' OR p_user_nm = '') + AND (b.dept_nm LIKE '%' || p_dept_nm || '%' OR p_dept_nm = '') + AND (p_use_yn = '' OR b.use_yn = p_use_yn) + ORDER BY a.member_id DESC, b.user_id ASC; + +END; +$$; + + +ALTER FUNCTION kngil.sp_users_r(p_member_id character varying, p_user_nm character varying, p_dept_nm character varying, p_use_yn character varying) OWNER TO postgres; + +-- +-- TOC entry 253 (class 1255 OID 16545) +-- Name: sp_users_u(character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying, character varying); Type: FUNCTION; Schema: kngil; Owner: postgres +-- + +CREATE FUNCTION kngil.sp_users_u(p_member_id character varying, p_user_id character varying, p_user_pw character varying, p_user_nm character varying, p_dept_nm character varying, p_posit_nm character varying, p_tel_no character varying, p_email character varying, p_auth_bc character varying, p_use_yn character varying, p_rmks character varying, p_mid character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE + v_exists INTEGER; +BEGIN + -- 1. 해당 사용자가 존재하는지 확인 + SELECT COUNT(*) INTO v_exists + FROM kngil.users + WHERE member_id = p_member_id AND user_id = p_user_id; + + IF v_exists = 0 THEN + RETURN 'ERROR: 수정할 사용자를 찾을 수 없습니다.'; + END IF; + + -- 2. 데이터 업데이트 + UPDATE kngil.users + SET + user_pw = COALESCE(NULLIF(p_user_pw, ''), user_pw), -- 비밀번호가 빈값이면 기존 유지 + user_nm = p_user_nm, -- 이름 + dept_nm = p_dept_nm, -- 부서명 + posit_nm = p_posit_nm, -- 직위 + tel_no = p_tel_no, -- 전화번호 + email = p_email, -- 메일주소 + auth_bc = p_auth_bc, -- 권한구분 + use_yn = p_use_yn, -- 사용여부 + rmks = p_rmks, -- 비고 + mid = p_mid, -- 수정자 ID 기록 + mdt = CURRENT_TIMESTAMP -- 수정일시 기록 + WHERE member_id = p_member_id + AND user_id = p_user_id; + + RETURN 'SUCCESS'; + +EXCEPTION WHEN OTHERS THEN + RETURN 'ERROR: ' || SQLERRM; +END; +$$; + + +ALTER FUNCTION kngil.sp_users_u(p_member_id character varying, p_user_id character varying, p_user_pw character varying, p_user_nm character varying, p_dept_nm character varying, p_posit_nm character varying, p_tel_no character varying, p_email character varying, p_auth_bc character varying, p_use_yn character varying, p_rmks character varying, p_mid character varying) OWNER TO postgres; + +-- +-- TOC entry 223 (class 1259 OID 16449) +-- Name: buy_item; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.buy_item ( + member_id character varying(20) NOT NULL, + sq_no integer NOT NULL, + buy_dt date NOT NULL, + itm_cd character(5) NOT NULL, + itm_qty numeric(18,0) NOT NULL, + itm_area numeric(18,0) NOT NULL, + add_area numeric(18,0), + sum_area numeric(18,0), + itm_amt numeric(18,2) NOT NULL, + dis_rt numeric(5,2), + buy_amt numeric(18,2), + vat_amt numeric(18,2), + sum_amt numeric(18,2), + end_dt date, + ok_yn character(1), + rmks character varying(100), + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone +); + + +ALTER TABLE kngil.buy_item OWNER TO postgres; + +-- +-- TOC entry 227 (class 1259 OID 16522) +-- Name: code_detail; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.code_detail ( + main_cd character varying(5) NOT NULL, + sub_cd character varying(5) NOT NULL, + sub_nm character varying(100), + base_cd character varying(10) NOT NULL, + use_yn character(1) NOT NULL, + sort_sq integer, + rmks character varying(100), + m1 character varying(50), + m2 character varying(50), + m3 character varying(50), + m4 character varying(50), + m5 character varying(50), + m6 character varying(50), + m7 character varying(50), + m8 character varying(50), + m9 character varying(50), + m10 character varying(50) +); + + +ALTER TABLE kngil.code_detail OWNER TO postgres; + +-- +-- TOC entry 5044 (class 0 OID 0) +-- Dependencies: 227 +-- Name: TABLE code_detail; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.code_detail IS '공통코드상세'; + + +-- +-- TOC entry 5045 (class 0 OID 0) +-- Dependencies: 227 +-- Name: COLUMN code_detail.sub_nm; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.code_detail.sub_nm IS '서브코드명'; + + +-- +-- TOC entry 226 (class 1259 OID 16497) +-- Name: code_master; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.code_master ( + main_cd character varying(5) NOT NULL, + nain_nm character varying(100), + use_yn character(1), + use_bc character varying(10), + sort_bc character varying(10), + rmks character varying(100), + t1 character varying(50), + t2 character varying(50), + t3 character varying(50), + t4 character varying(50), + t5 character varying(50), + t6 character varying(50), + t7 character varying(50), + t8 character varying(50), + t9 character varying(50), + t10 character varying(50) +); + + +ALTER TABLE kngil.code_master OWNER TO postgres; + +-- +-- TOC entry 5046 (class 0 OID 0) +-- Dependencies: 226 +-- Name: TABLE code_master; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.code_master IS '공통코드마스터'; + + +-- +-- TOC entry 5047 (class 0 OID 0) +-- Dependencies: 226 +-- Name: COLUMN code_master.main_cd; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.code_master.main_cd IS '메인코드'; + + +-- +-- TOC entry 5048 (class 0 OID 0) +-- Dependencies: 226 +-- Name: COLUMN code_master.nain_nm; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.code_master.nain_nm IS '코드명'; + + +-- +-- TOC entry 236 (class 1259 OID 16679) +-- Name: fa_comments_fa_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.fa_comments ALTER COLUMN fa_id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME kngil.fa_comments_fa_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 221 (class 1259 OID 16427) +-- Name: item; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.item ( + itm_cd character varying(5) NOT NULL, + itm_nm character varying(50), + area numeric(18,0), + itm_amt numeric(18,2), + use_yn character(1), + rmks character varying(100), + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone +); + + +ALTER TABLE kngil.item OWNER TO postgres; + +-- +-- TOC entry 5049 (class 0 OID 0) +-- Dependencies: 221 +-- Name: TABLE item; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.item IS '서비스 상품 관리 테이블'; + + +-- +-- TOC entry 225 (class 1259 OID 16484) +-- Name: login_history; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.login_history ( + user_id character varying(20) NOT NULL, + sq_no integer NOT NULL, + public_ip character varying(45), + local_ip character varying(45), + login_tm timestamp without time zone +); + + +ALTER TABLE kngil.login_history OWNER TO postgres; + +-- +-- TOC entry 220 (class 1259 OID 16414) +-- Name: members; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.members ( + member_id character varying(20) CONSTRAINT member_member_id_not_null NOT NULL, + member_nm character varying(50) CONSTRAINT member_member_nm_not_null NOT NULL, + co_bc character varying(10) CONSTRAINT member_co_bc_not_null NOT NULL, + bs_no character varying(15), + co_nm character varying(50) CONSTRAINT member_co_nm_not_null NOT NULL, + co_tel character varying(13), + tel_no character varying(13), + email character varying(50), + join_dt date CONSTRAINT member_join_dt_not_null NOT NULL, + end_dt date, + buy_area numeric(18,0), + use_area numeric(18,0), + stat_bc character varying(10) CONSTRAINT member_stat_bc_not_null NOT NULL, + memo text, + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone +); + + +ALTER TABLE kngil.members OWNER TO postgres; + +-- +-- TOC entry 5050 (class 0 OID 0) +-- Dependencies: 220 +-- Name: TABLE members; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.members IS '회원 관리 테이블'; + + +-- +-- TOC entry 5051 (class 0 OID 0) +-- Dependencies: 220 +-- Name: COLUMN members.member_id; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.members.member_id IS '회원ID'; + + +-- +-- TOC entry 228 (class 1259 OID 16594) +-- Name: qa_attachments; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.qa_attachments ( + post_id integer, + ori_name character varying, + save_path character varying, + file_size integer, + uploaded_at timestamp with time zone, + id bigint NOT NULL +); + + +ALTER TABLE kngil.qa_attachments OWNER TO postgres; + +-- +-- TOC entry 5052 (class 0 OID 0) +-- Dependencies: 228 +-- Name: TABLE qa_attachments; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.qa_attachments IS 'Q&A 첨부파일 테이블'; + + +-- +-- TOC entry 237 (class 1259 OID 16687) +-- Name: qa_attachments_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.qa_attachments ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME kngil.qa_attachments_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 234 (class 1259 OID 16627) +-- Name: qa_comment_images; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.qa_comment_images ( + id integer NOT NULL, + comment_id integer, + file_name character varying(255), + file_path character varying(255), + thumb_path character varying(255), + file_size integer, + uploaded_at timestamp with time zone +); + + +ALTER TABLE kngil.qa_comment_images OWNER TO postgres; + +-- +-- TOC entry 5053 (class 0 OID 0) +-- Dependencies: 234 +-- Name: TABLE qa_comment_images; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.qa_comment_images IS '뎃글 이미지'; + + +-- +-- TOC entry 233 (class 1259 OID 16626) +-- Name: qa_comment_images_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.qa_comment_images ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME kngil.qa_comment_images_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 232 (class 1259 OID 16618) +-- Name: qa_comments; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.qa_comments ( + comment_id integer NOT NULL, + post_id integer, + commenter character varying(255), + content text, + cdt_dt timestamp with time zone, + user_nm character varying(100), + mdt_dt timestamp with time zone +); + + +ALTER TABLE kngil.qa_comments OWNER TO postgres; + +-- +-- TOC entry 5054 (class 0 OID 0) +-- Dependencies: 232 +-- Name: TABLE qa_comments; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.qa_comments IS '뎃글'; + + +-- +-- TOC entry 231 (class 1259 OID 16617) +-- Name: qa_comments_comment_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.qa_comments ALTER COLUMN comment_id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME kngil.qa_comments_comment_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 230 (class 1259 OID 16603) +-- Name: qa_posts; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.qa_posts ( + post_id integer NOT NULL, + tel_no character varying(20), + user_id character varying(64), + user_nm character varying(100), + category character varying(20), + co_nm character varying(100), + dept_nm character varying(100), + title character varying(255), + content text, + attachment character varying(500), + stat_bc character varying(50), + complete_form character(1) DEFAULT 0, + cdt_dt timestamp with time zone, + mid_dt timestamp with time zone, + is_secret character(1) DEFAULT 'N'::bpchar, + is_read_admin character(1) DEFAULT 0 +); + + +ALTER TABLE kngil.qa_posts OWNER TO postgres; + +-- +-- TOC entry 5055 (class 0 OID 0) +-- Dependencies: 230 +-- Name: TABLE qa_posts; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.qa_posts IS 'Q&A 내용등록'; + + +-- +-- TOC entry 5056 (class 0 OID 0) +-- Dependencies: 230 +-- Name: COLUMN qa_posts.co_nm; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON COLUMN kngil.qa_posts.co_nm IS '회사명 +'; + + +-- +-- TOC entry 229 (class 1259 OID 16602) +-- Name: qa_posts_post_id_seq; Type: SEQUENCE; Schema: kngil; Owner: postgres +-- + +ALTER TABLE kngil.qa_posts ALTER COLUMN post_id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME kngil.qa_posts_post_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- TOC entry 224 (class 1259 OID 16471) +-- Name: use_history; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.use_history ( + user_id character varying(20) NOT NULL, + sq_no integer NOT NULL, + use_dt date, + use_area numeric(18,0), + ser_bc character varying(10), + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone +); + + +ALTER TABLE kngil.use_history OWNER TO postgres; + +-- +-- TOC entry 222 (class 1259 OID 16433) +-- Name: users; Type: TABLE; Schema: kngil; Owner: postgres +-- + +CREATE TABLE kngil.users ( + member_id character varying(20) CONSTRAINT user_member_id_not_null NOT NULL, + user_id character varying(20) CONSTRAINT user_user_id_not_null NOT NULL, + user_pw character varying(255), + user_nm character varying(50) CONSTRAINT user_user_nm_not_null NOT NULL, + dept_nm character varying(50), + posit_nm character varying(50), + tel_no character varying(13) CONSTRAINT user_tel_no_not_null NOT NULL, + email character varying(50), + auth_bc character varying(10) CONSTRAINT user_auth_bc_not_null NOT NULL, + use_yn character varying(10) CONSTRAINT user_use_yn_not_null NOT NULL, + rmks character varying(100), + cid character varying(20), + cdt timestamp without time zone, + mid character varying(20), + mdt timestamp without time zone, + oidc_sub character varying(255) +); + + +ALTER TABLE kngil.users OWNER TO postgres; + +-- +-- TOC entry 5057 (class 0 OID 0) +-- Dependencies: 222 +-- Name: TABLE users; Type: COMMENT; Schema: kngil; Owner: postgres +-- + +COMMENT ON TABLE kngil.users IS '사용자관리 테이블'; + + +-- +-- TOC entry 5024 (class 0 OID 16449) +-- Dependencies: 223 +-- Data for Name: buy_item; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.buy_item (member_id, sq_no, buy_dt, itm_cd, itm_qty, itm_area, add_area, sum_area, itm_amt, dis_rt, buy_amt, vat_amt, sum_amt, end_dt, ok_yn, rmks, cid, cdt, mid, mdt) FROM stdin; +B260001 1 2026-01-14 BSV01 1 10000 500 10500 5000000.00 0.00 5000000.00 500000.00 5500000.00 2026-12-31 N test m24031 2026-01-14 18:48:27.938147 m24031 2026-01-14 18:48:27.938147 +sdisdi 2 2026-01-28 BSV01 3 0 100 100 5000000.00 10.00 13500000.00 1350000.00 14850000.00 \N N ADMIN 2026-01-29 14:02:04.478554 ADMIN 2026-01-29 16:57:37.297659 +sdisdi 1 2026-01-28 실버 2 0 0 0 5000000.00 10.00 9000000.00 900000.00 9900000.00 \N N ADMIN 2026-01-29 13:20:06.016857 ADMIN 2026-01-29 16:57:37.297659 +sdisdi 4 2026-01-28 BSV01 1 10000 0 10000 5000000.00 5.00 5000000.00 500000.00 5500000.00 2026-12-31 N ADMIN 2026-01-29 16:57:37.297659 ADMIN 2026-01-29 16:57:37.297659 +B260001 4 2026-01-16 DDA01 2 10000 0 10000 10000000.00 20.00 8000000.00 800000.00 8800000.00 2026-01-29 Y test1 ADMIN 2026-01-19 09:29:15.302285 ADMIN 2026-01-19 09:29:26.957085 +B260001 6 2026-01-19 DDA01 2 100000 0 100000 10000000.00 25.00 15000000.00 1500000.00 16500000.00 2026-01-29 Y ADMIN 2026-01-19 09:53:04.703453 ADMIN 2026-01-19 11:18:22.708297 +B260001 5 2026-01-19 DDA01 4 10000 0 10000 10000000.00 10.00 36000000.00 3600000.00 39600000.00 2026-01-30 Y ADMIN 2026-01-19 09:33:44.805899 ADMIN 2026-01-19 11:18:22.708297 +B260001 3 2026-01-16 DDA01 1 10000 0 10000 10000000.00 50.00 5000000.00 500000.00 5500000.00 2026-01-26 Y test ADMIN 2026-01-16 17:43:34.138282 ADMIN 2026-01-19 09:29:26.957085 +B260001 2 2026-01-16 DDA01 1 10000 0 10000 10000000.00 10.00 9000000.00 900000.00 9900000.00 2026-01-29 Y test ADMIN 2026-01-16 17:29:57.797917 ADMIN 2026-01-19 09:29:26.957085 +B260001 7 2026-01-19 BSV01 1 10000 0 10000 5000000.00 20.00 5000000.00 500000.00 5500000.00 2026-02-06 N ADMIN 2026-01-19 11:18:09.714842 ADMIN 2026-01-19 11:18:22.708297 +sdisdi 5 2026-01-29 CGD01 2 20000 500 20500 10000000.00 25.00 15000000.00 1500000.00 16500000.00 2026-12-30 N ADMIN 2026-01-29 17:10:39.39493 ADMIN 2026-01-29 17:12:56.807569 +sdisdi 3 2026-01-29 BSV01 1 10000 0 10000 5000000.00 5.00 4750000.00 475000.00 5225000.00 2026-12-31 N ADMIN 2026-01-29 15:31:42.295678 ADMIN 2026-01-29 17:12:56.807569 +tester001 1 2025-04-12 BSV01 1 10000 \N 10000 5000000.00 0.00 5000000.00 500000.00 5500000.00 2025-12-31 Y \N \N \N \N \N +tester001 2 2026-01-01 BSV01 1 10000 300 10300 5000000.00 0.00 5000000.00 500000.00 5500000.00 2026-12-31 Y \N \N \N \N \N +tester001 3 2026-01-01 BSV01 1 10000 \N 10000 5000000.00 0.00 5000000.00 500000.00 5500000.00 2026-12-31 Y \N \N \N \N \N +tester001 4 2026-01-01 BSV01 1 10000 \N 10000 5000000.00 0.00 5000000.00 500000.00 5500000.00 2026-12-31 Y \N \N \N \N \N +sdi9429 1 2026-01-29 골드 1 20000 0 20000 10000000.00 0.00 10000000.00 1000000.00 11000000.00 \N Y ADMIN 2026-01-29 13:05:15.730538 ADMIN 2026-01-29 13:14:38.877759 +sdisdi 6 2026-01-29 ZET01 3000 1 0 1 0.00 0.00 0.00 0.00 0.00 2026-12-31 N ADMIN 2026-01-29 17:12:56.807569 ADMIN 2026-01-29 17:12:56.807569 +am24031 1 2026-01-30 A0000 1 3000 0 3000 0.00 0.00 0.00 0.00 0.00 2026-12-31 Y 최초 가입 제공 am24031 2026-01-30 13:25:57.005379 am24031 2026-01-30 13:25:57.005379 + + +-- +-- TOC entry 5028 (class 0 OID 16522) +-- Dependencies: 227 +-- Data for Name: code_detail; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.code_detail (main_cd, sub_cd, sub_nm, base_cd, use_yn, sort_sq, rmks, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10) FROM stdin; +BS100 300 메인 BS100300 Y 3 \N 1 \N \N \N \N \N \N \N \N \N +BS110 100 오름차순 BS110100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +BS110 200 정의 BS110200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +BS200 100 코드1 BS200100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +BS200 200 코드2 BS200200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +BS210 Y Y BS210Y Y 1 \N \N \N \N \N \N \N \N \N \N \N +BS210 N N BS210N Y 2 \N \N \N \N \N \N \N \N \N \N \N +BS220 1 사용 BS2201 Y 1 \N \N \N \N \N \N \N \N \N \N \N +BS220 0 미사용 BS2200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +SA100 100 사용 SA100100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +SA100 200 미사용 SA100200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +BS100 100 개발자 BS100100 Y 1 \N \N 1 \N \N \N \N \N \N \N \N +BS100 200 관리자 BS100200 Y 2 \N \N 1 \N \N \N \N \N \N \N \N +BS100 400 서브 BS100400 Y 4 \N 1 \N \N \N \N \N \N \N \N \N +BS100 500 일반 BS100500 Y 5 \N 1 \N \N \N \N \N \N \N \N \N +SA150 200 개인 SA150200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +SA150 100 기업 SA150100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +SA200 100 상하수도 기초현황 보고서 SA200100 Y 1 \N \N \N \N \N \N \N \N \N \N \N +SA200 200 도시계획 기초현황 보고서 SA200200 Y 2 \N \N \N \N \N \N \N \N \N \N \N +SA200 300 기초현황 DATA 파일 SA200300 Y 3 \N \N \N \N \N \N \N \N \N \N \N + + +-- +-- TOC entry 5027 (class 0 OID 16497) +-- Dependencies: 226 +-- Data for Name: code_master; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.code_master (main_cd, nain_nm, use_yn, use_bc, sort_bc, rmks, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) FROM stdin; +BS110 정렬기준 Y BS200100 BS110100 \N \N \N \N \N \N \N \N \N \N \N +BS200 사용구분\n Y BS200100 BS110100 \N \N \N \N \N \N \N \N \N \N \N +BS210 Y/N 구분 Y BS200200 BS110100 \N \N \N \N \N \N \N \N \N \N \N +BS220 사용여부 Y BS200200 BS110100 \N \N \N \N \N \N \N \N \N \N \N +SA100 회원상태 Y BS200100 BS110100 \N \N \N \N \N \N \N \N \N \N \N +SA200 서비스구분 Y BS200100 BS110100 \N \N \N \N \N \N \N \N \N \N \N +BS100 권한관리 Y BS200100 BS110100 \N 사용자 관리자 \N \N \N \N \N \N \N \N +SA150 법인구분 Y BS200100 BS110200 \N \N \N \N \N \N \N \N \N \N \N + + +-- +-- TOC entry 5036 (class 0 OID 16671) +-- Dependencies: 235 +-- Data for Name: fa_comments; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.fa_comments (fa_id, fa_subject, fa_content, sq_no, use_yn, cid, cdt, mid, mdt) FROM stdin; +3 계정은 어떻게 생성하나요? 회원가입 메뉴를 통해 계정을 생성할 수 있습니다. 2 Y \N \N \N \N +1 KNGIL은 어떤 서비스인가요? KNGIL은 교량 BIM 기반 설계·관리 플랫폼입니다. hahaha 1 Y \N \N \N \N +13 이것은질문 이것은
답글 0 Y m24031 2026-01-29 14:12:20.625501+09 m24031 2026-01-29 14:26:04.837518+09 +14 ㅇㅇㅇㅇ343 ㅇㅁㅇㄹㅇㅇㄹㅇㄹㅇㄹ 0 Y m24031 2026-02-02 11:22:02.315133+09 m24031 2026-02-02 11:22:07.740956+09 + + +-- +-- TOC entry 5022 (class 0 OID 16427) +-- Dependencies: 221 +-- Data for Name: item; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.item (itm_cd, itm_nm, area, itm_amt, use_yn, rmks, cid, cdt, mid, mdt) FROM stdin; +CGD01 골드 20000 10000000.00 Y test m24031 2026-01-14 17:55:27.062562 m24031 2026-01-14 17:55:27.062562 +ZET01 서비스 1 0.00 Y test m24031 2026-01-14 17:56:47.427489 m24031 2026-01-14 17:56:47.427489 +BSV01 실버 10000 5000000.00 Y test m24031 2026-01-14 17:54:53.937187 m24031 2026-01-14 17:58:17.831158 +BBBB TEST23 4 2.00 Y m24031 2026-01-23 14:28:12.793976 m24031 2026-01-23 15:23:19.231161 +DDA01 다이아 30000 30000000.00 Y testㅇㅇㅇ m24031 2026-01-14 17:56:22.996253 m24031 2026-01-29 13:50:16.978103 +A0000 회원가입 제공 3000 0.00 Y m24031 2026-01-30 10:01:26.15998 m24031 2026-01-30 10:01:26.15998 + + +-- +-- TOC entry 5026 (class 0 OID 16484) +-- Dependencies: 225 +-- Data for Name: login_history; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.login_history (user_id, sq_no, public_ip, local_ip, login_tm) FROM stdin; + + +-- +-- TOC entry 5021 (class 0 OID 16414) +-- Dependencies: 220 +-- Data for Name: members; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.members (member_id, member_nm, co_bc, bs_no, co_nm, co_tel, tel_no, email, join_dt, end_dt, buy_area, use_area, stat_bc, memo, cid, cdt, mid, mdt) FROM stdin; +b25027 김수현 CB100100 223-33-44445 한맥기술 \N 010-5645-5153 b25027@hanmaceng.co.kr 2026-01-29 \N \N \N SA100100 \N b25027 2026-01-29 11:25:14.184682 b24014 2026-01-29 16:21:12.842875 +B260001 바론 관리자 SA150100 222222222 바론 컨설턴트 \N 010-1111-1111 sdi@sdi.com 2026-01-01 \N 140000 1200 SA100100 \N \N \N SYSTEM 2026-01-21 15:45:40.131991 +tester001 테스터10 SA150100 \N 기업1 \N 010-1111-1111 111@gmail.com 2026-01-20 \N 3000 \N SA100100 \N m24031 2026-01-20 19:50:23.029203 \N \N +ctest004 c테스터33 SA150200 \N 기업3 \N 010-3333-3333 333@gmail.com 2026-01-20 \N 1000 \N SA100100 \N m24031 2026-01-20 19:57:42.806965 \N \N +sdi9429 송대일 CB100100 \N 바론 \N 010-8627-0921 sdi9429@naver.com 2026-01-22 \N 1000 \N SA100100 \N sdi9429 2026-01-22 12:56:43.651431 \N \N +tester003 테스터33 SA150200 222222222 기업3 \N 010-3333-3333 333@gmail.com 2026-01-20 \N 2000 \N SA100100 \N m24031 2026-01-20 19:52:19.322838 m24031 2026-01-23 16:50:12.264163 +tester002 테스터10 SA150100 222222 기업1 \N 010-1111-1111 111@gmail.com 2026-01-20 \N 2000 \N SA100100 \N m24031 2026-01-20 19:51:18.809202 m24031 2026-01-27 10:44:31.258757 +sdisdi 송대일 CB100100 \N 바론11 \N 010-8627-0923 sdi9429@naver.com 2026-01-22 \N 0 \N SA100100 \N sdisdi 2026-01-22 13:12:29.376769 m24031 2026-01-27 10:44:47.776389 +Km24031 권오재 CB100100 \N 한맥기술 \N 010-9114-3944 koj111@naver.com 2026-01-30 \N \N \N SA100100 \N Km24031 2026-01-30 09:02:15.195496 \N \N +am24031 권오재A CB100100 \N 한맥 \N 010-2222-2222 ddd@gmail.com 2026-01-30 \N 3000 \N SA100100 \N am24031 2026-01-30 13:25:57.005379 \N \N + + +-- +-- TOC entry 5029 (class 0 OID 16594) +-- Dependencies: 228 +-- Data for Name: qa_attachments; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.qa_attachments (post_id, ori_name, save_path, file_size, uploaded_at, id) FROM stdin; +7 traffic_image.jpg /kngil/uploads/qa/1769509499_417330b6f49e.jpg 123753 2026-01-27 19:24:59.134487+09 1 +8 traffic_image.jpg /kngil/uploads/qa/1769513369_959a050cb972.jpg 123753 2026-01-27 20:29:29.973998+09 2 + + +-- +-- TOC entry 5035 (class 0 OID 16627) +-- Dependencies: 234 +-- Data for Name: qa_comment_images; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.qa_comment_images (id, comment_id, file_name, file_path, thumb_path, file_size, uploaded_at) FROM stdin; +1 2 og-main-thumb.JPG /kngil/uploads/comment/1769511961_dda23bd3.jpg /kngil/uploads/comment/1769511961_dda23bd3.jpg 112930 2026-01-27 20:06:01.050825+09 + + +-- +-- TOC entry 5033 (class 0 OID 16618) +-- Dependencies: 232 +-- Data for Name: qa_comments; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.qa_comments (comment_id, post_id, commenter, content, cdt_dt, user_nm, mdt_dt) FROM stdin; +2 7 b24014 test 2026-01-27 20:06:01.050825+09 송대일1 \N + + +-- +-- TOC entry 5031 (class 0 OID 16603) +-- Dependencies: 230 +-- Data for Name: qa_posts; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.qa_posts (post_id, tel_no, user_id, user_nm, category, co_nm, dept_nm, title, content, attachment, stat_bc, complete_form, cdt_dt, mid_dt, is_secret, is_read_admin) FROM stdin; +7 \N b24014 송대일1 오류문의 \N \N 테스트

1234

\N review 0 2026-01-27 19:24:59.131617+09 2026-01-27 20:14:21.993651+09 0 Y +8 \N b24014 송대일1 오류문의 \N \N 첨부파일 업로드

123

\N deep 0 2026-01-27 20:29:29.970856+09 2026-01-27 20:32:21.107049+09 0 Y +10 010-8627-0921 b24014 송대일1 오류문의 바론 컨설턴트 총괄기획실 test

1qasdweqwe

\N review 0 2026-01-28 10:43:24.655249+09 2026-01-28 11:00:56.361487+09 Y Y +1 오류문의 1111

3333

\N wait 0 2026-01-27 14:09:56.639903+09 \N Y 0 +2 \N b24014 송대일1 오류문의 \N \N 1234

3333

\N wait 0 2026-01-27 15:56:13.097696+09 \N 0 N +5 \N b24014 송대일1 오류문의 \N \N 123

456

\N wait 0 2026-01-27 16:40:55.238147+09 \N 0 N +9 \N b24014 송대일1 오류문의 \N \N 최종테스트

11111333

\N wait 0 2026-01-28 10:09:07.943555+09 \N 0 Y +6 \N b24014 송대일1 오류문의 \N \N 첨부파일 업로드

123

\N wait 0 2026-01-27 19:17:08.118218+09 \N 0 Y +4 \N b24014 송대일1 개선문의 \N \N tests

tests

\N wait 0 2026-01-27 16:00:43.415892+09 \N 0 Y +12 010-8627-0921 b24014 송대일1 일반문의 바론 컨설턴트 총괄기획실 테스트 작성글

ㅅㅅㅅㅅㅅㅅㅅ

\N wait 0 2026-01-30 16:10:49.845849+09 2026-02-02 09:32:40.063076+09 N Y +11 010-8627-0921 b24014 송대일1 오류문의 바론 컨설턴트 총괄기획실 1111

3123123

\N wait 0 2026-01-30 16:10:30.821517+09 2026-02-02 09:32:54.632593+09 N Y +13 010-8627-0921 b24014 송대일1 오류문의 바론 컨설턴트 총괄기획실 테스트 중입니다

3333444

\N wait 0 2026-02-02 09:33:22.636799+09 \N N Y +14 010-8627-0921 b24014 송대일1 공지사항 바론 컨설턴트 총괄기획실 공지 테스트

1234

\N wait 0 2026-02-02 09:40:50.464071+09 \N N Y + + +-- +-- TOC entry 5025 (class 0 OID 16471) +-- Dependencies: 224 +-- Data for Name: use_history; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.use_history (user_id, sq_no, use_dt, use_area, ser_bc, cid, cdt, mid, mdt) FROM stdin; +m24031 1 2026-01-18 100 SA200100 \N \N \N \N +test001 1 2026-01-18 200 SA200100 \N \N \N \N +test001 2 2026-01-19 200 SA200100 \N \N \N \N +m24031 2 2026-01-19 200 SA200300 \N \N \N \N +m24031 3 2026-01-19 300 SA200200 \N \N \N \N +m24031 4 2026-01-19 100 SA200300 \N \N \N \N +m24031 5 2026-01-19 20000 SA200300 \N \N \N \N +m24031 6 2026-01-19 1000 SA200200 \N \N \N \N +m24031 7 2025-01-19 20000 SA200100 \N \N \N \N + + +-- +-- TOC entry 5023 (class 0 OID 16433) +-- Dependencies: 222 +-- Data for Name: users; Type: TABLE DATA; Schema: kngil; Owner: postgres +-- + +COPY kngil.users (member_id, user_id, user_pw, user_nm, dept_nm, posit_nm, tel_no, email, auth_bc, use_yn, rmks, cid, cdt, mid, mdt, oidc_sub) FROM stdin; +B260001 test003 test003 테스터3 기술개발센터 수석연구원 010-1111-3333 test@test.com BS100400 N test 입니다. m24031 2026-01-14 18:39:43.030024 m24031 2026-01-14 18:42:22.488198 \N +sdisdi song12124 test1234!!@ 테스터4 erp 1012346770 test@test.co.kr BS100500 Y song12124 2026-01-30 11:12:14.172743 song12124 2026-01-30 11:12:14.172743 \N +tester002 tester002 tester002 류호성 전산실 \N 010-3371-5649 111@gmail.com BS100100 Y \N m24031 2026-01-20 19:51:18.809202 m24031 2026-01-27 10:44:31.258757 \N +sdisdi song12125 test1234!!@ 테스터5 erp 1012346771 test@test.co.kr BS100500 Y song12125 2026-01-30 11:12:14.184522 song12125 2026-01-30 11:12:14.184522 \N +ctest004 ctest004 ctest004 권오재2 전산실 \N 010-9114-3943 333@gmail.com BS100300 N \N m24031 2026-01-20 19:57:42.806965 \N \N \N +B260001 sdi1108 sdi1108 어드민 ERP팀ㅁ 010-1111-1112 test@test1.com BS100400 Y \N 2026-01-15 20:08:48.731648 m24031 2026-01-29 13:29:52.388359 \N +sdisdi song12126 test1234!!@ 테스터6 erp 1012346772 test@test.co.kr BS100500 Y song12126 2026-01-30 11:18:04.916446 song12126 2026-01-30 11:18:04.916446 \N +sdisdi song12127 test1234!!@ 테스터7 erp 1012346773 test@test.co.kr BS100500 Y song12127 2026-01-30 11:18:04.921195 song12127 2026-01-30 11:18:04.921195 \N +sdisdi song12128 test1234!!@ 테스터8 erp 1012346774 test@test.co.kr BS100500 Y song12128 2026-01-30 12:53:06.276828 song12128 2026-01-30 12:53:06.276828 \N +sdisdi song12129 test1234!!@ 테스터9 erp 1012346775 test@test.co.kr BS100500 Y song12129 2026-01-30 12:53:06.284774 song12129 2026-01-30 12:53:06.284774 \N +sdisdi song12130 test1234!!@ 테스터10 erp 1012346776 test@test.co.kr BS100500 Y song12130 2026-01-30 13:01:11.566978 song12130 2026-01-30 13:01:11.566978 \N +sdisdi song12131 test1234!!@ 테스터11 erp 1012346777 test@test.co.kr BS100500 Y song12131 2026-01-30 13:01:11.57227 song12131 2026-01-30 13:01:11.57227 \N +am24031 am24031 !rnjsdhwo729 권오재A ERP 기획 \N 010-2222-2222 ddd@gmail.com BS100300 Y \N am24031 2026-01-30 13:25:57.005379 \N \N \N +b25027 b25027 a1357125!@23 김수현 디자인기획팀 \N 010-5645-5153 b25027@hanmaceng.co.kr BS100300 Y \N b25027 2026-01-29 11:25:14.184682 b24014 2026-01-29 16:21:12.842875 \N +B260001 test005 b23008 염승호 총괄기획실 수석 연구원 010-8835-0501 df BS100100 Y m24031 2026-01-29 13:30:30.456963 m24031 2026-01-29 13:30:30.456963 \N +B260001 test002 test002 테스터2 기술개발센터 010-1111-2222 test@test.com BS100400 Y m24031 2026-01-14 18:39:03.432801 m24031 2026-01-29 20:31:27.681975 \N +tester003 btest001 btest001 테스터33 도로부 BS100500 Y test m24031 2026-01-20 19:56:32.467743 m24031 2026-01-20 19:56:32.467743 \N +tester003 btest002 btest002 b테스터22 도로부 BS100500 Y test m24031 2026-01-20 19:56:48.750462 m24031 2026-01-20 19:56:48.750462 \N +B260001 m24031 m24031 권오재 총괄기획실 선임 연구원 010-9114-3943 m24031@hanmaceng.co.kr BS100100 Y \N \N \N \N \N \N +Km24031 Km24031 !rnjsdhwo729 권오재 ERP기획팀 \N 010-9114-3944 koj111@naver.com BS100300 Y \N Km24031 2026-01-30 09:02:15.195496 \N \N \N +sdi9429 sdi9429 test1111 송대일 총괄기획실 \N 010-8627-0922 sdi9429@naver.com BS100300 Y \N sdi9429 2026-01-22 12:56:43.651431 \N \N \N +B260001 b24014 test001 송대일1 총괄기획실 010-8627-0921 b24014@hanmaceng.co.kr\n BS100100 Y 123 \N \N SYSTEM 2026-01-19 16:44:25.814475 \N +tester003 tester003 test001 테스터33 전산실 \N 010-3333-3333 333@gmail.com BS100300 Y \N m24031 2026-01-20 19:52:19.322838 m24031 2026-01-23 16:50:12.264163 \N +B260001 test001 test001 회사관리자 총괄기획실1 010-3189-1514 sdi1111@sdi.com BS100300 Y 권한위임[sdi1108] \N \N m24031 2026-01-22 17:11:58.275998 \N +B260001 test0005 test0005 테스트55 총괄기획실1 010-4158-5840 b24000@hanmaceng.co.kr BS100400 Y 비고 입력 테스트 SYSTEM 2026-01-19 19:39:10.476205 SYSTEM 2026-01-19 19:39:10.476205 \N +tester001 tester001 test001 테스터10 전산실 \N 010-9523-0055 111@gmail.com BS100300 Y \N m24031 2026-01-20 19:50:23.029203 \N \N \N +sdisdi sdisdi song1108! 송대일 총괄기획실 \N 010-8627-0923 sdi9429@naver.com BS100300 Y \N sdisdi 2026-01-22 13:12:29.376769 m24031 2026-01-27 10:44:47.776389 \N +sdisdi song1212 test1234!!@ 테스터 erp 01012345689 test@test.co.kr BS100500 N song1212 2026-01-30 09:58:45.274688 sdisdi 2026-01-30 10:02:41.963234 \N +sdisdi song12121 test1234!!@ 테스터1 erp 1012346767 test@test.co.kr BS100400 N song12121 2026-01-30 10:05:59.726345 song12121 2026-01-30 10:05:59.726345 \N +sdisdi song12122 test1234!!@ 테스터2 erp 1012346768 test@test.co.kr BS100500 Y song12122 2026-01-30 10:46:04.719717 song12122 2026-01-30 10:46:04.719717 \N +sdisdi song12123 test1234!!@ 테스터3 erp 1012346769 test@test.co.kr BS100500 Y song12123 2026-01-30 10:46:04.729306 song12123 2026-01-30 10:46:04.729306 \N +B260001 sdi111 000000 test123 ert33 010-8623-6564 test@test.com BS100500 Y b24014 2026-01-30 13:02:45.327993 b24014 2026-01-30 14:08:03.710466 \N +B260001 sdisssss 1234asd!! test1232 erpp 010-8686-2323 test@test.com BS100500 Y b24014 2026-01-30 13:40:50.09753 b24014 2026-01-30 14:08:14.578146 \N +B260001 test1010 test1010 test12 erp 010-8888-2222 test@test1.co.kr BS100500 Y b24014 2026-01-30 13:56:43.807999 b24014 2026-01-30 14:18:12.484107 \N +am24031 kwon001 kwon001 권1 dd 010-1111-2111 kwon001 BS100500 Y kwon001 2026-01-30 16:05:35.819863 kwon001 2026-01-30 16:05:35.819863 \N +am24031 kwon002 kwon002 권2 aa 010-1111-2112 kwon002 BS100500 Y kwon002 2026-01-30 16:05:35.846448 kwon002 2026-01-30 16:05:35.846448 \N + + +-- +-- TOC entry 5058 (class 0 OID 0) +-- Dependencies: 236 +-- Name: fa_comments_fa_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.fa_comments_fa_id_seq', 14, true); + + +-- +-- TOC entry 5059 (class 0 OID 0) +-- Dependencies: 237 +-- Name: qa_attachments_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.qa_attachments_id_seq', 2, true); + + +-- +-- TOC entry 5060 (class 0 OID 0) +-- Dependencies: 233 +-- Name: qa_comment_images_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.qa_comment_images_id_seq', 1, true); + + +-- +-- TOC entry 5061 (class 0 OID 0) +-- Dependencies: 231 +-- Name: qa_comments_comment_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.qa_comments_comment_id_seq', 2, true); + + +-- +-- TOC entry 5062 (class 0 OID 0) +-- Dependencies: 229 +-- Name: qa_posts_post_id_seq; Type: SEQUENCE SET; Schema: kngil; Owner: postgres +-- + +SELECT pg_catalog.setval('kngil.qa_posts_post_id_seq', 14, true); + + +-- +-- TOC entry 4867 (class 2606 OID 16678) +-- Name: fa_comments fa_comments_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.fa_comments + ADD CONSTRAINT fa_comments_pkey PRIMARY KEY (fa_id); + + +-- +-- TOC entry 4855 (class 2606 OID 16643) +-- Name: login_history login_history_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.login_history + ADD CONSTRAINT login_history_pkey PRIMARY KEY (user_id, sq_no); + + +-- +-- TOC entry 4851 (class 2606 OID 16460) +-- Name: buy_item pk_buy_item; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.buy_item + ADD CONSTRAINT pk_buy_item PRIMARY KEY (member_id, sq_no); + + +-- +-- TOC entry 4859 (class 2606 OID 16532) +-- Name: code_detail pk_code_detail; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.code_detail + ADD CONSTRAINT pk_code_detail PRIMARY KEY (main_cd, sub_cd); + + +-- +-- TOC entry 4857 (class 2606 OID 16504) +-- Name: code_master pk_code_master; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.code_master + ADD CONSTRAINT pk_code_master PRIMARY KEY (main_cd); + + +-- +-- TOC entry 4845 (class 2606 OID 16555) +-- Name: item pk_item; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.item + ADD CONSTRAINT pk_item PRIMARY KEY (itm_cd); + + +-- +-- TOC entry 4843 (class 2606 OID 16426) +-- Name: members pk_members; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.members + ADD CONSTRAINT pk_members PRIMARY KEY (member_id); + + +-- +-- TOC entry 4861 (class 2606 OID 16690) +-- Name: qa_attachments qa_attachments_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.qa_attachments + ADD CONSTRAINT qa_attachments_pkey PRIMARY KEY (id); + + +-- +-- TOC entry 4865 (class 2606 OID 16625) +-- Name: qa_comments qa_comments_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.qa_comments + ADD CONSTRAINT qa_comments_pkey PRIMARY KEY (comment_id); + + +-- +-- TOC entry 4863 (class 2606 OID 16616) +-- Name: qa_posts qa_posts_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.qa_posts + ADD CONSTRAINT qa_posts_pkey PRIMARY KEY (post_id); + + +-- +-- TOC entry 4853 (class 2606 OID 16641) +-- Name: use_history use_history_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.use_history + ADD CONSTRAINT use_history_pkey PRIMARY KEY (user_id, sq_no); + + +-- +-- TOC entry 4847 (class 2606 OID 16721) +-- Name: users users_oidc_sub_key; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.users + ADD CONSTRAINT users_oidc_sub_key UNIQUE (oidc_sub); + + +-- +-- TOC entry 4849 (class 2606 OID 16645) +-- Name: users users_pkey; Type: CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.users + ADD CONSTRAINT users_pkey PRIMARY KEY (user_id); + + +-- +-- TOC entry 4873 (class 2620 OID 16705) +-- Name: buy_item trg_buy_item_changed; Type: TRIGGER; Schema: kngil; Owner: postgres +-- + +CREATE TRIGGER trg_buy_item_changed AFTER INSERT OR DELETE OR UPDATE ON kngil.buy_item FOR EACH ROW EXECUTE FUNCTION kngil.fn_update_buy_area(); + + +-- +-- TOC entry 4872 (class 2606 OID 16533) +-- Name: code_detail fk_code_master_to_code_detail; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.code_detail + ADD CONSTRAINT fk_code_master_to_code_detail FOREIGN KEY (main_cd) REFERENCES kngil.code_master(main_cd) ON DELETE CASCADE; + + +-- +-- TOC entry 4868 (class 2606 OID 16656) +-- Name: users fk_member_to_user; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.users + ADD CONSTRAINT fk_member_to_user FOREIGN KEY (member_id) REFERENCES kngil.members(member_id) NOT VALID; + + +-- +-- TOC entry 4869 (class 2606 OID 16461) +-- Name: buy_item fk_members_to_buy_item; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.buy_item + ADD CONSTRAINT fk_members_to_buy_item FOREIGN KEY (member_id) REFERENCES kngil.members(member_id); + + +-- +-- TOC entry 4871 (class 2606 OID 16651) +-- Name: login_history fk_user_to_login_history; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.login_history + ADD CONSTRAINT fk_user_to_login_history FOREIGN KEY (user_id) REFERENCES kngil.users(user_id) NOT VALID; + + +-- +-- TOC entry 4870 (class 2606 OID 16646) +-- Name: use_history fk_user_to_use_history; Type: FK CONSTRAINT; Schema: kngil; Owner: postgres +-- + +ALTER TABLE ONLY kngil.use_history + ADD CONSTRAINT fk_user_to_use_history FOREIGN KEY (user_id) REFERENCES kngil.users(user_id) NOT VALID; + + +-- Completed on 2026-02-02 14:06:04 + +-- +-- PostgreSQL database dump complete +-- + + From 8d7ae422e4d69176ef699566d4b388a1a174d95e Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 17:27:32 +0900 Subject: [PATCH 20/21] Ensure kngil DB exists in init script --- docker/initdb/01_kngil_DB.sql | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docker/initdb/01_kngil_DB.sql b/docker/initdb/01_kngil_DB.sql index 33ebe4f..60695f6 100644 --- a/docker/initdb/01_kngil_DB.sql +++ b/docker/initdb/01_kngil_DB.sql @@ -20,6 +20,14 @@ SET xmloption = content; SET client_min_messages = warning; SET row_security = off; +-- +-- Ensure target database exists (idempotent for init scripts) +-- +\connect postgres +SELECT 'CREATE DATABASE kngil' +WHERE NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'kngil')\gexec +\connect kngil + -- -- TOC entry 6 (class 2615 OID 16413) -- Name: kngil; Type: SCHEMA; Schema: -; Owner: postgres @@ -2378,4 +2386,3 @@ ALTER TABLE ONLY kngil.use_history -- \unrestrict osPaC8Gqjay0KBMwX4hwgDvmjwF5rTGmBMzQBdxAne3SBCLMuCNQu2Xg15dPVeb - From 90dbb86c944e58814e16c62b9373f9c3cc40cc78 Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Wed, 4 Feb 2026 17:32:28 +0900 Subject: [PATCH 21/21] Create kngil DB in init shell script --- docker/initdb/00_create_db.sh | 12 ++++++++++++ docker/initdb/01_kngil_DB.sql | 8 -------- 2 files changed, 12 insertions(+), 8 deletions(-) create mode 100755 docker/initdb/00_create_db.sh diff --git a/docker/initdb/00_create_db.sh b/docker/initdb/00_create_db.sh new file mode 100755 index 0000000..62f71bb --- /dev/null +++ b/docker/initdb/00_create_db.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -e + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "postgres" <<'EOSQL' +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'kngil') THEN + CREATE DATABASE kngil; + END IF; +END +$$; +EOSQL diff --git a/docker/initdb/01_kngil_DB.sql b/docker/initdb/01_kngil_DB.sql index 60695f6..f439597 100644 --- a/docker/initdb/01_kngil_DB.sql +++ b/docker/initdb/01_kngil_DB.sql @@ -20,14 +20,6 @@ SET xmloption = content; SET client_min_messages = warning; SET row_security = off; --- --- Ensure target database exists (idempotent for init scripts) --- -\connect postgres -SELECT 'CREATE DATABASE kngil' -WHERE NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'kngil')\gexec -\connect kngil - -- -- TOC entry 6 (class 2615 OID 16413) -- Name: kngil; Type: SCHEMA; Schema: -; Owner: postgres