feat(pmr): 라벨 디자인 개선 + 약품명 정규화
라벨 미리보기: - 지그재그 테두리 (가위로 자른 느낌) - 환자명 공백 + 폰트 확대 (44px) - 복용량 박스 + 총량 표시 - 시그니처 박스 (청 춘 약 국) - 조제일 표시 약품명 정규화: - 밀리그램/밀리그람 → mg - 마이크로그램 → μg - 그램/그람 → g - 밀리리터 → mL - 언더스코어(_) 뒤 내용 제거 - 대괄호 내용 제거 프론트엔드: - data-med-name 속성으로 순수 약품명 전달 - 번호/뱃지 제외된 이름 사용
This commit is contained in:
parent
849ce4c3c0
commit
7b71ea0179
@ -577,10 +577,82 @@ def preview_label():
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
def normalize_medication_name(med_name):
|
||||
"""
|
||||
약품명 정제 - 밀리그램 등을 mg로 변환, 불필요한 부분 제거
|
||||
"""
|
||||
import re
|
||||
if not med_name:
|
||||
return med_name
|
||||
|
||||
# 언더스코어 뒤 내용 제거 (예: 휴니즈레바미피드정_ → 휴니즈레바미피드정)
|
||||
med_name = re.sub(r'_.*$', '', med_name)
|
||||
|
||||
# 대괄호 및 내용 제거
|
||||
med_name = re.sub(r'\[.*?\]', '', med_name)
|
||||
med_name = re.sub(r'\[.*$', '', med_name)
|
||||
|
||||
# 밀리그램 변환
|
||||
med_name = re.sub(r'밀리그램|밀리그람|미리그램|미리그람', 'mg', med_name)
|
||||
# 마이크로그램 변환
|
||||
med_name = re.sub(r'마이크로그램|마이크로그람', 'μg', med_name)
|
||||
# 그램 변환 (mg/μg 제외)
|
||||
med_name = re.sub(r'(?<!m)(?<!μ)그램|그람', 'g', med_name)
|
||||
# 밀리리터 변환
|
||||
med_name = re.sub(r'밀리리터|밀리리타|미리리터|미리리타', 'mL', med_name)
|
||||
|
||||
# 공백 정리
|
||||
med_name = re.sub(r'\s+', ' ', med_name).strip()
|
||||
|
||||
return med_name
|
||||
|
||||
|
||||
def draw_scissor_border(draw, width, height, edge_size=5, steps=20):
|
||||
"""
|
||||
지그재그 패턴의 테두리를 그립니다 (가위로 자른 느낌).
|
||||
"""
|
||||
# 상단 테두리
|
||||
top_points = []
|
||||
step_x = width / (steps * 2)
|
||||
for i in range(steps * 2 + 1):
|
||||
x = i * step_x
|
||||
y = 0 if i % 2 == 0 else edge_size
|
||||
top_points.append((int(x), int(y)))
|
||||
draw.line(top_points, fill="black", width=2)
|
||||
|
||||
# 하단 테두리
|
||||
bottom_points = []
|
||||
for i in range(steps * 2 + 1):
|
||||
x = i * step_x
|
||||
y = height if i % 2 == 0 else height - edge_size
|
||||
bottom_points.append((int(x), int(y)))
|
||||
draw.line(bottom_points, fill="black", width=2)
|
||||
|
||||
# 좌측 테두리
|
||||
left_points = []
|
||||
step_y = height / (steps * 2)
|
||||
for i in range(steps * 2 + 1):
|
||||
y = i * step_y
|
||||
x = 0 if i % 2 == 0 else edge_size
|
||||
left_points.append((int(x), int(y)))
|
||||
draw.line(left_points, fill="black", width=2)
|
||||
|
||||
# 우측 테두리
|
||||
right_points = []
|
||||
for i in range(steps * 2 + 1):
|
||||
y = i * step_y
|
||||
x = width if i % 2 == 0 else width - edge_size
|
||||
right_points.append((int(x), int(y)))
|
||||
draw.line(right_points, fill="black", width=2)
|
||||
|
||||
|
||||
def create_label_image(patient_name, med_name, add_info='', dosage=0, frequency=0, duration=0, unit='정'):
|
||||
"""
|
||||
라벨 이미지 생성 (29mm 용지 기준)
|
||||
라벨 이미지 생성 (29mm 용지 기준) - 레거시 디자인 적용
|
||||
"""
|
||||
# 약품명 정제 (밀리그램 → mg 등)
|
||||
med_name = normalize_medication_name(med_name)
|
||||
|
||||
# 라벨 크기 (29mm 용지, 300dpi 기준)
|
||||
label_width = 306
|
||||
label_height = 380
|
||||
@ -594,15 +666,38 @@ def create_label_image(patient_name, med_name, add_info='', dosage=0, frequency=
|
||||
font_path = "C:/Windows/Fonts/malgun.ttf"
|
||||
|
||||
try:
|
||||
name_font = ImageFont.truetype(font_path, 36)
|
||||
drug_font = ImageFont.truetype(font_path, 24)
|
||||
info_font = ImageFont.truetype(font_path, 22)
|
||||
small_font = ImageFont.truetype(font_path, 18)
|
||||
name_font = ImageFont.truetype(font_path, 44) # 환자명 폰트 크게
|
||||
drug_font = ImageFont.truetype(font_path, 32) # 약품명
|
||||
info_font = ImageFont.truetype(font_path, 30) # 복용 정보
|
||||
small_font = ImageFont.truetype(font_path, 20) # 조제일
|
||||
additional_font = ImageFont.truetype(font_path, 27) # 총량/효능
|
||||
signature_font = ImageFont.truetype(font_path, 32) # 시그니처
|
||||
except:
|
||||
name_font = ImageFont.load_default()
|
||||
drug_font = ImageFont.load_default()
|
||||
info_font = ImageFont.load_default()
|
||||
small_font = ImageFont.load_default()
|
||||
additional_font = ImageFont.load_default()
|
||||
signature_font = ImageFont.load_default()
|
||||
|
||||
# 동적 폰트 크기 조정 함수
|
||||
def get_adaptive_font(text, max_width, initial_font_size, min_font_size=20):
|
||||
"""텍스트가 max_width를 초과하지 않도록 폰트 크기를 동적으로 조정"""
|
||||
current_size = initial_font_size
|
||||
while current_size >= min_font_size:
|
||||
try:
|
||||
test_font = ImageFont.truetype(font_path, current_size)
|
||||
except:
|
||||
return ImageFont.load_default()
|
||||
bbox = draw.textbbox((0, 0), text, font=test_font)
|
||||
text_width = bbox[2] - bbox[0]
|
||||
if text_width <= max_width:
|
||||
return test_font
|
||||
current_size -= 2
|
||||
try:
|
||||
return ImageFont.truetype(font_path, min_font_size)
|
||||
except:
|
||||
return ImageFont.load_default()
|
||||
|
||||
# 중앙 정렬 텍스트 함수
|
||||
def draw_centered(text, y, font, fill="black"):
|
||||
@ -650,33 +745,37 @@ def create_label_image(patient_name, med_name, add_info='', dosage=0, frequency=
|
||||
for line in name_lines:
|
||||
y = draw_centered(line, y, drug_font)
|
||||
|
||||
# 효능효과 (add_info)
|
||||
# 효능효과 (add_info) - 동적 폰트 크기 적용
|
||||
if add_info:
|
||||
y = draw_centered(f"({add_info})", y, small_font, fill="gray")
|
||||
efficacy_text = f"({add_info})"
|
||||
adaptive_efficacy_font = get_adaptive_font(efficacy_text, label_width - 40, 30, 20)
|
||||
y = draw_centered(efficacy_text, y, adaptive_efficacy_font, fill="black")
|
||||
|
||||
y += 5
|
||||
|
||||
# 총량 계산
|
||||
# 총량 계산 및 표시
|
||||
if dosage > 0 and frequency > 0 and duration > 0:
|
||||
total = dosage * frequency * duration
|
||||
total_str = str(int(total)) if total == int(total) else f"{total:.1f}"
|
||||
total_text = f"총 {total_str}{unit} / {duration}일분"
|
||||
y = draw_centered(total_text, y, info_font)
|
||||
total_str = str(int(total)) if total == int(total) else f"{total:.2f}".rstrip('0').rstrip('.')
|
||||
total_text = f"총{total_str}{unit}/{duration}일분"
|
||||
y = draw_centered(total_text, y, additional_font)
|
||||
|
||||
y += 5
|
||||
|
||||
# 용법 박스
|
||||
# 용법 박스 (테두리 있는 박스)
|
||||
box_margin = 20
|
||||
box_height = 75
|
||||
box_top = y
|
||||
box_bottom = y + 70
|
||||
box_bottom = y + box_height
|
||||
box_width = label_width - 2 * box_margin
|
||||
draw.rectangle(
|
||||
[(box_margin, box_top), (label_width - box_margin, box_bottom)],
|
||||
outline="black",
|
||||
width=2
|
||||
)
|
||||
|
||||
# 박스 내용
|
||||
dosage_str = str(int(dosage)) if dosage == int(dosage) else f"{dosage:.2f}".rstrip('0').rstrip('.')
|
||||
# 박스 내용 - 1회 복용량
|
||||
dosage_str = str(int(dosage)) if dosage == int(dosage) else f"{dosage:.4f}".rstrip('0').rstrip('.')
|
||||
dosage_text = f"{dosage_str}{unit}"
|
||||
|
||||
# 복용 시간
|
||||
@ -689,24 +788,51 @@ def create_label_image(patient_name, med_name, add_info='', dosage=0, frequency=
|
||||
else:
|
||||
time_text = f"1일 {frequency}회"
|
||||
|
||||
box_center_y = (box_top + box_bottom) // 2
|
||||
draw_centered(dosage_text, box_center_y - 20, info_font)
|
||||
draw_centered(time_text, box_center_y + 5, info_font)
|
||||
# 박스 내 텍스트 중앙 배치 (수직 중앙 정렬)
|
||||
line_spacing = 5
|
||||
bbox1 = draw.textbbox((0, 0), dosage_text, font=info_font)
|
||||
text1_height = bbox1[3] - bbox1[1]
|
||||
bbox2 = draw.textbbox((0, 0), time_text, font=info_font)
|
||||
text2_height = bbox2[3] - bbox2[1]
|
||||
total_text_height = text1_height + line_spacing + text2_height
|
||||
|
||||
center_y = (box_top + box_bottom) // 2
|
||||
start_y = center_y - (total_text_height // 2) - 5 # 약간 위로 조정
|
||||
|
||||
draw_centered(dosage_text, start_y, info_font)
|
||||
draw_centered(time_text, start_y + text1_height + line_spacing, info_font)
|
||||
|
||||
y = box_bottom + 10
|
||||
|
||||
# 조제일
|
||||
# 조제일 (시그니처 위쪽에 배치)
|
||||
today = datetime.now().strftime('%Y-%m-%d')
|
||||
y = draw_centered(f"조제일: {today}", y, small_font)
|
||||
print_date_text = f"조제일 : {today}"
|
||||
bbox = draw.textbbox((0, 0), print_date_text, font=small_font)
|
||||
date_w, date_h = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
||||
print_date_y = label_height - date_h - 70 # 시그니처 위쪽
|
||||
draw.text(((label_width - date_w) / 2, print_date_y), print_date_text, font=small_font, fill="black")
|
||||
|
||||
# 약국명 (하단)
|
||||
pharmacy_y = label_height - 40
|
||||
draw.rectangle(
|
||||
[(50, pharmacy_y - 5), (label_width - 50, pharmacy_y + 25)],
|
||||
outline="black",
|
||||
width=1
|
||||
)
|
||||
draw_centered("청 춘 약 국", pharmacy_y, info_font)
|
||||
# 시그니처 박스 (하단 - 약국명)
|
||||
signature_text = "청 춘 약 국"
|
||||
bbox = draw.textbbox((0, 0), signature_text, font=signature_font)
|
||||
w_sig, h_sig = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
||||
|
||||
# 시그니처 박스 패딩 및 위치 계산
|
||||
padding_top = int(h_sig * 0.1)
|
||||
padding_bottom = int(h_sig * 0.5)
|
||||
padding_sides = int(h_sig * 0.2)
|
||||
|
||||
box_x = (label_width - w_sig) / 2 - padding_sides
|
||||
box_y = label_height - h_sig - padding_top - padding_bottom - 10
|
||||
box_x2 = box_x + w_sig + 2 * padding_sides
|
||||
box_y2 = box_y + h_sig + padding_top + padding_bottom
|
||||
|
||||
# 시그니처 테두리 및 텍스트
|
||||
draw.rectangle([(box_x, box_y), (box_x2, box_y2)], outline="black", width=1)
|
||||
draw.text(((label_width - w_sig) / 2, box_y + padding_top), signature_text, font=signature_font, fill="black")
|
||||
|
||||
# 지그재그 테두리 (가위로 자른 느낌)
|
||||
draw_scissor_border(draw, label_width, label_height, edge_size=10, steps=20)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
@ -1562,7 +1562,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
${data.medications.map((m, i) => `
|
||||
<tr data-add-info="${escapeHtml(m.add_info || '')}" data-unit="${m.unit || '정'}" ${m.is_substituted ? 'class="substituted-row"' : ''}>
|
||||
<tr data-add-info="${escapeHtml(m.add_info || '')}" data-unit="${m.unit || '정'}" data-med-name="${escapeHtml(m.med_name || m.medication_code)}" ${m.is_substituted ? 'class="substituted-row"' : ''}>
|
||||
<td><input type="checkbox" class="med-check" data-code="${m.medication_code}" ${m.is_auto_print ? 'checked' : ''}></td>
|
||||
<td>
|
||||
<div class="med-name">
|
||||
@ -2600,8 +2600,8 @@
|
||||
const tr = checkbox.closest('tr');
|
||||
const cells = tr.querySelectorAll('td');
|
||||
|
||||
// 약품명: 두 번째 셀의 .med-name
|
||||
const medName = tr.querySelector('.med-name')?.textContent?.trim() || '';
|
||||
// 약품명: data-med-name 속성에서 (번호/뱃지 제외된 순수 약품명)
|
||||
const medName = tr.dataset.medName || '';
|
||||
const addInfo = tr.dataset.addInfo || '';
|
||||
// 용량: 세 번째 셀 (index 2) - 제형 컬럼 제거됨
|
||||
const dosageText = cells[2]?.textContent?.replace(/[^0-9.]/g, '') || '0';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user