Add pharmacy auto-registration and infrastructure improvements
- Auto-generate pharmacy_code (P001~P999) when creating new pharmacy - Add new pharmacy fields: owner info, institution code/type, API port - Change Headplane port mapping: 3000 → 3001 to avoid conflicts - Add code-server setup script for development environment - Add LXC Caddy setup documentation - Update .gitignore to exclude farmq-admin submodule 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -42,31 +42,51 @@ class JSONType(TypeDecorator):
|
||||
class PharmacyInfo(FarmqBase):
|
||||
"""약국 정보 테이블 - Headscale과 독립적"""
|
||||
__tablename__ = 'pharmacies'
|
||||
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
|
||||
|
||||
# 약국 코드 (핵심 식별자) - P001~P999
|
||||
pharmacy_code = Column(String(10), unique=True)
|
||||
|
||||
# Headscale 연결 정보 (느슨한 결합)
|
||||
headscale_user_name = Column(String(255)) # users.name 참조 (외래키 제약조건 없음)
|
||||
headscale_user_id = Column(Integer) # users.id 참조 (외래키 제약조건 없음)
|
||||
|
||||
|
||||
# 약국 기본 정보
|
||||
pharmacy_name = Column(String(255), nullable=False)
|
||||
business_number = Column(String(20))
|
||||
manager_name = Column(String(100))
|
||||
manager_name = Column(String(100)) # deprecated - use owner_name
|
||||
phone = Column(String(20))
|
||||
address = Column(Text)
|
||||
|
||||
# 기술적 정보
|
||||
|
||||
# 대표자 정보 (신규)
|
||||
owner_name = Column(String(100))
|
||||
owner_license = Column(String(50))
|
||||
owner_phone = Column(String(20))
|
||||
owner_email = Column(String(100))
|
||||
|
||||
# 요양기관 정보 (신규)
|
||||
institution_code = Column(String(20))
|
||||
institution_type = Column(String(20))
|
||||
|
||||
# 운영 정보 (신규)
|
||||
opening_date = Column(DateTime)
|
||||
business_hours = Column(Text)
|
||||
|
||||
# API 포트 (신규)
|
||||
api_port = Column(Integer, default=8082)
|
||||
|
||||
# 기술적 정보 (deprecated - pharmacy_servers로 이동)
|
||||
proxmox_host = Column(String(255))
|
||||
proxmox_username = Column(String(100))
|
||||
proxmox_api_token = Column(Text) # 암호화 권장
|
||||
tailscale_ip = Column(String(45)) # IPv4/IPv6 지원
|
||||
|
||||
tailscale_ip = Column(String(45)) # IPv4/IPv6 지원 (deprecated)
|
||||
|
||||
# 상태 관리
|
||||
status = Column(String(20), default='active') # active, inactive, maintenance
|
||||
last_sync = Column(DateTime) # 마지막 동기화 시간
|
||||
notes = Column(Text) # 관리 메모
|
||||
|
||||
|
||||
# 타임스탬프
|
||||
created_at = Column(DateTime, default=datetime.now)
|
||||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
@@ -78,6 +98,7 @@ class PharmacyInfo(FarmqBase):
|
||||
"""딕셔너리로 변환"""
|
||||
return {
|
||||
'id': self.id,
|
||||
'pharmacy_code': self.pharmacy_code,
|
||||
'headscale_user_name': self.headscale_user_name,
|
||||
'headscale_user_id': self.headscale_user_id,
|
||||
'pharmacy_name': self.pharmacy_name,
|
||||
@@ -85,6 +106,15 @@ class PharmacyInfo(FarmqBase):
|
||||
'manager_name': self.manager_name,
|
||||
'phone': self.phone,
|
||||
'address': self.address,
|
||||
'owner_name': self.owner_name,
|
||||
'owner_license': self.owner_license,
|
||||
'owner_phone': self.owner_phone,
|
||||
'owner_email': self.owner_email,
|
||||
'institution_code': self.institution_code,
|
||||
'institution_type': self.institution_type,
|
||||
'opening_date': self.opening_date.isoformat() if self.opening_date else None,
|
||||
'business_hours': self.business_hours,
|
||||
'api_port': self.api_port,
|
||||
'proxmox_host': self.proxmox_host,
|
||||
'tailscale_ip': self.tailscale_ip,
|
||||
'status': self.status,
|
||||
@@ -179,6 +209,97 @@ class MachineProfile(FarmqBase):
|
||||
}
|
||||
|
||||
|
||||
class PharmacyServer(FarmqBase):
|
||||
"""약국 서버 테이블 - 약국과 서버 분리"""
|
||||
__tablename__ = 'pharmacy_servers'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
|
||||
# 약국 연결
|
||||
pharmacy_id = Column(Integer, nullable=False)
|
||||
pharmacy_code = Column(String(10), nullable=False)
|
||||
|
||||
# Headscale 노드 연결
|
||||
headscale_node_id = Column(Integer, unique=True)
|
||||
headscale_user_id = Column(Integer)
|
||||
|
||||
# 네트워크 정보
|
||||
vpn_ip = Column(String(45), nullable=False)
|
||||
api_port = Column(Integer, default=8082)
|
||||
is_online = Column(Boolean, default=False)
|
||||
last_seen_at = Column(DateTime)
|
||||
|
||||
# 서버 역할
|
||||
server_role = Column(String(20), default='primary') # primary, backup, test
|
||||
is_active = Column(Boolean, default=True)
|
||||
|
||||
# 하드웨어 정보
|
||||
hostname = Column(String(255))
|
||||
machine_name = Column(String(255))
|
||||
serial_number = Column(String(100))
|
||||
|
||||
cpu_model = Column(String(255))
|
||||
cpu_cores = Column(Integer)
|
||||
cpu_threads = Column(Integer)
|
||||
ram_gb = Column(Integer)
|
||||
storage_type = Column(String(50))
|
||||
storage_gb = Column(Integer)
|
||||
gpu_model = Column(String(255))
|
||||
gpu_memory_gb = Column(Integer)
|
||||
network_interfaces = Column(JSONType)
|
||||
|
||||
os_type = Column(String(50))
|
||||
os_version = Column(String(100))
|
||||
tailscale_version = Column(String(50))
|
||||
installed_software = Column(JSONType)
|
||||
|
||||
# 관리 정보
|
||||
status = Column(String(20), default='active')
|
||||
location = Column(String(255))
|
||||
purchase_date = Column(DateTime)
|
||||
warranty_expires = Column(DateTime)
|
||||
last_maintenance = Column(DateTime)
|
||||
|
||||
# 베이스라인 메트릭
|
||||
baseline_cpu_temp = Column(Float)
|
||||
baseline_cpu_usage = Column(Float)
|
||||
baseline_memory_usage = Column(Float)
|
||||
|
||||
# 메타데이터
|
||||
notes = Column(Text)
|
||||
created_at = Column(DateTime, default=datetime.now)
|
||||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<PharmacyServer(id={self.id}, pharmacy_code='{self.pharmacy_code}', vpn_ip='{self.vpn_ip}')>"
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
'id': self.id,
|
||||
'pharmacy_id': self.pharmacy_id,
|
||||
'pharmacy_code': self.pharmacy_code,
|
||||
'headscale_node_id': self.headscale_node_id,
|
||||
'vpn_ip': self.vpn_ip,
|
||||
'api_port': self.api_port,
|
||||
'is_online': self.is_online,
|
||||
'last_seen_at': self.last_seen_at.isoformat() if self.last_seen_at else None,
|
||||
'server_role': self.server_role,
|
||||
'is_active': self.is_active,
|
||||
'hostname': self.hostname,
|
||||
'machine_name': self.machine_name,
|
||||
'cpu_model': self.cpu_model,
|
||||
'cpu_cores': self.cpu_cores,
|
||||
'ram_gb': self.ram_gb,
|
||||
'storage_gb': self.storage_gb,
|
||||
'os_type': self.os_type,
|
||||
'os_version': self.os_version,
|
||||
'status': self.status,
|
||||
'location': self.location,
|
||||
'created_at': self.created_at.isoformat(),
|
||||
'updated_at': self.updated_at.isoformat()
|
||||
}
|
||||
|
||||
|
||||
class MonitoringMetrics(FarmqBase):
|
||||
"""실시간 모니터링 메트릭스 - 시계열 데이터"""
|
||||
__tablename__ = 'monitoring_metrics'
|
||||
|
||||
Reference in New Issue
Block a user