Merge branch 'AttachmentsToChat'
This commit is contained in:
@@ -103,11 +103,21 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> i
|
|||||||
|
|
||||||
binding.tvPetStatus.setText(pet.getPetStatus());
|
binding.tvPetStatus.setText(pet.getPetStatus());
|
||||||
|
|
||||||
//Set the status color depending on availability. If available, green, otherwise red
|
//Set the status color depending on availability. If available, green, If Pending, yellow, otherwise red
|
||||||
if (pet.getPetStatus() != null && pet.getPetStatus().equals("Available")) {
|
if (pet.getPetStatus() != null) {
|
||||||
binding.tvPetStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
|
switch (pet.getPetStatus()) {
|
||||||
|
case "Available":
|
||||||
|
binding.tvPetStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
|
||||||
|
break;
|
||||||
|
case "Pending":
|
||||||
|
binding.tvPetStatus.setBackgroundColor(Color.parseColor("#FF9800"));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
binding.tvPetStatus.setBackgroundColor(Color.parseColor("#F44336"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.tvPetStatus.setBackgroundColor(Color.parseColor("#F44336"));
|
binding.tvPetStatus.setBackgroundColor(Color.parseColor("#9E9E9E"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load pet image using Glide
|
// Load pet image using Glide
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ public interface PetApi {
|
|||||||
@GET("api/v1/dropdowns/pet-species")
|
@GET("api/v1/dropdowns/pet-species")
|
||||||
Call<List<DropdownDTO>> getPetSpeciesDropdowns();
|
Call<List<DropdownDTO>> getPetSpeciesDropdowns();
|
||||||
|
|
||||||
|
@GET("api/v1/dropdowns/pet-breeds")
|
||||||
|
Call<List<DropdownDTO>> getPetBreedsDropdowns(@Query("species") String species);
|
||||||
|
|
||||||
// Get pet by id
|
// Get pet by id
|
||||||
@GET("api/v1/pets/{id}")
|
@GET("api/v1/pets/{id}")
|
||||||
Call<PetDTO> getPetById(@Path("id") Long id);
|
Call<PetDTO> getPetById(@Path("id") Long id);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public class CustomerDTO {
|
|||||||
private String createdAt;
|
private String createdAt;
|
||||||
private String updatedAt;
|
private String updatedAt;
|
||||||
private String password;
|
private String password;
|
||||||
|
private String role;
|
||||||
|
|
||||||
public CustomerDTO() {}
|
public CustomerDTO() {}
|
||||||
|
|
||||||
@@ -73,4 +74,7 @@ public class CustomerDTO {
|
|||||||
|
|
||||||
public String getPassword() { return password; }
|
public String getPassword() { return password; }
|
||||||
public void setPassword(String password) { this.password = password; }
|
public void setPassword(String password) { this.password = password; }
|
||||||
|
|
||||||
|
public String getRole() { return role; }
|
||||||
|
public void setRole(String role) { this.role = role; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ public class SaleDTO {
|
|||||||
private BigDecimal subtotalAmount;
|
private BigDecimal subtotalAmount;
|
||||||
private BigDecimal couponDiscountAmount;
|
private BigDecimal couponDiscountAmount;
|
||||||
private BigDecimal employeeDiscountAmount;
|
private BigDecimal employeeDiscountAmount;
|
||||||
|
private BigDecimal loyaltyDiscountAmount;
|
||||||
|
private BigDecimal pointsDiscountAmount;
|
||||||
|
private Integer pointsUsed;
|
||||||
private String paymentMethod;
|
private String paymentMethod;
|
||||||
private String channel;
|
private String channel;
|
||||||
private Boolean isRefund;
|
private Boolean isRefund;
|
||||||
@@ -78,6 +81,22 @@ public class SaleDTO {
|
|||||||
return employeeDiscountAmount;
|
return employeeDiscountAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BigDecimal getLoyaltyDiscountAmount() {
|
||||||
|
return loyaltyDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoyaltyDiscountAmount(BigDecimal loyaltyDiscountAmount) {
|
||||||
|
this.loyaltyDiscountAmount = loyaltyDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPointsUsed() {
|
||||||
|
return pointsUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPointsUsed(Integer pointsUsed) {
|
||||||
|
this.pointsUsed = pointsUsed;
|
||||||
|
}
|
||||||
|
|
||||||
public String getPaymentMethod() {
|
public String getPaymentMethod() {
|
||||||
return paymentMethod;
|
return paymentMethod;
|
||||||
}
|
}
|
||||||
@@ -126,6 +145,14 @@ public class SaleDTO {
|
|||||||
return customerName;
|
return customerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BigDecimal getPointsDiscountAmount() {
|
||||||
|
return pointsDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPointsDiscountAmount(BigDecimal pointsDiscountAmount) {
|
||||||
|
this.pointsDiscountAmount = pointsDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
// Nested SaleItemDTO
|
// Nested SaleItemDTO
|
||||||
public static class SaleItemDTO {
|
public static class SaleItemDTO {
|
||||||
private Long saleItemId;
|
private Long saleItemId;
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupStatusFilter() {
|
private void setupStatusFilter() {
|
||||||
String[] statuses = {"All Statuses", "Available", "Adopted", "Owned"};
|
String[] statuses = {"All Statuses", "Available", "Adopted", "Owned", "Pending"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, this::loadPetData);
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, this::loadPetData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,11 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
|
|
||||||
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
||||||
|
|
||||||
|
if (isAdmin()) {
|
||||||
|
binding.fabAddSale.setVisibility(View.GONE);
|
||||||
|
binding.btnOpenRefund.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
binding.fabAddSale.setOnClickListener(v ->
|
binding.fabAddSale.setOnClickListener(v ->
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_sale_detail));
|
NavHostFragment.findNavController(this).navigate(R.id.nav_sale_detail));
|
||||||
|
|
||||||
@@ -110,6 +115,10 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isAdmin() {
|
||||||
|
return "ADMIN".equalsIgnoreCase(tokenManager.getRole());
|
||||||
|
}
|
||||||
|
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadSales(true));
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadSales(true));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ import com.example.petstoremobile.utils.InputValidator;
|
|||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.CustomerDetailViewModel;
|
import com.example.petstoremobile.viewmodels.CustomerDetailViewModel;
|
||||||
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -24,6 +27,8 @@ public class CustomerDetailFragment extends Fragment {
|
|||||||
private FragmentCustomerDetailBinding binding;
|
private FragmentCustomerDetailBinding binding;
|
||||||
private CustomerDetailViewModel viewModel;
|
private CustomerDetailViewModel viewModel;
|
||||||
|
|
||||||
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
private final String[] STATUSES = {"Active", "Inactive"};
|
private final String[] STATUSES = {"Active", "Inactive"};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -65,7 +70,10 @@ public class CustomerDetailFragment extends Fragment {
|
|||||||
|
|
||||||
// Show loyalty points
|
// Show loyalty points
|
||||||
binding.tvLoyaltyPointsLabel.setVisibility(View.VISIBLE);
|
binding.tvLoyaltyPointsLabel.setVisibility(View.VISIBLE);
|
||||||
binding.tvCustomerLoyaltyPoints.setVisibility(View.VISIBLE);
|
binding.etCustomerLoyaltyPoints.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
boolean isAdmin = "ADMIN".equalsIgnoreCase(tokenManager.getRole());
|
||||||
|
binding.etCustomerLoyaltyPoints.setEnabled(isAdmin);
|
||||||
|
|
||||||
loadCustomerData(customerId);
|
loadCustomerData(customerId);
|
||||||
} else {
|
} else {
|
||||||
@@ -74,7 +82,7 @@ public class CustomerDetailFragment extends Fragment {
|
|||||||
binding.btnDeleteCustomer.setVisibility(View.GONE);
|
binding.btnDeleteCustomer.setVisibility(View.GONE);
|
||||||
binding.tvCustomerId.setVisibility(View.GONE);
|
binding.tvCustomerId.setVisibility(View.GONE);
|
||||||
binding.tvLoyaltyPointsLabel.setVisibility(View.GONE);
|
binding.tvLoyaltyPointsLabel.setVisibility(View.GONE);
|
||||||
binding.tvCustomerLoyaltyPoints.setVisibility(View.GONE);
|
binding.etCustomerLoyaltyPoints.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +99,7 @@ public class CustomerDetailFragment extends Fragment {
|
|||||||
binding.etCustomerPhone.setText(c.getPhone() != null ? c.getPhone() : "");
|
binding.etCustomerPhone.setText(c.getPhone() != null ? c.getPhone() : "");
|
||||||
binding.spinnerCustomerStatus.setSelection(Boolean.TRUE.equals(c.getActive()) ? 0 : 1);
|
binding.spinnerCustomerStatus.setSelection(Boolean.TRUE.equals(c.getActive()) ? 0 : 1);
|
||||||
int pts = c.getLoyaltyPoints() != null ? c.getLoyaltyPoints() : 0;
|
int pts = c.getLoyaltyPoints() != null ? c.getLoyaltyPoints() : 0;
|
||||||
binding.tvCustomerLoyaltyPoints.setText(String.valueOf(pts));
|
binding.etCustomerLoyaltyPoints.setText(String.valueOf(pts));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -121,6 +129,12 @@ public class CustomerDetailFragment extends Fragment {
|
|||||||
if (!InputValidator.isValidEmail(binding.etCustomerEmail)) return;
|
if (!InputValidator.isValidEmail(binding.etCustomerEmail)) return;
|
||||||
if (!InputValidator.isValidPhone(binding.etCustomerPhone)) return;
|
if (!InputValidator.isValidPhone(binding.etCustomerPhone)) return;
|
||||||
|
|
||||||
|
Integer loyaltyPoints = null;
|
||||||
|
if (viewModel.isEditing()) {
|
||||||
|
if (!InputValidator.isPositiveInteger(binding.etCustomerLoyaltyPoints, "Loyalty Points")) return;
|
||||||
|
loyaltyPoints = Integer.parseInt(binding.etCustomerLoyaltyPoints.getText().toString().trim());
|
||||||
|
}
|
||||||
|
|
||||||
String username = binding.etCustomerUsername.getText().toString().trim();
|
String username = binding.etCustomerUsername.getText().toString().trim();
|
||||||
String password = viewModel.isEditing() ? null : binding.etCustomerPassword.getText().toString().trim();
|
String password = viewModel.isEditing() ? null : binding.etCustomerPassword.getText().toString().trim();
|
||||||
String firstName = binding.etCustomerFirstName.getText().toString().trim();
|
String firstName = binding.etCustomerFirstName.getText().toString().trim();
|
||||||
@@ -129,9 +143,7 @@ public class CustomerDetailFragment extends Fragment {
|
|||||||
String phone = binding.etCustomerPhone.getText().toString().trim();
|
String phone = binding.etCustomerPhone.getText().toString().trim();
|
||||||
boolean active = binding.spinnerCustomerStatus.getSelectedItemPosition() == 0;
|
boolean active = binding.spinnerCustomerStatus.getSelectedItemPosition() == 0;
|
||||||
|
|
||||||
CustomerDTO dto = new CustomerDTO(username, password, firstName, lastName, email, phone);
|
CustomerDTO dto = viewModel.createCustomerDto(username, password, firstName, lastName, email, phone, active, loyaltyPoints);
|
||||||
dto.setFullName(firstName + " " + lastName);
|
|
||||||
dto.setActive(active);
|
|
||||||
|
|
||||||
viewModel.saveCustomer(dto).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.saveCustomer(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
|
|||||||
@@ -101,6 +101,14 @@ public class PetDetailFragment extends Fragment {
|
|||||||
DropdownDTO::getLabel, "-- Select Species --", null, DropdownDTO::getId);
|
DropdownDTO::getLabel, "-- Select Species --", null, DropdownDTO::getId);
|
||||||
SpinnerUtils.setSelectionByValue(binding.spinnerPetSpecies, selectedSpecies);
|
SpinnerUtils.setSelectionByValue(binding.spinnerPetSpecies, selectedSpecies);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
viewModel.getBreedList().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
PetDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||||
|
String selectedBreed = state != null ? state.selectedBreed : null;
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPetBreed, list,
|
||||||
|
DropdownDTO::getLabel, "-- Select Breed --", null, DropdownDTO::getId);
|
||||||
|
SpinnerUtils.setSelectionByValue(binding.spinnerPetBreed, selectedBreed);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLoading(boolean loading) {
|
private void setLoading(boolean loading) {
|
||||||
@@ -118,7 +126,7 @@ public class PetDetailFragment extends Fragment {
|
|||||||
private void savePet() {
|
private void savePet() {
|
||||||
if (!InputValidator.isNotEmpty(binding.etPetName, "Pet Name")) return;
|
if (!InputValidator.isNotEmpty(binding.etPetName, "Pet Name")) return;
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerPetSpecies, "Species")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerPetSpecies, "Species")) return;
|
||||||
if (!InputValidator.isNotEmpty(binding.etPetBreed, "Breed")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerPetBreed, "Breed")) return;
|
||||||
if (!InputValidator.isPositiveInteger(binding.etPetAge, "Age")) return;
|
if (!InputValidator.isPositiveInteger(binding.etPetAge, "Age")) return;
|
||||||
if (!InputValidator.isPositiveDecimal(binding.etPetPrice, "Price")) return;
|
if (!InputValidator.isPositiveDecimal(binding.etPetPrice, "Price")) return;
|
||||||
|
|
||||||
@@ -127,7 +135,11 @@ public class PetDetailFragment extends Fragment {
|
|||||||
String species = (speciesOptions != null && binding.spinnerPetSpecies.getSelectedItemPosition() > 0)
|
String species = (speciesOptions != null && binding.spinnerPetSpecies.getSelectedItemPosition() > 0)
|
||||||
? speciesOptions.get(binding.spinnerPetSpecies.getSelectedItemPosition() - 1).getLabel()
|
? speciesOptions.get(binding.spinnerPetSpecies.getSelectedItemPosition() - 1).getLabel()
|
||||||
: "";
|
: "";
|
||||||
String breed = binding.etPetBreed.getText().toString().trim();
|
|
||||||
|
List<DropdownDTO> breedOptions = viewModel.getBreedList().getValue();
|
||||||
|
String breed = (breedOptions != null && binding.spinnerPetBreed.getSelectedItemPosition() > 0)
|
||||||
|
? breedOptions.get(binding.spinnerPetBreed.getSelectedItemPosition() - 1).getLabel()
|
||||||
|
: "";
|
||||||
int age = Integer.parseInt(binding.etPetAge.getText().toString().trim());
|
int age = Integer.parseInt(binding.etPetAge.getText().toString().trim());
|
||||||
double price = Double.parseDouble(binding.etPetPrice.getText().toString().trim());
|
double price = Double.parseDouble(binding.etPetPrice.getText().toString().trim());
|
||||||
String status = binding.spinnerPetStatus.getSelectedItem().toString();
|
String status = binding.spinnerPetStatus.getSelectedItem().toString();
|
||||||
@@ -152,6 +164,10 @@ public class PetDetailFragment extends Fragment {
|
|||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Owner")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Owner")) return;
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return;
|
||||||
}
|
}
|
||||||
|
if ("Pending".equalsIgnoreCase(status)) {
|
||||||
|
if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Owner")) return;
|
||||||
|
if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return;
|
||||||
|
}
|
||||||
|
|
||||||
PetDTO petDTO = new PetDTO();
|
PetDTO petDTO = new PetDTO();
|
||||||
petDTO.setPetName(name);
|
petDTO.setPetName(name);
|
||||||
@@ -236,7 +252,6 @@ public class PetDetailFragment extends Fragment {
|
|||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
PetDTO p = resource.data;
|
PetDTO p = resource.data;
|
||||||
binding.etPetName.setText(p.getPetName());
|
binding.etPetName.setText(p.getPetName());
|
||||||
binding.etPetBreed.setText(p.getPetBreed());
|
|
||||||
binding.etPetAge.setText(String.valueOf(p.getPetAge()));
|
binding.etPetAge.setText(String.valueOf(p.getPetAge()));
|
||||||
if (p.getPetPrice() != null) {
|
if (p.getPetPrice() != null) {
|
||||||
binding.etPetPrice.setText(String.format(Locale.getDefault(), "%.2f", p.getPetPrice()));
|
binding.etPetPrice.setText(String.format(Locale.getDefault(), "%.2f", p.getPetPrice()));
|
||||||
@@ -279,6 +294,11 @@ public class PetDetailFragment extends Fragment {
|
|||||||
viewModel.onSpeciesSelected(p);
|
viewModel.onSpeciesSelected(p);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerPetBreed, p -> {
|
||||||
|
if (isUpdatingUI) return;
|
||||||
|
viewModel.onBreedSelected(p);
|
||||||
|
});
|
||||||
|
|
||||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerCustomer, p -> {
|
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerCustomer, p -> {
|
||||||
if (isUpdatingUI) return;
|
if (isUpdatingUI) return;
|
||||||
viewModel.onCustomerSelected(p);
|
viewModel.onCustomerSelected(p);
|
||||||
@@ -306,7 +326,7 @@ public class PetDetailFragment extends Fragment {
|
|||||||
binding.btnSavePet.setText(state.saveButtonText);
|
binding.btnSavePet.setText(state.saveButtonText);
|
||||||
|
|
||||||
UIUtils.setViewsEnabled(state.isSpeciesEnabled, binding.spinnerPetSpecies);
|
UIUtils.setViewsEnabled(state.isSpeciesEnabled, binding.spinnerPetSpecies);
|
||||||
UIUtils.setViewsEnabled(state.isBreedEnabled, binding.etPetBreed);
|
UIUtils.setViewsEnabled(state.isBreedEnabled, binding.spinnerPetBreed);
|
||||||
UIUtils.setViewsEnabled(state.isCustomerEnabled, binding.spinnerCustomer);
|
UIUtils.setViewsEnabled(state.isCustomerEnabled, binding.spinnerCustomer);
|
||||||
UIUtils.setViewsEnabled(state.isStoreEnabled, binding.spinnerStore);
|
UIUtils.setViewsEnabled(state.isStoreEnabled, binding.spinnerStore);
|
||||||
|
|
||||||
@@ -323,6 +343,13 @@ public class PetDetailFragment extends Fragment {
|
|||||||
SpinnerUtils.setSelectionByValue(binding.spinnerPetSpecies, state.selectedSpecies);
|
SpinnerUtils.setSelectionByValue(binding.spinnerPetSpecies, state.selectedSpecies);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<DropdownDTO> breeds = viewModel.getBreedList().getValue();
|
||||||
|
if (breeds != null) {
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPetBreed, breeds,
|
||||||
|
DropdownDTO::getLabel, "-- Select Breed --", null, DropdownDTO::getId);
|
||||||
|
SpinnerUtils.setSelectionByValue(binding.spinnerPetBreed, state.selectedBreed);
|
||||||
|
}
|
||||||
|
|
||||||
if (!state.isCustomerEnabled && binding.spinnerCustomer.getSelectedItemPosition() != 0) {
|
if (!state.isCustomerEnabled && binding.spinnerCustomer.getSelectedItemPosition() != 0) {
|
||||||
binding.spinnerCustomer.setSelection(0);
|
binding.spinnerCustomer.setSelection(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,11 +302,7 @@ public class RefundFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateRefundTotal() {
|
private void updateRefundTotal() {
|
||||||
BigDecimal total = BigDecimal.ZERO;
|
BigDecimal total = viewModel.calculateRefundTotal();
|
||||||
List<RefundViewModel.RefundItem> cart = viewModel.getRefundCart().getValue();
|
|
||||||
if (cart != null) {
|
|
||||||
for (RefundViewModel.RefundItem item : cart) total = total.add(item.getTotal());
|
|
||||||
}
|
|
||||||
binding.tvRefundTotal.setText("Refund Total: $" + total.setScale(2, RoundingMode.HALF_UP));
|
binding.tvRefundTotal.setText("Refund Total: $" + total.setScale(2, RoundingMode.HALF_UP));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,9 +317,7 @@ public class RefundFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String payment = PAYMENT_METHODS[binding.spinnerRefundPayment.getSelectedItemPosition()];
|
String payment = PAYMENT_METHODS[binding.spinnerRefundPayment.getSelectedItemPosition()];
|
||||||
BigDecimal total = BigDecimal.ZERO;
|
final BigDecimal finalTotal = viewModel.calculateRefundTotal();
|
||||||
for (RefundViewModel.RefundItem item : viewModel.getRefundCart().getValue()) total = total.add(item.getTotal());
|
|
||||||
final BigDecimal finalTotal = total;
|
|
||||||
|
|
||||||
DialogUtils.showConfirmDialog(requireContext(), "Confirm Refund",
|
DialogUtils.showConfirmDialog(requireContext(), "Confirm Refund",
|
||||||
"Process refund for Sale #" + viewModel.getCurrentSale().getSaleId()
|
"Process refund for Sale #" + viewModel.getCurrentSale().getSaleId()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import com.example.petstoremobile.dtos.*;
|
|||||||
import com.example.petstoremobile.viewmodels.SaleDetailViewModel;
|
import com.example.petstoremobile.viewmodels.SaleDetailViewModel;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.DialogUtils;
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
|
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
@@ -68,6 +69,10 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isAdmin() {
|
||||||
|
return "ADMIN".equalsIgnoreCase(tokenManager.getRole());
|
||||||
|
}
|
||||||
|
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
||||||
Long primaryStoreId = tokenManager.getPrimaryStoreId();
|
Long primaryStoreId = tokenManager.getPrimaryStoreId();
|
||||||
@@ -77,7 +82,7 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
if (isStaff()) {
|
if (isStaff()) {
|
||||||
UIUtils.setViewsEnabled(false, binding.spinnerSaleStore);
|
UIUtils.setViewsEnabled(false, binding.spinnerSaleStore);
|
||||||
if (primaryStoreId == null) {
|
if (primaryStoreId == null) {
|
||||||
Toast.makeText(requireContext(), "No store assigned to your account. Contact an admin.", Toast.LENGTH_LONG).show();
|
UIUtils.showToast(requireContext(), "No store assigned to your account. Contact an admin.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -96,6 +101,17 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
renderCartItems();
|
renderCartItems();
|
||||||
updateTotal();
|
updateTotal();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
viewModel.getSelectedCustomerData().observe(getViewLifecycleOwner(), customer -> {
|
||||||
|
if (customer != null && !viewModel.isViewOnly()) {
|
||||||
|
binding.llLoyaltyPoints.setVisibility(View.VISIBLE);
|
||||||
|
binding.tvAvailablePoints.setText("(Available: " + customer.getLoyaltyPoints() + ")");
|
||||||
|
binding.cbUseLoyaltyPoints.setEnabled(customer.getLoyaltyPoints() >= 20);
|
||||||
|
} else {
|
||||||
|
binding.llLoyaltyPoints.setVisibility(View.GONE);
|
||||||
|
binding.cbUseLoyaltyPoints.setChecked(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
@@ -105,11 +121,11 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
boolean viewOnly = a.getBoolean("viewOnly", false);
|
boolean viewOnly = a.getBoolean("viewOnly", false);
|
||||||
viewModel.setSaleId(saleId, viewOnly);
|
viewModel.setSaleId(saleId, viewOnly);
|
||||||
|
|
||||||
binding.tvSaleMode.setText("Sale #" + saleId);
|
binding.tvSaleMode.setText("Sale #" + DateTimeUtils.formatId(saleId));
|
||||||
binding.tvSaleDetailId.setText("ID: " + saleId);
|
binding.tvSaleDetailId.setText("ID: " + DateTimeUtils.formatId(saleId));
|
||||||
|
|
||||||
boolean isRefund = a.getBoolean("isRefund", false);
|
boolean isRefund = a.getBoolean("isRefund", false);
|
||||||
if (isRefund) {
|
if (isRefund || isAdmin()) {
|
||||||
binding.btnRefundSale.setVisibility(View.GONE);
|
binding.btnRefundSale.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,8 +145,7 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
binding.tvSaleStore.setVisibility(View.VISIBLE);
|
binding.tvSaleStore.setVisibility(View.VISIBLE);
|
||||||
binding.tvSalePaymentMethod.setVisibility(View.VISIBLE);
|
binding.tvSalePaymentMethod.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
// Show refund button only if it's not already a refund
|
binding.btnRefundSale.setVisibility((isRefund || isAdmin()) ? View.GONE : View.VISIBLE);
|
||||||
binding.btnRefundSale.setVisibility(isRefund ? View.GONE : View.VISIBLE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSaleDetails();
|
loadSaleDetails();
|
||||||
@@ -173,23 +188,36 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
setLoading(resource.status == Resource.Status.LOADING);
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
SaleDTO sale = resource.data;
|
SaleDTO sale = resource.data;
|
||||||
binding.tvSaleDetailTotal.setText("Total: $" + sale.getTotalAmount());
|
binding.tvSaleDetailTotal.setText("Total: $" + String.format(Locale.getDefault(), "%.2f", sale.getTotalAmount()));
|
||||||
binding.tvSaleSubtotal.setText("$" + (sale.getSubtotalAmount() != null ? sale.getSubtotalAmount() : sale.getTotalAmount()));
|
binding.tvSaleSubtotal.setText("$" + String.format(Locale.getDefault(), "%.2f", (sale.getSubtotalAmount() != null ? sale.getSubtotalAmount() : sale.getTotalAmount())));
|
||||||
|
|
||||||
if (sale.getCouponDiscountAmount() != null && sale.getCouponDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
if (sale.getCouponDiscountAmount() != null && sale.getCouponDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||||
binding.llCouponDiscount.setVisibility(View.VISIBLE);
|
binding.llCouponDiscount.setVisibility(View.VISIBLE);
|
||||||
binding.tvSaleCouponDiscount.setText("-$" + sale.getCouponDiscountAmount());
|
binding.tvSaleCouponDiscount.setText("-$" + String.format(Locale.getDefault(), "%.2f", sale.getCouponDiscountAmount()));
|
||||||
} else {
|
} else {
|
||||||
binding.llCouponDiscount.setVisibility(View.GONE);
|
binding.llCouponDiscount.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sale.getEmployeeDiscountAmount() != null && sale.getEmployeeDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
if (sale.getEmployeeDiscountAmount() != null && sale.getEmployeeDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||||
binding.llEmployeeDiscount.setVisibility(View.VISIBLE);
|
binding.llEmployeeDiscount.setVisibility(View.VISIBLE);
|
||||||
binding.tvSaleEmployeeDiscount.setText("-$" + sale.getEmployeeDiscountAmount());
|
binding.tvSaleEmployeeDiscount.setText("-$" + String.format(Locale.getDefault(), "%.2f", sale.getEmployeeDiscountAmount()));
|
||||||
} else {
|
} else {
|
||||||
binding.llEmployeeDiscount.setVisibility(View.GONE);
|
binding.llEmployeeDiscount.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sale.getPointsDiscountAmount() != null && sale.getPointsDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
binding.llLoyaltyDiscount.setVisibility(View.VISIBLE);
|
||||||
|
binding.tvSaleLoyaltyDiscount.setText("-$" + String.format(Locale.getDefault(), "%.2f", sale.getPointsDiscountAmount()));
|
||||||
|
if (sale.getPointsUsed() != null) {
|
||||||
|
binding.tvLoyaltyDiscountLabel.setText("Loyalty Discount (" + sale.getPointsUsed() + " pts):");
|
||||||
|
}
|
||||||
|
} else if (sale.getLoyaltyDiscountAmount() != null && sale.getLoyaltyDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
binding.llLoyaltyDiscount.setVisibility(View.VISIBLE);
|
||||||
|
binding.tvSaleLoyaltyDiscount.setText("-$" + String.format(Locale.getDefault(), "%.2f", sale.getLoyaltyDiscountAmount()));
|
||||||
|
} else {
|
||||||
|
binding.llLoyaltyDiscount.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : "—");
|
binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : "—");
|
||||||
binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0));
|
binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0));
|
||||||
binding.tvSaleStore.setText(sale.getStoreName() != null ? sale.getStoreName() : "—");
|
binding.tvSaleStore.setText(sale.getStoreName() != null ? sale.getStoreName() : "—");
|
||||||
@@ -216,7 +244,7 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
binding.btnApplyCoupon.setOnClickListener(v -> {
|
binding.btnApplyCoupon.setOnClickListener(v -> {
|
||||||
String code = binding.etCouponCode.getText().toString().trim();
|
String code = binding.etCouponCode.getText().toString().trim();
|
||||||
if (code.isEmpty()) {
|
if (code.isEmpty()) {
|
||||||
Toast.makeText(getContext(), "Enter a coupon code", Toast.LENGTH_SHORT).show();
|
UIUtils.showToast(getContext(), "Enter a coupon code");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -286,7 +314,7 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
|
|
||||||
for (SaleDTO.SaleItemDTO existing : viewModel.getCartItems().getValue()) {
|
for (SaleDTO.SaleItemDTO existing : viewModel.getCartItems().getValue()) {
|
||||||
if (existing.getProdId().equals(product.getProdId())) {
|
if (existing.getProdId().equals(product.getProdId())) {
|
||||||
Toast.makeText(getContext(), "Product already added", Toast.LENGTH_SHORT).show();
|
UIUtils.showToast(getContext(), "Product already added");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,6 +322,19 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
viewModel.addToCart(new SaleDTO.SaleItemDTO(product.getProdId(), qty));
|
viewModel.addToCart(new SaleDTO.SaleItemDTO(product.getProdId(), qty));
|
||||||
binding.etSaleQuantity.setText("");
|
binding.etSaleQuantity.setText("");
|
||||||
});
|
});
|
||||||
|
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerSaleCustomer, p -> {
|
||||||
|
if (p > 0) {
|
||||||
|
Long id = viewModel.getCustomerList().getValue().get(p - 1).getId();
|
||||||
|
viewModel.selectCustomer(id);
|
||||||
|
} else {
|
||||||
|
viewModel.selectCustomer(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.cbUseLoyaltyPoints.setOnCheckedChangeListener((v, checked) -> {
|
||||||
|
viewModel.setUseLoyaltyPoints(checked);
|
||||||
|
updateTotal();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderCartItems() {
|
private void renderCartItems() {
|
||||||
@@ -333,7 +374,7 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
|
|
||||||
TextView tvPrice = new TextView(getContext());
|
TextView tvPrice = new TextView(getContext());
|
||||||
tvPrice.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
tvPrice.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||||
tvPrice.setText(price != null ? "$" + price : "");
|
tvPrice.setText(price != null ? "$" + String.format(Locale.getDefault(), "%.2f", price) : "");
|
||||||
|
|
||||||
row.addView(tvName);
|
row.addView(tvName);
|
||||||
row.addView(tvQty);
|
row.addView(tvQty);
|
||||||
@@ -357,23 +398,35 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private void updateTotal() {
|
private void updateTotal() {
|
||||||
BigDecimal subtotal = viewModel.calculateSubtotal();
|
BigDecimal subtotal = viewModel.calculateSubtotal();
|
||||||
BigDecimal discount = viewModel.calculateDiscount();
|
BigDecimal couponDiscount = viewModel.calculateCouponDiscount();
|
||||||
BigDecimal total = subtotal.subtract(discount);
|
BigDecimal loyaltyDiscount = viewModel.calculateLoyaltyDiscount();
|
||||||
binding.tvSaleSubtotal.setText("$" + subtotal);
|
BigDecimal total = subtotal.subtract(couponDiscount).subtract(loyaltyDiscount);
|
||||||
if (discount.compareTo(BigDecimal.ZERO) > 0) {
|
|
||||||
|
binding.tvSaleSubtotal.setText("$" + String.format(Locale.getDefault(), "%.2f", subtotal));
|
||||||
|
|
||||||
|
if (couponDiscount.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
binding.llCouponDiscount.setVisibility(View.VISIBLE);
|
binding.llCouponDiscount.setVisibility(View.VISIBLE);
|
||||||
binding.tvSaleCouponDiscount.setText("-$" + discount);
|
binding.tvSaleCouponDiscount.setText("-$" + String.format(Locale.getDefault(), "%.2f", couponDiscount));
|
||||||
} else {
|
} else {
|
||||||
binding.llCouponDiscount.setVisibility(View.GONE);
|
binding.llCouponDiscount.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
binding.tvSaleDetailTotal.setText("Total: $" + total);
|
|
||||||
|
if (loyaltyDiscount.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
binding.llLoyaltyDiscount.setVisibility(View.VISIBLE);
|
||||||
|
binding.tvLoyaltyDiscountLabel.setText("Loyalty Discount (" + viewModel.calculatePointsToUse() + " pts):");
|
||||||
|
binding.tvSaleLoyaltyDiscount.setText("-$" + String.format(Locale.getDefault(), "%.2f", loyaltyDiscount));
|
||||||
|
} else {
|
||||||
|
binding.llLoyaltyDiscount.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tvSaleDetailTotal.setText("Total: $" + String.format(Locale.getDefault(), "%.2f", total));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveSale() {
|
private void saveSale() {
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerSaleStore, "Store")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerSaleStore, "Store")) return;
|
||||||
|
|
||||||
if (viewModel.getCartItems().getValue() == null || viewModel.getCartItems().getValue().isEmpty()) {
|
if (viewModel.getCartItems().getValue() == null || viewModel.getCartItems().getValue().isEmpty()) {
|
||||||
Toast.makeText(getContext(), "Add at least one item", Toast.LENGTH_SHORT).show();
|
UIUtils.showToast(getContext(), "Add at least one item");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,11 +441,15 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
SaleDTO dto = new SaleDTO(store.getId(), payment, viewModel.getCartItems().getValue(), false, null, customerId);
|
SaleDTO dto = new SaleDTO(store.getId(), payment, viewModel.getCartItems().getValue(), false, null, customerId);
|
||||||
dto.setCouponId(viewModel.getAppliedCouponId());
|
dto.setCouponId(viewModel.getAppliedCouponId());
|
||||||
|
|
||||||
|
if (Boolean.TRUE.equals(viewModel.getUseLoyaltyPoints().getValue())) {
|
||||||
|
dto.setPointsUsed(viewModel.calculatePointsToUse());
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
setLoading(resource.status == Resource.Status.LOADING);
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
Toast.makeText(getContext(), "Sale saved!", Toast.LENGTH_SHORT).show();
|
UIUtils.showToast(getContext(), "Sale saved!");
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
DialogUtils.showInfoDialog(requireContext(), "Save Error", resource.message);
|
DialogUtils.showInfoDialog(requireContext(), "Save Error", resource.message);
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
List<DropdownDTO> stores = viewModel.getStoreList().getValue();
|
List<DropdownDTO> stores = viewModel.getStoreList().getValue();
|
||||||
Long storeId = stores.get(binding.spinnerStaffStore.getSelectedItemPosition() - 1).getId();
|
Long storeId = stores.get(binding.spinnerStaffStore.getSelectedItemPosition() - 1).getId();
|
||||||
|
|
||||||
EmployeeDTO dto = new EmployeeDTO(
|
EmployeeDTO dto = viewModel.createEmployeeDto(
|
||||||
username,
|
username,
|
||||||
password.isEmpty() ? null : password,
|
password.isEmpty() ? null : password,
|
||||||
firstName,
|
firstName,
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ public class PetProfileFragment extends Fragment {
|
|||||||
|
|
||||||
String status = pet.getPetStatus();
|
String status = pet.getPetStatus();
|
||||||
|
|
||||||
if ("Adopted".equalsIgnoreCase(status) || "Owned".equalsIgnoreCase(status)) {
|
if ("Adopted".equalsIgnoreCase(status) || "Owned".equalsIgnoreCase(status) || "Pending".equalsIgnoreCase(status)) {
|
||||||
binding.layoutPetOwner.setVisibility(View.VISIBLE);
|
binding.layoutPetOwner.setVisibility(View.VISIBLE);
|
||||||
if (pet.getCustomerName() != null && !pet.getCustomerName().isEmpty()) {
|
if (pet.getCustomerName() != null && !pet.getCustomerName().isEmpty()) {
|
||||||
binding.tvPetOwner.setText(pet.getCustomerName());
|
binding.tvPetOwner.setText(pet.getCustomerName());
|
||||||
@@ -137,7 +137,7 @@ public class PetProfileFragment extends Fragment {
|
|||||||
binding.layoutPetOwner.setVisibility(View.GONE);
|
binding.layoutPetOwner.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Available".equalsIgnoreCase(status) || "Adopted".equalsIgnoreCase(status)) {
|
if ("Available".equalsIgnoreCase(status) || "Adopted".equalsIgnoreCase(status) || "Pending".equalsIgnoreCase(status)) {
|
||||||
binding.layoutPetStore.setVisibility(View.VISIBLE);
|
binding.layoutPetStore.setVisibility(View.VISIBLE);
|
||||||
if (pet.getStoreName() != null && !pet.getStoreName().isEmpty()) {
|
if (pet.getStoreName() != null && !pet.getStoreName().isEmpty()) {
|
||||||
binding.tvPetStore.setText(pet.getStoreName());
|
binding.tvPetStore.setText(pet.getStoreName());
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ public class PetRepository extends BaseRepository {
|
|||||||
return executeCall(petApi.getPetSpeciesDropdowns());
|
return executeCall(petApi.getPetSpeciesDropdowns());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> getPetBreedsDropdowns(String species) {
|
||||||
|
return executeCall(petApi.getPetBreedsDropdowns(species));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves available pets for a specific store.
|
* Retrieves available pets for a specific store.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -100,12 +100,13 @@ public class InputValidator {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if the phone number is valid
|
// Checks if the phone number is valid in (XXX) XXX-XXXX format
|
||||||
public static boolean isValidPhone(EditText field) {
|
public static boolean isValidPhone(EditText field) {
|
||||||
String phone = field.getText().toString().trim();
|
String phone = field.getText().toString().trim();
|
||||||
// Android built in phone validation pattern
|
// Matches (XXX) XXX-XXXX format
|
||||||
if (phone.isEmpty() || !android.util.Patterns.PHONE.matcher(phone).matches()) {
|
String pattern = "^\\(\\d{3}\\) \\d{3}-\\d{4}$";
|
||||||
field.setError("Enter a valid phone number");
|
if (phone.isEmpty() || !phone.matches(pattern)) {
|
||||||
|
field.setError("Enter a valid phone number: (XXX) XXX-XXXX");
|
||||||
field.requestFocus();
|
field.requestFocus();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,19 @@ public class CustomerDetailViewModel extends ViewModel {
|
|||||||
public long getCustomerId() { return customerId; }
|
public long getCustomerId() { return customerId; }
|
||||||
public boolean isEditing() { return isEditing; }
|
public boolean isEditing() { return isEditing; }
|
||||||
|
|
||||||
|
public CustomerDTO createCustomerDto(String username, String password, String firstName,
|
||||||
|
String lastName, String email, String phone,
|
||||||
|
boolean active, Integer loyaltyPoints) {
|
||||||
|
CustomerDTO dto = new CustomerDTO(username, password, firstName, lastName, email, phone);
|
||||||
|
dto.setFullName(firstName + " " + lastName);
|
||||||
|
dto.setActive(active);
|
||||||
|
dto.setRole("CUSTOMER");
|
||||||
|
if (isEditing && loyaltyPoints != null) {
|
||||||
|
dto.setLoyaltyPoints(loyaltyPoints);
|
||||||
|
}
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
public LiveData<Resource<CustomerDTO>> loadCustomer(long id) {
|
public LiveData<Resource<CustomerDTO>> loadCustomer(long id) {
|
||||||
return repository.getCustomerById(id);
|
return repository.getCustomerById(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
private static final String STATUS_AVAILABLE = "Available";
|
private static final String STATUS_AVAILABLE = "Available";
|
||||||
private static final String STATUS_ADOPTED = "Adopted";
|
private static final String STATUS_ADOPTED = "Adopted";
|
||||||
private static final String STATUS_OWNED = "Owned";
|
private static final String STATUS_OWNED = "Owned";
|
||||||
|
private static final String STATUS_PENDING = "Pending";
|
||||||
|
|
||||||
private final PetRepository petRepository;
|
private final PetRepository petRepository;
|
||||||
private final CustomerRepository customerRepository;
|
private final CustomerRepository customerRepository;
|
||||||
@@ -33,6 +34,7 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<List<DropdownDTO>> speciesList = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<DropdownDTO>> speciesList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> breedList = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
||||||
|
|
||||||
@@ -40,6 +42,7 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
private Long selectedCustomerId = null;
|
private Long selectedCustomerId = null;
|
||||||
private Long selectedStoreId = null;
|
private Long selectedStoreId = null;
|
||||||
private String selectedSpecies = null;
|
private String selectedSpecies = null;
|
||||||
|
private String selectedBreed = null;
|
||||||
private boolean isOriginallyOwnedOrAdopted = false;
|
private boolean isOriginallyOwnedOrAdopted = false;
|
||||||
private Long originalCustomerId = null;
|
private Long originalCustomerId = null;
|
||||||
|
|
||||||
@@ -111,10 +114,33 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
List<DropdownDTO> list = speciesList.getValue();
|
List<DropdownDTO> list = speciesList.getValue();
|
||||||
if (position > 0 && list != null && position <= list.size()) {
|
if (position > 0 && list != null && position <= list.size()) {
|
||||||
selectedSpecies = list.get(position - 1).getLabel();
|
selectedSpecies = list.get(position - 1).getLabel();
|
||||||
|
loadBreeds(selectedSpecies);
|
||||||
} else {
|
} else {
|
||||||
selectedSpecies = null;
|
selectedSpecies = null;
|
||||||
|
breedList.setValue(new ArrayList<>());
|
||||||
}
|
}
|
||||||
updateViewState(state -> state.selectedSpecies = selectedSpecies);
|
updateViewState(state -> {
|
||||||
|
state.selectedSpecies = selectedSpecies;
|
||||||
|
state.isBreedEnabled = !state.isEditing && (selectedSpecies != null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onBreedSelected(int position) {
|
||||||
|
List<DropdownDTO> list = breedList.getValue();
|
||||||
|
if (position > 0 && list != null && position <= list.size()) {
|
||||||
|
selectedBreed = list.get(position - 1).getLabel();
|
||||||
|
} else {
|
||||||
|
selectedBreed = null;
|
||||||
|
}
|
||||||
|
updateViewState(state -> state.selectedBreed = selectedBreed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadBreeds(String species) {
|
||||||
|
observeOnce(petRepository.getPetBreedsDropdowns(species), resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
breedList.setValue(resource.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onStoreSelected(int position) {
|
public void onStoreSelected(int position) {
|
||||||
@@ -154,12 +180,15 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
selectedCustomerId = null;
|
selectedCustomerId = null;
|
||||||
selectedStoreId = null;
|
selectedStoreId = null;
|
||||||
selectedSpecies = null;
|
selectedSpecies = null;
|
||||||
|
selectedBreed = null;
|
||||||
state.selectedCustomerId = null;
|
state.selectedCustomerId = null;
|
||||||
state.selectedStoreId = null;
|
state.selectedStoreId = null;
|
||||||
state.selectedSpecies = null;
|
state.selectedSpecies = null;
|
||||||
|
state.selectedBreed = null;
|
||||||
state.selectedStatus = STATUS_AVAILABLE;
|
state.selectedStatus = STATUS_AVAILABLE;
|
||||||
state.isCustomerEnabled = false;
|
state.isCustomerEnabled = false;
|
||||||
state.isStoreEnabled = true;
|
state.isStoreEnabled = true;
|
||||||
|
state.isBreedEnabled = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -172,15 +201,22 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
selectedCustomerId = pet.getCustomerId();
|
selectedCustomerId = pet.getCustomerId();
|
||||||
selectedStoreId = pet.getStoreId();
|
selectedStoreId = pet.getStoreId();
|
||||||
selectedSpecies = pet.getPetSpecies();
|
selectedSpecies = pet.getPetSpecies();
|
||||||
|
selectedBreed = pet.getPetBreed();
|
||||||
isOriginallyOwnedOrAdopted = STATUS_OWNED.equalsIgnoreCase(pet.getPetStatus())
|
isOriginallyOwnedOrAdopted = STATUS_OWNED.equalsIgnoreCase(pet.getPetStatus())
|
||||||
|| STATUS_ADOPTED.equalsIgnoreCase(pet.getPetStatus());
|
|| STATUS_ADOPTED.equalsIgnoreCase(pet.getPetStatus());
|
||||||
originalCustomerId = pet.getCustomerId();
|
originalCustomerId = pet.getCustomerId();
|
||||||
|
|
||||||
|
if (selectedSpecies != null) {
|
||||||
|
loadBreeds(selectedSpecies);
|
||||||
|
}
|
||||||
|
|
||||||
updateViewState(state -> {
|
updateViewState(state -> {
|
||||||
state.selectedCustomerId = selectedCustomerId;
|
state.selectedCustomerId = selectedCustomerId;
|
||||||
state.selectedStoreId = selectedStoreId;
|
state.selectedStoreId = selectedStoreId;
|
||||||
state.selectedSpecies = selectedSpecies;
|
state.selectedSpecies = selectedSpecies;
|
||||||
|
state.selectedBreed = selectedBreed;
|
||||||
state.selectedStatus = normalizeStatus(pet.getPetStatus());
|
state.selectedStatus = normalizeStatus(pet.getPetStatus());
|
||||||
|
state.isBreedEnabled = !state.isEditing && (selectedSpecies != null);
|
||||||
applyStatusRules(state, false);
|
applyStatusRules(state, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -215,6 +251,10 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
return speciesList;
|
return speciesList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LiveData<List<DropdownDTO>> getBreedList() {
|
||||||
|
return breedList;
|
||||||
|
}
|
||||||
|
|
||||||
public LiveData<Boolean> getIsLoading() {
|
public LiveData<Boolean> getIsLoading() {
|
||||||
return isLoading;
|
return isLoading;
|
||||||
}
|
}
|
||||||
@@ -253,6 +293,7 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
String normalized = status.trim();
|
String normalized = status.trim();
|
||||||
if (STATUS_ADOPTED.equalsIgnoreCase(normalized)) return STATUS_ADOPTED;
|
if (STATUS_ADOPTED.equalsIgnoreCase(normalized)) return STATUS_ADOPTED;
|
||||||
if (STATUS_OWNED.equalsIgnoreCase(normalized)) return STATUS_OWNED;
|
if (STATUS_OWNED.equalsIgnoreCase(normalized)) return STATUS_OWNED;
|
||||||
|
if (STATUS_PENDING.equalsIgnoreCase(normalized)) return STATUS_PENDING;
|
||||||
return STATUS_AVAILABLE;
|
return STATUS_AVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,9 +331,10 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
public boolean isStoreEnabled = true;
|
public boolean isStoreEnabled = true;
|
||||||
public String modeTitle = "Add Pet";
|
public String modeTitle = "Add Pet";
|
||||||
public String saveButtonText = "Add";
|
public String saveButtonText = "Add";
|
||||||
public String[] availableStatuses = new String[]{STATUS_AVAILABLE, STATUS_ADOPTED, STATUS_OWNED};
|
public String[] availableStatuses = new String[]{STATUS_AVAILABLE, STATUS_ADOPTED, STATUS_OWNED, STATUS_PENDING};
|
||||||
public String selectedStatus = STATUS_AVAILABLE;
|
public String selectedStatus = STATUS_AVAILABLE;
|
||||||
public String selectedSpecies = null;
|
public String selectedSpecies = null;
|
||||||
|
public String selectedBreed = null;
|
||||||
public Long selectedCustomerId = null;
|
public Long selectedCustomerId = null;
|
||||||
public Long selectedStoreId = null;
|
public Long selectedStoreId = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import com.example.petstoremobile.repositories.SaleRepository;
|
|||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -125,6 +126,33 @@ public class RefundViewModel extends ViewModel {
|
|||||||
refundCart.setValue(cart);
|
refundCart.setValue(cart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BigDecimal calculateRefundTotal() {
|
||||||
|
SaleDTO sale = currentSale.getValue();
|
||||||
|
List<RefundItem> cart = refundCart.getValue();
|
||||||
|
if (sale == null || cart == null || cart.isEmpty()) return BigDecimal.ZERO;
|
||||||
|
|
||||||
|
BigDecimal cartSubtotal = BigDecimal.ZERO;
|
||||||
|
for (RefundItem item : cart) cartSubtotal = cartSubtotal.add(item.getTotal());
|
||||||
|
|
||||||
|
BigDecimal originalSubtotal = sale.getSubtotalAmount();
|
||||||
|
if (originalSubtotal == null || originalSubtotal.compareTo(BigDecimal.ZERO) == 0) {
|
||||||
|
if (sale.getItems() != null) {
|
||||||
|
originalSubtotal = BigDecimal.ZERO;
|
||||||
|
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
|
||||||
|
if (item.getUnitPrice() != null && item.getQuantity() != null)
|
||||||
|
originalSubtotal = originalSubtotal.add(item.getUnitPrice().multiply(BigDecimal.valueOf(Math.abs(item.getQuantity()))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (originalSubtotal == null || originalSubtotal.compareTo(BigDecimal.ZERO) == 0) return cartSubtotal;
|
||||||
|
|
||||||
|
BigDecimal originalTotal = sale.getTotalAmount();
|
||||||
|
if (originalTotal == null) return cartSubtotal;
|
||||||
|
|
||||||
|
BigDecimal ratio = cartSubtotal.divide(originalSubtotal, 10, RoundingMode.HALF_UP);
|
||||||
|
return originalTotal.abs().multiply(ratio).setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
public LiveData<Resource<SaleDTO>> submitRefund(String paymentMethod) {
|
public LiveData<Resource<SaleDTO>> submitRefund(String paymentMethod) {
|
||||||
SaleDTO sale = currentSale.getValue();
|
SaleDTO sale = currentSale.getValue();
|
||||||
List<RefundItem> cart = refundCart.getValue();
|
List<RefundItem> cart = refundCart.getValue();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData;
|
|||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.CouponDTO;
|
import com.example.petstoremobile.dtos.CouponDTO;
|
||||||
|
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import com.example.petstoremobile.dtos.SaleDTO;
|
import com.example.petstoremobile.dtos.SaleDTO;
|
||||||
@@ -39,6 +40,8 @@ public class SaleDetailViewModel extends ViewModel {
|
|||||||
private final MutableLiveData<List<ProductDTO>> productList = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<ProductDTO>> productList = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<List<SaleDTO.SaleItemDTO>> cartItems = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<SaleDTO.SaleItemDTO>> cartItems = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<CouponDTO> appliedCoupon = new MutableLiveData<>(null);
|
private final MutableLiveData<CouponDTO> appliedCoupon = new MutableLiveData<>(null);
|
||||||
|
private final MutableLiveData<CustomerDTO> selectedCustomerData = new MutableLiveData<>(null);
|
||||||
|
private final MutableLiveData<Boolean> useLoyaltyPoints = new MutableLiveData<>(false);
|
||||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -111,6 +114,34 @@ public class SaleDetailViewModel extends ViewModel {
|
|||||||
appliedCoupon.setValue(coupon);
|
appliedCoupon.setValue(coupon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUseLoyaltyPoints(boolean use) {
|
||||||
|
useLoyaltyPoints.setValue(use);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Boolean> getUseLoyaltyPoints() {
|
||||||
|
return useLoyaltyPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<CustomerDTO> getSelectedCustomerData() {
|
||||||
|
return selectedCustomerData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectCustomer(Long customerId) {
|
||||||
|
if (customerId == null) {
|
||||||
|
selectedCustomerData.setValue(null);
|
||||||
|
useLoyaltyPoints.setValue(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
customerRepository.getCustomerById(customerId).observeForever(new androidx.lifecycle.Observer<Resource<CustomerDTO>>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(Resource<CustomerDTO> resource) {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS) {
|
||||||
|
selectedCustomerData.setValue(resource.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void clearCoupon() {
|
public void clearCoupon() {
|
||||||
appliedCoupon.setValue(null);
|
appliedCoupon.setValue(null);
|
||||||
}
|
}
|
||||||
@@ -125,6 +156,10 @@ public class SaleDetailViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal calculateDiscount() {
|
public BigDecimal calculateDiscount() {
|
||||||
|
return calculateCouponDiscount().add(calculateLoyaltyDiscount());
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal calculateCouponDiscount() {
|
||||||
CouponDTO coupon = appliedCoupon.getValue();
|
CouponDTO coupon = appliedCoupon.getValue();
|
||||||
if (coupon == null || coupon.getDiscountValue() == null) return BigDecimal.ZERO;
|
if (coupon == null || coupon.getDiscountValue() == null) return BigDecimal.ZERO;
|
||||||
BigDecimal subtotal = calculateSubtotal();
|
BigDecimal subtotal = calculateSubtotal();
|
||||||
@@ -135,6 +170,25 @@ public class SaleDetailViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BigDecimal calculateLoyaltyDiscount() {
|
||||||
|
if (Boolean.FALSE.equals(useLoyaltyPoints.getValue())) return BigDecimal.ZERO;
|
||||||
|
CustomerDTO customer = selectedCustomerData.getValue();
|
||||||
|
if (customer == null || customer.getLoyaltyPoints() == null || customer.getLoyaltyPoints() < 20) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal subtotalAfterCoupon = calculateSubtotal().subtract(calculateCouponDiscount());
|
||||||
|
int maxPointsNeeded = subtotalAfterCoupon.multiply(BigDecimal.valueOf(20)).intValue();
|
||||||
|
int pointsToUse = Math.min(customer.getLoyaltyPoints(), maxPointsNeeded);
|
||||||
|
|
||||||
|
return BigDecimal.valueOf(pointsToUse).multiply(BigDecimal.valueOf(0.05)).setScale(2, java.math.RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int calculatePointsToUse() {
|
||||||
|
BigDecimal loyaltyDiscount = calculateLoyaltyDiscount();
|
||||||
|
return loyaltyDiscount.divide(BigDecimal.valueOf(0.05), 0, java.math.RoundingMode.HALF_UP).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
public BigDecimal calculateSubtotal() {
|
public BigDecimal calculateSubtotal() {
|
||||||
BigDecimal total = BigDecimal.ZERO;
|
BigDecimal total = BigDecimal.ZERO;
|
||||||
List<SaleDTO.SaleItemDTO> items = cartItems.getValue();
|
List<SaleDTO.SaleItemDTO> items = cartItems.getValue();
|
||||||
|
|||||||
@@ -60,6 +60,14 @@ public class StaffDetailViewModel extends ViewModel {
|
|||||||
return isEditing;
|
return isEditing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EmployeeDTO createEmployeeDto(String username, String password, String firstName,
|
||||||
|
String lastName, String email, String phone,
|
||||||
|
String role, String staffRole, boolean active, Long storeId) {
|
||||||
|
EmployeeDTO dto = new EmployeeDTO(username, password, firstName, lastName, email, phone, role, staffRole, active, storeId);
|
||||||
|
dto.setFullName(firstName + " " + lastName);
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
public LiveData<Resource<EmployeeDTO>> saveEmployee(EmployeeDTO dto) {
|
public LiveData<Resource<EmployeeDTO>> saveEmployee(EmployeeDTO dto) {
|
||||||
if (isEditing && employeeId > 0) {
|
if (isEditing && employeeId > 0) {
|
||||||
return repository.updateEmployee(employeeId, dto);
|
return repository.updateEmployee(employeeId, dto);
|
||||||
|
|||||||
@@ -195,14 +195,13 @@
|
|||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<TextView
|
<EditText
|
||||||
android:id="@+id/tvCustomerLoyaltyPoints"
|
android:id="@+id/etCustomerLoyaltyPoints"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="0"
|
android:hint="0"
|
||||||
android:textColor="@color/text_dark"
|
android:inputType="number"
|
||||||
android:textSize="16sp"
|
android:layout_marginBottom="16dp"/>
|
||||||
android:layout_marginBottom="8dp"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -109,14 +109,11 @@
|
|||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<EditText
|
<Spinner
|
||||||
android:id="@+id/etPetBreed"
|
android:id="@+id/spinnerPetBreed"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="Enter breed"
|
android:layout_marginBottom="16dp"/>
|
||||||
android:inputType="text"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
@@ -128,7 +128,30 @@
|
|||||||
android:id="@+id/spinnerSaleCustomer"
|
android:id="@+id/spinnerSaleCustomer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llLoyaltyPoints"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/cbUseLoyaltyPoints"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Use Loyalty Points"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAvailablePoints"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:text="(Available: 0)"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Payment Method -->
|
<!-- Payment Method -->
|
||||||
<TextView
|
<TextView
|
||||||
@@ -390,6 +413,27 @@
|
|||||||
android:textColor="@color/status_adopted"/>
|
android:textColor="@color/status_adopted"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llLoyaltyDiscount"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLoyaltyDiscountLabel"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Loyalty Discount:"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSaleLoyaltyDiscount"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="-$0.00"
|
||||||
|
android:textColor="@color/status_adopted"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvSaleDetailTotal"
|
android:id="@+id/tvSaleDetailTotal"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotEmpty;
|
|||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
public class SaleRequest {
|
public class SaleRequest {
|
||||||
@NotNull(message = "Store ID is required")
|
@NotNull(message = "Store ID is required")
|
||||||
@@ -28,7 +29,9 @@ public class SaleRequest {
|
|||||||
|
|
||||||
private Long cartId;
|
private Long cartId;
|
||||||
|
|
||||||
private Boolean useLoyaltyPoints = false;
|
private Integer pointsUsed;
|
||||||
|
|
||||||
|
private BigDecimal pointsDiscountAmount;
|
||||||
|
|
||||||
public Long getStoreId() {
|
public Long getStoreId() {
|
||||||
return storeId;
|
return storeId;
|
||||||
@@ -102,12 +105,20 @@ public class SaleRequest {
|
|||||||
this.cartId = cartId;
|
this.cartId = cartId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean getUseLoyaltyPoints() {
|
public Integer getPointsUsed() {
|
||||||
return useLoyaltyPoints;
|
return pointsUsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUseLoyaltyPoints(Boolean useLoyaltyPoints) {
|
public void setPointsUsed(Integer pointsUsed) {
|
||||||
this.useLoyaltyPoints = useLoyaltyPoints;
|
this.pointsUsed = pointsUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getPointsDiscountAmount() {
|
||||||
|
return pointsDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPointsDiscountAmount(BigDecimal pointsDiscountAmount) {
|
||||||
|
this.pointsDiscountAmount = pointsDiscountAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ public class SaleResponse {
|
|||||||
private BigDecimal loyaltyDiscountAmount;
|
private BigDecimal loyaltyDiscountAmount;
|
||||||
private Integer pointsUsed;
|
private Integer pointsUsed;
|
||||||
private Integer pointsEarned;
|
private Integer pointsEarned;
|
||||||
|
private Integer pointsUsed;
|
||||||
|
private BigDecimal pointsDiscountAmount;
|
||||||
private String channel;
|
private String channel;
|
||||||
private Long couponId;
|
private Long couponId;
|
||||||
private Long cartId;
|
private Long cartId;
|
||||||
@@ -153,6 +155,22 @@ public class SaleResponse {
|
|||||||
this.pointsEarned = pointsEarned;
|
this.pointsEarned = pointsEarned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getPointsUsed() {
|
||||||
|
return pointsUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPointsUsed(Integer pointsUsed) {
|
||||||
|
this.pointsUsed = pointsUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getPointsDiscountAmount() {
|
||||||
|
return pointsDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPointsDiscountAmount(BigDecimal pointsDiscountAmount) {
|
||||||
|
this.pointsDiscountAmount = pointsDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
public String getChannel() {
|
public String getChannel() {
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ public class UserRequest {
|
|||||||
|
|
||||||
private Boolean active = true;
|
private Boolean active = true;
|
||||||
|
|
||||||
|
private Integer loyaltyPoints;
|
||||||
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
@@ -127,6 +129,14 @@ public class UserRequest {
|
|||||||
this.active = active;
|
this.active = active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getLoyaltyPoints() {
|
||||||
|
return loyaltyPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoyaltyPoints(Integer loyaltyPoints) {
|
||||||
|
this.loyaltyPoints = loyaltyPoints;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ public class Sale {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private Integer pointsEarned = 0;
|
private Integer pointsEarned = 0;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Integer pointsUsed = 0;
|
||||||
|
|
||||||
|
@Column(nullable = false, precision = 10, scale = 2)
|
||||||
|
private BigDecimal pointsDiscountAmount = BigDecimal.ZERO;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "sale", cascade = CascadeType.ALL)
|
@OneToMany(mappedBy = "sale", cascade = CascadeType.ALL)
|
||||||
private List<SaleItem> items = new ArrayList<>();
|
private List<SaleItem> items = new ArrayList<>();
|
||||||
|
|
||||||
@@ -233,6 +239,22 @@ public class Sale {
|
|||||||
this.pointsEarned = pointsEarned;
|
this.pointsEarned = pointsEarned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getPointsUsed() {
|
||||||
|
return pointsUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPointsUsed(Integer pointsUsed) {
|
||||||
|
this.pointsUsed = pointsUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getPointsDiscountAmount() {
|
||||||
|
return pointsDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPointsDiscountAmount(BigDecimal pointsDiscountAmount) {
|
||||||
|
this.pointsDiscountAmount = pointsDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
public List<SaleItem> getItems() {
|
public List<SaleItem> getItems() {
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public interface PetRepository extends JpaRepository<Pet, Long> {
|
|||||||
"WHERE LOWER(p.petStatus) = 'available' " +
|
"WHERE LOWER(p.petStatus) = 'available' " +
|
||||||
"AND NOT EXISTS (" +
|
"AND NOT EXISTS (" +
|
||||||
" SELECT 1 FROM Adoption a " +
|
" SELECT 1 FROM Adoption a " +
|
||||||
" WHERE a.pet = p AND LOWER(a.adoptionStatus) = 'completed'" +
|
" WHERE a.pet = p AND (LOWER(a.adoptionStatus) = 'completed' OR LOWER(a.adoptionStatus) = 'pending')" +
|
||||||
") " +
|
") " +
|
||||||
"ORDER BY p.petName ASC")
|
"ORDER BY p.petName ASC")
|
||||||
List<Pet> findAdoptablePetsOrderByPetNameAsc();
|
List<Pet> findAdoptablePetsOrderByPetNameAsc();
|
||||||
@@ -29,7 +29,7 @@ public interface PetRepository extends JpaRepository<Pet, Long> {
|
|||||||
"AND (:storeId IS NULL OR p.store.storeId = :storeId) " +
|
"AND (:storeId IS NULL OR p.store.storeId = :storeId) " +
|
||||||
"AND NOT EXISTS (" +
|
"AND NOT EXISTS (" +
|
||||||
" SELECT 1 FROM Adoption a " +
|
" SELECT 1 FROM Adoption a " +
|
||||||
" WHERE a.pet = p AND LOWER(a.adoptionStatus) = 'completed'" +
|
" WHERE a.pet = p AND (LOWER(a.adoptionStatus) = 'completed' OR LOWER(a.adoptionStatus) = 'pending')" +
|
||||||
") " +
|
") " +
|
||||||
"ORDER BY p.petName ASC")
|
"ORDER BY p.petName ASC")
|
||||||
List<Pet> findAdoptablePetsByStore(@Param("storeId") Long storeId);
|
List<Pet> findAdoptablePetsByStore(@Param("storeId") Long storeId);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public class AdoptionService {
|
|||||||
private static final String ADOPTION_STATUS_MISSED = "Missed";
|
private static final String ADOPTION_STATUS_MISSED = "Missed";
|
||||||
private static final String PET_STATUS_AVAILABLE = "Available";
|
private static final String PET_STATUS_AVAILABLE = "Available";
|
||||||
private static final String PET_STATUS_ADOPTED = "Adopted";
|
private static final String PET_STATUS_ADOPTED = "Adopted";
|
||||||
|
private static final String PET_STATUS_PENDING = "Pending";
|
||||||
|
|
||||||
private final AdoptionRepository adoptionRepository;
|
private final AdoptionRepository adoptionRepository;
|
||||||
private final PetRepository petRepository;
|
private final PetRepository petRepository;
|
||||||
@@ -263,10 +264,14 @@ public class AdoptionService {
|
|||||||
private void syncPetStatus(Pet pet, String adoptionStatus, Long adoptionId, User customer) {
|
private void syncPetStatus(Pet pet, String adoptionStatus, Long adoptionId, User customer) {
|
||||||
boolean completedElsewhere = adoptionId != null
|
boolean completedElsewhere = adoptionId != null
|
||||||
&& adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(pet.getPetId(), ADOPTION_STATUS_COMPLETED, adoptionId);
|
&& adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(pet.getPetId(), ADOPTION_STATUS_COMPLETED, adoptionId);
|
||||||
|
|
||||||
if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) || completedElsewhere) {
|
if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) || completedElsewhere) {
|
||||||
pet.setPetStatus(PET_STATUS_ADOPTED);
|
pet.setPetStatus(PET_STATUS_ADOPTED);
|
||||||
pet.setOwner(customer);
|
pet.setOwner(customer);
|
||||||
pet.setStore(null);
|
pet.setStore(null);
|
||||||
|
} else if (ADOPTION_STATUS_PENDING.equalsIgnoreCase(adoptionStatus)) {
|
||||||
|
pet.setPetStatus(PET_STATUS_PENDING);
|
||||||
|
pet.setOwner(customer);
|
||||||
} else {
|
} else {
|
||||||
pet.setPetStatus(PET_STATUS_AVAILABLE);
|
pet.setPetStatus(PET_STATUS_AVAILABLE);
|
||||||
pet.setOwner(null);
|
pet.setOwner(null);
|
||||||
|
|||||||
@@ -160,14 +160,33 @@ public class SaleService {
|
|||||||
saleItems.add(saleItem);
|
saleItems.add(saleItem);
|
||||||
subtotalAmount = subtotalAmount.add(itemTotal);
|
subtotalAmount = subtotalAmount.add(itemTotal);
|
||||||
}
|
}
|
||||||
subtotalAmount = subtotalAmount.negate();
|
|
||||||
sale.setSubtotalAmount(subtotalAmount);
|
Sale originalSale = sale.getOriginalSale();
|
||||||
sale.setTotalAmount(subtotalAmount);
|
BigDecimal originalSubtotal = originalSale.getSubtotalAmount() != null
|
||||||
sale.setCouponDiscountAmount(BigDecimal.ZERO);
|
? originalSale.getSubtotalAmount()
|
||||||
sale.setEmployeeDiscountAmount(BigDecimal.ZERO);
|
: originalSale.getItems().stream()
|
||||||
sale.setLoyaltyDiscountAmount(BigDecimal.ZERO);
|
.map(i -> i.getUnitPrice().multiply(BigDecimal.valueOf(Math.abs(i.getQuantity()))))
|
||||||
sale.setPointsUsed(0);
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
sale.setPointsEarned(0);
|
|
||||||
|
BigDecimal refundRatio = originalSubtotal.compareTo(BigDecimal.ZERO) != 0
|
||||||
|
? subtotalAmount.divide(originalSubtotal, 10, RoundingMode.HALF_UP)
|
||||||
|
: BigDecimal.ONE;
|
||||||
|
|
||||||
|
BigDecimal refundTotal = originalSale.getTotalAmount().abs()
|
||||||
|
.multiply(refundRatio).setScale(2, RoundingMode.HALF_UP);
|
||||||
|
|
||||||
|
sale.setSubtotalAmount(subtotalAmount.negate());
|
||||||
|
sale.setTotalAmount(refundTotal.negate());
|
||||||
|
|
||||||
|
User refundCustomer = customer != null ? customer : originalSale.getCustomer();
|
||||||
|
if (refundCustomer != null) {
|
||||||
|
int pointsToRestore = BigDecimal.valueOf(originalSale.getPointsUsed())
|
||||||
|
.multiply(refundRatio).setScale(0, RoundingMode.FLOOR).intValue();
|
||||||
|
int pointsToDeduct = BigDecimal.valueOf(originalSale.getPointsEarned())
|
||||||
|
.multiply(refundRatio).setScale(0, RoundingMode.FLOOR).intValue();
|
||||||
|
refundCustomer.setLoyaltyPoints(refundCustomer.getLoyaltyPoints() + pointsToRestore - pointsToDeduct);
|
||||||
|
userRepository.save(refundCustomer);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (request.getItems() == null || request.getItems().isEmpty()) {
|
if (request.getItems() == null || request.getItems().isEmpty()) {
|
||||||
throw new BusinessException("At least one item is required");
|
throw new BusinessException("At least one item is required");
|
||||||
@@ -204,14 +223,23 @@ public class SaleService {
|
|||||||
BigDecimal couponDiscount = calculateCouponDiscount(sale.getCoupon(), subtotalAmount);
|
BigDecimal couponDiscount = calculateCouponDiscount(sale.getCoupon(), subtotalAmount);
|
||||||
sale.setCouponDiscountAmount(couponDiscount);
|
sale.setCouponDiscountAmount(couponDiscount);
|
||||||
|
|
||||||
BigDecimal employeeDiscount = calculateEmployeeDiscount(customer, subtotalAmount.subtract(couponDiscount));
|
BigDecimal pointsDiscount = BigDecimal.ZERO;
|
||||||
|
int pointsUsed = 0;
|
||||||
|
if (customer != null && request.getPointsUsed() != null && request.getPointsUsed() > 0) {
|
||||||
|
if (customer.getLoyaltyPoints() < request.getPointsUsed()) {
|
||||||
|
throw new BusinessException("Customer does not have enough loyalty points");
|
||||||
|
}
|
||||||
|
pointsUsed = request.getPointsUsed();
|
||||||
|
pointsDiscount = calculatePointsDiscount(pointsUsed);
|
||||||
|
customer.setLoyaltyPoints(customer.getLoyaltyPoints() - pointsUsed);
|
||||||
|
}
|
||||||
|
sale.setPointsUsed(pointsUsed);
|
||||||
|
sale.setPointsDiscountAmount(pointsDiscount);
|
||||||
|
|
||||||
|
BigDecimal employeeDiscount = calculateEmployeeDiscount(customer, subtotalAmount.subtract(couponDiscount).subtract(pointsDiscount));
|
||||||
sale.setEmployeeDiscountAmount(employeeDiscount);
|
sale.setEmployeeDiscountAmount(employeeDiscount);
|
||||||
|
|
||||||
BigDecimal loyaltyDiscount = calculateLoyaltyDiscount(customer, subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount), Boolean.TRUE.equals(request.getUseLoyaltyPoints()));
|
BigDecimal finalTotal = subtotalAmount.subtract(couponDiscount).subtract(pointsDiscount).subtract(employeeDiscount);
|
||||||
sale.setLoyaltyDiscountAmount(loyaltyDiscount);
|
|
||||||
sale.setPointsUsed(toPointsUsed(loyaltyDiscount));
|
|
||||||
|
|
||||||
BigDecimal finalTotal = subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount).subtract(loyaltyDiscount);
|
|
||||||
sale.setTotalAmount(finalTotal.max(BigDecimal.ZERO));
|
sale.setTotalAmount(finalTotal.max(BigDecimal.ZERO));
|
||||||
|
|
||||||
sale.setPointsEarned(sale.getTotalAmount().setScale(0, RoundingMode.FLOOR).intValue());
|
sale.setPointsEarned(sale.getTotalAmount().setScale(0, RoundingMode.FLOOR).intValue());
|
||||||
@@ -262,6 +290,10 @@ public class SaleService {
|
|||||||
return discount.min(subtotal).setScale(2, RoundingMode.HALF_UP);
|
return discount.min(subtotal).setScale(2, RoundingMode.HALF_UP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BigDecimal calculatePointsDiscount(int pointsUsed) {
|
||||||
|
return new BigDecimal(pointsUsed).divide(new BigDecimal("20"), 2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
private BigDecimal calculateEmployeeDiscount(User customer, BigDecimal remainingAmount) {
|
private BigDecimal calculateEmployeeDiscount(User customer, BigDecimal remainingAmount) {
|
||||||
if (customer == null || remainingAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
if (customer == null || remainingAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
return BigDecimal.ZERO;
|
return BigDecimal.ZERO;
|
||||||
@@ -328,6 +360,8 @@ public class SaleService {
|
|||||||
response.setLoyaltyDiscountAmount(sale.getLoyaltyDiscountAmount());
|
response.setLoyaltyDiscountAmount(sale.getLoyaltyDiscountAmount());
|
||||||
response.setPointsUsed(sale.getPointsUsed());
|
response.setPointsUsed(sale.getPointsUsed());
|
||||||
response.setPointsEarned(sale.getPointsEarned());
|
response.setPointsEarned(sale.getPointsEarned());
|
||||||
|
response.setPointsUsed(sale.getPointsUsed());
|
||||||
|
response.setPointsDiscountAmount(sale.getPointsDiscountAmount());
|
||||||
response.setChannel(sale.getChannel());
|
response.setChannel(sale.getChannel());
|
||||||
if (sale.getCoupon() != null) {
|
if (sale.getCoupon() != null) {
|
||||||
response.setCouponId(sale.getCoupon().getCouponId());
|
response.setCouponId(sale.getCoupon().getCouponId());
|
||||||
|
|||||||
@@ -75,6 +75,9 @@ public class UserService {
|
|||||||
user.setStaffRole(trimToNull(request.getStaffRole()));
|
user.setStaffRole(trimToNull(request.getStaffRole()));
|
||||||
user.setPrimaryStore(resolveStore(request.getPrimaryStoreId()));
|
user.setPrimaryStore(resolveStore(request.getPrimaryStoreId()));
|
||||||
user.setActive(request.getActive() != null ? request.getActive() : true);
|
user.setActive(request.getActive() != null ? request.getActive() : true);
|
||||||
|
if (request.getLoyaltyPoints() != null) {
|
||||||
|
user.setLoyaltyPoints(request.getLoyaltyPoints());
|
||||||
|
}
|
||||||
|
|
||||||
validateUniquePhone(user.getPhone(), null);
|
validateUniquePhone(user.getPhone(), null);
|
||||||
|
|
||||||
@@ -111,6 +114,9 @@ public class UserService {
|
|||||||
user.setStaffRole(trimToNull(request.getStaffRole()));
|
user.setStaffRole(trimToNull(request.getStaffRole()));
|
||||||
user.setPrimaryStore(resolveStore(request.getPrimaryStoreId()));
|
user.setPrimaryStore(resolveStore(request.getPrimaryStoreId()));
|
||||||
user.setActive(request.getActive() != null ? request.getActive() : true);
|
user.setActive(request.getActive() != null ? request.getActive() : true);
|
||||||
|
if (request.getLoyaltyPoints() != null) {
|
||||||
|
user.setLoyaltyPoints(request.getLoyaltyPoints());
|
||||||
|
}
|
||||||
if (invalidateToken) {
|
if (invalidateToken) {
|
||||||
user.setTokenVersion(user.getTokenVersion() + 1);
|
user.setTokenVersion(user.getTokenVersion() + 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE sale
|
||||||
|
ADD COLUMN pointsUsed INT NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN pointsDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00;
|
||||||
@@ -230,6 +230,8 @@ CREATE TABLE IF NOT EXISTS sale (
|
|||||||
couponDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
couponDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||||||
employeeDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
employeeDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||||||
pointsEarned INT NOT NULL DEFAULT 0,
|
pointsEarned INT NOT NULL DEFAULT 0,
|
||||||
|
pointsUsed INT NOT NULL DEFAULT 0,
|
||||||
|
pointsDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
CONSTRAINT fk_sale_employee FOREIGN KEY (employeeId) REFERENCES users(id),
|
CONSTRAINT fk_sale_employee FOREIGN KEY (employeeId) REFERENCES users(id),
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ public class Validator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the input is a valid phone number in format XXX-XXX-XXXX
|
* Checks if the input is a valid phone number in format (XXX) XXX-XXXX
|
||||||
* @param value input of string
|
* @param value input of string
|
||||||
* @param name name of input
|
* @param name name of input
|
||||||
* @return error msg if input is not in valid phone format, otherwise empty
|
* @return error msg if input is not in valid phone format, otherwise empty
|
||||||
@@ -191,14 +191,14 @@ public class Validator {
|
|||||||
public static String isValidPhoneNumber(String value, String name){
|
public static String isValidPhoneNumber(String value, String name){
|
||||||
String msg = "";
|
String msg = "";
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
msg += name + " must be in format XXX-XXX-XXXX. \n";
|
msg += name + " must be in format (XXX) XXX-XXXX. \n";
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
String regex = "^\\d{3}-\\d{3}-\\d{4}$";
|
String regex = "^\\(\\d{3}\\) \\d{3}-\\d{4}$";
|
||||||
|
|
||||||
if (!value.matches(regex)){
|
if (!value.matches(regex)){
|
||||||
msg += name + " must be in format XXX-XXX-XXXX. \n";
|
msg += name + " must be in format (XXX) XXX-XXXX. \n";
|
||||||
}
|
}
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ public class UserResponse {
|
|||||||
private String phone;
|
private String phone;
|
||||||
private String role;
|
private String role;
|
||||||
private Boolean active;
|
private Boolean active;
|
||||||
|
private Integer loyaltyPoints;
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
@@ -72,6 +73,14 @@ public class UserResponse {
|
|||||||
this.active = active;
|
this.active = active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getLoyaltyPoints() {
|
||||||
|
return loyaltyPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoyaltyPoints(Integer loyaltyPoints) {
|
||||||
|
this.loyaltyPoints = loyaltyPoints;
|
||||||
|
}
|
||||||
|
|
||||||
public LocalDateTime getCreatedAt() {
|
public LocalDateTime getCreatedAt() {
|
||||||
return createdAt;
|
return createdAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,15 @@ public class DropdownApi {
|
|||||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropdownOption> getPetBreeds(String species) throws Exception {
|
||||||
|
String encoded = java.net.URLEncoder.encode(species, java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
String response = apiClient.getRawResponse("/api/v1/dropdowns/pet-breeds?species=" + encoded);
|
||||||
|
if (response == null || response.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Empty response from pet breeds endpoint");
|
||||||
|
}
|
||||||
|
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropdownOption> getProducts() throws Exception {
|
public List<DropdownOption> getProducts() throws Exception {
|
||||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/products");
|
String response = apiClient.getRawResponse("/api/v1/dropdowns/products");
|
||||||
if (response == null || response.isEmpty()) {
|
if (response == null || response.isEmpty()) {
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.collections.transformation.FilteredList;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.example.petshopdesktop.api.dto.user.UserResponse;
|
||||||
|
import org.example.petshopdesktop.api.endpoints.CustomerApi;
|
||||||
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
|
import org.example.petshopdesktop.util.TableViewSupport;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class CustomerAccountsController {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableView<UserResponse> tvCustomers;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<UserResponse, String> colCustomerUsername;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<UserResponse, String> colCustomerName;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<UserResponse, String> colCustomerEmail;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<UserResponse, String> colCustomerPhone;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<UserResponse, Object> colCustomerLoyaltyPoints;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<UserResponse, String> colCustomerStatus;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<UserResponse, Object> colCustomerCreated;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField txtSearchCustomer;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnEditCustomer;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnRefresh;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label lblError;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label lblStatus;
|
||||||
|
|
||||||
|
private final ObservableList<UserResponse> customerAccounts = FXCollections.observableArrayList();
|
||||||
|
private FilteredList<UserResponse> filteredCustomers;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
colCustomerUsername.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getUsername()));
|
||||||
|
colCustomerName.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getFullName()));
|
||||||
|
colCustomerEmail.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getEmail()));
|
||||||
|
colCustomerPhone.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getPhone()));
|
||||||
|
colCustomerLoyaltyPoints.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getLoyaltyPoints()));
|
||||||
|
colCustomerStatus.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getActive() != null && data.getValue().getActive() ? "Active" : "Inactive"));
|
||||||
|
colCustomerCreated.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getCreatedAt()));
|
||||||
|
|
||||||
|
filteredCustomers = new FilteredList<>(customerAccounts, a -> true);
|
||||||
|
TableViewSupport.bindSortedItems(tvCustomers, filteredCustomers);
|
||||||
|
TableViewSupport.installDoubleClickAction(tvCustomers, this::openEditDialog);
|
||||||
|
|
||||||
|
tvCustomers.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) ->
|
||||||
|
btnEditCustomer.setDisable(newVal == null));
|
||||||
|
btnEditCustomer.setDisable(true);
|
||||||
|
|
||||||
|
txtSearchCustomer.textProperty().addListener((obs, o, n) -> applyCustomerFilter(n));
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnRefreshClicked(ActionEvent event) {
|
||||||
|
txtSearchCustomer.clear();
|
||||||
|
TableViewSupport.clearSort(tvCustomers);
|
||||||
|
refresh();
|
||||||
|
TableViewSupport.flashStatus(lblStatus, "Refreshed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnEditCustomerClicked(ActionEvent event) {
|
||||||
|
lblError.setText("");
|
||||||
|
openEditDialog(tvCustomers.getSelectionModel().getSelectedItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openEditDialog(UserResponse selected) {
|
||||||
|
if (selected == null) {
|
||||||
|
lblError.setText("Select a customer to edit.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml"));
|
||||||
|
Stage dialog = new Stage();
|
||||||
|
dialog.initOwner(tvCustomers.getScene().getWindow());
|
||||||
|
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||||
|
dialog.setTitle("Edit Customer Account");
|
||||||
|
dialog.setScene(new Scene(loader.load()));
|
||||||
|
dialog.setResizable(false);
|
||||||
|
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.StaffEditDialogController) loader.getController();
|
||||||
|
controller.setUser(selected);
|
||||||
|
dialog.showAndWait();
|
||||||
|
refresh();
|
||||||
|
} catch (Exception e) {
|
||||||
|
ActivityLogger.getInstance().logException("CustomerAccountsController.openEditDialog", e, "Opening customer edit dialog");
|
||||||
|
lblError.setText("Could not open customer account editor.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refresh() {
|
||||||
|
lblError.setText("");
|
||||||
|
tvCustomers.setDisable(true);
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
Comparator<UserResponse> byCreated = Comparator.comparing(
|
||||||
|
UserResponse::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder()));
|
||||||
|
|
||||||
|
List<UserResponse> customers = CustomerApi.getInstance().listCustomers(null).stream()
|
||||||
|
.sorted(byCreated)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
customerAccounts.setAll(customers);
|
||||||
|
tvCustomers.setDisable(false);
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
ActivityLogger.getInstance().logException("CustomerAccountsController.refresh", e, "Loading customer accounts");
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
lblError.setText("Could not load customer accounts.");
|
||||||
|
tvCustomers.setDisable(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyCustomerFilter(String text) {
|
||||||
|
String q = text == null ? "" : text.trim().toLowerCase();
|
||||||
|
if (q.isEmpty()) {
|
||||||
|
filteredCustomers.setPredicate(a -> true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
filteredCustomers.setPredicate(a ->
|
||||||
|
safe(a.getUsername()).contains(q)
|
||||||
|
|| safe(a.getFullName()).contains(q)
|
||||||
|
|| safe(a.getEmail()).contains(q)
|
||||||
|
|| safe(a.getPhone()).contains(q)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String safe(String v) {
|
||||||
|
return v == null ? "" : v.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,6 +89,9 @@ public class MainLayoutController {
|
|||||||
@FXML
|
@FXML
|
||||||
private Button btnStaffAccounts;
|
private Button btnStaffAccounts;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnCustomers;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button btnAnalytics;
|
private Button btnAnalytics;
|
||||||
|
|
||||||
@@ -179,6 +182,12 @@ public class MainLayoutController {
|
|||||||
updateButtons(btnStaffAccounts);
|
updateButtons(btnStaffAccounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnCustomersClicked(ActionEvent event) {
|
||||||
|
loadView("customer-accounts-view.fxml");
|
||||||
|
updateButtons(btnCustomers);
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void btnAnalyticsClicked(ActionEvent event) {
|
void btnAnalyticsClicked(ActionEvent event) {
|
||||||
loadView("analytics-view.fxml");
|
loadView("analytics-view.fxml");
|
||||||
@@ -415,8 +424,13 @@ public class MainLayoutController {
|
|||||||
btnPurchaseOrders.setManaged(isAdmin);
|
btnPurchaseOrders.setManaged(isAdmin);
|
||||||
|
|
||||||
if (btnStaffAccounts != null) {
|
if (btnStaffAccounts != null) {
|
||||||
btnStaffAccounts.setVisible(true);
|
btnStaffAccounts.setVisible(isAdmin);
|
||||||
btnStaffAccounts.setManaged(true);
|
btnStaffAccounts.setManaged(isAdmin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btnCustomers != null) {
|
||||||
|
btnCustomers.setVisible(true);
|
||||||
|
btnCustomers.setManaged(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lblAdminSection != null) {
|
if (lblAdminSection != null) {
|
||||||
@@ -493,6 +507,7 @@ public class MainLayoutController {
|
|||||||
btnProducts,
|
btnProducts,
|
||||||
btnPurchaseOrders,
|
btnPurchaseOrders,
|
||||||
btnStaffAccounts,
|
btnStaffAccounts,
|
||||||
|
btnCustomers,
|
||||||
btnAnalytics,
|
btnAnalytics,
|
||||||
btnActivityLogs,
|
btnActivityLogs,
|
||||||
btnCoupons,
|
btnCoupons,
|
||||||
|
|||||||
@@ -255,6 +255,8 @@ public class SaleController {
|
|||||||
boolean isAdmin = UserSession.getInstance().isAdmin();
|
boolean isAdmin = UserSession.getInstance().isAdmin();
|
||||||
vbCreateSale.setVisible(!isAdmin);
|
vbCreateSale.setVisible(!isAdmin);
|
||||||
vbCreateSale.setManaged(!isAdmin);
|
vbCreateSale.setManaged(!isAdmin);
|
||||||
|
btnRefund.setVisible(!isAdmin);
|
||||||
|
btnRefund.setManaged(!isAdmin);
|
||||||
lblModeNote.setText(isAdmin ? "(View only)" : "(Staff can create sales)");
|
lblModeNote.setText(isAdmin ? "(View only)" : "(Staff can create sales)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,26 +8,25 @@ import javafx.event.ActionEvent;
|
|||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.control.TableColumn;
|
|
||||||
import javafx.scene.control.TableView;
|
|
||||||
import javafx.scene.control.TextField;
|
|
||||||
import javafx.stage.Modality;
|
import javafx.stage.Modality;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.example.petshopdesktop.api.dto.user.UserResponse;
|
import org.example.petshopdesktop.api.dto.user.UserResponse;
|
||||||
import org.example.petshopdesktop.api.endpoints.UserApi;
|
import org.example.petshopdesktop.api.endpoints.UserApi;
|
||||||
import org.example.petshopdesktop.api.endpoints.CustomerApi;
|
|
||||||
import org.example.petshopdesktop.auth.UserSession;
|
import org.example.petshopdesktop.auth.UserSession;
|
||||||
import org.example.petshopdesktop.util.ActivityLogger;
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
import org.example.petshopdesktop.util.TableViewSupport;
|
import org.example.petshopdesktop.util.TableViewSupport;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class StaffAccountsController {
|
public class StaffAccountsController {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private VBox staffSection;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableView<UserResponse> tvStaff;
|
private TableView<UserResponse> tvStaff;
|
||||||
|
|
||||||
@@ -55,12 +54,6 @@ public class StaffAccountsController {
|
|||||||
@FXML
|
@FXML
|
||||||
private TextField txtSearch;
|
private TextField txtSearch;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label lblError;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label lblStatus;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button btnRefresh;
|
private Button btnRefresh;
|
||||||
|
|
||||||
@@ -70,8 +63,14 @@ public class StaffAccountsController {
|
|||||||
@FXML
|
@FXML
|
||||||
private Button btnEditAccount;
|
private Button btnEditAccount;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label lblError;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label lblStatus;
|
||||||
|
|
||||||
private final ObservableList<UserResponse> staffAccounts = FXCollections.observableArrayList();
|
private final ObservableList<UserResponse> staffAccounts = FXCollections.observableArrayList();
|
||||||
private FilteredList<UserResponse> filtered;
|
private FilteredList<UserResponse> filteredStaff;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
@@ -83,21 +82,15 @@ public class StaffAccountsController {
|
|||||||
colStatus.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getActive() != null && data.getValue().getActive() ? "Active" : "Inactive"));
|
colStatus.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getActive() != null && data.getValue().getActive() ? "Active" : "Inactive"));
|
||||||
colCreated.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getCreatedAt()));
|
colCreated.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getCreatedAt()));
|
||||||
|
|
||||||
filtered = new FilteredList<>(staffAccounts, a -> true);
|
filteredStaff = new FilteredList<>(staffAccounts, a -> true);
|
||||||
TableViewSupport.bindSortedItems(tvStaff, filtered);
|
TableViewSupport.bindSortedItems(tvStaff, filteredStaff);
|
||||||
TableViewSupport.installDoubleClickAction(tvStaff, this::openEditDialog);
|
TableViewSupport.installDoubleClickAction(tvStaff, this::openEditDialog);
|
||||||
|
|
||||||
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
|
tvStaff.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) ->
|
||||||
|
btnEditAccount.setDisable(newVal == null));
|
||||||
|
btnEditAccount.setDisable(true);
|
||||||
|
|
||||||
tvStaff.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> {
|
txtSearch.textProperty().addListener((obs, o, n) -> applyStaffFilter(n));
|
||||||
if (btnEditAccount != null) {
|
|
||||||
btnEditAccount.setDisable(newValue == null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (btnEditAccount != null) {
|
|
||||||
btnEditAccount.setDisable(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
@@ -132,8 +125,7 @@ public class StaffAccountsController {
|
|||||||
@FXML
|
@FXML
|
||||||
void btnEditAccountClicked(ActionEvent event) {
|
void btnEditAccountClicked(ActionEvent event) {
|
||||||
lblError.setText("");
|
lblError.setText("");
|
||||||
UserResponse selected = tvStaff.getSelectionModel().getSelectedItem();
|
openEditDialog(tvStaff.getSelectionModel().getSelectedItem());
|
||||||
openEditDialog(selected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openEditDialog(UserResponse selected) {
|
private void openEditDialog(UserResponse selected) {
|
||||||
@@ -156,7 +148,7 @@ public class StaffAccountsController {
|
|||||||
Stage dialog = new Stage();
|
Stage dialog = new Stage();
|
||||||
dialog.initOwner(tvStaff.getScene().getWindow());
|
dialog.initOwner(tvStaff.getScene().getWindow());
|
||||||
dialog.initModality(Modality.APPLICATION_MODAL);
|
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||||
dialog.setTitle("Edit User Account");
|
dialog.setTitle("Edit Staff Account");
|
||||||
dialog.setScene(new Scene(loader.load()));
|
dialog.setScene(new Scene(loader.load()));
|
||||||
dialog.setResizable(false);
|
dialog.setResizable(false);
|
||||||
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.StaffEditDialogController) loader.getController();
|
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.StaffEditDialogController) loader.getController();
|
||||||
@@ -164,7 +156,7 @@ public class StaffAccountsController {
|
|||||||
dialog.showAndWait();
|
dialog.showAndWait();
|
||||||
refresh();
|
refresh();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ActivityLogger.getInstance().logException("StaffAccountsController.btnEditAccountClicked", e, "Opening user edit dialog");
|
ActivityLogger.getInstance().logException("StaffAccountsController.openEditDialog", e, "Opening user edit dialog");
|
||||||
lblError.setText("Could not open user account editor.");
|
lblError.setText("Could not open user account editor.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,48 +167,40 @@ public class StaffAccountsController {
|
|||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
UserSession session = UserSession.getInstance();
|
Comparator<UserResponse> byCreated = Comparator.comparing(
|
||||||
List<UserResponse> users;
|
UserResponse::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder()));
|
||||||
if (session.isAdmin()) {
|
|
||||||
users = UserApi.getInstance().listUsers(null);
|
|
||||||
} else {
|
|
||||||
users = CustomerApi.getInstance().listCustomers(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<UserResponse> sortedUsers = users.stream()
|
List<UserResponse> staff = UserApi.getInstance().listUsers(null).stream()
|
||||||
.sorted(Comparator.comparing(UserResponse::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder())))
|
.filter(u -> !"CUSTOMER".equalsIgnoreCase(u.getRole()))
|
||||||
.collect(Collectors.toList());
|
.sorted(byCreated)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
staffAccounts.setAll(sortedUsers);
|
staffAccounts.setAll(staff);
|
||||||
tvStaff.setDisable(false);
|
tvStaff.setDisable(false);
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading user accounts");
|
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading staff accounts");
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
String message = e.getMessage();
|
lblError.setText("Could not load staff accounts.");
|
||||||
lblError.setText(message == null || message.isBlank()
|
|
||||||
? "Could not load user accounts."
|
|
||||||
: "Could not load user accounts: " + message);
|
|
||||||
tvStaff.setDisable(false);
|
tvStaff.setDisable(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyFilter(String text) {
|
private void applyStaffFilter(String text) {
|
||||||
String q = text == null ? "" : text.trim().toLowerCase();
|
String q = text == null ? "" : text.trim().toLowerCase();
|
||||||
if (q.isEmpty()) {
|
if (q.isEmpty()) {
|
||||||
filtered.setPredicate(a -> true);
|
filteredStaff.setPredicate(a -> true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
filteredStaff.setPredicate(a ->
|
||||||
filtered.setPredicate(a ->
|
safe(a.getUsername()).contains(q)
|
||||||
safe(a.getUsername()).contains(q)
|
|| safe(a.getFullName()).contains(q)
|
||||||
|| safe(a.getFullName()).contains(q)
|
|| safe(a.getEmail()).contains(q)
|
||||||
|| safe(a.getEmail()).contains(q)
|
|| safe(a.getPhone()).contains(q)
|
||||||
|| safe(a.getPhone()).contains(q)
|
|| safe(a.getRole()).contains(q)
|
||||||
|| safe(a.getRole()).contains(q)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class AdoptionDialogController {
|
|||||||
private boolean suppressPaymentDialog = false;
|
private boolean suppressPaymentDialog = false;
|
||||||
|
|
||||||
private ObservableList<String> statusList = FXCollections.observableArrayList(
|
private ObservableList<String> statusList = FXCollections.observableArrayList(
|
||||||
"Pending", "Completed", "Cancelled"
|
"Pending", "Completed", "Missed", "Cancelled"
|
||||||
);
|
);
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -282,6 +282,7 @@ public class AdoptionDialogController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suppressPaymentDialog = true;
|
suppressPaymentDialog = true;
|
||||||
|
cbAdoptionStatus.setItems(statusList);
|
||||||
for (String status : cbAdoptionStatus.getItems()) {
|
for (String status : cbAdoptionStatus.getItems()) {
|
||||||
if (status.equals(adoption.getAdoptionStatus())) {
|
if (status.equals(adoption.getAdoptionStatus())) {
|
||||||
cbAdoptionStatus.getSelectionModel().select(status);
|
cbAdoptionStatus.getSelectionModel().select(status);
|
||||||
@@ -289,13 +290,57 @@ public class AdoptionDialogController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
suppressPaymentDialog = false;
|
suppressPaymentDialog = false;
|
||||||
|
applyEditModeLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyEditModeLock() {
|
||||||
|
String status = cbAdoptionStatus.getValue();
|
||||||
|
|
||||||
|
if ("Cancelled".equalsIgnoreCase(status)) {
|
||||||
|
cbPet.setDisable(true);
|
||||||
|
cbCustomer.setDisable(true);
|
||||||
|
cbEmployee.setDisable(true);
|
||||||
|
dpAdoptionDate.setDisable(true);
|
||||||
|
cbAdoptionStatus.setDisable(true);
|
||||||
|
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Cancelled"));
|
||||||
|
btnSave.setDisable(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDate adoptionDate = dpAdoptionDate.getValue();
|
||||||
|
boolean isPast = adoptionDate != null && adoptionDate.isBefore(LocalDate.now());
|
||||||
|
|
||||||
|
cbPet.setDisable(true);
|
||||||
|
cbCustomer.setDisable(true);
|
||||||
|
dpAdoptionDate.setDisable(false);
|
||||||
|
cbEmployee.setDisable(false);
|
||||||
|
cbAdoptionStatus.setDisable(false);
|
||||||
|
|
||||||
|
suppressPaymentDialog = true;
|
||||||
|
if (isPast) {
|
||||||
|
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Completed", "Missed"));
|
||||||
|
dpAdoptionDate.setDisable(true);
|
||||||
|
} else {
|
||||||
|
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Pending", "Cancelled"));
|
||||||
|
}
|
||||||
|
if (!cbAdoptionStatus.getItems().contains(cbAdoptionStatus.getValue())) {
|
||||||
|
cbAdoptionStatus.getSelectionModel().selectFirst();
|
||||||
|
}
|
||||||
|
suppressPaymentDialog = false;
|
||||||
|
}
|
||||||
|
|
||||||
public void setMode(String mode) {
|
public void setMode(String mode) {
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
lblMode.setText(mode + " Adoption");
|
lblMode.setText(mode + " Adoption");
|
||||||
lblAdoptionId.setVisible(mode.equals("Edit"));
|
lblAdoptionId.setVisible(mode.equals("Edit"));
|
||||||
|
if (mode.equals("Add")) {
|
||||||
|
suppressPaymentDialog = true;
|
||||||
|
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Pending"));
|
||||||
|
cbAdoptionStatus.setValue("Pending");
|
||||||
|
cbAdoptionStatus.setDisable(true);
|
||||||
|
suppressPaymentDialog = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applySelectedPet() {
|
private void applySelectedPet() {
|
||||||
|
|||||||
@@ -46,21 +46,23 @@ public class AppointmentDialogController {
|
|||||||
private String mode = null;
|
private String mode = null;
|
||||||
private AppointmentDTO selectedAppointment = null;
|
private AppointmentDTO selectedAppointment = null;
|
||||||
private Long pendingPetSelectionId = null;
|
private Long pendingPetSelectionId = null;
|
||||||
|
private boolean isOriginallyCancel = false;
|
||||||
private ObservableList<String> statusList =
|
private boolean isOriginallyCompletedOrMissed = false;
|
||||||
FXCollections.observableArrayList(
|
|
||||||
"Booked", "Completed", "Missed", "Cancelled"
|
|
||||||
);
|
|
||||||
|
|
||||||
public void setMode(String mode) {
|
public void setMode(String mode) {
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
lblMode.setText(mode + " Appointment");
|
lblMode.setText(mode + " Appointment");
|
||||||
lblAppointmentId.setVisible(!mode.equals("Add"));
|
lblAppointmentId.setVisible(!mode.equals("Add"));
|
||||||
|
if (mode.equals("Add")) {
|
||||||
|
cbAppointmentStatus.setItems(FXCollections.observableArrayList("Booked"));
|
||||||
|
cbAppointmentStatus.setValue("Booked");
|
||||||
|
cbAppointmentStatus.setDisable(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
cbAppointmentStatus.setItems(statusList);
|
cbAppointmentStatus.setItems(FXCollections.observableArrayList("Booked", "Completed", "Missed", "Cancelled"));
|
||||||
cbPet.setDisable(true);
|
cbPet.setDisable(true);
|
||||||
cbEmployee.setPromptText("Select an employee");
|
cbEmployee.setPromptText("Select an employee");
|
||||||
cbPet.setPromptText("Select a customer first");
|
cbPet.setPromptText("Select a customer first");
|
||||||
@@ -228,6 +230,46 @@ public class AppointmentDialogController {
|
|||||||
applySelectedService();
|
applySelectedService();
|
||||||
applySelectedCustomer();
|
applySelectedCustomer();
|
||||||
applySelectedEmployee();
|
applySelectedEmployee();
|
||||||
|
applyEditModeLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyEditModeLock() {
|
||||||
|
String status = cbAppointmentStatus.getValue();
|
||||||
|
isOriginallyCancel = "Cancelled".equalsIgnoreCase(status);
|
||||||
|
isOriginallyCompletedOrMissed = "Completed".equalsIgnoreCase(status) || "Missed".equalsIgnoreCase(status);
|
||||||
|
|
||||||
|
if (isOriginallyCancel) {
|
||||||
|
cbService.setDisable(true);
|
||||||
|
cbCustomer.setDisable(true);
|
||||||
|
cbPet.setDisable(true);
|
||||||
|
cbEmployee.setDisable(true);
|
||||||
|
cbHour.setDisable(true);
|
||||||
|
cbMinute.setDisable(true);
|
||||||
|
dpAppointmentDate.setDisable(true);
|
||||||
|
cbAppointmentStatus.setDisable(true);
|
||||||
|
cbAppointmentStatus.setItems(FXCollections.observableArrayList("Cancelled"));
|
||||||
|
btnSave.setDisable(true);
|
||||||
|
} else if (isOriginallyCompletedOrMissed) {
|
||||||
|
cbService.setDisable(true);
|
||||||
|
cbCustomer.setDisable(true);
|
||||||
|
cbPet.setDisable(true);
|
||||||
|
cbEmployee.setDisable(true);
|
||||||
|
cbHour.setDisable(true);
|
||||||
|
cbMinute.setDisable(true);
|
||||||
|
dpAppointmentDate.setDisable(true);
|
||||||
|
cbAppointmentStatus.setDisable(false);
|
||||||
|
cbAppointmentStatus.setItems(FXCollections.observableArrayList("Completed", "Missed"));
|
||||||
|
} else {
|
||||||
|
cbService.setDisable(true);
|
||||||
|
cbCustomer.setDisable(true);
|
||||||
|
cbPet.setDisable(true);
|
||||||
|
cbEmployee.setDisable(false);
|
||||||
|
cbHour.setDisable(false);
|
||||||
|
cbMinute.setDisable(false);
|
||||||
|
dpAppointmentDate.setDisable(false);
|
||||||
|
cbAppointmentStatus.setDisable(false);
|
||||||
|
cbAppointmentStatus.setItems(FXCollections.observableArrayList("Booked", "Cancelled"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buttonSaveClicked(MouseEvent e) {
|
private void buttonSaveClicked(MouseEvent e) {
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ public class InventoryDialogController {
|
|||||||
//Validate inputs
|
//Validate inputs
|
||||||
errorMsg += Validator.isPresent(txtQuantity.getText(), "Quantity");
|
errorMsg += Validator.isPresent(txtQuantity.getText(), "Quantity");
|
||||||
errorMsg += Validator.isLessThanVarChars(txtQuantity.getText(), "Quantity", 11);
|
errorMsg += Validator.isLessThanVarChars(txtQuantity.getText(), "Quantity", 11);
|
||||||
errorMsg += Validator.isNonNegativeInteger(txtQuantity.getText(), "Quantity");
|
errorMsg += Validator.isPositiveInteger(txtQuantity.getText(), "Quantity");
|
||||||
|
|
||||||
//Operation only occurs if there are no errors
|
//Operation only occurs if there are no errors
|
||||||
if (errorMsg.isEmpty()) {
|
if (errorMsg.isEmpty()) {
|
||||||
|
|||||||
@@ -28,65 +28,34 @@ import java.math.BigDecimal;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class PetDialogController {
|
public class PetDialogController {
|
||||||
|
|
||||||
@FXML
|
private static final String STATUS_AVAILABLE = "Available";
|
||||||
private Button btnCancel;
|
private static final String STATUS_ADOPTED = "Adopted";
|
||||||
|
private static final String STATUS_OWNED = "Owned";
|
||||||
|
private static final String STATUS_PENDING = "Pending";
|
||||||
|
|
||||||
@FXML
|
@FXML private Button btnCancel;
|
||||||
private Button btnSave;
|
@FXML private Button btnSave;
|
||||||
|
@FXML private Button btnChangeImage;
|
||||||
@FXML
|
@FXML private Button btnRemoveImage;
|
||||||
private Button btnChangeImage;
|
@FXML private ComboBox<String> cbPetStatus;
|
||||||
|
@FXML private ComboBox<String> cbPetSpecies;
|
||||||
@FXML
|
@FXML private ComboBox<String> cbPetBreed;
|
||||||
private Button btnRemoveImage;
|
@FXML private ComboBox<DropdownOption> cbCustomer;
|
||||||
|
@FXML private ComboBox<DropdownOption> cbStore;
|
||||||
@FXML
|
@FXML private VBox vbCustomerField;
|
||||||
private ComboBox<String> cbPetStatus;
|
@FXML private VBox vbStoreField;
|
||||||
|
@FXML private VBox vbPriceField;
|
||||||
@FXML
|
@FXML private Label lblMode;
|
||||||
private ComboBox<DropdownOption> cbCustomer;
|
@FXML private Label lblPetId;
|
||||||
|
@FXML private Label lblImageStatus;
|
||||||
@FXML
|
@FXML private ImageView imgPetPreview;
|
||||||
private ComboBox<DropdownOption> cbStore;
|
@FXML private TextField txtPetAge;
|
||||||
|
@FXML private TextField txtPetName;
|
||||||
@FXML
|
@FXML private TextField txtPetPrice;
|
||||||
private VBox vbCustomerField;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private VBox vbStoreField;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private VBox vbPriceField;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label lblMode;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label lblPetId;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label lblImageStatus;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ImageView imgPetPreview;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TextField txtPetAge;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TextField txtPetBreed;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TextField txtPetName;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TextField txtPetPrice;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ComboBox<String> cbPetSpecies;
|
|
||||||
|
|
||||||
private String mode = null;
|
private String mode = null;
|
||||||
private File selectedImageFile;
|
private File selectedImageFile;
|
||||||
@@ -96,15 +65,17 @@ public class PetDialogController {
|
|||||||
private Long pendingCustomerId = null;
|
private Long pendingCustomerId = null;
|
||||||
private Long pendingStoreId = null;
|
private Long pendingStoreId = null;
|
||||||
private Long originalCustomerId = null;
|
private Long originalCustomerId = null;
|
||||||
|
private boolean isOriginallyOwnedOrAdopted = false;
|
||||||
|
private String pendingBreedValue = null;
|
||||||
|
|
||||||
private ObservableList<String> statusList = FXCollections.observableArrayList(
|
private final ObservableList<String> statusList = FXCollections.observableArrayList(
|
||||||
"Available", "Adopted", "Owned", "Pending"
|
STATUS_AVAILABLE, STATUS_ADOPTED, STATUS_OWNED, STATUS_PENDING
|
||||||
);
|
);
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void initialize() {
|
void initialize() {
|
||||||
|
|
||||||
cbPetStatus.setItems(statusList);
|
cbPetStatus.setItems(statusList);
|
||||||
|
cbPetBreed.setDisable(true);
|
||||||
|
|
||||||
cbCustomer.setCellFactory(param -> new ListCell<>() {
|
cbCustomer.setCellFactory(param -> new ListCell<>() {
|
||||||
@Override protected void updateItem(DropdownOption o, boolean empty) {
|
@Override protected void updateItem(DropdownOption o, boolean empty) {
|
||||||
@@ -132,14 +103,26 @@ public class PetDialogController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setFieldVisibility(vbCustomerField, false);
|
applyStatusRules(STATUS_AVAILABLE, false);
|
||||||
setFieldVisibility(vbStoreField, false);
|
|
||||||
setFieldVisibility(vbPriceField, true);
|
|
||||||
|
|
||||||
loadSpecies();
|
loadSpecies();
|
||||||
|
loadCustomers();
|
||||||
|
loadStores();
|
||||||
|
|
||||||
|
cbPetSpecies.valueProperty().addListener((obs, oldVal, newVal) -> {
|
||||||
|
if (newVal != null && !newVal.isBlank()) {
|
||||||
|
if (!cbPetSpecies.isDisabled()) cbPetBreed.setDisable(false);
|
||||||
|
loadBreeds(newVal);
|
||||||
|
} else {
|
||||||
|
cbPetBreed.setItems(FXCollections.observableArrayList());
|
||||||
|
cbPetBreed.setValue(null);
|
||||||
|
cbPetBreed.setPromptText("Select Species first");
|
||||||
|
if (!cbPetSpecies.isDisabled()) cbPetBreed.setDisable(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
cbPetStatus.valueProperty().addListener((obs, oldVal, newVal) -> {
|
cbPetStatus.valueProperty().addListener((obs, oldVal, newVal) -> {
|
||||||
updateStatusFieldVisibility(newVal);
|
if (newVal != null) applyStatusRules(newVal, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||||
@@ -159,52 +142,81 @@ public class PetDialogController {
|
|||||||
btnChangeImage.setOnMouseClicked(mouseEvent -> handleChangeImage());
|
btnChangeImage.setOnMouseClicked(mouseEvent -> handleChangeImage());
|
||||||
btnRemoveImage.setOnMouseClicked(mouseEvent -> handleRemoveImage());
|
btnRemoveImage.setOnMouseClicked(mouseEvent -> handleRemoveImage());
|
||||||
refreshImagePreview();
|
refreshImagePreview();
|
||||||
|
}
|
||||||
|
|
||||||
loadCustomers();
|
private void loadBreeds(String species) {
|
||||||
loadStores();
|
cbPetBreed.setPromptText("Loading breeds...");
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
List<DropdownOption> options = DropdownApi.getInstance().getPetBreeds(species);
|
||||||
|
List<String> breeds = options.stream().map(DropdownOption::getLabel).collect(Collectors.toList());
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
cbPetBreed.setItems(FXCollections.observableArrayList(breeds));
|
||||||
|
cbPetBreed.setPromptText("Select Breed");
|
||||||
|
if (pendingBreedValue != null) {
|
||||||
|
cbPetBreed.setValue(pendingBreedValue);
|
||||||
|
pendingBreedValue = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
ActivityLogger.getInstance().logException("PetDialogController.loadBreeds", e, "Loading breeds for species: " + species);
|
||||||
|
cbPetBreed.setPromptText("Unable to load breeds");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyStatusRules(String status, boolean clearInvalidSelections) {
|
||||||
|
if (STATUS_AVAILABLE.equalsIgnoreCase(status)) {
|
||||||
|
vbCustomerField.setDisable(true);
|
||||||
|
vbStoreField.setDisable(false);
|
||||||
|
if (clearInvalidSelections) cbCustomer.setValue(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (STATUS_OWNED.equalsIgnoreCase(status)) {
|
||||||
|
vbCustomerField.setDisable(false);
|
||||||
|
vbStoreField.setDisable(true);
|
||||||
|
if (clearInvalidSelections) cbStore.setValue(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vbCustomerField.setDisable(false);
|
||||||
|
vbStoreField.setDisable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buttonSaveClicked(MouseEvent mouseEvent) {
|
private void buttonSaveClicked(MouseEvent mouseEvent) {
|
||||||
String errorMsg = "";
|
String errorMsg = "";
|
||||||
|
|
||||||
//Check validation (input required)
|
|
||||||
errorMsg += Validator.isPresent(txtPetName.getText(), "Pet Name");
|
errorMsg += Validator.isPresent(txtPetName.getText(), "Pet Name");
|
||||||
errorMsg += Validator.isPresent(txtPetAge.getText(), "Age");
|
errorMsg += Validator.isPresent(txtPetAge.getText(), "Age");
|
||||||
errorMsg += Validator.isPresent(txtPetBreed.getText(), "Breed");
|
|
||||||
String speciesValue = cbPetSpecies.getValue() != null ? cbPetSpecies.getValue().trim() : "";
|
String speciesValue = cbPetSpecies.getValue() != null ? cbPetSpecies.getValue().trim() : "";
|
||||||
if (speciesValue.isEmpty()) errorMsg += "Species is required\n";
|
if (speciesValue.isEmpty()) errorMsg += "Species is required\n";
|
||||||
|
String breedValue = cbPetBreed.getValue() != null ? cbPetBreed.getValue().trim() : "";
|
||||||
|
if (breedValue.isEmpty()) errorMsg += "Breed is required\n";
|
||||||
|
if (cbPetStatus.getSelectionModel().getSelectedItem() == null) errorMsg += "Status is required\n";
|
||||||
|
errorMsg += Validator.isPresent(txtPetPrice.getText(), "Price");
|
||||||
|
|
||||||
String selectedStatus = cbPetStatus.getValue();
|
String selectedStatus = cbPetStatus.getValue();
|
||||||
boolean needsPrice = !("Owned".equalsIgnoreCase(selectedStatus) || "Adopted".equalsIgnoreCase(selectedStatus));
|
if (STATUS_AVAILABLE.equalsIgnoreCase(selectedStatus) && cbStore.getValue() == null) {
|
||||||
if (needsPrice) {
|
errorMsg += "Store is required for Available status\n";
|
||||||
errorMsg += Validator.isPresent(txtPetPrice.getText(), "Price");
|
|
||||||
}
|
}
|
||||||
if (cbPetStatus.getSelectionModel().getSelectedItem() == null){
|
if (STATUS_OWNED.equalsIgnoreCase(selectedStatus) && cbCustomer.getValue() == null) {
|
||||||
errorMsg += "Status is required";
|
|
||||||
}
|
|
||||||
if ("Owned".equalsIgnoreCase(selectedStatus) && cbCustomer.getValue() == null && UserSession.getInstance().isAdmin()) {
|
|
||||||
errorMsg += "Customer is required for Owned status\n";
|
errorMsg += "Customer is required for Owned status\n";
|
||||||
}
|
}
|
||||||
boolean storeRequired = requiresStore(selectedStatus) && !"Adopted".equalsIgnoreCase(selectedStatus);
|
if (STATUS_ADOPTED.equalsIgnoreCase(selectedStatus)) {
|
||||||
if (storeRequired && cbStore.getValue() == null) {
|
if (cbCustomer.getValue() == null) errorMsg += "Customer is required for Adopted status\n";
|
||||||
errorMsg += "Store is required for " + selectedStatus + " status\n";
|
if (cbStore.getValue() == null) errorMsg += "Store is required for Adopted status\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check validation (length size)
|
|
||||||
errorMsg += Validator.isLessThanVarChars(txtPetName.getText(), "Pet Name", 50);
|
errorMsg += Validator.isLessThanVarChars(txtPetName.getText(), "Pet Name", 50);
|
||||||
errorMsg += Validator.isLessThanVarChars(speciesValue, "Species", 50);
|
errorMsg += Validator.isLessThanVarChars(speciesValue, "Species", 50);
|
||||||
errorMsg += Validator.isLessThanVarChars(txtPetBreed.getText(), "Breed", 50);
|
errorMsg += Validator.isLessThanVarChars(breedValue, "Breed", 50);
|
||||||
if (needsPrice) {
|
errorMsg += Validator.isLessThanVarChars(txtPetPrice.getText(), "Price", 12);
|
||||||
errorMsg += Validator.isLessThanVarChars(txtPetPrice.getText(), "Price", 12);
|
|
||||||
}
|
|
||||||
errorMsg += Validator.isLessThanVarChars(txtPetAge.getText(), "Age", 11);
|
errorMsg += Validator.isLessThanVarChars(txtPetAge.getText(), "Age", 11);
|
||||||
|
errorMsg += Validator.isNonNegativeDouble(txtPetPrice.getText(), "Price");
|
||||||
//Check validation (format)
|
|
||||||
if (needsPrice) {
|
|
||||||
errorMsg += Validator.isNonNegativeDouble(txtPetPrice.getText(), "Price");
|
|
||||||
}
|
|
||||||
errorMsg += Validator.isPositiveInteger(txtPetAge.getText(), "Age");
|
errorMsg += Validator.isPositiveInteger(txtPetAge.getText(), "Age");
|
||||||
|
|
||||||
if(errorMsg.isEmpty()){
|
if (errorMsg.isEmpty()) {
|
||||||
if ("Edit".equals(mode) && UserSession.getInstance().isAdmin()) {
|
if ("Edit".equals(mode) && UserSession.getInstance().isAdmin()) {
|
||||||
Long newCustomerId = cbCustomer.getValue() != null ? cbCustomer.getValue().getId() : null;
|
Long newCustomerId = cbCustomer.getValue() != null ? cbCustomer.getValue().getId() : null;
|
||||||
if (!Objects.equals(originalCustomerId, newCustomerId)) {
|
if (!Objects.equals(originalCustomerId, newCustomerId)) {
|
||||||
@@ -212,44 +224,34 @@ public class PetDialogController {
|
|||||||
confirm.setHeaderText("Confirm Owner Change");
|
confirm.setHeaderText("Confirm Owner Change");
|
||||||
confirm.setContentText("Are you sure you want to reassign this pet to a different owner?");
|
confirm.setContentText("Are you sure you want to reassign this pet to a different owner?");
|
||||||
Optional<ButtonType> result = confirm.showAndWait();
|
Optional<ButtonType> result = confirm.showAndWait();
|
||||||
if (result.isEmpty() || result.get() != ButtonType.OK) {
|
if (result.isEmpty() || result.get() != ButtonType.OK) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PetRequest request = buildPetRequest();
|
PetRequest request = buildPetRequest();
|
||||||
try {
|
try {
|
||||||
if(mode.equals("Add")) {
|
if (mode.equals("Add")) {
|
||||||
PetResponse response = PetApi.getInstance().createPet(request);
|
PetResponse response = PetApi.getInstance().createPet(request);
|
||||||
applyImageChanges(response.getPetId());
|
applyImageChanges(response.getPetId());
|
||||||
} else {
|
} else {
|
||||||
String[] parts = lblPetId.getText().split(": ");
|
String[] parts = lblPetId.getText().split(": ");
|
||||||
if (parts.length < 2) {
|
if (parts.length < 2) throw new IllegalStateException("Invalid pet ID format");
|
||||||
throw new IllegalStateException("Invalid pet ID format");
|
|
||||||
}
|
|
||||||
Long petId = Long.parseLong(parts[1]);
|
Long petId = Long.parseLong(parts[1]);
|
||||||
PetApi.getInstance().updatePet(petId, request);
|
PetApi.getInstance().updatePet(petId, request);
|
||||||
applyImageChanges(petId);
|
applyImageChanges(petId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//tell the user operation was successful
|
|
||||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||||
alert.setHeaderText("Saved");
|
alert.setHeaderText("Saved");
|
||||||
alert.setContentText(mode + " succeeded");
|
alert.setContentText(mode + " succeeded");
|
||||||
alert.showAndWait();
|
alert.showAndWait();
|
||||||
closeStage(mouseEvent);
|
closeStage(mouseEvent);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ActivityLogger.getInstance().logException(
|
ActivityLogger.getInstance().logException("PetDialogController.buttonSaveClicked", e, mode + " pet record");
|
||||||
"PetDialogController.buttonSaveClicked",
|
|
||||||
e,
|
|
||||||
mode + " pet record");
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||||
alert.setHeaderText("Operation Error");
|
alert.setHeaderText("Operation Error");
|
||||||
alert.setContentText(mode + " failed: " + e.getMessage());
|
alert.setContentText(mode + " failed: " + e.getMessage());
|
||||||
alert.showAndWait();
|
alert.showAndWait();
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||||
alert.setHeaderText("Input Error");
|
alert.setHeaderText("Input Error");
|
||||||
alert.setContentText(errorMsg);
|
alert.setContentText(errorMsg);
|
||||||
@@ -261,11 +263,10 @@ public class PetDialogController {
|
|||||||
PetRequest request = new PetRequest();
|
PetRequest request = new PetRequest();
|
||||||
request.setPetName(txtPetName.getText());
|
request.setPetName(txtPetName.getText());
|
||||||
request.setPetSpecies(cbPetSpecies.getValue() != null ? cbPetSpecies.getValue().trim() : "");
|
request.setPetSpecies(cbPetSpecies.getValue() != null ? cbPetSpecies.getValue().trim() : "");
|
||||||
request.setPetBreed(txtPetBreed.getText());
|
request.setPetBreed(cbPetBreed.getValue() != null ? cbPetBreed.getValue().trim() : "");
|
||||||
request.setPetStatus(cbPetStatus.getValue());
|
request.setPetStatus(cbPetStatus.getValue());
|
||||||
String buildStatus = cbPetStatus.getValue();
|
|
||||||
boolean buildNeedsPrice = !("Owned".equalsIgnoreCase(buildStatus) || "Adopted".equalsIgnoreCase(buildStatus));
|
if (txtPetPrice.getText() != null && !txtPetPrice.getText().isBlank()) {
|
||||||
if (buildNeedsPrice && txtPetPrice.getText() != null && !txtPetPrice.getText().isBlank()) {
|
|
||||||
try {
|
try {
|
||||||
request.setPetPrice(new BigDecimal(txtPetPrice.getText()));
|
request.setPetPrice(new BigDecimal(txtPetPrice.getText()));
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
@@ -273,21 +274,14 @@ public class PetDialogController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int age;
|
|
||||||
try {
|
try {
|
||||||
age = Integer.parseInt(txtPetAge.getText());
|
request.setPetAge(Integer.parseInt(txtPetAge.getText()));
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new IllegalArgumentException("Invalid age format");
|
throw new IllegalArgumentException("Invalid age format");
|
||||||
}
|
}
|
||||||
request.setPetAge(age);
|
|
||||||
|
|
||||||
String status = cbPetStatus.getValue();
|
if (cbCustomer.getValue() != null) request.setCustomerId(cbCustomer.getValue().getId());
|
||||||
if (("Owned".equalsIgnoreCase(status) || "Adopted".equalsIgnoreCase(status)) && cbCustomer.getValue() != null) {
|
if (cbStore.getValue() != null) request.setStoreId(cbStore.getValue().getId());
|
||||||
request.setCustomerId(cbCustomer.getValue().getId());
|
|
||||||
}
|
|
||||||
if (requiresStore(status) && cbStore.getValue() != null) {
|
|
||||||
request.setStoreId(cbStore.getValue().getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
@@ -296,15 +290,11 @@ public class PetDialogController {
|
|||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
List<DropdownOption> options = DropdownApi.getInstance().getPetSpecies();
|
List<DropdownOption> options = DropdownApi.getInstance().getPetSpecies();
|
||||||
List<String> species = options.stream()
|
List<String> species = options.stream().map(DropdownOption::getLabel).collect(Collectors.toList());
|
||||||
.map(DropdownOption::getLabel)
|
|
||||||
.collect(java.util.stream.Collectors.toList());
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
String current = cbPetSpecies.getValue();
|
String current = cbPetSpecies.getValue();
|
||||||
cbPetSpecies.setItems(FXCollections.observableArrayList(species));
|
cbPetSpecies.setItems(FXCollections.observableArrayList(species));
|
||||||
if (current != null && !current.isBlank()) {
|
if (current != null && !current.isBlank()) cbPetSpecies.setValue(current);
|
||||||
cbPetSpecies.setValue(current);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Platform.runLater(() -> ActivityLogger.getInstance().logException(
|
Platform.runLater(() -> ActivityLogger.getInstance().logException(
|
||||||
@@ -323,8 +313,7 @@ public class PetDialogController {
|
|||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
ActivityLogger.getInstance().logException(
|
ActivityLogger.getInstance().logException("PetDialogController.loadCustomers", e, "Loading customers");
|
||||||
"PetDialogController.loadCustomers", e, "Loading customers");
|
|
||||||
cbCustomer.setDisable(true);
|
cbCustomer.setDisable(true);
|
||||||
cbCustomer.setPromptText("Unable to load customers");
|
cbCustomer.setPromptText("Unable to load customers");
|
||||||
});
|
});
|
||||||
@@ -342,8 +331,7 @@ public class PetDialogController {
|
|||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
ActivityLogger.getInstance().logException(
|
ActivityLogger.getInstance().logException("PetDialogController.loadStores", e, "Loading stores");
|
||||||
"PetDialogController.loadStores", e, "Loading stores");
|
|
||||||
cbStore.setDisable(true);
|
cbStore.setDisable(true);
|
||||||
cbStore.setPromptText("Unable to load stores");
|
cbStore.setPromptText("Unable to load stores");
|
||||||
});
|
});
|
||||||
@@ -383,55 +371,78 @@ public class PetDialogController {
|
|||||||
stage.close();
|
stage.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void displayPetDetails(Pet pet){
|
public void displayPetDetails(Pet pet) {
|
||||||
if (pet!=null){
|
if (pet == null) return;
|
||||||
lblPetId.setText("ID: " + pet.getPetId());
|
|
||||||
txtPetName.setText(pet.getPetName());
|
|
||||||
cbPetSpecies.setValue(pet.getPetSpecies());
|
|
||||||
txtPetBreed.setText(pet.getPetBreed());
|
|
||||||
txtPetAge.setText(pet.getPetAge() + "");
|
|
||||||
txtPetPrice.setText(pet.getPetPrice() + "");
|
|
||||||
currentImageUrl = pet.getImageUrl();
|
|
||||||
selectedImageFile = null;
|
|
||||||
removeImageRequested = false;
|
|
||||||
refreshImagePreview();
|
|
||||||
|
|
||||||
pendingCustomerId = pet.getCustomerId() > 0 ? pet.getCustomerId() : null;
|
lblPetId.setText("ID: " + pet.getPetId());
|
||||||
originalCustomerId = pendingCustomerId;
|
txtPetName.setText(pet.getPetName());
|
||||||
pendingStoreId = pet.getStoreId() > 0 ? pet.getStoreId() : null;
|
txtPetAge.setText(String.valueOf(pet.getPetAge()));
|
||||||
|
txtPetPrice.setText(String.valueOf(pet.getPetPrice()));
|
||||||
|
currentImageUrl = pet.getImageUrl();
|
||||||
|
selectedImageFile = null;
|
||||||
|
removeImageRequested = false;
|
||||||
|
refreshImagePreview();
|
||||||
|
|
||||||
for (String status : cbPetStatus.getItems()) {
|
pendingCustomerId = pet.getCustomerId() > 0 ? pet.getCustomerId() : null;
|
||||||
if(status.equals(pet.getPetStatus())){
|
originalCustomerId = pendingCustomerId;
|
||||||
cbPetStatus.getSelectionModel().select(status);
|
pendingStoreId = pet.getStoreId() > 0 ? pet.getStoreId() : null;
|
||||||
break;
|
|
||||||
}
|
isOriginallyOwnedOrAdopted = STATUS_OWNED.equalsIgnoreCase(pet.getPetStatus())
|
||||||
|
|| STATUS_ADOPTED.equalsIgnoreCase(pet.getPetStatus());
|
||||||
|
|
||||||
|
pendingBreedValue = pet.getPetBreed();
|
||||||
|
cbPetSpecies.setValue(pet.getPetSpecies());
|
||||||
|
|
||||||
|
for (String status : cbPetStatus.getItems()) {
|
||||||
|
if (status.equals(pet.getPetStatus())) {
|
||||||
|
cbPetStatus.getSelectionModel().select(status);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
updateStatusFieldVisibility(cbPetStatus.getValue());
|
}
|
||||||
|
|
||||||
|
applyStatusRules(cbPetStatus.getValue(), false);
|
||||||
|
applyEditModeLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyEditModeLock() {
|
||||||
|
cbPetSpecies.setDisable(true);
|
||||||
|
cbPetBreed.setDisable(true);
|
||||||
|
|
||||||
|
boolean isStaff = !UserSession.getInstance().isAdmin();
|
||||||
|
if (isStaff && isOriginallyOwnedOrAdopted) {
|
||||||
|
cbPetStatus.setDisable(true);
|
||||||
|
vbCustomerField.setDisable(true);
|
||||||
|
vbStoreField.setDisable(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMode(String mode) {
|
public void setMode(String mode) {
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
lblMode.setText(mode + " Pet");
|
lblMode.setText(mode + " Pet");
|
||||||
if(mode.equals("Add")) {
|
|
||||||
|
if (mode.equals("Add")) {
|
||||||
lblPetId.setVisible(false);
|
lblPetId.setVisible(false);
|
||||||
currentImageUrl = null;
|
currentImageUrl = null;
|
||||||
selectedImageFile = null;
|
selectedImageFile = null;
|
||||||
removeImageRequested = false;
|
removeImageRequested = false;
|
||||||
|
cbPetSpecies.setDisable(false);
|
||||||
|
cbPetBreed.setDisable(true);
|
||||||
|
cbPetBreed.setItems(FXCollections.observableArrayList());
|
||||||
|
cbPetBreed.setValue(null);
|
||||||
|
cbPetBreed.setPromptText("Select Species first");
|
||||||
|
cbPetStatus.setDisable(false);
|
||||||
|
cbPetStatus.getSelectionModel().select(STATUS_AVAILABLE);
|
||||||
|
applyStatusRules(STATUS_AVAILABLE, false);
|
||||||
refreshImagePreview();
|
refreshImagePreview();
|
||||||
}
|
} else if (mode.equals("Edit")) {
|
||||||
else if(mode.equals("Edit")) {
|
|
||||||
lblPetId.setVisible(true);
|
lblPetId.setVisible(true);
|
||||||
refreshImagePreview();
|
refreshImagePreview();
|
||||||
}
|
}
|
||||||
updateStatusFieldVisibility(cbPetStatus.getValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleChangeImage() {
|
private void handleChangeImage() {
|
||||||
File file = FilePickerSupport.pickImageFile(btnSave.getScene().getWindow());
|
File file = FilePickerSupport.pickImageFile(btnSave.getScene().getWindow());
|
||||||
if (file == null) {
|
if (file == null) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
selectedImageFile = file;
|
selectedImageFile = file;
|
||||||
removeImageRequested = false;
|
removeImageRequested = false;
|
||||||
lblImageStatus.setText("Selected: " + file.getName());
|
lblImageStatus.setText("Selected: " + file.getName());
|
||||||
@@ -468,9 +479,7 @@ public class PetDialogController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void refreshImagePreview() {
|
private void refreshImagePreview() {
|
||||||
if (imgPetPreview == null || lblImageStatus == null || btnRemoveImage == null) {
|
if (imgPetPreview == null || lblImageStatus == null || btnRemoveImage == null) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
imgPetPreview.setImage(null);
|
imgPetPreview.setImage(null);
|
||||||
if (selectedImageFile != null) {
|
if (selectedImageFile != null) {
|
||||||
lblImageStatus.setText("Selected: " + selectedImageFile.getName());
|
lblImageStatus.setText("Selected: " + selectedImageFile.getName());
|
||||||
@@ -487,30 +496,4 @@ public class PetDialogController {
|
|||||||
lblImageStatus.setText("No image selected");
|
lblImageStatus.setText("No image selected");
|
||||||
btnRemoveImage.setDisable(true);
|
btnRemoveImage.setDisable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStatusFieldVisibility(String status) {
|
|
||||||
boolean statusNeedsCustomer = "Owned".equalsIgnoreCase(status) || "Adopted".equalsIgnoreCase(status);
|
|
||||||
boolean needsCustomer = statusNeedsCustomer && UserSession.getInstance().isAdmin();
|
|
||||||
boolean storeBased = requiresStore(status);
|
|
||||||
boolean needsPrice = !statusNeedsCustomer;
|
|
||||||
setFieldVisibility(vbCustomerField, needsCustomer);
|
|
||||||
setFieldVisibility(vbStoreField, storeBased);
|
|
||||||
setFieldVisibility(vbPriceField, needsPrice);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean requiresStore(String status) {
|
|
||||||
return "Available".equalsIgnoreCase(status)
|
|
||||||
|| "Pending".equalsIgnoreCase(status)
|
|
||||||
|| "Unadopted".equalsIgnoreCase(status)
|
|
||||||
|| "Adopted".equalsIgnoreCase(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setFieldVisibility(VBox field, boolean visible) {
|
|
||||||
if (field == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
field.setVisible(visible);
|
|
||||||
field.setManaged(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import javafx.fxml.FXMLLoader;
|
|||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Modality;
|
import javafx.stage.Modality;
|
||||||
import org.example.petshopdesktop.util.ActivityLogger;
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
|
import org.example.petshopdesktop.auth.UserSession;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -46,6 +47,12 @@ public class SaleDetailDialogController {
|
|||||||
colQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
colQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||||
colUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
colUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
||||||
colLineTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
|
colLineTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
|
||||||
|
|
||||||
|
if (btnRefund != null) {
|
||||||
|
boolean isAdmin = UserSession.getInstance().isAdmin();
|
||||||
|
btnRefund.setVisible(!isAdmin);
|
||||||
|
btnRefund.setManaged(!isAdmin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void displaySaleDetails(SaleDetail sale) {
|
public void displaySaleDetails(SaleDetail sale) {
|
||||||
@@ -57,7 +64,10 @@ public class SaleDetailDialogController {
|
|||||||
lblTotal.setText(currency.format(sale.getTotalAmount()));
|
lblTotal.setText(currency.format(sale.getTotalAmount()));
|
||||||
tvItems.setItems(sale.getItems());
|
tvItems.setItems(sale.getItems());
|
||||||
if (btnRefund != null) {
|
if (btnRefund != null) {
|
||||||
btnRefund.setDisable(sale.isRefund());
|
boolean isAdmin = UserSession.getInstance().isAdmin();
|
||||||
|
if (!isAdmin) {
|
||||||
|
btnRefund.setDisable(sale.isRefund());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.example.petshopdesktop.api.endpoints.UserApi;
|
|||||||
import org.example.petshopdesktop.api.endpoints.CustomerApi;
|
import org.example.petshopdesktop.api.endpoints.CustomerApi;
|
||||||
import org.example.petshopdesktop.auth.UserSession;
|
import org.example.petshopdesktop.auth.UserSession;
|
||||||
import org.example.petshopdesktop.util.ActivityLogger;
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
|
import org.example.petshopdesktop.util.TextFieldFormatSupport;
|
||||||
|
|
||||||
public class StaffEditDialogController {
|
public class StaffEditDialogController {
|
||||||
|
|
||||||
@@ -47,6 +48,11 @@ public class StaffEditDialogController {
|
|||||||
|
|
||||||
private UserResponse user;
|
private UserResponse user;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void initialize() {
|
||||||
|
TextFieldFormatSupport.applyPhoneNumberFormat(txtPhone);
|
||||||
|
}
|
||||||
|
|
||||||
public void setUser(UserResponse user) {
|
public void setUser(UserResponse user) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
String fullName = user.getFullName() == null ? "" : user.getFullName();
|
String fullName = user.getFullName() == null ? "" : user.getFullName();
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.example.petshopdesktop.api.endpoints.CustomerApi;
|
|||||||
import org.example.petshopdesktop.auth.UserSession;
|
import org.example.petshopdesktop.auth.UserSession;
|
||||||
import org.example.petshopdesktop.Validator;
|
import org.example.petshopdesktop.Validator;
|
||||||
import org.example.petshopdesktop.util.ActivityLogger;
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
|
import org.example.petshopdesktop.util.TextFieldFormatSupport;
|
||||||
|
|
||||||
public class StaffRegisterDialogController {
|
public class StaffRegisterDialogController {
|
||||||
|
|
||||||
@@ -45,6 +46,11 @@ public class StaffRegisterDialogController {
|
|||||||
@FXML
|
@FXML
|
||||||
private Button btnCreate;
|
private Button btnCreate;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void initialize() {
|
||||||
|
TextFieldFormatSupport.applyPhoneNumberFormat(txtPhone);
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void btnCreateClicked(ActionEvent event) {
|
void btnCreateClicked(ActionEvent event) {
|
||||||
lblError.setText("");
|
lblError.setText("");
|
||||||
@@ -82,6 +88,10 @@ public class StaffRegisterDialogController {
|
|||||||
lblError.setText("Password is required.");
|
lblError.setText("Password is required.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (password.length() < 6) {
|
||||||
|
lblError.setText("Password must be at least 6 characters.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!password.equals(confirm)) {
|
if (!password.equals(confirm)) {
|
||||||
lblError.setText("Passwords do not match.");
|
lblError.setText("Passwords do not match.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.example.petshopdesktop.api.dto.supplier.SupplierResponse;
|
|||||||
import org.example.petshopdesktop.api.endpoints.SupplierApi;
|
import org.example.petshopdesktop.api.endpoints.SupplierApi;
|
||||||
import org.example.petshopdesktop.models.Supplier;
|
import org.example.petshopdesktop.models.Supplier;
|
||||||
import org.example.petshopdesktop.util.ActivityLogger;
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
|
import org.example.petshopdesktop.util.TextFieldFormatSupport;
|
||||||
|
|
||||||
public class SupplierDialogController {
|
public class SupplierDialogController {
|
||||||
|
|
||||||
@@ -52,6 +53,8 @@ public class SupplierDialogController {
|
|||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
void initialize() {
|
void initialize() {
|
||||||
|
TextFieldFormatSupport.applyPhoneNumberFormat(txtPhone);
|
||||||
|
|
||||||
//Set up mouse handlers for buttons
|
//Set up mouse handlers for buttons
|
||||||
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package org.example.petshopdesktop.util;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.TextFormatter;
|
||||||
|
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
public class TextFieldFormatSupport {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a phone number formatter to a TextField.
|
||||||
|
* The formatter only allows digits and automatically formats the input as (XXX) XXX-XXXX.
|
||||||
|
*
|
||||||
|
* @param textField The TextField to apply the formatter to.
|
||||||
|
*/
|
||||||
|
public static void applyPhoneNumberFormat(TextField textField) {
|
||||||
|
textField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue == null) return;
|
||||||
|
|
||||||
|
// Remove all non-digit characters
|
||||||
|
String digits = newValue.replaceAll("\\D", "");
|
||||||
|
|
||||||
|
// Limit to 10 digits
|
||||||
|
if (digits.length() > 10) {
|
||||||
|
digits = digits.substring(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder formatted = new StringBuilder();
|
||||||
|
int len = digits.length();
|
||||||
|
|
||||||
|
if (len > 0) {
|
||||||
|
formatted.append("(");
|
||||||
|
if (len <= 3) {
|
||||||
|
formatted.append(digits);
|
||||||
|
} else {
|
||||||
|
formatted.append(digits, 0, 3).append(") ");
|
||||||
|
if (len <= 6) {
|
||||||
|
formatted.append(digits.substring(3));
|
||||||
|
} else {
|
||||||
|
formatted.append(digits, 3, 6).append("-").append(digits.substring(6));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = formatted.toString();
|
||||||
|
if (!result.equals(newValue)) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
textField.setText(result);
|
||||||
|
textField.positionCaret(result.length());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
<Font name="System Bold" size="16.0" />
|
<Font name="System Bold" size="16.0" />
|
||||||
</font>
|
</font>
|
||||||
</Label>
|
</Label>
|
||||||
<ComboBox fx:id="cbPetSpecies" editable="true" prefHeight="29.0" prefWidth="336.0" promptText="Select or enter species" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
|
<ComboBox fx:id="cbPetSpecies" prefHeight="29.0" prefWidth="336.0" promptText="Select Species" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
|
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
|
||||||
</padding>
|
</padding>
|
||||||
@@ -105,11 +105,11 @@
|
|||||||
<Font name="System Bold" size="16.0" />
|
<Font name="System Bold" size="16.0" />
|
||||||
</font>
|
</font>
|
||||||
</Label>
|
</Label>
|
||||||
<TextField fx:id="txtPetBreed" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10;">
|
<ComboBox fx:id="cbPetBreed" prefHeight="29.0" prefWidth="336.0" promptText="Select Species first" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="7.0" left="10.0" right="10.0" top="7.0" />
|
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
|
||||||
</padding>
|
</padding>
|
||||||
</TextField>
|
</ComboBox>
|
||||||
</children>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||||
|
|||||||
@@ -176,6 +176,14 @@
|
|||||||
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||||
</padding>
|
</padding>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button fx:id="btnCustomers" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnCustomersClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Customers" textFill="#cbd5e1">
|
||||||
|
<font>
|
||||||
|
<Font name="System" size="12.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Separator fx:id="separatorAdmin" prefWidth="200.0" style="-fx-background-color: #444444; -fx-opacity: 0.35;" />
|
<Separator fx:id="separatorAdmin" prefWidth="200.0" style="-fx-background-color: #444444; -fx-opacity: 0.35;" />
|
||||||
|
|
||||||
@@ -220,7 +228,7 @@
|
|||||||
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||||
</padding>
|
</padding>
|
||||||
</Button>
|
</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="User 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>
|
||||||
<Font name="System" size="12.0" />
|
<Font name="System" size="12.0" />
|
||||||
</font>
|
</font>
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?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.control.TextField?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
|
<VBox spacing="16.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.CustomerAccountsController">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</padding>
|
||||||
|
|
||||||
|
<children>
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="20.0">
|
||||||
|
<children>
|
||||||
|
<Label text="Customers" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="30.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
|
<Button fx:id="btnRefresh" mnemonicParsing="false" onAction="#btnRefreshClicked" prefHeight="44.0" prefWidth="118.0" style="-fx-background-color: #4ECDC4; -fx-cursor: hand; -fx-background-radius: 8;" text="Refresh" textFill="WHITE">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<VBox spacing="10.0" VBox.vgrow="ALWAYS">
|
||||||
|
<children>
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="12.0">
|
||||||
|
<children>
|
||||||
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
|
<Button fx:id="btnEditCustomer" mnemonicParsing="false" onAction="#btnEditCustomerClicked" prefHeight="40.0" style="-fx-background-color: #F4A261; -fx-cursor: hand; -fx-background-radius: 8;" text="Edit Customer" textFill="WHITE">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="20.0" right="20.0" top="10.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 1; -fx-border-radius: 14; -fx-border-color: #e6e6e6;">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="15.0" right="15.0" top="10.0" />
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<TextField fx:id="txtSearchCustomer" promptText="Search customers..." style="-fx-border-width: 0; -fx-background-color: transparent;" HBox.hgrow="ALWAYS">
|
||||||
|
<font>
|
||||||
|
<Font size="15.0" />
|
||||||
|
</font>
|
||||||
|
</TextField>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<TableView fx:id="tvCustomers" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
||||||
|
<columns>
|
||||||
|
<TableColumn fx:id="colCustomerUsername" prefWidth="130.0" text="Username" />
|
||||||
|
<TableColumn fx:id="colCustomerName" prefWidth="160.0" text="Name" />
|
||||||
|
<TableColumn fx:id="colCustomerEmail" prefWidth="200.0" text="Email" />
|
||||||
|
<TableColumn fx:id="colCustomerPhone" prefWidth="130.0" text="Phone" />
|
||||||
|
<TableColumn fx:id="colCustomerLoyaltyPoints" prefWidth="120.0" text="Loyalty Points" />
|
||||||
|
<TableColumn fx:id="colCustomerStatus" prefWidth="90.0" text="Status" />
|
||||||
|
<TableColumn fx:id="colCustomerCreated" prefWidth="150.0" text="Created" />
|
||||||
|
</columns>
|
||||||
|
</TableView>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<Label fx:id="lblStatus" text="" textFill="#64748b" visible="false" managed="true">
|
||||||
|
<font>
|
||||||
|
<Font size="13.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="lblError" text="" textFill="#FF6B6B" wrapText="true" />
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
</HBox>
|
</HBox>
|
||||||
</children>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
<FlowPane hgap="8.0" maxWidth="Infinity" prefWrapLength="260.0" vgap="8.0">
|
<HBox alignment="CENTER_RIGHT" spacing="8.0">
|
||||||
<children>
|
<children>
|
||||||
<Button fx:id="btnRefund" mnemonicParsing="false" onAction="#btnRefund" prefHeight="32.0" style="-fx-background-color: #FF6b6b; -fx-cursor: hand; -fx-background-radius: 8;" text="Process Refund" textFill="WHITE">
|
<Button fx:id="btnRefund" mnemonicParsing="false" onAction="#btnRefund" prefHeight="32.0" style="-fx-background-color: #FF6b6b; -fx-cursor: hand; -fx-background-radius: 8;" text="Process Refund" textFill="WHITE">
|
||||||
<font>
|
<font>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
</padding>
|
</padding>
|
||||||
</Button>
|
</Button>
|
||||||
</children>
|
</children>
|
||||||
</FlowPane>
|
</HBox>
|
||||||
</children>
|
</children>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import javafx.scene.text.Font?>
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
<VBox spacing="20.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.StaffAccountsController">
|
<VBox spacing="16.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.StaffAccountsController">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
</padding>
|
</padding>
|
||||||
@@ -19,28 +19,12 @@
|
|||||||
<children>
|
<children>
|
||||||
<HBox alignment="CENTER_LEFT" spacing="20.0">
|
<HBox alignment="CENTER_LEFT" spacing="20.0">
|
||||||
<children>
|
<children>
|
||||||
<Label text="User Accounts" textFill="#2c3e50">
|
<Label text="Staff Accounts" textFill="#2c3e50">
|
||||||
<font>
|
<font>
|
||||||
<Font name="System Bold" size="30.0" />
|
<Font name="System Bold" size="30.0" />
|
||||||
</font>
|
</font>
|
||||||
</Label>
|
</Label>
|
||||||
<Region HBox.hgrow="ALWAYS" />
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
<Button fx:id="btnCreateAccount" mnemonicParsing="false" onAction="#btnCreateAccountClicked" prefHeight="44.0" style="-fx-background-color: #FF6B6B; -fx-cursor: hand; -fx-background-radius: 8;" text="Create Account" textFill="WHITE">
|
|
||||||
<font>
|
|
||||||
<Font name="System Bold" size="14.0" />
|
|
||||||
</font>
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
|
|
||||||
</padding>
|
|
||||||
</Button>
|
|
||||||
<Button fx:id="btnEditAccount" mnemonicParsing="false" onAction="#btnEditAccountClicked" prefHeight="44.0" style="-fx-background-color: #F4A261; -fx-cursor: hand; -fx-background-radius: 8;" text="Edit Account" textFill="WHITE">
|
|
||||||
<font>
|
|
||||||
<Font name="System Bold" size="14.0" />
|
|
||||||
</font>
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
|
|
||||||
</padding>
|
|
||||||
</Button>
|
|
||||||
<Button fx:id="btnRefresh" mnemonicParsing="false" onAction="#btnRefreshClicked" prefHeight="44.0" prefWidth="118.0" style="-fx-background-color: #4ECDC4; -fx-cursor: hand; -fx-background-radius: 8;" text="Refresh" textFill="WHITE">
|
<Button fx:id="btnRefresh" mnemonicParsing="false" onAction="#btnRefreshClicked" prefHeight="44.0" prefWidth="118.0" style="-fx-background-color: #4ECDC4; -fx-cursor: hand; -fx-background-radius: 8;" text="Refresh" textFill="WHITE">
|
||||||
<font>
|
<font>
|
||||||
<Font name="System Bold" size="14.0" />
|
<Font name="System Bold" size="14.0" />
|
||||||
@@ -52,31 +36,56 @@
|
|||||||
</children>
|
</children>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<HBox alignment="CENTER_LEFT" spacing="10.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 1; -fx-border-radius: 14; -fx-border-color: #e6e6e6;">
|
<VBox fx:id="staffSection" spacing="10.0" VBox.vgrow="ALWAYS">
|
||||||
<padding>
|
|
||||||
<Insets bottom="10.0" left="15.0" right="15.0" top="10.0" />
|
|
||||||
</padding>
|
|
||||||
<children>
|
<children>
|
||||||
<TextField fx:id="txtSearch" promptText="Search users..." style="-fx-border-width: 0; -fx-background-color: transparent;" HBox.hgrow="ALWAYS">
|
<HBox alignment="CENTER_LEFT" spacing="12.0">
|
||||||
<font>
|
<children>
|
||||||
<Font size="15.0" />
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
</font>
|
<Button fx:id="btnCreateAccount" mnemonicParsing="false" onAction="#btnCreateAccountClicked" prefHeight="40.0" style="-fx-background-color: #FF6B6B; -fx-cursor: hand; -fx-background-radius: 8;" text="Create Account" textFill="WHITE">
|
||||||
</TextField>
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="20.0" right="20.0" top="10.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
<Button fx:id="btnEditAccount" mnemonicParsing="false" onAction="#btnEditAccountClicked" prefHeight="40.0" style="-fx-background-color: #F4A261; -fx-cursor: hand; -fx-background-radius: 8;" text="Edit Account" textFill="WHITE">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="20.0" right="20.0" top="10.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 1; -fx-border-radius: 14; -fx-border-color: #e6e6e6;">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="15.0" right="15.0" top="10.0" />
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<TextField fx:id="txtSearch" promptText="Search staff..." style="-fx-border-width: 0; -fx-background-color: transparent;" HBox.hgrow="ALWAYS">
|
||||||
|
<font>
|
||||||
|
<Font size="15.0" />
|
||||||
|
</font>
|
||||||
|
</TextField>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<TableView fx:id="tvStaff" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
||||||
|
<columns>
|
||||||
|
<TableColumn fx:id="colUsername" prefWidth="140.0" text="Username" />
|
||||||
|
<TableColumn fx:id="colName" prefWidth="170.0" text="Name" />
|
||||||
|
<TableColumn fx:id="colEmail" prefWidth="210.0" text="Email" />
|
||||||
|
<TableColumn fx:id="colPhone" prefWidth="130.0" text="Phone" />
|
||||||
|
<TableColumn fx:id="colRole" prefWidth="100.0" text="Role" />
|
||||||
|
<TableColumn fx:id="colStatus" prefWidth="90.0" text="Status" />
|
||||||
|
<TableColumn fx:id="colCreated" prefWidth="150.0" text="Created" />
|
||||||
|
</columns>
|
||||||
|
</TableView>
|
||||||
</children>
|
</children>
|
||||||
</HBox>
|
</VBox>
|
||||||
|
|
||||||
<TableView fx:id="tvStaff" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
|
||||||
<columns>
|
|
||||||
<TableColumn fx:id="colUsername" prefWidth="140.0" text="Username" />
|
|
||||||
<TableColumn fx:id="colName" prefWidth="170.0" text="Name" />
|
|
||||||
<TableColumn fx:id="colEmail" prefWidth="210.0" text="Email" />
|
|
||||||
<TableColumn fx:id="colPhone" prefWidth="130.0" text="Phone" />
|
|
||||||
<TableColumn fx:id="colRole" prefWidth="100.0" text="Role" />
|
|
||||||
<TableColumn fx:id="colStatus" prefWidth="90.0" text="Status" />
|
|
||||||
<TableColumn fx:id="colCreated" prefWidth="150.0" text="Created" />
|
|
||||||
</columns>
|
|
||||||
</TableView>
|
|
||||||
|
|
||||||
|
|
||||||
<Label fx:id="lblStatus" text="" textFill="#64748b" visible="false" managed="true">
|
<Label fx:id="lblStatus" text="" textFill="#64748b" visible="false" managed="true">
|
||||||
<font>
|
<font>
|
||||||
|
|||||||
Reference in New Issue
Block a user