최초 커밋
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
<?php
|
||||
include __DIR__ . '/layout_sales.php';
|
||||
sales_layout_start("영업 목표 설정");
|
||||
?>
|
||||
|
||||
<h2 class="text-2xl font-bold mb-4">영업 목표 설정</h2>
|
||||
|
||||
<div id="grid_targets" style="width:100%;height:720px;"></div>
|
||||
|
||||
<script type="module">
|
||||
import { w2grid } from "https://cdn.jsdelivr.net/gh/vitmalina/w2ui@master/dist/w2ui.es6.min.js";
|
||||
|
||||
/* ------------------------------------------------------
|
||||
공통 유틸
|
||||
------------------------------------------------------ */
|
||||
function clean(v) {
|
||||
return (v === undefined || v === null) ? '' : v;
|
||||
}
|
||||
function cleanNumber(v) {
|
||||
if (v === undefined || v === null || v === '' || isNaN(v)) return 0;
|
||||
return Number(v);
|
||||
}
|
||||
function normalizeListValue(v) {
|
||||
if (typeof v === "object" && v !== null && v.id) return v.id;
|
||||
return v ?? '';
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------
|
||||
직원 목록 로드
|
||||
------------------------------------------------------ */
|
||||
let employeeList = [];
|
||||
|
||||
async function loadEmployees() {
|
||||
let res = await fetch('/egbim/bbs/sales_members.php?action=list');
|
||||
let json = await res.json();
|
||||
|
||||
if (json.status === "ok") {
|
||||
employeeList = json.records.map(m => ({
|
||||
id: m.emp_no,
|
||||
text: `${m.emp_name} (${m.emp_no})`
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
await loadEmployees();
|
||||
|
||||
/* ------------------------------------------------------
|
||||
GRID 정의
|
||||
------------------------------------------------------ */
|
||||
let grid = new w2grid({
|
||||
name: 'grid_targets',
|
||||
box: '#grid_targets',
|
||||
|
||||
show: {
|
||||
toolbar: true,
|
||||
footer: true,
|
||||
toolbarSave: true,
|
||||
toolbarReload: true,
|
||||
toolbarSearch: true,
|
||||
toolbarColumns: true,
|
||||
lineNumbers: false
|
||||
},
|
||||
|
||||
multiSearch: true,
|
||||
|
||||
searches: [
|
||||
{ field: 'target_month', label: '목표월', type: 'text' },
|
||||
{ field: 'emp_no', label: '영업담당자', type: 'text' }
|
||||
],
|
||||
|
||||
columns: [
|
||||
{ field: 'target_month', text: '목표월', size: '120px',
|
||||
editable: { type: 'text' }, sortable: true },
|
||||
|
||||
{
|
||||
field: 'emp_no',
|
||||
text: '영업담당자',
|
||||
size: '160px',
|
||||
editable: { type: 'combo', items: employeeList, showAll: true, filter: true },
|
||||
sortable: true,
|
||||
// render(record) {
|
||||
// if (typeof record.emp_no === "object" && record.emp_no !== null)
|
||||
// return record.emp_no.text;
|
||||
|
||||
// const item = employeeList.find(e => e.id == record.emp_no);
|
||||
// return item ? item.text : record.emp_no;
|
||||
// }
|
||||
},
|
||||
|
||||
{ field: 'target_qty', text: '목표수량', size: '120px',
|
||||
editable: { type: 'int' }, sortable: true, render: 'int', style: 'text-align:right' },
|
||||
|
||||
{ field: 'target_amount', text: '목표금액', size: '150px',
|
||||
editable: { type: 'float' }, sortable: true, render: 'int', style: 'text-align:right' },
|
||||
|
||||
{ field: 'created_at', text: '등록일', size: '160px', sortable: true }
|
||||
],
|
||||
|
||||
toolbar: {
|
||||
items: [
|
||||
{ id: 'add', type: 'button', text: '추가', icon: 'w2ui-icon-plus' },
|
||||
{ id: 'delete', type: 'button', text: '삭제', icon: 'w2ui-icon-cross' }
|
||||
],
|
||||
|
||||
onClick(event) {
|
||||
const g = this.owner;
|
||||
|
||||
if (event.target === 'w2ui-reload') {
|
||||
loadTargets();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target === 'add') {
|
||||
let recid = grid.records.length + 2;
|
||||
g.add({recid:recid,target_month:'',emp_no:'',target_qty:0,target_amount:0,created_at:''});
|
||||
g.scrollIntoView(recid);
|
||||
g.editField(recid, 0);
|
||||
/*
|
||||
const newId = "new-" + Math.random().toString(36).substr(2, 9);
|
||||
g.add({
|
||||
recid: newId,
|
||||
target_month: "",
|
||||
emp_no: "",
|
||||
target_qty: 0,
|
||||
target_amount: 0,
|
||||
created_at: ""
|
||||
});
|
||||
g.refresh();
|
||||
return;
|
||||
*/
|
||||
}
|
||||
|
||||
if (event.target === 'delete') {
|
||||
|
||||
const sel = g.getSelection();
|
||||
if (!sel.length) return;
|
||||
|
||||
if (!confirm("정말 삭제하시겠습니까?")) return;
|
||||
|
||||
sel.forEach(id => {
|
||||
|
||||
if (String(id).startsWith("new-")) {
|
||||
g.remove(id);
|
||||
return;
|
||||
}
|
||||
|
||||
fetch("/egbim/bbs/sales_targets.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({
|
||||
action: "delete",
|
||||
id: id
|
||||
})
|
||||
});
|
||||
|
||||
g.remove(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/* ------------------------------------------------------
|
||||
저장 처리
|
||||
------------------------------------------------------ */
|
||||
onSave(event) {
|
||||
const changes = this.getChanges();
|
||||
if (!changes.length) return;
|
||||
|
||||
Promise.all(
|
||||
changes.map(ch => {
|
||||
let rec = this.get(ch.recid);
|
||||
|
||||
let empRaw = ch.emp_no ?? rec.emp_no;
|
||||
let empText = ""; // 저장될 값: 이름(사번)
|
||||
let empId = ""; // 검증용 사번
|
||||
|
||||
/* ------------------------------------------------------
|
||||
1) combo 선택한 경우 (object)
|
||||
------------------------------------------------------ */
|
||||
if (typeof empRaw === "object" && empRaw !== null) {
|
||||
empId = String(empRaw.id);
|
||||
|
||||
// combo text를 그대로 믿지 말고 employeeList 기준으로 재확정
|
||||
const found = employeeList.find(e => String(e.id) === empId);
|
||||
if (found) {
|
||||
empText = found.text;
|
||||
} else {
|
||||
empText = empRaw.text; // fallback
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------
|
||||
2) 직접 입력한 경우
|
||||
- 이름(사번)
|
||||
- 이름 (사번)
|
||||
- 사번만 입력 → 허용 X (반드시 이름(사번)으로 변환)
|
||||
------------------------------------------------------ */
|
||||
else {
|
||||
let typed = String(empRaw ?? "").trim();
|
||||
|
||||
// (1) 이름(사번) 패턴
|
||||
let match = typed.match(/(.+)\(([A-Za-z0-9]+)\)$/);
|
||||
if (match) {
|
||||
empText = typed;
|
||||
empId = match[2];
|
||||
}
|
||||
else {
|
||||
// (2) 사번만 입력 → 리스트에서 검색하여 이름(사번) 변환
|
||||
let foundById = employeeList.find(e => e.id === typed);
|
||||
if (foundById) {
|
||||
empId = foundById.id;
|
||||
empText = foundById.text; // 이름(사번)
|
||||
}
|
||||
else {
|
||||
// (3) 이름만 입력 → 리스트에서 검색하여 이름(사번) 변환
|
||||
let foundByName = employeeList.find(e =>
|
||||
e.text.startsWith(typed + "(") // "이름(" 패턴 확인
|
||||
);
|
||||
|
||||
if (foundByName) {
|
||||
empId = foundByName.id;
|
||||
empText = foundByName.text;
|
||||
} else {
|
||||
alert("⚠ 올바른 이름 또는 이름(사번)을 입력해주세요.");
|
||||
throw 'invalid-format';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------
|
||||
3) employeeList 존재 여부 체크
|
||||
------------------------------------------------------ */
|
||||
const normalize = v => String(v).replace(/\s+/g, '').trim();
|
||||
|
||||
const exists = employeeList.some(e =>
|
||||
normalize(e.text) === normalize(empText)
|
||||
);
|
||||
|
||||
if (!exists) {
|
||||
alert(`영업담당자 '${empText}' 은(는) 등록된 직원이 아닙니다.`);
|
||||
throw 'invalid-emp';
|
||||
}
|
||||
|
||||
let body = {
|
||||
target_month: clean(ch.target_month ?? rec.target_month),
|
||||
emp_no: empText,
|
||||
target_qty: cleanNumber(ch.target_qty ?? rec.target_qty),
|
||||
target_amount: cleanNumber(ch.target_amount ?? rec.target_amount)
|
||||
};
|
||||
|
||||
if (!/^\d{4}-(0[1-9]|1[0-2])$/.test(body.target_month)) {
|
||||
alert("목표월은 YYYY-MM 형식이어야 합니다.");
|
||||
throw 'invalid-month';
|
||||
}
|
||||
|
||||
if (String(ch.recid).startsWith("new-")) {
|
||||
body.action = "insert";
|
||||
} else {
|
||||
body.action = "update";
|
||||
body.id = ch.recid;
|
||||
}
|
||||
|
||||
return fetch("/egbim/bbs/sales_targets.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams(body)
|
||||
});
|
||||
|
||||
})
|
||||
).then(() => {
|
||||
loadTargets();
|
||||
w2alert("저장되었습니다!");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/* ------------------------------------------------------
|
||||
데이터 로드
|
||||
------------------------------------------------------ */
|
||||
function loadTargets() {
|
||||
fetch("/egbim/bbs/sales_targets.php?action=list")
|
||||
.then(r => r.json())
|
||||
.then(res => {
|
||||
if (res.status === "ok") {
|
||||
grid.records = res.records.map(r => ({
|
||||
recid: r.id,
|
||||
...r
|
||||
}));
|
||||
grid.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
loadTargets();
|
||||
</script>
|
||||
|
||||
<?php sales_layout_end(); ?>
|
||||
Reference in New Issue
Block a user