From c40e92462cbfa254de888897d99eee895f67626a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=AA=20Th=C3=A0nh=20=C4=90=E1=BA=A1t?= <91755733+lds217@users.noreply.github.com> Date: Thu, 31 Jul 2025 17:54:30 +0700 Subject: [PATCH] [update] Refactor CheckUpPage to improve PDF upload functionality by including checkup ID in upload requests. Update MedicineManagementPanel and ServiceManagementPanel to incorporate soft delete and supplement status checkboxes. Enhance data retrieval in ServerHandler for medicines and services, ensuring accurate handling of deleted status. Remove unused print dialog components to streamline the codebase. --- .../ui/component/CheckUpPage/CheckUpPage.java | 34 +- .../PrintDialog/MedicineInvoice.java | 5 +- .../PrintDialog/UltrasoundResult.java | 2 +- .../PrintDialog/print_forms/InvoiceItem.java | 2 +- .../print_forms/medserinvoice.jrxml | Bin 0 -> 39360 bytes .../print_forms/test_ultrasound_print.java | 2 +- .../print_forms/ultrasoundresult.jrxml | 0 .../ultrasoundresult_potrait.jrxml | 0 .../DataDialog/MedicineManagementPanel.java | 187 ++++-- .../DataDialog/ServiceManagementPanel.java | 154 ++--- .../common/MedicineDialog/MedicineDialog.java | 82 +-- .../print_forms/medserinvoice.jrxml | 621 ------------------ .../PrintDialog/print_forms/test_print.java | 106 --- .../common/ServiceDialog/ServiceDialog.java | 25 +- src/main/java/BsK/common/entity/Medicine.java | 14 +- src/main/java/BsK/common/entity/Service.java | 8 +- .../common/packet/req/AddCheckupRequest.java | 10 +- .../common/packet/req/AddMedicineRequest.java | 8 +- .../common/packet/req/AddPatientRequest.java | 14 +- .../common/packet/req/AddServiceRequest.java | 13 + .../common/packet/req/CallPatientRequest.java | 8 +- .../packet/req/EditMedicineRequest.java | 19 + .../common/packet/req/EditServiceRequest.java | 14 + .../common/packet/req/GetAllUserRequest.java | 11 + .../common/packet/res/GetMedInfoResponse.java | 2 +- .../common/packet/res/GetSerInfoResponse.java | 2 +- .../server/network/handler/ServerHandler.java | 269 ++++++-- src/main/resources/database/BSK.db | Bin 1339392 -> 1339392 bytes 28 files changed, 585 insertions(+), 1027 deletions(-) rename src/main/java/BsK/client/ui/component/{common => CheckUpPage}/PrintDialog/MedicineInvoice.java (99%) rename src/main/java/BsK/client/ui/component/{common => CheckUpPage}/PrintDialog/UltrasoundResult.java (99%) rename src/main/java/BsK/client/ui/component/{common => CheckUpPage}/PrintDialog/print_forms/InvoiceItem.java (97%) create mode 100644 src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/medserinvoice.jrxml rename src/main/java/BsK/client/ui/component/{common => CheckUpPage}/PrintDialog/print_forms/test_ultrasound_print.java (98%) rename src/main/java/BsK/client/ui/component/{common => CheckUpPage}/PrintDialog/print_forms/ultrasoundresult.jrxml (100%) rename src/main/java/BsK/client/ui/component/{common => CheckUpPage}/PrintDialog/print_forms/ultrasoundresult_potrait.jrxml (100%) delete mode 100644 src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/medserinvoice.jrxml delete mode 100644 src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/test_print.java create mode 100644 src/main/java/BsK/common/packet/req/AddServiceRequest.java create mode 100644 src/main/java/BsK/common/packet/req/EditMedicineRequest.java create mode 100644 src/main/java/BsK/common/packet/req/EditServiceRequest.java create mode 100644 src/main/java/BsK/common/packet/req/GetAllUserRequest.java diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java index 3fcedf6..c11606a 100644 --- a/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/CheckUpPage.java @@ -10,11 +10,11 @@ import BsK.client.network.handler.ResponseListener; import BsK.client.ui.component.common.AddDialog.AddDialog; import BsK.client.ui.component.common.HistoryViewDialog.HistoryViewDialog; import BsK.client.ui.component.common.MedicineDialog.MedicineDialog; -import BsK.client.ui.component.common.PrintDialog.MedicineInvoice; -import BsK.client.ui.component.common.PrintDialog.UltrasoundResult; import BsK.client.ui.component.common.ServiceDialog.ServiceDialog; import BsK.client.ui.component.common.TemplateDialog.TemplateDialog; import BsK.client.ui.component.MainFrame; +import BsK.client.ui.component.CheckUpPage.PrintDialog.MedicineInvoice; +import BsK.client.ui.component.CheckUpPage.PrintDialog.UltrasoundResult; import BsK.client.ui.component.common.DateLabelFormatter; import BsK.client.ui.component.common.NavBar; import BsK.client.ui.component.common.RoundedPanel; @@ -1909,8 +1909,8 @@ public class CheckUpPage extends JPanel { ultrasoundDoctorNameForPrint, datePicker.getJFormattedTextField().getText(), TextUtils.scaleRtfFontSize(getRtfContentAsString()), - conclusionField.getText(), - suggestionField.getText(), + conclusionField.getText() == null ? "" : conclusionField.getText(), + suggestionField.getText() == null ? "" : suggestionField.getText(), recheckupDatePicker.getJFormattedTextField().getText(), selectedImagesForPrint, (String) orientationComboBox.getSelectedItem(), // Use selected orientation @@ -1941,7 +1941,7 @@ public class CheckUpPage extends JPanel { // } String statusToSavePrint = (String) statusComboBox.getSelectedItem(); handleSave(); - afterSaveActions(statusToSavePrint, "Đã lưu. Đang gửi lệnh in..."); + // Step 4: Finally, show print dialog with the pre-made object. try { @@ -1952,7 +1952,8 @@ public class CheckUpPage extends JPanel { } // Step 5: Upload PDF to backend after successful print dialog - uploadPdfToBackend(ultrasoundResultPrint, "ultrasound_result"); + uploadPdfToBackend(ultrasoundResultPrint, "ultrasound_result", checkupIdField.getText()); + afterSaveActions(statusToSavePrint, "Đã lưu. Đang gửi lệnh in..."); break; case "loupe": // This case now handles "Lưu & Xem" // Step 1: Validate conditions first. @@ -1990,8 +1991,8 @@ public class CheckUpPage extends JPanel { ultrasoundDoctorNameForView, datePicker.getJFormattedTextField().getText(), TextUtils.scaleRtfFontSize(getRtfContentAsString()), - conclusionField.getText(), - suggestionField.getText(), + conclusionField.getText() == null ? "" : conclusionField.getText(), + suggestionField.getText() == null ? "" : suggestionField.getText(), recheckupDatePicker.getJFormattedTextField().getText(), selectedImagesForPrint, (String) orientationComboBox.getSelectedItem(), // Use selected orientation @@ -2019,13 +2020,14 @@ public class CheckUpPage extends JPanel { // NO automatic status change for "Lưu & Xem" String statusToSaveView = (String) statusComboBox.getSelectedItem(); handleSave(); - afterSaveActions(statusToSaveView, "Đã lưu. Đang tạo bản xem trước..."); + // Step 4: Finally, show the viewer with the pre-made object. JasperViewer.viewReport(jasperPrintToView, false); // Step 5: Upload PDF to backend after successful view - uploadPdfToBackend(ultrasoundResultView, "ultrasound_result"); + uploadPdfToBackend(ultrasoundResultView, "ultrasound_result", checkupIdField.getText()); + afterSaveActions(statusToSaveView, "Đã lưu. Đang tạo bản xem trước..."); break; } } @@ -3692,11 +3694,7 @@ public class CheckUpPage extends JPanel { * @param ultrasoundResult The UltrasoundResult object to export as PDF * @param pdfType The type of PDF ("ultrasound_result" or "medserinvoice") */ - private void uploadPdfToBackend(UltrasoundResult ultrasoundResult, String pdfType) { - if (currentCheckupIdForMedia == null) { - log.warn("No current checkup ID for PDF upload"); - return; - } + private void uploadPdfToBackend(UltrasoundResult ultrasoundResult, String pdfType, String checkupId) { // Run PDF export and upload in background thread to avoid blocking UI CompletableFuture.runAsync(() -> { @@ -3709,17 +3707,17 @@ public class CheckUpPage extends JPanel { // Create and send upload request UploadCheckupPdfRequest request = new UploadCheckupPdfRequest( - currentCheckupIdForMedia, + checkupId, pdfBytes, fileName, pdfType ); NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); - log.info("Sent PDF upload request for checkup: {}, type: {}", currentCheckupIdForMedia, pdfType); + log.info("Sent PDF upload request for checkup: {}, type: {}", checkupId, pdfType); } catch (Exception e) { - log.error("Failed to export or upload PDF for checkup: {}", currentCheckupIdForMedia, e); + log.error("Failed to export or upload PDF for checkup: {}", checkupId, e); SwingUtilities.invokeLater(() -> { JOptionPane.showMessageDialog(this, "Lỗi khi tạo hoặc tải PDF: " + e.getMessage(), diff --git a/src/main/java/BsK/client/ui/component/common/PrintDialog/MedicineInvoice.java b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/MedicineInvoice.java similarity index 99% rename from src/main/java/BsK/client/ui/component/common/PrintDialog/MedicineInvoice.java rename to src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/MedicineInvoice.java index 8e6446d..dfa3ff5 100644 --- a/src/main/java/BsK/client/ui/component/common/PrintDialog/MedicineInvoice.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/MedicineInvoice.java @@ -1,8 +1,7 @@ -package BsK.client.ui.component.common.PrintDialog; +package BsK.client.ui.component.CheckUpPage.PrintDialog; import BsK.client.LocalStorage; -import BsK.client.ui.component.common.PrintDialog.print_forms.InvoiceItem; - +import BsK.client.ui.component.CheckUpPage.PrintDialog.print_forms.InvoiceItem; // JasperReports imports import net.sf.jasperreports.engine.*; import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource; diff --git a/src/main/java/BsK/client/ui/component/common/PrintDialog/UltrasoundResult.java b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/UltrasoundResult.java similarity index 99% rename from src/main/java/BsK/client/ui/component/common/PrintDialog/UltrasoundResult.java rename to src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/UltrasoundResult.java index bd79768..59a263d 100644 --- a/src/main/java/BsK/client/ui/component/common/PrintDialog/UltrasoundResult.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/UltrasoundResult.java @@ -1,4 +1,4 @@ -package BsK.client.ui.component.common.PrintDialog; +package BsK.client.ui.component.CheckUpPage.PrintDialog; import BsK.client.LocalStorage; import com.itextpdf.io.font.constants.StandardFonts; diff --git a/src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/InvoiceItem.java b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/InvoiceItem.java similarity index 97% rename from src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/InvoiceItem.java rename to src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/InvoiceItem.java index 16329ab..bdc973b 100644 --- a/src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/InvoiceItem.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/InvoiceItem.java @@ -1,4 +1,4 @@ -package BsK.client.ui.component.common.PrintDialog.print_forms; +package BsK.client.ui.component.CheckUpPage.PrintDialog.print_forms; /** * Represents a single item on the invoice, which can be a medicine, a service, or a supplement. diff --git a/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/medserinvoice.jrxml b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/medserinvoice.jrxml new file mode 100644 index 0000000000000000000000000000000000000000..861a71bd327bfe8b5d74611f600484615b594f01 GIT binary patch literal 39360 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

cvgSg=5dPM$-^?qpYyZg9pEKaLRl6V)Em%pvYp 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 Tj@%B)Pd&+sv%5(l+!y}<^0EJa literal 0 HcmV?d00001 diff --git a/src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/test_ultrasound_print.java b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/test_ultrasound_print.java similarity index 98% rename from src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/test_ultrasound_print.java rename to src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/test_ultrasound_print.java index 7ce72e8..4405d24 100644 --- a/src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/test_ultrasound_print.java +++ b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/test_ultrasound_print.java @@ -1,4 +1,4 @@ -package BsK.client.ui.component.common.PrintDialog.print_forms; +package BsK.client.ui.component.CheckUpPage.PrintDialog.print_forms; import net.sf.jasperreports.engine.JREmptyDataSource; import net.sf.jasperreports.engine.JasperReport; diff --git a/src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/ultrasoundresult.jrxml b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/ultrasoundresult.jrxml similarity index 100% rename from src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/ultrasoundresult.jrxml rename to src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/ultrasoundresult.jrxml diff --git a/src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/ultrasoundresult_potrait.jrxml b/src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/ultrasoundresult_potrait.jrxml similarity index 100% rename from src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/ultrasoundresult_potrait.jrxml rename to src/main/java/BsK/client/ui/component/CheckUpPage/PrintDialog/print_forms/ultrasoundresult_potrait.jrxml diff --git a/src/main/java/BsK/client/ui/component/DataDialog/MedicineManagementPanel.java b/src/main/java/BsK/client/ui/component/DataDialog/MedicineManagementPanel.java index ef613b2..fc8bd2c 100644 --- a/src/main/java/BsK/client/ui/component/DataDialog/MedicineManagementPanel.java +++ b/src/main/java/BsK/client/ui/component/DataDialog/MedicineManagementPanel.java @@ -3,6 +3,8 @@ package BsK.client.ui.component.DataDialog; import BsK.client.network.handler.ClientHandler; import BsK.client.network.handler.ResponseListener; import BsK.common.entity.Medicine; +import BsK.common.packet.req.AddMedicineRequest; +import BsK.common.packet.req.EditMedicineRequest; import BsK.common.packet.req.GetMedInfoRequest; import BsK.common.packet.res.GetMedInfoResponse; import BsK.common.util.network.NetworkUtil; @@ -14,7 +16,6 @@ import javax.swing.border.TitledBorder; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableRowSorter; import java.awt.*; import java.util.ArrayList; import java.util.List; @@ -25,12 +26,12 @@ import java.util.List; */ public class MedicineManagementPanel extends JPanel { - // --- UI Components + // --- UI Components --- private JTable medicineTable; private DefaultTableModel medicineTableModel; private JTextField medicineSearchField; - // --- Input Fields + // --- Input Fields --- private JTextField medicineNameField; private JTextField medicineCompanyField; private JTextArea medicineDescriptionField; @@ -38,15 +39,17 @@ public class MedicineManagementPanel extends JPanel { private JTextField medicinePriceField; private JSpinner morningSpinner, noonSpinner, eveningSpinner; private JTextArea noteField; + private JCheckBox chkIsDeleted; // Checkbox for soft delete + private JCheckBox chkIsSupplement; // Checkbox for supplement status // --- Action Buttons --- - private JButton btnAdd, btnEdit, btnDelete, btnClear; + private JButton btnAdd, btnEdit, btnClear; - // --- Data & State + // --- Data & State --- private List allMedicines = new ArrayList<>(); private String selectedMedicineId = null; - // --- Networking + // --- Networking --- private final ResponseListener getMedInfoResponseListener = this::handleGetMedInfoResponse; public MedicineManagementPanel() { @@ -91,7 +94,7 @@ public class MedicineManagementPanel extends JPanel { TitledBorder.LEADING, TitledBorder.TOP, titleFont, new Color(50, 50, 50) )); - + GridBagConstraints gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.anchor = GridBagConstraints.WEST; @@ -183,7 +186,7 @@ public class MedicineManagementPanel extends JPanel { medicinePriceField.setPreferredSize(textFieldSize); quantityPricePanel.add(medicinePriceField, gbc); gbc.weightx = 0.0; - + mainGbc.gridx = 0; mainGbc.gridy = 1; mainInputPanel.add(quantityPricePanel, mainGbc); @@ -230,7 +233,7 @@ public class MedicineManagementPanel extends JPanel { eveningSpinner.setPreferredSize(new Dimension(60,30)); dosagePanel.add(eveningSpinner, gbc); gbc.weightx = 0.0; - + mainGbc.gridx = 0; mainGbc.gridy = 2; mainInputPanel.add(dosagePanel, mainGbc); @@ -241,7 +244,7 @@ public class MedicineManagementPanel extends JPanel { TitledBorder.LEADING, TitledBorder.TOP, titleFont, new Color(50, 50, 50) )); - + gbc.gridy = 0; gbc.gridx = 0; JLabel noteLabel = new JLabel("Ghi chú:"); @@ -266,7 +269,7 @@ public class MedicineManagementPanel extends JPanel { mainGbc.gridx = 0; mainGbc.gridy = 3; mainInputPanel.add(notePanel, mainGbc); - + // --- Sub-Panel 5: Action Buttons --- mainGbc.gridx = 0; mainGbc.gridy = 4; mainGbc.fill = GridBagConstraints.HORIZONTAL; mainInputPanel.add(createMedicineButtonPanel(), mainGbc); @@ -283,60 +286,150 @@ public class MedicineManagementPanel extends JPanel { JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 5)); btnAdd = new JButton("Thêm mới"); btnEdit = new JButton("Chỉnh sửa"); - btnDelete = new JButton("Xoá"); btnClear = new JButton("Làm mới"); + Font checkboxFont = new Font("Arial", Font.BOLD, 13); + chkIsSupplement = new JCheckBox("Thực phẩm bổ sung"); + chkIsSupplement.setFont(checkboxFont); + chkIsDeleted = new JCheckBox("Ẩn (Xoá)"); + chkIsDeleted.setFont(checkboxFont); + Dimension btnSize = new Dimension(100, 35); btnAdd.setPreferredSize(btnSize); btnEdit.setPreferredSize(btnSize); - btnDelete.setPreferredSize(btnSize); btnClear.setPreferredSize(btnSize); + // --- Component Order Changed Here --- + buttonPanel.add(chkIsSupplement); + buttonPanel.add(chkIsDeleted); buttonPanel.add(btnAdd); buttonPanel.add(btnEdit); - buttonPanel.add(btnDelete); buttonPanel.add(btnClear); // --- Button Actions --- btnClear.addActionListener(e -> clearMedicineFields()); + // --- ADD MEDICINE ACTION --- btnAdd.addActionListener(e -> { - String name = medicineNameField.getText(); - if (name.trim().isEmpty()) { + // 1. Validate required fields + String name = medicineNameField.getText().trim(); + if (name.isEmpty()) { JOptionPane.showMessageDialog(this, "Tên thuốc không được để trống.", "Lỗi", JOptionPane.ERROR_MESSAGE); + medicineNameField.requestFocusInWindow(); return; } - // TODO: Implement Add Medicine network request - JOptionPane.showMessageDialog(this, "Chức năng 'Thêm mới' cho thuốc '" + name + "' sẽ được thực hiện tại đây.", "Thông báo", JOptionPane.INFORMATION_MESSAGE); + + String priceText = medicinePriceField.getText().trim(); + if (priceText.isEmpty()) { + JOptionPane.showMessageDialog(this, "Giá thuốc không được để trống.", "Lỗi", JOptionPane.ERROR_MESSAGE); + medicinePriceField.requestFocusInWindow(); + return; + } + + // 2. Gather data from all other fields + String company = medicineCompanyField.getText().trim(); + String description = medicineDescriptionField.getText().trim(); + String unit = medicineUnitField.getText().trim(); + Boolean isSupplement = chkIsSupplement.isSelected(); + Boolean isDeleted = false; // New medicines are active by default + + // 3. Parse price and handle potential errors + double price; + try { + price = Double.parseDouble(priceText); + if (price < 0) { + JOptionPane.showMessageDialog(this, "Giá thuốc không được là số âm.", "Lỗi", JOptionPane.ERROR_MESSAGE); + medicinePriceField.requestFocusInWindow(); + return; + } + } catch (NumberFormatException nfe) { + JOptionPane.showMessageDialog(this, "Giá thuốc phải là một con số hợp lệ.", "Lỗi", JOptionPane.ERROR_MESSAGE); + medicinePriceField.requestFocusInWindow(); + return; + } + + // 4. Construct the special 'preferedNote' string + String preferedNote = morningSpinner.getValue().toString() + "," + + noonSpinner.getValue().toString() + "," + + eveningSpinner.getValue().toString() + "," + + noteField.getText().trim(); + + // 5. Create the request packet and send it to the server + // IMPORTANT: Assumes your AddMedicineRequest packet is updated to include 'supplement' + AddMedicineRequest request = new AddMedicineRequest(name, company, description, unit, price, preferedNote, isSupplement, isDeleted); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); + + // 6. Provide feedback and reset the form + JOptionPane.showMessageDialog(this, "Yêu cầu thêm thuốc '" + name + "' đã được gửi.", "Thành công", JOptionPane.INFORMATION_MESSAGE); + clearMedicineFields(); }); + // --- EDIT MEDICINE ACTION --- btnEdit.addActionListener(e -> { + // 1. Check if a medicine has been selected if (selectedMedicineId == null) { JOptionPane.showMessageDialog(this, "Vui lòng chọn một loại thuốc để chỉnh sửa.", "Chưa chọn thuốc", JOptionPane.WARNING_MESSAGE); return; } - // TODO: Implement Edit Medicine network request - String name = medicineNameField.getText(); - JOptionPane.showMessageDialog(this, "Chức năng 'Chỉnh sửa' cho thuốc ID: " + selectedMedicineId + " (" + name + ") sẽ được thực hiện tại đây.", "Thông báo", JOptionPane.INFORMATION_MESSAGE); - }); - btnDelete.addActionListener(e -> { - if (selectedMedicineId == null) { - JOptionPane.showMessageDialog(this, "Vui lòng chọn một loại thuốc để xóa.", "Chưa chọn thuốc", JOptionPane.WARNING_MESSAGE); + // 2. Validate required fields + String name = medicineNameField.getText().trim(); + if (name.isEmpty()) { + JOptionPane.showMessageDialog(this, "Tên thuốc không được để trống.", "Lỗi", JOptionPane.ERROR_MESSAGE); + medicineNameField.requestFocusInWindow(); return; } - int choice = JOptionPane.showConfirmDialog(this, - "Bạn có chắc chắn muốn xóa thuốc '" + medicineNameField.getText() + "' không?", - "Xác nhận xóa", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); - if (choice == JOptionPane.YES_OPTION) { - // TODO: Implement Delete Medicine network request - JOptionPane.showMessageDialog(this, "Chức năng 'Xoá' cho thuốc ID: " + selectedMedicineId + " sẽ được thực hiện tại đây.", "Thông báo", JOptionPane.INFORMATION_MESSAGE); + + String priceText = medicinePriceField.getText().trim(); + if (priceText.isEmpty()) { + JOptionPane.showMessageDialog(this, "Giá thuốc không được để trống.", "Lỗi", JOptionPane.ERROR_MESSAGE); + medicinePriceField.requestFocusInWindow(); + return; } + + // 3. Gather data from all fields + String id = selectedMedicineId; + String company = medicineCompanyField.getText().trim(); + String description = medicineDescriptionField.getText().trim(); + String unit = medicineUnitField.getText().trim(); + Boolean isSupplement = chkIsSupplement.isSelected(); + Boolean isDeleted = chkIsDeleted.isSelected(); + + // 4. Parse price and handle errors + Double price; + try { + price = Double.parseDouble(priceText); + if (price < 0) { + JOptionPane.showMessageDialog(this, "Giá thuốc không được là số âm.", "Lỗi", JOptionPane.ERROR_MESSAGE); + medicinePriceField.requestFocusInWindow(); + return; + } + } catch (NumberFormatException nfe) { + JOptionPane.showMessageDialog(this, "Giá thuốc phải là một con số hợp lệ.", "Lỗi", JOptionPane.ERROR_MESSAGE); + medicinePriceField.requestFocusInWindow(); + return; + } + + // 5. Construct the 'preferedNote' string + String preferedNote = morningSpinner.getValue().toString() + "," + + noonSpinner.getValue().toString() + "," + + eveningSpinner.getValue().toString() + "," + + noteField.getText().trim(); + + // 6. Create the request packet and send it + // IMPORTANT: Assumes your EditMedicineRequest packet is updated to include 'supplement' + EditMedicineRequest request = new EditMedicineRequest(id, name, company, description, unit, price, preferedNote, isSupplement, isDeleted); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); + + // 7. Provide user feedback and clear the form + JOptionPane.showMessageDialog(this, "Yêu cầu chỉnh sửa thuốc '" + name + "' đã được gửi.", "Thành công", JOptionPane.INFORMATION_MESSAGE); + clearMedicineFields(); }); // Initial button states btnEdit.setEnabled(false); - btnDelete.setEnabled(false); + chkIsSupplement.setEnabled(true); // Can set for new items + chkIsDeleted.setEnabled(false); // Can only set for existing items return buttonPanel; } @@ -348,7 +441,7 @@ public class MedicineManagementPanel extends JPanel { TitledBorder.LEFT, TitledBorder.TOP, new Font("Arial", Font.BOLD, 14))); // --- Table Setup --- - String[] medicineColumns = {"ID", "Tên thuốc", "Công ty", "Tồn kho", "ĐVT", "Đơn giá"}; + String[] medicineColumns = {"ID", "Tên thuốc", "Bổ sung", "Trạng thái"}; medicineTableModel = new DefaultTableModel(medicineColumns, 0) { @Override public boolean isCellEditable(int row, int column) { return false; } @@ -376,6 +469,7 @@ public class MedicineManagementPanel extends JPanel { JPanel searchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); searchPanel.add(new JLabel("Tìm kiếm thuốc:")); medicineSearchField = new JTextField(25); + // FIX: Add the text field to the panel, not the panel to itself searchPanel.add(medicineSearchField); medicineSearchField.getDocument().addDocumentListener(new DocumentListener() { @@ -413,10 +507,15 @@ public class MedicineManagementPanel extends JPanel { medicineUnitField.setText(med.getUnit()); medicinePriceField.setText(med.getSellingPrice()); + // Set checkboxes based on the medicine's status + chkIsSupplement.setSelected("1".equals(med.getSupplement())); + chkIsDeleted.setSelected("1".equals(med.getDeleted())); + // Update button states btnAdd.setEnabled(false); btnEdit.setEnabled(true); - btnDelete.setEnabled(true); + chkIsSupplement.setEnabled(true); + chkIsDeleted.setEnabled(true); } private void clearMedicineFields() { @@ -426,13 +525,16 @@ public class MedicineManagementPanel extends JPanel { medicineDescriptionField.setText(""); medicineUnitField.setText(""); medicinePriceField.setText(""); + chkIsSupplement.setSelected(false); + chkIsDeleted.setSelected(false); medicineTable.clearSelection(); medicineNameField.requestFocusInWindow(); // Reset button states btnAdd.setEnabled(true); btnEdit.setEnabled(false); - btnDelete.setEnabled(false); + chkIsSupplement.setEnabled(true); + chkIsDeleted.setEnabled(false); } private void filterMedicineTable() { @@ -442,14 +544,19 @@ public class MedicineManagementPanel extends JPanel { medicineTableModel.setRowCount(0); // Clear the table before adding filtered results for (Medicine med : allMedicines) { - if (filterText.isEmpty() || TextUtils.removeAccents(med.getName().toLowerCase()).contains(lowerCaseFilterText)) { + // Check if name matches filter text + boolean nameMatches = filterText.isEmpty() || TextUtils.removeAccents(med.getName().toLowerCase()).contains(lowerCaseFilterText); + + if (nameMatches) { + // Determine the status text based on the flags + String supplementStatus = "1".equals(med.getSupplement()) ? "Có" : "Không"; + String deletedStatus = "0".equals(med.getDeleted()) ? "Đang bán" : "Đã ẩn"; + medicineTableModel.addRow(new Object[]{ med.getId(), med.getName(), - med.getCompany(), - med.getQuantity(), - med.getUnit(), - med.getSellingPrice() + supplementStatus, + deletedStatus }); } } diff --git a/src/main/java/BsK/client/ui/component/DataDialog/ServiceManagementPanel.java b/src/main/java/BsK/client/ui/component/DataDialog/ServiceManagementPanel.java index 66a4415..cd0fdfb 100644 --- a/src/main/java/BsK/client/ui/component/DataDialog/ServiceManagementPanel.java +++ b/src/main/java/BsK/client/ui/component/DataDialog/ServiceManagementPanel.java @@ -3,6 +3,8 @@ package BsK.client.ui.component.DataDialog; import BsK.client.network.handler.ClientHandler; import BsK.client.network.handler.ResponseListener; import BsK.common.entity.Service; +import BsK.common.packet.req.AddServiceRequest; +import BsK.common.packet.req.EditServiceRequest; import BsK.common.packet.req.GetSerInfoRequest; import BsK.common.packet.res.GetSerInfoResponse; import BsK.common.util.network.NetworkUtil; @@ -14,7 +16,6 @@ import javax.swing.border.TitledBorder; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableRowSorter; import java.awt.*; import java.util.ArrayList; import java.util.List; @@ -33,9 +34,10 @@ public class ServiceManagementPanel extends JPanel { // --- Input Fields (Left Side) --- private JTextField serviceNameField; private JTextField priceField; - private JTextArea noteField; + private JCheckBox chkIsDeleted; // Checkbox for soft delete - private JButton btnAdd, btnEdit, btnDelete, btnClear; + // --- Action Buttons --- + private JButton btnAdd, btnEdit, btnClear; // --- Data & State --- private List allServices = new ArrayList<>(); @@ -88,20 +90,11 @@ public class ServiceManagementPanel extends JPanel { mainGbc.gridy = 0; mainInputPanel.add(serviceInfoPanel, mainGbc); - JPanel notePanel = new JPanel(new GridBagLayout()); - notePanel.setBorder(BorderFactory.createTitledBorder( - BorderFactory.createEtchedBorder(), "Ghi chú", - javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.TOP, - titleFont, new Color(50, 50, 50) - )); mainGbc.gridy = 1; - mainInputPanel.add(notePanel, mainGbc); - - mainGbc.gridy = 2; mainInputPanel.add(createServiceButtonPanel(), mainGbc); - mainGbc.gridy = 3; - mainGbc.weighty = 1.0; + mainGbc.gridy = 2; + mainGbc.weighty = 1.0; // Pushes content to the top mainInputPanel.add(new JPanel(), mainGbc); GridBagConstraints gbc = new GridBagConstraints(); @@ -119,91 +112,87 @@ public class ServiceManagementPanel extends JPanel { serviceInfoPanel.add(nameLabel, gbc); gbc.gridx = 1; - gbc.gridwidth = 3; gbc.weightx = 1.0; serviceNameField = new JTextField(20); serviceNameField.setFont(textFont); serviceNameField.setPreferredSize(textFieldSize); serviceInfoPanel.add(serviceNameField, gbc); - gbc.gridwidth = 1; - gbc.weightx = 0.0; // --- Đơn giá --- gbc.gridy++; gbc.gridx = 0; + gbc.weightx = 0.0; JLabel priceLabel = new JLabel("Đơn giá (VNĐ):"); priceLabel.setFont(labelFont); serviceInfoPanel.add(priceLabel, gbc); gbc.gridx = 1; - gbc.gridwidth = 3; gbc.weightx = 1.0; priceField = new JTextField(10); priceField.setFont(textFont); priceField.setPreferredSize(textFieldSize); serviceInfoPanel.add(priceField, gbc); - gbc.gridwidth = 1; - gbc.weightx = 0.0; - - // --- Ghi chú --- - gbc.gridy = 0; - gbc.gridx = 0; - gbc.anchor = GridBagConstraints.NORTHWEST; - JLabel noteLabel = new JLabel("Ghi chú:"); - noteLabel.setFont(labelFont); - notePanel.add(noteLabel, gbc); - gbc.anchor = GridBagConstraints.WEST; - - gbc.gridx = 1; - gbc.weightx = 1.0; - gbc.weighty = 1.0; - gbc.fill = GridBagConstraints.BOTH; - noteField = new JTextArea(3, 20); - noteField.setFont(textFont); - noteField.setLineWrap(true); - noteField.setWrapStyleWord(true); - JScrollPane noteScrollPane = new JScrollPane(noteField); - noteScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); - notePanel.add(noteScrollPane, gbc); - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.weightx = 0.0; - gbc.weighty = 0.0; return mainInputPanel; } /** - * Creates the panel containing the "Thêm", "Sửa", "Xóa", "Làm mới" buttons. + * Creates the panel containing the action buttons and checkboxes. */ private JPanel createServiceButtonPanel() { JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 5)); btnAdd = new JButton("Thêm mới"); btnEdit = new JButton("Chỉnh sửa"); - btnDelete = new JButton("Xoá"); btnClear = new JButton("Làm mới"); + chkIsDeleted = new JCheckBox("Ẩn (Xoá)"); + chkIsDeleted.setFont(new Font("Arial", Font.BOLD, 13)); Dimension btnSize = new Dimension(100, 35); btnAdd.setPreferredSize(btnSize); btnEdit.setPreferredSize(btnSize); - btnDelete.setPreferredSize(btnSize); btnClear.setPreferredSize(btnSize); + // --- Component Order Changed Here --- + buttonPanel.add(chkIsDeleted); buttonPanel.add(btnAdd); buttonPanel.add(btnEdit); - buttonPanel.add(btnDelete); buttonPanel.add(btnClear); // Button Actions btnClear.addActionListener(e -> clearServiceFields()); btnAdd.addActionListener(e -> { - String name = serviceNameField.getText(); - if (name.trim().isEmpty()) { + String name = serviceNameField.getText().trim(); + if (name.isEmpty()) { JOptionPane.showMessageDialog(this, "Tên dịch vụ không được để trống.", "Lỗi", JOptionPane.ERROR_MESSAGE); + serviceNameField.requestFocusInWindow(); return; } - // TODO: Implement Add Service network request - JOptionPane.showMessageDialog(this, "Chức năng 'Thêm mới' cho dịch vụ '" + name + "' sẽ được thực hiện tại đây.", "Thông báo", JOptionPane.INFORMATION_MESSAGE); + String priceText = priceField.getText().trim(); + if (priceText.isEmpty()) { + JOptionPane.showMessageDialog(this, "Giá dịch vụ không được để trống.", "Lỗi", JOptionPane.ERROR_MESSAGE); + priceField.requestFocusInWindow(); + return; + } + + Double price; + try { + price = Double.parseDouble(priceText); + if (price < 0) { + JOptionPane.showMessageDialog(this, "Giá dịch vụ không được là số âm.", "Lỗi", JOptionPane.ERROR_MESSAGE); + return; + } + } catch (NumberFormatException nfe) { + JOptionPane.showMessageDialog(this, "Giá dịch vụ phải là một con số hợp lệ.", "Lỗi", JOptionPane.ERROR_MESSAGE); + return; + } + + Boolean deleted = false; // New services are active by default + AddServiceRequest request = new AddServiceRequest(name, price, deleted); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); + + JOptionPane.showMessageDialog(this, "Yêu cầu thêm dịch vụ '" + name + "' đã được gửi.", "Thành công", JOptionPane.INFORMATION_MESSAGE); + clearServiceFields(); }); btnEdit.addActionListener(e -> { @@ -211,28 +200,40 @@ public class ServiceManagementPanel extends JPanel { JOptionPane.showMessageDialog(this, "Vui lòng chọn một dịch vụ để chỉnh sửa.", "Chưa chọn dịch vụ", JOptionPane.WARNING_MESSAGE); return; } - // TODO: Implement Edit Service network request - String name = serviceNameField.getText(); - JOptionPane.showMessageDialog(this, "Chức năng 'Chỉnh sửa' cho dịch vụ ID: " + selectedServiceId + " (" + name + ") sẽ được thực hiện tại đây.", "Thông báo", JOptionPane.INFORMATION_MESSAGE); - }); - - btnDelete.addActionListener(e -> { - if (selectedServiceId == null) { - JOptionPane.showMessageDialog(this, "Vui lòng chọn một dịch vụ để xóa.", "Chưa chọn dịch vụ", JOptionPane.WARNING_MESSAGE); + String name = serviceNameField.getText().trim(); + if (name.isEmpty()) { + JOptionPane.showMessageDialog(this, "Tên dịch vụ không được để trống.", "Lỗi", JOptionPane.ERROR_MESSAGE); return; } - int choice = JOptionPane.showConfirmDialog(this, - "Bạn có chắc chắn muốn xóa dịch vụ '" + serviceNameField.getText() + "' không?", - "Xác nhận xóa", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); - if (choice == JOptionPane.YES_OPTION) { - // TODO: Implement Delete Service network request - JOptionPane.showMessageDialog(this, "Chức năng 'Xoá' cho dịch vụ ID: " + selectedServiceId + " sẽ được thực hiện tại đây.", "Thông báo", JOptionPane.INFORMATION_MESSAGE); + String priceText = priceField.getText().trim(); + if (priceText.isEmpty()) { + JOptionPane.showMessageDialog(this, "Giá dịch vụ không được để trống.", "Lỗi", JOptionPane.ERROR_MESSAGE); + return; } + + Double price; + try { + price = Double.parseDouble(priceText); + if (price < 0) { + JOptionPane.showMessageDialog(this, "Giá dịch vụ không được là số âm.", "Lỗi", JOptionPane.ERROR_MESSAGE); + return; + } + } catch (NumberFormatException nfe) { + JOptionPane.showMessageDialog(this, "Giá dịch vụ phải là một con số hợp lệ.", "Lỗi", JOptionPane.ERROR_MESSAGE); + return; + } + + Boolean deleted = chkIsDeleted.isSelected(); + EditServiceRequest request = new EditServiceRequest(selectedServiceId, name, price, deleted); + NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request); + + JOptionPane.showMessageDialog(this, "Yêu cầu chỉnh sửa dịch vụ '" + name + "' đã được gửi.", "Thành công", JOptionPane.INFORMATION_MESSAGE); + clearServiceFields(); }); // Initial button states btnEdit.setEnabled(false); - btnDelete.setEnabled(false); + chkIsDeleted.setEnabled(false); return buttonPanel; } @@ -247,7 +248,7 @@ public class ServiceManagementPanel extends JPanel { TitledBorder.LEFT, TitledBorder.TOP, new Font("Arial", Font.BOLD, 14))); // Table - String[] serviceColumns = {"ID", "Tên dịch vụ", "Đơn giá (VNĐ)"}; + String[] serviceColumns = {"ID", "Tên dịch vụ", "Đơn giá (VNĐ)", "Trạng thái"}; serviceTableModel = new DefaultTableModel(serviceColumns, 0) { @Override public boolean isCellEditable(int row, int column) { return false; } @@ -261,10 +262,9 @@ public class ServiceManagementPanel extends JPanel { serviceTable.getSelectionModel().addListSelectionListener(e -> { if (!e.getValueIsAdjusting() && serviceTable.getSelectedRow() != -1) { - // IMPORTANT: We need to find the original index from the 'allServices' list int viewRow = serviceTable.getSelectedRow(); String serviceId = (String) serviceTableModel.getValueAt(viewRow, 0); - + allServices.stream() .filter(service -> service.getId().equals(serviceId)) .findFirst() @@ -309,26 +309,28 @@ public class ServiceManagementPanel extends JPanel { selectedServiceId = service.getId(); serviceNameField.setText(service.getName()); priceField.setText(service.getCost()); - noteField.setText(""); // Assuming notes are not stored in the main service entity + + // Update checkbox state + chkIsDeleted.setSelected("1".equals(service.getDeleted())); // Update button states btnAdd.setEnabled(false); btnEdit.setEnabled(true); - btnDelete.setEnabled(true); + chkIsDeleted.setEnabled(true); } private void clearServiceFields() { selectedServiceId = null; serviceNameField.setText(""); priceField.setText(""); - noteField.setText(""); + chkIsDeleted.setSelected(false); serviceTable.clearSelection(); serviceNameField.requestFocusInWindow(); // Reset button states btnAdd.setEnabled(true); btnEdit.setEnabled(false); - btnDelete.setEnabled(false); + chkIsDeleted.setEnabled(false); } private void filterServiceTable() { @@ -339,10 +341,12 @@ public class ServiceManagementPanel extends JPanel { for (Service service : allServices) { if (filterText.isEmpty() || TextUtils.removeAccents(service.getName().toLowerCase()).contains(lowerCaseFilterText)) { + String status = "0".equals(service.getDeleted()) ? "Đang bán" : "Đã ẩn"; serviceTableModel.addRow(new Object[]{ service.getId(), service.getName(), - service.getCost() + service.getCost(), + status }); } } diff --git a/src/main/java/BsK/client/ui/component/common/MedicineDialog/MedicineDialog.java b/src/main/java/BsK/client/ui/component/common/MedicineDialog/MedicineDialog.java index 1521cc8..8bb06d2 100644 --- a/src/main/java/BsK/client/ui/component/common/MedicineDialog/MedicineDialog.java +++ b/src/main/java/BsK/client/ui/component/common/MedicineDialog/MedicineDialog.java @@ -37,15 +37,14 @@ public class MedicineDialog extends JDialog { private JTextField medicineCompanyField; private JTextArea medicineDescriptionField; private JSpinner quantitySpinner; - private JTextField quantityLeftField; private JComboBox UnitComboBox; private JTextField priceField; private JTextField totalField; private JSpinner morningSpinner, noonSpinner, eveningSpinner; private JTextArea noteField; private DefaultTableModel tableModel, selectedTableModel; - private String[] medcineColumns = {"ID", "Tên thuốc", "Công ty", "Mô tả", "Tồn kho", "ĐVT", "Giá"}; - private String[] suggestionColumns = {"Tên thuốc", "Tồn kho", "ĐVT", "Giá", "Supplement"}; + private String[] medcineColumns = {"ID", "Tên thuốc", "Công ty", "Mô tả", "ĐVT", "Giá"}; + private String[] suggestionColumns = {"Tên thuốc", "ĐVT", "Giá", "Supplement"}; private String[][] medicineData; private final ResponseListener getMedInfoResponseListener = this::getMedInfoHandler; private TableColumnModel columnModel; @@ -280,18 +279,6 @@ public class MedicineDialog extends JDialog { gbc.gridx = 2; gbc.weightx = 0.0; - JLabel quantityLeftLabel = new JLabel("Còn lại:"); - quantityLeftLabel.setFont(labelFont); - quantityPricePanel.add(quantityLeftLabel, gbc); - - gbc.gridx = 3; - gbc.weightx = 0.5; - quantityLeftField = new JTextField(5); - quantityLeftField.setFont(textFont); - quantityLeftField.setPreferredSize(textFieldSize); - quantityLeftField.setEditable(false); - quantityPricePanel.add(quantityLeftField, gbc); - gbc.weightx = 0.0; gbc.gridy++; gbc.gridx = 0; @@ -417,7 +404,6 @@ public class MedicineDialog extends JDialog { suggestionPopup.add(suggestionScrollPane, BorderLayout.CENTER); suggestionPopup.setFocusable(false); - String[] selectedColumnNames = {"ID", "Tên thuốc", "SL", "ĐVT", "Sáng", "Trưa", "Chiều", "Đơn giá", "Thành tiền", "Ghi chú", "Supplement"}; selectedTableModel = new DefaultTableModel(new String[][]{}, selectedColumnNames) { @Override @@ -561,7 +547,7 @@ public class MedicineDialog extends JDialog { } else { if (e.getKeyCode() == KeyEvent.VK_ENTER) { handleExactMatch(); - } + } } } }); @@ -601,10 +587,10 @@ public class MedicineDialog extends JDialog { // If there are changes but no items selected, show a confirmation if (medicinePrescription == null || medicinePrescription.length == 0) { int choice = JOptionPane.showConfirmDialog(this, - "Bạn đã xóa tất cả thuốc khỏi đơn. Bạn có muốn lưu thay đổi này không?", - "Xác nhận", - JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE); + "Bạn đã xóa tất cả thuốc khỏi đơn. Bạn có muốn lưu thay đổi này không?", + "Xác nhận", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); if (choice != JOptionPane.YES_OPTION) { return; } @@ -644,13 +630,15 @@ public class MedicineDialog extends JDialog { String lowerCaseFilterText = TextUtils.removeAccents(filterText.toLowerCase()); for (Medicine med : medicines) { - if (filterText.isEmpty() || TextUtils.removeAccents(med.getName().toLowerCase()).contains(lowerCaseFilterText)) { + // Check if the medicine is not deleted and matches the filter text + if ("0".equals(med.getDeleted()) && + (filterText.isEmpty() || TextUtils.removeAccents(med.getName().toLowerCase()).contains(lowerCaseFilterText))) { + filteredMedicines.add(med); // Show "Có" if supplement is "1", otherwise "Không" String isSupplementText = "1".equals(med.getSupplement()) ? "Có" : "Không"; filteredDisplayData.add(new String[]{ med.getName(), - med.getQuantity(), med.getUnit(), med.getSellingPrice(), isSupplementText @@ -677,17 +665,15 @@ public class MedicineDialog extends JDialog { medicineNameField.requestFocusInWindow(); } else { suggestionPopup.setVisible(false); - // clearInputFields(); // Don't clear fields if the user is typing } } private void resizeSuggestionTableColumns() { columnModel = suggestionTable.getColumnModel(); columnModel.getColumn(0).setPreferredWidth(180); // Tên thuốc - columnModel.getColumn(1).setPreferredWidth(50); // Tồn kho - columnModel.getColumn(2).setPreferredWidth(50); // ĐVT - columnModel.getColumn(3).setPreferredWidth(70); // Giá - columnModel.getColumn(4).setPreferredWidth(70); // Supplement + columnModel.getColumn(1).setPreferredWidth(60); // ĐVT + columnModel.getColumn(2).setPreferredWidth(70); // Giá + columnModel.getColumn(3).setPreferredWidth(70); // Supplement } private void handleMedicineTableRowSelection(int viewRow) { @@ -709,7 +695,8 @@ public class MedicineDialog extends JDialog { String normalizedSearchName = TextUtils.removeAccents(searchName.toLowerCase()); for (Medicine med : medicines) { - if (TextUtils.removeAccents(med.getName().toLowerCase()).equals(normalizedSearchName)) { + // Also check the deleted flag here for consistency + if ("0".equals(med.getDeleted()) && TextUtils.removeAccents(med.getName().toLowerCase()).equals(normalizedSearchName)) { foundMedicine = med; break; } @@ -733,7 +720,6 @@ public class MedicineDialog extends JDialog { medicineCompanyField.setText(medicine.getCompany()); medicineDescriptionField.setText(medicine.getDescription()); - quantityLeftField.setText(medicine.getQuantity()); UnitComboBox.setSelectedItem(medicine.getUnit()); priceField.setText(medicine.getSellingPrice()); @@ -771,7 +757,6 @@ public class MedicineDialog extends JDialog { // Don't clear the name field as the user is typing in it medicineCompanyField.setText(""); medicineDescriptionField.setText(""); - quantityLeftField.setText(""); UnitComboBox.setSelectedIndex(0); priceField.setText(""); totalField.setText(""); @@ -810,32 +795,27 @@ public class MedicineDialog extends JDialog { // Prioritize selection from the popup table if it's open and has a selection if (suggestionPopup.isVisible() && selectedRowInSuggestionTable != -1) { selectedMedicine = filteredMedicines.get(suggestionTable.convertRowIndexToModel(selectedRowInSuggestionTable)); - medicineId = selectedMedicine.getId(); - name = selectedMedicine.getName(); - unit = selectedMedicine.getUnit(); - currentPrice = selectedMedicine.getSellingPrice(); } else { // Otherwise, try to find an exact match from the text field String normalizedSearchName = TextUtils.removeAccents(searchName.toLowerCase()); - for (Medicine med : medicines) { - if (TextUtils.removeAccents(med.getName().toLowerCase()).equals(normalizedSearchName)) { + // Also check the deleted flag here + if ("0".equals(med.getDeleted()) && TextUtils.removeAccents(med.getName().toLowerCase()).equals(normalizedSearchName)) { selectedMedicine = med; break; } } - - if (selectedMedicine == null) { - JOptionPane.showMessageDialog(this, "Không tìm thấy thuốc khớp với: '" + searchName + "'. Vui lòng chọn từ danh sách gợi ý.", "Lỗi", JOptionPane.ERROR_MESSAGE); - return; - } - medicineId = selectedMedicine.getId(); - name = selectedMedicine.getName(); - unit = selectedMedicine.getUnit(); - currentPrice = selectedMedicine.getSellingPrice(); } - // Get supplement status + if (selectedMedicine == null) { + JOptionPane.showMessageDialog(this, "Không tìm thấy thuốc khớp với: '" + searchName + "'. Vui lòng chọn từ danh sách gợi ý.", "Lỗi", JOptionPane.ERROR_MESSAGE); + return; + } + + medicineId = selectedMedicine.getId(); + name = selectedMedicine.getName(); + unit = selectedMedicine.getUnit(); + currentPrice = selectedMedicine.getSellingPrice(); supplement = selectedMedicine.getSupplement(); int quantity = (Integer) quantitySpinner.getValue(); @@ -862,9 +842,9 @@ public class MedicineDialog extends JDialog { } private void removeSelectedMedicine() { - int selectedRow = chosenMedicineTable.getSelectedRow(); - if (selectedRow != -1) { - selectedTableModel.removeRow(selectedRow); + int selectedRow = chosenMedicineTable.getSelectedRow(); + if (selectedRow != -1) { + selectedTableModel.removeRow(selectedRow); } else { JOptionPane.showMessageDialog(this, "Vui lòng chọn một thuốc từ bảng dưới để xóa.", "Chưa chọn thuốc", JOptionPane.WARNING_MESSAGE); } @@ -972,4 +952,4 @@ public class MedicineDialog extends JDialog { frame.setVisible(true); }); } -} +} \ No newline at end of file diff --git a/src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/medserinvoice.jrxml b/src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/medserinvoice.jrxml deleted file mode 100644 index 48a416b..0000000 --- a/src/main/java/BsK/client/ui/component/common/PrintDialog/print_forms/medserinvoice.jrxml +++ /dev/null @@ -1,621 +0,0 @@ - - - - -