구매리스트 엑셀 업로드 - 코드 컬럼 검증 보강 및 숫자 형식 검증 추가
- USE_YN(사용여부) 정적 코드('사용'/'미사용') 검증 추가
(mutator가 잘못된 값을 기본 '사용'으로 변환해 묻혔던 케이스 차단)
- 소재단가/발주수량/가공단가 숫자 형식 검증 추가 (천단위 콤마 허용)
- fn_reverseSelectValue 강건화: 코드 컬럼 화이트리스트 기반으로
list 비어 있어도 isCodeColumn=true 처리, trim 비교 적용
- 코드 컬럼은 editable 체크 없이 무조건 검증 (가공업체 누락 방지)
- 알람을 html 모드로 변경: 행번호 정렬, 줄바꿈, 좌측정렬·모노스페이스
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1431,21 +1431,26 @@ function fn_buildHeaderFieldMap() {
|
||||
}
|
||||
|
||||
// Select2 코드 컬럼: 엑셀의 코드명(text) → 코드값(id) 역매핑
|
||||
// Select2 코드 컬럼: 엑셀의 코드명(text) → 코드값(id) 역매핑
|
||||
// 반환: { value: 변환값(매칭 시 id, 미매칭/비코드 시 원본), isCodeColumn: 코드 컬럼 여부, matched: 기준정보 매칭 여부 }
|
||||
// isCodeColumn=true && matched=false 이면 기준정보에 없는 값 (호출부에서 invalid 처리)
|
||||
// Select2/정적 코드 컬럼: 엑셀의 코드명(text) → 코드값(id) 역매핑
|
||||
// 반환: { value, isCodeColumn, matched }
|
||||
// isCodeColumn=true && matched=false 이면 기준정보에 없는 값 (호출부에서 invalid 처리)
|
||||
// 화이트리스트에 있는 필드는 list가 비어 있어도 isCodeColumn=true로 간주(검증 누락 방지)
|
||||
function fn_reverseSelectValue(field, value) {
|
||||
if(value === null || value === undefined || value === '') {
|
||||
return { value: value, isCodeColumn: false, matched: true };
|
||||
}
|
||||
var list = null;
|
||||
if(field === 'VENDOR_PM' || field === 'PROCESSING_VENDOR') list = processingVendorList;
|
||||
else if(field === 'CURRENCY') list = currencyList;
|
||||
if(!list || list.length === 0) {
|
||||
var isCode = false;
|
||||
if(field === 'VENDOR_PM' || field === 'PROCESSING_VENDOR') { list = processingVendorList; isCode = true; }
|
||||
else if(field === 'CURRENCY') { list = currencyList; isCode = true; }
|
||||
else if(field === 'USE_YN') { list = [{id:'사용', text:'사용'}, {id:'미사용', text:'미사용'}]; isCode = true; }
|
||||
if(!isCode) {
|
||||
return { value: value, isCodeColumn: false, matched: true };
|
||||
}
|
||||
if(!list) list = [];
|
||||
var v = String(value).trim();
|
||||
for(var i = 0; i < list.length; i++) {
|
||||
if(list[i].text === value || String(list[i].id) === String(value)) {
|
||||
if(String(list[i].text).trim() === v || String(list[i].id).trim() === v) {
|
||||
return { value: list[i].id, isCodeColumn: true, matched: true };
|
||||
}
|
||||
}
|
||||
@@ -1457,9 +1462,16 @@ function fn_getFieldLabel(field) {
|
||||
if(field === 'VENDOR_PM') return '공급업체';
|
||||
if(field === 'PROCESSING_VENDOR') return '가공업체';
|
||||
if(field === 'CURRENCY') return '환종';
|
||||
if(field === 'USE_YN') return '사용여부';
|
||||
if(field === 'UNIT_PRICE') return '소재단가';
|
||||
if(field === 'PO_QTY') return '발주수량';
|
||||
if(field === 'PROCESSING_UNIT_PRICE') return '가공단가';
|
||||
return field;
|
||||
}
|
||||
|
||||
// 숫자만 허용 컬럼 (엑셀 업로드 시 형식 검증). 천단위 콤마는 허용.
|
||||
var NUMERIC_FIELDS = { UNIT_PRICE: true, PO_QTY: true, PROCESSING_UNIT_PRICE: true };
|
||||
|
||||
// 저장 버튼 표시/숨김 (엑셀 업로드에서 기준정보 미매칭 발생 시 저장 차단)
|
||||
function fn_toggleSaveBtn(show) {
|
||||
var $btn = $('#btnSave');
|
||||
@@ -1468,6 +1480,12 @@ function fn_toggleSaveBtn(show) {
|
||||
else $btn.hide();
|
||||
}
|
||||
|
||||
// SweetAlert html 모드에서 사용자 입력값이 HTML로 해석되지 않도록 이스케이프
|
||||
function fn_escapeHtml(s) {
|
||||
if(s === null || s === undefined) return '';
|
||||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
// 엑셀 업로드: 파싱 → 역매핑 → OBJID 무결성 검증 → PART_NO 우선 매칭(중복 시 OBJID) → 그리드 주입
|
||||
function fn_excelUpload(file) {
|
||||
if(!file) return;
|
||||
@@ -1507,9 +1525,9 @@ function fn_excelUpload(file) {
|
||||
if(oid) existingObjidSet[oid] = true;
|
||||
}
|
||||
|
||||
// 1) 헤더 → field 변환 + Select2 역매핑 + 기준정보 미매칭 수집 (편집 가능 컬럼만)
|
||||
// 1) 헤더 → field 변환 + Select2 역매핑 + 기준정보 미매칭/숫자 형식 오류 수집 (편집 가능 컬럼만)
|
||||
var converted = [];
|
||||
var invalidSelects = [];
|
||||
var invalidCells = []; // { row, field, label, value, reason }
|
||||
for(var i = 0; i < rows.length; i++) {
|
||||
var src = rows[i];
|
||||
var out = {};
|
||||
@@ -1517,14 +1535,34 @@ function fn_excelUpload(file) {
|
||||
if(!src.hasOwnProperty(header)) continue;
|
||||
var field = headerFieldMap[header];
|
||||
if(!field) continue;
|
||||
var r = fn_reverseSelectValue(field, src[header]);
|
||||
var rawVal = src[header];
|
||||
// 숫자 컬럼: 빈 값 통과, 변환 실패 시 invalid
|
||||
if(NUMERIC_FIELDS[field] && editableFields[field]) {
|
||||
if(rawVal !== null && rawVal !== undefined && String(rawVal).trim() !== '') {
|
||||
var numVal = Number(String(rawVal).replace(/,/g, ''));
|
||||
if(isNaN(numVal)) {
|
||||
invalidCells.push({
|
||||
row: i + 2, field: field, label: fn_getFieldLabel(field),
|
||||
value: rawVal, reason: '숫자만 입력 가능합니다'
|
||||
});
|
||||
out[field] = rawVal;
|
||||
} else {
|
||||
out[field] = numVal;
|
||||
}
|
||||
} else {
|
||||
out[field] = rawVal;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// 코드 컬럼: 역매핑 + 기준정보 매칭 검증
|
||||
// (editable 체크 없이 검증 — 코드 컬럼은 모두 사용자 입력 대상이므로
|
||||
// editableFields 판정 누락 시에도 안전하게 잡기 위함)
|
||||
var r = fn_reverseSelectValue(field, rawVal);
|
||||
out[field] = r.value;
|
||||
if(r.isCodeColumn && !r.matched && editableFields[field]) {
|
||||
invalidSelects.push({
|
||||
row: i + 2,
|
||||
field: field,
|
||||
label: fn_getFieldLabel(field),
|
||||
value: src[header]
|
||||
if(r.isCodeColumn && !r.matched) {
|
||||
invalidCells.push({
|
||||
row: i + 2, field: field, label: fn_getFieldLabel(field),
|
||||
value: rawVal, reason: '기준정보에 없습니다'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1629,17 +1667,23 @@ function fn_excelUpload(file) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 5) 기준정보 미매칭 분기: 데이터는 그리드에 반영하되 저장 버튼 숨김
|
||||
if(invalidSelects.length > 0) {
|
||||
// 5) 잘못된 셀(기준정보 미매칭/숫자 형식 오류) 분기: 데이터는 그리드에 반영하되 저장 버튼 숨김
|
||||
if(invalidCells.length > 0) {
|
||||
_tabulGrid.replaceData(merged).then(function() {
|
||||
fn_toggleSaveBtn(false);
|
||||
var msg = '기준정보에 없는 값이 포함되어 저장이 차단됩니다.\n정상 엑셀로 다시 업로드하세요.\n\n';
|
||||
for(var i = 0; i < Math.min(invalidSelects.length, 10); i++) {
|
||||
var it = invalidSelects[i];
|
||||
msg += ' - ' + it.row + '행: ' + it.label + " '" + it.value + "' 기준정보에 없습니다\n";
|
||||
invalidCells.sort(function(a, b) { return (a.row || 0) - (b.row || 0); });
|
||||
var lines = '';
|
||||
for(var i = 0; i < Math.min(invalidCells.length, 20); i++) {
|
||||
var it = invalidCells[i];
|
||||
lines += ' - ' + it.row + '행: ' + fn_escapeHtml(it.label)
|
||||
+ " '" + fn_escapeHtml(it.value) + "' "
|
||||
+ fn_escapeHtml(it.reason) + '<br>';
|
||||
}
|
||||
if(invalidSelects.length > 10) msg += ' ... (총 ' + invalidSelects.length + '건)';
|
||||
Swal.fire('오류', msg, 'error');
|
||||
if(invalidCells.length > 20) lines += ' ... (총 ' + invalidCells.length + '건)<br>';
|
||||
var html = '잘못된 값이 포함되어 저장이 차단됩니다.<br>'
|
||||
+ '정상 엑셀로 다시 업로드하세요.<br><br>'
|
||||
+ '<div style="text-align:left; font-family:monospace; font-size:13px;">' + lines + '</div>';
|
||||
Swal.fire({ title: '오류', html: html, icon: 'error', width: 700 });
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user