실적관리 추가
This commit is contained in:
955
app/Views/pages/results/m412/stats.php
Normal file
955
app/Views/pages/results/m412/stats.php
Normal file
@@ -0,0 +1,955 @@
|
||||
<?= $this->extend('layouts/main') ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<style>
|
||||
th {
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table td {
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#resultList tbody tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.blockUI {
|
||||
z-index: 1500 !important;
|
||||
}
|
||||
|
||||
.swal2-cancel {
|
||||
background-color: #ff0000 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1>확인매물 일자별실적</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="main-card mb-3 card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">검색 / 전송 설정</h5>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<!-- 기준일자 + 검색 -->
|
||||
<form id="frm_srch_info" onsubmit="return false;">
|
||||
<div class="row g-3 align-items-end">
|
||||
|
||||
<div class="col-3">
|
||||
<label class="form-label mb-1">기준일자</label>
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-5">
|
||||
<input type="date" class="form-control" id="sdate" name="sdate" value="">
|
||||
</div>
|
||||
<div class="col-2 d-flex justify-content-center">
|
||||
<span class="text-muted">~</span>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<input type="date" class="form-control" id="edate" name="edate" value="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-1 d-grid">
|
||||
<button type="submit" class="btn btn-primary" id="btnSearch">
|
||||
검색
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr class=" my-4">
|
||||
|
||||
<!-- 전송 On/Off 설정 목록 -->
|
||||
<div class="row g-3">
|
||||
|
||||
<!-- 아이템 1 -->
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="border rounded p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="fw-semibold">홍보확인서 전송</div>
|
||||
<div class="ms-auto d-flex align-items-center gap-2">
|
||||
<select class="form-select form-select-sm" name="select_H" id="select_H"
|
||||
style="width: 140px;">
|
||||
<option value="">-On/Off-</option>
|
||||
<option value="Y">On</option>
|
||||
<option value="N">Off</option>
|
||||
</select>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" style="width:60px;"
|
||||
onclick="send_yn('H');">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 아이템 2 -->
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="border rounded p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="fw-semibold">전화확인 전송</div>
|
||||
<div class="ms-auto d-flex align-items-center gap-2">
|
||||
<select class="form-select form-select-sm" name="select_T" id="select_T"
|
||||
style="width: 140px;">
|
||||
<option value="">-On/Off-</option>
|
||||
<option value="Y">On</option>
|
||||
<option value="N">Off</option>
|
||||
</select>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" style="width:60px;"
|
||||
onclick="send_yn('T');">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 아이템 3 -->
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="border rounded p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="fw-semibold">등기부등본 전송</div>
|
||||
<div class="ms-auto d-flex align-items-center gap-2">
|
||||
<select class="form-select form-select-sm" name="select_D" id="select_D"
|
||||
style="width: 140px;">
|
||||
<option value="">-On/Off-</option>
|
||||
<option value="Y">On</option>
|
||||
<option value="N">Off</option>
|
||||
</select>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" style="width:60px;"
|
||||
onclick="send_yn('D');">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 아이템 4 -->
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="border rounded p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="fw-semibold">신홍보확인서 전송</div>
|
||||
<div class="ms-auto d-flex align-items-center gap-2">
|
||||
<select class="form-select form-select-sm" name="select_N" id="select_N"
|
||||
style="width: 140px;">
|
||||
<option value="">-On/Off-</option>
|
||||
<option value="Y">On</option>
|
||||
<option value="N">Off</option>
|
||||
</select>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" style="width:60px;"
|
||||
onclick="send_yn('N');">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 아이템 5 -->
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="border rounded p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="fw-semibold">공동중개매물 전송</div>
|
||||
<div class="ms-auto d-flex align-items-center gap-2">
|
||||
<select class="form-select form-select-sm" name="select_J" id="select_J"
|
||||
style="width: 140px;">
|
||||
<option value="">-On/Off-</option>
|
||||
<option value="Y">On</option>
|
||||
<option value="N">Off</option>
|
||||
</select>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" style="width:60px;"
|
||||
onclick="send_yn('J');">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 아이템 6 -->
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="border rounded p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="fw-semibold">모바일확인 V2 전송</div>
|
||||
<div class="ms-auto d-flex align-items-center gap-2">
|
||||
<select class="form-select form-select-sm" name="select_O" id="select_O"
|
||||
style="width: 140px;">
|
||||
<option value="">-On/Off-</option>
|
||||
<option value="Y">On</option>
|
||||
<option value="N">Off</option>
|
||||
</select>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" style="width:60px;"
|
||||
onclick="send_yn('O');">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<!-- 하단 액션 버튼 -->
|
||||
<div class="d-flex justify-content-end">
|
||||
<button type="button" class="btn btn-shadow btn-outline-secondary" style="min-width: 320px;"
|
||||
id="btnSilver_send_35">
|
||||
전화(서류) 확인 완료 총([0건]) 처리
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-12 col-xl-12">
|
||||
<div class="main-card mb-3 card">
|
||||
<div class="card-header d-flex align-items-center">
|
||||
<h3 class="card-title mb-0">매체사 목록</h3>
|
||||
<div class="ms-auto d-flex align-items-center gap-3">
|
||||
<button class="mb-2 me-2 border-0 btn-transition btn btn-shadow btn-outline-success"
|
||||
id="excel-download">엑셀다운로드</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table id="resultList" class="table table-hover table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="3">날짜</th>
|
||||
<th rowspan="3">매체사</th>
|
||||
<th colspan="11">전화확인</th>
|
||||
<th colspan="10">홍보확인서</th>
|
||||
<th rowspan="2" colspan="4">등기부확인</th>
|
||||
<th rowspan="2" colspan="7">Report</th>
|
||||
<th rowspan="3">미수신삭제</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="3">확인대상</th>
|
||||
<th colspan="5">확인결과</th>
|
||||
<th colspan="3">확인내역</th>
|
||||
|
||||
<th colspan="3">확인대상</th>
|
||||
<th colspan="4">확인결과</th>
|
||||
<th colspan="3">확인내역</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>전일<br />미확인</th>
|
||||
<th>1차<br />재시도</th>
|
||||
<th>접수</th>
|
||||
|
||||
<th>일치</th>
|
||||
<th>불일치</th>
|
||||
<th>거부</th>
|
||||
<th>무응답<br />외</th>
|
||||
<th>총확인</th>
|
||||
|
||||
<th>1차<br />실패</th>
|
||||
<th>최종<br />실패</th>
|
||||
<th>미확인</th>
|
||||
|
||||
<th>전일<br />미확인</th>
|
||||
<th>1차<br />재시도</th>
|
||||
<th>접수</th>
|
||||
|
||||
<th>일치</th>
|
||||
<th>불일치</th>
|
||||
<th>기타</th>
|
||||
<th>총확인</th>
|
||||
|
||||
<th>1차<br />실패</th>
|
||||
<th>최종<br />실패</th>
|
||||
<th>미확인</th>
|
||||
|
||||
<th>일치</th>
|
||||
<th>불일치</th>
|
||||
<th>등기부<br />없음</th>
|
||||
<th>총확인</th>
|
||||
|
||||
<th>총검증<br />대상</th>
|
||||
<th>검증<br />시도</th>
|
||||
<th>검증<br />완료</th>
|
||||
<th>검증미<br />완료</th>
|
||||
<th>간소화<br />확인율</th>
|
||||
<th>검증<br />시도율</th>
|
||||
<th>시도대비<br />완료율</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</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>
|
||||
<style>
|
||||
table.dataTable thead th {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
|
||||
const sendH = '<?= $sendH ?>';
|
||||
const sendD = '<?= $sendD ?>';
|
||||
const sendT = '<?= $sendT ?>';
|
||||
const sendN = '<?= $sendN ?>';
|
||||
const sendJ = '<?= $sendJ ?>';
|
||||
const sendO = '<?= $sendO ?>';
|
||||
|
||||
let date = new Date();
|
||||
|
||||
const SUM_KEYS = [
|
||||
'T0101', 'T0102', 'T0103',
|
||||
'T0201', 'T0202', 'T0203', 'T0204', 'T0205',
|
||||
'T0301', 'T0302', 'T0303',
|
||||
'D0101', 'D0102', 'D0103',
|
||||
'D0201', 'D0202', 'D0203', 'D0204',
|
||||
'D0301', 'D0302', 'D0303',
|
||||
'R0101', 'R0102', 'R0103', 'R0104', // 등기부 컬럼도 테이블에 있으면 포함
|
||||
'Z0101', 'Z0102', 'Z0103', 'Z0104',
|
||||
'R0105',
|
||||
'A0101'
|
||||
];
|
||||
|
||||
|
||||
$(function () {
|
||||
initSelect();
|
||||
|
||||
let table = $('#resultList').DataTable({
|
||||
language: lang_kor,
|
||||
processing: true,
|
||||
ajax: {
|
||||
url: '/m412/m412a/getResultList',
|
||||
type: 'GET',
|
||||
beforeSend: function () {
|
||||
blockUI.blockPage({
|
||||
message: tpl
|
||||
})
|
||||
},
|
||||
complete: function () {
|
||||
blockUI.unblockPage()
|
||||
},
|
||||
data: function (d) {
|
||||
d.sdate = $("#frm_srch_info [name=sdate]").val()
|
||||
d.edate = $("#frm_srch_info [name=edate]").val()
|
||||
|
||||
d.start = d.start || 0
|
||||
d.length = d.length || 10
|
||||
}
|
||||
},
|
||||
"columnDefs": [
|
||||
{ 'targets': '_all', "defaultContent": "" },
|
||||
{ className: 'text-center', targets: '_all' },
|
||||
],
|
||||
columns: [
|
||||
{ data: 'st_date', "width": "80px" },
|
||||
{ data: 'cpid', "width": "80px" },
|
||||
|
||||
{ data: 'T0101' },
|
||||
{ data: 'T0102' },
|
||||
{ data: 'T0103' },
|
||||
|
||||
{ data: 'T0201' },
|
||||
{ data: 'T0202' },
|
||||
{ data: 'T0203' },
|
||||
{ data: 'T0204' },
|
||||
{ data: 'T0205' },
|
||||
|
||||
{ data: 'T0301' },
|
||||
{ data: 'T0302' },
|
||||
{ data: 'T0303' },
|
||||
|
||||
{ data: 'R0101' },
|
||||
{ data: 'R0102' },
|
||||
{ data: 'R0103' },
|
||||
{ data: 'R0104' },
|
||||
|
||||
{ data: 'D0101' },
|
||||
{ data: 'D0102' },
|
||||
{ data: 'D0103' },
|
||||
|
||||
{ data: 'D0201' },
|
||||
{ data: 'D0202' },
|
||||
{ data: 'D0203' },
|
||||
{ data: 'D0204' },
|
||||
|
||||
{ data: 'D0301' },
|
||||
{ data: 'D0302' },
|
||||
{ data: 'D0303' },
|
||||
|
||||
{ data: 'Z0101' }, { data: 'Z0102' }, { data: 'Z0103' }, { data: 'Z0104' },
|
||||
{ data: 'Z0107', render: d => d ? `${d} %` : '-' },
|
||||
{ data: 'Z0105', render: d => d ? `${d} %` : '-' },
|
||||
{ data: 'Z0106', render: d => d ? `${d} %` : '-' },
|
||||
|
||||
{ data: 'A0101' },
|
||||
],
|
||||
// 옵션들 예시
|
||||
paging: false,
|
||||
searching: false,
|
||||
ordering: false,
|
||||
serverSide: true,
|
||||
drawCallback: function () {
|
||||
const api = this.api();
|
||||
const rows = api.rows({ page: 'current' }).nodes();
|
||||
const data = api.rows({ page: 'current' }).data();
|
||||
|
||||
// 기존 소계 제거(재그릴 때 중복 방지)
|
||||
$(api.table().body()).find('tr.tr-day-sum').remove();
|
||||
|
||||
let curDate = null;
|
||||
let sum = null;
|
||||
|
||||
function resetSum() {
|
||||
const o = {};
|
||||
SUM_KEYS.forEach(k => o[k] = 0);
|
||||
return o;
|
||||
}
|
||||
|
||||
function appendSumRow(afterRowNode, st_date, sum) {
|
||||
// 퍼센트는 “소계 기준” 재계산
|
||||
const Z0105 = pct(sum.Z0102, sum.Z0101); // Z0102 / Z0101
|
||||
const Z0106 = pct(sum.Z0103, sum.Z0102); // Z0103 / Z0102
|
||||
const Z0107 = pct(sum.R0105, (sum.R0101 + sum.R0102)); // R0105 / (R0101+R0102)
|
||||
|
||||
// 컬럼 개수만큼 TD를 정확히 만들어야 함
|
||||
// 아래는 "st_date, cpid, ... 나머지" 구조를 가정 (너의 columns 순서에 맞춰서 채워줘야 함)
|
||||
const tds = [];
|
||||
tds.push(`<td></td>`); // 날짜 칸 비움
|
||||
tds.push(`<td>일 계</td>`); // 매체사 칸에 "일계"
|
||||
|
||||
// 예: 이후 컬럼들을 네 columns 순서대로 push
|
||||
const colKeysInOrder = [
|
||||
'T0101', 'T0102', 'T0103',
|
||||
'T0201', 'T0202', 'T0203', 'T0204', 'T0205',
|
||||
'T0301', 'T0302', 'T0303',
|
||||
'D0101', 'D0102', 'D0103',
|
||||
'D0201', 'D0202', 'D0203', 'D0204',
|
||||
'D0301', 'D0302', 'D0303',
|
||||
'R0101', 'R0102', 'R0103', 'R0104',
|
||||
'Z0101', 'Z0102', 'Z0103', 'Z0104',
|
||||
'Z0107', 'Z0105', 'Z0106',
|
||||
'A0101'
|
||||
];
|
||||
|
||||
colKeysInOrder.forEach(k => {
|
||||
if (k === 'Z0105') tds.push(`<td>${Z0105}</td>`);
|
||||
else if (k === 'Z0106') tds.push(`<td>${Z0106}</td>`);
|
||||
else if (k === 'Z0107') tds.push(`<td>${Z0107}</td>`);
|
||||
else tds.push(`<td>${n(sum[k]).toLocaleString()}</td>`);
|
||||
});
|
||||
|
||||
const tr = $(`<tr class="bolder tr-day-sum">${tds.join('')}</tr>`);
|
||||
$(afterRowNode).after(tr);
|
||||
}
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const row = data[i];
|
||||
const st = row.st_date;
|
||||
|
||||
if (curDate === null) {
|
||||
curDate = st;
|
||||
sum = resetSum();
|
||||
}
|
||||
|
||||
// 날짜가 바뀌면 이전 날짜 소계 출력 후 리셋
|
||||
if (st !== curDate) {
|
||||
appendSumRow(rows[i - 1], curDate, sum);
|
||||
curDate = st;
|
||||
sum = resetSum();
|
||||
}
|
||||
|
||||
// 합산
|
||||
SUM_KEYS.forEach(k => sum[k] += n(row[k]));
|
||||
}
|
||||
|
||||
// 마지막 날짜 소계 출력
|
||||
if (data.length > 0) {
|
||||
appendSumRow(rows[data.length - 1], curDate, sum);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// $('#resultList').on('xhr.dt', function (e, settings, json) {
|
||||
// const rows = json?.data || [];
|
||||
|
||||
|
||||
// const fields = [
|
||||
// 't_cnt', 'hong1_cnt', 'hong2_cnt', 'm_cnt', 'v2_cnt', 'total'
|
||||
// , 'd_o', 'd_x', 'd_tot'
|
||||
// , 't_o', 't_x', 't_n', 't_e', 't_tot'
|
||||
// , 'r_o', 'r_x', 'r_e', 'done', 'r_tot'
|
||||
// , 's_1', 's_2'
|
||||
// ];
|
||||
|
||||
// const toNum = (v) => {
|
||||
// if (v == null || v === '') return 0;
|
||||
// return Number(String(v).replace(/,/g, '')) || 0; // "18" 같은 문자열 대응
|
||||
// };
|
||||
|
||||
// // 합계 객체 생성
|
||||
// const sum = {};
|
||||
// fields.forEach(f => sum[f] = 0);
|
||||
|
||||
// // rows 합산
|
||||
// rows.forEach(r => {
|
||||
// fields.forEach(f => sum[f] += toNum(r[f]));
|
||||
// });
|
||||
|
||||
// // footer에 주입
|
||||
// fields.forEach(f => {
|
||||
// $('#resultList tfoot .sum-' + f).text(sum[f].toLocaleString());
|
||||
// });
|
||||
// });
|
||||
|
||||
|
||||
|
||||
$('#resultList tbody').on('click', 'tr', function () {
|
||||
const row = table.row(this).data()
|
||||
if (!row) return
|
||||
|
||||
});
|
||||
|
||||
// [검색] 버튼 눌렀을 때 다시 조회
|
||||
$('#btnSearch').on('click', function () {
|
||||
table.ajax.reload()
|
||||
});
|
||||
|
||||
|
||||
// 전화확인 완료처리
|
||||
$("#btnSilver_send_35").on("function", function () {
|
||||
var sdate = $("#frm_srch_info [name=sdate]").val();
|
||||
var edate = $("#frm_srch_info [name=edate]").val();
|
||||
|
||||
if (sdate == "") {
|
||||
Swal.fire({
|
||||
title: "시작일자를 선택해 주세요.",
|
||||
icon: "warning"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (edate == "") {
|
||||
Swal.fire({
|
||||
title: "종료일자를 선택해 주세요.",
|
||||
icon: "warning"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
swal.fire({
|
||||
text: message,
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "예",
|
||||
cancelButtonText: "아니오",
|
||||
confirmButtonColor: "#3085d6",
|
||||
cancelButtonColor: "#ff0000",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
$.ajax({
|
||||
url: '/m412/m412a/saveSendComplete',
|
||||
contentType: 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
method: 'POST',
|
||||
data: {
|
||||
'sdate': sdate,
|
||||
'edate': edate,
|
||||
},
|
||||
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"
|
||||
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
location.reload()
|
||||
}, 1000)
|
||||
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: result.msg,
|
||||
icon: "error"
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 엑셀다운 click
|
||||
$("#excel-download").on("click", function () {
|
||||
|
||||
$.ajax({
|
||||
url: "/m412/m412a/excel",
|
||||
method: "GET",
|
||||
dataType: "json",
|
||||
data: $("#frm_srch_info").serialize(),
|
||||
beforeSend: function () {
|
||||
blockUI.blockPage({
|
||||
message: tpl
|
||||
})
|
||||
},
|
||||
complete: function () {
|
||||
blockUI.unblockPage()
|
||||
},
|
||||
success: function (result) {
|
||||
downloadExcel_M412_Daily(result.data);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function downloadExcel_M412_Daily(dataRows, filenamePrefix = "확인매물_일자별실적") {
|
||||
// ===== 유틸 =====
|
||||
const n = (v) => {
|
||||
const x = parseFloat(v);
|
||||
return Number.isFinite(x) ? x : 0;
|
||||
};
|
||||
const pct = (num, den) => {
|
||||
if (!den) return "-";
|
||||
return Math.round((num / den) * 100) + " %";
|
||||
};
|
||||
|
||||
// ===== DataTables 컬럼 순서(35개) =====
|
||||
// ※ HTML 헤더(전화확인 -> 홍보확인서 -> 등기부 -> Report -> 미수신삭제)와 동일 순서로 맞춤
|
||||
const COL_KEYS = [
|
||||
"st_date", "cpid",
|
||||
// 전화확인(11)
|
||||
"T0101", "T0102", "T0103",
|
||||
"T0201", "T0202", "T0203", "T0204", "T0205",
|
||||
"T0301", "T0302", "T0303",
|
||||
// 홍보확인서(10)
|
||||
"D0101", "D0102", "D0103",
|
||||
"D0201", "D0202", "D0203", "D0204",
|
||||
"D0301", "D0302", "D0303",
|
||||
// 등기부확인(4)
|
||||
"R0101", "R0102", "R0103", "R0104",
|
||||
// Report(7)
|
||||
"Z0101", "Z0102", "Z0103", "Z0104", "Z0107", "Z0105", "Z0106",
|
||||
// 미수신삭제(1)
|
||||
"A0101"
|
||||
];
|
||||
const COLS = COL_KEYS.length; // 35
|
||||
|
||||
// ===== 헤더 3줄 =====
|
||||
const header1 = Array(COLS).fill("");
|
||||
header1[0] = "날짜";
|
||||
header1[1] = "매체사";
|
||||
header1[2] = "전화확인";
|
||||
header1[13] = "홍보확인서";
|
||||
header1[23] = "등기부확인";
|
||||
header1[27] = "Report";
|
||||
header1[34] = "미수신삭제";
|
||||
|
||||
const header2 = Array(COLS).fill("");
|
||||
// 전화확인(2~12)
|
||||
header2[2] = "확인대상";
|
||||
header2[5] = "확인결과";
|
||||
header2[10] = "확인내역";
|
||||
// 홍보확인서(13~22)
|
||||
header2[13] = "확인대상";
|
||||
header2[16] = "확인결과";
|
||||
header2[20] = "확인내역";
|
||||
// 등기부확인(23~26) rowspan2라 비움
|
||||
// Report(27~33) rowspan2라 비움
|
||||
|
||||
const header3 = [
|
||||
"날짜", "매체사",
|
||||
// 전화확인(11)
|
||||
"전일미확인", "1차재시도", "접수",
|
||||
"일치", "불일치", "거부", "무응답외", "총확인",
|
||||
"1차실패", "최종실패", "미확인",
|
||||
// 홍보확인서(10)
|
||||
"전일미확인", "1차재시도", "접수",
|
||||
"일치", "불일치", "기타", "총확인",
|
||||
"1차실패", "최종실패", "미확인",
|
||||
// 등기부확인(4)
|
||||
"일치", "불일치", "등기부없음", "총확인",
|
||||
// Report(7)
|
||||
"총검증대상", "검증시도", "검증완료", "검증미완료", "간소화확인율", "검증시도율", "시도대비완료율",
|
||||
// 미수신삭제(1)
|
||||
"미수신삭제"
|
||||
];
|
||||
|
||||
// ===== 일자별 소계(일계) 포함해서 바디 만들기 =====
|
||||
// dataRows는 st_date 기준으로 정렬되어 있다고 가정(서버에서 ORDER BY st_date, cpid 권장)
|
||||
const body = [];
|
||||
let cur = null;
|
||||
|
||||
const SUM_KEYS = [
|
||||
"T0101", "T0102", "T0103",
|
||||
"T0201", "T0202", "T0203", "T0204", "T0205",
|
||||
"T0301", "T0302", "T0303",
|
||||
"D0101", "D0102", "D0103",
|
||||
"D0201", "D0202", "D0203", "D0204",
|
||||
"D0301", "D0302", "D0303",
|
||||
"R0101", "R0102", "R0103", "R0104",
|
||||
"Z0101", "Z0102", "Z0103", "Z0104",
|
||||
"R0105",
|
||||
"A0101"
|
||||
];
|
||||
|
||||
const resetSum = () => {
|
||||
const s = {};
|
||||
SUM_KEYS.forEach(k => s[k] = 0);
|
||||
return s;
|
||||
};
|
||||
|
||||
const appendDaySum = (st_date, sum) => {
|
||||
// 퍼센트 3개는 소계 기준 재계산
|
||||
const z0105 = pct(sum.Z0102, sum.Z0101);
|
||||
const z0106 = pct(sum.Z0103, sum.Z0102);
|
||||
const z0107 = pct(sum.R0105, (sum.R0101 + sum.R0102));
|
||||
|
||||
// “일계” 행은 날짜칸 비우고 매체사에 '일 계'
|
||||
const row = Array(COLS).fill("");
|
||||
row[0] = "";
|
||||
row[1] = "일 계";
|
||||
|
||||
// 숫자/지표 채우기 (COL_KEYS 기준)
|
||||
COL_KEYS.forEach((k, idx) => {
|
||||
if (idx < 2) return; // st_date, cpid 제외
|
||||
if (k === "Z0105") row[idx] = z0105;
|
||||
else if (k === "Z0106") row[idx] = z0106;
|
||||
else if (k === "Z0107") row[idx] = z0107;
|
||||
else row[idx] = n(sum[k]).toLocaleString();
|
||||
});
|
||||
|
||||
body.push(row);
|
||||
};
|
||||
|
||||
let sum = resetSum();
|
||||
|
||||
for (let i = 0; i < (dataRows || []).length; i++) {
|
||||
const r = dataRows[i];
|
||||
const st = r.st_date;
|
||||
|
||||
if (cur === null) cur = st;
|
||||
|
||||
// 날짜 변경 시 일계 추가
|
||||
if (st !== cur) {
|
||||
appendDaySum(cur, sum);
|
||||
cur = st;
|
||||
sum = resetSum();
|
||||
}
|
||||
|
||||
// 원본 row 추가
|
||||
const row = COL_KEYS.map((k) => {
|
||||
if (k === "Z0105" || k === "Z0106" || k === "Z0107") {
|
||||
return (r[k] === undefined || r[k] === null || r[k] === "") ? "-" : (String(r[k]).includes("%") ? r[k] : `${r[k]} %`);
|
||||
}
|
||||
if (k === "st_date" || k === "cpid") return r[k] ?? "";
|
||||
return (r[k] === undefined || r[k] === null || r[k] === "") ? "0" : String(r[k]);
|
||||
});
|
||||
body.push(row);
|
||||
|
||||
// 합산 누적
|
||||
SUM_KEYS.forEach(k => sum[k] += n(r[k]));
|
||||
}
|
||||
|
||||
// 마지막 날짜 일계
|
||||
if ((dataRows || []).length > 0) {
|
||||
appendDaySum(cur, sum);
|
||||
}
|
||||
|
||||
// ===== Sheet 생성 =====
|
||||
const aoa = [header1, header2, header3, ...body];
|
||||
const ws = XLSX.utils.aoa_to_sheet(aoa);
|
||||
|
||||
// ===== Merge 설정 (정확히 35컬럼 기준) =====
|
||||
ws["!merges"] = [
|
||||
// Row1(0): 날짜/매체/미수신삭제 rowspan 3
|
||||
{ s: { r: 0, c: 0 }, e: { r: 2, c: 0 } }, // 날짜
|
||||
{ s: { r: 0, c: 1 }, e: { r: 2, c: 1 } }, // 매체사
|
||||
{ s: { r: 0, c: 34 }, e: { r: 2, c: 34 } }, // 미수신삭제
|
||||
|
||||
// Row1: 상단 그룹
|
||||
{ s: { r: 0, c: 2 }, e: { r: 0, c: 12 } }, // 전화확인(11)
|
||||
{ s: { r: 0, c: 13 }, e: { r: 0, c: 22 } }, // 홍보확인서(10)
|
||||
{ s: { r: 0, c: 23 }, e: { r: 1, c: 26 } }, // 등기부확인 rowspan2 (4)
|
||||
{ s: { r: 0, c: 27 }, e: { r: 1, c: 33 } }, // Report rowspan2 (7)
|
||||
|
||||
// Row2(1): 전화확인 하위
|
||||
{ s: { r: 1, c: 2 }, e: { r: 1, c: 4 } }, // 확인대상(3)
|
||||
{ s: { r: 1, c: 5 }, e: { r: 1, c: 9 } }, // 확인결과(5)
|
||||
{ s: { r: 1, c: 10 }, e: { r: 1, c: 12 } }, // 확인내역(3)
|
||||
|
||||
// Row2: 홍보확인서 하위
|
||||
{ s: { r: 1, c: 13 }, e: { r: 1, c: 15 } }, // 확인대상(3)
|
||||
{ s: { r: 1, c: 16 }, e: { r: 1, c: 19 } }, // 확인결과(4)
|
||||
{ s: { r: 1, c: 20 }, e: { r: 1, c: 22 } } // 확인내역(3)
|
||||
];
|
||||
|
||||
// ===== 가독성: 컬럼 너비 =====
|
||||
ws["!cols"] = [
|
||||
{ wpx: 90 }, // 날짜
|
||||
{ wpx: 90 }, // 매체사
|
||||
...Array(COLS - 2).fill({ wpx: 70 })
|
||||
];
|
||||
|
||||
// ===== 저장 =====
|
||||
const wb = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(wb, ws, "sheet1");
|
||||
XLSX.writeFile(wb, `${filenamePrefix}_${getDateTimeString()}.xlsx`);
|
||||
}
|
||||
|
||||
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}`;
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
document.getElementById("sdate").value = today;
|
||||
document.getElementById("edate").value = today;
|
||||
});
|
||||
|
||||
function n(v) { // 문자열 "0" 같은 것도 숫자로
|
||||
const x = parseFloat(v);
|
||||
return Number.isFinite(x) ? x : 0;
|
||||
}
|
||||
function pct(num, den) {
|
||||
if (!den) return '-';
|
||||
return Math.round((num / den) * 100) + ' %';
|
||||
}
|
||||
|
||||
function initSelect() {
|
||||
|
||||
$("#select_H").val(sendH);
|
||||
$("#select_D").val(sendD);
|
||||
$("#select_T").val(sendT);
|
||||
$("#select_N").val(sendN);
|
||||
$("#select_J").val(sendJ);
|
||||
$("#select_O").val(sendO);
|
||||
|
||||
}
|
||||
|
||||
function send_yn(type) {
|
||||
var val = $("#select_" + type).val();
|
||||
|
||||
var nm = "";
|
||||
if (type == 'H') {
|
||||
nm = '홍보확인서';
|
||||
} else if (type == 'T') {
|
||||
nm = '전화확인';
|
||||
} else if (type == 'N') {
|
||||
nm = '신홍보확인서';
|
||||
} else if (type == 'J') {
|
||||
nm = '공동중개매물';
|
||||
} else if (type == 'O') {
|
||||
nm = '모바일확인 V2';
|
||||
} else {
|
||||
nm = '등기부등본';
|
||||
}
|
||||
|
||||
var onOff = "";
|
||||
val == "Y" ? onOff = "ON" : onOff = "OFF";
|
||||
|
||||
|
||||
var message = nm + '의 전송을 ' + onOff + '(으)로\n변경하시겠습니까?';
|
||||
|
||||
swal.fire({
|
||||
text: message,
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "예",
|
||||
cancelButtonText: "아니오",
|
||||
confirmButtonColor: "#3085d6",
|
||||
cancelButtonColor: "#ff0000",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
$.ajax({
|
||||
url: '/m412/m412a/saveSendType',
|
||||
contentType: 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
method: 'POST',
|
||||
data: {
|
||||
'type': type,
|
||||
'yn': val
|
||||
},
|
||||
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"
|
||||
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
location.reload()
|
||||
}, 1000)
|
||||
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: result.msg,
|
||||
icon: "error"
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
Reference in New Issue
Block a user