feat: 전화번호/주민번호 포맷팅 및 대시보드 매출 통계 추가
- 전화번호 포맷팅 (010-1234-5678 형식) 전역 적용 - 주민번호 마스킹 포맷팅 (980520-1****** 형식) - 대시보드에 총 마일리지, 이번달 매출, 마진, 마진율 통계 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
605db69daa
commit
3d13c0b1f3
@ -14,6 +14,57 @@ let currentLotAllocation = {
|
||||
// 재고 계산 모드 (localStorage에 저장)
|
||||
let inventoryCalculationMode = localStorage.getItem('inventoryMode') || 'all';
|
||||
|
||||
// ==================== 포맷팅 함수 ====================
|
||||
|
||||
// 전화번호 포맷팅 (010-1234-5678 형식)
|
||||
function formatPhoneNumber(phone) {
|
||||
if (!phone) return '-';
|
||||
|
||||
// 숫자만 추출
|
||||
const cleaned = phone.replace(/\D/g, '');
|
||||
|
||||
// 길이에 따라 다른 포맷 적용
|
||||
if (cleaned.length === 11) {
|
||||
// 010-1234-5678
|
||||
return cleaned.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
|
||||
} else if (cleaned.length === 10) {
|
||||
// 02-1234-5678 또는 031-123-4567
|
||||
if (cleaned.startsWith('02')) {
|
||||
return cleaned.replace(/(\d{2})(\d{4})(\d{4})/, '$1-$2-$3');
|
||||
} else {
|
||||
return cleaned.replace(/(\d{3})(\d{3})(\d{4})/, '$1-$2-$3');
|
||||
}
|
||||
} else if (cleaned.length === 9) {
|
||||
// 02-123-4567
|
||||
return cleaned.replace(/(\d{2})(\d{3})(\d{4})/, '$1-$2-$3');
|
||||
}
|
||||
|
||||
return phone; // 포맷팅할 수 없는 경우 원본 반환
|
||||
}
|
||||
|
||||
// 주민번호 포맷팅 (980520-1****** 형식)
|
||||
function formatJuminNumber(jumin, masked = true) {
|
||||
if (!jumin) return '-';
|
||||
|
||||
// 숫자만 추출
|
||||
const cleaned = jumin.replace(/\D/g, '');
|
||||
|
||||
if (cleaned.length >= 13) {
|
||||
const front = cleaned.substring(0, 6);
|
||||
const back = cleaned.substring(6, 13);
|
||||
|
||||
if (masked) {
|
||||
// 뒷자리 마스킹
|
||||
return `${front.replace(/(\d{2})(\d{2})(\d{2})/, '$1$2$3')}-${back.charAt(0)}******`;
|
||||
} else {
|
||||
// 전체 표시 (편집 시)
|
||||
return `${front.replace(/(\d{2})(\d{2})(\d{2})/, '$1$2$3')}-${back}`;
|
||||
}
|
||||
}
|
||||
|
||||
return jumin; // 포맷팅할 수 없는 경우 원본 반환
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
// 페이지 네비게이션
|
||||
$('.sidebar .nav-link').on('click', function(e) {
|
||||
@ -70,23 +121,40 @@ $(document).ready(function() {
|
||||
|
||||
// 대시보드 데이터 로드
|
||||
function loadDashboard() {
|
||||
// 환자 수
|
||||
// 환자 수 + 총 마일리지
|
||||
$.get('/api/patients', function(response) {
|
||||
if (response.success) {
|
||||
$('#totalPatients').text(response.data.length);
|
||||
const totalMileage = response.data.reduce((sum, p) => sum + (p.mileage_balance || 0), 0);
|
||||
$('#totalMileage').text(totalMileage.toLocaleString());
|
||||
}
|
||||
});
|
||||
|
||||
// 재고 현황 (저장된 모드 사용)
|
||||
loadInventorySummary();
|
||||
|
||||
// 오늘 조제 수 및 최근 조제 내역
|
||||
// 오늘 조제 수, 이번달 매출/마진, 최근 조제 내역
|
||||
$.get('/api/compounds', function(response) {
|
||||
if (response.success) {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const currentMonth = new Date().toISOString().slice(0, 7);
|
||||
const todayCompounds = response.data.filter(c => c.compound_date === today);
|
||||
$('#todayCompounds').text(todayCompounds.length);
|
||||
|
||||
// 이번달 매출/마진 계산
|
||||
const monthData = response.data.filter(c =>
|
||||
c.compound_date && c.compound_date.startsWith(currentMonth) &&
|
||||
['PAID', 'PENDING_DELIVERY', 'DELIVERED', 'COMPLETED'].includes(c.status)
|
||||
);
|
||||
const monthSales = monthData.reduce((sum, c) => sum + (c.actual_payment_amount || c.sell_price_total || 0), 0);
|
||||
const monthCost = monthData.reduce((sum, c) => sum + (c.cost_total || 0), 0);
|
||||
const monthProfit = monthSales - monthCost;
|
||||
const profitRate = monthSales > 0 ? ((monthProfit / monthSales) * 100).toFixed(1) : 0;
|
||||
|
||||
$('#monthSales').text(monthSales.toLocaleString());
|
||||
$('#monthProfit').text(monthProfit.toLocaleString());
|
||||
$('#profitRate').text(profitRate + '%');
|
||||
|
||||
// 최근 조제 내역 (최근 5개)
|
||||
const tbody = $('#recentCompounds');
|
||||
tbody.empty();
|
||||
@ -97,10 +165,16 @@ $(document).ready(function() {
|
||||
let statusBadge = '';
|
||||
switch(compound.status) {
|
||||
case 'PREPARED':
|
||||
statusBadge = '<span class="badge bg-success">조제완료</span>';
|
||||
statusBadge = '<span class="badge bg-primary">조제완료</span>';
|
||||
break;
|
||||
case 'DISPENSED':
|
||||
statusBadge = '<span class="badge bg-primary">출고완료</span>';
|
||||
case 'PAID':
|
||||
statusBadge = '<span class="badge bg-success">결제완료</span>';
|
||||
break;
|
||||
case 'DELIVERED':
|
||||
statusBadge = '<span class="badge bg-secondary">배송완료</span>';
|
||||
break;
|
||||
case 'COMPLETED':
|
||||
statusBadge = '<span class="badge bg-dark">판매완료</span>';
|
||||
break;
|
||||
case 'CANCELLED':
|
||||
statusBadge = '<span class="badge bg-danger">취소</span>';
|
||||
@ -153,7 +227,7 @@ $(document).ready(function() {
|
||||
tbody.append(`
|
||||
<tr>
|
||||
<td><strong>${patient.name}</strong></td>
|
||||
<td>${patient.phone}</td>
|
||||
<td>${formatPhoneNumber(patient.phone)}</td>
|
||||
<td>${patient.gender === 'M' ? '남' : patient.gender === 'F' ? '여' : '-'}</td>
|
||||
<td>${patient.birth_date || '-'}</td>
|
||||
<td>
|
||||
@ -1363,7 +1437,7 @@ $(document).ready(function() {
|
||||
<td>${response.data.length - index}</td>
|
||||
<td>${compound.compound_date || ''}<br><small class="text-muted">${compound.created_at ? compound.created_at.split(' ')[1] : ''}</small></td>
|
||||
<td><strong>${compound.patient_name || '직접조제'}</strong></td>
|
||||
<td>${compound.patient_phone || '-'}</td>
|
||||
<td>${formatPhoneNumber(compound.patient_phone)}</td>
|
||||
<td>${compound.formula_name || '직접조제'}</td>
|
||||
<td>${compound.je_count || 0}</td>
|
||||
<td>${compound.cheop_total || 0}</td>
|
||||
@ -1442,7 +1516,7 @@ $(document).ready(function() {
|
||||
|
||||
// 환자 정보
|
||||
$('#detailPatientName').text(data.patient_name || '직접조제');
|
||||
$('#detailPatientPhone').text(data.patient_phone || '-');
|
||||
$('#detailPatientPhone').text(formatPhoneNumber(data.patient_phone));
|
||||
$('#detailCompoundDate').text(data.compound_date || '-');
|
||||
|
||||
// 처방 정보 (가감방 표시 포함)
|
||||
|
||||
@ -168,6 +168,38 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 추가 통계 - 마일리지 및 마진 정보 -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-3">
|
||||
<div class="stat-card bg-light">
|
||||
<h5><i class="bi bi-piggy-bank-fill text-warning"></i> 총 마일리지</h5>
|
||||
<div class="value text-warning" id="totalMileage">0</div>
|
||||
<small class="text-muted">전체 환자 보유액</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="stat-card bg-light">
|
||||
<h5><i class="bi bi-graph-up-arrow text-success"></i> 이번달 매출</h5>
|
||||
<div class="value text-success" id="monthSales">0</div>
|
||||
<small class="text-muted">결제 완료 기준</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="stat-card bg-light">
|
||||
<h5><i class="bi bi-calculator text-info"></i> 이번달 마진</h5>
|
||||
<div class="value text-info" id="monthProfit">0</div>
|
||||
<small class="text-muted">매출 - 원가 (마일리지 제외)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="stat-card bg-light">
|
||||
<h5><i class="bi bi-percent text-primary"></i> 마진율</h5>
|
||||
<div class="value text-primary" id="profitRate">0%</div>
|
||||
<small class="text-muted">이번달 평균</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user