|
@@ -114,13 +114,14 @@
|
|
|
<div class="caption--box">
|
|
<div class="caption--box">
|
|
|
- 주문 내역 입력 후 저장 버튼을 꼭 클릭해 주세요.<br />
|
|
- 주문 내역 입력 후 저장 버튼을 꼭 클릭해 주세요.<br />
|
|
|
- 엑셀 파일은 최대 10MB까지 업로드 가능합니다.<br />
|
|
- 엑셀 파일은 최대 10MB까지 업로드 가능합니다.<br />
|
|
|
- - 엑셀 파일의 헤더명(주문번호, 구매자명)이 일치해야 정상적으로 업로드됩니다.
|
|
|
|
|
|
|
+ - 엑셀의 헤더는 20개를 초과할 수 없습니다.<br>
|
|
|
|
|
+ - 고유번호는 시스템 관리용 번호입니다. 변경 시 오류가 발생할 수 있습니다.
|
|
|
</div>
|
|
</div>
|
|
|
</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
|
|
><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
|
|
><i class="ico"></i>항목 삭제</v-btn
|
|
|
>
|
|
>
|
|
|
<input
|
|
<input
|
|
@@ -133,7 +134,7 @@
|
|
|
<v-btn class="custom-btn btn-excel" @click="$refs.excelFileInput.click()"
|
|
<v-btn class="custom-btn btn-excel" @click="$refs.excelFileInput.click()"
|
|
|
><i class="ico"></i>엑셀 업로드</v-btn
|
|
><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
|
|
><i class="ico"></i>엑셀 다운로드</v-btn
|
|
|
>
|
|
>
|
|
|
<v-btn class="custom-btn btn-purple mini" @click="fnRegEvt"
|
|
<v-btn class="custom-btn btn-purple mini" @click="fnRegEvt"
|
|
@@ -278,61 +279,17 @@ const pageType = ref("");
|
|
|
// ag-grid 관련 변수
|
|
// ag-grid 관련 변수
|
|
|
const tblItems = ref([]);
|
|
const tblItems = ref([]);
|
|
|
const gridApi = ref(null);
|
|
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({
|
|
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: {
|
|
autoSizeStrategy: {
|
|
|
type: "fitGridWidth", // width맞춤
|
|
type: "fitGridWidth", // width맞춤
|
|
|
},
|
|
},
|
|
@@ -340,7 +297,8 @@ const gridOptions = ref({
|
|
|
defaultColDef: {
|
|
defaultColDef: {
|
|
|
sortable: true,
|
|
sortable: true,
|
|
|
filter: true,
|
|
filter: true,
|
|
|
- resizable: false,
|
|
|
|
|
|
|
+ resizable: true, // 리사이즈 가능하게 변경
|
|
|
|
|
+ minWidth: 100, // 최소 너비 설정
|
|
|
},
|
|
},
|
|
|
suppressMovableColumns: true,
|
|
suppressMovableColumns: true,
|
|
|
suppressPaginationPanel: true, // 하단 default 페이징 컨트롤 숨김
|
|
suppressPaginationPanel: true, // 하단 default 페이징 컨트롤 숨김
|
|
@@ -353,6 +311,13 @@ const gridOptions = ref({
|
|
|
},
|
|
},
|
|
|
localeText: {
|
|
localeText: {
|
|
|
noRowsToShow: '주문 내역이 없습니다.'
|
|
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 gridHeight = computed(() => {
|
|
|
const rowCount = tblItems.value.length;
|
|
const rowCount = tblItems.value.length;
|
|
|
- const minRows = 3; // 최소 5줄 높이
|
|
|
|
|
|
|
+ const minRows = 5; // 최소 5줄 높이
|
|
|
const maxRows = 10; // 최대 15줄 높이 (스크롤 시작점)
|
|
const maxRows = 10; // 최대 15줄 높이 (스크롤 시작점)
|
|
|
const rowHeight = 2.94; // rem 단위
|
|
const rowHeight = 2.94; // rem 단위
|
|
|
|
|
|
|
@@ -377,18 +342,43 @@ const gridHeight = computed(() => {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
const addEmptyRow = () => {
|
|
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 사용)
|
|
// 맨 앞에 추가 (unshift 사용)
|
|
|
tblItems.value.unshift(newRow);
|
|
tblItems.value.unshift(newRow);
|
|
@@ -430,15 +420,27 @@ const deleteSelectedRows = () => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const fnDeleteSelected = (selectedRows) => {
|
|
const fnDeleteSelected = (selectedRows) => {
|
|
|
- // 선택된 행들을 tblItems에서 제거
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 선택된 행들을 tblItems에서 제거 (고유번호 기준으로 찾기)
|
|
|
selectedRows.forEach((selectedRow) => {
|
|
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) {
|
|
if (index > -1) {
|
|
|
tblItems.value.splice(index, 1);
|
|
tblItems.value.splice(index, 1);
|
|
|
}
|
|
}
|
|
@@ -446,9 +448,21 @@ const fnDeleteSelected = (selectedRows) => {
|
|
|
|
|
|
|
|
pageObj.value.totalCnt = tblItems.value.length;
|
|
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}개 항목이 삭제되었습니다.`);
|
|
$toast.success(`${selectedRows.length}개 항목이 삭제되었습니다.`);
|
|
@@ -500,152 +514,347 @@ const handleExcelUpload = async (event) => {
|
|
|
const headers = jsonData[0];
|
|
const headers = jsonData[0];
|
|
|
const rows = jsonData.slice(1);
|
|
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 = {};
|
|
const mappedRow = {};
|
|
|
let hasValidData = false;
|
|
let hasValidData = false;
|
|
|
|
|
+ let uploadDateValue = null;
|
|
|
|
|
+ let uniqueIdValue = null;
|
|
|
|
|
|
|
|
|
|
+ // 각 셀을 CONTENT_1, CONTENT_2... 형태로 매핑 (업로드 일자, 고유번호 제외)
|
|
|
|
|
+ let contentIndex = 0;
|
|
|
headers.forEach((header, index) => {
|
|
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) {
|
|
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) {
|
|
if (mappedData.length === 0) {
|
|
|
- $toast.error("매핑 가능한 데이터가 없습니다. 엑셀 헤더명과 데이터를 확인해주세요.");
|
|
|
|
|
|
|
+ $toast.error("매핑 가능한 데이터가 없습니다.");
|
|
|
return;
|
|
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;
|
|
pageObj.value.totalCnt = tblItems.value.length;
|
|
|
-
|
|
|
|
|
- // ag-grid 데이터 갱신
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // gridOptions와 ag-grid API 모두 업데이트
|
|
|
|
|
+ gridOptions.value.columnDefs = newColumnDefs;
|
|
|
|
|
+
|
|
|
if (gridApi.value) {
|
|
if (gridApi.value) {
|
|
|
|
|
+ // 컬럼과 데이터를 순차적으로 업데이트
|
|
|
|
|
+ gridApi.value.setGridOption("columnDefs", newColumnDefs);
|
|
|
gridApi.value.setGridOption("rowData", tblItems.value);
|
|
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 {
|
|
} else {
|
|
|
- $toast.success(`${newCount}건의 주문 내역이 추가되었습니다.`);
|
|
|
|
|
|
|
+ message = `처리된 데이터가 없습니다.`;
|
|
|
|
|
+ }
|
|
|
|
|
+ $toast.success(message);
|
|
|
|
|
+
|
|
|
|
|
+ // 컬럼 너비를 그리드 전체 너비에 맞춤
|
|
|
|
|
+ if (gridApi.value) {
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ gridApi.value.sizeColumnsToFit();
|
|
|
|
|
+ }, 100);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 파일 입력 초기화
|
|
// 파일 입력 초기화
|
|
|
event.target.value = "";
|
|
event.target.value = "";
|
|
|
|
|
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
- console.error("엑셀 파일 처리 중 오류:", error);
|
|
|
|
|
$toast.error("엑셀 파일을 읽는 중 오류가 발생했습니다. 파일 형식을 확인해주세요.");
|
|
$toast.error("엑셀 파일을 읽는 중 오류가 발생했습니다. 파일 형식을 확인해주세요.");
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
@@ -663,27 +872,30 @@ const downloadExcel = () => {
|
|
|
return;
|
|
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];
|
|
const worksheetData = [headers, ...excelData];
|
|
@@ -693,15 +905,20 @@ const downloadExcel = () => {
|
|
|
|
|
|
|
|
// 워크북 생성
|
|
// 워크북 생성
|
|
|
const workbook = XLSX.utils.book_new();
|
|
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 today = new Date();
|
|
|
const dateString =
|
|
const dateString =
|
|
|
today.getFullYear() +
|
|
today.getFullYear() +
|
|
|
String(today.getMonth() + 1).padStart(2, "0") +
|
|
String(today.getMonth() + 1).padStart(2, "0") +
|
|
|
String(today.getDate()).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);
|
|
XLSX.writeFile(workbook, fileName);
|
|
@@ -843,20 +1060,124 @@ const fnDelete = () => {
|
|
|
|
|
|
|
|
const getOrderList = () => {
|
|
const getOrderList = () => {
|
|
|
let req = {
|
|
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()
|
|
useAxios()
|
|
|
- .get(`/deli/orderList/${req.ITEM_SEQ}`, req)
|
|
|
|
|
|
|
+ .get(`/order/orderList/${req.ITEM_SEQ}`, req)
|
|
|
.then(async (res) => {
|
|
.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) => {
|
|
.catch((error) => {
|
|
|
})
|
|
})
|
|
@@ -867,10 +1188,29 @@ const getOrderList = () => {
|
|
|
// ag-grid 관련 함수들
|
|
// ag-grid 관련 함수들
|
|
|
const onGridReady = (params) => {
|
|
const onGridReady = (params) => {
|
|
|
gridApi.value = params.api;
|
|
gridApi.value = params.api;
|
|
|
|
|
+ // 그리드가 준비되면 컬럼 너비를 그리드 전체 너비에 맞춤
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ gridApi.value.sizeColumnsToFit();
|
|
|
|
|
+ }, 100);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const onCellValueChanged = (params) => {
|
|
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) => {
|
|
const chgPage = (page) => {
|
|
@@ -941,63 +1281,110 @@ const fnInsert = async () => {
|
|
|
gridApi.value.stopEditing();
|
|
gridApi.value.stopEditing();
|
|
|
|
|
|
|
|
try {
|
|
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 = {
|
|
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) {
|
|
} catch (error) {
|
|
|
- //console.error('주문 내역 저장 중 오류:', error);
|
|
|
|
|
|
|
|
|
|
// 백엔드 에러 응답 확인
|
|
// 백엔드 에러 응답 확인
|
|
|
if (error.response?.data) {
|
|
if (error.response?.data) {
|
|
|
const errorData = 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);
|
|
$toast.error(errorData.message);
|
|
|
- } else if (errorData.messages?.error) {
|
|
|
|
|
- $toast.error(errorData.messages.error);
|
|
|
|
|
} else {
|
|
} else {
|
|
|
- $toast.error('주문번호, 구매자명은 필수로 입력해야 합니다.');
|
|
|
|
|
|
|
+ $toast.error('저장 중 오류가 발생했습니다.');
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
- $toast.error('주문번호, 구매자명은 필수로 입력해야 합니다.');
|
|
|
|
|
|
|
+ if (tblItems.value.length === 0) {
|
|
|
|
|
+ $toast.warning("저장할 데이터가 없습니다.");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $toast.error('저장 중 오류가 발생했습니다.');
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|