summaryrefslogtreecommitdiff
path: root/lib/tech-vendors/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tech-vendors/service.ts')
-rw-r--r--lib/tech-vendors/service.ts261
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 추가 시 사용할 아이템 목록 조회 (전체 목록 반환)
* 아이템 코드, 이름, 설명만 간소화해서 반환