From 06d4753d61a803e2f8447bc3167dced3434107d4 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 22 Sep 2025 08:54:41 +0000 Subject: (최겸) 구매 협력업체 관리 피드백 반영(PQ, 실사, QM 담당자 등) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vendor-table/basic-contract-sign-dialog.tsx | 10 ++- lib/pq/pq-criteria/pq-table-column.tsx | 40 +++++------ .../pq-review-table-new/vendors-table-columns.tsx | 10 +++ lib/pq/service.ts | 81 +++++++++++++++++----- lib/vendors/table/request-pq-dialog.tsx | 18 +++-- 5 files changed, 114 insertions(+), 45 deletions(-) (limited to 'lib') diff --git a/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx b/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx index 534a2705..635993fb 100644 --- a/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx +++ b/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx @@ -35,7 +35,7 @@ import { getVendorAttachments, processBuyerSignatureAction } from "../service"; // 계약서 상태 타입 정의 interface ContractStatus { id: number; - status: 'pending' | 'completed' | 'error'; + status: 'pending' | 'completed' | 'error' | 'vendor_signed'; errorMessage?: string; } @@ -151,7 +151,9 @@ const canCompleteCurrentContract = React.useMemo(() => { }, [contracts, contractStatuses.length]); // 완료된 계약서 수 계산 - const completedCount = contractStatuses.filter(status => status.status === 'completed').length; + const completedCount = contractStatuses.filter(status => + status.status === 'completed' || status.status === 'vendor_signed' + ).length; const totalCount = contracts.length; const allCompleted = completedCount === totalCount && totalCount > 0; @@ -162,7 +164,9 @@ const canCompleteCurrentContract = React.useMemo(() => { // 다음 미완료 계약서 찾기 const getNextPendingContract = () => { - const pendingStatuses = contractStatuses.filter(status => status.status === 'pending'); + const pendingStatuses = contractStatuses.filter(status => + status.status === 'pending' || status.status === 'error' + ); if (pendingStatuses.length === 0) return null; const nextPendingId = pendingStatuses[0].id; diff --git a/lib/pq/pq-criteria/pq-table-column.tsx b/lib/pq/pq-criteria/pq-table-column.tsx index 924d80c4..de7396bf 100644 --- a/lib/pq/pq-criteria/pq-table-column.tsx +++ b/lib/pq/pq-criteria/pq-table-column.tsx @@ -125,26 +125,26 @@ export function getColumns({ minSize: 180, size: 180, }, - // { - // accessorKey: "remarks", - // header: ({ column }) => ( - // - // ), - // cell: ({ row }) => { - // const text = row.getValue("remarks") as string - // return ( - //
- // {text || "-"} - //
- // ) - // }, - // meta: { - // excelHeader: "Remarks" - // }, - // enableResizing: true, - // minSize: 180, - // size: 180, - // }, + { + accessorKey: "remarks", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const text = row.getValue("remarks") as string + return ( +
+ {text || "-"} +
+ ) + }, + meta: { + excelHeader: "Remarks" + }, + enableResizing: true, + minSize: 180, + size: 180, + }, { accessorKey: "inputFormat", header: ({ column }) => ( diff --git a/lib/pq/pq-review-table-new/vendors-table-columns.tsx b/lib/pq/pq-review-table-new/vendors-table-columns.tsx index 449b69be..e3687f52 100644 --- a/lib/pq/pq-review-table-new/vendors-table-columns.tsx +++ b/lib/pq/pq-review-table-new/vendors-table-columns.tsx @@ -175,6 +175,8 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef {row.original.vendorCode ? row.original.vendorCode : "-"}/{row.original.taxId} ), + enableSorting: true, + enableHiding: true, } // PQ 유형 컬럼 @@ -200,6 +202,8 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef filterFn: (row, id, value) => { return value.includes(row.getValue(id)); }, + enableSorting: true, + enableHiding: true, } // 프로젝트 컬럼 @@ -225,6 +229,8 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef ) }, + enableSorting: true, + enableHiding: true, } // 상태 컬럼 @@ -241,6 +247,8 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef const combinedStatus = getCombinedStatus(row.original); return value.includes(combinedStatus.status); }, + enableSorting: true, + enableHiding: true, }; // PQ 상태와 실사 상태를 결합하는 헬퍼 함수 @@ -362,6 +370,8 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef if (!investigation || !investigation.evaluationResult) return value.includes("null"); return value.includes(investigation.evaluationResult); }, + enableSorting: true, + enableHiding: true, }; // 답변 수 컬럼 diff --git a/lib/pq/service.ts b/lib/pq/service.ts index 5870a77f..172542a3 100644 --- a/lib/pq/service.ts +++ b/lib/pq/service.ts @@ -18,7 +18,7 @@ import { vendorRegularRegistrations } from "@/db/schema/vendorRegistrations"; import { saveFile, saveDRMFile } from "@/lib/file-stroage"; import { GetVendorsSchema } from "../vendors/validations"; import { selectVendors } from "../vendors/repository"; -import { projects, users } from "@/db/schema"; +import { projects, users, roles, userRoles } from "@/db/schema"; import { headers } from 'next/headers'; import { getServerSession } from "next-auth/next" import { authOptions } from "@/app/api/auth/[...nextauth]/route" @@ -1763,8 +1763,20 @@ export async function getPQSubmissions(input: GetPQSubmissionsSchema) { // 7) 정렬 및 페이징 처리된 데이터 조회 const orderByColumns = input.sort.map((sort) => { - const column = sort.id as keyof typeof vendorPQSubmissions.$inferSelect; - return sort.desc ? desc(vendorPQSubmissions[column]) : asc(vendorPQSubmissions[column]); + const column = sort.id; + + // JOIN된 테이블의 컬럼인 경우 적절한 테이블 참조 + if (column === 'vendorName') { + return sort.desc ? desc(vendors.vendorName) : asc(vendors.vendorName); + } else if (column === 'projectName') { + return sort.desc ? desc(projects.name) : asc(projects.name); + } else if (column === 'requesterName') { + return sort.desc ? desc(users.name) : asc(users.name); + } else { + // vendorPQSubmissions 테이블의 컬럼인 경우 + const dbColumn = column as keyof typeof vendorPQSubmissions.$inferSelect; + return sort.desc ? desc(vendorPQSubmissions[dbColumn]) : asc(vendorPQSubmissions[dbColumn]); + } }); if (orderByColumns.length === 0) { @@ -2818,40 +2830,75 @@ function getInvestigationMethodLabel(method: string): string { } } +// export async function getQMManagers() { +// try { +// // QM 부서 사용자만 필터링 (department 필드가 있다고 가정) +// // 또는 QM 역할을 가진 사용자만 필터링 (role 필드가 있다고 가정) +// const qmUsers = await db +// .select({ +// id: users.id, +// name: users.name, +// email: users.email, +// }) +// .from(users) +// // .where( +// // // 필요에 따라 조건 조정 (예: QM 부서 또는 특정 역할만) +// // // eq(users.department, "QM") 또는 +// // // eq(users.role, "QM_MANAGER") +// // // 테스트를 위해 모든 사용자 반환도 가능 +// // eq(users.active, true) +// // ) +// .orderBy(users.name) + +// return { +// data: qmUsers, +// success: true +// } +// } catch (error) { +// console.error("QM 담당자 목록 조회 오류:", error) +// return { +// data: [], +// success: false, +// error: error instanceof Error ? error.message : "QM 담당자 목록을 가져오는 중 오류가 발생했습니다." +// } +// } +// } export async function getQMManagers() { try { - // QM 부서 사용자만 필터링 (department 필드가 있다고 가정) - // 또는 QM 역할을 가진 사용자만 필터링 (role 필드가 있다고 가정) + // QM 역할이 할당된 사용자들을 조회 const qmUsers = await db .select({ id: users.id, name: users.name, email: users.email, + employeeNumber: users.employeeNumber, + deptName: users.deptName, + isActive: users.isActive, }) .from(users) - // .where( - // // 필요에 따라 조건 조정 (예: QM 부서 또는 특정 역할만) - // // eq(users.department, "QM") 또는 - // // eq(users.role, "QM_MANAGER") - // // 테스트를 위해 모든 사용자 반환도 가능 - // eq(users.active, true) - // ) + .innerJoin(userRoles, eq(users.id, userRoles.userId)) + .innerJoin(roles, eq(userRoles.roleId, roles.id)) + .where( + and( + ilike(roles.name, "%QM%"), // "QM"이 포함된 역할명 + eq(users.isActive, true) // 활성 사용자만 + ) + ) .orderBy(users.name) - return { + return { data: qmUsers, - success: true + success: true } } catch (error) { console.error("QM 담당자 목록 조회 오류:", error) - return { + return { data: [], success: false, - error: error instanceof Error ? error.message : "QM 담당자 목록을 가져오는 중 오류가 발생했습니다." + error: error instanceof Error ? error.message : "QM 담당자 목록을 가져오는 중 오류가 발생했습니다." } } } - export async function getFactoryLocationAnswer(vendorId: number, projectId: number | null = null) { try { // 1. "Location of Factory" 체크포인트를 가진 criteria 찾기 diff --git a/lib/vendors/table/request-pq-dialog.tsx b/lib/vendors/table/request-pq-dialog.tsx index aeb0c717..206846df 100644 --- a/lib/vendors/table/request-pq-dialog.tsx +++ b/lib/vendors/table/request-pq-dialog.tsx @@ -154,10 +154,14 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro if (isAllForeign) { // 외자: 준법서약 (영문), GTC 선택 (GTC는 1개만 선택하도록) - const foreignTemplates = templates.filter(template => - template.templateName?.includes('준법서약') && template.templateName?.includes('영문') || - template.templateName?.includes('gtc') - ) + // 비밀유지 계약서, 기술자료 요구서 제외 + const foreignTemplates = templates.filter(template => { + const name = template.templateName?.toLowerCase() || '' + return ( + (template.templateName?.includes('준법서약') && template.templateName?.includes('영문')) || + template.templateName?.includes('gtc') + ) && !name.includes('비밀유지') && !name.includes('기술자료') + }) // GTC 템플릿 중 최신 리비전의 것만 선택 const gtcTemplates = foreignTemplates.filter(t => t.templateName?.includes('gtc')) const nonGtcTemplates = foreignTemplates.filter(t => !t.templateName?.includes('gtc')) @@ -173,9 +177,13 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro } } else if (isAllDomestic) { // 내자: 준법서약 (영문), GTC 제외한 모든 템플릿 선택 + // 비밀유지 계약서, 기술자료 요구서 제외 const domesticTemplates = templates.filter(template => { const name = template.templateName?.toLowerCase() || '' - return !(name.includes('준법서약') && name.includes('영문')) && !name.includes('gtc') + return !(name.includes('준법서약') && name.includes('영문')) && + !name.includes('gtc') && + !name.includes('비밀유지') && + !name.includes('기술자료') }) setSelectedTemplateIds(domesticTemplates.map(t => t.id)) } -- cgit v1.2.3