Compare commits
21
Commits
22625feade
..
Main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dd68e2c98 | ||
|
|
7622ed2793 | ||
|
|
a8f14e8c76 | ||
|
|
4bb2692ad0 | ||
|
|
7d184d2e97 | ||
|
|
a510f34f71 | ||
|
|
6790991471 | ||
|
|
aca94d175c | ||
|
|
fd49c71910 | ||
|
|
7477a803e9 | ||
|
|
0c052abfa7 | ||
|
|
62b25b045b | ||
|
|
0f32f8e7e7 | ||
|
|
754764edfa | ||
|
|
20dcb839dd | ||
|
|
bc5d68e465 | ||
|
|
54def57e99 | ||
|
|
6ac0964618 | ||
|
|
0bae5fa7c3 | ||
|
|
551a2447a4 | ||
|
|
d848de48ae |
No files matched your search
+1
-1
@@ -7,5 +7,5 @@ matching.db
|
||||
csv
|
||||
*.png
|
||||
*.xlsx
|
||||
*.txt
|
||||
gitea-api.txt
|
||||
data
|
||||
@@ -17,3 +17,9 @@ Thumbs.db
|
||||
|
||||
# Local scratch
|
||||
*.tmp
|
||||
|
||||
# SQLite runtime files / local recovery backups
|
||||
*.db-wal
|
||||
*.db-shm
|
||||
*.db.bak-*
|
||||
db-backups/
|
||||
+21
-6
@@ -2,18 +2,33 @@ FROM python:3.12-slim
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PORT=8090
|
||||
PORT=8091 \
|
||||
CHROME_PATH=/usr/bin/chromium
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
chromium \
|
||||
fonts-noto-cjk \
|
||||
nodejs \
|
||||
npm \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY app.py index.html detail-view.html detail-view-project.html ./
|
||||
RUN npm install --omit=dev playwright-core
|
||||
|
||||
# matching.db는 볼륨으로 마운트해서 데이터 보존
|
||||
VOLUME ["/app/data"]
|
||||
COPY mysql_preview_server.py \
|
||||
index.html \
|
||||
detail-view.html \
|
||||
detail-view-project.html \
|
||||
mysql-preview.html \
|
||||
people-unified.html \
|
||||
project-codes.html \
|
||||
./
|
||||
|
||||
EXPOSE 8090
|
||||
EXPOSE 8091 8092
|
||||
|
||||
CMD ["sh", "-c", "ln -sf /app/data/matching.db /app/matching.db 2>/dev/null || true; python3 app.py"]
|
||||
CMD ["python3", "mysql_preview_server.py"]
|
||||
@@ -0,0 +1,241 @@
|
||||
# 장헌산업 업무 데이터 조회 시스템
|
||||
|
||||
이 저장소는 장헌산업 내부 업무 데이터를 조회하고, ERP 정보와 매칭해서 웹 화면으로 보여주는 사내용 도구입니다.
|
||||
|
||||
주요 목적은 아래 2가지입니다.
|
||||
|
||||
- `8091` 페이지: 사람별/프로젝트별 근무 데이터 조회
|
||||
- `8092` 페이지: 시공 코드, 계약정보, 공사개요, 연계코드 조회
|
||||
|
||||
|
||||
## 구성 개요
|
||||
|
||||
이 프로젝트는 Python 기반의 간단한 웹 서버와 SQLite DB(`matching.db`)를 사용합니다.
|
||||
|
||||
- 웹 서버: `mysql_preview_server.py`
|
||||
- 공용 DB: `matching.db`
|
||||
- 메인 대시보드 화면: `index.html`
|
||||
- 사람별 근무 화면: `people-unified.html`
|
||||
- 시공 코드 조회 화면: `project-codes.html`
|
||||
|
||||
같은 저장소 안에서 여러 HTML 화면을 제공하지만, 데이터는 공통으로 `matching.db`를 사용합니다.
|
||||
|
||||
|
||||
## 페이지 설명
|
||||
|
||||
### `8091` 메인/근무 데이터 페이지
|
||||
|
||||
`8091` 포트는 근무 데이터와 집계 화면용입니다.
|
||||
|
||||
포함 내용:
|
||||
|
||||
- 프로젝트별 투입시간 집계
|
||||
- 사람별 근무내역 조회
|
||||
- 월별/기간별 프로젝트 투입 현황
|
||||
- 사업관리 작업일보 기반 추가 공수 데이터 조회
|
||||
|
||||
관련 화면:
|
||||
|
||||
- `index.html`: 메인 대시보드
|
||||
- `people-unified.html`: 사람별 / 프로젝트별 근무내역
|
||||
- `detail-view.html`, `detail-view-project.html`: 상세 보기
|
||||
|
||||
|
||||
### `8092` 시공 코드 조회 페이지
|
||||
|
||||
`8092` 포트는 ERP 시공 코드 전용 조회 화면입니다.
|
||||
|
||||
포함 내용:
|
||||
|
||||
- 시공코드 목록
|
||||
- 계약정보
|
||||
- 공사개요
|
||||
- 교량 매칭 정보
|
||||
- 공사시행계획서 기반 교량제원
|
||||
- 연계코드 표시
|
||||
|
||||
연계코드는 아래 형식으로 표시됩니다.
|
||||
|
||||
- `영업/설계 약칭(코드번호)`가 아니라
|
||||
- 현재는 `약칭(코드번호) · 약칭(코드번호)` 형식
|
||||
|
||||
예시:
|
||||
|
||||
- `새만금~전주간 고속도로 건설고사(제4공구) [8차](25-교영-09)`
|
||||
- `새만금~전주간 고속도로 건설고사(제4공구) [8차](25-설계-05)`
|
||||
|
||||
관련 화면:
|
||||
|
||||
- `project-codes.html`
|
||||
|
||||
|
||||
## 데이터 소스
|
||||
|
||||
이 시스템은 여러 데이터 소스를 함께 사용합니다.
|
||||
|
||||
### 1. SQLite 캐시 DB
|
||||
|
||||
- 파일: `matching.db`
|
||||
- 저장소에 포함되어 있음
|
||||
- 클론한 사람도 같은 시점의 데이터를 바로 볼 수 있음
|
||||
|
||||
주요 저장 내용:
|
||||
|
||||
- 직원 정보
|
||||
- `dailyproject` 근무 데이터
|
||||
- 프로젝트 약칭
|
||||
- ERP 시공 코드 캐시
|
||||
- ERP 계약정보 캐시
|
||||
- ERP 교량 개요 캐시
|
||||
- ERP 공사시행계획서 캐시
|
||||
- 영업/설계/시공 연계코드 캐시
|
||||
|
||||
|
||||
### 2. MySQL 원본 데이터
|
||||
|
||||
사내 MySQL에서 근무 데이터를 읽어와 SQLite로 적재합니다.
|
||||
|
||||
관련 환경값:
|
||||
|
||||
- `MYSQL_HOST`
|
||||
- `MYSQL_PORT`
|
||||
- `MYSQL_USER`
|
||||
- `MYSQL_PASSWORD`
|
||||
- `MYSQL_DB`
|
||||
|
||||
|
||||
### 3. ERP 데이터
|
||||
|
||||
ERP에서 아래 정보를 가져옵니다.
|
||||
|
||||
- 프로젝트 약칭
|
||||
- 시공 코드 목록
|
||||
- 계약정보
|
||||
- 공사관리 / 공사개요
|
||||
- 공사시행계획서
|
||||
- 영업/설계/시공 연계코드
|
||||
|
||||
관련 기본 URL:
|
||||
|
||||
- `http://erp.jangheon.co.kr/projt_mng`
|
||||
|
||||
|
||||
## DB 관련 중요 사항
|
||||
|
||||
이 저장소는 **코드만이 아니라 `matching.db`도 함께 관리**합니다.
|
||||
|
||||
이유:
|
||||
|
||||
- 클론한 사람이 바로 같은 데이터를 볼 수 있어야 함
|
||||
- 내부 페이지가 DB 캐시를 전제로 동작함
|
||||
- ERP/MySQL 접속 없이도 기본 조회가 가능해야 함
|
||||
|
||||
즉, 이 저장소에서는 `matching.db`도 실제 배포/공유 자산입니다.
|
||||
|
||||
다만 아래 파일은 운영 중 자동 생성될 수 있습니다.
|
||||
|
||||
- `matching.db-wal`
|
||||
- `matching.db-shm`
|
||||
- `matching.db.bak-*`
|
||||
|
||||
이 파일들은 보조 파일/백업 파일이며, 기본 공유 대상은 `matching.db`입니다.
|
||||
|
||||
|
||||
## 연계코드 저장 방식
|
||||
|
||||
시공코드 페이지의 연계코드는 DB에도 저장됩니다.
|
||||
|
||||
관련 테이블:
|
||||
|
||||
- `project_alias`
|
||||
- `erp_project_alias_cache`
|
||||
- `erp_linked_code_cache`
|
||||
|
||||
예를 들어 시공코드 하나에 대해 아래 정보를 저장합니다.
|
||||
|
||||
- 시공코드
|
||||
- 사업코드
|
||||
- 연관 영업코드
|
||||
- 연관 영업 약칭
|
||||
- 연관 설계코드
|
||||
- 연관 설계 약칭
|
||||
|
||||
그래서 나중에 아래와 같은 질문에 바로 답할 수 있습니다.
|
||||
|
||||
- “이 시공코드의 연관 영업코드는 뭐야?”
|
||||
- “이 설계코드 약칭이 뭐야?”
|
||||
|
||||
|
||||
## 실행 방법
|
||||
|
||||
### 로컬 실행
|
||||
|
||||
Python 서버 실행:
|
||||
|
||||
```bash
|
||||
python3 mysql_preview_server.py
|
||||
```
|
||||
|
||||
기본 포트:
|
||||
|
||||
- `8091`: 메인/근무 데이터
|
||||
- `8092`: 시공 코드 조회
|
||||
|
||||
|
||||
### Docker 실행
|
||||
|
||||
이미지/컨테이너는 `docker-compose.yml`과 `Dockerfile`로 실행할 수 있습니다.
|
||||
|
||||
관련 문서:
|
||||
|
||||
- `DEPLOY_DOCKER.md`
|
||||
|
||||
|
||||
## 주요 파일 설명
|
||||
|
||||
- `mysql_preview_server.py`: 현재 실제 기능 대부분이 들어있는 메인 서버
|
||||
- `app.py`: 이전/보조 서버 코드
|
||||
- `index.html`: 8091 메인 대시보드
|
||||
- `people-unified.html`: 사람별/프로젝트별 근무 조회
|
||||
- `project-codes.html`: 8092 시공 코드 전용 화면
|
||||
- `matching.db`: 공용 SQLite 데이터베이스
|
||||
- `docker-compose.yml`: Docker 배포 설정
|
||||
- `DEPLOY_DOCKER.md`: Docker 배포 방법
|
||||
|
||||
|
||||
## 사용 흐름
|
||||
|
||||
### 근무 데이터 흐름
|
||||
|
||||
1. MySQL 원본 데이터를 읽음
|
||||
2. SQLite `matching.db`에 적재
|
||||
3. `8091` 페이지에서 집계/상세 조회
|
||||
|
||||
|
||||
### 시공 코드 데이터 흐름
|
||||
|
||||
1. ERP에서 시공코드 목록 조회
|
||||
2. ERP 계약정보 / 공사개요 / 공사시행계획서 조회
|
||||
3. 영업/설계/시공 연계코드와 약칭 매칭
|
||||
4. SQLite `matching.db`에 캐시 저장
|
||||
5. `8092` 페이지에서 빠르게 조회
|
||||
|
||||
|
||||
## 운영 시 주의사항
|
||||
|
||||
- `matching.db`는 실제 운영 데이터가 들어 있으므로 함부로 초기화하면 안 됩니다.
|
||||
- ERP 재조회 버튼을 누르면 캐시가 갱신될 수 있습니다.
|
||||
- 운영 중에는 DB 파일이 변경될 수 있어 `matching.db-wal`, `matching.db-shm`가 생길 수 있습니다.
|
||||
- 다른 사람이 같은 결과를 보려면 저장소의 `matching.db`도 함께 최신 상태여야 합니다.
|
||||
|
||||
|
||||
## 권장 공유 방식
|
||||
|
||||
다른 사람이 이 저장소를 받아서 바로 확인하려면:
|
||||
|
||||
1. 저장소를 클론
|
||||
2. `matching.db`가 포함되어 있는지 확인
|
||||
3. 서버 실행
|
||||
4. `8091`, `8092` 페이지 접속
|
||||
|
||||
이렇게 하면 ERP를 다시 긁지 않아도 커밋 시점 기준의 데이터를 바로 볼 수 있습니다.
|
||||
@@ -21,12 +21,10 @@
|
||||
.legend{display:grid;grid-template-columns:1fr 1fr;gap:8px}
|
||||
.legend-item{display:flex;gap:8px;align-items:center;font-size:12px;background:#fff;border:1px solid #e1d7cb;border-radius:10px;padding:7px 9px}
|
||||
.dot{width:11px;height:11px;border-radius:999px}
|
||||
.shared-note{margin:0 0 12px;padding:10px 12px;border:1px solid #d7e5ee;border-radius:12px;background:#eef8fb;color:#355468;font-size:12px;line-height:1.55}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="shared-note">이 화면은 선택한 프로젝트의 년도별 인원 투입 비중과 월별 투입 상세를 보여줍니다.</div>
|
||||
<div class="card"><div class="row" style="justify-content:space-between"><h3 id="title" style="margin:0">선택 프로젝트 상세 (년도별)</h3><label>년도 <select id="yearSel"></select></label></div></div>
|
||||
<div class="card"><div id="donutWrap" class="donut-wrap"></div></div>
|
||||
<div class="card"><h3 style="margin:0 0 8px">인원 투입 상세 (년/월)</h3><div class="table-wrap"><table id="tblA"></table></div></div>
|
||||
|
||||
@@ -23,12 +23,10 @@
|
||||
.dot{width:11px;height:11px;border-radius:999px}
|
||||
.hours-summary{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:8px}
|
||||
.hours-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border:1px solid #e1d7cb;border-radius:999px;font-size:12px;background:#fff}
|
||||
.shared-note{margin:0 0 12px;padding:10px 12px;border:1px solid #d7e5ee;border-radius:12px;background:#eef8fb;color:#355468;font-size:12px;line-height:1.55}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="shared-note">이 화면은 선택한 인원의 년도별 프로젝트 투입 비중과 월별 투입 상세를 보여줍니다.</div>
|
||||
<div class="card"><div class="row" style="justify-content:space-between"><h3 id="detailTitle" style="margin:0">선택 인원 상세 (년도별)</h3><label>년도 <select id="yearSel"></select></label></div></div>
|
||||
<div class="card"><div id="donutWrap" class="donut-wrap"></div></div>
|
||||
<div class="card"><h3 style="margin:0 0 8px">프로젝트 투입 상세 (년/월/유형)</h3><div class="table-wrap"><table id="tblA"></table></div></div>
|
||||
|
||||
+31
-7
@@ -1,13 +1,12 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
manhour-dashboard:
|
||||
jh-people-8091:
|
||||
build: .
|
||||
container_name: manhour-dashboard
|
||||
container_name: jh-people-8091
|
||||
ports:
|
||||
- "8090:8090"
|
||||
- "8091:8091"
|
||||
environment:
|
||||
PORT: "8090"
|
||||
PORT: "8091"
|
||||
STARTUP_MAINTENANCE: "0"
|
||||
MYSQL_HOST: "${MYSQL_HOST:-172.16.42.111}"
|
||||
MYSQL_PORT: "${MYSQL_PORT:-3306}"
|
||||
MYSQL_USER: "${MYSQL_USER:-root}"
|
||||
@@ -20,5 +19,30 @@ services:
|
||||
INTRANET_LOGIN_ID: "${INTRANET_LOGIN_ID:-G25001}"
|
||||
INTRANET_LOGIN_PW: "${INTRANET_LOGIN_PW:-00000}"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- ./matching.db:/app/matching.db
|
||||
restart: unless-stopped
|
||||
|
||||
jh-project-8092:
|
||||
build: .
|
||||
container_name: jh-project-8092
|
||||
ports:
|
||||
- "8092:8092"
|
||||
environment:
|
||||
PORT: "8092"
|
||||
STARTUP_MAINTENANCE: "0"
|
||||
MYSQL_HOST: "${MYSQL_HOST:-172.16.42.111}"
|
||||
MYSQL_PORT: "${MYSQL_PORT:-3306}"
|
||||
MYSQL_USER: "${MYSQL_USER:-root}"
|
||||
MYSQL_PASSWORD: "${MYSQL_PASSWORD:-hanmacerp!}"
|
||||
MYSQL_DB: "${MYSQL_DB:-jangheon_manhour}"
|
||||
ERP_BASE_URL: "${ERP_BASE_URL:-http://erp.jangheon.co.kr/projt_mng}"
|
||||
ERP_LOGIN_ID: "${ERP_LOGIN_ID:-g25001}"
|
||||
ERP_LOGIN_PW: "${ERP_LOGIN_PW:-00000}"
|
||||
INTRANET_BASE_URL: "${INTRANET_BASE_URL:-http://erp.jangheon.co.kr/intranet/}"
|
||||
INTRANET_LOGIN_ID: "${INTRANET_LOGIN_ID:-G25001}"
|
||||
INTRANET_LOGIN_PW: "${INTRANET_LOGIN_PW:-00000}"
|
||||
volumes:
|
||||
- ./matching.db:/app/matching.db
|
||||
depends_on:
|
||||
- jh-people-8091
|
||||
restart: unless-stopped
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 145 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 151 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 305 KiB |
@@ -1,146 +0,0 @@
|
||||
{
|
||||
"left_core_tables": [
|
||||
"dallyproject_tbl",
|
||||
"member_tbl"
|
||||
],
|
||||
"right_tables": [
|
||||
"dallyproject_2005_2015_tbl",
|
||||
"dallyproject_2016_tbl",
|
||||
"dallyproject_2017_tbl",
|
||||
"dallyproject_2018_tbl",
|
||||
"dallyproject_2019_tbl",
|
||||
"dallyproject_2020_tbl",
|
||||
"project_tbl",
|
||||
"systemconfig_tbl",
|
||||
"userstate_tbl",
|
||||
"worker_tardy_tbl"
|
||||
],
|
||||
"relations": [
|
||||
{
|
||||
"from": "dallyproject_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2005_2015_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2016_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2017_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2018_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2019_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2020_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "worker_tardy_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_tbl",
|
||||
"from_col": "EntryPCode",
|
||||
"to": "project_tbl",
|
||||
"to_col": "ProjectCode",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2005_2015_tbl",
|
||||
"from_col": "EntryPCode",
|
||||
"to": "project_tbl",
|
||||
"to_col": "ProjectCode",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2016_tbl",
|
||||
"from_col": "EntryPCode",
|
||||
"to": "project_tbl",
|
||||
"to_col": "ProjectCode",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2017_tbl",
|
||||
"from_col": "EntryPCode",
|
||||
"to": "project_tbl",
|
||||
"to_col": "ProjectCode",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2018_tbl",
|
||||
"from_col": "EntryPCode",
|
||||
"to": "project_tbl",
|
||||
"to_col": "ProjectCode",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2019_tbl",
|
||||
"from_col": "EntryPCode",
|
||||
"to": "project_tbl",
|
||||
"to_col": "ProjectCode",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2020_tbl",
|
||||
"from_col": "EntryPCode",
|
||||
"to": "project_tbl",
|
||||
"to_col": "ProjectCode",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "member_tbl",
|
||||
"from_col": "RankCode",
|
||||
"to": "systemconfig_tbl",
|
||||
"to_col": "Code(PositionCode)",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "member_tbl",
|
||||
"from_col": "GroupCode",
|
||||
"to": "systemconfig_tbl",
|
||||
"to_col": "Code(GroupCode*)",
|
||||
"card": "N:1"
|
||||
},
|
||||
{
|
||||
"from": "member_tbl",
|
||||
"from_col": "WorkPosition",
|
||||
"to": "systemconfig_tbl",
|
||||
"to_col": "Code(WorkPositionCode)",
|
||||
"card": "N:1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
{
|
||||
"database": "jangheon_manhour",
|
||||
"tables": [
|
||||
"dallyproject_2005_2015_tbl",
|
||||
"dallyproject_2016_tbl",
|
||||
"dallyproject_2017_tbl",
|
||||
"dallyproject_2018_tbl",
|
||||
"dallyproject_2019_tbl",
|
||||
"dallyproject_2020_tbl",
|
||||
"dallyproject_tbl",
|
||||
"member_tbl",
|
||||
"project_tbl",
|
||||
"systemconfig_tbl",
|
||||
"userstate_tbl",
|
||||
"worker_tardy_tbl"
|
||||
],
|
||||
"relations": [
|
||||
{
|
||||
"from_table": "dallyproject_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to_table": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "dallyproject_2005_2015_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to_table": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "dallyproject_2016_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to_table": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "dallyproject_2017_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to_table": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "dallyproject_2018_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to_table": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "dallyproject_2019_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to_table": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "dallyproject_2020_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to_table": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "worker_tardy_tbl",
|
||||
"from_col": "MemberNo",
|
||||
"to_table": "member_tbl",
|
||||
"to_col": "MemberNo",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "dallyproject_tbl",
|
||||
"from_col": "EntryPCode",
|
||||
"to_table": "project_tbl",
|
||||
"to_col": "ProjectCode",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "dallyproject_2005_2015_tbl",
|
||||
"from_col": "EntryPCode",
|
||||
"to_table": "project_tbl",
|
||||
"to_col": "ProjectCode",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "dallyproject_2016_tbl",
|
||||
"from_col": "EntryPCode",
|
||||
"to_table": "project_tbl",
|
||||
"to_col": "ProjectCode",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "dallyproject_2017_tbl",
|
||||
"from_col": "EntryPCode",
|
||||
"to_table": "project_tbl",
|
||||
"to_col": "ProjectCode",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "dallyproject_2018_tbl",
|
||||
"from_col": "EntryPCode",
|
||||
"to_table": "project_tbl",
|
||||
"to_col": "ProjectCode",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "dallyproject_2019_tbl",
|
||||
"from_col": "EntryPCode",
|
||||
"to_table": "project_tbl",
|
||||
"to_col": "ProjectCode",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "dallyproject_2020_tbl",
|
||||
"from_col": "EntryPCode",
|
||||
"to_table": "project_tbl",
|
||||
"to_col": "ProjectCode",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "member_tbl",
|
||||
"from_col": "RankCode",
|
||||
"to_table": "systemconfig_tbl",
|
||||
"to_col": "Code (SysKey=PositionCode)",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "member_tbl",
|
||||
"from_col": "GroupCode",
|
||||
"to_table": "systemconfig_tbl",
|
||||
"to_col": "Code (SysKey=GroupCode*)",
|
||||
"cardinality": "N:1"
|
||||
},
|
||||
{
|
||||
"from_table": "member_tbl",
|
||||
"from_col": "WorkPosition",
|
||||
"to_table": "systemconfig_tbl",
|
||||
"to_col": "Code (SysKey=WorkPositionCode)",
|
||||
"cardinality": "N:1"
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 441 KiB |
@@ -1,340 +0,0 @@
|
||||
{
|
||||
"tables": [
|
||||
"dallyproject_2005_2015_tbl",
|
||||
"dallyproject_2016_tbl",
|
||||
"dallyproject_2017_tbl",
|
||||
"dallyproject_2018_tbl",
|
||||
"dallyproject_2019_tbl",
|
||||
"dallyproject_2020_tbl",
|
||||
"dallyproject_tbl",
|
||||
"member_tbl",
|
||||
"project_tbl",
|
||||
"systemconfig_tbl",
|
||||
"userstate_tbl",
|
||||
"worker_tardy_tbl"
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"from": "dallyproject_2005_2015_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2016_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2005_2015_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2017_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2005_2015_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2018_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2005_2015_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2019_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2005_2015_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2020_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2005_2015_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2005_2015_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "member_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2005_2015_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "userstate_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2005_2015_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "worker_tardy_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2016_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2017_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2016_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2018_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2016_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2019_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2016_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2020_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2016_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2016_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "member_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2016_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "userstate_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2016_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "worker_tardy_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2017_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2018_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2017_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2019_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2017_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2020_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2017_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2017_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "member_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2017_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "userstate_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2017_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "worker_tardy_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2018_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2019_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2018_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2020_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2018_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2018_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "member_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2018_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "userstate_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2018_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "worker_tardy_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2019_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_2020_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2019_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2019_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "member_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2019_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "userstate_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2019_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "worker_tardy_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2020_tbl",
|
||||
"from_col": "entryjobcode,entrypcode",
|
||||
"to": "dallyproject_tbl",
|
||||
"to_col": "entryjobcode,entrypcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2020_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "member_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2020_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "userstate_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_2020_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "worker_tardy_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "member_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "userstate_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "dallyproject_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "worker_tardy_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "member_tbl",
|
||||
"from_col": "groupcode,memberno",
|
||||
"to": "userstate_tbl",
|
||||
"to_col": "groupcode,memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "member_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "worker_tardy_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "project_tbl",
|
||||
"from_col": "projectcode",
|
||||
"to": "userstate_tbl",
|
||||
"to_col": "projectcode",
|
||||
"type": "INFERRED"
|
||||
},
|
||||
{
|
||||
"from": "userstate_tbl",
|
||||
"from_col": "memberno",
|
||||
"to": "worker_tardy_tbl",
|
||||
"to_col": "memberno",
|
||||
"type": "INFERRED"
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 182 KiB |
@@ -1,62 +0,0 @@
|
||||
table,column,keyflag,rows,distinct,empty,fill_rate,uniqueness,usage_score
|
||||
userstate_tbl,num,PRI,14133,14133,0,1.0,1.0,1.0
|
||||
project_tbl,ProjectCode,PRI,2149,2149,0,1.0,1.0,1.0
|
||||
worker_tardy_tbl,row_num,PRI,233,233,0,1.0,1.0,1.0
|
||||
member_tbl,MemberNo,PRI,320,319,0,1.0,0.9969,0.9984
|
||||
member_tbl,korName,PRI,320,301,2,0.9938,0.9406,0.9672
|
||||
dallyproject_tbl,EntryTime,PRI,73489,60540,0,1.0,0.8238,0.9119
|
||||
dallyproject_2020_tbl,EntryTime,PRI,8732,7172,0,1.0,0.8213,0.9107
|
||||
dallyproject_2019_tbl,EntryTime,PRI,8779,6747,0,1.0,0.7685,0.8843
|
||||
dallyproject_2016_tbl,EntryTime,PRI,9359,7167,0,1.0,0.7658,0.8829
|
||||
dallyproject_2018_tbl,EntryTime,PRI,9485,7219,0,1.0,0.7611,0.8805
|
||||
dallyproject_2017_tbl,EntryTime,PRI,9177,6859,0,1.0,0.7474,0.8737
|
||||
dallyproject_2005_2015_tbl,EntryTime,PRI,80964,57059,0,1.0,0.7047,0.8524
|
||||
systemconfig_tbl,Code,-,676,352,0,1.0,0.5207,0.7604
|
||||
worker_tardy_tbl,memberno,-,233,103,0,1.0,0.4421,0.721
|
||||
project_tbl,mpCode,-,2149,884,355,0.8348,0.4114,0.6231
|
||||
project_tbl,oldProjectCode,-,2149,906,669,0.6887,0.4216,0.5551
|
||||
member_tbl,RankCode,-,320,22,0,1.0,0.0688,0.5344
|
||||
userstate_tbl,ProjectCode,-,14133,604,15,0.9989,0.0427,0.5208
|
||||
member_tbl,GroupCode,-,320,12,0,1.0,0.0375,0.5188
|
||||
dallyproject_2016_tbl,EntryPCode,-,9359,214,17,0.9982,0.0229,0.5105
|
||||
dallyproject_2017_tbl,EntryPCode,-,9177,195,8,0.9991,0.0212,0.5102
|
||||
dallyproject_2020_tbl,EntryPCode,-,8732,176,3,0.9997,0.0202,0.5099
|
||||
dallyproject_2019_tbl,EntryPCode,-,8779,153,2,0.9998,0.0174,0.5086
|
||||
dallyproject_2018_tbl,EntryPCode,-,9485,166,10,0.9989,0.0175,0.5082
|
||||
member_tbl,WorkPosition,-,320,4,0,1.0,0.0125,0.5062
|
||||
dallyproject_tbl,EntryPCode,-,73489,854,35,0.9995,0.0116,0.5056
|
||||
userstate_tbl,MemberNo,-,14133,109,0,1.0,0.0077,0.5039
|
||||
dallyproject_2018_tbl,MemberNo,PRI,9485,58,1,0.9999,0.0061,0.503
|
||||
dallyproject_2016_tbl,MemberNo,PRI,9359,55,1,0.9999,0.0059,0.5029
|
||||
dallyproject_2019_tbl,MemberNo,PRI,8779,52,1,0.9999,0.0059,0.5029
|
||||
dallyproject_2017_tbl,MemberNo,PRI,9177,53,1,0.9999,0.0058,0.5028
|
||||
dallyproject_2005_2015_tbl,EntryPCode,-,80964,603,160,0.998,0.0074,0.5027
|
||||
dallyproject_2020_tbl,MemberNo,PRI,8732,47,0,1.0,0.0054,0.5027
|
||||
dallyproject_2019_tbl,EntryJobCode,-,8779,30,2,0.9998,0.0034,0.5016
|
||||
dallyproject_2020_tbl,EntryJobCode,-,8732,32,4,0.9995,0.0037,0.5016
|
||||
dallyproject_2016_tbl,EntryJobCode,-,9359,40,14,0.9985,0.0043,0.5014
|
||||
dallyproject_2017_tbl,EntryJobCode,-,9177,33,9,0.999,0.0036,0.5013
|
||||
dallyproject_2018_tbl,EntryJobCode,-,9485,33,11,0.9988,0.0035,0.5012
|
||||
dallyproject_2005_2015_tbl,MemberNo,PRI,80964,149,36,0.9996,0.0018,0.5007
|
||||
dallyproject_tbl,MemberNo,PRI,73489,96,6,0.9999,0.0013,0.5006
|
||||
userstate_tbl,GroupCode,-,14133,10,0,1.0,0.0007,0.5004
|
||||
dallyproject_tbl,EntryJobCode,-,73489,49,57,0.9992,0.0007,0.4999
|
||||
dallyproject_2005_2015_tbl,EntryJobCode,-,80964,124,743,0.9908,0.0015,0.4962
|
||||
dallyproject_2020_tbl,LeavePCode,-,8732,168,1486,0.8298,0.0192,0.4245
|
||||
dallyproject_2016_tbl,LeavePCode,-,9359,206,1643,0.8244,0.022,0.4232
|
||||
dallyproject_2020_tbl,LeaveJobCode,-,8732,32,1488,0.8296,0.0037,0.4166
|
||||
dallyproject_2019_tbl,LeavePCode,-,8779,149,1621,0.8154,0.017,0.4162
|
||||
dallyproject_2017_tbl,LeavePCode,-,9177,189,1748,0.8095,0.0206,0.4151
|
||||
dallyproject_tbl,LeavePCode,-,73489,861,13412,0.8175,0.0117,0.4146
|
||||
dallyproject_2016_tbl,LeaveJobCode,-,9359,36,1640,0.8248,0.0038,0.4143
|
||||
dallyproject_2019_tbl,LeaveJobCode,-,8779,29,1622,0.8152,0.0033,0.4093
|
||||
dallyproject_tbl,LeaveJobCode,-,73489,44,13500,0.8163,0.0006,0.4084
|
||||
dallyproject_2005_2015_tbl,LeavePCode,-,80964,594,15683,0.8063,0.0073,0.4068
|
||||
dallyproject_2017_tbl,LeaveJobCode,-,9177,31,1749,0.8094,0.0034,0.4064
|
||||
dallyproject_2018_tbl,LeavePCode,-,9485,162,2057,0.7831,0.0171,0.4001
|
||||
dallyproject_2018_tbl,LeaveJobCode,-,9485,33,2062,0.7826,0.0035,0.393
|
||||
dallyproject_2005_2015_tbl,LeaveJobCode,-,80964,112,22652,0.7202,0.0014,0.3608
|
||||
project_tbl,OrderCompanyCode,-,2149,12,1256,0.4155,0.0056,0.2106
|
||||
systemconfig_tbl,orderno,-,676,50,555,0.179,0.074,0.1265
|
||||
userstate_tbl,sub_code,-,14133,449,12614,0.1075,0.0318,0.0696
|
||||
userstate_tbl,active_code,-,14133,34,13939,0.0137,0.0024,0.0081
|
||||
|
Binary file not shown.
|
Before Width: | Height: | Size: 251 KiB |
BIN
Binary file not shown.
@@ -33,7 +33,6 @@
|
||||
box-shadow: 0 8px 20px rgba(24, 33, 47, 0.06);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.shared-note { margin-bottom: 16px; padding: 12px 14px; border: 1px solid #d7e5ee; border-radius: 12px; background: #eef8fb; color: #355468; font-size: 13px; line-height: 1.6; }
|
||||
h1 { margin: 0 0 10px; font-size: 24px; }
|
||||
p { margin: 8px 0; color: var(--muted); }
|
||||
.row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
|
||||
@@ -62,7 +61,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="shared-note">이 화면은 `MemberNo`와 `korName` 매칭 상태를 확인하고 비교하는 페이지입니다.</div>
|
||||
<div class="card">
|
||||
<h1>MemberNo → korName 매칭 페이지</h1>
|
||||
<p>`dallyproject.csv`의 `MemberNo`를 `member.csv`의 `MemberNo`와 매치해서 `korName`을 붙입니다.</p>
|
||||
|
||||
@@ -24,12 +24,10 @@
|
||||
table { border-collapse: collapse; width: 100%; min-width: 900px; background: #fff; }
|
||||
th, td { border-bottom: 1px solid #ebf0f8; padding: 7px 9px; font-size: 13px; text-align: left; white-space: nowrap; }
|
||||
th { position: sticky; top: 0; background: #f8fbff; z-index: 1; }
|
||||
.shared-note { margin-bottom: 14px; padding: 12px 14px; border: 1px solid #d7e5ee; border-radius: 12px; background: #eef8fb; color: #355468; font-size: 13px; line-height: 1.6; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="shared-note">이 화면은 MySQL 테이블 목록과 선택한 테이블의 원본 데이터를 미리보는 페이지입니다.</div>
|
||||
<div class="card">
|
||||
<h2 style="margin-top:0;">MySQL 데이터 미리보기</h2>
|
||||
<div class="row">
|
||||
|
||||
+985
-99
File diff suppressed because it is too large.
Load diff
@@ -38,7 +38,6 @@
|
||||
.summary-metrics { display:flex; flex-wrap:wrap; gap:8px 14px; margin-bottom:8px; font-size:12px; color:#334155; }
|
||||
.metric { padding:0; border:0; background:transparent; }
|
||||
.metric-label { font-size:11px; color:#64748b; margin-right:4px; }
|
||||
.shared-note { margin-bottom: 12px; padding: 12px 14px; border: 1px solid #d7e5ee; border-radius: 12px; background: #eef8fb; color: #355468; font-size: 13px; line-height: 1.6; }
|
||||
.metric-value { font-size:12px; font-weight:700; color:#0f172a; display:inline; }
|
||||
.summary-sub { font-size:11px; color:#64748b; margin:6px 0 8px; }
|
||||
.summary-table-wrap { max-height:none; overflow:visible; border:1px solid #dbe4f0; border-radius:8px; background:#fff; }
|
||||
@@ -106,7 +105,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="shared-note">이 화면은 사람별 근무내역, 현장 기록, 월별 요약을 한 번에 보는 통합 페이지입니다.</div>
|
||||
<div class="card">
|
||||
<h2 style="margin:0 0 10px;">dailyproject 사람별 근무내역</h2>
|
||||
<div class="tabs">
|
||||
|
||||
+329
-53
@@ -352,6 +352,31 @@
|
||||
opacity: 0.45;
|
||||
cursor: default;
|
||||
}
|
||||
.map-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 48px;
|
||||
height: 28px;
|
||||
margin-top: 6px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid rgba(15, 118, 110, 0.24);
|
||||
border-radius: 999px;
|
||||
background: #e9f8f5;
|
||||
color: var(--accent-strong);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.map-button:hover {
|
||||
filter: brightness(0.96);
|
||||
}
|
||||
.map-location {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
justify-items: start;
|
||||
}
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@@ -540,8 +565,8 @@
|
||||
<col style="width: 58px;">
|
||||
<col style="width: 58px;">
|
||||
<col style="width: 58px;">
|
||||
<col style="width: 64px;">
|
||||
<col style="width: 64px;">
|
||||
<col style="width: 84px;">
|
||||
<col style="width: 84px;">
|
||||
<col style="width: 64px;">
|
||||
<col style="width: 64px;">
|
||||
<col style="width: 58px;">
|
||||
@@ -610,6 +635,7 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const LINKED_CODE_BY_BUSINESS = {"26-002":{"salesCode":"26-교영-02","designCode":"26-설계-02","constCode":""},"26-004":{"salesCode":"26-교영-04","designCode":"26-설계-03","constCode":"26-시공-03"},"25-003":{"salesCode":"25-교영-02","designCode":"25-설계-01","constCode":"25-시공-01"},"25-005":{"salesCode":"25-교영-04","designCode":"25-설계-02","constCode":"25-시공-37"},"25-008":{"salesCode":"25-교영-07","designCode":"25-설계-04","constCode":"25-시공-08"},"25-009":{"salesCode":"25-교영-09","designCode":"25-설계-05","constCode":"26-시공-02"},"25-004":{"salesCode":"25-교영-03","designCode":"25-설계-06","constCode":""},"25-032":{"salesCode":"25-교영-32","designCode":"26-설계-01","constCode":""},"25-028":{"salesCode":"25-교영-28","designCode":"26-설계-04","constCode":""},"24-035":{"salesCode":"24-교영-35","designCode":"24-설계-01","constCode":""},"24-034":{"salesCode":"24-교영-34","designCode":"24-설계-03","constCode":""},"24-026":{"salesCode":"24-교영-26","designCode":"24-설계-05","constCode":""},"24-039":{"salesCode":"24-교영-39","designCode":"24-설계-09","constCode":"25-시공-13"},"24-044":{"salesCode":"24-교영-44","designCode":"24-설계-14","constCode":"25-시공-30"},"24-056":{"salesCode":"24-교영-85","designCode":"24-설계-31","constCode":"24-시공-24"},"24-061":{"salesCode":"24-교영-63","designCode":"24-설계-20","constCode":"24-시공-25"},"24-068":{"salesCode":"24-교영-69","designCode":"24-설계-21","constCode":"24-시공-27"},"24-072":{"salesCode":"24-교영-75","designCode":"24-설계-23","constCode":""},"24-074":{"salesCode":"24-교영-77","designCode":"24-설계-24","constCode":""},"24-078":{"salesCode":"24-교영-81","designCode":"24-설계-25","constCode":"26-시공-01"},"24-079":{"salesCode":"24-교영-82","designCode":"24-설계-26","constCode":"24-시공-33"},"24-080":{"salesCode":"24-교영-83","designCode":"24-설계-27","constCode":""},"24-081":{"salesCode":"24-교영-86","designCode":"24-설계-30","constCode":""},"24-064":{"salesCode":"24-교영-65","designCode":"24-설계-32","constCode":""},"24-066":{"salesCode":"24-교영-67","designCode":"24-설계-33","constCode":""},"24-067":{"salesCode":"24-교영-68","designCode":"24-설계-34","constCode":""},"24-017":{"salesCode":"24-교영-17","designCode":"24-설계-35","constCode":""},"24-020":{"salesCode":"24-교영-20","designCode":"24-설계-36","constCode":""},"24-084":{"salesCode":"24-교영-89","designCode":"24-설계-37","constCode":""},"24-063":{"salesCode":"24-교영-64","designCode":"25-설계-03","constCode":""},"24-073":{"salesCode":"24-교영-76","designCode":"25-설계-07","constCode":"25-시공-15"},"24-024":{"salesCode":"24-교영-24","designCode":"25-설계-09","constCode":""},"24-019":{"salesCode":"24-교영-19","designCode":"25-설계-11","constCode":"25-시공-24"},"23-042":{"salesCode":"23-교영-38","designCode":"24-설계-28","constCode":""},"23-004":{"salesCode":"23-교영-04","designCode":"23-설계-07","constCode":""},"23-008":{"salesCode":"23-교영-08","designCode":"23-설계-08","constCode":"25-시공-32"},"23-003":{"salesCode":"23-교영-03","designCode":"23-설계-10","constCode":""},"23-019":{"salesCode":"23-교영-19","designCode":"23-설계-12","constCode":""},"23-020":{"salesCode":"23-교영-20","designCode":"23-설계-13","constCode":""},"23-035":{"salesCode":"23-교영-35","designCode":"23-설계-14","constCode":"24-시공-21"},"23-030":{"salesCode":"23-교영-30","designCode":"24-설계-02","constCode":""},"23-037":{"salesCode":"23-교영-37","designCode":"24-설계-18","constCode":""},"24-048":{"salesCode":"23-교영-37","designCode":"24-설계-18","constCode":""},"23-041":{"salesCode":"23-교영-39","designCode":"24-설계-29","constCode":""},"22-046":{"salesCode":"22-교영-46","designCode":"22-설계-01","constCode":""},"22-016":{"salesCode":"22-교영-16","designCode":"22-설계-02","constCode":"23-시공-27"},"22-045":{"salesCode":"22-교영-45","designCode":"22-설계-03","constCode":""},"22-017":{"salesCode":"22-교영-17","designCode":"22-설계-04","constCode":""},"22-014":{"salesCode":"22-교영-14","designCode":"22-설계-05","constCode":""},"22-044":{"salesCode":"22-교영-44","designCode":"22-설계-06","constCode":""},"22-043":{"salesCode":"22-교영-43","designCode":"22-설계-07","constCode":""},"22-011":{"salesCode":"22-교영-11","designCode":"22-설계-08","constCode":""},"22-041":{"salesCode":"22-교영-41","designCode":"22-설계-10","constCode":""},"22-033":{"salesCode":"22-교영-33","designCode":"23-설계-01","constCode":"25-시공-33"},"22-027":{"salesCode":"22-교영-27","designCode":"23-설계-02","constCode":""},"22-012":{"salesCode":"22-교영-12","designCode":"23-설계-03","constCode":""},"22-039":{"salesCode":"22-교영-39","designCode":"23-설계-04","constCode":"25-시공-31"},"22-038":{"salesCode":"22-교영-38","designCode":"23-설계-11","constCode":""},"22-036":{"salesCode":"22-교영-36"Line truncated
|
||||
const state = {
|
||||
rows: [],
|
||||
filteredRows: [],
|
||||
@@ -620,6 +646,7 @@
|
||||
sourceTable: '',
|
||||
sourceColumns: {},
|
||||
};
|
||||
const FACTORY_ADDRESS = '충청남도 당진시 고대면 성산로 464';
|
||||
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const contractTypeFilter = document.getElementById('contractTypeFilter');
|
||||
@@ -662,19 +689,63 @@
|
||||
const text = String(value || '').trim();
|
||||
if (!text) return '-';
|
||||
return text
|
||||
.replace(/\+/g, '+\n')
|
||||
.replace(/~/g, '~\n')
|
||||
.replace(/=/g, '=\n')
|
||||
.replace(/\//g, '/\n')
|
||||
.replace(/(?<=\d)-(?=\d)/g, '-\n');
|
||||
}
|
||||
|
||||
function extractBusinessCodeKey(value) {
|
||||
const text = String(value || '').trim();
|
||||
const match = text.match(/\d{2}-\d{3}/);
|
||||
return match ? match[0] : text;
|
||||
}
|
||||
|
||||
function resolveLinkedCodes(detail) {
|
||||
const apiLinked = detail.linkedCodes || {};
|
||||
if (apiLinked.salesCode || apiLinked.designCode) {
|
||||
return apiLinked;
|
||||
}
|
||||
const key = extractBusinessCodeKey(detail.businessCode);
|
||||
return LINKED_CODE_BY_BUSINESS[key] || { salesCode: '', designCode: '' };
|
||||
}
|
||||
|
||||
function compactLinkedCodeName(name, fallbackName) {
|
||||
const cleanName = String(name || '').trim();
|
||||
const cleanFallback = String(fallbackName || '').trim();
|
||||
if (!cleanName) return cleanFallback;
|
||||
if (cleanFallback && (cleanName.length > 24 || cleanName.includes('[') || cleanName.includes('민간투자사업'))) {
|
||||
return cleanFallback;
|
||||
}
|
||||
return cleanName;
|
||||
}
|
||||
|
||||
function formatLinkedCodeLabel(code, name, fallbackName = '') {
|
||||
const cleanCode = String(code || '').trim();
|
||||
const cleanName = compactLinkedCodeName(name, fallbackName);
|
||||
if (!cleanCode) return '-';
|
||||
if (!cleanName) return cleanCode;
|
||||
return `${cleanName}(${cleanCode})`;
|
||||
}
|
||||
|
||||
function normalizeCrossbeamRows(crossbeam) {
|
||||
if (Array.isArray(crossbeam)) {
|
||||
return crossbeam.length ? crossbeam : [{ height: '', length: '', quantity: '', remarks: '' }];
|
||||
}
|
||||
if (crossbeam && typeof crossbeam === 'object') {
|
||||
return [crossbeam];
|
||||
}
|
||||
return [{ height: '', length: '', quantity: '', remarks: '' }];
|
||||
}
|
||||
|
||||
function renderBudgetPlanInlineTables(plan) {
|
||||
if (!plan) return '-';
|
||||
const inputDays = plan.inputDays || {};
|
||||
const inputRebar = plan.inputRebar || {};
|
||||
const girderSpecs = Array.isArray(plan.girderSpecs) ? plan.girderSpecs : [];
|
||||
const predeck = plan.predeck || {};
|
||||
const crossbeam = plan.crossbeam || {};
|
||||
const crossbeamRows = normalizeCrossbeamRows(plan.crossbeam);
|
||||
|
||||
return `
|
||||
<div class="inline-plan-wrap">
|
||||
@@ -735,28 +806,36 @@
|
||||
</tr>
|
||||
`
|
||||
}
|
||||
</table>
|
||||
<table class="plan-table plan-table-split">
|
||||
<tr>
|
||||
<th colspan="4">프리덱</th>
|
||||
<th colspan="4">가로보</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan="2">프리덱</th>
|
||||
<th>일반부(㎡)</th>
|
||||
<th>중분대(㎡)</th>
|
||||
<th>방호벽부(㎡)</th>
|
||||
<th>비고</th>
|
||||
<th rowspan="2">가로보</th>
|
||||
<th>형고(m)</th>
|
||||
<th>길이(m)</th>
|
||||
<th>수량(EA)</th>
|
||||
<th>비고</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${escapeHtml(predeck.generalArea || '-')}</td>
|
||||
<td>${escapeHtml(predeck.medianArea || '-')}</td>
|
||||
<td>${escapeHtml(predeck.barrierArea || '-')}</td>
|
||||
<td class="plan-left">${escapeHtml(predeck.remarks || '-')}</td>
|
||||
<td>${escapeHtml(crossbeam.height || '-')}</td>
|
||||
<td>${escapeHtml(crossbeam.length || '-')}</td>
|
||||
<td>${escapeHtml(crossbeam.quantity || '-')}</td>
|
||||
<td class="plan-left">${escapeHtml(crossbeam.remarks || '-')}</td>
|
||||
</tr>
|
||||
${crossbeamRows.map((row, index) => `
|
||||
<tr>
|
||||
${index === 0 ? `
|
||||
<td rowspan="${crossbeamRows.length}">${escapeHtml(predeck.generalArea || '-')}</td>
|
||||
<td rowspan="${crossbeamRows.length}">${escapeHtml(predeck.medianArea || '-')}</td>
|
||||
<td rowspan="${crossbeamRows.length}">${escapeHtml(predeck.barrierArea || '-')}</td>
|
||||
<td rowspan="${crossbeamRows.length}" class="plan-left">${escapeHtml(predeck.remarks || '-')}</td>
|
||||
` : ''}
|
||||
<td>${escapeHtml(row.height || '-')}</td>
|
||||
<td>${escapeHtml(row.length || '-')}</td>
|
||||
<td>${escapeHtml(row.quantity || '-')}</td>
|
||||
<td class="plan-left">${escapeHtml(row.remarks || '-')}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
@@ -769,6 +848,13 @@
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function compareProjectCodeDesc(a, b) {
|
||||
return String(b.projectCode || '').localeCompare(String(a.projectCode || ''), 'ko', {
|
||||
numeric: true,
|
||||
sensitivity: 'base',
|
||||
});
|
||||
}
|
||||
|
||||
function renderRows(rows) {
|
||||
if (!rows.length) {
|
||||
resultBody.innerHTML = '<tr><td colspan="2" class="empty">조건에 맞는 프로젝트가 없습니다.</td></tr>';
|
||||
@@ -823,7 +909,7 @@
|
||||
function renderDetail(detail) {
|
||||
if (!detail) {
|
||||
detailTitle.textContent = '계약정보 표';
|
||||
detailMeta.textContent = '시공코드나 약칭을 클릭하면 DB에 저장된 계약정보를 먼저 보여줍니다.';
|
||||
detailMeta.textContent = '시공코드나 약칭을 클릭하면 DB 캐시를 먼저 보여줍니다. 최신 정보가 필요하면 새로 가져오기를 누르세요.';
|
||||
detailBody.innerHTML = '<tr><td colspan="2" class="empty">선택된 시공코드가 없습니다.</td></tr>';
|
||||
bridgeMeta.textContent = '공사규모와 공사개요를 교량명 기준으로 매칭해서 보여줍니다.';
|
||||
bridgeBody.innerHTML = '<tr><td colspan="19" class="empty">선택된 시공코드가 없습니다.</td></tr>';
|
||||
@@ -832,12 +918,22 @@
|
||||
|
||||
detailTitle.textContent = `${detail.projectName || '-'} [${detail.projectCode}]`;
|
||||
detailMeta.textContent = `사업코드 ${detail.businessCode || '-'} · 마지막 동기화 ${detail.syncedAt || '-'}`;
|
||||
renderDetailRows(detail);
|
||||
renderMergedBridgeRows(detail.scaleRows || [], state.bridgeOverviews || [], state.budgetPlan);
|
||||
}
|
||||
|
||||
function renderDetailRows(detail) {
|
||||
const linkedCodes = resolveLinkedCodes(detail);
|
||||
const linkedCodeText = [
|
||||
formatLinkedCodeLabel(linkedCodes.salesCode, linkedCodes.salesName, detail.projectName),
|
||||
formatLinkedCodeLabel(linkedCodes.designCode, linkedCodes.designName, detail.projectName),
|
||||
].join(' · ');
|
||||
const rows = [
|
||||
['사업코드', detail.businessCode],
|
||||
['약칭', detail.projectName],
|
||||
['시공코드', detail.projectCode],
|
||||
['현장위치', detail.siteLocation],
|
||||
['연계코드', linkedCodeText],
|
||||
['현장위치', renderSiteLocationCell(detail.siteLocation, detail.projectName)],
|
||||
['발주처', detail.clientName],
|
||||
['최종계약금액', detail.finalContractAmountText],
|
||||
['계약종류', detail.contractType],
|
||||
@@ -852,11 +948,32 @@
|
||||
detailBody.innerHTML = rows.map(([label, value]) => `
|
||||
<tr>
|
||||
<th>${escapeHtml(label)}</th>
|
||||
<td>${typeof value === 'string' && value.includes('<table') ? value : escapeHtml(value || '-')}</td>
|
||||
<td>${typeof value === 'string' && (value.includes('<table') || value.includes('class="map-location"')) ? value : escapeHtml(value || '-')}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
Array.from(detailBody.querySelectorAll('.map-button')).forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
openBridgeMap(button.getAttribute('data-bridge') || '', button.getAttribute('data-location') || '');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
renderMergedBridgeRows(detail.scaleRows || [], state.bridgeOverviews || [], state.budgetPlan);
|
||||
function renderSiteLocationCell(siteLocation, projectName) {
|
||||
const location = normalizeValue(siteLocation);
|
||||
const disabled = !location || location === '-';
|
||||
return `
|
||||
<div class="map-location">
|
||||
<div>${toHtmlWithBreaks(location)}</div>
|
||||
<button
|
||||
class="map-button"
|
||||
type="button"
|
||||
data-bridge="${escapeAttr(projectName || '')}"
|
||||
data-location="${escapeAttr(location)}"
|
||||
${disabled ? 'disabled' : ''}
|
||||
title="공장 위치와 현장 위치를 지도에서 보기"
|
||||
>지도</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function normalizeBridgeName(value) {
|
||||
@@ -897,6 +1014,86 @@
|
||||
return `${text.slice(0, splitIndex).trim()}\n${text.slice(splitIndex + 1).trim()}`;
|
||||
}
|
||||
|
||||
function normalizeMapQuery(value) {
|
||||
return String(value || '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/\n+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function extractAddressForMap(value) {
|
||||
const original = normalizeMapQuery(value);
|
||||
const pointMatch = original.match(/\(시점\)\s*([^()]+?)(?=\s*\(종점\)|$)/);
|
||||
if (pointMatch && pointMatch[1]) {
|
||||
return normalizeMapQuery(pointMatch[1]);
|
||||
}
|
||||
const pointLabelMatch = original.match(/시점\s*[::]?\s*([^()]+?)(?=\s*종점\s*[::]?|$)/);
|
||||
if (pointLabelMatch && pointLabelMatch[1]) {
|
||||
return normalizeMapQuery(pointLabelMatch[1]);
|
||||
}
|
||||
|
||||
let text = original
|
||||
.replace(/^\(주\)\s*장헌\s*/, '')
|
||||
.replace(/^\(주\)장헌\s*/, '')
|
||||
.replace(/^\[.*?\]\s*/, '')
|
||||
.replace(/\(.*?\)/g, ' ')
|
||||
.replace(/\[.*?\]/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
const addressStart = text.search(/(서울특별시|서울시|부산광역시|부산시|대구광역시|대구시|인천광역시|인천시|광주광역시|광주시|대전광역시|대전시|울산광역시|울산시|세종특별자치시|세종시|경기도|강원특별자치도|강원도|충청북도|충북|충청남도|충남|전북특별자치도|전라북도|전북|전라남도|전남|경상북도|경북|경상남도|경남|제주특별자치도|제주도)/);
|
||||
if (addressStart > 0) {
|
||||
text = text.slice(addressStart).trim();
|
||||
}
|
||||
text = text
|
||||
.split(/\s*~\s*/)[0]
|
||||
.split(/\s+종점\s+/)[0]
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
return text;
|
||||
}
|
||||
|
||||
async function openBridgeMap(bridgeName, bridgeLocation) {
|
||||
const site = extractAddressForMap(bridgeLocation);
|
||||
if (!site || site === '-') {
|
||||
alert('교량 위치 정보가 없습니다.');
|
||||
return;
|
||||
}
|
||||
const popup = window.open(
|
||||
'',
|
||||
`jhBridgeRouteMap_${Date.now()}`,
|
||||
'popup=yes,width=1280,height=820,left=80,top=60,resizable=yes,scrollbars=yes'
|
||||
);
|
||||
if (!popup) {
|
||||
alert('팝업이 차단되었습니다. 브라우저 팝업 허용 후 다시 눌러주세요.');
|
||||
return;
|
||||
}
|
||||
popup.document.write('<!doctype html><meta charset="utf-8"><title>길찾기 준비 중</title><body style="font-family:sans-serif;padding:24px;">네이버 자동차 길찾기를 준비하는 중입니다...</body>');
|
||||
popup.focus();
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
origin: FACTORY_ADDRESS,
|
||||
destination: site,
|
||||
});
|
||||
const response = await fetch(`/api/naver-route-url?${params.toString()}`, { cache: 'no-store' });
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result.ok || !result.url) {
|
||||
throw new Error(result.error || '네이버 길찾기 URL을 만들지 못했습니다.');
|
||||
}
|
||||
popup.location.href = result.url;
|
||||
} catch (error) {
|
||||
const fallbackUrl = `https://map.naver.com/p/search/${encodeURIComponent(site)}`;
|
||||
popup.document.body.innerHTML = `
|
||||
<div style="font-family:sans-serif;padding:24px;line-height:1.6;">
|
||||
<h2 style="margin:0 0 12px;color:#b91c1c;">네이버 자동차 길찾기를 바로 열지 못했습니다.</h2>
|
||||
<p>현장 주소: <strong>${escapeHtml(site)}</strong></p>
|
||||
<p style="white-space:pre-wrap;color:#7f1d1d;">${escapeHtml(error.message || String(error))}</p>
|
||||
<p><a href="${escapeAttr(fallbackUrl)}" style="color:#0f766e;font-weight:700;">네이버지도에서 현장 위치 먼저 열기</a></p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function formatStatus(scaleRow, overviewRow) {
|
||||
const status = overviewRow?.constructionStatus || scaleRow?.constructionStatus || '';
|
||||
return status || '-';
|
||||
@@ -915,7 +1112,7 @@
|
||||
const inputRebar = plan.inputRebar || {};
|
||||
const girderSpecs = Array.isArray(plan.girderSpecs) ? plan.girderSpecs : [];
|
||||
const predeck = plan.predeck || {};
|
||||
const crossbeam = plan.crossbeam || {};
|
||||
const crossbeamRows = normalizeCrossbeamRows(plan.crossbeam);
|
||||
|
||||
planModalTitle.textContent = `${bridgeName || plan.projectName || '프로젝트'} 공사시행계획서`;
|
||||
planModalBody.innerHTML = `
|
||||
@@ -943,10 +1140,25 @@
|
||||
</table>
|
||||
<div class="plan-section-title">교량제원 - 프리덱 / 가로보</div>
|
||||
<table class="plan-table">
|
||||
<tr><th>프리덱 일반부</th><td>${escapeHtml(predeck.generalArea || '-')}</td><th>가로보 형고</th><td>${escapeHtml(crossbeam.height || '-')}</td></tr>
|
||||
<tr><th>프리덱 중분대</th><td>${escapeHtml(predeck.medianArea || '-')}</td><th>가로보 길이</th><td>${escapeHtml(crossbeam.length || '-')}</td></tr>
|
||||
<tr><th>프리덱 방호벽부</th><td>${escapeHtml(predeck.barrierArea || '-')}</td><th>가로보 수량</th><td>${escapeHtml(crossbeam.quantity || '-')}</td></tr>
|
||||
<tr><th>프리덱 비고</th><td>${escapeHtml(predeck.remarks || '-')}</td><th>가로보 비고</th><td>${escapeHtml(crossbeam.remarks || '-')}</td></tr>
|
||||
<tr><th colspan="2">프리덱</th><th>형고(m)</th><th>길이(m)</th><th>수량(EA)</th><th>비고</th></tr>
|
||||
${crossbeamRows.map((row, index) => `
|
||||
<tr>
|
||||
${index === 0 ? `<th>일반부(㎡)</th><td>${escapeHtml(predeck.generalArea || '-')}</td>` : ''}
|
||||
${index === 1 ? `<th>중분대(㎡)</th><td>${escapeHtml(predeck.medianArea || '-')}</td>` : ''}
|
||||
${index === 2 ? `<th>방호벽부(㎡)</th><td>${escapeHtml(predeck.barrierArea || '-')}</td>` : ''}
|
||||
${index >= 3 ? `<th>비고</th><td>${escapeHtml(predeck.remarks || '-')}</td>` : ''}
|
||||
<td>${escapeHtml(row.height || '-')}</td>
|
||||
<td>${escapeHtml(row.length || '-')}</td>
|
||||
<td>${escapeHtml(row.quantity || '-')}</td>
|
||||
<td>${escapeHtml(row.remarks || '-')}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
${crossbeamRows.length < 4 ? `
|
||||
${crossbeamRows.length <= 0 ? '' : ''}
|
||||
${crossbeamRows.length < 2 ? `<tr><th>중분대(㎡)</th><td>${escapeHtml(predeck.medianArea || '-')}</td><td colspan="4"></td></tr>` : ''}
|
||||
${crossbeamRows.length < 3 ? `<tr><th>방호벽부(㎡)</th><td>${escapeHtml(predeck.barrierArea || '-')}</td><td colspan="4"></td></tr>` : ''}
|
||||
${crossbeamRows.length < 4 ? `<tr><th>비고</th><td>${escapeHtml(predeck.remarks || '-')}</td><td colspan="4"></td></tr>` : ''}
|
||||
` : ''}
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
@@ -997,6 +1209,7 @@
|
||||
crossbeamCount: scaleRow.crossbeamCount || '-',
|
||||
panelCount: scaleRow.panelCount || '-',
|
||||
rebarCount: scaleRow.rebarCount || '-',
|
||||
rawBridgeLocation: overviewRow.bridgeLocation || '',
|
||||
bridgeLocation: formatBridgeLocation(overviewRow.bridgeLocation),
|
||||
remarks: overviewRow.remarks || '-',
|
||||
};
|
||||
@@ -1005,6 +1218,8 @@
|
||||
|
||||
function renderMergedBridgeRows(scaleRows, overviews, budgetPlan) {
|
||||
const mergedRows = mergeBridgeRows(scaleRows, overviews, budgetPlan);
|
||||
const siteLocation = normalizeValue(state.detail?.siteLocation || state.selectedRow?.siteLocation || '');
|
||||
const hasSiteLocation = siteLocation && siteLocation !== '-';
|
||||
bridgeMeta.textContent = mergedRows.length
|
||||
? `공사규모 ${scaleRows.length}건 · 공사개요 ${overviews.length}건을 교량명 기준으로 매칭했습니다.`
|
||||
: '연결된 공사규모나 공사개요 데이터가 없습니다.';
|
||||
@@ -1032,7 +1247,11 @@
|
||||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.crossbeamCount))}</td>
|
||||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.panelCount))}</td>
|
||||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.rebarCount))}</td>
|
||||
<td class="bridge-summary cell-wide">${toHtmlWithBreaks(row.bridgeLocation)}</td>
|
||||
<td class="bridge-summary cell-wide">
|
||||
<div class="map-location">
|
||||
<div>${toHtmlWithBreaks(row.bridgeLocation)}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
class="remark-button"
|
||||
@@ -1097,6 +1316,7 @@
|
||||
if (selectedApplicationType) {
|
||||
state.filteredRows = state.filteredRows.filter((row) => splitApplicationTypes(row.applicationType).includes(selectedApplicationType));
|
||||
}
|
||||
state.filteredRows.sort(compareProjectCodeDesc);
|
||||
|
||||
countChip.textContent = `총 ${state.rows.length.toLocaleString()}건 / 표시 ${state.filteredRows.length.toLocaleString()}건`;
|
||||
statusText.textContent = keyword
|
||||
@@ -1147,10 +1367,10 @@
|
||||
}
|
||||
detailMeta.textContent = refresh
|
||||
? 'ERP 계약정보를 DB로 동기화하는 중입니다.'
|
||||
: 'DB에 저장된 계약정보를 불러오는 중입니다.';
|
||||
: 'DB 캐시에 저장된 계약정보를 불러오는 중입니다.';
|
||||
bridgeMeta.textContent = refresh
|
||||
? 'ERP 공사규모와 공사개요를 동기화하는 중입니다.'
|
||||
: 'DB에 저장된 공사규모와 공사개요를 불러오는 중입니다.';
|
||||
: 'DB 캐시에 저장된 공사규모와 공사개요를 불러오는 중입니다.';
|
||||
detailSyncButton.disabled = true;
|
||||
try {
|
||||
const detailUrl = `/api/erp-contract-detail?page=const&projectCode=${encodeURIComponent(projectCode)}&projectName=${encodeURIComponent(projectName || '')}&refresh=${refresh ? '1' : '0'}`;
|
||||
@@ -1164,42 +1384,98 @@
|
||||
const data = await detailResponse.json();
|
||||
const overviewData = await overviewResponse.json();
|
||||
const budgetPlanData = await budgetPlanResponse.json();
|
||||
const fetchFreshOverviewIfNeeded = async () => {
|
||||
if (refresh || (overviewResponse.ok && Array.isArray(overviewData.overviews) && overviewData.overviews.length)) {
|
||||
return Array.isArray(overviewData.overviews) ? overviewData.overviews : [];
|
||||
}
|
||||
try {
|
||||
const freshOverviewResponse = await fetch(
|
||||
`/api/erp-bridge-overviews?page=const&projectCode=${encodeURIComponent(projectCode)}&projectName=${encodeURIComponent(projectName || '')}&refresh=1`,
|
||||
{ cache: 'no-store' }
|
||||
);
|
||||
const freshOverviewData = await freshOverviewResponse.json();
|
||||
if (freshOverviewResponse.ok) {
|
||||
return Array.isArray(freshOverviewData.overviews) ? freshOverviewData.overviews : [];
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
return Array.isArray(overviewData.overviews) ? overviewData.overviews : [];
|
||||
};
|
||||
const fetchFreshBudgetPlanIfNeeded = async () => {
|
||||
if (refresh || (budgetPlanResponse.ok && budgetPlanData.plan)) {
|
||||
return budgetPlanResponse.ok ? (budgetPlanData.plan || null) : null;
|
||||
}
|
||||
try {
|
||||
const freshBudgetPlanResponse = await fetch(
|
||||
`/api/erp-budget-plan?page=const&projectCode=${encodeURIComponent(projectCode)}&projectName=${encodeURIComponent(projectName || '')}&refresh=1`,
|
||||
{ cache: 'no-store' }
|
||||
);
|
||||
const freshBudgetPlanData = await freshBudgetPlanResponse.json();
|
||||
if (freshBudgetPlanResponse.ok) {
|
||||
return freshBudgetPlanData.plan || null;
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
return budgetPlanResponse.ok ? (budgetPlanData.plan || null) : null;
|
||||
};
|
||||
|
||||
if (!detailResponse.ok) {
|
||||
if (!refresh && detailResponse.status === 404) {
|
||||
return loadDetail(projectCode, projectName, true);
|
||||
state.bridgeOverviews = await fetchFreshOverviewIfNeeded();
|
||||
state.budgetPlan = await fetchFreshBudgetPlanIfNeeded();
|
||||
detailTitle.textContent = `${projectName || '-'} [${projectCode}]`;
|
||||
detailMeta.textContent = 'DB 캐시에 계약정보가 없어 ERP에서 기본 계약정보를 가져오는 중입니다.';
|
||||
try {
|
||||
const freshDetailResponse = await fetch(
|
||||
`/api/erp-contract-detail?page=const&projectCode=${encodeURIComponent(projectCode)}&projectName=${encodeURIComponent(projectName || '')}&refresh=1`,
|
||||
{ cache: 'no-store' }
|
||||
);
|
||||
const freshDetailData = await freshDetailResponse.json();
|
||||
if (freshDetailResponse.ok) {
|
||||
state.detail = freshDetailData.detail || null;
|
||||
if (state.selectedRow && state.detail) {
|
||||
state.selectedRow.businessCode = state.detail.businessCode || state.selectedRow.businessCode || '';
|
||||
state.selectedRow.siteLocation = state.detail.siteLocation || state.selectedRow.siteLocation || '';
|
||||
state.selectedRow.clientName = state.detail.clientName || state.selectedRow.clientName || '';
|
||||
state.selectedRow.finalContractAmountText = state.detail.finalContractAmountText || state.selectedRow.finalContractAmountText || '';
|
||||
state.selectedRow.contractType = state.detail.contractType || state.selectedRow.contractType || '';
|
||||
state.selectedRow.syncedAt = state.detail.syncedAt || state.selectedRow.syncedAt || '';
|
||||
}
|
||||
renderDetail(state.detail);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
|
||||
state.detail = null;
|
||||
const selectedSummary = state.selectedRow || {};
|
||||
detailMeta.textContent = '기본 계약정보를 아직 가져오지 않았습니다. 새 정보 다시 가져오기를 누르면 ERP에서 수집합니다.';
|
||||
renderDetailRows({
|
||||
businessCode: selectedSummary.businessCode || '',
|
||||
projectName: projectName || selectedSummary.projectName || '',
|
||||
projectCode,
|
||||
siteLocation: selectedSummary.siteLocation || '',
|
||||
clientName: selectedSummary.clientName || '',
|
||||
finalContractAmountText: selectedSummary.finalContractAmountText || '',
|
||||
contractType: selectedSummary.contractType || '',
|
||||
syncedAt: selectedSummary.syncedAt || '',
|
||||
});
|
||||
renderMergedBridgeRows([], state.bridgeOverviews || [], state.budgetPlan);
|
||||
return;
|
||||
}
|
||||
throw new Error(data.error || '계약정보 조회에 실패했습니다.');
|
||||
}
|
||||
|
||||
state.detail = data.detail || null;
|
||||
if (!refresh && (!state.detail || !Array.isArray(state.detail.scaleRows) || !state.detail.scaleRows.length)) {
|
||||
return loadDetail(projectCode, projectName, true);
|
||||
}
|
||||
state.bridgeOverviews = Array.isArray(overviewData.overviews) ? overviewData.overviews : [];
|
||||
state.budgetPlan = budgetPlanResponse.ok ? (budgetPlanData.plan || null) : null;
|
||||
if (!refresh && !state.bridgeOverviews.length) {
|
||||
const freshOverviewResponse = await fetch(
|
||||
`/api/erp-bridge-overviews?page=const&projectCode=${encodeURIComponent(projectCode)}&projectName=${encodeURIComponent(projectName || '')}&refresh=1`,
|
||||
{ cache: 'no-store' }
|
||||
);
|
||||
const freshOverviewData = await freshOverviewResponse.json();
|
||||
if (freshOverviewResponse.ok) {
|
||||
state.bridgeOverviews = Array.isArray(freshOverviewData.overviews) ? freshOverviewData.overviews : [];
|
||||
}
|
||||
}
|
||||
if (!refresh && !state.budgetPlan) {
|
||||
const freshPlanResponse = await fetch(
|
||||
`/api/erp-budget-plan?page=const&projectCode=${encodeURIComponent(projectCode)}&projectName=${encodeURIComponent(projectName || '')}&refresh=1`,
|
||||
{ cache: 'no-store' }
|
||||
);
|
||||
const freshPlanData = await freshPlanResponse.json();
|
||||
if (freshPlanResponse.ok) {
|
||||
state.budgetPlan = freshPlanData.plan || null;
|
||||
}
|
||||
}
|
||||
state.bridgeOverviews = await fetchFreshOverviewIfNeeded();
|
||||
state.budgetPlan = await fetchFreshBudgetPlanIfNeeded();
|
||||
if (state.selectedRow && state.detail) {
|
||||
state.selectedRow.businessCode = state.detail.businessCode || state.selectedRow.businessCode || '';
|
||||
state.selectedRow.siteLocation = state.detail.siteLocation || state.selectedRow.siteLocation || '';
|
||||
state.selectedRow.clientName = state.detail.clientName || state.selectedRow.clientName || '';
|
||||
state.selectedRow.finalContractAmountText = state.detail.finalContractAmountText || state.selectedRow.finalContractAmountText || '';
|
||||
state.selectedRow.contractType = state.detail.contractType || state.selectedRow.contractType || '';
|
||||
state.selectedRow.syncedAt = state.detail.syncedAt || state.selectedRow.syncedAt || '';
|
||||
state.selectedRow.applicationType = Array.from(new Set(
|
||||
state.bridgeOverviews.map((item) => String(item.applicationType || '').trim()).filter(Boolean)
|
||||
)).sort((a, b) => a.localeCompare(b, 'ko')).join('||') || state.selectedRow.applicationType || '';
|
||||
|
||||
@@ -18,7 +18,7 @@ class ProjectCodeViewerHandler(base.Handler):
|
||||
if __name__ == '__main__':
|
||||
os.makedirs(base.BASE_DIR, exist_ok=True)
|
||||
try:
|
||||
with sqlite3.connect(base.DB_PATH, timeout=30) as conn:
|
||||
with base.open_db_connection() as conn:
|
||||
base.init_db(conn)
|
||||
except sqlite3.OperationalError as error:
|
||||
if 'locked' in str(error).lower():
|
||||
|
||||
Reference in new issue
Block a user