summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-20 11:53:08 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-20 11:53:08 +0900
commit01b7b192acc316b4f8969893d1d9bb6369425776 (patch)
treec885effdc80380ddabd4c52e9b24d98e8c9565e3 /lib
parent77cbcaf27c9de8b361a6c5a13f0eefb37fd0d0e5 (diff)
(김준회) PO 및 RFQ 관련 구매 피드백 반영
- PO매핑 오류 수정, 스키마 컬럼추가, 숫자포매팅 등 - rfq 담당자 문제 수정 등
Diffstat (limited to 'lib')
-rw-r--r--lib/po/vendor-table/service.ts127
-rw-r--r--lib/po/vendor-table/types.ts53
-rw-r--r--lib/po/vendor-table/vendor-po-actions.tsx7
-rw-r--r--lib/po/vendor-table/vendor-po-columns.tsx9
-rw-r--r--lib/po/vendor-table/vendor-po-items-dialog.tsx123
-rw-r--r--lib/rfq-last/service.ts2
-rw-r--r--lib/soap/ecc/mapper/po-mapper.ts254
-rw-r--r--lib/soap/ecc/mapper/rfq-and-pr-mapper.ts3
-rw-r--r--lib/utils.ts2
9 files changed, 453 insertions, 127 deletions
diff --git a/lib/po/vendor-table/service.ts b/lib/po/vendor-table/service.ts
index 224dd2f1..bc4a693b 100644
--- a/lib/po/vendor-table/service.ts
+++ b/lib/po/vendor-table/service.ts
@@ -366,14 +366,60 @@ export async function getVendorPOItems(contractId: number): Promise<VendorPOItem
id: contractItems.id,
contractId: contractItems.contractId,
itemId: contractItems.itemId,
+ itemNo: contractItems.itemNo, // EBELP - 구매오더품목번호 (품번)
+ prNo: contractItems.prNo, // BANFN - 구매요청번호 (PR번호)
+ prItemNo: contractItems.prItemNo, // BNFPO - 구매요청품목번호 (PR 품번)
+ materialGroup: contractItems.materialGroup, // MATKL - 자재그룹
+ weight: contractItems.weight, // NTGEW - 순중량
+ weightUnit: contractItems.weightUnit, // GEWEI - 중량단위
+ totalWeight: contractItems.totalWeight, // BRGEW - 총중량
description: contractItems.description,
quantity: contractItems.quantity,
- unitPrice: contractItems.unitPrice,
+ unitPrice: contractItems.unitPrice, // NETPR - 구매단가
+
+ // 가격 관련 추가 필드
+ PEINH: contractItems.PEINH, // 가격단위값
+ BPRME: contractItems.BPRME, // 구매단가단위
+ ZNETPR: contractItems.ZNETPR, // 발주단가
+ ZREF_NETPR: contractItems.ZREF_NETPR, // 참조단가
+
taxRate: contractItems.taxRate,
taxAmount: contractItems.taxAmount,
+ taxType: contractItems.taxType, // MWSKZ - 매출부가가치세코드
totalLineAmount: contractItems.totalLineAmount,
remark: contractItems.remark,
+ // SAP ECC 추가 필드들
+ ZPO_UNIT: contractItems.ZPO_UNIT, // 구매오더수량단위
+ NETWR: contractItems.NETWR, // 오더정가 (최종 정가)
+ BRTWR: contractItems.BRTWR, // 오더총액 (기본 총액)
+ ZPDT_EXDS_AMT: contractItems.ZPDT_EXDS_AMT, // 할인/할증금액
+
+ // 위치 정보
+ WERKS: contractItems.WERKS, // 플랜트코드
+ LGORT: contractItems.LGORT, // 저장위치
+
+ // RFQ 추적
+ ANFNR: contractItems.ANFNR, // RFQ번호
+ ANFPS: contractItems.ANFPS, // RFQ품목번호
+
+ // 자재 추적
+ ZPO_LOT_NO: contractItems.ZPO_LOT_NO, // Steel Material Marking No
+
+ // 볼륨 정보
+ VOLUM: contractItems.VOLUM, // 볼륨
+ VOLEH: contractItems.VOLEH, // 볼륨단위
+
+ // 날짜 정보
+ ZPO_DLV_DT: contractItems.ZPO_DLV_DT, // PO납기일자
+ ZPLN_ST_DT: contractItems.ZPLN_ST_DT, // 예정시작일자
+ ZPLN_ED_DT: contractItems.ZPLN_ED_DT, // 예정종료일자
+ LFDAT: contractItems.LFDAT, // PR Delivery Date
+ ZRCV_DT: contractItems.ZRCV_DT, // 구매접수일자
+
+ // 기타
+ ZCON_IND: contractItems.ZCON_IND, // 시리즈구분
+
// contracts 테이블 필드들
contractNo: contracts.contractNo,
@@ -393,33 +439,74 @@ export async function getVendorPOItems(contractId: number): Promise<VendorPOItem
.orderBy(contractItems.id);
// VendorPOItem 타입으로 변환
+ // SAP에서 제공하지 않는 데이터는 임의로 만들지 않고 그대로 표시 (FE에서 '-' 처리)
const vendorPOItems: VendorPOItem[] = rawItems.map(row => ({
contractNo: row.contractNo || '',
- itemNo: row.itemCode || 'AUTO-ITEM', // mock 데이터용
- prNo: `PR-${new Date().getFullYear()}-${String(row.id).padStart(4, '0')}`, // mock 데이터용
- materialGroup: row.packageCode || 'Unknown Group',
- priceStandard: 'FOB', // mock 데이터용 기본값
+ itemNo: row.itemNo || '', // EBELP - 구매오더품목번호 (품번)
+ prNo: row.prNo || '', // BANFN - 구매요청번호 (PR번호)
+ prItemNo: row.prItemNo || '', // BNFPO - 구매요청품목번호 (PR 품번)
+ materialGroup: row.materialGroup || '', // MATKL - 자재그룹
+ priceStandard: '', // SAP에서 제공되지 않음 - FE에서 '-' 처리
materialNo: row.itemCode || '',
itemDescription: row.itemName || '',
materialSpec: row.description || '',
- fittingNo: undefined, // contract_items에 없는 필드
- cert: undefined, // contract_items에 없는 필드
+ fittingNo: undefined, // SAP에서 제공되지 않음
+ cert: undefined, // SAP에서 제공되지 않음
material: row.gradeMaterial || undefined,
specification: row.description || '',
- quantity: row.quantity || 1,
- quantityUnit: row.unitOfMeasure || 'EA',
- weight: undefined, // contract_items에 없는 필드
- weightUnit: undefined, // contract_items에 없는 필드
- totalWeight: undefined, // contract_items에 없는 필드
- unitPrice: row.unitPrice ? Number(row.unitPrice) : 0,
- priceUnit: 'KRW', // 기본값
- priceUnitValue: '원/EA', // 기본값
- contractAmount: row.totalLineAmount ? Number(row.totalLineAmount) : 0,
- adjustmentAmount: undefined, // contract_items에 없는 필드
- deliveryDate: new Date().toISOString().split('T')[0], // 기본값 (오늘 날짜)
- vatType: row.taxRate && Number(row.taxRate) > 0 ? '과세' : '면세',
+ quantity: row.quantity ?? 0, // null/undefined면 0, 0이면 0 표시
+ quantityUnit: row.ZPO_UNIT || row.unitOfMeasure || '', // SAP 단위 우선, 없으면 items 테이블 단위
+ ZPO_UNIT: row.ZPO_UNIT || undefined, // SAP 구매오더수량단위
+ weight: row.weight ? Number(row.weight) : undefined, // NTGEW - 순중량
+ weightUnit: row.weightUnit || undefined, // GEWEI - 중량단위
+ totalWeight: row.totalWeight ? Number(row.totalWeight) : undefined, // BRGEW - 총중량
+ unitPrice: row.unitPrice ? Number(row.unitPrice) : 0, // NETPR - 구매단가
+
+ // 가격 관련 추가 필드
+ PEINH: row.PEINH ?? undefined, // 가격단위값
+ BPRME: row.BPRME || undefined, // 구매단가단위
+ ZNETPR: row.ZNETPR ? Number(row.ZNETPR) : undefined, // 발주단가
+ ZREF_NETPR: row.ZREF_NETPR ? Number(row.ZREF_NETPR) : undefined, // 참조단가
+
+ priceUnit: row.BPRME || '', // BPRME을 priceUnit으로 사용
+ priceUnitValue: row.PEINH ? String(row.PEINH) : '', // PEINH를 priceUnitValue로 사용
+ contractAmount: row.NETWR ? Number(row.NETWR) : (row.totalLineAmount ? Number(row.totalLineAmount) : 0), // NETWR 우선, 없으면 totalLineAmount
+ adjustmentAmount: row.ZPDT_EXDS_AMT ? Number(row.ZPDT_EXDS_AMT) : undefined, // SAP 조정금액
+
+ // SAP ECC 금액 필드
+ NETWR: row.NETWR ? Number(row.NETWR) : undefined, // 오더정가 (최종 정가)
+ BRTWR: row.BRTWR ? Number(row.BRTWR) : undefined, // 오더총액 (기본 총액)
+ ZPDT_EXDS_AMT: row.ZPDT_EXDS_AMT ? Number(row.ZPDT_EXDS_AMT) : undefined, // 할인/할증금액
+
+ // 위치 정보
+ WERKS: row.WERKS || undefined, // 플랜트코드
+ LGORT: row.LGORT || undefined, // 저장위치
+
+ // RFQ 추적
+ ANFNR: row.ANFNR || undefined, // RFQ번호
+ ANFPS: row.ANFPS || undefined, // RFQ품목번호
+
+ // 자재 추적
+ ZPO_LOT_NO: row.ZPO_LOT_NO || undefined, // Steel Material Marking No
+
+ // 볼륨 정보
+ VOLUM: row.VOLUM ? Number(row.VOLUM) : undefined, // 볼륨
+ VOLEH: row.VOLEH || undefined, // 볼륨단위
+
+ deliveryDate: row.ZPO_DLV_DT || '', // SAP 납기일자, 없으면 빈 문자열 (FE에서 '-' 처리)
+ ZPO_DLV_DT: row.ZPO_DLV_DT || undefined, // SAP PO납기일자
+ ZPLN_ST_DT: row.ZPLN_ST_DT || undefined, // SAP 예정시작일자
+ ZPLN_ED_DT: row.ZPLN_ED_DT || undefined, // SAP 예정종료일자
+ LFDAT: row.LFDAT || undefined, // PR Delivery Date
+ ZRCV_DT: row.ZRCV_DT || undefined, // 구매접수일자
+
+ // 기타
+ ZCON_IND: row.ZCON_IND || undefined, // 시리즈구분 (SS 등)
+
+ vatType: row.taxType || '', // MWSKZ - 매출부가가치세코드 (V1, V2 등)
steelSpec: row.steelType || undefined,
- prManager: 'AUTO-MANAGER', // mock 데이터용 기본값
+ prManager: '', // SAP에서 제공되지 않음 - FE에서 '-' 처리
+ remark: row.remark || undefined,
}));
return vendorPOItems;
diff --git a/lib/po/vendor-table/types.ts b/lib/po/vendor-table/types.ts
index f8bc3ea2..e318ff39 100644
--- a/lib/po/vendor-table/types.ts
+++ b/lib/po/vendor-table/types.ts
@@ -76,8 +76,9 @@ export interface VendorPO {
export interface VendorPOItem {
contractNo: string // PO/계약번호
- itemNo: string // 품번
- prNo: string // P/R번호
+ itemNo: string // 품번 (EBELP)
+ prNo: string // P/R번호 (BANFN)
+ prItemNo: string // PR 품번 (BNFPO)
materialGroup: string // 자재그룹(명)
priceStandard: string // 단가기준
materialNo: string // 자재번호
@@ -89,16 +90,54 @@ export interface VendorPOItem {
material?: string // 재질
specification: string // 규격
quantity: number // 수량
- quantityUnit: string // 수량단위
+ quantityUnit: string // 수량단위 (기존)
+ ZPO_UNIT?: string // SAP 구매오더수량단위
weight?: number // 중량
weightUnit?: string // 중량단위
totalWeight?: number // 총중량
- unitPrice: number // 단가기준 (단가)
+ unitPrice: number // 단가기준 (단가 - NETPR)
+
+ // 가격 관련 추가 필드
+ PEINH?: number // 가격단위값 (예: 1, 10, 100)
+ BPRME?: string // 구매단가단위 (EA, KG 등)
+ ZNETPR?: number // 발주단가
+ ZREF_NETPR?: number // 참조단가
+
priceUnit: string // 단가단위
priceUnitValue: string // 가격단위값
- contractAmount: number // PO계약금액
- adjustmentAmount?: number // 조정금액
- deliveryDate: string // 납기일자
+ contractAmount: number // PO계약금액 (기존 필드 - 호환성 유지)
+ adjustmentAmount?: number // 조정금액 (기존 필드 - 호환성 유지)
+
+ // SAP ECC 금액 필드 (금액 관계: NETWR = BRTWR + ZPDT_EXDS_AMT)
+ NETWR?: number // 오더정가 (최종 정가)
+ BRTWR?: number // 오더총액 (기본 총액)
+ ZPDT_EXDS_AMT?: number // 할인/할증금액 (조정금액: 할인은 음수, 할증은 양수)
+
+ // 위치 정보
+ WERKS?: string // 플랜트코드
+ LGORT?: string // 저장위치
+
+ // RFQ 추적
+ ANFNR?: string // RFQ번호
+ ANFPS?: string // RFQ품목번호
+
+ // 자재 추적
+ ZPO_LOT_NO?: string // Steel Material Marking No
+
+ // 볼륨 정보
+ VOLUM?: number // 볼륨
+ VOLEH?: string // 볼륨단위
+
+ deliveryDate: string // 납기일자 (기존)
+ ZPO_DLV_DT?: string // SAP PO납기일자
+ ZPLN_ST_DT?: string // SAP 예정시작일자
+ ZPLN_ED_DT?: string // SAP 예정종료일자
+ LFDAT?: string // PR Delivery Date
+ ZRCV_DT?: string // 구매접수일자
+
+ // 기타
+ ZCON_IND?: string // 시리즈구분 (SS 등)
+
vatType: string // VAT구분
steelSpec?: string // 철의장 SPEC
prManager: string // P/R 담당자
diff --git a/lib/po/vendor-table/vendor-po-actions.tsx b/lib/po/vendor-table/vendor-po-actions.tsx
index 329d91fd..08fe3f88 100644
--- a/lib/po/vendor-table/vendor-po-actions.tsx
+++ b/lib/po/vendor-table/vendor-po-actions.tsx
@@ -160,13 +160,6 @@ export function VendorPOActions({ row, setRowAction }: VendorPOActionsProps) {
<DropdownMenuContent align="end">
<DropdownMenuLabel>액션</DropdownMenuLabel>
<DropdownMenuItem
- onClick={() => setRowAction({ row, type: "view-items" })}
- >
- <FileTextIcon className="mr-2 h-4 w-4" />
- 상세품목 보기
- </DropdownMenuItem>
- <DropdownMenuSeparator />
- <DropdownMenuItem
onClick={handlePcrCreate}
disabled={isLoading || !canCreatePcr}
>
diff --git a/lib/po/vendor-table/vendor-po-columns.tsx b/lib/po/vendor-table/vendor-po-columns.tsx
index c954b872..de44adce 100644
--- a/lib/po/vendor-table/vendor-po-columns.tsx
+++ b/lib/po/vendor-table/vendor-po-columns.tsx
@@ -9,6 +9,7 @@ import { Checkbox } from "@/components/ui/checkbox"
import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
import { VendorPO, VendorPOActionType } from "./types"
import { VendorPOActions } from "./vendor-po-actions"
+import { formatNumber } from "@/lib/utils"
interface GetVendorColumnsProps {
setRowAction: React.Dispatch<React.SetStateAction<{ row: { original: VendorPO }; type: VendorPOActionType } | null>>
@@ -193,7 +194,13 @@ export function getVendorColumns({ setRowAction, selectedRows = [], onRowSelect
),
cell: ({ row }) => {
const amount = row.getValue("totalAmount") as string | number
- return <div className="text-sm text-right font-mono">{amount || '-'}</div>
+ const currency = row.getValue("currency") as string
+
+ // 통화별 소수점 자리수 결정 (KRW, JPY: 0자리, 나머지: 2자리)
+ const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2
+ const formattedAmount = formatNumber(amount, decimals)
+
+ return <div className="text-sm text-right font-mono">{formattedAmount}</div>
},
size: 120,
},
diff --git a/lib/po/vendor-table/vendor-po-items-dialog.tsx b/lib/po/vendor-table/vendor-po-items-dialog.tsx
index 647950c4..d88d88d1 100644
--- a/lib/po/vendor-table/vendor-po-items-dialog.tsx
+++ b/lib/po/vendor-table/vendor-po-items-dialog.tsx
@@ -16,10 +16,10 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table"
-import { Badge } from "@/components/ui/badge"
import { Skeleton } from "@/components/ui/skeleton"
import { VendorPO, VendorPOItem } from "./types"
import { getVendorPOItemsByContractNo } from "./service"
+import { formatNumber } from "@/lib/utils"
interface VendorPOItemsDialogProps {
open: boolean
@@ -99,8 +99,9 @@ export function VendorPOItemsDialog({ open, onOpenChange, po }: VendorPOItemsDia
<TableHead className="min-w-[120px] whitespace-nowrap">PO/계약번호</TableHead>
<TableHead className="min-w-[100px] whitespace-nowrap">품번</TableHead>
<TableHead className="min-w-[100px] whitespace-nowrap">P/R번호</TableHead>
+ <TableHead className="min-w-[100px] whitespace-nowrap">PR 품번</TableHead>
<TableHead className="min-w-[120px] whitespace-nowrap">자재그룹(명)</TableHead>
- <TableHead className="min-w-[100px] whitespace-nowrap">단가기준</TableHead>
+ {/* <TableHead className="min-w-[100px] whitespace-nowrap">단가기준</TableHead> */}
<TableHead className="min-w-[100px] whitespace-nowrap">자재번호</TableHead>
<TableHead className="min-w-[200px] whitespace-nowrap">품목/자재내역</TableHead>
{/* <TableHead className="min-w-[200px] whitespace-nowrap">자재내역사양</TableHead>
@@ -114,15 +115,31 @@ export function VendorPOItemsDialog({ open, onOpenChange, po }: VendorPOItemsDia
<TableHead className="min-w-[80px] text-right whitespace-nowrap">중량</TableHead>
<TableHead className="min-w-[80px] whitespace-nowrap">중량단위</TableHead>
<TableHead className="min-w-[100px] text-right whitespace-nowrap">총중량</TableHead>
- <TableHead className="min-w-[100px] text-right whitespace-nowrap">단가기준</TableHead>
- <TableHead className="min-w-[80px] whitespace-nowrap">단가단위</TableHead>
- <TableHead className="min-w-[100px] whitespace-nowrap">가격단위값</TableHead>
- <TableHead className="min-w-[120px] text-right whitespace-nowrap">PO계약금액</TableHead>
- <TableHead className="min-w-[100px] text-right whitespace-nowrap">조정금액</TableHead>
+ <TableHead className="min-w-[100px] text-right whitespace-nowrap">단가(NETPR)</TableHead>
+ <TableHead className="min-w-[80px] whitespace-nowrap">단가단위(BPRME)</TableHead>
+ <TableHead className="min-w-[80px] text-right whitespace-nowrap">가격단위값(PEINH)</TableHead>
+ <TableHead className="min-w-[100px] text-right whitespace-nowrap">참조단가</TableHead>
+ <TableHead className="min-w-[100px] text-right whitespace-nowrap">발주단가</TableHead>
+ <TableHead className="min-w-[120px] text-right whitespace-nowrap">기본총액(BRTWR)</TableHead>
+ <TableHead className="min-w-[120px] text-right whitespace-nowrap">조정금액(ZPDT_EXDS_AMT)</TableHead>
+ <TableHead className="min-w-[120px] text-right whitespace-nowrap">최종정가(NETWR)</TableHead>
+ <TableHead className="min-w-[100px] whitespace-nowrap">예정시작일자</TableHead>
<TableHead className="min-w-[100px] whitespace-nowrap">납기일자</TableHead>
- <TableHead className="min-w-[80px] whitespace-nowrap">VAT구분</TableHead>
+ <TableHead className="min-w-[100px] whitespace-nowrap">예정종료일자</TableHead>
+ <TableHead className="min-w-[100px] whitespace-nowrap">PR납기일</TableHead>
+ <TableHead className="min-w-[100px] whitespace-nowrap">구매접수일</TableHead>
+ <TableHead className="min-w-[80px] whitespace-nowrap">세금코드</TableHead>
+ <TableHead className="min-w-[100px] whitespace-nowrap">플랜트</TableHead>
+ <TableHead className="min-w-[100px] whitespace-nowrap">저장위치</TableHead>
+ <TableHead className="min-w-[100px] whitespace-nowrap">RFQ번호</TableHead>
+ <TableHead className="min-w-[80px] whitespace-nowrap">RFQ품번</TableHead>
+ <TableHead className="min-w-[150px] whitespace-nowrap">LOT No.</TableHead>
+ <TableHead className="min-w-[80px] text-right whitespace-nowrap">볼륨</TableHead>
+ <TableHead className="min-w-[80px] whitespace-nowrap">볼륨단위</TableHead>
+ <TableHead className="min-w-[80px] whitespace-nowrap">시리즈구분</TableHead>
+ <TableHead className="min-w-[150px] whitespace-nowrap">비고</TableHead>
{/* <TableHead className="min-w-[120px] whitespace-nowrap">철의장 SPEC</TableHead> */}
- <TableHead className="min-w-[100px] whitespace-nowrap">P/R 담당자</TableHead>
+ {/* <TableHead className="min-w-[100px] whitespace-nowrap">P/R 담당자</TableHead> */}
</TableRow>
</TableHeader>
<TableBody>
@@ -131,8 +148,9 @@ export function VendorPOItemsDialog({ open, onOpenChange, po }: VendorPOItemsDia
<TableCell className="font-medium">{item.contractNo || '-'}</TableCell>
<TableCell>{item.itemNo || '-'}</TableCell>
<TableCell>{item.prNo || '-'}</TableCell>
+ <TableCell>{item.prItemNo || '-'}</TableCell>
<TableCell>{item.materialGroup || '-'}</TableCell>
- <TableCell>{item.priceStandard || '-'}</TableCell>
+ {/* <TableCell>{item.priceStandard || '-'}</TableCell> */}
<TableCell className="font-mono text-sm">{item.materialNo || '-'}</TableCell>
<TableCell className="max-w-[200px]">
<div className="truncate" title={item.itemDescription || ''}>
@@ -151,7 +169,7 @@ export function VendorPOItemsDialog({ open, onOpenChange, po }: VendorPOItemsDia
<TableCell>{item.material || '-'}</TableCell>
<TableCell>{item.specification || '-'}</TableCell> */}
<TableCell className="text-right font-mono">
- {item.quantity?.toLocaleString() || '-'}
+ {item.quantity !== undefined && item.quantity !== null ? item.quantity.toLocaleString() : '-'}
</TableCell>
<TableCell>{item.quantityUnit || '-'}</TableCell>
<TableCell className="text-right font-mono">
@@ -162,20 +180,76 @@ export function VendorPOItemsDialog({ open, onOpenChange, po }: VendorPOItemsDia
{item.totalWeight ? item.totalWeight.toLocaleString() : '-'}
</TableCell>
<TableCell className="text-right font-mono">
- {item.unitPrice?.toLocaleString() || '-'}
+ {(() => {
+ const currency = po?.currency || ''
+ const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2
+ return formatNumber(item.unitPrice, decimals)
+ })()}
</TableCell>
- <TableCell>{item.priceUnit || '-'}</TableCell>
- <TableCell>{item.priceUnitValue || '-'}</TableCell>
- <TableCell className="text-right font-mono font-semibold">
- {item.contractAmount?.toLocaleString() || '-'}
+ <TableCell>{item.BPRME || '-'}</TableCell>
+ <TableCell className="text-right">{item.PEINH ?? '-'}</TableCell>
+ <TableCell className="text-right font-mono">
+ {(() => {
+ const currency = po?.currency || ''
+ const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2
+ return item.ZREF_NETPR ? formatNumber(item.ZREF_NETPR, decimals) : '-'
+ })()}
+ </TableCell>
+ <TableCell className="text-right font-mono">
+ {(() => {
+ const currency = po?.currency || ''
+ const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2
+ return item.ZNETPR ? formatNumber(item.ZNETPR, decimals) : '-'
+ })()}
</TableCell>
<TableCell className="text-right font-mono">
- {item.adjustmentAmount ? item.adjustmentAmount.toLocaleString() : '-'}
+ {(() => {
+ const currency = po?.currency || ''
+ const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2
+ return item.BRTWR ? formatNumber(item.BRTWR, decimals) : '-'
+ })()}
+ </TableCell>
+ <TableCell className="text-right font-mono">
+ {(() => {
+ const currency = po?.currency || ''
+ const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2
+ return item.ZPDT_EXDS_AMT ? formatNumber(item.ZPDT_EXDS_AMT, decimals) : '-'
+ })()}
+ </TableCell>
+ <TableCell className="text-right font-mono font-semibold">
+ {(() => {
+ const currency = po?.currency || ''
+ const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2
+ return item.NETWR ? formatNumber(item.NETWR, decimals) : '-'
+ })()}
</TableCell>
- <TableCell>{item.deliveryDate || '-'}</TableCell>
+ <TableCell>{item.ZPLN_ST_DT || '-'}</TableCell>
+ <TableCell>{item.ZPO_DLV_DT || item.deliveryDate || '-'}</TableCell>
+ <TableCell>{item.ZPLN_ED_DT || '-'}</TableCell>
+ <TableCell>{item.LFDAT || '-'}</TableCell>
+ <TableCell>{item.ZRCV_DT || '-'}</TableCell>
<TableCell>{item.vatType || '-'}</TableCell>
+ <TableCell>{item.WERKS || '-'}</TableCell>
+ <TableCell>{item.LGORT || '-'}</TableCell>
+ <TableCell>{item.ANFNR || '-'}</TableCell>
+ <TableCell>{item.ANFPS || '-'}</TableCell>
+ <TableCell className="max-w-[150px]">
+ <div className="truncate" title={item.ZPO_LOT_NO || ''}>
+ {item.ZPO_LOT_NO || '-'}
+ </div>
+ </TableCell>
+ <TableCell className="text-right font-mono">
+ {item.VOLUM ? item.VOLUM.toLocaleString() : '-'}
+ </TableCell>
+ <TableCell>{item.VOLEH || '-'}</TableCell>
+ <TableCell>{item.ZCON_IND || '-'}</TableCell>
+ <TableCell className="max-w-[150px]">
+ <div className="truncate" title={item.remark || ''}>
+ {item.remark || '-'}
+ </div>
+ </TableCell>
{/* <TableCell>{item.steelSpec || '-'}</TableCell> */}
- <TableCell>{item.prManager || '-'}</TableCell>
+ {/* <TableCell>{item.prManager || '-'}</TableCell> */}
</TableRow>
))}
</TableBody>
@@ -190,7 +264,16 @@ export function VendorPOItemsDialog({ open, onOpenChange, po }: VendorPOItemsDia
총 {items.length}개 품목
</div>
<div className="text-sm font-medium">
- 총 계약금액: {items.reduce((sum, item) => sum + item.contractAmount, 0).toLocaleString()} 원
+ {(() => {
+ // NETWR(최종정가) 합계 계산
+ const totalNETWR = items.reduce((sum, item) => sum + (item.NETWR || item.contractAmount || 0), 0)
+ // 통화별 소수점 자리수 결정 (KRW, JPY: 0자리, 나머지: 2자리)
+ const currency = po?.currency || ''
+ const decimals = (currency === 'KRW' || currency === 'JPY') ? 0 : 2
+ const formattedAmount = formatNumber(totalNETWR, decimals)
+
+ return `총 계약금액(NETWR): ${currency} ${formattedAmount}`
+ })()}
</div>
</div>
)}
diff --git a/lib/rfq-last/service.ts b/lib/rfq-last/service.ts
index 52d67280..7ebec795 100644
--- a/lib/rfq-last/service.ts
+++ b/lib/rfq-last/service.ts
@@ -2645,7 +2645,7 @@ export async function getRfqFullInfo(rfqId: number): Promise<RfqFullInfo> {
picCode: rfq.picCode,
picName: rfq.picName,
picUserName: picUser?.name ?? null,
- picTeam: picUser?.department ?? null, // users 테이블에 department 필드가 있다고 가정
+ picTeam: picUser?.deptName ?? null, // users 테이블의 deptName 필드 사용
// 설계 담당자
engPicName: rfq.EngPicName,
diff --git a/lib/soap/ecc/mapper/po-mapper.ts b/lib/soap/ecc/mapper/po-mapper.ts
index 4f8b8034..fef85662 100644
--- a/lib/soap/ecc/mapper/po-mapper.ts
+++ b/lib/soap/ecc/mapper/po-mapper.ts
@@ -42,21 +42,33 @@ export async function mapECCPOHeaderToBusiness(
debugLog('ECC PO 헤더 매핑 시작', { ebeln: eccHeader.EBELN });
// projectId 찾기 (PSPID → projects.code 기반)
+ // PSPID가 없으면 불완전 데이터로 처리하고 에러 발생
let projectId: number | null = null;
- if (eccHeader.PSPID) {
- try {
- const project = await db.query.projects.findFirst({
- where: eq(projects.code, eccHeader.PSPID),
+ if (!eccHeader.PSPID) {
+ debugError('PSPID 누락 - 불완전 데이터', {
+ ebeln: eccHeader.EBELN,
+ ztitle: eccHeader.ZTITLE
+ });
+ throw new Error(`PSPID가 없는 불완전 데이터: EBELN=${eccHeader.EBELN}`);
+ }
+
+ try {
+ const project = await db.query.projects.findFirst({
+ where: eq(projects.code, eccHeader.PSPID),
+ });
+ if (project) {
+ projectId = project.id;
+ debugLog('프로젝트 ID 찾음', { pspid: eccHeader.PSPID, projectId });
+ } else {
+ debugError('프로젝트를 찾을 수 없음 - 불완전 데이터', {
+ pspid: eccHeader.PSPID,
+ ebeln: eccHeader.EBELN
});
- if (project) {
- projectId = project.id;
- debugLog('프로젝트 ID 찾음', { pspid: eccHeader.PSPID, projectId });
- } else {
- debugError('프로젝트를 찾을 수 없음', { pspid: eccHeader.PSPID });
- }
- } catch (error) {
- debugError('프로젝트 조회 중 오류', { pspid: eccHeader.PSPID, error });
+ throw new Error(`프로젝트를 찾을 수 없습니다: PSPID=${eccHeader.PSPID}, EBELN=${eccHeader.EBELN}`);
}
+ } catch (error) {
+ debugError('프로젝트 조회 중 오류', { pspid: eccHeader.PSPID, error });
+ throw error;
}
// vendorId 찾기 (LIFNR 기반)
@@ -104,14 +116,13 @@ export async function mapECCPOHeaderToBusiness(
}
};
- // projectId와 vendorId 필수 체크
- if (!projectId) {
- debugError('프로젝트를 찾을 수 없어 매핑을 건너뜁니다', { pspid: eccHeader.PSPID });
- throw new Error(`프로젝트를 찾을 수 없습니다: PSPID=${eccHeader.PSPID}`);
- }
+ // vendorId 필수 체크 (projectId는 위에서 이미 체크됨)
if (!vendorId) {
- debugError('벤더를 찾을 수 없어 매핑을 건너뜁니다', { lifnr: eccHeader.LIFNR });
- throw new Error(`벤더를 찾을 수 없습니다: LIFNR=${eccHeader.LIFNR}`);
+ debugError('벤더를 찾을 수 없어 매핑을 건너뜁니다', {
+ lifnr: eccHeader.LIFNR,
+ ebeln: eccHeader.EBELN
+ });
+ throw new Error(`벤더를 찾을 수 없습니다: LIFNR=${eccHeader.LIFNR}, EBELN=${eccHeader.EBELN}`);
}
// 계약서 내용 구성 (ZMM_NOTE에서 가져옴)
@@ -134,10 +145,12 @@ export async function mapECCPOHeaderToBusiness(
contractNo: eccHeader.EBELN || '', // EBELN - 구매오더번호
contractName: eccHeader.ZTITLE || eccHeader.EBELN || '', // ZTITLE - 발주제목
contractContent, // 계약서 내용
- status: eccHeader.ZPO_CNFM_STAT || 'ACTIVE', // ZPO_CNFM_STAT - 구매오더확인상태
+ // TODO: ZPO_CNFM_STAT 값을 ContractStatus enum으로 매핑하는 로직 필요
+ // 현재는 ECC에서 받은 값을 그대로 사용하되, null인 경우 undefined로 변환
+ status: eccHeader.ZPO_CNFM_STAT || undefined, // ZPO_CNFM_STAT - 구매오더확인상태
startDate: parseDate(eccHeader.ZPO_DT || null), // ZPO_DT - 발주일자
- endDate: null, // ZMM_DT에서 가져와야 함
- deliveryDate: null, // ZMM_DT에서 가져와야 함
+ endDate: null, // TODO: ZMM_DT의 ZPLN_ED_DT(예정종료일자) 중 최대값으로 계산 필요
+ deliveryDate: null, // TODO: ZMM_DT의 ZPO_DLV_DT(PO납기일자) 중 최대값으로 계산 필요
// SAP ECC 기본 필드들
paymentTerms: eccHeader.ZTERM || null, // ZTERM - 지급조건코드
@@ -151,7 +164,8 @@ export async function mapECCPOHeaderToBusiness(
purchaseOrg: eccHeader.EKORG || null, // EKORG - 구매조직코드
purchaseGroup: eccHeader.EKGRP || null, // EKGRP - 구매그룹코드
exchangeRate: eccHeader.WKURS ? parseAmount(eccHeader.WKURS) : null, // WKURS - 환율
- poConfirmStatus: eccHeader.ZPO_CNFM_STAT || null, // ZPO_CNFM_STAT - 구매오더확인상태
+ poConfirmStatus: eccHeader.ZPO_CNFM_STAT || null, // ZPO_CNFM_STAT - 구매오더확인상태 (기존 필드)
+ ZPO_CNFM_STAT: eccHeader.ZPO_CNFM_STAT || null, // SAP 구매오더확인상태 원본값
// 계약/보증 관련
contractGuaranteeCode: eccHeader.ZCNRT_GRNT_CD || null, // ZCNRT_GRNT_CD - 계약보증코드
@@ -162,7 +176,7 @@ export async function mapECCPOHeaderToBusiness(
// 금액 관련
budgetAmount: parseAmount(eccHeader.ZBGT_AMT || null), // ZBGT_AMT - 예산금액
budgetCurrency: eccHeader.ZBGT_CURR || null, // ZBGT_CURR - 예산금액 통화키
- currency: eccHeader.ZPO_CURR || 'KRW', // ZPO_CURR - 통화키
+ currency: eccHeader.ZPO_CURR || null, // ZPO_CURR - 통화키 (null 가능)
totalAmount: parseAmount(eccHeader.ZPO_AMT || null), // ZPO_AMT - 발주금액
totalAmountKrw: parseAmount(eccHeader.ZPO_AMT_KRW || null), // ZPO_AMT_KRW - 발주금액 KRW
@@ -185,12 +199,12 @@ export async function mapECCPOHeaderToBusiness(
netTotal: parseAmount(eccHeader.ZPO_AMT || null), // ZPO_AMT와 동일
remarks: eccHeader.ETC_2 || null, // ETC_2 - 확장2
- // 기본값들
- discount: null,
- tax: null,
- shippingFee: null,
- partialShippingAllowed: false,
- partialPaymentAllowed: false,
+ // 기본값들 (ECC 인터페이스에서 제공되지 않는 필드들)
+ discount: null, // TODO: 개별 품목별로는 할인 정보 있을 수 있음 (ZPDT_EXDS_AMT - 할인/할증금액)
+ tax: null, // TODO: 개별 품목별 세금 합산 필요
+ shippingFee: null, // TODO: 운송비 정보 (ZTRNS_UPR - 운송단가)가 있으나 헤더 레벨 집계 로직 필요
+ partialShippingAllowed: false, // ECC에서 제공되지 않음
+ partialPaymentAllowed: false, // ECC에서 제공되지 않음
version: 1,
};
@@ -231,25 +245,25 @@ export async function mapECCPODetailToBusiness(
// 2. 아이템이 없으면 새로 생성
debugLog('아이템이 없어서 새로 생성', { matnr: eccDetail.MATNR });
- // 프로젝트 정보 설정
- const projectNo = eccDetail.PSPID || 'DEFAULT';
- const packageCode = 'AUTO_GENERATED'; // 기본값으로 설정
+ // PSPID를 ProjectNo로 사용 (projects.code와 매핑됨)
+ // PSPID가 없으면 불완전 데이터이므로 이미 상위에서 에러 발생되어 여기까지 오지 않음
+ const projectNo = eccDetail.PSPID || 'UNKNOWN'; // notNull 필드이므로 기본값 필요
const newItemData = {
ProjectNo: projectNo,
itemCode: eccDetail.MATNR,
- itemName: eccDetail.MAKTX || eccDetail.MATNR || 'Unknown Item',
- packageCode: packageCode,
- smCode: null, // SM 코드는 ECC 데이터에서 제공되지 않음
+ itemName: eccDetail.MAKTX || eccDetail.MATNR, // notNull 필드
+ packageCode: null, // nullable 필드 - ECC에서 제공되지 않으므로 null
+ smCode: null, // ECC 데이터에서 제공되지 않음
description: eccDetail.MAKTX || null,
parentItemCode: null,
itemLevel: null,
deleteFlag: 'N',
- unitOfMeasure: null,
- steelType: null,
- gradeMaterial: null,
+ unitOfMeasure: eccDetail.ZPO_UNIT || null, // ZPO_UNIT - 구매오더수량단위
+ steelType: null, // ECC 데이터에서 제공되지 않음
+ gradeMaterial: null, // ECC 데이터에서 제공되지 않음
changeDate: null,
- baseUnitOfMeasure: null,
+ baseUnitOfMeasure: eccDetail.BPRME || null, // BPRME - 구매단가단위
};
const [insertedItem] = await db.insert(items).values(newItemData).returning({ id: items.id });
@@ -291,49 +305,117 @@ export async function mapECCPODetailToBusiness(
}
};
- // 세율 계산 (MWSKZ 기반)
- const calculateTaxRate = (mwskz: string | null): string | null => {
- if (!mwskz) return null;
- // 일반적인 한국 세율 매핑 (실제 비즈니스 로직에 따라 조정 필요)
- switch (mwskz) {
- case '10':
- return '10.00';
- case '00':
- return '0.00';
- default:
- return '10.00'; // 기본값
- }
- };
-
const quantity = parseQuantity(eccDetail.MENGE || null);
const unitPrice = parseAmount(eccDetail.NETPR || null);
- const taxRate = calculateTaxRate(eccDetail.MWSKZ || null);
- const totalLineAmount = parseAmount(eccDetail.NETWR || null);
+
+ // SAP ECC 금액 필드
+ // 금액 관계: NETWR = BRTWR + ZPDT_EXDS_AMT
+ // - BRTWR: 기본 오더총액
+ // - ZPDT_EXDS_AMT: 조정금액 (할인은 음수, 할증은 양수)
+ // - NETWR: 최종 오더정가
+ const NETWR = parseAmount(eccDetail.NETWR || null); // 오더정가 (최종 정가)
+ const BRTWR = parseAmount(eccDetail.BRTWR || null); // 오더총액 (기본 총액)
+ const ZPDT_EXDS_AMT = parseAmount(eccDetail.ZPDT_EXDS_AMT || null); // 할인/할증금액 (조정금액)
+
+ // 세율과 세액 - SAP에서 계산된 값이 있으면 그대로 사용
+ // SAP에서 세금 정보를 별도로 제공하지 않으므로, 일단 null로 설정
+ const taxRate: string | null = null;
+ const taxAmount: string | null = null;
+
+ // 세금코드 (MWSKZ - 매출부가가치세코드: V1, V2 등의 두자리 코드)
+ const taxType = eccDetail.MWSKZ || null;
+
+ // totalLineAmount는 최종 정가(NETWR)를 사용
+ const totalLineAmount = NETWR;
+
+ // 금액은 SAP에서 이미 계산/검증되어 오므로 그대로 저장
+ // 금액 관계: NETWR = BRTWR + ZPDT_EXDS_AMT
+
+ // MWSKZ(매출부가가치세코드)를 taxType에 매핑
+ // 각 코드(V1, V2 등)별 taxRate는 추후 별도로 관리될 예정
+
+ // 날짜 파싱
+ const parseDate = (dateStr: string | null): string | null => {
+ if (!dateStr || dateStr.length !== 8) return null;
+ try {
+ const year = dateStr.substring(0, 4);
+ const month = dateStr.substring(4, 6);
+ const day = dateStr.substring(6, 8);
+ return `${year}-${month}-${day}`;
+ } catch (error) {
+ debugError('날짜 파싱 오류', { dateStr, error });
+ return null;
+ }
+ };
- // 세액 계산
- let taxAmount: string | null = null;
- if (unitPrice && taxRate) {
+ // 정수 파싱
+ const parseInteger = (intStr: string | null): number | null => {
+ if (!intStr) return null;
try {
- const unitPriceNum = parseFloat(unitPrice);
- const taxRateNum = parseFloat(taxRate);
- const calculatedTaxAmount = (unitPriceNum * quantity * taxRateNum) / 100;
- taxAmount = calculatedTaxAmount.toString();
+ const num = parseInt(intStr);
+ return isNaN(num) ? null : num;
} catch (error) {
- debugError('세액(taxAmount) 계산 오류((unitPriceNum * quantity * taxRateNum) / 100)', { unitPrice, taxRate, quantity, error });
+ debugError('정수 파싱 오류', { intStr, error });
+ return null;
}
- }
+ };
// 매핑
const mappedData: ContractItemData = {
contractId,
itemId: itemId!, // 아이템이 없으면 자동 생성되므로 null이 될 수 없음
+ itemNo: eccDetail.EBELP || null, // EBELP - 구매오더품목번호 (품번)
+ prNo: eccDetail.BANFN || null, // BANFN - 구매요청번호 (PR번호)
+ prItemNo: eccDetail.BNFPO || null, // BNFPO - 구매요청품목번호 (PR 품번)
+ materialGroup: eccDetail.MATKL || null, // MATKL - 자재그룹
+ weight: parseAmount(eccDetail.NTGEW || null), // NTGEW - 순중량
+ weightUnit: eccDetail.GEWEI || null, // GEWEI - 중량단위
+ totalWeight: parseAmount(eccDetail.BRGEW || null), // BRGEW - 총중량
description: eccDetail.MAKTX || null,
quantity,
- unitPrice,
+ ZPO_UNIT: eccDetail.ZPO_UNIT || null, // 구매오더수량단위
+ unitPrice, // NETPR - 구매단가
+
+ // 가격 관련 추가 필드
+ PEINH: parseInteger(eccDetail.PEINH || null), // 가격단위값 (예: 1, 10, 100)
+ BPRME: eccDetail.BPRME || null, // 구매단가단위 (EA, KG 등)
+ ZNETPR: parseAmount(eccDetail.ZNETPR || null), // 발주단가
+ ZREF_NETPR: parseAmount(eccDetail.ZREF_NETPR || null), // 참조단가
+
taxRate,
taxAmount,
+ taxType, // MWSKZ - 매출부가가치세코드
+ NETWR, // 오더정가 (최종 정가)
+ BRTWR, // 오더총액 (기본 총액)
+ ZPDT_EXDS_AMT, // 할인/할증금액 (조정금액)
totalLineAmount,
- remark: eccDetail.ZPO_RMK || null,
+
+ // 위치 정보
+ WERKS: eccDetail.WERKS || null, // 플랜트코드
+ LGORT: eccDetail.LGORT || null, // 저장위치
+
+ // RFQ 추적
+ ANFNR: eccDetail.ANFNR || null, // RFQ번호
+ ANFPS: eccDetail.ANFPS || null, // RFQ품목번호
+
+ // 자재 추적
+ ZPO_LOT_NO: eccDetail.ZPO_LOT_NO || null, // Steel Material Marking No
+
+ // 볼륨 정보
+ VOLUM: parseAmount(eccDetail.VOLUM || null), // 볼륨
+ VOLEH: eccDetail.VOLEH || null, // 볼륨단위
+
+ // 날짜 정보
+ ZPO_DLV_DT: parseDate(eccDetail.ZPO_DLV_DT || null), // PO납기일자
+ ZPLN_ST_DT: parseDate(eccDetail.ZPLN_ST_DT || null), // 예정시작일자
+ ZPLN_ED_DT: parseDate(eccDetail.ZPLN_ED_DT || null), // 예정종료일자
+ LFDAT: parseDate(eccDetail.LFDAT || null), // PR Delivery Date
+ ZRCV_DT: parseDate(eccDetail.ZRCV_DT || null), // 구매접수일자
+
+ // 기타
+ ZCON_IND: eccDetail.ZCON_IND || null, // 시리즈구분 (SS 등)
+
+ remark: eccDetail.ZPO_RMK || null, // 발주비고
};
debugSuccess('ECC PO 상세 매핑 완료', {
@@ -365,6 +447,39 @@ export async function mapAndSaveECCPOData(
// 1. 헤더 매핑 및 저장
const contractData = await mapECCPOHeaderToBusiness(header);
+ // Details에서 날짜 정보 집계
+ const parseDate = (dateStr: string | null): Date | null => {
+ if (!dateStr || dateStr.length !== 8) return null;
+ try {
+ const year = dateStr.substring(0, 4);
+ const month = dateStr.substring(4, 6);
+ const day = dateStr.substring(6, 8);
+ return new Date(`${year}-${month}-${day}`);
+ } catch {
+ return null;
+ }
+ };
+
+ // 모든 details의 납기일자 중 최대값을 deliveryDate로 설정
+ const deliveryDates = details
+ .map(d => parseDate(d.ZPO_DLV_DT || null))
+ .filter((date): date is Date => date !== null);
+
+ if (deliveryDates.length > 0) {
+ const maxDeliveryDate = new Date(Math.max(...deliveryDates.map(d => d.getTime())));
+ contractData.deliveryDate = maxDeliveryDate.toISOString().split('T')[0];
+ }
+
+ // 모든 details의 예정종료일자 중 최대값을 endDate로 설정
+ const endDates = details
+ .map(d => parseDate(d.ZPLN_ED_DT || null))
+ .filter((date): date is Date => date !== null);
+
+ if (endDates.length > 0) {
+ const maxEndDate = new Date(Math.max(...endDates.map(d => d.getTime())));
+ contractData.endDate = maxEndDate.toISOString().split('T')[0];
+ }
+
// 중복 체크 (contractNo 기준)
const existingContract = await tx.query.contracts.findFirst({
where: eq(contracts.contractNo, contractData.contractNo),
@@ -429,10 +544,10 @@ export async function mapAndSaveECCPOData(
}
processedCount++;
- } catch (error) {
+ } catch (err) {
debugError('PO 데이터 처리 중 오류', {
ebeln: header.EBELN,
- error
+ error: err
});
// 개별 PO 처리 실패 시 해당 PO만 스킵하고 계속 진행
continue;
@@ -509,3 +624,4 @@ export function validateECCPOData(
errors,
};
}
+
diff --git a/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts b/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts
index c0557d0c..c1c56cf3 100644
--- a/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts
+++ b/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts
@@ -155,6 +155,7 @@ export async function mapECCRfqHeaderToRfqLast(
// 담당자 찾기
const inChargeUserInfo = await findUserInfoByEKGRP(eccHeader.EKGRP || null);
const inChargeUserId = inChargeUserInfo?.userId || null;
+ const inChargeUserName = inChargeUserInfo?.userName || null; // 담당자명 추가
// 대표 PR Item 기반으로 projectId, itemCode, itemName, 설계담당자 설정 (없으면 첫번째 PR Item 사용)
let projectId: number | null = null;
@@ -219,7 +220,7 @@ export async function mapECCRfqHeaderToRfqLast(
remark: null,
pic: inChargeUserId, // 담당자 ID
picCode: eccHeader.EKGRP || null, // 구매그룹코드
- picName: null, // 담당자명은 별도 조회 필요
+ picName: inChargeUserName, // 담당자명 (EKGRP로 조회)
sentBy: null,
createdBy: inChargeUserId || 1,
updatedBy: inChargeUserId || 1,
diff --git a/lib/utils.ts b/lib/utils.ts
index 98142389..8c6d2396 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -291,7 +291,7 @@ export function compareItemNumber(a?: string, b?: string) {
}
/**
- * 숫자를 3자리마다 콤마(,)로 구분하여 포맷팅합니다.
+ * 숫자를 3자리마다 콤마(,)로 구분하여 포맷팅합니다. (금액 등 자리수 구분 필요한 경우, KRW 및 JPY는 소수점 0자리, 나머지는 2자리)
*
* @param value 포맷팅할 숫자 (number, string, null, undefined 허용)
* @param decimals 소수점 자릿수 (선택, 지정하지 않으면 원본 소수점 유지)