248 lines
7.5 KiB
TypeScript
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();
|