diff options
Diffstat (limited to 'lib/tech-vendors/service.ts')
| -rw-r--r-- | lib/tech-vendors/service.ts | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/lib/tech-vendors/service.ts b/lib/tech-vendors/service.ts index 940e59ce..5290b6a0 100644 --- a/lib/tech-vendors/service.ts +++ b/lib/tech-vendors/service.ts @@ -703,6 +703,267 @@ export interface ItemDropdownOption { subItemList: string | null; } +export interface ItemForVendorMapping { + id: number; + itemCode: string | null; + itemList: string | null; + workType: string | null; + shipTypes?: string | null; + subItemList?: string | null; + itemType: "SHIP" | "TOP" | "HULL"; + createdAt: Date; + updatedAt: Date; +} + +export interface VendorForItemMapping { + id: number; + vendorName: string; + email: string | null; + techVendorType: string; + status: string; +} + +const itemTypeToVendorType: Record<"SHIP" | "TOP" | "HULL", string> = { + SHIP: "조선", + TOP: "해양TOP", + HULL: "해양HULL", +}; + +function parseVendorTypes(value: string | string[] | null) { + if (!value) return [] as string[]; + if (Array.isArray(value)) { + return value + .map((type) => type.trim()) + .filter((type) => type.length > 0); + } + return value + .split(",") + .map((type) => type.trim()) + .filter((type) => type.length > 0); +} + +/** + * 아이템 기준으로 벤더 매핑 시 사용할 전체 아이템 목록 조회 + * 벤더에 관계없이 전 타입을 모두 가져온다. + */ +export async function getItemsForVendorMapping() { + return unstable_cache( + async () => { + try { + const items: ItemForVendorMapping[] = []; + + const shipbuildingItems = await db + .select({ + id: itemShipbuilding.id, + createdAt: itemShipbuilding.createdAt, + updatedAt: itemShipbuilding.updatedAt, + itemCode: itemShipbuilding.itemCode, + itemList: itemShipbuilding.itemList, + workType: itemShipbuilding.workType, + shipTypes: itemShipbuilding.shipTypes, + }) + .from(itemShipbuilding) + .orderBy(asc(itemShipbuilding.itemCode)); + + items.push( + ...shipbuildingItems + .filter((item) => item.itemCode != null) + .map((item) => ({ + ...item, + itemType: "SHIP" as const, + })) + ); + + const offshoreTopItems = await db + .select({ + id: itemOffshoreTop.id, + createdAt: itemOffshoreTop.createdAt, + updatedAt: itemOffshoreTop.updatedAt, + itemCode: itemOffshoreTop.itemCode, + itemList: itemOffshoreTop.itemList, + workType: itemOffshoreTop.workType, + subItemList: itemOffshoreTop.subItemList, + }) + .from(itemOffshoreTop) + .orderBy(asc(itemOffshoreTop.itemCode)); + + items.push( + ...offshoreTopItems + .filter((item) => item.itemCode != null) + .map((item) => ({ + ...item, + itemType: "TOP" as const, + })) + ); + + const offshoreHullItems = await db + .select({ + id: itemOffshoreHull.id, + createdAt: itemOffshoreHull.createdAt, + updatedAt: itemOffshoreHull.updatedAt, + itemCode: itemOffshoreHull.itemCode, + itemList: itemOffshoreHull.itemList, + workType: itemOffshoreHull.workType, + subItemList: itemOffshoreHull.subItemList, + }) + .from(itemOffshoreHull) + .orderBy(asc(itemOffshoreHull.itemCode)); + + items.push( + ...offshoreHullItems + .filter((item) => item.itemCode != null) + .map((item) => ({ + ...item, + itemType: "HULL" as const, + })) + ); + + return { data: items, error: null }; + } catch (err) { + console.error("Failed to fetch items for vendor mapping:", err); + return { + data: [], + error: "아이템 목록을 불러오는데 실패했습니다.", + }; + } + }, + ["items-for-vendor-mapping"], + { + revalidate: 3600, + tags: ["items"], + } + )(); +} + +/** + * 특정 아이템에 연결 가능한 벤더 목록을 조회 + * - 이미 연결된 벤더는 제외 + * - 아이템 타입과 벤더 타입(조선/해양TOP/해양HULL) 매칭 + */ +export async function getConnectableVendorsForItem( + itemId: number, + itemType: "SHIP" | "TOP" | "HULL" +) { + unstable_noStore(); + + try { + // 1) 이미 연결된 벤더 ID 조회 + const existingVendors = await db + .select({ vendorId: techVendorPossibleItems.vendorId }) + .from(techVendorPossibleItems) + .where( + itemType === "SHIP" + ? eq(techVendorPossibleItems.shipbuildingItemId, itemId) + : itemType === "TOP" + ? eq(techVendorPossibleItems.offshoreTopItemId, itemId) + : eq(techVendorPossibleItems.offshoreHullItemId, itemId) + ); + + const existingVendorIds = existingVendors.map((row) => row.vendorId); + + // 2) 모든 벤더 조회 후 타입 매칭 + 중복 제외 + const vendorRows = await db + .select({ + id: techVendors.id, + vendorName: techVendors.vendorName, + email: techVendors.email, + techVendorType: techVendors.techVendorType, + status: techVendors.status, + }) + .from(techVendors); + + const targetType = itemTypeToVendorType[itemType]; + + const availableVendors: VendorForItemMapping[] = vendorRows + .map((vendor) => ({ + ...vendor, + vendorTypes: parseVendorTypes(vendor.techVendorType), + })) + .filter( + (vendor) => + vendor.vendorTypes.includes(targetType) && + !existingVendorIds.includes(vendor.id) + ) + .map(({ vendorTypes, ...rest }) => rest); + + return { data: availableVendors, error: null }; + } catch (err) { + console.error("Failed to fetch connectable vendors:", err); + return { data: [], error: "연결 가능한 벤더 조회에 실패했습니다." }; + } +} + +/** + * 선택한 아이템을 여러 벤더와 연결 + * - 중복 연결은 건너뜀 + */ +export async function connectItemWithVendors(input: { + itemId: number; + itemType: "SHIP" | "TOP" | "HULL"; + vendorIds: number[]; +}) { + unstable_noStore(); + + if (!input.vendorIds || input.vendorIds.length === 0) { + return { success: false, error: "연결할 벤더를 선택해주세요." }; + } + + try { + let successCount = 0; + const skipped: number[] = []; + + await db.transaction(async (tx) => { + for (const vendorId of input.vendorIds) { + const whereConditions = [eq(techVendorPossibleItems.vendorId, vendorId)]; + + if (input.itemType === "SHIP") { + whereConditions.push(eq(techVendorPossibleItems.shipbuildingItemId, input.itemId)); + } else if (input.itemType === "TOP") { + whereConditions.push(eq(techVendorPossibleItems.offshoreTopItemId, input.itemId)); + } else { + whereConditions.push(eq(techVendorPossibleItems.offshoreHullItemId, input.itemId)); + } + + const existing = await tx.query.techVendorPossibleItems.findFirst({ + where: and(...whereConditions), + }); + + if (existing) { + skipped.push(vendorId); + continue; + } + + const insertData: { + vendorId: number; + shipbuildingItemId?: number; + offshoreTopItemId?: number; + offshoreHullItemId?: number; + } = { vendorId }; + + if (input.itemType === "SHIP") { + insertData.shipbuildingItemId = input.itemId; + } else if (input.itemType === "TOP") { + insertData.offshoreTopItemId = input.itemId; + } else { + insertData.offshoreHullItemId = input.itemId; + } + + await tx.insert(techVendorPossibleItems).values(insertData); + successCount += 1; + } + }); + + input.vendorIds.forEach((vendorId) => { + revalidateTag(`tech-vendor-possible-items-${vendorId}`); + }); + + return { success: true, successCount, skipped }; + } catch (err) { + console.error("Failed to connect item with vendors:", err); + return { success: false, error: getErrorMessage(err) }; + } +} + /** * Vendor Item 추가 시 사용할 아이템 목록 조회 (전체 목록 반환) * 아이템 코드, 이름, 설명만 간소화해서 반환 |
