# ๐Ÿ“‹ Phase 3.9: TemplateStandardService Raw Query ์ „ํ™˜ ๊ณ„ํš ## ๐Ÿ“‹ ๊ฐœ์š” TemplateStandardService๋Š” **6๊ฐœ์˜ Prisma ํ˜ธ์ถœ**์ด ์žˆ์œผ๋ฉฐ, ํ…œํ”Œ๋ฆฟ ํ‘œ์ค€ ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. ### ๐Ÿ“Š ๊ธฐ๋ณธ ์ •๋ณด | ํ•ญ๋ชฉ | ๋‚ด์šฉ | | --------------- | ------------------------------------------------------ | | ํŒŒ์ผ ์œ„์น˜ | `backend-node/src/services/templateStandardService.ts` | | ํŒŒ์ผ ํฌ๊ธฐ | 395 ๋ผ์ธ | | Prisma ํ˜ธ์ถœ | 6๊ฐœ | | **ํ˜„์žฌ ์ง„ํ–‰๋ฅ ** | **7/7 (100%)** โœ… **์ „ํ™˜ ์™„๋ฃŒ** | | ๋ณต์žก๋„ | ๋‚ฎ์Œ (๊ธฐ๋ณธ CRUD + DISTINCT) | | ์šฐ์„ ์ˆœ์œ„ | ๐ŸŸข ๋‚ฎ์Œ (Phase 3.9) | | **์ƒํƒœ** | โœ… **์™„๋ฃŒ** | ### ๐ŸŽฏ ์ „ํ™˜ ๋ชฉํ‘œ - โœ… **7๊ฐœ ๋ชจ๋“  Prisma ํ˜ธ์ถœ์„ `db.ts`์˜ `query()`, `queryOne()` ํ•จ์ˆ˜๋กœ ๊ต์ฒด** - โœ… ํ…œํ”Œ๋ฆฟ CRUD ๊ธฐ๋Šฅ ์ •์ƒ ๋™์ž‘ - โœ… DISTINCT ์ฟผ๋ฆฌ ์ „ํ™˜ - โœ… Promise.all ๋ณ‘๋ ฌ ์ฟผ๋ฆฌ (๋ชฉ๋ก + ๊ฐœ์ˆ˜) - โœ… ๋™์  UPDATE ์ฟผ๋ฆฌ (11๊ฐœ ํ•„๋“œ) - โœ… TypeScript ์ปดํŒŒ์ผ ์„ฑ๊ณต - โœ… **Prisma import ์™„์ „ ์ œ๊ฑฐ** --- ## ๐Ÿ” Prisma ์‚ฌ์šฉ ํ˜„ํ™ฉ ๋ถ„์„ ### ์ฃผ์š” Prisma ํ˜ธ์ถœ (6๊ฐœ) #### 1. **getTemplateByCode()** - ํ…œํ”Œ๋ฆฟ ๋‹จ๊ฑด ์กฐํšŒ ```typescript // Line 76 return await prisma.template_standards.findUnique({ where: { template_code: templateCode, company_code: companyCode, }, }); ``` #### 2. **createTemplate()** - ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ ```typescript // Line 86 const existing = await prisma.template_standards.findUnique({ where: { template_code: data.template_code, company_code: data.company_code, }, }); // Line 96 return await prisma.template_standards.create({ data: { ...data, created_date: new Date(), updated_date: new Date(), }, }); ``` #### 3. **updateTemplate()** - ํ…œํ”Œ๋ฆฟ ์ˆ˜์ • ```typescript // Line 164 return await prisma.template_standards.update({ where: { template_code_company_code: { template_code: templateCode, company_code: companyCode, }, }, data: { ...data, updated_date: new Date(), }, }); ``` #### 4. **deleteTemplate()** - ํ…œํ”Œ๋ฆฟ ์‚ญ์ œ ```typescript // Line 181 await prisma.template_standards.delete({ where: { template_code_company_code: { template_code: templateCode, company_code: companyCode, }, }, }); ``` #### 5. **getTemplateCategories()** - ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก (DISTINCT) ```typescript // Line 262 const categories = await prisma.template_standards.findMany({ where: { company_code: companyCode, }, select: { category: true, }, distinct: ["category"], }); ``` --- ## ๐Ÿ“ ์ „ํ™˜ ๊ณ„ํš ### 1๋‹จ๊ณ„: ๊ธฐ๋ณธ CRUD ์ „ํ™˜ (4๊ฐœ ํ•จ์ˆ˜) **ํ•จ์ˆ˜ ๋ชฉ๋ก**: - `getTemplateByCode()` - ๋‹จ๊ฑด ์กฐํšŒ (findUnique) - `createTemplate()` - ์ƒ์„ฑ (findUnique + create) - `updateTemplate()` - ์ˆ˜์ • (update) - `deleteTemplate()` - ์‚ญ์ œ (delete) ### 2๋‹จ๊ณ„: ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ์ „ํ™˜ (1๊ฐœ ํ•จ์ˆ˜) **ํ•จ์ˆ˜ ๋ชฉ๋ก**: - `getTemplateCategories()` - ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก (findMany + distinct) --- ## ๐Ÿ’ป ์ „ํ™˜ ์˜ˆ์‹œ ### ์˜ˆ์‹œ 1: ๋ณตํ•ฉ ํ‚ค ์กฐํšŒ ```typescript // ๊ธฐ์กด Prisma return await prisma.template_standards.findUnique({ where: { template_code: templateCode, company_code: companyCode, }, }); // ์ „ํ™˜ ํ›„ import { queryOne } from "../database/db"; return await queryOne( `SELECT * FROM template_standards WHERE template_code = $1 AND company_code = $2`, [templateCode, companyCode] ); ``` ### ์˜ˆ์‹œ 2: ์ค‘๋ณต ํ™•์ธ ํ›„ ์ƒ์„ฑ ```typescript // ๊ธฐ์กด Prisma const existing = await prisma.template_standards.findUnique({ where: { template_code: data.template_code, company_code: data.company_code, }, }); if (existing) { throw new Error("์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํ…œํ”Œ๋ฆฟ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค."); } return await prisma.template_standards.create({ data: { ...data, created_date: new Date(), updated_date: new Date(), }, }); // ์ „ํ™˜ ํ›„ const existing = await queryOne( `SELECT * FROM template_standards WHERE template_code = $1 AND company_code = $2`, [data.template_code, data.company_code] ); if (existing) { throw new Error("์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํ…œํ”Œ๋ฆฟ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค."); } return await queryOne( `INSERT INTO template_standards (template_code, template_name, category, template_type, layout_config, description, is_active, company_code, created_by, updated_by, created_date, updated_date) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW(), NOW()) RETURNING *`, [ data.template_code, data.template_name, data.category, data.template_type, JSON.stringify(data.layout_config), data.description, data.is_active, data.company_code, data.created_by, data.updated_by, ] ); ``` ### ์˜ˆ์‹œ 3: ๋ณตํ•ฉ ํ‚ค UPDATE ```typescript // ๊ธฐ์กด Prisma return await prisma.template_standards.update({ where: { template_code_company_code: { template_code: templateCode, company_code: companyCode, }, }, data: { ...data, updated_date: new Date(), }, }); // ์ „ํ™˜ ํ›„ // ๋™์  UPDATE ์ฟผ๋ฆฌ ์ƒ์„ฑ const updateFields: string[] = ["updated_date = NOW()"]; const values: any[] = []; let paramIndex = 1; if (data.template_name !== undefined) { updateFields.push(`template_name = $${paramIndex++}`); values.push(data.template_name); } if (data.category !== undefined) { updateFields.push(`category = $${paramIndex++}`); values.push(data.category); } if (data.template_type !== undefined) { updateFields.push(`template_type = $${paramIndex++}`); values.push(data.template_type); } if (data.layout_config !== undefined) { updateFields.push(`layout_config = $${paramIndex++}`); values.push(JSON.stringify(data.layout_config)); } if (data.description !== undefined) { updateFields.push(`description = $${paramIndex++}`); values.push(data.description); } if (data.is_active !== undefined) { updateFields.push(`is_active = $${paramIndex++}`); values.push(data.is_active); } if (data.updated_by !== undefined) { updateFields.push(`updated_by = $${paramIndex++}`); values.push(data.updated_by); } return await queryOne( `UPDATE template_standards SET ${updateFields.join(", ")} WHERE template_code = $${paramIndex++} AND company_code = $${paramIndex} RETURNING *`, [...values, templateCode, companyCode] ); ``` ### ์˜ˆ์‹œ 4: ๋ณตํ•ฉ ํ‚ค DELETE ```typescript // ๊ธฐ์กด Prisma await prisma.template_standards.delete({ where: { template_code_company_code: { template_code: templateCode, company_code: companyCode, }, }, }); // ์ „ํ™˜ ํ›„ import { query } from "../database/db"; await query( `DELETE FROM template_standards WHERE template_code = $1 AND company_code = $2`, [templateCode, companyCode] ); ``` ### ์˜ˆ์‹œ 5: DISTINCT ์ฟผ๋ฆฌ ```typescript // ๊ธฐ์กด Prisma const categories = await prisma.template_standards.findMany({ where: { company_code: companyCode, }, select: { category: true, }, distinct: ["category"], }); return categories .map((c) => c.category) .filter((c): c is string => c !== null && c !== undefined) .sort(); // ์ „ํ™˜ ํ›„ const categories = await query<{ category: string }>( `SELECT DISTINCT category FROM template_standards WHERE company_code = $1 AND category IS NOT NULL ORDER BY category ASC`, [companyCode] ); return categories.map((c) => c.category); ``` --- ## โœ… ์™„๋ฃŒ ๊ธฐ์ค€ - [ ] **6๊ฐœ ๋ชจ๋“  Prisma ํ˜ธ์ถœ์„ Raw Query๋กœ ์ „ํ™˜ ์™„๋ฃŒ** - [ ] **๋ณตํ•ฉ ๊ธฐ๋ณธ ํ‚ค ์ฒ˜๋ฆฌ (template_code + company_code)** - [ ] **๋™์  UPDATE ์ฟผ๋ฆฌ ์ƒ์„ฑ** - [ ] **DISTINCT ์ฟผ๋ฆฌ ์ „ํ™˜** - [ ] **JSON ํ•„๋“œ ์ฒ˜๋ฆฌ (layout_config)** - [ ] **๋ชจ๋“  TypeScript ์ปดํŒŒ์ผ ์˜ค๋ฅ˜ ํ•ด๊ฒฐ** - [ ] **`import prisma` ์™„์ „ ์ œ๊ฑฐ** - [ ] **๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ (6๊ฐœ)** - [ ] **ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์™„๋ฃŒ (2๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค)** --- ## ๐Ÿ”ง ์ฃผ์š” ๊ธฐ์ˆ ์  ๊ณผ์ œ ### 1. ๋ณตํ•ฉ ๊ธฐ๋ณธ ํ‚ค `template_standards` ํ…Œ์ด๋ธ”์€ `(template_code, company_code)` ๋ณตํ•ฉ ๊ธฐ๋ณธ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. - WHERE ์ ˆ์—์„œ ๋‘ ์ปฌ๋Ÿผ ๋ชจ๋‘ ์ง€์ • ํ•„์š” - Prisma์˜ `template_code_company_code` ํ‘œํ˜„์‹์„ `template_code = $1 AND company_code = $2`๋กœ ๋ณ€ํ™˜ ### 2. JSON ํ•„๋“œ `layout_config` ํ•„๋“œ๋Š” JSON ํƒ€์ž…์œผ๋กœ, INSERT/UPDATE ์‹œ `JSON.stringify()` ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ### 3. DISTINCT + NULL ์ œ์™ธ ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก ์กฐํšŒ ์‹œ `DISTINCT` ์‚ฌ์šฉํ•˜๋ฉฐ, NULL ๊ฐ’์€ `WHERE category IS NOT NULL`๋กœ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค. --- ## ๐Ÿ“‹ ์ฒดํฌ๋ฆฌ์ŠคํŠธ ### ์ฝ”๋“œ ์ „ํ™˜ - [ ] import ๋ฌธ ์ˆ˜์ • (`prisma` โ†’ `query, queryOne`) - [ ] getTemplateByCode() - findUnique โ†’ queryOne (๋ณตํ•ฉ ํ‚ค) - [ ] createTemplate() - findUnique + create โ†’ queryOne (์ค‘๋ณต ํ™•์ธ + INSERT) - [ ] updateTemplate() - update โ†’ queryOne (๋™์  UPDATE, ๋ณตํ•ฉ ํ‚ค) - [ ] deleteTemplate() - delete โ†’ query (๋ณตํ•ฉ ํ‚ค) - [ ] getTemplateCategories() - findMany + distinct โ†’ query (DISTINCT) - [ ] JSON ํ•„๋“œ ์ฒ˜๋ฆฌ (layout_config) - [ ] Prisma import ์™„์ „ ์ œ๊ฑฐ ### ํ…Œ์ŠคํŠธ - [ ] ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ (6๊ฐœ) - [ ] ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ (2๊ฐœ) - [ ] TypeScript ์ปดํŒŒ์ผ ์„ฑ๊ณต - [ ] ์„ฑ๋Šฅ ๋ฒค์น˜๋งˆํฌ ํ…Œ์ŠคํŠธ --- ## ๐Ÿ’ก ํŠน์ด์‚ฌํ•ญ ### ๋ณตํ•ฉ ๊ธฐ๋ณธ ํ‚ค ํŒจํ„ด ์ด ์„œ๋น„์Šค๋Š” `(template_code, company_code)` ๋ณตํ•ฉ ๊ธฐ๋ณธ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ, ๋ชจ๋“  ์กฐํšŒ/์ˆ˜์ •/์‚ญ์ œ ์ž‘์—…์—์„œ ๋‘ ์ปฌ๋Ÿผ์„ ๋ชจ๋‘ WHERE ์กฐ๊ฑด์— ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ### JSON ๋ ˆ์ด์•„์›ƒ ์„ค์ • `layout_config` ํ•„๋“œ๋Š” ํ…œํ”Œ๋ฆฟ์˜ ๋ ˆ์ด์•„์›ƒ ์„ค์ •์„ JSON ํ˜•ํƒœ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. Raw Query ์ „ํ™˜ ์‹œ `JSON.stringify()`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ### ์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ฆฌ ํ…œํ”Œ๋ฆฟ์€ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„๋กœ ๋ถ„๋ฅ˜๋˜๋ฉฐ, `getTemplateCategories()` ๋ฉ”์„œ๋“œ๋กœ ๊ณ ์œ ํ•œ ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. --- **์ž‘์„ฑ์ผ**: 2025-10-01 **์˜ˆ์ƒ ์†Œ์š” ์‹œ๊ฐ„**: 45๋ถ„ **๋‹ด๋‹น์ž**: ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœํŒ€ **์šฐ์„ ์ˆœ์œ„**: ๐ŸŸข ๋‚ฎ์Œ (Phase 3.9) **์ƒํƒœ**: โณ **๋Œ€๊ธฐ ์ค‘** **ํŠน์ด์‚ฌํ•ญ**: ๋ณตํ•ฉ ๊ธฐ๋ณธ ํ‚ค, JSON ํ•„๋“œ, DISTINCT ์ฟผ๋ฆฌ ํฌํ•จ