수량관리 추가
Some checks failed
Close Pull Request / main (pull_request_target) Has been cancelled

This commit is contained in:
yangsh
2026-01-19 13:15:57 +09:00
parent 8bea5766a3
commit ab3560487a
4 changed files with 1299 additions and 0 deletions

View File

@@ -0,0 +1,809 @@
<?= $this->extend('layouts/main') ?>
<?= $this->section('content') ?>
<style>
table th {
text-align: center;
}
.tab-header {
display: inline;
}
</style>
<h4 class="mb-3">처리가능 수량관리</h4>
<div class="col-12">
<div class="main-card mb-3 card">
<!-- 탭은 card-header 안에 -->
<div class="card-header tab-header pb-0">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#tab-eg10-0">일자별 처리가능 수량</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#tab-eg10-1">지역별 수량</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#tab-eg10-2">기본 수량</a>
</li>
</ul>
</div>
<div class="card-body">
<!-- 공통 검색/필터 영역 -->
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 mb-3">
<div class="d-flex flex-wrap align-items-end gap-2">
<label class="form-label mb-1 small me-2">조회조건</label>
<!-- 첫번째탭 -->
<div id="form1" class="d-flex flex-wrap align-items-end gap-2">
<div class="input-group input-group-sm" style="min-width: 320px;">
<input type="date" class="form-control" name="sdate" id="sdate">
<span class="input-group-text">~</span>
<input type="date" class="form-control" name="edate" id="edate">
</div>
</div>
<!-- 두번째탭 -->
<div id="form2" class="d-none">
<div class="d-flex align-items-end gap-1">
<select class="form-select form-select-sm" name="region2" id="region2" style="min-width: 180px;">
<option value="">지역 선택</option>
<?php foreach ($sido as $s): ?>
<option value="<?= $s['region_cd'] ?>" <?php if ($s['region_cd'] == "1100000000") {
echo "selected";
} ?>>
<?= $s['region_nm'] ?>
</option>
<?php endforeach; ?>
</select>
<div class="input-group input-group-sm" style="min-width: 180px;">
<input type="date" class="form-control" name="sdate2" id="sdate2">
</div>
</div>
</div>
<!-- 세번째탭 -->
<div id="form3" class="d-none">
<div class="d-flex align-items-end gap-1">
<select class="form-select form-select-sm" name="region3" id="region3" style="min-width: 180px;">
<option value="">지역 선택</option>
<?php foreach ($sido as $s): ?>
<option value="<?= $s['region_cd'] ?>" <?php if ($s['region_cd'] == "1100000000") {
echo "selected";
} ?>>
<?= $s['region_nm'] ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
<div class="d-flex gap-1 ms-auto">
<button class="btn btn-sm btn-outline-success" id="excel-download" type="button">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i> 엑셀다운로드
</button>
<button class="btn btn-sm btn-outline-light" type="button" id="btnSearch">
조회
</button>
</div>
</div>
<!-- 탭 컨텐츠는 tab-content 바로 아래에 -->
<div class="tab-content">
<!-- 1) 일자별 -->
<div class="tab-pane fade show active" id="tab-eg10-0" role="tabpanel">
<div class="border rounded p-3 bg-white">
<table class="table table-sm table-hover table-striped mb-0 align-middle text-center w-100" id="tbl1">
<thead>
<tr>
<th rowspan="2" class="align-middle">날짜</th>
<th rowspan="2" class="align-middle">기본적용 여부</th>
<th rowspan="2" class="align-middle">처리가능건수</th>
<th colspan="2" style="text-align: center;">시간대별</th>
</tr>
<tr>
<th>오전</th>
<th>오후</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<!-- 2) 지역별 -->
<div class="tab-pane fade" id="tab-eg10-1" role="tabpanel">
<div class="border rounded p-3 bg-white">
<table class="table table-sm table-hover table-striped mb-0 align-middle text-center w-100" id="tbl2">
<thead>
<tr>
<th rowspan="2" class="align-middle">지역</th>
<th rowspan="2" class="align-middle">건수</th>
<th rowspan="2" class="align-middle">개별초기화</th>
<th colspan="2" style="text-align: center;">시간대별</th>
</tr>
<tr>
<th>오전</th>
<th>오후</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<!-- 3) 기본 수량 -->
<div class="tab-pane fade" id="tab-eg10-2" role="tabpanel">
<div class="border rounded p-3 bg-white">
<table class="table table-sm table-hover table-striped mb-0 align-middle text-center w-100" id="tbl3">
<thead>
<tr>
<th rowspan="2" class="align-middle">지역</th>
<th rowspan="2" class="align-middle">건수</th>
<th colspan="2" style="text-align: center;">시간대별</th>
</tr>
<tr>
<th>오전</th>
<th>오후</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card-footer justify-content-end gap-1">
<button class="btn btn-sm btn-outline-secondary proc-area" type="button" onclick="fn_data_reset('clear');"
style="display: none;">
초기화
</button>
<button class="btn btn-sm btn-outline-secondary proc-area" type="button" onclick="fn_data_reset('basic');"
style="display: none;">
기본적용
</button>
<button class="btn btn-sm btn-outline-primary btn-save" type="button" onclick="fn_data_save();"
style="display: none;">
저장
</button>
</div>
</div>
</div>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css" />
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
<script type="text/javascript">
const date = new Date();
var table1, table2, table3;
$(function () {
initForm();
$("#sdate, #edate").on("change", function () {
table1.ajax.reload();
});
$("#btnSearch").on("click", function () {
const activeTab = $('.nav-link.active');
const href = activeTab.attr('href'); // #tab-eg10-0 등
if (href === '#tab-eg10-0') {
table1.ajax.reload();
} else if (href === '#tab-eg10-1') {
table2.ajax.reload();
} else if (href === '#tab-eg10-2') {
table3.ajax.reload();
}
});
table1 = $('#tbl1').DataTable({
language: lang_kor,
serverSide: true,
processing: true,
ajax: {
url: '/article/processible/getList1',
type: 'GET',
beforeSend: function () {
blockUI.blockPage({
message: tpl
})
},
complete: function () {
blockUI.unblockPage()
},
data: function (d) {
d.sdate = $("#sdate").val(); // 시작일
d.edate = $("#edate").val(); // 종료일
d.start = d.start || 0
d.length = d.length || 10
},
},
"columnDefs": [
{ className: 'text-center', targets: '_all' },
{ 'targets': '_all', "defaultContent": "" },
],
columns: [
{
data: 'sc_date',
render: function (data, type, row) {
const day = parseInt(row.day_week, 10);
if (day === 0) return `<span style="color:red">${data}</span>`;
if (day === 6) return `<span style="color:blue">${data}</span>`;
return data;
}
},
{
data: 'svc_check',
render: function (data, type, row) {
const day = parseInt(row.day_week, 10);
var title = "";
data == "N" ? title = "기본지정" : title = "별도지정";
if (day === 0) return `<span style="color:red">${title}</span>`;
if (day === 6) return `<span style="color:blue">${title}</span>`;
return title;
}
},
{ data: null, render: fn_total_render },
{
data: 'am_cnt',
render: function (data, type, row) {
const day = parseInt(row.day_week, 10);
if (day === 0) return `<span style="color:red">${priceFormatter(data)}</span>`;
if (day === 6) return `<span style="color:blue">${priceFormatter(data)}</span>`;
return priceFormatter(data);
}
},
{
data: 'pm_cnt',
render: function (data, type, row) {
const day = parseInt(row.day_week, 10);
if (day === 0) return `<span style="color:red">${priceFormatter(data)}</span>`;
if (day === 6) return `<span style="color:blue">${priceFormatter(data)}</span>`;
return priceFormatter(data);
}
},
],
// 옵션들 예시
destroy: true,
deferRender: true,
scrollX: false,
autoWidth: false,
paging: true,
searching: false,
ordering: false,
});
$('a[data-bs-toggle="tab"]').on('shown.bs.tab', function (e) {
const target = $(e.target).attr('href'); // 활성화된 탭 ID
// 모두 숨김
$('#form1, #form2, #form3').addClass('d-none');
// 탭에 따라 표시
if (target === '#tab-eg10-0') {
$('#form1').removeClass('d-none');
$("#excel-download").show();
$(".proc-area").hide();
$(".btn-save").hide();
} else if (target === '#tab-eg10-1') {
$('#form2').removeClass('d-none');
$("#excel-download").hide();
$(".proc-area").show();
$(".btn-save").show();
fn_call_area();
} else if (target === '#tab-eg10-2') {
$('#form3').removeClass('d-none');
$("#excel-download").hide();
$(".proc-area").hide();
$(".btn-save").show();
fn_call_basic();
}
});
// 엑셀다운 click
$("#excel-download").on("click", function () {
$.ajax({
url: "/article/processible/excel",
method: "GET",
contentType: 'application/x-www-form-urlencoded;charset=UTF-8',
data: {
'sdate': $("#sdate").val(),
'edate': $("#edate").val(),
},
beforeSend: function () {
blockUI.blockPage({
message: tpl
})
},
complete: function () {
blockUI.unblockPage()
},
success: function (result) {
downloadExcelWithHeader(result.data);
}
});
});
});
function initForm() {
const fmt = d => d.toISOString().slice(0, 10);
$('#sdate').val(fmt(date));
const lastDate = getLastDateOfMonth(date.getFullYear(), date.getMonth() + 1);
$('#edate').val(fmt(lastDate));
$('#sdate2').val(fmt(date));
}
function fn_total_render(data, type, row) {
const day = parseInt(row.day_week, 10);
const total = (parseInt(row.am_cnt) + parseInt(row.pm_cnt));
if (day === 0) return `<span style="color:red">${priceFormatter(total)}</span>`;
if (day === 6) return `<span style="color:blue">${priceFormatter(total)}</span>`;
return priceFormatter(total);
}
// 지역별 수량 테이블 생성
function fn_call_area() {
$("#tbl2").DataTable().clear().destroy();
table2 = $('#tbl2').DataTable({
language: lang_kor,
serverSide: true,
processing: true,
ajax: {
url: '/article/processible/getList2',
type: 'GET',
beforeSend: function () {
blockUI.blockPage({
message: tpl
})
},
complete: function () {
blockUI.unblockPage()
},
data: function (d) {
d.sdate = $("#sdate2").val(); // 시작일
d.region = $("#region2").val();
d.start = d.start || 0
d.length = d.length || 10
},
},
"columnDefs": [
{ className: 'text-center', targets: '_all' },
{ 'targets': '_all', "defaultContent": "" },
],
columns: [
{ data: 'region_nm' },
{
data: null, render: function (data, type, row) {
if (row.am_cnt == null && row.pm_cnt == null) {
return priceFormatter(parseInt(row.default_am_cnt) + parseInt(row.default_pm_cnt));
} else {
return priceFormatter(parseInt(row.am_cnt) + parseInt(row.pm_cnt));
}
}
},
{
data: null, render: function (data, type, row, meta) {
var str = `
<button class="btn btn-sm btn-outline-dark" type="button" onclick="fn_row_clear('${meta.row}')">
초기화
</button>
`;
return str;
}
},
{
data: null, render: function (data, type, row, meta) {
var str = `
<div class="d-flex justify-content-center gap-1">
<input type="text" id="am_cnt_${meta.row}" value="${row.default_am_cnt}"/ style="width: 80px;">
(${row.default_am_cnt})
</div>
`;
return str;
}
},
{
data: null, render: function (data, type, row, meta) {
var str = `
<div class="d-flex justify-content-center gap-1">
<input type="text" id="pm_cnt_${meta.row}" value="${row.default_pm_cnt}"/ style="width: 80px;">
(${row.default_pm_cnt})
</div>
`;
return str;
}
},
],
// 옵션들 예시
destroy: true,
deferRender: false,
scrollX: false,
autoWidth: false,
paging: false,
searching: false,
ordering: false,
});
}
function fn_call_basic() {
$("#tbl3").DataTable().clear().destroy();
table3 = $('#tbl3').DataTable({
language: lang_kor,
serverSide: true,
processing: true,
ajax: {
url: '/article/processible/getList3',
type: 'GET',
beforeSend: function () {
blockUI.blockPage({
message: tpl
})
},
complete: function () {
blockUI.unblockPage()
},
data: function (d) {
d.region = $("#region3").val();
d.start = d.start || 0
d.length = d.length || 10
},
},
"columnDefs": [
{ className: 'text-center', targets: '_all' },
{ 'targets': '_all', "defaultContent": "" },
],
columns: [
{ data: 'region_nm' },
{
data: null, render: function (data, type, row) {
if (row.am_cnt == null && row.pm_cnt == null) {
return priceFormatter(parseInt(row.default_am_cnt) + parseInt(row.default_pm_cnt));
} else {
return priceFormatter(parseInt(row.am_cnt) + parseInt(row.pm_cnt));
}
}
},
{
data: null, render: function (data, type, row, meta) {
var str = `
<div class="d-flex justify-content-center gap-1">
<input type="text" id="am_cnt2__${meta.row}" value="${row.am_cnt}"/ style="width: 80px;">
</div>
`;
return str;
}
},
{
data: null, render: function (data, type, row) {
var str = `
<div class="d-flex justify-content-center gap-1">
<input type="text" id="pm_cnt2_${meta.row}" value="${row.pm_cnt}"/ style="width: 80px;">
</div>
`;
return str;
}
},
],
// 옵션들 예시
destroy: true,
deferRender: false,
scrollX: false,
autoWidth: false,
paging: false,
searching: false,
ordering: false,
});
}
// 엑셀 다운로드
function downloadExcelWithHeader(dataRows) {
// ✅ 고정컬럼은 3개만 두고, 시간대별은 그룹헤더로만 사용
const fixedCols = ["날짜", "기본적용여부", "처리가능건수"];
const timeCols = ["오전", "오후"];
const COLS = fixedCols.length + timeCols.length; // 3 + 2 = 5
const fitCols = (arr) => {
const a = (arr || []).slice(0, COLS);
while (a.length < COLS) a.push("");
return a;
};
const safe = (v) => (v === undefined || v === null) ? "" : v;
const num = (v) => (v === undefined || v === null || v === "") ? 0 : Number(v);
// 1행: 고정 3개 + "시간대별"(colspan2)
const header1 = fitCols([
...fixedCols,
"시간대별",
"" // colspan 2 빈칸
]);
// 2행: 고정은 빈칸 + 오전/오후
const header2 = fitCols([
...Array(fixedCols.length).fill(""),
...timeCols
]);
const body = (dataRows || []).map((r) => {
return fitCols([
safe(r.sc_date),
safe(r.sc_type),
num(r.day_cnt),
num(r.am_cnt),
num(r.pm_cnt),
]);
});
const aoa = [header1, header2, ...body];
const ws = XLSX.utils.aoa_to_sheet(aoa);
// ✅ merge: 고정 3개는 rowspan2, 시간대별은 colspan2
const timeStart = fixedCols.length; // 3
const timeEnd = timeStart + timeCols.length - 1; // 4
ws["!merges"] = [
{ s: { r: 0, c: 0 }, e: { r: 1, c: 0 } }, // 날짜
{ s: { r: 0, c: 1 }, e: { r: 1, c: 1 } }, // 기본적용여부
{ s: { r: 0, c: 2 }, e: { r: 1, c: 2 } }, // 처리가능건수
{ s: { r: 0, c: timeStart }, e: { r: 0, c: timeEnd } }, // 시간대별 (오전~오후)
];
// 컬럼 너비 (5칸에 맞춤)
ws["!cols"] = [
{ wpx: 120 }, // 날짜
{ wpx: 100 }, // 기본적용여부
{ wpx: 100 }, // 처리가능건수
{ wpx: 80 }, // 오전
{ wpx: 80 }, // 오후
];
// (스타일/테두리) 기존 로직 그대로 사용 가능
const lastRow = aoa.length - 1;
const lastCol = COLS - 1;
const headerStyle = {
font: { bold: true },
alignment: { horizontal: "center", vertical: "center" },
fill: { patternType: "solid", fgColor: { rgb: "F2F2F2" } },
border: {
top: { style: "thin", color: { rgb: "D9D9D9" } },
bottom: { style: "thin", color: { rgb: "D9D9D9" } },
left: { style: "thin", color: { rgb: "D9D9D9" } },
right: { style: "thin", color: { rgb: "D9D9D9" } },
}
};
const cellBorder = {
border: {
top: { style: "thin", color: { rgb: "E0E0E0" } },
bottom: { style: "thin", color: { rgb: "E0E0E0" } },
left: { style: "thin", color: { rgb: "E0E0E0" } },
right: { style: "thin", color: { rgb: "E0E0E0" } },
}
};
for (let r = 0; r <= lastRow; r++) {
for (let c = 0; c <= lastCol; c++) {
const addr = XLSX.utils.encode_cell({ r, c });
if (!ws[addr]) ws[addr] = { t: "s", v: "" };
ws[addr].s = Object.assign({}, ws[addr].s || {}, cellBorder);
if (r <= 1) ws[addr].s = Object.assign({}, ws[addr].s, headerStyle);
}
}
ws["!rows"] = [{ hpx: 24 }, { hpx: 24 }];
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "sheet1");
XLSX.writeFile(wb, "일자별_처리가능_수량_" + getDateTimeString() + ".xlsx");
}
// value 초기화
function fn_row_clear(row) {
$('#am_cnt_' + row).val('0');
$('#pm_cnt_' + row).val('0');
}
function fn_data_reset(type) {
table2.rows().every(function (rowIdx) {
const rowData = this.data();
if (type === "clear") {
$('#am_cnt_' + rowIdx).val('0');
$('#pm_cnt_' + rowIdx).val('0');
} else if (type === "basic") {
$('#am_cnt_' + rowIdx).val(rowData.default_am_cnt ?? 0);
$('#pm_cnt_' + rowIdx).val(rowData.default_pm_cnt ?? 0);
}
});
}
function fn_data_save() {
var datas = new Array();
var path = "";
const activeTab = $('.nav-link.active');
const href = activeTab.attr('href'); // #tab-eg10-0 등
if (href === '#tab-eg10-1') {
path = "saveArea";
table2.rows().every(function (rowIdx) {
const rowData = this.data();
var data = {
'sc_date': $("#sdate2").val(),
'region_cd': rowData.region_cd,
'am_cnt': $('#am_cnt_' + rowIdx).val(),
'pm_cnt': $('#pm_cnt_' + rowIdx).val(),
};
datas.push(data);
});
} else if (href === '#tab-eg10-2') {
path = "saveCount";
table3.rows().every(function (rowIdx) {
const rowData = this.data();
var data = {
'region_cd': rowData.region_cd,
'am_cnt': $('#am_cnt2_' + rowIdx).val(),
'pm_cnt': $('#pm_cnt2_' + rowIdx).val(),
};
datas.push(data);
});
}
// console.log(datas)
// return
if (datas.length == 0) {
Swal.fire({
title: "저장 가능한 데이터가 없습니다.",
icon: "warning"
});
return;
}
swal.fire({
text: "저장 하시겠습니까?",
type: "warning",
showCancelButton: true,
confirmButtonText: "예",
cancelButtonText: "아니오",
closeOnConfirm: false,
closeOnCancel: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
}).then((result) => {
if (result.isConfirmed) {
const param = {
'rows': JSON.stringify(datas),
};
$.ajax({
url: '/article/processible/' + path,
contentType: 'application/x-www-form-urlencoded;charset=UTF-8',
method: 'POST',
data: param,
beforeSend: function () {
blockUI.blockPage({
message: tpl
})
},
complete: function () {
blockUI.unblockPage()
},
error: function (xhr, error, thrown) {
blockUI.unblockPage()
var msg = "";
if (xhr.responseText != null) {
msg = xhr.responseText
} else {
msg = "잠시후 다시 시도해 주세요."
}
Swal.fire({
title: msg,
icon: "error"
})
},
success: function (result) {
if (result.code == '0') {
Swal.fire({
title: '정상 처리되었습니다.',
icon: "success"
});
table.ajax.reload();
} else {
Swal.fire({
title: result.msg,
icon: "error"
})
}
}
});
}
});
}
function getLastDateOfMonth(year, month) {
return new Date(year, month, 0); // 말일 Date 객체
}
function priceFormatter(data) {
return data.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function getDateTimeString() {
const d = new Date();
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
const hh = String(d.getHours()).padStart(2, '0');
const mi = String(d.getMinutes()).padStart(2, '0');
const ss = String(d.getSeconds()).padStart(2, '0');
return `${yyyy}${mm}${dd}${hh}${mi}${ss}`;
}
</script>
<?= $this->endSection() ?>