summaryrefslogtreecommitdiff
path: root/lib/tech-vendors/service.ts
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-06-15 04:41:55 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-06-15 04:41:55 +0000
commit7305a614ca20d50e6ab50bbcfbb128a6f1f90e53 (patch)
tree92317f538a4368fa54447ef25fc3ad3616f1b349 /lib/tech-vendors/service.ts
parentc5002d77087b256599b174ada611621657fcc523 (diff)
(최겸) 기술영업 벤더 개발
Diffstat (limited to 'lib/tech-vendors/service.ts')
-rw-r--r--lib/tech-vendors/service.ts441
1 files changed, 361 insertions, 80 deletions
diff --git a/lib/tech-vendors/service.ts b/lib/tech-vendors/service.ts
index 657314e6..7513a283 100644
--- a/lib/tech-vendors/service.ts
+++ b/lib/tech-vendors/service.ts
@@ -34,7 +34,7 @@ import type {
CreateTechVendorItemSchema,
} from "./validations";
-import { asc, desc, ilike, inArray, and, or, eq, isNull } from "drizzle-orm";
+import { asc, desc, ilike, inArray, and, or, eq, isNull, not } from "drizzle-orm";
import path from "path";
import fs from "fs/promises";
import { randomUUID } from "crypto";
@@ -144,9 +144,6 @@ export async function getTechVendorStatusCounts() {
"ACTIVE": 0,
"INACTIVE": 0,
"BLACKLISTED": 0,
- "PENDING_REVIEW": 0,
- "IN_REVIEW": 0,
- "REJECTED": 0
};
const result = await db.transaction(async (tx) => {
@@ -267,6 +264,11 @@ export async function createTechVendor(input: CreateTechVendorSchema) {
taxId: input.taxId,
address: input.address || null,
country: input.country,
+ countryEng: null,
+ countryFab: null,
+ agentName: null,
+ agentPhone: null,
+ agentEmail: null,
phone: input.phone || null,
email: input.email,
website: input.website || null,
@@ -275,9 +277,8 @@ export async function createTechVendor(input: CreateTechVendorSchema) {
representativeBirth: input.representativeBirth || null,
representativeEmail: input.representativeEmail || null,
representativePhone: input.representativePhone || null,
- corporateRegistrationNumber: input.corporateRegistrationNumber || null,
items: input.items || null,
- status: "PENDING_REVIEW"
+ status: "ACTIVE"
});
// 2. 연락처 정보 등록
@@ -547,8 +548,10 @@ export async function getTechVendorItems(input: GetTechVendorItemsSchema, id: nu
export interface ItemDropdownOption {
itemCode: string;
- itemName: string;
- description: string | null;
+ itemList: string;
+ workType: string | null;
+ shipTypes: string | null;
+ subItemList: string | null;
}
/**
@@ -559,31 +562,140 @@ export async function getItemsForTechVendor(vendorId: number) {
return unstable_cache(
async () => {
try {
- // 해당 vendorId가 이미 가지고 있는 itemCode 목록을 서브쿼리로 구함
- // 그 아이템코드를 제외(notIn)하여 모든 items 테이블에서 조회
- const itemsData = await db
+ // 1. 벤더 정보 조회로 벤더 타입 확인
+ const vendor = await db.query.techVendors.findFirst({
+ where: eq(techVendors.id, vendorId),
+ columns: {
+ techVendorType: true
+ }
+ });
+
+ if (!vendor) {
+ return {
+ data: [],
+ error: "벤더를 찾을 수 없습니다.",
+ };
+ }
+
+ // 2. 해당 벤더가 이미 가지고 있는 itemCode 목록 조회
+ const existingItems = await db
.select({
- itemCode: items.itemCode,
- itemName: items.itemName,
- description: items.description,
+ itemCode: techVendorPossibleItems.itemCode,
})
- .from(items)
- .leftJoin(
- techVendorPossibleItems,
- eq(items.itemCode, techVendorPossibleItems.itemCode)
- )
- // vendorPossibleItems.vendorId가 이 vendorId인 행이 없는(즉 아직 등록되지 않은) 아이템만
- .where(
- isNull(techVendorPossibleItems.id)
- )
- .orderBy(asc(items.itemName));
+ .from(techVendorPossibleItems)
+ .where(eq(techVendorPossibleItems.vendorId, vendorId));
+
+ const existingItemCodes = existingItems.map(item => item.itemCode);
+
+ // 3. 벤더 타입에 따라 해당 타입의 아이템만 조회
+ // let availableItems: ItemDropdownOption[] = [];
+ let availableItems: (typeof itemShipbuilding.$inferSelect | typeof itemOffshoreTop.$inferSelect | typeof itemOffshoreHull.$inferSelect)[] = [];
+ switch (vendor.techVendorType) {
+ case "조선":
+ 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)
+ .where(
+ existingItemCodes.length > 0
+ ? not(inArray(itemShipbuilding.itemCode, existingItemCodes))
+ : undefined
+ )
+ .orderBy(asc(itemShipbuilding.itemCode));
+
+ availableItems = shipbuildingItems
+ .filter(item => item.itemCode != null)
+ .map(item => ({
+ id: item.id,
+ createdAt: item.createdAt,
+ updatedAt: item.updatedAt,
+ itemCode: item.itemCode!,
+ itemList: item.itemList || "조선 아이템",
+ workType: item.workType || "조선 관련 아이템",
+ shipTypes: item.shipTypes || "조선 관련 아이템"
+ }));
+ break;
+
+ case "해양TOP":
+ 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)
+ .where(
+ existingItemCodes.length > 0
+ ? not(inArray(itemOffshoreTop.itemCode, existingItemCodes))
+ : undefined
+ )
+ .orderBy(asc(itemOffshoreTop.itemCode));
+
+ availableItems = offshoreTopItems
+ .filter(item => item.itemCode != null)
+ .map(item => ({
+ id: item.id,
+ createdAt: item.createdAt,
+ updatedAt: item.updatedAt,
+ itemCode: item.itemCode!,
+ itemList: item.itemList || "해양TOP 아이템",
+ workType: item.workType || "해양TOP 관련 아이템",
+ subItemList: item.subItemList || "해양TOP 관련 아이템"
+ }));
+ break;
+
+ case "해양HULL":
+ 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)
+ .where(
+ existingItemCodes.length > 0
+ ? not(inArray(itemOffshoreHull.itemCode, existingItemCodes))
+ : undefined
+ )
+ .orderBy(asc(itemOffshoreHull.itemCode));
+
+ availableItems = offshoreHullItems
+ .filter(item => item.itemCode != null)
+ .map(item => ({
+ id: item.id,
+ createdAt: item.createdAt,
+ updatedAt: item.updatedAt,
+ itemCode: item.itemCode!,
+ itemList: item.itemList || "해양HULL 아이템",
+ workType: item.workType || "해양HULL 관련 아이템",
+ subItemList: item.subItemList || "해양HULL 관련 아이템"
+ }));
+ break;
+
+ default:
+ return {
+ data: [],
+ error: `지원하지 않는 벤더 타입입니다: ${vendor.techVendorType}`,
+ };
+ }
return {
- data: itemsData.map((item) => ({
- itemCode: item.itemCode ?? "", // null이라면 ""로 치환
- itemName: item.itemName,
- description: item.description ?? "" // null이라면 ""로 치환
- })),
+ data: availableItems,
error: null
};
} catch (err) {
@@ -827,7 +939,7 @@ export async function rejectTechVendors(input: ApproveTechVendorsInput) {
const [updated] = await tx
.update(techVendors)
.set({
- status: "REJECTED",
+ status: "INACTIVE",
updatedAt: new Date()
})
.where(inArray(techVendors.id, input.ids.map(id => parseInt(id))))
@@ -918,7 +1030,6 @@ export async function exportTechVendorDetails(vendorIds: number[]) {
representativeEmail: techVendors.representativeEmail,
representativePhone: techVendors.representativePhone,
representativeBirth: techVendors.representativeBirth,
- corporateRegistrationNumber: techVendors.corporateRegistrationNumber,
items: techVendors.items,
createdAt: techVendors.createdAt,
updatedAt: techVendors.updatedAt,
@@ -1090,75 +1201,124 @@ export const findVendorById = async (id: number): Promise<TechVendor | null> =>
export async function importTechVendorsFromExcel(
vendors: Array<{
vendorName: string;
+ vendorCode?: string | null;
email: string;
taxId: string;
- address?: string;
- country?: string;
- phone?: string;
- website?: string;
+ country?: string | null;
+ countryEng?: string | null;
+ countryFab?: string | null;
+ agentName?: string | null;
+ agentPhone?: string | null;
+ agentEmail?: string | null;
+ address?: string | null;
+ phone?: string | null;
+ website?: string | null;
techVendorType: string;
- items: string; // 쉼표로 구분된 아이템 코드들
+ representativeName?: string | null;
+ representativeEmail?: string | null;
+ representativePhone?: string | null;
+ representativeBirth?: string | null;
+ items: string;
}>,
) {
unstable_noStore();
try {
+ console.log("Import 시작 - 벤더 수:", vendors.length);
+ console.log("첫 번째 벤더 데이터:", vendors[0]);
+
const result = await db.transaction(async (tx) => {
const createdVendors = [];
for (const vendor of vendors) {
- // 1. 벤더 생성
- const [newVendor] = await tx.insert(techVendors).values({
- vendorName: vendor.vendorName,
- vendorCode: null, // 자동 생성
- taxId: vendor.taxId,
- address: vendor.address || null,
- country: vendor.country || null,
- phone: vendor.phone || null,
- email: vendor.email,
- website: vendor.website || null,
- techVendorType: vendor.techVendorType as "조선" | "해양TOP" | "해양HULL",
- status: "PENDING_REVIEW"
- }).returning();
-
- // 2. 유저 생성 (이메일이 있는 경우)
- if (vendor.email) {
- // 이미 존재하는 유저인지 확인
- const existingUser = await tx.query.users.findFirst({
- where: eq(users.email, vendor.email),
- columns: { id: true }
+ console.log("벤더 처리 시작:", vendor.vendorName);
+
+ try {
+ // 1. 벤더 생성
+ console.log("벤더 생성 시도:", {
+ vendorName: vendor.vendorName,
+ email: vendor.email,
+ techVendorType: vendor.techVendorType
});
- // 유저가 존재하지 않는 경우에만 생성
- if (!existingUser) {
- await tx.insert(users).values({
- name: vendor.vendorName,
- email: vendor.email,
- companyId: newVendor.id,
- domain: "partners",
+ const [newVendor] = await tx.insert(techVendors).values({
+ vendorName: vendor.vendorName,
+ vendorCode: vendor.vendorCode || null,
+ taxId: vendor.taxId,
+ country: vendor.country || null,
+ countryEng: vendor.countryEng || null,
+ countryFab: vendor.countryFab || null,
+ agentName: vendor.agentName || null,
+ agentPhone: vendor.agentPhone || null,
+ agentEmail: vendor.agentEmail || null,
+ address: vendor.address || null,
+ phone: vendor.phone || null,
+ email: vendor.email,
+ website: vendor.website || null,
+ techVendorType: vendor.techVendorType as "조선" | "해양TOP" | "해양HULL",
+ status: "ACTIVE",
+ representativeName: vendor.representativeName || null,
+ representativeEmail: vendor.representativeEmail || null,
+ representativePhone: vendor.representativePhone || null,
+ representativeBirth: vendor.representativeBirth || null,
+ }).returning();
+
+ console.log("벤더 생성 성공:", newVendor.id);
+
+ // 2. 유저 생성 (이메일이 있는 경우)
+ if (vendor.email) {
+ console.log("유저 생성 시도:", vendor.email);
+
+ // 이미 존재하는 유저인지 확인
+ const existingUser = await tx.query.users.findFirst({
+ where: eq(users.email, vendor.email),
+ columns: { id: true }
});
- }
- }
- // 3. 아이템 등록
- if (vendor.items) {
- const itemCodes = vendor.items.split(',').map(code => code.trim());
- for (const itemCode of itemCodes) {
- // 아이템 정보 조회
- const [item] = await tx.select().from(items).where(eq(items.itemCode, itemCode));
- if (item && item.itemCode && item.itemName) {
- await tx.insert(techVendorPossibleItems).values({
- vendorId: newVendor.id,
- itemCode: item.itemCode,
- itemName: item.itemName,
+ // 유저가 존재하지 않는 경우에만 생성
+ if (!existingUser) {
+ await tx.insert(users).values({
+ name: vendor.vendorName,
+ email: vendor.email,
+ techCompanyId: newVendor.id, // techCompanyId 설정
+ domain: "partners",
});
+ console.log("유저 생성 성공");
+ } else {
+ console.log("이미 존재하는 유저:", existingUser.id);
}
}
- }
- createdVendors.push(newVendor);
+ // // 3. 아이템 등록
+ // if (vendor.items) {
+ // console.log("아이템 등록 시도:", vendor.items);
+ // const itemCodes = vendor.items.split(',').map(code => code.trim());
+
+ // for (const itemCode of itemCodes) {
+ // // 아이템 정보 조회
+ // const [item] = await tx.select().from(items).where(eq(items.itemCode, itemCode));
+ // if (item && item.itemCode && item.itemName) {
+ // await tx.insert(techVendorPossibleItems).values({
+ // vendorId: newVendor.id,
+ // itemCode: item.itemCode,
+ // itemName: item.itemName,
+ // });
+ // console.log("아이템 등록 성공:", itemCode);
+ // } else {
+ // console.log("아이템을 찾을 수 없음:", itemCode);
+ // }
+ // }
+ // }
+
+ createdVendors.push(newVendor);
+ console.log("벤더 처리 완료:", vendor.vendorName);
+ } catch (error) {
+ console.error("벤더 처리 중 오류 발생:", vendor.vendorName, error);
+ throw error;
+ }
}
+ console.log("모든 벤더 처리 완료. 생성된 벤더 수:", createdVendors.length);
return createdVendors;
});
@@ -1166,9 +1326,130 @@ export async function importTechVendorsFromExcel(
revalidateTag("tech-vendors");
revalidateTag("users");
+ console.log("Import 완료 - 결과:", result);
+ return { success: true, data: result };
+ } catch (error) {
+ console.error("Import 실패:", error);
+ return { success: false, error: getErrorMessage(error) };
+ }
+}
+
+export async function findTechVendorById(id: number): Promise<TechVendor | null> {
+ const result = await db
+ .select()
+ .from(techVendors)
+ .where(eq(techVendors.id, id))
+ .limit(1)
+
+ return result[0] || null
+}
+
+/**
+ * 단일 기술영업 벤더 추가 (사용자 계정도 함께 생성)
+ */
+export async function addTechVendor(input: {
+ vendorName: string;
+ vendorCode?: string | null;
+ email: string;
+ taxId: string;
+ country?: string | null;
+ countryEng?: string | null;
+ countryFab?: string | null;
+ agentName?: string | null;
+ agentPhone?: string | null;
+ agentEmail?: string | null;
+ address?: string | null;
+ phone?: string | null;
+ website?: string | null;
+ techVendorType: "조선" | "해양TOP" | "해양HULL";
+ representativeName?: string | null;
+ representativeEmail?: string | null;
+ representativePhone?: string | null;
+ representativeBirth?: string | null;
+}) {
+ unstable_noStore();
+
+ try {
+ console.log("벤더 추가 시작:", input.vendorName);
+
+ const result = await db.transaction(async (tx) => {
+ // 1. 이메일 중복 체크
+ const existingVendor = await tx.query.techVendors.findFirst({
+ where: eq(techVendors.email, input.email),
+ columns: { id: true, vendorName: true }
+ });
+
+ if (existingVendor) {
+ throw new Error(`이미 등록된 이메일입니다: ${input.email} (업체명: ${existingVendor.vendorName})`);
+ }
+
+ // 2. 벤더 생성
+ console.log("벤더 생성 시도:", {
+ vendorName: input.vendorName,
+ email: input.email,
+ techVendorType: input.techVendorType
+ });
+
+ const [newVendor] = await tx.insert(techVendors).values({
+ vendorName: input.vendorName,
+ vendorCode: input.vendorCode || null,
+ taxId: input.taxId,
+ country: input.country || null,
+ countryEng: input.countryEng || null,
+ countryFab: input.countryFab || null,
+ agentName: input.agentName || null,
+ agentPhone: input.agentPhone || null,
+ agentEmail: input.agentEmail || null,
+ address: input.address || null,
+ phone: input.phone || null,
+ email: input.email,
+ website: input.website || null,
+ techVendorType: input.techVendorType,
+ status: "ACTIVE",
+ representativeName: input.representativeName || null,
+ representativeEmail: input.representativeEmail || null,
+ representativePhone: input.representativePhone || null,
+ representativeBirth: input.representativeBirth || null,
+ }).returning();
+
+ console.log("벤더 생성 성공:", newVendor.id);
+
+ // 3. 유저 생성 (techCompanyId 설정)
+ console.log("유저 생성 시도:", input.email);
+
+ // 이미 존재하는 유저인지 확인
+ const existingUser = await tx.query.users.findFirst({
+ where: eq(users.email, input.email),
+ columns: { id: true }
+ });
+
+ let userId = null;
+ // 유저가 존재하지 않는 경우에만 생성
+ if (!existingUser) {
+ const [newUser] = await tx.insert(users).values({
+ name: input.vendorName,
+ email: input.email,
+ techCompanyId: newVendor.id, // techCompanyId 설정
+ domain: "partners",
+ }).returning();
+ userId = newUser.id;
+ console.log("유저 생성 성공:", userId);
+ } else {
+ console.log("이미 존재하는 유저:", existingUser.id);
+ }
+
+ return { vendor: newVendor, userId };
+ });
+
+ // 캐시 무효화
+ revalidateTag("tech-vendors");
+ revalidateTag("users");
+
+ console.log("벤더 추가 완료:", result);
return { success: true, data: result };
} catch (error) {
- console.error("Failed to import tech vendors:", error);
+ console.error("벤더 추가 실패:", error);
return { success: false, error: getErrorMessage(error) };
}
-} \ No newline at end of file
+}
+