diff options
Diffstat (limited to 'lib/pq/pq-review-table-new')
5 files changed, 197 insertions, 83 deletions
diff --git a/lib/pq/pq-review-table-new/edit-investigation-dialog.tsx b/lib/pq/pq-review-table-new/edit-investigation-dialog.tsx index c4057798..8e139b79 100644 --- a/lib/pq/pq-review-table-new/edit-investigation-dialog.tsx +++ b/lib/pq/pq-review-table-new/edit-investigation-dialog.tsx @@ -51,7 +51,7 @@ const editInvestigationSchema = z.object({ z.string().transform((str) => str ? new Date(str) : undefined)
]).optional(),
evaluationResult: z.enum(["APPROVED", "SUPPLEMENT", "REJECTED"]).optional(),
- investigationNotes: z.string().max(1000, "QM 의견은 1000자 이내로 입력해주세요.").optional(),
+ investigationNotes: z.string().max(1000, "구매 의견은 1000자 이내로 입력해주세요.").optional(),
attachments: z.array(z.instanceof(File)).optional(),
})
@@ -210,9 +210,9 @@ export function EditInvestigationDialog({ <Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
<DialogHeader>
- <DialogTitle>실사 정보 수정</DialogTitle>
+ <DialogTitle>구매자체평가 실사 결과 수정</DialogTitle>
<DialogDescription>
- 구매자체평가 실사 정보를 수정합니다.
+ 구매자체평가 실사 결과를 수정합니다.
</DialogDescription>
</DialogHeader>
diff --git a/lib/pq/pq-review-table-new/site-visit-dialog.tsx b/lib/pq/pq-review-table-new/site-visit-dialog.tsx index 2b65d03e..b1474150 100644 --- a/lib/pq/pq-review-table-new/site-visit-dialog.tsx +++ b/lib/pq/pq-review-table-new/site-visit-dialog.tsx @@ -140,6 +140,7 @@ interface SiteVisitDialogProps { projectCode?: string
pqItems?: Array<{itemCode: string, itemName: string}> | null
}
+ isReinspection?: boolean // 재실사 모드 플래그
}
export function SiteVisitDialog({
@@ -147,6 +148,7 @@ export function SiteVisitDialog({ onClose,
onSubmit,
investigation,
+ isReinspection = false,
}: SiteVisitDialogProps) {
const [isPending, setIsPending] = React.useState(false)
const [selectedFiles, setSelectedFiles] = React.useState<File[]>([])
@@ -184,58 +186,88 @@ export function SiteVisitDialog({ },
})
- // Dialog가 열릴 때마다 폼 재설정 및 기존 요청 확인
+ // Dialog가 열릴 때마다 폼 재설정 및 기존 요청 로딩
React.useEffect(() => {
if (isOpen) {
- // 기존 방문실사 요청이 있는지 확인
- const checkExistingRequest = async () => {
+ const loadExistingRequest = async () => {
try {
+ // 기존 방문실사 요청이 있는지 확인하고 최신 것을 로드
const existingRequest = await getSiteVisitRequestAction(investigation.id)
-
+
if (existingRequest.success && existingRequest.data) {
- toast.error("이미 방문실사 요청이 존재합니다. 추가 요청은 불가능합니다.")
- onClose()
+ // 기존 데이터를 form에 로드
+ const data = existingRequest.data
+ form.reset({
+ inspectionDuration: data.inspectionDuration || 1.0,
+ requestedStartDate: data.requestedStartDate ? new Date(data.requestedStartDate) : undefined,
+ requestedEndDate: data.requestedEndDate ? new Date(data.requestedEndDate) : undefined,
+ shiAttendees: data.shiAttendees || {
+ technicalSales: { checked: false, count: 0, details: "" },
+ design: { checked: false, count: 0, details: "" },
+ procurement: { checked: false, count: 0, details: "" },
+ quality: { checked: false, count: 0, details: "" },
+ production: { checked: false, count: 0, details: "" },
+ commissioning: { checked: false, count: 0, details: "" },
+ other: { checked: false, count: 0, details: "" },
+ },
+ shiAttendeeDetails: data.shiAttendeeDetails || "",
+ vendorRequests: data.vendorRequests || {
+ availableDates: false,
+ factoryName: false,
+ factoryLocation: false,
+ factoryAddress: false,
+ factoryPicName: false,
+ factoryPicPhone: false,
+ factoryPicEmail: false,
+ factoryDirections: false,
+ accessProcedure: false,
+ other: false,
+ },
+ otherVendorRequests: data.otherVendorRequests || "",
+ additionalRequests: data.additionalRequests || "",
+ })
return
}
+
+ // 기본값으로 폼 초기화 (기존 요청이 없는 경우)
+ form.reset({
+ inspectionDuration: 1.0,
+ requestedStartDate: undefined,
+ requestedEndDate: undefined,
+ shiAttendees: {
+ technicalSales: { checked: false, count: 0, details: "" },
+ design: { checked: false, count: 0, details: "" },
+ procurement: { checked: false, count: 0, details: "" },
+ quality: { checked: false, count: 0, details: "" },
+ production: { checked: false, count: 0, details: "" },
+ commissioning: { checked: false, count: 0, details: "" },
+ other: { checked: false, count: 0, details: "" },
+ },
+ shiAttendeeDetails: "",
+ vendorRequests: {
+ availableDates: false,
+ factoryName: false,
+ factoryLocation: false,
+ factoryAddress: false,
+ factoryPicName: false,
+ factoryPicPhone: false,
+ factoryPicEmail: false,
+ factoryDirections: false,
+ accessProcedure: false,
+ other: false,
+ },
+ otherVendorRequests: "",
+ additionalRequests: "",
+ })
} catch (error) {
- console.error("방문실사 요청 상태 확인 중 오류:", error)
- toast.error("방문실사 요청 상태 확인 중 오류가 발생했습니다.")
+ console.error("방문실사 요청 데이터 로드 중 오류:", error)
+ toast.error("방문실사 요청 데이터 로드 중 오류가 발생했습니다.")
onClose()
return
}
}
-
- checkExistingRequest()
-
- form.reset({
- inspectionDuration: 1.0,
- requestedStartDate: undefined,
- requestedEndDate: undefined,
- shiAttendees: {
- technicalSales: { checked: false, count: 0, details: "" },
- design: { checked: false, count: 0, details: "" },
- procurement: { checked: false, count: 0, details: "" },
- quality: { checked: false, count: 0, details: "" },
- production: { checked: false, count: 0, details: "" },
- commissioning: { checked: false, count: 0, details: "" },
- other: { checked: false, count: 0, details: "" },
- },
- shiAttendeeDetails: "",
- vendorRequests: {
- availableDates: false,
- factoryName: false,
- factoryLocation: false,
- factoryAddress: false,
- factoryPicName: false,
- factoryPicPhone: false,
- factoryPicEmail: false,
- factoryDirections: false,
- accessProcedure: false,
- other: false,
- },
- otherVendorRequests: "",
- additionalRequests: "",
- })
+
+ loadExistingRequest()
setSelectedFiles([])
}
}, [isOpen, form, investigation.id, onClose])
@@ -243,19 +275,11 @@ export function SiteVisitDialog({ async function handleSubmit(data: SiteVisitRequestFormValues) {
setIsPending(true)
try {
- // 제출 전에 한 번 더 기존 요청이 있는지 확인
- const existingRequest = await getSiteVisitRequestAction(investigation.id)
-
- if (existingRequest.success && existingRequest.data) {
- toast.error("이미 방문실사 요청이 존재합니다. 추가 요청은 불가능합니다.")
- onClose()
- return
- }
-
+
await onSubmit(data, selectedFiles)
- toast.success("방문실사 요청이 성공적으로 발송되었습니다.")
+ toast.success(isReinspection ? "재실사 요청이 성공적으로 발송되었습니다." : "방문실사 요청이 성공적으로 발송되었습니다.")
} catch (error) {
- toast.error("방문실사 요청 발송 중 오류가 발생했습니다.")
+ toast.error(isReinspection ? "재실사 요청 발송 중 오류가 발생했습니다." : "방문실사 요청 발송 중 오류가 발생했습니다.")
console.error("방문실사 요청 오류:", error)
} finally {
setIsPending(false)
@@ -294,9 +318,12 @@ export function SiteVisitDialog({ <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
- <DialogTitle>방문실사 요청 생성</DialogTitle>
+ <DialogTitle>{isReinspection ? "재실사 요청 생성" : "방문실사 요청 생성"}</DialogTitle>
<DialogDescription>
- 협력업체에 방문실사 요청을 생성하고, 협력업체가 입력할 정보 항목을 설정합니다.
+ {isReinspection
+ ? "협력업체에 재실사 요청을 생성하고, 협력업체가 입력할 정보 항목을 설정합니다."
+ : "협력업체에 방문실사 요청을 생성하고, 협력업체가 입력할 정보 항목을 설정합니다."
+ }
</DialogDescription>
</DialogHeader>
@@ -710,7 +737,7 @@ export function SiteVisitDialog({ 취소
</Button>
<Button type="submit" disabled={isPending}>
- {isPending ? "처리 중..." : "방문실사 요청 생성"}
+ {isPending ? "처리 중..." : (isReinspection ? "재실사 요청 생성" : "방문실사 요청 생성")}
</Button>
</DialogFooter>
</form>
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 b4d7d038..3e10177d 100644 --- a/lib/pq/pq-review-table-new/vendors-table-columns.tsx +++ b/lib/pq/pq-review-table-new/vendors-table-columns.tsx @@ -75,6 +75,7 @@ export interface PQSubmission { qmManagerEmail: string | null // QM 담당자 이메일
investigationAddress: string | null
investigationMethod: string | null
+ hasSupplementRequested: boolean
scheduledStartAt: Date | null
scheduledEndAt: Date | null
requestedAt: Date | null
@@ -100,24 +101,6 @@ interface GetColumnsProps { router: NextRouter;
}
-// 상태에 따른 Badge 변형 결정 함수
-function getStatusBadge(status: string) {
- switch (status) {
- case "REQUESTED":
- return <Badge variant="outline">요청됨</Badge>
- case "IN_PROGRESS":
- return <Badge variant="secondary">진행 중</Badge>
- case "SUBMITTED":
- return <Badge>제출됨</Badge>
- case "APPROVED":
- return <Badge variant="success">승인됨</Badge>
- case "REJECTED":
- return <Badge variant="destructive">거부됨</Badge>
- default:
- return <Badge variant="outline">{status}</Badge>
- }
-}
-
/**
* tanstack table 컬럼 정의
*/
@@ -285,15 +268,15 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ExtendedC const combinedStatus = getCombinedStatus(row.original);
return value.includes(combinedStatus.status);
},
- enableSorting: true,
+ enableSorting: false,
enableHiding: true,
excelHeader: "진행현황",
};
// PQ 상태와 실사 상태를 결합하는 헬퍼 함수
function getCombinedStatus(submission: PQSubmission) {
- // PQ가 승인되지 않은 경우, PQ 상태를 우선 표시
- if (submission.status !== "APPROVED") {
+ // PQ가 QM 승인되지 않은 경우, PQ 상태를 우선 표시
+ if (submission.status !== "QM_APPROVED") {
switch (submission.status) {
case "REQUESTED":
return { status: "PQ_REQUESTED", label: "PQ 요청됨", variant: "outline" as const };
@@ -301,22 +284,30 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ExtendedC return { status: "PQ_IN_PROGRESS", label: "PQ 진행 중", variant: "secondary" as const };
case "SUBMITTED":
return { status: "PQ_SUBMITTED", label: "PQ 제출됨", variant: "default" as const };
+ case "APPROVED":
+ return { status: "PQ_APPROVED", label: "PQ 승인됨", variant: "success" as const };
case "REJECTED":
return { status: "PQ_REJECTED", label: "PQ 거부됨", variant: "destructive" as const };
+ case "QM_REVIEWING":
+ return { status: "PQ_QM_REVIEWING", label: "QM 검토 중", variant: "secondary" as const };
+ case "QM_REJECTED":
+ return { status: "PQ_QM_REJECTED", label: "QM 거부됨", variant: "destructive" as const };
default:
return { status: submission.status, label: submission.status, variant: "outline" as const };
}
}
- // PQ가 승인되었지만 실사가 없는 경우
+ // PQ가 QM 승인되었지만 실사가 없는 경우
if (!submission.investigation) {
- return { status: "PQ_APPROVED", label: "PQ 승인됨", variant: "success" as const };
+ return { status: "PQ_QM_APPROVED", label: "PQ 승인됨", variant: "success" as const };
}
// PQ가 승인되고 실사가 있는 경우
switch (submission.investigation.investigationStatus) {
case "PLANNED":
return { status: "INVESTIGATION_PLANNED", label: "실사 계획됨", variant: "outline" as const };
+ case "QM_REVIEW_CONFIRMED":
+ return { status: "INVESTIGATION_QM_REVIEW_CONFIRMED", label: "QM 검토 완료", variant: "outline" as const };
case "IN_PROGRESS":
return { status: "INVESTIGATION_IN_PROGRESS", label: "실사 진행 중", variant: "secondary" as const };
case "COMPLETED":
@@ -343,6 +334,10 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ExtendedC case "SUPPLEMENT_REQUIRED":
return { status: "INVESTIGATION_SUPPLEMENT_REQUIRED", label: "실사 보완 요구됨", variant: "secondary" as const };
case "RESULT_SENT":
+ // 보완을 통해 최종 합격/불합격한 경우
+ if (submission.investigation.hasSupplementRequested) {
+ return { status: "INVESTIGATION_RESULT_SENT_SUPPLEMENT", label: "실사 결과 발송(보완)", variant: "success" as const };
+ }
return { status: "INVESTIGATION_RESULT_SENT", label: "실사 결과 발송", variant: "success" as const };
default:
return {
@@ -761,7 +756,7 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ExtendedC }}
>
<Edit className="mr-2 h-4 w-4" />
- 실사 정보 수정
+ 구매 자체 평가
</DropdownMenuItem>
)}
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 ea6b6189..98b1cc76 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 @@ -15,6 +15,7 @@ import { getFactoryLocationAnswer,
getQMManagers
} from "@/lib/pq/service"
+import { SiteVisitDialog } from "./site-visit-dialog"
import { RequestInvestigationDialog } from "./request-investigation-dialog"
import { CancelInvestigationDialog, ReRequestInvestigationDialog } from "./cancel-investigation-dialog"
import { SendResultsDialog } from "./send-results-dialog"
@@ -49,6 +50,7 @@ export function VendorsTableToolbarActions({ table }: VendorsTableToolbarActions const [isCancelDialogOpen, setIsCancelDialogOpen] = React.useState(false)
const [isSendResultsDialogOpen, setIsSendResultsDialogOpen] = React.useState(false)
const [isReRequestDialogOpen, setIsReRequestDialogOpen] = React.useState(false)
+ const [isReinspectionDialogOpen, setIsReinspectionDialogOpen] = React.useState(false)
const [isApprovalDialogOpen, setIsApprovalDialogOpen] = React.useState(false)
const [isReRequestApprovalDialogOpen, setIsReRequestApprovalDialogOpen] = React.useState(false)
@@ -441,6 +443,53 @@ const handleOpenRequestDialog = async () => { }
}
+ // 재실사 요청 처리
+ const handleRequestReinspection = async (data: {
+ qmManagerId: number,
+ forecastedAt: Date,
+ investigationAddress: string,
+ investigationNotes?: string
+ }) => {
+ try {
+ // 보완-재실사 대상 실사만 필터링
+ const supplementReinspectInvestigations = selectedRows.filter(row =>
+ row.original.investigation &&
+ row.original.investigation.evaluationResult === "SUPPLEMENT_REINSPECT"
+ );
+
+ if (supplementReinspectInvestigations.length === 0) {
+ toast.error("보완-재실사 대상 실사가 없습니다.");
+ return;
+ }
+
+ // 첫 번째 대상 실사로 재실사 요청 생성
+ const targetInvestigation = supplementReinspectInvestigations[0].original.investigation!;
+ const { requestSupplementReinspectionAction } = await import('@/lib/vendor-investigation/service');
+
+ const result = await requestSupplementReinspectionAction({
+ investigationId: targetInvestigation.id,
+ siteVisitData: {
+ inspectionDuration: 1.0, // 기본 1일
+ requestedStartDate: data.forecastedAt,
+ requestedEndDate: new Date(data.forecastedAt.getTime() + 24 * 60 * 60 * 1000), // 1일 후
+ shiAttendees: {},
+ vendorRequests: {},
+ additionalRequests: data.investigationNotes || "보완을 위한 재실사 요청입니다.",
+ }
+ });
+
+ if (result.success) {
+ toast.success("재실사 요청이 생성되었습니다.");
+ window.location.reload();
+ } else {
+ toast.error(result.error || "재실사 요청 생성 중 오류가 발생했습니다.");
+ }
+ } catch (error) {
+ console.error("재실사 요청 오류:", error);
+ toast.error("재실사 요청 중 오류가 발생했습니다.");
+ }
+ };
+
// 실사 결과 발송 처리
const handleSendInvestigationResults = async (data: { purchaseComment?: string }) => {
try {
@@ -505,8 +554,14 @@ const handleOpenRequestDialog = async () => { row.original.investigation.investigationStatus === "CANCELED"
).length
+ // 재실사 요청 대상 수 확인 (보완-재실사 결과만)
+ const reinspectInvestigationsCount = selectedRows.filter(row =>
+ row.original.investigation &&
+ row.original.investigation.evaluationResult === "SUPPLEMENT_REINSPECT"
+ ).length
+
// 미실사 PQ가 선택되었는지 확인
- const hasNonInspectionPQ = selectedRows.some(row =>
+ const hasNonInspectionPQ = selectedRows.some(row =>
row.original.type === "NON_INSPECTION"
)
@@ -651,6 +706,22 @@ const handleOpenRequestDialog = async () => { <span className="hidden sm:inline">실사 재의뢰</span>
</Button>
+ {/* 재실사 요청 버튼 */}
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => setIsReinspectionDialogOpen(true)}
+ disabled={
+ isLoading ||
+ selectedRows.length === 0 ||
+ reinspectInvestigationsCount === 0
+ }
+ className="gap-2"
+ >
+ <RefreshCw className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">재방문 실사 요청</span>
+ </Button>
+
{/* 실사 결과 발송 버튼 */}
<Button
variant="outline"
@@ -727,6 +798,24 @@ const handleOpenRequestDialog = async () => { auditResults={auditResults}
/>
+ {/* 재방문실사 요청 Dialog */}
+ <SiteVisitDialog
+ isOpen={isReinspectionDialogOpen}
+ onClose={() => setIsReinspectionDialogOpen(false)}
+ onSubmit={handleRequestReinspection}
+ investigation={{
+ id: 0, // 재실사용으로 0으로 설정 (기존 데이터 로드 안함)
+ investigationMethod: "SITE_VISIT_EVAL",
+ investigationAddress: "",
+ vendorName: "재실사 대상",
+ vendorCode: "N/A",
+ projectName: "",
+ projectCode: "",
+ pqItems: null
+ }}
+ isReinspection={true}
+ />
+
{/* 결재 미리보기 Dialog - 실사 의뢰 */}
{session?.user && investigationFormData && (
<ApprovalPreviewDialog
diff --git a/lib/pq/pq-review-table-new/vendors-table.tsx b/lib/pq/pq-review-table-new/vendors-table.tsx index f8f9928e..e55da8c5 100644 --- a/lib/pq/pq-review-table-new/vendors-table.tsx +++ b/lib/pq/pq-review-table-new/vendors-table.tsx @@ -310,7 +310,10 @@ export function PQSubmissionsTable({ promises, className }: PQSubmissionsTablePr enableAdvancedFilter: true,
enableRowSelection: true,
maxSelections: 1,
- initialState,
+ initialState: {
+ sorting: [{ id: "updatedAt", desc: true }],
+ columnPinning: { right: ["actions"] },
+ },
getRowId: (originalRow) => String(originalRow.id),
shallow: false,
clearOnDefault: true,
|
