diff --git a/database/BSK.db b/database/BSK.db index 39b3566..22cc969 100644 Binary files a/database/BSK.db and b/database/BSK.db differ diff --git a/database/BSK.db-shm b/database/BSK.db-shm new file mode 100644 index 0000000..6cccc9a Binary files /dev/null and b/database/BSK.db-shm differ diff --git a/database/BSK.db-wal b/database/BSK.db-wal new file mode 100644 index 0000000..75d3cd8 Binary files /dev/null and b/database/BSK.db-wal differ diff --git a/image/checkup_media/3/ultrasound_3_20250804_120429_739.jpg b/image/checkup_media/3/ultrasound_3_20250804_120429_739.jpg new file mode 100644 index 0000000..95403d9 Binary files /dev/null and b/image/checkup_media/3/ultrasound_3_20250804_120429_739.jpg differ diff --git a/image/checkup_media/3/ultrasound_3_20250804_121459_607.jpg b/image/checkup_media/3/ultrasound_3_20250804_121459_607.jpg new file mode 100644 index 0000000..772cc07 Binary files /dev/null and b/image/checkup_media/3/ultrasound_3_20250804_121459_607.jpg differ diff --git a/image/checkup_media/4/IMG_4_20250804_134627.jpg b/image/checkup_media/4/IMG_4_20250804_134627.jpg new file mode 100644 index 0000000..d241a9d Binary files /dev/null and b/image/checkup_media/4/IMG_4_20250804_134627.jpg differ diff --git a/image/checkup_media/4/IMG_4_20250804_134628.jpg b/image/checkup_media/4/IMG_4_20250804_134628.jpg new file mode 100644 index 0000000..6a60a78 Binary files /dev/null and b/image/checkup_media/4/IMG_4_20250804_134628.jpg differ diff --git a/image/checkup_media/4/IMG_4_20250804_134630.jpg b/image/checkup_media/4/IMG_4_20250804_134630.jpg new file mode 100644 index 0000000..a27e117 Binary files /dev/null and b/image/checkup_media/4/IMG_4_20250804_134630.jpg differ diff --git a/image/checkup_media/4/IMG_4_20250804_134631.jpg b/image/checkup_media/4/IMG_4_20250804_134631.jpg new file mode 100644 index 0000000..29896f9 Binary files /dev/null and b/image/checkup_media/4/IMG_4_20250804_134631.jpg differ diff --git a/pom.xml b/pom.xml index 3154827..c1073c2 100644 --- a/pom.xml +++ b/pom.xml @@ -236,5 +236,15 @@ poi-ooxml 5.2.5 + + com.zaxxer + HikariCP + 5.1.0 + + + org.xerial + sqlite-jdbc + 3.45.1.0 + \ No newline at end of file diff --git a/src/main/java/BsK/client/Client.java b/src/main/java/BsK/client/Client.java index e213a50..0b2117b 100644 --- a/src/main/java/BsK/client/Client.java +++ b/src/main/java/BsK/client/Client.java @@ -13,10 +13,8 @@ import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler; import io.netty.handler.codec.http.websocketx.WebSocketVersion; -import io.netty.handler.timeout.IdleStateHandler; import java.net.URI; import java.net.URISyntaxException; -import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; import java.io.InputStream; import java.util.Properties; @@ -178,10 +176,8 @@ public class Client { @Override protected void initChannel(SocketChannel ch) { ch.pipeline() - .addLast(new IdleStateHandler(60 * 30, - 0, 0, TimeUnit.SECONDS)) .addLast(new HttpClientCodec()) - .addLast(new HttpObjectAggregator(50 * 1024 * 1024)) + .addLast(new HttpObjectAggregator(10 * 1024 * 1024)) .addLast( new WebSocketClientProtocolHandler( finalUri, @@ -189,7 +185,7 @@ public class Client { null, true, new DefaultHttpHeaders(), - 50 * 1024 * 1024)) + 10 * 1024 * 1024)) .addLast("ws", ClientHandler.INSTANCE); } }); diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java index 313630c..7200f7a 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java @@ -226,7 +226,7 @@ public class CheckUpPage extends JPanel { private final Map> uploadTimeoutTasks = new ConcurrentHashMap<>(); // Ultrasound folder monitoring - private static final String ULTRASOUND_FOLDER_PATH = "ANH SIEU AM"; + private static final String ULTRASOUND_FOLDER_PATH = LocalStorage.ULTRASOUND_FOLDER_PATH; private WatchService watchService; private ExecutorService folderWatchExecutor; private volatile boolean isWatchingFolder = false; @@ -1821,27 +1821,25 @@ public class CheckUpPage extends JPanel { // First, show the print preview to the user try { medicineInvoice.showDirectJasperViewer(); + medicineInvoice.generatePdfBytesAsync().thenAccept(pdfBytes -> { + if (pdfBytes != null && pdfBytes.length > 0) { + String checkupId = checkupIdField.getText(); + String fileName = "medserinvoice.pdf"; + String pdfType = "medserinvoice"; - // Then, asynchronously generate the PDF bytes and upload them - // medicineInvoice.generatePdfBytesAsync().thenAccept(pdfBytes -> { - // if (pdfBytes != null && pdfBytes.length > 0) { - // String checkupId = checkupIdField.getText(); - // String fileName = "medserinvoice.pdf"; - // String pdfType = "medserinvoice"; + log.info("Uploading {} ({}) for checkupId: {}", fileName, pdfType, checkupId); - // log.info("Uploading {} ({}) for checkupId: {}", fileName, pdfType, checkupId); - - // UploadCheckupPdfRequest request = new UploadCheckupPdfRequest(checkupId, pdfBytes, fileName, pdfType); - // NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); - // } - // }).exceptionally(ex -> { - // log.error("Failed to generate or upload medicine invoice PDF", ex); - // // Show error in the UI thread - // SwingUtilities.invokeLater(() -> - // JOptionPane.showMessageDialog(this, "Lỗi khi tạo hoặc tải lên file PDF hóa đơn: " + ex.getMessage(), "Lỗi PDF", JOptionPane.ERROR_MESSAGE) - // ); - // return null; - // }); + UploadCheckupPdfRequest request = new UploadCheckupPdfRequest(checkupId, pdfBytes, fileName, pdfType); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); + } + }).exceptionally(ex -> { + log.error("Failed to generate or upload medicine invoice PDF", ex); + // Show error in the UI thread + SwingUtilities.invokeLater(() -> + JOptionPane.showMessageDialog(this, "Lỗi khi tạo hoặc tải lên file PDF hóa đơn: " + ex.getMessage(), "Lỗi PDF", JOptionPane.ERROR_MESSAGE) + ); + return null; + }); } catch (Exception e) { log.error("Failed to show direct JasperViewer", e); JOptionPane.showMessageDialog(this, "Lỗi khi hiển thị hộp thoại in: " + e.getMessage(), "Lỗi In", JOptionPane.ERROR_MESSAGE); @@ -2015,7 +2013,7 @@ public class CheckUpPage extends JPanel { callingStatusLabel.setText(message); callingStatusLabel.setBackground(new Color(230, 255, 230)); callingStatusLabel.setForeground(new Color(0, 100, 0)); - updateUpdateQueue(); + // updateUpdateQueue(); // If patient is marked as "ĐÃ KHÁM", clear the selection and details immediately. if ("ĐÃ KHÁM".equals(statusToSave)) { @@ -3597,7 +3595,7 @@ public class CheckUpPage extends JPanel { SwingUtilities.invokeLater(() -> { try { // Create media directory for this checkup if it doesn't exist - java.io.File mediaDir = new java.io.File("image/checkup_media", response.getCheckupId()); + java.io.File mediaDir = new java.io.File(LocalStorage.checkupMediaBaseDir, response.getCheckupId()); if (!mediaDir.exists()) { mediaDir.mkdirs(); } @@ -4426,7 +4424,7 @@ public class CheckUpPage extends JPanel { // Find the patient in the patientQueue if (checkupId == null) return -1; for (int i = 0; i < queueTableModel.getRowCount(); i++) { - if (checkupId.equals(queueTableModel.getValueAt(i, 0))) { + if (checkupId.equals(queueTableModel.getValueAt(i, 1))) { return i; } } @@ -4450,10 +4448,15 @@ public class CheckUpPage extends JPanel { queueTable.setRowSelectionInterval(rowToSelect, rowToSelect); queueTable.scrollRectToVisible(queueTable.getCellRect(rowToSelect, 0, true)); } else { - // The previously selected patient is no longer in the queue (e.g., status changed to "ĐÃ KHÁM") - // Clear the details panel to avoid confusion - clearPatientDetails(); - // Auto-select first row if available + JOptionPane.showMessageDialog( + mainFrame, + "Trạng thái của bệnh nhân bạn đang chọn đã thay đổi và không còn trong hàng chờ.\n" + + "Giao diện của bạn sẽ được làm mới.", + "Cập Nhật Trạng Thái Bệnh Nhân", + JOptionPane.INFORMATION_MESSAGE + ); + + clearPatientDetails(); // Now clear the details if (queueTable.getRowCount() > 0) { queueTable.setRowSelectionInterval(0, 0); } diff --git a/src/main/java/BsK/common/Error.java b/src/main/java/BsK/common/Error.java index 43164b5..f579245 100644 --- a/src/main/java/BsK/common/Error.java +++ b/src/main/java/BsK/common/Error.java @@ -5,7 +5,9 @@ public enum Error { UNKNOWN, USERNAME_NOT_FOUND, PASSWORD_INCORRECT, - USER_ALREADY_EXISTS,INVALID_CREDENTIALS, + USERNAME_EXISTS, + INVALID_CREDENTIALS, SQL_EXCEPTION, - ACCESS_DENIED + ACCESS_DENIED, + INVALID_INPUT } \ No newline at end of file diff --git a/src/main/java/BsK/common/packet/res/ErrorResponse.java b/src/main/java/BsK/common/packet/res/ErrorResponse.java index bd32144..239ae3c 100644 --- a/src/main/java/BsK/common/packet/res/ErrorResponse.java +++ b/src/main/java/BsK/common/packet/res/ErrorResponse.java @@ -27,12 +27,15 @@ public class ErrorResponse implements Packet { if (error.equals(Error.PASSWORD_INCORRECT)) { return "Mật khẩu không chính xác, vui lòng kiểm tra lại mật khẩu"; } - if (error.equals(Error.USER_ALREADY_EXISTS)) { - return "Tên đăng nhập đã tồn tại, vui lòng chọn tên đăng nhập khác"; - } if (error.equals(Error.INVALID_CREDENTIALS)) { return "Tên đăng nhập hoặc mật khẩu không chính xác, vui lòng kiểm tra lại"; } + if (error.equals(Error.INVALID_INPUT)) { + return "Dữ liệu nhập không hợp lệ, vui lòng kiểm tra lại"; + } + if (error.equals(Error.USERNAME_EXISTS)) { + return "Tên đăng nhập đã tồn tại, vui lòng chọn tên đăng nhập khác"; + } return error.toString(); } diff --git a/src/main/java/BsK/server/Server.java b/src/main/java/BsK/server/Server.java index 6058cd6..814e5de 100644 --- a/src/main/java/BsK/server/Server.java +++ b/src/main/java/BsK/server/Server.java @@ -1,5 +1,6 @@ package BsK.server; +import BsK.server.database.DatabaseManager; import BsK.server.network.handler.ServerHandler; import BsK.server.service.GoogleDriveServiceOAuth; import io.netty.bootstrap.ServerBootstrap; @@ -16,7 +17,6 @@ import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.stream.ChunkedWriteHandler; -import io.netty.handler.timeout.IdleStateHandler; import java.awt.Color; import java.io.File; import java.io.IOException; @@ -36,7 +36,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class Server { private static int PORT; - public static Connection connection; + // public static Connection connection; public static GoogleDriveServiceOAuth googleDriveService; private static boolean googleDriveEnabled = true; // Can be configured private static String googleDriveRootFolderName = "BSK_Clinic_Patient_Files"; // Default @@ -116,8 +116,8 @@ public class Server { log.info("Checkup media base dir: {}", checkupMediaBaseDir); // Directly use the provided database path - String dbPath = "database/BSK.db"; - connection = DriverManager.getConnection("jdbc:sqlite:" + dbPath); + //String dbPath = "database/BSK.db"; + //connection = DriverManager.getConnection("jdbc:sqlite:" + dbPath); // Initialize Google Drive service if enabled initializeGoogleDriveService(); @@ -251,13 +251,14 @@ public class Server { throw new UnsupportedOperationException("extractDatabaseFile is not used anymore. Database is read directly from the specified path."); } - public static Statement statement; + //public static Statement statement; static { try { - statement = connection.createStatement(); - } catch (SQLException e) { - throw new RuntimeException(e); + // KHỞI TẠO CONNECTION POOL + DatabaseManager.initialize(); + } catch (Exception e) { + throw new RuntimeException("Failed to initialize server", e); } } @@ -294,11 +295,10 @@ public class Server { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline() - .addLast(new IdleStateHandler(60 * 30, 0, 0, TimeUnit.SECONDS)) .addLast(new HttpServerCodec()) .addLast(new ChunkedWriteHandler()) - .addLast(new HttpObjectAggregator(50 * 1024 * 1024)) - .addLast(new WebSocketServerProtocolHandler("/", null, true, 50 * 1024 * 1024)) + .addLast(new HttpObjectAggregator(10 * 1024 * 1024)) + .addLast(new WebSocketServerProtocolHandler("/", null, true, 10 * 1024 * 1024)) .addLast(new ServerHandler()); } }); diff --git a/src/main/java/BsK/server/database/DatabaseManager.java b/src/main/java/BsK/server/database/DatabaseManager.java new file mode 100644 index 0000000..32a32ef --- /dev/null +++ b/src/main/java/BsK/server/database/DatabaseManager.java @@ -0,0 +1,50 @@ +package BsK.server.database; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import lombok.extern.slf4j.Slf4j; + +import java.sql.Connection; +import java.sql.SQLException; + +@Slf4j +public class DatabaseManager { + + private static HikariDataSource dataSource; + + public static void initialize() { + log.info("Initializing database connection pool..."); + HikariConfig config = new HikariConfig(); + + // Sử dụng đường dẫn tương đối đến file DB + String dbPath = "database/BSK.db"; + config.setJdbcUrl("jdbc:sqlite:" + dbPath); + + // Cấu hình cho SQLite + config.addDataSourceProperty("journal_mode", "WAL"); // Rất quan trọng để tăng khả năng ghi đồng thời + config.addDataSourceProperty("busy_timeout", "5000"); // Chờ 5 giây nếu DB bị lock + config.setConnectionTestQuery("SELECT 1"); + + // Cấu hình pool + config.setPoolName("BSK-Clinic-Pool"); + config.setMaximumPoolSize(10); // 10 kết nối đồng thời là quá đủ cho ứng dụng phòng khám + config.setMinimumIdle(2); + config.setConnectionTimeout(30000); // 30 giây + config.setIdleTimeout(600000); // 10 phút + config.setMaxLifetime(1800000); // 30 phút + + dataSource = new HikariDataSource(config); + log.info("Database connection pool initialized successfully."); + } + + public static Connection getConnection() throws SQLException { + return dataSource.getConnection(); + } + + public static void close() { + if (dataSource != null) { + dataSource.close(); + log.info("Database connection pool closed."); + } + } +} \ No newline at end of file diff --git a/src/main/java/BsK/server/network/entity/User.java b/src/main/java/BsK/server/network/entity/User.java index 81e3a3d..7f07b23 100644 --- a/src/main/java/BsK/server/network/entity/User.java +++ b/src/main/java/BsK/server/network/entity/User.java @@ -8,7 +8,9 @@ import lombok.extern.slf4j.Slf4j; import java.sql.ResultSet; import java.sql.SQLException; -import static BsK.server.Server.statement; +import BsK.server.database.DatabaseManager; +import java.sql.Connection; +import java.sql.PreparedStatement; @Slf4j @Data @@ -24,28 +26,52 @@ public class User { this.sessionId = sessionId; } + /** + * Xác thực người dùng. + * PHIÊN BẢN NÀY ĐÃ SỬA LỖI SQL INJECTION VÀ LỖI KỸ THUẬT, + * NHƯNG VẪN CÓ RỦI RO BẢO MẬT VÌ DÙNG MẬT KHẨU PLAINTEXT. + */ public void authenticate(String username, String password) { - try { - ResultSet rs = statement.executeQuery("select * from User where user_name = '" + username + "' and password = '" + password + "'"); - if (!rs.isBeforeFirst()) { - System.out.println("No data found in the User table."); - } else { - while(rs.next()) - { - log.info(rs.getString("user_name") + " logged in"); - role = Role.valueOf(rs.getString("role_name")); - userId = rs.getInt("user_id"); - // Update the client connection with the new role - Boolean deleted = rs.getBoolean("deleted"); - if (deleted) { - role = Role.GUEST; - } - SessionManager.updateUserRole(channel.id().asLongText(), role.toString(), userId); - } - } - } catch (SQLException e) { - throw new RuntimeException(e); - } + // Sử dụng PreparedStatement để chống SQL Injection + String sql = "SELECT user_id, role_name, deleted FROM User WHERE user_name = ? AND password = ?"; + + // Sử dụng try-with-resources để quản lý kết nối và tài nguyên an toàn + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setString(1, username); + stmt.setString(2, password); // Vẫn so sánh plaintext, cần được thay đổi + + try (ResultSet rs = stmt.executeQuery()) { + // Tên đăng nhập là duy nhất, chỉ nên có 1 kết quả. Dùng if thay vì while. + if (rs.next()) { + boolean isDeleted = rs.getBoolean("deleted"); + if (isDeleted) { + log.warn("Login attempt for deleted user: {}", username); + // Không xác thực tài khoản đã bị xóa + this.role = Role.GUEST; + this.userId = -1; + } else { + log.info("User '{}' logged in successfully.", username); + this.userId = rs.getInt("user_id"); + this.role = Role.valueOf(rs.getString("role_name")); + // Việc cập nhật session/channel nên được thực hiện ở ServerHandler + // sau khi kiểm tra trạng thái xác thực của đối tượng User này. + } + } else { + log.warn("Failed login attempt for username: {}", username); + // Không tìm thấy user hoặc sai mật khẩu + this.role = Role.GUEST; + this.userId = -1; + } + } + + } catch (SQLException e) { + log.error("Database error during authentication for user '{}'", username, e); + // Đảm bảo người dùng không được xác thực nếu có lỗi + this.role = Role.GUEST; + this.userId = -1; + } } public boolean isAuthenticated() { diff --git a/src/main/java/BsK/server/network/handler/ServerHandler.java b/src/main/java/BsK/server/network/handler/ServerHandler.java index 8268576..9e28657 100644 --- a/src/main/java/BsK/server/network/handler/ServerHandler.java +++ b/src/main/java/BsK/server/network/handler/ServerHandler.java @@ -1,6 +1,7 @@ package BsK.server.network.handler; import BsK.common.packet.Packet; +import BsK.server.database.DatabaseManager; import BsK.common.packet.PacketSerializer; import BsK.common.packet.req.*; import BsK.common.packet.res.*; @@ -49,8 +50,11 @@ import BsK.common.entity.Service; import BsK.common.entity.PatientHistory; import BsK.common.entity.Template; import BsK.common.util.text.TextUtils; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.concurrent.CompletableFuture; + -import static BsK.server.Server.statement; @Slf4j @@ -107,123 +111,130 @@ public class ServerHandler extends SimpleChannelInboundHandler resultList = new ArrayList<>(); - while (rs.next()) { - String checkupId = rs.getString("checkup_id"); - String checkupDate = rs.getString("checkup_date"); - long checkupDateLong = Long.parseLong(checkupDate); - Timestamp timestamp = new Timestamp(checkupDateLong); - Date date = new Date(timestamp.getTime()); // Needed to recode - SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); - checkupDate = sdf.format(date); - String customerLastName = rs.getString("customer_last_name"); - String customerFirstName = rs.getString("customer_first_name"); - String doctorFirstName = rs.getString("doctor_first_name"); - String doctorLastName = rs.getString("doctor_last_name"); - String suggestion = rs.getString("suggestion"); - String diagnosis = rs.getString("diagnosis"); - String notes = rs.getString("notes"); - String status = rs.getString("status"); - String customerId = rs.getString("customer_id"); - String customerNumber = rs.getString("customer_number"); - String customerAddress = rs.getString("customer_address"); - String customerWeight = rs.getString("customer_weight"); - String customerHeight = rs.getString("customer_height"); - String customerGender = rs.getString("customer_gender"); - String customerDob = rs.getString("customer_dob"); - String checkupType = rs.getString("checkup_type"); - String conclusion = rs.getString("conclusion"); - String reCheckupDate = rs.getString("reCheckupDate"); - String cccdDdcn = rs.getString("cccd_ddcn"); - String heartBeat = rs.getString("heart_beat"); - String bloodPressure = rs.getString("blood_pressure"); - String driveUrl = rs.getString("drive_url"); - String doctorUltrasoundId = rs.getString("doctor_ultrasound_id"); - String queueNumber = String.format("%02d", rs.getInt("queue_number")); - if (driveUrl == null) { - driveUrl = ""; - } - String result = String.join("|", checkupId, - checkupDate, customerLastName, customerFirstName, - doctorLastName + " " + doctorFirstName, suggestion, - diagnosis, notes, status, customerId, customerNumber, customerAddress, customerWeight, customerHeight, - customerGender, customerDob, checkupType, conclusion, reCheckupDate, cccdDdcn, heartBeat, bloodPressure, - driveUrl, doctorUltrasoundId, queueNumber - ); - - resultList.add(result); + + String sql = """ + SELECT + a.checkup_id, a.checkup_date, c.customer_last_name, c.customer_first_name, + d.doctor_first_name, d.doctor_last_name, a.suggestion, a.diagnosis, a.notes, + a.status, a.customer_id, c.customer_number, c.customer_address, + a.customer_weight, a.customer_height, c.customer_gender, c.customer_dob, + a.checkup_type, a.conclusion, a.reCheckupDate, c.cccd_ddcn, a.heart_beat, + a.blood_pressure, c.drive_url, a.doctor_ultrasound_id, a.queue_number + FROM + checkup AS a + JOIN + customer AS c ON a.customer_id = c.customer_id + JOIN + Doctor D ON a.doctor_id = D.doctor_id + WHERE + date(a.checkup_date / 1000, 'unixepoch', '+7 hours') = date('now', '+7 hours')"""; + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql); + ResultSet rs = stmt.executeQuery()) { + + if (!rs.isBeforeFirst()) { + log.warn("No data found in the checkup table for today."); + // Gửi về một mảng rỗng thay vì không gửi gì + UserUtil.sendPacket(currentUser.getSessionId(), new GetCheckUpQueueResponse(new String[0][0])); + } else { + // Phần code xử lý logic bên trong không thay đổi + ArrayList resultList = new ArrayList<>(); + while (rs.next()) { + String checkupId = rs.getString("checkup_id"); + String checkupDate = rs.getString("checkup_date"); + long checkupDateLong = Long.parseLong(checkupDate); + Timestamp timestamp = new Timestamp(checkupDateLong); + Date date = new Date(timestamp.getTime()); + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + checkupDate = sdf.format(date); + String customerLastName = rs.getString("customer_last_name"); + String customerFirstName = rs.getString("customer_first_name"); + String doctorFirstName = rs.getString("doctor_first_name"); + String doctorLastName = rs.getString("doctor_last_name"); + String suggestion = rs.getString("suggestion"); + String diagnosis = rs.getString("diagnosis"); + String notes = rs.getString("notes"); + String status = rs.getString("status"); + String customerId = rs.getString("customer_id"); + String customerNumber = rs.getString("customer_number"); + String customerAddress = rs.getString("customer_address"); + String customerWeight = rs.getString("customer_weight"); + String customerHeight = rs.getString("customer_height"); + String customerGender = rs.getString("customer_gender"); + String customerDob = rs.getString("customer_dob"); + String checkupType = rs.getString("checkup_type"); + String conclusion = rs.getString("conclusion"); + String reCheckupDate = rs.getString("reCheckupDate"); + String cccdDdcn = rs.getString("cccd_ddcn"); + String heartBeat = rs.getString("heart_beat"); + String bloodPressure = rs.getString("blood_pressure"); + String driveUrl = rs.getString("drive_url"); + String doctorUltrasoundId = rs.getString("doctor_ultrasound_id"); + String queueNumber = String.format("%02d", rs.getInt("queue_number")); + if (driveUrl == null) { + driveUrl = ""; + } + String result = String.join("|", checkupId, + checkupDate, customerLastName, customerFirstName, + doctorLastName + " " + doctorFirstName, suggestion, + diagnosis, notes, status, customerId, customerNumber, customerAddress, customerWeight, customerHeight, + customerGender, customerDob, checkupType, conclusion, reCheckupDate, cccdDdcn, heartBeat, bloodPressure, + driveUrl, doctorUltrasoundId, queueNumber + ); + resultList.add(result); + } + + String[] resultString = resultList.toArray(new String[0]); + String[][] resultArray = new String[resultString.length][]; + for (int i = 0; i < resultString.length; i++) { + resultArray[i] = resultString[i].split("\\|"); + } + UserUtil.sendPacket(currentUser.getSessionId(), new GetCheckUpQueueResponse(resultArray)); } - - String[] resultString = resultList.toArray(new String[0]); - String[][] resultArray = new String[resultString.length][]; - for (int i = 0; i < resultString.length; i++) { - resultArray[i] = resultString[i].split("\\|"); - } - UserUtil.sendPacket(currentUser.getSessionId(), new GetCheckUpQueueResponse(resultArray)); - } } catch (SQLException e) { - throw new RuntimeException(e); + + log.error("Error processing GetCheckUpQueueRequest", e); + UserUtil.sendPacket(currentUser.getSessionId(), new ErrorResponse(Error.SQL_EXCEPTION)); } - } + } if (packet instanceof GetCheckUpQueueUpdateRequest) { log.debug("Received GetCheckUpQueueUpdateRequest"); broadcastQueueUpdate(); } - // Get general doctor info if (packet instanceof GetDoctorGeneralInfo) { log.debug("Received GetDoctorGeneralInfo"); - try { - ResultSet rs = statement.executeQuery( - "select concat(doctor_last_name, ' ', doctor_first_name), doctor_id from Doctor" - ); + String sql = "SELECT doctor_last_name || ' ' || doctor_first_name, doctor_id FROM Doctor"; + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql); + ResultSet rs = stmt.executeQuery()) { + if (!rs.isBeforeFirst()) { - System.out.println("No data found in the doctor table."); + log.warn("No data found in the doctor table."); + UserUtil.sendPacket(currentUser.getSessionId(), new GetDoctorGeneralInfoResponse(new String[0][0])); } else { - ArrayList resultList = new ArrayList<>(); - while (rs.next()) { - String doctorName = rs.getString(1); - String doctorId = rs.getString(2); - resultList.add(doctorName + "|" + doctorId); - } - String[] resultString = resultList.toArray(new String[0]); - String[][] resultArray = new String[resultString.length][]; - for (int i = 0; i < resultString.length; i++) { - resultArray[i] = resultString[i].split("\\|"); - } - UserUtil.sendPacket(currentUser.getSessionId(), new GetDoctorGeneralInfoResponse(resultArray)); - log.info("Send response to client GetDoctorGeneralInfo"); - } - } - catch (SQLException e) { - throw new RuntimeException(e); + ArrayList resultList = new ArrayList<>(); + while (rs.next()) { + String doctorName = rs.getString(1); // Lấy theo chỉ số cột + String doctorId = rs.getString(2); // Lấy theo chỉ số cột + resultList.add(doctorName + "|" + doctorId); + } + + String[] resultString = resultList.toArray(new String[0]); + String[][] resultArray = new String[resultString.length][]; + for (int i = 0; i < resultString.length; i++) { + resultArray[i] = resultString[i].split("\\|"); + } + + UserUtil.sendPacket(currentUser.getSessionId(), new GetDoctorGeneralInfoResponse(resultArray)); + log.info("Sent response to client GetDoctorGeneralInfo"); + } + } catch (SQLException e) { + log.error("Error processing GetDoctorGeneralInfo", e); + UserUtil.sendPacket(currentUser.getSessionId(), new ErrorResponse(Error.SQL_EXCEPTION)); } } @@ -247,11 +258,13 @@ public class ServerHandler extends SimpleChannelInboundHandler resultList = new ArrayList<>(); + while (rs.next()) { + String customerId = rs.getString("customer_id"); + String customerLastName = rs.getString("customer_last_name"); + String customerFirstName = rs.getString("customer_first_name"); + String customerDob = rs.getString("customer_dob"); + String year = Integer.toString(DateUtils.extractYearFromTimestamp(customerDob)); + String customerNumber = rs.getString("customer_number"); + String customerAddress = rs.getString("customer_address"); + String customerGender = rs.getString("customer_gender"); + String cccdDdcn = rs.getString("cccd_ddcn"); + + String result = String.join("|", customerId, customerLastName + " " + customerFirstName, + year, customerNumber, customerAddress, customerGender, customerDob, cccdDdcn); + resultList.add(result); + } + + String[] resultString = resultList.toArray(new String[0]); + String[][] resultArray = new String[resultString.length][]; + for (int i = 0; i < resultString.length; i++) { + resultArray[i] = resultString[i].split("\\|"); + } + + + UserUtil.sendPacket(currentUser.getSessionId(), new GetRecentPatientResponse(resultArray, totalCount, currentPage, totalPages, pageSize)); + } } - } - - // Add ORDER BY - queryBuilder.append(" ORDER BY customer_id DESC"); - - // Count total records for pagination - String countQuery = queryBuilder.toString().replace("SELECT customer_id, customer_last_name, customer_first_name, customer_dob, customer_number, customer_address, customer_gender, cccd_ddcn", "SELECT COUNT(*)"); - PreparedStatement countStmt = Server.connection.prepareStatement(countQuery); - - int paramIndex = 1; - if (hasNameSearch) { - String searchName = "%" + getRecentPatientRequest.getSearchName().toLowerCase().trim() + "%"; - countStmt.setString(paramIndex++, searchName); - countStmt.setString(paramIndex++, searchName); - countStmt.setString(paramIndex++, searchName); - } - if (hasPhoneSearch) { - String searchPhone = "%" + getRecentPatientRequest.getSearchPhone().trim() + "%"; - countStmt.setString(paramIndex++, searchPhone); - } - - ResultSet countRs = countStmt.executeQuery(); - int totalCount = 0; - if (countRs.next()) { - totalCount = countRs.getInt(1); - } - countRs.close(); - countStmt.close(); - - // Calculate pagination - int pageSize = getRecentPatientRequest.getPageSize(); - int currentPage = getRecentPatientRequest.getPage(); - int totalPages = (int) Math.ceil((double) totalCount / pageSize); - int offset = (currentPage - 1) * pageSize; - - // Add LIMIT and OFFSET for pagination - queryBuilder.append(" LIMIT ? OFFSET ?"); - - PreparedStatement stmt = Server.connection.prepareStatement(queryBuilder.toString()); - - // Set parameters for the main query - paramIndex = 1; - if (hasNameSearch) { - String searchName = "%" + getRecentPatientRequest.getSearchName().toLowerCase().trim() + "%"; - stmt.setString(paramIndex++, searchName); - stmt.setString(paramIndex++, searchName); - stmt.setString(paramIndex++, searchName); - } - if (hasPhoneSearch) { - String searchPhone = "%" + getRecentPatientRequest.getSearchPhone().trim() + "%"; - stmt.setString(paramIndex++, searchPhone); - } - stmt.setInt(paramIndex++, pageSize); - stmt.setInt(paramIndex++, offset); - - ResultSet rs = stmt.executeQuery(); - - if (!rs.isBeforeFirst()) { - log.debug("No data found in the customer table for the given search criteria."); - UserUtil.sendPacket(currentUser.getSessionId(), new GetRecentPatientResponse(new String[0][], totalCount, currentPage, totalPages, pageSize)); - } else { - ArrayList resultList = new ArrayList<>(); - while (rs.next()) { - String customerId = rs.getString("customer_id"); - String customerLastName = rs.getString("customer_last_name"); - String customerFirstName = rs.getString("customer_first_name"); - String customerDob = rs.getString("customer_dob"); - String year = Integer.toString(DateUtils.extractYearFromTimestamp(customerDob)); - String customerNumber = rs.getString("customer_number"); - String customerAddress = rs.getString("customer_address"); - String customerGender = rs.getString("customer_gender"); - String cccdDdcn = rs.getString("cccd_ddcn"); - - String result = String.join("|", customerId, customerLastName + " " + customerFirstName, - year, customerNumber, customerAddress, customerGender, customerDob, cccdDdcn); - resultList.add(result); - } - - String[] resultString = resultList.toArray(new String[0]); - String[][] resultArray = new String[resultString.length][]; - for (int i = 0; i < resultString.length; i++) { - resultArray[i] = resultString[i].split("\\|"); - } - - UserUtil.sendPacket(currentUser.getSessionId(), new GetRecentPatientResponse(resultArray, totalCount, currentPage, totalPages, pageSize)); - } - - rs.close(); - stmt.close(); } catch (SQLException e) { - log.error("Error processing GetRecentPatientRequest", e); - throw new RuntimeException(e); + // Xử lý lỗi an toàn + log.error("Error processing GetRecentPatientRequest", e); + UserUtil.sendPacket(currentUser.getSessionId(), new ErrorResponse(Error.SQL_EXCEPTION)); } } if (packet instanceof GetProvinceRequest getProvinceRequest) { log.debug("Received GetProvinceRequest"); - try { - ResultSet rs = statement.executeQuery( - "select provinces.code, provinces.name\n" + - "from provinces" + - " order by provinces.name" - ); - - if (!rs.isBeforeFirst()) { - System.out.println("No data found in the province table."); - } else { + + String sql = "SELECT code, name FROM provinces ORDER BY name"; + + // SỬA ĐỔI: Sử dụng try-with-resources để quản lý kết nối và tài nguyên + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql); + ResultSet rs = stmt.executeQuery()) { + ArrayList resultList = new ArrayList<>(); HashMap provinceIdMap = new HashMap<>(); + + // Luôn thêm giá trị mặc định cho ComboBox ở client resultList.add("Tỉnh/Thành phố"); + + // Vòng lặp while sẽ tự xử lý trường hợp không có dữ liệu while (rs.next()) { - String provinceId = rs.getString("code"); - String provinceName = rs.getString("name"); - provinceIdMap.put(provinceName, provinceId); - resultList.add(provinceName); + String provinceId = rs.getString("code"); + String provinceName = rs.getString("name"); + provinceIdMap.put(provinceName, provinceId); + resultList.add(provinceName); } - + + if (resultList.size() <= 1) { + log.warn("No data found in the province table."); + } + String[] resultString = resultList.toArray(new String[0]); UserUtil.sendPacket(currentUser.getSessionId(), new GetProvinceResponse(resultString, provinceIdMap)); - } + } catch (SQLException e) { - throw new RuntimeException(e); + // SỬA ĐỔI: Xử lý lỗi an toàn + log.error("Error processing GetProvinceRequest", e); + UserUtil.sendPacket(currentUser.getSessionId(), new ErrorResponse(Error.SQL_EXCEPTION)); } } if (packet instanceof GetWardRequest getWardRequest) { log.debug("Received GetWardRequest for province {}", getWardRequest.getProvinceId()); - String sql = "SELECT wards.name FROM wards WHERE province_code = ? ORDER BY wards.name"; - - // Use try-with-resources to automatically close resources - try (PreparedStatement preparedStatement = Server.connection.prepareStatement(sql)) { + String sql = "SELECT name FROM wards WHERE province_code = ? ORDER BY name"; + + // SỬA ĐỔI: Sử dụng try-with-resources cho Connection và đổi 'Server.connection' thành 'conn' + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + preparedStatement.setString(1, getWardRequest.getProvinceId()); + try (ResultSet rs = preparedStatement.executeQuery()) { - if (!rs.isBeforeFirst()) { - System.out.println("No data found in the ward table."); - // Send empty response - UserUtil.sendPacket(currentUser.getSessionId(), new GetWardResponse(new String[]{"Phường/Xã"})); - } else { - ArrayList resultList = new ArrayList<>(); - resultList.add("Phường/Xã"); - while (rs.next()) { - resultList.add(rs.getString("name")); - } - String[] resultString = resultList.toArray(new String[0]); - UserUtil.sendPacket(currentUser.getSessionId(), new GetWardResponse(resultString)); + ArrayList resultList = new ArrayList<>(); + // Luôn thêm giá trị mặc định cho ComboBox ở client + resultList.add("Phường/Xã"); + + // Vòng lặp while sẽ tự xử lý trường hợp không có dữ liệu + while (rs.next()) { + resultList.add(rs.getString("name")); } + + if (resultList.size() <= 1) { + log.warn("No data found in the ward table for province_code: {}", getWardRequest.getProvinceId()); + } + + String[] resultString = resultList.toArray(new String[0]); + UserUtil.sendPacket(currentUser.getSessionId(), new GetWardResponse(resultString)); } } catch (SQLException e) { - log.error("Error fetching wards for province {}", getWardRequest.getProvinceId(), e); - throw new RuntimeException(e); + // SỬA ĐỔI: Xử lý lỗi an toàn + log.error("Error fetching wards for province {}: {}", getWardRequest.getProvinceId(), e.getMessage()); + UserUtil.sendPacket(currentUser.getSessionId(), new ErrorResponse(Error.SQL_EXCEPTION)); } } if (packet instanceof AddPatientRequest addPatientRequest) { log.debug("Received AddPatientRequest"); - try { - // Disable auto-commit for transaction - Server.connection.setAutoCommit(false); - - PreparedStatement preparedStatement = Server.connection.prepareStatement( - "INSERT INTO Customer (customer_last_name, customer_first_name, customer_dob, customer_number, customer_address, customer_gender, cccd_ddcn) VALUES (?, ?, ?, ?, ?, ?, ?)", - PreparedStatement.RETURN_GENERATED_KEYS // Use this instead of separate query - ); - preparedStatement.setString(1, addPatientRequest.getPatientLastName()); - preparedStatement.setString(2, addPatientRequest.getPatientFirstName()); - preparedStatement.setLong(3, addPatientRequest.getPatientDob()); - preparedStatement.setString(4, addPatientRequest.getPatientPhone()); - preparedStatement.setString(5, addPatientRequest.getPatientAddress()); - preparedStatement.setString(6, addPatientRequest.getPatientGender()); - preparedStatement.setString(7, addPatientRequest.getPatientCccdDdcn()); - preparedStatement.executeUpdate(); - - // Get the generated key safely - int customerId = 0; - try (ResultSet generatedKeys = preparedStatement.getGeneratedKeys()) { - if (generatedKeys.next()) { - customerId = generatedKeys.getInt(1); - } else { - throw new SQLException("Creating patient failed, no ID obtained."); + + String sql = "INSERT INTO Customer (customer_last_name, customer_first_name, customer_dob, customer_number, customer_address, customer_gender, cccd_ddcn) VALUES (?, ?, ?, ?, ?, ?, ?)"; + + // SỬA ĐỔI: Bắt đầu bằng try-with-resources để lấy Connection từ pool + try (Connection conn = DatabaseManager.getConnection()) { + + // 1. Bắt đầu transaction trên kết nối vừa mượn + conn.setAutoCommit(false); + + try (PreparedStatement preparedStatement = conn.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS)) { + + preparedStatement.setString(1, addPatientRequest.getPatientLastName()); + preparedStatement.setString(2, addPatientRequest.getPatientFirstName()); + preparedStatement.setLong(3, addPatientRequest.getPatientDob()); + preparedStatement.setString(4, addPatientRequest.getPatientPhone()); + preparedStatement.setString(5, addPatientRequest.getPatientAddress()); + preparedStatement.setString(6, addPatientRequest.getPatientGender()); + preparedStatement.setString(7, addPatientRequest.getPatientCccdDdcn()); + preparedStatement.executeUpdate(); + + // Lấy customerId vừa được tạo ra + int customerId = 0; + try (ResultSet generatedKeys = preparedStatement.getGeneratedKeys()) { + if (generatedKeys.next()) { + customerId = generatedKeys.getInt(1); + } else { + // Nếu không lấy được ID, ném lỗi để kích hoạt rollback + throw new SQLException("Creating patient failed, no ID obtained."); + } + } + + // 2. Nếu mọi thứ thành công, commit transaction + conn.commit(); + + // 3. Gửi phản hồi thành công và thực hiện các tác vụ bất đồng bộ + UserUtil.sendPacket(currentUser.getSessionId(), new AddPatientResponse(true, customerId, "Thêm bệnh nhân thành công")); + createPatientGoogleDriveFolderAsync(customerId, addPatientRequest.getPatientLastName(), addPatientRequest.getPatientFirstName()); + + } catch (SQLException e) { + // 4. Nếu có bất kỳ lỗi nào xảy ra trong khối try bên trong, rollback lại + log.error("Error during AddPatient transaction, rolling back.", e); + conn.rollback(); + + // Gửi phản hồi lỗi về client + String errorMessage = e.getMessage(); + UserUtil.sendPacket(currentUser.getSessionId(), new AddPatientResponse(false, -1, "Lỗi: " + errorMessage)); + } finally { + // 5. Rất quan trọng: Luôn trả lại trạng thái auto-commit về true + // trước khi kết nối được trả về pool bởi try-with-resources + conn.setAutoCommit(true); } - } - - // Commit the transaction - Server.connection.commit(); - - // Close resources - preparedStatement.close(); - - // Send immediate response to client - UserUtil.sendPacket(currentUser.getSessionId(), new AddPatientResponse(true, customerId, "Thêm bệnh nhân thành công")); - - // Create Google Drive folder asynchronously (don't block the response) - createPatientGoogleDriveFolderAsync(customerId, addPatientRequest.getPatientLastName(), addPatientRequest.getPatientFirstName()); + } catch (SQLException e) { - try { - // Rollback on error - Server.connection.rollback(); - } catch (SQLException rollbackEx) { - log.error("Error during rollback", rollbackEx); - } - String errorMessage = e.getMessage(); - UserUtil.sendPacket(currentUser.getSessionId(), new AddPatientResponse(false, -1, "Lỗi: " + errorMessage)); - log.error("Error adding patient", e); - } finally { - try { - // Reset auto-commit - Server.connection.setAutoCommit(true); - } catch (SQLException e) { - log.error("Error resetting auto-commit", e); - } + // Khối catch này chỉ bắt lỗi khi không thể lấy được kết nối từ pool + log.error("Failed to get DB connection for AddPatientRequest", e); + UserUtil.sendPacket(currentUser.getSessionId(), new AddPatientResponse(false, -1, "Lỗi: Không thể kết nối CSDL.")); } } if (packet instanceof AddCheckupRequest addCheckupRequest) { log.debug("Received AddCheckupRequest to add for customer {}", addCheckupRequest.getCustomerId()); - - // Khai báo các biến ID ở ngoài khối try để có thể sử dụng sau này - int generatedCheckupId = 0; - int queueNumber = 0; - try { - // Bắt đầu một transaction - Server.connection.setAutoCommit(false); + // --- SỬA ĐỔI QUAN TRỌNG: Sử dụng một kết nối riêng từ pool cho toàn bộ giao dịch --- + // try-with-resources sẽ đảm bảo kết nối được trả về pool ngay cả khi có lỗi. + try (Connection conn = DatabaseManager.getConnection()) { + int generatedCheckupId = 0; + int queueNumber = 0; - // --- 1. LẤY SỐ THỨ TỰ (QUEUE NUMBER) MỘT CÁCH AN TOÀN --- - // Sử dụng UPSERT (INSERT ON CONFLICT) để đảm bảo thao tác là nguyên tử - String queueSql = "INSERT INTO DailyQueueCounter (date, current_count) " + - "VALUES (date('now', 'localtime'), 1) " + - "ON CONFLICT(date) DO UPDATE SET current_count = current_count + 1 " + - "RETURNING current_count"; - - try (PreparedStatement queueStmt = Server.connection.prepareStatement(queueSql)) { - ResultSet rs = queueStmt.executeQuery(); - if (rs.next()) { - queueNumber = rs.getInt(1); - } else { - throw new SQLException("Failed to get or update queue number."); - } - } + try { + // 1. Bắt đầu một transaction trên kết nối CỤC BỘ (conn) này + conn.setAutoCommit(false); - // --- 2. CHÈN VÀO BẢNG CHECKUP VÀ LẤY LẠI CHECKUP_ID --- - String checkupSql = "INSERT INTO Checkup (customer_id, doctor_id, checkup_type, status, queue_number) VALUES (?, ?, ?, ?, ?)"; - try (PreparedStatement checkupStmt = Server.connection.prepareStatement(checkupSql, PreparedStatement.RETURN_GENERATED_KEYS)) { - checkupStmt.setInt(1, addCheckupRequest.getCustomerId()); - checkupStmt.setInt(2, addCheckupRequest.getDoctorId()); - checkupStmt.setString(3, addCheckupRequest.getCheckupType()); - checkupStmt.setString(4, addCheckupRequest.getStatus()); - checkupStmt.setInt(5, queueNumber); - checkupStmt.executeUpdate(); - - // Lấy checkup_id vừa được tạo ra một cách an toàn - try (ResultSet generatedKeys = checkupStmt.getGeneratedKeys()) { - if (generatedKeys.next()) { - generatedCheckupId = generatedKeys.getInt(1); + // 2. LẤY SỐ THỨ TỰ (QUEUE NUMBER) + // Sử dụng UPSERT để đảm bảo thao tác là nguyên tử + String queueSql = """ + INSERT INTO DailyQueueCounter (date, current_count) + VALUES (date('now', 'localtime'), 1) + ON CONFLICT(date) DO UPDATE SET current_count = current_count + 1 + RETURNING current_count + """; + + try (PreparedStatement queueStmt = conn.prepareStatement(queueSql); + ResultSet rs = queueStmt.executeQuery()) { + if (rs.next()) { + queueNumber = rs.getInt(1); } else { - throw new SQLException("Creating checkup failed, no ID obtained."); + throw new SQLException("Failed to get or update queue number."); } } - } - // --- 3. CHÈN VÀO BẢNG MEDICINEORDER VÀ LẤY LẠI PRESCRIPTION_ID --- - int generatedPrescriptionId = 0; - String medOrderSql = "INSERT INTO MedicineOrder (checkup_id, customer_id, processed_by) VALUES (?, ?, ?)"; - try (PreparedStatement medOrderStmt = Server.connection.prepareStatement(medOrderSql, PreparedStatement.RETURN_GENERATED_KEYS)) { - medOrderStmt.setInt(1, generatedCheckupId); // Sử dụng ID đã lấy được - medOrderStmt.setInt(2, addCheckupRequest.getCustomerId()); - medOrderStmt.setInt(3, addCheckupRequest.getProcessedById()); - medOrderStmt.executeUpdate(); + // 3. CHÈN VÀO BẢNG CHECKUP VÀ LẤY LẠI CHECKUP_ID + String checkupSql = "INSERT INTO Checkup (customer_id, doctor_id, checkup_type, status, queue_number) VALUES (?, ?, ?, ?, ?)"; + try (PreparedStatement checkupStmt = conn.prepareStatement(checkupSql, PreparedStatement.RETURN_GENERATED_KEYS)) { + checkupStmt.setInt(1, addCheckupRequest.getCustomerId()); + checkupStmt.setInt(2, addCheckupRequest.getDoctorId()); + checkupStmt.setString(3, addCheckupRequest.getCheckupType()); + checkupStmt.setString(4, addCheckupRequest.getStatus()); + checkupStmt.setInt(5, queueNumber); + checkupStmt.executeUpdate(); - // Lấy prescription_id vừa được tạo ra một cách an toàn - try (ResultSet generatedKeys = medOrderStmt.getGeneratedKeys()) { - if (generatedKeys.next()) { - generatedPrescriptionId = generatedKeys.getInt(1); - } else { - throw new SQLException("Creating medicine order failed, no ID obtained."); + try (ResultSet generatedKeys = checkupStmt.getGeneratedKeys()) { + if (generatedKeys.next()) { + generatedCheckupId = generatedKeys.getInt(1); + } else { + throw new SQLException("Creating checkup failed, no ID obtained."); + } } } + + // 4. CHÈN VÀO BẢNG MEDICINEORDER VÀ LẤY LẠI PRESCRIPTION_ID + int generatedPrescriptionId = 0; + String medOrderSql = "INSERT INTO MedicineOrder (checkup_id, customer_id, processed_by) VALUES (?, ?, ?)"; + try (PreparedStatement medOrderStmt = conn.prepareStatement(medOrderSql, PreparedStatement.RETURN_GENERATED_KEYS)) { + medOrderStmt.setInt(1, generatedCheckupId); + medOrderStmt.setInt(2, addCheckupRequest.getCustomerId()); + medOrderStmt.setInt(3, addCheckupRequest.getProcessedById()); + medOrderStmt.executeUpdate(); + + try (ResultSet generatedKeys = medOrderStmt.getGeneratedKeys()) { + if (generatedKeys.next()) { + generatedPrescriptionId = generatedKeys.getInt(1); + } else { + throw new SQLException("Creating medicine order failed, no ID obtained."); + } + } + } + + // 5. CẬP NHẬT BẢNG CHECKUP VỚI PRESCRIPTION_ID + String updateSql = "UPDATE Checkup SET prescription_id = ? WHERE checkup_id = ?"; + try (PreparedStatement updateStmt = conn.prepareStatement(updateSql)) { + updateStmt.setInt(1, generatedPrescriptionId); + updateStmt.setInt(2, generatedCheckupId); + updateStmt.executeUpdate(); + } + + // 6. Nếu tất cả các bước thành công, commit transaction + conn.commit(); + + // Gửi phản hồi thành công và broadcast cập nhật + UserUtil.sendPacket(currentUser.getSessionId(), new AddCheckupResponse(true, "Thêm bệnh nhân thành công", queueNumber)); + broadcastQueueUpdate(); + + // Tạo thư mục Google Drive bất đồng bộ + createCheckupGoogleDriveFolderAsync(generatedCheckupId, addCheckupRequest.getCustomerId()); + + } catch (SQLException e) { + // Nếu có bất kỳ lỗi nào trong khối try ở trên, rollback transaction + log.error("SQLException during AddCheckupRequest, rolling back transaction.", e); + conn.rollback(); // Quan trọng: rollback trên kết nối cục bộ + + // Gửi phản hồi lỗi về client + String errorMessage = e.getMessage(); + UserUtil.sendPacket(currentUser.getSessionId(), new AddCheckupResponse(false, "Lỗi Server: " + errorMessage, -1)); } - - // --- 4. CẬP NHẬT BẢNG CHECKUP VỚI PRESCRIPTION_ID --- - String updateSql = "UPDATE Checkup SET prescription_id = ? WHERE checkup_id = ?"; - try (PreparedStatement updateStmt = Server.connection.prepareStatement(updateSql)) { - updateStmt.setInt(1, generatedPrescriptionId); // Sử dụng ID đã lấy được - updateStmt.setInt(2, generatedCheckupId); // Sử dụng ID đã lấy được - updateStmt.executeUpdate(); - } - - // Nếu tất cả các bước thành công, commit transaction - Server.connection.commit(); - - // Gửi phản hồi thành công về cho client - UserUtil.sendPacket(currentUser.getSessionId(), new AddCheckupResponse(true, "Thêm bệnh nhân thành công", queueNumber)); - - // Broadcast cập nhật hàng chờ cho các client khác - broadcastQueueUpdate(); - - // Tạo thư mục Google Drive bất đồng bộ (không làm chậm phản hồi) - // generatedCheckupId bây giờ đã có giá trị chính xác - createCheckupGoogleDriveFolderAsync(generatedCheckupId, addCheckupRequest.getCustomerId()); + // KHÔNG CẦN khối `finally` để `setAutoCommit(true)`. + // HikariCP sẽ tự động reset trạng thái của kết nối khi nó được trả về pool. } catch (SQLException e) { - log.error("SQLException during AddCheckupRequest, rolling back transaction.", e); - try { - // Rollback nếu có lỗi - Server.connection.rollback(); - } catch (SQLException rollbackEx) { - log.error("Error during rollback", rollbackEx); - } - String errorMessage = e.getMessage(); - UserUtil.sendPacket(currentUser.getSessionId(), new AddCheckupResponse(false, "Lỗi Server: " + errorMessage, -1)); - // Không cần throw RuntimeException nữa để server không bị sập - - } finally { - try { - // Luôn luôn reset auto-commit về true - Server.connection.setAutoCommit(true); - } catch (SQLException e) { - log.error("Error resetting auto-commit", e); - } + // Lỗi này xảy ra nếu không thể lấy kết nối từ pool, hoặc có lỗi nghiêm trọng khi rollback + log.error("Critical error during database transaction management for AddCheckupRequest.", e); + UserUtil.sendPacket(currentUser.getSessionId(), new AddCheckupResponse(false, "Lỗi hệ thống: Không thể xử lý giao dịch CSDL.", -1)); } - } + } if (packet instanceof CallPatientRequest callPatientRequest) { log.debug("Received CallPatientRequest to call patient checkup_id: {}", callPatientRequest.getPatientId()); @@ -698,220 +728,171 @@ public class ServerHandler extends SimpleChannelInboundHandler 0) { - try (PreparedStatement medicineOrderStmt = conn.prepareStatement(medicineOrderSql, PreparedStatement.RETURN_GENERATED_KEYS)) { - double totalMedicineAmount = 0; - for (String[] medicine : saveCheckupRequest.getMedicinePrescription()) { - if (medicine.length > 8) { - totalMedicineAmount += Double.parseDouble(medicine[8]); - } - } - - medicineOrderStmt.setInt(1, saveCheckupRequest.getCheckupId()); - medicineOrderStmt.setInt(2, saveCheckupRequest.getCustomerId()); - medicineOrderStmt.setDouble(3, totalMedicineAmount); - medicineOrderStmt.setString(4, ""); // payment_method - medicineOrderStmt.setString(5, "Pending"); // status - medicineOrderStmt.setString(6, "Unpaid"); // payment_status - medicineOrderStmt.setString(7, ""); // processed_by - medicineOrderStmt.setString(8, ""); // notes - medicineOrderStmt.executeUpdate(); - - // B. Retrieve the generated key - try (ResultSet generatedKeys = medicineOrderStmt.getGeneratedKeys()) { - if (generatedKeys.next()) { - newPrescriptionId = generatedKeys.getInt(1); - log.info("New prescription_id generated: {}", newPrescriptionId); - } else { - throw new SQLException("Creating medicine order failed, no ID obtained."); - } - } - } - + // --- SỬA ĐỔI QUAN TRỌNG: Lấy một kết nối riêng từ pool cho toàn bộ giao dịch --- + try (Connection conn = DatabaseManager.getConnection()) { + try { + // 1. Bắt đầu transaction trên kết nối CỤC BỘ (conn) + conn.setAutoCommit(false); - // C. Insert OrderItems using the new prescription_id - String orderItemSql = """ - INSERT INTO OrderItem ( - prescription_id, med_id, quantity_ordered, dosage, duration, - price_per_unit, total_price, checkup_id, notes - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + // 2. CẬP NHẬT THÔNG TIN KHÁCH HÀNG (CUSTOMER) + String customerSql = """ + INSERT INTO Customer ( + customer_id, customer_first_name, customer_last_name, customer_dob, + customer_gender, customer_address, customer_number, cccd_ddcn + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(customer_id) DO UPDATE SET + customer_first_name = excluded.customer_first_name, + customer_last_name = excluded.customer_last_name, + customer_dob = excluded.customer_dob, + customer_gender = excluded.customer_gender, + customer_address = excluded.customer_address, + customer_number = excluded.customer_number, + cccd_ddcn = excluded.cccd_ddcn """; - try (PreparedStatement orderItemStmt = conn.prepareStatement(orderItemSql)) { - for (String[] medicine : saveCheckupRequest.getMedicinePrescription()) { - if (medicine.length >= 10) { - orderItemStmt.setInt(1, newPrescriptionId); // Use the new ID - orderItemStmt.setString(2, medicine[0]); // med_id - orderItemStmt.setString(3, medicine[2]); // quantity - String dosage = String.format("Sáng %s, Trưa %s, Chiều %s", medicine[4], medicine[5], medicine[6]); - orderItemStmt.setString(4, dosage); - orderItemStmt.setString(5, ""); // duration - orderItemStmt.setString(6, medicine[7]); // unit_price - orderItemStmt.setString(7, medicine[8]); // total_price - orderItemStmt.setInt(8, saveCheckupRequest.getCheckupId()); - orderItemStmt.setString(9, medicine[9]); // notes - orderItemStmt.addBatch(); + try (PreparedStatement customerStmt = conn.prepareStatement(customerSql)) { + customerStmt.setInt(1, saveCheckupRequest.getCustomerId()); + customerStmt.setString(2, saveCheckupRequest.getCustomerFirstName()); + customerStmt.setString(3, saveCheckupRequest.getCustomerLastName()); + customerStmt.setLong(4, saveCheckupRequest.getCustomerDob()); + customerStmt.setString(5, saveCheckupRequest.getCustomerGender()); + customerStmt.setString(6, saveCheckupRequest.getCustomerAddress()); + customerStmt.setString(7, saveCheckupRequest.getCustomerNumber()); + customerStmt.setString(8, saveCheckupRequest.getCustomerCccdDdcn()); + customerStmt.executeUpdate(); + } + + // 3. XỬ LÝ ĐƠN THUỐC (MEDICINE PRESCRIPTION) + Integer newPrescriptionId = null; + + // Xóa dữ liệu cũ của đơn thuốc liên quan đến lần khám này + try (PreparedStatement deleteStmt = conn.prepareStatement("DELETE FROM OrderItem WHERE checkup_id = ?")) { + deleteStmt.setInt(1, saveCheckupRequest.getCheckupId()); + deleteStmt.executeUpdate(); + } + try (PreparedStatement deleteStmt = conn.prepareStatement("DELETE FROM MedicineOrder WHERE checkup_id = ?")) { + deleteStmt.setInt(1, saveCheckupRequest.getCheckupId()); + deleteStmt.executeUpdate(); + } + log.info("Cleared previous medicine prescription for checkup_id: {}", saveCheckupRequest.getCheckupId()); + + // Nếu có đơn thuốc mới, tạo lại từ đầu + if (saveCheckupRequest.getMedicinePrescription() != null && saveCheckupRequest.getMedicinePrescription().length > 0) { + // A. Chèn vào MedicineOrder để lấy prescription_id mới + String medicineOrderSql = "INSERT INTO MedicineOrder (checkup_id, customer_id, total_amount, status, payment_status) VALUES (?, ?, ?, ?, ?)"; + try (PreparedStatement medicineOrderStmt = conn.prepareStatement(medicineOrderSql, PreparedStatement.RETURN_GENERATED_KEYS)) { + double totalMedicineAmount = 0; + for (String[] medicine : saveCheckupRequest.getMedicinePrescription()) { + if (medicine.length > 8) totalMedicineAmount += Double.parseDouble(medicine[8]); + } + medicineOrderStmt.setInt(1, saveCheckupRequest.getCheckupId()); + medicineOrderStmt.setInt(2, saveCheckupRequest.getCustomerId()); + medicineOrderStmt.setDouble(3, totalMedicineAmount); + medicineOrderStmt.setString(4, "Pending"); // status + medicineOrderStmt.setString(5, "Unpaid"); // payment_status + medicineOrderStmt.executeUpdate(); + + try (ResultSet generatedKeys = medicineOrderStmt.getGeneratedKeys()) { + if (generatedKeys.next()) { + newPrescriptionId = generatedKeys.getInt(1); + } else { + throw new SQLException("Creating medicine order failed, no ID obtained."); + } } } - orderItemStmt.executeBatch(); - log.info("OrderItems saved for prescription_id: {}", newPrescriptionId); - } - } - // 3. Insert/Update Checkup with the correct newPrescriptionId - String checkupSql = """ - INSERT INTO Checkup ( - checkup_date, checkup_id, customer_id, doctor_id, suggestion, diagnosis, - prescription_id, notes, status, checkup_type, conclusion, reCheckupDate, - customer_weight, customer_height, heart_beat, blood_pressure, doctor_ultrasound_id - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(checkup_id) DO UPDATE SET - suggestion = excluded.suggestion, diagnosis = excluded.diagnosis, - prescription_id = excluded.prescription_id, notes = excluded.notes, - status = excluded.status, checkup_type = excluded.checkup_type, - conclusion = excluded.conclusion, reCheckupDate = excluded.reCheckupDate, - customer_weight = excluded.customer_weight, customer_height = excluded.customer_height, - heart_beat = excluded.heart_beat, blood_pressure = excluded.blood_pressure, - doctor_ultrasound_id = excluded.doctor_ultrasound_id, doctor_id = excluded.doctor_id, - checkup_date = excluded.checkup_date - """; - - try (PreparedStatement checkupStmt = conn.prepareStatement(checkupSql)) { - checkupStmt.setLong(1, saveCheckupRequest.getCheckupDate()); - checkupStmt.setInt(2, saveCheckupRequest.getCheckupId()); - checkupStmt.setInt(3, saveCheckupRequest.getCustomerId()); - checkupStmt.setInt(4, saveCheckupRequest.getDoctorId()); - checkupStmt.setString(5, saveCheckupRequest.getSuggestions()); - checkupStmt.setString(6, saveCheckupRequest.getDiagnosis()); - // Use setObject to handle the case where newPrescriptionId is null - checkupStmt.setObject(7, newPrescriptionId); - checkupStmt.setString(8, saveCheckupRequest.getNotes()); - checkupStmt.setString(9, saveCheckupRequest.getStatus()); - checkupStmt.setString(10, saveCheckupRequest.getCheckupType()); - checkupStmt.setString(11, saveCheckupRequest.getConclusion()); - if (saveCheckupRequest.getReCheckupDate() != null) { - checkupStmt.setLong(12, saveCheckupRequest.getReCheckupDate()); - } else { - checkupStmt.setLong(12, 0); + // B. Chèn các chi tiết thuốc vào OrderItem bằng batch + String orderItemSql = "INSERT INTO OrderItem (prescription_id, med_id, quantity_ordered, dosage, price_per_unit, total_price, checkup_id, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; + try (PreparedStatement orderItemStmt = conn.prepareStatement(orderItemSql)) { + for (String[] medicine : saveCheckupRequest.getMedicinePrescription()) { + if (medicine.length >= 10) { + orderItemStmt.setInt(1, newPrescriptionId); + orderItemStmt.setString(2, medicine[0]); // med_id + orderItemStmt.setString(3, medicine[2]); // quantity + String dosage = String.format("Sáng %s, Trưa %s, Chiều %s", medicine[4], medicine[5], medicine[6]); + orderItemStmt.setString(4, dosage); + orderItemStmt.setString(5, medicine[7]); // unit_price + orderItemStmt.setString(6, medicine[8]); // total_price + orderItemStmt.setInt(7, saveCheckupRequest.getCheckupId()); + orderItemStmt.setString(8, medicine[9]); // notes + orderItemStmt.addBatch(); + } + } + orderItemStmt.executeBatch(); + } } - checkupStmt.setDouble(13, saveCheckupRequest.getCustomerWeight()); - checkupStmt.setDouble(14, saveCheckupRequest.getCustomerHeight()); - checkupStmt.setInt(15, saveCheckupRequest.getHeartBeat()); - checkupStmt.setString(16, saveCheckupRequest.getBloodPressure()); - checkupStmt.setInt(17, saveCheckupRequest.getDoctorUltrasoundId()); - checkupStmt.executeUpdate(); - log.info("Checkup saved successfully"); - } - - // 4. Handle Service Prescriptions - // clear old data - try (PreparedStatement deleteServiceStmt = conn.prepareStatement("DELETE FROM CheckupService WHERE checkup_id = ?")) { - deleteServiceStmt.setInt(1, saveCheckupRequest.getCheckupId()); - deleteServiceStmt.executeUpdate(); - } - log.info("Cleared previous service prescription for checkup_id: {}", saveCheckupRequest.getCheckupId()); - - String serviceSql = """ - INSERT INTO CheckupService (checkup_id, service_id, quantity, total_cost, notes) - VALUES (?, ?, ?, ?, ?) - """; - try (PreparedStatement serviceStmt = conn.prepareStatement(serviceSql)) { - for (String[] service : saveCheckupRequest.getServicePrescription()) { - serviceStmt.setInt(1, saveCheckupRequest.getCheckupId()); - serviceStmt.setString(2, service[0]); - serviceStmt.setInt(3, Integer.parseInt(service[2])); - serviceStmt.setDouble(4, Double.parseDouble(service[4])); - serviceStmt.setString(5, service[5]); - serviceStmt.executeUpdate(); + + // 4. CẬP NHẬT THÔNG TIN LẦN KHÁM (CHECKUP) + String checkupSql = """ + UPDATE Checkup SET + suggestion = ?, diagnosis = ?, prescription_id = ?, notes = ?, status = ?, + checkup_type = ?, conclusion = ?, reCheckupDate = ?, customer_weight = ?, + customer_height = ?, heart_beat = ?, blood_pressure = ?, + doctor_ultrasound_id = ?, doctor_id = ?, checkup_date = ? + WHERE checkup_id = ? + """; + try (PreparedStatement checkupStmt = conn.prepareStatement(checkupSql)) { + checkupStmt.setString(1, saveCheckupRequest.getSuggestions()); + checkupStmt.setString(2, saveCheckupRequest.getDiagnosis()); + checkupStmt.setObject(3, newPrescriptionId); // setObject an toàn cho giá trị null + checkupStmt.setString(4, saveCheckupRequest.getNotes()); + checkupStmt.setString(5, saveCheckupRequest.getStatus()); + checkupStmt.setString(6, saveCheckupRequest.getCheckupType()); + checkupStmt.setString(7, saveCheckupRequest.getConclusion()); + checkupStmt.setObject(8, saveCheckupRequest.getReCheckupDate()); // setObject an toàn cho null + checkupStmt.setDouble(9, saveCheckupRequest.getCustomerWeight()); + checkupStmt.setDouble(10, saveCheckupRequest.getCustomerHeight()); + checkupStmt.setInt(11, saveCheckupRequest.getHeartBeat()); + checkupStmt.setString(12, saveCheckupRequest.getBloodPressure()); + checkupStmt.setInt(13, saveCheckupRequest.getDoctorUltrasoundId()); + checkupStmt.setInt(14, saveCheckupRequest.getDoctorId()); + checkupStmt.setLong(15, saveCheckupRequest.getCheckupDate()); + checkupStmt.setInt(16, saveCheckupRequest.getCheckupId()); // WHERE clause + checkupStmt.executeUpdate(); } - log.info("Service saved successfully"); - } - - conn.commit(); // Commit the entire transaction - log.info("Successfully saved checkup {} with all related data", saveCheckupRequest.getCheckupId()); - UserUtil.sendPacket(currentUser.getSessionId(), new SaveCheckupRes(true, "Checkup saved successfully")); - - } catch (Exception e) { - log.error("Error saving checkup transaction", e); - if (conn != null) { - try { - conn.rollback(); - log.info("Transaction rolled back due to error"); - } catch (SQLException rollbackEx) { - log.error("Error rolling back transaction", rollbackEx); + + // 5. XỬ LÝ DỊCH VỤ (SERVICES) + try (PreparedStatement deleteStmt = conn.prepareStatement("DELETE FROM CheckupService WHERE checkup_id = ?")) { + deleteStmt.setInt(1, saveCheckupRequest.getCheckupId()); + deleteStmt.executeUpdate(); } + log.info("Cleared previous services for checkup_id: {}", saveCheckupRequest.getCheckupId()); + + // TỐI ƯU: Dùng batch để chèn dịch vụ + if (saveCheckupRequest.getServicePrescription() != null && saveCheckupRequest.getServicePrescription().length > 0) { + String serviceSql = "INSERT INTO CheckupService (checkup_id, service_id, quantity, total_cost, notes) VALUES (?, ?, ?, ?, ?)"; + try (PreparedStatement serviceStmt = conn.prepareStatement(serviceSql)) { + for (String[] service : saveCheckupRequest.getServicePrescription()) { + serviceStmt.setInt(1, saveCheckupRequest.getCheckupId()); + serviceStmt.setString(2, service[0]); + serviceStmt.setInt(3, Integer.parseInt(service[2])); + serviceStmt.setDouble(4, Double.parseDouble(service[4])); + serviceStmt.setString(5, service[5]); + serviceStmt.addBatch(); + } + serviceStmt.executeBatch(); + } + } + + // 6. Hoàn tất và COMMIT giao dịch + conn.commit(); + log.info("Successfully saved checkup transaction for checkup_id: {}", saveCheckupRequest.getCheckupId()); + UserUtil.sendPacket(currentUser.getSessionId(), new SaveCheckupRes(true, "Lưu thông tin khám bệnh thành công.")); + + } catch (SQLException e) { + // Nếu có lỗi, ROLLBACK toàn bộ giao dịch + log.error("Error during save checkup transaction, rolling back.", e); + conn.rollback(); + UserUtil.sendPacket(currentUser.getSessionId(), new ErrorResponse(Error.SQL_EXCEPTION)); } + // Không cần khối `finally` để reset auto-commit. HikariCP sẽ tự lo việc này. + + } catch (SQLException e) { + // Lỗi này xảy ra nếu không thể lấy kết nối từ pool hoặc có lỗi khi rollback + log.error("Critical error during database transaction management for SaveCheckupRequest.", e); UserUtil.sendPacket(currentUser.getSessionId(), new ErrorResponse(Error.SQL_EXCEPTION)); - } finally { - if (conn != null) { - try { - conn.setAutoCommit(true); // Restore auto-commit mode - } catch (SQLException e) { - log.error("Error restoring auto-commit", e); - } - } } - } + } if (packet instanceof GetOrderInfoByCheckupReq getOrderInfoByCheckupReq) { log.debug("Received GetOrderInfoByCheckupReq for checkupId: {}", getOrderInfoByCheckupReq.getCheckupId()); @@ -919,151 +900,138 @@ public class ServerHandler extends SimpleChannelInboundHandler medList = new ArrayList<>(); - while(medRs.next()) { - String[] med = new String[12]; - med[0] = medRs.getString("med_id"); - med[1] = medRs.getString("med_name"); - med[2] = medRs.getString("quantity_ordered"); - med[3] = medRs.getString("med_unit"); + + // --- SỬA ĐỔI: Lấy thông tin đơn thuốc (Medicine Prescription) --- + // Sử dụng try-with-resources để quản lý TẤT CẢ tài nguyên JDBC (Connection, PreparedStatement, ResultSet) + String medSql = """ + SELECT M.med_id, M.med_name, OI.quantity_ordered, M.med_unit, OI.dosage, + OI.price_per_unit, OI.total_price, OI.notes, M.supplement, M.route + FROM OrderItem OI + JOIN Medicine M ON OI.med_id = M.med_id + WHERE OI.checkup_id = ? + """; + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement medStmt = conn.prepareStatement(medSql)) { - // Parse dosage: "Sáng 1, Trưa 1, Chiều 1" - String dosage = medRs.getString("dosage"); - String morning = "0", noon = "0", evening = "0"; - if (dosage != null && !dosage.isEmpty()) { - String[] parts = dosage.split(", "); - for (String part : parts) { - String[] dosagePart = part.split(" "); - if (dosagePart.length == 2) { - if ("Sáng".equals(dosagePart[0])) morning = dosagePart[1]; - else if ("Trưa".equals(dosagePart[0])) noon = dosagePart[1]; - else if ("Chiều".equals(dosagePart[0])) evening = dosagePart[1]; + medStmt.setString(1, checkupId); + try (ResultSet medRs = medStmt.executeQuery()) { + ArrayList medList = new ArrayList<>(); + while(medRs.next()) { + String[] med = new String[12]; + med[0] = medRs.getString("med_id"); + med[1] = medRs.getString("med_name"); + med[2] = medRs.getString("quantity_ordered"); + med[3] = medRs.getString("med_unit"); + + // Parse dosage: "Sáng 1, Trưa 1, Chiều 1" + String dosage = medRs.getString("dosage"); + String morning = "0", noon = "0", evening = "0"; + if (dosage != null && !dosage.isEmpty()) { + String[] parts = dosage.split(", "); + for (String part : parts) { + String[] dosagePart = part.split(" "); + if (dosagePart.length == 2) { + if ("Sáng".equals(dosagePart[0])) morning = dosagePart[1]; + else if ("Trưa".equals(dosagePart[0])) noon = dosagePart[1]; + else if ("Chiều".equals(dosagePart[0])) evening = dosagePart[1]; + } + } } + med[4] = morning; + med[5] = noon; + med[6] = evening; + + med[7] = medRs.getString("price_per_unit"); + med[8] = medRs.getString("total_price"); + med[9] = medRs.getString("notes"); + med[10] = medRs.getString("supplement"); + med[11] = medRs.getString("route"); + medList.add(med); } + medicinePrescription = medList.toArray(new String[0][]); } - med[4] = morning; - med[5] = noon; - med[6] = evening; - - med[7] = medRs.getString("price_per_unit"); - med[8] = medRs.getString("total_price"); - med[9] = medRs.getString("notes"); - med[10] = medRs.getString("supplement"); - med[11] = medRs.getString("route"); - medList.add(med); - } - medicinePrescription = medList.toArray(new String[0][]); - } catch (SQLException e) { - log.error("Error getting medicine prescription for checkupId: " + checkupId, e); + log.error("Error getting medicine prescription for checkupId: " + checkupId, e); + // Nếu có lỗi, medicinePrescription sẽ vẫn là null } - // Get Service Prescription - try { - PreparedStatement serStmt = Server.connection.prepareStatement( - """ - SELECT - S.service_id, - S.service_name, - CS.quantity, - S.service_cost, - CS.total_cost, - CS.notes + // --- SỬA ĐỔI: Lấy thông tin dịch vụ (Service Prescription) --- + String serSql = """ + SELECT S.service_id, S.service_name, CS.quantity, + S.service_cost, CS.total_cost, CS.notes FROM CheckupService CS JOIN Service S ON CS.service_id = S.service_id WHERE CS.checkup_id = ? - """ - ); - serStmt.setString(1, checkupId); - ResultSet serRs = serStmt.executeQuery(); - - ArrayList serList = new ArrayList<>(); - while(serRs.next()) { - String[] ser = new String[6]; - ser[0] = serRs.getString("service_id"); - ser[1] = serRs.getString("service_name"); - ser[2] = serRs.getString("quantity"); - ser[3] = serRs.getString("service_cost"); - ser[4] = serRs.getString("total_cost"); - ser[5] = serRs.getString("notes"); - serList.add(ser); - } - servicePrescription = serList.toArray(new String[0][]); - + """; + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement serStmt = conn.prepareStatement(serSql)) { + + serStmt.setString(1, checkupId); + try (ResultSet serRs = serStmt.executeQuery()) { + ArrayList serList = new ArrayList<>(); + while(serRs.next()) { + String[] ser = new String[6]; + ser[0] = serRs.getString("service_id"); + ser[1] = serRs.getString("service_name"); + ser[2] = serRs.getString("quantity"); + ser[3] = serRs.getString("service_cost"); + ser[4] = serRs.getString("total_cost"); + ser[5] = serRs.getString("notes"); + serList.add(ser); + } + servicePrescription = serList.toArray(new String[0][]); + } } catch (SQLException e) { - log.error("Error getting service prescription for checkupId: " + checkupId, e); + log.error("Error getting service prescription for checkupId: " + checkupId, e); + // Nếu có lỗi, servicePrescription sẽ vẫn là null } UserUtil.sendPacket(currentUser.getSessionId(), new GetOrderInfoByCheckupRes(medicinePrescription, servicePrescription)); log.info("Sent order info for checkup {} to client", checkupId); - } - if (packet instanceof AddTemplateReq addTemplateReq) { + } + + if (packet instanceof AddTemplateReq addTemplateReq) { log.debug("Received AddTemplateReq: {}", addTemplateReq); - String templateName = addTemplateReq.getTemplateName(); - String templateTitle = addTemplateReq.getTemplateTitle(); - String templateDiagnosis = addTemplateReq.getTemplateDiagnosis(); - String templateConclusion = addTemplateReq.getTemplateConclusion(); - String templateSuggestion = addTemplateReq.getTemplateSuggestion(); - String templateImageCount = addTemplateReq.getTemplateImageCount(); - String templatePrintType = addTemplateReq.getTemplatePrintType(); - String templateGender = addTemplateReq.getTemplateGender(); - String templateContent = addTemplateReq.getTemplateContent(); - boolean templateVisible = addTemplateReq.isVisible(); - int templateStt = addTemplateReq.getStt(); - try { - PreparedStatement templateStmt = Server.connection.prepareStatement( - """ - INSERT INTO CheckupTemplate (template_gender, template_name, template_title, photo_num, print_type, content, conclusion, suggestion, diagnosis, visible, stt) + + // --- SỬA ĐỔI: Sử dụng try-with-resources để đảm bảo an toàn và tự động đóng tài nguyên --- + String sql = """ + INSERT INTO CheckupTemplate (template_gender, template_name, template_title, photo_num, + print_type, content, conclusion, suggestion, diagnosis, + visible, stt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """); - templateStmt.setString(1, templateGender); - templateStmt.setString(2, templateName); - templateStmt.setString(3, templateTitle); - templateStmt.setString(4, templateImageCount); - templateStmt.setString(5, templatePrintType); - templateStmt.setString(6, templateContent); - templateStmt.setString(7, templateConclusion); - templateStmt.setString(8, templateSuggestion); - templateStmt.setString(9, templateDiagnosis); - templateStmt.setBoolean(10, templateVisible); - templateStmt.setInt(11, templateStt); - templateStmt.executeUpdate(); - log.info("Template saved successfully"); - UserUtil.sendPacket(currentUser.getSessionId(), new AddTemplateRes(true, "Template saved successfully")); + """; + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement templateStmt = conn.prepareStatement(sql)) { + + templateStmt.setString(1, addTemplateReq.getTemplateGender()); + templateStmt.setString(2, addTemplateReq.getTemplateName()); + templateStmt.setString(3, addTemplateReq.getTemplateTitle()); + templateStmt.setString(4, addTemplateReq.getTemplateImageCount()); + templateStmt.setString(5, addTemplateReq.getTemplatePrintType()); + templateStmt.setString(6, addTemplateReq.getTemplateContent()); + templateStmt.setString(7, addTemplateReq.getTemplateConclusion()); + templateStmt.setString(8, addTemplateReq.getTemplateSuggestion()); + templateStmt.setString(9, addTemplateReq.getTemplateDiagnosis()); + templateStmt.setBoolean(10, addTemplateReq.isVisible()); + templateStmt.setInt(11, addTemplateReq.getStt()); + templateStmt.executeUpdate(); + + log.info("Template saved successfully"); + UserUtil.sendPacket(currentUser.getSessionId(), new AddTemplateRes(true, "Template saved successfully")); } catch (SQLException e) { - log.error("Error saving template", e); - UserUtil.sendPacket(currentUser.getSessionId(), new ErrorResponse(Error.SQL_EXCEPTION)); + log.error("Error saving template", e); + UserUtil.sendPacket(currentUser.getSessionId(), new ErrorResponse(Error.SQL_EXCEPTION)); } } if (packet instanceof GetAllTemplatesReq) { log.debug("Received GetAllTemplatesReq"); - try { - PreparedStatement stmt = Server.connection.prepareStatement("SELECT * FROM CheckupTemplate"); - ResultSet rs = stmt.executeQuery(); + + // --- SỬA ĐỔI: Sử dụng try-with-resources để quản lý TẤT CẢ tài nguyên --- + String sql = "SELECT * FROM CheckupTemplate ORDER BY stt"; // Thêm ORDER BY để sắp xếp + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql); + ResultSet rs = stmt.executeQuery()) { + List