Kaynağa Gözat

엑셀 업로드 수정 (최대 콘텐츠 20개까지, 고유번호와 업로드일자 추가)

interscope_003\interscope 3 ay önce
ebeveyn
işleme
5234d3733c

+ 5 - 1
backend/app/Config/Routes.php

@@ -64,13 +64,17 @@ $routes->post('item/search', 'Item::itemSearch');
 // 파일 다운로드
 $routes->get('item/download/(:segment)', 'Item::file/$1');
 
-// 제품 주문 라우트
+// 제품 주문 라우트 :: old
 $routes->get('deli/orderList/(:segment)', 'Deli::orderList');
 $routes->post('deli/reg', 'Deli::orderRegister');
 $routes->post('deli/search', 'Deli::deliSearch');
 $routes->post('deli/itemlist', 'Deli::itemlist');
 $routes->post('deli/list', 'Deli::delilist');
 
+// 제품 주문 내역 라우트 :: new (20250904)
+$routes->get('order/orderList/(:segment)', 'Order::orderList');
+$routes->post('order/reg', 'Order::orderRegister');
+
 // 마이페이지 라우트
 $routes->post('mypage/detail', 'Mypage::myDetail');
 $routes->post('mypage/update', 'Mypage::myUpdate');

+ 191 - 0
backend/app/Controllers/Order.php

@@ -0,0 +1,191 @@
+<?php
+
+namespace App\Controllers;
+
+use CodeIgniter\RESTful\ResourceController;
+
+class Order extends ResourceController
+{
+    public function orderList($itemSeq = null)
+    {
+        $db = \Config\Database::connect();
+
+        // ORDER_HEADER_LIST에서 전체 데이터 가져오기
+        $headerBuilder = $db->table('ORDER_HEADER_LIST');
+        $headerData = $headerBuilder->get()->getResultArray();
+        
+        // 헤더 데이터에 업로드 일자, 고유번호 필수 추가 (HEADER_21, 22 위치에)
+        foreach ($headerData as &$header) {
+            // 항상 HEADER_21, 22에 고정값 추가
+            $header['HEADER_21'] = '업로드 일자';
+            $header['HEADER_22'] = '고유번호';
+        }
+
+        // ORDER_LIST에서 전체 데이터 가져오기
+        $orderBuilder = $db->table('ORDER_LIST')
+            ->orderBy('CONTENT_REGDATE', 'DESC');
+        $orderData = $orderBuilder->get()->getResultArray();
+        
+        // 주문 데이터는 그대로 반환 (CONTENT_REGDATE, COLUMN_CODE 포함)
+
+        // 응답 데이터 구성
+        $response = [
+            'headerList' => $headerData,  // ORDER_HEADER_LIST 전체 데이터
+            'orderList' => $orderData      // ORDER_LIST 전체 데이터
+        ];
+
+        return $this->respond($response, 200);
+    }
+
+    public function orderRegister()
+    {
+        $db = \Config\Database::connect();
+        $request = $this->request->getJSON(true);
+
+        $itemSeq = $request['itemSeq'] ?? null;
+        $headers = $request['headers'] ?? [];
+        $orderData = $request['orderData'] ?? [];
+
+        // 디버깅 로그
+        log_message('info', 'orderRegister 호출됨');
+        log_message('info', 'itemSeq: ' . $itemSeq);
+        log_message('info', 'headers: ' . json_encode($headers));
+        log_message('info', 'orderData 개수: ' . count($orderData));
+
+        if (!$itemSeq) {
+            return $this->respond(['error' => 'ITEM_SEQ가 필요합니다.'], 400);
+        }
+
+        $db->transBegin();
+
+        try {
+            // 헤더와 주문 데이터가 모두 비어있으면 전체 삭제
+            if (empty($headers) && empty($orderData)) {
+                log_message('info', '전체 데이터 삭제 요청');
+                
+                // ORDER_HEADER_LIST에서 해당 ITEM_SEQ 데이터 삭제
+                $db->table('ORDER_HEADER_LIST')->where('ITEM_SEQ', $itemSeq)->delete();
+                
+                // ORDER_LIST에서 해당 ITEM_SEQ 데이터 삭제
+                $db->table('ORDER_LIST')->where('ITEM_SEQ', $itemSeq)->delete();
+                
+                $db->transCommit();
+                
+                return $this->respond([
+                    'success' => true,
+                    'message' => '모든 주문 데이터가 삭제되었습니다.',
+                    'headerCount' => 0,
+                    'orderCount' => 0
+                ], 200);
+            }
+            
+            // 1. ORDER_HEADER_LIST에 헤더 정보 저장/업데이트
+            if (!empty($headers)) {
+                log_message('info', '헤더 데이터 저장 시작');
+
+                // 기존 헤더 데이터 삭제
+                $deleteCount = $db->table('ORDER_HEADER_LIST')->where('ITEM_SEQ', $itemSeq)->countAllResults();
+                log_message('info', '삭제할 기존 헤더 개수: ' . $deleteCount);
+
+                $db->table('ORDER_HEADER_LIST')->where('ITEM_SEQ', $itemSeq)->delete();
+
+                // headers 배열에는 주문일, 고유번호 추가하지 않음 (프론트엔드에서 처리)
+
+                // 새로운 헤더 데이터 삽입
+                $headerInsertData = ['ITEM_SEQ' => $itemSeq];
+
+                // 순수 엑셀 헤더만 저장 (업로드 일자, 고유번호 제외)
+                $pureHeaders = [];
+                foreach ($headers as $header) {
+                    if ($header !== '업로드 일자' && $header !== '고유번호') {
+                        $pureHeaders[] = $header;
+                    }
+                }
+                
+                foreach ($pureHeaders as $index => $header) {
+                    if ($index < 20 && !empty($header)) {
+                        $headerKey = 'HEADER_' . ($index + 1);
+                        $headerInsertData[$headerKey] = $header;
+                        log_message('info', "헤더 매핑: {$headerKey} = {$header}");
+                    }
+                }
+                
+                // HEADER_21과 HEADER_22에 고정값 설정
+                $headerInsertData['HEADER_21'] = '업로드 일자';
+                $headerInsertData['HEADER_22'] = '고유번호';
+
+                log_message('info', '헤더 삽입 데이터: ' . json_encode($headerInsertData));
+
+                $result = $db->table('ORDER_HEADER_LIST')->insert($headerInsertData);
+                log_message('info', '헤더 삽입 결과: ' . ($result ? 'success' : 'failed'));
+
+            } else {
+                log_message('info', '헤더 데이터가 비어있음');
+            }
+
+            // 2. ORDER_LIST에 주문 데이터 저장 (기존 데이터 모두 삭제 후 새로 저장)
+            if (!empty($orderData)) {
+                log_message('info', '주문 데이터 저장 시작');
+                
+                // 기존 주문 데이터 모두 삭제
+                $deleteCount = $db->table('ORDER_LIST')->where('ITEM_SEQ', $itemSeq)->countAllResults();
+                log_message('info', '삭제할 기존 주문 개수: ' . $deleteCount);
+                
+                $db->table('ORDER_LIST')->where('ITEM_SEQ', $itemSeq)->delete();
+
+                // 새로운 주문 데이터 모두 삽입
+                foreach ($orderData as $row) {
+                    $orderInsertData = ['ITEM_SEQ' => $itemSeq];
+
+                    // CONTENT_1 ~ CONTENT_20만 매핑 (21, 22는 제외)
+                    for ($i = 1; $i <= 20; $i++) {
+                        $contentKey = 'CONTENT_' . $i;
+                        if (isset($row[$contentKey])) {
+                            $orderInsertData[$contentKey] = $row[$contentKey];
+                        }
+                    }
+
+                    // CONTENT_REGDATE와 COLUMN_CODE 설정 (프론트엔드에서 전달받은 값 사용)
+                    if (isset($row['CONTENT_REGDATE']) && !empty($row['CONTENT_REGDATE'])) {
+                        $orderInsertData['CONTENT_REGDATE'] = $row['CONTENT_REGDATE']; // 업로드 일자
+                    }
+                    
+                    if (isset($row['COLUMN_CODE']) && !empty($row['COLUMN_CODE'])) {
+                        $orderInsertData['COLUMN_CODE'] = $row['COLUMN_CODE']; // 고유번호
+                    } else {
+                        // 고유번호가 없으면 새로 생성
+                        $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+                        $hash = '';
+                        for ($j = 0; $j < 50; $j++) {
+                            $hash .= $chars[rand(0, strlen($chars) - 1)];
+                        }
+                        $orderInsertData['COLUMN_CODE'] = $hash;
+                        log_message('info', '고유번호 자동생성: ' . $hash);
+                    }
+                    
+                    $db->table('ORDER_LIST')->insert($orderInsertData);
+                    log_message('info', '새 데이터 인서트: ' . $orderInsertData['COLUMN_CODE']);
+                }
+            }
+
+            $db->transCommit();
+
+            $response = [
+                'success' => true,
+                'message' => '주문 데이터가 성공적으로 저장되었습니다.',
+                'headerCount' => count($headers),
+                'orderCount' => count($orderData)
+            ];
+
+            return $this->respond($response, 200);
+
+        } catch (Exception $e) {
+            $db->transRollback();
+
+            return $this->respond([
+                'error' => '데이터 저장 중 오류가 발생했습니다.',
+                'details' => $e->getMessage()
+            ], 500);
+        }
+    }
+}

+ 1 - 1
components/common/header.vue

@@ -134,7 +134,7 @@
         {
           menuId: "menu01",
           parentMenuId: "menu01",
-          menuName: "제품 관리",
+          menuName: "공동구매",
           linkType: "/view/common/item",
         },
         // {

+ 647 - 260
pages/view/common/item/detail.vue

@@ -114,13 +114,14 @@
                       <div class="caption--box">
                         - 주문 내역 입력 후 저장 버튼을 꼭 클릭해 주세요.<br />
                         - 엑셀 파일은 최대 10MB까지 업로드 가능합니다.<br />
-                        - 엑셀 파일의 헤더명(주문번호, 구매자명)이 일치해야 정상적으로 업로드됩니다.
+                        - 엑셀의 헤더는 20개를 초과할 수 없습니다.<br>
+                        - 고유번호는 시스템 관리용 번호입니다. 변경 시 오류가 발생할 수 있습니다.
                       </div>
                     </div>
-                    <v-btn class="custom-btn btn-white mini" @click="addEmptyRow"
+                    <v-btn v-if="hasData" class="custom-btn btn-white mini" @click="addEmptyRow"
                       ><i class="ico"></i>항목 추가</v-btn
                     >
-                    <v-btn class="custom-btn btn-white mini" @click="deleteSelectedRows"
+                    <v-btn v-if="hasData" class="custom-btn btn-white mini" @click="deleteSelectedRows"
                       ><i class="ico"></i>항목 삭제</v-btn
                     >
                     <input
@@ -133,7 +134,7 @@
                     <v-btn class="custom-btn btn-excel" @click="$refs.excelFileInput.click()"
                       ><i class="ico"></i>엑셀 업로드</v-btn
                     >
-                    <v-btn class="custom-btn btn-excel" @click="downloadExcel"
+                    <v-btn v-if="hasData" class="custom-btn btn-excel" @click="downloadExcel"
                       ><i class="ico"></i>엑셀 다운로드</v-btn
                     >
                     <v-btn class="custom-btn btn-purple mini" @click="fnRegEvt"
@@ -278,61 +279,17 @@ const pageType = ref("");
 // ag-grid 관련 변수
 const tblItems = ref([]);
 const gridApi = ref(null);
+const uploadedHeaders = ref([]); // 엑셀에서 업로드한 헤더 저장
+
+// 데이터 존재 여부 확인 (컬럼이 있고 데이터도 있어야 함)
+const hasData = computed(() => {
+  return gridOptions.value.columnDefs && 
+         gridOptions.value.columnDefs.length > 0 && 
+         tblItems.value && 
+         tblItems.value.length > 0;
+});
 const gridOptions = ref({
-  columnDefs: [
-    { checkboxSelection: true, headerCheckboxSelection: true, width: 50, sortable: false, filter: false,},
-    {
-      headerName: "No",
-      valueGetter: (params) => params.api.getDisplayedRowCount() - params.node.rowIndex,
-      sortable: false,
-      filter: false,
-      width: 80,
-    },
-    {
-      headerName: "주문번호",
-      field: "ORDER_NUMB",
-      cellStyle: { textAlign: 'center' },
-      editable: true,
-    },
-    {
-      headerName: "구매자명",
-      field: "BUYER_NAME",
-      editable: true,
-      width: 120,
-    },
-    {
-      headerName: "연락처",
-      field: "PHONE",
-      editable: true,
-      width: 150,
-    },
-    {
-      headerName: "주소",
-      field: "ADDRESS",
-      editable: true,
-      resizable: true,
-    },
-    {
-      headerName: "수량",
-      field: "QTY",
-      width: 100,
-      editable: true,
-      valueFormatter: (params) => {
-        return params.value ? Number(params.value).toLocaleString() : '0';
-      }
-    },
-    {
-      headerName: "배송업체",
-      field: "DELI_COMP",
-      editable: true,
-      width: 150,
-    },
-    {
-      headerName: "송장번호",
-      field: "DELI_NUMB",
-      editable: true,
-    },
-  ],
+  columnDefs: [], // 초기에는 빈 배열, ORDER_HEADER_LIST에서 동적으로 생성
   autoSizeStrategy: {
     type: "fitGridWidth", // width맞춤
   },
@@ -340,7 +297,8 @@ const gridOptions = ref({
   defaultColDef: {
     sortable: true,
     filter: true,
-    resizable: false,
+    resizable: true, // 리사이즈 가능하게 변경
+    minWidth: 100, // 최소 너비 설정
   },
   suppressMovableColumns: true,
   suppressPaginationPanel: true, // 하단 default 페이징 컨트롤 숨김
@@ -353,6 +311,13 @@ const gridOptions = ref({
   },
   localeText: {
     noRowsToShow: '주문 내역이 없습니다.'
+  },
+  getRowStyle: (params) => {
+    // 새로 추가되거나 수정된 행은 연두색 배경
+    if (params.data && params.data._isNewOrModified) {
+      return { backgroundColor: '#e8f5e8' }; // 연두색 배경
+    }
+    return null;
   }
 });
 
@@ -363,7 +328,7 @@ const gridOptions = ref({
 // 동적 높이 계산
 const gridHeight = computed(() => {
   const rowCount = tblItems.value.length;
-  const minRows = 3; // 최소 5줄 높이
+  const minRows = 5; // 최소 5줄 높이
   const maxRows = 10; // 최대 15줄 높이 (스크롤 시작점)
   const rowHeight = 2.94; // rem 단위
   
@@ -377,18 +342,43 @@ const gridHeight = computed(() => {
 });
 
 const addEmptyRow = () => {
-  const newRow = {
-    BUYER_NAME: "",
-    ADDRESS: "",
-    PHONE: "",
-    EMAIL: "",
-    QTY: "",
-    TOTAL: "",
-    DELI_COMP: "",
-    DELI_ADDR: "",
-    DELI_NUMB: "",
-    ORDER_DATE: "",
+  const newRow = {};
+  
+  // 현재 시간 포맷팅
+  const getCurrentTime = () => {
+    const now = new Date();
+    return now.getFullYear() + '-' + 
+           String(now.getMonth() + 1).padStart(2, '0') + '-' + 
+           String(now.getDate()).padStart(2, '0') + ' ' +
+           String(now.getHours()).padStart(2, '0') + ':' + 
+           String(now.getMinutes()).padStart(2, '0') + ':' + 
+           String(now.getSeconds()).padStart(2, '0');
+  };
+
+  // 해시 생성 함수
+  const generateHash = () => {
+    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+    let result = '';
+    for (let i = 0; i < 50; i++) {
+      result += chars.charAt(Math.floor(Math.random() * chars.length));
+    }
+    return result;
   };
+  
+  // 현재 컬럼 정의에 따라 빈 행 생성
+  const currentColumns = gridOptions.value.columnDefs;
+  currentColumns.forEach(col => {
+    if (col.field && col.field.startsWith('CONTENT_')) {
+      newRow[col.field] = "";
+    }
+  });
+
+  // 업로드 일자와 고유번호 자동 설정
+  newRow['CONTENT_REGDATE'] = getCurrentTime();
+  newRow['COLUMN_CODE'] = generateHash();
+  
+  // 새로 추가된 행 표시
+  newRow['_isNewOrModified'] = true;
 
   // 맨 앞에 추가 (unshift 사용)
   tblItems.value.unshift(newRow);
@@ -430,15 +420,27 @@ const deleteSelectedRows = () => {
 };
 
 const fnDeleteSelected = (selectedRows) => {
-  // 선택된 행들을 tblItems에서 제거
+  
+  // 선택된 행들을 tblItems에서 제거 (고유번호 기준으로 찾기)
   selectedRows.forEach((selectedRow) => {
-    const index = tblItems.value.findIndex(
-      (item) =>
-        item.BUYER_NAME === selectedRow.BUYER_NAME &&
-        item.ADDRESS === selectedRow.ADDRESS &&
-        item.PHONE === selectedRow.PHONE &&
-        item.EMAIL === selectedRow.EMAIL
-    );
+    const index = tblItems.value.findIndex((item) => {
+      // 고유번호가 있으면 고유번호로 비교
+      if (selectedRow.COLUMN_CODE && item.COLUMN_CODE) {
+        return item.COLUMN_CODE === selectedRow.COLUMN_CODE;
+      }
+      
+      // 고유번호가 없으면 모든 CONTENT_ 필드로 비교
+      let isMatch = true;
+      for (let i = 1; i <= 20; i++) {
+        const contentKey = `CONTENT_${i}`;
+        if (selectedRow[contentKey] !== item[contentKey]) {
+          isMatch = false;
+          break;
+        }
+      }
+      return isMatch;
+    });
+    
     if (index > -1) {
       tblItems.value.splice(index, 1);
     }
@@ -446,9 +448,21 @@ const fnDeleteSelected = (selectedRows) => {
 
   pageObj.value.totalCnt = tblItems.value.length;
 
-  // ag-grid 데이터 갱신
-  if (gridApi.value) {
-    gridApi.value.setGridOption("rowData", tblItems.value);
+  // 데이터가 모두 삭제되었으면 헤더도 초기화
+  if (tblItems.value.length === 0) {
+    gridOptions.value.columnDefs = [];
+    uploadedHeaders.value = [];
+    
+    if (gridApi.value) {
+      gridApi.value.setGridOption("columnDefs", []);
+      gridApi.value.setGridOption("rowData", []);
+    }
+    
+  } else {
+    // ag-grid 데이터 갱신
+    if (gridApi.value) {
+      gridApi.value.setGridOption("rowData", tblItems.value);
+    }
   }
 
   $toast.success(`${selectedRows.length}개 항목이 삭제되었습니다.`);
@@ -500,152 +514,347 @@ const handleExcelUpload = async (event) => {
       const headers = jsonData[0];
       const rows = jsonData.slice(1);
 
-      // 헤더 매핑 (다양한 형태의 헤더명 지원)
-      const headerMapping = {
-        "주문번호": "ORDER_NUMB",
-        "구매자명": "BUYER_NAME",
-        "구매자이름": "BUYER_NAME", 
-        "구매자성명": "BUYER_NAME",
-        "연락처": "PHONE",
-        "수량": "QTY",
-        "배송업체": "DELI_COMP",
-        "택배사": "DELI_COMP",
-        "주소": "ADDRESS",
-        "배송주소": "ADDRESS",
-        "배송지": "ADDRESS",
-        "수령주소": "ADDRESS",
-        "수령지": "ADDRESS",
-        "받는주소": "ADDRESS",
-        "수취주소": "ADDRESS",
-        "송장번호": "DELI_NUMB"
+      // 해시 생성 함수
+      const generateHash = () => {
+        const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+        let result = '';
+        for (let i = 0; i < 50; i++) {
+          result += chars.charAt(Math.floor(Math.random() * chars.length));
+        }
+        return result;
       };
-
-      // 필수 헤더 검증 (like 검색으로 변경)
-      const requiredHeaders = ["주문번호", "구매자명"];
-      const foundHeaders = headers.filter(header => 
-        requiredHeaders.some(required => 
-          (required === "주문번호" && header.includes("주문번호")) ||
-          (required === "구매자명" && (
-            header === "구매자" || header === "수취인" ||
-            (header.includes("구매자") && (header.includes("이름") || header.includes("성함") || header.includes("명"))) ||
-            (header.includes("수취인") && (header.includes("이름") || header.includes("성함") || header.includes("명")))
-          ))
-        )
-      );
       
-      if (foundHeaders.length < requiredHeaders.length) {
-        $toast.error(`필수 헤더가 누락되었습니다. 필요한 헤더: ${requiredHeaders.join(", ")}`);
-        return;
-      }
+      // 현재 시간 포맷팅
+      const getCurrentTime = () => {
+        const now = new Date();
+        return now.getFullYear() + '-' + 
+               String(now.getMonth() + 1).padStart(2, '0') + '-' + 
+               String(now.getDate()).padStart(2, '0') + ' ' +
+               String(now.getHours()).padStart(2, '0') + ':' + 
+               String(now.getMinutes()).padStart(2, '0') + ':' + 
+               String(now.getSeconds()).padStart(2, '0');
+      };
 
-      // 데이터 변환
-      const mappedData = rows
-        .map((row, rowIndex) => {
+      // 최초 업로드인지 확인 (기존 데이터가 없으면 최초 업로드)
+      const isFirstUpload = tblItems.value.length === 0;
+      
+      // 엑셀에 업로드 일자와 고유번호 헤더가 있는지 확인
+      const hasUploadDateHeader = headers.includes("업로드 일자");
+      const hasUniqueIdHeader = headers.includes("고유번호");
+      let uploadDateIndex = headers.findIndex(h => h === "업로드 일자");
+      let uniqueIdIndex = headers.findIndex(h => h === "고유번호");
+      
+      // 업데이트할 행과 새로 추가할 행 분리
+      let updatedItems = [];
+      let updateCount = 0;
+      let insertCount = 0;
+      
+      // 최초 업로드인 경우
+      if (isFirstUpload) {
+        
+        rows.forEach((row) => {
           const mappedRow = {};
           let hasValidData = false;
+          let uploadDateValue = null;
+          let uniqueIdValue = null;
 
+          // 각 셀을 CONTENT_1, CONTENT_2... 형태로 매핑 (업로드 일자, 고유번호 제외)
+          let contentIndex = 0;
           headers.forEach((header, index) => {
-            let fieldName = null;
+            if (header === "업로드 일자") {
+              uploadDateValue = row[index] ? row[index].toString().trim() : null;
+            } else if (header === "고유번호") {
+              uniqueIdValue = row[index] ? row[index].toString().trim() : null;
+            } else {
+              // 일반 데이터는 CONTENT_ 매핑
+              contentIndex++;
+              const colKey = `CONTENT_${contentIndex}`;
+              if (row[index] !== undefined && row[index] !== "") {
+                mappedRow[colKey] = row[index].toString().trim();
+                hasValidData = true;
+              }
+            }
+          });
+          
+          if (hasValidData) {
+            // 업로드 일자 설정 (CONTENT_REGDATE에 저장)
+            mappedRow['CONTENT_REGDATE'] = getCurrentTime();
             
-            // 헤더 매핑 로직
-            if (header.includes("주문번호")) fieldName = "ORDER_NUMB";
-            else if (header === "구매자" || header === "수취인" ||
-                    (header.includes("구매자") && (header.includes("이름") || header.includes("성함") || header.includes("명"))) ||
-                    (header.includes("수취인") && (header.includes("이름") || header.includes("성함") || header.includes("명")))) {
-              fieldName = "BUYER_NAME";
+            // 고유번호 설정 (COLUMN_CODE에 저장)
+            if (!hasUniqueIdHeader || !uniqueIdValue) {
+              mappedRow['COLUMN_CODE'] = generateHash();
+            } else {
+              mappedRow['COLUMN_CODE'] = uniqueIdValue;
             }
-            else if (header.includes("연락처")) fieldName = "PHONE";
-            else if (header.includes("수량")) fieldName = "QTY";
-            else if (header.includes("배송업체") || header.includes("택배사")) fieldName = "DELI_COMP";
-            else if (header === "주소" || header.includes("배송주소") || header.includes("배송지") || 
-                    header.includes("수령주소") || header.includes("수령지") || header.includes("받는주소") || 
-                    header.includes("수취주소")) fieldName = "ADDRESS";
-            else if (header.includes("송장")) fieldName = "DELI_NUMB";
             
-            if (fieldName && row[index] !== undefined && row[index] !== "") {
-              mappedRow[fieldName] = row[index].toString().trim();
-              hasValidData = true;
+            // 새로 추가된 행 표시
+            mappedRow['_isNewOrModified'] = true;
+            
+            updatedItems.unshift(mappedRow); // 새 데이터를 맨 앞에 추가
+            insertCount++;
+          }
+        });
+        
+      } else if (hasUniqueIdHeader && uniqueIdIndex >= 0) {
+        // 재업로드 + 고유번호 헤더가 있는 경우 - 업데이트/인서트 로직
+        
+        // 기존 데이터의 고유번호 맵 생성 (COLUMN_CODE 사용)
+        const existingDataMap = {};
+        tblItems.value.forEach((item, index) => {
+          if (item['COLUMN_CODE']) {
+            existingDataMap[item['COLUMN_CODE']] = { item, index };
+          }
+        });
+        
+        updatedItems = [...tblItems.value];
+        const processedUniqueIds = new Set();
+        
+        // 데이터 처리
+        rows.forEach((row) => {
+          const mappedRow = {};
+          let hasValidData = false;
+          let existingUniqueId = null;
+
+          // 각 셀을 CONTENT_1, CONTENT_2... 형태로 매핑 (업로드 일자, 고유번호 제외)
+          let contentIndex = 0;
+          headers.forEach((header, index) => {
+            if (header === "업로드 일자") {
+              // 업로드 일자는 매핑하지 않음 (기존 값 유지)
+              return;
+            } else if (header === "고유번호") {
+              // 고유번호만 확인용으로 저장
+              if (row[index] !== undefined && row[index] !== "") {
+                existingUniqueId = row[index].toString().trim();
+              }
+              return;
+            } else {
+              // 일반 데이터만 CONTENT_ 매핑
+              contentIndex++;
+              const colKey = `CONTENT_${contentIndex}`;
+              if (row[index] !== undefined && row[index] !== "") {
+                mappedRow[colKey] = row[index].toString().trim();
+                hasValidData = true;
+              }
             }
           });
-
-          // 필수 필드 검증
+          
           if (hasValidData) {
-            const missingFields = [];
-            if (!mappedRow.ORDER_NUMB) missingFields.push("주문번호");
-            if (!mappedRow.BUYER_NAME) missingFields.push("구매자명");
-
-            if (missingFields.length > 0) {
-              console.warn(`${rowIndex + 2}행: 필수 필드 누락 - ${missingFields.join(", ")}`);
-              return null;
+            // 고유번호가 있고 기존 데이터에 존재하는 경우 - 업데이트
+            if (existingUniqueId && existingDataMap[existingUniqueId]) {
+              const { index } = existingDataMap[existingUniqueId];
+              // 업데이트 시에는 업로드 일자와 고유번호를 변경하지 않음 (기존 값 완전 유지)
+              const updatedItem = { ...updatedItems[index] };
+              
+              // 고유번호와 업로드 일자를 제외한 나머지 필드만 업데이트
+              Object.keys(mappedRow).forEach(key => {
+                updatedItem[key] = mappedRow[key];
+              });
+              
+              // 업데이트된 행은 연두색 표시하지 않음 (기존 데이터이므로)
+              
+              updatedItems[index] = updatedItem;
+              processedUniqueIds.add(existingUniqueId);
+              updateCount++;
+            } else {
+              // 새로운 데이터 - 인서트 (고유번호가 없거나 일치하지 않는 경우)
+              if (!existingUniqueId) {
+                mappedRow['COLUMN_CODE'] = generateHash(); // 고유번호 없으면 생성
+              } else {
+                mappedRow['COLUMN_CODE'] = existingUniqueId; // 엑셀에 있던 고유번호 사용
+              }
+              // 새로운 데이터에만 현재 시간으로 업로드 일자 설정
+              mappedRow['CONTENT_REGDATE'] = getCurrentTime(); // 업로드 일자
+              
+              // 새로 추가된 행 표시
+              mappedRow['_isNewOrModified'] = true;
+              
+              updatedItems.unshift(mappedRow); // 새 데이터를 맨 앞에 추가  
+              insertCount++;
             }
           }
+        });
+        
+      } else {
+        // 재업로드 + 고유번호 헤더가 없는 경우 - 기존 데이터는 유지하고 새 데이터만 추가
+        
+        // 기존 데이터는 그대로 유지 (고유번호 변경하지 않음)
+        updatedItems = [...tblItems.value];
+        
+        // 기존 헤더에서 업로드 일자와 고유번호 위치 찾기
+        const existingHeaders = uploadedHeaders.value || [];
+        const existingUploadDateIndex = existingHeaders.findIndex(h => h === "업로드 일자");
+        const existingUniqueIdIndex = existingHeaders.findIndex(h => h === "고유번호");
+        
+        rows.forEach((row) => {
+          const mappedRow = {};
+          let hasValidData = false;
 
-          return hasValidData ? mappedRow : null;
-        })
-        .filter((row) => row !== null);
+          // 각 셀을 CONTENT_1, CONTENT_2... 형태로 매핑 (업로드 일자, 고유번호 제외)
+          let contentIndex = 0;
+          headers.forEach((header, index) => {
+            // 업로드 일자와 고유번호는 CONTENT_ 매핑에서 제외
+            if (header !== "업로드 일자" && header !== "고유번호") {
+              contentIndex++;
+              const colKey = `CONTENT_${contentIndex}`;
+              if (row[index] !== undefined && row[index] !== "") {
+                mappedRow[colKey] = row[index].toString().trim();
+                hasValidData = true;
+              }
+            }
+          });
+          
+          if (hasValidData) {
+            // 새로 추가되는 데이터에만 업로드 일자와 고유번호 부여
+            mappedRow['CONTENT_REGDATE'] = getCurrentTime(); // 업로드 일자
+            mappedRow['COLUMN_CODE'] = generateHash(); // 고유번호 (새 데이터만)
+            
+            // 새로 추가된 행 표시
+            mappedRow['_isNewOrModified'] = true;
+            
+            updatedItems.unshift(mappedRow); // 새 데이터를 맨 앞에 추가
+            insertCount++;
+          }
+        });
+      }
+      
+      const mappedData = updatedItems;
 
       if (mappedData.length === 0) {
-        $toast.error("매핑 가능한 데이터가 없습니다. 엑셀 헤더명과 데이터를 확인해주세요.");
+        $toast.error("매핑 가능한 데이터가 없습니다.");
         return;
       }
 
-      // 기존 데이터 업데이트 및 신규 데이터 추가 처리
-      let updatedCount = 0;
-      let newCount = 0;
+      // 먼저 ORDER_HEADER_LIST 업데이트를 위한 헤더 저장
+      const headerData = {};
+      headers.forEach((header, index) => {
+        const headerKey = `HEADER_${index + 1}`;
+        if (index < 20) { // 최대 20개 헤더만
+          headerData[headerKey] = header || '';
+        }
+      });
+      
+      // ag-grid 컬럼을 엑셀 헤더로 동적 생성
+      const newColumnDefs = [];
       
-      mappedData.forEach(newItem => {
-        // 기존 데이터에서 주문번호 + 구매자명이 일치하는 항목 찾기
-        const existingIndex = tblItems.value.findIndex(existingItem => 
-          existingItem.ORDER_NUMB === newItem.ORDER_NUMB && 
-          existingItem.BUYER_NAME === newItem.BUYER_NAME
-        );
+      // 체크박스 컬럼
+      newColumnDefs.push({
+        checkboxSelection: true,
+        headerCheckboxSelection: true,
+        width: 50,
+        sortable: false,
+        filter: false,
+      });
+      
+      // No 컬럼
+      newColumnDefs.push({
+        headerName: "No",
+        valueGetter: (params) => params.api.getDisplayedRowCount() - params.node.rowIndex,
+        sortable: false,
+        filter: false,
+        width: 80,
+      });
+      
+      // 헤더 처리: 업로드 일자와 고유번호를 제외한 순수 헤더만 먼저 추출
+      let pureHeaders = headers.filter(header => 
+        header !== "업로드 일자" && header !== "고유번호"
+      );
+      
+      // 업로드 일자와 고유번호를 항상 맨 끝에 추가
+      let enhancedHeaders = [...pureHeaders];
+      
+      if (isFirstUpload) {
+        // 최초 업로드 시 항상 맨 끝에 추가
+        enhancedHeaders.push("업로드 일자");
+        enhancedHeaders.push("고유번호");
+      } else {
+        // 재업로드인 경우에도 항상 맨 끝에 추가
+        const existingHeaders = uploadedHeaders.value || [];
+        const hasExistingUploadDate = existingHeaders.includes("업로드 일자");
+        const hasExistingUniqueId = existingHeaders.includes("고유번호");
         
-        if (existingIndex !== -1) {
-          // 업데이트 메타데이터 추가
-          newItem._metadata = {
-            isUpdated: true,
-            isNew: false,
-            originalCreatedAt: tblItems.value[existingIndex].created_at || tblItems.value[existingIndex]._metadata?.originalCreatedAt,
-            lastModifiedAt: new Date().toISOString()
-          };
-          // 기존 데이터 업데이트
-          tblItems.value[existingIndex] = { ...tblItems.value[existingIndex], ...newItem };
-          updatedCount++;
-        } else {
-          // 신규 메타데이터 추가
-          newItem._metadata = {
-            isUpdated: false,
-            isNew: true,
-            originalCreatedAt: new Date().toISOString(),
-            lastModifiedAt: new Date().toISOString()
+        // 기존에 있었던 경우 맨 끝에 추가
+        if (hasExistingUploadDate || !hasUploadDateHeader) {
+          enhancedHeaders.push("업로드 일자");
+        }
+        if (hasExistingUniqueId || !hasUniqueIdHeader) {
+          enhancedHeaders.push("고유번호");
+        }
+      }
+      
+      // 전역 변수에 헤더 저장 (저장 시 사용)
+      uploadedHeaders.value = enhancedHeaders;
+      
+      // 엑셀 헤더를 기반으로 컬럼 생성
+      enhancedHeaders.forEach((header, index) => {
+        if (header) { // 헤더가 있으면 처리
+          const colDef = {
+            headerName: header,
+            field: `CONTENT_${index + 1}`,  // 기본적으로 인덱스 기반 필드명
+            editable: header !== "업로드 일자" && header !== "고유번호", // 업로드 일자, 고유번호는 편집 불가
+            width: 150,
+            resizable: true
           };
-          // 신규 데이터 추가
-          tblItems.value.push(newItem);
-          newCount++;
+          
+          // 업로드 일자는 CONTENT_REGDATE 필드 참조
+          if (header === "업로드 일자") {
+            colDef.field = "CONTENT_REGDATE";
+            //colDef.cellStyle = { backgroundColor: '#f5f5f5' };
+          }
+          
+          // 고유번호는 COLUMN_CODE 필드 참조
+          if (header === "고유번호") {
+            colDef.field = "COLUMN_CODE";
+            colDef.cellStyle = { backgroundColor: '#f5f5f5' };
+            colDef.hide = true;
+          }
+          
+          // 20개를 초과하지 않는 일반 컬럼이거나, 업로드 일자/고유번호인 경우 추가
+          if (index < 20 || header === "업로드 일자" || header === "고유번호") {
+            newColumnDefs.push(colDef);
+          }
         }
       });
       
+      // 데이터 설정
+      tblItems.value = mappedData;
       pageObj.value.totalCnt = tblItems.value.length;
-
-      // ag-grid 데이터 갱신
+      
+      // gridOptions와 ag-grid API 모두 업데이트
+      gridOptions.value.columnDefs = newColumnDefs;
+      
       if (gridApi.value) {
+        // 컬럼과 데이터를 순차적으로 업데이트
+        gridApi.value.setGridOption("columnDefs", newColumnDefs);
         gridApi.value.setGridOption("rowData", tblItems.value);
+        
+        // 강제로 그리드 새로고침
+        gridApi.value.refreshCells({ force: true });
       }
 
       // 결과 메시지 표시
-      if (updatedCount > 0 && newCount > 0) {
-        $toast.success(`총 ${mappedData.length}건 처리완료 (업데이트: ${updatedCount}건, 신규추가: ${newCount}건)`);
-      } else if (updatedCount > 0) {
-        $toast.success(`${updatedCount}건의 기존 주문이 업데이트되었습니다.`);
+      let message = '';
+      if (updateCount > 0 && insertCount > 0) {
+        message = `${updateCount}건 업데이트, ${insertCount}건 추가되었습니다. (총 ${mappedData.length}건)`;
+      } else if (updateCount > 0) {
+        message = `${updateCount}건의 주문 내역이 업데이트되었습니다.`;
+      } else if (insertCount > 0) {
+        message = `${insertCount}건의 주문 내역이 추가되었습니다.`;
       } else {
-        $toast.success(`${newCount}건의 주문 내역이 추가되었습니다.`);
+        message = `처리된 데이터가 없습니다.`;
+      }
+      $toast.success(message);
+
+      // 컬럼 너비를 그리드 전체 너비에 맞춤
+      if (gridApi.value) {
+        setTimeout(() => {
+          gridApi.value.sizeColumnsToFit();
+        }, 100);
       }
 
       // 파일 입력 초기화
       event.target.value = "";
 
     } catch (error) {
-      console.error("엑셀 파일 처리 중 오류:", error);
       $toast.error("엑셀 파일을 읽는 중 오류가 발생했습니다. 파일 형식을 확인해주세요.");
     }
   };
@@ -663,27 +872,30 @@ const downloadExcel = () => {
     return;
   }
 
-  // 한글 헤더명 배열
-  const headers = [
-    "주문번호",
-    "구매자명",
-    "연락처",
-    "주소",
-    "구매수량",
-    "배송업체",
-    "송장번호",
-  ];
+  // 현재 ag-grid에서 사용 중인 컬럼 정의에서 헤더 추출
+  const visibleColumns = gridOptions.value.columnDefs?.filter(col => 
+    col.headerName && 
+    col.headerName !== "" && 
+    !col.checkboxSelection && 
+    col.headerName !== "No"
+    // 엑셀 다운로드에는 숨김 컬럼(고유번호)도 포함
+  ) || [];
+
+  if (visibleColumns.length === 0) {
+    $toast.warning("다운로드할 컬럼이 없습니다.");
+    return;
+  }
 
+  // 동적 헤더 생성
+  const headers = visibleColumns.map(col => col.headerName);
+  
   // 데이터를 엑셀 형식으로 변환
-  const excelData = tblItems.value.map((item) => [
-    item.ORDER_NUMB || "",
-    item.BUYER_NAME || "",
-    item.PHONE || "",
-    item.ADDRESS || "",
-    item.QTY || "",
-    item.DELI_COMP || "",
-    item.DELI_NUMB || "",
-  ]);
+  const excelData = tblItems.value.map((item) => {
+    return visibleColumns.map(col => {
+      const fieldName = col.field;
+      return item[fieldName] || "";
+    });
+  });
 
   // 헤더를 첫 번째 행에 추가
   const worksheetData = [headers, ...excelData];
@@ -693,15 +905,20 @@ const downloadExcel = () => {
 
   // 워크북 생성
   const workbook = XLSX.utils.book_new();
-  XLSX.utils.book_append_sheet(workbook, worksheet, "배송관리");
+  XLSX.utils.book_append_sheet(workbook, worksheet, "주문관리");
 
-  // 파일명 생성 (현재 날짜 포함)
+  // 파일명 생성 (제품명과 현재 날짜 포함)
   const today = new Date();
   const dateString =
     today.getFullYear() +
     String(today.getMonth() + 1).padStart(2, "0") +
     String(today.getDate()).padStart(2, "0");
-  const fileName = `배송관리_${dateString}.xlsx`;
+  
+  // 제품명에서 파일명에 사용할 수 없는 문자 제거
+  const productName = form.value.formValue1 ? 
+    form.value.formValue1.replace(/[\\/:*?"<>|]/g, "_") : "제품";
+  
+  const fileName = `${productName}_주문관리_${dateString}.xlsx`;
 
   // 엑셀 파일 다운로드
   XLSX.writeFile(workbook, fileName);
@@ -843,20 +1060,124 @@ const fnDelete = () => {
 
 const getOrderList = () => {
   let req = {
-    MEMBER_TYPE: memberType,
-    COMPANY_NUMBER: useAtStore.auth.companyNumber || "1",
-    INF_SEQ: useAtStore.auth.seq,
-    TYPE: itemType,
-    ITEM_SEQ: useDtStore.boardInfo.seq // 특정 아이템의 주문만 조회하려면 추가
+    ITEM_SEQ: useDtStore.boardInfo.seq
   };
   
   useAxios()
-    .get(`/deli/orderList/${req.ITEM_SEQ}`, req)
+    .get(`/order/orderList/${req.ITEM_SEQ}`, req)
     .then(async (res) => {
       // 특정 아이템의 주문만 필터링
-      const filteredData = res.data.filter(item => item.ITEM_SEQ == useDtStore.boardInfo.seq);
-      tblItems.value = filteredData;
-      pageObj.value.totalCnt = filteredData.length;
+      const filteredHeader = res.data.headerList.filter(item => item.ITEM_SEQ == useDtStore.boardInfo.seq);
+      const filteredContent = res.data.orderList.filter(item => item.ITEM_SEQ == useDtStore.boardInfo.seq);
+
+      
+      // 헤더가 있으면 새로운 columnDefs 생성
+      if (filteredHeader && filteredHeader.length > 0) {
+        const headerRow = filteredHeader[0];
+        
+        // 새로운 columnDefs 배열 생성
+        const newColumnDefs = [];
+        
+        // 체크박스 컬럼 (항상 첫 번째)
+        newColumnDefs.push({
+          checkboxSelection: true,
+          headerCheckboxSelection: true,
+          width: 50,
+          sortable: false,
+          filter: false,
+        });
+        
+        // No 컬럼 (항상 두 번째)
+        newColumnDefs.push({
+          headerName: "No",
+          valueGetter: (params) => params.api.getDisplayedRowCount() - params.node.rowIndex,
+          sortable: false,
+          filter: false,
+          width: 80,
+        });
+        
+        // HEADER_1 ~ HEADER_22에서 값이 있는 것만 컬럼으로 추가 (업로드 일자, 고유번호 포함)
+        for (let i = 1; i <= 22; i++) {
+          const headerKey = `HEADER_${i}`;
+          const colKey = `CONTENT_${i}`;  // ORDER_LIST 테이블의 실제 필드명
+          
+          if (headerRow[headerKey] && headerRow[headerKey].trim() !== '') {
+            const headerName = headerRow[headerKey];
+            const colDef = {
+              headerName: headerName,
+              field: colKey,
+              editable: true,
+              resizable: true
+            };
+            
+            // 업로드 일자는 CONTENT_REGDATE 필드 참조
+            if (headerName === '업로드 일자') {
+              colDef.field = 'CONTENT_REGDATE';
+              colDef.editable = false;
+              //colDef.cellStyle = { backgroundColor: '#f5f5f5' };
+            }
+            
+            // 고유번호는 COLUMN_CODE 필드 참조
+            if (headerName === '고유번호') {
+              colDef.field = 'COLUMN_CODE';
+              colDef.editable = false;
+              colDef.cellStyle = { backgroundColor: '#f5f5f5' };
+              colDef.resizable = false;
+              colDef.hide = true;
+              colDef.sortable = false;
+            }
+            
+            newColumnDefs.push(colDef);
+          }
+        }
+        
+        // ORDER_LIST 데이터 설정
+        tblItems.value = filteredContent;
+        pageObj.value.totalCnt = filteredContent.length;
+        
+        // gridOptions와 ag-grid API 모두 업데이트
+        gridOptions.value.columnDefs = newColumnDefs;
+        
+        if (gridApi.value) {
+          // 컬럼과 데이터를 순차적으로 업데이트
+          gridApi.value.setGridOption("columnDefs", newColumnDefs);
+          gridApi.value.setGridOption("rowData", tblItems.value);
+          
+          // 강제로 그리드 새로고침
+          gridApi.value.refreshCells({ force: true });
+          
+          // 컬럼 너비를 그리드 전체 너비에 맞춤
+          setTimeout(() => {
+            gridApi.value.sizeColumnsToFit();
+          }, 100);
+        }
+        
+        // DB에서 불러온 헤더도 uploadedHeaders에 저장 (HEADER_21, 22 포함)
+        const extractedHeaders = [];
+        for (let i = 1; i <= 22; i++) {
+          const headerKey = `HEADER_${i}`;
+          if (headerRow[headerKey] && headerRow[headerKey].trim() !== '') {
+            extractedHeaders.push(headerRow[headerKey]);
+          }
+        }
+        uploadedHeaders.value = extractedHeaders;
+        
+        
+      } else {
+        
+        // 헤더가 없으면 컬럼 정의를 완전히 비움
+        const emptyColumnDefs = [];
+        
+        tblItems.value = [];
+        pageObj.value.totalCnt = 0;
+        
+        gridOptions.value.columnDefs = emptyColumnDefs;
+        
+        if (gridApi.value) {
+          gridApi.value.setGridOption("columnDefs", emptyColumnDefs);
+          gridApi.value.setGridOption("rowData", []);
+        }
+      }
     })
     .catch((error) => {
     })
@@ -867,10 +1188,29 @@ const getOrderList = () => {
 // ag-grid 관련 함수들
 const onGridReady = (params) => {
   gridApi.value = params.api;
+  // 그리드가 준비되면 컬럼 너비를 그리드 전체 너비에 맞춤
+  setTimeout(() => {
+    gridApi.value.sizeColumnsToFit();
+  }, 100);
 };
 
 const onCellValueChanged = (params) => {
-  console.log('셀 값 변경:', params);
+  // 셀 값 변경은 기존 데이터 수정이므로 연두색 표시하지 않음
+  // (새로 insert되는 행만 연두색 표시)
+};
+
+// 모든 행의 수정 플래그 제거
+const clearModifiedFlags = () => {
+  if (tblItems.value) {
+    tblItems.value.forEach(item => {
+      delete item._isNewOrModified;
+    });
+    
+    // 그리드 새로고침
+    if (gridApi.value) {
+      gridApi.value.refreshCells({ force: true });
+    }
+  }
 };
 
 const chgPage = (page) => {
@@ -941,63 +1281,110 @@ const fnInsert = async () => {
   gridApi.value.stopEditing();
   
   try {
+    // 데이터가 비어있으면 전체 삭제 처리
+    if (!tblItems.value || tblItems.value.length === 0) {
+      
+      const req = {
+        itemSeq: useDtStore.boardInfo.seq,
+        headers: [],
+        orderData: []
+      };
+      
+      const response = await useAxios().post('/order/reg', req);
+      
+      
+      if (response.data && response.data.success) {
+        $toast.success('저장이 완료되었습니다.');
+        getOrderList(); // 데이터 다시 로드
+      } else if (response.success) {
+        $toast.success('저장이 완료되었습니다.');
+        getOrderList(); // 데이터 다시 로드
+      } else {
+        $toast.error('삭제에 실패했습니다.');
+      }
+      
+      return;
+    }
+    
+    // 데이터가 있는 경우 기존 로직
+    // uploadedHeaders가 있으면 사용, 없으면 columnDefs에서 추출
+    let headers = [];
+    
+    if (uploadedHeaders.value && uploadedHeaders.value.length > 0) {
+      // 엑셀에서 업로드한 헤더 사용 (업로드 일자, 고유번호 제외)
+      headers = uploadedHeaders.value.filter(header => 
+        header !== '업로드 일자' && header !== '고유번호'
+      );
+    } else {
+      // 기존 데이터에서 헤더 추출 (DB에서 불러온 경우, 업로드 일자와 고유번호 제외)
+      const currentColumns = gridOptions.value.columnDefs;
+      currentColumns.forEach(col => {
+        if (col.field && col.field.startsWith('CONTENT_')) {
+          const headerName = col.headerName || '';
+          if (headerName !== '업로드 일자' && headerName !== '고유번호') {
+            headers.push(headerName);
+          }
+        }
+      });
+    }
+    
+    // 헤더가 비어있는 경우 처리
+    if (headers.length === 0) {
+      $toast.warning('헤더 정보가 없습니다. 엑셀을 먼저 업로드해주세요.');
+      return;
+    }
+    
     const req = {
-      item_seq: useDtStore.boardInfo.seq,
-      inf_seq: form.value.contact_inf,
-      orderList: tblItems.value || []
+      itemSeq: useDtStore.boardInfo.seq,
+      headers: headers,
+      orderData: tblItems.value || []
     };
     
-    const response = await useAxios().post('/deli/reg', req);
+    const response = await useAxios().post('/order/reg', req);
+    
     
-    if (response.data) {
-      const { message, updated_count, new_count, deleted_count, errors } = response.data;
+    // axios 응답 데이터는 response.data에 있음
+    if (response.data && response.data.success) {
+      $toast.success(`${response.data.message} (주문: ${response.data.orderCount}건)`);
       
-      // 결과 메시지 표시
-      const totalProcessed = updated_count + new_count + deleted_count;
+      // 저장 성공 후 모든 행의 수정 플래그 제거
+      clearModifiedFlags();
       
-      if (totalProcessed === 0) {
-        $toast.success("주문 내역이 저장되었습니다.");
-      } else {
-        let message = `주문 내역이 저장되었습니다.`;
-        
-        $toast.success(message);
-      }
-
-      // 에러가 있으면 콘솔에 출력하고 토스트로 표시
-      if (errors && errors.length > 0) {
-        //console.warn('저장 중 일부 오류 발생:', errors);
-        errors.forEach(error => {
-          $toast.error(error);
-        });
-      }
-
-      // 저장 후 페이지 새로고침
-      window.location.reload();
+      // 저장 후 데이터 다시 로드
+      getOrderList();
+      
+    } else if (response.success) {
+      // 만약 response에 직접 success가 있는 경우
+      $toast.success(`${response.message} (주문: ${response.orderCount}건)`);
+      
+      // 저장 성공 후 모든 행의 수정 플래그 제거
+      clearModifiedFlags();
+      
+      // 저장 후 데이터 다시 로드
+      getOrderList();
+    } else {
+      $toast.error('저장에 실패했습니다.');
     }
+    
   } catch (error) {
-    //console.error('주문 내역 저장 중 오류:', error);
     
     // 백엔드 에러 응답 확인
     if (error.response?.data) {
       const errorData = error.response.data;
       
-      // 아무 작업 없이 저장 버튼 클릭 시
-      if (errorData.messages?.error === "처리할 수 있는 데이터가 없습니다." || 
-        errorData.message === "처리할 수 있는 데이터가 없습니다.") {
-        $toast.warning("저장할 데이터가 없습니다.");
-        return; // 새로고침하지 않음
-      }
-      
-      // 다른 에러 메시지
-      if (errorData.message) {
+      if (errorData.error) {
+        $toast.error(errorData.error);
+      } else if (errorData.message) {
         $toast.error(errorData.message);
-      } else if (errorData.messages?.error) {
-        $toast.error(errorData.messages.error);
       } else {
-        $toast.error('주문번호, 구매자명은 필수로 입력해야 합니다.');
+        $toast.error('저장 중 오류가 발생했습니다.');
       }
     } else {
-      $toast.error('주문번호, 구매자명은 필수로 입력해야 합니다.');
+      if (tblItems.value.length === 0) {
+        $toast.warning("저장할 데이터가 없습니다.");
+      } else {
+        $toast.error('저장 중 오류가 발생했습니다.');
+      }
     }
   }
 };