SpeedMIS v7 소개

SpeedMIS는 코딩 없이 업무 시스템(MIS)을 구축하는 노코드 웹 빌더입니다. 2005년부터 21년간 발전해온 기업용 플랫폼으로, 메뉴와 필드를 정의하면 목록·조회·수정·삭제 화면이 자동 생성됩니다.

v7 기술 스택

구분기술
백엔드PHP 8.3 + Slim 4
프론트엔드React 18 + Vite
데이터베이스MariaDB 10.11 / MySQL 8
스타일Tailwind CSS + CSS 변수 디자인 시스템
코드 에디터Monaco Editor (VS Code 엔진)

핵심 특징

  • 노코드 CRUD — 메뉴+필드 등록만으로 완전한 프로그램 자동 생성
  • 서버 훅 시스템 — 16개 이벤트 훅으로 비즈니스 로직 확장 (PHP)
  • 실시간 목록편집 — 그리드에서 직접 값 수정, Enter로 셀 이동
  • 인쇄양식 빌더 — HTML 템플릿 + 자식 프로그램 루프
  • 마스터-디테일 — 부모-자식 프로그램 탭으로 연결
  • 모바일 자동 대응 — 토스/Samsung One UI 감성 카드형 UI
  • 다크모드 — 라이트/다크 원클릭 전환
  • 공통 로직_common.php + _common_udef.php

화면 구성 가이드

SpeedMIS v7의 데스크톱 화면은 크게 탑바(Topbar), 사이드바(Sidebar), 메인 영역(Main)으로 구성됩니다. 아래 다이어그램에서 각 영역의 이름과 역할을 확인하세요.

전체 레이아웃

거래처관리
매출현황
재고관리
홍길동
영업관리
거래처관리
매출현황
계약관리
재고관리
재고현황
입출고관리
영업관리 > 거래처관리
엑셀
신규
필터:
총 127건
No거래처명대표자전화번호지역
1이재용02-1234-5678서울
2구광모02-9876-5432서울
3정의선031-123-4567경기
4박정호031-789-0123경기
5최정우054-220-0114경북
1
2
3
기본정보
매출내역
계약
1
2
3
4
거래처명
삼성전자
사업자번호
124-81-00998
대표자
이재용
전화
02-1234-5678
주소
서울 서초구 강남대로
업태
제조업
등록일
2024-01-15
삭제
저장

영역별 명칭과 역할

영역이름설명
■ 탑바Topbar로고, 열린 프로그램 탭, 사용자 이름, 설정(⚙) 버튼. 높이 48px 고정
■ 사이드바Sidebar1레벨(카테고리)·2레벨(프로그램) 메뉴 트리. 펼침 240px, 접힘 56px
■ 툴바Toolbar필터 드롭다운, 검색, 엑셀/신규 버튼, 전체 건수 표시
■ 그리드DataGrid목록 테이블. 컬럼 헤더 클릭 정렬, 행 클릭으로 패널 열기, 인라인 편집 지원
■ 패널Panel (DataForm)선택한 행의 상세·수정 폼. 탭으로 자식 프로그램 전환, 4단계 크기 조절(1~4)
■ 페이저Pagination페이지 네비게이션. 기본 50건/페이지, pageSize 파라미터로 조절

탑바(Topbar) 상세

거래처관리 ✕
매출현황 ✕
재고관리 ✕
홍길동
요소설명
로고사이트 타이틀 (.env의 SITE_TITLE). 클릭 시 홈
프로그램 탭열린 프로그램 목록. 클릭으로 전환, ✕로 닫기. 다중 탭 지원
사용자 이름로그인 사용자. 클릭 시 설정 패널 열림
설정 ⚙다크/라이트 모드, PC/모바일 전환, 개발자 모드, 보기 설정 통합 패널

사이드바(Sidebar) 상세

▾ 영업관리
거래처관리
매출현황
계약관리
▸ 재고관리
▸ 인사관리
  • 1레벨 (카테고리) — 펼침/접힘 가능한 메뉴 그룹. ▾/▸ 토글
  • 2레벨 (프로그램) — 실제 프로그램 링크. 클릭 시 탭으로 열림
  • 접힘 모드 — 아이콘만 표시 (56px), 마우스 오버 시 메뉴명 팝업
  • 즐겨찾기 ★ — 자주 쓰는 메뉴를 상단에 고정

그리드(DataGrid) 상세

상태:
전체 ▾
지역:
전체 ▾
총 127건
엑셀
신규
No 거래처명 ▲ 대표자 전화번호 상태
1이재용02-1234-5678거래중
2구광모02-9876-5432거래중
3정의선031-123-4567보류
◀ 이전
1
2
3
4
5
다음 ▶
요소설명
필터(Toolbar)필드 타입이 selectbox인 컬럼은 자동으로 필터 드롭다운 생성
컬럼 헤더클릭으로 정렬 토글 (▲오름/▼내림). Shift+클릭으로 복수 정렬
데이터 행클릭 시 우측 패널에 상세 표시. 선택 행은 파란 배경으로 강조
뱃지selectbox 값은 자동으로 컬러 뱃지로 렌더링
페이저하단 페이지 네비게이션. 기본 50건, pageSize로 변경 가능

패널(Panel / DataForm) 상세

기본정보
매출내역
계약
1
2
3
4
거래처명
삼성전자
사업자번호
124-81-00998
대표자
이재용
전화
02-1234-5678
업태/종목
제조업 / 전자부품
주소
서울 서초구 강남대로 129
메모
주요 거래처. VIP 관리 대상
삭제
저장
요소설명
탭 헤더첫 번째 탭 = 현재 프로그램 폼, 나머지 = 자식 프로그램 (마스터-디테일)
크기 조절 (1~4)1=최소(폼만), 2=기본(그리드+패널), 3=넓은 패널, 4=전체화면
폼 필드왼쪽 라벨 + 오른쪽 값. 수정 모드에서 입력 가능한 필드가 활성화
섹션 타이틀필드 제목에 콜론(:)이 포함되면 전체 너비 구분선으로 표시
⋯ 더보기인쇄, 이력보기, 공유 등 확장 메뉴
저장/삭제 버튼저장 시 스피너 표시 → 성공/실패 토스트 알림

모바일 화면 구성

모바일 모드에서는 토스(Toss) 앱 감성의 카드형 UI로 자동 전환됩니다.

거래처관리
🔍 검색어 입력...
삼성전자
거래중
대표: 이재용
전화: 02-1234-5678
LG전자
거래중
대표: 구광모
전화: 02-9876-5432
📋
거래처
📊
매출
📦
재고
설정
영역설명
상단바햄버거(☰) 메뉴, 프로그램명, 설정. 사이드바 드로어로 열림
검색상단 검색바. 필터 드롭다운은 아래 시트로 표시
카드 리스트데스크톱 그리드 대신 카드형 레이아웃. 주요 필드만 표시
하단 탭바2레벨 메뉴를 하단 탭으로 표시. 3레벨은 목록으로 전환

설정(Settings) 패널

탑바 우측 ⚙ 버튼을 클릭하면 통합 설정 패널이 열립니다.

설정
테마
☀ 라이트 🌙 다크
모드
🖥 PC 📱 모바일
개발자 모드
목록 보기
기본 카드
로그아웃
💡
설정은 서버에 저장되어 다른 기기에서 로그인해도 동일한 환경이 유지됩니다. 개발자 모드는 관리자만 활성화할 수 있습니다.

설치 가이드

SpeedMIS v7 은 3개의 DBMS 배포판으로 제공됩니다. 각각 GitHub Public 레포에서 받아 install.php 한 번 호출로 설치 끝.

1) 배포판 선택

DBMSGitHub 레포무료 데모PHP 확장
MariaDB / MySQLspeedmis_v7_mariadbv7ma.speedmis.compdo_mysql
Microsoft SQL Serverspeedmis_v7_mssqlv7ms.speedmis.compdo_sqlsrv
PostgreSQLspeedmis_v7_postgresqlv7po.speedmis.compdo_pgsql

2) 공통 요구사항

  • PHP 8.3 이상 (mbstring, json, openssl, fileinfo + 위 표의 PDO 확장)
  • DB 서버: MariaDB 10.4+ / MySQL 8.0+ / MSSQL 2019+ / PostgreSQL 14+ 중 택1
  • 웹서버: Nginx, Apache, IIS 어느 것이든 (PHP-FPM 또는 mod_php)

3) 설치 (3단계)

STEP 1. 원하는 배포판을 다운로드/클론 (예: MariaDB 판)

git clone https://github.com/speedmis/speedmis_v7_mariadb.git
# 또는 GitHub 의 "Code → Download ZIP" 으로 받아 압축 해제

STEP 2. 웹서버 root 를 해당 폴더로 지정. nginx 예시:

server {
    listen 80;
    server_name yourdomain.com;
    root /var/www/speedmis_v7_mariadb;
    index index.php install.php;
    location / { try_files $uri $uri/ /index.php?$args; }
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

STEP 3. 브라우저에서 https://yourdomain.com/install.php 호출 → DB 호스트/포트/사용자/비밀번호 입력 → 연결 & 자동 설치 버튼 한 번.

  • DB 가 없으면 자동 생성 (MariaDB 는 utf8mb4_unicode_ci, MSSQL 은 default collation, PG 는 UTF8)
  • 스키마 + 마스킹된 샘플데이터(116 테이블) 가 자동 적재
  • 접속 호스트에서 SITE_ID 자동 생성 (예: yourdomain.comyourdom)
  • .env 자동 작성 — JWT 키 자동 생성

4) 로그인

설치 직후 접속:

아이디: admin     (또는 gadmin)
비밀번호: 4321    ← .env 의 MASTER_PASSWORD (만능비번)
⚠ 운영 전환 시 필수.envMASTER_PASSWORD=4321비우거나 강력한 값으로 변경하고, 보안을 위해 install.php삭제하세요.

5) 자주 묻는 질문

Q. 동봉 DB 의 비밀번호는 뭔가요?
A. Public 레포이므로 모든 비밀번호는 마스킹(passwd_decrypt = NULL)되어 있습니다. MASTER_PASSWORD=4321 만능비번으로 처음 로그인 후, mis_users 메뉴에서 본인 비밀번호 설정하세요.

Q. 외부 IP / 도메인 없는 환경에서도 설치 가능?
A. 가능. install.php 가 GitHub Public 레포에서 DB 번들을 file_get_contents 로 직접 받습니다. 인터넷만 되면 OK. 인터넷도 안 되는 폐쇄망이면 GitHub raw URL 의 db/*.sql.gz 을 미리 받아 db/ 안에 두면 로컬 우선 사용.

Q. composer install / npm build 가 필요한가요?
A. 일반적으로는 불필요. 모든 배포판은 vendor/public/build/ 가 이미 포함되어 있습니다 (다운로드 즉시 실행 가능). 개발자가 직접 코드를 수정할 때만 필요합니다.

Q. 다른 DBMS 로 옮길 수 있나요?
A. 가능. 같은 코드 베이스에서 .envDB_DRIVER 값만 다릅니다 (mysql / sqlsrv / pgsql). 데이터 마이그레이션은 별도 작업이지만, 메뉴/필드 정의(mis_menus / mis_menu_fields) 는 호환됩니다.

빠른 시작 — 5분 만에 프로그램 만들기

1단계: 메뉴 등록

웹소스관리(프로그램 314)에서 새 메뉴를 등록합니다.

항목설명
메뉴명고객관리좌측 메뉴에 표시
기준테이블my_customersDB 테이블명
메뉴타입01일반 프로그램

2단계: 필드 정의

웹소스관리 디테일(프로그램 267)에서 필드를 추가합니다.

번호항목명필드명표시폭
1idxidx0 (숨김)
2고객명name20
3전화번호phone15
4이메일email25
5메모remark30

3단계: 즉시 사용

메뉴를 클릭하면 목록, 등록, 수정, 삭제 화면이 자동으로 생성됩니다. 별도 배포가 필요 없습니다.

💡
DB 테이블이 없으면 먼저 CREATE TABLE로 생성해야 합니다. SpeedMIS는 테이블을 자동 생성하지 않습니다.

서버 훅 시스템

훅은 programs/{real_pid}.php 파일에 정의합니다. DataHandler가 각 이벤트 시점에 자동으로 호출합니다.

훅 실행 순서

common_훅()  →  user_훅()  →  개별_훅()

_common.php         → SpeedMIS 기본 공통
_common_udef.php → 고객사 전용 공통
{real_pid}.php      → 프로그램별 개별

전체 훅 목록

시점파라미터
pageLoad()프로그램 로드 1회없음
before_query($menu, $fields, $params)쿼리 빌드 전메뉴, 필드, 파라미터
list_query(&$select, &$count)목록 쿼리 생성 후SELECT/COUNT SQL (수정 가능)
list_json_init()목록 로딩 전없음
list_json_load(&$data)목록 각 행행 데이터 (수정 가능)
view_query(&$viewSql)단건 조회 쿼리 후SELECT SQL (수정 가능)
view_load(&$row)단건 조회 후레코드 (수정 가능)
save_updateReady(&$saveList)저장 전 검증POST 원본 데이터
save_updateBefore(&$updateList)UPDATE 직전DB 컬럼 데이터
save_updateQueryBefore(&$sql, &$bindings)UPDATE SQL 직전SQL + 바인딩
save_updateAfter($idx, &$afterScript)UPDATE 완료 후레코드 idx
save_writeBefore(&$updateList)INSERT 직전DB 컬럼 데이터
save_writeQueryBefore(&$sql, &$bindings)INSERT SQL 직전SQL + 바인딩
save_writeAfter($newIdx, &$afterScript)INSERT 완료 후새 레코드 idx
save_deleteBefore($idx, &$cancelDelete)삭제 전 검증idx, 취소 플래그
save_deleteAfter($idx, &$afterScript)삭제 완료 후idx
addLogic_treat(&$result)커스텀 API결과 배열

예제: 저장 전 확인

function save_updateReady(&$saveList) {
    global $isListEdit, $idx;

    if ((int)$saveList['amount'] > 1000000) {
        $GLOBALS['_client_confirm'] = '100만원 이상입니다. 저장할까요?';
    }
}

예제: 저장 후 연관 데이터 처리

function save_updateAfter($idx, &$afterScript) {
    global $__pdo, $misSessionUserId;

    execSql("INSERT INTO change_log (ref_idx, user_id, wdate)
             VALUES ({$idx}, '{$misSessionUserId}', NOW())");

    $GLOBALS['_client_toast'] = '저장 완료 + 로그 기록됨';
}

전역변수 레퍼런스

훅 함수 내에서 global 선언 후 사용할 수 있는 변수입니다.

세션/요청 정보

변수타입설명
$actionFlagstring현재 액션 (list/view/modify/write/delete)
$gubunint메뉴 idx
$idxint레코드 idx
$real_pidstring프로그램 ID (speedmis000036 형태)
$menu_namestring프로그램명
$parent_idxint마스터-디테일 상위 idx
$misSessionUserIdstring로그인 사용자 ID
$misSessionIsAdminstring관리자 여부 ('Y' 또는 '')
$misSessionPositionCodestring직급 코드
$isFirstLoadbool프로그램 최초 로딩 여부
$isListEditbool목록편집(인라인) 저장 여부
$listEditFieldarray목록편집 시 변경된 필드명 배열
$customActionstring사용자 정의 버튼 action 값
$__pdoPDODB 인스턴스

클라이언트 제어 ($GLOBALS[...])

효과
_client_alertstringalert() 팝업
_client_toaststring토스트 알림
_client_confirmstring저장 전 확인 (Yes→저장, No→취소)
_client_openTabarray새 탭 열기 {gubun, label, idx, openFull}
_client_redirectarray현재 탭 교체 {gubun, label}
_client_cssstringCSS 주입
_client_buttonTextarray버튼 텍스트 변경 {write, reset}
_client_buttonsarray리스트 헤더 커스텀 버튼 [{label, action}]
_client_formButtonsarray우측 폼 상단 버튼 [{label, realPid|gubun, idx, openFull}] — view_load에서 설정
_client_fieldsarray필드 속성 오버라이드
_client_viewPrefstring조회설정 (list/auto)
_onlyListbool리스트전용 모드

클라이언트 제어

버튼 텍스트 변경

function pageLoad() {
    $GLOBALS['_client_buttonText'] = [
        'write' => '접수하기',    // +등록 → 접수하기
        'reset' => '전체보기',    // 초기화 → 전체보기
    ];
}

사용자 정의 버튼

function pageLoad() {
    $GLOBALS['_client_buttons'] = [
        ['label' => '일괄적용', 'action' => 'apply'],
        ['label' => '마감처리', 'action' => 'close'],
    ];
}

function list_json_init() {
    global $customAction;
    if ($customAction === 'apply') {
        execSql("UPDATE my_table SET status='적용' WHERE status='대기'");
        $GLOBALS['_client_toast'] = '일괄 적용 완료!';
    }
}

CSS 주입

function pageLoad() {
    $GLOBALS['_client_css'] = '
        #mis-btn-write { display: none; }
        #mis-btn-reset { background: #3182F6; color: #fff; }
    ';
}

// 주요 CSS ID:
// #mis-program, #mis-header, #mis-title
// #mis-btn-write, #mis-btn-reset, #mis-btn-custom-0

그리드 셀 버튼 (openTabBtn)

function list_json_load(&$data) {
    $data['__html']['gname'] = $data['gname']
        . openTabBtn('상세', [
            'real_pid' => 'speedmis000314',
            'idx'      => $data['idx'],
        ]);
}

// 버튼 스타일: btn-open(파랑), btn-danger(빨강), btn-success(초록), btn-sm(작게)

우측 폼 상단 버튼 (_client_formButtons)

view_load 훅에서 설정. 리스트의 data-opentab과 동일한 전역 위임을 사용해 클릭 시 탭이 열립니다.

function view_load(&$row) {
    $rp = $row['real_pid'] ?? '';
    if ($rp) {
        $GLOBALS['_client_formButtons'] = [
            ['label' => '연결', 'realPid' => $rp, 'openFull' => true],
            ['label' => '히스토리', 'gubun' => 42, 'idx' => $row['idx']],
        ];
    }
}

필드 속성 커스텀

$GLOBALS['_client_fields']로 런타임에 필드 속성을 동적 변경합니다.

function pageLoad() {
    global $misSessionUserId;

    $GLOBALS['_client_fields'] = [
        'remark' => [
            'col_title'     => '비고',           // 항목명 변경
            'col_width'     => 30,              // 표시폭
            'max_length'    => 200,             // 입력글수
            'required'      => 'Y',            // 필수입력
            'items'         => '옵션1,옵션2',    // 셀렉트박스
            'grid_list_edit'=> 'Y',            // 목록편집
        ],
    ];
}

변경 가능한 속성

설명
col_title항목명 (그리드 헤더 + 폼 레이블)
col_width그리드 표시폭
max_length입력 최대 글수
default_value등록 시 기본값
required필수입력 (Y/N)
schema_type데이터타입
items셀렉트박스 목록
form_group폼 탭그룹명
grid_list_edit목록편집 (Y/N)
grid_ctl_name객체명 (check/dropdownlist 등)
grid_is_handle필터 (s=셀렉트, t=텍스트)

목록 HTML 렌더링 (__html)

그리드 셀에 HTML을 렌더링합니다. 원본 데이터는 보존됩니다.

function list_json_load(&$data) {
    // 링크
    $data['__html']['site'] = '<a href="'.$data['url'].'" target="_blank">'.$data['site'].'</a>';

    // 상태 뱃지
    $st = $data['status'];
    $color = $st === '완료' ? '#22c55e' : '#ef4444';
    $data['__html']['status'] = '<span class="badge" style="background:'.$color.';color:#fff">'.$st.'</span>';

    // 조건부 버튼
    $data['__html']['action'] = openTabBtn('처리', [
        'real_pid' => 'speedmis000100',
        'idx' => $data['idx'],
        'class' => 'btn-danger btn-sm',
    ]);
}

인쇄양식 템플릿

설정

  1. mis_menus.is_use_print = 1 체크
  2. programs/{real_pid}_print.html 파일 작성

문법

문법설명
{{alias_name}}메인 레코드 필드값
{{#each childAlias}}...{{/each}}자식 프로그램 루프
{{@index}}루프 인덱스 (1부터)
{{@total}}자식 전체 건수

예제

<h2>{{gname}} 그룹 상세</h2>
<table>
  <tr><th>그룹명</th><td>{{gname}}</td></tr>
  <tr><th>메모</th><td>{{remark}}</td></tr>
</table>

<h3>멤버 목록</h3>
<table>
  {{#each zmembeohwagin}}
  <tr>
    <td>{{@index}}</td>
    <td>{{user_id}}</td>
  </tr>
  {{/each}}
</table>

SQL 실행 헬퍼

// 단일 쿼리 (바인딩)
$result = execSql("INSERT INTO t (name) VALUES (?)", ['홍길동']);

// 멀티 쿼리 (세미콜론 구분)
$result = execSql("
    UPDATE a SET status='done' WHERE idx=1;
    INSERT INTO b (ref) VALUES (1)
");

// 결과
// $result['resultCode']    — 'success' 또는 'fail'
// $result['resultMessage'] — 에러 메시지
// $result['lastInsertId']  — 마지막 INSERT idx
// $result['rowCount']      — 영향받은 행 수
💡
개발자모드에서 execSql 실행 내역은 SQL 모달에 자동 표시됩니다.

공통 / 사용자 로직

파일 구조

파일접두어업데이트 시용도
_common.phpcommon_덮어씀SpeedMIS 기본
_common_udef.phpuser_보존고객사 전용
{real_pid}.php(없음)보존프로그램별

실행 순서

common_save_updateAfter()  →  user_save_updateAfter()  →  save_updateAfter()

일반 헬퍼 함수

// _common_udef.php
function getUserName($userId) {
    global $__pdo;
    return $__pdo->query("SELECT user_name FROM mis_users
        WHERE user_id='{$userId}'")->fetchColumn() ?: '';
}

// 개별 프로그램에서 바로 사용
function save_updateAfter($idx, &$afterScript) {
    $name = getUserName($GLOBALS['misSessionUserId']);
    $GLOBALS['_client_toast'] = "{$name}님이 수정";
}

API 레퍼런스

모든 API는 /api.php?act= 방식입니다.

actMethod설명
listGET목록 조회
viewGET단건 조회
savePOST등록/수정 (CSRF 필수)
deletePOST삭제 (CSRF 필수)
menuGET메뉴 트리
menuItemGET메뉴 단건
loginPOST로그인
logoutPOST로그아웃
meGET현재 사용자
treatPOST커스텀 API 훅
briefInsertPOST간편추가
fileUploadPOST파일 업로드
fileDownGET파일 다운로드

메뉴 관리

프로그램 314번(웹소스관리)에서 메뉴를 등록/수정합니다.

주요 필드

필드설명예시
menu_name메뉴명고객관리
table_name기준 테이블my_customers
menu_type메뉴 타입01(일반), 13(iframe)
auth_code권한 코드01(전체), 02(멤버만)
g01프로그램 모드simple_list, only_one_list, gantt
g07읽기전용Y
base_filter기본 WHERE 조건table_m.use_yn='1'
brief_insert_sql간편추가 SQL(name, wdater) values ('', '@misSessionUserId')

필드 관리

프로그램 267번(웹소스관리 디테일)에서 필드를 관리합니다.

col_width 규칙

의미
양수 (예: 20)그리드에 표시 (폭 = 값 × 8px)
0그리드 숨김, 폼에만 표시
-1숨김 PK (첫 번째 visible 필드가 링크)
-2완전 숨김

grid_ctl_name 객체명

설명
text텍스트 입력
check체크박스
dropdownlist드롭다운 (prime_key 연동)
attach파일 첨부
child자식 프로그램 탭
datepicker날짜 선택

max_length (입력글수) 규칙

값 형식일반 필드 동작첨부/이미지 (attach/image)
양수 (예: 50)등록·수정 모두 입력 가능 (최대 N자)업로드 크기 제한 N MB
음수 (예: -14)등록 시만 abs(N)자 입력 가능 / 수정 시 읽기전용해당 없음
0등록·수정 모두 읽기전용해당 없음
빈값기본 200자, 등록·수정 모두 가능기본 제한 없음
끝이 ! (예: 500!)행 단위 readonly(rowReadOnly) 무시 — 그 필드만 편집 허용multi-attach (여러 파일 첨부 가능)
💡
! suffix 의 의미 — 일반 필드에선 "잎 노드만 삭제" 같은 row-level readonly 규칙(g04)이 걸려도 해당 필드만은 부분수정 가능. 첨부에선 멀티파일 허용. 두 의미가 다르므로 컨트롤 종류에 따라 자동 분기됩니다.

예시: 트리 데이터의 부모 노드도 비고만 수정 허용

parts_cate_a 트리에서 자식 있는 부모는 read_only_cond 로 readonly 처리되어 일반 수정/삭제 차단. 그러나 비고(remark) 필드의 max_length 를 500! 로 두면:

  • 조회 화면에 [부분수정] 버튼 + 🔒 일부 필드만 수정 가능 안내
  • 수정 모드 진입 시 비고만 편집, 다른 필드는 readonly
  • 저장 → 비고만 UPDATE, 그 외 변경은 BE 가 무시

권한 관리

권한 코드 (auth_code)

코드의미
01 또는 빈값전체 공개
02멤버만 접근 (mis_menu_auth 기반)

그룹 권한

mis_groups + mis_group_members 테이블로 관리. 프로그램별 new_gidx 필드로 그룹 지정.

프로그램 모드

g01모드동작
(빈값)일반목록 + 등록/수정/삭제
simple_list심플 목록목록 + 수정만 (등록/삭제 없음)
only_one_list단건리스트 없이 최근 1건만 수정
gantt간트차트그리드 대신 간트차트 전체화면

서버로직으로 모드 제어

function pageLoad() {
    $GLOBALS['_onlyList'] = true;        // 리스트전용
    $GLOBALS['_client_viewPref'] = 'list'; // 내용 자동열림 비활성
}

설정

topbar 우측 ⚙ 아이콘에서 접근합니다.

설정저장 위치설명
PC / 모바일localStorage레이아웃 전환
라이트 / 다크localStorage + DB테마
실사용 / 개발자localStorageSQL 디버그 도구
조회설정localStorage목록만 / 자동열림 / 개별

그리드 사용법

기본 조작

  • 행 클릭 → 우측에 상세 내용 표시
  • Shift+클릭 → 새 탭으로 열기
  • Ctrl+클릭 → 새 창으로 열기
  • 조회모드 👁 → 클릭 시 조회
  • 수정모드 ✏ → 클릭 시 수정폼 열기

패널 크기

우측 4 3 2 1 버튼으로 조절:

  • 4 — 전체화면 (그리드 숨김)
  • 3 — 75%
  • 2 — 50%
  • 1 — 25%

폼 사용법

  • 조회 모드 → 데이터 확인만 가능
  • 수정 버튼 → 수정 모드 전환
  • 저장 버튼 → 데이터 저장
  • 삭제 버튼 → 확인 후 삭제
  • 그룹 탭 → 필드가 많으면 탭으로 분리
  • 인쇄 🖨 → 인쇄양식 있으면 표시

필터 & 검색

  • 툴바 필터 — 상단의 셀렉트박스/텍스트 입력으로 필터링
  • 컬럼 헤더 필터 — 그리드 헤더 아래 행에서 직접 입력
  • 최근순 — 최근 등록순 정렬 토글
  • 초기화 — 모든 필터 + 정렬 초기화 (마스터-디테일의 자식 프로그램에도 지원)
  • 컬럼 정렬 — 헤더 클릭 (Ctrl+클릭: 다중 정렬)
  • 간편추가 — 1/2/3/5/10/50줄 옵션 (mis_menus.brief_insert_sql 설정 필요)

부분합 / 총합 (aggregate)

URL 파라미터 &aggregate= 로 목록에 부분합·총합 행을 자동 삽입합니다.

모드 4종

모드동작
aggregate=auto실데이터 + 부분합/총합 (숫자 필드=합계, 그 외=건수)
aggregate=sum실데이터 + 부분합/총합 (숫자 필드만 합계, 나머지는 공란)
aggregate=simple.auto실데이터 숨김, 집계 행만 표시. 행 클릭 → 실데이터 팝업
aggregate=simple.sum실데이터 숨김, 숫자 합계만. 행 클릭 → 실데이터 팝업

규칙

  • orderby의 alias 조합을 그룹 키로 사용 → 그룹이 바뀔 때 소계 행 삽입
  • recently=Y(최근순)이면 aggregate 무시
  • 합계: schema_typenumber로 시작하는 필드. 굵은 글씨로 표시
  • 건수: 그 외 필드. 필드별로 값이 null/빈 문자열인 행은 제외하고 카운트. 이탤릭체로 표시
  • 우측 패널 자동 열기 비활성
  • psize/pageSize 미지정 시 기본 1000
  • 엑셀 다운로드에도 집계 행 포함 (No 칸=소계/합계). 숫자 필드는 Excel의 숫자 셀(bold), 건수 셀은 이탤릭으로 출력

예시

/?gubun=36&orderby=dept,position&aggregate=auto
/?gubun=36&orderby=dept&aggregate=simple.sum

dept, position으로 정렬되어 있으면 두 값 조합이 바뀔 때마다 소계 행이 삽입되고, 마지막에 총합 행이 추가됩니다.

목록편집

grid_list_edit=Y인 필드는 그리드에서 직접 편집 가능합니다.

조작법

동작결과
더블클릭 또는 선택 후 클릭편집 모드 진입
Enter저장 → 아래 셀 이동
Shift+Enter저장 → 위 셀 이동
Escape취소
체크박스 클릭확인 후 즉시 토글
저장 완료 시 해당 행이 초록색으로 잠깐 깜빡입니다.

모바일

설정 → 뷰 모드 → 📱 모바일로 전환하거나, URL에 ?mode=mobile 추가.

  • 햄버거 메뉴 → 1레벨 메뉴
  • 하단 탭바 → 2레벨 메뉴
  • 카드형 리스트 → 터치 최적화
  • 카드 클릭 → 풀스크린 폼
  • 자동 감지 — 모바일 기기는 자동 모바일 모드

단축키

동작
Ctrl+C선택 영역 복사 (탭 구분)
Ctrl+A전체 선택
Enter목록편집: 저장 + 아래로
Shift+Enter목록편집: 저장 + 위로
Escape목록편집: 취소

자주 묻는 질문 (FAQ)

운영 중 자주 마주치는 상황과 해결책 모음.

Q. 행을 삭제하려는데 "읽기전용 행"이라고 막혀요

해당 메뉴의 read_only_cond (g04) 가 1 을 반환한 것입니다. 트리 구조에서 부모 노드는 자식이 모두 삭제된 후에만 삭제 가능 — 잎(leaf)부터 차례로 지우세요.

일부 필드만은 그래도 수정하고 싶다면 그 필드의 max_length 끝에 ! 를 붙이면 됩니다. (예: 500!) 해당 필드만 편집 가능한 [부분수정] 버튼이 활성화됩니다.

Q. 6068 통합인쇄대기열에 추가했는데 안 보여요

인쇄 대기열 추가 = `print_request_time = NOW()` UPDATE. 뷰가 다른 DB 의 테이블을 참조하면 동기화 안 됨. v7 마이그레이션 후엔 v7 테이블을 가리키도록 뷰 재생성 필요.

관리자(gadmin)면 ⚙ 설정 → 🗑 캐시 비우기 후 재시도.

Q. RealCid Unknown column 에러

v6 PascalCase 컬럼명이 mis_menu_fields.items SQL 등 저장된 SQL 조각에 남아있는 경우. 코어가 자동으로 v7 snake_case 로 치환하지만, 일부 자유서식(items 의 select)은 못 잡을 수 있습니다. → 해당 SQL 을 직접 v7 컬럼명으로 수정하거나, 코어가 처리할 수 있는 표준 식으로 바꾸세요.

Q. 기본 폼에서 작성자명만 보이고 사번이 안 보여요

v7 의 Qn 패턴(table_XXXQnYYY) 은 편집모드에서 selectbox 하나로 통합 표시하므로 value 필드를 숨깁니다. 조회(view) 모드에서는 함께 표시되도록 자동 처리됩니다 (이름 + 사번 동시 노출).

Q. virtual_field 행이 리스트에 안 나타나요

virtual_field 도 일반 필드와 동일한 col_width 룰을 따릅니다 — col_width=0 은 리스트 숨김 / 폼만 표시. 리스트에 보이려면 col_width 를 양수로 설정하세요. 그러면 grid_ctl_name 에 따라 컨트롤이 렌더되고, 사용자로직 (list_json_load / view_load) 에서 값을 채워 넣고, 저장 시엔 BE 의 filterData 가 db_table≠'table_m' 항목을 자동 skip 합니다.

Q. 저장 직후 "real_pid='631'" 같은 이상한 쿼리

숨김 PK (col_width=-1) 메뉴에서 발생하던 이슈. 코어가 저장 응답에 visible-key 값(예: 'speedmis000631')을 돌려주도록 수정되어 해결. 캐시 비우기 후 재시도하세요.

Q. 캐시 비우기 버튼이 어디 있나요?

topbar ⚙ 설정 → 🗑 캐시 비우기. gadmin 또는 개발자 그룹(group_idx=83) 멤버만 노출.

Q. 운영 모드 / 개발자 모드 전환은?

설정 패널의 운영 모드 토글. 개발자 모드 켜면 SQL 디버그 출력, 라벨 tooltip 에 컬럼/alias 정보 표시됨. gadmin 또는 개발자 그룹 만 토글 가능. 비멤버도 콘솔에서 misDevOn() / misDevOff() 로 임시 사용 가능.

필드 설계 팁

1. 그룹화 — col_title 의 콤마 형식

같은 그룹 필드의 col_title 을 "그룹명,컬럼명" 형식으로 두면 자동으로 묶임:

분류,제조사모델분류
,제조사모델분류코드          ← 콤마 + 빈 그룹명 = 직전 그룹 계속
,변경할 제조사모델분류
,부품분류명
부품분류코드               ← 콤마 없음 = 그룹 종료
  • 리스트(그리드): 2단 헤더 — 그룹 띠가 위에 colspan 으로 표시
  • 폼(뷰): 곡선 테두리(fieldset+legend) 박스로 묶임. 그룹 시작/끝 자동 줄바꿈

2. : 콜론으로 섹션 제목

col_title 에 : 가 포함되면 폼에서 데이터 영역 없는 섹션 헤더로 렌더 (전체 폭 가로띠).

3. max_length 활용

의미
양수일반 (입력 한도)
음수등록만 가능, 수정 불가 (예: 자동 발번 필드)
0등록·수정 모두 readonly (조회 전용)
끝이 !row readonly 무시 + 부분수정 허용 (첨부는 multi 의미)

4. virtual_field 사용

DB 컬럼 없이 폼/리스트에 가상 필드 노출:

db_table = 'virtual_field'
db_field = 'price'           -- 가상 컬럼명 (alias_name 의 Qn 뒷부분)
grid_ctl_name = 'text'       -- 컨트롤
max_length = '13'            -- 입력 한도
col_title = '실판매가'

BE 는 '' AS alias 로 빈 값 반환. 사용자로직에서 list_json_load / view_load 훅으로 값 채워 넣고, save_writeBefore 등에서 $_POST 로 입력값 받아 별도 처리. 저장 시엔 코어가 자동 무시.

5. read_only_cond (g04) — 행 단위 잠금

mis_menus.read_only_cond 에 SQL 식을 넣으면 1=잠금 / 0=정상. 행 단위 readonly 처리되어 수정·삭제 차단.

-- 트리 잎 노드만 삭제 허용 (parts_cate_a)
EXISTS(SELECT 1 FROM parts_cate_a c WHERE c.upidx = table_m.idx AND c.useflag = '1')

-- gadmin 외엔 종료된 데이터 잠금
case when datediff(IFNULL(table_m.종료일,'2100-01-01'), NOW()) < 0
     and '$misSessionUserId' <> 'gadmin' then 1 else 0 end

6. ip / hit / wdate 자동 컬럼

테이블에 다음 컬럼이 있으면 코어가 자동 처리 (사용자로직 불필요):

컬럼동작
wdater / wdateINSERT 시 작성자 / 작성일시
lastupdater / lastupdateUPDATE 시 수정자 / 수정일시
ip 또는 ip_addressINSERT 시 접속자 IP (프록시 헤더 우선)
hit조회 시 +1, 같은 브라우저 세션에서 같은 레코드는 1회만 카운트

7. col_width 음수 활용

리스트
양수 (10)표시 (10*8px)표시
0숨김표시
-1숨김 (PK 일 경우 visible 필드가 링크)숨김
-2완전 숨김숨김

성능 / 캐시 팁

캐시 계층

레벨저장무효화 시점
스키마 캐시APCu / 파일fields, menusmis_menus / mis_menu_fields 변경 시 자동
데이터 캐시APCu / 파일{real_pid}_{user}_{md5(filter+orderby+page)}해당 real_pid 저장/삭제 시 자동
클라이언트TanStack QueryqueryKeyinvalidate 호출 시

캐시 비우기

topbar ⚙ 설정 → 🗑 캐시 비우기 (gadmin / 개발자 그룹 멤버).

또는 그리드 패널 더보기 (...) 메뉴 → 캐시 비우기 (gadmin 만).

같은 테이블을 쓰는 형제 메뉴 자동 무효화

예: 6062 (parts_cate_a) 에서 저장 → 6038 (같은 테이블 쓰는 다른 메뉴)도 자동 invalidate.

뷰(view) 를 통해 간접 참조하는 경우도 INFORMATION_SCHEMA.VIEW_TABLE_USAGE 로 추적해 함께 비웁니다.

SSR initialData

첫 페이지 로드 시 PHP 가 첫 데이터를 window.__INITIAL_DATA__ 에 주입 → 별도 API 호출 없이 즉시 표시.

대용량 리스트 — psize 999999

URL 파라미터 psize=999999 또는 pageSize=999999 로 페이징을 숨길 수 있지만 데이터가 많으면 브라우저 메모리 부담. 5만건 이하 권장.

aggregate 모드 활용

대용량 조회를 즉시 집계행만 보고 싶을 때 aggregate=simple.auto / simple.sum 사용. 실데이터는 집계 행 클릭 시 팝업.

v6 → v7 마이그레이션 컨벤션

이름 규칙

  • 테이블: PascalCasemis_ 접두어 + snake_case (예: MisMenuList → mis_menus)
  • 컬럼: 동일 (RealPid → real_pid)
  • 예외: g5_*, mis_parts_*, v(v)_mis_parts_* 외부 테이블/뷰의 컬럼은 변경 금지 (useflag/HIT 만 rename 가능)

이름은 그대로, snake_case 변환만

v6 의 의미있는 컬럼명은 그대로 유지. PascalCase → snake_case 변환만 적용. 이름 자체를 교체하지 마세요 (예: lastupdate → updated_at 같은 변경 금지).

v7 코어가 자동 변환하는 영역

  • 저장된 SQL 조각의 컬럼명 (read_only_cond, base_filter, items, group_compute, prime_key)
  • 식별자 alias 변환 (table_RealPidtable_real_pid collapsed 매칭)
  • FROM/JOIN 절의 v6 테이블명 → v7 테이블명

훅 함수 이름

v6v7
list_query()list_query(&$selectQuery, &$countQuery)
save_updateAfter()save_updateAfter($idx, &$afterScript)
save_writeAfter()save_writeAfter($newIdx, &$afterScript)
list_json_load() — global $datalist_json_load(&$data) — 파라미터 참조

전역 변수 케이스

v7 은 $misSessionUserId 같은 camelCase 표준이지만, v6 호환 위해 $MisSession_UserID 도 같은 값으로 자동 alias.

alias_name_old 매핑

v6 로직 이식 시 변수명이 다른 경우 mis_menu_fields.alias_name_old 컬럼으로 v7 alias_name 을 찾아 치환할 수 있습니다.

프로젝트 구조

clude_speedmis_v7/
├── index.php          # 프론트 컨트롤러
├── api.php            # JSON API (?act= 방식)
├── .env               # 환경변수
├── config/
│   └── constants.php  # 상수 + execSql + openTabBtn
├── core/src/
│   ├── DataHandler.php    # CRUD 핵심 + 훅 시스템
│   ├── QueryBuilder.php   # allFilter → SQL
│   ├── MenuRouter.php     # 메뉴 라우터
│   ├── AuthMiddleware.php # JWT 인증
│   ├── PrintRenderer.php  # 인쇄양식 렌더러
│   └── MisCache.php       # APCu/파일 캐시
├── programs/
│   ├── _common.php              # SpeedMIS 공통
│   ├── _common_udef.php  # 고객사 공통
│   ├── speedmis000036.php       # 개별 프로그램
│   └── speedmis000036_print.html # 인쇄양식
├── src/               # React 소스
│   ├── components/
│   │   ├── Layout.jsx
│   │   ├── MainContent.jsx
│   │   ├── DataGrid.jsx
│   │   ├── DataForm.jsx
│   │   ├── GanttChart.jsx
│   │   └── mobile/
│   └── api.js
└── public/build/      # 빌드 결과