Files
2026-07-23 16:15:57 +09:00

298 lines
10 KiB
PHP

<?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(); ?>