diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/[lng]/evcp/(evcp)/(procurement)/rfq-last/[id]/layout.tsx | 23 | ||||
| -rw-r--r-- | app/[lng]/partners/(partners)/rfq-last/[id]/page.tsx | 37 | ||||
| -rw-r--r-- | app/api/partners/rfq-last/[id]/response/route.ts | 144 |
3 files changed, 193 insertions, 11 deletions
diff --git a/app/[lng]/evcp/(evcp)/(procurement)/rfq-last/[id]/layout.tsx b/app/[lng]/evcp/(evcp)/(procurement)/rfq-last/[id]/layout.tsx index 6dcbf018..8fffb221 100644 --- a/app/[lng]/evcp/(evcp)/(procurement)/rfq-last/[id]/layout.tsx +++ b/app/[lng]/evcp/(evcp)/(procurement)/rfq-last/[id]/layout.tsx @@ -4,7 +4,6 @@ import { Separator } from "@/components/ui/separator" import { SidebarNav } from "@/components/layout/sidebar-nav" import { formatDate } from "@/lib/utils" import { Button } from "@/components/ui/button" -import { Badge } from "@/components/ui/badge" import { ArrowLeft, Clock, AlertTriangle, CheckCircle, XCircle, AlertCircle, Calendar, CalendarDays } from "lucide-react" import { RfqsLastView } from "@/db/schema" import { findRfqLastById } from "@/lib/rfq-last/service" @@ -47,7 +46,27 @@ export default async function RfqLayout({ // 2) DB에서 해당 협력업체 정보 조회 const rfq: RfqsLastView | null = await findRfqLastById(rfqId) - // 3) 사이드바 메뉴 + // 3) 취소된 RFQ 접근 제어 + if (rfq?.status === "RFQ 삭제") { + return ( + <div className="p-4 space-y-4"> + <Alert variant="destructive"> + <AlertCircle className="h-4 w-4" /> + <AlertTitle>접근 불가</AlertTitle> + <AlertDescription> + 이 RFQ는 삭제되어 접근할 수 없습니다. + </AlertDescription> + </Alert> + <div className="p-4 bg-muted rounded-lg"> + <p className="text-sm font-medium mb-2">삭제 사유:</p> + <p className="text-sm text-muted-foreground whitespace-pre-wrap">{rfq.deleteReason}</p> + </div> + + </div> + ); + } + + // 4) 사이드바 메뉴 const sidebarNavItems = [ { title: "견적 문서관리", diff --git a/app/[lng]/partners/(partners)/rfq-last/[id]/page.tsx b/app/[lng]/partners/(partners)/rfq-last/[id]/page.tsx index 7a68e3a2..9052de6f 100644 --- a/app/[lng]/partners/(partners)/rfq-last/[id]/page.tsx +++ b/app/[lng]/partners/(partners)/rfq-last/[id]/page.tsx @@ -86,6 +86,23 @@ export default async function VendorResponsePage({ params }: PageProps) { if (!rfq || !rfq.rfqDetails[0]) { notFound() } + + // 취소된 RFQ 접근 제어 + if (rfq.status === "RFQ 삭제") { + return ( + <div className="flex h-full items-center justify-center"> + <div className="text-center max-w-md"> + <h2 className="text-xl font-bold">접근 불가</h2> + <p className="mt-2 text-muted-foreground">이 RFQ는 삭제되어 접근할 수 없습니다.</p> + <div className="mt-4 p-4 bg-muted rounded-lg text-left"> + <p className="text-sm font-medium mb-2">삭제 사유:</p> + <p className="text-sm text-muted-foreground whitespace-pre-wrap">{rfq.deleteReason}</p> + </div> + + </div> + </div> + ) + } const rfqDetail = rfq.rfqDetails[0] @@ -99,8 +116,28 @@ export default async function VendorResponsePage({ params }: PageProps) { with: { quotationItems: true, attachments: true, + priceAdjustmentForm: true, } }) + + // 취소된 RFQ 접근 제어 (vendor response가 취소 상태인 경우) + if (existingResponse?.status === "취소" || rfqDetail.cancelReason) { + return ( + <div className="flex h-full items-center justify-center"> + <div className="text-center"> + <h2 className="text-xl font-bold">RFQ 취소됨</h2> + <p className="mt-2 text-muted-foreground"> + 이 RFQ는 취소되어 더 이상 견적을 제출할 수 없습니다. + </p> + {rfqDetail.cancelReason && ( + <p className="mt-4 text-sm text-muted-foreground"> + 취소 사유: {rfqDetail.cancelReason} + </p> + )} + </div> + </div> + ) + } // PR Items 가져오기 const prItems = await db.query.rfqPrItems.findMany({ diff --git a/app/api/partners/rfq-last/[id]/response/route.ts b/app/api/partners/rfq-last/[id]/response/route.ts index 5d05db50..06ace9a0 100644 --- a/app/api/partners/rfq-last/[id]/response/route.ts +++ b/app/api/partners/rfq-last/[id]/response/route.ts @@ -7,9 +7,10 @@ import { rfqLastVendorResponses, rfqLastVendorQuotationItems, rfqLastVendorAttachments, - rfqLastVendorResponseHistory + rfqLastVendorResponseHistory, + rfqLastPriceAdjustmentForms } from "@/db/schema" -import { eq, and, inArray } from "drizzle-orm" +import { eq, and } from "drizzle-orm" import { writeFile, mkdir } from "fs/promises" import { createWriteStream } from "fs" import { pipeline } from "stream/promises" @@ -139,17 +140,77 @@ export async function POST( await tx.insert(rfqLastVendorQuotationItems).values(quotationItemsData) } - // 이력 기록 + // 3. 연동제 정보 저장 (연동제 적용이 true이고 연동제 정보가 있는 경우) + if (data.vendorMaterialPriceRelatedYn && data.priceAdjustmentForm && vendorResponse.id) { + const priceAdjustmentData: { + rfqLastVendorResponsesId: number; + itemName?: string | null; + adjustmentReflectionPoint?: string | null; + majorApplicableRawMaterial?: string | null; + adjustmentFormula?: string | null; + rawMaterialPriceIndex?: string | null; + referenceDate?: string | null; + comparisonDate?: string | null; + adjustmentRatio?: number | null; + notes?: string | null; + adjustmentConditions?: string | null; + majorNonApplicableRawMaterial?: string | null; + adjustmentPeriod?: string | null; + contractorWriter?: string | null; + adjustmentDate?: string | null; + nonApplicableReason?: string | null; + } = { + rfqLastVendorResponsesId: vendorResponse.id, + itemName: data.priceAdjustmentForm.itemName || null, + adjustmentReflectionPoint: data.priceAdjustmentForm.adjustmentReflectionPoint || null, + majorApplicableRawMaterial: data.priceAdjustmentForm.majorApplicableRawMaterial || null, + adjustmentFormula: data.priceAdjustmentForm.adjustmentFormula || null, + rawMaterialPriceIndex: data.priceAdjustmentForm.rawMaterialPriceIndex || null, + referenceDate: data.priceAdjustmentForm.referenceDate || null, + comparisonDate: data.priceAdjustmentForm.comparisonDate || null, + adjustmentRatio: data.priceAdjustmentForm.adjustmentRatio || null, + notes: data.priceAdjustmentForm.notes || null, + adjustmentConditions: data.priceAdjustmentForm.adjustmentConditions || null, + majorNonApplicableRawMaterial: data.priceAdjustmentForm.majorNonApplicableRawMaterial || null, + adjustmentPeriod: data.priceAdjustmentForm.adjustmentPeriod || null, + contractorWriter: data.priceAdjustmentForm.contractorWriter || null, + adjustmentDate: data.priceAdjustmentForm.adjustmentDate || null, + nonApplicableReason: data.priceAdjustmentForm.nonApplicableReason || null, + } + + // 기존 연동제 정보가 있는지 확인 + const existingPriceAdjustment = await tx + .select() + .from(rfqLastPriceAdjustmentForms) + .where(eq(rfqLastPriceAdjustmentForms.rfqLastVendorResponsesId, vendorResponse.id)) + .limit(1) + + if (existingPriceAdjustment.length > 0) { + // 업데이트 + await tx + .update(rfqLastPriceAdjustmentForms) + .set({ + ...priceAdjustmentData, + updatedAt: new Date(), + }) + .where(eq(rfqLastPriceAdjustmentForms.rfqLastVendorResponsesId, vendorResponse.id)) + } else { + // 새로 생성 + await tx.insert(rfqLastPriceAdjustmentForms).values(priceAdjustmentData) + } + } + + // 4. 이력 기록 await tx.insert(rfqLastVendorResponseHistory).values({ - vendorResponseId: vendorResponseId, - action: isNewResponse ? "생성" : (data.status === "제출완료" ? "제출" : "수정"), - previousStatus: existingResponse?.status || null, + vendorResponseId: vendorResponse.id, + action: "생성", + previousStatus: null, newStatus: data.status || "작성중", changeDetails: data, performedBy: session.user.id, }) - return { id: vendorResponseId, isNew: isNewResponse } + return { id: vendorResponse.id, isNew: true } }) // 파일 저장 (트랜잭션 밖에서 처리) @@ -262,7 +323,7 @@ export async function PUT( const previousStatus = existingResponse.status // 2. 새 버전 생성 (제출 시) 또는 기존 버전 업데이트 - let responseId = existingResponse.id + const responseId = existingResponse.id // if (data.status === "제출완료" && previousStatus !== "제출완료") { // // 기존 버전을 비활성화 @@ -362,7 +423,72 @@ export async function PUT( await tx.insert(rfqLastVendorQuotationItems).values(quotationItemsData) } - // 4. 이력 기록 + // 4. 연동제 정보 저장/업데이트 (연동제 적용이 true이고 연동제 정보가 있는 경우) + if (data.vendorMaterialPriceRelatedYn && data.priceAdjustmentForm && responseId) { + const priceAdjustmentData: { + rfqLastVendorResponsesId: number; + itemName?: string | null; + adjustmentReflectionPoint?: string | null; + majorApplicableRawMaterial?: string | null; + adjustmentFormula?: string | null; + rawMaterialPriceIndex?: string | null; + referenceDate?: string | null; + comparisonDate?: string | null; + adjustmentRatio?: number | null; + notes?: string | null; + adjustmentConditions?: string | null; + majorNonApplicableRawMaterial?: string | null; + adjustmentPeriod?: string | null; + contractorWriter?: string | null; + adjustmentDate?: string | null; + nonApplicableReason?: string | null; + } = { + rfqLastVendorResponsesId: responseId, + itemName: data.priceAdjustmentForm.itemName || null, + adjustmentReflectionPoint: data.priceAdjustmentForm.adjustmentReflectionPoint || null, + majorApplicableRawMaterial: data.priceAdjustmentForm.majorApplicableRawMaterial || null, + adjustmentFormula: data.priceAdjustmentForm.adjustmentFormula || null, + rawMaterialPriceIndex: data.priceAdjustmentForm.rawMaterialPriceIndex || null, + referenceDate: data.priceAdjustmentForm.referenceDate || null, + comparisonDate: data.priceAdjustmentForm.comparisonDate || null, + adjustmentRatio: data.priceAdjustmentForm.adjustmentRatio || null, + notes: data.priceAdjustmentForm.notes || null, + adjustmentConditions: data.priceAdjustmentForm.adjustmentConditions || null, + majorNonApplicableRawMaterial: data.priceAdjustmentForm.majorNonApplicableRawMaterial || null, + adjustmentPeriod: data.priceAdjustmentForm.adjustmentPeriod || null, + contractorWriter: data.priceAdjustmentForm.contractorWriter || null, + adjustmentDate: data.priceAdjustmentForm.adjustmentDate || null, + nonApplicableReason: data.priceAdjustmentForm.nonApplicableReason || null, + } + + // 기존 연동제 정보가 있는지 확인 + const existingPriceAdjustment = await tx + .select() + .from(rfqLastPriceAdjustmentForms) + .where(eq(rfqLastPriceAdjustmentForms.rfqLastVendorResponsesId, responseId)) + .limit(1) + + if (existingPriceAdjustment.length > 0) { + // 업데이트 + await tx + .update(rfqLastPriceAdjustmentForms) + .set({ + ...priceAdjustmentData, + updatedAt: new Date(), + }) + .where(eq(rfqLastPriceAdjustmentForms.rfqLastVendorResponsesId, responseId)) + } else { + // 새로 생성 + await tx.insert(rfqLastPriceAdjustmentForms).values(priceAdjustmentData) + } + } else if (data.vendorMaterialPriceRelatedYn === false && responseId) { + // 연동제 미적용 시 기존 데이터 삭제 + await tx + .delete(rfqLastPriceAdjustmentForms) + .where(eq(rfqLastPriceAdjustmentForms.rfqLastVendorResponsesId, responseId)) + } + + // 5. 이력 기록 await tx.insert(rfqLastVendorResponseHistory).values({ vendorResponseId: responseId, action: data.status === "제출완료" ? "제출" : "수정", |
