summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-11-11 08:44:22 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-11-11 08:44:22 +0000
commitd76ab1855ec68432a1b362c5d922af7235658d10 (patch)
treee85c266c3667bcb2ce060823bce66218ae88a5a5
parentda6606ef891d8498a4479cadb82e270e52b19166 (diff)
(임수민) 구매 피드백 수정사항들
PO 금액 표시, RFQ 품목 컬럼 추가, 스크롤 시 헤더 고정, 납기일과 SAP 한국시간, 히스토리 수정
-rw-r--r--lib/po/table/po-table-columns.tsx10
-rw-r--r--lib/rfq-last/service.ts81
-rw-r--r--lib/rfq-last/shared/rfq-items-dialog.tsx37
-rw-r--r--lib/rfq-last/vendor-response/editor/quotation-items-table.tsx2
-rw-r--r--lib/rfq-last/vendor/vendor-detail-dialog.tsx4
-rw-r--r--lib/soap/ecc/mapper/rfq-and-pr-mapper.ts17
-rw-r--r--lib/vendor-candidates/service.ts45
7 files changed, 126 insertions, 70 deletions
diff --git a/lib/po/table/po-table-columns.tsx b/lib/po/table/po-table-columns.tsx
index 7edd2435..20b7c64b 100644
--- a/lib/po/table/po-table-columns.tsx
+++ b/lib/po/table/po-table-columns.tsx
@@ -26,6 +26,7 @@ import { Badge } from "@/components/ui/badge"
import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
import { poColumnsConfig } from "@/config/poColumnsConfig"
import { ContractDetailParsed } from "@/db/schema/contract"
+import { formatNumber } from "@/lib/utils"
interface GetColumnsProps {
setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<ContractDetailParsed> | null>>
@@ -251,8 +252,15 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<Contrac
if (cfg.type === "date") {
const dateVal = cell.getValue() as Date
return formatDate(dateVal, "KR")
+ }
+ if (cfg.id === "totalAmount") {
+ const value = cell.getValue() as number | string | null | undefined
+ return (
+ <div className="text-sm text-right font-mono">
+ {formatNumber(value)}
+ </div>
+ )
}
- // ...
return row.getValue(cfg.id) ?? ""
},
}
diff --git a/lib/rfq-last/service.ts b/lib/rfq-last/service.ts
index 462b5604..52d67280 100644
--- a/lib/rfq-last/service.ts
+++ b/lib/rfq-last/service.ts
@@ -669,41 +669,48 @@ export async function getRfqItemsAction(rfqId: number) {
.where(eq(prItemsLastView.rfqsLastId, rfqId))
.orderBy(prItemsLastView.majorYn, prItemsLastView.rfqItem, prItemsLastView.materialCode)
- const formattedItems = items.map(item => ({
- id: item.id,
- rfqsLastId: item.rfqsLastId,
- rfqItem: item.rfqItem,
- prItem: item.prItem,
- prNo: item.prNo,
- materialCode: item.materialCode,
- materialCategory: item.materialCategory,
- acc: item.acc,
- materialDescription: item.materialDescription,
- size: item.size,
- deliveryDate: item.deliveryDate,
- quantity: Number(item.quantity) || 0, // 여기서 숫자로 변환
- uom: item.uom,
- grossWeight: Number(item.grossWeight) || 0, // 여기서 숫자로 변환
- gwUom: item.gwUom,
- specNo: item.specNo,
- specUrl: item.specUrl,
- trackingNo: item.trackingNo,
- majorYn: item.majorYn,
- remark: item.remark,
- projectDef: item.projectDef,
- projectSc: item.projectSc,
- projectKl: item.projectKl,
- projectLc: item.projectLc,
- projectDl: item.projectDl,
- // RFQ 관련 정보
- rfqCode: item.rfqCode,
- rfqType: item.rfqType,
- rfqTitle: item.rfqTitle,
- itemCode: item.itemCode,
- itemName: item.itemName,
- projectCode: item.projectCode,
- projectName: item.projectName,
- }))
+ const formattedItems = items.map(item => {
+ const specification =
+ (item as { specification?: string | null }).specification ?? null
+
+ return {
+ id: item.id,
+ rfqsLastId: item.rfqsLastId,
+ rfqItem: item.rfqItem,
+ prItem: item.prItem,
+ prNo: item.prNo,
+ materialCode: item.materialCode,
+ materialCategory: item.materialCategory,
+ acc: item.acc,
+ materialDescription: item.materialDescription,
+ size: item.size,
+ deliveryDate: item.deliveryDate,
+ quantity: Number(item.quantity) || 0, // 여기서 숫자로 변환
+ uom: item.uom,
+ grossWeight: Number(item.grossWeight) || 0, // 여기서 숫자로 변환
+ gwUom: item.gwUom,
+ specNo: item.specNo,
+ specUrl: item.specUrl,
+ trackingNo: item.trackingNo,
+ specification,
+ majorYn: item.majorYn,
+ remark: item.remark,
+ projectDef: item.projectDef,
+ projectSc: item.projectSc,
+ projectKl: item.projectKl,
+ projectLc: item.projectLc,
+ projectDl: item.projectDl,
+ prIssueDate: item.prIssueDate ? new Date(item.prIssueDate) : null,
+ // RFQ 관련 정보
+ rfqCode: item.rfqCode,
+ rfqType: item.rfqType,
+ rfqTitle: item.rfqTitle,
+ itemCode: item.itemCode,
+ itemName: item.itemName,
+ projectCode: item.projectCode,
+ projectName: item.projectName,
+ }
+ })
// 주요 품목과 일반 품목 분리 및 통계
const majorItems = formattedItems.filter(item => item.majorYn)
@@ -5068,11 +5075,11 @@ export async function updateShortList(
})
);
- // 2-3. RFQ 상태를 "Short List 확정"으로 업데이트
+ // 2-3. RFQ 상태를 "TBE 요청"으로 업데이트
await tx
.update(rfqsLast)
.set({
- status: "Short List 확정" as RfqStatus,
+ status: "TBE 요청" as RfqStatus,
updatedBy: Number(session.user.id),
updatedAt: new Date()
})
diff --git a/lib/rfq-last/shared/rfq-items-dialog.tsx b/lib/rfq-last/shared/rfq-items-dialog.tsx
index 4b41897b..f3095c98 100644
--- a/lib/rfq-last/shared/rfq-items-dialog.tsx
+++ b/lib/rfq-last/shared/rfq-items-dialog.tsx
@@ -20,7 +20,6 @@ import {
} from "@/components/ui/table"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
-import { ScrollArea } from "@/components/ui/scroll-area"
import { Skeleton } from "@/components/ui/skeleton"
import { Separator } from "@/components/ui/separator"
import { toast } from "sonner"
@@ -49,6 +48,7 @@ interface RfqItem {
specNo: string | null
specUrl: string | null
trackingNo: string | null
+ specification: string | null
majorYn: boolean | null
remark: string | null
projectDef: string | null
@@ -56,6 +56,7 @@ interface RfqItem {
projectKl: string | null
projectLc: string | null
projectDl: string | null
+ prIssueDate: Date | null
// RFQ 관련 정보
rfqCode: string | null
rfqType: string | null
@@ -258,22 +259,23 @@ export function RfqItemsDialog({
</>
)}
- <ScrollArea className="flex-1">
+ <div className="flex-1 overflow-auto">
{isLoading ? (
<Table>
- <TableHeader>
+ <TableHeader className="sticky top-0 z-10 bg-background">
<TableRow>
<TableHead className="w-[60px]">아이템</TableHead>
<TableHead className="w-[120px]">자재코드</TableHead>
<TableHead>자재명</TableHead>
+ <TableHead className="w-[140px]">사양</TableHead>
<TableHead className="w-[80px]">수량</TableHead>
<TableHead className="w-[60px]">수량단위</TableHead>
<TableHead className="w-[80px]">중량</TableHead>
<TableHead className="w-[60px]">중량단위</TableHead>
- <TableHead className="w-[100px]">납기일</TableHead>
+ <TableHead className="w-[110px]">PR 발행일</TableHead>
+ <TableHead className="w-[100px]">PR납기 요청일</TableHead>
<TableHead className="w-[100px]">PR번호</TableHead>
<TableHead className="w-[120px]">사양/설계문서</TableHead>
- <TableHead>비고</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -290,6 +292,7 @@ export function RfqItemsDialog({
<TableCell><Skeleton className="h-8 w-full" /></TableCell>
<TableCell><Skeleton className="h-8 w-full" /></TableCell>
<TableCell><Skeleton className="h-8 w-full" /></TableCell>
+ <TableCell><Skeleton className="h-8 w-full" /></TableCell>
</TableRow>
))}
</TableBody>
@@ -301,21 +304,22 @@ export function RfqItemsDialog({
</div>
) : (
<Table>
- <TableHeader>
+ <TableHeader className="sticky top-0 z-10 bg-background">
<TableRow>
<TableHead className="w-[60px]">아이템</TableHead>
<TableHead className="w-[120px]">자재코드</TableHead>
<TableHead>자재명</TableHead>
+ <TableHead className="w-[140px]">사양</TableHead>
<TableHead className="w-[80px]">수량</TableHead>
<TableHead className="w-[60px]">수량단위</TableHead>
<TableHead className="w-[80px]">중량</TableHead>
<TableHead className="w-[60px]">중량단위</TableHead>
- <TableHead className="w-[100px]">납기일</TableHead>
+ <TableHead className="w-[110px]">PR 발행일</TableHead>
+ <TableHead className="w-[100px]">PR납기 요청일</TableHead>
<TableHead className="w-[100px]">PR번호</TableHead>
<TableHead className="w-[100px]">PR 아이템 번호</TableHead>
<TableHead className="w-[120px]">사양/설계문서</TableHead>
<TableHead className="w-[100px]">프로젝트</TableHead>
- <TableHead>비고</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -360,6 +364,11 @@ export function RfqItemsDialog({
</TableCell>
<TableCell>
<span className="text-sm font-medium">
+ {item.specification?.trim() ? item.specification : "-"}
+ </span>
+ </TableCell>
+ <TableCell>
+ <span className="text-sm font-medium">
{item.quantity ? item.quantity.toLocaleString() : "-"}
</span>
</TableCell>
@@ -379,6 +388,11 @@ export function RfqItemsDialog({
</span>
</TableCell>
<TableCell>
+ <span className="text-sm font-medium">
+ {item.prIssueDate ? format(new Date(item.prIssueDate), "yyyy-MM-dd") : "-"}
+ </span>
+ </TableCell>
+ <TableCell>
<span className="text-sm">
{item.deliveryDate ? format(new Date(item.deliveryDate), "yyyy-MM-dd") : "-"}
</span>
@@ -446,17 +460,12 @@ export function RfqItemsDialog({
].filter(Boolean).join(" | ") || "-"}
</div>
</TableCell>
- <TableCell>
- <span className="text-xs" title={item.remark || ""}>
- {item.remark ? (item.remark.length > 30 ? `${item.remark.slice(0, 30)}...` : item.remark) : "-"}
- </span>
- </TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
- </ScrollArea>
+ </div>
{/* 하단 통계 정보 */}
{statistics && !isLoading && (
diff --git a/lib/rfq-last/vendor-response/editor/quotation-items-table.tsx b/lib/rfq-last/vendor-response/editor/quotation-items-table.tsx
index b0c1488a..d2e0ff0b 100644
--- a/lib/rfq-last/vendor-response/editor/quotation-items-table.tsx
+++ b/lib/rfq-last/vendor-response/editor/quotation-items-table.tsx
@@ -483,7 +483,7 @@ export default function QuotationItemsTable({ prItems }: QuotationItemsTableProp
<TableHead className="w-[60px]">중량단위</TableHead>
<TableHead className="w-[150px]">단가</TableHead>
<TableHead className="text-right w-[150px]">총액</TableHead>
- <TableHead className="w-[150px]">납기일</TableHead>
+ <TableHead className="w-[150px]">PR납기 요청일</TableHead>
<TableHead className="w-[180px]">사양/POS</TableHead>
<TableHead className="w-[120px]">프로젝트</TableHead>
<TableHead className="w-[80px]">상세</TableHead>
diff --git a/lib/rfq-last/vendor/vendor-detail-dialog.tsx b/lib/rfq-last/vendor/vendor-detail-dialog.tsx
index 0eee1b8b..f379b032 100644
--- a/lib/rfq-last/vendor/vendor-detail-dialog.tsx
+++ b/lib/rfq-last/vendor/vendor-detail-dialog.tsx
@@ -398,7 +398,7 @@ export function VendorResponseDetailDialog({
</div>
<div className="space-y-3">
<div className="flex items-center justify-between">
- <span className="text-sm text-muted-foreground">납기일</span>
+ <span className="text-sm text-muted-foreground">PR납기 요청일</span>
<span className="text-sm">
{data.deliveryDate
? format(new Date(data.deliveryDate), "yyyy-MM-dd")
@@ -596,7 +596,7 @@ export function VendorResponseDetailDialog({
<TableHead className="text-right">단가</TableHead>
<TableHead className="text-right">금액</TableHead>
<TableHead>통화</TableHead>
- <TableHead>납기일</TableHead>
+ <TableHead>PR납기 요청일</TableHead>
</TableRow>
</TableHeader>
<TableBody>
diff --git a/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts b/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts
index cc241aa6..85fbb918 100644
--- a/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts
+++ b/lib/soap/ecc/mapper/rfq-and-pr-mapper.ts
@@ -240,9 +240,20 @@ export function mapECCRfqItemToRfqPrItem(
// 날짜 파싱
let deliveryDate: Date | null = null;
- if (eccItem.LFDAT) {
- const dateStr = parseSAPDateTime(eccItem.LFDAT, '000000');
- deliveryDate = dateStr;
+ if (eccItem.LFDAT && eccItem.LFDAT.length === 8) {
+ try {
+ // YYYYMMDD 형식을 YYYY-MM-DD로 변환 후 한국 시간 기준으로 설정
+ const year = eccItem.LFDAT.substring(0, 4);
+ const month = eccItem.LFDAT.substring(4, 6);
+ const day = eccItem.LFDAT.substring(6, 8);
+ // 한국 시간(KST, UTC+9) 기준으로 날짜 생성
+ deliveryDate = new Date(`${year}-${month}-${day}T00:00:00+09:00`);
+ } catch (error) {
+ debugError('SAP 날짜 파싱 오류', {
+ lfdat: eccItem.LFDAT,
+ error,
+ });
+ }
}
const mappedData: RfqPrItemData = {
diff --git a/lib/vendor-candidates/service.ts b/lib/vendor-candidates/service.ts
index bfeb3090..2b6421f5 100644
--- a/lib/vendor-candidates/service.ts
+++ b/lib/vendor-candidates/service.ts
@@ -252,20 +252,41 @@ export async function updateVendorCandidate(input: UpdateVendorCandidateSchema,
.returning();
// 로그 작성
- const statusChanged =
- updateData.status &&
+ const statusChanged =
+ updateData.status !== undefined &&
existingCandidate.status !== updateData.status;
- await tx.insert(vendorCandidateLogs).values({
- vendorCandidateId: id,
- userId: userId,
- action: statusChanged ? "status_change" : "update",
- oldStatus: statusChanged ? existingCandidate.status : undefined,
- newStatus: statusChanged ? updateData.status : undefined,
- comment: statusChanged
- ? `Status changed from ${existingCandidate.status} to ${updateData.status}`
- : `Updated vendor candidate: ${existingCandidate.companyName}`
- });
+ // 상태가 변경된 경우에만 상태 변경 로그 기록
+ if (statusChanged) {
+ await tx.insert(vendorCandidateLogs).values({
+ vendorCandidateId: id,
+ userId: userId,
+ action: "status_change",
+ oldStatus: existingCandidate.status,
+ newStatus: updateData.status,
+ comment: `Status changed from ${existingCandidate.status} to ${updateData.status}`
+ });
+ }
+
+ // 상태가 변경되지 않았지만 다른 필드가 변경된 경우에만 일반 업데이트 로그 기록
+ // (실제로 변경된 필드가 있는지 확인하는 로직은 복잡하므로, 상태 변경이 아닌 경우에만 로그 기록)
+ // 참고: 모든 필드가 동일한 경우도 있지만, 사용자가 저장 버튼을 눌렀다는 것은 변경 의도가 있다는 의미
+ if (!statusChanged) {
+ // 다른 필드 변경 여부를 간단히 확인 (실제로는 더 정교한 비교가 필요할 수 있음)
+ const hasOtherChanges = Object.keys(updateData).some(key => {
+ if (key === 'status' || key === 'updatedAt') return false;
+ return (existingCandidate as any)[key] !== (updateData as any)[key];
+ });
+
+ if (hasOtherChanges) {
+ await tx.insert(vendorCandidateLogs).values({
+ vendorCandidateId: id,
+ userId: userId,
+ action: "update",
+ comment: `Updated vendor candidate: ${existingCandidate.companyName}`
+ });
+ }
+ }