diff options
Diffstat (limited to 'lib/pq')
| -rw-r--r-- | lib/pq/pq-review-table-new/cancel-investigation-dialog.tsx | 29 | ||||
| -rw-r--r-- | lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx | 317 |
2 files changed, 305 insertions, 41 deletions
diff --git a/lib/pq/pq-review-table-new/cancel-investigation-dialog.tsx b/lib/pq/pq-review-table-new/cancel-investigation-dialog.tsx index e135d8b8..e22b9ca1 100644 --- a/lib/pq/pq-review-table-new/cancel-investigation-dialog.tsx +++ b/lib/pq/pq-review-table-new/cancel-investigation-dialog.tsx @@ -11,6 +11,8 @@ import { DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
+import { Textarea } from "@/components/ui/textarea"
+import { Label } from "@/components/ui/label"
interface CancelInvestigationDialogProps {
isOpen: boolean
@@ -71,7 +73,7 @@ export function CancelInvestigationDialog({ interface ReRequestInvestigationDialogProps {
isOpen: boolean
onClose: () => void
- onConfirm: () => Promise<void>
+ onConfirm: (reason?: string) => Promise<void>
selectedCount: number
}
@@ -82,16 +84,24 @@ export function ReRequestInvestigationDialog({ selectedCount,
}: ReRequestInvestigationDialogProps) {
const [isPending, setIsPending] = React.useState(false)
+ const [reason, setReason] = React.useState("")
async function handleConfirm() {
setIsPending(true)
try {
- await onConfirm()
+ await onConfirm(reason || undefined)
} finally {
setIsPending(false)
}
}
+ // Dialog가 닫힐 때 상태 초기화
+ React.useEffect(() => {
+ if (!isOpen) {
+ setReason("")
+ }
+ }, [isOpen])
+
return (
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent>
@@ -102,6 +112,21 @@ export function ReRequestInvestigationDialog({ 취소 상태인 실사만 재의뢰할 수 있습니다.
</DialogDescription>
</DialogHeader>
+
+ <div className="space-y-4 py-4">
+ <div className="space-y-2">
+ <Label htmlFor="reason">재의뢰 사유 (선택)</Label>
+ <Textarea
+ id="reason"
+ placeholder="재의뢰 사유를 입력해주세요..."
+ value={reason}
+ onChange={(e) => setReason(e.target.value)}
+ rows={4}
+ disabled={isPending}
+ />
+ </div>
+ </div>
+
<DialogFooter>
<Button
type="button"
diff --git a/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx b/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx index 95cdd4d1..a5185cab 100644 --- a/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx +++ b/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx @@ -4,20 +4,27 @@ import * as React from "react" import { type Table } from "@tanstack/react-table"
import { Download, ClipboardCheck, X, Send, RefreshCw } from "lucide-react"
import { toast } from "sonner"
+import { useSession } from "next-auth/react"
import { exportTableToExcel } from "@/lib/export"
import { Button } from "@/components/ui/button"
import { PQSubmission } from "./vendors-table-columns"
import {
- requestInvestigationAction,
cancelInvestigationAction,
sendInvestigationResultsAction,
getFactoryLocationAnswer,
- reRequestInvestigationAction
+ getQMManagers
} from "@/lib/pq/service"
import { RequestInvestigationDialog } from "./request-investigation-dialog"
import { CancelInvestigationDialog, ReRequestInvestigationDialog } from "./cancel-investigation-dialog"
import { SendResultsDialog } from "./send-results-dialog"
+import { ApprovalPreviewDialog } from "@/components/approval/ApprovalPreviewDialog"
+import {
+ requestPQInvestigationWithApproval,
+ reRequestPQInvestigationWithApproval
+} from "@/lib/vendor-investigation/approval-actions"
+import type { ApprovalLineItem } from "@/components/knox/approval/ApprovalLineSelector"
+import { debugLog, debugError, debugSuccess } from "@/lib/debug-utils"
interface VendorsTableToolbarActionsProps {
table: Table<PQSubmission>
@@ -35,15 +42,38 @@ interface InvestigationInitialData { export function VendorsTableToolbarActions({ table }: VendorsTableToolbarActionsProps) {
const selectedRows = table.getFilteredSelectedRowModel().rows
const [isLoading, setIsLoading] = React.useState(false)
+ const { data: session } = useSession()
// Dialog 상태 관리
const [isRequestDialogOpen, setIsRequestDialogOpen] = React.useState(false)
const [isCancelDialogOpen, setIsCancelDialogOpen] = React.useState(false)
const [isSendResultsDialogOpen, setIsSendResultsDialogOpen] = React.useState(false)
const [isReRequestDialogOpen, setIsReRequestDialogOpen] = React.useState(false)
+ const [isApprovalDialogOpen, setIsApprovalDialogOpen] = React.useState(false)
+ const [isReRequestApprovalDialogOpen, setIsReRequestApprovalDialogOpen] = React.useState(false)
// 초기 데이터 상태
const [dialogInitialData, setDialogInitialData] = React.useState<InvestigationInitialData | undefined>(undefined)
+
+ // 실사 의뢰 임시 데이터 (결재 다이얼로그로 전달)
+ const [investigationFormData, setInvestigationFormData] = React.useState<{
+ qmManagerId: number;
+ qmManagerName: string;
+ qmManagerEmail?: string;
+ forecastedAt: Date;
+ investigationAddress: string;
+ investigationNotes?: string;
+ } | null>(null)
+
+ // 실사 재의뢰 임시 데이터
+ const [reRequestData, setReRequestData] = React.useState<{
+ investigationIds: number[];
+ vendorNames: string;
+ } | null>(null)
+
+ // 결재 템플릿 변수
+ const [approvalVariables, setApprovalVariables] = React.useState<Record<string, string>>({})
+ const [reRequestApprovalVariables, setReRequestApprovalVariables] = React.useState<Record<string, string>>({})
// 실사 의뢰 대화상자 열기 핸들러
// 실사 의뢰 대화상자 열기 핸들러
@@ -132,14 +162,13 @@ const handleOpenRequestDialog = async () => { setIsRequestDialogOpen(true);
}
};
- // 실사 의뢰 요청 처리
+ // 실사 의뢰 요청 처리 - Step 1: RequestInvestigationDialog에서 정보 입력 후
const handleRequestInvestigation = async (formData: {
qmManagerId: number,
forecastedAt: Date,
investigationAddress: string,
investigationNotes?: string
}) => {
- setIsLoading(true)
try {
// 승인된 PQ 제출만 필터링 (미실사 PQ 제외)
const approvedPQs = selectedRows.filter(row =>
@@ -157,26 +186,119 @@ const handleOpenRequestDialog = async () => { return
}
- // 서버 액션 호출
- const result = await requestInvestigationAction(
- approvedPQs.map(row => row.original.id),
- formData
- )
+ // QM 담당자 이름 및 이메일 조회
+ const qmManagersResult = await getQMManagers()
+ const qmManager = qmManagersResult.success
+ ? qmManagersResult.data.find(m => m.id === formData.qmManagerId)
+ : null
+ const qmManagerName = qmManager?.name || `QM담당자 #${formData.qmManagerId}`
+ const qmManagerEmail = qmManager?.email || undefined
+
+ // 협력사 이름 목록 생성
+ const vendorNames = approvedPQs
+ .map(row => row.original.vendorName)
+ .join(', ')
+
+ // 실사 폼 데이터 저장 (이메일 추가)
+ setInvestigationFormData({
+ qmManagerId: formData.qmManagerId,
+ qmManagerName,
+ qmManagerEmail,
+ forecastedAt: formData.forecastedAt,
+ investigationAddress: formData.investigationAddress,
+ investigationNotes: formData.investigationNotes,
+ })
- if (result.success) {
+ // 결재 템플릿 변수 생성
+ const requestedAt = new Date()
+ const { mapPQInvestigationToTemplateVariables } = await import('@/lib/vendor-investigation/handlers')
+ const variables = await mapPQInvestigationToTemplateVariables({
+ vendorNames,
+ qmManagerName,
+ qmManagerEmail,
+ forecastedAt: formData.forecastedAt,
+ investigationAddress: formData.investigationAddress,
+ investigationNotes: formData.investigationNotes,
+ requestedAt,
+ })
- toast.success(`${result.count}개 업체에 대해 실사가 의뢰되었습니다.`)
- window.location.reload()
- } else {
- toast.error(result.error || "실사 의뢰 처리 중 오류가 발생했습니다.")
- }
- } catch (error) {
- console.error("실사 의뢰 중 오류 발생:", error)
- toast.error("실사 의뢰 중 오류가 발생했습니다.")
- } finally {
- setIsLoading(false)
+ setApprovalVariables(variables)
+
+ // RequestInvestigationDialog 닫고 ApprovalPreviewDialog 열기
setIsRequestDialogOpen(false)
- setDialogInitialData(undefined); // 초기 데이터 초기화
+ setIsApprovalDialogOpen(true)
+ } catch (error) {
+ console.error("결재 준비 중 오류 발생:", error)
+ toast.error("결재 준비 중 오류가 발생했습니다.")
+ }
+ }
+
+ // 실사 의뢰 결재 요청 처리 - Step 2: ApprovalPreviewDialog에서 결재선 선택 후
+ const handleApprovalSubmit = async (approvers: ApprovalLineItem[]) => {
+ debugLog('[InvestigationApproval] 실사 의뢰 결재 요청 시작', {
+ approversCount: approvers.length,
+ hasSession: !!session?.user,
+ hasFormData: !!investigationFormData,
+ });
+
+ if (!session?.user || !investigationFormData) {
+ debugError('[InvestigationApproval] 세션 또는 폼 데이터 없음');
+ throw new Error('세션 정보가 없습니다.');
+ }
+
+ // 승인된 PQ 제출만 필터링
+ const approvedPQs = selectedRows.filter(row =>
+ row.original.status === "APPROVED" &&
+ !row.original.investigation &&
+ row.original.type !== "NON_INSPECTION"
+ )
+
+ debugLog('[InvestigationApproval] 승인된 PQ 건수', {
+ count: approvedPQs.length,
+ });
+
+ // 협력사 이름 목록
+ const vendorNames = approvedPQs
+ .map(row => row.original.vendorName)
+ .join(', ')
+
+ // 결재선에서 EP ID 추출 (상신자 제외)
+ const approverEpIds = approvers
+ .filter((line) => line.seq !== "0" && line.epId)
+ .map((line) => line.epId!)
+
+ debugLog('[InvestigationApproval] 결재선 추출 완료', {
+ approverEpIds,
+ });
+
+ // 결재 워크플로우 시작
+ const result = await requestPQInvestigationWithApproval({
+ pqSubmissionIds: approvedPQs.map(row => row.original.id),
+ vendorNames,
+ qmManagerId: investigationFormData.qmManagerId,
+ qmManagerName: investigationFormData.qmManagerName,
+ qmManagerEmail: investigationFormData.qmManagerEmail,
+ forecastedAt: investigationFormData.forecastedAt,
+ investigationAddress: investigationFormData.investigationAddress,
+ investigationNotes: investigationFormData.investigationNotes,
+ currentUser: {
+ id: Number(session.user.id),
+ epId: session.user.epId || null,
+ email: session.user.email || undefined,
+ },
+ approvers: approverEpIds,
+ })
+
+ debugSuccess('[InvestigationApproval] 결재 요청 성공', {
+ approvalId: result.approvalId,
+ pendingActionId: result.pendingActionId,
+ });
+
+ if (result.status === 'pending_approval') {
+ // 성공 시에만 상태 초기화 및 페이지 리로드
+ setInvestigationFormData(null)
+ setDialogInitialData(undefined)
+ window.location.reload()
}
}
@@ -221,9 +343,8 @@ const handleOpenRequestDialog = async () => { }
}
- // 실사 재의뢰 처리
- const handleReRequestInvestigation = async () => {
- setIsLoading(true)
+ // 실사 재의뢰 처리 - Step 1: 확인 다이얼로그에서 확인 후
+ const handleReRequestInvestigation = async (reason?: string) => {
try {
// 취소된 실사만 필터링
const canceledInvestigations = selectedRows.filter(row =>
@@ -236,23 +357,87 @@ const handleOpenRequestDialog = async () => { return
}
- // 서버 액션 호출
- const result = await reRequestInvestigationAction(
- canceledInvestigations.map(row => row.original.investigation!.id)
- )
+ // 협력사 이름 목록 생성
+ const vendorNames = canceledInvestigations
+ .map(row => row.original.vendorName)
+ .join(', ')
- if (result.success) {
- toast.success(`${result.count}개 업체에 대한 실사가 재의뢰되었습니다.`)
- window.location.reload()
- } else {
- toast.error(result.error || "실사 재의뢰 처리 중 오류가 발생했습니다.")
- }
- } catch (error) {
- console.error("실사 재의뢰 중 오류 발생:", error)
- toast.error("실사 재의뢰 중 오류가 발생했습니다.")
- } finally {
- setIsLoading(false)
+ // 재의뢰 데이터 저장
+ const investigationIds = canceledInvestigations.map(row => row.original.investigation!.id)
+ setReRequestData({
+ investigationIds,
+ vendorNames,
+ })
+
+ // 결재 템플릿 변수 생성
+ const reRequestedAt = new Date()
+ const { mapPQReRequestToTemplateVariables } = await import('@/lib/vendor-investigation/handlers')
+ const variables = await mapPQReRequestToTemplateVariables({
+ vendorNames,
+ investigationCount: investigationIds.length,
+ reRequestedAt,
+ reason,
+ })
+
+ setReRequestApprovalVariables(variables)
+
+ // ReRequestInvestigationDialog 닫고 ApprovalPreviewDialog 열기
setIsReRequestDialogOpen(false)
+ setIsReRequestApprovalDialogOpen(true)
+ } catch (error) {
+ console.error("재의뢰 결재 준비 중 오류 발생:", error)
+ toast.error("재의뢰 결재 준비 중 오류가 발생했습니다.")
+ }
+ }
+
+ // 실사 재의뢰 결재 요청 처리 - Step 2: ApprovalPreviewDialog에서 결재선 선택 후
+ const handleReRequestApprovalSubmit = async (approvers: ApprovalLineItem[]) => {
+ debugLog('[ReRequestApproval] 실사 재의뢰 결재 요청 시작', {
+ approversCount: approvers.length,
+ hasSession: !!session?.user,
+ hasReRequestData: !!reRequestData,
+ });
+
+ if (!session?.user || !reRequestData) {
+ debugError('[ReRequestApproval] 세션 또는 재의뢰 데이터 없음');
+ throw new Error('세션 정보가 없습니다.');
+ }
+
+ debugLog('[ReRequestApproval] 재의뢰 대상', {
+ investigationIds: reRequestData.investigationIds,
+ vendorNames: reRequestData.vendorNames,
+ });
+
+ // 결재선에서 EP ID 추출 (상신자 제외)
+ const approverEpIds = approvers
+ .filter((line) => line.seq !== "0" && line.epId)
+ .map((line) => line.epId!)
+
+ debugLog('[ReRequestApproval] 결재선 추출 완료', {
+ approverEpIds,
+ });
+
+ // 결재 워크플로우 시작
+ const result = await reRequestPQInvestigationWithApproval({
+ investigationIds: reRequestData.investigationIds,
+ vendorNames: reRequestData.vendorNames,
+ currentUser: {
+ id: Number(session.user.id),
+ epId: session.user.epId || null,
+ email: session.user.email || undefined,
+ },
+ approvers: approverEpIds,
+ })
+
+ debugSuccess('[ReRequestApproval] 재의뢰 결재 요청 성공', {
+ approvalId: result.approvalId,
+ pendingActionId: result.pendingActionId,
+ });
+
+ if (result.status === 'pending_approval') {
+ // 성공 시에만 상태 초기화 및 페이지 리로드
+ setReRequestData(null)
+ window.location.reload()
}
}
@@ -521,6 +706,60 @@ const handleOpenRequestDialog = async () => { selectedCount={completedInvestigationsCount}
auditResults={auditResults}
/>
+
+ {/* 결재 미리보기 Dialog - 실사 의뢰 */}
+ {session?.user && investigationFormData && (
+ <ApprovalPreviewDialog
+ open={isApprovalDialogOpen}
+ onOpenChange={(open) => {
+ setIsApprovalDialogOpen(open)
+ if (!open) {
+ // 다이얼로그가 닫히면 실사 폼 데이터도 초기화
+ setInvestigationFormData(null)
+ }
+ }}
+ templateName="Vendor 실사의뢰"
+ variables={approvalVariables}
+ title={`Vendor 실사의뢰 - ${selectedRows.filter(row =>
+ row.original.status === "APPROVED" &&
+ !row.original.investigation &&
+ row.original.type !== "NON_INSPECTION"
+ ).map(row => row.original.vendorName).join(', ')}`}
+ description={`${approvedPQsCount}개 업체에 대한 실사 의뢰`}
+ currentUser={{
+ id: Number(session.user.id),
+ epId: session.user.epId || null,
+ name: session.user.name || null,
+ email: session.user.email || '',
+ }}
+ onSubmit={handleApprovalSubmit}
+ />
+ )}
+
+ {/* 결재 미리보기 Dialog - 실사 재의뢰 */}
+ {session?.user && reRequestData && (
+ <ApprovalPreviewDialog
+ open={isReRequestApprovalDialogOpen}
+ onOpenChange={(open) => {
+ setIsReRequestApprovalDialogOpen(open)
+ if (!open) {
+ // 다이얼로그가 닫히면 재의뢰 데이터도 초기화
+ setReRequestData(null)
+ }
+ }}
+ templateName="Vendor 실사 재의뢰"
+ variables={reRequestApprovalVariables}
+ title={`Vendor 실사 재의뢰 - ${reRequestData.vendorNames}`}
+ description={`${reRequestData.investigationIds.length}개 업체에 대한 실사 재의뢰`}
+ currentUser={{
+ id: Number(session.user.id),
+ epId: session.user.epId || null,
+ name: session.user.name || null,
+ email: session.user.email || '',
+ }}
+ onSubmit={handleReRequestApprovalSubmit}
+ />
+ )}
</>
)
}
\ No newline at end of file |
