Files
saman/문서/architecture_low.md
T
2026-07-20 13:55:46 +09:00

38 KiB

(주)삼안 ERP 시스템 (Satis) 초상세 로우레벨 분석 명세서

본 문서는 (주)삼안의 **ERP 시스템 (Satis)**의 실제 소스 코드를 직접 분석한
초상세 로우레벨(Ultra-Detailed Low-Level) 구현 명세서입니다.
OracleClass.php 전체 메소드 명세, 비즈니스 로직(Model / Satis/sys/model/*) 20개 모듈 구조, 전자결재 알고리즘,
문서번호 채번 방식, 인코딩 파이프라인, 결재 서명 렌더링, 시스템 글로벌 변수, DB 데이터 모델, 보안 핸들링 등을 명세합니다.


1. 모듈별 컨트롤러, 모델 & 뷰 맵핑 딕셔너리 (Controller - Model - View Map)

1.1 회계 관리 모듈 (Account) — 30개 컨트롤러

컨트롤러 (Satis/sys/controller/Account/) 대응 View (Smarty/templates/Satis/Account/) 주요 기능
AccountSlipTemp_Controller.php AccountSlipTemp.tpl 임시전표 작성/조회/수정/결재 상신
AccountSlipConfirmed_Controller.php AccountSlipConfirmed.tpl 확정전표 조회 및 결재 상태 확인
AccountSlipAutoManager_Controller.php AccountSlipAutoManager.tpl 더존 iCube ERP 자동전표 전송
AccountBalanceStatement_Controller.php AccountBalanceStatement.tpl 합계잔액시산표 생성
AccountFinancialStatement_Controller.php AccountFinancialStatement.tpl 손익계산서(P/L) / 대차대조표(B/S)
AccountGisungCalculation_Controller.php AccountGisungCalculation.tpl 프로젝트 기성 청구금액 산출
AccountJointOfficeBudgetManager_Controller.php AccountJointOfficeBudget.tpl 공동도급(JV) 예산/지분율 원가 관리
AccountFixAsset_Controller.php AccountFixAsset.tpl 고정자산 감가상각비/대장 관리
AccountCorporateTax_Controller.php AccountCorporateTax.tpl 법인세 계산 및 세무 조정
AccountWithHoldingTax_Controller.php AccountWithHoldingTax.tpl 원천세 신고 / 원천징수 영수증
AccountVAT_Controller.php AccountVAT.tpl 부가가치세 신고 데이터 집계
AccountCostCalculation_Controller.php AccountCostCalculation.tpl 원가 계산 및 분석
AccountCostManagement_Controller.php AccountCostManagement.tpl 원가 관리 현황
AccountFundPlan_Controller.php AccountFundPlan.tpl 자금 계획 관리
AccountDepositsManager_Controller.php AccountDepositsManager.tpl 예금/차입금 관리
AccountLoanManager_Controller.php AccountLoanManager.tpl 대출 관리
AccountSecurities_Controller.php AccountSecurities.tpl 유가증권 관리
AccountEntertainmentExpenses_Controller.php AccountEntertainmentExpenses.tpl 접대비 관리
AccountCarManager_Controller.php AccountCarManager.tpl 차량 운행/유지비 관리
AccountDailyWorkTask_Controller.php AccountDailyWorkTask.tpl 일일 업무 실적 처리
AccountSummary_Controller.php AccountSummary.tpl 회계 요약 현황
AccountSummaryReport_Controller.php AccountSummaryReport.tpl 회계 종합 보고서
AccountDeadlineManager_Controller.php AccountDeadlineManager.tpl 결산 기한 관리
AccountMonthSupplementary_Controller.php AccountMonthSupplementary.tpl 월별 추가 분개 관리
AccountBillCheckManager_Controller.php AccountBillCheckManager.tpl 어음/수표 관리
AccountFinaCode_Controller.php AccountFinaCode.tpl 재무 코드 관리
AccountFundCode_Controller.php AccountFundCode.tpl 자금 코드 관리
AccountAssetCode_Controller.php AccountAssetCode.tpl 자산 코드 관리
FinalClosing_Controller.php FinalClosing.tpl 결산 확정 처리
AccountGisungCalculation_Controller.php AccountGisungCalculation.tpl 기성 계산 처리

1.2 인사/급여 관리 모듈 (Person) — 23개 컨트롤러

컨트롤러 (Satis/sys/controller/Person/) 대응 View (Smarty/templates/Satis/Person/) 주요 기능
PersonInfoManager_Controller.php PersonInfoManager.tpl 인사기록 카드, 신상정보, 이력 관리
PersonAppointment_Controller.php PersonAppointment.tpl 발령 처리 (입사/퇴사/이동/승진/전보)
PersonInsaLicense_Controller.php PersonInsaLicense.tpl 엔지니어링 기술자 자격증 관리
PersonCode_Controller.php PersonCode.tpl 인사 코드 마스터 관리
PersonTime_Controller.php PersonTime.tpl 근태 관리 (연차/야근/출퇴근)
PersonPayBase_Controller.php PersonPayBase.tpl 급여 기준 정보 설정
PersonPayBasic_Controller.php PersonPayBasic.tpl 기본급/수당 항목 설정
PersonPaymentCalculation_Controller.php PersonPaymentCalculation.tpl 월 급여/상여 계산 엔진
PersonPaymentReport_Controller.php PersonPaymentReport.tpl 급여명세서 / 부서별 지급 총괄
PersonSlipPaymentManager_Controller.php PersonSlipPaymentManager.tpl 급여 전표 연동 처리
PersonRetirementPay_Controller.php PersonRetirementPay.tpl 퇴직금 계산 및 충당금 정산
PersonYearEndTax_Controller.php PersonYearEndTax.tpl 연말정산 (yeta_data 연동)
PersonInsaReport_Controller.php PersonInsaReport.tpl 인사 현황 보고서
PersonInsaStatusReport_Controller.php PersonInsaStatusReport.tpl 인사 상태 보고서
PersonOrganiz_Controller.php PersonOrganiz.tpl 조직도 관리
Duzon_link_controller.php Duzon_link.tpl 더존 인사/급여 ERP 데이터 싱크
auth_pay_controller.php auth_pay.tpl 급여 지급 권한 처리
emp_check_controller.php emp_check.tpl 재직 여부 확인

1.3 프로젝트/현장 공정 모듈 (Project) — 15개 컨트롤러

컨트롤러 (Satis/sys/controller/Project/) 대응 View (Smarty/templates/Satis/Project/) 주요 기능
Project_Controller.php Project.tpl 프로젝트 마스터 등록/관리
ProjectOrderContract_Controller.php ProjectOrderContract.tpl 수주 계약, 변경 계약, 공동도급 지분율
ProjectOrderGisung_Controller.php ProjectOrderGisung.tpl 기성 청구 / 수금 / 미수금
ProjectMenHour_Controller.php ProjectMenHour.tpl M/M 공수 집계 및 원가 배부
ProjectCostStatus_Controller.php ProjectCostStatus.tpl 투입 원가(인건비/직접비/경비) 현황
ProjectCostTotal_Controller.php ProjectCostTotal.tpl 원가 합계 현황
ProjectPrePayment_Controller.php ProjectPrePayment.tpl 선급금 신청/수금/정산
ProjectStatus_Controller.php ProjectStatus.tpl 프로젝트 진행 현황
ProjectBaseCode_Controller.php ProjectBaseCode.tpl 프로젝트 기본 코드 관리
ProjectPartner_Controller.php ProjectPartner.tpl 협력업체 관리
DeptAssessment_Controller.php DeptAssessment.tpl 부서 평가 관리
TargetManagement_Controller.php TargetManagement.tpl 수주 목표 관리
PrinterCopy_Controller.php PrinterCopy.tpl 인쇄 출력 처리

1.4 비즈니스 로직 (Model 계층: Satis/sys/model/*) 20개 모듈 명세

Model 계층은 MVC 아키텍처 중 비즈니스 데이터 처리 및 DB 저장 프로시저(SP) 파라미터 구성, 결과 데이터 가공을 담당합니다. 모든 Logic 클래스는 생성자에서 $this->oracle = new OracleClass($smarty)를 초기화하여 데이터베이스에 접근합니다.

A. 모듈별 대표 Logic 클래스 목록

모듈 디렉토리 (Satis/sys/model/) 포함 Logic 파일 수 주요 Logic 클래스명 (*_logic.php) 핵심 역할 및 기능
Account 41개 AccountSlipTemp_logic.php, AccountSlipConfirmed_logic.php, AccountGisungCalculation_logic.php, FinalClosing_logic.php 임시/확정 전표 처리, 기성 청구금액 계산, 원가 배부, 결산 처리
Person 36개 PersonInfoManager_logic.php, PersonPaymentCalculation_logic.php, PersonYearEndTax_logic.php, PersonAppointment_logic.php 인사기록, 급여/상여 산출 엔진, 연말정산(yeta_data), 인사 발령
Project 16개 Project_logic.php, ProjectOrderContract_logic.php, ProjectMenHour_logic.php, ProjectOrderGisung_logic.php 프로젝트 마스터, 수주/변경 계약, M/M 공수 투입 집계, 기성 수금
Document 1개 Document_logic.php 전자결재 서식 생성, 문서 양식 데이터 파싱
Sign 6개 SignLogic.php, SignDocumentLogic.php 전자서명 문서 생성, 결재선 저장, 결재 상태 검증 및 서명 렌더링
Sale 9개 SaleBid_logic.php, SaleContractManager_logic.php, SalePerformanceManager_logic.php 영업 입찰, 수주 계약, 수금 및 수주 실적 관리
Purchase 4개 PurchaseClaim_logic.php, PurchaseOrder_logic.php, PurchaseExactCalculation_logic.php 구매 신청, 발주 처리, 대금 정산
Travel 1개 Travel_logic.php 국내/해외 출장 신청 및 여비 정산 처리
Asset 3개 AssetInfo_logic.php, AssetPurchase_logic.php, AssetBasicCode_logic.php 고정자산 등록, 자산 구매, 자산 코드 관리
Book 1개 Book_logic.php 도서 목록 등록 및 대여/반납 처리
Association 1개 Association_logic.php 엔지니어링 협회 회원 관리 및 수수료 정산
Management 4개 CommonCode_logic.php, MenuAuth_logic.php, Notify_logic.php 시스템 공통 코드, 사용자 메뉴 권한, 알림 팝업
Supervisor 2개 Supervisor_logic.php 시스템 전체 공정코드, 부서코드, 권한 마스터 관리
Common 17개 sms_logic.php, PdfLogic.php, popup_logic.php, work_logic.php SMS 발송 연동, PDF 출력 변환, 공통 팝업 데이터 공급
Jqgrid 1개 Jqgrid_logic.php jqGrid 요청에 대응하는 JSON 데이터 바인딩 로직
Fileupload 8개 Fileupload_logic.php, AddCode01_upload_insa_image.php 첨부파일 업로드/다운로드, 인사 사진 업로드 및 삭제
Help 1개 Help_logic.php 시스템 사용 도움말 및 Q&A 로직
Report 1개 Report_logic.php 경영진 보고서 및 엑셀 보고서 데이터 가공
Sample 1개 Sample01_logic.php 표준 개발용 샘플 로직
TestLogic 1개 TestLogic.php 개발자 테스트 전용 로직

B. Model (Logic) 클래스의 공통 설계 구조 패턴

  1. 초기화 및 의존성 주입:

    class AccountSlipTempLogic {
        var $oracle;
        var $smarty;
    
        function AccountSlipTempLogic($smarty) {
            $this->smarty = $smarty;
            $this->oracle = new OracleClass($smarty); // DB 객체 인스턴스화
        }
    }
    
  2. Controller 파라미터 바인딩 및 프로시저 호출:

    • Controller의 $ActionMode 요청에 따라 Logic 클래스 내 동명의 메소드가 실행됩니다.
    • 입력 파라미터를 sanitize 및 인코딩 변환(HangleEncodeUTF8_EUCKR) 후 Oracle 저장 프로시저 호출 구문(BEGIN usp_...; END;)을 완성합니다.
    • LoadProcedure_new() 또는 executeSelect()를 호출하여 결과를 Smarty 뷰로 전달합니다.
  3. 파일 업로드 및 이미지 관리 로직 (Fileupload_logic.php & AddCode01_...):

    • 인사기록카드 사원 사진 업로드 시 AddCode01_upload_insa_image.php를 통해 이미지를 서브 디렉터리(erpphoto/)로 이송하고 파일명을 사번(MemberNo.jpg)으로 표준화합니다.
    • 파일 업로드 시 확장자 및 용량 제한 검증을 수행하고, Oracle DB에 첨부파일 메타데이터를 저장합니다.

2. 핵심 클래스 전체 메소드 명세 (OracleClass.php — 1,392라인)

Satis/sys/util/OracleClass.php는 시스템 전체 Oracle DB 연동의 핵심 클래스입니다.
실제 소스 분석 기반의 전체 메소드 분류표입니다.

2.1 DB 커넥션 및 기본 쿼리 메소드

메소드명 설명
OracleClass($smarty) 생성자. OCI 커넥션 생성. oci_connect('satis', '************', 'samandb')
db_close_oracle() 커넥션 명시적 해제. oci_close()
Query($i_azsql) SQL 파싱 및 실행 후 Statement 핸들 반환.
fetch_array($stmt) oci_fetch_array($stmt, OCI_BOTH) — 연관+인덱스 배열 동시 반환
ExcuteQuery($i_azsql) DML 단순 실행 (반환값 없음)
LoadSql($i_azsql) 레거시 ociparse/ociexecute/ocifetchinto API 기반 단일 컬럼 조회
LoadConfigData($i_azsql) 설정 데이터를 name=value 포맷으로 직접 echo 출력
__destruct() PHP 소멸자: Statement 자동 해제 + 커넥션 자동 close

2.2 Stored Procedure (저장 프로시저) 조회 메소드

메소드명 인자 반환 방식 특이사항
LoadProcedure($sql, $title, $short_array) SQL, Smarty 변수명 $query_data 배열 + Smarty assign 기본 프로시저 호출 메소드
LoadProcedure_old($sql, $title, $short_array) SQL, Smarty 변수명 $query_data 배열 $_short 축약형 컬럼 지원 (구버전)
LoadProcedure_new($sql, $title, $short_array, $return_type) SQL, 변수명, 반환 타입 array / json / jsonchart / print_r / Smarty 출력 타입 지정 가능
LoadProcedure_new2($sql, $title, $short_array, $return_type) SQL, 변수명, 반환 타입 단일 Row 전용 단건 조회용 최적화
LoadProcedure_empty($sql, $title, $load_type, $return_type) SQL, 변수명, 로드타입 $query_data 배열 w2ui 모드 시 recid 자동 부여
LoadProcedure_broken_cp949($sql, $title, $load_type, $return_type) SQL, 변수명, 로드타입 $query_data 배열 CP949 인코딩 깨짐 데이터 처리용
LoadProcedureAjax($sql) SQL print_r(urldecode(json_encode($data))) Ajax 요청 전용 JSON 직접 출력
LoadProcedureApi($sql, $output_type) SQL, 출력 타입 array / json / jsonchart API/외부 호출 전용
Get_ProcedureData($Param01~05) 최대 5개 파라미터 array usp_get_procedure_data 프로시저 공통 호출 래퍼
executeSelect($sql) SQL 구조화된 결과 객체 (success, rows, columns, error) 최신 트랜잭션 안전 조회 함수 (2025-07-25 추가)

2.3 DML 실행 메소드 (INSERT / UPDATE / DELETE)

메소드명 설명 에러 반환 방식
ProcedureExcuteQuery($sql) 프로시저 DML 실행, 에러 시 echo echo '-----'.$err['message']
ProcedureExcuteQuery_new($sql, $encode_type, $return_type) 인코딩 선택(utf8/euckr) 후 DML 실행 echo 출력
ProcedureExcuteQuery_boolean($sql) DML 실행 후 true/false 반환 boolean
ProcedureExcuteQuery_boolean2($sql) DML 실행 후 result/error 구조체 반환 배열 구조체
ProcedureExcuteQuery_ErrorCheck($sql, $memberId, $date, $returnYN, $encode_type, $output_msg) 프로시저 실행 후 Common_Confirm_Error 프로시저로 이중 에러 검증 echo 또는 return
ProcedureExcuteQuery_Return($sql, $ReturnContent) DML 후 RESULT_CODE / RESULT_MSG / ALL 선택 반환 문자열 또는 배열
ProcedureExcuteQuery_ExcReturn($sql, $ReturnContent) 최신 try-catch 기반 DML. Oracle 에러코드 한국어 메시지 매핑 JSON 또는 한국어 에러 문자열
executeDml($sql) 트랜잭션 안전 DML 실행 (2025-07-25 신규 추가) 구조화된 결과 객체
ProcedureExcuteQuery_ErrorPrint($sql, $encode_type, $return_type) 에러 발생 시 콘솔 출력 echo

2.4 Oracle 에러코드 → 한국어 메시지 매핑 테이블

ProcedureExcuteQuery_ExcReturn() 내부의 $ErrorKind_Array로 Oracle ORA- 에러 코드를 사용자 친화적 한국어 메시지로 변환합니다.

$ErrorKind_Array = [
    "-1"     => "존재하는 내용입니다. 확인 후 입력해 주세요.",         // ORA-00001: DUP_VAL_ON_INDEX
    "-1400"  => "빈값을 입력 할 수 없습니다.",                       // NOT NULL 위반
    "-1722"  => "숫자자리에 문자를 입력할 수 없습니다.",               // INVALID_NUMBER
    "-1407"  => "빈값을 입력 할 수 없습니다.",                       // NOT NULL 위반 (다른 컬럼)
    "-1438"  => "숫자값이 너무 커서 입력할 수 없습니다.",              // 숫자 정밀도 초과
    "-12899" => "글자가 너무 길어 입력할 수 없습니다.",               // VARCHAR2 길이 초과
    "-6550"  => "필수항목이 입력되지 않았습니다.",                    // PL/SQL 컴파일 에러
    "-6511"  => "(CURSOR_ALREADY_OPEN) : 커서가 이미 OPEN 상태",
    "-6502"  => "(VALUE_ERROR) : 수치 또는 값 오류",
    "-1476"  => "(ZERO_DIVIDE) : 0으로 나눌때",
    "-1017"  => "(LOGIN_DENIED) : 잘못된 사용자 이름이나 비밀번호로 로그인 시도",
    "-1422"  => "(TOO_MANY_ROWS) : SELECT INTO 결과가 두 건 이상",
];

2.5 문자 인코딩 변환 메소드 (CP949 ↔ UTF-8 파이프라인)

Oracle DB는 CP949 (EUC-KR) 인코딩을 사용하고, 웹 화면은 UTF-8로 구성되어 있어 양방향 변환이 필수입니다.

graph LR
    DB["Oracle DB (CP949/EUC-KR)"]
    PHP["PHP Application (UTF-8)"]
    Browser["Web Browser (UTF-8)"]

    DB -->|HangleEncode: ICONV cp949→UTF-8| PHP
    PHP -->|HangleEncodeUTF8_EUCKR: ICONV UTF-8→cp949| DB
    PHP -->|HangleEncodeAjax: cp949→UTF-8 Ajax 전용| Browser
메소드명 변환 방향 비고
HangleEncode($item) CP949 → UTF-8 공백은 &nbsp로 대체
HangleEncode_empty($item) CP949 → UTF-8 공백 대체 없음
HangleEncodeAjax($item) CP949 → UTF-8 Ajax 응답 전용
HangleEncodeUTF8_EUCKR($item) UTF-8 → CP949 SQL 삽입 시 한글 보호
bear3StrCut($str, $len, $tail) 멀티바이트 문자열 정규식 자르기 (preg_match)
resizeString($str, $size, $addStr) mb_strlen/mb_substr 기반 UTF-8 자르기

2.6 로그 기록 메소드

function OracleLogWrite($msg, $menu) {
    // 로그 파일 경로: Satis/sys/log/YYYY-MM-DD_{메소드명}.txt
    $cfile = "../../log/" . date("Y-m-d") . "_" . $menu . ".txt";

    // 기존 파일 내용 읽기 → 새 내용 추가 → 파일 쓰기
    if (file_exists($cfile)) {
        $fd = fopen($cfile, 'r');
        $con = fread($fd, filesize($cfile));
        fclose($fd);
    }
    $fp = fopen($cfile, 'w');
    $cond = $con . date("Y-m-d H:i:s") . "  " . $msg . "\n";
    fwrite($fp, $cond);
    fclose($fp);
}

로그 파일 명명 규칙: YYYY-MM-DD_{호출된메소드명}.txt
예: 2026-07-20_LoadProcedure.txt, 2026-07-20_ExcuteQuery_new.txt

2.7 최신 트랜잭션 안전 메소드 (2025-07-25 추가, 김병철)

function executeSelect($sql) {
    try {
        set_error_handler(array($this, 'custom_error_handler'));
        $stmt = oci_parse($this->connection, $this->HangleEncodeUTF8_EUCKR($sql));

        // Ref Cursor 포함 여부 자동 감지
        if (strpos($sql, ":entries") !== false) {
            $entries = oci_new_cursor($this->connection);
            oci_bind_by_name($stmt, ":entries", $entries, -1, OCI_B_CURSOR);
            oci_execute($stmt, OCI_DEFAULT);
            oci_execute($entries);
            $fetchResult = $this->fetchResults($entries, $result);
        } else {
            oci_execute($stmt, OCI_DEFAULT);
            $fetchResult = $this->fetchResults($stmt, $result);
        }
        $result["rows"] = $fetchResult[0];
        $result["columns"] = $fetchResult[1];

    } catch (Exception $e) {
        oci_rollback($this->connection);  // 에러 시 자동 롤백
        $this->makeErrorResult($result, $stmt, $e->getMessage());

        // Oracle 에러코드 20000 이상 = 사용자 정의 예외 → DB 에러로그 삽입
        if ($result["error"]["datas"]["oci_error_data"]["code"] >= 20000) {
            $this->insertSmErrorLog($_SESSION["satis_user_id"], $errorMessage);
        }
    }
    restore_error_handler();
    return $result;
}

3. 전자결재 엔진 알고리즘 (Satis/inc/approval_function.php — 1,224라인)

3.1 전자결재 상태 머신 (State Machine)

stateDiagram-v2
    [*] --> CREATE : 기안 문서 작성 (DOC_STATUS_CREATE)

    CREATE --> TEMP   : 임시저장 (TEMP-00001)
    CREATE --> APPROVE: 결재 상신 (PROCESS_APPROVE=1)
    TEMP   --> APPROVE: 상신 실행

    state APPROVE {
        [*] --> DEPT   : 부서내결재 (부서내 / PROCESS_CODE)
        DEPT  --> RECV : 부서접수 (RECEIVE)
        RECV  --> SANCTION: 결의부서내 (SANCTION_CODE)
        SANCTION --> FINISH_LOOP: 처리부서내 (SANCTION_CODE2)
    }

    APPROVE --> BACK     : 이전 결재자 반송 (PROCESS_BACK="BACK")
    APPROVE --> RETURN   : 부결/반송 (PROCESS_RETURN="RETURN")
    APPROVE --> FINISH   : 최종 승인 (PROCESS_FINISH="FINISH")
    APPROVE --> DECISION : 전결 (PROCESS_DECISION="FINISH-DECISION")

    BACK   --> APPROVE : 재결재
    RETURN --> CREATE  : 재기안
    FINISH   --> [*]
    DECISION --> [*]

3.2 전체 상태 상수 정의 (approval_var.php)

/* 결재 진행 상태 코드 */
$PROCESS_APPROVE    = 1;               // 결재진행중
$PROCESS_TEMPORARY  = "TEMP-";        // 임시저장 (형식: TEMP-00001 또는 TEMP-00001-T02211)
$PROCESS_ACCEPT     = "ACCEPT";       // 가결
$PROCESS_REJECTION  = "REJECT";       // 부결 (기능삭제됨)
$PROCESS_RETURN     = "RETURN";       // 반송 (재기안 가능)
$PROCESS_BACK       = "BACK";         // 반송 - 이전 결재자에게 재전달
$PROCESS_FINISH     = "FINISH";       // 최종 결재완료
$PROCESS_DECISION   = "FINISH-DECISION"; // 전결 처리
$PROCESS_RECEIVE    = "RECEIVE";      // 부서접수 대기
$PROCESS_CANCEL     = "CANCEL";       // 기안취소
$PROCESS_DELETE     = "DELETE";       // 기안 삭제

/* 문서 편집 상태 코드 */
$DOC_STATUS_CREATE  = "CREATE";   // 새 기안 문서 작성
$DOC_STATUS_EDIT    = "EDIT";     // 기안 문서 편집
$DOC_STATUS_VIEW    = "VIEWER";   // 기안 문서 보기 (편집불가)
$DOC_STATUS_APPROVE = "APPROVE";  // 결재함 문서 결재하기
$DOC_STATUS_ACCEPT  = "ACCEPT";   // 부서 문서 접수하기

/* 결재 라인 위치 코드 */
$PROCESS_CODE    = "부서내";           // 부서내 결재 진행 중
$TEMPORARY_CODE  = "부서내임시저장";   // 임시저장 코드
$SANCTION_CODE   = "결의부서내";       // 결의부서 결재 진행 중
$SANCTION_CODE2  = "처리부서내";       // 처리부서 결재 진행 중
$STEP_NO         = 9;                  // 최대 결재 단계 수

3.3 문서 번호 채번 알고리즘 (Serial Number Generation)

결재 상신 시 문서번호가 자동으로 채번되는 두 가지 방식이 있습니다.

A. 임시저장 문서번호 (TempSerialNo / TempSerialNo2)

// 형식: "TEMP-00001" 또는 "TEMP-00001-T02211"
function TempSerialNo() {
    // SanctionDoc_tbl에서 TEMP-로 시작하는 마지막 번호 조회
    $res04 = mysql_query("SELECT * FROM SanctionDoc_tbl WHERE DocSN LIKE 'TEMP-%' ORDER BY DocSN DESC LIMIT 0,1", $db);
    if (mysql_num_rows($res04) > 0) {
        $aa = mysql_result($res04, 0, "DocSN");
        $bb = split("TEMP-", $aa);
        return "TEMP-" . sprintf("%05d", ($bb[1] + 1)); // 5자리 zero-padding
    } else {
        return "TEMP-" . sprintf("%05d", 1); // 최초: TEMP-00001
    }
}

// TempSerialNo2: 사번 suffix 포함 형식 (TEMP-00001-T02211)
// split("-", "TEMP-00021-T02211") → $bb[1] = "00021"

B. 결재 상신 문서번호 (NewSerialNo / NewSerialNo2)

// 형식: "2026-00001" 또는 "2026-00001-T02211"
function NewSerialNo() {
    $NewKey = date('Y-'); // "2026-"
    $res04 = mysql_query("SELECT * FROM SanctionDoc_tbl WHERE DocSN LIKE '$NewKey%' ORDER BY DocSN DESC LIMIT 0,1", $db);
    if (mysql_num_rows($res04) > 0) {
        $aa = mysql_result($res04, 0, "DocSN");
        $bb = split($NewKey, $aa); // "2026-00005" → $bb[1] = "00005"
        return $NewKey . sprintf("%05d", ($bb[1] + 1)); // "2026-00006"
    } else {
        return $NewKey . sprintf("%05d", 1); // 최초: "2026-00001"
    }
}

3.4 결재선 순회 알고리즘 (NextSanctionState)

결재 진행 시 현재 결재 위치를 다음 결재자로 이동시키는 핵심 알고리즘입니다.

function NextSanctionState($Tmp_Sanction, $Tmp_SanctionState) {
    // $Tmp_Sanction = "A001:B002:RECEIVE:C003:D004:FINISH" (결재선 배열, ':' 구분)
    // $Tmp_SanctionState = "1:부서내:A001:2026-07-20" (현재 단계:코드:사번:날짜)
    
    $TmpState = split(":", $Tmp_SanctionState);
    $Start = $TmpState[0]; // 현재 결재 인덱스

    // RECEIVE 위치(index) 먼저 탐색
    for ($i = $Start; $i < count($TmpArr); $i++) {
        if ($TmpArr[$i] == $PROCESS_RECEIVE) {
            $RECEIVE_INDEX = $i; break;
        }
    }

    // 다음 결재자 상태 결정
    for ($i = $Start; $i < count($TmpArr); $i++) {
        switch ($TmpArr[$i]) {
            case $PROCESS_RECEIVE:  // 부서접수로 넘김
                $Res_SanctionState = ($i+1).":".$PROCESS_RECEIVE.":".$TmpState[2].":".date('Y-m-d');
                break;
            case $PROCESS_FINISH:   // 결재 완료
                $Res_SanctionState = ($i+1).":".$PROCESS_FINISH.":".$TmpState[2].":".date('Y-m-d');
                break;
            default:                // 다음 결재자에게 넘김
                // RECEIVE 이후면 처리부서내(SANCTION_CODE2), 이전이면 결의부서내(SANCTION_CODE)
                $SANCTION_CODE_ = ($i > $RECEIVE_INDEX) ? $SANCTION_CODE2 : $SANCTION_CODE;
                $Res_SanctionState = ($i+1).":".$SANCTION_CODE_.":".$TmpArr[$i].":".date('Y-m-d');
                break;
        }
        break; // 첫 번째 유효 결재자만 처리
    }
    return $Res_SanctionState;
}

3.5 결재 서명/인장 렌더링 알고리즘 (FindSanctionState / FindSanctionState2)

결재 문서 화면에서 결재자 서명 이미지(인장) 또는 텍스트를 표시하는 렌더링 로직입니다.

function FindSanctionState2($TmpSN, $sName, $sGroup) {
    // SanctionState_tbl에서 해당 결재자의 결재 이력 조회
    $azSQL = "SELECT * FROM SanctionState_tbl WHERE DocSN='$TmpSN' AND MemberNo LIKE '%$sName%'";
    
    $SanctionState = mysql_result($res06, 0, "SanctionState");
    $src_photo = "../../../erpphoto/$MemberNo.jpg"; // 결재자 서명 사진 경로

    // 대리결재 감지
    if ($sGroup == "결의부서내") {
        if (strpos(mysql_result($res06, 0, "MemberNo"), "대결") !== false) {
            $Member_Name .= "-대결"; // 대리결재 표시
        }
    }

    if ($SanctionState == "ACCEPT" || $SanctionState == "FINISH-DECISION") {
        if (file_exists($src_photo)) {
            // 서명 이미지 존재 시: <img src='erpphoto/A001.jpg' height=30>
            return "<img src='$src_photo' height=30><br>$Member_Name<br>($SanctionDate)";
        } else {
            // 서명 없음 시: 빨간색 텍스트("승인" 또는 "전결")
            $ok_str = ($SanctionState == "ACCEPT") ? "승인" : "전결";
            return "<font color=red>$ok_str</font><br><br>$Member_Name<br>($SanctionDate)";
        }
    } else {
        return "<br><br>$Member_Name<br>(미결)"; // 아직 결재 안 됨
    }
}

3.6 공통 코드 룩업 함수 목록 (approval_function.php)

전자결재 처리 중 MySQL intranet_tbl / member_tbl / SystemConfig_Tbl을 조회하는 헬퍼 함수 목록입니다.

함수명 조회 테이블 반환값
MemberNo2Ip($mCode) intranet_tbl 사번으로 IP 주소 반환 (끝자리 0/1이면 공백)
MemberNo2BossIP($Tmp_Sanction, $Step) intranet_tbl 결재선 특정 스텝의 결재자 IP
MemberNo2Name($mCode) member_tbl 사번 → 한글 성명
MemberNo2Rank($mCode) member_tbl, SystemConfig_Tbl 사번 → 직급명
MemberNo2GroupCode($mCode) member_tbl 사번 → 부서코드 (2자리 zero-pad)
MemberNo2GroupName($mCode) member_tbl, SystemConfig_Tbl 사번 → 부서명
MemberNo2SiteName($mCode) systemconfig_tbl 사번 → 현장명 (TeamLeader 기준)
DocCode2Name($DocCode) systemconfig_tbl 결재 서식 코드 → 서식명
ProjectCode2Name($mCode) project_tbl 프로젝트 코드 → 약칭
ProjectCode2Bridge($mCode, $no) project_cs_summary_tbl 교량 프로젝트 약칭
Code2Name($mCode, $dbSysKey, $Kind) SystemConfig_Tbl 일반 코드 → 명칭 (Kind=0) 또는 설명 (Kind=1)
Group2Manager($mCode) SystemConfig_Tbl 부서코드 → 부서장 사번 (Note 컬럼)
ProcessingState($TmpSN, $tmpState, $mCode) SanctionDoc_tbl 현재 결재 상태를 한국어 명칭으로 변환
FindSanctionDate($TmpSN, $TmpMemberNo) SanctionState_tbl 특정 결재자의 결재일자 반환
TempState($mCode) 임시저장 상태 문자열 생성 (0:부서내임시저장:A001:YYYY-MM-DD)

4. 시스템 글로벌 변수 및 초기화 파일 (DefaultInformation.php)

모든 컨트롤러에서 include "../../util/DefaultInformation.php"로 로드되는
시스템 기본 정보 세션 초기화 파일입니다.

/* 화면 요청 정보 */
$smarty->assign('auth_page_id',    $_REQUEST['target_id']);
$smarty->assign('auth_page_devYN2', 'Y');
$smarty->assign('satis_user_id',    $_SESSION['satis_user_id']);

/* 개발자 화이트리스트 (devYN='Y' 권한 부여) */
$devUserList = [
    "203155" => "한정규",
    "216070" => "정명준",
    "209171" => "신지호",
    "216011" => "장계석",
    "221064" => "김병철",
    "223158" => "김진선",
];

/* 로그인 사용자가 개발자 목록에 있으면 devYN='Y' 설정 */
if (array_key_exists($_SESSION['satis_user_id'], $devUserList)) {
    $smarty->assign('auth_page_devYN', 'Y');
}

/* 회사 기본 정보 */
$site_code = "SAMAN";           // 회사 영문 식별 코드
$site_name = "삼안";
$Corporation_name = "(주)삼안";
$company_registration_number = "211-81-25425"; // 사업자등록번호
$smarty->assign('site_code', $site_code);
$smarty->assign('site_name', $site_name);
$smarty->assign('Corporation_name', $Corporation_name);
$smarty->assign('company_registration_number', $company_registration_number);

5. 공통 헬퍼 함수 라이브러리 (Satis/inc/function.php — 1,093라인)

함수명 설명
str_cutting($str, $len) 멀티바이트 문자열 자르기 (한글 2바이트 처리, 생략 시 ... 추가)
substr1($str, $len) str_cutting의 Trailing 없는 단순 잘라내기 버전
ConfigName($category, $code) config_tbl 코드 → 명칭 반환
ConfigName2($category, $code) config_tbl 코드 → 설명(desc) 반환
Code2Name($mCode, $dbSysKey, $Kind) SystemConfig_Tbl 일반 코드 → 명칭 반환
change_code($pcode) 연도 코드 XX를 실제 연도(2자리)로 치환

6. 데이터베이스 스키마 상세 (MySQL 인트라넷 DB — hallaerp)

approval_function.php의 실제 SQL 쿼리 분석으로 도출한 인트라넷 MySQL DB 테이블 구조입니다.

테이블명 주요 컬럼 역할
member_tbl MemberNo, KorName, RankCode, GroupCode, WorkPosition 사원 기본 정보 (WorkPosition='9' = 퇴직자)
intranet_tbl MemberNo, UseIP 사원 네트워크 IP 정보
SanctionDoc_tbl DocSN, DocCode, PG_Date, DocTitle, DocStatus 전자결재 문서 마스터
SanctionState_tbl DocSN, MemberNo, SanctionOrder, SanctionState, SanctionDate 결재자별 결재 이력
SystemConfig_Tbl SysKey, Code, Name, Description, Note 시스템 공통 코드 사전 (부서코드, 직급코드, 결재서식 등)
project_tbl ProjectCode, ProjectNickname 프로젝트 약칭
project_cs_summary_tbl ProjectCode, no, Item 교량/구조물 프로젝트 세부 항목
config_tbl category, code, name, desc 화면별 코드 설정

7. 예외 처리 & 보안 핸들링 상세 (AuthClass.php — 591라인)

7.1 CSRF 토큰 생성 알고리즘

function Auth_TokenGenerater($assign_boolean = false) {
    // 62자 문자풀에서 10자리 난수 토큰 생성
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $randomString = '';
    for ($i = 0; $i < 10; $i++) {
        $randomString .= $characters[rand(0, 61)];
    }
    if ($assign_boolean) {
        $_SESSION['Token'] = $randomString;    // 서버 세션에 저장
        $this->smarty->assign('Token', $randomString); // 뷰로 전달 (히든 필드 렌더링)
    } else {
        return $randomString;
    }
}

7.2 토큰 검증 흐름

sequenceDiagram
    autonumber
    actor User
    participant Controller
    participant AuthClass
    participant Session

    User->>Controller: 폼 제출 (Token 파라미터 포함)
    Controller->>AuthClass: Auth_TokenChecker($Token, $page_move=true)
    AuthClass->>Session: $_SESSION['Token'] 조회
    alt 토큰 일치
        AuthClass->>Controller: Smarty assign('Token', ...) 후 정상 처리
    else 토큰 불일치
        AuthClass->>Session: session_destroy() 세션 파기
        AuthClass->>User: auth_move_page.tpl 출력 (로그인 페이지 이동)
        AuthClass->>Controller: exit() 강제 종료
    end

7.3 세션 변수 목록

세션 키 설명
$_SESSION['satis_user_id'] 로그인 사원번호 (예: "221064")
$_SESSION['satis_user_name'] 로그인 사원 성명
$_SESSION['satis_user_deptcode'] 사원 부서코드
$_SESSION['satis_user_deptname'] 사원 부서명
$_SESSION['Token'] CSRF Anti-Forgery Token (10자리 난수)
$_SESSION['Title'] 현재 화면 제목
$_SESSION['Auth'] 권한 레벨
$_SESSION['Type'] 사용자 유형

8. 핵심 소스 파일 사전 (Source File Dictionary — 전체)

파일 경로 용량 / 라인 핵심 역할
Satis/sys/util/OracleClass.php 46KB / 1,392라인 Oracle OCI 커넥션, Ref Cursor 조회/DML, 인코딩 변환
Satis/sys/util/AuthClass.php 18KB / 591라인 세션 토큰 생성/검증, CSRF 방어
Satis/sys/util/CommonCodeList.php 294KB 시스템 공통 코드 PHP 배열 인메모리 캐시
Satis/inc/approval_function.php 35KB / 1,224라인 전자결재 상태 처리, 결재선 순회, 서명 렌더링
Satis/inc/function.php 30KB / 1,093라인 문자열 처리, 코드룩업, 공통 헬퍼 함수
Satis/inc/function_intranet.php 33KB 인트라넷 전용 헬퍼 함수
Satis/inc/approval_var.php 1.6KB / 29라인 전자결재 상태 상수 정의
Satis/inc/dbcon.inc 0.4KB 인트라넷 MySQL 커넥션 (erp.samaneng.com/hallaerp)
Satis/inc/saman_icube_dbcon.inc 0.5KB 더존 iCube MS-SQL 커넥션 (192.168.13.50:5539/DZICUBE)
Satis/sys/util/DefaultInformation.php 1.3KB 시스템 글로벌 변수, 개발자 권한, 회사 정보 초기화
SmartyConfig.php 0.3KB Smarty 엔진 경로 설정
Satis/sys/util/PrintAgent.php 1KB 인쇄 에이전트 유틸리티
Satis/sys/util/ReportViewExcel.php 2.8KB Excel 보고서 출력 처리

9. M/M (Man-Month) 투입 공수 및 원가 배부 공식

엔지니어링 업계 특성상 프로젝트별 인건비 원가 배부는 투입 M/M 비율을 기준으로 계산됩니다.

\text{Project}_{i} \text{ 원가 배부액} = \text{직원 월 급여} \times \left( \frac{\text{Project}_{i} \text{ 투입 M/M}}{\displaystyle\sum_{j=1}^{n} \text{Project}_{j} \text{ 투입 M/M}} \right)

처리 흐름:

  1. PersonPaymentCalculation: 직원의 당월 급여 총액 및 4대보험 회사 부담금 계산
  2. ProjectMenHour: 해당 월 직원의 프로젝트별 투입 M/M 입력
  3. Oracle 저장 프로시저: 비율 계산 → 각 프로젝트 원가 테이블 자동 배부
  4. AccountGisungCalculation: 프로젝트 기성 청구 시 누적 원가 반영

10. 로우레벨 개발 시 주의사항

Caution

SQL Injection 위험: approval_function.phpfunction.php의 MySQL 함수들은 대부분 $mCode 값을 필터링 없이 SQL에 직접 삽입합니다. 수정 시 반드시 mysql_real_escape_string() 또는 PDO Prepared Statement로 교체해야 합니다.

Warning

레거시 드라이버: mysql_connect(), mssql_connect() 등 PHP 7.x에서 완전히 제거된 드라이버를 사용합니다. PHP 5.6 이하 환경에서만 실행 가능합니다.

Warning

하드코딩된 개발자 화이트리스트: DefaultInformation.php에 개발자 사번이 하드코딩되어 있습니다. 인사 이동 시 소스 직접 수정이 필요합니다.

Note

Oracle 트랜잭션: ProcedureExcuteQuery()OCI_COMMIT_ON_SUCCESS(자동 커밋) 모드로 동작합니다. 여러 DML을 하나의 트랜잭션으로 묶으려면 OCI_DEFAULT 모드의 executeDml() / executeSelect()를 사용해야 합니다.

Note

Session 의존성: 모든 컨트롤러 로직은 session_start()GetUserInfo.phpDefaultInformation.php 순서로 초기화가 완료되어야 합니다. 순서가 바뀌면 $_SESSION['satis_user_id']가 null이 되어 개발자 권한 판별이 실패합니다.