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.*; import BsK.common.Error; import BsK.common.entity.Status; import BsK.common.util.date.DateUtils; import BsK.server.Server; import BsK.server.ServerDashboard; import BsK.server.network.manager.SessionManager; import BsK.server.network.util.UserUtil; import BsK.server.network.entity.Role; import BsK.server.network.entity.User; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.Utf8FrameValidator; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler.HandshakeComplete; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import javax.imageio.ImageIO; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.time.ZoneId; import java.time.LocalDate; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import BsK.common.entity.Medicine; 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; @Slf4j public class ServerHandler extends SimpleChannelInboundHandler { @Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) { // Update last activity time for the client var connectedUser = SessionManager.getUserByChannel(ctx.channel().id().asLongText()); if (connectedUser != null) { ServerDashboard.getInstance().refreshNetworkTable(); } Packet packet = PacketSerializer.GSON.fromJson(frame.text(), Packet.class); if (packet instanceof LoginRequest loginRequest) { log.debug( "Received login request: {}, {}", loginRequest.getUsername(), loginRequest.getPassword()); var user = SessionManager.getUserByChannel(ctx.channel().id().asLongText()); user.authenticate(loginRequest.getUsername(), loginRequest.getPassword()); if (user.isAuthenticated()) { UserUtil.sendPacket(user.getSessionId(), new LoginSuccessResponse(user.getUserId(), user.getRole())); log.info("Send response to client User {} authenticated, role {}, session {}", user.getUserId(), user.getRole(), user.getSessionId()); // Update client connection info SessionManager.updateUserRole(ctx.channel().id().asLongText(), user.getRole().toString(), user.getUserId()); } else { log.info("User {} failed to authenticate", user.getUserId()); UserUtil.sendPacket(user.getSessionId(), new ErrorResponse(Error.INVALID_CREDENTIALS)); } } else if (packet instanceof RegisterRequest registerRequest) { log.debug( "Received register request: {}, {}", registerRequest.getUsername(), registerRequest.getPassword()); // Tạo user trong database hoặc check exist boolean isUserExist = false; } else if (packet instanceof LogoutRequest) { var logoutUser = SessionManager.getUserByChannel(ctx.channel().id().asLongText()); if (logoutUser != null) { log.info("User {} (Session {}) requested logout.", logoutUser.getUserId(), logoutUser.getSessionId()); logoutUser.resetAuthentication(); } else { log.warn("Received LogoutRequest from a channel with no active user session: {}", ctx.channel().id().asLongText()); } } else { // Check if user is authenticated var currentUser = SessionManager.getUserByChannel(ctx.channel().id().asLongText()); if (currentUser == null || !currentUser.isAuthenticated()) { log.warn("Received packet from unauthenticated user: {}", packet); return; } if (packet instanceof GetCheckUpQueueRequest) { log.debug("Received GetCheckUpQueueRequest"); 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)); } } catch (SQLException 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(); } if (packet instanceof GetDoctorGeneralInfo) { log.debug("Received GetDoctorGeneralInfo"); 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()) { 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); // 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)); } } if (packet instanceof GetPatientHistoryRequest getPatientHistoryRequest) { log.debug("Received GetPatientHistoryRequest for patientId: {}", getPatientHistoryRequest.getPatientId()); String sql = """ SELECT C.checkup_date, C.checkup_id, C.suggestion, C.diagnosis, C.prescription_id, C.notes, C.checkup_type, C.conclusion, C.reCheckupDate, D.doctor_last_name, D.doctor_first_name, C.customer_height, C.customer_weight, C.heart_beat, C.blood_pressure FROM Checkup C JOIN Doctor D ON C.doctor_id = D.doctor_id WHERE C.status = ? AND C.customer_id = ? ORDER BY C.checkup_date DESC; """; try (Connection conn = DatabaseManager.getConnection(); PreparedStatement historyStmt = conn.prepareStatement(sql)) { historyStmt.setString(1, "ĐÃ KHÁM"); historyStmt.setInt(2, getPatientHistoryRequest.getPatientId()); try (ResultSet rs = historyStmt.executeQuery()) { if (!rs.isBeforeFirst()) { log.info("No history data found for patientId: {}", getPatientHistoryRequest.getPatientId()); UserUtil.sendPacket(currentUser.getSessionId(), new GetPatientHistoryResponse(new String[0][0])); } else { ArrayList resultList = new ArrayList<>(); while (rs.next()) { String[] historyEntry = new String[15]; String checkupDateStr = rs.getString("checkup_date"); try { long checkupDateLong = Long.parseLong(checkupDateStr); Timestamp timestamp = new Timestamp(checkupDateLong); Date date = new Date(timestamp.getTime()); SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); historyEntry[0] = sdf.format(date); } catch (Exception e) { historyEntry[0] = checkupDateStr; } historyEntry[1] = rs.getString("checkup_id"); historyEntry[2] = rs.getString("suggestion"); historyEntry[3] = rs.getString("diagnosis"); historyEntry[4] = rs.getString("prescription_id"); historyEntry[5] = rs.getString("notes"); historyEntry[6] = rs.getString("checkup_type"); historyEntry[7] = rs.getString("conclusion"); historyEntry[8] = rs.getString("reCheckupDate"); historyEntry[9] = rs.getString("doctor_last_name"); historyEntry[10] = rs.getString("doctor_first_name"); historyEntry[11] = rs.getString("customer_height"); historyEntry[12] = rs.getString("customer_weight"); historyEntry[13] = rs.getString("heart_beat"); historyEntry[14] = rs.getString("blood_pressure"); resultList.add(historyEntry); } String[][] resultArray = resultList.toArray(new String[0][]); UserUtil.sendPacket(currentUser.getSessionId(), new GetPatientHistoryResponse(resultArray)); log.info("Sent patient history for patientId: {}", getPatientHistoryRequest.getPatientId()); } } } catch (SQLException e) { log.error("Error fetching patient history for patientId: {}", getPatientHistoryRequest.getPatientId(), e); UserUtil.sendPacket(currentUser.getSessionId(), new ErrorResponse(Error.SQL_EXCEPTION)); } } if (packet instanceof GetMedInfoRequest getMedInfoRequest) { log.debug("Received GetMedInfoRequest"); getMedInfo(currentUser.getSessionId()); } if (packet instanceof GetSerInfoRequest getSerInfoRequest) { log.debug("Received GetSerInfoRequest"); getSerInfo(currentUser.getSessionId()); } if (packet instanceof ClinicInfoRequest clinicInfoRequest) { log.debug("Received ClinicInfoRequest"); // Thêm LIMIT 1 vì ta chỉ mong đợi 1 dòng kết quả String sql = "SELECT name, address, phone, prefix FROM Clinic LIMIT 1"; // SỬA ĐỔI: Sử dụng try-with-resources try (Connection conn = DatabaseManager.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery()) { // SỬA ĐỔI: Dùng if (rs.next()) là cách làm chuẩn và an toàn hơn if (rs.next()) { String clinicName = rs.getString("name"); String clinicAddress = rs.getString("address"); String clinicPhone = rs.getString("phone"); String clinicPrefix = rs.getString("prefix"); UserUtil.sendPacket(currentUser.getSessionId(), new ClinicInfoResponse(clinicName, clinicAddress, clinicPhone, clinicPrefix)); } else { // Ghi log nếu không tìm thấy thông tin phòng khám log.warn("No data found in the Clinic table. Cannot send clinic info."); // Bạn có thể gửi một response lỗi ở đây nếu cần, nhưng thường thì client có thể xử lý thông tin rỗng. } } catch (SQLException e) { // SỬA ĐỔI: Xử lý lỗi an toàn log.error("Error processing ClinicInfoRequest", e); UserUtil.sendPacket(currentUser.getSessionId(), new ErrorResponse(Error.SQL_EXCEPTION)); } } if (packet instanceof GetRecentPatientRequest getRecentPatientRequest) { log.debug("Received GetRecentPatientRequest"); // Sử dụng try-with-resources cho Connection để quản lý toàn bộ thao tác try (Connection conn = DatabaseManager.getConnection()) { // --- 1. Xây dựng câu truy vấn động --- StringBuilder queryBuilder = new StringBuilder(); queryBuilder.append("SELECT customer_id, customer_last_name, customer_first_name, customer_dob, customer_number, ") .append("customer_address, customer_gender, cccd_ddcn FROM Customer"); boolean hasNameSearch = getRecentPatientRequest.getSearchName() != null && !getRecentPatientRequest.getSearchName().trim().isEmpty(); boolean hasPhoneSearch = getRecentPatientRequest.getSearchPhone() != null && !getRecentPatientRequest.getSearchPhone().trim().isEmpty(); if (hasNameSearch || hasPhoneSearch) { queryBuilder.append(" WHERE "); if (hasNameSearch) { // SỬA LỖI SQL: Dùng || thay vì CONCAT cho SQLite queryBuilder.append("(LOWER(customer_first_name) LIKE ? OR LOWER(customer_last_name) LIKE ? OR ") .append("LOWER(customer_last_name || ' ' || customer_first_name) LIKE ?)"); } if (hasNameSearch && hasPhoneSearch) { queryBuilder.append(" AND "); } if (hasPhoneSearch) { queryBuilder.append("customer_number LIKE ?"); } } queryBuilder.append(" ORDER BY customer_id DESC"); // --- 2. Đếm tổng số bản ghi để phân trang --- int totalCount = 0; 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(*)" ); try (PreparedStatement countStmt = conn.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); } try (ResultSet countRs = countStmt.executeQuery()) { if (countRs.next()) { totalCount = countRs.getInt(1); } } } // --- 3. Tính toán thông tin phân trang --- int pageSize = getRecentPatientRequest.getPageSize(); int currentPage = getRecentPatientRequest.getPage(); int totalPages = (int) Math.ceil((double) totalCount / pageSize); int offset = (currentPage - 1) * pageSize; // --- 4. Truy vấn lấy dữ liệu của trang hiện tại --- queryBuilder.append(" LIMIT ? OFFSET ?"); try (PreparedStatement stmt = conn.prepareStatement(queryBuilder.toString())) { int 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); // Sửa lại: Dùng paramIndex thay vì paramIndex++ try (ResultSet rs = stmt.executeQuery()) { // Xử lý kết quả (phần này không thay đổi) 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)); } } } catch (SQLException 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"); 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); } 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) { // 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 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()) { 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) { // 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"); 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); } } catch (SQLException 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()); // --- 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; try { // 1. Bắt đầu một transaction trên kết nối CỤC BỘ (conn) này conn.setAutoCommit(false); // 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("Failed to get or update queue number."); } } // 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(); 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)); } // 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) { // 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()); int checkupId = callPatientRequest.getPatientId(); // This is the checkup_id int roomId = callPatientRequest.getRoomId(); Status status = callPatientRequest.getStatus(); String queueNumber = callPatientRequest.getQueueNumber(); // send to all clients with the queue number included int maxCurId = SessionManager.getMaxSessionId(); for (int sessionId = 1; sessionId <= maxCurId; sessionId++) { UserUtil.sendPacket(sessionId, new CallPatientResponse(checkupId, roomId, queueNumber, status)); } } if (packet instanceof SaveCheckupRequest saveCheckupRequest) { log.debug("Received SaveCheckupRequest to save checkup {}", saveCheckupRequest.getCheckupId()); // --- 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); // 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 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."); } } } // 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(); } } // 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(); } // 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)); } } if (packet instanceof GetOrderInfoByCheckupReq getOrderInfoByCheckupReq) { log.debug("Received GetOrderInfoByCheckupReq for checkupId: {}", getOrderInfoByCheckupReq.getCheckupId()); String checkupId = getOrderInfoByCheckupReq.getCheckupId(); String[][] medicinePrescription = null; String[][] servicePrescription = null; // --- 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)) { 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][]); } } catch (SQLException e) { log.error("Error getting medicine prescription for checkupId: " + checkupId, e); // Nếu có lỗi, medicinePrescription sẽ vẫn là null } // --- 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 = ? """; 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); // 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) { log.debug("Received AddTemplateReq: {}", addTemplateReq); // --- 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """; 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)); } } if (packet instanceof GetAllTemplatesReq) { log.debug("Received GetAllTemplatesReq"); // --- 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