summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/po/vendor-table/mock-data.ts1940
-rw-r--r--lib/po/vendor-table/service.ts417
-rw-r--r--lib/po/vendor-table/shi-vendor-po-columns.tsx462
-rw-r--r--lib/po/vendor-table/shi-vendor-po-table.tsx267
-rw-r--r--lib/po/vendor-table/shi-vendor-po-toolbar-actions.tsx66
-rw-r--r--lib/po/vendor-table/types.ts119
-rw-r--r--lib/po/vendor-table/validations.ts58
-rw-r--r--lib/po/vendor-table/vendor-po-columns.tsx511
-rw-r--r--lib/po/vendor-table/vendor-po-items-dialog.tsx199
-rw-r--r--lib/po/vendor-table/vendor-po-table.tsx247
-rw-r--r--lib/po/vendor-table/vendor-po-toolbar-actions.tsx214
-rw-r--r--lib/soap/ecc/mapper/po-mapper.ts64
12 files changed, 4552 insertions, 12 deletions
diff --git a/lib/po/vendor-table/mock-data.ts b/lib/po/vendor-table/mock-data.ts
new file mode 100644
index 00000000..a932ccbb
--- /dev/null
+++ b/lib/po/vendor-table/mock-data.ts
@@ -0,0 +1,1940 @@
+import { VendorPO, VendorPOItem } from "./types"
+
+// 상세품목 목업 데이터
+export const mockVendorPOItems: VendorPOItem[] = [
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-002",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-002",
+ itemDescription: "Steel Angle 50x50x5",
+ materialSpec: "SS400, 50x50x5mm x 6000mm",
+ specification: "50x50x5mm x 6000mm",
+ quantity: 200,
+ quantityUnit: "M",
+ weight: 3.77,
+ weightUnit: "KG/M",
+ totalWeight: 754.0,
+ unitPrice: 8000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/M",
+ contractAmount: 1600000,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-002",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-002",
+ itemDescription: "Steel Angle 50x50x5",
+ materialSpec: "SS400, 50x50x5mm x 6000mm",
+ specification: "50x50x5mm x 6000mm",
+ quantity: 200,
+ quantityUnit: "M",
+ weight: 3.77,
+ weightUnit: "KG/M",
+ totalWeight: 754.0,
+ unitPrice: 8000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/M",
+ contractAmount: 1600000,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-002",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-002",
+ itemDescription: "Steel Angle 50x50x5",
+ materialSpec: "SS400, 50x50x5mm x 6000mm",
+ specification: "50x50x5mm x 6000mm",
+ quantity: 200,
+ quantityUnit: "M",
+ weight: 3.77,
+ weightUnit: "KG/M",
+ totalWeight: 754.0,
+ unitPrice: 8000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/M",
+ contractAmount: 1600000,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-002",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-002",
+ itemDescription: "Steel Angle 50x50x5",
+ materialSpec: "SS400, 50x50x5mm x 6000mm",
+ specification: "50x50x5mm x 6000mm",
+ quantity: 200,
+ quantityUnit: "M",
+ weight: 3.77,
+ weightUnit: "KG/M",
+ totalWeight: 754.0,
+ unitPrice: 8000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/M",
+ contractAmount: 1600000,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-002",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-002",
+ itemDescription: "Steel Angle 50x50x5",
+ materialSpec: "SS400, 50x50x5mm x 6000mm",
+ specification: "50x50x5mm x 6000mm",
+ quantity: 200,
+ quantityUnit: "M",
+ weight: 3.77,
+ weightUnit: "KG/M",
+ totalWeight: 754.0,
+ unitPrice: 8000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/M",
+ contractAmount: 1600000,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-002",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-002",
+ itemDescription: "Steel Angle 50x50x5",
+ materialSpec: "SS400, 50x50x5mm x 6000mm",
+ specification: "50x50x5mm x 6000mm",
+ quantity: 200,
+ quantityUnit: "M",
+ weight: 3.77,
+ weightUnit: "KG/M",
+ totalWeight: 754.0,
+ unitPrice: 8000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/M",
+ contractAmount: 1600000,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-002",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-002",
+ itemDescription: "Steel Angle 50x50x5",
+ materialSpec: "SS400, 50x50x5mm x 6000mm",
+ specification: "50x50x5mm x 6000mm",
+ quantity: 200,
+ quantityUnit: "M",
+ weight: 3.77,
+ weightUnit: "KG/M",
+ totalWeight: 754.0,
+ unitPrice: 8000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/M",
+ contractAmount: 1600000,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-002",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-002",
+ itemDescription: "Steel Angle 50x50x5",
+ materialSpec: "SS400, 50x50x5mm x 6000mm",
+ specification: "50x50x5mm x 6000mm",
+ quantity: 200,
+ quantityUnit: "M",
+ weight: 3.77,
+ weightUnit: "KG/M",
+ totalWeight: 754.0,
+ unitPrice: 8000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/M",
+ contractAmount: 1600000,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-002",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-002",
+ itemDescription: "Steel Angle 50x50x5",
+ materialSpec: "SS400, 50x50x5mm x 6000mm",
+ specification: "50x50x5mm x 6000mm",
+ quantity: 200,
+ quantityUnit: "M",
+ weight: 3.77,
+ weightUnit: "KG/M",
+ totalWeight: 754.0,
+ unitPrice: 8000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/M",
+ contractAmount: 1600000,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-002",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-002",
+ itemDescription: "Steel Angle 50x50x5",
+ materialSpec: "SS400, 50x50x5mm x 6000mm",
+ specification: "50x50x5mm x 6000mm",
+ quantity: 200,
+ quantityUnit: "M",
+ weight: 3.77,
+ weightUnit: "KG/M",
+ totalWeight: 754.0,
+ unitPrice: 8000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/M",
+ contractAmount: 1600000,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-002",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-002",
+ itemDescription: "Steel Angle 50x50x5",
+ materialSpec: "SS400, 50x50x5mm x 6000mm",
+ specification: "50x50x5mm x 6000mm",
+ quantity: 200,
+ quantityUnit: "M",
+ weight: 3.77,
+ weightUnit: "KG/M",
+ totalWeight: 754.0,
+ unitPrice: 8000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/M",
+ contractAmount: 1600000,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-002",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-002",
+ itemDescription: "Steel Angle 50x50x5",
+ materialSpec: "SS400, 50x50x5mm x 6000mm",
+ specification: "50x50x5mm x 6000mm",
+ quantity: 200,
+ quantityUnit: "M",
+ weight: 3.77,
+ weightUnit: "KG/M",
+ totalWeight: 754.0,
+ unitPrice: 8000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/M",
+ contractAmount: 1600000,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-002",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-002",
+ itemDescription: "Steel Angle 50x50x5",
+ materialSpec: "SS400, 50x50x5mm x 6000mm",
+ specification: "50x50x5mm x 6000mm",
+ quantity: 200,
+ quantityUnit: "M",
+ weight: 3.77,
+ weightUnit: "KG/M",
+ totalWeight: 754.0,
+ unitPrice: 8000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/M",
+ contractAmount: 1600000,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+ {
+ contractNo: "PO-2024-001",
+ itemNo: "ITM-001",
+ prNo: "PR-2024-0001",
+ materialGroup: "Steel Materials",
+ priceStandard: "FOB",
+ materialNo: "MAT-ST-001",
+ itemDescription: "Steel Plate 10mm",
+ materialSpec: "SS400, 10mm x 2000mm x 6000mm",
+ fittingNo: "FIT-001",
+ cert: "Mill Certificate",
+ material: "SS400",
+ specification: "10mm x 2000mm x 6000mm",
+ quantity: 100,
+ quantityUnit: "EA",
+ weight: 942.0,
+ weightUnit: "KG",
+ totalWeight: 94200.0,
+ unitPrice: 50000,
+ priceUnit: "KRW",
+ priceUnitValue: "원/EA",
+ contractAmount: 5000000,
+ adjustmentAmount: 0,
+ deliveryDate: "2024-03-15",
+ vatType: "과세",
+ steelSpec: "KS D 3503",
+ prManager: "김철수"
+ },
+]
+
+// 벤더 PO 목업 데이터
+export const mockVendorPOs: VendorPO[] = [
+ {
+ id: 1,
+ contractNo: "PO-2024-001",
+ revision: "Rev.01",
+ itemNo: "ITM-001",
+ contractStatus: "승인대기",
+ contractType: "구매계약",
+ details: "상세보기",
+ projectName: "해상풍력 프로젝트 A",
+ contractName: "철강재 공급계약",
+ contractPeriod: "2024-01-01 ~ 2024-12-31",
+ contractQuantity: "300 EA",
+ currency: "KRW",
+ paymentTerms: "월말결제",
+ tax: "10%",
+ exchangeRate: "1,330.50",
+ deliveryTerms: "FOB",
+ purchaseManager: "이구매",
+ poReceiveDate: "2024-01-15",
+ contractDate: "2024-01-20",
+ lcNo: "LC-2024-001",
+ priceIndexTarget: true,
+ linkedContractNo: "PO-2024-000",
+ lastModifiedDate: "2024-01-25",
+ lastModifiedBy: "김수정",
+ items: mockVendorPOItems.filter(item => item.contractNo === "PO-2024-001")
+ },
+ {
+ id: 2,
+ contractNo: "PO-2024-002",
+ revision: "Rev.00",
+ itemNo: "ITM-003",
+ contractStatus: "계약완료",
+ contractType: "서비스계약",
+ details: "상세보기",
+ projectName: "해상풍력 프로젝트 B",
+ contractName: "설치 서비스 계약",
+ contractPeriod: "2024-02-01 ~ 2024-06-30",
+ contractQuantity: "1 LOT",
+ currency: "USD",
+ paymentTerms: "선급금 30%, 잔금 70%",
+ tax: "면세",
+ exchangeRate: "1,330.50",
+ deliveryTerms: "CIF",
+ purchaseManager: "박구매",
+ poReceiveDate: "2024-02-01",
+ contractDate: "2024-02-05",
+ priceIndexTarget: false,
+ lastModifiedDate: "2024-02-10",
+ lastModifiedBy: "이수정",
+ items: []
+ },
+ {
+ id: 3,
+ contractNo: "PO-2024-003",
+ revision: "Rev.02",
+ itemNo: "ITM-004",
+ contractStatus: "수정요청",
+ contractType: "구매계약",
+ details: "상세보기",
+ projectName: "조선 프로젝트 C",
+ contractName: "선박용 부품 공급계약",
+ contractPeriod: "2024-03-01 ~ 2024-09-30",
+ contractQuantity: "150 SET",
+ currency: "EUR",
+ paymentTerms: "인도 후 30일",
+ tax: "10%",
+ exchangeRate: "1,450.20",
+ deliveryTerms: "EXW",
+ purchaseManager: "최구매",
+ poReceiveDate: "2024-03-01",
+ contractDate: "2024-03-10",
+ lcNo: "LC-2024-003",
+ priceIndexTarget: true,
+ linkedContractNo: "PO-2024-002",
+ lastModifiedDate: "2024-03-15",
+ lastModifiedBy: "정수정",
+ items: []
+ },
+ {
+ id: 4,
+ contractNo: "PO-2024-004",
+ revision: "Rev.00",
+ itemNo: "ITM-005",
+ contractStatus: "거절됨",
+ contractType: "임가공계약",
+ details: "상세보기",
+ projectName: "플랜트 프로젝트 D",
+ contractName: "가공 서비스 계약",
+ contractPeriod: "2024-04-01 ~ 2024-08-31",
+ contractQuantity: "500 HR",
+ currency: "KRW",
+ paymentTerms: "월 단위 정산",
+ tax: "10%",
+ exchangeRate: "1,330.50",
+ deliveryTerms: "DDP",
+ purchaseManager: "김구매",
+ poReceiveDate: "2024-04-01",
+ contractDate: "",
+ priceIndexTarget: false,
+ lastModifiedDate: "2024-04-05",
+ lastModifiedBy: "박수정",
+ items: []
+ },
+ {
+ id: 5,
+ contractNo: "PO-2024-005",
+ revision: "Rev.01",
+ itemNo: "ITM-006",
+ contractStatus: "진행중",
+ contractType: "구매계약",
+ details: "상세보기",
+ projectName: "해상풍력 프로젝트 E",
+ contractName: "전기설비 공급계약",
+ contractPeriod: "2024-05-01 ~ 2024-11-30",
+ contractQuantity: "25 SET",
+ currency: "USD",
+ paymentTerms: "LC 90일",
+ tax: "면세",
+ exchangeRate: "1,330.50",
+ deliveryTerms: "FOB",
+ purchaseManager: "이구매",
+ poReceiveDate: "2024-05-01",
+ contractDate: "2024-05-10",
+ lcNo: "LC-2024-005",
+ priceIndexTarget: true,
+ lastModifiedDate: "2024-05-15",
+ lastModifiedBy: "최수정",
+ items: []
+ }
+]
+
+// 페이지네이션을 위한 헬퍼 함수
+export function getVendorPOsPage(
+ page: number = 1,
+ perPage: number = 10,
+ search?: string,
+ filters?: any[]
+): { data: VendorPO[]; pageCount: number; total: number } {
+ let filteredData = [...mockVendorPOs]
+
+ // 검색 필터링
+ if (search && search.trim()) {
+ const searchTerm = search.toLowerCase()
+ filteredData = filteredData.filter(po =>
+ po.contractNo.toLowerCase().includes(searchTerm) ||
+ po.contractName.toLowerCase().includes(searchTerm) ||
+ po.projectName.toLowerCase().includes(searchTerm) ||
+ po.contractStatus.toLowerCase().includes(searchTerm)
+ )
+ }
+
+ // 추가 필터 적용 (필요시 구현)
+ // if (filters && filters.length > 0) {
+ // // 필터 로직 구현
+ // }
+
+ const total = filteredData.length
+ const pageCount = Math.ceil(total / perPage)
+ const offset = (page - 1) * perPage
+ const data = filteredData.slice(offset, offset + perPage)
+
+ return { data, pageCount, total }
+}
diff --git a/lib/po/vendor-table/service.ts b/lib/po/vendor-table/service.ts
new file mode 100644
index 00000000..88f6ddd5
--- /dev/null
+++ b/lib/po/vendor-table/service.ts
@@ -0,0 +1,417 @@
+"use server";
+
+import { GetVendorPOSchema } from "./validations";
+import { getVendorPOsPage } from "./mock-data";
+import { VendorPO, VendorPOItem } from "./types";
+import db from "@/db/db";
+import { contracts, contractItems } from "@/db/schema/contract";
+import { projects } from "@/db/schema/projects";
+import { vendors } from "@/db/schema/vendors";
+import { items } from "@/db/schema/items";
+import { eq, and, or, ilike, count, desc, asc } from "drizzle-orm";
+
+/**
+ * 벤더 PO 목록 조회
+ * contracts 테이블에서 실제 데이터를 조회합니다.
+ */
+export async function getVendorPOs(input: GetVendorPOSchema) {
+ try {
+ // 실제 데이터베이스 조회
+ const offset = (input.page - 1) * input.perPage;
+
+ // 검색 조건 구성
+ let whereConditions = [];
+ if (input.search) {
+ const searchTerm = `%${input.search}%`;
+ whereConditions.push(
+ or(
+ ilike(contracts.contractNo, searchTerm),
+ ilike(contracts.contractName, searchTerm),
+ ilike(projects.name, searchTerm),
+ ilike(vendors.vendorName, searchTerm)
+ )
+ );
+ }
+
+ // 벤더 필터링 (partners 페이지에서 사용)
+ if (input.vendorId && input.vendorId > 0) {
+ whereConditions.push(eq(contracts.vendorId, input.vendorId));
+ }
+
+ // 필터 조건 추가
+ if (input.filters && input.filters.length > 0) {
+ for (const filter of input.filters) {
+ if (filter.id && filter.value) {
+ switch (filter.id) {
+ case "contractStatus":
+ whereConditions.push(ilike(contracts.status, `%${filter.value}%`));
+ break;
+ case "contractType":
+ whereConditions.push(ilike(contracts.purchaseDocType, `%${filter.value}%`));
+ break;
+ case "currency":
+ whereConditions.push(eq(contracts.currency, filter.value));
+ break;
+ // 추가 필터 조건들...
+ }
+ }
+ }
+ }
+
+ const finalWhere = whereConditions.length > 0 ? and(...whereConditions) : undefined;
+
+ // 정렬 조건
+ const orderBy = input.sort && input.sort.length > 0
+ ? input.sort.map((item) => {
+ switch (item.id) {
+ case "contractNo":
+ return item.desc ? desc(contracts.contractNo) : asc(contracts.contractNo);
+ case "contractName":
+ return item.desc ? desc(contracts.contractName) : asc(contracts.contractName);
+ case "lastModifiedDate":
+ return item.desc ? desc(contracts.updatedAt) : asc(contracts.updatedAt);
+ default:
+ return desc(contracts.updatedAt);
+ }
+ })
+ : [desc(contracts.updatedAt)];
+
+ // 데이터 조회 (조인 포함)
+ const rawData = await db
+ .select({
+ // contracts 테이블 필드들
+ id: contracts.id,
+ contractNo: contracts.contractNo,
+ contractName: contracts.contractName,
+ status: contracts.status,
+ startDate: contracts.startDate,
+ endDate: contracts.endDate,
+ currency: contracts.currency,
+ totalAmount: contracts.totalAmount,
+ totalAmountKrw: contracts.totalAmountKrw,
+ paymentTerms: contracts.paymentTerms,
+ deliveryTerms: contracts.deliveryTerms,
+ deliveryLocation: contracts.deliveryLocation,
+ shippmentPlace: contracts.shippmentPlace,
+ exchangeRate: contracts.exchangeRate,
+
+ // SAP ECC 추가 필드들
+ poVersion: contracts.poVersion,
+ purchaseDocType: contracts.purchaseDocType,
+ purchaseOrg: contracts.purchaseOrg,
+ purchaseGroup: contracts.purchaseGroup,
+ poConfirmStatus: contracts.poConfirmStatus,
+ contractGuaranteeCode: contracts.contractGuaranteeCode,
+ defectGuaranteeCode: contracts.defectGuaranteeCode,
+ guaranteePeriodCode: contracts.guaranteePeriodCode,
+ advancePaymentYn: contracts.advancePaymentYn,
+ budgetAmount: contracts.budgetAmount,
+ budgetCurrency: contracts.budgetCurrency,
+ electronicContractYn: contracts.electronicContractYn,
+ electronicApprovalDate: contracts.electronicApprovalDate,
+ electronicApprovalTime: contracts.electronicApprovalTime,
+ ownerApprovalYn: contracts.ownerApprovalYn,
+ plannedInOutFlag: contracts.plannedInOutFlag,
+ settlementStandard: contracts.settlementStandard,
+ weightSettlementFlag: contracts.weightSettlementFlag,
+ priceIndexYn: contracts.priceIndexYn,
+ writtenContractNo: contracts.writtenContractNo,
+ contractVersion: contracts.contractVersion,
+
+ createdAt: contracts.createdAt,
+ updatedAt: contracts.updatedAt,
+
+ // 조인된 테이블 필드들
+ projectName: projects.name,
+ vendorName: vendors.vendorName,
+ })
+ .from(contracts)
+ .leftJoin(projects, eq(contracts.projectId, projects.id))
+ .leftJoin(vendors, eq(contracts.vendorId, vendors.id))
+ .where(finalWhere)
+ .orderBy(...orderBy)
+ .offset(offset)
+ .limit(input.perPage);
+
+ // 총 개수 조회
+ const [{ totalCount }] = await db
+ .select({ totalCount: count() })
+ .from(contracts)
+ .leftJoin(projects, eq(contracts.projectId, projects.id))
+ .leftJoin(vendors, eq(contracts.vendorId, vendors.id))
+ .where(finalWhere);
+
+ const pageCount = Math.ceil(totalCount / input.perPage);
+
+ // VendorPO 타입으로 변환
+ const data: VendorPO[] = rawData.map(row => ({
+ id: row.id,
+ contractNo: row.contractNo || '',
+ revision: 'Rev.01', // mock 데이터용 기본값
+ itemNo: 'ITM-AUTO', // mock 데이터용 기본값
+ contractStatus: row.status || '',
+ contractType: row.purchaseDocType || '',
+ details: '상세보기', // mock 데이터용 기본값
+ projectName: row.projectName || '',
+ contractName: row.contractName || '',
+ contractPeriod: row.startDate && row.endDate
+ ? `${row.startDate} ~ ${row.endDate}`
+ : '',
+ contractQuantity: '1 LOT', // 기본값 (실제로는 contract_items에서 계산 필요)
+ currency: row.currency || 'KRW',
+ paymentTerms: row.paymentTerms || '',
+ tax: '10%', // 기본값 (실제로는 contract_items에서 계산 필요)
+ exchangeRate: row.exchangeRate?.toString() || '',
+ deliveryTerms: row.deliveryTerms || '',
+ purchaseManager: '', // 사용자 테이블 조인 필요
+ poReceiveDate: row.createdAt?.toISOString().split('T')[0] || '',
+ contractDate: row.startDate || '',
+ lcNo: undefined,
+ priceIndexTarget: row.priceIndexYn === 'Y',
+ linkedContractNo: undefined,
+ lastModifiedDate: row.updatedAt?.toISOString().split('T')[0] || '',
+ lastModifiedBy: '', // 사용자 테이블 조인 필요
+
+ // SAP ECC 추가 필드들
+ poVersion: row.poVersion || undefined,
+ purchaseDocType: row.purchaseDocType || undefined,
+ purchaseOrg: row.purchaseOrg || undefined,
+ purchaseGroup: row.purchaseGroup || undefined,
+ poConfirmStatus: row.poConfirmStatus || undefined,
+ contractGuaranteeCode: row.contractGuaranteeCode || undefined,
+ defectGuaranteeCode: row.defectGuaranteeCode || undefined,
+ guaranteePeriodCode: row.guaranteePeriodCode || undefined,
+ advancePaymentYn: row.advancePaymentYn || undefined,
+ budgetAmount: row.budgetAmount ? Number(row.budgetAmount) : undefined,
+ budgetCurrency: row.budgetCurrency || undefined,
+ totalAmount: row.totalAmount ? Number(row.totalAmount) : undefined,
+ totalAmountKrw: row.totalAmountKrw ? Number(row.totalAmountKrw) : undefined,
+ electronicContractYn: row.electronicContractYn || undefined,
+ electronicApprovalDate: row.electronicApprovalDate || undefined,
+ electronicApprovalTime: row.electronicApprovalTime || undefined,
+ ownerApprovalYn: row.ownerApprovalYn || undefined,
+ plannedInOutFlag: row.plannedInOutFlag || undefined,
+ settlementStandard: row.settlementStandard || undefined,
+ weightSettlementFlag: row.weightSettlementFlag || undefined,
+ priceIndexYn: row.priceIndexYn || undefined,
+ writtenContractNo: row.writtenContractNo || undefined,
+ contractVersion: row.contractVersion || undefined,
+ }));
+
+ return {
+ data,
+ pageCount
+ };
+
+ // 목업 데이터 사용 (개발/테스트용)
+ // const result = getVendorPOsPage(
+ // input.page,
+ // input.perPage,
+ // input.search,
+ // input.filters
+ // );
+
+ // 실제 데이터베이스 연동시에는 아래와 같은 구조로 구현
+ // const offset = (input.page - 1) * input.perPage;
+ //
+ // // 검색 조건 구성
+ // let whereConditions = [];
+ // if (input.search) {
+ // const searchTerm = `%${input.search}%`;
+ // whereConditions.push(
+ // or(
+ // ilike(vendorPOTable.contractNo, searchTerm),
+ // ilike(vendorPOTable.contractName, searchTerm),
+ // ilike(vendorPOTable.projectName, searchTerm)
+ // )
+ // );
+ // }
+ //
+ // // 필터 조건 추가
+ // if (input.contractStatus) {
+ // whereConditions.push(eq(vendorPOTable.contractStatus, input.contractStatus));
+ // }
+ //
+ // const finalWhere = whereConditions.length > 0 ? and(...whereConditions) : undefined;
+ //
+ // // 정렬 조건
+ // const orderBy = input.sort.length > 0
+ // ? input.sort.map((item) =>
+ // item.desc
+ // ? desc(vendorPOTable[item.id])
+ // : asc(vendorPOTable[item.id])
+ // )
+ // : [desc(vendorPOTable.lastModifiedDate)];
+ //
+ // // 데이터 조회
+ // const data = await db
+ // .select()
+ // .from(vendorPOTable)
+ // .where(finalWhere)
+ // .orderBy(...orderBy)
+ // .offset(offset)
+ // .limit(input.perPage);
+ //
+ // // 총 개수 조회
+ // const [{ count }] = await db
+ // .select({ count: count() })
+ // .from(vendorPOTable)
+ // .where(finalWhere);
+ //
+ // const pageCount = Math.ceil(count / input.perPage);
+
+ return {
+ data: result.data,
+ pageCount: result.pageCount
+ };
+ } catch (err) {
+ console.error("Error in getVendorPOs:", err);
+ return { data: [], pageCount: 0 };
+ }
+}
+
+/**
+ * 벤더 PO 상세 정보 조회
+ */
+export async function getVendorPOById(id: number): Promise<VendorPO | null> {
+ try {
+ // 목업 데이터에서 조회
+ const result = getVendorPOsPage(1, 100); // 모든 데이터 가져오기
+ const po = result.data.find(item => item.id === id);
+
+ return po || null;
+ } catch (err) {
+ console.error("Error in getVendorPOById:", err);
+ return null;
+ }
+}
+
+/**
+ * 벤더 PO 액션 처리
+ * PCR생성, 승인, 거절 등의 액션을 처리
+ */
+export async function handleVendorPOAction(
+ poId: number,
+ action: string,
+ data?: any
+): Promise<{ success: boolean; message: string }> {
+ try {
+ // 목업에서는 성공 응답만 반환
+ // 실제 구현시에는 각 액션별로 비즈니스 로직 구현
+
+ switch (action) {
+ case "pcr-create":
+ return { success: true, message: "PCR이 성공적으로 생성되었습니다." };
+ case "approve":
+ return { success: true, message: "계약이 승인되었습니다." };
+ case "cancel-approve":
+ return { success: true, message: "승인이 취소되었습니다." };
+ case "reject-contract":
+ return { success: true, message: "계약이 거절되었습니다." };
+ case "print-contract":
+ return { success: true, message: "계약서 출력이 요청되었습니다." };
+ default:
+ return { success: false, message: "알 수 없는 액션입니다." };
+ }
+ } catch (err) {
+ console.error("Error in handleVendorPOAction:", err);
+ return { success: false, message: "액션 처리 중 오류가 발생했습니다." };
+ }
+}
+
+/**
+ * 특정 계약의 상세품목 조회
+ * contract_items 테이블에서 실제 데이터를 조회합니다.
+ */
+export async function getVendorPOItems(contractId: number): Promise<VendorPOItem[]> {
+ try {
+ const rawItems = await db
+ .select({
+ // contract_items 테이블 필드들
+ id: contractItems.id,
+ contractId: contractItems.contractId,
+ itemId: contractItems.itemId,
+ description: contractItems.description,
+ quantity: contractItems.quantity,
+ unitPrice: contractItems.unitPrice,
+ taxRate: contractItems.taxRate,
+ taxAmount: contractItems.taxAmount,
+ totalLineAmount: contractItems.totalLineAmount,
+ remark: contractItems.remark,
+
+ // contracts 테이블 필드들
+ contractNo: contracts.contractNo,
+
+ // items 테이블 필드들
+ itemCode: items.itemCode,
+ itemName: items.itemName,
+ packageCode: items.packageCode,
+ unitOfMeasure: items.unitOfMeasure,
+ gradeMaterial: items.gradeMaterial,
+ steelType: items.steelType,
+ smCode: items.smCode,
+ })
+ .from(contractItems)
+ .leftJoin(contracts, eq(contractItems.contractId, contracts.id))
+ .leftJoin(items, eq(contractItems.itemId, items.id))
+ .where(eq(contractItems.contractId, contractId))
+ .orderBy(contractItems.id);
+
+ // VendorPOItem 타입으로 변환
+ 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 데이터용 기본값
+ materialNo: row.itemCode || '',
+ itemDescription: row.itemName || '',
+ materialSpec: row.description || '',
+ fittingNo: undefined, // contract_items에 없는 필드
+ cert: undefined, // contract_items에 없는 필드
+ 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 ? '과세' : '면세',
+ steelSpec: row.steelType || undefined,
+ prManager: 'AUTO-MANAGER', // mock 데이터용 기본값
+ }));
+
+ return vendorPOItems;
+ } catch (err) {
+ console.error("Error in getVendorPOItems:", err);
+ throw err;
+ }
+}
+
+/**
+ * 계약번호로 상세품목 조회 (외부에서 contractNo로 호출할 때 사용)
+ */
+export async function getVendorPOItemsByContractNo(contractNo: string): Promise<VendorPOItem[]> {
+ try {
+ // 먼저 계약 ID 조회
+ const contract = await db.query.contracts.findFirst({
+ where: eq(contracts.contractNo, contractNo),
+ });
+
+ if (!contract) {
+ console.warn(`Contract not found: ${contractNo}`);
+ return [];
+ }
+
+ return await getVendorPOItems(contract.id);
+ } catch (err) {
+ console.error("Error in getVendorPOItemsByContractNo:", err);
+ throw err;
+ }
+}
diff --git a/lib/po/vendor-table/shi-vendor-po-columns.tsx b/lib/po/vendor-table/shi-vendor-po-columns.tsx
new file mode 100644
index 00000000..041e0c05
--- /dev/null
+++ b/lib/po/vendor-table/shi-vendor-po-columns.tsx
@@ -0,0 +1,462 @@
+"use client"
+
+import * as React from "react"
+import { type ColumnDef } from "@tanstack/react-table"
+import {
+ SendIcon,
+ FileTextIcon,
+ MoreHorizontalIcon,
+} from "lucide-react"
+
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Checkbox } from "@/components/ui/checkbox"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+
+import { VendorPO } from "./types"
+
+// 서명 요청 전용 액션 타입
+type ShiVendorPORowAction = {
+ row: { original: VendorPO }
+ type: "view-items" | "signature-request"
+}
+
+interface GetShiVendorColumnsProps {
+ setRowAction: React.Dispatch<React.SetStateAction<ShiVendorPORowAction | null>>
+ selectedRows: number[]
+ onRowSelect: (id: number, selected: boolean) => void
+}
+
+export function getShiVendorColumns({
+ setRowAction,
+ selectedRows,
+ onRowSelect,
+}: GetShiVendorColumnsProps): ColumnDef<VendorPO>[] {
+ return [
+ // 선택 체크박스 (1개만 선택 가능)
+ {
+ id: "select",
+ header: () => <div className="text-center">선택</div>,
+ cell: ({ row }) => (
+ <div className="flex justify-center">
+ <Checkbox
+ checked={selectedRows.includes(row.original.id)}
+ onCheckedChange={(checked) => {
+ onRowSelect(row.original.id, !!checked)
+ }}
+ aria-label="행 선택"
+ />
+ </div>
+ ),
+ enableSorting: false,
+ enableHiding: false,
+ size: 40,
+ },
+
+ // No. (ID)
+ {
+ accessorKey: "id",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="No." />
+ ),
+ cell: ({ row }) => {
+ const id = row.getValue("id") as number
+ return <div className="text-sm font-mono">{id}</div>
+ },
+ size: 60,
+ },
+
+ // PO/계약번호
+ {
+ accessorKey: "contractNo",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="PO/계약번호" />
+ ),
+ cell: ({ row }) => {
+ const contractNo = row.getValue("contractNo") as string
+ return (
+ <div className="font-medium">
+ {contractNo}
+ </div>
+ )
+ },
+ size: 120,
+ },
+
+ // Rev. / 품번 (PO 버전)
+ {
+ accessorKey: "poVersion",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Rev. / 품번" />
+ ),
+ cell: ({ row }) => {
+ const version = row.getValue("poVersion") as number
+ return <div className="text-sm font-medium">{version || '-'}</div>
+ },
+ size: 80,
+ },
+
+ // 계약상태
+ {
+ accessorKey: "contractStatus",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약상태" />
+ ),
+ cell: ({ row }) => {
+ const status = row.getValue("contractStatus") as string
+ return (
+ <Badge variant="outline">
+ {status || '-'}
+ </Badge>
+ )
+ },
+ size: 100,
+ },
+
+ // 계약종류
+ {
+ accessorKey: "contractType",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약종류" />
+ ),
+ cell: ({ row }) => {
+ const type = row.getValue("contractType") as string
+ return <Badge variant="outline">{type || '-'}</Badge>
+ },
+ size: 100,
+ },
+
+ // 상세품목 (버튼)
+ {
+ id: "itemsAction",
+ header: () => <div className="text-center">상세품목</div>,
+ cell: ({ row }) => (
+ <div className="flex justify-center">
+ <Button
+ variant="outline"
+ size="sm"
+ className="h-8 px-2"
+ onClick={() => setRowAction({ row, type: "view-items" })}
+ >
+ <FileTextIcon className="h-3.5 w-3.5 mr-1" />
+ 보기
+ </Button>
+ </div>
+ ),
+ enableSorting: false,
+ size: 80,
+ },
+
+ // 프로젝트
+ {
+ accessorKey: "projectName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="프로젝트" />
+ ),
+ cell: ({ row }) => {
+ const projectName = row.getValue("projectName") as string
+ return (
+ <div className="max-w-[150px] truncate" title={projectName}>
+ {projectName || '-'}
+ </div>
+ )
+ },
+ size: 150,
+ },
+
+ // 계약명/자재내역
+ {
+ accessorKey: "contractName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약명/자재내역" />
+ ),
+ cell: ({ row }) => {
+ const contractName = row.getValue("contractName") as string
+ return (
+ <div className="max-w-[200px] truncate" title={contractName}>
+ {contractName || '-'}
+ </div>
+ )
+ },
+ size: 200,
+ },
+
+ // PO/계약기간
+ {
+ accessorKey: "contractPeriod",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="PO/계약기간" />
+ ),
+ cell: ({ row }) => {
+ const period = row.getValue("contractPeriod") as string
+ return (
+ <div className="text-sm whitespace-nowrap">
+ {period || '-'}
+ </div>
+ )
+ },
+ size: 150,
+ },
+
+ // PO/계약금액
+ {
+ accessorKey: "totalAmount",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="PO/계약금액" />
+ ),
+ cell: ({ row }) => {
+ const amount = row.getValue("totalAmount") as string | number
+ return <div className="text-sm text-right font-mono">{amount || '-'}</div>
+ },
+ size: 120,
+ },
+
+ // 계약통화
+ {
+ accessorKey: "currency",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약통화" />
+ ),
+ cell: ({ row }) => {
+ const currency = row.getValue("currency") as string
+ return <div className="text-sm font-mono">{currency || '-'}</div>
+ },
+ size: 80,
+ },
+
+ // 지불조건
+ {
+ accessorKey: "paymentTerms",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="지불조건" />
+ ),
+ cell: ({ row }) => {
+ const terms = row.getValue("paymentTerms") as string
+ return (
+ <div className="max-w-[120px] truncate text-sm" title={terms}>
+ {terms || '-'}
+ </div>
+ )
+ },
+ size: 120,
+ },
+
+ // Tax
+ {
+ accessorKey: "tax",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Tax" />
+ ),
+ cell: ({ row }) => {
+ const tax = row.getValue("tax") as string
+ return <div className="text-sm">{tax || '-'}</div>
+ },
+ size: 80,
+ },
+
+ // 환율
+ {
+ accessorKey: "exchangeRate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="환율" />
+ ),
+ cell: ({ row }) => {
+ const rate = row.getValue("exchangeRate") as string
+ return <div className="text-sm font-mono">{rate || '-'}</div>
+ },
+ size: 100,
+ },
+
+ // 인도조건
+ {
+ accessorKey: "deliveryTerms",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="인도조건" />
+ ),
+ cell: ({ row }) => {
+ const terms = row.getValue("deliveryTerms") as string
+ return <div className="text-sm">{terms || '-'}</div>
+ },
+ size: 100,
+ },
+
+ // 구매/계약담당
+ {
+ accessorKey: "purchaseManager",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="구매/계약담당" />
+ ),
+ cell: ({ row }) => {
+ const manager = row.getValue("purchaseManager") as string
+ return <div className="text-sm">{manager || '-'}</div>
+ },
+ size: 120,
+ },
+
+ // PO/계약수신일
+ {
+ accessorKey: "poReceiveDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="PO/계약수신일" />
+ ),
+ cell: ({ row }) => {
+ const date = row.getValue("poReceiveDate") as string
+ return <div className="text-sm">{date || '-'}</div>
+ },
+ size: 120,
+ },
+
+ // 계약체결일
+ {
+ accessorKey: "contractDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약체결일" />
+ ),
+ cell: ({ row }) => {
+ const date = row.getValue("contractDate") as string
+ return <div className="text-sm">{date || '-'}</div>
+ },
+ size: 120,
+ },
+
+ // L/C No.
+ {
+ accessorKey: "lcNo",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="L/C No." />
+ ),
+ cell: ({ row }) => {
+ const lcNo = row.getValue("lcNo") as string
+ return <div className="text-sm">{lcNo || '-'}</div>
+ },
+ size: 120,
+ },
+
+ // 납품대금 연동제 대상
+ {
+ accessorKey: "priceIndexTarget",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="납품대금 연동제 대상" />
+ ),
+ cell: ({ row }) => {
+ const target = row.getValue("priceIndexTarget") as string | boolean
+ return <div className="text-sm">{target?.toString() || '-'}</div>
+ },
+ size: 140,
+ },
+
+ // 연계 PO/계약번호
+ {
+ accessorKey: "linkedContractNo",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="연계 PO/계약번호" />
+ ),
+ cell: ({ row }) => {
+ const linkedNo = row.getValue("linkedContractNo") as string
+ return <div className="text-sm">{linkedNo || '-'}</div>
+ },
+ size: 140,
+ },
+
+ // 최종수정일
+ {
+ accessorKey: "lastModifiedDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="최종수정일" />
+ ),
+ cell: ({ row }) => {
+ const date = row.getValue("lastModifiedDate") as string
+ return <div className="text-sm">{date || '-'}</div>
+ },
+ size: 120,
+ },
+
+ // 최종수정자
+ {
+ accessorKey: "lastModifiedBy",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="최종수정자" />
+ ),
+ cell: ({ row }) => {
+ const user = row.getValue("lastModifiedBy") as string
+ return <div className="text-sm">{user || '-'}</div>
+ },
+ size: 120,
+ },
+
+ // 액션 컬럼 (서명 요청 중심)
+ {
+ id: "actions",
+ enableHiding: false,
+ header: () => <div className="text-center">Actions</div>,
+ cell: ({ row }) => {
+ return (
+ <div className="flex items-center justify-center gap-2">
+ {/* 서명 요청 버튼 */}
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button
+ variant="outline"
+ size="sm"
+ className="h-8 px-2 text-blue-600 border-blue-200 hover:bg-blue-50"
+ onClick={() => setRowAction({ row, type: "signature-request" })}
+ >
+ <SendIcon className="h-3.5 w-3.5 mr-1" aria-hidden="true" />
+ 서명 요청
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>
+ 벤더에게 전자서명 요청
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+
+ {/* 드롭다운 메뉴 (추가 액션)
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button variant="ghost" className="h-8 w-8 p-0">
+ <span className="sr-only">Open menu</span>
+ <MoreHorizontalIcon className="h-4 w-4" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end">
+ <DropdownMenuLabel>액션</DropdownMenuLabel>
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "view-items" })}
+ >
+ <FileTextIcon className="mr-2 h-4 w-4" />
+ 상세품목 보기
+ </DropdownMenuItem>
+ <DropdownMenuSeparator />
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "signature-request" })}
+ className="text-blue-600"
+ >
+ <SendIcon className="mr-2 h-4 w-4" />
+ 서명 요청
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu> */}
+ </div>
+ )
+ },
+ size: 160,
+ minSize: 160,
+ },
+ ]
+} \ No newline at end of file
diff --git a/lib/po/vendor-table/shi-vendor-po-table.tsx b/lib/po/vendor-table/shi-vendor-po-table.tsx
new file mode 100644
index 00000000..851f831e
--- /dev/null
+++ b/lib/po/vendor-table/shi-vendor-po-table.tsx
@@ -0,0 +1,267 @@
+"use client"
+
+import * as React from "react"
+import type {
+ DataTableAdvancedFilterField,
+ DataTableFilterField,
+ DataTableRowAction,
+} from "@/types/table"
+
+import { useDataTable } from "@/hooks/use-data-table"
+import { DataTable } from "@/components/data-table/data-table"
+import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
+import { toast } from "sonner"
+
+import { getVendorPOs, handleVendorPOAction } from "./service"
+import { getShiVendorColumns } from "./shi-vendor-po-columns"
+import { VendorPO, VendorPOActionType } from "./types"
+import { VendorPOItemsDialog } from "./vendor-po-items-dialog"
+import { ShiVendorPOToolbarActions } from "./shi-vendor-po-toolbar-actions"
+import { SignatureRequestModal } from "@/lib/po/table/sign-request-dialog"
+
+interface ShiVendorPoTableProps {
+ promises: Promise<
+ [
+ Awaited<ReturnType<typeof getVendorPOs>>,
+ ]
+ >
+}
+
+// Interface for signing party (서명 요청에 사용)
+interface SigningParty {
+ signerEmail: string;
+ signerName: string;
+ signerPosition: string;
+ signerType: "REQUESTER" | "VENDOR";
+ vendorContactId?: number;
+}
+
+export function ShiVendorPoTable({ promises }: ShiVendorPoTableProps) {
+ const [data, setData] = React.useState<{
+ data: VendorPO[];
+ pageCount: number;
+ }>({ data: [], pageCount: 0 });
+
+ const [selectedRows, setSelectedRows] = React.useState<number[]>([])
+
+ // 데이터 로딩
+ React.useEffect(() => {
+ promises.then(([result]) => {
+ console.log("Vendor PO data:", result.data)
+ setData(result);
+ });
+ }, [promises]);
+
+ const [rowAction, setRowAction] =
+ React.useState<DataTableRowAction<VendorPO> | null>(null)
+
+ // 다이얼로그 상태
+ const [itemsDialogOpen, setItemsDialogOpen] = React.useState(false)
+ const [signatureModalOpen, setSignatureModalOpen] = React.useState(false)
+ const [selectedPO, setSelectedPO] = React.useState<VendorPO | null>(null)
+
+ // 행 선택 처리 (1개만 선택 가능)
+ const handleRowSelect = (id: number, selected: boolean) => {
+ if (selected) {
+ setSelectedRows([id]) // 1개만 선택
+ } else {
+ setSelectedRows([])
+ }
+ }
+
+ // 행 액션 처리
+ React.useEffect(() => {
+ if (!rowAction) return
+
+ const po = rowAction.row.original
+ setSelectedPO(po)
+
+ switch (rowAction.type as VendorPOActionType) {
+ case "view-items":
+ setItemsDialogOpen(true)
+ break
+ case "signature-request":
+ setSignatureModalOpen(true)
+ break
+ case "item-status":
+ setItemsDialogOpen(true)
+ break
+ default:
+ toast.info("해당 기능은 개발 중입니다.")
+ }
+
+ setRowAction(null)
+ }, [rowAction])
+
+ // 액션 처리 함수
+ const handleAction = async (poId: number, action: string) => {
+ try {
+ const result = await handleVendorPOAction(poId, action)
+ if (result.success) {
+ toast.success(result.message)
+ // 필요시 데이터 새로고침
+ } else {
+ toast.error(result.message)
+ }
+ } catch (error) {
+ console.error("Action error:", error)
+ toast.error("액션 처리 중 오류가 발생했습니다.")
+ }
+ }
+
+ // 서명 요청 처리 함수
+ const handleSignatureRequest = async (
+ values: { signers: SigningParty[] },
+ contractId: number
+ ): Promise<void> => {
+ try {
+ // TODO: 실제 서명 요청 API 호출
+ console.log("Signature request for contract:", contractId, values);
+ toast.success("서명 요청이 성공적으로 전송되었습니다.");
+ } catch (error) {
+ console.error("Error sending signature requests:", error);
+ toast.error("서명 요청 전송 중 오류가 발생했습니다.");
+ }
+ }
+
+ const columns = React.useMemo(
+ () => getShiVendorColumns({
+ setRowAction,
+ selectedRows,
+ onRowSelect: handleRowSelect
+ }),
+ [selectedRows]
+ )
+
+ const filterFields: DataTableFilterField<VendorPO>[] = [
+ {
+ id: "contractStatus",
+ label: "계약상태",
+ options: [
+ { label: "승인대기", value: "승인대기" },
+ { label: "계약완료", value: "계약완료" },
+ { label: "진행중", value: "진행중" },
+ { label: "수정요청", value: "수정요청" },
+ { label: "거절됨", value: "거절됨" },
+ ]
+ },
+ {
+ id: "contractType",
+ label: "계약종류",
+ options: [
+ { label: "구매계약", value: "구매계약" },
+ { label: "서비스계약", value: "서비스계약" },
+ { label: "임가공계약", value: "임가공계약" },
+ ]
+ },
+ {
+ id: "currency",
+ label: "계약통화",
+ options: [
+ { label: "KRW", value: "KRW" },
+ { label: "USD", value: "USD" },
+ { label: "EUR", value: "EUR" },
+ { label: "JPY", value: "JPY" },
+ ]
+ }
+ ]
+
+ const advancedFilterFields: DataTableAdvancedFilterField<VendorPO>[] = [
+ {
+ id: "contractNo",
+ label: "PO/계약번호",
+ type: "text",
+ },
+ {
+ id: "contractName",
+ label: "계약명/자재내역",
+ type: "text",
+ },
+ {
+ id: "projectName",
+ label: "프로젝트",
+ type: "text",
+ },
+ {
+ id: "purchaseManager",
+ label: "구매/계약담당",
+ type: "text",
+ },
+ {
+ id: "poReceiveDate",
+ label: "PO/계약수신일",
+ type: "date",
+ },
+ {
+ id: "contractDate",
+ label: "계약체결일",
+ type: "date",
+ },
+ {
+ id: "lastModifiedDate",
+ label: "최종수정일",
+ type: "date",
+ },
+ ]
+
+ const { table } = useDataTable({
+ data: data.data,
+ columns,
+ pageCount: data.pageCount,
+ filterFields,
+ enablePinning: true,
+ enableAdvancedFilter: true,
+ initialState: {
+ sorting: [{ id: "lastModifiedDate", desc: true }],
+ columnPinning: { right: ["actions"] },
+ },
+ getRowId: (originalRow) => String(originalRow.id),
+ shallow: false,
+ clearOnDefault: true,
+ })
+
+ return (
+ <>
+ <DataTable
+ table={table}
+ >
+ <DataTableAdvancedToolbar
+ table={table}
+ filterFields={advancedFilterFields}
+ shallow={false}
+ >
+ <ShiVendorPOToolbarActions
+ table={table}
+ selectedRows={selectedRows}
+ onAction={handleAction}
+ onViewItems={(po) => {
+ setSelectedPO(po)
+ setItemsDialogOpen(true)
+ }}
+ />
+ </DataTableAdvancedToolbar>
+ </DataTable>
+
+ <VendorPOItemsDialog
+ open={itemsDialogOpen}
+ onOpenChange={setItemsDialogOpen}
+ po={selectedPO}
+ />
+
+ {/* 서명 요청 모달 */}
+ {selectedPO && (
+ <SignatureRequestModal
+ contract={{
+ id: selectedPO.id,
+ contractNo: selectedPO.contractNo,
+ contractName: selectedPO.contractName,
+ // 필요한 다른 필드들 매핑
+ } as any}
+ open={signatureModalOpen}
+ onOpenChange={setSignatureModalOpen}
+ onSubmit={handleSignatureRequest}
+ />
+ )}
+ </>
+ )
+}
diff --git a/lib/po/vendor-table/shi-vendor-po-toolbar-actions.tsx b/lib/po/vendor-table/shi-vendor-po-toolbar-actions.tsx
new file mode 100644
index 00000000..a18c02da
--- /dev/null
+++ b/lib/po/vendor-table/shi-vendor-po-toolbar-actions.tsx
@@ -0,0 +1,66 @@
+"use client"
+
+import * as React from "react"
+import { type Table } from "@tanstack/react-table"
+import { Download, RefreshCcw } from "lucide-react"
+import { toast } from "sonner"
+
+import { exportTableToExcel } from "@/lib/export"
+import { Button } from "@/components/ui/button"
+import { VendorPO } from "./types"
+
+interface ShiVendorPOToolbarActionsProps {
+ table: Table<VendorPO>
+ selectedRows: number[]
+ onAction: (poId: number, action: string) => Promise<void>
+ onViewItems: (po: VendorPO) => void
+}
+
+export function ShiVendorPOToolbarActions({
+ table,
+ selectedRows,
+ onAction,
+ onViewItems
+}: ShiVendorPOToolbarActionsProps) {
+
+ const handleRefresh = async () => {
+ try {
+ toast.success("데이터를 새로고침했습니다.")
+ // TODO: 실제 데이터 새로고침 로직 추가
+ window.location.reload()
+ } catch (error) {
+ toast.error("데이터 새로고침 중 오류가 발생했습니다.")
+ }
+ }
+
+ return (
+ <div className="flex items-center gap-2">
+ {/* Refresh 버튼 */}
+ <Button
+ variant="samsung"
+ size="sm"
+ className="gap-2"
+ onClick={handleRefresh}
+ >
+ <RefreshCcw className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">Refresh</span>
+ </Button>
+
+ {/* Export 버튼 */}
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() =>
+ exportTableToExcel(table, {
+ filename: "vendor-po-list",
+ excludeColumns: ["select", "actions"],
+ })
+ }
+ className="gap-2"
+ >
+ <Download className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">Export</span>
+ </Button>
+ </div>
+ )
+}
diff --git a/lib/po/vendor-table/types.ts b/lib/po/vendor-table/types.ts
new file mode 100644
index 00000000..97572ffc
--- /dev/null
+++ b/lib/po/vendor-table/types.ts
@@ -0,0 +1,119 @@
+/**
+ * 벤더용 PO 데이터 타입 정의
+ */
+
+export interface VendorPO {
+ id: number
+ // 선택 체크박스 (1개만 선택 가능)
+
+ // 메인 테이블 정보
+ contractNo: string // PO/계약번호 (EBELN)
+ revision: string // Rev. (mock 데이터용)
+ itemNo: string // 품번 (mock 데이터용)
+ contractStatus: string // 계약상태 (ZPO_CNFM_STAT)
+ contractType: string // 계약종류 (BSART)
+ details: string // 상세 (mock 데이터용)
+ projectName: string // 프로젝트
+ contractName: string // 계약명/자재내역 (ZTITLE)
+ contractPeriod: string // PO/계약기간
+ contractQuantity: string // PO/계약수량
+ currency: string // 계약통화 (ZPO_CURR)
+ paymentTerms: string // 지불조건 (ZTERM)
+ tax: string // Tax
+ exchangeRate: string // 환율 (WKURS)
+ deliveryTerms: string // 인도조건 (INCO1)
+ purchaseManager: string // 구매/계약담당
+ poReceiveDate: string // PO/계약수신일 (AEDAT)
+ contractDate: string // 계약체결일
+ lcNo?: string // L/C No.
+ priceIndexTarget: boolean // 납품대금 연동제 대상 (ZDLV_PRICE_T)
+ linkedContractNo?: string // 연계 PO/계약번호
+ lastModifiedDate: string // 최종수정일
+ lastModifiedBy: string // 최종수정자
+
+ // SAP ECC 추가 필드들
+ poVersion?: number // PO 버전 (ZPO_VER)
+ purchaseDocType?: string // 구매문서유형 (BSART)
+ purchaseOrg?: string // 구매조직 (EKORG)
+ purchaseGroup?: string // 구매그룹 (EKGRP)
+ poConfirmStatus?: string // PO확인상태 (ZPO_CNFM_STAT)
+
+ // 계약/보증 관련
+ contractGuaranteeCode?: string // 계약보증코드 (ZCNRT_GRNT_CD)
+ defectGuaranteeCode?: string // 하자보증코드 (ZDFCT_GRNT_CD)
+ guaranteePeriodCode?: string // 보증기간코드 (ZGRNT_PRD_CD)
+ advancePaymentYn?: string // 선급금여부 (ZPAMT_YN)
+
+ // 금액 관련
+ budgetAmount?: number // 예산금액 (ZBGT_AMT)
+ budgetCurrency?: string // 예산통화 (ZBGT_CURR)
+ totalAmount?: number // 총 계약 금액 (ZPO_AMT)
+ totalAmountKrw?: number // 발주금액KRW (ZPO_AMT_KRW)
+
+ // 전자계약/승인 관련
+ electronicContractYn?: string // 전자계약필요여부 (ZELC_CNRT_ND_YN)
+ electronicApprovalDate?: string // 전자승인일자 (ZELC_AGR_DT)
+ electronicApprovalTime?: string // 전자승인시간 (ZELC_AGR_TM)
+ ownerApprovalYn?: string // 선주승인필요여부 (ZOWN_AGR_IND_YN)
+
+ // 기타
+ plannedInOutFlag?: string // 계획내외구분 (ZPLN_INO_GB)
+ settlementStandard?: string // 정산기준 (ZECAL_BSE)
+ weightSettlementFlag?: string // 중량정산구분 (ZWGT_ECAL_GB)
+
+ // 연동제 관련
+ priceIndexYn?: string // 납품대금연동제대상여부 (ZDLV_PRICE_T)
+ writtenContractNo?: string // 서면계약번호 (ZWEBELN)
+ contractVersion?: number // 서면계약차수 (ZVER_NO)
+
+ // 상세품목 정보 (다이얼로그에서 표시)
+ items?: VendorPOItem[]
+}
+
+export interface VendorPOItem {
+ contractNo: string // PO/계약번호
+ itemNo: string // 품번
+ prNo: string // P/R번호
+ materialGroup: string // 자재그룹(명)
+ priceStandard: string // 단가기준
+ materialNo: string // 자재번호
+ itemDescription: string // 품목/자재내역
+ materialSpec: string // 자재내역사양
+ designMaterialNo?: string // 설계자재번호
+ fittingNo?: string // Fitting No.
+ cert?: string // Cert.
+ material?: string // 재질
+ specification: string // 규격
+ quantity: number // 수량
+ quantityUnit: string // 수량단위
+ weight?: number // 중량
+ weightUnit?: string // 중량단위
+ totalWeight?: number // 총중량
+ unitPrice: number // 단가기준 (단가)
+ priceUnit: string // 단가단위
+ priceUnitValue: string // 가격단위값
+ contractAmount: number // PO계약금액
+ adjustmentAmount?: number // 조정금액
+ deliveryDate: string // 납기일자
+ vatType: string // VAT구분
+ steelSpec?: string // 철의장 SPEC
+ prManager: string // P/R 담당자
+}
+
+// 파싱된 벤더 PO 타입 (JSON 필드들이 파싱된 상태)
+export interface VendorPOParsed extends Omit<VendorPO, 'items'> {
+ items: VendorPOItem[]
+}
+
+// 벤더 PO 액션 타입들
+export type VendorPOActionType =
+ | "pcr-create" // PCR생성
+ | "item-status" // 상세품목현황
+ | "contract-detail" // 계약상세
+ | "po-note" // PO Note
+ | "price-index" // 연동표입력
+ | "approve" // 승인
+ | "cancel-approve" // 승인취소
+ | "reject-contract" // 계약거절
+ | "print-contract" // 계약서출력
+ | "view-items" // 상세품목 보기 (다이얼로그)
diff --git a/lib/po/vendor-table/validations.ts b/lib/po/vendor-table/validations.ts
new file mode 100644
index 00000000..70b3d87a
--- /dev/null
+++ b/lib/po/vendor-table/validations.ts
@@ -0,0 +1,58 @@
+import {
+ createSearchParamsCache,
+ parseAsArrayOf,
+ parseAsInteger,
+ parseAsString,
+ parseAsStringEnum,
+} from "nuqs/server"
+import * as z from "zod"
+
+import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers"
+import { VendorPO } from "./types"
+
+export const vendorPoSearchParamsCache = createSearchParamsCache({
+ // UI 모드나 플래그 관련
+ flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault([]),
+
+ // 페이징
+ page: parseAsInteger.withDefault(1),
+ perPage: parseAsInteger.withDefault(10),
+
+ // 정렬 (최종수정일 기준 내림차순)
+ sort: getSortingStateParser<VendorPO>().withDefault([
+ { id: "lastModifiedDate", desc: true },
+ ]),
+
+ // 벤더 PO 관련 필드
+ contractNo: parseAsString.withDefault(""), // PO/계약번호
+ contractName: parseAsString.withDefault(""), // 계약명/자재내역
+ projectName: parseAsString.withDefault(""), // 프로젝트
+ contractStatus: parseAsString.withDefault(""), // 계약상태
+ contractType: parseAsString.withDefault(""), // 계약종류
+ currency: parseAsString.withDefault(""), // 계약통화
+ paymentTerms: parseAsString.withDefault(""), // 지불조건
+ deliveryTerms: parseAsString.withDefault(""), // 인도조건
+ purchaseManager: parseAsString.withDefault(""), // 구매/계약담당
+
+ // 날짜 관련
+ poReceiveDate: parseAsString.withDefault(""), // PO/계약수신일
+ contractDate: parseAsString.withDefault(""), // 계약체결일
+ lastModifiedDate: parseAsString.withDefault(""), // 최종수정일
+
+ // 기타
+ lcNo: parseAsString.withDefault(""), // L/C No.
+ priceIndexTarget: parseAsStringEnum(["true", "false"]).withDefault(""), // 납품대금 연동제 대상
+ linkedContractNo: parseAsString.withDefault(""), // 연계 PO/계약번호
+ lastModifiedBy: parseAsString.withDefault(""), // 최종수정자
+
+ // 벤더 필터링 (내부적으로 사용)
+ vendorId: parseAsInteger.withDefault(0), // 특정 벤더의 PO만 조회
+
+ // 고급 필터(Advanced) & 검색
+ filters: getFiltersStateParser().withDefault([]),
+ joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
+ search: parseAsString.withDefault(""),
+})
+
+// 최종 타입
+export type GetVendorPOSchema = Awaited<ReturnType<typeof vendorPoSearchParamsCache.parse>>
diff --git a/lib/po/vendor-table/vendor-po-columns.tsx b/lib/po/vendor-table/vendor-po-columns.tsx
new file mode 100644
index 00000000..1a655b0c
--- /dev/null
+++ b/lib/po/vendor-table/vendor-po-columns.tsx
@@ -0,0 +1,511 @@
+"use client"
+
+import * as React from "react"
+import { type ColumnDef } from "@tanstack/react-table"
+import {
+ FileTextIcon,
+ MoreHorizontalIcon,
+ EyeIcon,
+ PrinterIcon,
+ FileXIcon,
+ PlusIcon,
+ EditIcon
+} from "lucide-react"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { Checkbox } from "@/components/ui/checkbox"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import { VendorPO, VendorPOActionType } from "./types"
+
+// 벤더 PO용 행 액션 타입
+type VendorPORowAction = {
+ row: { original: VendorPO }
+ type: VendorPOActionType
+}
+
+interface GetVendorColumnsProps {
+ setRowAction: React.Dispatch<React.SetStateAction<VendorPORowAction | null>>
+ selectedRows?: number[]
+ onRowSelect?: (id: number, selected: boolean) => void
+}
+
+export function getVendorColumns({ setRowAction, selectedRows = [], onRowSelect }: GetVendorColumnsProps): ColumnDef<VendorPO>[] {
+ return [
+ // 선택 체크박스 (1개만 선택 가능)
+ {
+ id: "select",
+ header: () => <div className="text-center">선택</div>,
+ cell: ({ row }) => (
+ <div className="flex justify-center">
+ <Checkbox
+ checked={selectedRows.includes(row.original.id)}
+ onCheckedChange={(checked) => {
+ if (onRowSelect) {
+ onRowSelect(row.original.id, !!checked)
+ }
+ }}
+ aria-label="행 선택"
+ />
+ </div>
+ ),
+ enableSorting: false,
+ enableHiding: false,
+ size: 40,
+ },
+
+ // No. (ID)
+ {
+ accessorKey: "id",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="No." />
+ ),
+ cell: ({ row }) => {
+ const id = row.getValue("id") as number
+ return <div className="text-sm font-mono">{id}</div>
+ },
+ size: 60,
+ },
+
+ // PO/계약번호
+ {
+ accessorKey: "contractNo",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="PO/계약번호" />
+ ),
+ cell: ({ row }) => {
+ const contractNo = row.getValue("contractNo") as string
+ return (
+ <div className="font-medium">
+ {contractNo}
+ </div>
+ )
+ },
+ size: 120,
+ },
+
+ // Rev. / 품번 (PO 버전)
+ {
+ accessorKey: "poVersion",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Rev. / 품번" />
+ ),
+ cell: ({ row }) => {
+ const version = row.getValue("poVersion") as number
+ return <div className="text-sm font-medium">{version || '-'}</div>
+ },
+ size: 80,
+ },
+
+ // 계약상태
+ {
+ accessorKey: "contractStatus",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약상태" />
+ ),
+ cell: ({ row }) => {
+ const status = row.getValue("contractStatus") as string
+ return (
+ <Badge variant="outline">
+ {status || '-'}
+ </Badge>
+ )
+ },
+ size: 100,
+ },
+
+ // 계약종류
+ {
+ accessorKey: "contractType",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약종류" />
+ ),
+ cell: ({ row }) => {
+ const type = row.getValue("contractType") as string
+ return <Badge variant="outline">{type || '-'}</Badge>
+ },
+ size: 100,
+ },
+
+ // 상세품목 (버튼)
+ {
+ id: "itemsAction",
+ header: () => <div className="text-center">상세품목</div>,
+ cell: ({ row }) => (
+ <div className="flex justify-center">
+ <Button
+ variant="outline"
+ size="sm"
+ className="h-8 px-2"
+ onClick={() => setRowAction({ row, type: "view-items" })}
+ >
+ <FileTextIcon className="h-3.5 w-3.5 mr-1" />
+ 보기
+ </Button>
+ </div>
+ ),
+ enableSorting: false,
+ size: 80,
+ },
+
+ // 프로젝트
+ {
+ accessorKey: "projectName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="프로젝트" />
+ ),
+ cell: ({ row }) => {
+ const projectName = row.getValue("projectName") as string
+ return (
+ <div className="max-w-[150px] truncate" title={projectName}>
+ {projectName || '-'}
+ </div>
+ )
+ },
+ size: 150,
+ },
+
+ // 계약명/자재내역
+ {
+ accessorKey: "contractName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약명/자재내역" />
+ ),
+ cell: ({ row }) => {
+ const contractName = row.getValue("contractName") as string
+ return (
+ <div className="max-w-[200px] truncate" title={contractName}>
+ {contractName || '-'}
+ </div>
+ )
+ },
+ size: 200,
+ },
+
+ // PO/계약기간
+ {
+ accessorKey: "contractPeriod",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="PO/계약기간" />
+ ),
+ cell: ({ row }) => {
+ const period = row.getValue("contractPeriod") as string
+ return (
+ <div className="text-sm whitespace-nowrap">
+ {period || '-'}
+ </div>
+ )
+ },
+ size: 150,
+ },
+
+ // PO/계약금액
+ {
+ accessorKey: "totalAmount",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="PO/계약금액" />
+ ),
+ cell: ({ row }) => {
+ const amount = row.getValue("totalAmount") as string | number
+ return <div className="text-sm text-right font-mono">{amount || '-'}</div>
+ },
+ size: 120,
+ },
+
+ // 계약통화
+ {
+ accessorKey: "currency",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약통화" />
+ ),
+ cell: ({ row }) => {
+ const currency = row.getValue("currency") as string
+ return <div className="text-sm font-mono">{currency || '-'}</div>
+ },
+ size: 80,
+ },
+
+ // 지불조건
+ {
+ accessorKey: "paymentTerms",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="지불조건" />
+ ),
+ cell: ({ row }) => {
+ const terms = row.getValue("paymentTerms") as string
+ return (
+ <div className="max-w-[120px] truncate text-sm" title={terms}>
+ {terms || '-'}
+ </div>
+ )
+ },
+ size: 120,
+ },
+
+ // Tax
+ {
+ accessorKey: "tax",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Tax" />
+ ),
+ cell: ({ row }) => {
+ const tax = row.getValue("tax") as string
+ return <div className="text-sm">{tax || '-'}</div>
+ },
+ size: 80,
+ },
+
+ // 환율
+ {
+ accessorKey: "exchangeRate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="환율" />
+ ),
+ cell: ({ row }) => {
+ const rate = row.getValue("exchangeRate") as string
+ return <div className="text-sm font-mono">{rate || '-'}</div>
+ },
+ size: 100,
+ },
+
+ // 인도조건
+ {
+ accessorKey: "deliveryTerms",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="인도조건" />
+ ),
+ cell: ({ row }) => {
+ const terms = row.getValue("deliveryTerms") as string
+ return <div className="text-sm">{terms || '-'}</div>
+ },
+ size: 100,
+ },
+
+ // 구매/계약담당
+ {
+ accessorKey: "purchaseManager",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="구매/계약담당" />
+ ),
+ cell: ({ row }) => {
+ const manager = row.getValue("purchaseManager") as string
+ return <div className="text-sm">{manager || '-'}</div>
+ },
+ size: 120,
+ },
+
+ // PO/계약수신일
+ {
+ accessorKey: "poReceiveDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="PO/계약수신일" />
+ ),
+ cell: ({ row }) => {
+ const date = row.getValue("poReceiveDate") as string
+ return <div className="text-sm">{date || '-'}</div>
+ },
+ size: 120,
+ },
+
+ // 계약체결일
+ {
+ accessorKey: "contractDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약체결일" />
+ ),
+ cell: ({ row }) => {
+ const date = row.getValue("contractDate") as string
+ return <div className="text-sm">{date || '-'}</div>
+ },
+ size: 120,
+ },
+
+ // L/C No.
+ {
+ accessorKey: "lcNo",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="L/C No." />
+ ),
+ cell: ({ row }) => {
+ const lcNo = row.getValue("lcNo") as string
+ return <div className="text-sm">{lcNo || '-'}</div>
+ },
+ size: 120,
+ },
+
+ // 납품대금 연동제 대상
+ {
+ accessorKey: "priceIndexTarget",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="납품대금 연동제 대상" />
+ ),
+ cell: ({ row }) => {
+ const target = row.getValue("priceIndexTarget") as string | boolean
+ return <div className="text-sm">{target?.toString() || '-'}</div>
+ },
+ size: 140,
+ },
+
+ // 연계 PO/계약번호
+ {
+ accessorKey: "linkedContractNo",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="연계 PO/계약번호" />
+ ),
+ cell: ({ row }) => {
+ const linkedNo = row.getValue("linkedContractNo") as string
+ return <div className="text-sm">{linkedNo || '-'}</div>
+ },
+ size: 140,
+ },
+
+ // 최종수정일
+ {
+ accessorKey: "lastModifiedDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="최종수정일" />
+ ),
+ cell: ({ row }) => {
+ const date = row.getValue("lastModifiedDate") as string
+ return <div className="text-sm">{date || '-'}</div>
+ },
+ size: 120,
+ },
+
+ // 최종수정자
+ {
+ accessorKey: "lastModifiedBy",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="최종수정자" />
+ ),
+ cell: ({ row }) => {
+ const user = row.getValue("lastModifiedBy") as string
+ return <div className="text-sm">{user || '-'}</div>
+ },
+ size: 120,
+ },
+
+ // 액션 버튼들
+ {
+ id: "actions",
+ enableHiding: false,
+ header: () => <div className="text-center">액션</div>,
+ cell: function Cell({ row }) {
+ return (
+ <div className="flex gap-1">
+ {/* 상세품목 버튼 */}
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button
+ variant="outline"
+ size="sm"
+ className="h-8 px-2"
+ onClick={() => setRowAction({ row, type: "view-items" })}
+ >
+ <FileTextIcon className="h-3.5 w-3.5" aria-hidden="true" />
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>
+ 상세품목 보기
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+
+ {/* 드롭다운 메뉴 */}
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button variant="ghost" className="h-8 w-8 p-0">
+ <span className="sr-only">Open menu</span>
+ <MoreHorizontalIcon className="h-4 w-4" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end">
+ <DropdownMenuLabel>액션</DropdownMenuLabel>
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "view-items" })}
+ >
+ <FileTextIcon className="mr-2 h-4 w-4" />
+ 상세품목 보기
+ </DropdownMenuItem>
+ <DropdownMenuSeparator />
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "pcr-create" })}
+ >
+ <PlusIcon className="mr-2 h-4 w-4" />
+ PCR생성
+ </DropdownMenuItem>
+
+ <DropdownMenuSeparator />
+
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "approve" })}
+ >
+ 승인
+ </DropdownMenuItem>
+
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "cancel-approve" })}
+ >
+ 승인취소
+ </DropdownMenuItem>
+
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "reject-contract" })}
+ className="text-red-600"
+ >
+ <FileXIcon className="mr-2 h-4 w-4" />
+ 계약거절
+ </DropdownMenuItem>
+
+ <DropdownMenuSeparator />
+
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "print-contract" })}
+ >
+ <PrinterIcon className="mr-2 h-4 w-4" />
+ 계약서출력
+ </DropdownMenuItem>
+
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "contract-detail" })}
+ >
+ <EyeIcon className="mr-2 h-4 w-4" />
+ 계약상세
+ </DropdownMenuItem>
+
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "po-note" })}
+ >
+ <EditIcon className="mr-2 h-4 w-4" />
+ PO Note
+ </DropdownMenuItem>
+
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "price-index" })}
+ >
+ 연동표입력
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ </div>
+ );
+ },
+ size: 120,
+ minSize: 100,
+ },
+ ]
+} \ No newline at end of file
diff --git a/lib/po/vendor-table/vendor-po-items-dialog.tsx b/lib/po/vendor-table/vendor-po-items-dialog.tsx
new file mode 100644
index 00000000..d3b33371
--- /dev/null
+++ b/lib/po/vendor-table/vendor-po-items-dialog.tsx
@@ -0,0 +1,199 @@
+"use client"
+
+import * as React from "react"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ 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"
+
+interface VendorPOItemsDialogProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ po: VendorPO | null
+}
+
+export function VendorPOItemsDialog({ open, onOpenChange, po }: VendorPOItemsDialogProps) {
+ const [items, setItems] = React.useState<VendorPOItem[]>([])
+ const [loading, setLoading] = React.useState(false)
+ const [error, setError] = React.useState<string | null>(null)
+
+ // 상세품목 데이터 로드
+ React.useEffect(() => {
+ if (!open || !po) {
+ setItems([])
+ setError(null)
+ return
+ }
+
+ const loadItems = async () => {
+ setLoading(true)
+ setError(null)
+ try {
+ const vendorPOItems = await getVendorPOItemsByContractNo(po.contractNo)
+ setItems(vendorPOItems)
+ } catch (err) {
+ console.error("Failed to load vendor PO items:", err)
+ setError("상세품목을 불러오는 중 오류가 발생했습니다.")
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ loadItems()
+ }, [open, po])
+
+ if (!po) return null
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="max-w-[95vw] max-h-[90vh] w-full flex flex-col">
+ <DialogHeader className="flex-shrink-0">
+ <DialogTitle className="text-lg font-semibold">
+ 상세품목 현황 - {po.contractNo}
+ </DialogTitle>
+ <DialogDescription>
+ {po.contractName} ({items.length}개 품목)
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="flex-1 overflow-hidden">
+ {loading ? (
+ <div className="space-y-4">
+ <div className="text-center py-4 text-muted-foreground">
+ 상세품목을 불러오는 중...
+ </div>
+ <div className="space-y-2">
+ {Array.from({ length: 3 }).map((_, i) => (
+ <Skeleton key={i} className="h-12 w-full" />
+ ))}
+ </div>
+ </div>
+ ) : error ? (
+ <div className="text-center py-8 text-destructive">
+ {error}
+ </div>
+ ) : items.length === 0 ? (
+ <div className="text-center py-8 text-muted-foreground">
+ 등록된 상세품목이 없습니다.
+ </div>
+ ) : (
+ <div className="overflow-auto max-h-[60vh]">
+ <Table>
+ <TableHeader className="sticky top-0 bg-background z-10">
+ <TableRow>
+ <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-[120px] 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>
+ <TableHead className="min-w-[120px] whitespace-nowrap">설계자재번호</TableHead>
+ <TableHead className="min-w-[100px] whitespace-nowrap">Fitting No.</TableHead>
+ <TableHead className="min-w-[80px] whitespace-nowrap">Cert.</TableHead>
+ <TableHead className="min-w-[80px] whitespace-nowrap">재질</TableHead>
+ <TableHead className="min-w-[150px] whitespace-nowrap">규격</TableHead>
+ <TableHead className="min-w-[80px] text-right whitespace-nowrap">수량</TableHead>
+ <TableHead className="min-w-[80px] whitespace-nowrap">수량단위</TableHead>
+ <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] whitespace-nowrap">납기일자</TableHead>
+ <TableHead className="min-w-[80px] whitespace-nowrap">VAT구분</TableHead>
+ <TableHead className="min-w-[120px] whitespace-nowrap">철의장 SPEC</TableHead>
+ <TableHead className="min-w-[100px] whitespace-nowrap">P/R 담당자</TableHead>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {items.map((item, index) => (
+ <TableRow key={`${item.contractNo}-${item.itemNo}-${index}`}>
+ <TableCell className="font-medium">{item.contractNo || '-'}</TableCell>
+ <TableCell>{item.itemNo || '-'}</TableCell>
+ <TableCell>{item.prNo || '-'}</TableCell>
+ <TableCell>{item.materialGroup || '-'}</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 || ''}>
+ {item.itemDescription || '-'}
+ </div>
+ </TableCell>
+ <TableCell className="max-w-[200px]">
+ <div className="truncate" title={item.materialSpec || ''}>
+ {item.materialSpec || '-'}
+ </div>
+ </TableCell>
+ <TableCell>{item.designMaterialNo || '-'}</TableCell>
+ <TableCell>{item.fittingNo || '-'}</TableCell>
+ <TableCell>{item.cert || '-'}</TableCell>
+ <TableCell>{item.material || '-'}</TableCell>
+ <TableCell>{item.specification || '-'}</TableCell>
+ <TableCell className="text-right font-mono">
+ {item.quantity?.toLocaleString() || '-'}
+ </TableCell>
+ <TableCell>{item.quantityUnit || '-'}</TableCell>
+ <TableCell className="text-right font-mono">
+ {item.weight ? item.weight.toLocaleString() : '-'}
+ </TableCell>
+ <TableCell>{item.weightUnit || '-'}</TableCell>
+ <TableCell className="text-right font-mono">
+ {item.totalWeight ? item.totalWeight.toLocaleString() : '-'}
+ </TableCell>
+ <TableCell className="text-right font-mono">
+ {item.unitPrice?.toLocaleString() || '-'}
+ </TableCell>
+ <TableCell>{item.priceUnit || '-'}</TableCell>
+ <TableCell>{item.priceUnitValue || '-'}</TableCell>
+ <TableCell className="text-right font-mono font-semibold">
+ {item.contractAmount?.toLocaleString() || '-'}
+ </TableCell>
+ <TableCell className="text-right font-mono">
+ {item.adjustmentAmount ? item.adjustmentAmount.toLocaleString() : '-'}
+ </TableCell>
+ <TableCell>{item.deliveryDate || '-'}</TableCell>
+ <TableCell>{item.vatType || '-'}</TableCell>
+ <TableCell>{item.steelSpec || '-'}</TableCell>
+ <TableCell>{item.prManager || '-'}</TableCell>
+ </TableRow>
+ ))}
+ </TableBody>
+ </Table>
+ </div>
+ )}
+ </div>
+
+ {items.length > 0 && (
+ <div className="flex justify-between items-center pt-4 border-t flex-shrink-0">
+ <div className="text-sm text-muted-foreground">
+ 총 {items.length}개 품목
+ </div>
+ <div className="text-sm font-medium">
+ 총 계약금액: {items.reduce((sum, item) => sum + item.contractAmount, 0).toLocaleString()} 원
+ </div>
+ </div>
+ )}
+ </DialogContent>
+ </Dialog>
+ )
+}
diff --git a/lib/po/vendor-table/vendor-po-table.tsx b/lib/po/vendor-table/vendor-po-table.tsx
new file mode 100644
index 00000000..a3ad4949
--- /dev/null
+++ b/lib/po/vendor-table/vendor-po-table.tsx
@@ -0,0 +1,247 @@
+"use client"
+
+import * as React from "react"
+import type {
+ DataTableAdvancedFilterField,
+ DataTableFilterField,
+ DataTableRowAction,
+} from "@/types/table"
+
+import { useDataTable } from "@/hooks/use-data-table"
+import { DataTable } from "@/components/data-table/data-table"
+import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
+import { toast } from "sonner"
+
+import { getVendorPOs, handleVendorPOAction } from "./service"
+import { getVendorColumns } from "./vendor-po-columns"
+import { VendorPO, VendorPOActionType } from "./types"
+import { VendorPOItemsDialog } from "./vendor-po-items-dialog"
+import { VendorPOToolbarActions } from "./vendor-po-toolbar-actions"
+
+interface VendorPoTableProps {
+ promises: Promise<
+ [
+ Awaited<ReturnType<typeof getVendorPOs>>,
+ ]
+ >
+}
+
+export function VendorPoTable({ promises }: VendorPoTableProps) {
+ const [data, setData] = React.useState<{
+ data: VendorPO[];
+ pageCount: number;
+ }>({ data: [], pageCount: 0 });
+
+ const [selectedRows, setSelectedRows] = React.useState<number[]>([])
+
+ // 데이터 로딩
+ React.useEffect(() => {
+ promises.then(([result]) => {
+ console.log("Vendor PO data:", result.data)
+ setData(result);
+ });
+ }, [promises]);
+
+ const [rowAction, setRowAction] =
+ React.useState<DataTableRowAction<VendorPO> | null>(null)
+
+ // 다이얼로그 상태
+ const [itemsDialogOpen, setItemsDialogOpen] = React.useState(false)
+ const [selectedPO, setSelectedPO] = React.useState<VendorPO | null>(null)
+
+ // 행 선택 처리 (1개만 선택 가능)
+ const handleRowSelect = (id: number, selected: boolean) => {
+ if (selected) {
+ setSelectedRows([id]) // 1개만 선택
+ } else {
+ setSelectedRows([])
+ }
+ }
+
+ // 행 액션 처리
+ React.useEffect(() => {
+ if (!rowAction) return
+
+ const po = rowAction.row.original
+ setSelectedPO(po)
+
+ switch (rowAction.type as VendorPOActionType) {
+ case "view-items":
+ setItemsDialogOpen(true)
+ break
+ case "pcr-create":
+ handleAction(po.id, "pcr-create")
+ break
+ case "approve":
+ handleAction(po.id, "approve")
+ break
+ case "cancel-approve":
+ handleAction(po.id, "cancel-approve")
+ break
+ case "reject-contract":
+ handleAction(po.id, "reject-contract")
+ break
+ case "print-contract":
+ handleAction(po.id, "print-contract")
+ break
+ case "item-status":
+ setItemsDialogOpen(true)
+ break
+ case "contract-detail":
+ toast.info("계약상세 기능은 개발 중입니다.")
+ break
+ case "po-note":
+ toast.info("PO Note 기능은 개발 중입니다.")
+ break
+ case "price-index":
+ toast.info("연동표입력 기능은 개발 중입니다.")
+ break
+ default:
+ toast.info("해당 기능은 개발 중입니다.")
+ }
+
+ setRowAction(null)
+ }, [rowAction])
+
+ // 액션 처리 함수
+ const handleAction = async (poId: number, action: string) => {
+ try {
+ const result = await handleVendorPOAction(poId, action)
+ if (result.success) {
+ toast.success(result.message)
+ // 필요시 데이터 새로고침
+ } else {
+ toast.error(result.message)
+ }
+ } catch (error) {
+ console.error("Action error:", error)
+ toast.error("액션 처리 중 오류가 발생했습니다.")
+ }
+ }
+
+ const columns = React.useMemo(
+ () => getVendorColumns({
+ setRowAction,
+ selectedRows,
+ onRowSelect: handleRowSelect
+ }),
+ [selectedRows]
+ )
+
+ const filterFields: DataTableFilterField<VendorPO>[] = [
+ {
+ id: "contractStatus",
+ label: "계약상태",
+ options: [
+ { label: "승인대기", value: "승인대기" },
+ { label: "계약완료", value: "계약완료" },
+ { label: "진행중", value: "진행중" },
+ { label: "수정요청", value: "수정요청" },
+ { label: "거절됨", value: "거절됨" },
+ ]
+ },
+ {
+ id: "contractType",
+ label: "계약종류",
+ options: [
+ { label: "구매계약", value: "구매계약" },
+ { label: "서비스계약", value: "서비스계약" },
+ { label: "임가공계약", value: "임가공계약" },
+ ]
+ },
+ {
+ id: "currency",
+ label: "계약통화",
+ options: [
+ { label: "KRW", value: "KRW" },
+ { label: "USD", value: "USD" },
+ { label: "EUR", value: "EUR" },
+ { label: "JPY", value: "JPY" },
+ ]
+ }
+ ]
+
+ const advancedFilterFields: DataTableAdvancedFilterField<VendorPO>[] = [
+ {
+ id: "contractNo",
+ label: "PO/계약번호",
+ type: "text",
+ },
+ {
+ id: "contractName",
+ label: "계약명/자재내역",
+ type: "text",
+ },
+ {
+ id: "projectName",
+ label: "프로젝트",
+ type: "text",
+ },
+ {
+ id: "purchaseManager",
+ label: "구매/계약담당",
+ type: "text",
+ },
+ {
+ id: "poReceiveDate",
+ label: "PO/계약수신일",
+ type: "date",
+ },
+ {
+ id: "contractDate",
+ label: "계약체결일",
+ type: "date",
+ },
+ {
+ id: "lastModifiedDate",
+ label: "최종수정일",
+ type: "date",
+ },
+ ]
+
+ const { table } = useDataTable({
+ data: data.data,
+ columns,
+ pageCount: data.pageCount,
+ filterFields,
+ enablePinning: true,
+ enableAdvancedFilter: true,
+ initialState: {
+ sorting: [{ id: "lastModifiedDate", desc: true }],
+ columnPinning: { right: ["actions"] },
+ },
+ getRowId: (originalRow) => String(originalRow.id),
+ shallow: false,
+ clearOnDefault: true,
+ })
+
+ return (
+ <>
+ <DataTable
+ table={table}
+ >
+ <DataTableAdvancedToolbar
+ table={table}
+ filterFields={advancedFilterFields}
+ shallow={false}
+ >
+ <VendorPOToolbarActions
+ table={table}
+ selectedRows={selectedRows}
+ onAction={handleAction}
+ onViewItems={(po) => {
+ setSelectedPO(po)
+ setItemsDialogOpen(true)
+ }}
+ />
+ </DataTableAdvancedToolbar>
+ </DataTable>
+
+ <VendorPOItemsDialog
+ open={itemsDialogOpen}
+ onOpenChange={setItemsDialogOpen}
+ po={selectedPO}
+ />
+ </>
+ )
+} \ No newline at end of file
diff --git a/lib/po/vendor-table/vendor-po-toolbar-actions.tsx b/lib/po/vendor-table/vendor-po-toolbar-actions.tsx
new file mode 100644
index 00000000..800a9e40
--- /dev/null
+++ b/lib/po/vendor-table/vendor-po-toolbar-actions.tsx
@@ -0,0 +1,214 @@
+"use client"
+
+import * as React from "react"
+import { type Table } from "@tanstack/react-table"
+import {
+ EyeIcon,
+ EditIcon,
+ FileXIcon,
+ PrinterIcon,
+} from "lucide-react"
+
+import { Button } from "@/components/ui/button"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip"
+import { toast } from "sonner"
+import { VendorPO } from "./types"
+
+interface VendorPOToolbarActionsProps {
+ table: Table<VendorPO>
+ selectedRows: number[]
+ onAction: (poId: number, action: string) => Promise<void>
+ onViewItems?: (po: VendorPO) => void
+}
+
+export function VendorPOToolbarActions({
+ table,
+ selectedRows,
+ onAction,
+ onViewItems
+}: VendorPOToolbarActionsProps) {
+ const hasSelectedRow = selectedRows.length === 1
+ const selectedPO = hasSelectedRow ?
+ table.getRowModel().rows.find(row => selectedRows.includes(row.original.id))?.original
+ : null
+
+ const handleToolbarAction = async (action: string) => {
+ if (!hasSelectedRow || !selectedPO) {
+ toast.error("먼저 PO를 선택해주세요.")
+ return
+ }
+
+ // view-items 액션은 특별히 처리
+ if (action === "view-items") {
+ if (onViewItems) {
+ onViewItems(selectedPO)
+ }
+ return
+ }
+
+ await onAction(selectedPO.id, action)
+ }
+
+ return (
+ <div className="flex items-center gap-2">
+ {/* 주요 액션 버튼들 */}
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button
+ variant="default"
+ size="sm"
+ onClick={() => handleToolbarAction("pcr-create")}
+ disabled={!hasSelectedRow}
+ className="h-8"
+ >
+ PCR생성
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>선택된 PO에 대한 PCR을 생성합니다</TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => handleToolbarAction("view-items")}
+ disabled={!hasSelectedRow}
+ className="h-8"
+ >
+ 상세품목현황
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>상세품목 현황을 확인합니다</TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+
+ {/* 승인 관련 액션 */}
+ {selectedPO?.contractStatus !== "승인완료" && (
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => handleToolbarAction("approve")}
+ disabled={!hasSelectedRow}
+ className="h-8 text-green-600 border-green-600 hover:bg-green-50"
+ >
+ 승인
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>선택된 계약을 승인합니다</TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ )}
+
+ {selectedPO?.contractStatus === "승인완료" && (
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => handleToolbarAction("cancel-approve")}
+ disabled={!hasSelectedRow}
+ className="h-8 text-orange-600 border-orange-600 hover:bg-orange-50"
+ >
+ 승인취소
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>승인을 취소합니다</TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ )}
+
+ {/* 더 많은 액션 드롭다운 */}
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button
+ variant="outline"
+ size="sm"
+ disabled={!hasSelectedRow}
+ className="h-8"
+ >
+ 더 많은 액션
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end" className="w-[200px]">
+ <DropdownMenuLabel>계약 관련</DropdownMenuLabel>
+ <DropdownMenuSeparator />
+
+ <DropdownMenuItem
+ onClick={() => handleToolbarAction("contract-detail")}
+ disabled={!hasSelectedRow}
+ >
+ <EyeIcon className="mr-2 h-4 w-4" />
+ 계약상세
+ </DropdownMenuItem>
+
+ <DropdownMenuItem
+ onClick={() => handleToolbarAction("po-note")}
+ disabled={!hasSelectedRow}
+ >
+ <EditIcon className="mr-2 h-4 w-4" />
+ PO Note
+ </DropdownMenuItem>
+
+ <DropdownMenuItem
+ onClick={() => handleToolbarAction("price-index")}
+ disabled={!hasSelectedRow}
+ >
+ <EditIcon className="mr-2 h-4 w-4" />
+ 연동표입력
+ </DropdownMenuItem>
+
+ <DropdownMenuSeparator />
+
+ <DropdownMenuItem
+ onClick={() => handleToolbarAction("reject-contract")}
+ disabled={!hasSelectedRow}
+ className="text-red-600 focus:text-red-600"
+ >
+ <FileXIcon className="mr-2 h-4 w-4" />
+ 계약거절
+ </DropdownMenuItem>
+
+ <DropdownMenuSeparator />
+
+ <DropdownMenuItem
+ onClick={() => handleToolbarAction("print-contract")}
+ disabled={!hasSelectedRow}
+ >
+ <PrinterIcon className="mr-2 h-4 w-4" />
+ 계약서출력
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+
+ {/* 선택된 행 정보 표시
+ {hasSelectedRow && selectedPO && (
+ <div className="flex items-center gap-2 ml-4 text-sm text-muted-foreground">
+ <span>선택됨:</span>
+ <span className="font-medium">{selectedPO.contractNo}</span>
+ <span>({selectedPO.contractName})</span>
+ </div>
+ )} */}
+ </div>
+ )
+}
diff --git a/lib/soap/ecc/mapper/po-mapper.ts b/lib/soap/ecc/mapper/po-mapper.ts
index 6e282b98..9303cbcd 100644
--- a/lib/soap/ecc/mapper/po-mapper.ts
+++ b/lib/soap/ecc/mapper/po-mapper.ts
@@ -108,23 +108,63 @@ export async function mapECCPOHeaderToBusiness(
throw new Error(`벤더를 찾을 수 없습니다: LIFNR=${eccHeader.LIFNR}`);
}
- // 매핑
+ // 매핑 - SAP ECC 필드명과 함께 주석 추가
const mappedData: ContractData = {
projectId,
vendorId,
- contractNo: eccHeader.EBELN || '',
- contractName: eccHeader.ZTITLE || eccHeader.EBELN || '',
- status: eccHeader.ZPO_CNFM_STAT || 'ACTIVE',
- startDate: parseDate(eccHeader.ZPO_DT || null),
+ contractNo: eccHeader.EBELN || '', // EBELN - 구매오더번호
+ contractName: eccHeader.ZTITLE || eccHeader.EBELN || '', // ZTITLE - 발주제목
+ status: eccHeader.ZPO_CNFM_STAT || 'ACTIVE', // ZPO_CNFM_STAT - 구매오더확인상태
+ startDate: parseDate(eccHeader.ZPO_DT || null), // ZPO_DT - 발주일자
endDate: null, // ZMM_DT에서 가져와야 함
deliveryDate: null, // ZMM_DT에서 가져와야 함
- paymentTerms: eccHeader.ZTERM || null,
- deliveryTerms: eccHeader.INCO1 || null,
- deliveryLocation: eccHeader.ZUNLD_PLC_CD || null,
- currency: eccHeader.ZPO_CURR || 'KRW',
- totalAmount: parseAmount(eccHeader.ZPO_AMT || null),
- netTotal: parseAmount(eccHeader.ZPO_AMT || null),
- remarks: eccHeader.ETC_2 || null,
+
+ // SAP ECC 기본 필드들
+ paymentTerms: eccHeader.ZTERM || null, // ZTERM - 지급조건코드
+ deliveryTerms: eccHeader.INCO1 || null, // INCO1 - 인도조건코드
+ shippmentPlace: eccHeader.ZSHIPMT_PLC_CD || null, // ZSHIPMT_PLC_CD - 선적지코드
+ deliveryLocation: eccHeader.ZUNLD_PLC_CD || null, // ZUNLD_PLC_CD - 하역지코드
+
+ // SAP ECC 추가 필드들
+ poVersion: eccHeader.ZPO_VER ? parseInt(eccHeader.ZPO_VER) : null, // ZPO_VER - 발주버전
+ purchaseDocType: eccHeader.BSART || null, // BSART - 구매문서유형
+ 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 - 구매오더확인상태
+
+ // 계약/보증 관련
+ contractGuaranteeCode: eccHeader.ZCNRT_GRNT_CD || null, // ZCNRT_GRNT_CD - 계약보증코드
+ defectGuaranteeCode: eccHeader.ZDFCT_GRNT_CD || null, // ZDFCT_GRNT_CD - 하자보증코드
+ guaranteePeriodCode: eccHeader.ZGRNT_PRD_CD || null, // ZGRNT_PRD_CD - 보증기간코드
+ advancePaymentYn: eccHeader.ZPAMT_YN || null, // ZPAMT_YN - 선급금여부
+
+ // 금액 관련
+ budgetAmount: parseAmount(eccHeader.ZBGT_AMT || null), // ZBGT_AMT - 예산금액
+ budgetCurrency: eccHeader.ZBGT_CURR || null, // ZBGT_CURR - 예산금액 통화키
+ currency: eccHeader.ZPO_CURR || 'KRW', // ZPO_CURR - 통화키
+ totalAmount: parseAmount(eccHeader.ZPO_AMT || null), // ZPO_AMT - 발주금액
+ totalAmountKrw: parseAmount(eccHeader.ZPO_AMT_KRW || null), // ZPO_AMT_KRW - 발주금액 KRW
+
+ // 전자계약/승인 관련
+ electronicContractYn: eccHeader.ZELC_CNRT_ND_YN || null, // ZELC_CNRT_ND_YN - 전자계약필요여부
+ electronicApprovalDate: parseDate(eccHeader.ZELC_AGR_DT || null), // ZELC_AGR_DT - 전자승인일자
+ electronicApprovalTime: eccHeader.ZELC_AGR_TM || null, // ZELC_AGR_TM - 전자승인시간
+ ownerApprovalYn: eccHeader.ZOWN_AGR_IND_YN || null, // ZOWN_AGR_IND_YN - 선주승인필요여부
+
+ // 기타
+ plannedInOutFlag: eccHeader.ZPLN_INO_GB || null, // ZPLN_INO_GB - 계획내외구분
+ settlementStandard: eccHeader.ZECAL_BSE || null, // ZECAL_BSE - 정산기준
+ weightSettlementFlag: eccHeader.ZWGT_ECAL_GB || null, // ZWGT_ECAL_GB - 중량정산구분
+
+ // 연동제 관련
+ priceIndexYn: eccHeader.ZDLV_PRICE_T || null, // ZDLV_PRICE_T - 납품대금연동제대상여부
+ writtenContractNo: eccHeader.ZWEBELN || null, // ZWEBELN - 서면계약번호
+ contractVersion: eccHeader.ZVER_NO ? parseInt(eccHeader.ZVER_NO) : null, // ZVER_NO - 서면계약차수
+
+ netTotal: parseAmount(eccHeader.ZPO_AMT || null), // ZPO_AMT와 동일
+ remarks: eccHeader.ETC_2 || null, // ETC_2 - 확장2
+
// 기본값들
discount: null,
tax: null,