merge origin/main into morefiles, resolve all conflicts
This commit is contained in:
@@ -15,16 +15,19 @@ public class AppointmentDTO {
|
||||
|
||||
private SimpleIntegerProperty serviceId;
|
||||
private SimpleStringProperty serviceName;
|
||||
private SimpleIntegerProperty employeeId;
|
||||
private SimpleStringProperty employeeName;
|
||||
|
||||
private SimpleStringProperty appointmentDate;
|
||||
private SimpleStringProperty appointmentTime;
|
||||
private SimpleStringProperty appointmentStatus;
|
||||
|
||||
// Constructor
|
||||
public AppointmentDTO(int appointmentId,
|
||||
int customerId, String customerName,
|
||||
int petId, String petName,
|
||||
int serviceId, String serviceName,
|
||||
int employeeId,
|
||||
String employeeName,
|
||||
String appointmentDate,
|
||||
String appointmentTime,
|
||||
String appointmentStatus) {
|
||||
@@ -36,12 +39,13 @@ public class AppointmentDTO {
|
||||
this.petName = new SimpleStringProperty(petName);
|
||||
this.serviceId = new SimpleIntegerProperty(serviceId);
|
||||
this.serviceName = new SimpleStringProperty(serviceName);
|
||||
this.employeeId = new SimpleIntegerProperty(employeeId);
|
||||
this.employeeName = new SimpleStringProperty(employeeName);
|
||||
this.appointmentDate = new SimpleStringProperty(appointmentDate);
|
||||
this.appointmentTime = new SimpleStringProperty(appointmentTime);
|
||||
this.appointmentStatus = new SimpleStringProperty(appointmentStatus);
|
||||
}
|
||||
|
||||
// Getters
|
||||
public int getAppointmentId() { return appointmentId.get(); }
|
||||
|
||||
public int getCustomerId() { return customerId.get(); }
|
||||
@@ -52,8 +56,10 @@ public class AppointmentDTO {
|
||||
|
||||
public int getServiceId() { return serviceId.get(); }
|
||||
public String getServiceName() { return serviceName.get(); }
|
||||
public int getEmployeeId() { return employeeId.get(); }
|
||||
public String getEmployeeName() { return employeeName.get(); }
|
||||
|
||||
public String getAppointmentDate() { return appointmentDate.get(); }
|
||||
public String getAppointmentTime() { return appointmentTime.get(); }
|
||||
public String getAppointmentStatus() { return appointmentStatus.get(); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.time.LocalDate;
|
||||
public class AdoptionRequest {
|
||||
private Long petId;
|
||||
private Long customerId;
|
||||
private Long employeeId;
|
||||
private Long sourceStoreId;
|
||||
private LocalDate adoptionDate;
|
||||
private String adoptionStatus;
|
||||
|
||||
@@ -27,6 +29,22 @@ public class AdoptionRequest {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public Long getEmployeeId() {
|
||||
return employeeId;
|
||||
}
|
||||
|
||||
public void setEmployeeId(Long employeeId) {
|
||||
this.employeeId = employeeId;
|
||||
}
|
||||
|
||||
public Long getSourceStoreId() {
|
||||
return sourceStoreId;
|
||||
}
|
||||
|
||||
public void setSourceStoreId(Long sourceStoreId) {
|
||||
this.sourceStoreId = sourceStoreId;
|
||||
}
|
||||
|
||||
public LocalDate getAdoptionDate() {
|
||||
return adoptionDate;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ public class AdoptionResponse {
|
||||
private Long adoptionId;
|
||||
private Long petId;
|
||||
private Long customerId;
|
||||
private Long employeeId;
|
||||
private String petName;
|
||||
private String customerName;
|
||||
private String employeeName;
|
||||
private LocalDate adoptionDate;
|
||||
private java.math.BigDecimal adoptionFee;
|
||||
private String adoptionStatus;
|
||||
@@ -39,6 +41,14 @@ public class AdoptionResponse {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public Long getEmployeeId() {
|
||||
return employeeId;
|
||||
}
|
||||
|
||||
public void setEmployeeId(Long employeeId) {
|
||||
this.employeeId = employeeId;
|
||||
}
|
||||
|
||||
public String getPetName() {
|
||||
return petName;
|
||||
}
|
||||
@@ -55,6 +65,14 @@ public class AdoptionResponse {
|
||||
this.customerName = customerName;
|
||||
}
|
||||
|
||||
public String getEmployeeName() {
|
||||
return employeeName;
|
||||
}
|
||||
|
||||
public void setEmployeeName(String employeeName) {
|
||||
this.employeeName = employeeName;
|
||||
}
|
||||
|
||||
public LocalDate getAdoptionDate() {
|
||||
return adoptionDate;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ public class DashboardResponse {
|
||||
private InventorySummary inventorySummary;
|
||||
private List<TopProduct> topProducts;
|
||||
private List<DailySales> dailySales;
|
||||
private List<PaymentMethodData> paymentMethods;
|
||||
private List<EmployeePerformanceData> employeePerformance;
|
||||
|
||||
public DashboardResponse() {
|
||||
}
|
||||
@@ -44,11 +46,28 @@ public class DashboardResponse {
|
||||
this.dailySales = dailySales;
|
||||
}
|
||||
|
||||
public List<PaymentMethodData> getPaymentMethods() {
|
||||
return paymentMethods;
|
||||
}
|
||||
|
||||
public void setPaymentMethods(List<PaymentMethodData> paymentMethods) {
|
||||
this.paymentMethods = paymentMethods;
|
||||
}
|
||||
|
||||
public List<EmployeePerformanceData> getEmployeePerformance() {
|
||||
return employeePerformance;
|
||||
}
|
||||
|
||||
public void setEmployeePerformance(List<EmployeePerformanceData> employeePerformance) {
|
||||
this.employeePerformance = employeePerformance;
|
||||
}
|
||||
|
||||
public static class SalesSummary {
|
||||
private BigDecimal totalRevenue;
|
||||
private Long totalSales;
|
||||
private BigDecimal totalRefunds;
|
||||
private Long totalRefundCount;
|
||||
private Long totalItemsSold;
|
||||
|
||||
public SalesSummary() {
|
||||
}
|
||||
@@ -84,6 +103,14 @@ public class DashboardResponse {
|
||||
public void setTotalRefundCount(Long totalRefundCount) {
|
||||
this.totalRefundCount = totalRefundCount;
|
||||
}
|
||||
|
||||
public Long getTotalItemsSold() {
|
||||
return totalItemsSold;
|
||||
}
|
||||
|
||||
public void setTotalItemsSold(Long totalItemsSold) {
|
||||
this.totalItemsSold = totalItemsSold;
|
||||
}
|
||||
}
|
||||
|
||||
public static class InventorySummary {
|
||||
@@ -118,4 +145,46 @@ public class DashboardResponse {
|
||||
this.outOfStockProducts = outOfStockProducts;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PaymentMethodData {
|
||||
private String paymentMethod;
|
||||
private Long count;
|
||||
|
||||
public String getPaymentMethod() {
|
||||
return paymentMethod;
|
||||
}
|
||||
|
||||
public void setPaymentMethod(String paymentMethod) {
|
||||
this.paymentMethod = paymentMethod;
|
||||
}
|
||||
|
||||
public Long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(Long count) {
|
||||
this.count = count;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmployeePerformanceData {
|
||||
private String employeeName;
|
||||
private BigDecimal revenue;
|
||||
|
||||
public String getEmployeeName() {
|
||||
return employeeName;
|
||||
}
|
||||
|
||||
public void setEmployeeName(String employeeName) {
|
||||
this.employeeName = employeeName;
|
||||
}
|
||||
|
||||
public BigDecimal getRevenue() {
|
||||
return revenue;
|
||||
}
|
||||
|
||||
public void setRevenue(BigDecimal revenue) {
|
||||
this.revenue = revenue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,28 +2,20 @@ package org.example.petshopdesktop.api.dto.appointment;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
|
||||
public class AppointmentRequest {
|
||||
private List<Long> petIds;
|
||||
private Long customerId;
|
||||
private Long storeId;
|
||||
private Long serviceId;
|
||||
private Long employeeId;
|
||||
private LocalDate appointmentDate;
|
||||
private LocalTime appointmentTime;
|
||||
private String appointmentStatus;
|
||||
private Long petId;
|
||||
|
||||
public AppointmentRequest() {
|
||||
}
|
||||
|
||||
public List<Long> getPetIds() {
|
||||
return petIds;
|
||||
}
|
||||
|
||||
public void setPetIds(List<Long> petIds) {
|
||||
this.petIds = petIds;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
@@ -48,6 +40,14 @@ public class AppointmentRequest {
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
public Long getEmployeeId() {
|
||||
return employeeId;
|
||||
}
|
||||
|
||||
public void setEmployeeId(Long employeeId) {
|
||||
this.employeeId = employeeId;
|
||||
}
|
||||
|
||||
public LocalDate getAppointmentDate() {
|
||||
return appointmentDate;
|
||||
}
|
||||
@@ -71,4 +71,12 @@ public class AppointmentRequest {
|
||||
public void setAppointmentStatus(String appointmentStatus) {
|
||||
this.appointmentStatus = appointmentStatus;
|
||||
}
|
||||
|
||||
public Long getPetId() {
|
||||
return petId;
|
||||
}
|
||||
|
||||
public void setPetId(Long petId) {
|
||||
this.petId = petId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,14 @@ public class AppointmentResponse {
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
private Long serviceId;
|
||||
private java.util.List<String> petNames;
|
||||
private java.util.List<Long> petIds;
|
||||
private String serviceName;
|
||||
private Long employeeId;
|
||||
private String employeeName;
|
||||
private LocalDate appointmentDate;
|
||||
private LocalTime appointmentTime;
|
||||
private String appointmentStatus;
|
||||
private String petName;
|
||||
private Long petId;
|
||||
|
||||
public AppointmentResponse() {
|
||||
}
|
||||
@@ -68,20 +70,20 @@ public class AppointmentResponse {
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
public java.util.List<String> getPetNames() {
|
||||
return petNames;
|
||||
public String getPetName() {
|
||||
return petName;
|
||||
}
|
||||
|
||||
public void setPetNames(java.util.List<String> petNames) {
|
||||
this.petNames = petNames;
|
||||
public void setPetName(String petName) {
|
||||
this.petName = petName;
|
||||
}
|
||||
|
||||
public java.util.List<Long> getPetIds() {
|
||||
return petIds;
|
||||
public Long getPetId() {
|
||||
return petId;
|
||||
}
|
||||
|
||||
public void setPetIds(java.util.List<Long> petIds) {
|
||||
this.petIds = petIds;
|
||||
public void setPetId(Long petId) {
|
||||
this.petId = petId;
|
||||
}
|
||||
|
||||
public String getServiceName() {
|
||||
@@ -92,6 +94,22 @@ public class AppointmentResponse {
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
public Long getEmployeeId() {
|
||||
return employeeId;
|
||||
}
|
||||
|
||||
public void setEmployeeId(Long employeeId) {
|
||||
this.employeeId = employeeId;
|
||||
}
|
||||
|
||||
public String getEmployeeName() {
|
||||
return employeeName;
|
||||
}
|
||||
|
||||
public void setEmployeeName(String employeeName) {
|
||||
this.employeeName = employeeName;
|
||||
}
|
||||
|
||||
public LocalDate getAppointmentDate() {
|
||||
return appointmentDate;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@ package org.example.petshopdesktop.api.dto.chat;
|
||||
|
||||
public class MessageRequest {
|
||||
private String content;
|
||||
private String attachmentUrl;
|
||||
private String attachmentName;
|
||||
private String attachmentMimeType;
|
||||
private Long attachmentSizeBytes;
|
||||
|
||||
public MessageRequest() {
|
||||
}
|
||||
@@ -17,4 +21,36 @@ public class MessageRequest {
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getAttachmentUrl() {
|
||||
return attachmentUrl;
|
||||
}
|
||||
|
||||
public void setAttachmentUrl(String attachmentUrl) {
|
||||
this.attachmentUrl = attachmentUrl;
|
||||
}
|
||||
|
||||
public String getAttachmentName() {
|
||||
return attachmentName;
|
||||
}
|
||||
|
||||
public void setAttachmentName(String attachmentName) {
|
||||
this.attachmentName = attachmentName;
|
||||
}
|
||||
|
||||
public String getAttachmentMimeType() {
|
||||
return attachmentMimeType;
|
||||
}
|
||||
|
||||
public void setAttachmentMimeType(String attachmentMimeType) {
|
||||
this.attachmentMimeType = attachmentMimeType;
|
||||
}
|
||||
|
||||
public Long getAttachmentSizeBytes() {
|
||||
return attachmentSizeBytes;
|
||||
}
|
||||
|
||||
public void setAttachmentSizeBytes(Long attachmentSizeBytes) {
|
||||
this.attachmentSizeBytes = attachmentSizeBytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ public class MessageResponse {
|
||||
private String content;
|
||||
private LocalDateTime timestamp;
|
||||
private Boolean isRead;
|
||||
private String attachmentUrl;
|
||||
private String attachmentName;
|
||||
private String attachmentMimeType;
|
||||
private Long attachmentSizeBytes;
|
||||
|
||||
public MessageResponse() {
|
||||
}
|
||||
@@ -60,4 +64,36 @@ public class MessageResponse {
|
||||
public void setIsRead(Boolean isRead) {
|
||||
this.isRead = isRead;
|
||||
}
|
||||
|
||||
public String getAttachmentUrl() {
|
||||
return attachmentUrl;
|
||||
}
|
||||
|
||||
public void setAttachmentUrl(String attachmentUrl) {
|
||||
this.attachmentUrl = attachmentUrl;
|
||||
}
|
||||
|
||||
public String getAttachmentName() {
|
||||
return attachmentName;
|
||||
}
|
||||
|
||||
public void setAttachmentName(String attachmentName) {
|
||||
this.attachmentName = attachmentName;
|
||||
}
|
||||
|
||||
public String getAttachmentMimeType() {
|
||||
return attachmentMimeType;
|
||||
}
|
||||
|
||||
public void setAttachmentMimeType(String attachmentMimeType) {
|
||||
this.attachmentMimeType = attachmentMimeType;
|
||||
}
|
||||
|
||||
public Long getAttachmentSizeBytes() {
|
||||
return attachmentSizeBytes;
|
||||
}
|
||||
|
||||
public void setAttachmentSizeBytes(Long attachmentSizeBytes) {
|
||||
this.attachmentSizeBytes = attachmentSizeBytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,14 @@ import java.util.List;
|
||||
public class PageResponse<T> {
|
||||
private List<T> content;
|
||||
|
||||
@JsonProperty("number")
|
||||
private int pageNumber;
|
||||
|
||||
@JsonProperty("size")
|
||||
private int pageSize;
|
||||
|
||||
private long totalElements;
|
||||
private int totalPages;
|
||||
private boolean last;
|
||||
private PageMetadata page;
|
||||
|
||||
public PageResponse() {
|
||||
}
|
||||
@@ -63,10 +62,71 @@ public class PageResponse<T> {
|
||||
}
|
||||
|
||||
public boolean isLast() {
|
||||
return last;
|
||||
if (last) {
|
||||
return true;
|
||||
}
|
||||
if (page != null) {
|
||||
return page.number >= Math.max(0, page.totalPages - 1);
|
||||
}
|
||||
return content == null || content.isEmpty();
|
||||
}
|
||||
|
||||
public void setLast(boolean last) {
|
||||
this.last = last;
|
||||
}
|
||||
|
||||
public PageMetadata getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(PageMetadata page) {
|
||||
this.page = page;
|
||||
if (page != null) {
|
||||
this.pageNumber = page.number;
|
||||
this.pageSize = page.size;
|
||||
this.totalElements = page.totalElements;
|
||||
this.totalPages = page.totalPages;
|
||||
this.last = page.number >= Math.max(0, page.totalPages - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class PageMetadata {
|
||||
private int size;
|
||||
private int number;
|
||||
private long totalElements;
|
||||
private int totalPages;
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(int size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public long getTotalElements() {
|
||||
return totalElements;
|
||||
}
|
||||
|
||||
public void setTotalElements(long totalElements) {
|
||||
this.totalElements = totalElements;
|
||||
}
|
||||
|
||||
public int getTotalPages() {
|
||||
return totalPages;
|
||||
}
|
||||
|
||||
public void setTotalPages(int totalPages) {
|
||||
this.totalPages = totalPages;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,12 @@ public class EmployeeRequest {
|
||||
private String password;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String fullName;
|
||||
private String email;
|
||||
private String phone;
|
||||
private String role;
|
||||
private String staffRole;
|
||||
private Long primaryStoreId;
|
||||
private Boolean active;
|
||||
|
||||
public String getUsername() { return username; }
|
||||
@@ -18,12 +21,18 @@ public class EmployeeRequest {
|
||||
public void setFirstName(String firstName) { this.firstName = firstName; }
|
||||
public String getLastName() { return lastName; }
|
||||
public void setLastName(String lastName) { this.lastName = lastName; }
|
||||
public String getFullName() { return fullName; }
|
||||
public void setFullName(String fullName) { this.fullName = fullName; }
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
public String getRole() { return role; }
|
||||
public void setRole(String role) { this.role = role; }
|
||||
public String getStaffRole() { return staffRole; }
|
||||
public void setStaffRole(String staffRole) { this.staffRole = staffRole; }
|
||||
public Long getPrimaryStoreId() { return primaryStoreId; }
|
||||
public void setPrimaryStoreId(Long primaryStoreId) { this.primaryStoreId = primaryStoreId; }
|
||||
public Boolean getActive() { return active; }
|
||||
public void setActive(Boolean active) { this.active = active; }
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.example.petshopdesktop.api.dto.employee;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class EmployeeResponse {
|
||||
private Long id;
|
||||
private Long employeeId;
|
||||
private Long userId;
|
||||
private String username;
|
||||
@@ -12,13 +13,17 @@ public class EmployeeResponse {
|
||||
private String email;
|
||||
private String phone;
|
||||
private String role;
|
||||
private String staffRole;
|
||||
private Long primaryStoreId;
|
||||
private Boolean active;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public Long getEmployeeId() { return employeeId; }
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public Long getEmployeeId() { return employeeId != null ? employeeId : id; }
|
||||
public void setEmployeeId(Long employeeId) { this.employeeId = employeeId; }
|
||||
public Long getUserId() { return userId; }
|
||||
public Long getUserId() { return userId != null ? userId : id; }
|
||||
public void setUserId(Long userId) { this.userId = userId; }
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
@@ -34,6 +39,10 @@ public class EmployeeResponse {
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
public String getRole() { return role; }
|
||||
public void setRole(String role) { this.role = role; }
|
||||
public String getStaffRole() { return staffRole; }
|
||||
public void setStaffRole(String staffRole) { this.staffRole = staffRole; }
|
||||
public Long getPrimaryStoreId() { return primaryStoreId; }
|
||||
public void setPrimaryStoreId(Long primaryStoreId) { this.primaryStoreId = primaryStoreId; }
|
||||
public Boolean getActive() { return active; }
|
||||
public void setActive(Boolean active) { this.active = active; }
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.example.petshopdesktop.api.dto.inventory;
|
||||
public class InventoryRequest {
|
||||
private Long prodId;
|
||||
private Integer quantity;
|
||||
private Long storeId;
|
||||
|
||||
public InventoryRequest() {
|
||||
}
|
||||
@@ -22,4 +23,12 @@ public class InventoryRequest {
|
||||
public void setQuantity(Integer quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ public class InventoryResponse {
|
||||
private Long prodId;
|
||||
private String productName;
|
||||
private String categoryName;
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
private Integer quantity;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
@@ -46,6 +48,22 @@ public class InventoryResponse {
|
||||
this.categoryName = categoryName;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
|
||||
public String getStoreName() {
|
||||
return storeName;
|
||||
}
|
||||
|
||||
public void setStoreName(String storeName) {
|
||||
this.storeName = storeName;
|
||||
}
|
||||
|
||||
public Integer getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ public class PetRequest {
|
||||
private Integer petAge;
|
||||
private String petStatus;
|
||||
private BigDecimal petPrice;
|
||||
private Long customerId;
|
||||
private Long storeId;
|
||||
|
||||
public PetRequest() {
|
||||
}
|
||||
@@ -60,4 +62,20 @@ public class PetRequest {
|
||||
public void setPetPrice(BigDecimal petPrice) {
|
||||
this.petPrice = petPrice;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ public class PetResponse {
|
||||
private String imageUrl;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
private Long customerId;
|
||||
private String customerName;
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
|
||||
public PetResponse() {
|
||||
}
|
||||
@@ -97,4 +101,36 @@ public class PetResponse {
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public String getCustomerName() {
|
||||
return customerName;
|
||||
}
|
||||
|
||||
public void setCustomerName(String customerName) {
|
||||
this.customerName = customerName;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
|
||||
public String getStoreName() {
|
||||
return storeName;
|
||||
}
|
||||
|
||||
public void setStoreName(String storeName) {
|
||||
this.storeName = storeName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,22 @@ public class DropdownApi {
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getProductCategories() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/product-categories");
|
||||
if (response == null || response.isEmpty()) {
|
||||
throw new IllegalStateException("Empty response from product categories endpoint");
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getPetSpecies() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/pet-species");
|
||||
if (response == null || response.isEmpty()) {
|
||||
throw new IllegalStateException("Empty response from pet species endpoint");
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getProducts() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/products");
|
||||
if (response == null || response.isEmpty()) {
|
||||
@@ -58,6 +74,14 @@ public class DropdownApi {
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getAppointmentCustomers() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/appointment-customers");
|
||||
if (response == null || response.isEmpty()) {
|
||||
throw new IllegalStateException("Empty response from appointment customers endpoint");
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getPets() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/pets");
|
||||
if (response == null || response.isEmpty()) {
|
||||
@@ -66,6 +90,22 @@ public class DropdownApi {
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getAdoptionPets() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/adoption-pets");
|
||||
if (response == null || response.isEmpty()) {
|
||||
throw new IllegalStateException("Empty response from adoption pets endpoint");
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getCustomerPets(Long customerId) throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/customers/" + customerId + "/pets");
|
||||
if (response == null || response.isEmpty()) {
|
||||
throw new IllegalStateException("Empty response from customer pets endpoint");
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getStores() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/stores");
|
||||
if (response == null || response.isEmpty()) {
|
||||
@@ -73,4 +113,20 @@ public class DropdownApi {
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getStoreEmployees(Long storeId) throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/stores/" + storeId + "/employees");
|
||||
if (response == null || response.isEmpty()) {
|
||||
throw new IllegalStateException("Empty response from store employees endpoint");
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
|
||||
public List<DropdownOption> getEmployees() throws Exception {
|
||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/employees");
|
||||
if (response == null || response.isEmpty()) {
|
||||
throw new IllegalStateException("Empty response from all employees endpoint");
|
||||
}
|
||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,20 @@ public class PetApi {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<PetResponse> listPets(String query) throws Exception {
|
||||
public List<PetResponse> listPets(String query, String species, String status, Long storeId) throws Exception {
|
||||
String path = "/api/v1/pets?page=0&size=1000";
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
if (species != null && !species.isEmpty()) {
|
||||
path += "&species=" + URLEncoder.encode(species, StandardCharsets.UTF_8);
|
||||
}
|
||||
if (status != null && !status.isEmpty()) {
|
||||
path += "&status=" + URLEncoder.encode(status, StandardCharsets.UTF_8);
|
||||
}
|
||||
if (storeId != null) {
|
||||
path += "&storeId=" + storeId;
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<PetResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
@@ -40,6 +49,14 @@ public class PetApi {
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public List<PetResponse> listPets(String query, String species, String status) throws Exception {
|
||||
return listPets(query, species, status, null);
|
||||
}
|
||||
|
||||
public List<PetResponse> listPets(String query) throws Exception {
|
||||
return listPets(query, null, null, null);
|
||||
}
|
||||
|
||||
public PetResponse createPet(PetRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/pets", request, PetResponse.class);
|
||||
}
|
||||
|
||||
@@ -24,11 +24,14 @@ public class ProductApi {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<ProductResponse> listProducts(String query) throws Exception {
|
||||
public List<ProductResponse> listProducts(String query, Long categoryId) throws Exception {
|
||||
String path = "/api/v1/products?page=0&size=1000";
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
if (categoryId != null) {
|
||||
path += "&categoryId=" + categoryId;
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<ProductResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
@@ -40,6 +43,10 @@ public class ProductApi {
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public List<ProductResponse> listProducts(String query) throws Exception {
|
||||
return listProducts(query, null);
|
||||
}
|
||||
|
||||
public ProductResponse createProduct(ProductRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/products", request, ProductResponse.class);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,39 @@ public class SaleApi {
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public List<SaleResponse> listAllSales(String query) throws Exception {
|
||||
int page = 0;
|
||||
int size = 250;
|
||||
List<SaleResponse> allSales = new java.util.ArrayList<>();
|
||||
|
||||
while (true) {
|
||||
String path = "/api/v1/sales?page=" + page + "&size=" + size;
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<SaleResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
new TypeReference<PageResponse<SaleResponse>>() {}
|
||||
);
|
||||
if (pageResponse == null) {
|
||||
throw new IllegalStateException("Null response from sales endpoint");
|
||||
}
|
||||
|
||||
if (pageResponse.getContent() != null) {
|
||||
allSales.addAll(pageResponse.getContent());
|
||||
}
|
||||
|
||||
if (pageResponse.isLast()) {
|
||||
break;
|
||||
}
|
||||
page++;
|
||||
}
|
||||
|
||||
return allSales;
|
||||
}
|
||||
|
||||
public SaleResponse getSale(Long id) throws Exception {
|
||||
return apiClient.get("/api/v1/sales/" + id, SaleResponse.class);
|
||||
}
|
||||
|
||||
@@ -92,4 +92,8 @@ public class UserSession {
|
||||
public boolean isAdmin() {
|
||||
return Role.ADMIN.equals(role);
|
||||
}
|
||||
|
||||
public boolean isStaff() {
|
||||
return Role.STAFF.equals(role);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.example.petshopdesktop.models.Adoption;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -42,6 +43,9 @@ public class AdoptionController {
|
||||
@FXML
|
||||
private TableColumn<Adoption, String> colCustomerName;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Adoption, String> colEmployeeName;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Adoption, String> colAdoptionDate;
|
||||
|
||||
@@ -64,12 +68,13 @@ public class AdoptionController {
|
||||
void initialize() {
|
||||
btnEdit.setDisable(true);
|
||||
btnDelete.setDisable(true);
|
||||
//Enable multiple selection
|
||||
|
||||
tvAdoptions.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
||||
|
||||
colAdoptionId.setCellValueFactory(new PropertyValueFactory<>("adoptionId"));
|
||||
colPetId.setCellValueFactory(new PropertyValueFactory<>("petName"));
|
||||
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
|
||||
colEmployeeName.setCellValueFactory(new PropertyValueFactory<>("employeeName"));
|
||||
colAdoptionDate.setCellValueFactory(new PropertyValueFactory<>("adoptionDate"));
|
||||
colAdoptionFee.setCellValueFactory(new PropertyValueFactory<>("adoptionFee"));
|
||||
colAdoptionStatus.setCellValueFactory(new PropertyValueFactory<>("adoptionStatus"));
|
||||
@@ -86,7 +91,6 @@ public class AdoptionController {
|
||||
displayFilteredAdoptions(newValue);
|
||||
});
|
||||
|
||||
//EventListener for DELETE key
|
||||
tvAdoptions.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||
if (tvAdoptions.getSelectionModel().getSelectedItem() != null) {
|
||||
@@ -104,11 +108,10 @@ public class AdoptionController {
|
||||
|
||||
@FXML
|
||||
void btnDeleteClicked(ActionEvent event) {
|
||||
//get selected adoptions
|
||||
|
||||
var selectedAdoptions = tvAdoptions.getSelectionModel().getSelectedItems();
|
||||
if (selectedAdoptions.isEmpty()) return;
|
||||
|
||||
//ask user to confirm
|
||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
question.setHeaderText("Please confirm delete");
|
||||
String message = selectedAdoptions.size() == 1
|
||||
@@ -118,7 +121,6 @@ public class AdoptionController {
|
||||
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
||||
Optional<ButtonType> result = question.showAndWait();
|
||||
|
||||
//if confirmed, start deletion
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
List<Long> ids = selectedAdoptions.stream()
|
||||
.map(a -> (long) a.getAdoptionId())
|
||||
@@ -141,7 +143,6 @@ public class AdoptionController {
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
//refresh display and reset inputs
|
||||
displayAdoptions();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
@@ -168,6 +169,7 @@ public class AdoptionController {
|
||||
List<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(filter);
|
||||
List<Adoption> adoptionList = adoptions.stream()
|
||||
.map(this::mapToAdoption)
|
||||
.sorted(Comparator.comparing(Adoption::getAdoptionDate).reversed())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
@@ -193,6 +195,7 @@ public class AdoptionController {
|
||||
List<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(null);
|
||||
List<Adoption> adoptionList = adoptions.stream()
|
||||
.map(this::mapToAdoption)
|
||||
.sorted(Comparator.comparing(Adoption::getAdoptionDate).reversed())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
@@ -249,8 +252,10 @@ public class AdoptionController {
|
||||
response.getAdoptionId().intValue(),
|
||||
response.getPetId() != null ? response.getPetId().intValue() : 0,
|
||||
response.getCustomerId() != null ? response.getCustomerId().intValue() : 0,
|
||||
response.getEmployeeId() != null ? response.getEmployeeId().intValue() : 0,
|
||||
response.getPetName(),
|
||||
response.getCustomerName(),
|
||||
response.getEmployeeName(),
|
||||
response.getAdoptionDate() != null ? response.getAdoptionDate().toString() : "",
|
||||
response.getAdoptionFee() != null ? response.getAdoptionFee().doubleValue() : 0.0,
|
||||
response.getAdoptionStatus()
|
||||
|
||||
@@ -9,9 +9,11 @@ import javafx.scene.control.Label;
|
||||
import org.example.petshopdesktop.api.dto.analytics.DailySales;
|
||||
import org.example.petshopdesktop.api.dto.analytics.DashboardResponse;
|
||||
import org.example.petshopdesktop.api.dto.analytics.TopProduct;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.AnalyticsApi;
|
||||
import org.example.petshopdesktop.api.endpoints.SaleApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
@@ -127,16 +129,17 @@ public class AnalyticsController {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
DashboardResponse dashboard = AnalyticsApi.getInstance().getDashboard(30, 10);
|
||||
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, Integer.MAX_VALUE, null);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
boolean isAdmin = UserSession.getInstance().isAdmin();
|
||||
loadSummaryData(dashboard);
|
||||
loadSalesOverTime(dashboard);
|
||||
loadTopProductsByRevenue(dashboard);
|
||||
loadTopProductsByQuantity(dashboard);
|
||||
loadPaymentMethodDistribution(sales);
|
||||
loadEmployeePerformance(sales);
|
||||
loadPaymentMethodDistribution(dashboard);
|
||||
loadEmployeePerformance(dashboard, isAdmin);
|
||||
applyRoleVisibility(isAdmin);
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
|
||||
lblError.setText("Error loading analytics data. Please try again.");
|
||||
@@ -144,6 +147,31 @@ public class AnalyticsController {
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
if (UserSession.getInstance().isStaff()) {
|
||||
try {
|
||||
DashboardResponse fallback = buildStaffFallbackDashboard();
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
loadSummaryData(fallback);
|
||||
loadSalesOverTime(fallback);
|
||||
loadTopProductsByRevenue(fallback);
|
||||
loadTopProductsByQuantity(fallback);
|
||||
loadPaymentMethodDistribution(fallback);
|
||||
loadEmployeePerformance(fallback, false);
|
||||
applyRoleVisibility(false);
|
||||
lblError.setVisible(false);
|
||||
} catch (Exception inner) {
|
||||
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", inner, "Rendering fallback analytics data");
|
||||
lblError.setText("Error loading analytics data. Please try again.");
|
||||
lblError.setVisible(true);
|
||||
}
|
||||
});
|
||||
return;
|
||||
} catch (Exception fallbackError) {
|
||||
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", fallbackError, "Loading fallback analytics data");
|
||||
}
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
|
||||
lblError.setText("Error loading analytics data. Please try again.");
|
||||
@@ -157,15 +185,12 @@ public class AnalyticsController {
|
||||
if (dashboard != null) {
|
||||
BigDecimal totalRevenue = BigDecimal.ZERO;
|
||||
Long totalSales = 0L;
|
||||
Long totalProducts = 0L;
|
||||
|
||||
if (dashboard.getSalesSummary() != null) {
|
||||
totalRevenue = dashboard.getSalesSummary().getTotalRevenue() != null ? dashboard.getSalesSummary().getTotalRevenue() : BigDecimal.ZERO;
|
||||
totalSales = dashboard.getSalesSummary().getTotalSales() != null ? dashboard.getSalesSummary().getTotalSales() : 0L;
|
||||
}
|
||||
|
||||
if (dashboard.getInventorySummary() != null) {
|
||||
totalProducts = dashboard.getInventorySummary().getTotalProducts() != null ? dashboard.getInventorySummary().getTotalProducts() : 0L;
|
||||
lblTotalItems.setText(wholeNumber.format(dashboard.getSalesSummary().getTotalItemsSold() != null ? dashboard.getSalesSummary().getTotalItemsSold() : 0L));
|
||||
} else {
|
||||
lblTotalItems.setText(wholeNumber.format(0));
|
||||
}
|
||||
|
||||
lblTotalRevenue.setText(currency.format(totalRevenue));
|
||||
@@ -176,7 +201,6 @@ public class AnalyticsController {
|
||||
avgTransaction = totalRevenue.divide(BigDecimal.valueOf(totalSales), 2, RoundingMode.HALF_UP);
|
||||
}
|
||||
lblAvgTransaction.setText(currency.format(avgTransaction));
|
||||
lblTotalItems.setText(wholeNumber.format(totalProducts));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,24 +267,14 @@ public class AnalyticsController {
|
||||
applyBarChartColor(chartTopQuantity, QUANTITY_COLOR);
|
||||
}
|
||||
|
||||
private void loadPaymentMethodDistribution(List<SaleResponse> sales) throws Exception {
|
||||
Map<String, Long> paymentMethodCount = sales.stream()
|
||||
.filter(sale -> sale.getIsRefund() == null || !sale.getIsRefund())
|
||||
.collect(Collectors.groupingBy(
|
||||
sale -> sale.getPaymentMethod() != null ? sale.getPaymentMethod() : "Unknown",
|
||||
Collectors.counting()
|
||||
));
|
||||
|
||||
private void loadPaymentMethodDistribution(DashboardResponse dashboard) throws Exception {
|
||||
chartPaymentMethods.getData().clear();
|
||||
|
||||
List<Map.Entry<String, Long>> paymentEntries = paymentMethodCount.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.toList();
|
||||
|
||||
for (Map.Entry<String, Long> entry : paymentEntries) {
|
||||
List<DashboardResponse.PaymentMethodData> paymentEntries = dashboard.getPaymentMethods() != null ? dashboard.getPaymentMethods() : List.of();
|
||||
for (DashboardResponse.PaymentMethodData entry : paymentEntries) {
|
||||
PieChart.Data slice = new PieChart.Data(
|
||||
entry.getKey() + " (" + entry.getValue() + ")",
|
||||
entry.getValue()
|
||||
entry.getPaymentMethod() + " (" + entry.getCount() + ")",
|
||||
entry.getCount()
|
||||
);
|
||||
chartPaymentMethods.getData().add(slice);
|
||||
}
|
||||
@@ -269,24 +283,14 @@ public class AnalyticsController {
|
||||
applyPieChartColors();
|
||||
}
|
||||
|
||||
private void loadEmployeePerformance(List<SaleResponse> sales) throws Exception {
|
||||
Map<String, Double> employeeRevenue = sales.stream()
|
||||
.filter(sale -> sale.getIsRefund() == null || !sale.getIsRefund())
|
||||
.filter(sale -> sale.getEmployeeName() != null)
|
||||
.collect(Collectors.groupingBy(
|
||||
SaleResponse::getEmployeeName,
|
||||
Collectors.summingDouble(sale -> sale.getTotalAmount() != null ? sale.getTotalAmount().doubleValue() : 0.0)
|
||||
));
|
||||
|
||||
private void loadEmployeePerformance(DashboardResponse dashboard, boolean isAdmin) throws Exception {
|
||||
XYChart.Series<String, Number> series = new XYChart.Series<>();
|
||||
series.setName("Revenue");
|
||||
series.setName(isAdmin ? "Revenue" : "My Revenue");
|
||||
|
||||
List<Map.Entry<String, Double>> employeeEntries = employeeRevenue.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.toList();
|
||||
|
||||
for (Map.Entry<String, Double> entry : employeeEntries) {
|
||||
series.getData().add(new XYChart.Data<>(entry.getKey(), entry.getValue()));
|
||||
List<DashboardResponse.EmployeePerformanceData> employeeEntries = dashboard.getEmployeePerformance() != null ? dashboard.getEmployeePerformance() : List.of();
|
||||
for (DashboardResponse.EmployeePerformanceData entry : employeeEntries) {
|
||||
BigDecimal revenue = entry.getRevenue() != null ? entry.getRevenue() : BigDecimal.ZERO;
|
||||
series.getData().add(new XYChart.Data<>(entry.getEmployeeName(), revenue));
|
||||
}
|
||||
|
||||
chartEmployeePerformance.getData().clear();
|
||||
@@ -294,6 +298,139 @@ public class AnalyticsController {
|
||||
applyBarChartColor(chartEmployeePerformance, EMPLOYEE_COLOR);
|
||||
}
|
||||
|
||||
private void applyRoleVisibility(boolean isAdmin) {
|
||||
chartEmployeePerformance.setVisible(isAdmin);
|
||||
chartEmployeePerformance.setManaged(isAdmin);
|
||||
if (chartEmployeePerformance.getParent() != null) {
|
||||
chartEmployeePerformance.getParent().setVisible(isAdmin);
|
||||
chartEmployeePerformance.getParent().setManaged(isAdmin);
|
||||
}
|
||||
}
|
||||
|
||||
private DashboardResponse buildStaffFallbackDashboard() throws Exception {
|
||||
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, Integer.MAX_VALUE, null);
|
||||
String employeeName = UserSession.getInstance().getEmployeeName();
|
||||
if (employeeName == null || employeeName.isBlank()) {
|
||||
employeeName = UserSession.getInstance().getUsername();
|
||||
}
|
||||
final String employeeNameFilter = employeeName;
|
||||
|
||||
List<SaleResponse> personalSales = sales.stream()
|
||||
.filter(sale -> sale.getEmployeeName() != null && sale.getEmployeeName().equalsIgnoreCase(employeeNameFilter))
|
||||
.toList();
|
||||
|
||||
DashboardResponse dashboard = new DashboardResponse();
|
||||
dashboard.setSalesSummary(buildSalesSummary(personalSales));
|
||||
dashboard.setDailySales(buildDailySales(personalSales, 30));
|
||||
dashboard.setTopProducts(buildTopProducts(personalSales, 10));
|
||||
dashboard.setPaymentMethods(buildPaymentMethods(personalSales));
|
||||
dashboard.setEmployeePerformance(List.of(new DashboardResponse.EmployeePerformanceData()));
|
||||
return dashboard;
|
||||
}
|
||||
|
||||
private DashboardResponse.SalesSummary buildSalesSummary(List<SaleResponse> sales) {
|
||||
DashboardResponse.SalesSummary summary = new DashboardResponse.SalesSummary();
|
||||
BigDecimal totalRevenue = BigDecimal.ZERO;
|
||||
long totalSales = 0L;
|
||||
BigDecimal totalRefunds = BigDecimal.ZERO;
|
||||
long totalRefundCount = 0L;
|
||||
long totalItemsSold = 0L;
|
||||
|
||||
for (SaleResponse sale : sales) {
|
||||
boolean refund = Boolean.TRUE.equals(sale.getIsRefund());
|
||||
BigDecimal amount = sale.getTotalAmount() != null ? sale.getTotalAmount() : BigDecimal.ZERO;
|
||||
if (refund) {
|
||||
totalRefunds = totalRefunds.add(amount);
|
||||
totalRefundCount++;
|
||||
continue;
|
||||
}
|
||||
totalRevenue = totalRevenue.add(amount);
|
||||
totalSales++;
|
||||
if (sale.getItems() != null) {
|
||||
totalItemsSold += sale.getItems().stream().mapToLong(item -> item.getQuantity() == null ? 0 : item.getQuantity()).sum();
|
||||
}
|
||||
}
|
||||
|
||||
summary.setTotalRevenue(totalRevenue);
|
||||
summary.setTotalSales(totalSales);
|
||||
summary.setTotalRefunds(totalRefunds);
|
||||
summary.setTotalRefundCount(totalRefundCount);
|
||||
summary.setTotalItemsSold(totalItemsSold);
|
||||
return summary;
|
||||
}
|
||||
|
||||
private List<DailySales> buildDailySales(List<SaleResponse> sales, int days) {
|
||||
Map<LocalDate, DailySales> daily = new LinkedHashMap<>();
|
||||
LocalDate start = LocalDate.now().minusDays(days - 1L);
|
||||
for (int i = 0; i < days; i++) {
|
||||
LocalDate date = start.plusDays(i);
|
||||
DailySales row = new DailySales();
|
||||
row.setDate(date.toString());
|
||||
row.setRevenue(BigDecimal.ZERO);
|
||||
row.setSalesCount(0L);
|
||||
daily.put(date, row);
|
||||
}
|
||||
for (SaleResponse sale : sales) {
|
||||
if (Boolean.TRUE.equals(sale.getIsRefund()) || sale.getSaleDate() == null) {
|
||||
continue;
|
||||
}
|
||||
LocalDate date = sale.getSaleDate().toLocalDate();
|
||||
DailySales row = daily.get(date);
|
||||
if (row != null) {
|
||||
row.setRevenue(row.getRevenue().add(sale.getTotalAmount() == null ? BigDecimal.ZERO : sale.getTotalAmount()));
|
||||
row.setSalesCount(row.getSalesCount() + 1);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(daily.values());
|
||||
}
|
||||
|
||||
private List<TopProduct> buildTopProducts(List<SaleResponse> sales, int top) {
|
||||
Map<Long, TopProduct> totals = new HashMap<>();
|
||||
for (SaleResponse sale : sales) {
|
||||
if (Boolean.TRUE.equals(sale.getIsRefund()) || sale.getItems() == null) {
|
||||
continue;
|
||||
}
|
||||
for (SaleItemResponse item : sale.getItems()) {
|
||||
if (item.getProdId() == null) {
|
||||
continue;
|
||||
}
|
||||
TopProduct product = totals.computeIfAbsent(item.getProdId(), id -> {
|
||||
TopProduct p = new TopProduct();
|
||||
p.setProductId(id);
|
||||
p.setProductName(item.getProductName());
|
||||
p.setQuantitySold(0L);
|
||||
p.setRevenue(BigDecimal.ZERO);
|
||||
return p;
|
||||
});
|
||||
long quantity = item.getQuantity() == null ? 0 : item.getQuantity();
|
||||
BigDecimal unitPrice = item.getUnitPrice() == null ? BigDecimal.ZERO : item.getUnitPrice();
|
||||
product.setQuantitySold(product.getQuantitySold() + quantity);
|
||||
product.setRevenue(product.getRevenue().add(unitPrice.multiply(BigDecimal.valueOf(quantity))));
|
||||
}
|
||||
}
|
||||
return totals.values().stream()
|
||||
.sorted((a, b) -> b.getRevenue().compareTo(a.getRevenue()))
|
||||
.limit(top)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<DashboardResponse.PaymentMethodData> buildPaymentMethods(List<SaleResponse> sales) {
|
||||
Map<String, Long> totals = new TreeMap<>();
|
||||
for (SaleResponse sale : sales) {
|
||||
if (Boolean.TRUE.equals(sale.getIsRefund())) {
|
||||
continue;
|
||||
}
|
||||
String method = sale.getPaymentMethod() == null || sale.getPaymentMethod().isBlank() ? "Unknown" : sale.getPaymentMethod();
|
||||
totals.merge(method, 1L, Long::sum);
|
||||
}
|
||||
return totals.entrySet().stream().map(entry -> {
|
||||
DashboardResponse.PaymentMethodData data = new DashboardResponse.PaymentMethodData();
|
||||
data.setPaymentMethod(entry.getKey());
|
||||
data.setCount(entry.getValue());
|
||||
return data;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
private void applyLineChartColor(LineChart<Number, Number> chart, String color) {
|
||||
Platform.runLater(() -> {
|
||||
for (XYChart.Series<Number, Number> series : chart.getData()) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialo
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Comparator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AppointmentController {
|
||||
@@ -32,6 +33,7 @@ public class AppointmentController {
|
||||
@FXML private TableColumn<AppointmentDTO,String> colAppointmentDate;
|
||||
@FXML private TableColumn<AppointmentDTO,String> colAppointmentTime;
|
||||
@FXML private TableColumn<AppointmentDTO,String> colCustomerName;
|
||||
@FXML private TableColumn<AppointmentDTO,String> colEmployeeName;
|
||||
@FXML private TableColumn<AppointmentDTO,String> colAppointmentStatus;
|
||||
|
||||
@FXML private Button btnAdd;
|
||||
@@ -45,7 +47,7 @@ public class AppointmentController {
|
||||
|
||||
@FXML
|
||||
public void initialize(){
|
||||
//Enable multiple selection
|
||||
|
||||
tvAppointments.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
||||
|
||||
colAppointmentId.setCellValueFactory(new PropertyValueFactory<>("appointmentId"));
|
||||
@@ -54,6 +56,7 @@ public class AppointmentController {
|
||||
colAppointmentDate.setCellValueFactory(new PropertyValueFactory<>("appointmentDate"));
|
||||
colAppointmentTime.setCellValueFactory(new PropertyValueFactory<>("appointmentTime"));
|
||||
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
|
||||
colEmployeeName.setCellValueFactory(new PropertyValueFactory<>("employeeName"));
|
||||
colAppointmentStatus.setCellValueFactory(new PropertyValueFactory<>("appointmentStatus"));
|
||||
|
||||
filtered = new FilteredList<>(appointments, a -> true);
|
||||
@@ -63,7 +66,6 @@ public class AppointmentController {
|
||||
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
|
||||
}
|
||||
|
||||
//EventListener for DELETE key
|
||||
tvAppointments.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||
if (tvAppointments.getSelectionModel().getSelectedItem() != null) {
|
||||
@@ -81,6 +83,7 @@ public class AppointmentController {
|
||||
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(null);
|
||||
List<AppointmentDTO> appointmentDTOs = responses.stream()
|
||||
.map(this::mapToAppointmentDTO)
|
||||
.sorted(Comparator.comparing((AppointmentDTO a) -> a.getAppointmentDate() + "T" + a.getAppointmentTime()).reversed())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
@@ -105,6 +108,7 @@ public class AppointmentController {
|
||||
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(query);
|
||||
List<AppointmentDTO> appointmentDTOs = responses.stream()
|
||||
.map(this::mapToAppointmentDTO)
|
||||
.sorted(Comparator.comparing((AppointmentDTO a) -> a.getAppointmentDate() + "T" + a.getAppointmentTime()).reversed())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
@@ -143,11 +147,10 @@ public class AppointmentController {
|
||||
|
||||
@FXML
|
||||
void btnDeleteClicked(ActionEvent event){
|
||||
//get selected appointments
|
||||
|
||||
var selectedAppointments = tvAppointments.getSelectionModel().getSelectedItems();
|
||||
if (selectedAppointments.isEmpty()) return;
|
||||
|
||||
//ask user to confirm
|
||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
question.setHeaderText("Please confirm delete");
|
||||
String message = selectedAppointments.size() == 1
|
||||
@@ -157,7 +160,6 @@ public class AppointmentController {
|
||||
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
||||
java.util.Optional<ButtonType> result = question.showAndWait();
|
||||
|
||||
//if confirmed, start deletion
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
List<Long> ids = selectedAppointments.stream()
|
||||
.map(a -> (long) a.getAppointmentId())
|
||||
@@ -180,7 +182,6 @@ public class AppointmentController {
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
//refresh display
|
||||
loadAppointments();
|
||||
}
|
||||
}
|
||||
@@ -230,15 +231,16 @@ public class AppointmentController {
|
||||
}
|
||||
|
||||
private AppointmentDTO mapToAppointmentDTO(AppointmentResponse response) {
|
||||
Long petId = response.getPetIds() != null && !response.getPetIds().isEmpty() ? response.getPetIds().get(0) : null;
|
||||
return new AppointmentDTO(
|
||||
response.getAppointmentId().intValue(),
|
||||
response.getCustomerId() != null ? response.getCustomerId().intValue() : 0,
|
||||
response.getCustomerName(),
|
||||
petId != null ? petId.intValue() : 0,
|
||||
String.join(", ", response.getPetNames()),
|
||||
response.getPetId() != null ? response.getPetId().intValue() : 0,
|
||||
response.getPetName(),
|
||||
response.getServiceId() != null ? response.getServiceId().intValue() : 0,
|
||||
response.getServiceName(),
|
||||
response.getEmployeeId() != null ? response.getEmployeeId().intValue() : 0,
|
||||
response.getEmployeeName(),
|
||||
response.getAppointmentDate().toString(),
|
||||
response.getAppointmentTime().toString(),
|
||||
response.getAppointmentStatus()
|
||||
|
||||
@@ -123,20 +123,28 @@ public class ChatController {
|
||||
|
||||
@FXML
|
||||
void btnSendClicked() {
|
||||
if (selectedConversation == null) {
|
||||
lblChatStatus.setText("Select a conversation");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (selectedConversation == null) {
|
||||
lblChatStatus.setText("Select a conversation");
|
||||
return;
|
||||
}
|
||||
|
||||
String content = txtMessage.getText() == null ? "" : txtMessage.getText().trim();
|
||||
if (content.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
String content = txtMessage.getText() == null ? "" : txtMessage.getText().trim();
|
||||
if (content.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
txtMessage.clear();
|
||||
boolean sent = realtimeClient.sendMessage(selectedConversation.getId(), content);
|
||||
if (!sent) {
|
||||
sendMessageFallback(selectedConversation.getId(), content);
|
||||
txtMessage.clear();
|
||||
boolean sent = realtimeClient.sendMessage(selectedConversation.getId(), content);
|
||||
if (!sent) {
|
||||
sendMessageFallback(selectedConversation.getId(), content);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ChatController.btnSendClicked",
|
||||
e,
|
||||
"Sending chat message");
|
||||
lblChatStatus.setText("Chat send failed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,16 +231,30 @@ public class ChatController {
|
||||
private void renderMessages(List<MessageResponse> messages) {
|
||||
vbMessages.getChildren().clear();
|
||||
for (MessageResponse message : messages) {
|
||||
vbMessages.getChildren().add(createMessageBubble(message));
|
||||
try {
|
||||
vbMessages.getChildren().add(createMessageBubble(message));
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ChatController.renderMessages",
|
||||
e,
|
||||
"Rendering chat message");
|
||||
}
|
||||
}
|
||||
scrollMessagesToBottom();
|
||||
}
|
||||
|
||||
private void appendMessageIfSelected(MessageResponse message) {
|
||||
upsertConversationForMessage(message);
|
||||
if (selectedConversation != null && selectedConversation.getId().equals(message.getConversationId())) {
|
||||
vbMessages.getChildren().add(createMessageBubble(message));
|
||||
scrollMessagesToBottom();
|
||||
try {
|
||||
upsertConversationForMessage(message);
|
||||
if (selectedConversation != null && selectedConversation.getId().equals(message.getConversationId())) {
|
||||
vbMessages.getChildren().add(createMessageBubble(message));
|
||||
scrollMessagesToBottom();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ChatController.appendMessageIfSelected",
|
||||
e,
|
||||
"Appending chat message");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,15 +306,34 @@ public class ChatController {
|
||||
Label author = new Label(resolveAuthorLabel(message));
|
||||
author.setStyle("-fx-font-weight: bold; -fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";");
|
||||
|
||||
Label content = new Label(message.getContent());
|
||||
content.setWrapText(true);
|
||||
content.setStyle("-fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";");
|
||||
|
||||
String timestampText = message.getTimestamp() == null ? "" : TIME_FORMATTER.format(message.getTimestamp());
|
||||
Label timestamp = new Label(timestampText);
|
||||
timestamp.setStyle("-fx-text-fill: " + (mine ? "#dbeafe" : "#94a3b8") + "; -fx-font-size: 11px;");
|
||||
|
||||
VBox bubble = new VBox(4, author, content, timestamp);
|
||||
VBox bubble = new VBox(4, author);
|
||||
String contentText = message.getContent() == null ? "" : message.getContent();
|
||||
if (!contentText.isBlank()) {
|
||||
Label content = new Label(contentText);
|
||||
content.setWrapText(true);
|
||||
content.setStyle("-fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";");
|
||||
bubble.getChildren().add(content);
|
||||
}
|
||||
|
||||
if (message.getAttachmentUrl() != null && !message.getAttachmentUrl().isBlank()) {
|
||||
String attachmentLabel = message.getAttachmentName();
|
||||
if (attachmentLabel == null || attachmentLabel.isBlank()) {
|
||||
attachmentLabel = "Attachment";
|
||||
}
|
||||
if (message.getAttachmentSizeBytes() != null && message.getAttachmentSizeBytes() > 0) {
|
||||
attachmentLabel = attachmentLabel + " (" + formatSize(message.getAttachmentSizeBytes()) + ")";
|
||||
}
|
||||
Label attachment = new Label(attachmentLabel);
|
||||
attachment.setWrapText(true);
|
||||
attachment.setStyle("-fx-text-fill: " + (mine ? "#dbeafe" : "#0f766e") + "; -fx-underline: true;");
|
||||
bubble.getChildren().add(attachment);
|
||||
}
|
||||
|
||||
bubble.getChildren().add(timestamp);
|
||||
bubble.setMaxWidth(420);
|
||||
bubble.setStyle(mine
|
||||
? "-fx-background-color: #0f766e; -fx-background-radius: 14; -fx-padding: 12;"
|
||||
@@ -347,4 +388,17 @@ public class ChatController {
|
||||
private void scrollMessagesToBottom() {
|
||||
Platform.runLater(() -> spMessages.setVvalue(1.0));
|
||||
}
|
||||
private String formatSize(Long bytes) {
|
||||
if (bytes == null || bytes <= 0) {
|
||||
return "";
|
||||
}
|
||||
double size = bytes;
|
||||
String[] units = {"B", "KB", "MB", "GB"};
|
||||
int unitIndex = 0;
|
||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||
size = size / 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
return unitIndex == 0 ? String.format("%.0f %s", size, units[unitIndex]) : String.format("%.1f %s", size, units[unitIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,8 +235,8 @@ public class InventoryController {
|
||||
response.getProdId() != null ? response.getProdId().intValue() : 0,
|
||||
response.getProductName(),
|
||||
response.getCategoryName() != null ? response.getCategoryName() : "",
|
||||
0,
|
||||
"N/A",
|
||||
response.getStoreId() != null ? response.getStoreId().intValue() : 0,
|
||||
response.getStoreName() != null ? response.getStoreName() : "",
|
||||
response.getQuantity() != null ? response.getQuantity() : 0,
|
||||
0
|
||||
);
|
||||
|
||||
@@ -358,6 +358,7 @@ public class MainLayoutController {
|
||||
lblRole.setText("Leon's Petstore");
|
||||
|
||||
boolean isAdmin = session.isAdmin();
|
||||
boolean canViewAnalytics = isAdmin || session.isStaff();
|
||||
btnInventory.setVisible(isAdmin);
|
||||
btnInventory.setManaged(isAdmin);
|
||||
btnSuppliers.setVisible(isAdmin);
|
||||
@@ -384,8 +385,8 @@ public class MainLayoutController {
|
||||
}
|
||||
|
||||
if (btnAnalytics != null) {
|
||||
btnAnalytics.setVisible(isAdmin);
|
||||
btnAnalytics.setManaged(isAdmin);
|
||||
btnAnalytics.setVisible(canViewAnalytics);
|
||||
btnAnalytics.setManaged(canViewAnalytics);
|
||||
}
|
||||
|
||||
btnSalesHistory.setText(isAdmin ? "Sales History" : "Sales");
|
||||
|
||||
@@ -15,6 +15,8 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.dto.pet.PetResponse;
|
||||
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
||||
import org.example.petshopdesktop.api.endpoints.DropdownApi;
|
||||
import org.example.petshopdesktop.api.endpoints.PetApi;
|
||||
import org.example.petshopdesktop.controllers.dialogcontrollers.PetDialogController;
|
||||
import org.example.petshopdesktop.models.Pet;
|
||||
@@ -61,9 +63,21 @@ public class PetController {
|
||||
@FXML
|
||||
private TableColumn<Pet, String> colPetStatus;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Pet, String> colCustomerName;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Pet, String> colStoreName;
|
||||
|
||||
@FXML
|
||||
private TableView<Pet> tvPets;
|
||||
|
||||
@FXML
|
||||
private ComboBox<String> cbSpeciesFilter;
|
||||
|
||||
@FXML
|
||||
private ComboBox<String> cbStatusFilter;
|
||||
|
||||
@FXML
|
||||
private TextField txtSearch;
|
||||
|
||||
@@ -148,8 +162,15 @@ public class PetController {
|
||||
colPetAge.setCellValueFactory(new PropertyValueFactory<Pet,Integer>("petAge"));
|
||||
colPetStatus.setCellValueFactory(new PropertyValueFactory<Pet,String>("petStatus"));
|
||||
colPetPrice.setCellValueFactory(new PropertyValueFactory<Pet,Double>("petPrice"));
|
||||
colCustomerName.setCellValueFactory(new PropertyValueFactory<Pet,String>("customerName"));
|
||||
colStoreName.setCellValueFactory(new PropertyValueFactory<Pet,String>("storeName"));
|
||||
configureImageColumn(colPetImage);
|
||||
|
||||
loadSpeciesFilter();
|
||||
|
||||
cbStatusFilter.setItems(FXCollections.observableArrayList("All Statuses", "Available", "Adopted", "Owned", "Pending"));
|
||||
cbStatusFilter.getSelectionModel().selectFirst();
|
||||
|
||||
displayPets();
|
||||
|
||||
tvPets.getSelectionModel().selectedItemProperty().addListener(
|
||||
@@ -159,9 +180,12 @@ public class PetController {
|
||||
});
|
||||
|
||||
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
displayFilteredPet(newValue);
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
cbSpeciesFilter.valueProperty().addListener((observable, oldValue, newValue) -> applyFilters());
|
||||
cbStatusFilter.valueProperty().addListener((observable, oldValue, newValue) -> applyFilters());
|
||||
|
||||
//EventListener for DELETE key
|
||||
tvPets.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||
@@ -173,12 +197,14 @@ public class PetController {
|
||||
}
|
||||
|
||||
private void displayFilteredPet(String filter) {
|
||||
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
|
||||
String species = selectedSpecies();
|
||||
String status = selectedStatus();
|
||||
if ((filter == null || filter.isEmpty()) && species == null && status == null){
|
||||
displayPets();
|
||||
} else {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<PetResponse> pets = PetApi.getInstance().listPets(filter);
|
||||
List<PetResponse> pets = PetApi.getInstance().listPets(filter, species, status);
|
||||
List<Pet> petList = pets.stream()
|
||||
.map(this::mapToPet)
|
||||
.collect(Collectors.toList());
|
||||
@@ -203,7 +229,7 @@ public class PetController {
|
||||
private void displayPets() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<PetResponse> pets = PetApi.getInstance().listPets(null);
|
||||
List<PetResponse> pets = PetApi.getInstance().listPets(null, selectedSpecies(), selectedStatus());
|
||||
List<Pet> petList = pets.stream()
|
||||
.map(this::mapToPet)
|
||||
.collect(Collectors.toList());
|
||||
@@ -224,6 +250,40 @@ public class PetController {
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void applyFilters() {
|
||||
displayFilteredPet(txtSearch.getText());
|
||||
}
|
||||
|
||||
private void loadSpeciesFilter() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<String> values = DropdownApi.getInstance().getPetSpecies().stream()
|
||||
.map(DropdownOption::getLabel)
|
||||
.collect(Collectors.toList());
|
||||
values.add(0, "All Species");
|
||||
Platform.runLater(() -> {
|
||||
cbSpeciesFilter.setItems(FXCollections.observableArrayList(values));
|
||||
cbSpeciesFilter.getSelectionModel().selectFirst();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> ActivityLogger.getInstance().logException(
|
||||
"PetController.loadSpeciesFilter",
|
||||
e,
|
||||
"Loading species filter options"));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private String selectedSpecies() {
|
||||
String value = cbSpeciesFilter.getValue();
|
||||
return value == null || value.equals("All Species") ? null : value;
|
||||
}
|
||||
|
||||
private String selectedStatus() {
|
||||
String value = cbStatusFilter.getValue();
|
||||
return value == null || value.equals("All Statuses") ? null : value;
|
||||
}
|
||||
|
||||
private void openDialog(Pet pet, String mode){
|
||||
//Get new view
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml"));
|
||||
@@ -264,7 +324,7 @@ public class PetController {
|
||||
}
|
||||
|
||||
private Pet mapToPet(PetResponse response) {
|
||||
return new Pet(
|
||||
Pet pet = new Pet(
|
||||
response.getPetId().intValue(),
|
||||
response.getPetName(),
|
||||
response.getPetSpecies(),
|
||||
@@ -274,6 +334,11 @@ public class PetController {
|
||||
response.getPetPrice().doubleValue(),
|
||||
response.getImageUrl()
|
||||
);
|
||||
pet.setCustomerName(response.getCustomerName());
|
||||
pet.setStoreName(response.getStoreName());
|
||||
pet.setCustomerId(response.getCustomerId() != null ? response.getCustomerId() : 0L);
|
||||
pet.setStoreId(response.getStoreId() != null ? response.getStoreId() : 0L);
|
||||
return pet;
|
||||
}
|
||||
|
||||
private void configureImageColumn(TableColumn<Pet, String> column) {
|
||||
|
||||
@@ -16,6 +16,8 @@ import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.DTOs.ProductDTO;
|
||||
import org.example.petshopdesktop.api.dto.product.ProductResponse;
|
||||
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
||||
import org.example.petshopdesktop.api.endpoints.DropdownApi;
|
||||
import org.example.petshopdesktop.api.endpoints.ProductApi;
|
||||
import org.example.petshopdesktop.controllers.dialogcontrollers.ProductDialogController;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
@@ -62,6 +64,9 @@ public class ProductController {
|
||||
@FXML
|
||||
private TableView<ProductDTO> tvProducts;
|
||||
|
||||
@FXML
|
||||
private ComboBox<DropdownOption> cbCategoryFilter;
|
||||
|
||||
@FXML
|
||||
private TextField txtSearch;
|
||||
|
||||
@@ -87,6 +92,7 @@ public class ProductController {
|
||||
colProductCategory.setCellValueFactory(new PropertyValueFactory<ProductDTO,String>("categoryName"));
|
||||
colProductDesc.setCellValueFactory(new PropertyValueFactory<ProductDTO,String>("prodDesc"));
|
||||
configureImageColumn(colProductImage);
|
||||
loadCategoryFilter();
|
||||
|
||||
displayProduct();
|
||||
|
||||
@@ -100,9 +106,11 @@ public class ProductController {
|
||||
|
||||
//EventListener to search when text is changed on searchbar
|
||||
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
displayFilteredProduct(newValue);
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
cbCategoryFilter.valueProperty().addListener((observable, oldValue, newValue) -> applyFilters());
|
||||
|
||||
//EventListener for DELETE key press
|
||||
tvProducts.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||
@@ -120,7 +128,7 @@ public class ProductController {
|
||||
private void displayProduct(){
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<ProductResponse> products = ProductApi.getInstance().listProducts(null);
|
||||
List<ProductResponse> products = ProductApi.getInstance().listProducts(null, selectedCategoryId());
|
||||
List<ProductDTO> productDTOs = products.stream()
|
||||
.map(this::mapToProductDTO)
|
||||
.collect(Collectors.toList());
|
||||
@@ -222,12 +230,12 @@ public class ProductController {
|
||||
* @param filter word to filter table
|
||||
*/
|
||||
private void displayFilteredProduct(String filter){
|
||||
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
|
||||
if ((txtSearch.getText() == null || txtSearch.getText().isEmpty()) && selectedCategoryId() == null){
|
||||
displayProduct();
|
||||
} else {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<ProductResponse> products = ProductApi.getInstance().listProducts(filter);
|
||||
List<ProductResponse> products = ProductApi.getInstance().listProducts(filter, selectedCategoryId());
|
||||
List<ProductDTO> productDTOs = products.stream()
|
||||
.map(this::mapToProductDTO)
|
||||
.collect(Collectors.toList());
|
||||
@@ -249,6 +257,37 @@ public class ProductController {
|
||||
}
|
||||
}
|
||||
|
||||
private void applyFilters() {
|
||||
displayFilteredProduct(txtSearch.getText());
|
||||
}
|
||||
|
||||
private void loadCategoryFilter() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> options = new ArrayList<>();
|
||||
DropdownOption all = new DropdownOption();
|
||||
all.setId(null);
|
||||
all.setLabel("All Categories");
|
||||
options.add(all);
|
||||
options.addAll(DropdownApi.getInstance().getProductCategories());
|
||||
Platform.runLater(() -> {
|
||||
cbCategoryFilter.setItems(FXCollections.observableArrayList(options));
|
||||
cbCategoryFilter.getSelectionModel().selectFirst();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> ActivityLogger.getInstance().logException(
|
||||
"ProductController.loadCategoryFilter",
|
||||
e,
|
||||
"Loading category filter options"));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private Long selectedCategoryId() {
|
||||
DropdownOption option = cbCategoryFilter.getValue();
|
||||
return option == null ? null : option.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to open the new Dialog for edit or adding
|
||||
* depending on the mode given
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.example.petshopdesktop.api.endpoints.PurchaseOrderApi;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Comparator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PurchaseOrderController {
|
||||
@@ -63,6 +64,7 @@ public class PurchaseOrderController {
|
||||
List<PurchaseOrderResponse> responses = PurchaseOrderApi.getInstance().listPurchaseOrders(null);
|
||||
List<PurchaseOrderDTO> dtos = responses.stream()
|
||||
.map(this::mapToPurchaseOrderDTO)
|
||||
.sorted(Comparator.comparing(PurchaseOrderDTO::getOrderDate).reversed())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
@@ -118,4 +120,4 @@ public class PurchaseOrderController {
|
||||
response.getOrderStatus()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import javafx.collections.transformation.FilteredList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
@@ -32,6 +34,7 @@ import org.example.petshopdesktop.api.dto.sale.SaleRequest;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
|
||||
import org.example.petshopdesktop.models.Product;
|
||||
import org.example.petshopdesktop.models.SaleCartItem;
|
||||
import org.example.petshopdesktop.models.SaleDetail;
|
||||
import org.example.petshopdesktop.models.SaleLineItem;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
@@ -39,6 +42,7 @@ import java.math.BigDecimal;
|
||||
import java.text.NumberFormat;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -128,6 +132,7 @@ public class SaleController {
|
||||
private final ObservableList<SaleCartItem> cartItems = FXCollections.observableArrayList();
|
||||
private final ObservableList<SaleLineItem> saleItems = FXCollections.observableArrayList();
|
||||
private FilteredList<SaleLineItem> filteredSales;
|
||||
private boolean saleSaveInProgress;
|
||||
|
||||
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||
@@ -165,6 +170,15 @@ public class SaleController {
|
||||
filteredSales = new FilteredList<>(saleItems, s -> true);
|
||||
tvSales.setItems(filteredSales);
|
||||
|
||||
tvSales.setOnMouseClicked(event -> {
|
||||
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||
SaleLineItem selected = tvSales.getSelectionModel().getSelectedItem();
|
||||
if (selected != null) {
|
||||
openSaleDetailDialog(selected.getSaleId());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
txtSearch.textProperty().addListener((obs, oldVal, newVal) -> applySalesFilter(newVal));
|
||||
}
|
||||
|
||||
@@ -177,22 +191,43 @@ public class SaleController {
|
||||
|
||||
updateCartTotal();
|
||||
|
||||
try {
|
||||
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
|
||||
ObservableList<Product> products = FXCollections.observableArrayList();
|
||||
for (ProductResponse pr : productResponses) {
|
||||
products.add(new Product(
|
||||
pr.getProdId().intValue(),
|
||||
pr.getProdName(),
|
||||
pr.getProdPrice().doubleValue(),
|
||||
0,
|
||||
pr.getProdDesc()
|
||||
));
|
||||
setCreateSaleControlsDisabled(true);
|
||||
|
||||
Task<ObservableList<Product>> task = new Task<>() {
|
||||
@Override
|
||||
protected ObservableList<Product> call() throws Exception {
|
||||
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
|
||||
ObservableList<Product> products = FXCollections.observableArrayList();
|
||||
for (ProductResponse pr : productResponses) {
|
||||
products.add(new Product(
|
||||
pr.getProdId().intValue(),
|
||||
pr.getProdName(),
|
||||
pr.getProdPrice().doubleValue(),
|
||||
0,
|
||||
pr.getProdDesc()
|
||||
));
|
||||
}
|
||||
return products;
|
||||
}
|
||||
cbProduct.setItems(products);
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("SaleController.setupCreateSale", e, "Loading products");
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(event -> {
|
||||
cbProduct.setItems(task.getValue());
|
||||
setCreateSaleControlsDisabled(false);
|
||||
});
|
||||
|
||||
task.setOnFailed(event -> {
|
||||
Throwable e = task.getException();
|
||||
ActivityLogger.getInstance().logException(
|
||||
"SaleController.setupCreateSale",
|
||||
e instanceof Exception ? (Exception) e : new RuntimeException(e),
|
||||
"Loading products"
|
||||
);
|
||||
setCreateSaleControlsDisabled(false);
|
||||
showError("Sales", "Could not load products. Check the backend connection and refresh the view.");
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
private void applyRoleMode() {
|
||||
@@ -207,10 +242,13 @@ public class SaleController {
|
||||
}
|
||||
|
||||
private void refreshSales(boolean showErrorDialog) {
|
||||
btnRefresh.setDisable(true);
|
||||
Task<List<SaleLineItem>> task = new Task<List<SaleLineItem>>() {
|
||||
@Override
|
||||
protected List<SaleLineItem> call() throws Exception {
|
||||
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, 1000, null);
|
||||
List<SaleResponse> sales = SaleApi.getInstance().listAllSales(null);
|
||||
sales.sort(Comparator.comparing(SaleResponse::getSaleDate, Comparator.nullsLast(Comparator.reverseOrder()))
|
||||
.thenComparing(SaleResponse::getSaleId, Comparator.nullsLast(Comparator.reverseOrder())));
|
||||
List<SaleLineItem> lineItems = new ArrayList<>();
|
||||
|
||||
for (SaleResponse sale : sales) {
|
||||
@@ -220,18 +258,23 @@ public class SaleController {
|
||||
|
||||
if (sale.getItems() != null && !sale.getItems().isEmpty()) {
|
||||
for (SaleItemResponse item : sale.getItems()) {
|
||||
boolean isRefund = sale.getIsRefund() != null && sale.getIsRefund();
|
||||
double unitPrice = item.getUnitPrice() != null ? item.getUnitPrice().doubleValue() : 0.0;
|
||||
double lineTotal = unitPrice * item.getQuantity();
|
||||
int quantity = item.getQuantity() != null ? item.getQuantity() : 0;
|
||||
if (isRefund && quantity > 0) {
|
||||
quantity = -quantity;
|
||||
}
|
||||
double lineTotal = unitPrice * quantity;
|
||||
lineItems.add(new SaleLineItem(
|
||||
sale.getSaleId().intValue(),
|
||||
saleDate,
|
||||
sale.getEmployeeName(),
|
||||
item.getProductName(),
|
||||
item.getQuantity(),
|
||||
quantity,
|
||||
unitPrice,
|
||||
lineTotal,
|
||||
sale.getPaymentMethod(),
|
||||
sale.getIsRefund() != null && sale.getIsRefund()
|
||||
isRefund
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -242,14 +285,14 @@ public class SaleController {
|
||||
|
||||
task.setOnSucceeded(event -> {
|
||||
saleItems.setAll(task.getValue());
|
||||
btnRefresh.setDisable(false);
|
||||
});
|
||||
|
||||
task.setOnFailed(event -> {
|
||||
Throwable e = task.getException();
|
||||
ActivityLogger.getInstance().logException("SaleController.refreshSales", (Exception) e, "Loading sales");
|
||||
if (showErrorDialog) {
|
||||
showError("Sales", "Could not load sales: " + e.getMessage());
|
||||
}
|
||||
btnRefresh.setDisable(false);
|
||||
showError("Sales", "Could not load sales: " + e.getMessage());
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
@@ -310,6 +353,9 @@ public class SaleController {
|
||||
|
||||
@FXML
|
||||
void btnSaveSale(ActionEvent event) {
|
||||
if (saleSaveInProgress) {
|
||||
return;
|
||||
}
|
||||
if (UserSession.getInstance().isAdmin()) {
|
||||
showError("Create Sale", "This action is restricted to staff.");
|
||||
return;
|
||||
@@ -332,36 +378,57 @@ public class SaleController {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SaleRequest request = new SaleRequest();
|
||||
request.setStoreId(storeId);
|
||||
request.setPaymentMethod(payment);
|
||||
SaleRequest request = new SaleRequest();
|
||||
request.setStoreId(storeId);
|
||||
request.setPaymentMethod(payment);
|
||||
|
||||
List<SaleItemRequest> itemRequests = new ArrayList<>();
|
||||
for (SaleCartItem cartItem : cartItems) {
|
||||
SaleItemRequest itemRequest = new SaleItemRequest();
|
||||
itemRequest.setProdId((long) cartItem.getProdId());
|
||||
itemRequest.setQuantity(cartItem.getQuantity());
|
||||
itemRequests.add(itemRequest);
|
||||
List<SaleItemRequest> itemRequests = new ArrayList<>();
|
||||
for (SaleCartItem cartItem : cartItems) {
|
||||
SaleItemRequest itemRequest = new SaleItemRequest();
|
||||
itemRequest.setProdId((long) cartItem.getProdId());
|
||||
itemRequest.setQuantity(cartItem.getQuantity());
|
||||
itemRequests.add(itemRequest);
|
||||
}
|
||||
request.setItems(itemRequests);
|
||||
|
||||
saleSaveInProgress = true;
|
||||
setCreateSaleControlsDisabled(true);
|
||||
btnRefund.setDisable(true);
|
||||
|
||||
Task<SaleResponse> task = new Task<>() {
|
||||
@Override
|
||||
protected SaleResponse call() throws Exception {
|
||||
return SaleApi.getInstance().createSale(request);
|
||||
}
|
||||
request.setItems(itemRequests);
|
||||
};
|
||||
|
||||
SaleResponse response = SaleApi.getInstance().createSale(request);
|
||||
task.setOnSucceeded(evt -> {
|
||||
saleSaveInProgress = false;
|
||||
setCreateSaleControlsDisabled(false);
|
||||
btnRefund.setDisable(false);
|
||||
SaleResponse response = task.getValue();
|
||||
showInfo("Sale saved", "Sale ID " + response.getSaleId() + " was created.");
|
||||
|
||||
cartItems.clear();
|
||||
updateCartTotal();
|
||||
|
||||
refreshSales(true);
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", e, "Creating sale");
|
||||
String errorMsg = e.getMessage();
|
||||
});
|
||||
|
||||
task.setOnFailed(evt -> {
|
||||
saleSaveInProgress = false;
|
||||
setCreateSaleControlsDisabled(false);
|
||||
btnRefund.setDisable(false);
|
||||
Throwable e = task.getException();
|
||||
Exception ex = e instanceof Exception ? (Exception) e : new RuntimeException(e);
|
||||
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", ex, "Creating sale");
|
||||
String errorMsg = e != null ? e.getMessage() : null;
|
||||
if (errorMsg != null && errorMsg.contains("Insufficient inventory")) {
|
||||
showError("Create Sale", "Insufficient stock for one or more items.");
|
||||
} else {
|
||||
showError("Create Sale", errorMsg != null ? errorMsg : "Could not save the sale.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -384,11 +451,13 @@ public class SaleController {
|
||||
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||
dialog.setTitle("Process Refund");
|
||||
dialog.setScene(new Scene(loader.load()));
|
||||
var controller = loader.<org.example.petshopdesktop.controllers.dialogcontrollers.RefundDialogController>getController();
|
||||
if (selectedSale != null) {
|
||||
loader.<org.example.petshopdesktop.controllers.dialogcontrollers.RefundDialogController>getController()
|
||||
.prefillSale((long) selectedSale.getSaleId());
|
||||
controller.prefillSale((long) selectedSale.getSaleId());
|
||||
}
|
||||
dialog.setResizable(false);
|
||||
dialog.setMinWidth(860);
|
||||
dialog.setMinHeight(680);
|
||||
dialog.setResizable(true);
|
||||
dialog.showAndWait();
|
||||
|
||||
refreshSales(true);
|
||||
@@ -397,11 +466,87 @@ public class SaleController {
|
||||
}
|
||||
}
|
||||
|
||||
private void openSaleDetailDialog(int saleId) {
|
||||
Task<SaleResponse> task = new Task<>() {
|
||||
@Override
|
||||
protected SaleResponse call() throws Exception {
|
||||
return SaleApi.getInstance().getSale((long) saleId);
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(event -> {
|
||||
try {
|
||||
SaleResponse sale = task.getValue();
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/example/petshopdesktop/dialogviews/sale-detail-dialog-view.fxml"));
|
||||
Stage dialog = new Stage();
|
||||
dialog.initOwner(tvSales.getScene().getWindow());
|
||||
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||
dialog.setTitle("Sale Details");
|
||||
dialog.setScene(new Scene(loader.load()));
|
||||
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.SaleDetailDialogController) loader.getController();
|
||||
controller.displaySaleDetails(mapToSaleDetail(sale));
|
||||
dialog.setResizable(false);
|
||||
dialog.showAndWait();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("SaleController.openSaleDetailDialog", e, "Opening sale detail dialog");
|
||||
showError("Sale Details", "Could not open the sale details.");
|
||||
}
|
||||
});
|
||||
|
||||
task.setOnFailed(event -> {
|
||||
Throwable e = task.getException();
|
||||
ActivityLogger.getInstance().logException("SaleController.openSaleDetailDialog", (Exception) e, "Loading sale detail");
|
||||
showError("Sale Details", "Could not open the sale details.");
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
private SaleDetail mapToSaleDetail(SaleResponse sale) {
|
||||
ObservableList<SaleDetail.SaleDetailItem> items = FXCollections.observableArrayList();
|
||||
if (sale.getItems() != null) {
|
||||
boolean isRefund = sale.getIsRefund() != null && sale.getIsRefund();
|
||||
for (SaleItemResponse item : sale.getItems()) {
|
||||
double unitPrice = item.getUnitPrice() != null ? item.getUnitPrice().doubleValue() : 0.0;
|
||||
int quantity = item.getQuantity() != null ? item.getQuantity() : 0;
|
||||
if (isRefund && quantity > 0) {
|
||||
quantity = -quantity;
|
||||
}
|
||||
items.add(new SaleDetail.SaleDetailItem(
|
||||
item.getProdId() != null ? item.getProdId().intValue() : 0,
|
||||
item.getProductName(),
|
||||
quantity,
|
||||
unitPrice,
|
||||
unitPrice * quantity
|
||||
));
|
||||
}
|
||||
}
|
||||
return new SaleDetail(
|
||||
sale.getSaleId().intValue(),
|
||||
sale.getSaleDate(),
|
||||
sale.getTotalAmount() != null ? sale.getTotalAmount().doubleValue() : 0.0,
|
||||
sale.getPaymentMethod(),
|
||||
sale.getEmployeeName(),
|
||||
items
|
||||
);
|
||||
}
|
||||
|
||||
private void updateCartTotal() {
|
||||
double total = cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum();
|
||||
lblCartTotal.setText(currency.format(total));
|
||||
}
|
||||
|
||||
private void setCreateSaleControlsDisabled(boolean disabled) {
|
||||
cbProduct.setDisable(disabled);
|
||||
spQuantity.setDisable(disabled);
|
||||
btnAddToCart.setDisable(disabled);
|
||||
btnRemoveSelected.setDisable(disabled);
|
||||
cbPaymentMethod.setDisable(disabled);
|
||||
btnClearCart.setDisable(disabled);
|
||||
btnSaveSale.setDisable(disabled);
|
||||
}
|
||||
|
||||
private void applySalesFilter(String filter) {
|
||||
String f = filter == null ? "" : filter.trim().toLowerCase();
|
||||
if (f.isEmpty()) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.example.petshopdesktop.util.ActivityLogger;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.Comparator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class StaffAccountsController {
|
||||
@@ -161,6 +162,7 @@ public class StaffAccountsController {
|
||||
List<EmployeeResponse> employees = EmployeeApi.getInstance().listEmployees(null);
|
||||
List<StaffAccount> accounts = employees.stream()
|
||||
.map(this::mapToStaffAccount)
|
||||
.sorted(Comparator.comparing(StaffAccount::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder())))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
@@ -184,10 +186,14 @@ public class StaffAccountsController {
|
||||
long userId = employee.getUserId() != null ? employee.getUserId() : 0L;
|
||||
long employeeId = employee.getEmployeeId() != null ? employee.getEmployeeId() : 0L;
|
||||
String username = employee.getUsername();
|
||||
String fullName = employee.getFullName() != null ? employee.getFullName() : "";
|
||||
String[] names = splitFullName(fullName);
|
||||
String firstName = names[0];
|
||||
String lastName = names[1];
|
||||
String firstName = employee.getFirstName() != null ? employee.getFirstName() : "";
|
||||
String lastName = employee.getLastName() != null ? employee.getLastName() : "";
|
||||
if (firstName.isBlank() && lastName.isBlank()) {
|
||||
String fullName = employee.getFullName() != null ? employee.getFullName() : "";
|
||||
String[] names = splitFullName(fullName);
|
||||
firstName = names[0];
|
||||
lastName = names[1];
|
||||
}
|
||||
String email = employee.getEmail() != null ? employee.getEmail() : "";
|
||||
String phone = employee.getPhone() != null ? employee.getPhone() : "";
|
||||
String role = employee.getRole() != null ? employee.getRole() : "STAFF";
|
||||
|
||||
@@ -11,21 +11,23 @@ import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.dto.adoption.AdoptionRequest;
|
||||
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
||||
import org.example.petshopdesktop.api.endpoints.AdoptionApi;
|
||||
import org.example.petshopdesktop.api.endpoints.DropdownApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.models.Adoption;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class AdoptionDialogController {
|
||||
|
||||
//FXML elements
|
||||
@FXML
|
||||
private Button btnCancel;
|
||||
|
||||
@@ -38,6 +40,9 @@ public class AdoptionDialogController {
|
||||
@FXML
|
||||
private ComboBox<DropdownOption> cbCustomer;
|
||||
|
||||
@FXML
|
||||
private ComboBox<DropdownOption> cbEmployee;
|
||||
|
||||
@FXML
|
||||
private ComboBox<DropdownOption> cbPet;
|
||||
|
||||
@@ -50,10 +55,9 @@ public class AdoptionDialogController {
|
||||
@FXML
|
||||
private Label lblMode;
|
||||
|
||||
//Stores if the dialog view is in add/edit mode
|
||||
private String mode = null;
|
||||
private Adoption selectedAdoption = null;
|
||||
|
||||
//Adoption statuses
|
||||
private ObservableList<String> statusList = FXCollections.observableArrayList(
|
||||
"Pending", "Completed", "Cancelled"
|
||||
);
|
||||
@@ -62,14 +66,16 @@ public class AdoptionDialogController {
|
||||
void initialize() {
|
||||
|
||||
cbAdoptionStatus.setItems(statusList);
|
||||
cbEmployee.setPromptText("Select an employee");
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> pets = DropdownApi.getInstance().getPets();
|
||||
List<DropdownOption> pets = DropdownApi.getInstance().getAdoptionPets();
|
||||
Platform.runLater(() -> {
|
||||
if (pets != null) {
|
||||
ObservableList<DropdownOption> petsObs = FXCollections.observableArrayList(pets);
|
||||
cbPet.setItems(petsObs);
|
||||
applySelectedPet();
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
@@ -83,6 +89,46 @@ public class AdoptionDialogController {
|
||||
}
|
||||
}).start();
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Long storeId = UserSession.getInstance().getStoreId();
|
||||
List<DropdownOption> employees;
|
||||
if (storeId != null && storeId > 0) {
|
||||
employees = DropdownApi.getInstance().getStoreEmployees(storeId);
|
||||
} else {
|
||||
employees = DropdownApi.getInstance().getEmployees();
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
cbEmployee.setItems(FXCollections.observableArrayList(employees));
|
||||
applySelectedEmployee();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AdoptionDialogController.initialize",
|
||||
e,
|
||||
"Loading employees for combo box");
|
||||
cbEmployee.setDisable(true);
|
||||
cbEmployee.setPromptText("Unable to load employees");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
|
||||
cbEmployee.setCellFactory(param -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption option, boolean empty) {
|
||||
super.updateItem(option, empty);
|
||||
setText(empty || option == null ? null : option.getLabel());
|
||||
}
|
||||
});
|
||||
cbEmployee.setButtonCell(new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption option, boolean empty) {
|
||||
super.updateItem(option, empty);
|
||||
setText(empty || option == null ? null : option.getLabel());
|
||||
}
|
||||
});
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
|
||||
@@ -90,6 +136,7 @@ public class AdoptionDialogController {
|
||||
if (customers != null) {
|
||||
ObservableList<DropdownOption> customersObs = FXCollections.observableArrayList(customers);
|
||||
cbCustomer.setItems(customersObs);
|
||||
applySelectedCustomer();
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
@@ -129,6 +176,10 @@ public class AdoptionDialogController {
|
||||
errorMsg += "Customer is required.\n";
|
||||
}
|
||||
|
||||
if (cbEmployee.getSelectionModel().getSelectedItem() == null) {
|
||||
errorMsg += "Employee is required.\n";
|
||||
}
|
||||
|
||||
if (dpAdoptionDate.getValue() == null) {
|
||||
errorMsg += "Adoption Date is required.\n";
|
||||
}
|
||||
@@ -139,9 +190,16 @@ public class AdoptionDialogController {
|
||||
|
||||
if (errorMsg.isEmpty()) {
|
||||
try {
|
||||
Long storeId = UserSession.getInstance().getStoreId();
|
||||
if (storeId == null || storeId <= 0) {
|
||||
throw new IllegalStateException("Store is not set for this account");
|
||||
}
|
||||
|
||||
AdoptionRequest request = new AdoptionRequest();
|
||||
request.setPetId(cbPet.getSelectionModel().getSelectedItem().getId());
|
||||
request.setCustomerId(cbCustomer.getSelectionModel().getSelectedItem().getId());
|
||||
request.setEmployeeId(cbEmployee.getSelectionModel().getSelectedItem().getId());
|
||||
request.setSourceStoreId(storeId);
|
||||
request.setAdoptionDate(dpAdoptionDate.getValue());
|
||||
request.setAdoptionStatus(cbAdoptionStatus.getValue());
|
||||
|
||||
@@ -179,7 +237,6 @@ public class AdoptionDialogController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void closeStage(MouseEvent mouseEvent) {
|
||||
Node node = (Node) mouseEvent.getSource();
|
||||
Stage stage = (Stage) node.getScene().getWindow();
|
||||
@@ -188,21 +245,11 @@ public class AdoptionDialogController {
|
||||
|
||||
public void displayAdoptionDetails(Adoption adoption) {
|
||||
if (adoption != null) {
|
||||
selectedAdoption = adoption;
|
||||
lblAdoptionId.setText("ID: " + adoption.getAdoptionId());
|
||||
|
||||
for (DropdownOption pet : cbPet.getItems()) {
|
||||
if (pet.getLabel().equals(adoption.getPetName())) {
|
||||
cbPet.getSelectionModel().select(pet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (DropdownOption customer : cbCustomer.getItems()) {
|
||||
if (customer.getLabel().equals(adoption.getCustomerName())) {
|
||||
cbCustomer.getSelectionModel().select(customer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
applySelectedPet();
|
||||
applySelectedCustomer();
|
||||
applySelectedEmployee();
|
||||
|
||||
if (adoption.getAdoptionDate() != null && !adoption.getAdoptionDate().isEmpty()) {
|
||||
try {
|
||||
@@ -229,4 +276,46 @@ public class AdoptionDialogController {
|
||||
lblMode.setText(mode + " Adoption");
|
||||
lblAdoptionId.setVisible(mode.equals("Edit"));
|
||||
}
|
||||
|
||||
private void applySelectedPet() {
|
||||
if (selectedAdoption == null || selectedAdoption.getPetId() <= 0) {
|
||||
return;
|
||||
}
|
||||
DropdownOption selected = findOptionById(cbPet.getItems(), (long) selectedAdoption.getPetId());
|
||||
if (selected != null && !Objects.equals(cbPet.getValue(), selected)) {
|
||||
cbPet.setValue(selected);
|
||||
}
|
||||
}
|
||||
|
||||
private void applySelectedCustomer() {
|
||||
if (selectedAdoption == null || selectedAdoption.getCustomerId() <= 0) {
|
||||
return;
|
||||
}
|
||||
DropdownOption selected = findOptionById(cbCustomer.getItems(), (long) selectedAdoption.getCustomerId());
|
||||
if (selected != null && !Objects.equals(cbCustomer.getValue(), selected)) {
|
||||
cbCustomer.setValue(selected);
|
||||
}
|
||||
}
|
||||
|
||||
private void applySelectedEmployee() {
|
||||
if (selectedAdoption == null || selectedAdoption.getEmployeeId() <= 0) {
|
||||
return;
|
||||
}
|
||||
DropdownOption selected = findOptionById(cbEmployee.getItems(), (long) selectedAdoption.getEmployeeId());
|
||||
if (selected != null && !Objects.equals(cbEmployee.getValue(), selected)) {
|
||||
cbEmployee.setValue(selected);
|
||||
}
|
||||
}
|
||||
|
||||
private DropdownOption findOptionById(List<DropdownOption> options, Long id) {
|
||||
if (id == null || options == null) {
|
||||
return null;
|
||||
}
|
||||
for (DropdownOption option : options) {
|
||||
if (option.getId() != null && option.getId().equals(id)) {
|
||||
return option;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,21 +19,19 @@ import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.util.Collections;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class AppointmentDialogController {
|
||||
|
||||
// ============================
|
||||
// FXML
|
||||
// ============================
|
||||
|
||||
@FXML private Button btnCancel;
|
||||
@FXML private Button btnSave;
|
||||
|
||||
@FXML private ComboBox<DropdownOption> cbService;
|
||||
@FXML private ComboBox<DropdownOption> cbCustomer;
|
||||
@FXML private ComboBox<DropdownOption> cbPet;
|
||||
@FXML private ComboBox<DropdownOption> cbEmployee;
|
||||
|
||||
@FXML private ComboBox<Integer> cbHour;
|
||||
@FXML private ComboBox<Integer> cbMinute;
|
||||
@@ -44,74 +42,38 @@ public class AppointmentDialogController {
|
||||
@FXML private Label lblAppointmentId;
|
||||
@FXML private Label lblMode;
|
||||
|
||||
// ============================
|
||||
// DATA
|
||||
// ============================
|
||||
|
||||
private String mode = null; // Add | Edit
|
||||
private String mode = null;
|
||||
private AppointmentDTO selectedAppointment = null;
|
||||
private Long pendingPetSelectionId = null;
|
||||
|
||||
private ObservableList<String> statusList =
|
||||
FXCollections.observableArrayList(
|
||||
"Booked", "Completed", "Cancelled"
|
||||
"Booked", "Completed", "Cancelled", "Missed"
|
||||
);
|
||||
|
||||
//
|
||||
// MODE
|
||||
//
|
||||
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
lblMode.setText(mode + " Appointment");
|
||||
lblAppointmentId.setVisible(!mode.equals("Add"));
|
||||
}
|
||||
|
||||
//
|
||||
// INITIALIZE
|
||||
//
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> services = DropdownApi.getInstance().getServices();
|
||||
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
|
||||
List<DropdownOption> pets = DropdownApi.getInstance().getPets();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (services != null) {
|
||||
cbService.setItems(FXCollections.observableArrayList(services));
|
||||
}
|
||||
if (customers != null) {
|
||||
cbCustomer.setItems(FXCollections.observableArrayList(customers));
|
||||
}
|
||||
if (pets != null) {
|
||||
cbPet.setItems(FXCollections.observableArrayList(pets));
|
||||
}
|
||||
syncSelectedAppointment();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AppointmentDialogController.initialize",
|
||||
e,
|
||||
"Loading combo box data for services, customers, and pets");
|
||||
e.printStackTrace();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
|
||||
cbAppointmentStatus.setItems(statusList);
|
||||
cbPet.setDisable(true);
|
||||
cbEmployee.setPromptText("Select an employee");
|
||||
cbPet.setPromptText("Select a customer first");
|
||||
cbCustomer.setPromptText("Select a customer");
|
||||
cbService.setPromptText("Select a service");
|
||||
dpAppointmentDate.setValue(LocalDate.now().plusDays(1));
|
||||
cbAppointmentStatus.setValue("Booked");
|
||||
|
||||
// Hours 9 AM - 5 PM
|
||||
for (int i = 9; i <= 17; i++) {
|
||||
cbHour.getItems().add(i);
|
||||
}
|
||||
|
||||
cbMinute.getItems().addAll(0, 15, 30, 45);
|
||||
|
||||
// Show dropdown labels
|
||||
cbService.setCellFactory(param -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption option, boolean empty) {
|
||||
@@ -157,18 +119,48 @@ public class AppointmentDialogController {
|
||||
}
|
||||
});
|
||||
|
||||
cbEmployee.setCellFactory(param -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption option, boolean empty) {
|
||||
super.updateItem(option, empty);
|
||||
setText(empty || option == null ? null : option.getLabel());
|
||||
}
|
||||
});
|
||||
cbEmployee.setButtonCell(new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(DropdownOption option, boolean empty) {
|
||||
super.updateItem(option, empty);
|
||||
setText(empty || option == null ? null : option.getLabel());
|
||||
}
|
||||
});
|
||||
|
||||
cbCustomer.valueProperty().addListener((obs, oldValue, newValue) -> {
|
||||
Long customerId = newValue != null ? newValue.getId() : null;
|
||||
cbPet.setValue(null);
|
||||
cbPet.setItems(FXCollections.observableArrayList());
|
||||
cbPet.setDisable(customerId == null);
|
||||
if (customerId != null) {
|
||||
cbPet.setPromptText("Loading customer pets...");
|
||||
loadCustomerPets(customerId);
|
||||
} else {
|
||||
cbPet.setPromptText("Select a customer first");
|
||||
pendingPetSelectionId = null;
|
||||
}
|
||||
});
|
||||
|
||||
btnSave.setOnMouseClicked(this::buttonSaveClicked);
|
||||
btnCancel.setOnMouseClicked(this::closeStage);
|
||||
}
|
||||
|
||||
//
|
||||
// DISPLAY FOR EDIT
|
||||
//
|
||||
loadServices();
|
||||
loadAppointmentCustomers();
|
||||
loadEmployees();
|
||||
}
|
||||
|
||||
public void displayAppointmentDetails(AppointmentDTO appt) {
|
||||
|
||||
selectedAppointment = appt;
|
||||
lblAppointmentId.setText("ID: " + appt.getAppointmentId());
|
||||
pendingPetSelectionId = appt.getPetId() > 0 ? (long) appt.getPetId() : null;
|
||||
|
||||
try {
|
||||
dpAppointmentDate.setValue(
|
||||
@@ -194,28 +186,17 @@ public class AppointmentDialogController {
|
||||
"Parsing appointment time");
|
||||
}
|
||||
|
||||
cbService.getItems().forEach(s -> {
|
||||
if (s.getId() != null && s.getId().longValue() == appt.getServiceId()) cbService.setValue(s);
|
||||
});
|
||||
|
||||
cbCustomer.getItems().forEach(c -> {
|
||||
if (c.getId() != null && c.getId().longValue() == appt.getCustomerId()) cbCustomer.setValue(c);
|
||||
});
|
||||
|
||||
cbPet.getItems().forEach(p -> {
|
||||
if (p.getId() != null && p.getId().longValue() == appt.getPetId()) cbPet.setValue(p);
|
||||
});
|
||||
applySelectedService();
|
||||
applySelectedCustomer();
|
||||
applySelectedEmployee();
|
||||
}
|
||||
|
||||
//
|
||||
// SAVE
|
||||
//
|
||||
|
||||
private void buttonSaveClicked(MouseEvent e) {
|
||||
|
||||
if (cbService.getValue() == null ||
|
||||
cbCustomer.getValue() == null ||
|
||||
cbPet.getValue() == null ||
|
||||
cbEmployee.getValue() == null ||
|
||||
dpAppointmentDate.getValue() == null ||
|
||||
cbHour.getValue() == null ||
|
||||
cbMinute.getValue() == null ||
|
||||
@@ -233,10 +214,11 @@ public class AppointmentDialogController {
|
||||
}
|
||||
|
||||
AppointmentRequest request = new AppointmentRequest();
|
||||
request.setPetIds(Collections.singletonList(cbPet.getValue().getId()));
|
||||
request.setPetId(cbPet.getValue().getId());
|
||||
request.setCustomerId(cbCustomer.getValue().getId());
|
||||
request.setStoreId(storeId);
|
||||
request.setServiceId(cbService.getValue().getId());
|
||||
request.setEmployeeId(cbEmployee.getValue().getId());
|
||||
request.setAppointmentDate(dpAppointmentDate.getValue());
|
||||
request.setAppointmentTime(appointmentTime);
|
||||
request.setAppointmentStatus(cbAppointmentStatus.getValue());
|
||||
@@ -267,10 +249,6 @@ public class AppointmentDialogController {
|
||||
}).start();
|
||||
}
|
||||
|
||||
//
|
||||
// UTIL
|
||||
//
|
||||
|
||||
private void closeStage(MouseEvent e) {
|
||||
Stage stage = (Stage) ((Node) e.getSource()).getScene().getWindow();
|
||||
stage.close();
|
||||
@@ -288,4 +266,156 @@ public class AppointmentDialogController {
|
||||
displayAppointmentDetails(selectedAppointment);
|
||||
}
|
||||
}
|
||||
|
||||
private void applySelectedService() {
|
||||
if (selectedAppointment == null || selectedAppointment.getServiceId() <= 0) {
|
||||
return;
|
||||
}
|
||||
DropdownOption selected = findOptionById(cbService.getItems(), (long) selectedAppointment.getServiceId());
|
||||
if (selected != null && !Objects.equals(cbService.getValue(), selected)) {
|
||||
cbService.setValue(selected);
|
||||
}
|
||||
}
|
||||
|
||||
private void applySelectedCustomer() {
|
||||
if (selectedAppointment == null || selectedAppointment.getCustomerId() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
DropdownOption selected = findOptionById(cbCustomer.getItems(), (long) selectedAppointment.getCustomerId());
|
||||
if (selected != null && !Objects.equals(cbCustomer.getValue(), selected)) {
|
||||
cbCustomer.setValue(selected);
|
||||
}
|
||||
}
|
||||
|
||||
private void applySelectedEmployee() {
|
||||
if (selectedAppointment == null || selectedAppointment.getEmployeeId() <= 0) {
|
||||
return;
|
||||
}
|
||||
DropdownOption selected = findOptionById(cbEmployee.getItems(), (long) selectedAppointment.getEmployeeId());
|
||||
if (selected != null && !Objects.equals(cbEmployee.getValue(), selected)) {
|
||||
cbEmployee.setValue(selected);
|
||||
}
|
||||
}
|
||||
|
||||
private DropdownOption findOptionById(List<DropdownOption> options, Long id) {
|
||||
if (id == null || options == null) {
|
||||
return null;
|
||||
}
|
||||
for (DropdownOption option : options) {
|
||||
if (option.getId() != null && option.getId().equals(id)) {
|
||||
return option;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void loadCustomerPets(Long customerId) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> pets = DropdownApi.getInstance().getCustomerPets(customerId);
|
||||
Platform.runLater(() -> {
|
||||
cbPet.setItems(FXCollections.observableArrayList(pets));
|
||||
cbPet.setDisable(pets == null || pets.isEmpty());
|
||||
cbPet.setPromptText(pets == null || pets.isEmpty() ? "No pets for selected customer" : "Select a pet");
|
||||
if (pendingPetSelectionId != null) {
|
||||
for (DropdownOption pet : cbPet.getItems()) {
|
||||
if (pet.getId() != null && pet.getId().equals(pendingPetSelectionId)) {
|
||||
cbPet.setValue(pet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
pendingPetSelectionId = null;
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AppointmentDialogController.loadCustomerPets",
|
||||
ex,
|
||||
"Loading customer pets for appointment dialog");
|
||||
cbPet.setDisable(true);
|
||||
cbPet.setPromptText("Unable to load pets");
|
||||
showError("Error loading pets for selected customer");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadServices() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> services = DropdownApi.getInstance().getServices();
|
||||
Platform.runLater(() -> {
|
||||
cbService.setItems(FXCollections.observableArrayList(services));
|
||||
applySelectedService();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AppointmentDialogController.loadServices",
|
||||
e,
|
||||
"Loading services for appointment dialog");
|
||||
cbService.setDisable(true);
|
||||
cbService.setPromptText("Unable to load services");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadAppointmentCustomers() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> customers = DropdownApi.getInstance().getAppointmentCustomers();
|
||||
Platform.runLater(() -> {
|
||||
cbCustomer.setItems(FXCollections.observableArrayList(customers));
|
||||
boolean hasCustomers = customers != null && !customers.isEmpty();
|
||||
cbCustomer.setDisable(!hasCustomers);
|
||||
cbPet.setDisable(true);
|
||||
cbPet.setItems(FXCollections.observableArrayList());
|
||||
cbCustomer.setPromptText(hasCustomers ? "Select a customer" : "No customers with pets yet");
|
||||
cbPet.setPromptText(hasCustomers ? "Select a customer first" : "No customer pets available");
|
||||
applySelectedCustomer();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AppointmentDialogController.loadAppointmentCustomers",
|
||||
e,
|
||||
"Loading appointment customers for appointment dialog");
|
||||
cbCustomer.setDisable(true);
|
||||
cbPet.setDisable(true);
|
||||
cbCustomer.setPromptText("Unable to load customers");
|
||||
cbPet.setPromptText("Unable to load pets");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadEmployees() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Long storeId = UserSession.getInstance().getStoreId();
|
||||
List<DropdownOption> employees;
|
||||
if (storeId != null && storeId > 0) {
|
||||
employees = DropdownApi.getInstance().getStoreEmployees(storeId);
|
||||
} else {
|
||||
employees = DropdownApi.getInstance().getEmployees();
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
cbEmployee.setItems(FXCollections.observableArrayList(employees));
|
||||
applySelectedEmployee();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"AppointmentDialogController.loadEmployees",
|
||||
e,
|
||||
"Loading employees for appointment dialog");
|
||||
cbEmployee.setDisable(true);
|
||||
cbEmployee.setPromptText("Unable to load employees");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.example.petshopdesktop.api.dto.inventory.InventoryResponse;
|
||||
import org.example.petshopdesktop.api.dto.product.ProductResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.InventoryApi;
|
||||
import org.example.petshopdesktop.api.endpoints.ProductApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.models.Product;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
@@ -127,6 +128,10 @@ public class InventoryDialogController {
|
||||
try {
|
||||
InventoryRequest request = new InventoryRequest();
|
||||
Product selectedProduct = cbProduct.getSelectionModel().getSelectedItem();
|
||||
Long storeId = UserSession.getInstance().getStoreId();
|
||||
if (storeId == null || storeId <= 0) {
|
||||
throw new IllegalStateException("Store is not set for this account");
|
||||
}
|
||||
request.setProdId((long) selectedProduct.getProdId());
|
||||
int quantity;
|
||||
try {
|
||||
@@ -135,6 +140,7 @@ public class InventoryDialogController {
|
||||
throw new IllegalArgumentException("Invalid quantity format");
|
||||
}
|
||||
request.setQuantity(quantity);
|
||||
request.setStoreId(storeId);
|
||||
|
||||
if (mode.equals("Add")) {
|
||||
InventoryApi.getInstance().createInventory(request);
|
||||
@@ -206,4 +212,4 @@ public class InventoryDialogController {
|
||||
lblMode.setText(mode + " Inventory");
|
||||
lblInventoryId.setVisible(mode.equals("Edit"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.EventHandler;
|
||||
@@ -10,8 +11,10 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.Validator;
|
||||
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
||||
import org.example.petshopdesktop.api.dto.pet.PetRequest;
|
||||
import org.example.petshopdesktop.api.dto.pet.PetResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.DropdownApi;
|
||||
import org.example.petshopdesktop.api.endpoints.PetApi;
|
||||
import org.example.petshopdesktop.models.Pet;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
@@ -20,6 +23,7 @@ import org.example.petshopdesktop.util.FilePickerSupport;
|
||||
|
||||
import java.io.File;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public class PetDialogController {
|
||||
|
||||
@@ -38,6 +42,12 @@ public class PetDialogController {
|
||||
@FXML
|
||||
private ComboBox<String> cbPetStatus;
|
||||
|
||||
@FXML
|
||||
private ComboBox<DropdownOption> cbCustomer;
|
||||
|
||||
@FXML
|
||||
private ComboBox<DropdownOption> cbStore;
|
||||
|
||||
@FXML
|
||||
private Label lblMode;
|
||||
|
||||
@@ -70,16 +80,54 @@ public class PetDialogController {
|
||||
private String currentImageUrl;
|
||||
private boolean removeImageRequested;
|
||||
|
||||
private Long pendingCustomerId = null;
|
||||
private Long pendingStoreId = null;
|
||||
|
||||
private ObservableList<String> statusList = FXCollections.observableArrayList(
|
||||
"Available", "Adopted"
|
||||
"Available", "Adopted", "Owned"
|
||||
);
|
||||
|
||||
@FXML
|
||||
void initialize() {
|
||||
|
||||
cbPetStatus.setItems(statusList); //set status combobox
|
||||
cbPetStatus.setItems(statusList);
|
||||
|
||||
cbCustomer.setCellFactory(param -> new ListCell<>() {
|
||||
@Override protected void updateItem(DropdownOption o, boolean empty) {
|
||||
super.updateItem(o, empty);
|
||||
setText(empty || o == null ? null : o.getLabel());
|
||||
}
|
||||
});
|
||||
cbCustomer.setButtonCell(new ListCell<>() {
|
||||
@Override protected void updateItem(DropdownOption o, boolean empty) {
|
||||
super.updateItem(o, empty);
|
||||
setText(empty || o == null ? null : o.getLabel());
|
||||
}
|
||||
});
|
||||
|
||||
cbStore.setCellFactory(param -> new ListCell<>() {
|
||||
@Override protected void updateItem(DropdownOption o, boolean empty) {
|
||||
super.updateItem(o, empty);
|
||||
setText(empty || o == null ? null : o.getLabel());
|
||||
}
|
||||
});
|
||||
cbStore.setButtonCell(new ListCell<>() {
|
||||
@Override protected void updateItem(DropdownOption o, boolean empty) {
|
||||
super.updateItem(o, empty);
|
||||
setText(empty || o == null ? null : o.getLabel());
|
||||
}
|
||||
});
|
||||
|
||||
cbCustomer.setVisible(false);
|
||||
cbStore.setVisible(false);
|
||||
|
||||
cbPetStatus.valueProperty().addListener((obs, oldVal, newVal) -> {
|
||||
boolean isOwned = "Owned".equalsIgnoreCase(newVal);
|
||||
boolean isAvailable = "Available".equalsIgnoreCase(newVal) || "Unadopted".equalsIgnoreCase(newVal);
|
||||
cbCustomer.setVisible(isOwned);
|
||||
cbStore.setVisible(isAvailable);
|
||||
});
|
||||
|
||||
//Set up mouse handlers for buttons
|
||||
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
@@ -97,6 +145,9 @@ public class PetDialogController {
|
||||
btnChangeImage.setOnMouseClicked(mouseEvent -> handleChangeImage());
|
||||
btnRemoveImage.setOnMouseClicked(mouseEvent -> handleRemoveImage());
|
||||
refreshImagePreview();
|
||||
|
||||
loadCustomers();
|
||||
loadStores();
|
||||
}
|
||||
|
||||
private void buttonSaveClicked(MouseEvent mouseEvent) {
|
||||
@@ -111,6 +162,10 @@ public class PetDialogController {
|
||||
if (cbPetStatus.getSelectionModel().getSelectedItem() == null){
|
||||
errorMsg += "Status is required";
|
||||
}
|
||||
String selectedStatus = cbPetStatus.getValue();
|
||||
if ("Owned".equalsIgnoreCase(selectedStatus) && cbCustomer.getValue() == null) {
|
||||
errorMsg += "Customer is required for Owned status\n";
|
||||
}
|
||||
|
||||
//Check validation (length size)
|
||||
errorMsg += Validator.isLessThanVarChars(txtPetName.getText(), "Pet Name", 50);
|
||||
@@ -184,9 +239,81 @@ public class PetDialogController {
|
||||
}
|
||||
request.setPetAge(age);
|
||||
|
||||
String status = cbPetStatus.getValue();
|
||||
if ("Owned".equalsIgnoreCase(status) && cbCustomer.getValue() != null) {
|
||||
request.setCustomerId(cbCustomer.getValue().getId());
|
||||
}
|
||||
if (("Available".equalsIgnoreCase(status) || "Unadopted".equalsIgnoreCase(status)) && cbStore.getValue() != null) {
|
||||
request.setStoreId(cbStore.getValue().getId());
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private void loadCustomers() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
|
||||
Platform.runLater(() -> {
|
||||
cbCustomer.setItems(FXCollections.observableArrayList(customers));
|
||||
applySelectedCustomer();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"PetDialogController.loadCustomers", e, "Loading customers");
|
||||
cbCustomer.setDisable(true);
|
||||
cbCustomer.setPromptText("Unable to load customers");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadStores() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> stores = DropdownApi.getInstance().getStores();
|
||||
Platform.runLater(() -> {
|
||||
cbStore.setItems(FXCollections.observableArrayList(stores));
|
||||
applySelectedStore();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"PetDialogController.loadStores", e, "Loading stores");
|
||||
cbStore.setDisable(true);
|
||||
cbStore.setPromptText("Unable to load stores");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void applySelectedCustomer() {
|
||||
if (pendingCustomerId == null) return;
|
||||
DropdownOption selected = findOptionById(cbCustomer.getItems(), pendingCustomerId);
|
||||
if (selected != null) {
|
||||
cbCustomer.setValue(selected);
|
||||
pendingCustomerId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void applySelectedStore() {
|
||||
if (pendingStoreId == null) return;
|
||||
DropdownOption selected = findOptionById(cbStore.getItems(), pendingStoreId);
|
||||
if (selected != null) {
|
||||
cbStore.setValue(selected);
|
||||
pendingStoreId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private DropdownOption findOptionById(List<DropdownOption> options, Long id) {
|
||||
if (id == null || options == null) return null;
|
||||
for (DropdownOption option : options) {
|
||||
if (option.getId() != null && option.getId().equals(id)) return option;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void closeStage(MouseEvent mouseEvent) {
|
||||
Node node = (Node) mouseEvent.getSource();
|
||||
Stage stage = (Stage) node.getScene().getWindow();
|
||||
@@ -206,14 +333,14 @@ public class PetDialogController {
|
||||
removeImageRequested = false;
|
||||
refreshImagePreview();
|
||||
|
||||
//get the right combobox selection
|
||||
pendingCustomerId = pet.getCustomerId() > 0 ? pet.getCustomerId() : null;
|
||||
pendingStoreId = pet.getStoreId() > 0 ? pet.getStoreId() : null;
|
||||
|
||||
for (String status : cbPetStatus.getItems()) {
|
||||
if(status.equals(pet.getPetStatus())){
|
||||
cbPetStatus.getSelectionModel().select(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ public class ProductDialogController {
|
||||
|
||||
//Set up combobox for selecting category
|
||||
try {
|
||||
List<DropdownOption> categories = DropdownApi.getInstance().getCategories();
|
||||
List<DropdownOption> categories = DropdownApi.getInstance().getProductCategories();
|
||||
if (categories != null) {
|
||||
ObservableList<DropdownOption> categoriesObs = FXCollections.observableArrayList(categories);
|
||||
cbProdCategory.setItems(categoriesObs);
|
||||
|
||||
@@ -4,8 +4,12 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
|
||||
@@ -90,6 +94,7 @@ public class RefundDialogController {
|
||||
private final ObservableList<SaleItemResponse> originalItems = FXCollections.observableArrayList();
|
||||
private final ObservableList<RefundItem> refundItems = FXCollections.observableArrayList();
|
||||
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
|
||||
private boolean refundInProgress;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
@@ -100,17 +105,19 @@ public class RefundDialogController {
|
||||
}
|
||||
|
||||
private void setupTables() {
|
||||
colOriginalProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
|
||||
colOriginalQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||
colOriginalUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
||||
colOriginalTotal.setCellValueFactory(new PropertyValueFactory<>("lineTotal"));
|
||||
tvOriginalItems.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
colOriginalProduct.setCellValueFactory(cell -> new ReadOnlyStringWrapper(cell.getValue().getProductName()));
|
||||
colOriginalQuantity.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getQuantity()));
|
||||
colOriginalUnitPrice.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getUnitPrice()));
|
||||
colOriginalTotal.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getLineTotal()));
|
||||
tvOriginalItems.setItems(originalItems);
|
||||
tvOriginalItems.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
|
||||
colRefundProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
|
||||
colRefundQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||
colRefundUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
||||
colRefundTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
|
||||
tvRefundItems.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
colRefundProduct.setCellValueFactory(cell -> new ReadOnlyStringWrapper(cell.getValue().getProductName()));
|
||||
colRefundQuantity.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getQuantity()));
|
||||
colRefundUnitPrice.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getUnitPrice()));
|
||||
colRefundTotal.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getTotal()));
|
||||
tvRefundItems.setItems(refundItems);
|
||||
tvRefundItems.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
}
|
||||
@@ -125,12 +132,13 @@ public class RefundDialogController {
|
||||
return;
|
||||
}
|
||||
txtSaleId.setText(String.valueOf(saleId));
|
||||
loadSale();
|
||||
Platform.runLater(this::loadSale);
|
||||
}
|
||||
|
||||
private void loadSale() {
|
||||
String saleIdText = txtSaleId.getText().trim();
|
||||
if (saleIdText.isEmpty()) {
|
||||
clearLoadedSale();
|
||||
showError("Load Sale", "Enter a transaction ID.");
|
||||
return;
|
||||
}
|
||||
@@ -139,22 +147,36 @@ public class RefundDialogController {
|
||||
try {
|
||||
saleId = Long.parseLong(saleIdText);
|
||||
} catch (NumberFormatException e) {
|
||||
clearLoadedSale();
|
||||
showError("Load Sale", "Invalid transaction ID.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
List<SaleResponse> allSales = SaleApi.getInstance().listSales(0, 1000, null);
|
||||
currentSale = SaleApi.getInstance().getSale(saleId);
|
||||
if (Boolean.TRUE.equals(currentSale.getIsRefund())) {
|
||||
clearLoadedSale();
|
||||
showError("Load Sale", "Select an original sale, not a refund record.");
|
||||
return;
|
||||
}
|
||||
List<SaleResponse> previousRefunds = allSales.stream()
|
||||
.filter(s -> Boolean.TRUE.equals(s.getIsRefund()) && saleId.equals(s.getOriginalSaleId()))
|
||||
.collect(Collectors.toList());
|
||||
setLoadingState(true, "Loading sale...");
|
||||
clearLoadedSale();
|
||||
|
||||
Task<LoadedSaleData> task = new Task<>() {
|
||||
@Override
|
||||
protected LoadedSaleData call() throws Exception {
|
||||
List<SaleResponse> allSales = SaleApi.getInstance().listAllSales(null);
|
||||
SaleResponse sale = SaleApi.getInstance().getSale(saleId);
|
||||
if (Boolean.TRUE.equals(sale.getIsRefund())) {
|
||||
throw new IllegalStateException("Select an original sale, not a refund record.");
|
||||
}
|
||||
List<SaleResponse> previousRefunds = allSales.stream()
|
||||
.filter(s -> Boolean.TRUE.equals(s.getIsRefund()) && saleId.equals(s.getOriginalSaleId()))
|
||||
.collect(Collectors.toList());
|
||||
List<SaleItemResponse> refundableItems = buildRefundableItems(sale, previousRefunds);
|
||||
if (refundableItems.isEmpty()) {
|
||||
throw new IllegalStateException("This sale has no remaining refundable items.");
|
||||
}
|
||||
return new LoadedSaleData(sale, refundableItems);
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(event -> {
|
||||
LoadedSaleData loaded = task.getValue();
|
||||
currentSale = loaded.sale();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||
String saleInfo = String.format("Sale Date: %s | Employee: %s | Original Total: %s | Payment: %s",
|
||||
currentSale.getSaleDate().format(formatter),
|
||||
@@ -162,26 +184,25 @@ public class RefundDialogController {
|
||||
currency.format(currentSale.getTotalAmount()),
|
||||
currentSale.getPaymentMethod());
|
||||
lblSaleInfo.setText(saleInfo);
|
||||
|
||||
List<SaleItemResponse> refundableItems = buildRefundableItems(currentSale, previousRefunds);
|
||||
if (refundableItems.isEmpty()) {
|
||||
showError("Load Sale", "This sale has no remaining refundable items.");
|
||||
return;
|
||||
}
|
||||
|
||||
baseOriginalItems.clear();
|
||||
baseOriginalItems.addAll(copySaleItems(refundableItems));
|
||||
originalItems.setAll(copySaleItems(refundableItems));
|
||||
baseOriginalItems.addAll(copySaleItems(loaded.refundableItems()));
|
||||
originalItems.setAll(copySaleItems(loaded.refundableItems()));
|
||||
cbPaymentMethod.getSelectionModel().select(currentSale.getPaymentMethod());
|
||||
|
||||
refundItems.clear();
|
||||
updateOriginalItemAvailability();
|
||||
updateRefundTotal();
|
||||
setLoadingState(false, saleInfo);
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("RefundDialogController.btnLoadSaleClicked", e, "Loading sale");
|
||||
task.setOnFailed(event -> {
|
||||
clearLoadedSale();
|
||||
Throwable e = task.getException();
|
||||
ActivityLogger.getInstance().logException("RefundDialogController.btnLoadSaleClicked", (Exception) e, "Loading sale");
|
||||
setLoadingState(false, "");
|
||||
showError("Load Sale", e.getMessage() != null ? e.getMessage() : "Could not load sale.");
|
||||
}
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -248,6 +269,9 @@ public class RefundDialogController {
|
||||
|
||||
@FXML
|
||||
void btnProcessRefundClicked(ActionEvent event) {
|
||||
if (refundInProgress) {
|
||||
return;
|
||||
}
|
||||
if (currentSale == null) {
|
||||
showError("Process Refund", "Load a sale first.");
|
||||
return;
|
||||
@@ -280,36 +304,53 @@ public class RefundDialogController {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SaleRequest request = new SaleRequest();
|
||||
request.setStoreId(storeId);
|
||||
request.setPaymentMethod(payment);
|
||||
request.setIsRefund(true);
|
||||
request.setOriginalSaleId(currentSale.getSaleId());
|
||||
SaleRequest request = new SaleRequest();
|
||||
request.setStoreId(storeId);
|
||||
request.setPaymentMethod(payment);
|
||||
request.setIsRefund(true);
|
||||
request.setOriginalSaleId(currentSale.getSaleId());
|
||||
|
||||
List<SaleItemRequest> items = new ArrayList<>();
|
||||
for (RefundItem item : refundItems) {
|
||||
SaleItemRequest saleItem = new SaleItemRequest();
|
||||
saleItem.setProdId((long) item.getProdId());
|
||||
saleItem.setQuantity(-item.getQuantity());
|
||||
items.add(saleItem);
|
||||
List<SaleItemRequest> items = new ArrayList<>();
|
||||
for (RefundItem item : refundItems) {
|
||||
SaleItemRequest saleItem = new SaleItemRequest();
|
||||
saleItem.setProdId((long) item.getProdId());
|
||||
saleItem.setQuantity(-item.getQuantity());
|
||||
items.add(saleItem);
|
||||
}
|
||||
request.setItems(items);
|
||||
|
||||
refundInProgress = true;
|
||||
setLoadingState(true, lblSaleInfo.getText());
|
||||
|
||||
Task<SaleResponse> task = new Task<>() {
|
||||
@Override
|
||||
protected SaleResponse call() throws Exception {
|
||||
return SaleApi.getInstance().createSale(request);
|
||||
}
|
||||
request.setItems(items);
|
||||
|
||||
SaleResponse refundResponse = SaleApi.getInstance().createSale(request);
|
||||
};
|
||||
|
||||
task.setOnSucceeded(evt -> {
|
||||
refundInProgress = false;
|
||||
setLoadingState(false, lblSaleInfo.getText());
|
||||
SaleResponse refundResponse = task.getValue();
|
||||
Alert success = new Alert(Alert.AlertType.INFORMATION);
|
||||
success.setTitle("Refund Processed");
|
||||
success.setHeaderText(null);
|
||||
success.setContentText("Refund ID " + refundResponse.getSaleId() + " was created successfully.");
|
||||
success.showAndWait();
|
||||
|
||||
closeDialog();
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("RefundDialogController.btnProcessRefundClicked", e, "Processing refund");
|
||||
showError("Process Refund", e.getMessage() != null ? e.getMessage() : "Could not process refund.");
|
||||
}
|
||||
task.setOnFailed(evt -> {
|
||||
refundInProgress = false;
|
||||
setLoadingState(false, lblSaleInfo.getText());
|
||||
Throwable e = task.getException();
|
||||
Exception ex = e instanceof Exception ? (Exception) e : new RuntimeException(e);
|
||||
ActivityLogger.getInstance().logException("RefundDialogController.btnProcessRefundClicked", ex, "Processing refund");
|
||||
showError("Process Refund", e != null && e.getMessage() != null ? e.getMessage() : "Could not process refund.");
|
||||
});
|
||||
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -326,6 +367,18 @@ public class RefundDialogController {
|
||||
updateRefundTotal();
|
||||
}
|
||||
|
||||
private void setLoadingState(boolean loading, String message) {
|
||||
btnLoadSale.setDisable(loading);
|
||||
btnAddToRefund.setDisable(loading);
|
||||
btnRemoveFromRefund.setDisable(loading);
|
||||
btnProcessRefund.setDisable(loading);
|
||||
txtSaleId.setDisable(loading);
|
||||
cbPaymentMethod.setDisable(loading);
|
||||
if (loading) {
|
||||
lblSaleInfo.setText(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void addOrMergeRefundItem(SaleItemResponse selected, int quantity) {
|
||||
for (int i = 0; i < refundItems.size(); i++) {
|
||||
RefundItem existing = refundItems.get(i);
|
||||
@@ -486,4 +539,7 @@ public class RefundDialogController {
|
||||
return quantity * unitPrice;
|
||||
}
|
||||
}
|
||||
|
||||
private record LoadedSaleData(SaleResponse sale, List<SaleItemResponse> refundableItems) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.models.SaleDetail;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Locale;
|
||||
|
||||
public class SaleDetailDialogController {
|
||||
|
||||
@FXML private Label lblSaleId;
|
||||
@FXML private Label lblSaleDate;
|
||||
@FXML private Label lblEmployee;
|
||||
@FXML private Label lblPayment;
|
||||
@FXML private Label lblTotal;
|
||||
@FXML private TableView<SaleDetail.SaleDetailItem> tvItems;
|
||||
@FXML private TableColumn<SaleDetail.SaleDetailItem, String> colProduct;
|
||||
@FXML private TableColumn<SaleDetail.SaleDetailItem, Integer> colQuantity;
|
||||
@FXML private TableColumn<SaleDetail.SaleDetailItem, Double> colUnitPrice;
|
||||
@FXML private TableColumn<SaleDetail.SaleDetailItem, Double> colLineTotal;
|
||||
|
||||
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
tvItems.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
colProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
|
||||
colQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||
colUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
||||
colLineTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
|
||||
}
|
||||
|
||||
public void displaySaleDetails(SaleDetail sale) {
|
||||
lblSaleId.setText(String.valueOf(sale.getSaleId()));
|
||||
lblSaleDate.setText(sale.getSaleDate() != null ? sale.getSaleDate().format(DATE_FORMATTER) : "");
|
||||
lblEmployee.setText(sale.getEmployeeName() != null ? sale.getEmployeeName() : "");
|
||||
lblPayment.setText(sale.getPaymentMethod() != null ? sale.getPaymentMethod() : "");
|
||||
lblTotal.setText(currency.format(sale.getTotalAmount()));
|
||||
tvItems.setItems(sale.getItems());
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnCloseClicked() {
|
||||
Stage stage = (Stage) tvItems.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.Validator;
|
||||
import org.example.petshopdesktop.api.dto.employee.EmployeeRequest;
|
||||
import org.example.petshopdesktop.api.endpoints.EmployeeApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.models.StaffAccount;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
@@ -104,14 +105,18 @@ public class StaffEditDialogController {
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Long storeId = UserSession.getInstance().getStoreId();
|
||||
EmployeeRequest request = new EmployeeRequest();
|
||||
request.setUsername(username);
|
||||
request.setPassword(password.isEmpty() ? null : password);
|
||||
request.setFirstName(firstName);
|
||||
request.setLastName(lastName);
|
||||
request.setFullName(firstName + " " + lastName);
|
||||
request.setEmail(email);
|
||||
request.setPhone(phone);
|
||||
request.setRole(staffAccount.getRole());
|
||||
request.setStaffRole("Staff");
|
||||
request.setPrimaryStoreId(storeId);
|
||||
request.setActive(staffAccount.isActive());
|
||||
|
||||
EmployeeApi.getInstance().updateEmployee(staffAccount.getEmployeeId(), request);
|
||||
|
||||
@@ -11,6 +11,7 @@ import javafx.scene.control.TextField;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.dto.employee.EmployeeRequest;
|
||||
import org.example.petshopdesktop.api.endpoints.EmployeeApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.Validator;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
@@ -89,14 +90,18 @@ public class StaffRegisterDialogController {
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Long storeId = UserSession.getInstance().getStoreId();
|
||||
EmployeeRequest request = new EmployeeRequest();
|
||||
request.setUsername(username);
|
||||
request.setPassword(password);
|
||||
request.setFirstName(firstName);
|
||||
request.setLastName(lastName);
|
||||
request.setFullName(firstName + " " + lastName);
|
||||
request.setEmail(email);
|
||||
request.setPhone(phone);
|
||||
request.setRole("STAFF");
|
||||
request.setStaffRole("Staff");
|
||||
request.setPrimaryStoreId(storeId);
|
||||
request.setActive(true);
|
||||
|
||||
EmployeeApi.getInstance().createEmployee(request);
|
||||
|
||||
@@ -8,18 +8,22 @@ public class Adoption {
|
||||
private SimpleIntegerProperty adoptionId;
|
||||
private SimpleIntegerProperty petId;
|
||||
private SimpleIntegerProperty customerId;
|
||||
private SimpleIntegerProperty employeeId;
|
||||
private SimpleStringProperty petName;
|
||||
private SimpleStringProperty customerName;
|
||||
private SimpleStringProperty employeeName;
|
||||
private SimpleStringProperty adoptionDate;
|
||||
private SimpleDoubleProperty adoptionFee;
|
||||
private SimpleStringProperty adoptionStatus;
|
||||
|
||||
public Adoption(int adoptionId, int petId, int customerId, String petName, String customerName, String adoptionDate, double adoptionFee, String adoptionStatus) {
|
||||
public Adoption(int adoptionId, int petId, int customerId, int employeeId, String petName, String customerName, String employeeName, String adoptionDate, double adoptionFee, String adoptionStatus) {
|
||||
this.adoptionId = new SimpleIntegerProperty(adoptionId);
|
||||
this.petId = new SimpleIntegerProperty(petId);
|
||||
this.customerId = new SimpleIntegerProperty(customerId);
|
||||
this.employeeId = new SimpleIntegerProperty(employeeId);
|
||||
this.petName = new SimpleStringProperty(petName);
|
||||
this.customerName = new SimpleStringProperty(customerName);
|
||||
this.employeeName = new SimpleStringProperty(employeeName);
|
||||
this.adoptionDate = new SimpleStringProperty(adoptionDate);
|
||||
this.adoptionFee = new SimpleDoubleProperty(adoptionFee);
|
||||
this.adoptionStatus = new SimpleStringProperty(adoptionStatus);
|
||||
@@ -43,6 +47,12 @@ public class Adoption {
|
||||
|
||||
public SimpleIntegerProperty customerIdProperty() { return customerId; }
|
||||
|
||||
public int getEmployeeId() { return employeeId.get(); }
|
||||
|
||||
public void setEmployeeId(int employeeId) { this.employeeId.set(employeeId); }
|
||||
|
||||
public SimpleIntegerProperty employeeIdProperty() { return employeeId; }
|
||||
|
||||
public String getPetName() { return petName.get(); }
|
||||
|
||||
public void setPetName(String petName) { this.petName.set(petName); }
|
||||
@@ -55,6 +65,12 @@ public class Adoption {
|
||||
|
||||
public SimpleStringProperty customerNameProperty() { return customerName; }
|
||||
|
||||
public String getEmployeeName() { return employeeName.get(); }
|
||||
|
||||
public void setEmployeeName(String employeeName) { this.employeeName.set(employeeName); }
|
||||
|
||||
public SimpleStringProperty employeeNameProperty() { return employeeName; }
|
||||
|
||||
public String getAdoptionDate() { return adoptionDate.get(); }
|
||||
|
||||
public void setAdoptionDate(String adoptionDate) { this.adoptionDate.set(adoptionDate); }
|
||||
|
||||
@@ -13,6 +13,10 @@ public class Pet {
|
||||
private SimpleStringProperty petStatus;
|
||||
private SimpleDoubleProperty petPrice;
|
||||
private SimpleStringProperty imageUrl;
|
||||
private SimpleStringProperty customerName = new SimpleStringProperty("");
|
||||
private SimpleStringProperty storeName = new SimpleStringProperty("");
|
||||
private long customerId = 0L;
|
||||
private long storeId = 0L;
|
||||
|
||||
public Pet(int petId, String petName, String petSpecies, String petBreed, int petAge, String petStatus, double petPrice, String imageUrl) {
|
||||
this.petId = new SimpleIntegerProperty(petId);
|
||||
@@ -120,4 +124,44 @@ public class Pet {
|
||||
public SimpleStringProperty imageUrlProperty() {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
public String getCustomerName() {
|
||||
return customerName.get();
|
||||
}
|
||||
|
||||
public void setCustomerName(String customerName) {
|
||||
this.customerName.set(customerName != null ? customerName : "");
|
||||
}
|
||||
|
||||
public SimpleStringProperty customerNameProperty() {
|
||||
return customerName;
|
||||
}
|
||||
|
||||
public String getStoreName() {
|
||||
return storeName.get();
|
||||
}
|
||||
|
||||
public void setStoreName(String storeName) {
|
||||
this.storeName.set(storeName != null ? storeName : "");
|
||||
}
|
||||
|
||||
public SimpleStringProperty storeNameProperty() {
|
||||
return storeName;
|
||||
}
|
||||
|
||||
public long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0">
|
||||
@@ -131,6 +132,20 @@
|
||||
</ComboBox>
|
||||
</children>
|
||||
</VBox>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="2">
|
||||
<children>
|
||||
<Label text="Employee:" textFill="#2c3e50">
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<ComboBox fx:id="cbEmployee" prefHeight="29.0" prefWidth="336.0" promptText="Select Employee" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
|
||||
<padding>
|
||||
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
|
||||
</padding>
|
||||
</ComboBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
<VBox.margin>
|
||||
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0">
|
||||
@@ -190,9 +191,9 @@
|
||||
</children>
|
||||
|
||||
</VBox>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="2">
|
||||
<children>
|
||||
<Label text="Pet:" textFill="#2c3e50">
|
||||
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="2">
|
||||
<children>
|
||||
<Label text="Pet:" textFill="#2c3e50">
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
@@ -201,9 +202,23 @@
|
||||
<padding>
|
||||
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
|
||||
</padding>
|
||||
</ComboBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</ComboBox>
|
||||
</children>
|
||||
</VBox>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.columnIndex="1" GridPane.rowIndex="3">
|
||||
<children>
|
||||
<Label text="Employee:" textFill="#2c3e50">
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<ComboBox fx:id="cbEmployee" prefHeight="29.0" prefWidth="336.0" promptText="Select Employee" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
|
||||
<padding>
|
||||
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
|
||||
</padding>
|
||||
</ComboBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
<VBox.margin>
|
||||
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
|
||||
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="560.0" prefWidth="790.0" spacing="20.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.PetDialogController">
|
||||
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="660.0" prefWidth="790.0" spacing="20.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.PetDialogController">
|
||||
<children>
|
||||
<HBox alignment="CENTER_LEFT" prefHeight="79.0" prefWidth="727.0" spacing="20.0" style="-fx-background-color: #2C3E50; -fx-background-radius: 14;">
|
||||
<children>
|
||||
@@ -153,6 +153,34 @@
|
||||
</TextField>
|
||||
</children>
|
||||
</VBox>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="3">
|
||||
<children>
|
||||
<Label text="Customer:" textFill="#2c3e50">
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<ComboBox fx:id="cbCustomer" prefHeight="29.0" prefWidth="336.0" promptText="Select Customer" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
|
||||
<padding>
|
||||
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
|
||||
</padding>
|
||||
</ComboBox>
|
||||
</children>
|
||||
</VBox>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.columnIndex="1" GridPane.rowIndex="3">
|
||||
<children>
|
||||
<Label text="Store:" textFill="#2c3e50">
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<ComboBox fx:id="cbStore" prefHeight="29.0" prefWidth="336.0" promptText="Select Store" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
|
||||
<padding>
|
||||
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
|
||||
</padding>
|
||||
</ComboBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
<VBox.margin>
|
||||
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
|
||||
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="900.0" spacing="20.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.RefundDialogController">
|
||||
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="720.0" prefWidth="920.0" spacing="22.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.RefundDialogController">
|
||||
<padding>
|
||||
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||
</padding>
|
||||
@@ -84,14 +84,14 @@
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
<VBox spacing="10.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 2; -fx-border-color: #FF6b6b; -fx-border-radius: 14;" VBox.vgrow="ALWAYS">
|
||||
<padding>
|
||||
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
|
||||
</padding>
|
||||
<children>
|
||||
<HBox alignment="CENTER_LEFT" spacing="10.0">
|
||||
<children>
|
||||
<Label text="Original Items" textFill="#2c3e50">
|
||||
<VBox spacing="18.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 2; -fx-border-color: #FF6b6b; -fx-border-radius: 14;" VBox.vgrow="ALWAYS">
|
||||
<padding>
|
||||
<Insets bottom="18.0" left="18.0" right="18.0" top="18.0" />
|
||||
</padding>
|
||||
<children>
|
||||
<HBox alignment="CENTER_LEFT" spacing="14.0">
|
||||
<children>
|
||||
<Label text="Original Items" textFill="#2c3e50">
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
@@ -107,20 +107,20 @@
|
||||
</Button>
|
||||
</children>
|
||||
</HBox>
|
||||
<TableView fx:id="tvOriginalItems" prefHeight="150.0" style="-fx-background-color: white; -fx-background-radius: 10;">
|
||||
<columns>
|
||||
<TableColumn fx:id="colOriginalProduct" prefWidth="350.0" text="Product Name" />
|
||||
<TableColumn fx:id="colOriginalQuantity" prefWidth="120.0" text="Quantity" />
|
||||
<TableColumn fx:id="colOriginalUnitPrice" prefWidth="150.0" text="Unit Price" />
|
||||
<TableColumn fx:id="colOriginalTotal" prefWidth="150.0" text="Total" />
|
||||
</columns>
|
||||
</TableView>
|
||||
<TableView fx:id="tvOriginalItems" prefHeight="190.0" style="-fx-background-color: white; -fx-background-radius: 10; -fx-border-color: #E8EBED; -fx-border-radius: 10;">
|
||||
<columns>
|
||||
<TableColumn fx:id="colOriginalProduct" prefWidth="430.0" text="Product Name" />
|
||||
<TableColumn fx:id="colOriginalQuantity" prefWidth="140.0" text="Quantity" />
|
||||
<TableColumn fx:id="colOriginalUnitPrice" prefWidth="170.0" text="Unit Price" />
|
||||
<TableColumn fx:id="colOriginalTotal" prefWidth="170.0" text="Total" />
|
||||
</columns>
|
||||
</TableView>
|
||||
|
||||
<Separator />
|
||||
<Separator />
|
||||
|
||||
<HBox alignment="CENTER_LEFT" spacing="10.0">
|
||||
<children>
|
||||
<Label text="Refund Items" textFill="#2c3e50">
|
||||
<HBox alignment="CENTER_LEFT" spacing="14.0">
|
||||
<children>
|
||||
<Label text="Refund Items" textFill="#2c3e50">
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
@@ -136,16 +136,16 @@
|
||||
</Button>
|
||||
</children>
|
||||
</HBox>
|
||||
<TableView fx:id="tvRefundItems" prefHeight="150.0" style="-fx-background-color: white; -fx-background-radius: 10;">
|
||||
<columns>
|
||||
<TableColumn fx:id="colRefundProduct" prefWidth="350.0" text="Product Name" />
|
||||
<TableColumn fx:id="colRefundQuantity" prefWidth="120.0" text="Quantity" />
|
||||
<TableColumn fx:id="colRefundUnitPrice" prefWidth="150.0" text="Unit Price" />
|
||||
<TableColumn fx:id="colRefundTotal" prefWidth="150.0" text="Total" />
|
||||
</columns>
|
||||
</TableView>
|
||||
</children>
|
||||
</VBox>
|
||||
<TableView fx:id="tvRefundItems" prefHeight="190.0" style="-fx-background-color: white; -fx-background-radius: 10; -fx-border-color: #E8EBED; -fx-border-radius: 10;" VBox.vgrow="ALWAYS">
|
||||
<columns>
|
||||
<TableColumn fx:id="colRefundProduct" prefWidth="430.0" text="Product Name" />
|
||||
<TableColumn fx:id="colRefundQuantity" prefWidth="140.0" text="Quantity" />
|
||||
<TableColumn fx:id="colRefundUnitPrice" prefWidth="170.0" text="Unit Price" />
|
||||
<TableColumn fx:id="colRefundTotal" prefWidth="170.0" text="Total" />
|
||||
</columns>
|
||||
</TableView>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
<VBox spacing="12.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 2; -fx-border-color: #FF6b6b; -fx-border-radius: 14;">
|
||||
<padding>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TableColumn?>
|
||||
<?import javafx.scene.control.TableView?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
|
||||
<VBox prefHeight="520.0" prefWidth="760.0" spacing="18.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.SaleDetailDialogController">
|
||||
<padding>
|
||||
<Insets bottom="18.0" left="18.0" right="18.0" top="18.0" />
|
||||
</padding>
|
||||
<children>
|
||||
<HBox alignment="CENTER_LEFT" spacing="12.0" style="-fx-background-color: #4ECDC4; -fx-background-radius: 12;">
|
||||
<padding>
|
||||
<Insets bottom="14.0" left="16.0" right="16.0" top="14.0" />
|
||||
</padding>
|
||||
<children>
|
||||
<Label text="Sale Details" textFill="WHITE">
|
||||
<font>
|
||||
<Font name="System Bold" size="24.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Region HBox.hgrow="ALWAYS" />
|
||||
<Button mnemonicParsing="false" onAction="#btnCloseClicked" style="-fx-background-color: white; -fx-text-fill: #2c3e50; -fx-background-radius: 8;" text="Close" />
|
||||
</children>
|
||||
</HBox>
|
||||
|
||||
<GridPane hgap="16.0" vgap="12.0" style="-fx-background-color: white; -fx-background-radius: 12; -fx-border-color: #e6e6e6; -fx-border-radius: 12; -fx-border-width: 1;">
|
||||
<padding>
|
||||
<Insets bottom="16.0" left="16.0" right="16.0" top="16.0" />
|
||||
</padding>
|
||||
<children>
|
||||
<Label text="Sale ID" GridPane.columnIndex="0" GridPane.rowIndex="0" />
|
||||
<Label fx:id="lblSaleId" GridPane.columnIndex="1" GridPane.rowIndex="0" />
|
||||
<Label text="Date" GridPane.columnIndex="2" GridPane.rowIndex="0" />
|
||||
<Label fx:id="lblSaleDate" GridPane.columnIndex="3" GridPane.rowIndex="0" />
|
||||
<Label text="Employee" GridPane.columnIndex="0" GridPane.rowIndex="1" />
|
||||
<Label fx:id="lblEmployee" GridPane.columnIndex="1" GridPane.rowIndex="1" />
|
||||
<Label text="Payment" GridPane.columnIndex="2" GridPane.rowIndex="1" />
|
||||
<Label fx:id="lblPayment" GridPane.columnIndex="3" GridPane.rowIndex="1" />
|
||||
<Label text="Total" GridPane.columnIndex="0" GridPane.rowIndex="2" />
|
||||
<Label fx:id="lblTotal" GridPane.columnIndex="1" GridPane.rowIndex="2" />
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
<TableView fx:id="tvItems" prefHeight="320.0" VBox.vgrow="ALWAYS">
|
||||
<columns>
|
||||
<TableColumn fx:id="colProduct" text="Product" prefWidth="330.0" />
|
||||
<TableColumn fx:id="colQuantity" text="Qty" prefWidth="90.0" />
|
||||
<TableColumn fx:id="colUnitPrice" text="Unit Price" prefWidth="130.0" />
|
||||
<TableColumn fx:id="colLineTotal" text="Total" prefWidth="130.0" />
|
||||
</columns>
|
||||
</TableView>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -91,7 +91,7 @@
|
||||
</padding>
|
||||
</Label>
|
||||
|
||||
<Button fx:id="btnAnalytics" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnAnalyticsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Analytics" textFill="#cbd5e1">
|
||||
<Button fx:id="btnAnalytics" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnAnalyticsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="📊 Analytics" textFill="#cbd5e1">
|
||||
<font>
|
||||
<Font name="System" size="12.0" />
|
||||
</font>
|
||||
@@ -99,7 +99,7 @@
|
||||
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||
</padding>
|
||||
</Button>
|
||||
<Button fx:id="btnSalesHistory" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnSalesHistoryClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Sales History" textFill="#cbd5e1">
|
||||
<Button fx:id="btnSalesHistory" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnSalesHistoryClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="💰 Sales History" textFill="#cbd5e1">
|
||||
<font>
|
||||
<Font name="System" size="12.0" />
|
||||
</font>
|
||||
@@ -107,7 +107,7 @@
|
||||
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||
</padding>
|
||||
</Button>
|
||||
<Button fx:id="btnAppointments" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnAppointmentsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Appointments" textFill="#cbd5e1">
|
||||
<Button fx:id="btnAppointments" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnAppointmentsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="📅 Appointments" textFill="#cbd5e1">
|
||||
<font>
|
||||
<Font name="System" size="12.0" />
|
||||
</font>
|
||||
@@ -115,7 +115,7 @@
|
||||
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||
</padding>
|
||||
</Button>
|
||||
<Button fx:id="btnServices" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnServicesClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Services" textFill="#cbd5e1">
|
||||
<Button fx:id="btnServices" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnServicesClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="✂️ Services" textFill="#cbd5e1">
|
||||
<font>
|
||||
<Font name="System" size="12.0" />
|
||||
</font>
|
||||
@@ -123,7 +123,7 @@
|
||||
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||
</padding>
|
||||
</Button>
|
||||
<Button fx:id="btnChat" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnChatClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Chat" textFill="#cbd5e1">
|
||||
<Button fx:id="btnChat" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnChatClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="💬 Chat" textFill="#cbd5e1">
|
||||
<font>
|
||||
<Font name="System" size="12.0" />
|
||||
</font>
|
||||
@@ -143,7 +143,7 @@
|
||||
</padding>
|
||||
</Label>
|
||||
|
||||
<Button fx:id="btnPets" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnPetsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Pets" textFill="#cbd5e1">
|
||||
<Button fx:id="btnPets" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnPetsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="🐾 Pets" textFill="#cbd5e1">
|
||||
<font>
|
||||
<Font name="System" size="12.0" />
|
||||
</font>
|
||||
@@ -151,7 +151,7 @@
|
||||
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||
</padding>
|
||||
</Button>
|
||||
<Button fx:id="btnAdoptions" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnAdoptionsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Adoptions" textFill="#cbd5e1">
|
||||
<Button fx:id="btnAdoptions" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnAdoptionsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="🏠 Adoptions" textFill="#cbd5e1">
|
||||
<font>
|
||||
<Font name="System" size="12.0" />
|
||||
</font>
|
||||
@@ -159,7 +159,7 @@
|
||||
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||
</padding>
|
||||
</Button>
|
||||
<Button fx:id="btnProducts" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnProductsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Products" textFill="#cbd5e1">
|
||||
<Button fx:id="btnProducts" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnProductsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="📦 Products" textFill="#cbd5e1">
|
||||
<font>
|
||||
<Font name="System" size="12.0" />
|
||||
</font>
|
||||
@@ -179,7 +179,7 @@
|
||||
</padding>
|
||||
</Label>
|
||||
|
||||
<Button fx:id="btnInventory" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnInventoryClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Inventory" textFill="#cbd5e1">
|
||||
<Button fx:id="btnInventory" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnInventoryClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="📋 Inventory" textFill="#cbd5e1">
|
||||
<font>
|
||||
<Font name="System" size="12.0" />
|
||||
</font>
|
||||
@@ -187,7 +187,7 @@
|
||||
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||
</padding>
|
||||
</Button>
|
||||
<Button fx:id="btnSuppliers" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnSuppliersClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Suppliers" textFill="#cbd5e1">
|
||||
<Button fx:id="btnSuppliers" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnSuppliersClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="🚛 Suppliers" textFill="#cbd5e1">
|
||||
<font>
|
||||
<Font name="System" size="12.0" />
|
||||
</font>
|
||||
@@ -195,7 +195,7 @@
|
||||
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||
</padding>
|
||||
</Button>
|
||||
<Button fx:id="btnProductSuppliers" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnProductSuppliersClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Product-Suppliers" textFill="#cbd5e1">
|
||||
<Button fx:id="btnProductSuppliers" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnProductSuppliersClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="🔗 Product-Suppliers" textFill="#cbd5e1">
|
||||
<font>
|
||||
<Font name="System" size="12.0" />
|
||||
</font>
|
||||
@@ -203,7 +203,7 @@
|
||||
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||
</padding>
|
||||
</Button>
|
||||
<Button fx:id="btnPurchaseOrders" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnPurchaseOrdersClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Purchase Orders" textFill="#cbd5e1">
|
||||
<Button fx:id="btnPurchaseOrders" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnPurchaseOrdersClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="📝 Purchase Orders" textFill="#cbd5e1">
|
||||
<font>
|
||||
<Font name="System" size="12.0" />
|
||||
</font>
|
||||
@@ -211,7 +211,7 @@
|
||||
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||
</padding>
|
||||
</Button>
|
||||
<Button fx:id="btnStaffAccounts" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnStaffAccountsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Staff Accounts" textFill="#cbd5e1">
|
||||
<Button fx:id="btnStaffAccounts" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnStaffAccountsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="👥 Staff Accounts" textFill="#cbd5e1">
|
||||
<font>
|
||||
<Font name="System" size="12.0" />
|
||||
</font>
|
||||
@@ -220,7 +220,7 @@
|
||||
</padding>
|
||||
</Button>
|
||||
|
||||
<Button fx:id="btnLogout" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnLogoutClicked" style="-fx-background-color: rgba(255,255,255,0.08); -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Logout" textFill="#e2e8f0">
|
||||
<Button fx:id="btnLogout" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnLogoutClicked" style="-fx-background-color: rgba(255,255,255,0.08); -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="🚪 Logout" textFill="#e2e8f0">
|
||||
<font>
|
||||
<Font name="System" size="12.0" />
|
||||
</font>
|
||||
|
||||
@@ -68,11 +68,12 @@
|
||||
<TableView fx:id="tvAdoptions" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
||||
<columns>
|
||||
<TableColumn fx:id="colAdoptionId" prefWidth="60.0" text="ID" />
|
||||
<TableColumn fx:id="colPetId" prefWidth="66.2857666015625" text="Pet ID" />
|
||||
<TableColumn fx:id="colCustomerName" prefWidth="200.57147216796875" text="Customer Name" />
|
||||
<TableColumn fx:id="colAdoptionDate" prefWidth="190.85711669921875" text="Adoption Date" />
|
||||
<TableColumn fx:id="colAdoptionFee" prefWidth="91.4285888671875" text="Fee" />
|
||||
<TableColumn fx:id="colAdoptionStatus" prefWidth="142.28570556640625" text="Status" />
|
||||
<TableColumn fx:id="colPetId" prefWidth="66.2857666015625" text="Pet ID" />
|
||||
<TableColumn fx:id="colCustomerName" prefWidth="200.57147216796875" text="Customer Name" />
|
||||
<TableColumn fx:id="colEmployeeName" prefWidth="160.0" text="Employee" />
|
||||
<TableColumn fx:id="colAdoptionDate" prefWidth="190.85711669921875" text="Adoption Date" />
|
||||
<TableColumn fx:id="colAdoptionFee" prefWidth="91.4285888671875" text="Fee" />
|
||||
<TableColumn fx:id="colAdoptionStatus" prefWidth="120.0" text="Status" />
|
||||
</columns>
|
||||
</TableView>
|
||||
</children>
|
||||
|
||||
@@ -68,12 +68,13 @@
|
||||
<TableView fx:id="tvAppointments" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
||||
<columns>
|
||||
<TableColumn fx:id="colAppointmentId" prefWidth="53.14288330078125" text="ID" />
|
||||
<TableColumn fx:id="colPetName" prefWidth="108.00003051757812" text="Pet Name" />
|
||||
<TableColumn fx:id="colServiceName" prefWidth="132.0" text="Service" />
|
||||
<TableColumn fx:id="colAppointmentDate" prefWidth="101.14288330078125" text="Date" />
|
||||
<TableColumn fx:id="colAppointmentTime" prefWidth="89.7142333984375" text="Time" />
|
||||
<TableColumn fx:id="colCustomerName" prefWidth="168.57147216796875" text="Customer" />
|
||||
<TableColumn fx:id="colAppointmentStatus" prefWidth="98.28570556640625" text="Status" />
|
||||
<TableColumn fx:id="colPetName" prefWidth="108.00003051757812" text="Pet Name" />
|
||||
<TableColumn fx:id="colServiceName" prefWidth="132.0" text="Service" />
|
||||
<TableColumn fx:id="colEmployeeName" prefWidth="132.0" text="Employee" />
|
||||
<TableColumn fx:id="colAppointmentDate" prefWidth="101.14288330078125" text="Date" />
|
||||
<TableColumn fx:id="colAppointmentTime" prefWidth="89.7142333984375" text="Time" />
|
||||
<TableColumn fx:id="colCustomerName" prefWidth="140.0" text="Customer" />
|
||||
<TableColumn fx:id="colAppointmentStatus" prefWidth="98.28570556640625" text="Status" />
|
||||
</columns>
|
||||
</TableView>
|
||||
</children>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ComboBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TableColumn?>
|
||||
<?import javafx.scene.control.TableView?>
|
||||
@@ -59,13 +60,15 @@
|
||||
<Insets bottom="10.0" left="15.0" right="15.0" top="10.0" />
|
||||
</padding>
|
||||
<children>
|
||||
<TextField fx:id="txtSearch" prefHeight="31.0" prefWidth="150.0" promptText="Search Pets..." style="-fx-border-width: 0; -fx-background-color: transparent;" HBox.hgrow="ALWAYS">
|
||||
<font>
|
||||
<Font size="15.0" />
|
||||
</font>
|
||||
</TextField>
|
||||
</children>
|
||||
</HBox>
|
||||
<TextField fx:id="txtSearch" prefHeight="31.0" prefWidth="150.0" promptText="Search Pets..." style="-fx-border-width: 0; -fx-background-color: transparent;" HBox.hgrow="ALWAYS">
|
||||
<font>
|
||||
<Font size="15.0" />
|
||||
</font>
|
||||
</TextField>
|
||||
<ComboBox fx:id="cbSpeciesFilter" prefWidth="150.0" promptText="Species" />
|
||||
<ComboBox fx:id="cbStatusFilter" prefWidth="150.0" promptText="Status" />
|
||||
</children>
|
||||
</HBox>
|
||||
<TableView fx:id="tvPets" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
||||
<columns>
|
||||
<TableColumn fx:id="colPetId" prefWidth="55.0" text="ID" />
|
||||
@@ -76,6 +79,8 @@
|
||||
<TableColumn fx:id="colPetAge" prefWidth="60.0" text="Age" />
|
||||
<TableColumn fx:id="colPetStatus" prefWidth="110.0" text="Status" />
|
||||
<TableColumn fx:id="colPetPrice" prefWidth="80.0" text="Price" />
|
||||
<TableColumn fx:id="colCustomerName" prefWidth="130.0" text="Owner" />
|
||||
<TableColumn fx:id="colStoreName" prefWidth="130.0" text="Store" />
|
||||
</columns>
|
||||
</TableView>
|
||||
</children>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ComboBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TableColumn?>
|
||||
<?import javafx.scene.control.TableView?>
|
||||
@@ -58,13 +59,14 @@
|
||||
<Insets bottom="10.0" left="15.0" right="15.0" top="10.0" />
|
||||
</padding>
|
||||
<children>
|
||||
<TextField fx:id="txtSearch" prefHeight="31.0" prefWidth="150.0" promptText="Search products..." style="-fx-border-width: 0; -fx-background-color: transparent;" HBox.hgrow="ALWAYS">
|
||||
<font>
|
||||
<Font size="15.0" />
|
||||
</font>
|
||||
</TextField>
|
||||
</children>
|
||||
</HBox>
|
||||
<TextField fx:id="txtSearch" prefHeight="31.0" prefWidth="150.0" promptText="Search products..." style="-fx-border-width: 0; -fx-background-color: transparent;" HBox.hgrow="ALWAYS">
|
||||
<font>
|
||||
<Font size="15.0" />
|
||||
</font>
|
||||
</TextField>
|
||||
<ComboBox fx:id="cbCategoryFilter" prefWidth="180.0" promptText="Category" />
|
||||
</children>
|
||||
</HBox>
|
||||
<TableView fx:id="tvProducts" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
||||
<columns>
|
||||
<TableColumn fx:id="colProductId" prefWidth="55.0" text="ID" />
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<Insets top="10.0" />
|
||||
</padding>
|
||||
</Label>
|
||||
<Region prefHeight="200.0" prefWidth="200.0" HBox.hgrow="ALWAYS" />
|
||||
<Region HBox.hgrow="ALWAYS" />
|
||||
<Button fx:id="btnRefund" mnemonicParsing="false" onAction="#btnRefund" prefHeight="44.0" style="-fx-background-color: #FF6b6b; -fx-cursor: hand; -fx-background-radius: 8;" text="Process Refund" textFill="WHITE">
|
||||
<font>
|
||||
<Font name="System Bold" size="14.0" />
|
||||
@@ -89,7 +89,7 @@
|
||||
</Button>
|
||||
</children>
|
||||
</HBox>
|
||||
<TableView fx:id="tvCart" prefHeight="170.0" style="-fx-background-color: white; -fx-background-radius: 10;" VBox.vgrow="NEVER">
|
||||
<TableView fx:id="tvCart" prefHeight="170.0" style="-fx-background-color: white; -fx-background-radius: 10;" VBox.vgrow="NEVER">
|
||||
<columns>
|
||||
<TableColumn fx:id="colCartProduct" prefWidth="310.0" text="Product" />
|
||||
<TableColumn fx:id="colCartQty" prefWidth="90.0" text="Qty" />
|
||||
@@ -151,16 +151,16 @@
|
||||
</TextField>
|
||||
</children>
|
||||
</HBox>
|
||||
<TableView fx:id="tvSales" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12; -fx-padding: 6;" VBox.vgrow="ALWAYS">
|
||||
<TableView fx:id="tvSales" prefHeight="362.0" style="-fx-background-color: white; -fx-background-radius: 12; -fx-padding: 6;" VBox.vgrow="ALWAYS">
|
||||
<columns>
|
||||
<TableColumn fx:id="colSaleId" minWidth="56.0" prefWidth="72.0" text="ID" />
|
||||
<TableColumn fx:id="colSaleDate" minWidth="150.0" prefWidth="180.0" text="Date" />
|
||||
<TableColumn fx:id="colEmployeeName" minWidth="150.0" prefWidth="180.0" text="Employee" />
|
||||
<TableColumn fx:id="colServiceProduct" minWidth="190.0" prefWidth="240.0" text="Product" />
|
||||
<TableColumn fx:id="colSaleQuantity" minWidth="70.0" prefWidth="80.0" text="Qty" />
|
||||
<TableColumn fx:id="colSaleUnitPrice" minWidth="110.0" prefWidth="130.0" text="Unit Price" />
|
||||
<TableColumn fx:id="colSaleTotal" minWidth="100.0" prefWidth="120.0" text="Total" />
|
||||
<TableColumn fx:id="colSalePaymentType" minWidth="100.0" prefWidth="120.0" text="Payment" />
|
||||
<TableColumn fx:id="colSaleId" minWidth="54.0" prefWidth="62.0" text="ID" />
|
||||
<TableColumn fx:id="colSaleDate" minWidth="145.0" prefWidth="165.0" text="Date" />
|
||||
<TableColumn fx:id="colEmployeeName" minWidth="130.0" prefWidth="155.0" text="Employee" />
|
||||
<TableColumn fx:id="colServiceProduct" minWidth="180.0" prefWidth="240.0" text="Product" />
|
||||
<TableColumn fx:id="colSaleQuantity" minWidth="62.0" prefWidth="72.0" text="Qty" />
|
||||
<TableColumn fx:id="colSaleUnitPrice" minWidth="95.0" prefWidth="115.0" text="Unit Price" />
|
||||
<TableColumn fx:id="colSaleTotal" minWidth="90.0" prefWidth="108.0" text="Total" />
|
||||
<TableColumn fx:id="colSalePaymentType" minWidth="88.0" prefWidth="100.0" text="Payment" />
|
||||
</columns>
|
||||
</TableView>
|
||||
</children>
|
||||
|
||||
Reference in New Issue
Block a user