feat: 한퓨어 엑셀 형식 지원 및 조제 용도 구분(usage_type) 추가
한퓨어 엑셀: - ExcelProcessor에 hanpure 형식 자동 감지 및 처리 추가 - 옵션항목에서 중량 파싱 (600g*5개 → 3000g 등) - 주문번호에서 입고일 추출, ingredient_code 직접 활용 조제 용도 구분: - compounds.usage_type 컬럼 추가 (SALE/SELF_USE/SAMPLE/DISPOSAL) - 조제 실행 시 용도 선택 드롭다운 - 조제 목록에서 용도 뱃지 클릭으로 사후 변경 가능 - 비판매 용도 시 sell_price_total=0, 매출 통계 제외 - PUT /api/compounds/:id/usage-type API 추가 - 용도 구분 설계 문서 (docs/조제_용도구분_usage_type.md) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -141,10 +141,11 @@ $(document).ready(function() {
|
||||
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)
|
||||
['PAID', 'PENDING_DELIVERY', 'DELIVERED', 'COMPLETED'].includes(c.status) &&
|
||||
(!c.usage_type || c.usage_type === 'SALE')
|
||||
);
|
||||
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);
|
||||
@@ -1627,6 +1628,7 @@ $(document).ready(function() {
|
||||
je_count: parseFloat($('#jeCount').val()),
|
||||
cheop_total: parseFloat($('#cheopTotal').val()),
|
||||
pouch_total: parseFloat($('#pouchTotal').val()),
|
||||
usage_type: $('#compoundUsageType').val() || 'SALE',
|
||||
ingredients: ingredients
|
||||
};
|
||||
|
||||
@@ -1637,8 +1639,11 @@ $(document).ready(function() {
|
||||
data: JSON.stringify(compoundData),
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert(`조제가 완료되었습니다.\n원가: ${formatCurrency(response.total_cost)}`);
|
||||
const usageType = $('#compoundUsageType').val();
|
||||
const usageLabel = {SELF_USE: '자가소비', SAMPLE: '샘플', DISPOSAL: '폐기'}[usageType] || '판매';
|
||||
alert(`조제가 완료되었습니다. [${usageLabel}]\n원가: ${formatCurrency(response.total_cost)}`);
|
||||
$('#compoundForm').hide();
|
||||
$('#compoundUsageType').val('SALE');
|
||||
loadCompounds();
|
||||
}
|
||||
},
|
||||
@@ -1701,6 +1706,13 @@ $(document).ready(function() {
|
||||
statusBadge = '<span class="badge bg-secondary">대기</span>';
|
||||
}
|
||||
|
||||
// 용도 뱃지 (클릭으로 변경 가능)
|
||||
const usageLabels = {SALE: '판매', SELF_USE: '자가소비', SAMPLE: '샘플', DISPOSAL: '폐기'};
|
||||
const usageColors = {SALE: 'success', SELF_USE: 'warning text-dark', SAMPLE: 'info', DISPOSAL: 'secondary'};
|
||||
const curUsage = compound.usage_type || 'SALE';
|
||||
const usageBadge = `<span class="badge bg-${usageColors[curUsage]} change-usage" style="cursor:pointer" data-id="${compound.compound_id}" data-current="${curUsage}" title="클릭하여 용도 변경">${usageLabels[curUsage]}</span>`;
|
||||
const isSale = curUsage === 'SALE';
|
||||
|
||||
const row = $(`
|
||||
<tr>
|
||||
<td>${response.data.length - index}</td>
|
||||
@@ -1712,14 +1724,14 @@ $(document).ready(function() {
|
||||
<td>${compound.cheop_total || 0}</td>
|
||||
<td>${compound.pouch_total || 0}</td>
|
||||
<td>${formatCurrency(compound.cost_total || 0)}</td>
|
||||
<td>${formatCurrency(compound.sell_price_total || 0)}</td>
|
||||
<td>${statusBadge}</td>
|
||||
<td>${isSale ? formatCurrency(compound.sell_price_total || 0) : '-'}</td>
|
||||
<td>${usageBadge} ${statusBadge}</td>
|
||||
<td>${compound.prescription_no || '-'}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-info view-compound-detail" data-id="${compound.compound_id}">
|
||||
<i class="bi bi-eye"></i> 상세
|
||||
</button>
|
||||
${compound.status === 'PREPARED' ? `
|
||||
${compound.status === 'PREPARED' && isSale ? `
|
||||
<button class="btn btn-sm btn-outline-success process-sale" data-id="${compound.compound_id}"
|
||||
data-formula="${compound.formula_name || '직접조제'}"
|
||||
data-patient="${compound.patient_name || '직접조제'}"
|
||||
@@ -1728,6 +1740,8 @@ $(document).ready(function() {
|
||||
data-price="${compound.sell_price_total || 0}">
|
||||
<i class="bi bi-cash-coin"></i> 판매
|
||||
</button>
|
||||
` : ''}
|
||||
${compound.status === 'PREPARED' ? `
|
||||
<button class="btn btn-sm btn-outline-danger cancel-compound" data-id="${compound.compound_id}">
|
||||
<i class="bi bi-x-circle"></i> 취소
|
||||
</button>
|
||||
@@ -1795,6 +1809,40 @@ $(document).ready(function() {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 용도 변경 뱃지 클릭 이벤트
|
||||
$('.change-usage').on('click', function() {
|
||||
const compoundId = $(this).data('id');
|
||||
const current = $(this).data('current');
|
||||
const options = {SALE: '판매', SELF_USE: '자가소비', SAMPLE: '샘플', DISPOSAL: '폐기'};
|
||||
const choices = Object.entries(options)
|
||||
.map(([k, v]) => `${k === current ? '● ' : ' '}${v}`)
|
||||
.join('\n');
|
||||
const input = prompt(`용도를 선택하세요 (현재: ${options[current]})\n\n1: 판매\n2: 자가소비\n3: 샘플\n4: 폐기`, current === 'SALE' ? '1' : current === 'SELF_USE' ? '2' : current === 'SAMPLE' ? '3' : '4');
|
||||
if (!input) return;
|
||||
const typeMap = {'1': 'SALE', '2': 'SELF_USE', '3': 'SAMPLE', '4': 'DISPOSAL'};
|
||||
const newType = typeMap[input.trim()];
|
||||
if (!newType) { alert('잘못된 입력입니다.'); return; }
|
||||
if (newType === current) return;
|
||||
|
||||
$.ajax({
|
||||
url: `/api/compounds/${compoundId}/usage-type`,
|
||||
method: 'PUT',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({ usage_type: newType }),
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
loadCompounds();
|
||||
loadDashboard();
|
||||
} else {
|
||||
alert(response.error || '변경 실패');
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
alert(xhr.responseJSON?.error || '변경 실패');
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
tbody.html('<tr><td colspan="13" class="text-center text-muted">조제 내역이 없습니다.</td></tr>');
|
||||
$('#todayCompoundCount').text(0);
|
||||
|
||||
Reference in New Issue
Block a user