/** * 바코드 라벨 관리 서비스 * ZD421 등 라벨 디자인 CRUD 및 기본 템플릿 제공 */ import { v4 as uuidv4 } from "uuid"; import { query, queryOne, transaction } from "../database/db"; import { BarcodeLabelLayout } from "../types/barcode"; export interface BarcodeLabelMaster { label_id: string; label_name_kor: string; label_name_eng: string | null; description: string | null; width_mm: number; height_mm: number; layout_json: string | null; use_yn: string; created_at: string; created_by: string | null; updated_at: string | null; updated_by: string | null; } export interface BarcodeLabelTemplate { template_id: string; template_name_kor: string; template_name_eng: string | null; width_mm: number; height_mm: number; layout_json: string; sort_order: number; } export interface GetBarcodeLabelsParams { page?: number; limit?: number; searchText?: string; useYn?: string; sortBy?: string; sortOrder?: "ASC" | "DESC"; } export interface GetBarcodeLabelsResult { items: BarcodeLabelMaster[]; total: number; page: number; limit: number; } export class BarcodeLabelService { async getLabels(params: GetBarcodeLabelsParams): Promise { const { page = 1, limit = 20, searchText = "", useYn = "Y", sortBy = "created_at", sortOrder = "DESC", } = params; const offset = (page - 1) * limit; const conditions: string[] = []; const values: any[] = []; let idx = 1; if (useYn) { conditions.push(`use_yn = $${idx++}`); values.push(useYn); } if (searchText) { conditions.push(`(label_name_kor LIKE $${idx} OR label_name_eng LIKE $${idx})`); values.push(`%${searchText}%`); idx++; } const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : ""; const countSql = `SELECT COUNT(*) as total FROM barcode_labels ${where}`; const countRow = await queryOne<{ total: string }>(countSql, values); const total = parseInt(countRow?.total || "0", 10); const listSql = ` SELECT label_id, label_name_kor, label_name_eng, description, width_mm, height_mm, layout_json, use_yn, created_at, created_by, updated_at, updated_by FROM barcode_labels ${where} ORDER BY ${sortBy} ${sortOrder} LIMIT $${idx++} OFFSET $${idx} `; const items = await query(listSql, [...values, limit, offset]); return { items, total, page, limit }; } async getLabelById(labelId: string): Promise { const sql = ` SELECT label_id, label_name_kor, label_name_eng, description, width_mm, height_mm, layout_json, use_yn, created_at, created_by, updated_at, updated_by FROM barcode_labels WHERE label_id = $1 `; return queryOne(sql, [labelId]); } async getLayout(labelId: string): Promise { const row = await this.getLabelById(labelId); if (!row?.layout_json) return null; try { return JSON.parse(row.layout_json) as BarcodeLabelLayout; } catch { return null; } } async createLabel( data: { labelNameKor: string; labelNameEng?: string; description?: string; templateId?: string }, userId: string ): Promise { const labelId = `LBL_${uuidv4().replace(/-/g, "").substring(0, 20)}`; let widthMm = 50; let heightMm = 30; let layoutJson: string | null = null; if (data.templateId) { const t = await this.getTemplateById(data.templateId); if (t) { widthMm = t.width_mm; heightMm = t.height_mm; layoutJson = t.layout_json; } } if (!layoutJson) { const defaultLayout: BarcodeLabelLayout = { width_mm: widthMm, height_mm: heightMm, components: [], }; layoutJson = JSON.stringify(defaultLayout); } await query( `INSERT INTO barcode_labels (label_id, label_name_kor, label_name_eng, description, width_mm, height_mm, layout_json, use_yn, created_by) VALUES ($1, $2, $3, $4, $5, $6, $7, 'Y', $8)`, [ labelId, data.labelNameKor, data.labelNameEng || null, data.description || null, widthMm, heightMm, layoutJson, userId, ] ); return labelId; } async updateLabel( labelId: string, data: { labelNameKor?: string; labelNameEng?: string; description?: string; useYn?: string }, userId: string ): Promise { const setClauses: string[] = []; const values: any[] = []; let idx = 1; if (data.labelNameKor !== undefined) { setClauses.push(`label_name_kor = $${idx++}`); values.push(data.labelNameKor); } if (data.labelNameEng !== undefined) { setClauses.push(`label_name_eng = $${idx++}`); values.push(data.labelNameEng); } if (data.description !== undefined) { setClauses.push(`description = $${idx++}`); values.push(data.description); } if (data.useYn !== undefined) { setClauses.push(`use_yn = $${idx++}`); values.push(data.useYn); } if (setClauses.length === 0) return false; setClauses.push(`updated_at = CURRENT_TIMESTAMP`); setClauses.push(`updated_by = $${idx++}`); values.push(userId); values.push(labelId); const updated = await query<{ label_id: string }>( `UPDATE barcode_labels SET ${setClauses.join(", ")} WHERE label_id = $${idx} RETURNING label_id`, values ); return updated.length > 0; } async saveLayout(labelId: string, layout: BarcodeLabelLayout, userId: string): Promise { const layoutJson = JSON.stringify(layout); await query( `UPDATE barcode_labels SET width_mm = $1, height_mm = $2, layout_json = $3, updated_at = CURRENT_TIMESTAMP, updated_by = $4 WHERE label_id = $5`, [layout.width_mm, layout.height_mm, layoutJson, userId, labelId] ); return true; } async deleteLabel(labelId: string): Promise { const deleted = await query<{ label_id: string }>( `DELETE FROM barcode_labels WHERE label_id = $1 RETURNING label_id`, [labelId] ); return deleted.length > 0; } async copyLabel(labelId: string, userId: string): Promise { const row = await this.getLabelById(labelId); if (!row) return null; const newId = `LBL_${uuidv4().replace(/-/g, "").substring(0, 20)}`; await query( `INSERT INTO barcode_labels (label_id, label_name_kor, label_name_eng, description, width_mm, height_mm, layout_json, use_yn, created_by) VALUES ($1, $2 || ' (복사)', $3, $4, $5, $6, $7, 'Y', $8)`, [ newId, row.label_name_kor, row.label_name_eng, row.description, row.width_mm, row.height_mm, row.layout_json, userId, ] ); return newId; } async getTemplates(): Promise { const sql = ` SELECT template_id, template_name_kor, template_name_eng, width_mm, height_mm, layout_json, sort_order FROM barcode_label_templates ORDER BY sort_order, template_id `; const rows = await query(sql); return rows || []; } async getTemplateById(templateId: string): Promise { const sql = `SELECT template_id, template_name_kor, template_name_eng, width_mm, height_mm, layout_json, sort_order FROM barcode_label_templates WHERE template_id = $1`; return queryOne(sql, [templateId]); } } export default new BarcodeLabelService();