diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-14 06:12:13 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-14 06:12:13 +0000 |
| commit | d0d2eaa2de58a0c33e9a21604b126961403cd69e (patch) | |
| tree | f66cd3c8d3a123ff04f800b4b868c573fab2da95 /lib/items-tech/service.ts | |
| parent | 21d8148fc5b1234cd4523e66ccaa8971ad104560 (diff) | |
(최겸) 기술영업 조선, 해양Top, 해양 Hull 아이템 리스트 개발(CRUD, excel import/export/template)
Diffstat (limited to 'lib/items-tech/service.ts')
| -rw-r--r-- | lib/items-tech/service.ts | 1139 |
1 files changed, 1139 insertions, 0 deletions
diff --git a/lib/items-tech/service.ts b/lib/items-tech/service.ts new file mode 100644 index 00000000..97aacfba --- /dev/null +++ b/lib/items-tech/service.ts @@ -0,0 +1,1139 @@ +// src/lib/items-ship/service.ts
+"use server"; // Next.js 서버 액션에서 직접 import하려면 (선택)
+
+import { revalidateTag, unstable_noStore } from "next/cache";
+import db from "@/db/db";
+
+import { filterColumns } from "@/lib/filter-columns";
+import { unstable_cache } from "@/lib/unstable-cache";
+import { getErrorMessage } from "@/lib/handle-error";
+
+import { asc, desc, ilike, and, or, eq, count, inArray, sql } from "drizzle-orm";
+import { GetItemsSchema, UpdateItemSchema, ShipbuildingItemCreateData, TypedItemCreateData, OffshoreTopItemCreateData, OffshoreHullItemCreateData } from "./validations";
+import { Item, items, itemShipbuilding, itemOffshoreTop, itemOffshoreHull } from "@/db/schema/items";
+import { findAllItems, insertItem, updateItem } from "./repository";
+
+/* -----------------------------------------------------
+ 1) 조회 관련
+----------------------------------------------------- */
+
+/**
+ * 복잡한 조건으로 Item 목록을 조회 (+ pagination) 하고,
+ * 총 개수에 따라 pageCount를 계산해서 리턴.
+ * Next.js의 unstable_cache를 사용해 일정 시간 캐시.
+ */
+export async function getShipbuildingItems(input: GetItemsSchema) {
+ return unstable_cache(
+ async () => {
+ try {
+ const offset = (input.page - 1) * input.perPage;
+
+ // advancedTable 모드면 filterColumns()로 where 절 구성
+ const advancedWhere = filterColumns({
+ table: items,
+ filters: input.filters,
+ joinOperator: input.joinOperator,
+ });
+
+ let globalWhere;
+ if (input.search) {
+ const s = `%${input.search}%`;
+ globalWhere = or(
+ ilike(items.itemCode, s),
+ ilike(items.itemName, s),
+ ilike(items.description, s)
+ );
+ }
+
+ const finalWhere = and(
+ advancedWhere,
+ globalWhere
+ );
+
+ const where = finalWhere;
+
+ const orderBy =
+ input.sort.length > 0
+ ? input.sort.map((item) =>
+ item.desc ? desc(items[item.id]) : asc(items[item.id])
+ )
+ : [asc(items.createdAt)];
+
+ // 조선 아이템 테이블과 기본 아이템 테이블 조인하여 조회
+ const result = await db.select({
+ id: itemShipbuilding.id,
+ itemId: itemShipbuilding.itemId,
+ workType: itemShipbuilding.workType,
+ shipTypes: itemShipbuilding.shipTypes,
+ itemCode: items.itemCode,
+ itemName: items.itemName,
+ description: items.description,
+ createdAt: itemShipbuilding.createdAt,
+ updatedAt: itemShipbuilding.updatedAt,
+ })
+ .from(itemShipbuilding)
+ .innerJoin(items, eq(itemShipbuilding.itemId, items.id))
+ .where(where)
+ .orderBy(...orderBy)
+ .offset(offset)
+ .limit(input.perPage);
+
+ // 전체 데이터 개수 조회
+ const [{ count: total }] = await db.select({
+ count: count()
+ })
+ .from(itemShipbuilding)
+ .innerJoin(items, eq(itemShipbuilding.itemId, items.id))
+ .where(where);
+
+ const pageCount = Math.ceil(Number(total) / input.perPage);
+
+ return { data: result, pageCount };
+ } catch (err) {
+ console.error("Error fetching shipbuilding items:", err);
+ return { data: [], pageCount: 0 };
+ }
+ },
+ [JSON.stringify(input)],
+ {
+ revalidate: 3600,
+ tags: ["items"],
+ }
+ )();
+}
+
+export async function getOffshoreTopItems(input: GetItemsSchema) {
+ return unstable_cache(
+ async () => {
+ try {
+ const offset = (input.page - 1) * input.perPage;
+
+ // advancedTable 모드면 filterColumns()로 where 절 구성
+ const advancedWhere = filterColumns({
+ table: items,
+ filters: input.filters,
+ joinOperator: input.joinOperator,
+ });
+
+ let globalWhere;
+ if (input.search) {
+ const s = `%${input.search}%`;
+ globalWhere = or(
+ ilike(items.itemCode, s),
+ ilike(items.itemName, s),
+ ilike(items.description, s)
+ );
+ }
+
+ const finalWhere = and(
+ advancedWhere,
+ globalWhere
+ );
+
+ const where = finalWhere;
+
+ const orderBy =
+ input.sort.length > 0
+ ? input.sort.map((item) =>
+ item.desc ? desc(items[item.id]) : asc(items[item.id])
+ )
+ : [asc(items.createdAt)];
+
+ // 해양 TOP 아이템 테이블과 기본 아이템 테이블 조인하여 조회
+ const result = await db.select({
+ id: itemOffshoreTop.id,
+ itemId: itemOffshoreTop.itemId,
+ workType: itemOffshoreTop.workType,
+ itemList1: itemOffshoreTop.itemList1,
+ itemList2: itemOffshoreTop.itemList2,
+ itemList3: itemOffshoreTop.itemList3,
+ itemList4: itemOffshoreTop.itemList4,
+ itemCode: items.itemCode,
+ itemName: items.itemName,
+ description: items.description,
+ createdAt: itemOffshoreTop.createdAt,
+ updatedAt: itemOffshoreTop.updatedAt,
+ })
+ .from(itemOffshoreTop)
+ .innerJoin(items, eq(itemOffshoreTop.itemId, items.id))
+ .where(where)
+ .orderBy(...orderBy)
+ .offset(offset)
+ .limit(input.perPage);
+
+ // 전체 데이터 개수 조회
+ const [{ count: total }] = await db.select({
+ count: count()
+ })
+ .from(itemOffshoreTop)
+ .innerJoin(items, eq(itemOffshoreTop.itemId, items.id))
+ .where(where);
+
+ const pageCount = Math.ceil(Number(total) / input.perPage);
+
+ return { data: result, pageCount };
+ } catch (err) {
+ console.error("Error fetching offshore top items:", err);
+ return { data: [], pageCount: 0 };
+ }
+ },
+ [JSON.stringify(input)],
+ {
+ revalidate: 3600,
+ tags: ["items"],
+ }
+ )();
+}
+
+export async function getOffshoreHullItems(input: GetItemsSchema) {
+ return unstable_cache(
+ async () => {
+ try {
+ const offset = (input.page - 1) * input.perPage;
+
+ // advancedTable 모드면 filterColumns()로 where 절 구성
+ const advancedWhere = filterColumns({
+ table: items,
+ filters: input.filters,
+ joinOperator: input.joinOperator,
+ });
+
+ let globalWhere;
+ if (input.search) {
+ const s = `%${input.search}%`;
+ globalWhere = or(
+ ilike(items.itemCode, s),
+ ilike(items.itemName, s),
+ ilike(items.description, s)
+ );
+ }
+
+ const finalWhere = and(
+ advancedWhere,
+ globalWhere
+ );
+
+ const where = finalWhere;
+
+ const orderBy =
+ input.sort.length > 0
+ ? input.sort.map((item) =>
+ item.desc ? desc(items[item.id]) : asc(items[item.id])
+ )
+ : [asc(items.createdAt)];
+
+ // 해양 HULL 아이템 테이블과 기본 아이템 테이블 조인하여 조회
+ const result = await db.select({
+ id: itemOffshoreHull.id,
+ itemId: itemOffshoreHull.itemId,
+ workType: itemOffshoreHull.workType,
+ itemList1: itemOffshoreHull.itemList1,
+ itemList2: itemOffshoreHull.itemList2,
+ itemList3: itemOffshoreHull.itemList3,
+ itemList4: itemOffshoreHull.itemList4,
+ itemCode: items.itemCode,
+ itemName: items.itemName,
+ description: items.description,
+ createdAt: itemOffshoreHull.createdAt,
+ updatedAt: itemOffshoreHull.updatedAt,
+ })
+ .from(itemOffshoreHull)
+ .innerJoin(items, eq(itemOffshoreHull.itemId, items.id))
+ .where(where)
+ .orderBy(...orderBy)
+ .offset(offset)
+ .limit(input.perPage);
+
+ // 전체 데이터 개수 조회
+ const [{ count: total }] = await db.select({
+ count: count()
+ })
+ .from(itemOffshoreHull)
+ .innerJoin(items, eq(itemOffshoreHull.itemId, items.id))
+ .where(where);
+
+ const pageCount = Math.ceil(Number(total) / input.perPage);
+
+ return { data: result, pageCount };
+ } catch (err) {
+ console.error("Error fetching offshore hull items:", err);
+ return { data: [], pageCount: 0 };
+ }
+ },
+ [JSON.stringify(input)],
+ {
+ revalidate: 3600,
+ tags: ["items"],
+ }
+ )();
+}
+
+/* -----------------------------------------------------
+ 2) 생성(Create)
+----------------------------------------------------- */
+
+/**
+ * Item 생성 - 아이템 타입에 따라 해당 테이블에 데이터 삽입
+ */
+export async function createShipbuildingItem(input: TypedItemCreateData) {
+ unstable_noStore()
+
+ try {
+ if (!input.itemCode || !input.itemName) {
+ return {
+ success: false,
+ message: "아이템 코드와 아이템 명은 필수입니다",
+ data: null,
+ error: "필수 필드 누락"
+ }
+ }
+ const result = await db.transaction(async (tx) => {
+ // 1. itemCode 정규화해서 직접 쿼리
+ const existRows = await tx.select().from(items)
+ .where(eq(items.itemCode, input.itemCode));
+ const existingItem = existRows[0];
+
+ let itemId: number;
+ let itemResult;
+
+ if (existingItem) {
+ // 이미 있으면 업데이트
+ itemResult = await updateItem(tx, existingItem.id, {
+ itemName: input.itemName,
+ description: input.description,
+ });
+ itemId = existingItem.id;
+ } else {
+ // 없으면 새로 생성
+ // 현재 가장 큰 ID 값 가져오기
+ const maxIdResult = await tx.select({ maxId: sql`MAX(id)` }).from(items);
+ const maxId = maxIdResult[0]?.maxId || 0;
+ const newId = Number(maxId) + 1;
+
+ // 새 ID로 아이템 생성
+ itemResult = await tx.insert(items).values({
+ id: newId,
+ itemCode: input.itemCode,
+ itemName: input.itemName,
+ description: input.description,
+ }).returning();
+
+ itemId = itemResult[0].id;
+ }
+
+ const shipData = input as ShipbuildingItemCreateData;
+ const typeResult = await tx.insert(itemShipbuilding).values({
+ itemId: itemId,
+ workType: shipData.workType ? (shipData.workType as '기장' | '전장' | '선실' | '배관' | '철의') : '기장',
+ shipTypes: shipData.shipTypes || '',
+ createdAt: new Date(),
+ updatedAt: new Date()
+ }).returning();
+
+ return { itemData: itemResult[0], shipbuildingData: typeResult[0] };
+ })
+
+ revalidateTag("items")
+
+ return {
+ success: true,
+ data: result || null,
+ error: null
+ }
+ } catch (err) {
+ console.error("아이템 생성/업데이트 오류:", err)
+
+ if (err instanceof Error && err.message.includes("unique constraint")) {
+ return {
+ success: false,
+ message: "이미 존재하는 아이템 코드입니다",
+ data: null,
+ error: "중복 키 오류"
+ }
+ }
+
+ return {
+ success: false,
+ message: getErrorMessage(err),
+ data: null,
+ error: getErrorMessage(err)
+ }
+ }
+}
+
+/**
+ * Excel import를 위한 조선 아이템 생성 함수
+ * 하나의 아이템 코드에 대해 여러 선종을 처리 (1:N 관계)
+ */
+export async function createShipbuildingImportItem(input: {
+ itemCode: string;
+ itemName: string;
+ workType: '기장' | '전장' | '선실' | '배관' | '철의';
+ description?: string | null;
+ shipTypes: Record<string, boolean>;
+}) {
+ unstable_noStore();
+
+ try {
+
+ if (!input.itemCode || !input.itemName) {
+ return {
+ success: false,
+ message: "아이템 코드와 아이템 명은 필수입니다",
+ data: null,
+ error: "필수 필드 누락"
+ }
+ }
+ const results = await db.transaction(async (tx) => {
+ // 1. itemCode 정규화해서 직접 쿼리
+ const existRows = await tx.select().from(items)
+ .where(eq(items.itemCode, input.itemCode));
+ const existingItem = existRows[0];
+
+ console.log('DB에서 직접 조회한 기존 아이템:', existingItem);
+
+ let itemId: number;
+
+ if (existingItem) {
+ // 이미 있으면 업데이트
+ await updateItem(tx, existingItem.id, {
+ itemName: input.itemName,
+ description: input.description,
+ });
+ itemId = existingItem.id;
+ console.log('기존 아이템 업데이트, id:', itemId);
+ } else {
+ // 없으면 새로 생성
+ // 현재 가장 큰 ID 값 가져오기
+ const maxIdResult = await tx.select({ maxId: sql`MAX(id)` }).from(items);
+ const maxId = maxIdResult[0]?.maxId || 0;
+ const newId = Number(maxId) + 1;
+ console.log('새 아이템 생성을 위한 ID 계산:', { maxId, newId });
+
+ // 새 ID로 아이템 생성
+ const insertResult = await tx.insert(items).values({
+ id: newId,
+ itemCode: input.itemCode,
+ itemName: input.itemName,
+ description: input.description,
+ }).returning();
+
+ itemId = insertResult[0].id;
+ console.log('새 아이템 생성 완료, id:', itemId);
+ }
+
+ const createdItems = [];
+ for (const shipType of Object.keys(input.shipTypes)) {
+ // 그대로 선종명 string으로 저장
+ const existShip = await tx.select().from(itemShipbuilding)
+ .where(
+ and(
+ eq(itemShipbuilding.itemId, itemId),
+ eq(itemShipbuilding.shipTypes, shipType)
+ )
+ );
+ if (!existShip[0]) {
+ const shipbuildingResult = await tx.insert(itemShipbuilding).values({
+ itemId: itemId,
+ workType: input.workType,
+ shipTypes: shipType,
+ createdAt: new Date(),
+ updatedAt: new Date()
+ }).returning();
+ createdItems.push({
+ ...shipbuildingResult[0]
+ });
+ console.log('조선아이템 생성:', shipType, shipbuildingResult[0]);
+ } else {
+ console.log('이미 존재하는 조선아이템:', shipType);
+ }
+ }
+ return createdItems;
+ });
+
+ revalidateTag("items");
+
+ return {
+ success: true,
+ data: results,
+ error: null
+ }
+ } catch (err) {
+
+ return {
+ success: false,
+ message: getErrorMessage(err),
+ data: null,
+ error: getErrorMessage(err)
+ }
+ }
+}
+
+export async function createOffshoreTopItem(data: OffshoreTopItemCreateData) {
+ unstable_noStore()
+
+ try {
+ if (!data.itemCode || !data.itemName) {
+ return {
+ success: false,
+ message: "아이템 코드와 아이템 명은 필수입니다",
+ data: null,
+ error: "필수 필드 누락"
+ }
+ }
+
+ // 트랜잭션 내에서 처리
+ const result = await db.transaction(async (tx) => {
+ // 1. itemCode 정규화해서 직접 쿼리
+ const existRows = await tx.select().from(items)
+ .where(eq(items.itemCode, data.itemCode));
+ const existingItem = existRows[0];
+
+ let itemId: number;
+ let itemResult;
+
+ if (existingItem) {
+ // 이미 있으면 업데이트
+ itemResult = await updateItem(tx, existingItem.id, {
+ itemName: data.itemName,
+ description: data.description,
+ });
+ itemId = existingItem.id;
+ } else {
+ // 없으면 새로 생성
+ // 현재 가장 큰 ID 값 가져오기
+ const maxIdResult = await tx.select({ maxId: sql`MAX(id)` }).from(items);
+ const maxId = maxIdResult[0]?.maxId || 0;
+ const newId = Number(maxId) + 1;
+
+ // 새 ID로 아이템 생성
+ itemResult = await tx.insert(items).values({
+ id: newId,
+ itemCode: data.itemCode,
+ itemName: data.itemName,
+ description: data.description,
+ }).returning();
+
+ itemId = itemResult[0].id;
+ }
+
+ const [offshoreTop] = await tx
+ .insert(itemOffshoreTop)
+ .values({
+ itemId: itemId,
+ workType: data.workType,
+ itemList1: data.itemList1 || null,
+ itemList2: data.itemList2 || null,
+ itemList3: data.itemList3 || null,
+ itemList4: data.itemList4 || null,
+ createdAt: new Date(),
+ updatedAt: new Date()
+ })
+ .returning();
+
+ return { itemData: itemResult[0], offshoreTopData: offshoreTop };
+ })
+
+ revalidateTag("items")
+
+ return {
+ success: true,
+ data: result,
+ error: null
+ }
+ } catch (err) {
+ console.error("아이템 생성/업데이트 오류:", err)
+
+ if (err instanceof Error && err.message.includes("unique constraint")) {
+ return {
+ success: false,
+ message: "이미 존재하는 아이템 코드입니다",
+ data: null,
+ error: "중복 키 오류"
+ }
+ }
+
+ return {
+ success: false,
+ message: getErrorMessage(err),
+ data: null,
+ error: getErrorMessage(err)
+ }
+ }
+}
+
+export async function createOffshoreHullItem(data: OffshoreHullItemCreateData) {
+ unstable_noStore()
+
+ try {
+ if (!data.itemCode || !data.itemName) {
+ return {
+ success: false,
+ message: "아이템 코드와 아이템 명은 필수입니다",
+ data: null,
+ error: "필수 필드 누락"
+ }
+ }
+
+ // 트랜잭션 내에서 처리
+ const result = await db.transaction(async (tx) => {
+ // 1. itemCode 정규화해서 직접 쿼리
+ const existRows = await tx.select().from(items)
+ .where(eq(items.itemCode, data.itemCode));
+ const existingItem = existRows[0];
+
+ let itemId: number;
+ let itemResult;
+
+ if (existingItem) {
+ // 이미 있으면 업데이트
+ itemResult = await updateItem(tx, existingItem.id, {
+ itemName: data.itemName,
+ description: data.description,
+ });
+ itemId = existingItem.id;
+ } else {
+ // 없으면 새로 생성
+ // 현재 가장 큰 ID 값 가져오기
+ const maxIdResult = await tx.select({ maxId: sql`MAX(id)` }).from(items);
+ const maxId = maxIdResult[0]?.maxId || 0;
+ const newId = Number(maxId) + 1;
+
+ // 새 ID로 아이템 생성
+ itemResult = await tx.insert(items).values({
+ id: newId,
+ itemCode: data.itemCode,
+ itemName: data.itemName,
+ description: data.description,
+ }).returning();
+
+ itemId = itemResult[0].id;
+ }
+
+ const [offshoreHull] = await tx
+ .insert(itemOffshoreHull)
+ .values({
+ itemId: itemId,
+ workType: data.workType,
+ itemList1: data.itemList1 || null,
+ itemList2: data.itemList2 || null,
+ itemList3: data.itemList3 || null,
+ itemList4: data.itemList4 || null,
+ createdAt: new Date(),
+ updatedAt: new Date()
+ })
+ .returning();
+
+ return { itemData: itemResult[0], offshoreHullData: offshoreHull };
+ })
+
+ revalidateTag("items")
+
+ return {
+ success: true,
+ data: result,
+ error: null
+ }
+ } catch (err) {
+ console.error("아이템 생성/업데이트 오류:", err)
+
+ if (err instanceof Error && err.message.includes("unique constraint")) {
+ return {
+ success: false,
+ message: "이미 존재하는 아이템 코드입니다",
+ data: null,
+ error: "중복 키 오류"
+ }
+ }
+
+ return {
+ success: false,
+ message: getErrorMessage(err),
+ data: null,
+ error: getErrorMessage(err)
+ }
+ }
+}
+
+/* -----------------------------------------------------
+ 3) 업데이트
+----------------------------------------------------- */
+
+// 업데이트 타입 정의 인터페이스
+interface UpdateShipbuildingItemInput extends UpdateItemSchema {
+ id: number;
+ workType?: string;
+ shipTypes?: string;
+ itemCode?: string;
+ itemName?: string;
+ description?: string;
+}
+
+/** 단건 업데이트 */
+export async function modifyShipbuildingItem(input: UpdateShipbuildingItemInput) {
+ unstable_noStore();
+ try {
+ const result = await db.transaction(async (tx) => {
+ // 기존 아이템 조회
+ const existingShipbuilding = await tx.query.itemShipbuilding.findFirst({
+ where: eq(itemShipbuilding.id, input.id),
+ with: {
+ item: true
+ }
+ });
+
+ if (!existingShipbuilding) {
+ throw new Error("아이템을 찾을 수 없습니다.");
+ }
+
+ const existingItem = existingShipbuilding.item;
+
+ // 아이템 테이블 정보가 변경되었는지 확인
+ const isItemChanged =
+ (input.itemCode && input.itemCode !== existingItem.itemCode) ||
+ (input.itemName && input.itemName !== existingItem.itemName) ||
+ (input.description !== undefined && input.description !== existingItem.description);
+
+ // 세부 아이템 정보만 변경된 경우
+ if (!isItemChanged) {
+ // 조선 아이템 테이블 업데이트
+ if (input.workType || input.shipTypes) {
+ await tx.update(itemShipbuilding)
+ .set({
+ workType: input.workType as '기장' | '전장' | '선실' | '배관' | '철의',
+ shipTypes: input.shipTypes
+ })
+ .where(eq(itemShipbuilding.id, input.id));
+ }
+
+ return {
+ data: { id: input.id },
+ error: null,
+ success: true,
+ message: "아이템이 성공적으로 업데이트되었습니다."
+ };
+ }
+ // 아이템 테이블 정보가 변경된 경우 - 새 아이템 생성하고 세부 아이템 연결
+ else {
+ // 새 아이템 생성
+ const [newItem] = await insertItem(tx, {
+ itemCode: input.itemCode || existingItem.itemCode,
+ itemName: input.itemName || existingItem.itemName,
+ description: input.description !== undefined ? input.description : existingItem.description,
+ });
+
+ // 세부 아이템 테이블 정보를 새 아이템에 연결
+ await tx.update(itemShipbuilding)
+ .set({
+ itemId: newItem.id,
+ workType: input.workType ? (input.workType as '기장' | '전장' | '선실' | '배관' | '철의') : existingShipbuilding.workType,
+ shipTypes: input.shipTypes || existingShipbuilding.shipTypes
+ })
+ .where(eq(itemShipbuilding.id, input.id));
+
+ return {
+ data: { id: input.id },
+ error: null,
+ success: true,
+ message: "새 아이템이 생성되고 세부 정보가 업데이트되었습니다."
+ };
+ }
+ });
+
+ // 캐시 무효화
+ revalidateTag("items");
+
+ return result;
+ } catch (err) {
+ return {
+ data: null,
+ error: getErrorMessage(err),
+ success: false,
+ message: "아이템 업데이트 중 오류가 발생했습니다."
+ };
+ }
+}
+
+// Offshore TOP 업데이트 타입 정의 인터페이스
+interface UpdateOffshoreTopItemInput extends UpdateItemSchema {
+ id: number;
+ workType?: string;
+ itemList1?: string;
+ itemList2?: string;
+ itemList3?: string;
+ itemList4?: string;
+ itemCode?: string;
+ itemName?: string;
+ description?: string;
+}
+
+/** Offshore TOP 단건 업데이트 */
+export async function modifyOffshoreTopItem(input: UpdateOffshoreTopItemInput) {
+ unstable_noStore();
+ try {
+ const result = await db.transaction(async (tx) => {
+ // 기존 아이템 조회
+ const existingOffshoreTop = await tx.query.itemOffshoreTop.findFirst({
+ where: eq(itemOffshoreTop.id, input.id),
+ with: {
+ item: true
+ }
+ });
+
+ if (!existingOffshoreTop) {
+ throw new Error("아이템을 찾을 수 없습니다.");
+ }
+
+ const existingItem = existingOffshoreTop.item;
+
+ // 아이템 테이블 정보가 변경되었는지 확인
+ const isItemChanged =
+ (input.itemCode && input.itemCode !== existingItem.itemCode) ||
+ (input.itemName && input.itemName !== existingItem.itemName) ||
+ (input.description !== undefined && input.description !== existingItem.description);
+
+ // 세부 아이템 정보만 변경된 경우
+ if (!isItemChanged) {
+ // Offshore TOP 아이템 테이블 업데이트
+ const updateData: Record<string, unknown> = {};
+
+ if (input.workType) updateData.workType = input.workType as 'TM' | 'TS' | 'TE' | 'TP';
+ if (input.itemList1 !== undefined) updateData.itemList1 = input.itemList1;
+ if (input.itemList2 !== undefined) updateData.itemList2 = input.itemList2;
+ if (input.itemList3 !== undefined) updateData.itemList3 = input.itemList3;
+ if (input.itemList4 !== undefined) updateData.itemList4 = input.itemList4;
+
+ if (Object.keys(updateData).length > 0) {
+ await tx.update(itemOffshoreTop)
+ .set(updateData)
+ .where(eq(itemOffshoreTop.id, input.id));
+ }
+
+ return {
+ data: { id: input.id },
+ error: null,
+ success: true,
+ message: "아이템이 성공적으로 업데이트되었습니다."
+ };
+ }
+ // 아이템 테이블 정보가 변경된 경우 - 새 아이템 생성하고 세부 아이템 연결
+ else {
+ // 새 아이템 생성
+ const [newItem] = await insertItem(tx, {
+ itemCode: input.itemCode || existingItem.itemCode,
+ itemName: input.itemName || existingItem.itemName,
+ description: input.description !== undefined ? input.description : existingItem.description,
+ });
+
+ // 세부 아이템 테이블 정보를 새 아이템에 연결
+ const updateData: Record<string, unknown> = {
+ itemId: newItem.id
+ };
+
+ if (input.workType) updateData.workType = input.workType as 'TM' | 'TS' | 'TE' | 'TP';
+ if (input.itemList1 !== undefined) updateData.itemList1 = input.itemList1;
+ if (input.itemList2 !== undefined) updateData.itemList2 = input.itemList2;
+ if (input.itemList3 !== undefined) updateData.itemList3 = input.itemList3;
+ if (input.itemList4 !== undefined) updateData.itemList4 = input.itemList4;
+
+ await tx.update(itemOffshoreTop)
+ .set(updateData)
+ .where(eq(itemOffshoreTop.id, input.id));
+
+ return {
+ data: { id: input.id },
+ error: null,
+ success: true,
+ message: "새 아이템이 생성되고 세부 정보가 업데이트되었습니다."
+ };
+ }
+ });
+
+ // 캐시 무효화
+ revalidateTag("items");
+
+ return result;
+ } catch (err) {
+ return {
+ data: null,
+ error: getErrorMessage(err),
+ success: false,
+ message: "아이템 업데이트 중 오류가 발생했습니다."
+ };
+ }
+}
+
+// Offshore HULL 업데이트 타입 정의 인터페이스
+interface UpdateOffshoreHullItemInput extends UpdateItemSchema {
+ id: number;
+ workType?: string;
+ itemList1?: string;
+ itemList2?: string;
+ itemList3?: string;
+ itemList4?: string;
+ itemCode?: string;
+ itemName?: string;
+ description?: string;
+}
+
+/** Offshore HULL 단건 업데이트 */
+export async function modifyOffshoreHullItem(input: UpdateOffshoreHullItemInput) {
+ unstable_noStore();
+ try {
+ const result = await db.transaction(async (tx) => {
+ // 기존 아이템 조회
+ const existingOffshoreHull = await tx.query.itemOffshoreHull.findFirst({
+ where: eq(itemOffshoreHull.id, input.id),
+ with: {
+ item: true
+ }
+ });
+
+ if (!existingOffshoreHull) {
+ throw new Error("아이템을 찾을 수 없습니다.");
+ }
+
+ const existingItem = existingOffshoreHull.item;
+
+ // 아이템 테이블 정보가 변경되었는지 확인
+ const isItemChanged =
+ (input.itemCode && input.itemCode !== existingItem.itemCode) ||
+ (input.itemName && input.itemName !== existingItem.itemName) ||
+ (input.description !== undefined && input.description !== existingItem.description);
+
+ // 세부 아이템 정보만 변경된 경우
+ if (!isItemChanged) {
+ // Offshore HULL 아이템 테이블 업데이트
+ const updateData: Record<string, unknown> = {};
+
+ if (input.workType) updateData.workType = input.workType as 'HA' | 'HE' | 'HH' | 'HM' | 'NC';
+ if (input.itemList1 !== undefined) updateData.itemList1 = input.itemList1;
+ if (input.itemList2 !== undefined) updateData.itemList2 = input.itemList2;
+ if (input.itemList3 !== undefined) updateData.itemList3 = input.itemList3;
+ if (input.itemList4 !== undefined) updateData.itemList4 = input.itemList4;
+
+ if (Object.keys(updateData).length > 0) {
+ await tx.update(itemOffshoreHull)
+ .set(updateData)
+ .where(eq(itemOffshoreHull.id, input.id));
+ }
+
+ return {
+ data: { id: input.id },
+ error: null,
+ success: true,
+ message: "아이템이 성공적으로 업데이트되었습니다."
+ };
+ }
+ // 아이템 테이블 정보가 변경된 경우 - 새 아이템 생성하고 세부 아이템 연결
+ else {
+ // 새 아이템 생성
+ const [newItem] = await insertItem(tx, {
+ itemCode: input.itemCode || existingItem.itemCode,
+ itemName: input.itemName || existingItem.itemName,
+ description: input.description !== undefined ? input.description : existingItem.description,
+ });
+
+ // 세부 아이템 테이블 정보를 새 아이템에 연결
+ const updateData: Record<string, unknown> = {
+ itemId: newItem.id
+ };
+
+ if (input.workType) updateData.workType = input.workType as 'HA' | 'HE' | 'HH' | 'HM' | 'NC';
+ if (input.itemList1 !== undefined) updateData.itemList1 = input.itemList1;
+ if (input.itemList2 !== undefined) updateData.itemList2 = input.itemList2;
+ if (input.itemList3 !== undefined) updateData.itemList3 = input.itemList3;
+ if (input.itemList4 !== undefined) updateData.itemList4 = input.itemList4;
+
+ await tx.update(itemOffshoreHull)
+ .set(updateData)
+ .where(eq(itemOffshoreHull.id, input.id));
+
+ return {
+ data: { id: input.id },
+ error: null,
+ success: true,
+ message: "새 아이템이 생성되고 세부 정보가 업데이트되었습니다."
+ };
+ }
+ });
+
+ // 캐시 무효화
+ revalidateTag("items");
+
+ return result;
+ } catch (err) {
+ return {
+ data: null,
+ error: getErrorMessage(err),
+ success: false,
+ message: "아이템 업데이트 중 오류가 발생했습니다."
+ };
+ }
+}
+
+/* -----------------------------------------------------
+ 4) 삭제
+----------------------------------------------------- */
+
+// 삭제 타입 정의 인터페이스
+interface DeleteItemInput {
+ id: number;
+}
+
+interface DeleteItemsInput {
+ ids: number[];
+}
+
+/** 단건 삭제 */
+export async function removeShipbuildingItem(input: DeleteItemInput) {
+ unstable_noStore();
+ try {
+ await db.transaction(async (tx) => {
+ // 세부 아이템만 삭제 (아이템 테이블은 유지)
+ await tx.delete(itemShipbuilding)
+ .where(eq(itemShipbuilding.id, input.id));
+ });
+
+ revalidateTag("items");
+
+ return {
+ data: null,
+ error: null,
+ success: true,
+ message: "아이템이 성공적으로 삭제되었습니다."
+ };
+ } catch (err) {
+ return {
+ data: null,
+ error: getErrorMessage(err),
+ success: false,
+ message: "아이템 삭제 중 오류가 발생했습니다."
+ };
+ }
+}
+
+/** 복수 삭제 */
+export async function removeShipbuildingItems(input: DeleteItemsInput) {
+ unstable_noStore();
+ try {
+ await db.transaction(async (tx) => {
+ if (input.ids.length > 0) {
+ // 세부 아이템만 삭제 (아이템 테이블은 유지)
+ await tx.delete(itemShipbuilding)
+ .where(inArray(itemShipbuilding.id, input.ids));
+ }
+ });
+
+ revalidateTag("items");
+
+ return { data: null, error: null, success: true, message: "아이템이 성공적으로 삭제되었습니다." };
+ } catch (err) {
+ return { data: null, error: getErrorMessage(err), success: false, message: "아이템 삭제 중 오류가 발생했습니다." };
+ }
+}
+
+/** Offshore TOP 단건 삭제 */
+export async function removeOffshoreTopItem(input: DeleteItemInput) {
+ unstable_noStore();
+ try {
+ await db.transaction(async (tx) => {
+ // 세부 아이템만 삭제 (아이템 테이블은 유지)
+ await tx.delete(itemOffshoreTop)
+ .where(eq(itemOffshoreTop.id, input.id));
+ });
+
+ revalidateTag("items");
+
+ return {
+ data: null,
+ error: null,
+ success: true,
+ message: "아이템이 성공적으로 삭제되었습니다."
+ };
+ } catch (err) {
+ return {
+ data: null,
+ error: getErrorMessage(err),
+ success: false,
+ message: "아이템 삭제 중 오류가 발생했습니다."
+ };
+ }
+}
+
+/** Offshore TOP 복수 삭제 */
+export async function removeOffshoreTopItems(input: DeleteItemsInput) {
+ unstable_noStore();
+ try {
+ await db.transaction(async (tx) => {
+ if (input.ids.length > 0) {
+ // 세부 아이템만 삭제 (아이템 테이블은 유지)
+ await tx.delete(itemOffshoreTop)
+ .where(inArray(itemOffshoreTop.id, input.ids));
+ }
+ });
+
+ revalidateTag("items");
+
+ return { data: null, error: null, success: true, message: "아이템이 성공적으로 삭제되었습니다." };
+ } catch (err) {
+ return { data: null, error: getErrorMessage(err), success: false, message: "아이템 삭제 중 오류가 발생했습니다." };
+ }
+}
+
+/** Offshore HULL 단건 삭제 */
+export async function removeOffshoreHullItem(input: DeleteItemInput) {
+ unstable_noStore();
+ try {
+ await db.transaction(async (tx) => {
+ // 세부 아이템만 삭제 (아이템 테이블은 유지)
+ await tx.delete(itemOffshoreHull)
+ .where(eq(itemOffshoreHull.id, input.id));
+ });
+
+ revalidateTag("items");
+
+ return {
+ data: null,
+ error: null,
+ success: true,
+ message: "아이템이 성공적으로 삭제되었습니다."
+ };
+ } catch (err) {
+ return {
+ data: null,
+ error: getErrorMessage(err),
+ success: false,
+ message: "아이템 삭제 중 오류가 발생했습니다."
+ };
+ }
+}
+
+/** Offshore HULL 복수 삭제 */
+export async function removeOffshoreHullItems(input: DeleteItemsInput) {
+ unstable_noStore();
+ try {
+ await db.transaction(async (tx) => {
+ if (input.ids.length > 0) {
+ // 세부 아이템만 삭제 (아이템 테이블은 유지)
+ await tx.delete(itemOffshoreHull)
+ .where(inArray(itemOffshoreHull.id, input.ids));
+ }
+ });
+
+ revalidateTag("items");
+
+ return { data: null, error: null, success: true, message: "아이템이 성공적으로 삭제되었습니다." };
+ } catch (err) {
+ return { data: null, error: getErrorMessage(err), success: false, message: "아이템 삭제 중 오류가 발생했습니다." };
+ }
+}
+
+export async function getAllShipbuildingItems(): Promise<Item[]> {
+ try {
+ return await findAllItems();
+ } catch (error) {
+ console.error("Failed to get items:", error);
+ throw new Error("Failed to get items");
+ }
+}
|
