615 lines
33 KiB
Markdown
615 lines
33 KiB
Markdown
# (주)삼안 ERP 및 그룹웨어 시스템 (Satis) 초상세 로우레벨 분석 명세서
|
|
|
|
본 문서는 (주)삼안의 **ERP 및 그룹웨어 시스템 (`saman-statis` / `Satis`)**의 실제 소스 코드를 직접 분석한
|
|
**초상세 로우레벨(Ultra-Detailed Low-Level) 구현 명세서**입니다.
|
|
`OracleClass.php` 전체 메소드 명세, 전자결재 알고리즘, 문서번호 채번 방식, 인코딩 파이프라인, 결재 서명 렌더링,
|
|
시스템 글로벌 변수, DB 데이터 모델, 보안 핸들링 등을 실제 소스 코드 기반으로 완전히 명세합니다.
|
|
|
|
---
|
|
|
|
## 1. 모듈별 컨트롤러 & 뷰 맵핑 딕셔너리 (Module Controller-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` | 인쇄 출력 처리 |
|
|
|
|
---
|
|
|
|
## 2. 핵심 클래스 전체 메소드 명세 (OracleClass.php — 1,392라인)
|
|
|
|
`Satis/sys/util/OracleClass.php`는 시스템 전체 Oracle DB 연동의 핵심 클래스입니다.
|
|
실제 소스 분석 기반의 **전체 메소드 분류표**입니다.
|
|
|
|
### 2.1 DB 커넥션 및 기본 쿼리 메소드
|
|
|
|
| 메소드명 | 설명 |
|
|
| :--- | :--- |
|
|
| `OracleClass($smarty)` | 생성자. OCI 커넥션 생성. `oci_connect('satis', 'SATIS11707808', '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- 에러 코드를 사용자 친화적 한국어 메시지로 변환합니다.
|
|
|
|
```php
|
|
$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`로 구성되어 있어 양방향 변환이 필수입니다.
|
|
|
|
```mermaid
|
|
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` | 공백은 ` `로 대체 |
|
|
| `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 로그 기록 메소드
|
|
|
|
```php
|
|
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 추가, 김병철)
|
|
|
|
```php
|
|
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)
|
|
|
|
```mermaid
|
|
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`)
|
|
|
|
```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`)
|
|
|
|
```php
|
|
// 형식: "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`)
|
|
|
|
```php
|
|
// 형식: "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`)
|
|
|
|
결재 진행 시 현재 결재 위치를 다음 결재자로 이동시키는 핵심 알고리즘입니다.
|
|
|
|
```php
|
|
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`)
|
|
|
|
결재 문서 화면에서 결재자 서명 이미지(인장) 또는 텍스트를 표시하는 렌더링 로직입니다.
|
|
|
|
```php
|
|
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"`로 로드되는
|
|
**시스템 기본 정보 세션 초기화 파일**입니다.
|
|
|
|
```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 토큰 생성 알고리즘
|
|
```php
|
|
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 토큰 검증 흐름
|
|
```mermaid
|
|
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.php` 및 `function.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.php` → `DefaultInformation.php` 순서로 초기화가 완료되어야 합니다. 순서가 바뀌면 `$_SESSION['satis_user_id']`가 null이 되어 개발자 권한 판별이 실패합니다.
|