diff --git a/app/models/uploaded_file.py b/app/models/uploaded_file.py index fe7dc7774077d5ea98a1284cf5078a094a841dbc..2230d4390dcf4087b062ab8998dc51eb5dbd7787 100644 --- a/app/models/uploaded_file.py +++ b/app/models/uploaded_file.py @@ -4,73 +4,60 @@ from app import db class UploadedFile(db.Model): """Model for uploaded CSV files and their processing status""" __tablename__ = 'uploadedfiles' - + id = db.Column('fileID', db.Integer, primary_key=True) fileName = db.Column(db.String(255), nullable=False) filePath = db.Column(db.String(255), nullable=False) - uploadDateTime = db.Column(db.DateTime, nullable=False) - - # Foreign key to users table user_id = db.Column('userID', db.Integer, db.ForeignKey('users.userID'), nullable=False) - + # Upload metadata original_filename = db.Column(db.String(256), nullable=False) - file_size = db.Column(db.Integer) # in bytes - - # Processing details - file_type = db.Column(db.String(64), default='csv') # csv, xls, etc. + file_size = db.Column(db.Integer) + file_type = db.Column(db.String(64), default='csv') delimiter = db.Column(db.String(10), default=',') - encoding = db.Column(db.String(20), default='utf-8') - + file_encoding = db.Column(db.String(20), default='utf-8') + # Status tracking - status = db.Column(db.String(20), default='pending') # pending, processing, completed, failed + status = db.Column(db.String(20), default='pending') process_start = db.Column(db.DateTime) process_end = db.Column(db.DateTime) - + # Results total_records = db.Column(db.Integer, default=0) processed_records = db.Column(db.Integer, default=0) error_records = db.Column(db.Integer, default=0) - error_details = db.Column(db.Text) # JSON string with error details - + error_details = db.Column(db.Text) + process_referrals = db.Column(db.Boolean, default=False) - description = db.Column(db.Text) notes = db.Column(db.Text) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - - # Trường upload_date để hỗ trợ sắp xếp upload_date = db.Column(db.DateTime, default=datetime.utcnow) - + # Relationships uploader = db.relationship('User', foreign_keys=[user_id], backref='uploaded_files') - + def __repr__(self): return f'<UploadedFile {self.id} ({self.fileName})>' - + @property def is_completed(self): return self.status == 'completed' - + @property def is_processing(self): return self.status == 'processing' - + @property def success_rate(self): if self.total_records > 0: return round((self.processed_records / self.total_records) * 100, 1) return 0 - + @property def processing_time(self): if self.process_start and self.process_end: delta = self.process_end - self.process_start return delta.total_seconds() return None - - def __init__(self, **kwargs): - super(UploadedFile, self).__init__(**kwargs) - self.uploadDateTime = kwargs.get('uploadDateTime', datetime.utcnow()) - self.upload_date = self.uploadDateTime \ No newline at end of file diff --git a/app/routes/upload.py b/app/routes/upload.py index 60a5b2c86888ec91c5170e01f425c1a69ce457a5..6eaaad182e23aa21922956e8a055d5f4cb9e8465 100644 --- a/app/routes/upload.py +++ b/app/routes/upload.py @@ -61,7 +61,7 @@ def index(): 'tab': '\t' }.get(request.form.get('delimiter', 'comma'), ',') - encoding = request.form.get('encoding', 'utf-8') + file_encoding = request.form.get('encoding', 'utf-8') description = request.form.get('description', '') process_referrals = True if request.form.get('process_referrals') else False @@ -73,7 +73,7 @@ def index(): file_size=file_size, uploaded_by=current_user.id, delimiter=delimiter, - encoding=encoding, + file_encoding=file_encoding, process_referrals=process_referrals, description=description, status='pending' @@ -83,7 +83,7 @@ def index(): db.session.commit() # Validate the CSV file format - validation_result = validate_csv(file_path, delimiter, encoding) + validation_result = validate_csv(file_path, delimiter, file_encoding) if validation_result['valid']: # Process the CSV in the background (in a real app, this would be a Celery/RQ task) @@ -167,7 +167,7 @@ def preview(file_id): df = pd.read_csv( uploaded_file.file_path, delimiter=uploaded_file.delimiter, - encoding=uploaded_file.encoding, + encoding=uploaded_file.file_encoding, nrows=10 ) diff --git a/app/templates/edit_patient.html b/app/templates/edit_patient.html new file mode 100644 index 0000000000000000000000000000000000000000..46d1af1ae6c101398001e71fe1fab1c0dcb05474 --- /dev/null +++ b/app/templates/edit_patient.html @@ -0,0 +1,180 @@ +{% extends "base.html" %} + +{% block title %}Edit Patient - {{ patient.full_name }}{% endblock %} + +{% block content %} +<div class="container mx-auto px-4 py-6"> + <!-- Breadcrumbs --> + <nav class="flex mb-5" aria-label="Breadcrumb"> + <ol class="inline-flex items-center space-x-1 md:space-x-3"> + <li class="inline-flex items-center"> + <a href="{{ url_for('index') }}" class="inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600"> + <svg class="w-3 h-3 mr-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20"> + <path d="m19.707 9.293-2-2-7-7a1 1 0 0 0-1.414 0l-7 7-2 2a1 1 0 0 0 1.414 1.414L2 10.414V18a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h3a2 2 0 0 0 2-2v-7.586l.293.293a1 1 0 0 0 1.414-1.414Z"/> + </svg> + Home + </a> + </li> + <li> + <div class="flex items-center"> + <svg class="w-3 h-3 text-gray-400 mx-1" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10"> + <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/> + </svg> + <a href="{{ url_for('patients.index') }}" class="ml-1 text-sm font-medium text-gray-700 hover:text-blue-600 md:ml-2">Patients</a> + </div> + </li> + <li> + <div class="flex items-center"> + <svg class="w-3 h-3 text-gray-400 mx-1" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10"> + <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/> + </svg> + <a href="{{ url_for('patients.patient_detail', patient_id=patient.patient_id) }}" class="ml-1 text-sm font-medium text-gray-700 hover:text-blue-600 md:ml-2">{{ patient.full_name }}</a> + </div> + </li> + <li aria-current="page"> + <div class="flex items-center"> + <svg class="w-3 h-3 text-gray-400 mx-1" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10"> + <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/> + </svg> + <span class="ml-1 text-sm font-medium text-gray-500 md:ml-2">Edit Patient</span> + </div> + </li> + </ol> + </nav> + + <!-- Page Title --> + <h1 class="text-2xl font-bold text-gray-900 mb-6 flex items-center"> + <svg class="w-6 h-6 text-gray-700 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"></path> + </svg> + Edit Patient: {{ patient.full_name }} + </h1> + + <!-- Form Card --> + <div class="bg-white rounded-lg shadow-md overflow-hidden"> + <div class="p-6"> + <form method="POST" action="{{ url_for('patients.edit_patient', patient_id=patient.patient_id) }}"> + <!-- Basic Info Section --> + <div class="mb-8"> + <h2 class="text-lg font-semibold text-gray-800 mb-4">Patient Information</h2> + <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> + <!-- Patient ID (readonly) --> + <div> + <label for="patient_id" class="block text-sm font-medium text-gray-700 mb-1">Patient ID</label> + <input type="text" id="patient_id" name="patient_id" value="{{ patient.patient_id }}" readonly + class="w-full px-3 py-2 border border-gray-200 bg-gray-100 rounded-md text-gray-500"> + </div> + + <!-- First Name --> + <div> + <label for="firstName" class="block text-sm font-medium text-gray-700 mb-1">First Name</label> + <input type="text" id="firstName" name="firstName" value="{{ patient.firstName }}" required + class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition"> + </div> + + <!-- Last Name --> + <div> + <label for="lastName" class="block text-sm font-medium text-gray-700 mb-1">Last Name</label> + <input type="text" id="lastName" name="lastName" value="{{ patient.lastName }}" required + class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition"> + </div> + + <!-- Date of Birth --> + <div> + <label for="date_of_birth" class="block text-sm font-medium text-gray-700 mb-1">Date of Birth</label> + <input type="date" id="date_of_birth" name="date_of_birth" value="{{ patient.date_of_birth.strftime('%Y-%m-%d') if patient.date_of_birth else '' }}" + class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition"> + </div> + + <!-- Gender --> + <div> + <label for="gender" class="block text-sm font-medium text-gray-700 mb-1">Gender</label> + <select id="gender" name="gender" required + class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition"> + <option value="">Select gender</option> + <option value="male" {% if patient.gender == 'male' %}selected{% endif %}>Male</option> + <option value="female" {% if patient.gender == 'female' %}selected{% endif %}>Female</option> + <option value="other" {% if patient.gender == 'other' %}selected{% endif %}>Other</option> + </select> + </div> + </div> + </div> + + <!-- Physical Measurements Section --> + <div class="mb-8"> + <h2 class="text-lg font-semibold text-gray-800 mb-4">Physical Measurements</h2> + <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> + <!-- Height --> + <div> + <label for="height" class="block text-sm font-medium text-gray-700 mb-1">Height (cm)</label> + <input type="number" id="height" name="height" min="0" step="0.1" value="{{ patient.height or '' }}" + class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition"> + </div> + + <!-- Weight --> + <div> + <label for="weight" class="block text-sm font-medium text-gray-700 mb-1">Weight (kg)</label> + <input type="number" id="weight" name="weight" min="0" step="0.1" value="{{ patient.weight or '' }}" + class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition"> + </div> + + <!-- Blood Type --> + <div> + <label for="blood_type" class="block text-sm font-medium text-gray-700 mb-1">Blood Type</label> + <select id="blood_type" name="blood_type" + class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition"> + <option value="">Select blood type</option> + <option value="A+" {% if patient.blood_type == 'A+' %}selected{% endif %}>A+</option> + <option value="A-" {% if patient.blood_type == 'A-' %}selected{% endif %}>A-</option> + <option value="B+" {% if patient.blood_type == 'B+' %}selected{% endif %}>B+</option> + <option value="B-" {% if patient.blood_type == 'B-' %}selected{% endif %}>B-</option> + <option value="AB+" {% if patient.blood_type == 'AB+' %}selected{% endif %}>AB+</option> + <option value="AB-" {% if patient.blood_type == 'AB-' %}selected{% endif %}>AB-</option> + <option value="O+" {% if patient.blood_type == 'O+' %}selected{% endif %}>O+</option> + <option value="O-" {% if patient.blood_type == 'O-' %}selected{% endif %}>O-</option> + <option value="Unknown" {% if patient.blood_type == 'Unknown' %}selected{% endif %}>Unknown</option> + </select> + </div> + </div> + </div> + + <!-- Form Buttons --> + <div class="flex justify-end space-x-4 mt-8"> + <a href="{{ url_for('patients.patient_detail', patient_id=patient.patient_id) }}" class="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition"> + Cancel + </a> + <button type="submit" class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition"> + Save Changes + </button> + </div> + </form> + </div> + </div> +</div> +{% endblock %} + +{% block scripts %} +<script> + document.addEventListener('DOMContentLoaded', function() { + // Calculate BMI when height or weight changes + const heightInput = document.getElementById('height'); + const weightInput = document.getElementById('weight'); + + function calculateBMI() { + const height = parseFloat(heightInput.value) / 100; // convert cm to m + const weight = parseFloat(weightInput.value); + + if (height && weight && height > 0 && weight > 0) { + const bmi = weight / (height * height); + console.log(`Calculated BMI: ${bmi.toFixed(2)}`); + // You could display this somewhere or add it as a hidden field + } + } + + if (heightInput && weightInput) { + heightInput.addEventListener('change', calculateBMI); + weightInput.addEventListener('change', calculateBMI); + } + }); +</script> +{% endblock %} \ No newline at end of file diff --git a/app/templates/patient_detail.html b/app/templates/patient_detail.html index 7d27fe5bb64312a3504865a8f0404415c60b6072..c4e7647d58e734ab5752e8e85d5a6dd888813ef3 100644 --- a/app/templates/patient_detail.html +++ b/app/templates/patient_detail.html @@ -4,46 +4,45 @@ {% block content %} <div class="animate-slide-in"> - <!-- Breadcrumb và actions --> + <!-- Breadcrumb --> + <div class="mb-6"> + <nav class="flex" aria-label="Breadcrumb"> + <ol class="inline-flex items-center space-x-1 md:space-x-3"> + <li class="inline-flex items-center"> + <a href="{{ url_for('index') }}" class="inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600 transition duration-200"> + <svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"></path></svg> + Trang chủ + </a> + </li> + <li> + <div class="flex items-center"> + <svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg> + <a href="{{ url_for('patients.index') }}" class="ml-1 text-sm font-medium text-gray-700 hover:text-blue-600 md:ml-2 transition duration-200">Bệnh nhân</a> + </div> + </li> + <li aria-current="page"> + <div class="flex items-center"> + <svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg> + {% set patient_number = patient.patient_id.split('-')[1]|int - 10000 %} + <span class="ml-1 text-sm font-medium text-gray-500 md:ml-2">Bệnh nhân {{ patient_number }}</span> + </div> + </li> + </ol> + </nav> + </div> + + <!-- Tiêu đề và nút thao tác --> <div class="md:flex md:items-center md:justify-between mb-6"> <div class="flex-1 min-w-0"> - <nav class="flex" aria-label="Breadcrumb"> - <ol class="flex items-center space-x-4"> - <li> - <div> - <a href="{{ url_for('dashboard.index') }}" class="text-gray-400 hover:text-gray-500 transition duration-150"> - <i class="fas fa-home"></i> - <span class="sr-only">Dashboard</span> - </a> - </div> - </li> - <li> - <div class="flex items-center"> - <i class="fas fa-chevron-right text-gray-400 text-sm"></i> - <a href="{{ url_for('patients.index') }}" class="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700 transition duration-150">Bệnh nhân</a> - </div> - </li> - <li> - <div class="flex items-center"> - <i class="fas fa-chevron-right text-gray-400 text-sm"></i> - <span class="ml-4 text-sm font-medium text-gray-700" aria-current="page">{{ patient.name|default('Bệnh nhân 1') }}</span> - </div> - </li> - </ol> - </nav> - <h2 class="mt-2 text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate"> - {{ patient.name|default('Bệnh nhân 1') }} + <h2 class="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate"> + {{ patient.full_name }} </h2> </div> <div class="mt-4 flex md:mt-0 md:ml-4 space-x-3"> - <button type="button" class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 transition-all duration-200 transform hover:-translate-y-1"> - <i class="fas fa-file-medical -ml-1 mr-2 text-gray-500"></i> - Xuất báo cáo - </button> - <button type="button" class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 transition-all duration-200 transform hover:-translate-y-1"> + <a href="{{ url_for('patients.edit_patient', patient_id=patient.patient_id) }}" class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 transition-all duration-200 transform hover:-translate-y-1"> <i class="fas fa-edit -ml-1 mr-2 text-gray-500"></i> Chỉnh sửa - </button> + </a> <button type="button" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 transition-all duration-200 transform hover:-translate-y-1"> <i class="fas fa-check -ml-1 mr-2"></i> Cập nhật trạng thái @@ -129,7 +128,7 @@ <div class="space-y-3"> <div class="flex justify-between"> <span class="text-sm font-medium text-gray-500">Tên đầy đủ</span> - <span class="text-sm text-gray-900">{{ patient.name|default('Bệnh nhân 1') }}</span> + <span class="text-sm text-gray-900 ml-4 max-w-[200px] truncate">{{ patient.full_name }}</span> </div> <div class="flex justify-between"> <span class="text-sm font-medium text-gray-500">Tuổi</span> diff --git a/app/templates/patients.html b/app/templates/patients.html index be3ca462f3677e10afd94a272e49fa1307cb29d4..469265f41d874b8895e16ec8b9dfc9ea64c05b0b 100644 --- a/app/templates/patients.html +++ b/app/templates/patients.html @@ -116,13 +116,13 @@ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ patient.admission_date.strftime('%Y-%m-%d') }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm font-medium"> <a href="{{ url_for('patients.patient_detail', patient_id=patient.patient_id) }}" class="text-blue-600 hover:text-blue-900 mr-3"> - <i class="fas fa-eye"></i> + <i class="fas fa-eye text-2xl"></i> </a> <a href="{{ url_for('patients.edit_patient', patient_id=patient.patient_id) }}" class="text-indigo-600 hover:text-indigo-900 mr-3"> - <i class="fas fa-edit"></i> + <i class="fas fa-edit text-2xl"></i> </a> <button type="button" data-patient-id="{{ patient.patient_id }}" class="text-red-600 hover:text-red-900 delete-patient"> - <i class="fas fa-trash"></i> + <i class="fas fa-trash text-2xl"></i> </button> </td> </tr> diff --git a/app/templates/upload.html b/app/templates/upload.html index b4e2b5f7c49a772c5765b2dfa9e7993ba9ec23df..43863856301c8c5ba5fe7b4a633f458ee9ba8bce 100644 --- a/app/templates/upload.html +++ b/app/templates/upload.html @@ -64,6 +64,7 @@ <div class="sm:col-span-3"> <label for="encoding" class="block text-sm font-medium text-gray-700">Mã hóa</label> + <!-- name="encoding" is kept for form submission, but it maps to file_encoding in the model --> <select id="encoding" name="encoding" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm rounded-md transition duration-200"> <option value="utf-8">UTF-8</option> <option value="latin-1">Latin-1</option> diff --git a/app/utils/csv_handler.py b/app/utils/csv_handler.py index c8f159058d0086eebe3ad3982e649b25d26bd096..06776857218a0210f12ff923d88b8f6335690341 100644 --- a/app/utils/csv_handler.py +++ b/app/utils/csv_handler.py @@ -87,7 +87,7 @@ def process_csv(uploaded_file_id): df = pd.read_csv( uploaded_file.file_path, delimiter=uploaded_file.delimiter, - encoding=uploaded_file.encoding + encoding=uploaded_file.file_encoding ) # Initialize counters diff --git a/clear_cache.bat b/clear_cache.bat deleted file mode 100644 index e33c0b651e486dcddd4ab300dc61da923347a12a..0000000000000000000000000000000000000000 --- a/clear_cache.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off -echo Đang xóa các file __pycache__ và .pyc... - -:: Xóa tất cả các thư mục __pycache__ -for /d /r . %%d in (__pycache__) do @if exist "%%d" rd /s /q "%%d" - -:: Xóa tất cả các file .pyc -del /s /q *.pyc - -echo Cache đã được xóa. Đang khởi động lại ứng dụng... -call run_app.bat \ No newline at end of file diff --git a/create_admin.py b/create_admin.py deleted file mode 100644 index ed2ccc1d5260a7f00ec440e4388c1d6e613ef23a..0000000000000000000000000000000000000000 --- a/create_admin.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from flask_bcrypt import Bcrypt -from flask import Flask -import mysql.connector - -# Cấu hình kết nối MySQL trực tiếp không qua SQLAlchemy -db_config = { - 'host': 'localhost', - 'user': 'root', - 'password': 'MinhDZ3009', - 'database': 'ccu' -} - -def create_admin_user(): - """ - Tạo tài khoản admin trực tiếp trong MySQL mà không cần sử dụng mô hình. - """ - try: - # Kết nối đến MySQL - connection = mysql.connector.connect(**db_config) - cursor = connection.cursor() - - # Kiểm tra xem admin đã tồn tại chưa - cursor.execute("SELECT * FROM users WHERE email = 'admin@ccuhtm.com'") - admin = cursor.fetchone() - - if not admin: - print("Đang tạo người dùng admin...") - - # Tạo mật khẩu băm - bcrypt = Bcrypt() - hashed_password = bcrypt.generate_password_hash('admin').decode('utf-8') - - # Thêm người dùng admin - query = """ - INSERT INTO users (username, password_hash, email, role) - VALUES (%s, %s, %s, %s) - """ - values = ('admin', hashed_password, 'admin@ccuhtm.com', 'Admin') - - cursor.execute(query, values) - - # Lấy ID của người dùng vừa tạo - cursor.execute("SELECT userID FROM users WHERE email = 'admin@ccuhtm.com'") - user_id = cursor.fetchone()[0] - - # Tạo dietitian info cho admin - query_dietitian = """ - INSERT INTO dietitians (userID, firstName, lastName) - VALUES (%s, %s, %s) - """ - values_dietitian = (user_id, 'Admin', 'System') - - cursor.execute(query_dietitian, values_dietitian) - - connection.commit() - print('Người dùng admin đã được tạo thành công!') - else: - print('Người dùng admin đã tồn tại.') - - # Đóng kết nối - cursor.close() - connection.close() - - return True - - except Exception as e: - print(f"Lỗi khi tạo người dùng admin: {str(e)}") - return False - -if __name__ == '__main__': - print("Bắt đầu tạo người dùng admin...") - create_admin_user() \ No newline at end of file diff --git a/init_database.py b/init_database.py deleted file mode 100644 index be6e3549f7545503e530f0346cb68d15e1f26196..0000000000000000000000000000000000000000 --- a/init_database.py +++ /dev/null @@ -1,359 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os -import sys -import mysql.connector -from flask import Flask -from app import create_app, db - -def create_mysql_database(): - """ - Tạo cơ sở dữ liệu MySQL nếu nó chưa tồn tại. - Trước khi chạy hàm này, bạn phải đảm bảo máy chủ MySQL đang chạy. - """ - try: - # Đọc thông tin kết nối từ cấu hình - # Format URI: mysql://username:password@localhost/database - app = create_app() - db_uri = app.config['SQLALCHEMY_DATABASE_URI'] - - if not db_uri.startswith('mysql'): - print("Không phải cấu hình MySQL, bỏ qua bước tạo cơ sở dữ liệu.") - return True - - # Phân tích chuỗi kết nối - db_parts = db_uri.replace('mysql://', '').split('@') - auth_parts = db_parts[0].split(':') - host_parts = db_parts[1].split('/') - - username = auth_parts[0] - password = auth_parts[1] if len(auth_parts) > 1 else '' - host = host_parts[0] - database = host_parts[1] - - print(f"Đang kết nối đến MySQL với người dùng '{username}' trên máy chủ '{host}'") - - # Kết nối đến MySQL (không có cơ sở dữ liệu) - connection = mysql.connector.connect( - host=host, - user=username, - password=password - ) - - cursor = connection.cursor() - - # Tạo cơ sở dữ liệu nếu nó chưa tồn tại - cursor.execute(f"CREATE DATABASE IF NOT EXISTS {database} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci") - print(f"Cơ sở dữ liệu '{database}' đã được tạo hoặc đã tồn tại.") - - # Đóng kết nối - cursor.close() - connection.close() - - return True - - except Exception as e: - print(f"Lỗi khi tạo cơ sở dữ liệu: {str(e)}") - return False - -def init_tables_and_admin(): - """ - Khởi tạo các bảng và tạo tài khoản admin - """ - try: - app = create_app() - - with app.app_context(): - # Xóa các bảng hiện có để tạo lại với cấu trúc mới - try: - # Xóa các bảng theo thứ tự để tránh lỗi khóa ngoại - db.session.execute(db.text("SET FOREIGN_KEY_CHECKS = 0")) - - # Xóa các bảng có phụ thuộc - db.session.execute(db.text("DROP TABLE IF EXISTS physiologicalmeasurements")) - db.session.execute(db.text("DROP TABLE IF EXISTS procedures")) - db.session.execute(db.text("DROP TABLE IF EXISTS referrals")) - db.session.execute(db.text("DROP TABLE IF EXISTS reports")) - db.session.execute(db.text("DROP TABLE IF EXISTS encounters")) - - # Xóa bảng chính - db.session.execute(db.text("DROP TABLE IF EXISTS patients")) - - db.session.execute(db.text("SET FOREIGN_KEY_CHECKS = 1")) - db.session.commit() - print("Dropped existing tables to recreate schema.") - except Exception as e: - db.session.rollback() - print(f"Error dropping tables: {str(e)}") - - # Tạo tất cả bảng từ các mô hình với cấu trúc mới - db.create_all() - print("All database tables created successfully with new schema.") - - # Thêm các trường cần thiết vào bảng uploadedfiles - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN original_filename VARCHAR(256) DEFAULT ''")) - db.session.commit() - print("Added original_filename column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN file_size INT DEFAULT 0")) - db.session.commit() - print("Added file_size column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN file_type VARCHAR(64) DEFAULT 'csv'")) - db.session.commit() - print("Added file_type column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN delimiter VARCHAR(10) DEFAULT ','")) - db.session.commit() - print("Added delimiter column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN encoding VARCHAR(20) DEFAULT 'utf-8'")) - db.session.commit() - print("Added encoding column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN status VARCHAR(20) DEFAULT 'pending'")) - db.session.commit() - print("Added status column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN process_start DATETIME NULL")) - db.session.commit() - print("Added process_start column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN process_end DATETIME NULL")) - db.session.commit() - print("Added process_end column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN total_records INT DEFAULT 0")) - db.session.commit() - print("Added total_records column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN processed_records INT DEFAULT 0")) - db.session.commit() - print("Added processed_records column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN error_records INT DEFAULT 0")) - db.session.commit() - print("Added error_records column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN error_details TEXT NULL")) - db.session.commit() - print("Added error_details column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN process_referrals BOOLEAN DEFAULT 0")) - db.session.commit() - print("Added process_referrals column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN description TEXT NULL")) - db.session.commit() - print("Added description column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN notes TEXT NULL")) - db.session.commit() - print("Added notes column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMP")) - db.session.commit() - print("Added created_at column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")) - db.session.commit() - print("Added updated_at column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - try: - db.session.execute(db.text("ALTER TABLE uploadedfiles ADD COLUMN upload_date DATETIME DEFAULT CURRENT_TIMESTAMP")) - db.session.commit() - print("Added upload_date column to uploadedfiles table.") - except Exception as e: - db.session.rollback() - print(f"Note: {str(e)}") - - # Thêm trường status vào bảng reports nếu chưa tồn tại - try: - db.session.execute(db.text("ALTER TABLE reports ADD COLUMN status VARCHAR(20) DEFAULT 'draft'")) - db.session.commit() - print("Added status column to reports table.") - except Exception as e: - # Nếu cột đã tồn tại hoặc lỗi khác, bỏ qua - db.session.rollback() - print(f"Note: {str(e)}") - - # Thêm trường created_at vào bảng reports nếu chưa tồn tại - try: - db.session.execute(db.text("ALTER TABLE reports ADD COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMP")) - db.session.commit() - print("Added created_at column to reports table.") - except Exception as e: - # Nếu cột đã tồn tại hoặc lỗi khác, bỏ qua - db.session.rollback() - print(f"Note: {str(e)}") - - # Thêm trường status vào bảng patients nếu chưa tồn tại - try: - db.session.execute(db.text("ALTER TABLE patients ADD COLUMN status VARCHAR(20) DEFAULT 'active'")) - db.session.commit() - print("Added status column to patients table.") - except Exception as e: - # Nếu cột đã tồn tại hoặc lỗi khác, bỏ qua - db.session.rollback() - print(f"Note: {str(e)}") - - # Thêm trường height vào bảng patients nếu chưa tồn tại - try: - db.session.execute(db.text("ALTER TABLE patients ADD COLUMN height FLOAT NULL")) - db.session.commit() - print("Added height column to patients table.") - except Exception as e: - # Nếu cột đã tồn tại hoặc lỗi khác, bỏ qua - db.session.rollback() - print(f"Note: {str(e)}") - - # Thêm trường weight vào bảng patients nếu chưa tồn tại - try: - db.session.execute(db.text("ALTER TABLE patients ADD COLUMN weight FLOAT NULL")) - db.session.commit() - print("Added weight column to patients table.") - except Exception as e: - # Nếu cột đã tồn tại hoặc lỗi khác, bỏ qua - db.session.rollback() - print(f"Note: {str(e)}") - - # Thêm trường blood_type vào bảng patients nếu chưa tồn tại - try: - db.session.execute(db.text("ALTER TABLE patients ADD COLUMN blood_type VARCHAR(10) NULL")) - db.session.commit() - print("Added blood_type column to patients table.") - except Exception as e: - # Nếu cột đã tồn tại hoặc lỗi khác, bỏ qua - db.session.rollback() - print(f"Note: {str(e)}") - - # Thêm trường admission_date vào bảng patients nếu chưa tồn tại - try: - db.session.execute(db.text("ALTER TABLE patients ADD COLUMN admission_date DATETIME NULL")) - db.session.commit() - print("Added admission_date column to patients table.") - except Exception as e: - # Nếu cột đã tồn tại hoặc lỗi khác, bỏ qua - db.session.rollback() - print(f"Note: {str(e)}") - - # Thêm trường created_at vào bảng patients nếu chưa tồn tại - try: - db.session.execute(db.text("ALTER TABLE patients ADD COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMP")) - db.session.commit() - print("Added created_at column to patients table.") - except Exception as e: - # Nếu cột đã tồn tại hoặc lỗi khác, bỏ qua - db.session.rollback() - print(f"Note: {str(e)}") - - # Tạo tài khoản admin - from app.models.user import User - from flask_bcrypt import Bcrypt - - bcrypt = Bcrypt() - - # Kiểm tra xem admin đã tồn tại chưa - admin = User.query.filter_by(email='admin@ccuhtm.com').first() - if not admin: - hashed_password = bcrypt.generate_password_hash('admin').decode('utf-8') - - # Tạo user admin đơn giản, không dùng is_admin - admin = User( - username='admin', - email='admin@ccuhtm.com', - password=hashed_password - ) - db.session.add(admin) - db.session.commit() - print('Admin user created.') - else: - print('Admin user already exists.') - - return True - - except Exception as e: - print(f"Error initializing tables: {str(e)}") - return False - -if __name__ == '__main__': - print("Starting database initialization...") - - # Tạo cơ sở dữ liệu MySQL - if create_mysql_database(): - # Tạo bảng và tài khoản admin - if init_tables_and_admin(): - print("Database initialization completed successfully!") - else: - print("Failed to initialize tables and admin account.") - else: - print("Failed to create MySQL database.") \ No newline at end of file diff --git a/run.py b/run.py index 6bcf9c4323c6b4e066abccd871a5fdf5ef57b96d..ad9bc5010d811626ff1f286ce6f59ce7af666053 100644 --- a/run.py +++ b/run.py @@ -1,9 +1,103 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from app import create_app +import os +import shutil +import argparse +import mysql.connector +from flask_bcrypt import Bcrypt +from flask import Flask +from datetime import datetime -app = create_app() +def clear_cache(): + print("\n[Cache] Cleaning __pycache__ and .pyc files...") + for dirpath, _, filenames in os.walk(os.path.dirname(__file__)): + for name in filenames: + if name.endswith('.pyc') or name == '__pycache__': + try: + path = os.path.join(dirpath, name) + os.remove(path) if name.endswith('.pyc') else shutil.rmtree(path) + print(f"Removed: {path}") + except Exception as e: + print(f"[Error] {e}") + +def create_mysql_database(app): + try: + uri = app.config['SQLALCHEMY_DATABASE_URI'] + if not uri.startswith('mysql'): + print("[Skip] Not a MySQL URI.") + return True + + creds, path = uri.replace('mysql://', '').split('@') + user, pwd = creds.split(':') + host, db = path.split('/') + + connection = mysql.connector.connect(host=host, user=user, password=pwd) + cursor = connection.cursor() + cursor.execute(f"CREATE DATABASE IF NOT EXISTS {db} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci") + cursor.close(); connection.close() + print(f"[DB] Database '{db}' ready.") + return True + except Exception as e: + print(f"[DB Error] {e}") + return False + +def init_tables_and_admin(app, db): + try: + from app.models.user import User + bcrypt = Bcrypt(app) + + with app.app_context(): + print("[DB] Dropping old tables...") + db.session.execute(db.text("SET FOREIGN_KEY_CHECKS = 0")) + for tbl in ["physiologicalmeasurements", "procedures", "referrals", "reports", "encounters", "patients"]: + db.session.execute(db.text(f"DROP TABLE IF EXISTS {tbl}")) + db.session.execute(db.text("SET FOREIGN_KEY_CHECKS = 1")) + db.session.commit() + + db.create_all() + print("[DB] Tables created.") + + # Create default admin + if not User.query.filter_by(email='admin@ccuhtm.com').first(): + admin = User( + username='admin', + email='admin@ccuhtm.com', + password=bcrypt.generate_password_hash('admin').decode('utf-8') + ) + db.session.add(admin) + db.session.commit() + print("[Admin] Created default admin user.") + else: + print("[Admin] Admin already exists.") + + return True + except Exception as e: + print(f"[Error] Init failed: {e}") + return False + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--init', action='store_true') + parser.add_argument('--clean', action='store_true') + parser.add_argument('--admin', action='store_true') + parser.add_argument('--port', type=int, default=5000) + args = parser.parse_args() + + from app import create_app, db + app = create_app() + + if args.clean: + clear_cache() + if args.init: + if create_mysql_database(app): + init_tables_and_admin(app, db) + if args.admin: + init_tables_and_admin(app, db) + + if not (args.init or args.admin): + print(f"[Run] Starting Flask app on port {args.port}...") + app.run(host='0.0.0.0', port=args.port, debug=True) if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000, debug=True) + main() diff --git a/run_app.bat b/run_app.bat deleted file mode 100644 index a4d0aa69045a6cd2ecd80fc4d1c86d2b8b3b05c3..0000000000000000000000000000000000000000 --- a/run_app.bat +++ /dev/null @@ -1,5 +0,0 @@ -@echo off -echo Starting CCU HTM... -cd %~dp0 -python run.py -pause \ No newline at end of file diff --git a/run_ccu.bat b/run_ccu.bat new file mode 100644 index 0000000000000000000000000000000000000000..e78ebac95421d226eb8005aca60c01033933e8fc --- /dev/null +++ b/run_ccu.bat @@ -0,0 +1,55 @@ +@echo off +echo CCU HTM Management System +echo ------------------------- + +:menu +cls +echo Pick an option: +echo 1. Database inittialization (for 1st time) +echo 2. App run +echo 3. Run app and clear cache +echo 4. Admin acc creation +echo 5. Exit +echo. + +set /p choice=Your choice (1-5): + +if "%choice%"=="1" ( + echo Data initializing... + python run.py --init + echo. + echo Press any key to back to menu... + pause >nul + goto menu +) +if "%choice%"=="2" ( + echo Running app... + python run.py + goto end +) +if "%choice%"=="3" ( + echo Deleting cache and running app... + python run.py --clean + goto end +) +if "%choice%"=="4" ( + echo Creating admin account... + python run.py --admin + echo. + echo Press any key to back to menu... + pause >nul + goto menu +) +if "%choice%"=="5" ( + goto end +) else ( + echo Choice invalid! + echo. + echo Press any key to back to menu... + pause >nul + goto menu +) + +:end +echo Thanks for using CCU HTM Management System! +exit \ No newline at end of file