summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-12-09 09:46:22 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-12-09 09:46:22 +0000
commit09858255bcea648c01939447233fea9e955a36ee (patch)
tree57e22338c523a64fcb9bdcd5e2d9c75236569765
parentbf4b3bd7ac7a8ab6fbf6672c639e9fc86c090f65 (diff)
(최겸) 일반계약 템플릿 매핑 수정, 템플릿 파일 변경완료(품질 업로드 완료)
-rw-r--r--app/[lng]/partners/(partners)/pq_new/[id]/page.tsx3
-rw-r--r--app/[lng]/partners/(partners)/pq_new/page.tsx10
-rw-r--r--lib/general-contracts/detail/general-contract-approval-request-dialog.tsx5
-rw-r--r--lib/general-contracts/utils.ts63
4 files changed, 75 insertions, 6 deletions
diff --git a/app/[lng]/partners/(partners)/pq_new/[id]/page.tsx b/app/[lng]/partners/(partners)/pq_new/[id]/page.tsx
index e43d600f..448267d0 100644
--- a/app/[lng]/partners/(partners)/pq_new/[id]/page.tsx
+++ b/app/[lng]/partners/(partners)/pq_new/[id]/page.tsx
@@ -134,9 +134,10 @@ export default async function PQEditPage(props: PQEditPageProps) {
);
// 상태에 따른 읽기 전용 모드 결정
- const isReadOnly = [ "APPROVED"].includes(pqSubmission.status);
+ const isReadOnly = [ "QM_APPROVED"].includes(pqSubmission.status);
const statusText = pqSubmission.status === "SUBMITTED" ? "제출됨" :
pqSubmission.status === "APPROVED" ? "승인됨" :
+ pqSubmission.status === "QM_APPROVED" ? "최종 승인됨" :
pqSubmission.status === "REJECTED" ? "거부됨" : "작성 중";
const pageTitle = pqSubmission.type === "PROJECT"
diff --git a/app/[lng]/partners/(partners)/pq_new/page.tsx b/app/[lng]/partners/(partners)/pq_new/page.tsx
index e72144c0..89a646a8 100644
--- a/app/[lng]/partners/(partners)/pq_new/page.tsx
+++ b/app/[lng]/partners/(partners)/pq_new/page.tsx
@@ -51,6 +51,12 @@ function getStatusBadge(status: string) {
return <Badge variant="default">승인됨</Badge>;
case "REJECTED":
return <Badge variant="destructive">거부됨</Badge>;
+ case "QM_REVIEWING":
+ return <Badge variant="secondary">QM 검토 중</Badge>;
+ case "QM_APPROVED":
+ return <Badge variant="default">최종 승인됨</Badge>;
+ case "QM_REJECTED":
+ return <Badge variant="destructive">최종 거부됨</Badge>;
default:
return <Badge variant="outline">{status}</Badge>;
}
@@ -262,8 +268,8 @@ export default async function PQListPage({ params }: IndexPageProps) {
</TableRow>
) : (
pqList.map((pq) => {
- const canEdit = ["REQUESTED", "IN_PROGRESS", "REJECTED"].includes(pq.status);
- const canView = ["SUBMITTED", "APPROVED"].includes(pq.status);
+ const canEdit = ["REQUESTED", "IN_PROGRESS", "REJECTED", "SAFETY_APPROVED", "QM_REVIEWING"].includes(pq.status);
+ const canView = ["SUBMITTED", "APPROVED", "QM_APPROVED", "SAFETY_REJECTED", "QM_REJECTED"].includes(pq.status);
return (
<TableRow key={pq.id}>
diff --git a/lib/general-contracts/detail/general-contract-approval-request-dialog.tsx b/lib/general-contracts/detail/general-contract-approval-request-dialog.tsx
index db0901cb..04054369 100644
--- a/lib/general-contracts/detail/general-contract-approval-request-dialog.tsx
+++ b/lib/general-contracts/detail/general-contract-approval-request-dialog.tsx
@@ -245,7 +245,10 @@ export function ContractApprovalRequestDialog({
if (contractData) {
summary.basicInfo = {
...summary.basicInfo,
- externalYardEntry: contractData.externalYardEntry || 'N'
+ externalYardEntry: contractData.externalYardEntry || 'N',
+ vendorCountry: (contractData as any)?.vendorCountry || summary.basicInfo.vendorCountry,
+ vendorName: (contractData as any)?.vendorName || summary.basicInfo.vendorName,
+ vendorCode: (contractData as any)?.vendorCode || summary.basicInfo.vendorCode,
}
}
} catch {
diff --git a/lib/general-contracts/utils.ts b/lib/general-contracts/utils.ts
index 5bbb5980..1262dc4d 100644
--- a/lib/general-contracts/utils.ts
+++ b/lib/general-contracts/utils.ts
@@ -170,6 +170,51 @@ export function mapContractDataToTemplateVariables(contractSummary: ContractSumm
).join('\n')
: ''
+ // PDFTron 템플릿 루프용 데이터 ({{#storageList}} ... {{/storageList}} 사용)
+ const storageList = storageItems.map(item => ({
+ project: (item.projectName || item.projectCode || '').toString().trim(),
+ poNumber: (item.poNumber || '').toString().trim(),
+ hullNumber: (item.hullNumber || '').toString().trim(),
+ remainingAmount: formatCurrency(item.remainingAmount),
+ }))
+
+ // 일반 견적 품목 루프용 데이터 ({{#itemsList}} ... {{/itemsList}} 사용)
+ const itemsList = (items || []).map((item, idx) => {
+ const quantityRaw = item.quantity ?? item.qty ?? ''
+ const unitPriceRaw = item.contractUnitPrice ?? item.unitPrice ?? ''
+ const amountRaw =
+ item.contractAmount ??
+ (Number(quantityRaw) * Number(unitPriceRaw))
+
+ const quantityNum = Number(quantityRaw)
+ const hasQuantity = !isNaN(quantityNum)
+ const unitPriceNum = Number(unitPriceRaw)
+ const hasUnitPrice = !isNaN(unitPriceNum)
+ const hasAmountCalc = !isNaN(amountRaw as number)
+
+ const amount = hasAmountCalc ? formatCurrency(amountRaw) : ''
+
+ return {
+ no: idx + 1, // NO
+ hullNumber: (item.projectCode || basicInfo.projectCode || '').toString().trim(), // 호선번호
+ shipType: (item.projectName || basicInfo.projectName || '').toString().trim(), // 선종/선형
+ exportCountry: (basicInfo.vendorCountry || basicInfo.country || '').toString().trim(), // 수출국
+ itemName: (item.itemInfo || item.description || item.itemCode || '').toString().trim(), // 품목
+ unit: (item.quantityUnit || '').toString().trim(), // 단위
+ unitPrice: hasUnitPrice ? formatCurrency(unitPriceNum) : formatCurrency(unitPriceRaw), // 단가
+ amount, // 금액
+ remark: (item.remark || item.remarks || item.note || '').toString().trim(), // 비고
+ // 보존용 기존 필드
+ itemCode: (item.itemCode || item.itemInfo || '').toString().trim(),
+ quantity: hasQuantity ? quantityNum : (quantityRaw ?? ''),
+ }
+ })
+
+ // 루프 미지원 템플릿을 위한 품목 텍스트 fallback
+ const itemsTableText = itemsList.length > 0
+ ? itemsList.map(i => `${i.no}. ${i.hullNumber || '-'} / ${i.shipType || '-'} / ${i.exportCountry || '-'} / ${i.itemName || '-'} / 단위:${i.unit || '-'} / 단가:${i.unitPrice || '-'} / 금액:${i.amount || '-'} / 비고:${i.remark || '-'}`).join('\n')
+ : ''
+
// ═══════════════════════════════════════════════════════════════
// 변수 매핑 시작
@@ -296,7 +341,13 @@ export function mapContractDataToTemplateVariables(contractSummary: ContractSumm
// ----------------------------------
storageTableText: storageTableText, // {{storageTableText}} (fallback)
// PDFTron에서 배열을 받아 테이블 루프를 돌릴 수 있다면 아래 키를 사용
- storageList: storageItems,
+ storageList,
+
+ // ----------------------------------
+ // 일반 견적 품목 루프 (템플릿 표에 {{#itemsList}} 사용)
+ // ----------------------------------
+ itemsList,
+ itemsTableText,
}
// 3. 모든 키를 순회하며 undefined나 null을 빈 문자열로 변환 (안전장치)
@@ -306,5 +357,13 @@ export function mapContractDataToTemplateVariables(contractSummary: ContractSumm
}
})
- return variables
+ // 4. PDF 템플릿에서 추출한 {{ }} 변수명이 공백을 포함할 수 있어 trim 처리 후 매핑
+ const normalizedVariables: Record<string, any> = {}
+ Object.entries(variables).forEach(([key, value]) => {
+ const trimmedKey = key.trim()
+ const trimmedValue = typeof value === 'string' ? value.trim() : value
+ normalizedVariables[trimmedKey] = trimmedValue
+ })
+
+ return normalizedVariables
}