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
|
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='정'):
|
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 기준)
|
# 라벨 크기 (29mm 용지, 300dpi 기준)
|
||||||
label_width = 306
|
label_width = 306
|
||||||
label_height = 380
|
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"
|
font_path = "C:/Windows/Fonts/malgun.ttf"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
name_font = ImageFont.truetype(font_path, 36)
|
name_font = ImageFont.truetype(font_path, 44) # 환자명 폰트 크게
|
||||||
drug_font = ImageFont.truetype(font_path, 24)
|
drug_font = ImageFont.truetype(font_path, 32) # 약품명
|
||||||
info_font = ImageFont.truetype(font_path, 22)
|
info_font = ImageFont.truetype(font_path, 30) # 복용 정보
|
||||||
small_font = ImageFont.truetype(font_path, 18)
|
small_font = ImageFont.truetype(font_path, 20) # 조제일
|
||||||
|
additional_font = ImageFont.truetype(font_path, 27) # 총량/효능
|
||||||
|
signature_font = ImageFont.truetype(font_path, 32) # 시그니처
|
||||||
except:
|
except:
|
||||||
name_font = ImageFont.load_default()
|
name_font = ImageFont.load_default()
|
||||||
drug_font = ImageFont.load_default()
|
drug_font = ImageFont.load_default()
|
||||||
info_font = ImageFont.load_default()
|
info_font = ImageFont.load_default()
|
||||||
small_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"):
|
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:
|
for line in name_lines:
|
||||||
y = draw_centered(line, y, drug_font)
|
y = draw_centered(line, y, drug_font)
|
||||||
|
|
||||||
# 효능효과 (add_info)
|
# 효능효과 (add_info) - 동적 폰트 크기 적용
|
||||||
if 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
|
y += 5
|
||||||
|
|
||||||
# 총량 계산
|
# 총량 계산 및 표시
|
||||||
if dosage > 0 and frequency > 0 and duration > 0:
|
if dosage > 0 and frequency > 0 and duration > 0:
|
||||||
total = dosage * frequency * duration
|
total = dosage * frequency * duration
|
||||||
total_str = str(int(total)) if total == int(total) else f"{total:.1f}"
|
total_str = str(int(total)) if total == int(total) else f"{total:.2f}".rstrip('0').rstrip('.')
|
||||||
total_text = f"총 {total_str}{unit} / {duration}일분"
|
total_text = f"총{total_str}{unit}/{duration}일분"
|
||||||
y = draw_centered(total_text, y, info_font)
|
y = draw_centered(total_text, y, additional_font)
|
||||||
|
|
||||||
y += 5
|
y += 5
|
||||||
|
|
||||||
# 용법 박스
|
# 용법 박스 (테두리 있는 박스)
|
||||||
box_margin = 20
|
box_margin = 20
|
||||||
|
box_height = 75
|
||||||
box_top = y
|
box_top = y
|
||||||
box_bottom = y + 70
|
box_bottom = y + box_height
|
||||||
|
box_width = label_width - 2 * box_margin
|
||||||
draw.rectangle(
|
draw.rectangle(
|
||||||
[(box_margin, box_top), (label_width - box_margin, box_bottom)],
|
[(box_margin, box_top), (label_width - box_margin, box_bottom)],
|
||||||
outline="black",
|
outline="black",
|
||||||
width=2
|
width=2
|
||||||
)
|
)
|
||||||
|
|
||||||
# 박스 내용
|
# 박스 내용 - 1회 복용량
|
||||||
dosage_str = str(int(dosage)) if dosage == int(dosage) else f"{dosage:.2f}".rstrip('0').rstrip('.')
|
dosage_str = str(int(dosage)) if dosage == int(dosage) else f"{dosage:.4f}".rstrip('0').rstrip('.')
|
||||||
dosage_text = f"{dosage_str}{unit}"
|
dosage_text = f"{dosage_str}{unit}"
|
||||||
|
|
||||||
# 복용 시간
|
# 복용 시간
|
||||||
@ -689,24 +788,51 @@ def create_label_image(patient_name, med_name, add_info='', dosage=0, frequency=
|
|||||||
else:
|
else:
|
||||||
time_text = f"1일 {frequency}회"
|
time_text = f"1일 {frequency}회"
|
||||||
|
|
||||||
box_center_y = (box_top + box_bottom) // 2
|
# 박스 내 텍스트 중앙 배치 (수직 중앙 정렬)
|
||||||
draw_centered(dosage_text, box_center_y - 20, info_font)
|
line_spacing = 5
|
||||||
draw_centered(time_text, box_center_y + 5, info_font)
|
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
|
y = box_bottom + 10
|
||||||
|
|
||||||
# 조제일
|
# 조제일 (시그니처 위쪽에 배치)
|
||||||
today = datetime.now().strftime('%Y-%m-%d')
|
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
|
signature_text = "청 춘 약 국"
|
||||||
draw.rectangle(
|
bbox = draw.textbbox((0, 0), signature_text, font=signature_font)
|
||||||
[(50, pharmacy_y - 5), (label_width - 50, pharmacy_y + 25)],
|
w_sig, h_sig = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
||||||
outline="black",
|
|
||||||
width=1
|
# 시그니처 박스 패딩 및 위치 계산
|
||||||
)
|
padding_top = int(h_sig * 0.1)
|
||||||
draw_centered("청 춘 약 국", pharmacy_y, info_font)
|
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
|
return image
|
||||||
|
|
||||||
|
|||||||
@ -1562,7 +1562,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
${data.medications.map((m, i) => `
|
${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><input type="checkbox" class="med-check" data-code="${m.medication_code}" ${m.is_auto_print ? 'checked' : ''}></td>
|
||||||
<td>
|
<td>
|
||||||
<div class="med-name">
|
<div class="med-name">
|
||||||
@ -2600,8 +2600,8 @@
|
|||||||
const tr = checkbox.closest('tr');
|
const tr = checkbox.closest('tr');
|
||||||
const cells = tr.querySelectorAll('td');
|
const cells = tr.querySelectorAll('td');
|
||||||
|
|
||||||
// 약품명: 두 번째 셀의 .med-name
|
// 약품명: data-med-name 속성에서 (번호/뱃지 제외된 순수 약품명)
|
||||||
const medName = tr.querySelector('.med-name')?.textContent?.trim() || '';
|
const medName = tr.dataset.medName || '';
|
||||||
const addInfo = tr.dataset.addInfo || '';
|
const addInfo = tr.dataset.addInfo || '';
|
||||||
// 용량: 세 번째 셀 (index 2) - 제형 컬럼 제거됨
|
// 용량: 세 번째 셀 (index 2) - 제형 컬럼 제거됨
|
||||||
const dosageText = cells[2]?.textContent?.replace(/[^0-9.]/g, '') || '0';
|
const dosageText = cells[2]?.textContent?.replace(/[^0-9.]/g, '') || '0';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user