완전한 약국 관리 및 사용자-약국 매칭 시스템 구현

🏥 약국 관리 API 구현:
- POST /api/pharmacy - 새 약국 생성 (모든 DB 칼럼 지원)
- PUT /api/pharmacy/<id> - 약국 정보 수정
- DELETE /api/pharmacy/<id>/delete - 약국 삭제
- 약국 관리 페이지 UI 완전 연동

👤 사용자-약국 매칭 시스템:
- POST /api/users/<user>/link-pharmacy - 사용자와 약국 연결
- 실시간 매칭 상태 표시 및 업데이트
- Headscale 사용자와 FARMQ 약국 간 완전한 연결

🔧 핵심 설계 원칙 100% 준수:
- Headscale CLI 기반 제어 (사용자 생성/삭제)
- 이중 사용자 구분 (Headscale ↔ FARMQ 약국)
- 느슨한 결합 (headscale_user_name 매핑)
- 실시간 동기화 (API 호출 즉시 반영)

 전체 시스템 통합 테스트 완료:
- 약국 생성 → 사용자 생성 → 매칭 → 실시간 확인
- DB 칼럼 구조와 완벽 일치
- UI/API 완전 연동

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-11 11:17:13 +09:00
parent fd8c5cbb81
commit e71cdb2cda
3 changed files with 265 additions and 44 deletions

View File

@@ -177,8 +177,8 @@
<input type="text" class="form-control" id="proxmox_host" placeholder="192.168.1.100">
</div>
<div class="col-md-6 mb-3">
<label for="user_id" class="form-label">연결된 사용자 ID</label>
<input type="text" class="form-control" id="user_id" placeholder="user1">
<label for="headscale_user_name" class="form-label">Headscale 사용자</label>
<input type="text" class="form-control" id="headscale_user_name" placeholder="myuser">
</div>
</div>
</div>
@@ -234,7 +234,7 @@ function showEditModal(pharmacyId) {
document.getElementById('phone').value = pharmacy.phone || '';
document.getElementById('address').value = pharmacy.address || '';
document.getElementById('proxmox_host').value = pharmacy.proxmox_host || '';
document.getElementById('user_id').value = pharmacy.user_id || '';
document.getElementById('headscale_user_name').value = pharmacy.headscale_user_name || '';
// 수정 모드임을 표시하기 위해 pharmacy ID를 form에 저장
document.getElementById('pharmacyForm').dataset.pharmacyId = pharmacyId;
@@ -264,12 +264,12 @@ document.getElementById('pharmacyForm').addEventListener('submit', function(e) {
phone: document.getElementById('phone').value,
address: document.getElementById('address').value,
proxmox_host: document.getElementById('proxmox_host').value,
user_id: document.getElementById('user_id').value
headscale_user_name: document.getElementById('headscale_user_name').value
};
if (mode === 'edit' && pharmacyId) {
// 수정 모드: PUT 요청
fetch(`/api/pharmacy/${pharmacyId}/update`, {
fetch(`/api/pharmacy/${pharmacyId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
@@ -278,12 +278,12 @@ document.getElementById('pharmacyForm').addEventListener('submit', function(e) {
})
.then(response => response.json())
.then(result => {
if (result.error) {
showToast(result.error, 'error');
} else {
showToast('약국 정보가 수정되었습니다.', 'success');
if (result.success) {
showToast(result.message, 'success');
pharmacyModal.hide();
setTimeout(() => location.reload(), 1000);
} else {
showToast(result.error, 'error');
}
})
.catch(error => {
@@ -291,9 +291,28 @@ document.getElementById('pharmacyForm').addEventListener('submit', function(e) {
showToast('약국 정보 수정에 실패했습니다.', 'error');
});
} else {
// 새 등록 모드: POST 요청 (향후 구현)
showToast('새 약국 등록 기능은 아직 구현 중입니다.', 'warning');
pharmacyModal.hide();
// 새 등록 모드: POST 요청
fetch('/api/pharmacy', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if (result.success) {
showToast(result.message, 'success');
pharmacyModal.hide();
setTimeout(() => location.reload(), 1000);
} else {
showToast(result.error, 'error');
}
})
.catch(error => {
console.error('약국 생성 실패:', error);
showToast('약국 생성에 실패했습니다.', 'error');
});
}
});