wing-ops/backend/src/hns/hnsService.ts
htlee 63645e9f85 refactor(phase4): HNS 물질정보 DB 이전 + 정적 데이터 정리
- HNS_SUBSTANCE 테이블 마이그레이션 SQL 추가 (002_hns_substance.sql)
- HNS 검색/상세 API 구현 (hnsRouter, hnsService)
- HNS 시드 스크립트 추가 (seedHns.ts, 20종 물질 데이터)
- 프론트엔드 HNSSubstanceView: 정적 HNS_SEARCH_DB → API 호출 전환
- HNSSearchSubstance 타입 common/types/hns.ts로 분리
- Mock 데이터 이동: data/ → common/mock/ (vesselMockData, backtrackMockData)
- layerDatabase.ts → common/services/layerService.ts 이동
- layerData.ts → common/data/layerData.ts 이동
- scat/index.ts 누락 수정 + .gitignore scat 규칙 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 14:52:46 +09:00

111 lines
3.0 KiB
TypeScript

import { wingPool } from '../db/wingDb.js'
interface HnsSearchParams {
q?: string
type?: 'abbreviation' | 'nameKr' | 'nameEn' | 'casNumber' | 'unNumber' | 'cargoCode'
sebc?: string
page?: number
limit?: number
}
export async function searchSubstances(params: HnsSearchParams) {
const { q, type = 'nameKr', sebc, page = 1, limit = 50 } = params
const conditions: string[] = ["USE_YN = 'Y'"]
const values: (string | number)[] = []
let paramIdx = 1
if (q && q.trim()) {
const keyword = q.trim()
switch (type) {
case 'abbreviation':
conditions.push(`ABBREVIATION ILIKE $${paramIdx}`)
values.push(`%${keyword}%`)
break
case 'nameKr':
conditions.push(`NM_KR ILIKE $${paramIdx}`)
values.push(`%${keyword}%`)
break
case 'nameEn':
conditions.push(`NM_EN ILIKE $${paramIdx}`)
values.push(`%${keyword}%`)
break
case 'casNumber':
conditions.push(`CAS_NO ILIKE $${paramIdx}`)
values.push(`%${keyword}%`)
break
case 'unNumber':
conditions.push(`UN_NO = $${paramIdx}`)
values.push(keyword)
break
case 'cargoCode':
conditions.push(`DATA->'cargoCodes' @> $${paramIdx}::jsonb`)
values.push(JSON.stringify([{ code: keyword }]))
break
default:
conditions.push(`(NM_KR ILIKE $${paramIdx} OR NM_EN ILIKE $${paramIdx} OR ABBREVIATION ILIKE $${paramIdx})`)
values.push(`%${keyword}%`)
}
paramIdx++
}
if (sebc && sebc.trim()) {
conditions.push(`SEBC ILIKE $${paramIdx}`)
values.push(`%${sebc.trim()}%`)
paramIdx++
}
const where = conditions.join(' AND ')
const offset = (page - 1) * limit
const countQuery = `SELECT COUNT(*) as total FROM HNS_SUBSTANCE WHERE ${where}`
const dataQuery = `
SELECT SBST_SN, ABBREVIATION, NM_KR, NM_EN, UN_NO, CAS_NO, SEBC, DATA
FROM HNS_SUBSTANCE
WHERE ${where}
ORDER BY SBST_SN
LIMIT $${paramIdx} OFFSET $${paramIdx + 1}
`
const [countResult, dataResult] = await Promise.all([
wingPool.query(countQuery, values),
wingPool.query(dataQuery, [...values, limit, offset]),
])
return {
total: parseInt(countResult.rows[0].total, 10),
page,
limit,
items: dataResult.rows.map(row => ({
id: row.sbst_sn,
abbreviation: row.abbreviation,
nameKr: row.nm_kr,
nameEn: row.nm_en,
unNumber: row.un_no,
casNumber: row.cas_no,
sebc: row.sebc,
...row.data,
})),
}
}
export async function getSubstanceById(id: number) {
const { rows } = await wingPool.query(
`SELECT SBST_SN, ABBREVIATION, NM_KR, NM_EN, UN_NO, CAS_NO, SEBC, DATA
FROM HNS_SUBSTANCE WHERE SBST_SN = $1`,
[id]
)
if (rows.length === 0) return null
const row = rows[0]
return {
id: row.sbst_sn,
abbreviation: row.abbreviation,
nameKr: row.nm_kr,
nameEn: row.nm_en,
unNumber: row.un_no,
casNumber: row.cas_no,
sebc: row.sebc,
...row.data,
}
}