ERP-node/backend-node/src/services/barcodeLabelService.ts

248 lines
7.5 KiB
TypeScript

/**
* 바코드 라벨 관리 서비스
* 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<GetBarcodeLabelsResult> {
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<BarcodeLabelMaster>(listSql, [...values, limit, offset]);
return { items, total, page, limit };
}
async getLabelById(labelId: string): Promise<BarcodeLabelMaster | null> {
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<BarcodeLabelMaster>(sql, [labelId]);
}
async getLayout(labelId: string): Promise<BarcodeLabelLayout | null> {
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<string> {
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<boolean> {
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<boolean> {
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<boolean> {
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<string | null> {
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<BarcodeLabelTemplate[]> {
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<BarcodeLabelTemplate>(sql);
return rows || [];
}
async getTemplateById(templateId: string): Promise<BarcodeLabelTemplate | null> {
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<BarcodeLabelTemplate>(sql, [templateId]);
}
}
export default new BarcodeLabelService();