[update] Remove deprecated app_settings.properties file and update config.properties with new clinic and server settings. Refactor LocalStorage to centralize clinic information and server configurations. Enhance CheckUpPage and related components for improved data handling and validation. Streamline database queries in ServerHandler for better performance and clarity.

This commit is contained in:
Lê Thành Đạt committed 2025-08-02 16:38:45 +07:00
1 parent 8c05076934
commit 736f16d818
15 files changed
+433 -377

No files matched your search

-17
View File
@@ -1,17 +0,0 @@
#BSK Client Application Settings
#Wed Jul 23 13:22:55 ICT 2025
app.auto_save=false
app.auto_save_interval=5
app.language=Ti\u1EBFng Vi\u1EC7t
app.theme=T\u1ED1i
backup.auto_enabled=false
backup.frequency=H\u1EB1ng ng\u00E0y
backup.path=
clinic.address=Khu v\u1EF1c 5, th\u1ECB tr\u1EA5n \u0110\u1EE9c H\u00F2a, huy\u1EC7n \u0110\u1EE9c H\u00F2a, t\u1EC9nh Long An
clinic.name=Ph\u00F2ng kh\u00E1m B\u00E1c S\u0129 Kh\u1EAFp
clinic.phone=0912345678
server.address=
server.auto_connect=false
server.port=1999
storage.image_path=
storage.pdf_path=
+6 -5
View File
@@ -1,12 +1,13 @@
#BSK Clinic Management System Configuration - Updated via Settings Dialog
#Wed Jul 23 13:42:36 ICT 2025
clinic.address=S\u1ED1 ABC, \u0110\u01B0\u1EDDng XYZ, Qu\u1EADn 1, TP.HCM
clinic.name=Ph\u00F2ng kh\u00E1m BSK
#Sat Aug 02 11:33:13 ICT 2025
clinic.address=So ABC, Duong XYZ, Quan 1, TP.HCM
clinic.name=Phong kham BSK
clinic.phone=0123456789
clinic.prefix=BSK
google.drive.enabled=true
google.drive.root.folder=BSK_Clinic_Patient_Files
server.address=127.0.0.1
server.port=1999
storage.checkup_media_base_dir=src/main/resources/image/checkup_media
storage.image_db_path=img_db
app.project_path=D:/Github Clones/BSK-All-in-One-Clinic-Management-System
storage.ultrasound_folder_path=ANH SIEU AM
app.auto_change_status_to_finished=false
+17 -12
View File
@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Date;
@AllArgsConstructor
@@ -19,32 +18,38 @@ public class LocalStorage {
public static String username;
public static Role role;
public static int userId;
public static String ClinicName = "Phòng khám BSK";
public static String ClinicAddress = "Số ABC, Đường XYZ, Quận 1, TP.HCM";
public static String ClinicPhone = "0123456789";
public static String ClinicPrefix = "BSK";
public static List<String> chatHistory = new ArrayList<>();
public static String pathToProject = "";
public static String dataDialogSearchTerm = "";
public static Date dataDialogFromDate = new Date(); // Default to today
public static Date dataDialogToDate = new Date(); // Default to today
public static String dataDialogDoctorName = "Tất cả"; // Default to "All"
public static Date dataDialogFromDate = new Date();
public static Date dataDialogToDate = new Date();
public static String dataDialogDoctorName = "Tất cả";
public static List<DoctorItem> doctorsName = new ArrayList<>();
public static String[] provinces = new String[]{};
public static String[] wards = new String[]{};
public static Map<String, String> provinceToId = new java.util.HashMap<>();
public static HashMap<String, String[]> cachedWards; // cache local areas later will do
public static HashMap<String, String[][]> medUnitConversion; // hash by med id inside is unit id and unit name and conversion rate and unit price
public static HashMap<String, String[]> cachedWards;
public static HashMap<String, String[][]> medUnitConversion;
// --- Settings managed in the SettingsDialog ---
// these variable can be changed by settings page
// need restart
public static String checkupMediaBaseDir = "src/main/resources/image/checkup_media";
public static String ULTRASOUND_FOLDER_PATH = "ANH SIEU AM";
public static String ClinicName = "Phòng khám BSK";
public static String ClinicAddress = "Số ABC, Đường XYZ, Quận 1, TP.HCM";
public static String ClinicPhone = "0123456789";
public static String ClinicPrefix = "BSK";
public static String serverAddress = "127.0.0.1";
public static String serverPort = "1999";
public static boolean googleDriveEnabled = true;
public static String googleDriveRootFolder = "BSK_Clinic_Patient_Files";
// does not need restart
public static Boolean autoChangeStatusToFinished = false; // turn this on will change status to finished when checkup is save by hitting print
public static Boolean autoChangeStatusToFinished = false;
}
@@ -2229,7 +2229,7 @@ public class CheckUpPage extends JPanel {
log.warn("Doctor '{}' not found in the list for patient selection.", selectedPatient.getDoctorName());
}
DoctorItem ultrasoundDoctorToSelect = findDoctorByName(selectedPatient.getDoctorUltrasoundId());
DoctorItem ultrasoundDoctorToSelect = findDoctorById(selectedPatient.getDoctorUltrasoundId());
if (ultrasoundDoctorToSelect != null) {
ultrasoundDoctorComboBox.setSelectedItem(ultrasoundDoctorToSelect);
} else if (ultrasoundDoctorComboBox.getItemCount() > 0) {
@@ -4615,7 +4615,19 @@ public class CheckUpPage extends JPanel {
return;
}
Date checkupDate = (Date) datePicker.getModel().getValue();
Date dob = (Date) dobPicker.getModel().getValue();
Date recheckupDate = (Date) recheckupDatePicker.getModel().getValue();
// Add validation for the required dates
if (checkupDate == null) {
JOptionPane.showMessageDialog(this, "Ngày khám không được để trống.", "Thiếu thông tin", JOptionPane.WARNING_MESSAGE);
return;
}
if (dob == null) {
JOptionPane.showMessageDialog(this, "Ngày sinh không được để trống.", "Thiếu thông tin", JOptionPane.WARNING_MESSAGE);
return;
}
if (needRecheckupCheckbox.isSelected() && recheckupDate == null) {
JOptionPane.showMessageDialog(this, "Vui lòng chọn ngày tái khám.", "Thiếu thông tin", JOptionPane.WARNING_MESSAGE);
return;
@@ -4627,7 +4639,7 @@ public class CheckUpPage extends JPanel {
Integer.parseInt(customerIdField.getText()),
Integer.parseInt(doctorIdStr),
ultrasoundDoctorIdStr != null ? Integer.parseInt(ultrasoundDoctorIdStr) : null,
DateUtils.convertToDate(datePicker.getJFormattedTextField().getText()).getTime(),
checkupDate.getTime(),
suggestionField.getText(),
diagnosisField.getText(),
getRtfContentAsString(),
@@ -4638,7 +4650,7 @@ public class CheckUpPage extends JPanel {
needRecheckupCheckbox.isSelected(),
customerFirstNameField.getText(),
customerLastNameField.getText(),
DateUtils.convertToDate(dobPicker.getJFormattedTextField().getText()).getTime(),
dob.getTime(),
(String) genderComboBox.getSelectedItem(),
customerAddressField.getText() + ", " + (wardComboBox.getSelectedItem() != null ? wardComboBox.getSelectedItem().toString() : "") + ", " + (provinceComboBox.getSelectedItem() != null ? provinceComboBox.getSelectedItem().toString() : ""),
customerPhoneField.getText(),
@@ -4954,6 +4966,25 @@ public class CheckUpPage extends JPanel {
return null;
}
/**
* Finds a DoctorItem in the LocalStorage list by their ID.
* @param id The ID of the doctor to find.
* @return The matching DoctorItem, or null if not found.
*/
private DoctorItem findDoctorById(String id) {
// Return null if the ID is invalid or the doctor list is not available
if (id == null || id.isEmpty() || id.equals("0") || LocalStorage.doctorsName == null) {
return null;
}
// Loop through the doctors and compare by ID
for (DoctorItem doctor : LocalStorage.doctorsName) {
if (doctor.getId().equals(id)) {
return doctor; // Found a match
}
}
return null; // No match found
}
public void loadPatientByCheckupId(Patient patient) {
handleRowSelection(-1, patient);
}
@@ -290,7 +290,7 @@ public class MedicineInvoice{
// --- CORRECTED THIS LINE ---
// Swapped 'amount' and 'supRoute' to match the method signature in InvoiceItem.java
// Call signature: (Tên, Ghi chú, Liều dùng, Số lượng, Đường dùng)
supplementItems.add(InvoiceItem.createSupplement(supName, supNote, dosageInfo, amount, supRoute));
supplementItems.add(InvoiceItem.createSupplement(supName, supNote, dosageInfo, supRoute, amount));
} catch (Exception e) {
System.err.println("Error parsing supplement data row: " + java.util.Arrays.toString(supplement) + " | Error: " + e.getMessage());
@@ -323,11 +323,20 @@ public class MedicineInvoice{
parameters.put("checkupNote", notes != null ? notes : "");
parameters.put("barcodeNumber", id != null ? id : "");
parameters.put("driveURL", driveURL != null ? driveURL : "");
parameters.put("logoImage", LocalStorage.pathToProject + "/src/main/java/BsK/client/ui/assets/icon/logo.jpg");
parameters.put("hasMedicines", !medicineItems.isEmpty());
parameters.put("hasServices", !serviceItems.isEmpty());
parameters.put("hasSupplements", !supplementItems.isEmpty());
String logoPath = "/BsK/client/ui/assets/icon/logo.jpg";
// Load the logo as an InputStream
InputStream logoStream = MedicineInvoice.class.getResourceAsStream(logoPath);
if (logoStream == null) {
System.err.println("ERROR: Logo image not found on classpath: " + logoPath);
parameters.put("logoImage", null);
} else {
// Pass the InputStream to the report parameter
parameters.put("logoImage", logoStream);
}
String resourcePath = "/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/medserinvoice.jrxml";
try (InputStream inputStream = MedicineInvoice.class.getResourceAsStream(resourcePath)) {
if (inputStream == null) {
@@ -173,9 +173,9 @@ public class UltrasoundResult {
public JasperPrint createJasperPrint() throws JRException, IOException {
String jrxmlPath;
if ("Dọc".equalsIgnoreCase(this.printType)) {
jrxmlPath = LocalStorage.pathToProject + "/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/ultrasoundresult_potrait.jrxml";
jrxmlPath = System.getProperty("user.dir") + "/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/ultrasoundresult_potrait.jrxml";
} else {
jrxmlPath =LocalStorage.pathToProject + "/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/ultrasoundresult.jrxml";
jrxmlPath = System.getProperty("user.dir") + "/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/ultrasoundresult.jrxml";
}
log.info("Using JRXML template: {}", jrxmlPath);
@@ -209,8 +209,8 @@ public class UltrasoundResult {
// Vitals data
parameters.put("checkupHeight", this.customerHeight != null ? this.customerHeight : "");
parameters.put("checkupWeight", this.customerWeight != null ? this.customerWeight : "");
parameters.put("checkupHeartBeat", this.heartRate != null ? this.heartRate : "");
parameters.put("checkupBloodPressure", this.bloodPressure != null ? this.bloodPressure : "");
parameters.put("checkupHeartBeat", this.heartRate);
parameters.put("checkupBloodPressure", this.bloodPressure);
// Template title
parameters.put("templateTitle", this.templateTitle);
@@ -227,8 +227,7 @@ public class UltrasoundResult {
int numberOfImages = this.selectedImages.size();
parameters.put("numberImage", numberOfImages);
String projectDir = LocalStorage.pathToProject;
parameters.put("logoImage", projectDir + "/src/main/java/BsK/client/ui/assets/icon/logo.jpg");
parameters.put("logoImage", System.getProperty("user.dir") + "/src/main/java/BsK/client/ui/assets/icon/logo.jpg");
for (int i = 0; i < 6; i++) {
if (i < numberOfImages) {
@@ -12,7 +12,6 @@ public class InvoiceItem {
private String medName;
private String dosage;
private String amount;
private Double unitPrice;
private String medRoute;
private String medNote;
@@ -24,10 +23,14 @@ public class InvoiceItem {
// --- Supplement Fields ---
private String supName;
private String supNote;
private String supDosage;
private String supAmount;
private String supRoute;
private String supNote;
// Private constructor to be used by factory methods
private InvoiceItem() {}
@@ -40,7 +43,6 @@ public class InvoiceItem {
item.dosage = dosage;
item.medRoute = route;
item.amount = amount;
item.unitPrice = 0.0;
item.medNote = medNote;
return item;
}
@@ -55,14 +57,14 @@ public class InvoiceItem {
return item;
}
public static InvoiceItem createSupplement(String supName, String supNote, String supDosage, String supAmount, String supRoute) {
public static InvoiceItem createSupplement(String supName, String supNote, String supDosage, String supRoute, String supAmount) {
InvoiceItem item = new InvoiceItem();
item.type = "SUP";
item.supName = supName;
item.supNote = supNote;
item.supDosage = supDosage;
item.supAmount = supAmount;
item.supRoute = supRoute;
item.supAmount = supAmount;
item.supNote = supNote;
return item;
}
@@ -74,7 +76,6 @@ public class InvoiceItem {
public String getDosage() { return dosage; }
public String getMedRoute() { return medRoute; }
public String getAmount() { return amount; }
public Double getUnitPrice() { return unitPrice; }
public String getMedNote() { return medNote; }
public String getSerName() { return serName; }
public String getSerNote() { return serNote; }
@@ -17,7 +17,6 @@
<field name="medName" class="java.lang.String"/>
<field name="dosage" class="java.lang.String"/>
<field name="amount" class="java.lang.String"/>
<field name="unitPrice" class="java.lang.Double"/>
<field name="medNote" class="java.lang.String"/>
<field name="serName" class="java.lang.String"/>
<field name="serNote" class="java.lang.String"/>
@@ -62,7 +61,7 @@
<parameter name="patientName" class="java.lang.String"/>
<parameter name="patientDOB" class="java.lang.String"/>
<parameter name="clinicAddress" class="java.lang.String"/>
<parameter name="logoImage" class="java.lang.String"/>
<parameter name="logoImage" class="java.io.InputStream"/>
<parameter name="barcodeNumber" class="java.lang.String"/>
<parameter name="patientGender" class="java.lang.String"/>
<parameter name="hasMedicines" class="java.lang.Boolean"/>
@@ -211,16 +210,16 @@
<band height="62">
<printWhenExpression><![CDATA[$P{hasSupplements}]]></printWhenExpression>
<element kind="component" uuid="71f6b965-7b44-475c-aa38-89e77ce55e87" x="0" y="1" width="555" height="59">
<printWhenExpression><![CDATA[$P{hasMedicines}]]></printWhenExpression>
<printWhenExpression><![CDATA[$P{hasSupplements}]]></printWhenExpression>
<component kind="table">
<datasetRun uuid="bddd7b63-0aeb-492a-99ec-561999a49e96" subDataset="med_pre">
<dataSourceExpression><![CDATA[$P{medicineDS} == null ? new net.sf.jasperreports.engine.JREmptyDataSource() : $P{medicineDS}]]></dataSourceExpression>
<datasetRun uuid="54f95387-084f-440b-ad64-df3ba24f449d" subDataset="med_pre">
<dataSourceExpression><![CDATA[$P{supplementDS} == null ? new net.sf.jasperreports.engine.JREmptyDataSource() : $P{supplementDS}]]></dataSourceExpression>
<returnValue toVariable="MedicineGrandTotal" fromVariable="medTotal"/>
</datasetRun>
<column kind="single" uuid="6ecff449-3a01-40a2-a424-cb32c6291fa4" width="350">
<columnHeader height="19" rowSpan="1">
<element kind="staticText" uuid="3891585b-57df-4552-a45b-87caaa47f731" x="0" y="0" width="350" height="19" fontName="Times New Roman" fontSize="16.0" bold="true" hTextAlign="Left" vTextAlign="Middle">
<text><![CDATA[THUỐC]]></text>
<text><![CDATA[THỰC PHẨM BỔ SUNG]]></text>
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
@@ -26,7 +26,6 @@ import javax.swing.filechooser.FileNameExtensionFilter;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; // Import Arrays for utility if needed, though not strictly required here
import java.util.List;
public class DataDialog extends JDialog {
@@ -49,7 +48,7 @@ public class DataDialog extends JDialog {
private JPanel mainPanel;
private JPanel paginationPanel;
private CheckUpPage checkUpPageInstance;
private String[][] currentCheckupData; // <<< MODIFIED: Changed type from List<String[]> to String[][]
private String[][] currentCheckupData;
//</editor-fold>
// --- Panels for the other tabs
@@ -352,7 +351,8 @@ public class DataDialog extends JDialog {
}
try {
ExcelExporter.exportToExcel(patientsToExport, this.fileForExport);
// <<< MODIFIED: Pass the doctors list to the exporter
ExcelExporter.exportToExcel(patientsToExport, LocalStorage.doctorsName, this.fileForExport);
JOptionPane.showMessageDialog(this, "Xuất file Excel thành công!\n" + this.fileForExport.getAbsolutePath(), "Thành công", JOptionPane.INFORMATION_MESSAGE);
} catch (IOException ex) {
JOptionPane.showMessageDialog(this, "Lỗi khi ghi file Excel: " + ex.getMessage(), "Lỗi", JOptionPane.ERROR_MESSAGE);
@@ -368,13 +368,11 @@ public class DataDialog extends JDialog {
this.totalPages = response.getTotalPages();
this.totalRecords = response.getTotalRecords();
this.recordsPerPage = response.getPageSize();
// This assignment is now correct as both types are String[][]
this.currentCheckupData = response.getCheckupData();
tableModel.setRowCount(0);
if (this.currentCheckupData != null) {
int stt = (currentPage - 1) * recordsPerPage + 1;
// Enhanced for-loop works perfectly on a 2D array
for (String[] rowData : this.currentCheckupData) {
try {
Patient patient = new Patient(rowData);
@@ -518,10 +516,15 @@ public class DataDialog extends JDialog {
setLayout(new FlowLayout(FlowLayout.CENTER, 2, 2));
ImageIcon editIcon = new ImageIcon("src/main/java/BsK/client/ui/assets/icon/edit.png");
editIcon.setImage(editIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH));
if (editIcon.getImage() != null) {
editIcon.setImage(editIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH));
}
editButton = new JButton(editIcon);
ImageIcon deleteIcon = new ImageIcon("src/main/java/BsK/client/ui/assets/icon/delete.png");
deleteIcon.setImage(deleteIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH));
if (deleteIcon.getImage() != null) {
deleteIcon.setImage(deleteIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH));
}
deleteButton = new JButton(deleteIcon);
editButton.setPreferredSize(new Dimension(30, 25));
@@ -554,10 +557,15 @@ public class DataDialog extends JDialog {
panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 2, 2));
ImageIcon editIcon = new ImageIcon("src/main/java/BsK/client/ui/assets/icon/edit.png");
editIcon.setImage(editIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH));
if (editIcon.getImage() != null) {
editIcon.setImage(editIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH));
}
editButton = new JButton(editIcon);
ImageIcon deleteIcon = new ImageIcon("src/main/java/BsK/client/ui/assets/icon/delete.png");
deleteIcon.setImage(deleteIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH));
if (deleteIcon.getImage() != null) {
deleteIcon.setImage(deleteIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH));
}
deleteButton = new JButton(deleteIcon);
editButton.setPreferredSize(new Dimension(30, 25));
@@ -572,9 +580,7 @@ public class DataDialog extends JDialog {
}
int modelRow = dataTable.convertRowIndexToModel(selectedViewRow);
// <<< MODIFIED: Use .length for array and check index validity
if (currentCheckupData != null && modelRow >= 0 && modelRow < currentCheckupData.length) {
// <<< MODIFIED: Use array index access `[modelRow]` instead of `.get()`
String[] rowData = currentCheckupData[modelRow];
try {
Patient selectedPatient = new Patient(rowData);
@@ -1,5 +1,6 @@
package BsK.client.ui.component.DataDialog;
import BsK.common.entity.DoctorItem;
import BsK.common.entity.Patient;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
@@ -13,15 +14,34 @@ import java.util.List;
public class ExcelExporter {
public static void exportToExcel(List<Patient> patientList, File file) throws IOException {
/**
* Finds a doctor's name from a list based on their ID.
* @param id The doctor ID to look for.
* @param doctorList The list of all available doctors.
* @return The doctor's name, or an empty string if not found.
*/
private static String getDoctorNameById(String id, List<DoctorItem> doctorList) {
if (id == null || id.isEmpty() || id.equals("0") || doctorList == null) {
return "";
}
for (DoctorItem doctor : doctorList) {
if (doctor.getId().equals(id)) {
return doctor.getName();
}
}
return ""; // Return empty if no match is found
}
// <<< MODIFIED: Method signature now accepts the list of doctors
public static void exportToExcel(List<Patient> patientList, List<DoctorItem> doctorList, File file) throws IOException {
// Create a new Excel workbook
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
XSSFSheet sheet = workbook.createSheet("DanhSachKhamBenh");
// Define header titles
// <<< MODIFIED: Added "Bác Sĩ Siêu Âm" column
String[] headers = {
"Mã Khám", "Mã BN", "Họ", "Tên", "Năm Sinh", "Giới Tính", "Số Điện Thoại",
"Địa Chỉ", "Ngày Khám", "Loại Khám", "Bác Sĩ Khám", "CCCD/DDCN", "Cân Nặng (kg)",
"Địa Chỉ", "Ngày Khám", "Loại Khám", "Bác Sĩ Khám", "Bác Sĩ Siêu Âm", "CCCD/DDCN", "Cân Nặng (kg)",
"Chiều Cao (cm)", "Nhịp Tim (l/p)", "Huyết Áp (mmHg)", "Chẩn Đoán", "Kết Luận",
"Đề Nghị", "Ghi Chú", "Ngày Tái Khám"
};
@@ -48,16 +68,18 @@ public class ExcelExporter {
row.createCell(8).setCellValue(patient.getCheckupDate());
row.createCell(9).setCellValue(patient.getCheckupType());
row.createCell(10).setCellValue(patient.getDoctorName());
row.createCell(11).setCellValue(patient.getCccdDdcn());
row.createCell(12).setCellValue(patient.getCustomerWeight());
row.createCell(13).setCellValue(patient.getCustomerHeight());
row.createCell(14).setCellValue(patient.getHeartBeat());
row.createCell(15).setCellValue(patient.getBloodPressure());
row.createCell(16).setCellValue(patient.getDiagnosis());
row.createCell(17).setCellValue(patient.getConclusion());
row.createCell(18).setCellValue(patient.getSuggestion());
row.createCell(19).setCellValue(patient.getNotes());
row.createCell(20).setCellValue(patient.getReCheckupDate());
String ultrasoundDoctorName = getDoctorNameById(patient.getDoctorUltrasoundId(), doctorList);
row.createCell(11).setCellValue(ultrasoundDoctorName);
row.createCell(12).setCellValue(patient.getCccdDdcn());
row.createCell(13).setCellValue(patient.getCustomerWeight());
row.createCell(14).setCellValue(patient.getCustomerHeight());
row.createCell(15).setCellValue(patient.getHeartBeat());
row.createCell(16).setCellValue(patient.getBloodPressure());
row.createCell(17).setCellValue(patient.getDiagnosis());
row.createCell(18).setCellValue(patient.getConclusion());
row.createCell(19).setCellValue(patient.getSuggestion());
row.createCell(20).setCellValue(patient.getNotes());
row.createCell(21).setCellValue(patient.getReCheckupDate());
}
// Write the output to a file
@@ -1,6 +1,5 @@
package BsK.client.ui.component.SettingsPage;
import BsK.client.Client;
import BsK.client.LocalStorage;
import lombok.extern.slf4j.Slf4j;
@@ -14,15 +13,27 @@ import java.util.Properties;
@Slf4j
public class SettingsDialog extends JDialog {
// Existing fields
private JTextField mediaDirectoryField;
private JTextField ultrasoundFolderField;
private JCheckBox autoChangeStatusCheckBox;
// Fields for additional settings
private JTextField clinicNameField;
private JTextField clinicAddressField;
private JTextField clinicPhoneField;
private JTextField clinicPrefixField;
private JTextField serverAddressField;
private JTextField serverPortField;
// <<< REMOVED: Google Drive UI fields are gone
private Properties originalProps;
private boolean hasChanges = false;
public SettingsDialog(JFrame parent) {
super(parent, "Cài đặt hệ thống", true); // Modal dialog
setSize(700, 600);
setSize(700, 700); // Adjusted height
setLocationRelativeTo(parent);
setLayout(new BorderLayout());
@@ -76,10 +87,10 @@ public class SettingsDialog extends JDialog {
JOptionPane.QUESTION_MESSAGE);
if (choice == JOptionPane.YES_OPTION) {
dispose(); // Close dialog without saving
dispose();
}
} else {
dispose(); // No changes, just close
dispose();
}
});
@@ -100,28 +111,22 @@ public class SettingsDialog extends JDialog {
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.insets = new Insets(5, 5, 5, 5);
// Auto change status setting
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 2;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1.0;
autoChangeStatusCheckBox = new JCheckBox("Tự động chuyển trạng thái thành 'Hoàn thành' khi in");
autoChangeStatusCheckBox.setFont(new Font("Arial", Font.PLAIN, 12));
autoChangeStatusCheckBox.setSelected(LocalStorage.autoChangeStatusToFinished);
autoChangeStatusCheckBox.addActionListener(e -> hasChanges = true);
parent.add(autoChangeStatusCheckBox, gbc);
gbc.gridy = 1;
gbc.weighty = 0;
JLabel autoStatusDescription = new JLabel("<html><i>Khi bật, trạng thái khám bệnh sẽ tự động chuyển thành 'Hoàn thành' sau khi in hóa đơn</i></html>");
autoStatusDescription.setFont(new Font("Arial", Font.PLAIN, 10));
autoStatusDescription.setForeground(Color.GRAY);
parent.add(autoStatusDescription, gbc);
// Add vertical glue to push content to top
gbc.gridy = 2;
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.VERTICAL;
@@ -132,120 +137,88 @@ public class SettingsDialog extends JDialog {
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.insets = new Insets(5, 5, 5, 5);
// Media directory setting
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 1;
gbc.fill = GridBagConstraints.NONE;
gbc.weightx = 0;
JLabel mediaLabel = new JLabel("Thư mục lưu trữ ảnh khám bệnh:");
mediaLabel.setFont(new Font("Arial", Font.BOLD, 12));
parent.add(mediaLabel, gbc);
gbc.gridx = 0;
gbc.gridy = 1;
gbc.gridwidth = 2;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1.0;
JPanel mediaInputPanel = new JPanel(new BorderLayout(5, 0));
mediaDirectoryField = new JTextField();
mediaDirectoryField.setText(LocalStorage.checkupMediaBaseDir);
mediaDirectoryField.setPreferredSize(new Dimension(300, 25));
mediaDirectoryField.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
int y = 0;
javax.swing.event.DocumentListener changeListener = new javax.swing.event.DocumentListener() {
public void insertUpdate(javax.swing.event.DocumentEvent e) { hasChanges = true; }
public void removeUpdate(javax.swing.event.DocumentEvent e) { hasChanges = true; }
public void changedUpdate(javax.swing.event.DocumentEvent e) { hasChanges = true; }
});
};
JButton mediaBrowseButton = new JButton("Chọn thư mục");
mediaBrowseButton.setPreferredSize(new Dimension(120, 25));
mediaBrowseButton.addActionListener(e -> {
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
chooser.setCurrentDirectory(new File(mediaDirectoryField.getText()));
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
mediaDirectoryField.setText(chooser.getSelectedFile().getAbsolutePath());
hasChanges = true;
}
});
mediaInputPanel.add(mediaDirectoryField, BorderLayout.CENTER);
mediaInputPanel.add(mediaBrowseButton, BorderLayout.EAST);
parent.add(mediaInputPanel, gbc);
gbc.gridy = 2;
gbc.weighty = 0;
JLabel mediaDescription = new JLabel("<html><i>Thư mục chứa ảnh được chụp trong quá trình khám bệnh</i></html>");
mediaDescription.setFont(new Font("Arial", Font.PLAIN, 10));
mediaDescription.setForeground(Color.GRAY);
parent.add(mediaDescription, gbc);
// Ultrasound folder setting
gbc.gridx = 0;
gbc.gridy = 3;
gbc.gridwidth = 1;
gbc.fill = GridBagConstraints.NONE;
gbc.weightx = 0;
gbc.insets = new Insets(15, 5, 5, 5); // Add extra top margin
JLabel ultrasoundLabel = new JLabel("Thư mục theo dõi ảnh siêu âm:");
ultrasoundLabel.setFont(new Font("Arial", Font.BOLD, 12));
parent.add(ultrasoundLabel, gbc);
gbc.gridx = 0;
gbc.gridy = 4;
gbc.gridwidth = 2;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1.0;
gbc.insets = new Insets(5, 5, 5, 5); // Reset insets
JPanel ultrasoundInputPanel = new JPanel(new BorderLayout(5, 0));
ultrasoundFolderField = new JTextField();
ultrasoundFolderField.setText(LocalStorage.ULTRASOUND_FOLDER_PATH);
ultrasoundFolderField.setPreferredSize(new Dimension(300, 25));
ultrasoundFolderField.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
public void insertUpdate(javax.swing.event.DocumentEvent e) { hasChanges = true; }
public void removeUpdate(javax.swing.event.DocumentEvent e) { hasChanges = true; }
public void changedUpdate(javax.swing.event.DocumentEvent e) { hasChanges = true; }
});
JButton ultrasoundBrowseButton = new JButton("Chọn thư mục");
ultrasoundBrowseButton.setPreferredSize(new Dimension(120, 25));
ultrasoundBrowseButton.addActionListener(e -> {
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
chooser.setCurrentDirectory(new File(ultrasoundFolderField.getText()));
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
ultrasoundFolderField.setText(chooser.getSelectedFile().getAbsolutePath());
hasChanges = true;
}
});
ultrasoundInputPanel.add(ultrasoundFolderField, BorderLayout.CENTER);
ultrasoundInputPanel.add(ultrasoundBrowseButton, BorderLayout.EAST);
parent.add(ultrasoundInputPanel, gbc);
gbc.gridy = 5;
gbc.weighty = 0;
JLabel ultrasoundDescription = new JLabel("<html><i>Thư mục được hệ thống theo dõi để tự động nhận ảnh siêu âm mới</i></html>");
ultrasoundDescription.setFont(new Font("Arial", Font.PLAIN, 10));
ultrasoundDescription.setForeground(Color.GRAY);
parent.add(ultrasoundDescription, gbc);
// Add vertical glue to push content to top
gbc.gridy = 6;
// --- Clinic Info ---
clinicNameField = addTextFieldSetting(parent, gbc, y++, "Tên phòng khám:", LocalStorage.ClinicName, "Tên sẽ hiển thị trên các phiếu in.", changeListener);
clinicAddressField = addTextFieldSetting(parent, gbc, y++, "Địa chỉ phòng khám:", LocalStorage.ClinicAddress, "Địa chỉ sẽ hiển thị trên các phiếu in.", changeListener);
clinicPhoneField = addTextFieldSetting(parent, gbc, y++, "SĐT phòng khám:", LocalStorage.ClinicPhone, "Số điện thoại sẽ hiển thị trên các phiếu in.", changeListener);
clinicPrefixField = addTextFieldSetting(parent, gbc, y++, "Tiền tố mã:", LocalStorage.ClinicPrefix, "Tiền tố cho mã bệnh nhân và các mã khác (ví dụ: BSK).", changeListener);
// --- Server Connection ---
serverAddressField = addTextFieldSetting(parent, gbc, y++, "Địa chỉ máy chủ:", LocalStorage.serverAddress, "Địa chỉ IP hoặc tên miền của máy chủ.", changeListener);
serverPortField = addTextFieldSetting(parent, gbc, y++, "Cổng máy chủ (Port):", LocalStorage.serverPort, "Cổng kết nối tới máy chủ.", changeListener);
// --- Storage Settings ---
mediaDirectoryField = addTextFieldSettingWithBrowse(parent, gbc, y++, "Thư mục lưu trữ ảnh khám bệnh:", LocalStorage.checkupMediaBaseDir, "Thư mục chứa ảnh được chụp trong quá trình khám bệnh.", changeListener);
ultrasoundFolderField = addTextFieldSettingWithBrowse(parent, gbc, y++, "Thư mục theo dõi ảnh siêu âm:", LocalStorage.ULTRASOUND_FOLDER_PATH, "Thư mục được hệ thống theo dõi để tự động nhận ảnh siêu âm mới.", changeListener);
// <<< REMOVED: Google Drive Integration UI is gone.
// Vertical glue to push all content up
gbc.gridy = y * 3; // Adjust glue position
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.VERTICAL;
parent.add(Box.createVerticalGlue(), gbc);
}
private JTextField addTextFieldSetting(JPanel parent, GridBagConstraints gbc, int yPos, String labelText, String initialValue, String description, javax.swing.event.DocumentListener listener) {
gbc.gridx = 0; gbc.gridy = yPos * 3; gbc.gridwidth = 2; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0; gbc.insets = (yPos == 0) ? new Insets(5, 5, 5, 5) : new Insets(15, 5, 5, 5);
JLabel label = new JLabel(labelText);
label.setFont(new Font("Arial", Font.BOLD, 12));
parent.add(label, gbc);
gbc.gridy = yPos * 3 + 1; gbc.insets = new Insets(5, 5, 5, 5);
JTextField textField = new JTextField(initialValue);
textField.getDocument().addDocumentListener(listener);
parent.add(textField, gbc);
gbc.gridy = yPos * 3 + 2;
JLabel descLabel = new JLabel("<html><i>" + description + "</i></html>");
descLabel.setFont(new Font("Arial", Font.PLAIN, 10));
descLabel.setForeground(Color.GRAY);
parent.add(descLabel, gbc);
return textField;
}
private JTextField addTextFieldSettingWithBrowse(JPanel parent, GridBagConstraints gbc, int yPos, String labelText, String initialValue, String description, javax.swing.event.DocumentListener listener) {
JTextField textField = addTextFieldSetting(parent, gbc, yPos, labelText, initialValue, description, listener);
parent.remove(textField);
JPanel inputPanel = new JPanel(new BorderLayout(5, 0));
inputPanel.add(textField, BorderLayout.CENTER);
JButton browseButton = new JButton("Chọn...");
browseButton.addActionListener(e -> {
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
try {
File currentDir = new File(textField.getText());
if (currentDir.exists()) {
chooser.setCurrentDirectory(currentDir);
}
} catch (Exception ex) { /* Ignore */ }
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
textField.setText(chooser.getSelectedFile().getAbsolutePath());
}
});
inputPanel.add(browseButton, BorderLayout.EAST);
gbc.gridy = yPos * 3 + 1;
parent.add(inputPanel, gbc);
return textField;
}
private JPanel createSection(String title, Color backgroundColor) {
JPanel panel = new JPanel(new GridBagLayout()); // Changed to GridBagLayout
JPanel panel = new JPanel(new GridBagLayout());
panel.setBackground(backgroundColor);
panel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder(
@@ -261,7 +234,6 @@ public class SettingsDialog extends JDialog {
panel.setAlignmentX(Component.LEFT_ALIGNMENT);
return panel;
}
private void loadSettings() {
try {
@@ -275,58 +247,64 @@ public class SettingsDialog extends JDialog {
hasChanges = false;
} catch (IOException e) {
log.error("Error loading config", e);
JOptionPane.showMessageDialog(this,
"Lỗi khi tải cài đặt: " + e.getMessage(),
"Lỗi",
JOptionPane.ERROR_MESSAGE);
JOptionPane.showMessageDialog(this, "Lỗi khi tải cài đặt: " + e.getMessage(), "Lỗi", JOptionPane.ERROR_MESSAGE);
}
}
private void saveSettings() {
// Check if there are any changes
boolean mediaChanged = !mediaDirectoryField.getText().equals(LocalStorage.checkupMediaBaseDir);
boolean ultrasoundChanged = !ultrasoundFolderField.getText().equals(LocalStorage.ULTRASOUND_FOLDER_PATH);
boolean autoStatusChanged = autoChangeStatusCheckBox.isSelected() != LocalStorage.autoChangeStatusToFinished;
if (!hasChanges && !mediaChanged && !ultrasoundChanged && !autoStatusChanged) {
JOptionPane.showMessageDialog(this,
"Không có thay đổi nào để lưu.",
"Thông báo",
JOptionPane.INFORMATION_MESSAGE);
// <<< MODIFIED: Removed Google Drive fields from change detection
boolean restartRequiredChanges =
!mediaDirectoryField.getText().trim().equals(LocalStorage.checkupMediaBaseDir) ||
!ultrasoundFolderField.getText().trim().equals(LocalStorage.ULTRASOUND_FOLDER_PATH) ||
!clinicNameField.getText().trim().equals(LocalStorage.ClinicName) ||
!clinicAddressField.getText().trim().equals(LocalStorage.ClinicAddress) ||
!clinicPhoneField.getText().trim().equals(LocalStorage.ClinicPhone) ||
!clinicPrefixField.getText().trim().equals(LocalStorage.ClinicPrefix) ||
!serverAddressField.getText().trim().equals(LocalStorage.serverAddress) ||
!serverPortField.getText().trim().equals(LocalStorage.serverPort);
boolean noRestartChanges = autoChangeStatusCheckBox.isSelected() != LocalStorage.autoChangeStatusToFinished;
if (!restartRequiredChanges && !noRestartChanges) {
JOptionPane.showMessageDialog(this, "Không có thay đổi nào để lưu.", "Thông báo", JOptionPane.INFORMATION_MESSAGE);
return;
}
try {
Properties props = new Properties();
if (originalProps != null) {
props.putAll(originalProps); // Preserve other settings
props.putAll(originalProps);
}
// Update settings that require restart
String newMediaDir = mediaDirectoryField.getText().trim();
String newUltrasoundFolder = ultrasoundFolderField.getText().trim();
props.setProperty("storage.checkup_media_base_dir", newMediaDir);
props.setProperty("storage.ultrasound_folder_path", newUltrasoundFolder);
// Clinic Info
props.setProperty("clinic.name", clinicNameField.getText().trim());
props.setProperty("clinic.address", clinicAddressField.getText().trim());
props.setProperty("clinic.phone", clinicPhoneField.getText().trim());
props.setProperty("clinic.prefix", clinicPrefixField.getText().trim());
// Update settings that don't require restart
// Server
props.setProperty("server.address", serverAddressField.getText().trim());
props.setProperty("server.port", serverPortField.getText().trim());
// Storage
props.setProperty("storage.checkup_media_base_dir", mediaDirectoryField.getText().trim());
props.setProperty("storage.ultrasound_folder_path", ultrasoundFolderField.getText().trim());
// <<< REMOVED: Saving of Google Drive properties
// App settings (no restart)
boolean newAutoStatus = autoChangeStatusCheckBox.isSelected();
props.setProperty("app.auto_change_status_to_finished", String.valueOf(newAutoStatus));
LocalStorage.autoChangeStatusToFinished = newAutoStatus; // Update immediately
LocalStorage.autoChangeStatusToFinished = newAutoStatus;
// Ensure config directory exists
// Save properties to file
new File("config").mkdirs();
// Save properties
File configFile = new File("config/config.properties");
try (var output = Files.newOutputStream(configFile.toPath())) {
props.store(output, "BSK Clinic Management System Configuration - Updated via Settings Dialog");
}
// Check if restart is required
boolean restartRequired = mediaChanged || ultrasoundChanged;
if (restartRequired) {
// Show success message with restart required
if (restartRequiredChanges) {
int choice = JOptionPane.showConfirmDialog(this,
"Cài đặt đã được lưu. Một số thay đổi cần khởi động lại ứng dụng để có hiệu lực.\n\nBạn có muốn khởi động lại ngay bây giờ không?",
"Cần khởi động lại",
@@ -334,28 +312,20 @@ public class SettingsDialog extends JDialog {
JOptionPane.QUESTION_MESSAGE);
if (choice == JOptionPane.YES_OPTION) {
// Exit the application
System.exit(0);
} else {
dispose(); // Close dialog after saving
dispose();
}
} else {
// No restart required
JOptionPane.showMessageDialog(this,
"Cài đặt đã được lưu thành công.",
"Thành công",
JOptionPane.INFORMATION_MESSAGE);
JOptionPane.showMessageDialog(this, "Cài đặt đã được lưu thành công.", "Thành công", JOptionPane.INFORMATION_MESSAGE);
dispose();
}
hasChanges = false;
} catch (IOException ex) {
log.error("Error saving settings", ex);
JOptionPane.showMessageDialog(this,
"Lỗi khi lưu cài đặt: " + ex.getMessage(),
"Lỗi",
JOptionPane.ERROR_MESSAGE);
JOptionPane.showMessageDialog(this, "Lỗi khi lưu cài đặt: " + ex.getMessage(), "Lỗi", JOptionPane.ERROR_MESSAGE);
}
}
}
}
@@ -1,73 +1,73 @@
package BsK.client.ui.component.common;
package BsK.client.ui.component.common;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import org.jdatepicker.*;
import org.jdatepicker.*;
import javax.swing.*;
import javax.swing.*;
public class DateLabelFormatter extends JFormattedTextField.AbstractFormatter {
public class DateLabelFormatter extends JFormattedTextField.AbstractFormatter {
private String datePattern = "dd/MM/yyyy";
private SimpleDateFormat dateFormatter = new SimpleDateFormat(datePattern, Locale.ENGLISH);
private String datePattern = "dd/MM/yyyy";
private SimpleDateFormat dateFormatter = new SimpleDateFormat(datePattern, Locale.ENGLISH);
public DateLabelFormatter() {
// Enable lenient parsing to be more flexible with user input
dateFormatter.setLenient(false);
}
public DateLabelFormatter() {
// Enable lenient parsing to be more flexible with user input
dateFormatter.setLenient(false);
}
@Override
public Object stringToValue(String text) throws ParseException {
if (text == null || text.trim().isEmpty()) {
@Override
public Object stringToValue(String text) throws ParseException {
if (text == null || text.trim().isEmpty()) {
return null;
}
// Clean up the input text
text = text.trim();
java.util.Date parsedDate;
// Handle different input formats that users might type
if (text.matches("\\d{1,2}/\\d{1,2}/\\d{4}")) {
// Standard format: dd/mm/yyyy or d/m/yyyy
parsedDate = (java.util.Date) dateFormatter.parseObject(text);
} else if (text.matches("\\d{1,2}-\\d{1,2}-\\d{4}")) {
// Handle dash format: dd-mm-yyyy
text = text.replace("-", "/");
parsedDate = (java.util.Date) dateFormatter.parseObject(text);
} else if (text.matches("\\d{1,2}\\.\\d{1,2}\\.\\d{4}")) {
// Handle dot format: dd.mm.yyyy
text = text.replace(".", "/");
parsedDate = (java.util.Date) dateFormatter.parseObject(text);
} else if (text.matches("\\d{8}")) {
// Handle compact format: ddmmyyyy
String day = text.substring(0, 2);
String month = text.substring(2, 4);
String year = text.substring(4, 8);
text = day + "/" + month + "/" + year;
parsedDate = (java.util.Date) dateFormatter.parseObject(text);
} else {
// If none of the formats match, try default parsing
parsedDate = (java.util.Date) dateFormatter.parseObject(text);
}
if (parsedDate != null) {
Calendar cal = Calendar.getInstance();
cal.setTime(parsedDate);
return cal;
}
return null;
}
// Clean up the input text
text = text.trim();
java.util.Date parsedDate;
@Override
public String valueToString(Object value) throws ParseException {
if (value != null) {
Calendar cal = (Calendar) value;
return dateFormatter.format(cal.getTime());
}
// Handle different input formats that users might type
if (text.matches("\\d{1,2}/\\d{1,2}/\\d{4}")) {
// Standard format: dd/mm/yyyy or d/m/yyyy
parsedDate = (java.util.Date) dateFormatter.parseObject(text);
} else if (text.matches("\\d{1,2}-\\d{1,2}-\\d{4}")) {
// Handle dash format: dd-mm-yyyy
text = text.replace("-", "/");
parsedDate = (java.util.Date) dateFormatter.parseObject(text);
} else if (text.matches("\\d{1,2}\\.\\d{1,2}\\.\\d{4}")) {
// Handle dot format: dd.mm.yyyy
text = text.replace(".", "/");
parsedDate = (java.util.Date) dateFormatter.parseObject(text);
} else if (text.matches("\\d{8}")) {
// Handle compact format: ddmmyyyy
String day = text.substring(0, 2);
String month = text.substring(2, 4);
String year = text.substring(4, 8);
text = day + "/" + month + "/" + year;
parsedDate = (java.util.Date) dateFormatter.parseObject(text);
} else {
// If none of the formats match, try default parsing
parsedDate = (java.util.Date) dateFormatter.parseObject(text);
return "";
}
if (parsedDate != null) {
Calendar cal = Calendar.getInstance();
cal.setTime(parsedDate);
return cal;
}
return null;
}
@Override
public String valueToString(Object value) throws ParseException {
if (value != null) {
Calendar cal = (Calendar) value;
return dateFormatter.format(cal.getTime());
}
return "";
}
}
}
@@ -97,7 +97,7 @@ public class MedicineDialog extends JDialog {
private void init(final Frame parent) {
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(1200, 700);
setSize(1200, 800);
setLocationRelativeTo(parent);
setResizable(true);
@@ -249,7 +249,7 @@ public class MedicineDialog extends JDialog {
gbc.gridx = 1;
gbc.weightx = 1.0;
medicineDescriptionField = new JTextArea(3, 20);
medicineDescriptionField = new JTextArea(3, 15);
medicineDescriptionField.setFont(textFont);
medicineDescriptionField.setLineWrap(true);
medicineDescriptionField.setWrapStyleWord(true);
@@ -808,11 +808,7 @@ public class MedicineDialog extends JDialog {
double price = priceField.getText().isEmpty() ? 0 : Double.parseDouble(priceField.getText());
double totalValue = quantity * price;
// Create formatter for Vietnamese locale
NumberFormat formatter = NumberFormat.getInstance(new Locale("vi", "VN"));
// Format the total and set the text
totalField.setText(formatter.format(totalValue));
totalField.setText(String.format("%.0f", totalValue));
} catch (NumberFormatException e) {
totalField.setText("Giá trị không hợp lệ");
@@ -109,15 +109,24 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
log.debug("Received GetCheckUpQueueRequest");
try {
ResultSet rs = statement.executeQuery(
"select a.checkup_id, a.checkup_date, c.customer_last_name, c.customer_first_name,\n" +
"d.doctor_first_name, d.doctor_last_name, a.suggestion, a.diagnosis, a.notes, a.status, a.customer_id, \n" +
"c.customer_number, c.customer_address, a.customer_weight, a.customer_height, c.customer_gender, c.customer_dob,\n" +
"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\n" +
"from checkup as a\n" +
"join customer as c on a.customer_id = c.customer_id\n" +
"join Doctor D on a.doctor_id = D.doctor_id\n" +
"where a.checkup_date >= CAST(strftime('%s', date('now', 'start of day')) AS INTEGER) * 1000\n" +
"AND a.checkup_date < CAST(strftime('%s', date('now', 'start of day', '+1 day')) AS INTEGER) * 1000"
"""
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
a.checkup_date >= CAST(strftime('%s', date('now', 'localtime', 'start of day')) AS INTEGER) * 1000
AND
a.checkup_date < CAST(strftime('%s', date('now', 'localtime', 'start of day', '+1 day')) AS INTEGER) * 1000"""
);
if (!rs.isBeforeFirst()) {
@@ -730,56 +739,58 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
Integer newPrescriptionId = null;
// 2. Handle Medicine Prescriptions
// First, clear old data for this checkup
try (PreparedStatement deleteOrderItemsStmt = conn.prepareStatement("DELETE FROM OrderItem WHERE checkup_id = ?")) {
deleteOrderItemsStmt.setInt(1, saveCheckupRequest.getCheckupId());
deleteOrderItemsStmt.executeUpdate();
}
try (PreparedStatement deleteMedicineOrderStmt = conn.prepareStatement("DELETE FROM MedicineOrder WHERE checkup_id = ?")) {
deleteMedicineOrderStmt.setInt(1, saveCheckupRequest.getCheckupId());
deleteMedicineOrderStmt.executeUpdate();
}
log.info("Cleared previous medicine prescription for checkup_id: {}", saveCheckupRequest.getCheckupId());
// A. Insert a new MedicineOrder record to get the auto-generated ID
String medicineOrderSql = """
INSERT INTO MedicineOrder (
checkup_id, customer_id, total_amount, payment_method,
status, payment_status, processed_by, notes
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""";
// Use RETURN_GENERATED_KEYS to get the new ID back
if (saveCheckupRequest.getMedicinePrescription() != null &&
saveCheckupRequest.getMedicinePrescription().length > 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.");
}
}
}
// First, clear old data for this checkup
try (PreparedStatement deleteOrderItemsStmt = conn.prepareStatement("DELETE FROM OrderItem WHERE checkup_id = ?")) {
deleteOrderItemsStmt.setInt(1, saveCheckupRequest.getCheckupId());
deleteOrderItemsStmt.executeUpdate();
}
try (PreparedStatement deleteMedicineOrderStmt = conn.prepareStatement("DELETE FROM MedicineOrder WHERE checkup_id = ?")) {
deleteMedicineOrderStmt.setInt(1, saveCheckupRequest.getCheckupId());
deleteMedicineOrderStmt.executeUpdate();
}
log.info("Cleared previous medicine prescription for checkup_id: {}", saveCheckupRequest.getCheckupId());
// A. Insert a new MedicineOrder record to get the auto-generated ID
String medicineOrderSql = """
INSERT INTO MedicineOrder (
checkup_id, customer_id, total_amount, payment_method,
status, payment_status, processed_by, notes
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""";
// Use RETURN_GENERATED_KEYS to get the new ID back
try (PreparedStatement medicineOrderStmt = conn.prepareStatement(medicineOrderSql, PreparedStatement.RETURN_GENERATED_KEYS)) {
// ... (your existing totalMedicineAmount calculation) ...
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.");
}
}
}
// C. Insert OrderItems using the new prescription_id
String orderItemSql = """
@@ -812,7 +823,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
// 3. Insert/Update Checkup with the correct newPrescriptionId
String checkupSql = """
INSERT INTO Checkup (
checkup_id, customer_id, doctor_id, checkup_date, suggestion, diagnosis,
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -823,14 +834,15 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
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
doctor_ultrasound_id = excluded.doctor_ultrasound_id, doctor_id = excluded.doctor_id,
checkup_date = excluded.checkup_date
""";
try (PreparedStatement checkupStmt = conn.prepareStatement(checkupSql)) {
checkupStmt.setInt(1, saveCheckupRequest.getCheckupId());
checkupStmt.setInt(2, saveCheckupRequest.getCustomerId());
checkupStmt.setInt(3, saveCheckupRequest.getDoctorId());
checkupStmt.setLong(4, saveCheckupRequest.getCheckupDate());
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
@@ -852,9 +864,30 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
checkupStmt.executeUpdate();
log.info("Checkup saved successfully");
}
// 4. Handle Service Prescriptions (Now safe with try-with-resources)
// ... (your existing service handling logic is fine, just wrap it) ...
// 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();
}
log.info("Service saved successfully");
}
conn.commit(); // Commit the entire transaction
log.info("Successfully saved checkup {} with all related data", saveCheckupRequest.getCheckupId());
@@ -960,7 +993,8 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
S.service_name,
CS.quantity,
S.service_cost,
CS.total_cost
CS.total_cost,
CS.notes
FROM CheckupService CS
JOIN Service S ON CS.service_id = S.service_id
WHERE CS.checkup_id = ?
@@ -977,7 +1011,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
ser[2] = serRs.getString("quantity");
ser[3] = serRs.getString("service_cost");
ser[4] = serRs.getString("total_cost");
ser[5] = ""; // Notes - not available in CheckupService table
ser[5] = serRs.getString("notes");
serList.add(ser);
}
servicePrescription = serList.toArray(new String[0][]);
@@ -2169,8 +2203,8 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
"from checkup as a\n" +
"join customer as c on a.customer_id = c.customer_id\n" +
"join Doctor D on a.doctor_id = D.doctor_id\n" +
"where a.checkup_date >= CAST(strftime('%s', date('now', 'start of day')) AS INTEGER) * 1000\n" +
"AND a.checkup_date < CAST(strftime('%s', date('now', 'start of day', '+1 day')) AS INTEGER) * 1000"
"where a.checkup_date >= CAST(strftime('%s', date('now', 'localtime', 'start of day')) AS INTEGER) * 1000\n" +
"AND a.checkup_date < CAST(strftime('%s', date('now', 'localtime', 'start of day', '+1 day')) AS INTEGER) * 1000"
);
String[][] resultArray;
Binary file not shown.