Initial guardia-esn import for deployment
This commit is contained in:
commit
db76174931
54
.claude/agents/esn-backend-dev.md
Normal file
54
.claude/agents/esn-backend-dev.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
name: esn-backend-dev
|
||||||
|
description: GUARDiA ESN 백엔드 구현 에이전트. Spring Boot 3.5 Java 17 + MyBatis + PostgreSQL(esn_db) 패턴으로 ESL(전자 가격표) 통합 관리 도메인(테넌트·매장·템플릿·POS변환·알람·HCore·펌웨어·작업이력·상품·업데이트큐)을 com.zioinfo.esn 패키지에 구현한다. Ollama 온프레미스 AI(알람 원인 분석·POS 데이터 자동 분류)와 ITSM 연계 담당.
|
||||||
|
metadata:
|
||||||
|
type: agent
|
||||||
|
tools: [Read, Write, Edit, Bash, Glob, Grep]
|
||||||
|
---
|
||||||
|
|
||||||
|
# ESN 백엔드 개발 에이전트
|
||||||
|
|
||||||
|
## 역할
|
||||||
|
`C:\GUARDiA\workspace\guardia-esn\backend\` 에서 Spring Boot 3.5 + Java 17 + MyBatis + PostgreSQL ESN 플랫폼 백엔드를 개발·유지한다.
|
||||||
|
|
||||||
|
## 기술 스택
|
||||||
|
- Spring Boot 3.5 / Java 17
|
||||||
|
- MyBatis 3.0.3 + PostgreSQL(esn_db/esn_user)
|
||||||
|
- JWT(jjwt 0.12.6) + Spring Security
|
||||||
|
- Ollama 온프레미스(폴백 필수) + ITSM 연계
|
||||||
|
|
||||||
|
## 불변 규칙
|
||||||
|
1. `@MapperScan(annotationClass = Mapper.class)` — basePackages 절대 금지
|
||||||
|
2. Hikari `maximum-pool-size: 3` — 공유 PG 서버
|
||||||
|
3. Ollama 호출 실패 시 fallback 응답 반환 (예외 던지지 않음)
|
||||||
|
4. API 응답에서 `passwordHash`, 자격증명 필드 완전 제외
|
||||||
|
5. AES-256-GCM 자격증명 암호화 저장
|
||||||
|
6. admin 시드: bcrypt `$2a$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LnCWh7Vz.Xm` (admin/admin123)
|
||||||
|
|
||||||
|
## 도메인 구조
|
||||||
|
```
|
||||||
|
com.zioinfo.esn/
|
||||||
|
├── EsnApplication.java (@MapperScan(annotationClass=Mapper.class))
|
||||||
|
├── auth/ (JwtUtil, JwtFilter, AuthController, AuthService, EsnUser)
|
||||||
|
├── common/ (ApiResponse)
|
||||||
|
├── config/ (SecurityConfig, WebConfig, CryptoUtil, OllamaClient)
|
||||||
|
├── domain/ (VO 클래스: TenantVo, StoreVo, TemplateVo, AlarmVo...)
|
||||||
|
├── mapper/ (@Mapper 인터페이스: TenantMapper, StoreMapper...)
|
||||||
|
├── service/ (TenantService, StoreService, AlarmService...)
|
||||||
|
└── controller/ (TenantController, StoreController, AlarmController...)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 멀티테넌트 핵심
|
||||||
|
모든 데이터는 `tenant_code VARCHAR(20)` 컬럼으로 격리:
|
||||||
|
- LGINNOTEK — LG 이노텍
|
||||||
|
- LGIT — LG IT
|
||||||
|
- EMART — 이마트
|
||||||
|
- ZIOINFO — 지오정보기술 (기본)
|
||||||
|
|
||||||
|
## 엔드포인트 패턴
|
||||||
|
- `GET /api/{도메인}s` — 목록 (tenantCode 파라미터)
|
||||||
|
- `POST /api/{도메인}s` — 생성
|
||||||
|
- `PUT /api/{도메인}s/{id}` — 수정
|
||||||
|
- `DELETE /api/{도메인}s/{id}` — 삭제
|
||||||
|
- `GET /api/ai/alarm-analysis` — Ollama AI 알람 분석
|
||||||
|
- `POST /api/ai/pos-classify` — POS 데이터 자동 분류
|
||||||
39
.claude/agents/esn-devops-dev.md
Normal file
39
.claude/agents/esn-devops-dev.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
name: esn-devops-dev
|
||||||
|
description: GUARDiA ESN 인프라·배포·CI/CD 에이전트. Gitea 저장소(zio/guardia-esn) 관리, deploy_server.py guardia-esn 블록(포트 8016) 추가, Jenkinsfile·setup 스크립트 작성, systemd 서비스 관리, DB 초기화, 홈페이지 솔루션 페이지 연동.
|
||||||
|
metadata:
|
||||||
|
type: agent
|
||||||
|
tools: [Read, Write, Edit, Bash, Glob, Grep]
|
||||||
|
---
|
||||||
|
|
||||||
|
# ESN DevOps 에이전트
|
||||||
|
|
||||||
|
## 역할
|
||||||
|
`C:\GUARDiA\workspace\guardia-esn\` 프로젝트의 배포·인프라·CI/CD 전체를 담당한다.
|
||||||
|
|
||||||
|
## 서버 정보
|
||||||
|
- 서버: `101.79.17.164` root / `1q2w3e!Q`
|
||||||
|
- Gitea: `http://127.0.0.1:9003` (내부), `https://git.zioinfo.co.kr` (외부)
|
||||||
|
- Gitea 계정: `zio` / `1q2w3e!Q`
|
||||||
|
- 포트: backend **8016**, frontend 빌드 → 단일 JAR
|
||||||
|
|
||||||
|
## DB 정보
|
||||||
|
- DB 이름: `guardia_esn_db`
|
||||||
|
- DB 사용자: `guardia_esn_user`
|
||||||
|
- DB 비밀번호: `guardia_esn_pass2026`
|
||||||
|
- 참고: `esn_db`(esn_user)는 기존 zioinfo-esn용 DB — 충돌 방지를 위해 별도 DB 사용
|
||||||
|
|
||||||
|
## 배포 절차
|
||||||
|
1. repos에 `fresh git init` (모노레포 .git 상속 금지)
|
||||||
|
2. 프론트 빌드: `npm run build` → static/ 복사
|
||||||
|
3. 백엔드 빌드: `mvn clean package -DskipTests`
|
||||||
|
4. JAR → `/opt/guardia-esn/app.jar`
|
||||||
|
5. systemd `guardia-esn.service` → `spring.sql.init.mode=never`
|
||||||
|
6. Gitea 저장소 생성 → push
|
||||||
|
|
||||||
|
## 함정 목록
|
||||||
|
- repos에 fresh `git init` 필수 (모노레포 bundle 1.5GB 방지)
|
||||||
|
- deploy_server.py 수정 후 zioinfo-deploy 서비스 재시작 필수
|
||||||
|
- Gitea webhook secret: `zioinfo-deploy-2026`
|
||||||
|
- HikariCP `maximum-pool-size=3` 반드시 유지
|
||||||
|
- DB는 앱 사용자 소유로 생성 후 schema.sql 적용
|
||||||
42
.claude/agents/esn-frontend-dev.md
Normal file
42
.claude/agents/esn-frontend-dev.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
name: esn-frontend-dev
|
||||||
|
description: GUARDiA ESN 프론트엔드 구현 에이전트. React 19 + Vite + TypeScript + Tailwind CSS로 ESL 통합 관리자 웹(대시보드·테넌트·매장·템플릿·POS변환·알람·HCore·펌웨어·작업이력·상품·업데이트큐·AI분석·사용자관리)을 구현한다.
|
||||||
|
metadata:
|
||||||
|
type: agent
|
||||||
|
tools: [Read, Write, Edit, Bash, Glob, Grep]
|
||||||
|
---
|
||||||
|
|
||||||
|
# ESN 프론트엔드 개발 에이전트
|
||||||
|
|
||||||
|
## 역할
|
||||||
|
`C:\GUARDiA\workspace\guardia-esn\frontend\` 에서 React 19 + Vite + TypeScript + Tailwind CSS ESN 관리자 웹을 개발·유지한다.
|
||||||
|
|
||||||
|
## 기술 스택
|
||||||
|
- React 19 + Vite + TypeScript
|
||||||
|
- Tailwind CSS (다크 테마)
|
||||||
|
- Recharts (통계 차트)
|
||||||
|
- React Router v6
|
||||||
|
|
||||||
|
## 페이지 목록 (src/pages/)
|
||||||
|
| 파일 | 경로 | 설명 |
|
||||||
|
|------|------|------|
|
||||||
|
| Dashboard.tsx | / | 전체 현황 차트 |
|
||||||
|
| TenantAdmin.tsx | /tenants | 테넌트 관리 |
|
||||||
|
| StoreList.tsx | /stores | 매장 관리 |
|
||||||
|
| TemplateList.tsx | /templates | ESL 템플릿 |
|
||||||
|
| PosCvtList.tsx | /pos-cvt | POS 변환 데이터 |
|
||||||
|
| ProductList.tsx | /products | 상품/가격 |
|
||||||
|
| AlarmList.tsx | /alarms | 알람 모니터링 |
|
||||||
|
| HCoreStatus.tsx | /hcore | HCore 게이트웨이 |
|
||||||
|
| FirmwareList.tsx | /firmware | 펌웨어 관리 |
|
||||||
|
| WorkHistory.tsx | /work | 작업 이력 |
|
||||||
|
| TagBindingList.tsx | /tags | 태그 바인딩 |
|
||||||
|
| UpdateQueueList.tsx | /queue | 업데이트 큐 |
|
||||||
|
| UserList.tsx | /users | 사용자 관리 |
|
||||||
|
| AiAnalysis.tsx | /ai | AI 분석 |
|
||||||
|
| Login.tsx | /login | 로그인 |
|
||||||
|
|
||||||
|
## 빌드 원칙
|
||||||
|
- Vite build → `backend/src/main/resources/static/` 복사 → `mvn package` (단일 JAR)
|
||||||
|
- `vite.config.ts` outDir: `../backend/src/main/resources/static`
|
||||||
|
- API 호출: `src/api/client.ts` (axios 기반, JWT Bearer 헤더)
|
||||||
29
.claude/agents/esn-qa.md
Normal file
29
.claude/agents/esn-qa.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
name: esn-qa
|
||||||
|
description: GUARDiA ESN QA 에이전트. 백엔드 API 응답 shape와 프론트엔드 호출을 대조 검증한다. 테넌트 격리, 알람 이력, HCore 상태, 펌웨어 배포 경계면의 필드 불일치·인증 흐름·민감정보(SSH·자격증명) 노출·RBAC 가드 누락·AI 온프레미스 준수를 검증한다.
|
||||||
|
metadata:
|
||||||
|
type: agent
|
||||||
|
tools: [Read, Write, Edit, Bash, Glob, Grep]
|
||||||
|
---
|
||||||
|
|
||||||
|
# ESN QA 에이전트
|
||||||
|
|
||||||
|
## 검증 항목
|
||||||
|
1. **테넌트 격리** — tenant_code 필터 누락 시 크로스 테넌트 데이터 노출 여부
|
||||||
|
2. **인증 흐름** — JWT 만료·무효 토큰 시 401 반환 확인
|
||||||
|
3. **민감정보 제외** — API 응답에 `passwordHash`, SSH 자격증명 필드 미포함
|
||||||
|
4. **AI 온프레미스** — Ollama 외 외부 AI API 호출 없음
|
||||||
|
5. **알람 통계** — Dashboard 집계 쿼리 정합성
|
||||||
|
6. **HCore 상태** — 연결/비연결 상태 전이 정확성
|
||||||
|
|
||||||
|
## 테스트 방법
|
||||||
|
```bash
|
||||||
|
# 서버에서 ESN API 테스트
|
||||||
|
curl -X POST http://localhost:8016/api/auth/login \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"username":"admin","password":"admin123"}'
|
||||||
|
|
||||||
|
# 테넌트 격리 검증
|
||||||
|
curl -H 'Authorization: Bearer {token}' \
|
||||||
|
'http://localhost:8016/api/stores?tenantCode=LGINNOTEK'
|
||||||
|
```
|
||||||
118
.claude/skills/guardia-esn-orchestrator/SKILL.md
Normal file
118
.claude/skills/guardia-esn-orchestrator/SKILL.md
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# guardia-esn-orchestrator
|
||||||
|
|
||||||
|
## 역할
|
||||||
|
레거시 ESL(전자 가격표) 관리 시스템(`workspace/esn/` 6개 프로젝트 — Spring Boot 1.5 + Java 8 + Thymeleaf)을 Spring Boot 3.5 + Java 17 + React 19 + PostgreSQL 단일 플랫폼으로 통합한 `workspace/guardia-esn/` 프로젝트를 운영·확장하는 오케스트레이터 스킬.
|
||||||
|
|
||||||
|
## 트리거
|
||||||
|
"guardia-esn", "ESN 통합", "ESL 플랫폼", "전자가격표", "HCore", "POS 변환", "PosCvt", "ESN 알람", "ESL 템플릿", "업데이트 큐", "다시 실행", "업데이트" 요청 시 이 스킬을 사용하라.
|
||||||
|
|
||||||
|
## 프로젝트 정보
|
||||||
|
- **위치:** `C:\GUARDiA\workspace\guardia-esn\`
|
||||||
|
- **포트:** backend:8016 (단일 JAR, Vite 번들)
|
||||||
|
- **DB:** `guardia_esn_db` / `guardia_esn_user` / `guardia_esn_pass2026`
|
||||||
|
- **패키지:** `com.zioinfo.esn`
|
||||||
|
- **멀티테넌트:** `tenant_code VARCHAR(20)` — LGINNOTEK / LGIT / EMART / ZIOINFO
|
||||||
|
- **Gitea:** `http://git.zioinfo.co.kr/zio/guardia-esn`
|
||||||
|
- **서버:** `101.79.17.164` `/opt/guardia-esn/`
|
||||||
|
|
||||||
|
## 레거시 소스 (C:\GUARDiA\workspace\esn\)
|
||||||
|
| 레거시 프로젝트 | 용도 | 현대화 대상 |
|
||||||
|
|----------------|------|------------|
|
||||||
|
| ESN_WEB_ZIOINFO | ZIOINFO ESL 웹 관리 (Thymeleaf) | → React 19 관리자 웹 |
|
||||||
|
| ESN_WEB_LGInnotek | LG Innotek ESL 웹 (Thymeleaf) | → 멀티테넌트 LGINNOTEK |
|
||||||
|
| ESN_DAEMON_EMART | 이마트 ESL 데몬 | → EMART 테넌트 |
|
||||||
|
| ESN_DAEMON_LGIT | LG IT ESL 데몬 | → LGIT 테넌트 |
|
||||||
|
| esn | ESN 코어 (Maven) | → 도메인 모델 |
|
||||||
|
| ESL_AUTO_UPDATE | 펌웨어 자동 업데이트 | → firmware 도메인 |
|
||||||
|
| restapi_lgit | LGIT REST API | → API 통합 |
|
||||||
|
| UCS_LGU | UCS 시스템 | → HCore 통합 |
|
||||||
|
|
||||||
|
## 아키텍처
|
||||||
|
```
|
||||||
|
레거시 ESL 8개 프로젝트 (Spring Boot 1.5 + Java 8 + Thymeleaf)
|
||||||
|
↓ 현대화
|
||||||
|
guardia-esn (Spring Boot 3.5 + Java 17 + React 19 + PostgreSQL)
|
||||||
|
backend:8016 ←→ frontend static bundle (단일 JAR)
|
||||||
|
↕
|
||||||
|
ITSM:9001 · Ollama:11434
|
||||||
|
```
|
||||||
|
|
||||||
|
## 도메인 개념
|
||||||
|
| 도메인 | 테이블 | 설명 |
|
||||||
|
|--------|--------|------|
|
||||||
|
| Tenant | esn_tenants | 멀티테넌트 — LGINNOTEK·LGIT·EMART·ZIOINFO |
|
||||||
|
| StoreGroup | esn_store_groups | 매장 그룹 |
|
||||||
|
| Store | esn_stores | 매장 (tenantCode 격리) |
|
||||||
|
| Template | esn_templates | ESL 표시 템플릿 |
|
||||||
|
| PosCvt | esn_pos_cvt | POS→ESL 가격 변환 데이터 |
|
||||||
|
| Alarm / AlarmHistory | esn_alarms | 장치 알람·이력 |
|
||||||
|
| HCore | esn_hcores | 게이트웨이/허브 장치 |
|
||||||
|
| WorkHistory | esn_work_history | 작업 이력 |
|
||||||
|
| Firmware | esn_firmware | 장치 펌웨어 |
|
||||||
|
| User | esn_users | 사용자 (멀티테넌트 RBAC) |
|
||||||
|
| Product | esn_products | 상품 가격 |
|
||||||
|
| TagBinding | esn_tag_bindings | ESL 태그↔상품 바인딩 |
|
||||||
|
| UpdateQueue | esn_update_queue | 업데이트 큐 |
|
||||||
|
|
||||||
|
## 에이전트 팀
|
||||||
|
| 에이전트 | 역할 |
|
||||||
|
|----------|------|
|
||||||
|
| `esn-backend-dev` | Spring Boot 3.5 백엔드 — 도메인·매퍼·서비스·컨트롤러 |
|
||||||
|
| `esn-frontend-dev` | React 19 + Vite + TypeScript — 관리자 웹 |
|
||||||
|
| `esn-devops-dev` | Gitea·deploy·Jenkinsfile·setup·홈페이지 |
|
||||||
|
| `esn-qa` | API 통합 테스트 + 크로스 경계면 검증 |
|
||||||
|
|
||||||
|
## 구현 원칙 (GUARDiA 불변 규칙)
|
||||||
|
1. `@MapperScan(annotationClass = Mapper.class)` — basePackages 아님
|
||||||
|
2. Hikari `maximum-pool-size=3` — 공유 PG 서버
|
||||||
|
3. repos에 `fresh git init` — 모노레포 .git 상속 금지
|
||||||
|
4. 단일 JAR: 프론트 빌드 → `backend/src/main/resources/static/` → mvn package
|
||||||
|
5. Ollama 폴백: 연결 실패 시 기본 응답 반환 (예외 던지지 않음)
|
||||||
|
6. admin 시드: bcrypt `$2a$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LnCWh7Vz.Xm` (admin/admin123)
|
||||||
|
7. API 응답에 `password_hash`, 자격증명 필드 절대 미포함
|
||||||
|
8. AES-256-GCM 자격증명 암호화 저장
|
||||||
|
9. SecurityConfig에 static 리소스 `permitAll` 필수 (SPA 403 방지)
|
||||||
|
|
||||||
|
## 파일 구조
|
||||||
|
```
|
||||||
|
workspace/guardia-esn/
|
||||||
|
├── .claude/
|
||||||
|
│ ├── agents/
|
||||||
|
│ │ ├── esn-backend-dev.md
|
||||||
|
│ │ ├── esn-frontend-dev.md
|
||||||
|
│ │ ├── esn-devops-dev.md
|
||||||
|
│ │ └── esn-qa.md
|
||||||
|
│ └── skills/guardia-esn-orchestrator/SKILL.md
|
||||||
|
├── CLAUDE.md
|
||||||
|
├── Jenkinsfile
|
||||||
|
├── backend/
|
||||||
|
│ ├── pom.xml
|
||||||
|
│ └── src/main/
|
||||||
|
│ ├── java/com/zioinfo/esn/
|
||||||
|
│ │ ├── EsnApplication.java (@MapperScan(annotationClass=Mapper.class))
|
||||||
|
│ │ ├── auth/
|
||||||
|
│ │ ├── common/
|
||||||
|
│ │ ├── config/ (SecurityConfig, WebConfig, CryptoUtil, OllamaClient)
|
||||||
|
│ │ ├── domain/ (VO 클래스)
|
||||||
|
│ │ ├── mapper/ (@Mapper 인터페이스)
|
||||||
|
│ │ ├── service/
|
||||||
|
│ │ └── controller/
|
||||||
|
│ └── resources/
|
||||||
|
│ ├── application.yml
|
||||||
|
│ ├── mapper/*.xml
|
||||||
|
│ ├── db/schema.sql
|
||||||
|
│ └── static/ (Vite 빌드 산출물)
|
||||||
|
└── frontend/
|
||||||
|
├── package.json
|
||||||
|
├── vite.config.ts
|
||||||
|
└── src/
|
||||||
|
├── App.tsx
|
||||||
|
├── api/client.ts
|
||||||
|
├── components/
|
||||||
|
└── pages/ (15개 관리자 화면)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 변경 이력
|
||||||
|
| 날짜 | 내용 | 사유 |
|
||||||
|
|------|------|------|
|
||||||
|
| 2026-06-16 | 초기 하네스 구성 + 통합 소스 생성 | 레거시 ESN 8개 프로젝트 → 단일 현대화 플랫폼 |
|
||||||
121
CLAUDE.md
Normal file
121
CLAUDE.md
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
# GUARDiA ESN — ESL 통합 관리 플랫폼
|
||||||
|
|
||||||
|
> 레거시 ESL(전자 가격표) 8개 Spring Boot 1.5 프로젝트를 단일 현대화 플랫폼으로 통합한 프로젝트
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 프로젝트 개요
|
||||||
|
|
||||||
|
`C:\GUARDiA\workspace\esn\` 의 레거시 ESN 시스템(ESN_WEB_ZIOINFO·ESN_WEB_LGInnotek·ESN_DAEMON_EMART·ESN_DAEMON_LGIT·esn·ESL_AUTO_UPDATE·restapi_lgit·UCS_LGU)을
|
||||||
|
**Spring Boot 3.5 + Java 17 + React 19 + PostgreSQL** 단일 플랫폼으로 재구현.
|
||||||
|
|
||||||
|
멀티테넌트 (TENANT_A / TENANT_B / EMART / ZIOINFO), Ollama 온프레미스 AI 알람 분석·POS 자동 분류.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 기술 스택
|
||||||
|
|
||||||
|
| 레이어 | 기술 |
|
||||||
|
|--------|------|
|
||||||
|
| Backend | Spring Boot 3.5 · Java 17 · MyBatis 3.0.3 |
|
||||||
|
| Database | PostgreSQL (`guardia_esn_db` / `guardia_esn_user`) |
|
||||||
|
| Frontend | React 19 · Vite · TypeScript · Tailwind CSS |
|
||||||
|
| AI | Ollama 온프레미스 (llama3, llava) |
|
||||||
|
| 인증 | Spring Security · JWT (jjwt 0.12.6) |
|
||||||
|
| 배포 | 단일 JAR (Vite 번들 → static/) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 실행 명령어
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 프론트엔드 빌드 (필수 — 백엔드 static/ 번들)
|
||||||
|
cd frontend && npm install && npm run build
|
||||||
|
|
||||||
|
# 백엔드 빌드 + 실행
|
||||||
|
cd backend && mvn clean package -DskipTests
|
||||||
|
java -jar target/zioinfo-esn-1.0.0.jar \
|
||||||
|
--spring.datasource.url=jdbc:postgresql://localhost:5432/guardia_esn_db \
|
||||||
|
--spring.datasource.username=guardia_esn_user \
|
||||||
|
--spring.datasource.password=guardia_esn_pass2026 \
|
||||||
|
--server.port=8016
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 환경변수
|
||||||
|
|
||||||
|
| 변수 | 기본값 | 설명 |
|
||||||
|
|------|--------|------|
|
||||||
|
| DB_URL | jdbc:postgresql://localhost:5432/esn_db | 운영에서는 guardia_esn_db |
|
||||||
|
| DB_USER | esn_user | 운영에서는 guardia_esn_user |
|
||||||
|
| DB_PASS | esn_pass2026 | 운영에서는 guardia_esn_pass2026 |
|
||||||
|
| OLLAMA_URL | http://localhost:11434 | Ollama 서버 |
|
||||||
|
| JWT_SECRET | ZioInfoEsnJwtSecret... | JWT 서명 키 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 디렉터리 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
workspace/guardia-esn/
|
||||||
|
├── .claude/
|
||||||
|
│ ├── agents/ (에이전트 4종)
|
||||||
|
│ └── skills/guardia-esn-orchestrator/SKILL.md
|
||||||
|
├── CLAUDE.md ← 이 파일
|
||||||
|
├── Jenkinsfile
|
||||||
|
├── backend/
|
||||||
|
│ ├── pom.xml
|
||||||
|
│ └── src/main/
|
||||||
|
│ ├── java/com/zioinfo/esn/ (67개 Java 파일)
|
||||||
|
│ └── resources/
|
||||||
|
│ ├── application.yml
|
||||||
|
│ ├── db/schema.sql (13개 테이블)
|
||||||
|
│ └── mapper/*.xml (15개 MyBatis XML)
|
||||||
|
└── frontend/
|
||||||
|
└── src/
|
||||||
|
├── App.tsx
|
||||||
|
├── api/client.ts
|
||||||
|
├── components/ (Layout, Sidebar, Header...)
|
||||||
|
└── pages/ (15개 관리자 화면)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 도메인 (13개)
|
||||||
|
|
||||||
|
| 도메인 | 테이블 | 설명 |
|
||||||
|
|--------|--------|------|
|
||||||
|
| Tenant | esn_tenants | 멀티테넌트 (LGINNOTEK·LGIT·EMART·ZIOINFO) |
|
||||||
|
| StoreGroup | esn_store_groups | 매장 그룹 |
|
||||||
|
| Store | esn_stores | 매장 (tenantCode 격리) |
|
||||||
|
| Template | esn_templates | ESL 표시 템플릿 |
|
||||||
|
| PosCvt | esn_pos_cvt | POS→ESL 가격 변환 |
|
||||||
|
| Alarm | esn_alarms | 장치 알람 |
|
||||||
|
| HCore | esn_hcores | 게이트웨이/허브 |
|
||||||
|
| WorkHistory | esn_work_history | 작업 이력 |
|
||||||
|
| Firmware | esn_firmware | 펌웨어 버전 |
|
||||||
|
| User | esn_users | 사용자 (RBAC) |
|
||||||
|
| Product | esn_products | 상품/가격 |
|
||||||
|
| TagBinding | esn_tag_bindings | ESL↔상품 바인딩 |
|
||||||
|
| UpdateQueue | esn_update_queue | 업데이트 큐 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 보안 제약 (불변)
|
||||||
|
|
||||||
|
1. `@MapperScan(annotationClass = Mapper.class)` — basePackages 절대 금지
|
||||||
|
2. Hikari `maximum-pool-size=3` — 공유 PG 서버 보호
|
||||||
|
3. 외부 AI API 절대 금지 — Ollama localhost:11434만 허용
|
||||||
|
4. API 응답에서 `passwordHash`, `ssh_*` 필드 완전 제외
|
||||||
|
5. SecurityConfig에 static 리소스 `permitAll` 필수 (SPA 403 방지)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 하네스 트리거
|
||||||
|
|
||||||
|
`guardia-esn-orchestrator` 스킬을 사용하라:
|
||||||
|
- ESN 기능 추가·수정
|
||||||
|
- 배포 작업
|
||||||
|
- 레거시 소스 분석·통합
|
||||||
|
- 알람·POS·HCore 관련 작업
|
||||||
65
Jenkinsfile
vendored
Normal file
65
Jenkinsfile
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
environment {
|
||||||
|
APP_NAME = 'guardia-esn'
|
||||||
|
DEPLOY_DIR = '/opt/guardia-esn'
|
||||||
|
SERVICE = 'guardia-esn'
|
||||||
|
JAR_NAME = 'zioinfo-esn-1.0.0.jar'
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build Frontend') {
|
||||||
|
steps {
|
||||||
|
dir('frontend') {
|
||||||
|
sh 'npm ci --legacy-peer-deps'
|
||||||
|
sh 'npm run build'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build Backend') {
|
||||||
|
steps {
|
||||||
|
dir('backend') {
|
||||||
|
sh 'mvn clean package -DskipTests -q'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deploy') {
|
||||||
|
steps {
|
||||||
|
sh """
|
||||||
|
sudo systemctl stop ${SERVICE} || true
|
||||||
|
sudo mkdir -p ${DEPLOY_DIR}
|
||||||
|
sudo cp backend/target/${JAR_NAME} ${DEPLOY_DIR}/app.jar
|
||||||
|
sudo systemctl start ${SERVICE}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Health Check') {
|
||||||
|
steps {
|
||||||
|
sh """
|
||||||
|
sleep 20
|
||||||
|
curl -sf http://localhost:8016/actuator/health | grep -q UP || exit 1
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
success {
|
||||||
|
echo "guardia-esn 배포 성공"
|
||||||
|
}
|
||||||
|
failure {
|
||||||
|
sh "sudo systemctl stop ${SERVICE} || true"
|
||||||
|
echo "guardia-esn 배포 실패"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
77
backend/pom.xml
Normal file
77
backend/pom.xml
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.5.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.zioinfo</groupId>
|
||||||
|
<artifactId>zioinfo-esn</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<name>zioinfo-esn</name>
|
||||||
|
<description>ESL(전자 가격표) 통합 관리 플랫폼 — 레거시 Spring Boot 1.5 현대화 (Spring Boot 3.5 + Java 17 + React 19)</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<jjwt.version>0.12.6</jjwt.version>
|
||||||
|
<mybatis.version>3.0.3</mybatis.version>
|
||||||
|
<postgresql.version>42.7.3</postgresql.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring Boot -->
|
||||||
|
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
|
||||||
|
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
|
||||||
|
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
|
||||||
|
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>
|
||||||
|
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mybatis.spring.boot</groupId>
|
||||||
|
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||||
|
<version>${mybatis.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- PostgreSQL -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>${postgresql.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JWT -->
|
||||||
|
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>${jjwt.version}</version></dependency>
|
||||||
|
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>${jjwt.version}</version><scope>runtime</scope></dependency>
|
||||||
|
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>${jjwt.version}</version><scope>runtime</scope></dependency>
|
||||||
|
|
||||||
|
<!-- Lombok -->
|
||||||
|
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
|
||||||
|
|
||||||
|
<!-- Test -->
|
||||||
|
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
<finalName>esn-1.0.0</finalName>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
30
backend/src/main/java/com/zioinfo/esn/EsnApplication.java
Normal file
30
backend/src/main/java/com/zioinfo/esn/EsnApplication.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package com.zioinfo.esn;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* zioinfo-esn ESL 통합 관리 플랫폼.
|
||||||
|
*
|
||||||
|
* <p>레거시 Spring Boot 1.5 / Java 8 ESL 시스템 6개를 Spring Boot 3.5 / Java 17 단일 플랫폼으로 재구현.
|
||||||
|
* 멀티테넌트(LGINNOTEK·LGIT·EMART·ZIOINFO), 매장/HCore 장치 관리, POS 가격 연동,
|
||||||
|
* 알람 모니터링, 펌웨어 관리, Ollama 온프레미스 AI 이상 감지.
|
||||||
|
*
|
||||||
|
* <p>보안 불변 규칙: 외부 AI API 절대 금지 (Ollama localhost:11434만 허용),
|
||||||
|
* 자격증명은 AES-256-GCM 암호화 저장, API 응답에서 password_hash 완전 제외.
|
||||||
|
*
|
||||||
|
* <p>CRITICAL: @MapperScan(annotationClass = Mapper.class) 패턴 사용 — basePackages 아님.
|
||||||
|
*/
|
||||||
|
@SpringBootApplication
|
||||||
|
@MapperScan(annotationClass = Mapper.class)
|
||||||
|
@EnableScheduling
|
||||||
|
@EnableAsync
|
||||||
|
public class EsnApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(EsnApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
package com.zioinfo.esn.auth;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/auth")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AuthController {
|
||||||
|
|
||||||
|
private final AuthService authService;
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ApiResponse<Map<String, String>> login(@RequestBody LoginRequest req) {
|
||||||
|
String token = authService.login(req.username(), req.password());
|
||||||
|
return ApiResponse.ok(Map.of("token", token, "type", "Bearer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/me")
|
||||||
|
public ApiResponse<Map<String, String>> me(@RequestHeader("Authorization") String header) {
|
||||||
|
String token = header.replace("Bearer ", "");
|
||||||
|
return ApiResponse.ok(authService.me(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/logout")
|
||||||
|
public ApiResponse<Void> logout() {
|
||||||
|
// JWT stateless — 클라이언트에서 토큰 삭제
|
||||||
|
return ApiResponse.ok("로그아웃 성공", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
record LoginRequest(String username, String password) {}
|
||||||
|
}
|
||||||
37
backend/src/main/java/com/zioinfo/esn/auth/AuthService.java
Normal file
37
backend/src/main/java/com/zioinfo/esn/auth/AuthService.java
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package com.zioinfo.esn.auth;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.auth.mapper.UserAuthMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AuthService {
|
||||||
|
|
||||||
|
private final UserAuthMapper userMapper;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final JwtUtil jwtUtil;
|
||||||
|
|
||||||
|
public String login(String username, String password) {
|
||||||
|
EsnUser user = userMapper.findByUsername(username);
|
||||||
|
if (user == null || !user.isActive()) {
|
||||||
|
throw new RuntimeException("ERR-AUTH-001: 존재하지 않거나 비활성 계정");
|
||||||
|
}
|
||||||
|
if (!passwordEncoder.matches(password, user.getPasswordHash())) {
|
||||||
|
throw new RuntimeException("ERR-AUTH-002: 비밀번호 불일치");
|
||||||
|
}
|
||||||
|
userMapper.updateLastLogin(username);
|
||||||
|
return jwtUtil.generate(username, user.getRole(), user.getTenantCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> me(String token) {
|
||||||
|
return Map.of(
|
||||||
|
"username", jwtUtil.getUsername(token),
|
||||||
|
"role", jwtUtil.getRole(token),
|
||||||
|
"tenant", jwtUtil.getTenant(token) != null ? jwtUtil.getTenant(token) : ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
backend/src/main/java/com/zioinfo/esn/auth/EsnUser.java
Normal file
21
backend/src/main/java/com/zioinfo/esn/auth/EsnUser.java
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package com.zioinfo.esn.auth;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class EsnUser {
|
||||||
|
private Long id;
|
||||||
|
private String tenantCode;
|
||||||
|
private String username;
|
||||||
|
@JsonIgnore
|
||||||
|
private String passwordHash; // API 응답 미노출
|
||||||
|
private String role; // ADMIN, MANAGER, USER
|
||||||
|
private String email;
|
||||||
|
private String phone;
|
||||||
|
private boolean active;
|
||||||
|
private LocalDateTime lastLoginAt;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
40
backend/src/main/java/com/zioinfo/esn/auth/JwtFilter.java
Normal file
40
backend/src/main/java/com/zioinfo/esn/auth/JwtFilter.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package com.zioinfo.esn.auth;
|
||||||
|
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class JwtFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private final JwtUtil jwtUtil;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
|
||||||
|
FilterChain chain) throws ServletException, IOException {
|
||||||
|
String header = req.getHeader("Authorization");
|
||||||
|
if (header != null && header.startsWith("Bearer ")) {
|
||||||
|
String token = header.substring(7);
|
||||||
|
if (jwtUtil.isValid(token)) {
|
||||||
|
String username = jwtUtil.getUsername(token);
|
||||||
|
String role = jwtUtil.getRole(token);
|
||||||
|
var auth = new UsernamePasswordAuthenticationToken(
|
||||||
|
username, null, List.of(new SimpleGrantedAuthority("ROLE_" + role))
|
||||||
|
);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chain.doFilter(req, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
backend/src/main/java/com/zioinfo/esn/auth/JwtUtil.java
Normal file
56
backend/src/main/java/com/zioinfo/esn/auth/JwtUtil.java
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package com.zioinfo.esn.auth;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.*;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class JwtUtil {
|
||||||
|
|
||||||
|
@Value("${guardia.jwt.secret:ZioInfoEsnJwtSecret2026VeryLongSecretKeyForHS256AlgorithmAtLeast256Bits}")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
@Value("${guardia.jwt.expiration:86400000}")
|
||||||
|
private long expirationMs;
|
||||||
|
|
||||||
|
private SecretKey key() {
|
||||||
|
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generate(String username, String role, String tenantCode) {
|
||||||
|
return Jwts.builder()
|
||||||
|
.subject(username)
|
||||||
|
.claim("role", role)
|
||||||
|
.claim("tenant", tenantCode)
|
||||||
|
.issuedAt(new Date())
|
||||||
|
.expiration(new Date(System.currentTimeMillis() + expirationMs))
|
||||||
|
.signWith(key())
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Claims parse(String token) {
|
||||||
|
return Jwts.parser().verifyWith(key()).build()
|
||||||
|
.parseSignedClaims(token).getPayload();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid(String token) {
|
||||||
|
try {
|
||||||
|
parse(token);
|
||||||
|
return true;
|
||||||
|
} catch (JwtException | IllegalArgumentException e) {
|
||||||
|
log.debug("JWT 검증 실패: {}", e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername(String token) { return parse(token).getSubject(); }
|
||||||
|
public String getRole(String token) { return parse(token).get("role", String.class); }
|
||||||
|
public String getTenant(String token) { return parse(token).get("tenant", String.class); }
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.zioinfo.esn.auth.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.auth.EsnUser;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface UserAuthMapper {
|
||||||
|
EsnUser findByUsername(@Param("username") String username);
|
||||||
|
int updateLastLogin(@Param("username") String username);
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package com.zioinfo.esn.common;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ApiResponse<T> {
|
||||||
|
private boolean success;
|
||||||
|
private String message;
|
||||||
|
private T data;
|
||||||
|
private LocalDateTime timestamp;
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> ok(T data) {
|
||||||
|
return new ApiResponse<>(true, "OK", data, LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> ok(String message, T data) {
|
||||||
|
return new ApiResponse<>(true, message, data, LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> fail(String message) {
|
||||||
|
return new ApiResponse<>(false, message, null, LocalDateTime.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
68
backend/src/main/java/com/zioinfo/esn/config/CryptoUtil.java
Normal file
68
backend/src/main/java/com/zioinfo/esn/config/CryptoUtil.java
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package com.zioinfo.esn.config;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.GCMParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES-256-GCM 자격증명 암호화 유틸리티.
|
||||||
|
* 보안 불변 규칙: 서버 자격증명은 암호화 DB에만 저장, API 응답에 절대 노출 금지.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class CryptoUtil {
|
||||||
|
|
||||||
|
private static final String ALGO = "AES/GCM/NoPadding";
|
||||||
|
private static final int GCM_IV_LEN = 12;
|
||||||
|
private static final int GCM_TAG_LEN = 128;
|
||||||
|
// 운영 시 환경변수로 주입 권장
|
||||||
|
private static final String KEY_HEX = "7a696f696e666f65736e6b657932303236736563726574";
|
||||||
|
|
||||||
|
private SecretKey secretKey() {
|
||||||
|
byte[] key = new byte[32];
|
||||||
|
byte[] src = KEY_HEX.getBytes(StandardCharsets.UTF_8);
|
||||||
|
System.arraycopy(src, 0, key, 0, Math.min(src.length, 32));
|
||||||
|
return new SecretKeySpec(key, "AES");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encrypt(String plain) {
|
||||||
|
if (plain == null) return null;
|
||||||
|
try {
|
||||||
|
byte[] iv = new byte[GCM_IV_LEN];
|
||||||
|
new SecureRandom().nextBytes(iv);
|
||||||
|
Cipher cipher = Cipher.getInstance(ALGO);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, secretKey(), new GCMParameterSpec(GCM_TAG_LEN, iv));
|
||||||
|
byte[] enc = cipher.doFinal(plain.getBytes(StandardCharsets.UTF_8));
|
||||||
|
byte[] result = new byte[iv.length + enc.length];
|
||||||
|
System.arraycopy(iv, 0, result, 0, iv.length);
|
||||||
|
System.arraycopy(enc, 0, result, iv.length, enc.length);
|
||||||
|
return Base64.getEncoder().encodeToString(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("암호화 실패", e);
|
||||||
|
throw new RuntimeException("CRYPTO_ERR", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String decrypt(String encrypted) {
|
||||||
|
if (encrypted == null) return null;
|
||||||
|
try {
|
||||||
|
byte[] raw = Base64.getDecoder().decode(encrypted);
|
||||||
|
byte[] iv = new byte[GCM_IV_LEN];
|
||||||
|
System.arraycopy(raw, 0, iv, 0, GCM_IV_LEN);
|
||||||
|
Cipher cipher = Cipher.getInstance(ALGO);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, secretKey(), new GCMParameterSpec(GCM_TAG_LEN, iv));
|
||||||
|
byte[] dec = cipher.doFinal(raw, GCM_IV_LEN, raw.length - GCM_IV_LEN);
|
||||||
|
return new String(dec, StandardCharsets.UTF_8);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("복호화 실패", e);
|
||||||
|
throw new RuntimeException("CRYPTO_ERR", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
package com.zioinfo.esn.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ollama 온프레미스 AI 클라이언트.
|
||||||
|
* 보안 불변 규칙: 외부 AI API 절대 금지 — Ollama localhost:11434만 허용.
|
||||||
|
* 연결 실패 시 폴백 응답 반환(예외 미전파).
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class OllamaClient {
|
||||||
|
|
||||||
|
@Value("${guardia.ollama-url:http://localhost:11434}")
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
@Value("${guardia.ollama-text-model:llama3}")
|
||||||
|
private String model;
|
||||||
|
|
||||||
|
private final HttpClient http = HttpClient.newBuilder()
|
||||||
|
.connectTimeout(Duration.ofSeconds(5))
|
||||||
|
.build();
|
||||||
|
private final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
public String chat(String prompt) {
|
||||||
|
return chat(prompt, "ESN AI 이상 분석 전문가 역할입니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String chat(String prompt, String systemPrompt) {
|
||||||
|
try {
|
||||||
|
var body = Map.of(
|
||||||
|
"model", model,
|
||||||
|
"messages", new Object[]{
|
||||||
|
Map.of("role", "system", "content", systemPrompt),
|
||||||
|
Map.of("role", "user", "content", prompt)
|
||||||
|
},
|
||||||
|
"stream", false
|
||||||
|
);
|
||||||
|
String json = mapper.writeValueAsString(body);
|
||||||
|
HttpRequest req = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(baseUrl + "/api/chat"))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(json))
|
||||||
|
.timeout(Duration.ofSeconds(30))
|
||||||
|
.build();
|
||||||
|
HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString());
|
||||||
|
if (resp.statusCode() == 200) {
|
||||||
|
var res = mapper.readTree(resp.body());
|
||||||
|
return res.path("message").path("content").asText("분석 결과를 생성했습니다.");
|
||||||
|
}
|
||||||
|
return fallback(prompt);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Ollama 연결 실패 — 폴백 응답 반환: {}", e.getMessage());
|
||||||
|
return fallback(prompt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String fallback(String prompt) {
|
||||||
|
if (prompt.toLowerCase().contains("alarm") || prompt.contains("알람")) {
|
||||||
|
return "알람 분석: 장치 연결 상태를 확인하고 네트워크 환경을 점검하세요. 지속 발생 시 현장 엔지니어 파견이 필요합니다.";
|
||||||
|
}
|
||||||
|
if (prompt.toLowerCase().contains("pos") || prompt.contains("가격")) {
|
||||||
|
return "POS 데이터 분류: 정상 가격 변환 데이터입니다. 이상 감지된 항목은 수동 검토가 필요합니다.";
|
||||||
|
}
|
||||||
|
return "AI 분석 결과: 현재 시스템 상태를 검토하고 운영 매뉴얼을 참조하세요.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
package com.zioinfo.esn.config;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.auth.JwtFilter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Security 6 — JWT 무상태 인증 + 멀티테넌트 RBAC.
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final JwtFilter jwtFilter;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.csrf(csrf -> csrf.disable())
|
||||||
|
.cors(cors -> {})
|
||||||
|
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
// 인증 불필요
|
||||||
|
.requestMatchers("/api/auth/**").permitAll()
|
||||||
|
.requestMatchers("/actuator/health").permitAll()
|
||||||
|
// 정적 리소스 (React SPA)
|
||||||
|
.requestMatchers(HttpMethod.GET, "/", "/index.html", "/assets/**", "/*.js", "/*.css", "/*.ico").permitAll()
|
||||||
|
|
||||||
|
// 관리자 전용
|
||||||
|
.requestMatchers("/api/tenants/**").hasRole("ADMIN")
|
||||||
|
.requestMatchers("/api/users/**").hasAnyRole("ADMIN", "MANAGER")
|
||||||
|
.requestMatchers("/api/firmware/**").hasAnyRole("ADMIN", "MANAGER")
|
||||||
|
.requestMatchers(HttpMethod.DELETE, "/api/**").hasAnyRole("ADMIN", "MANAGER")
|
||||||
|
.requestMatchers(HttpMethod.POST, "/api/**").hasAnyRole("ADMIN", "MANAGER", "USER")
|
||||||
|
.requestMatchers(HttpMethod.PUT, "/api/**").hasAnyRole("ADMIN", "MANAGER", "USER")
|
||||||
|
|
||||||
|
// 그 외 GET은 인증 사용자 허용
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||||
|
return config.getAuthenticationManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
23
backend/src/main/java/com/zioinfo/esn/config/WebConfig.java
Normal file
23
backend/src/main/java/com/zioinfo/esn/config/WebConfig.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package com.zioinfo.esn.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class WebConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsFilter corsFilter() {
|
||||||
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
|
config.addAllowedOriginPattern("*");
|
||||||
|
config.addAllowedMethod("*");
|
||||||
|
config.addAllowedHeader("*");
|
||||||
|
config.setAllowCredentials(true);
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", config);
|
||||||
|
return new CorsFilter(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.config.OllamaClient;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/ai")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AiController {
|
||||||
|
|
||||||
|
private final OllamaClient ollama;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 알람 AI 이상 분석 — Ollama 온프레미스만 허용.
|
||||||
|
*/
|
||||||
|
@PostMapping("/analyze-alarm")
|
||||||
|
public ApiResponse<Map<String, String>> analyzeAlarm(@RequestBody Map<String, Object> body) {
|
||||||
|
String alarmType = (String) body.getOrDefault("alarmType", "UNKNOWN");
|
||||||
|
String message = (String) body.getOrDefault("message", "");
|
||||||
|
String severity = (String) body.getOrDefault("severity", "LOW");
|
||||||
|
|
||||||
|
String prompt = String.format(
|
||||||
|
"ESL 장치 알람 분석:\n유형: %s\n심각도: %s\n메시지: %s\n\n" +
|
||||||
|
"원인 분석 및 조치 방안을 3줄 이내로 간결하게 제시하세요.",
|
||||||
|
alarmType, severity, message
|
||||||
|
);
|
||||||
|
|
||||||
|
String result = ollama.chat(prompt,
|
||||||
|
"당신은 ESL(전자 가격표) 시스템 전문 AI 엔지니어입니다. 알람을 분석하고 실용적인 조치를 제안하세요.");
|
||||||
|
|
||||||
|
return ApiResponse.ok(Map.of("analysis", result, "alarmType", alarmType, "severity", severity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POS 데이터 AI 분류 — 이상 데이터 자동 감지.
|
||||||
|
*/
|
||||||
|
@PostMapping("/classify-pos")
|
||||||
|
public ApiResponse<Map<String, String>> classifyPos(@RequestBody Map<String, Object> body) {
|
||||||
|
String productCode = (String) body.getOrDefault("productCode", "");
|
||||||
|
String price = String.valueOf(body.getOrDefault("price", "0"));
|
||||||
|
String salePrice = String.valueOf(body.getOrDefault("salePrice", "0"));
|
||||||
|
|
||||||
|
String prompt = String.format(
|
||||||
|
"POS 가격 데이터 검증:\n상품코드: %s\n정가: %s\n판매가: %s\n\n" +
|
||||||
|
"이 데이터가 정상인지 이상(오류/이상값)인지 판단하고, 분류(NORMAL/ABNORMAL)와 이유를 2줄 이내로 제시하세요.",
|
||||||
|
productCode, price, salePrice
|
||||||
|
);
|
||||||
|
|
||||||
|
String result = ollama.chat(prompt,
|
||||||
|
"당신은 POS 데이터 품질 관리 AI입니다. 가격 데이터의 이상을 감지하세요.");
|
||||||
|
|
||||||
|
String classification = result.toUpperCase().contains("ABNORMAL") ? "ABNORMAL" : "NORMAL";
|
||||||
|
|
||||||
|
return ApiResponse.ok(Map.of(
|
||||||
|
"classification", classification,
|
||||||
|
"analysis", result,
|
||||||
|
"productCode", productCode
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 일반 AI 채팅 — ESN 운영 관련 질문.
|
||||||
|
*/
|
||||||
|
@PostMapping("/chat")
|
||||||
|
public ApiResponse<Map<String, String>> chat(@RequestBody Map<String, String> body) {
|
||||||
|
String message = body.getOrDefault("message", "");
|
||||||
|
String result = ollama.chat(message, "ESL 전자 가격표 통합 관리 플랫폼 운영 전문가입니다.");
|
||||||
|
return ApiResponse.ok(Map.of("response", result));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.domain.AlarmVo;
|
||||||
|
import com.zioinfo.esn.service.AlarmService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/alarms")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AlarmController {
|
||||||
|
private final AlarmService service;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<List<AlarmVo>> list(
|
||||||
|
@RequestParam(required = false) String tenantCode,
|
||||||
|
@RequestParam(required = false) Long storeId,
|
||||||
|
@RequestParam(required = false) String severity,
|
||||||
|
@RequestParam(required = false) String status) {
|
||||||
|
return ApiResponse.ok(service.list(tenantCode, storeId, severity, status));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<AlarmVo> get(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(service.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<AlarmVo> create(@RequestBody AlarmVo v) {
|
||||||
|
return ApiResponse.ok("알람 생성 완료", service.create(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResponse<AlarmVo> update(@PathVariable Long id, @RequestBody AlarmVo v) {
|
||||||
|
return ApiResponse.ok("알람 수정 완료", service.update(id, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}/resolve")
|
||||||
|
public ApiResponse<Void> resolve(@PathVariable Long id,
|
||||||
|
@RequestBody Map<String, String> body,
|
||||||
|
Authentication auth) {
|
||||||
|
String resolvedBy = auth != null ? auth.getName() : "system";
|
||||||
|
service.resolve(id, resolvedBy, body.getOrDefault("resolution", ""));
|
||||||
|
return ApiResponse.ok("알람 해결 완료", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResponse<Void> delete(@PathVariable Long id) {
|
||||||
|
service.delete(id);
|
||||||
|
return ApiResponse.ok("알람 삭제 완료", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.domain.DashboardVo;
|
||||||
|
import com.zioinfo.esn.mapper.DashboardMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/dashboard")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DashboardController {
|
||||||
|
private final DashboardMapper mapper;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<DashboardVo> summary(@RequestParam(required = false) String tenantCode) {
|
||||||
|
return ApiResponse.ok(mapper.getSummary(tenantCode));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.domain.FirmwareVo;
|
||||||
|
import com.zioinfo.esn.service.FirmwareService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/firmware")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class FirmwareController {
|
||||||
|
private final FirmwareService service;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<List<FirmwareVo>> list(
|
||||||
|
@RequestParam(required = false) String tenantCode,
|
||||||
|
@RequestParam(required = false) String deviceType) {
|
||||||
|
return ApiResponse.ok(service.list(tenantCode, deviceType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<FirmwareVo> get(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(service.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<FirmwareVo> create(@RequestBody FirmwareVo v) {
|
||||||
|
return ApiResponse.ok("펌웨어 등록 완료", service.create(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResponse<FirmwareVo> update(@PathVariable Long id, @RequestBody FirmwareVo v) {
|
||||||
|
return ApiResponse.ok("펌웨어 수정 완료", service.update(id, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResponse<Void> delete(@PathVariable Long id) {
|
||||||
|
service.delete(id);
|
||||||
|
return ApiResponse.ok("펌웨어 삭제 완료", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.domain.HCoreVo;
|
||||||
|
import com.zioinfo.esn.service.HCoreService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/hcore")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class HCoreController {
|
||||||
|
private final HCoreService service;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<List<HCoreVo>> list(
|
||||||
|
@RequestParam(required = false) String tenantCode,
|
||||||
|
@RequestParam(required = false) Long storeId,
|
||||||
|
@RequestParam(required = false) String deviceType,
|
||||||
|
@RequestParam(required = false) String status) {
|
||||||
|
return ApiResponse.ok(service.list(tenantCode, storeId, deviceType, status));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<HCoreVo> get(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(service.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<HCoreVo> create(@RequestBody HCoreVo v) {
|
||||||
|
return ApiResponse.ok("장치 등록 완료", service.create(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResponse<HCoreVo> update(@PathVariable Long id, @RequestBody HCoreVo v) {
|
||||||
|
return ApiResponse.ok("장치 수정 완료", service.update(id, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}/status")
|
||||||
|
public ApiResponse<Void> updateStatus(@PathVariable Long id, @RequestBody Map<String, String> body) {
|
||||||
|
service.updateStatus(id, body.getOrDefault("status", "OFFLINE"));
|
||||||
|
return ApiResponse.ok("상태 업데이트 완료", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResponse<Void> delete(@PathVariable Long id) {
|
||||||
|
service.delete(id);
|
||||||
|
return ApiResponse.ok("장치 삭제 완료", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.domain.PosCvtVo;
|
||||||
|
import com.zioinfo.esn.service.PosCvtService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/pos-cvt")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PosCvtController {
|
||||||
|
private final PosCvtService service;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<List<PosCvtVo>> list(
|
||||||
|
@RequestParam(required = false) String tenantCode,
|
||||||
|
@RequestParam(required = false) Long storeId,
|
||||||
|
@RequestParam(required = false) String status,
|
||||||
|
@RequestParam(required = false) String keyword) {
|
||||||
|
return ApiResponse.ok(service.list(tenantCode, storeId, status, keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<PosCvtVo> get(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(service.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}/process")
|
||||||
|
public ApiResponse<Integer> process(@PathVariable Long id) {
|
||||||
|
int queued = service.process(id);
|
||||||
|
return ApiResponse.ok("처리 완료 (태그 큐 " + queued + "건)", queued);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}/ignore")
|
||||||
|
public ApiResponse<Void> ignore(@PathVariable Long id) {
|
||||||
|
service.ignore(id);
|
||||||
|
return ApiResponse.ok("무시 처리 완료", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}/error")
|
||||||
|
public ApiResponse<Void> error(@PathVariable Long id, @RequestBody Map<String, String> body) {
|
||||||
|
service.markError(id, body.getOrDefault("message", "처리 오류"));
|
||||||
|
return ApiResponse.ok("오류 처리 완료", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.domain.ProductVo;
|
||||||
|
import com.zioinfo.esn.service.ProductService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/products")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ProductController {
|
||||||
|
private final ProductService service;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<List<ProductVo>> list(
|
||||||
|
@RequestParam(required = false) String tenantCode,
|
||||||
|
@RequestParam(required = false) Long storeId,
|
||||||
|
@RequestParam(required = false) String keyword) {
|
||||||
|
return ApiResponse.ok(service.list(tenantCode, storeId, keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<ProductVo> get(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(service.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<ProductVo> create(@RequestBody ProductVo v) {
|
||||||
|
return ApiResponse.ok("상품 등록 완료", service.create(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResponse<ProductVo> update(@PathVariable Long id, @RequestBody ProductVo v) {
|
||||||
|
return ApiResponse.ok("상품 수정 완료", service.update(id, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResponse<Void> delete(@PathVariable Long id) {
|
||||||
|
service.delete(id);
|
||||||
|
return ApiResponse.ok("상품 삭제 완료", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.domain.StoreVo;
|
||||||
|
import com.zioinfo.esn.service.StoreService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/stores")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class StoreController {
|
||||||
|
private final StoreService service;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<List<StoreVo>> list(
|
||||||
|
@RequestParam(required = false) String tenantCode,
|
||||||
|
@RequestParam(required = false) Long storeGroupId,
|
||||||
|
@RequestParam(required = false) String keyword) {
|
||||||
|
return ApiResponse.ok(service.list(tenantCode, storeGroupId, keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<StoreVo> get(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(service.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<StoreVo> create(@RequestBody StoreVo v) {
|
||||||
|
return ApiResponse.ok("매장 생성 완료", service.create(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResponse<StoreVo> update(@PathVariable Long id, @RequestBody StoreVo v) {
|
||||||
|
return ApiResponse.ok("매장 수정 완료", service.update(id, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResponse<Void> delete(@PathVariable Long id) {
|
||||||
|
service.delete(id);
|
||||||
|
return ApiResponse.ok("매장 삭제 완료", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.domain.StoreGroupVo;
|
||||||
|
import com.zioinfo.esn.service.StoreGroupService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/store-groups")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class StoreGroupController {
|
||||||
|
private final StoreGroupService service;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<List<StoreGroupVo>> list(@RequestParam(required = false) String tenantCode) {
|
||||||
|
return ApiResponse.ok(service.list(tenantCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<StoreGroupVo> get(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(service.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<StoreGroupVo> create(@RequestBody StoreGroupVo v) {
|
||||||
|
return ApiResponse.ok("매장그룹 생성 완료", service.create(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResponse<StoreGroupVo> update(@PathVariable Long id, @RequestBody StoreGroupVo v) {
|
||||||
|
return ApiResponse.ok("매장그룹 수정 완료", service.update(id, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResponse<Void> delete(@PathVariable Long id) {
|
||||||
|
service.delete(id);
|
||||||
|
return ApiResponse.ok("매장그룹 삭제 완료", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.domain.TagBindingVo;
|
||||||
|
import com.zioinfo.esn.service.TagBindingService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* e-paper ESL 태그 ↔ 선반 위치 ↔ 상품 바인딩 관리
|
||||||
|
* 핵심 플로우: 태그 스캔 → 상품 선택 → 위치 지정 → bind() → 가격 변경 시 자동 큐
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/tag-binding")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TagBindingController {
|
||||||
|
private final TagBindingService service;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<List<TagBindingVo>> list(
|
||||||
|
@RequestParam(required = false) String tenantCode,
|
||||||
|
@RequestParam(required = false) Long storeId,
|
||||||
|
@RequestParam(required = false) String bindStatus,
|
||||||
|
@RequestParam(required = false) String keyword) {
|
||||||
|
return ApiResponse.ok(service.list(tenantCode, storeId, bindStatus, keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<TagBindingVo> get(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(service.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/device/{deviceId}")
|
||||||
|
public ApiResponse<TagBindingVo> getByDevice(@PathVariable String deviceId) {
|
||||||
|
return ApiResponse.ok(service.getByDeviceId(deviceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 태그에 상품+위치 바인딩 */
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<TagBindingVo> bind(@RequestBody TagBindingVo vo) {
|
||||||
|
return ApiResponse.ok(service.bind(vo));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResponse<TagBindingVo> update(@PathVariable Long id, @RequestBody TagBindingVo vo) {
|
||||||
|
vo.setId(id);
|
||||||
|
return ApiResponse.ok(service.bind(vo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 태그 언바인딩 (선반에서 태그 제거 시) */
|
||||||
|
@PutMapping("/{id}/unbind")
|
||||||
|
public ApiResponse<Void> unbind(@PathVariable Long id) {
|
||||||
|
service.unbind(id);
|
||||||
|
return ApiResponse.ok("언바인딩 완료", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.domain.TemplateVo;
|
||||||
|
import com.zioinfo.esn.service.TemplateService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/templates")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TemplateController {
|
||||||
|
private final TemplateService service;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<List<TemplateVo>> list(
|
||||||
|
@RequestParam(required = false) String tenantCode,
|
||||||
|
@RequestParam(required = false) String templateType) {
|
||||||
|
return ApiResponse.ok(service.list(tenantCode, templateType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<TemplateVo> get(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(service.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<TemplateVo> create(@RequestBody TemplateVo v) {
|
||||||
|
return ApiResponse.ok("템플릿 생성 완료", service.create(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResponse<TemplateVo> update(@PathVariable Long id, @RequestBody TemplateVo v) {
|
||||||
|
return ApiResponse.ok("템플릿 수정 완료", service.update(id, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResponse<Void> delete(@PathVariable Long id) {
|
||||||
|
service.delete(id);
|
||||||
|
return ApiResponse.ok("템플릿 삭제 완료", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.domain.TenantVo;
|
||||||
|
import com.zioinfo.esn.service.TenantService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/tenants")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TenantController {
|
||||||
|
private final TenantService service;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<List<TenantVo>> list() {
|
||||||
|
return ApiResponse.ok(service.list());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<TenantVo> get(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(service.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<TenantVo> create(@RequestBody TenantVo v) {
|
||||||
|
return ApiResponse.ok("테넌트 생성 완료", service.create(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResponse<TenantVo> update(@PathVariable Long id, @RequestBody TenantVo v) {
|
||||||
|
return ApiResponse.ok("테넌트 수정 완료", service.update(id, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResponse<Void> delete(@PathVariable Long id) {
|
||||||
|
service.delete(id);
|
||||||
|
return ApiResponse.ok("테넌트 삭제 완료", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.domain.UpdateQueueVo;
|
||||||
|
import com.zioinfo.esn.service.UpdateQueueService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* e-paper 태그 가격 업데이트 전파 큐
|
||||||
|
* Gateway가 이 API를 주기적으로 폴링 → 태그에 RF 전송 → confirm 호출
|
||||||
|
*
|
||||||
|
* Gateway 연동 흐름:
|
||||||
|
* GET /api/update-queue/poll?tenantCode=EMART&limit=50 → PENDING 목록 수신
|
||||||
|
* PUT /api/update-queue/{id}/sent → RF 전송 완료
|
||||||
|
* PUT /api/update-queue/confirm?deviceId=ESL-001 → 태그 ACK 수신
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/update-queue")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UpdateQueueController {
|
||||||
|
private final UpdateQueueService service;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<List<UpdateQueueVo>> list(
|
||||||
|
@RequestParam(required = false) String tenantCode,
|
||||||
|
@RequestParam(required = false) Long storeId,
|
||||||
|
@RequestParam(required = false) String status) {
|
||||||
|
return ApiResponse.ok(service.list(tenantCode, storeId, status));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<UpdateQueueVo> get(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(service.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gateway 폴링 엔드포인트: PENDING → SENDING 전환 후 반환 */
|
||||||
|
@GetMapping("/poll")
|
||||||
|
public ApiResponse<List<UpdateQueueVo>> poll(
|
||||||
|
@RequestParam(required = false) String tenantCode,
|
||||||
|
@RequestParam(defaultValue = "50") int limit) {
|
||||||
|
return ApiResponse.ok(service.pollPending(tenantCode, limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gateway가 RF 전송 완료 후 호출 */
|
||||||
|
@PutMapping("/{id}/sent")
|
||||||
|
public ApiResponse<Void> sent(@PathVariable Long id) {
|
||||||
|
service.markSent(id);
|
||||||
|
return ApiResponse.ok("전송 완료", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 태그로부터 확인 응답 수신 (device_id 기반 일괄 확인) */
|
||||||
|
@PutMapping("/confirm")
|
||||||
|
public ApiResponse<Void> confirm(@RequestParam String deviceId) {
|
||||||
|
service.confirm(deviceId);
|
||||||
|
return ApiResponse.ok("확인 완료", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 전송 실패 처리 */
|
||||||
|
@PutMapping("/{id}/failed")
|
||||||
|
public ApiResponse<Void> failed(@PathVariable Long id,
|
||||||
|
@RequestBody Map<String, String> body) {
|
||||||
|
service.markFailed(id, body.getOrDefault("message", "전송 실패"));
|
||||||
|
return ApiResponse.ok("실패 처리 완료", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 큐 항목 취소 */
|
||||||
|
@PutMapping("/{id}/cancel")
|
||||||
|
public ApiResponse<Void> cancel(@PathVariable Long id) {
|
||||||
|
service.cancel(id);
|
||||||
|
return ApiResponse.ok("취소 완료", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.auth.EsnUser;
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.service.UserService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/users")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserController {
|
||||||
|
private final UserService service;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<List<EsnUser>> list(
|
||||||
|
@RequestParam(required = false) String tenantCode,
|
||||||
|
@RequestParam(required = false) String role) {
|
||||||
|
return ApiResponse.ok(service.list(tenantCode, role));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<EsnUser> get(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(service.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<EsnUser> create(@RequestBody Map<String, Object> body) {
|
||||||
|
EsnUser user = new EsnUser();
|
||||||
|
user.setUsername((String) body.get("username"));
|
||||||
|
user.setRole((String) body.getOrDefault("role", "USER"));
|
||||||
|
user.setTenantCode((String) body.get("tenantCode"));
|
||||||
|
user.setEmail((String) body.get("email"));
|
||||||
|
user.setPhone((String) body.get("phone"));
|
||||||
|
user.setActive(true);
|
||||||
|
String rawPassword = (String) body.getOrDefault("password", "changeme123");
|
||||||
|
return ApiResponse.ok("사용자 생성 완료", service.create(user, rawPassword));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResponse<EsnUser> update(@PathVariable Long id, @RequestBody EsnUser v) {
|
||||||
|
return ApiResponse.ok("사용자 수정 완료", service.update(id, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}/password")
|
||||||
|
public ApiResponse<Void> changePassword(@PathVariable Long id, @RequestBody Map<String, String> body) {
|
||||||
|
service.changePassword(id, body.getOrDefault("password", "changeme123"));
|
||||||
|
return ApiResponse.ok("비밀번호 변경 완료", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResponse<Void> delete(@PathVariable Long id) {
|
||||||
|
service.delete(id);
|
||||||
|
return ApiResponse.ok("사용자 삭제 완료", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package com.zioinfo.esn.controller;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.common.ApiResponse;
|
||||||
|
import com.zioinfo.esn.domain.WorkHistoryVo;
|
||||||
|
import com.zioinfo.esn.service.WorkService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/works")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WorkController {
|
||||||
|
private final WorkService service;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ApiResponse<List<WorkHistoryVo>> list(
|
||||||
|
@RequestParam(required = false) String tenantCode,
|
||||||
|
@RequestParam(required = false) Long storeId,
|
||||||
|
@RequestParam(required = false) String status,
|
||||||
|
@RequestParam(required = false) String workType) {
|
||||||
|
return ApiResponse.ok(service.list(tenantCode, storeId, status, workType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<WorkHistoryVo> get(@PathVariable Long id) {
|
||||||
|
return ApiResponse.ok(service.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ApiResponse<WorkHistoryVo> create(@RequestBody WorkHistoryVo v) {
|
||||||
|
return ApiResponse.ok("작업 등록 완료", service.create(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResponse<WorkHistoryVo> update(@PathVariable Long id, @RequestBody WorkHistoryVo v) {
|
||||||
|
return ApiResponse.ok("작업 수정 완료", service.update(id, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResponse<Void> delete(@PathVariable Long id) {
|
||||||
|
service.delete(id);
|
||||||
|
return ApiResponse.ok("작업 삭제 완료", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
backend/src/main/java/com/zioinfo/esn/domain/AlarmVo.java
Normal file
21
backend/src/main/java/com/zioinfo/esn/domain/AlarmVo.java
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package com.zioinfo.esn.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class AlarmVo {
|
||||||
|
private Long id;
|
||||||
|
private String tenantCode;
|
||||||
|
private Long storeId;
|
||||||
|
private String storeName;
|
||||||
|
private String alarmType; // DEVICE, NETWORK, BATTERY, FIRMWARE, SYSTEM
|
||||||
|
private String alarmCode;
|
||||||
|
private String severity; // CRITICAL, HIGH, MEDIUM, LOW
|
||||||
|
private String message;
|
||||||
|
private String status; // OPEN, ACKNOWLEDGED, RESOLVED
|
||||||
|
private String resolvedBy;
|
||||||
|
private String resolution;
|
||||||
|
private LocalDateTime resolvedAt;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package com.zioinfo.esn.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class DashboardVo {
|
||||||
|
private long totalStores;
|
||||||
|
private long activeStores;
|
||||||
|
private long totalAlarms;
|
||||||
|
private long unresolvedAlarms;
|
||||||
|
private long criticalAlarms;
|
||||||
|
private long totalDevices;
|
||||||
|
private long onlineDevices;
|
||||||
|
private long offlineDevices;
|
||||||
|
private long totalWorkToday;
|
||||||
|
private long completedWorkToday;
|
||||||
|
private long pendingPosCvt;
|
||||||
|
private long processedPosCvtToday;
|
||||||
|
}
|
||||||
19
backend/src/main/java/com/zioinfo/esn/domain/FirmwareVo.java
Normal file
19
backend/src/main/java/com/zioinfo/esn/domain/FirmwareVo.java
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package com.zioinfo.esn.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class FirmwareVo {
|
||||||
|
private Long id;
|
||||||
|
private String tenantCode;
|
||||||
|
private String firmwareVersion;
|
||||||
|
private String deviceType; // GATEWAY, HUB, ESL_DEVICE
|
||||||
|
private String fileName;
|
||||||
|
private String filePath;
|
||||||
|
private Long fileSize;
|
||||||
|
private String checksum;
|
||||||
|
private String description;
|
||||||
|
private boolean latest;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
22
backend/src/main/java/com/zioinfo/esn/domain/HCoreVo.java
Normal file
22
backend/src/main/java/com/zioinfo/esn/domain/HCoreVo.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package com.zioinfo.esn.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class HCoreVo {
|
||||||
|
private Long id;
|
||||||
|
private String tenantCode;
|
||||||
|
private Long storeId;
|
||||||
|
private String storeName;
|
||||||
|
private String deviceType; // GATEWAY, HUB, ESL_DEVICE
|
||||||
|
private String deviceId;
|
||||||
|
private String ipAddress;
|
||||||
|
private String macAddress;
|
||||||
|
private String firmwareVersion;
|
||||||
|
private String status; // ONLINE, OFFLINE, ERROR, UPDATING
|
||||||
|
private Integer batteryLevel;
|
||||||
|
private String signalStrength;
|
||||||
|
private LocalDateTime lastSeenAt;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
23
backend/src/main/java/com/zioinfo/esn/domain/PosCvtVo.java
Normal file
23
backend/src/main/java/com/zioinfo/esn/domain/PosCvtVo.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package com.zioinfo.esn.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class PosCvtVo {
|
||||||
|
private Long id;
|
||||||
|
private String tenantCode;
|
||||||
|
private Long storeId;
|
||||||
|
private String storeName;
|
||||||
|
private String posCode;
|
||||||
|
private String productCode;
|
||||||
|
private String productName;
|
||||||
|
private BigDecimal price;
|
||||||
|
private BigDecimal salePrice;
|
||||||
|
private String currency;
|
||||||
|
private String status; // PENDING, PROCESSED, ERROR, IGNORED
|
||||||
|
private String errorMessage;
|
||||||
|
private LocalDateTime processedAt;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
22
backend/src/main/java/com/zioinfo/esn/domain/ProductVo.java
Normal file
22
backend/src/main/java/com/zioinfo/esn/domain/ProductVo.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package com.zioinfo.esn.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ProductVo {
|
||||||
|
private Long id;
|
||||||
|
private String tenantCode;
|
||||||
|
private Long storeId;
|
||||||
|
private String storeName;
|
||||||
|
private String productCode;
|
||||||
|
private String productName;
|
||||||
|
private String category;
|
||||||
|
private BigDecimal price;
|
||||||
|
private BigDecimal salePrice;
|
||||||
|
private String currency;
|
||||||
|
private boolean active;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.zioinfo.esn.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class StoreGroupVo {
|
||||||
|
private Long id;
|
||||||
|
private String tenantCode;
|
||||||
|
private String groupCode;
|
||||||
|
private String groupName;
|
||||||
|
private String description;
|
||||||
|
private boolean active;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
19
backend/src/main/java/com/zioinfo/esn/domain/StoreVo.java
Normal file
19
backend/src/main/java/com/zioinfo/esn/domain/StoreVo.java
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package com.zioinfo.esn.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class StoreVo {
|
||||||
|
private Long id;
|
||||||
|
private String tenantCode;
|
||||||
|
private String storeCode;
|
||||||
|
private String storeName;
|
||||||
|
private String address;
|
||||||
|
private String phone;
|
||||||
|
private Long storeGroupId;
|
||||||
|
private String storeGroupName;
|
||||||
|
private String managerName;
|
||||||
|
private boolean active;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package com.zioinfo.esn.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TagBindingVo {
|
||||||
|
private Long id;
|
||||||
|
private String tenantCode;
|
||||||
|
private Long storeId;
|
||||||
|
private String storeName;
|
||||||
|
private String deviceId; // ESL_DEVICE의 device_id
|
||||||
|
private String productCode;
|
||||||
|
private String productName;
|
||||||
|
private String locationCode; // 선반 위치 (예: A01-03)
|
||||||
|
private String templateCode;
|
||||||
|
private String displayData; // 현재 화면 렌더링 데이터 (JSON)
|
||||||
|
private BigDecimal lastPrice;
|
||||||
|
private LocalDateTime lastUpdatedAt;
|
||||||
|
private String bindStatus; // ACTIVE, UNBOUND, ERROR
|
||||||
|
// HCore 장치 정보 (join)
|
||||||
|
private String deviceStatus; // ONLINE, OFFLINE 등
|
||||||
|
private Integer batteryLevel;
|
||||||
|
private String signalStrength;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
19
backend/src/main/java/com/zioinfo/esn/domain/TemplateVo.java
Normal file
19
backend/src/main/java/com/zioinfo/esn/domain/TemplateVo.java
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package com.zioinfo.esn.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TemplateVo {
|
||||||
|
private Long id;
|
||||||
|
private String tenantCode;
|
||||||
|
private String templateCode;
|
||||||
|
private String templateName;
|
||||||
|
private String templateType; // PRICE, INFO, PROMO, CUSTOM
|
||||||
|
private Integer width;
|
||||||
|
private Integer height;
|
||||||
|
private String description;
|
||||||
|
private String layoutJson; // JSON 레이아웃 정의
|
||||||
|
private boolean active;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
14
backend/src/main/java/com/zioinfo/esn/domain/TenantVo.java
Normal file
14
backend/src/main/java/com/zioinfo/esn/domain/TenantVo.java
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package com.zioinfo.esn.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TenantVo {
|
||||||
|
private Long id;
|
||||||
|
private String tenantCode;
|
||||||
|
private String tenantName;
|
||||||
|
private String description;
|
||||||
|
private boolean active;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.zioinfo.esn.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class UpdateQueueVo {
|
||||||
|
private Long id;
|
||||||
|
private String tenantCode;
|
||||||
|
private Long storeId;
|
||||||
|
private String storeName;
|
||||||
|
private Long bindingId;
|
||||||
|
private String deviceId;
|
||||||
|
private String locationCode;
|
||||||
|
private String productCode;
|
||||||
|
private String productName;
|
||||||
|
private BigDecimal newPrice;
|
||||||
|
private BigDecimal newSalePrice;
|
||||||
|
private String templateCode;
|
||||||
|
private String renderData;
|
||||||
|
private Integer priority; // 1(긴급)~10(일반)
|
||||||
|
private String status; // PENDING, SENDING, SENT, CONFIRMED, FAILED, CANCELLED
|
||||||
|
private Integer retryCount;
|
||||||
|
private Integer maxRetry;
|
||||||
|
private String errorMessage;
|
||||||
|
private Long posCvtId;
|
||||||
|
private LocalDateTime sentAt;
|
||||||
|
private LocalDateTime confirmedAt;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package com.zioinfo.esn.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class WorkHistoryVo {
|
||||||
|
private Long id;
|
||||||
|
private String tenantCode;
|
||||||
|
private Long storeId;
|
||||||
|
private String storeName;
|
||||||
|
private String workType; // INSTALL, MAINTENANCE, FIRMWARE_UPDATE, REPLACEMENT, INSPECTION
|
||||||
|
private String workContent;
|
||||||
|
private String workerName;
|
||||||
|
private String status; // SCHEDULED, IN_PROGRESS, COMPLETED, CANCELLED
|
||||||
|
private String remarks;
|
||||||
|
private LocalDateTime startedAt;
|
||||||
|
private LocalDateTime completedAt;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package com.zioinfo.esn.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.AlarmVo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface AlarmMapper {
|
||||||
|
List<AlarmVo> findAll(@Param("tenantCode") String tenantCode,
|
||||||
|
@Param("storeId") Long storeId,
|
||||||
|
@Param("severity") String severity,
|
||||||
|
@Param("status") String status);
|
||||||
|
AlarmVo findById(@Param("id") Long id);
|
||||||
|
int insert(AlarmVo alarm);
|
||||||
|
int update(AlarmVo alarm);
|
||||||
|
int resolve(@Param("id") Long id, @Param("resolvedBy") String resolvedBy,
|
||||||
|
@Param("resolution") String resolution);
|
||||||
|
int delete(@Param("id") Long id);
|
||||||
|
long countUnresolved(@Param("tenantCode") String tenantCode);
|
||||||
|
long countCritical(@Param("tenantCode") String tenantCode);
|
||||||
|
long countAll(@Param("tenantCode") String tenantCode);
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package com.zioinfo.esn.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.DashboardVo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface DashboardMapper {
|
||||||
|
DashboardVo getSummary(@Param("tenantCode") String tenantCode);
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package com.zioinfo.esn.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.FirmwareVo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface FirmwareMapper {
|
||||||
|
List<FirmwareVo> findAll(@Param("tenantCode") String tenantCode,
|
||||||
|
@Param("deviceType") String deviceType);
|
||||||
|
FirmwareVo findById(@Param("id") Long id);
|
||||||
|
FirmwareVo findLatest(@Param("deviceType") String deviceType,
|
||||||
|
@Param("tenantCode") String tenantCode);
|
||||||
|
int insert(FirmwareVo firmware);
|
||||||
|
int update(FirmwareVo firmware);
|
||||||
|
int clearLatest(@Param("deviceType") String deviceType, @Param("tenantCode") String tenantCode);
|
||||||
|
int delete(@Param("id") Long id);
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package com.zioinfo.esn.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.HCoreVo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface HCoreMapper {
|
||||||
|
List<HCoreVo> findAll(@Param("tenantCode") String tenantCode,
|
||||||
|
@Param("storeId") Long storeId,
|
||||||
|
@Param("deviceType") String deviceType,
|
||||||
|
@Param("status") String status);
|
||||||
|
HCoreVo findById(@Param("id") Long id);
|
||||||
|
HCoreVo findByDeviceId(@Param("deviceId") String deviceId);
|
||||||
|
int insert(HCoreVo hcore);
|
||||||
|
int update(HCoreVo hcore);
|
||||||
|
int updateStatus(@Param("id") Long id, @Param("status") String status);
|
||||||
|
int delete(@Param("id") Long id);
|
||||||
|
long countOnline(@Param("tenantCode") String tenantCode);
|
||||||
|
long countOffline(@Param("tenantCode") String tenantCode);
|
||||||
|
long countAll(@Param("tenantCode") String tenantCode);
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package com.zioinfo.esn.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.PosCvtVo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface PosCvtMapper {
|
||||||
|
List<PosCvtVo> findAll(@Param("tenantCode") String tenantCode,
|
||||||
|
@Param("storeId") Long storeId,
|
||||||
|
@Param("status") String status,
|
||||||
|
@Param("keyword") String keyword);
|
||||||
|
PosCvtVo findById(@Param("id") Long id);
|
||||||
|
int insert(PosCvtVo posCvt);
|
||||||
|
int updateStatus(@Param("id") Long id, @Param("status") String status,
|
||||||
|
@Param("errorMessage") String errorMessage);
|
||||||
|
long countPending(@Param("tenantCode") String tenantCode);
|
||||||
|
long countProcessedToday(@Param("tenantCode") String tenantCode);
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package com.zioinfo.esn.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.ProductVo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface ProductMapper {
|
||||||
|
List<ProductVo> findAll(@Param("tenantCode") String tenantCode,
|
||||||
|
@Param("storeId") Long storeId,
|
||||||
|
@Param("keyword") String keyword);
|
||||||
|
ProductVo findById(@Param("id") Long id);
|
||||||
|
ProductVo findByCode(@Param("productCode") String productCode,
|
||||||
|
@Param("storeId") Long storeId);
|
||||||
|
int insert(ProductVo product);
|
||||||
|
int update(ProductVo product);
|
||||||
|
int delete(@Param("id") Long id);
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.zioinfo.esn.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.StoreGroupVo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface StoreGroupMapper {
|
||||||
|
List<StoreGroupVo> findAll(@Param("tenantCode") String tenantCode);
|
||||||
|
StoreGroupVo findById(@Param("id") Long id);
|
||||||
|
int insert(StoreGroupVo group);
|
||||||
|
int update(StoreGroupVo group);
|
||||||
|
int delete(@Param("id") Long id);
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package com.zioinfo.esn.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.StoreVo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface StoreMapper {
|
||||||
|
List<StoreVo> findAll(@Param("tenantCode") String tenantCode,
|
||||||
|
@Param("storeGroupId") Long storeGroupId,
|
||||||
|
@Param("keyword") String keyword);
|
||||||
|
StoreVo findById(@Param("id") Long id);
|
||||||
|
StoreVo findByCode(@Param("storeCode") String storeCode, @Param("tenantCode") String tenantCode);
|
||||||
|
int insert(StoreVo store);
|
||||||
|
int update(StoreVo store);
|
||||||
|
int delete(@Param("id") Long id);
|
||||||
|
long countActive(@Param("tenantCode") String tenantCode);
|
||||||
|
long countAll(@Param("tenantCode") String tenantCode);
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.zioinfo.esn.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.TagBindingVo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface TagBindingMapper {
|
||||||
|
List<TagBindingVo> findAll(@Param("tenantCode") String tenantCode,
|
||||||
|
@Param("storeId") Long storeId,
|
||||||
|
@Param("bindStatus") String bindStatus,
|
||||||
|
@Param("keyword") String keyword);
|
||||||
|
TagBindingVo findById(@Param("id") Long id);
|
||||||
|
TagBindingVo findByDeviceId(@Param("deviceId") String deviceId);
|
||||||
|
List<TagBindingVo> findByProductCode(@Param("tenantCode") String tenantCode,
|
||||||
|
@Param("storeId") Long storeId,
|
||||||
|
@Param("productCode") String productCode);
|
||||||
|
int insert(TagBindingVo vo);
|
||||||
|
int update(TagBindingVo vo);
|
||||||
|
int unbind(@Param("id") Long id);
|
||||||
|
int delete(@Param("id") Long id);
|
||||||
|
long countAll(@Param("tenantCode") String tenantCode);
|
||||||
|
long countActive(@Param("tenantCode") String tenantCode);
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package com.zioinfo.esn.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.TemplateVo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface TemplateMapper {
|
||||||
|
List<TemplateVo> findAll(@Param("tenantCode") String tenantCode,
|
||||||
|
@Param("templateType") String templateType);
|
||||||
|
TemplateVo findById(@Param("id") Long id);
|
||||||
|
int insert(TemplateVo template);
|
||||||
|
int update(TemplateVo template);
|
||||||
|
int delete(@Param("id") Long id);
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package com.zioinfo.esn.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.TenantVo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface TenantMapper {
|
||||||
|
List<TenantVo> findAll();
|
||||||
|
TenantVo findById(@Param("id") Long id);
|
||||||
|
TenantVo findByCode(@Param("tenantCode") String tenantCode);
|
||||||
|
int insert(TenantVo tenant);
|
||||||
|
int update(TenantVo tenant);
|
||||||
|
int delete(@Param("id") Long id);
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package com.zioinfo.esn.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.UpdateQueueVo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface UpdateQueueMapper {
|
||||||
|
List<UpdateQueueVo> findAll(@Param("tenantCode") String tenantCode,
|
||||||
|
@Param("storeId") Long storeId,
|
||||||
|
@Param("status") String status);
|
||||||
|
List<UpdateQueueVo> findPending(@Param("tenantCode") String tenantCode,
|
||||||
|
@Param("limit") int limit);
|
||||||
|
UpdateQueueVo findById(@Param("id") Long id);
|
||||||
|
int insert(UpdateQueueVo vo);
|
||||||
|
int updateStatus(@Param("id") Long id, @Param("status") String status,
|
||||||
|
@Param("errorMessage") String errorMessage);
|
||||||
|
int confirmByDeviceId(@Param("deviceId") String deviceId);
|
||||||
|
int incrementRetry(@Param("id") Long id);
|
||||||
|
long countPending(@Param("tenantCode") String tenantCode);
|
||||||
|
long countFailed(@Param("tenantCode") String tenantCode);
|
||||||
|
long countByStatus(@Param("tenantCode") String tenantCode, @Param("status") String status);
|
||||||
|
}
|
||||||
18
backend/src/main/java/com/zioinfo/esn/mapper/UserMapper.java
Normal file
18
backend/src/main/java/com/zioinfo/esn/mapper/UserMapper.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package com.zioinfo.esn.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.auth.EsnUser;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface UserMapper {
|
||||||
|
List<EsnUser> findAll(@Param("tenantCode") String tenantCode,
|
||||||
|
@Param("role") String role);
|
||||||
|
EsnUser findById(@Param("id") Long id);
|
||||||
|
EsnUser findByUsername(@Param("username") String username);
|
||||||
|
int insert(EsnUser user);
|
||||||
|
int update(EsnUser user);
|
||||||
|
int updatePassword(@Param("id") Long id, @Param("passwordHash") String passwordHash);
|
||||||
|
int delete(@Param("id") Long id);
|
||||||
|
}
|
||||||
20
backend/src/main/java/com/zioinfo/esn/mapper/WorkMapper.java
Normal file
20
backend/src/main/java/com/zioinfo/esn/mapper/WorkMapper.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package com.zioinfo.esn.mapper;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.WorkHistoryVo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface WorkMapper {
|
||||||
|
List<WorkHistoryVo> findAll(@Param("tenantCode") String tenantCode,
|
||||||
|
@Param("storeId") Long storeId,
|
||||||
|
@Param("status") String status,
|
||||||
|
@Param("workType") String workType);
|
||||||
|
WorkHistoryVo findById(@Param("id") Long id);
|
||||||
|
int insert(WorkHistoryVo work);
|
||||||
|
int update(WorkHistoryVo work);
|
||||||
|
int delete(@Param("id") Long id);
|
||||||
|
long countToday(@Param("tenantCode") String tenantCode);
|
||||||
|
long countCompletedToday(@Param("tenantCode") String tenantCode);
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.zioinfo.esn.service;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.AlarmVo;
|
||||||
|
import com.zioinfo.esn.mapper.AlarmMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AlarmService {
|
||||||
|
private final AlarmMapper mapper;
|
||||||
|
|
||||||
|
public List<AlarmVo> list(String tenantCode, Long storeId, String severity, String status) {
|
||||||
|
return mapper.findAll(tenantCode, storeId, severity, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlarmVo get(Long id) { return mapper.findById(id); }
|
||||||
|
|
||||||
|
public AlarmVo create(AlarmVo v) {
|
||||||
|
mapper.insert(v);
|
||||||
|
return mapper.findById(v.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlarmVo update(Long id, AlarmVo v) {
|
||||||
|
v.setId(id);
|
||||||
|
mapper.update(v);
|
||||||
|
return mapper.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resolve(Long id, String resolvedBy, String resolution) {
|
||||||
|
mapper.resolve(id, resolvedBy, resolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Long id) { mapper.delete(id); }
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package com.zioinfo.esn.service;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.FirmwareVo;
|
||||||
|
import com.zioinfo.esn.mapper.FirmwareMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class FirmwareService {
|
||||||
|
private final FirmwareMapper mapper;
|
||||||
|
|
||||||
|
public List<FirmwareVo> list(String tenantCode, String deviceType) {
|
||||||
|
return mapper.findAll(tenantCode, deviceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FirmwareVo get(Long id) { return mapper.findById(id); }
|
||||||
|
|
||||||
|
public FirmwareVo create(FirmwareVo v) {
|
||||||
|
if (v.isLatest()) {
|
||||||
|
mapper.clearLatest(v.getDeviceType(), v.getTenantCode());
|
||||||
|
}
|
||||||
|
mapper.insert(v);
|
||||||
|
return mapper.findById(v.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public FirmwareVo update(Long id, FirmwareVo v) {
|
||||||
|
v.setId(id);
|
||||||
|
if (v.isLatest()) {
|
||||||
|
mapper.clearLatest(v.getDeviceType(), v.getTenantCode());
|
||||||
|
}
|
||||||
|
mapper.update(v);
|
||||||
|
return mapper.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Long id) { mapper.delete(id); }
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.zioinfo.esn.service;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.HCoreVo;
|
||||||
|
import com.zioinfo.esn.mapper.HCoreMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class HCoreService {
|
||||||
|
private final HCoreMapper mapper;
|
||||||
|
|
||||||
|
public List<HCoreVo> list(String tenantCode, Long storeId, String deviceType, String status) {
|
||||||
|
return mapper.findAll(tenantCode, storeId, deviceType, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HCoreVo get(Long id) { return mapper.findById(id); }
|
||||||
|
|
||||||
|
public HCoreVo create(HCoreVo v) {
|
||||||
|
mapper.insert(v);
|
||||||
|
return mapper.findById(v.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public HCoreVo update(Long id, HCoreVo v) {
|
||||||
|
v.setId(id);
|
||||||
|
mapper.update(v);
|
||||||
|
return mapper.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateStatus(Long id, String status) {
|
||||||
|
mapper.updateStatus(id, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Long id) { mapper.delete(id); }
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package com.zioinfo.esn.service;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.PosCvtVo;
|
||||||
|
import com.zioinfo.esn.mapper.PosCvtMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PosCvtService {
|
||||||
|
private final PosCvtMapper mapper;
|
||||||
|
private final TagBindingService tagBindingService;
|
||||||
|
|
||||||
|
public List<PosCvtVo> list(String tenantCode, Long storeId, String status, String keyword) {
|
||||||
|
return mapper.findAll(tenantCode, storeId, status, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PosCvtVo get(Long id) { return mapper.findById(id); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POS 가격변경 처리 → 바인딩된 e-paper 태그에 업데이트 큐 자동 생성
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public int process(Long id) {
|
||||||
|
PosCvtVo pos = mapper.findById(id);
|
||||||
|
if (pos == null) return 0;
|
||||||
|
|
||||||
|
mapper.updateStatus(id, "PROCESSED", null);
|
||||||
|
|
||||||
|
// 이 상품에 바인딩된 e-paper 태그들에 가격 업데이트 큐 생성
|
||||||
|
int queued = tagBindingService.enqueueByProductCode(
|
||||||
|
pos.getTenantCode(), pos.getStoreId(),
|
||||||
|
pos.getProductCode(), pos.getPrice(), pos.getSalePrice(), id);
|
||||||
|
|
||||||
|
log.info("POS 처리 완료: posCvtId={}, product={}, 큐생성={}개",
|
||||||
|
id, pos.getProductCode(), queued);
|
||||||
|
return queued;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markError(Long id, String errorMessage) {
|
||||||
|
mapper.updateStatus(id, "ERROR", errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ignore(Long id) {
|
||||||
|
mapper.updateStatus(id, "IGNORED", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.zioinfo.esn.service;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.ProductVo;
|
||||||
|
import com.zioinfo.esn.mapper.ProductMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ProductService {
|
||||||
|
private final ProductMapper mapper;
|
||||||
|
|
||||||
|
public List<ProductVo> list(String tenantCode, Long storeId, String keyword) {
|
||||||
|
return mapper.findAll(tenantCode, storeId, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProductVo get(Long id) { return mapper.findById(id); }
|
||||||
|
|
||||||
|
public ProductVo create(ProductVo v) {
|
||||||
|
mapper.insert(v);
|
||||||
|
return mapper.findById(v.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProductVo update(Long id, ProductVo v) {
|
||||||
|
v.setId(id);
|
||||||
|
mapper.update(v);
|
||||||
|
return mapper.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Long id) { mapper.delete(id); }
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.zioinfo.esn.service;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.StoreGroupVo;
|
||||||
|
import com.zioinfo.esn.mapper.StoreGroupMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class StoreGroupService {
|
||||||
|
private final StoreGroupMapper mapper;
|
||||||
|
|
||||||
|
public List<StoreGroupVo> list(String tenantCode) { return mapper.findAll(tenantCode); }
|
||||||
|
public StoreGroupVo get(Long id) { return mapper.findById(id); }
|
||||||
|
|
||||||
|
public StoreGroupVo create(StoreGroupVo v) {
|
||||||
|
mapper.insert(v);
|
||||||
|
return mapper.findById(v.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public StoreGroupVo update(Long id, StoreGroupVo v) {
|
||||||
|
v.setId(id);
|
||||||
|
mapper.update(v);
|
||||||
|
return mapper.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Long id) { mapper.delete(id); }
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.zioinfo.esn.service;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.StoreVo;
|
||||||
|
import com.zioinfo.esn.mapper.StoreMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class StoreService {
|
||||||
|
private final StoreMapper mapper;
|
||||||
|
|
||||||
|
public List<StoreVo> list(String tenantCode, Long storeGroupId, String keyword) {
|
||||||
|
return mapper.findAll(tenantCode, storeGroupId, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StoreVo get(Long id) { return mapper.findById(id); }
|
||||||
|
|
||||||
|
public StoreVo create(StoreVo v) {
|
||||||
|
mapper.insert(v);
|
||||||
|
return mapper.findById(v.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public StoreVo update(Long id, StoreVo v) {
|
||||||
|
v.setId(id);
|
||||||
|
mapper.update(v);
|
||||||
|
return mapper.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Long id) { mapper.delete(id); }
|
||||||
|
}
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
package com.zioinfo.esn.service;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.TagBindingVo;
|
||||||
|
import com.zioinfo.esn.domain.UpdateQueueVo;
|
||||||
|
import com.zioinfo.esn.mapper.TagBindingMapper;
|
||||||
|
import com.zioinfo.esn.mapper.UpdateQueueMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TagBindingService {
|
||||||
|
private final TagBindingMapper bindingMapper;
|
||||||
|
private final UpdateQueueMapper queueMapper;
|
||||||
|
|
||||||
|
public List<TagBindingVo> list(String tenantCode, Long storeId,
|
||||||
|
String bindStatus, String keyword) {
|
||||||
|
return bindingMapper.findAll(tenantCode, storeId, bindStatus, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagBindingVo get(Long id) { return bindingMapper.findById(id); }
|
||||||
|
|
||||||
|
public TagBindingVo getByDeviceId(String deviceId) {
|
||||||
|
return bindingMapper.findByDeviceId(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public TagBindingVo bind(TagBindingVo vo) {
|
||||||
|
// 기존 바인딩이 있으면 해제 후 재바인딩
|
||||||
|
TagBindingVo existing = bindingMapper.findByDeviceId(vo.getDeviceId());
|
||||||
|
if (existing != null) {
|
||||||
|
bindingMapper.unbind(existing.getId());
|
||||||
|
}
|
||||||
|
bindingMapper.insert(vo);
|
||||||
|
return bindingMapper.findById(vo.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void unbind(Long id) {
|
||||||
|
bindingMapper.unbind(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 가격 변경 시 해당 상품에 바인딩된 모든 태그에 업데이트 큐 생성
|
||||||
|
* POS 가격변경 → 이 메서드 → e-paper 태그들에게 전파
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public int enqueueByProductCode(String tenantCode, Long storeId,
|
||||||
|
String productCode, java.math.BigDecimal newPrice,
|
||||||
|
java.math.BigDecimal newSalePrice, Long posCvtId) {
|
||||||
|
List<TagBindingVo> bindings = bindingMapper.findByProductCode(tenantCode, storeId, productCode);
|
||||||
|
for (TagBindingVo b : bindings) {
|
||||||
|
UpdateQueueVo q = new UpdateQueueVo();
|
||||||
|
q.setTenantCode(tenantCode);
|
||||||
|
q.setStoreId(storeId);
|
||||||
|
q.setBindingId(b.getId());
|
||||||
|
q.setDeviceId(b.getDeviceId());
|
||||||
|
q.setProductCode(productCode);
|
||||||
|
q.setNewPrice(newPrice);
|
||||||
|
q.setNewSalePrice(newSalePrice);
|
||||||
|
q.setTemplateCode(b.getTemplateCode());
|
||||||
|
q.setPriority(5);
|
||||||
|
q.setPosCvtId(posCvtId);
|
||||||
|
queueMapper.insert(q);
|
||||||
|
}
|
||||||
|
return bindings.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.zioinfo.esn.service;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.TemplateVo;
|
||||||
|
import com.zioinfo.esn.mapper.TemplateMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TemplateService {
|
||||||
|
private final TemplateMapper mapper;
|
||||||
|
|
||||||
|
public List<TemplateVo> list(String tenantCode, String templateType) {
|
||||||
|
return mapper.findAll(tenantCode, templateType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TemplateVo get(Long id) { return mapper.findById(id); }
|
||||||
|
|
||||||
|
public TemplateVo create(TemplateVo v) {
|
||||||
|
mapper.insert(v);
|
||||||
|
return mapper.findById(v.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TemplateVo update(Long id, TemplateVo v) {
|
||||||
|
v.setId(id);
|
||||||
|
mapper.update(v);
|
||||||
|
return mapper.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Long id) { mapper.delete(id); }
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.zioinfo.esn.service;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.TenantVo;
|
||||||
|
import com.zioinfo.esn.mapper.TenantMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TenantService {
|
||||||
|
private final TenantMapper mapper;
|
||||||
|
|
||||||
|
public List<TenantVo> list() { return mapper.findAll(); }
|
||||||
|
public TenantVo get(Long id) { return mapper.findById(id); }
|
||||||
|
|
||||||
|
public TenantVo create(TenantVo v) {
|
||||||
|
mapper.insert(v);
|
||||||
|
return mapper.findById(v.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TenantVo update(Long id, TenantVo v) {
|
||||||
|
v.setId(id);
|
||||||
|
mapper.update(v);
|
||||||
|
return mapper.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Long id) { mapper.delete(id); }
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
package com.zioinfo.esn.service;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.UpdateQueueVo;
|
||||||
|
import com.zioinfo.esn.mapper.UpdateQueueMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UpdateQueueService {
|
||||||
|
private final UpdateQueueMapper mapper;
|
||||||
|
|
||||||
|
public List<UpdateQueueVo> list(String tenantCode, Long storeId, String status) {
|
||||||
|
return mapper.findAll(tenantCode, storeId, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateQueueVo get(Long id) { return mapper.findById(id); }
|
||||||
|
|
||||||
|
/** Gateway 폴링: PENDING 큐 N건 반환 후 SENDING으로 전환 */
|
||||||
|
@Transactional
|
||||||
|
public List<UpdateQueueVo> pollPending(String tenantCode, int limit) {
|
||||||
|
List<UpdateQueueVo> items = mapper.findPending(tenantCode, limit);
|
||||||
|
for (UpdateQueueVo q : items) {
|
||||||
|
mapper.updateStatus(q.getId(), "SENDING", null);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gateway → 태그 전송 완료 */
|
||||||
|
@Transactional
|
||||||
|
public void markSent(Long id) {
|
||||||
|
mapper.updateStatus(id, "SENT", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 태그 확인 응답 수신 (device_id 기반) */
|
||||||
|
@Transactional
|
||||||
|
public void confirm(String deviceId) {
|
||||||
|
mapper.confirmByDeviceId(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 전송 실패 처리 — max_retry 초과 시 FAILED */
|
||||||
|
@Transactional
|
||||||
|
public void markFailed(Long id, String errorMessage) {
|
||||||
|
UpdateQueueVo q = mapper.findById(id);
|
||||||
|
if (q == null) return;
|
||||||
|
mapper.incrementRetry(id);
|
||||||
|
if (q.getRetryCount() + 1 >= q.getMaxRetry()) {
|
||||||
|
mapper.updateStatus(id, "FAILED", errorMessage);
|
||||||
|
} else {
|
||||||
|
mapper.updateStatus(id, "PENDING", errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void cancel(Long id) {
|
||||||
|
mapper.updateStatus(id, "CANCELLED", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package com.zioinfo.esn.service;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.auth.EsnUser;
|
||||||
|
import com.zioinfo.esn.mapper.UserMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserService {
|
||||||
|
private final UserMapper mapper;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
public List<EsnUser> list(String tenantCode, String role) {
|
||||||
|
return mapper.findAll(tenantCode, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EsnUser get(Long id) { return mapper.findById(id); }
|
||||||
|
|
||||||
|
public EsnUser create(EsnUser v, String rawPassword) {
|
||||||
|
v.setPasswordHash(passwordEncoder.encode(rawPassword));
|
||||||
|
mapper.insert(v);
|
||||||
|
return mapper.findById(v.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public EsnUser update(Long id, EsnUser v) {
|
||||||
|
v.setId(id);
|
||||||
|
mapper.update(v);
|
||||||
|
return mapper.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changePassword(Long id, String rawPassword) {
|
||||||
|
mapper.updatePassword(id, passwordEncoder.encode(rawPassword));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Long id) { mapper.delete(id); }
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.zioinfo.esn.service;
|
||||||
|
|
||||||
|
import com.zioinfo.esn.domain.WorkHistoryVo;
|
||||||
|
import com.zioinfo.esn.mapper.WorkMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WorkService {
|
||||||
|
private final WorkMapper mapper;
|
||||||
|
|
||||||
|
public List<WorkHistoryVo> list(String tenantCode, Long storeId, String status, String workType) {
|
||||||
|
return mapper.findAll(tenantCode, storeId, status, workType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorkHistoryVo get(Long id) { return mapper.findById(id); }
|
||||||
|
|
||||||
|
public WorkHistoryVo create(WorkHistoryVo v) {
|
||||||
|
mapper.insert(v);
|
||||||
|
return mapper.findById(v.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorkHistoryVo update(Long id, WorkHistoryVo v) {
|
||||||
|
v.setId(id);
|
||||||
|
mapper.update(v);
|
||||||
|
return mapper.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Long id) { mapper.delete(id); }
|
||||||
|
}
|
||||||
48
backend/src/main/resources/application.yml
Normal file
48
backend/src/main/resources/application.yml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
server:
|
||||||
|
port: 8016
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: zioinfo-esn
|
||||||
|
datasource:
|
||||||
|
url: ${DB_URL:jdbc:postgresql://localhost:5432/esn_db}
|
||||||
|
username: ${DB_USER:esn_user}
|
||||||
|
password: ${DB_PASS:esn_pass2026}
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
hikari:
|
||||||
|
maximum-pool-size: 3
|
||||||
|
connection-timeout: 30000
|
||||||
|
web:
|
||||||
|
resources:
|
||||||
|
static-locations: classpath:/static/
|
||||||
|
mvc:
|
||||||
|
throw-exception-if-no-handler-found: true
|
||||||
|
|
||||||
|
mybatis:
|
||||||
|
mapper-locations: classpath:mapper/*.xml
|
||||||
|
configuration:
|
||||||
|
map-underscore-to-camel-case: true
|
||||||
|
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
|
||||||
|
type-aliases-package: com.zioinfo.esn.domain
|
||||||
|
|
||||||
|
guardia:
|
||||||
|
ollama-url: ${OLLAMA_URL:http://localhost:11434}
|
||||||
|
ollama-text-model: ${OLLAMA_TEXT_MODEL:llama3}
|
||||||
|
itsm-url: ${ITSM_URL:http://localhost:9001}
|
||||||
|
jwt:
|
||||||
|
secret: ${JWT_SECRET:ZioInfoEsnJwtSecret2026VeryLongSecretKeyForHS256AlgorithmAtLeast256Bits}
|
||||||
|
expiration: 86400000
|
||||||
|
|
||||||
|
management:
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include: health
|
||||||
|
endpoint:
|
||||||
|
health:
|
||||||
|
show-details: never
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.zioinfo.esn: DEBUG
|
||||||
|
org.mybatis: WARN
|
||||||
333
backend/src/main/resources/db/schema.sql
Normal file
333
backend/src/main/resources/db/schema.sql
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- zioinfo-esn ESL 통합 플랫폼 — PostgreSQL 스키마 (esn_db)
|
||||||
|
-- 레거시 Spring Boot 1.5 / Java 8 6개 프로젝트 → 단일 플랫폼
|
||||||
|
-- 멀티테넌트: LGINNOTEK / LGIT / EMART / ZIOINFO
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- ── 테넌트 ──────────────────────────────────────────────────────────────────
|
||||||
|
CREATE TABLE IF NOT EXISTS esn_tenant (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_code VARCHAR(20) UNIQUE NOT NULL,
|
||||||
|
tenant_name VARCHAR(200) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO esn_tenant (tenant_code, tenant_name, description) VALUES
|
||||||
|
('LGINNOTEK', 'LG 이노텍', 'LG 이노텍 ESL 관리'),
|
||||||
|
('LGIT', 'LG IT', 'LG IT 서비스 ESL'),
|
||||||
|
('EMART', '이마트', '이마트 전자가격표 시스템'),
|
||||||
|
('ZIOINFO', '지오정보기술', '지오정보기술 자체 ESL')
|
||||||
|
ON CONFLICT (tenant_code) DO NOTHING;
|
||||||
|
|
||||||
|
-- ── 매장 그룹 ────────────────────────────────────────────────────────────────
|
||||||
|
CREATE TABLE IF NOT EXISTS esn_store_group (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_code VARCHAR(20) NOT NULL REFERENCES esn_tenant(tenant_code),
|
||||||
|
group_code VARCHAR(50) NOT NULL,
|
||||||
|
group_name VARCHAR(200) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
UNIQUE (tenant_code, group_code)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO esn_store_group (tenant_code, group_code, group_name) VALUES
|
||||||
|
('LGINNOTEK', 'GRP-LGI-01', 'LG이노텍 구미공장'),
|
||||||
|
('LGIT', 'GRP-LGIT-01','LG IT 본사'),
|
||||||
|
('EMART', 'GRP-EM-SEOUL', '이마트 서울권'),
|
||||||
|
('EMART', 'GRP-EM-GYEONG','이마트 경기권'),
|
||||||
|
('ZIOINFO', 'GRP-ZIO-01', '지오정보기술 본사')
|
||||||
|
ON CONFLICT (tenant_code, group_code) DO NOTHING;
|
||||||
|
|
||||||
|
-- ── 매장 ─────────────────────────────────────────────────────────────────────
|
||||||
|
CREATE TABLE IF NOT EXISTS esn_store (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_code VARCHAR(20) NOT NULL REFERENCES esn_tenant(tenant_code),
|
||||||
|
store_code VARCHAR(50) NOT NULL,
|
||||||
|
store_name VARCHAR(200) NOT NULL,
|
||||||
|
address VARCHAR(500),
|
||||||
|
phone VARCHAR(50),
|
||||||
|
store_group_id BIGINT REFERENCES esn_store_group(id),
|
||||||
|
manager_name VARCHAR(100),
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
UNIQUE (tenant_code, store_code)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO esn_store (tenant_code, store_code, store_name, address, manager_name, store_group_id)
|
||||||
|
SELECT 'LGINNOTEK', 'LGI-001', 'LG이노텍 구미 1라인', '경북 구미시 공단동 1', '김철수', g.id
|
||||||
|
FROM esn_store_group g WHERE g.group_code = 'GRP-LGI-01' AND g.tenant_code = 'LGINNOTEK'
|
||||||
|
ON CONFLICT (tenant_code, store_code) DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO esn_store (tenant_code, store_code, store_name, address, manager_name, store_group_id)
|
||||||
|
SELECT 'EMART', 'EM-001', '이마트 강남점', '서울 강남구 삼성동', '박영희', g.id
|
||||||
|
FROM esn_store_group g WHERE g.group_code = 'GRP-EM-SEOUL' AND g.tenant_code = 'EMART'
|
||||||
|
ON CONFLICT (tenant_code, store_code) DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO esn_store (tenant_code, store_code, store_name, address, manager_name, store_group_id)
|
||||||
|
SELECT 'EMART', 'EM-002', '이마트 수원점', '경기 수원시 팔달구', '이민수', g.id
|
||||||
|
FROM esn_store_group g WHERE g.group_code = 'GRP-EM-GYEONG' AND g.tenant_code = 'EMART'
|
||||||
|
ON CONFLICT (tenant_code, store_code) DO NOTHING;
|
||||||
|
|
||||||
|
-- ── ESL 템플릿 ───────────────────────────────────────────────────────────────
|
||||||
|
CREATE TABLE IF NOT EXISTS esn_template (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_code VARCHAR(20) NOT NULL REFERENCES esn_tenant(tenant_code),
|
||||||
|
template_code VARCHAR(50) NOT NULL,
|
||||||
|
template_name VARCHAR(200) NOT NULL,
|
||||||
|
template_type VARCHAR(50) DEFAULT 'PRICE', -- PRICE, INFO, PROMO, CUSTOM
|
||||||
|
width INTEGER,
|
||||||
|
height INTEGER,
|
||||||
|
description TEXT,
|
||||||
|
layout_json TEXT,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
UNIQUE (tenant_code, template_code)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO esn_template (tenant_code, template_code, template_name, template_type, width, height, description) VALUES
|
||||||
|
('EMART', 'EM-PRICE-STD', '이마트 표준 가격표', 'PRICE', 152, 76, '표준 2.9인치 ESL 템플릿'),
|
||||||
|
('EMART', 'EM-PROMO', '이마트 프로모션', 'PROMO', 296, 128, '5인치 프로모션 표시'),
|
||||||
|
('LGINNOTEK', 'LGI-INFO', 'LG이노텍 부품 정보', 'INFO', 152, 76, '부품 코드·수량 표시')
|
||||||
|
ON CONFLICT (tenant_code, template_code) DO NOTHING;
|
||||||
|
|
||||||
|
-- ── POS 가격 변환 ─────────────────────────────────────────────────────────────
|
||||||
|
CREATE TABLE IF NOT EXISTS esn_pos_cvt (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_code VARCHAR(20) NOT NULL REFERENCES esn_tenant(tenant_code),
|
||||||
|
store_id BIGINT REFERENCES esn_store(id),
|
||||||
|
pos_code VARCHAR(100),
|
||||||
|
product_code VARCHAR(100) NOT NULL,
|
||||||
|
product_name VARCHAR(300),
|
||||||
|
price NUMERIC(12,2),
|
||||||
|
sale_price NUMERIC(12,2),
|
||||||
|
currency VARCHAR(10) DEFAULT 'KRW',
|
||||||
|
status VARCHAR(20) DEFAULT 'PENDING', -- PENDING, PROCESSED, ERROR, IGNORED
|
||||||
|
error_message TEXT,
|
||||||
|
processed_at TIMESTAMP,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_pos_cvt_tenant ON esn_pos_cvt(tenant_code, status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_pos_cvt_store ON esn_pos_cvt(store_id, status);
|
||||||
|
|
||||||
|
-- ── 알람 ─────────────────────────────────────────────────────────────────────
|
||||||
|
CREATE TABLE IF NOT EXISTS esn_alarm (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_code VARCHAR(20) NOT NULL REFERENCES esn_tenant(tenant_code),
|
||||||
|
store_id BIGINT REFERENCES esn_store(id),
|
||||||
|
alarm_type VARCHAR(50) NOT NULL, -- DEVICE, NETWORK, BATTERY, FIRMWARE, SYSTEM
|
||||||
|
alarm_code VARCHAR(100),
|
||||||
|
severity VARCHAR(20) NOT NULL, -- CRITICAL, HIGH, MEDIUM, LOW
|
||||||
|
message TEXT,
|
||||||
|
status VARCHAR(20) DEFAULT 'OPEN', -- OPEN, ACKNOWLEDGED, RESOLVED
|
||||||
|
resolved_by VARCHAR(100),
|
||||||
|
resolution TEXT,
|
||||||
|
resolved_at TIMESTAMP,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_alarm_tenant ON esn_alarm(tenant_code, status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_alarm_severity ON esn_alarm(severity, status);
|
||||||
|
|
||||||
|
-- 샘플 알람
|
||||||
|
INSERT INTO esn_alarm (tenant_code, store_id, alarm_type, alarm_code, severity, message, status)
|
||||||
|
SELECT 'EMART', s.id, 'DEVICE', 'DEV-OFFLINE', 'HIGH', 'ESL 장치 오프라인 감지 — 매장 내 점검 필요', 'OPEN'
|
||||||
|
FROM esn_store s WHERE s.store_code = 'EM-001' LIMIT 1;
|
||||||
|
|
||||||
|
INSERT INTO esn_alarm (tenant_code, store_id, alarm_type, alarm_code, severity, message, status)
|
||||||
|
SELECT 'EMART', s.id, 'BATTERY', 'BAT-LOW', 'MEDIUM', 'ESL 배터리 잔량 15% 미만 — 배터리 교체 예정', 'OPEN'
|
||||||
|
FROM esn_store s WHERE s.store_code = 'EM-002' LIMIT 1;
|
||||||
|
|
||||||
|
-- ── HCore 장치 (게이트웨이/허브/ESL) ─────────────────────────────────────────
|
||||||
|
CREATE TABLE IF NOT EXISTS esn_hcore_device (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_code VARCHAR(20) NOT NULL REFERENCES esn_tenant(tenant_code),
|
||||||
|
store_id BIGINT REFERENCES esn_store(id),
|
||||||
|
device_type VARCHAR(50) NOT NULL, -- GATEWAY, HUB, ESL_DEVICE
|
||||||
|
device_id VARCHAR(100) NOT NULL,
|
||||||
|
ip_address VARCHAR(50),
|
||||||
|
mac_address VARCHAR(50),
|
||||||
|
firmware_version VARCHAR(50),
|
||||||
|
status VARCHAR(20) DEFAULT 'ONLINE', -- ONLINE, OFFLINE, ERROR, UPDATING
|
||||||
|
battery_level INTEGER,
|
||||||
|
signal_strength VARCHAR(20),
|
||||||
|
last_seen_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
UNIQUE (device_id)
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_hcore_tenant ON esn_hcore_device(tenant_code, status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_hcore_store ON esn_hcore_device(store_id, status);
|
||||||
|
|
||||||
|
-- 샘플 HCore 장치
|
||||||
|
INSERT INTO esn_hcore_device (tenant_code, store_id, device_type, device_id, ip_address, firmware_version, status)
|
||||||
|
SELECT 'EMART', s.id, 'GATEWAY', 'GW-EM-001-001', '192.168.1.1', '2.1.5', 'ONLINE'
|
||||||
|
FROM esn_store s WHERE s.store_code = 'EM-001' LIMIT 1
|
||||||
|
ON CONFLICT (device_id) DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO esn_hcore_device (tenant_code, store_id, device_type, device_id, ip_address, firmware_version, status, battery_level)
|
||||||
|
SELECT 'EMART', s.id, 'ESL_DEVICE', 'ESL-EM-001-0001', NULL, '1.8.2', 'ONLINE', 85
|
||||||
|
FROM esn_store s WHERE s.store_code = 'EM-001' LIMIT 1
|
||||||
|
ON CONFLICT (device_id) DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO esn_hcore_device (tenant_code, store_id, device_type, device_id, ip_address, firmware_version, status, battery_level)
|
||||||
|
SELECT 'EMART', s.id, 'ESL_DEVICE', 'ESL-EM-001-0002', NULL, '1.8.2', 'OFFLINE', 8
|
||||||
|
FROM esn_store s WHERE s.store_code = 'EM-001' LIMIT 1
|
||||||
|
ON CONFLICT (device_id) DO NOTHING;
|
||||||
|
|
||||||
|
-- ── 작업 이력 ────────────────────────────────────────────────────────────────
|
||||||
|
CREATE TABLE IF NOT EXISTS esn_work_history (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_code VARCHAR(20) NOT NULL REFERENCES esn_tenant(tenant_code),
|
||||||
|
store_id BIGINT REFERENCES esn_store(id),
|
||||||
|
work_type VARCHAR(50) NOT NULL, -- INSTALL, MAINTENANCE, FIRMWARE_UPDATE, REPLACEMENT, INSPECTION
|
||||||
|
work_content TEXT,
|
||||||
|
worker_name VARCHAR(100),
|
||||||
|
status VARCHAR(20) DEFAULT 'SCHEDULED', -- SCHEDULED, IN_PROGRESS, COMPLETED, CANCELLED
|
||||||
|
remarks TEXT,
|
||||||
|
started_at TIMESTAMP,
|
||||||
|
completed_at TIMESTAMP,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_work_tenant ON esn_work_history(tenant_code, status);
|
||||||
|
|
||||||
|
-- ── 펌웨어 ───────────────────────────────────────────────────────────────────
|
||||||
|
CREATE TABLE IF NOT EXISTS esn_firmware (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_code VARCHAR(20) NOT NULL REFERENCES esn_tenant(tenant_code),
|
||||||
|
firmware_version VARCHAR(50) NOT NULL,
|
||||||
|
device_type VARCHAR(50) NOT NULL,
|
||||||
|
file_name VARCHAR(300),
|
||||||
|
file_path VARCHAR(500),
|
||||||
|
file_size BIGINT,
|
||||||
|
checksum VARCHAR(200),
|
||||||
|
description TEXT,
|
||||||
|
is_latest BOOLEAN DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO esn_firmware (tenant_code, firmware_version, device_type, file_name, description, is_latest) VALUES
|
||||||
|
('EMART', '2.1.5', 'GATEWAY', 'gw_v2.1.5.bin', 'EMART 게이트웨이 최신 펌웨어', true),
|
||||||
|
('EMART', '1.8.2', 'ESL_DEVICE', 'esl_v1.8.2.bin', 'EMART ESL 디바이스 최신 펌웨어', true)
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- ── 사용자 ───────────────────────────────────────────────────────────────────
|
||||||
|
CREATE TABLE IF NOT EXISTS esn_user (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_code VARCHAR(20) REFERENCES esn_tenant(tenant_code),
|
||||||
|
username VARCHAR(200) UNIQUE NOT NULL,
|
||||||
|
password_hash VARCHAR(500) NOT NULL,
|
||||||
|
role VARCHAR(50) DEFAULT 'USER', -- ADMIN, MANAGER, USER
|
||||||
|
email VARCHAR(300),
|
||||||
|
phone VARCHAR(50),
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
last_login_at TIMESTAMP,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 기본 관리자 (비밀번호: admin123 / BCrypt)
|
||||||
|
INSERT INTO esn_user (tenant_code, username, password_hash, role)
|
||||||
|
VALUES (NULL, 'admin', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LnCWh7Vz.Xm', 'ADMIN')
|
||||||
|
ON CONFLICT (username) DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO esn_user (tenant_code, username, password_hash, role)
|
||||||
|
VALUES ('EMART', 'emart_manager', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LnCWh7Vz.Xm', 'MANAGER')
|
||||||
|
ON CONFLICT (username) DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO esn_user (tenant_code, username, password_hash, role)
|
||||||
|
VALUES ('LGINNOTEK', 'lgi_manager', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LnCWh7Vz.Xm', 'MANAGER')
|
||||||
|
ON CONFLICT (username) DO NOTHING;
|
||||||
|
|
||||||
|
-- ── 상품 ─────────────────────────────────────────────────────────────────────
|
||||||
|
CREATE TABLE IF NOT EXISTS esn_product (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_code VARCHAR(20) NOT NULL REFERENCES esn_tenant(tenant_code),
|
||||||
|
store_id BIGINT REFERENCES esn_store(id),
|
||||||
|
product_code VARCHAR(100) NOT NULL,
|
||||||
|
product_name VARCHAR(300) NOT NULL,
|
||||||
|
category VARCHAR(100),
|
||||||
|
price NUMERIC(12,2),
|
||||||
|
sale_price NUMERIC(12,2),
|
||||||
|
currency VARCHAR(10) DEFAULT 'KRW',
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_product_store ON esn_product(store_id, product_code);
|
||||||
|
|
||||||
|
-- 샘플 상품
|
||||||
|
INSERT INTO esn_product (tenant_code, store_id, product_code, product_name, category, price, sale_price)
|
||||||
|
SELECT 'EMART', s.id, 'P-001', '신라면', '라면/면류', 850, 800
|
||||||
|
FROM esn_store s WHERE s.store_code = 'EM-001' LIMIT 1
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- ── 태그 위치/상품 바인딩 ──────────────────────────────────────────────────────
|
||||||
|
-- e-paper 태그와 선반 위치 + 상품을 연결하는 핵심 테이블
|
||||||
|
CREATE TABLE IF NOT EXISTS esn_tag_binding (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_code VARCHAR(20) NOT NULL REFERENCES esn_tenant(tenant_code),
|
||||||
|
store_id BIGINT NOT NULL REFERENCES esn_store(id),
|
||||||
|
device_id VARCHAR(100) NOT NULL, -- ESL_DEVICE device_id
|
||||||
|
product_code VARCHAR(100), -- 현재 표시 중인 상품
|
||||||
|
location_code VARCHAR(100), -- 선반 위치코드 (예: A01-03, 통로A-1단)
|
||||||
|
template_code VARCHAR(50), -- 적용 템플릿
|
||||||
|
display_data TEXT, -- 현재 화면에 표시된 데이터 (JSON)
|
||||||
|
last_price NUMERIC(12,2), -- 마지막으로 전송된 가격
|
||||||
|
last_updated_at TIMESTAMP, -- 마지막 화면 갱신 시각
|
||||||
|
bind_status VARCHAR(20) DEFAULT 'ACTIVE', -- ACTIVE, UNBOUND, ERROR
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
UNIQUE (device_id)
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_binding_store ON esn_tag_binding(store_id, bind_status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_binding_product ON esn_tag_binding(tenant_code, product_code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_binding_loc ON esn_tag_binding(store_id, location_code);
|
||||||
|
|
||||||
|
-- 샘플 바인딩 (이마트 강남점 ESL-EM-001-0001 ↔ 신라면)
|
||||||
|
INSERT INTO esn_tag_binding (tenant_code, store_id, device_id, product_code, location_code, template_code, last_price)
|
||||||
|
SELECT 'EMART', s.id, 'ESL-EM-001-0001', 'P-001', 'A01-03', 'EM-PRICE-STD', 850
|
||||||
|
FROM esn_store s WHERE s.store_code = 'EM-001' LIMIT 1
|
||||||
|
ON CONFLICT (device_id) DO NOTHING;
|
||||||
|
|
||||||
|
-- ── 가격 업데이트 전파 큐 ────────────────────────────────────────────────────────
|
||||||
|
-- POS 가격 변경이 e-paper 태그에 도달하는 전 과정을 추적
|
||||||
|
CREATE TABLE IF NOT EXISTS esn_update_queue (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_code VARCHAR(20) NOT NULL REFERENCES esn_tenant(tenant_code),
|
||||||
|
store_id BIGINT REFERENCES esn_store(id),
|
||||||
|
binding_id BIGINT REFERENCES esn_tag_binding(id),
|
||||||
|
device_id VARCHAR(100) NOT NULL,
|
||||||
|
product_code VARCHAR(100),
|
||||||
|
new_price NUMERIC(12,2),
|
||||||
|
new_sale_price NUMERIC(12,2),
|
||||||
|
template_code VARCHAR(50),
|
||||||
|
render_data TEXT, -- 렌더링된 화면 데이터 (JSON)
|
||||||
|
priority INTEGER DEFAULT 5, -- 1(긴급) ~ 10(일반)
|
||||||
|
status VARCHAR(20) DEFAULT 'PENDING', -- PENDING, SENDING, SENT, CONFIRMED, FAILED, CANCELLED
|
||||||
|
retry_count INTEGER DEFAULT 0,
|
||||||
|
max_retry INTEGER DEFAULT 3,
|
||||||
|
error_message TEXT,
|
||||||
|
sent_at TIMESTAMP,
|
||||||
|
confirmed_at TIMESTAMP,
|
||||||
|
pos_cvt_id BIGINT REFERENCES esn_pos_cvt(id),
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_queue_device ON esn_update_queue(device_id, status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_queue_store ON esn_update_queue(store_id, status, created_at);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_queue_pend ON esn_update_queue(status, priority, created_at) WHERE status = 'PENDING';
|
||||||
|
|
||||||
|
-- ── 감사 로그 ─────────────────────────────────────────────────────────────────
|
||||||
|
CREATE TABLE IF NOT EXISTS esn_audit_log (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_code VARCHAR(20),
|
||||||
|
username VARCHAR(200),
|
||||||
|
action VARCHAR(200) NOT NULL,
|
||||||
|
resource VARCHAR(200),
|
||||||
|
resource_id VARCHAR(100),
|
||||||
|
detail TEXT,
|
||||||
|
ip_address VARCHAR(50),
|
||||||
|
user_agent VARCHAR(500),
|
||||||
|
success BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_audit_created ON esn_audit_log(created_at DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_audit_tenant ON esn_audit_log(tenant_code, created_at DESC);
|
||||||
86
backend/src/main/resources/mapper/AlarmMapper.xml
Normal file
86
backend/src/main/resources/mapper/AlarmMapper.xml
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.mapper.AlarmMapper">
|
||||||
|
|
||||||
|
<resultMap id="alarmMap" type="com.zioinfo.esn.domain.AlarmVo">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="tenantCode" column="tenant_code"/>
|
||||||
|
<result property="storeId" column="store_id"/>
|
||||||
|
<result property="storeName" column="store_name"/>
|
||||||
|
<result property="alarmType" column="alarm_type"/>
|
||||||
|
<result property="alarmCode" column="alarm_code"/>
|
||||||
|
<result property="severity" column="severity"/>
|
||||||
|
<result property="message" column="message"/>
|
||||||
|
<result property="status" column="status"/>
|
||||||
|
<result property="resolvedBy" column="resolved_by"/>
|
||||||
|
<result property="resolution" column="resolution"/>
|
||||||
|
<result property="resolvedAt" column="resolved_at"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="findAll" resultMap="alarmMap">
|
||||||
|
SELECT a.id, a.tenant_code, a.store_id, s.store_name, a.alarm_type, a.alarm_code,
|
||||||
|
a.severity, a.message, a.status, a.resolved_by, a.resolution, a.resolved_at, a.created_at
|
||||||
|
FROM esn_alarm a
|
||||||
|
LEFT JOIN esn_store s ON a.store_id = s.id
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND a.tenant_code = #{tenantCode}</if>
|
||||||
|
<if test="storeId != null">AND a.store_id = #{storeId}</if>
|
||||||
|
<if test="severity != null and severity != ''">AND a.severity = #{severity}</if>
|
||||||
|
<if test="status != null and status != ''">AND a.status = #{status}</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY a.created_at DESC LIMIT 200
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="alarmMap">
|
||||||
|
SELECT a.id, a.tenant_code, a.store_id, s.store_name, a.alarm_type, a.alarm_code,
|
||||||
|
a.severity, a.message, a.status, a.resolved_by, a.resolution, a.resolved_at, a.created_at
|
||||||
|
FROM esn_alarm a
|
||||||
|
LEFT JOIN esn_store s ON a.store_id = s.id
|
||||||
|
WHERE a.id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO esn_alarm (tenant_code, store_id, alarm_type, alarm_code, severity, message, status)
|
||||||
|
VALUES (#{tenantCode}, #{storeId}, #{alarmType}, #{alarmCode}, #{severity}, #{message}, 'OPEN')
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="update">
|
||||||
|
UPDATE esn_alarm SET
|
||||||
|
alarm_type = #{alarmType},
|
||||||
|
severity = #{severity},
|
||||||
|
message = #{message},
|
||||||
|
status = #{status}
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="resolve">
|
||||||
|
UPDATE esn_alarm SET
|
||||||
|
status = 'RESOLVED',
|
||||||
|
resolved_by = #{resolvedBy},
|
||||||
|
resolution = #{resolution},
|
||||||
|
resolved_at = NOW()
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="delete">DELETE FROM esn_alarm WHERE id = #{id}</delete>
|
||||||
|
|
||||||
|
<select id="countUnresolved" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_alarm WHERE status != 'RESOLVED'
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countCritical" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_alarm WHERE severity = 'CRITICAL' AND status != 'RESOLVED'
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countAll" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_alarm
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</where>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
46
backend/src/main/resources/mapper/DashboardMapper.xml
Normal file
46
backend/src/main/resources/mapper/DashboardMapper.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.mapper.DashboardMapper">
|
||||||
|
|
||||||
|
<select id="getSummary" resultType="com.zioinfo.esn.domain.DashboardVo">
|
||||||
|
SELECT
|
||||||
|
(SELECT COUNT(*) FROM esn_store
|
||||||
|
WHERE 1=1 <if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
) AS total_stores,
|
||||||
|
(SELECT COUNT(*) FROM esn_store WHERE is_active = true
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
) AS active_stores,
|
||||||
|
(SELECT COUNT(*) FROM esn_alarm
|
||||||
|
WHERE 1=1 <if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
) AS total_alarms,
|
||||||
|
(SELECT COUNT(*) FROM esn_alarm WHERE status != 'RESOLVED'
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
) AS unresolved_alarms,
|
||||||
|
(SELECT COUNT(*) FROM esn_alarm WHERE severity = 'CRITICAL' AND status != 'RESOLVED'
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
) AS critical_alarms,
|
||||||
|
(SELECT COUNT(*) FROM esn_hcore_device
|
||||||
|
WHERE 1=1 <if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
) AS total_devices,
|
||||||
|
(SELECT COUNT(*) FROM esn_hcore_device WHERE status = 'ONLINE'
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
) AS online_devices,
|
||||||
|
(SELECT COUNT(*) FROM esn_hcore_device WHERE status = 'OFFLINE'
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
) AS offline_devices,
|
||||||
|
(SELECT COUNT(*) FROM esn_work_history WHERE created_at::date = CURRENT_DATE
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
) AS total_work_today,
|
||||||
|
(SELECT COUNT(*) FROM esn_work_history WHERE status = 'COMPLETED' AND completed_at::date = CURRENT_DATE
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
) AS completed_work_today,
|
||||||
|
(SELECT COUNT(*) FROM esn_pos_cvt WHERE status = 'PENDING'
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
) AS pending_pos_cvt,
|
||||||
|
(SELECT COUNT(*) FROM esn_pos_cvt WHERE status = 'PROCESSED' AND processed_at::date = CURRENT_DATE
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
) AS processed_pos_cvt_today
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
69
backend/src/main/resources/mapper/FirmwareMapper.xml
Normal file
69
backend/src/main/resources/mapper/FirmwareMapper.xml
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.mapper.FirmwareMapper">
|
||||||
|
|
||||||
|
<resultMap id="firmwareMap" type="com.zioinfo.esn.domain.FirmwareVo">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="tenantCode" column="tenant_code"/>
|
||||||
|
<result property="firmwareVersion" column="firmware_version"/>
|
||||||
|
<result property="deviceType" column="device_type"/>
|
||||||
|
<result property="fileName" column="file_name"/>
|
||||||
|
<result property="filePath" column="file_path"/>
|
||||||
|
<result property="fileSize" column="file_size"/>
|
||||||
|
<result property="checksum" column="checksum"/>
|
||||||
|
<result property="description" column="description"/>
|
||||||
|
<result property="latest" column="is_latest"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="findAll" resultMap="firmwareMap">
|
||||||
|
SELECT id, tenant_code, firmware_version, device_type, file_name, file_path,
|
||||||
|
file_size, checksum, description, is_latest, created_at
|
||||||
|
FROM esn_firmware
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
<if test="deviceType != null and deviceType != ''">AND device_type = #{deviceType}</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="firmwareMap">
|
||||||
|
SELECT id, tenant_code, firmware_version, device_type, file_name, file_path,
|
||||||
|
file_size, checksum, description, is_latest, created_at
|
||||||
|
FROM esn_firmware WHERE id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findLatest" resultMap="firmwareMap">
|
||||||
|
SELECT id, tenant_code, firmware_version, device_type, file_name, file_path,
|
||||||
|
file_size, checksum, description, is_latest, created_at
|
||||||
|
FROM esn_firmware
|
||||||
|
WHERE device_type = #{deviceType} AND is_latest = true
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO esn_firmware (tenant_code, firmware_version, device_type, file_name, file_path,
|
||||||
|
file_size, checksum, description, is_latest)
|
||||||
|
VALUES (#{tenantCode}, #{firmwareVersion}, #{deviceType}, #{fileName}, #{filePath},
|
||||||
|
#{fileSize}, #{checksum}, #{description}, #{latest})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="update">
|
||||||
|
UPDATE esn_firmware SET
|
||||||
|
firmware_version = #{firmwareVersion},
|
||||||
|
description = #{description},
|
||||||
|
is_latest = #{latest}
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="clearLatest">
|
||||||
|
UPDATE esn_firmware SET is_latest = false
|
||||||
|
WHERE device_type = #{deviceType}
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="delete">DELETE FROM esn_firmware WHERE id = #{id}</delete>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
99
backend/src/main/resources/mapper/HCoreMapper.xml
Normal file
99
backend/src/main/resources/mapper/HCoreMapper.xml
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.mapper.HCoreMapper">
|
||||||
|
|
||||||
|
<resultMap id="hcoreMap" type="com.zioinfo.esn.domain.HCoreVo">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="tenantCode" column="tenant_code"/>
|
||||||
|
<result property="storeId" column="store_id"/>
|
||||||
|
<result property="storeName" column="store_name"/>
|
||||||
|
<result property="deviceType" column="device_type"/>
|
||||||
|
<result property="deviceId" column="device_id"/>
|
||||||
|
<result property="ipAddress" column="ip_address"/>
|
||||||
|
<result property="macAddress" column="mac_address"/>
|
||||||
|
<result property="firmwareVersion" column="firmware_version"/>
|
||||||
|
<result property="status" column="status"/>
|
||||||
|
<result property="batteryLevel" column="battery_level"/>
|
||||||
|
<result property="signalStrength" column="signal_strength"/>
|
||||||
|
<result property="lastSeenAt" column="last_seen_at"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="findAll" resultMap="hcoreMap">
|
||||||
|
SELECT h.id, h.tenant_code, h.store_id, s.store_name, h.device_type, h.device_id,
|
||||||
|
h.ip_address, h.mac_address, h.firmware_version, h.status, h.battery_level,
|
||||||
|
h.signal_strength, h.last_seen_at, h.created_at
|
||||||
|
FROM esn_hcore_device h
|
||||||
|
LEFT JOIN esn_store s ON h.store_id = s.id
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND h.tenant_code = #{tenantCode}</if>
|
||||||
|
<if test="storeId != null">AND h.store_id = #{storeId}</if>
|
||||||
|
<if test="deviceType != null and deviceType != ''">AND h.device_type = #{deviceType}</if>
|
||||||
|
<if test="status != null and status != ''">AND h.status = #{status}</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY h.device_id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="hcoreMap">
|
||||||
|
SELECT h.id, h.tenant_code, h.store_id, s.store_name, h.device_type, h.device_id,
|
||||||
|
h.ip_address, h.mac_address, h.firmware_version, h.status, h.battery_level,
|
||||||
|
h.signal_strength, h.last_seen_at, h.created_at
|
||||||
|
FROM esn_hcore_device h
|
||||||
|
LEFT JOIN esn_store s ON h.store_id = s.id
|
||||||
|
WHERE h.id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findByDeviceId" resultMap="hcoreMap">
|
||||||
|
SELECT h.id, h.tenant_code, h.store_id, s.store_name, h.device_type, h.device_id,
|
||||||
|
h.ip_address, h.mac_address, h.firmware_version, h.status, h.battery_level,
|
||||||
|
h.signal_strength, h.last_seen_at, h.created_at
|
||||||
|
FROM esn_hcore_device h
|
||||||
|
LEFT JOIN esn_store s ON h.store_id = s.id
|
||||||
|
WHERE h.device_id = #{deviceId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO esn_hcore_device (tenant_code, store_id, device_type, device_id, ip_address,
|
||||||
|
mac_address, firmware_version, status)
|
||||||
|
VALUES (#{tenantCode}, #{storeId}, #{deviceType}, #{deviceId}, #{ipAddress},
|
||||||
|
#{macAddress}, #{firmwareVersion}, #{status})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="update">
|
||||||
|
UPDATE esn_hcore_device SET
|
||||||
|
ip_address = #{ipAddress},
|
||||||
|
mac_address = #{macAddress},
|
||||||
|
firmware_version = #{firmwareVersion},
|
||||||
|
status = #{status},
|
||||||
|
battery_level = #{batteryLevel},
|
||||||
|
signal_strength = #{signalStrength},
|
||||||
|
last_seen_at = NOW()
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="updateStatus">
|
||||||
|
UPDATE esn_hcore_device SET status = #{status}, last_seen_at = NOW()
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="delete">DELETE FROM esn_hcore_device WHERE id = #{id}</delete>
|
||||||
|
|
||||||
|
<select id="countOnline" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_hcore_device WHERE status = 'ONLINE'
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countOffline" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_hcore_device WHERE status = 'OFFLINE'
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countAll" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_hcore_device
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</where>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
75
backend/src/main/resources/mapper/PosCvtMapper.xml
Normal file
75
backend/src/main/resources/mapper/PosCvtMapper.xml
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.mapper.PosCvtMapper">
|
||||||
|
|
||||||
|
<resultMap id="posMap" type="com.zioinfo.esn.domain.PosCvtVo">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="tenantCode" column="tenant_code"/>
|
||||||
|
<result property="storeId" column="store_id"/>
|
||||||
|
<result property="storeName" column="store_name"/>
|
||||||
|
<result property="posCode" column="pos_code"/>
|
||||||
|
<result property="productCode" column="product_code"/>
|
||||||
|
<result property="productName" column="product_name"/>
|
||||||
|
<result property="price" column="price"/>
|
||||||
|
<result property="salePrice" column="sale_price"/>
|
||||||
|
<result property="currency" column="currency"/>
|
||||||
|
<result property="status" column="status"/>
|
||||||
|
<result property="errorMessage" column="error_message"/>
|
||||||
|
<result property="processedAt" column="processed_at"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="findAll" resultMap="posMap">
|
||||||
|
SELECT p.id, p.tenant_code, p.store_id, s.store_name, p.pos_code,
|
||||||
|
p.product_code, p.product_name, p.price, p.sale_price, p.currency,
|
||||||
|
p.status, p.error_message, p.processed_at, p.created_at
|
||||||
|
FROM esn_pos_cvt p
|
||||||
|
LEFT JOIN esn_store s ON p.store_id = s.id
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND p.tenant_code = #{tenantCode}</if>
|
||||||
|
<if test="storeId != null">AND p.store_id = #{storeId}</if>
|
||||||
|
<if test="status != null and status != ''">AND p.status = #{status}</if>
|
||||||
|
<if test="keyword != null and keyword != ''">
|
||||||
|
AND (p.product_code ILIKE '%' || #{keyword} || '%' OR p.product_name ILIKE '%' || #{keyword} || '%')
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY p.created_at DESC LIMIT 500
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="posMap">
|
||||||
|
SELECT p.id, p.tenant_code, p.store_id, s.store_name, p.pos_code,
|
||||||
|
p.product_code, p.product_name, p.price, p.sale_price, p.currency,
|
||||||
|
p.status, p.error_message, p.processed_at, p.created_at
|
||||||
|
FROM esn_pos_cvt p
|
||||||
|
LEFT JOIN esn_store s ON p.store_id = s.id
|
||||||
|
WHERE p.id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO esn_pos_cvt (tenant_code, store_id, pos_code, product_code, product_name,
|
||||||
|
price, sale_price, currency, status)
|
||||||
|
VALUES (#{tenantCode}, #{storeId}, #{posCode}, #{productCode}, #{productName},
|
||||||
|
#{price}, #{salePrice}, #{currency}, 'PENDING')
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="updateStatus">
|
||||||
|
UPDATE esn_pos_cvt SET
|
||||||
|
status = #{status},
|
||||||
|
error_message = #{errorMessage},
|
||||||
|
processed_at = CASE WHEN #{status} = 'PROCESSED' THEN NOW() ELSE processed_at END
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<select id="countPending" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_pos_cvt WHERE status = 'PENDING'
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countProcessedToday" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_pos_cvt
|
||||||
|
WHERE status = 'PROCESSED' AND processed_at::date = CURRENT_DATE
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
74
backend/src/main/resources/mapper/ProductMapper.xml
Normal file
74
backend/src/main/resources/mapper/ProductMapper.xml
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.mapper.ProductMapper">
|
||||||
|
|
||||||
|
<resultMap id="productMap" type="com.zioinfo.esn.domain.ProductVo">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="tenantCode" column="tenant_code"/>
|
||||||
|
<result property="storeId" column="store_id"/>
|
||||||
|
<result property="storeName" column="store_name"/>
|
||||||
|
<result property="productCode" column="product_code"/>
|
||||||
|
<result property="productName" column="product_name"/>
|
||||||
|
<result property="category" column="category"/>
|
||||||
|
<result property="price" column="price"/>
|
||||||
|
<result property="salePrice" column="sale_price"/>
|
||||||
|
<result property="currency" column="currency"/>
|
||||||
|
<result property="active" column="is_active"/>
|
||||||
|
<result property="updatedAt" column="updated_at"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="findAll" resultMap="productMap">
|
||||||
|
SELECT p.id, p.tenant_code, p.store_id, s.store_name, p.product_code, p.product_name,
|
||||||
|
p.category, p.price, p.sale_price, p.currency, p.is_active, p.updated_at, p.created_at
|
||||||
|
FROM esn_product p
|
||||||
|
LEFT JOIN esn_store s ON p.store_id = s.id
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND p.tenant_code = #{tenantCode}</if>
|
||||||
|
<if test="storeId != null">AND p.store_id = #{storeId}</if>
|
||||||
|
<if test="keyword != null and keyword != ''">
|
||||||
|
AND (p.product_code ILIKE '%' || #{keyword} || '%' OR p.product_name ILIKE '%' || #{keyword} || '%')
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY p.product_code LIMIT 500
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="productMap">
|
||||||
|
SELECT p.id, p.tenant_code, p.store_id, s.store_name, p.product_code, p.product_name,
|
||||||
|
p.category, p.price, p.sale_price, p.currency, p.is_active, p.updated_at, p.created_at
|
||||||
|
FROM esn_product p
|
||||||
|
LEFT JOIN esn_store s ON p.store_id = s.id
|
||||||
|
WHERE p.id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findByCode" resultMap="productMap">
|
||||||
|
SELECT p.id, p.tenant_code, p.store_id, s.store_name, p.product_code, p.product_name,
|
||||||
|
p.category, p.price, p.sale_price, p.currency, p.is_active, p.updated_at, p.created_at
|
||||||
|
FROM esn_product p
|
||||||
|
LEFT JOIN esn_store s ON p.store_id = s.id
|
||||||
|
WHERE p.product_code = #{productCode} AND p.store_id = #{storeId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO esn_product (tenant_code, store_id, product_code, product_name,
|
||||||
|
category, price, sale_price, currency, is_active)
|
||||||
|
VALUES (#{tenantCode}, #{storeId}, #{productCode}, #{productName},
|
||||||
|
#{category}, #{price}, #{salePrice}, #{currency}, #{active})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="update">
|
||||||
|
UPDATE esn_product SET
|
||||||
|
product_name = #{productName},
|
||||||
|
category = #{category},
|
||||||
|
price = #{price},
|
||||||
|
sale_price = #{salePrice},
|
||||||
|
currency = #{currency},
|
||||||
|
is_active = #{active},
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="delete">DELETE FROM esn_product WHERE id = #{id}</delete>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
45
backend/src/main/resources/mapper/StoreGroupMapper.xml
Normal file
45
backend/src/main/resources/mapper/StoreGroupMapper.xml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.mapper.StoreGroupMapper">
|
||||||
|
|
||||||
|
<resultMap id="groupMap" type="com.zioinfo.esn.domain.StoreGroupVo">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="tenantCode" column="tenant_code"/>
|
||||||
|
<result property="groupCode" column="group_code"/>
|
||||||
|
<result property="groupName" column="group_name"/>
|
||||||
|
<result property="description" column="description"/>
|
||||||
|
<result property="active" column="is_active"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="findAll" resultMap="groupMap">
|
||||||
|
SELECT id, tenant_code, group_code, group_name, description, is_active, created_at
|
||||||
|
FROM esn_store_group
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY group_code
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="groupMap">
|
||||||
|
SELECT id, tenant_code, group_code, group_name, description, is_active, created_at
|
||||||
|
FROM esn_store_group WHERE id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO esn_store_group (tenant_code, group_code, group_name, description, is_active)
|
||||||
|
VALUES (#{tenantCode}, #{groupCode}, #{groupName}, #{description}, #{active})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="update">
|
||||||
|
UPDATE esn_store_group SET
|
||||||
|
group_name = #{groupName},
|
||||||
|
description = #{description},
|
||||||
|
is_active = #{active}
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="delete">DELETE FROM esn_store_group WHERE id = #{id}</delete>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
84
backend/src/main/resources/mapper/StoreMapper.xml
Normal file
84
backend/src/main/resources/mapper/StoreMapper.xml
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.mapper.StoreMapper">
|
||||||
|
|
||||||
|
<resultMap id="storeMap" type="com.zioinfo.esn.domain.StoreVo">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="tenantCode" column="tenant_code"/>
|
||||||
|
<result property="storeCode" column="store_code"/>
|
||||||
|
<result property="storeName" column="store_name"/>
|
||||||
|
<result property="address" column="address"/>
|
||||||
|
<result property="phone" column="phone"/>
|
||||||
|
<result property="storeGroupId" column="store_group_id"/>
|
||||||
|
<result property="storeGroupName" column="group_name"/>
|
||||||
|
<result property="managerName" column="manager_name"/>
|
||||||
|
<result property="active" column="is_active"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="findAll" resultMap="storeMap">
|
||||||
|
SELECT s.id, s.tenant_code, s.store_code, s.store_name, s.address, s.phone,
|
||||||
|
s.store_group_id, g.group_name, s.manager_name, s.is_active, s.created_at
|
||||||
|
FROM esn_store s
|
||||||
|
LEFT JOIN esn_store_group g ON s.store_group_id = g.id
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND s.tenant_code = #{tenantCode}</if>
|
||||||
|
<if test="storeGroupId != null">AND s.store_group_id = #{storeGroupId}</if>
|
||||||
|
<if test="keyword != null and keyword != ''">
|
||||||
|
AND (s.store_name ILIKE '%' || #{keyword} || '%' OR s.store_code ILIKE '%' || #{keyword} || '%')
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY s.store_code
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="storeMap">
|
||||||
|
SELECT s.id, s.tenant_code, s.store_code, s.store_name, s.address, s.phone,
|
||||||
|
s.store_group_id, g.group_name, s.manager_name, s.is_active, s.created_at
|
||||||
|
FROM esn_store s
|
||||||
|
LEFT JOIN esn_store_group g ON s.store_group_id = g.id
|
||||||
|
WHERE s.id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findByCode" resultMap="storeMap">
|
||||||
|
SELECT s.id, s.tenant_code, s.store_code, s.store_name, s.address, s.phone,
|
||||||
|
s.store_group_id, g.group_name, s.manager_name, s.is_active, s.created_at
|
||||||
|
FROM esn_store s
|
||||||
|
LEFT JOIN esn_store_group g ON s.store_group_id = g.id
|
||||||
|
WHERE s.store_code = #{storeCode} AND s.tenant_code = #{tenantCode}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO esn_store (tenant_code, store_code, store_name, address, phone,
|
||||||
|
store_group_id, manager_name, is_active)
|
||||||
|
VALUES (#{tenantCode}, #{storeCode}, #{storeName}, #{address}, #{phone},
|
||||||
|
#{storeGroupId}, #{managerName}, #{active})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="update">
|
||||||
|
UPDATE esn_store SET
|
||||||
|
store_name = #{storeName},
|
||||||
|
address = #{address},
|
||||||
|
phone = #{phone},
|
||||||
|
store_group_id = #{storeGroupId},
|
||||||
|
manager_name = #{managerName},
|
||||||
|
is_active = #{active}
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="delete">DELETE FROM esn_store WHERE id = #{id}</delete>
|
||||||
|
|
||||||
|
<select id="countActive" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_store
|
||||||
|
WHERE is_active = true
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countAll" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_store
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</where>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
139
backend/src/main/resources/mapper/TagBindingMapper.xml
Normal file
139
backend/src/main/resources/mapper/TagBindingMapper.xml
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.mapper.TagBindingMapper">
|
||||||
|
|
||||||
|
<resultMap id="bindingMap" type="com.zioinfo.esn.domain.TagBindingVo">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="tenantCode" column="tenant_code"/>
|
||||||
|
<result property="storeId" column="store_id"/>
|
||||||
|
<result property="storeName" column="store_name"/>
|
||||||
|
<result property="deviceId" column="device_id"/>
|
||||||
|
<result property="productCode" column="product_code"/>
|
||||||
|
<result property="productName" column="product_name"/>
|
||||||
|
<result property="locationCode" column="location_code"/>
|
||||||
|
<result property="templateCode" column="template_code"/>
|
||||||
|
<result property="displayData" column="display_data"/>
|
||||||
|
<result property="lastPrice" column="last_price"/>
|
||||||
|
<result property="lastUpdatedAt" column="last_updated_at"/>
|
||||||
|
<result property="bindStatus" column="bind_status"/>
|
||||||
|
<result property="deviceStatus" column="device_status"/>
|
||||||
|
<result property="batteryLevel" column="battery_level"/>
|
||||||
|
<result property="signalStrength" column="signal_strength"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="findAll" resultMap="bindingMap">
|
||||||
|
SELECT b.id, b.tenant_code, b.store_id, s.store_name,
|
||||||
|
b.device_id, b.product_code, p.product_name,
|
||||||
|
b.location_code, b.template_code, b.display_data,
|
||||||
|
b.last_price, b.last_updated_at, b.bind_status,
|
||||||
|
h.status AS device_status, h.battery_level, h.signal_strength,
|
||||||
|
b.created_at
|
||||||
|
FROM esn_tag_binding b
|
||||||
|
LEFT JOIN esn_store s ON b.store_id = s.id
|
||||||
|
LEFT JOIN esn_product p ON b.store_id = p.store_id AND b.product_code = p.product_code
|
||||||
|
LEFT JOIN esn_hcore_device h ON b.device_id = h.device_id
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND b.tenant_code = #{tenantCode}</if>
|
||||||
|
<if test="storeId != null">AND b.store_id = #{storeId}</if>
|
||||||
|
<if test="bindStatus != null and bindStatus != ''">AND b.bind_status = #{bindStatus}</if>
|
||||||
|
<if test="keyword != null and keyword != ''">
|
||||||
|
AND (b.device_id ILIKE '%'||#{keyword}||'%'
|
||||||
|
OR b.product_code ILIKE '%'||#{keyword}||'%'
|
||||||
|
OR b.location_code ILIKE '%'||#{keyword}||'%'
|
||||||
|
OR p.product_name ILIKE '%'||#{keyword}||'%')
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY b.store_id, b.location_code
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="bindingMap">
|
||||||
|
SELECT b.id, b.tenant_code, b.store_id, s.store_name,
|
||||||
|
b.device_id, b.product_code, p.product_name,
|
||||||
|
b.location_code, b.template_code, b.display_data,
|
||||||
|
b.last_price, b.last_updated_at, b.bind_status,
|
||||||
|
h.status AS device_status, h.battery_level, h.signal_strength,
|
||||||
|
b.created_at
|
||||||
|
FROM esn_tag_binding b
|
||||||
|
LEFT JOIN esn_store s ON b.store_id = s.id
|
||||||
|
LEFT JOIN esn_product p ON b.store_id = p.store_id AND b.product_code = p.product_code
|
||||||
|
LEFT JOIN esn_hcore_device h ON b.device_id = h.device_id
|
||||||
|
WHERE b.id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findByDeviceId" resultMap="bindingMap">
|
||||||
|
SELECT b.id, b.tenant_code, b.store_id, s.store_name,
|
||||||
|
b.device_id, b.product_code, p.product_name,
|
||||||
|
b.location_code, b.template_code, b.display_data,
|
||||||
|
b.last_price, b.last_updated_at, b.bind_status,
|
||||||
|
h.status AS device_status, h.battery_level, h.signal_strength,
|
||||||
|
b.created_at
|
||||||
|
FROM esn_tag_binding b
|
||||||
|
LEFT JOIN esn_store s ON b.store_id = s.id
|
||||||
|
LEFT JOIN esn_product p ON b.store_id = p.store_id AND b.product_code = p.product_code
|
||||||
|
LEFT JOIN esn_hcore_device h ON b.device_id = h.device_id
|
||||||
|
WHERE b.device_id = #{deviceId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findByProductCode" resultMap="bindingMap">
|
||||||
|
SELECT b.id, b.tenant_code, b.store_id, s.store_name,
|
||||||
|
b.device_id, b.product_code, p.product_name,
|
||||||
|
b.location_code, b.template_code, b.display_data,
|
||||||
|
b.last_price, b.last_updated_at, b.bind_status,
|
||||||
|
h.status AS device_status, h.battery_level, h.signal_strength,
|
||||||
|
b.created_at
|
||||||
|
FROM esn_tag_binding b
|
||||||
|
LEFT JOIN esn_store s ON b.store_id = s.id
|
||||||
|
LEFT JOIN esn_product p ON b.store_id = p.store_id AND b.product_code = p.product_code
|
||||||
|
LEFT JOIN esn_hcore_device h ON b.device_id = h.device_id
|
||||||
|
WHERE b.tenant_code = #{tenantCode}
|
||||||
|
AND b.store_id = #{storeId}
|
||||||
|
AND b.product_code = #{productCode}
|
||||||
|
AND b.bind_status = 'ACTIVE'
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO esn_tag_binding
|
||||||
|
(tenant_code, store_id, device_id, product_code, location_code,
|
||||||
|
template_code, last_price, bind_status)
|
||||||
|
VALUES
|
||||||
|
(#{tenantCode}, #{storeId}, #{deviceId}, #{productCode}, #{locationCode},
|
||||||
|
#{templateCode}, #{lastPrice}, 'ACTIVE')
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="update">
|
||||||
|
UPDATE esn_tag_binding SET
|
||||||
|
product_code = #{productCode},
|
||||||
|
location_code = #{locationCode},
|
||||||
|
template_code = #{templateCode},
|
||||||
|
display_data = #{displayData},
|
||||||
|
last_price = #{lastPrice},
|
||||||
|
last_updated_at = NOW(),
|
||||||
|
bind_status = #{bindStatus}
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="unbind">
|
||||||
|
UPDATE esn_tag_binding SET
|
||||||
|
bind_status = 'UNBOUND',
|
||||||
|
product_code = NULL,
|
||||||
|
last_updated_at = NOW()
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="delete">DELETE FROM esn_tag_binding WHERE id = #{id}</delete>
|
||||||
|
|
||||||
|
<select id="countAll" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_tag_binding
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</where>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countActive" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_tag_binding WHERE bind_status = 'ACTIVE'
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
58
backend/src/main/resources/mapper/TemplateMapper.xml
Normal file
58
backend/src/main/resources/mapper/TemplateMapper.xml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.mapper.TemplateMapper">
|
||||||
|
|
||||||
|
<resultMap id="templateMap" type="com.zioinfo.esn.domain.TemplateVo">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="tenantCode" column="tenant_code"/>
|
||||||
|
<result property="templateCode" column="template_code"/>
|
||||||
|
<result property="templateName" column="template_name"/>
|
||||||
|
<result property="templateType" column="template_type"/>
|
||||||
|
<result property="width" column="width"/>
|
||||||
|
<result property="height" column="height"/>
|
||||||
|
<result property="description" column="description"/>
|
||||||
|
<result property="layoutJson" column="layout_json"/>
|
||||||
|
<result property="active" column="is_active"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="findAll" resultMap="templateMap">
|
||||||
|
SELECT id, tenant_code, template_code, template_name, template_type,
|
||||||
|
width, height, description, layout_json, is_active, created_at
|
||||||
|
FROM esn_template
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
<if test="templateType != null and templateType != ''">AND template_type = #{templateType}</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY template_code
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="templateMap">
|
||||||
|
SELECT id, tenant_code, template_code, template_name, template_type,
|
||||||
|
width, height, description, layout_json, is_active, created_at
|
||||||
|
FROM esn_template WHERE id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO esn_template (tenant_code, template_code, template_name, template_type,
|
||||||
|
width, height, description, layout_json, is_active)
|
||||||
|
VALUES (#{tenantCode}, #{templateCode}, #{templateName}, #{templateType},
|
||||||
|
#{width}, #{height}, #{description}, #{layoutJson}, #{active})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="update">
|
||||||
|
UPDATE esn_template SET
|
||||||
|
template_name = #{templateName},
|
||||||
|
template_type = #{templateType},
|
||||||
|
width = #{width},
|
||||||
|
height = #{height},
|
||||||
|
description = #{description},
|
||||||
|
layout_json = #{layoutJson},
|
||||||
|
is_active = #{active}
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="delete">DELETE FROM esn_template WHERE id = #{id}</delete>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
47
backend/src/main/resources/mapper/TenantMapper.xml
Normal file
47
backend/src/main/resources/mapper/TenantMapper.xml
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.mapper.TenantMapper">
|
||||||
|
|
||||||
|
<resultMap id="tenantMap" type="com.zioinfo.esn.domain.TenantVo">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="tenantCode" column="tenant_code"/>
|
||||||
|
<result property="tenantName" column="tenant_name"/>
|
||||||
|
<result property="description" column="description"/>
|
||||||
|
<result property="active" column="is_active"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="findAll" resultMap="tenantMap">
|
||||||
|
SELECT id, tenant_code, tenant_name, description, is_active, created_at
|
||||||
|
FROM esn_tenant ORDER BY tenant_code
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="tenantMap">
|
||||||
|
SELECT id, tenant_code, tenant_name, description, is_active, created_at
|
||||||
|
FROM esn_tenant WHERE id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findByCode" resultMap="tenantMap">
|
||||||
|
SELECT id, tenant_code, tenant_name, description, is_active, created_at
|
||||||
|
FROM esn_tenant WHERE tenant_code = #{tenantCode}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO esn_tenant (tenant_code, tenant_name, description, is_active)
|
||||||
|
VALUES (#{tenantCode}, #{tenantName}, #{description}, #{active})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="update">
|
||||||
|
UPDATE esn_tenant SET
|
||||||
|
tenant_name = #{tenantName},
|
||||||
|
description = #{description},
|
||||||
|
is_active = #{active}
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="delete">
|
||||||
|
DELETE FROM esn_tenant WHERE id = #{id}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
131
backend/src/main/resources/mapper/UpdateQueueMapper.xml
Normal file
131
backend/src/main/resources/mapper/UpdateQueueMapper.xml
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.mapper.UpdateQueueMapper">
|
||||||
|
|
||||||
|
<resultMap id="queueMap" type="com.zioinfo.esn.domain.UpdateQueueVo">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="tenantCode" column="tenant_code"/>
|
||||||
|
<result property="storeId" column="store_id"/>
|
||||||
|
<result property="storeName" column="store_name"/>
|
||||||
|
<result property="bindingId" column="binding_id"/>
|
||||||
|
<result property="deviceId" column="device_id"/>
|
||||||
|
<result property="locationCode" column="location_code"/>
|
||||||
|
<result property="productCode" column="product_code"/>
|
||||||
|
<result property="productName" column="product_name"/>
|
||||||
|
<result property="newPrice" column="new_price"/>
|
||||||
|
<result property="newSalePrice" column="new_sale_price"/>
|
||||||
|
<result property="templateCode" column="template_code"/>
|
||||||
|
<result property="renderData" column="render_data"/>
|
||||||
|
<result property="priority" column="priority"/>
|
||||||
|
<result property="status" column="status"/>
|
||||||
|
<result property="retryCount" column="retry_count"/>
|
||||||
|
<result property="maxRetry" column="max_retry"/>
|
||||||
|
<result property="errorMessage" column="error_message"/>
|
||||||
|
<result property="posCvtId" column="pos_cvt_id"/>
|
||||||
|
<result property="sentAt" column="sent_at"/>
|
||||||
|
<result property="confirmedAt" column="confirmed_at"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="findAll" resultMap="queueMap">
|
||||||
|
SELECT q.id, q.tenant_code, q.store_id, s.store_name,
|
||||||
|
q.binding_id, q.device_id, b.location_code,
|
||||||
|
q.product_code, p.product_name,
|
||||||
|
q.new_price, q.new_sale_price, q.template_code, q.render_data,
|
||||||
|
q.priority, q.status, q.retry_count, q.max_retry, q.error_message,
|
||||||
|
q.pos_cvt_id, q.sent_at, q.confirmed_at, q.created_at
|
||||||
|
FROM esn_update_queue q
|
||||||
|
LEFT JOIN esn_store s ON q.store_id = s.id
|
||||||
|
LEFT JOIN esn_tag_binding b ON q.binding_id = b.id
|
||||||
|
LEFT JOIN esn_product p ON q.store_id = p.store_id AND q.product_code = p.product_code
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND q.tenant_code = #{tenantCode}</if>
|
||||||
|
<if test="storeId != null">AND q.store_id = #{storeId}</if>
|
||||||
|
<if test="status != null and status != ''">AND q.status = #{status}</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY q.priority ASC, q.created_at ASC
|
||||||
|
LIMIT 200
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- Gateway가 전송할 PENDING 목록 (우선순위·시간 순) -->
|
||||||
|
<select id="findPending" resultMap="queueMap">
|
||||||
|
SELECT q.id, q.tenant_code, q.store_id, s.store_name,
|
||||||
|
q.binding_id, q.device_id, b.location_code,
|
||||||
|
q.product_code, p.product_name,
|
||||||
|
q.new_price, q.new_sale_price, q.template_code, q.render_data,
|
||||||
|
q.priority, q.status, q.retry_count, q.max_retry, q.error_message,
|
||||||
|
q.pos_cvt_id, q.sent_at, q.confirmed_at, q.created_at
|
||||||
|
FROM esn_update_queue q
|
||||||
|
LEFT JOIN esn_store s ON q.store_id = s.id
|
||||||
|
LEFT JOIN esn_tag_binding b ON q.binding_id = b.id
|
||||||
|
LEFT JOIN esn_product p ON q.store_id = p.store_id AND q.product_code = p.product_code
|
||||||
|
WHERE q.status = 'PENDING'
|
||||||
|
AND q.retry_count < q.max_retry
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND q.tenant_code = #{tenantCode}</if>
|
||||||
|
ORDER BY q.priority ASC, q.created_at ASC
|
||||||
|
LIMIT #{limit}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="queueMap">
|
||||||
|
SELECT q.id, q.tenant_code, q.store_id, s.store_name,
|
||||||
|
q.binding_id, q.device_id, b.location_code,
|
||||||
|
q.product_code, p.product_name,
|
||||||
|
q.new_price, q.new_sale_price, q.template_code, q.render_data,
|
||||||
|
q.priority, q.status, q.retry_count, q.max_retry, q.error_message,
|
||||||
|
q.pos_cvt_id, q.sent_at, q.confirmed_at, q.created_at
|
||||||
|
FROM esn_update_queue q
|
||||||
|
LEFT JOIN esn_store s ON q.store_id = s.id
|
||||||
|
LEFT JOIN esn_tag_binding b ON q.binding_id = b.id
|
||||||
|
LEFT JOIN esn_product p ON q.store_id = p.store_id AND q.product_code = p.product_code
|
||||||
|
WHERE q.id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO esn_update_queue
|
||||||
|
(tenant_code, store_id, binding_id, device_id, product_code,
|
||||||
|
new_price, new_sale_price, template_code, priority, status, pos_cvt_id)
|
||||||
|
VALUES
|
||||||
|
(#{tenantCode}, #{storeId}, #{bindingId}, #{deviceId}, #{productCode},
|
||||||
|
#{newPrice}, #{newSalePrice}, #{templateCode}, #{priority}, 'PENDING', #{posCvtId})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="updateStatus">
|
||||||
|
UPDATE esn_update_queue SET
|
||||||
|
status = #{status},
|
||||||
|
error_message = #{errorMessage},
|
||||||
|
sent_at = CASE WHEN #{status} = 'SENT' THEN NOW() ELSE sent_at END,
|
||||||
|
confirmed_at = CASE WHEN #{status} = 'CONFIRMED' THEN NOW() ELSE confirmed_at END
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!-- 태그가 확인 응답 시 — device_id로 SENT → CONFIRMED -->
|
||||||
|
<update id="confirmByDeviceId">
|
||||||
|
UPDATE esn_update_queue SET
|
||||||
|
status = 'CONFIRMED',
|
||||||
|
confirmed_at = NOW()
|
||||||
|
WHERE device_id = #{deviceId}
|
||||||
|
AND status IN ('SENDING', 'SENT')
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="incrementRetry">
|
||||||
|
UPDATE esn_update_queue SET retry_count = retry_count + 1
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<select id="countPending" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_update_queue WHERE status = 'PENDING'
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countFailed" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_update_queue WHERE status = 'FAILED'
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countByStatus" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_update_queue WHERE status = #{status}
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
30
backend/src/main/resources/mapper/UserAuthMapper.xml
Normal file
30
backend/src/main/resources/mapper/UserAuthMapper.xml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.auth.mapper.UserAuthMapper">
|
||||||
|
|
||||||
|
<resultMap id="userMap" type="com.zioinfo.esn.auth.EsnUser">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="tenantCode" column="tenant_code"/>
|
||||||
|
<result property="username" column="username"/>
|
||||||
|
<result property="passwordHash" column="password_hash"/>
|
||||||
|
<result property="role" column="role"/>
|
||||||
|
<result property="email" column="email"/>
|
||||||
|
<result property="phone" column="phone"/>
|
||||||
|
<result property="active" column="is_active"/>
|
||||||
|
<result property="lastLoginAt" column="last_login_at"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="findByUsername" resultMap="userMap">
|
||||||
|
SELECT id, tenant_code, username, password_hash, role, email, phone,
|
||||||
|
is_active, last_login_at, created_at
|
||||||
|
FROM esn_user
|
||||||
|
WHERE username = #{username}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="updateLastLogin">
|
||||||
|
UPDATE esn_user SET last_login_at = NOW() WHERE username = #{username}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
61
backend/src/main/resources/mapper/UserMapper.xml
Normal file
61
backend/src/main/resources/mapper/UserMapper.xml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.mapper.UserMapper">
|
||||||
|
|
||||||
|
<resultMap id="userMap" type="com.zioinfo.esn.auth.EsnUser">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="tenantCode" column="tenant_code"/>
|
||||||
|
<result property="username" column="username"/>
|
||||||
|
<result property="role" column="role"/>
|
||||||
|
<result property="email" column="email"/>
|
||||||
|
<result property="phone" column="phone"/>
|
||||||
|
<result property="active" column="is_active"/>
|
||||||
|
<result property="lastLoginAt" column="last_login_at"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
<!-- password_hash 는 resultMap에서 제외 — API 응답 미노출 -->
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="findAll" resultMap="userMap">
|
||||||
|
SELECT id, tenant_code, username, role, email, phone, is_active, last_login_at, created_at
|
||||||
|
FROM esn_user
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
<if test="role != null and role != ''">AND role = #{role}</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY username
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="userMap">
|
||||||
|
SELECT id, tenant_code, username, role, email, phone, is_active, last_login_at, created_at
|
||||||
|
FROM esn_user WHERE id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findByUsername" resultType="com.zioinfo.esn.auth.EsnUser">
|
||||||
|
SELECT id, tenant_code, username, password_hash, role, email, phone,
|
||||||
|
is_active, last_login_at, created_at
|
||||||
|
FROM esn_user WHERE username = #{username}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO esn_user (tenant_code, username, password_hash, role, email, phone, is_active)
|
||||||
|
VALUES (#{tenantCode}, #{username}, #{passwordHash}, #{role}, #{email}, #{phone}, #{active})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="update">
|
||||||
|
UPDATE esn_user SET
|
||||||
|
tenant_code = #{tenantCode},
|
||||||
|
role = #{role},
|
||||||
|
email = #{email},
|
||||||
|
phone = #{phone},
|
||||||
|
is_active = #{active}
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="updatePassword">
|
||||||
|
UPDATE esn_user SET password_hash = #{passwordHash} WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="delete">DELETE FROM esn_user WHERE id = #{id}</delete>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
75
backend/src/main/resources/mapper/WorkMapper.xml
Normal file
75
backend/src/main/resources/mapper/WorkMapper.xml
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.zioinfo.esn.mapper.WorkMapper">
|
||||||
|
|
||||||
|
<resultMap id="workMap" type="com.zioinfo.esn.domain.WorkHistoryVo">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="tenantCode" column="tenant_code"/>
|
||||||
|
<result property="storeId" column="store_id"/>
|
||||||
|
<result property="storeName" column="store_name"/>
|
||||||
|
<result property="workType" column="work_type"/>
|
||||||
|
<result property="workContent" column="work_content"/>
|
||||||
|
<result property="workerName" column="worker_name"/>
|
||||||
|
<result property="status" column="status"/>
|
||||||
|
<result property="remarks" column="remarks"/>
|
||||||
|
<result property="startedAt" column="started_at"/>
|
||||||
|
<result property="completedAt" column="completed_at"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="findAll" resultMap="workMap">
|
||||||
|
SELECT w.id, w.tenant_code, w.store_id, s.store_name, w.work_type, w.work_content,
|
||||||
|
w.worker_name, w.status, w.remarks, w.started_at, w.completed_at, w.created_at
|
||||||
|
FROM esn_work_history w
|
||||||
|
LEFT JOIN esn_store s ON w.store_id = s.id
|
||||||
|
<where>
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND w.tenant_code = #{tenantCode}</if>
|
||||||
|
<if test="storeId != null">AND w.store_id = #{storeId}</if>
|
||||||
|
<if test="status != null and status != ''">AND w.status = #{status}</if>
|
||||||
|
<if test="workType != null and workType != ''">AND w.work_type = #{workType}</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY w.created_at DESC LIMIT 200
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="workMap">
|
||||||
|
SELECT w.id, w.tenant_code, w.store_id, s.store_name, w.work_type, w.work_content,
|
||||||
|
w.worker_name, w.status, w.remarks, w.started_at, w.completed_at, w.created_at
|
||||||
|
FROM esn_work_history w
|
||||||
|
LEFT JOIN esn_store s ON w.store_id = s.id
|
||||||
|
WHERE w.id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO esn_work_history (tenant_code, store_id, work_type, work_content,
|
||||||
|
worker_name, status, remarks, started_at)
|
||||||
|
VALUES (#{tenantCode}, #{storeId}, #{workType}, #{workContent},
|
||||||
|
#{workerName}, #{status}, #{remarks}, #{startedAt})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="update">
|
||||||
|
UPDATE esn_work_history SET
|
||||||
|
work_type = #{workType},
|
||||||
|
work_content = #{workContent},
|
||||||
|
worker_name = #{workerName},
|
||||||
|
status = #{status},
|
||||||
|
remarks = #{remarks},
|
||||||
|
started_at = #{startedAt},
|
||||||
|
completed_at = #{completedAt}
|
||||||
|
WHERE id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="delete">DELETE FROM esn_work_history WHERE id = #{id}</delete>
|
||||||
|
|
||||||
|
<select id="countToday" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_work_history WHERE created_at::date = CURRENT_DATE
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countCompletedToday" resultType="long">
|
||||||
|
SELECT COUNT(*) FROM esn_work_history
|
||||||
|
WHERE status = 'COMPLETED' AND completed_at::date = CURRENT_DATE
|
||||||
|
<if test="tenantCode != null and tenantCode != ''">AND tenant_code = #{tenantCode}</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>zioinfo-esn — ESL 통합 관리 플랫폼</title>
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3416
frontend/package-lock.json
generated
Normal file
3416
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
frontend/package.json
Normal file
30
frontend/package.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "zioinfo-esn-frontend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --port 3014",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"react-router-dom": "^7.0.0",
|
||||||
|
"axios": "^1.7.0",
|
||||||
|
"@tanstack/react-query": "^5.0.0",
|
||||||
|
"recharts": "^2.12.0",
|
||||||
|
"lucide-react": "^0.400.0",
|
||||||
|
"date-fns": "^3.6.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.0",
|
||||||
|
"typescript": "^5.5.0",
|
||||||
|
"vite": "^5.3.0",
|
||||||
|
"tailwindcss": "^3.4.0",
|
||||||
|
"autoprefixer": "^10.4.0",
|
||||||
|
"postcss": "^8.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
frontend/postcss.config.js
Normal file
6
frontend/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
BIN
frontend/public/login.png
Normal file
BIN
frontend/public/login.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
49
frontend/src/App.tsx
Normal file
49
frontend/src/App.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
|
import Layout from './components/Layout'
|
||||||
|
import Login from './pages/Login'
|
||||||
|
import Dashboard from './pages/Dashboard'
|
||||||
|
import StoreList from './pages/StoreList'
|
||||||
|
import TemplateList from './pages/TemplateList'
|
||||||
|
import PosCvtList from './pages/PosCvtList'
|
||||||
|
import AlarmList from './pages/AlarmList'
|
||||||
|
import HCoreStatus from './pages/HCoreStatus'
|
||||||
|
import WorkHistory from './pages/WorkHistory'
|
||||||
|
import FirmwareList from './pages/FirmwareList'
|
||||||
|
import UserList from './pages/UserList'
|
||||||
|
import ProductList from './pages/ProductList'
|
||||||
|
import TenantAdmin from './pages/TenantAdmin'
|
||||||
|
import AiAnalysis from './pages/AiAnalysis'
|
||||||
|
import TagBindingList from './pages/TagBindingList'
|
||||||
|
import UpdateQueueList from './pages/UpdateQueueList'
|
||||||
|
|
||||||
|
const qc = new QueryClient()
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={qc}>
|
||||||
|
<BrowserRouter>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/login" element={<Login />} />
|
||||||
|
<Route element={<Layout />}>
|
||||||
|
<Route path="/dashboard" element={<Dashboard />} />
|
||||||
|
<Route path="/stores" element={<StoreList />} />
|
||||||
|
<Route path="/templates" element={<TemplateList />} />
|
||||||
|
<Route path="/pos-cvt" element={<PosCvtList />} />
|
||||||
|
<Route path="/tag-binding" element={<TagBindingList />} />
|
||||||
|
<Route path="/update-queue" element={<UpdateQueueList />} />
|
||||||
|
<Route path="/alarms" element={<AlarmList />} />
|
||||||
|
<Route path="/hcore" element={<HCoreStatus />} />
|
||||||
|
<Route path="/works" element={<WorkHistory />} />
|
||||||
|
<Route path="/firmware" element={<FirmwareList />} />
|
||||||
|
<Route path="/products" element={<ProductList />} />
|
||||||
|
<Route path="/users" element={<UserList />} />
|
||||||
|
<Route path="/tenants" element={<TenantAdmin />} />
|
||||||
|
<Route path="/ai" element={<AiAnalysis />} />
|
||||||
|
</Route>
|
||||||
|
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
</QueryClientProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
105
frontend/src/api/client.ts
Normal file
105
frontend/src/api/client.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const api = axios.create({ baseURL: '' })
|
||||||
|
|
||||||
|
api.interceptors.request.use(config => {
|
||||||
|
const token = localStorage.getItem('esn_token')
|
||||||
|
if (token) config.headers.Authorization = `Bearer ${token}`
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
|
api.interceptors.response.use(
|
||||||
|
res => res,
|
||||||
|
err => {
|
||||||
|
if (err.response?.status === 401) {
|
||||||
|
localStorage.removeItem('esn_token')
|
||||||
|
if (location.pathname !== '/login') location.href = '/login'
|
||||||
|
}
|
||||||
|
return Promise.reject(err)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default api
|
||||||
|
|
||||||
|
const unwrap = (p: Promise<any>) => p.then(r => r.data?.data)
|
||||||
|
|
||||||
|
// ── Auth ──────────────────────────────────────────────────────────────────
|
||||||
|
export const login = (username: string, password: string) =>
|
||||||
|
api.post('/api/auth/login', { username, password })
|
||||||
|
export const getMe = () => api.get('/api/auth/me')
|
||||||
|
export const logout = () => api.post('/api/auth/logout')
|
||||||
|
|
||||||
|
// ── Dashboard ────────────────────────────────────────────────────────────
|
||||||
|
export const getDashboard = (tenantCode?: string) =>
|
||||||
|
unwrap(api.get(`/api/dashboard${tenantCode ? `?tenantCode=${tenantCode}` : ''}`))
|
||||||
|
|
||||||
|
// ── Tenants ──────────────────────────────────────────────────────────────
|
||||||
|
export const getTenants = () => unwrap(api.get('/api/tenants'))
|
||||||
|
export const createTenant = (d: object) => unwrap(api.post('/api/tenants', d))
|
||||||
|
export const updateTenant = (id: number, d: object) => unwrap(api.put(`/api/tenants/${id}`, d))
|
||||||
|
export const deleteTenant = (id: number) => api.delete(`/api/tenants/${id}`)
|
||||||
|
|
||||||
|
// ── Stores ───────────────────────────────────────────────────────────────
|
||||||
|
export const getStores = (params?: object) => unwrap(api.get('/api/stores', { params }))
|
||||||
|
export const getStore = (id: number) => unwrap(api.get(`/api/stores/${id}`))
|
||||||
|
export const createStore = (d: object) => unwrap(api.post('/api/stores', d))
|
||||||
|
export const updateStore = (id: number, d: object) => unwrap(api.put(`/api/stores/${id}`, d))
|
||||||
|
export const deleteStore = (id: number) => api.delete(`/api/stores/${id}`)
|
||||||
|
|
||||||
|
// ── Store Groups ─────────────────────────────────────────────────────────
|
||||||
|
export const getStoreGroups = (tenantCode?: string) =>
|
||||||
|
unwrap(api.get('/api/store-groups', { params: { tenantCode } }))
|
||||||
|
export const createStoreGroup = (d: object) => unwrap(api.post('/api/store-groups', d))
|
||||||
|
export const updateStoreGroup = (id: number, d: object) => unwrap(api.put(`/api/store-groups/${id}`, d))
|
||||||
|
export const deleteStoreGroup = (id: number) => api.delete(`/api/store-groups/${id}`)
|
||||||
|
|
||||||
|
// ── Templates ────────────────────────────────────────────────────────────
|
||||||
|
export const getTemplates = (params?: object) => unwrap(api.get('/api/templates', { params }))
|
||||||
|
export const createTemplate = (d: object) => unwrap(api.post('/api/templates', d))
|
||||||
|
export const updateTemplate = (id: number, d: object) => unwrap(api.put(`/api/templates/${id}`, d))
|
||||||
|
export const deleteTemplate = (id: number) => api.delete(`/api/templates/${id}`)
|
||||||
|
|
||||||
|
// ── POS CVT ──────────────────────────────────────────────────────────────
|
||||||
|
export const getPosCvt = (params?: object) => unwrap(api.get('/api/pos-cvt', { params }))
|
||||||
|
export const processPosCvt = (id: number) => unwrap(api.put(`/api/pos-cvt/${id}/process`))
|
||||||
|
export const ignorePosCvt = (id: number) => unwrap(api.put(`/api/pos-cvt/${id}/ignore`))
|
||||||
|
|
||||||
|
// ── Alarms ───────────────────────────────────────────────────────────────
|
||||||
|
export const getAlarms = (params?: object) => unwrap(api.get('/api/alarms', { params }))
|
||||||
|
export const createAlarm = (d: object) => unwrap(api.post('/api/alarms', d))
|
||||||
|
export const resolveAlarm = (id: number, resolution: string) =>
|
||||||
|
unwrap(api.put(`/api/alarms/${id}/resolve`, { resolution }))
|
||||||
|
export const deleteAlarm = (id: number) => api.delete(`/api/alarms/${id}`)
|
||||||
|
|
||||||
|
// ── HCore ────────────────────────────────────────────────────────────────
|
||||||
|
export const getHCore = (params?: object) => unwrap(api.get('/api/hcore', { params }))
|
||||||
|
export const updateHCoreStatus = (id: number, status: string) =>
|
||||||
|
unwrap(api.put(`/api/hcore/${id}/status`, { status }))
|
||||||
|
|
||||||
|
// ── Works ────────────────────────────────────────────────────────────────
|
||||||
|
export const getWorks = (params?: object) => unwrap(api.get('/api/works', { params }))
|
||||||
|
export const createWork = (d: object) => unwrap(api.post('/api/works', d))
|
||||||
|
export const updateWork = (id: number, d: object) => unwrap(api.put(`/api/works/${id}`, d))
|
||||||
|
export const deleteWork = (id: number) => api.delete(`/api/works/${id}`)
|
||||||
|
|
||||||
|
// ── Firmware ─────────────────────────────────────────────────────────────
|
||||||
|
export const getFirmware = (params?: object) => unwrap(api.get('/api/firmware', { params }))
|
||||||
|
export const createFirmware = (d: object) => unwrap(api.post('/api/firmware', d))
|
||||||
|
export const deleteFirmware = (id: number) => api.delete(`/api/firmware/${id}`)
|
||||||
|
|
||||||
|
// ── Users ────────────────────────────────────────────────────────────────
|
||||||
|
export const getUsers = (params?: object) => unwrap(api.get('/api/users', { params }))
|
||||||
|
export const createUser = (d: object) => unwrap(api.post('/api/users', d))
|
||||||
|
export const updateUser = (id: number, d: object) => unwrap(api.put(`/api/users/${id}`, d))
|
||||||
|
export const deleteUser = (id: number) => api.delete(`/api/users/${id}`)
|
||||||
|
|
||||||
|
// ── Products ─────────────────────────────────────────────────────────────
|
||||||
|
export const getProducts = (params?: object) => unwrap(api.get('/api/products', { params }))
|
||||||
|
export const createProduct = (d: object) => unwrap(api.post('/api/products', d))
|
||||||
|
export const updateProduct = (id: number, d: object) => unwrap(api.put(`/api/products/${id}`, d))
|
||||||
|
export const deleteProduct = (id: number) => api.delete(`/api/products/${id}`)
|
||||||
|
|
||||||
|
// ── AI ───────────────────────────────────────────────────────────────────
|
||||||
|
export const analyzeAlarm = (d: object) => unwrap(api.post('/api/ai/analyze-alarm', d))
|
||||||
|
export const classifyPos = (d: object) => unwrap(api.post('/api/ai/classify-pos', d))
|
||||||
|
export const aiChat = (message: string) => unwrap(api.post('/api/ai/chat', { message }))
|
||||||
16
frontend/src/components/AlarmBadge.tsx
Normal file
16
frontend/src/components/AlarmBadge.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
interface AlarmBadgeProps {
|
||||||
|
severity: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AlarmBadge({ severity }: AlarmBadgeProps) {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
CRITICAL: 'bg-red-500/20 text-red-400 border border-red-500/40',
|
||||||
|
HIGH: 'bg-orange-500/20 text-orange-400 border border-orange-500/40',
|
||||||
|
MEDIUM: 'bg-yellow-500/20 text-yellow-400 border border-yellow-500/40',
|
||||||
|
LOW: 'bg-gray-500/20 text-gray-400 border border-gray-500/40',
|
||||||
|
}
|
||||||
|
const cls = map[severity] || map.LOW
|
||||||
|
return (
|
||||||
|
<span className={`px-2 py-0.5 rounded text-xs font-medium ${cls}`}>{severity}</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user