diff options
Diffstat (limited to 'lib/tech-vendor-possible-items/service.ts')
| -rw-r--r-- | lib/tech-vendor-possible-items/service.ts | 893 |
1 files changed, 148 insertions, 745 deletions
diff --git a/lib/tech-vendor-possible-items/service.ts b/lib/tech-vendor-possible-items/service.ts index c630e33a..48f9b869 100644 --- a/lib/tech-vendor-possible-items/service.ts +++ b/lib/tech-vendor-possible-items/service.ts @@ -1,5 +1,6 @@ -"use server"; // Next.js 서버 액션에서 직접 import하려면 (선택)
-import { eq, and, inArray, desc, asc, or, ilike, isNull } from "drizzle-orm";
+"use server";
+
+import { eq } from "drizzle-orm";
import db from "@/db/db";
import {
techVendors,
@@ -7,69 +8,48 @@ import { } from "@/db/schema/techVendors";
import { itemShipbuilding, itemOffshoreTop, itemOffshoreHull } from "@/db/schema/items";
import { unstable_cache } from "@/lib/unstable-cache";
-import { filterColumns } from "@/lib/filter-columns";
-import type { GetTechVendorPossibleItemsSchema } from "./validations";
-import {
- selectTechVendorPossibleItemsWithJoin,
- countTechVendorPossibleItemsWithJoin,
-} from "./repository";
+// 새로운 스키마에 맞는 데이터 인터페이스
export interface TechVendorPossibleItemsData {
id: number;
vendorId: number;
+ shipbuildingItemId: number | null;
+ offshoreTopItemId: number | null;
+ offshoreHullItemId: number | null;
+ createdAt: Date;
+ updatedAt: Date;
+ // 조인된 벤더 정보
vendorCode: string | null;
vendorName: string;
vendorEmail: string | null;
+ vendorStatus: string;
techVendorType: string;
+ // 조인된 아이템 정보
itemCode: string;
workType: string | null;
shipTypes: string | null;
itemList: string | null;
subItemList: string | null;
- createdAt: Date;
- updatedAt: Date;
}
-export interface CreateTechVendorPossibleItemData {
- vendorId: number; // 필수: 벤더 ID (Add Dialog에서 벤더 선택 시 사용)
- itemCode: string; // 필수: 아이템 코드
- workType?: string | null; // 공종 (아이템에서 가져온 정보)
- shipTypes?: string | null; // 선종 (아이템에서 가져온 정보)
- itemList?: string | null; // 아이템리스트 (아이템에서 가져온 정보)
- subItemList?: string | null; // 서브아이템리스트 (아이템에서 가져온 정보)
-}
-
-export interface ImportTechVendorPossibleItemData {
+export interface GetTechVendorPossibleItemsSchema {
+ page: number;
+ perPage: number;
+ search?: string;
+ filters?: Array<{ id: string; value: string | number | boolean; operator?: string }>;
+ joinOperator?: "and" | "or";
+ sort?: Array<{ id: string; desc: boolean }>;
vendorCode?: string;
- vendorEmail: string; // 필수: 벤더 이메일
- itemCode: string; // 필수: 아이템 코드
- workType?: string;
- shipTypes?: string;
- itemList?: string;
- subItemList?: string;
-}
-
-export interface ImportResult {
- success: boolean;
- totalRows: number;
- successCount: number;
- failedRows: {
- row: number;
- error: string;
- vendorCode?: string;
- vendorEmail?: string;
- itemCode?: string;
- workType?: string;
- shipTypes?: string;
- itemList?: string;
- subItemList?: string;
- }[];
+ vendorName?: string;
+ vendorEmail?: string;
+ vendorStatus?: string;
+ itemCode?: string;
+ vendorType?: string;
}
-
-
/**
- * 견적프로젝트 패턴에 맞는 메인 조회 함수
+ * 새로운 스키마에 맞는 tech vendor possible items 조회 함수
+ * 벤더 정보와 각 아이템 테이블별 정보를 조인해서 가져옴
*/
export async function getTechVendorPossibleItems(input: GetTechVendorPossibleItemsSchema) {
return unstable_cache(
@@ -77,39 +57,112 @@ export async function getTechVendorPossibleItems(input: GetTechVendorPossibleIte try {
const offset = (input.page - 1) * input.perPage;
- // 고급 필터링 (DataTableAdvancedToolbar용)
- const advancedWhere = filterColumns({
- table: techVendorPossibleItems,
- filters: input.filters,
- joinOperator: input.joinOperator,
- });
-
- // 전역 검색 (search box용)
- let globalWhere;
+ // 한 쿼리로 모든 아이템 조회 (각 레코드는 3개 중 하나의 ID만 가짐)
+ const rawItems = await db
+ .select({
+ id: techVendorPossibleItems.id,
+ vendorId: techVendorPossibleItems.vendorId,
+ shipbuildingItemId: techVendorPossibleItems.shipbuildingItemId,
+ offshoreTopItemId: techVendorPossibleItems.offshoreTopItemId,
+ offshoreHullItemId: techVendorPossibleItems.offshoreHullItemId,
+ createdAt: techVendorPossibleItems.createdAt,
+ updatedAt: techVendorPossibleItems.updatedAt,
+ // 벤더 정보
+ vendorCode: techVendors.vendorCode,
+ vendorName: techVendors.vendorName,
+ vendorEmail: techVendors.email,
+ vendorStatus: techVendors.status,
+ techVendorType: techVendors.techVendorType,
+ // 조선 아이템 정보 (shipbuildingItemId가 있을 때만)
+ shipItemCode: itemShipbuilding.itemCode,
+ shipWorkType: itemShipbuilding.workType,
+ shipItemList: itemShipbuilding.itemList,
+ shipTypes: itemShipbuilding.shipTypes,
+ // 해양 TOP 아이템 정보 (offshoreTopItemId가 있을 때만)
+ topItemCode: itemOffshoreTop.itemCode,
+ topWorkType: itemOffshoreTop.workType,
+ topItemList: itemOffshoreTop.itemList,
+ topSubItemList: itemOffshoreTop.subItemList,
+ // 해양 HULL 아이템 정보 (offshoreHullItemId가 있을 때만)
+ hullItemCode: itemOffshoreHull.itemCode,
+ hullWorkType: itemOffshoreHull.workType,
+ hullItemList: itemOffshoreHull.itemList,
+ hullSubItemList: itemOffshoreHull.subItemList,
+ })
+ .from(techVendorPossibleItems)
+ .innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
+ .leftJoin(itemShipbuilding, eq(techVendorPossibleItems.shipbuildingItemId, itemShipbuilding.id))
+ .leftJoin(itemOffshoreTop, eq(techVendorPossibleItems.offshoreTopItemId, itemOffshoreTop.id))
+ .leftJoin(itemOffshoreHull, eq(techVendorPossibleItems.offshoreHullItemId, itemOffshoreHull.id));
+
+ // 결과를 통합된 형태로 변환
+ const allItems: TechVendorPossibleItemsData[] = rawItems.map((item: Record<string, any>) => ({
+ id: item.id,
+ vendorId: item.vendorId,
+ shipbuildingItemId: item.shipbuildingItemId,
+ offshoreTopItemId: item.offshoreTopItemId,
+ offshoreHullItemId: item.offshoreHullItemId,
+ createdAt: item.createdAt,
+ updatedAt: item.updatedAt,
+ vendorCode: item.vendorCode,
+ vendorName: item.vendorName,
+ vendorEmail: item.vendorEmail,
+ vendorStatus: item.vendorStatus,
+ techVendorType: item.techVendorType,
+ // 어떤 타입의 아이템인지에 따라 적절한 값 선택
+ itemCode: item.shipItemCode || item.topItemCode || item.hullItemCode || "",
+ workType: item.shipWorkType || item.topWorkType || item.hullWorkType,
+ itemList: item.shipItemList || item.topItemList || item.hullItemList,
+ shipTypes: item.shipTypes, // 조선에만 있음
+ subItemList: item.topSubItemList || item.hullSubItemList, // 해양에만 있음
+ }));
+
+ let filteredItems = allItems;
+
+ // 필터링 적용
if (input.search) {
- const s = `%${input.search}%`;
- globalWhere = or(
- ilike(techVendors.vendorCode, s),
- ilike(techVendors.vendorName, s),
- ilike(techVendorPossibleItems.vendorEmail, s),
- ilike(techVendorPossibleItems.itemCode, s),
- ilike(techVendorPossibleItems.workType, s),
- ilike(techVendorPossibleItems.shipTypes, s),
- ilike(techVendorPossibleItems.itemList, s),
- ilike(techVendorPossibleItems.subItemList, s),
+ const s = input.search.toLowerCase();
+ filteredItems = filteredItems.filter(item =>
+ item.vendorCode?.toLowerCase().includes(s) ||
+ item.vendorName?.toLowerCase().includes(s) ||
+ item.vendorEmail?.toLowerCase().includes(s) ||
+ item.itemCode?.toLowerCase().includes(s) ||
+ item.workType?.toLowerCase().includes(s) ||
+ item.itemList?.toLowerCase().includes(s) ||
+ item.shipTypes?.toLowerCase().includes(s) ||
+ item.subItemList?.toLowerCase().includes(s)
);
}
- // 기존 호환성을 위한 개별 필터들
- const legacyFilters = [];
+ // 개별 필터들
if (input.vendorCode) {
- legacyFilters.push(ilike(techVendorPossibleItems.vendorCode, `%${input.vendorCode}%`));
+ filteredItems = filteredItems.filter(item =>
+ item.vendorCode?.toLowerCase().includes(input.vendorCode!.toLowerCase())
+ );
}
+
if (input.vendorName) {
- legacyFilters.push(ilike(techVendors.vendorName, `%${input.vendorName}%`));
+ filteredItems = filteredItems.filter(item =>
+ item.vendorName?.toLowerCase().includes(input.vendorName!.toLowerCase())
+ );
+ }
+
+ if (input.vendorEmail) {
+ filteredItems = filteredItems.filter(item =>
+ item.vendorEmail?.toLowerCase().includes(input.vendorEmail!.toLowerCase())
+ );
}
+
+ if (input.vendorStatus) {
+ filteredItems = filteredItems.filter(item =>
+ item.vendorStatus === input.vendorStatus
+ );
+ }
+
if (input.itemCode) {
- legacyFilters.push(ilike(techVendorPossibleItems.itemCode, `%${input.itemCode}%`));
+ filteredItems = filteredItems.filter(item =>
+ item.itemCode?.toLowerCase().includes(input.itemCode!.toLowerCase())
+ );
}
// 벤더 타입 필터링
@@ -123,31 +176,36 @@ export async function getTechVendorPossibleItems(input: GetTechVendorPossibleIte const actualVendorType = vendorTypeMap[input.vendorType as keyof typeof vendorTypeMap] || input.vendorType;
if (actualVendorType) {
- legacyFilters.push(ilike(techVendors.techVendorType, `%${actualVendorType}%`));
+ filteredItems = filteredItems.filter(item =>
+ item.techVendorType?.includes(actualVendorType)
+ );
}
}
- // 모든 조건 결합
- const finalWhere = and(
- advancedWhere,
- globalWhere,
- ...(legacyFilters.length > 0 ? [and(...legacyFilters)] : [])
- );
-
- // 정렬 조건
- const orderBy = [desc(techVendorPossibleItems.createdAt)];
-
- // 트랜잭션 내에서 Repository 호출
- const { data, total } = await db.transaction(async (tx) => {
- const data = await selectTechVendorPossibleItemsWithJoin(tx, finalWhere, orderBy, offset, input.perPage);
+ // 정렬
+ if (input.sort && input.sort.length > 0) {
+ filteredItems.sort((a, b) => {
+ for (const sortItem of input.sort!) {
+ const aVal = (a as Record<string, any>)[sortItem.id] || "";
+ const bVal = (b as Record<string, any>)[sortItem.id] || "";
+
+ if (aVal < bVal) return sortItem.desc ? 1 : -1;
+ if (aVal > bVal) return sortItem.desc ? -1 : 1;
+ }
+ return 0;
+ });
+ } else {
+ // 기본 정렬: createdAt 내림차순
+ filteredItems.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
+ }
- const total = await countTechVendorPossibleItemsWithJoin(tx, finalWhere);
- return { data, total };
- });
+ const totalCount = filteredItems.length;
+ const pageCount = Math.ceil(totalCount / input.perPage);
- const pageCount = Math.ceil(total / input.perPage);
+ // 페이지네이션 적용
+ const data = filteredItems.slice(offset, offset + input.perPage);
- return { data, pageCount, totalCount: total };
+ return { data, pageCount, totalCount };
} catch (error) {
console.error("Error fetching tech vendor possible items:", error);
return { data: [], pageCount: 0, totalCount: 0 };
@@ -159,659 +217,4 @@ export async function getTechVendorPossibleItems(input: GetTechVendorPossibleIte tags: ["tech-vendor-possible-items"],
}
)();
-}
-
-
-
-/**
- * 페이지네이션을 포함한 tech vendor possible items 조회
- */
-// export async function getTechVendorPossibleItemsWithPagination(
-// page: number = 1,
-// pageSize: number = 50,
-// searchTerm?: string,
-// vendorType?: string
-// ): Promise<{
-// data: TechVendorPossibleItemsData[];
-// totalCount: number;
-// totalPages: number;
-// }> {
-// const whereConditions = [];
-
-// if (searchTerm) {
-// whereConditions.push(
-// sql`(
-// ${techVendors.vendorName} ILIKE ${`%${searchTerm}%`} OR
-// ${techVendors.vendorCode} ILIKE ${`%${searchTerm}%`} OR
-// ${techVendorPossibleItems.itemCode} ILIKE ${`%${searchTerm}%`}
-// )`
-// );
-// }
-
-// // 벤더 타입 필터링 로직 추가
-// if (vendorType && vendorType !== "all") {
-// // URL의 vendorType 파라미터를 실제 벤더 타입으로 매핑
-// const vendorTypeMap = {
-// "ship": "조선",
-// "top": "해양TOP",
-// "hull": "해양HULL"
-// };
-
-// const actualVendorType = vendorType in vendorTypeMap
-// ? vendorTypeMap[vendorType as keyof typeof vendorTypeMap]
-// : vendorType; // 매핑되지 않는 경우 원본 값 사용
-
-// if (actualVendorType) {
-// // techVendorType 필드는 콤마로 구분된 문자열이므로 LIKE 사용
-// whereConditions.push(sql`${techVendors.techVendorType} ILIKE ${`%${actualVendorType}%`}`);
-// }
-// }
-
-// const whereClause = whereConditions.length > 0 ? and(...whereConditions) : undefined;
-
-// // 총 개수 조회
-// const [totalCountResult] = await db
-// .select({ count: count() })
-// .from(techVendorPossibleItems)
-// .innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
-// .where(whereClause);
-
-// const totalCount = totalCountResult.count;
-// const totalPages = Math.ceil(totalCount / pageSize);
-// const offset = (page - 1) * pageSize;
-
-// // 데이터 조회
-// const data = await db
-// .select({
-// id: techVendorPossibleItems.id,
-// vendorId: techVendorPossibleItems.vendorId,
-// vendorCode: techVendors.vendorCode,
-// vendorName: techVendors.vendorName,
-// techVendorType: techVendors.techVendorType,
-// itemCode: techVendorPossibleItems.itemCode,
-// createdAt: techVendorPossibleItems.createdAt,
-// updatedAt: techVendorPossibleItems.updatedAt,
-// })
-// .from(techVendorPossibleItems)
-// .innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
-// .where(whereClause)
-// .orderBy(desc(techVendorPossibleItems.createdAt))
-// .limit(pageSize)
-// .offset(offset);
-
-// return {
-// data,
-// totalCount,
-// totalPages,
-// };
-// }
-
-/**
- * tech vendor possible item 생성 (Add Dialog용 - vendorId 기반)
- */
-export async function createTechVendorPossibleItem(
- data: CreateTechVendorPossibleItemData
-): Promise<{ success: boolean; error?: string }> {
- try {
- // 벤더 ID로 벤더 조회
- const vendor = await db
- .select()
- .from(techVendors)
- .where(eq(techVendors.id, data.vendorId))
- .limit(1);
-
- if (!vendor[0]) {
- return { success: false, error: "벤더를 찾을 수 없습니다." };
- }
-
- // 중복 체크 (벤더 + 아이템코드 + 공종 + 선종 조합)
- const existing = await db
- .select()
- .from(techVendorPossibleItems)
- .where(
- and(
- eq(techVendorPossibleItems.vendorId, data.vendorId),
- eq(techVendorPossibleItems.itemCode, data.itemCode),
- data.workType
- ? eq(techVendorPossibleItems.workType, data.workType)
- : isNull(techVendorPossibleItems.workType),
- data.shipTypes
- ? eq(techVendorPossibleItems.shipTypes, data.shipTypes)
- : isNull(techVendorPossibleItems.shipTypes)
- )
- )
- .limit(1);
-
- if (existing.length > 0) {
- return { success: false, error: "이미 존재하는 벤더-아이템 조합입니다." };
- }
-
- // 새로운 아이템 생성 (선택한 아이템의 정보를 그대로 저장)
- await db.insert(techVendorPossibleItems).values({
- vendorId: vendor[0].id,
- vendorCode: vendor[0].vendorCode,
- vendorEmail: vendor[0].email,
- itemCode: data.itemCode,
- workType: data.workType,
- shipTypes: data.shipTypes,
- itemList: data.itemList,
- subItemList: data.subItemList,
- });
-
- return { success: true };
- } catch (error) {
- console.error("Failed to create tech vendor possible item:", error);
- return {
- success: false,
- error: error instanceof Error ? error.message : "생성 중 오류가 발생했습니다."
- };
- }
-}
-
-/**
- * tech vendor possible items 삭제
- */
-export async function deleteTechVendorPossibleItems(
- ids: number[]
-): Promise<{ success: boolean; error?: string }> {
- try {
- await db
- .delete(techVendorPossibleItems)
- .where(inArray(techVendorPossibleItems.id, ids));
-
- return { success: true };
- } catch (error) {
- console.error("Failed to delete tech vendor possible items:", error);
- return {
- success: false,
- error: error instanceof Error ? error.message : "삭제 중 오류가 발생했습니다."
- };
- }
-}
-
-/**
- * 벤더 코드로 벤더 정보 조회
- */
-export async function getTechVendorByCode(vendorCode: string) {
- const result = await db
- .select()
- .from(techVendors)
- .where(eq(techVendors.vendorCode, vendorCode))
- .limit(1);
-
- return result[0] || null;
-}
-
-/**
- * 벤더 이메일로 벤더 정보 조회
- */
-export async function getTechVendorByEmail(vendorEmail: string) {
- const result = await db
- .select()
- .from(techVendors)
- .where(eq(techVendors.email, vendorEmail))
- .limit(1);
-
- return result[0] || null;
-}
-
-/**
- * 벤더 타입에 따라 적절한 아이템 테이블에서 아이템 조회
- */
-export async function getItemByCodeAndVendorType(itemCode: string, vendorType: string) {
- try {
- switch (vendorType) {
- case "조선":
- const shipItem = await db
- .select()
- .from(itemShipbuilding)
- .where(eq(itemShipbuilding.itemCode, itemCode))
- .limit(1);
- return shipItem[0] ? {
- itemCode: shipItem[0].itemCode,
- workType: shipItem[0].workType
- } : null;
-
- case "해양TOP":
- const topItem = await db
- .select()
- .from(itemOffshoreTop)
- .where(eq(itemOffshoreTop.itemCode, itemCode))
- .limit(1);
- return topItem[0] ? {
- itemCode: topItem[0].itemCode,
- workType: topItem[0].workType
- } : null;
-
- case "해양HULL":
- const hullItem = await db
- .select()
- .from(itemOffshoreHull)
- .where(eq(itemOffshoreHull.itemCode, itemCode))
- .limit(1);
- return hullItem[0] ? {
- itemCode: hullItem[0].itemCode,
- workType: hullItem[0].workType
- } : null;
-
- default:
- return null;
- }
- } catch (error) {
- console.error("Error fetching item by code and vendor type:", error);
- return null;
- }
-}
-
-/**
- * 아이템 코드로 아이템 정보 조회 (기존 함수 - 호환성 유지)
- */
-export async function getItemByCode(itemCode: string) {
- // 기존 items 테이블 대신 조선 테이블에서 먼저 조회 시도
- try {
- const shipItem = await db
- .select()
- .from(itemShipbuilding)
- .where(eq(itemShipbuilding.itemCode, itemCode))
- .limit(1);
-
- if (shipItem[0]) {
- return {
- itemCode: shipItem[0].itemCode,
- };
- }
-
- const topItem = await db
- .select()
- .from(itemOffshoreTop)
- .where(eq(itemOffshoreTop.itemCode, itemCode))
- .limit(1);
-
- if (topItem[0]) {
- return {
- itemCode: topItem[0].itemCode,
- };
- }
-
- const hullItem = await db
- .select()
- .from(itemOffshoreHull)
- .where(eq(itemOffshoreHull.itemCode, itemCode))
- .limit(1);
-
- if (hullItem[0]) {
- return {
- itemCode: hullItem[0].itemCode,
- };
- }
-
- return null;
- } catch (error) {
- console.error("Error fetching item by code:", error);
- return null;
- }
-}
-
-/**
- * Import 기능: 벤더이메일과 아이템정보를 통한 batch insert (새로운 스키마 버전)
- */
-export async function importTechVendorPossibleItems(
- data: ImportTechVendorPossibleItemData[]
-): Promise<ImportResult> {
- const result: ImportResult = {
- success: true,
- totalRows: data.length,
- successCount: 0,
- failedRows: [],
- };
-
- for (let i = 0; i < data.length; i++) {
- const row = data[i];
- const rowNumber = i + 1;
-
- try {
- // 벤더 이메일로 벤더 찾기 (필수)
- let vendor = null;
-
- if (row.vendorEmail && row.vendorEmail.trim()) {
- vendor = await getTechVendorByEmail(row.vendorEmail);
- } else {
- result.failedRows.push({
- row: rowNumber,
- error: "벤더 이메일은 필수입니다.",
- vendorCode: row.vendorCode,
- vendorEmail: row.vendorEmail,
- itemCode: row.itemCode,
- workType: row.workType,
- shipTypes: row.shipTypes,
- itemList: row.itemList,
- subItemList: row.subItemList,
- });
- continue;
- }
-
- if (!vendor) {
- result.failedRows.push({
- row: rowNumber,
- error: `벤더 이메일 '${row.vendorEmail}'을(를) 찾을 수 없습니다.`,
- vendorCode: row.vendorCode,
- vendorEmail: row.vendorEmail,
- itemCode: row.itemCode,
- workType: row.workType,
- shipTypes: row.shipTypes,
- itemList: row.itemList,
- subItemList: row.subItemList,
- });
- continue;
- }
-
- // 중복 체크 (벤더 + 아이템코드 + 공종 + 선종 조합)
- const existing = await db
- .select()
- .from(techVendorPossibleItems)
- .where(
- and(
- eq(techVendorPossibleItems.vendorId, vendor.id),
- eq(techVendorPossibleItems.itemCode, row.itemCode),
- row.workType
- ? eq(techVendorPossibleItems.workType, row.workType)
- : isNull(techVendorPossibleItems.workType),
- row.shipTypes
- ? eq(techVendorPossibleItems.shipTypes, row.shipTypes)
- : isNull(techVendorPossibleItems.shipTypes)
- )
- )
- .limit(1);
-
- if (existing.length > 0) {
- result.failedRows.push({
- row: rowNumber,
- error: `이미 존재하는 벤더-아이템 조합입니다.`,
- vendorCode: row.vendorCode,
- vendorEmail: row.vendorEmail,
- itemCode: row.itemCode,
- workType: row.workType,
- shipTypes: row.shipTypes,
- itemList: row.itemList,
- subItemList: row.subItemList,
- });
- continue;
- }
-
- // 새로운 아이템 생성
- await db.insert(techVendorPossibleItems).values({
- vendorId: vendor.id,
- vendorCode: vendor.vendorCode,
- vendorEmail: vendor.email,
- itemCode: row.itemCode,
- workType: row.workType || null,
- shipTypes: row.shipTypes || null,
- itemList: row.itemList || null,
- subItemList: row.subItemList || null,
- });
-
- result.successCount++;
- } catch (error) {
- result.failedRows.push({
- row: rowNumber,
- error: error instanceof Error ? error.message : "알 수 없는 오류",
- vendorCode: row.vendorCode,
- vendorEmail: row.vendorEmail,
- itemCode: row.itemCode,
- workType: row.workType,
- shipTypes: row.shipTypes,
- itemList: row.itemList,
- subItemList: row.subItemList,
- });
- }
- }
-
- if (result.failedRows.length > 0) {
- result.success = false;
- }
-
- return result;
-}
-
-/**
- * 모든 기술영업 벤더 조회 (드롭다운용)
- */
-export async function getAllTechVendors() {
- return await db
- .select({
- id: techVendors.id,
- vendorCode: techVendors.vendorCode,
- vendorName: techVendors.vendorName,
- techVendorType: techVendors.techVendorType,
- })
- .from(techVendors)
- .where(eq(techVendors.status, "ACTIVE"))
- .orderBy(asc(techVendors.vendorName));
-}
-
-/**
- * 고유한 벤더 타입 목록 조회 (필터용)
- */
-export async function getUniqueTechVendorTypes(): Promise<string[]> {
- try {
- const result = await db
- .select({
- techVendorType: techVendors.techVendorType,
- })
- .from(techVendors)
- .where(eq(techVendors.status, "ACTIVE"));
-
- // techVendorType이 JSON 배열 형태로 저장된 경우를 고려
- const allTypes = new Set<string>();
-
- result.forEach(row => {
- try {
- // techVendorType이 JSON 문자열인지 확인
- if (row.techVendorType && row.techVendorType.startsWith('[')) {
- const types = JSON.parse(row.techVendorType);
- if (Array.isArray(types)) {
- types.forEach(type => {
- if (type && typeof type === 'string') {
- allTypes.add(type.trim());
- }
- });
- }
- } else if (row.techVendorType) {
- // 단순 문자열인 경우
- row.techVendorType.split(',').forEach(type => {
- const trimmedType = type.trim();
- if (trimmedType) {
- allTypes.add(trimmedType);
- }
- });
- }
- } catch {
- // JSON 파싱 실패시 문자열로 처리
- if (row.techVendorType) {
- row.techVendorType.split(',').forEach(type => {
- const trimmedType = type.trim();
- if (trimmedType) {
- allTypes.add(trimmedType);
- }
- });
- }
- }
- });
-
- return Array.from(allTypes).sort();
- } catch (error) {
- console.error("Error fetching unique tech vendor types:", error);
- // 오류 발생시 기본 벤더 타입 반환
- return ["조선", "해양TOP", "해양HULL"];
- }
-}
-
-/**
- * 벤더 타입에 따른 아이템 목록 조회
- */
-export async function getItemsByVendorType(vendorTypes: string): Promise<{
- itemCode: string;
- itemList: string | null;
- workType: string | null;
- shipTypes?: string | null;
- subItemList?: string | null;
-}[]> {
- try {
- // 벤더 타입 파싱 개선
- let types: string[] = [];
- if (!vendorTypes) {
- return [];
- }
-
- if (vendorTypes.startsWith('[') && vendorTypes.endsWith(']')) {
- // JSON 배열 형태
- try {
- const parsed = JSON.parse(vendorTypes);
- types = Array.isArray(parsed) ? parsed.filter(Boolean) : [vendorTypes];
- } catch {
- types = [vendorTypes];
- }
- } else if (vendorTypes.includes(',')) {
- // 콤마로 구분된 문자열
- types = vendorTypes.split(',').map(t => t.trim()).filter(Boolean);
- } else {
- // 단일 문자열
- types = [vendorTypes.trim()].filter(Boolean);
- }
- // 벤더 타입 정렬 - 조선 > 해양TOP > 해양HULL 순
- const typeOrder = ["조선", "해양TOP", "해양HULL"];
- types.sort((a, b) => {
- const indexA = typeOrder.indexOf(a);
- const indexB = typeOrder.indexOf(b);
-
- // 정의된 순서에 있는 경우 우선순위 적용
- if (indexA !== -1 && indexB !== -1) {
- return indexA - indexB;
- }
- // 정의된 순서에 없는 경우 마지막에 배치하고 알파벳 순으로 정렬
- if (indexA !== -1) return -1;
- if (indexB !== -1) return 1;
- return a.localeCompare(b);
- });
-
- const allItems: any[] = [];
-
- // 각 벤더 타입에 따라 해당 아이템 테이블에서 조회
- for (const type of types) {
- switch (type) {
- case "조선":
- const shipItems = await db
- .select({
- itemCode: itemShipbuilding.itemCode,
- itemList: itemShipbuilding.itemList,
- workType: itemShipbuilding.workType,
- shipTypes: itemShipbuilding.shipTypes,
- })
- .from(itemShipbuilding);
- allItems.push(...shipItems);
- break;
-
- case "해양TOP":
- const topItems = await db
- .select({
- itemCode: itemOffshoreTop.itemCode,
- itemList: itemOffshoreTop.itemList,
- workType: itemOffshoreTop.workType,
- subItemList: itemOffshoreTop.subItemList,
- })
- .from(itemOffshoreTop);
- allItems.push(...topItems);
- break;
-
- case "해양HULL":
- const hullItems = await db
- .select({
- itemCode: itemOffshoreHull.itemCode,
- itemList: itemOffshoreHull.itemList,
- workType: itemOffshoreHull.workType,
- subItemList: itemOffshoreHull.subItemList,
- })
- .from(itemOffshoreHull);
- allItems.push(...hullItems);
- break;
- }
- }
- // // 중복 제거 (itemCode 기준)
- // const uniqueItems = allItems.filter((item, index, self) =>
- // index === self.findIndex(i => i.itemCode === item.itemCode)
- // );
-
- // const finalItems = uniqueItems.filter(item => item.itemCode); // itemCode가 있는 것만 반환
- // console.log("Final items after deduplication and filtering:", finalItems.length);
-
- return allItems;
- } catch (error) {
- console.error("Error fetching items by vendor type:", error);
- return [];
- }
-}
-
-/**
- * Excel Export 기능: 기술영업 벤더 가능 아이템 목록 내보내기
- */
-export async function exportTechVendorPossibleItemsToExcel(): Promise<{
- success: boolean;
- data?: Array<{
- 벤더코드: string | null;
- 벤더명: string;
- 벤더이메일: string | null;
- 벤더타입: string;
- 아이템코드: string;
- 공종: string | null;
- 선종: string | null;
- 아이템리스트: string | null;
- 서브아이템리스트: string | null;
- 생성일: string;
- }>;
- error?: string;
-}> {
- try {
- // 모든 데이터 조회 (페이지네이션 없이)
- const allData = await db
- .select({
- vendorCode: techVendorPossibleItems.vendorCode,
- vendorName: techVendors.vendorName,
- vendorEmail: techVendorPossibleItems.vendorEmail,
- techVendorType: techVendors.techVendorType,
- itemCode: techVendorPossibleItems.itemCode,
- workType: techVendorPossibleItems.workType,
- shipTypes: techVendorPossibleItems.shipTypes,
- itemList: techVendorPossibleItems.itemList,
- subItemList: techVendorPossibleItems.subItemList,
- createdAt: techVendorPossibleItems.createdAt,
- })
- .from(techVendorPossibleItems)
- .innerJoin(techVendors, eq(techVendorPossibleItems.vendorId, techVendors.id))
- .orderBy(desc(techVendorPossibleItems.createdAt));
-
- // Excel 형태로 변환
- const excelData = allData.map(item => ({
- 벤더코드: item.vendorCode,
- 벤더명: item.vendorName,
- 벤더이메일: item.vendorEmail,
- 벤더타입: item.techVendorType,
- 아이템코드: item.itemCode,
- 공종: item.workType,
- 선종: item.shipTypes,
- 아이템리스트: item.itemList,
- 서브아이템리스트: item.subItemList,
- 생성일: item.createdAt.toISOString().split('T')[0], // YYYY-MM-DD 형식
- }));
-
- return {
- success: true,
- data: excelData,
- };
- } catch (error) {
- console.error("Error exporting tech vendor possible items:", error);
- return {
- success: false,
- error: error instanceof Error ? error.message : "내보내기 중 오류가 발생했습니다.",
- };
- }
-}
+}
\ No newline at end of file |
