created viewmodels for detailFragments

This commit is contained in:
Alex
2026-04-09 14:17:51 -06:00
parent 67cb178f46
commit 8559a46cb9
23 changed files with 1359 additions and 1129 deletions

View File

@@ -16,11 +16,7 @@ import com.example.petstoremobile.utils.DialogUtils;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.AdoptionViewModel;
import com.example.petstoremobile.viewmodels.CustomerViewModel;
import com.example.petstoremobile.viewmodels.PetViewModel;
import com.example.petstoremobile.viewmodels.StoreViewModel;
import com.example.petstoremobile.viewmodels.UserViewModel;
import com.example.petstoremobile.viewmodels.AdoptionDetailViewModel;
import java.math.BigDecimal;
import java.util.*;
@@ -34,35 +30,19 @@ import dagger.hilt.android.AndroidEntryPoint;
public class AdoptionDetailFragment extends Fragment {
private FragmentAdoptionDetailBinding binding;
private AdoptionDetailViewModel viewModel;
private long adoptionId = -1;
private boolean isEditing = false;
private long preselectedPetId = -1;
private long preselectedCustomerId = -1;
private long preselectedStoreId = -1;
private long preselectedEmployeeId = -1;
private List<DropdownDTO> petList = new ArrayList<>();
private List<DropdownDTO> customerList = new ArrayList<>();
private List<DropdownDTO> storeList = new ArrayList<>();
private List<DropdownDTO> employeeList = new ArrayList<>();
private final String[] STATUSES = {"Pending", "Completed", "Cancelled"};
private AdoptionViewModel adoptionViewModel;
private PetViewModel petViewModel;
private CustomerViewModel customerViewModel;
private StoreViewModel storeViewModel;
private UserViewModel userViewModel;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
adoptionViewModel = new ViewModelProvider(this).get(AdoptionViewModel.class);
petViewModel = new ViewModelProvider(this).get(PetViewModel.class);
customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class);
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
viewModel = new ViewModelProvider(this).get(AdoptionDetailViewModel.class);
}
@Override
@@ -77,6 +57,7 @@ public class AdoptionDetailFragment extends Fragment {
super.onViewCreated(view, savedInstanceState);
setupSpinners();
setupDatePicker();
observeViewModel();
loadSpinnersData();
handleArguments();
@@ -85,29 +66,31 @@ public class AdoptionDetailFragment extends Fragment {
binding.btnDeleteAdoption.setOnClickListener(v -> confirmDelete());
}
private void observeViewModel() {
viewModel.getPetList().observe(getViewLifecycleOwner(), list -> refreshPetSpinner());
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> refreshCustomerSpinner());
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner());
viewModel.getEmployeeList().observe(getViewLifecycleOwner(), list -> refreshEmployeeSpinner());
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/**
* Configures the spinner for adoption status.
*/
private void setupSpinners() {
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, STATUSES);
// Pet spinner disabled by default until customer is selected
UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet);
// Listener to enable pet spinner based on customer selection
binding.spinnerAdoptionCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position > 0) {
UIUtils.setViewsEnabled(true, binding.spinnerAdoptionPet);
} else {
if (!isEditing) {
if (!viewModel.isEditing()) {
binding.spinnerAdoptionPet.setSelection(0);
UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet);
}
@@ -117,27 +100,21 @@ public class AdoptionDetailFragment extends Fragment {
public void onNothingSelected(AdapterView<?> parent) {}
});
// Listener to load employees based on selected store
binding.spinnerAdoptionStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position > 0 && position <= storeList.size()) {
DropdownDTO selectedStore = storeList.get(position - 1);
if (position > 0 && viewModel.getStoreList().getValue() != null && position <= viewModel.getStoreList().getValue().size()) {
DropdownDTO selectedStore = viewModel.getStoreList().getValue().get(position - 1);
loadEmployees(selectedStore.getId());
} else {
employeeList.clear();
refreshEmployeeSpinner();
viewModel.setEmployeeList(new ArrayList<>());
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
});
}
/**
* Configures the date picker dialog for the adoption date field.
*/
private void setupDatePicker() {
binding.etAdoptionDate.setOnClickListener(v -> {
Calendar c = Calendar.getInstance();
@@ -150,129 +127,77 @@ public class AdoptionDetailFragment extends Fragment {
});
}
/**
* Fetches required data for spinners from the backend.
*/
private void loadSpinnersData() {
loadPets();
loadCustomers();
loadStores();
}
/**
* Loads the list of pets from the API.
*/
private void loadPets() {
petViewModel.getAdoptionPets().observe(getViewLifecycleOwner(), resource -> {
viewModel.loadPets().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
petList = resource.data;
refreshPetSpinner();
viewModel.setPetList(resource.data);
}
});
viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
viewModel.setCustomerList(resource.data);
}
});
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
viewModel.setStoreList(resource.data);
}
});
}
/**
* Populates the pet selection spinner with data.
*/
private void refreshPetSpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionPet, petList,
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionPet, viewModel.getPetList().getValue(),
DropdownDTO::getLabel, "-- Select Pet --",
preselectedPetId, DropdownDTO::getId);
}
/**
* Loads the list of customers from the API.
*/
private void loadCustomers() {
customerViewModel.getCustomerDropdowns().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
customerList = resource.data;
refreshCustomerSpinner();
}
});
}
/**
* Populates the customer selection spinner with data.
*/
private void refreshCustomerSpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionCustomer, customerList,
DropdownDTO::getLabel,
"-- Select Customer --",
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionCustomer, viewModel.getCustomerList().getValue(),
DropdownDTO::getLabel, "-- Select Customer --",
preselectedCustomerId, DropdownDTO::getId);
}
/**
* Loads the list of stores from the API.
*/
private void loadStores() {
storeViewModel.getStoreDropdowns().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
storeList = resource.data;
refreshStoreSpinner();
}
});
}
/**
* Populates the store selection spinner with data.
*/
private void refreshStoreSpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionStore, storeList,
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionStore, viewModel.getStoreList().getValue(),
DropdownDTO::getLabel, "-- Select Store --",
preselectedStoreId, DropdownDTO::getId);
}
/**
* Loads the list of employees for a specific store.
*/
private void loadEmployees(Long storeId) {
storeViewModel.getStoreEmployees(storeId).observe(getViewLifecycleOwner(), resource -> {
viewModel.loadEmployees(storeId).observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
employeeList = resource.data;
refreshEmployeeSpinner();
viewModel.setEmployeeList(resource.data);
}
});
}
/**
* Populates the employee selection spinner with data.
*/
private void refreshEmployeeSpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee, employeeList,
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee, viewModel.getEmployeeList().getValue(),
DropdownDTO::getLabel, "-- Select Staff --",
preselectedEmployeeId, DropdownDTO::getId);
}
/**
* Handles arguments to determine if the fragment is in edit or add mode.
*/
private void handleArguments() {
Bundle a = getArguments();
if (a != null && a.containsKey("adoptionId")) {
isEditing = true;
adoptionId = a.getLong("adoptionId");
long adoptionId = a.getLong("adoptionId");
viewModel.setAdoptionId(adoptionId);
binding.tvAdoptionMode.setText("Edit Adoption");
binding.tvAdoptionId.setText("ID: " + adoptionId);
binding.tvAdoptionId.setVisibility(View.VISIBLE);
binding.btnDeleteAdoption.setVisibility(View.VISIBLE);
loadAdoptionData();
} else {
isEditing = false;
viewModel.setAdoptionId(-1);
binding.tvAdoptionMode.setText("Add Adoption");
binding.btnDeleteAdoption.setVisibility(View.GONE);
binding.tvAdoptionId.setVisibility(View.GONE);
// Explicitly disable in add mode
UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet);
}
}
/**
* Fetches specific adoption details from the backend using the ID.
*/
private void loadAdoptionData() {
adoptionViewModel.getAdoptionById(adoptionId).observe(getViewLifecycleOwner(), resource -> {
viewModel.loadAdoption().observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
AdoptionDTO a = resource.data;
@@ -289,7 +214,6 @@ public class AdoptionDetailFragment extends Fragment {
refreshCustomerSpinner();
refreshStoreSpinner();
// In edit mode, if a customer is already set, ensure pet spinner is enabled
if (preselectedCustomerId != -1) {
UIUtils.setViewsEnabled(true, binding.spinnerAdoptionPet);
}
@@ -299,9 +223,6 @@ public class AdoptionDetailFragment extends Fragment {
});
}
/**
* Validates input and saves the adoption request to the backend.
*/
private void saveAdoption() {
if (binding.spinnerAdoptionCustomer.getSelectedItemPosition() == 0) {
Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return;
@@ -328,13 +249,13 @@ public class AdoptionDetailFragment extends Fragment {
}
}
DropdownDTO customer = customerList.get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1);
DropdownDTO pet = petList.get(binding.spinnerAdoptionPet.getSelectedItemPosition() - 1);
DropdownDTO store = storeList.get(binding.spinnerAdoptionStore.getSelectedItemPosition() - 1);
DropdownDTO customer = viewModel.getCustomerList().getValue().get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1);
DropdownDTO pet = viewModel.getPetList().getValue().get(binding.spinnerAdoptionPet.getSelectedItemPosition() - 1);
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerAdoptionStore.getSelectedItemPosition() - 1);
Long employeeId = null;
if (binding.spinnerAdoptionEmployee.getSelectedItemPosition() > 0) {
employeeId = employeeList.get(binding.spinnerAdoptionEmployee.getSelectedItemPosition() - 1).getId();
employeeId = viewModel.getEmployeeList().getValue().get(binding.spinnerAdoptionEmployee.getSelectedItemPosition() - 1).getId();
}
String status = STATUSES[binding.spinnerAdoptionStatus.getSelectedItemPosition()];
@@ -349,33 +270,19 @@ public class AdoptionDetailFragment extends Fragment {
fee
);
if (isEditing) {
adoptionViewModel.updateAdoption(adoptionId, dto).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), "Updated", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
} else {
adoptionViewModel.createAdoption(dto).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), "Saved", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
}
viewModel.saveAdoption(dto).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), viewModel.isEditing() ? "Updated" : "Saved", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
}
/**
* Shows a confirmation dialog before deleting an adoption request.
*/
private void confirmDelete() {
DialogUtils.showDeleteConfirmDialog(requireContext(), "Adoption", () ->
adoptionViewModel.deleteAdoption(adoptionId).observe(getViewLifecycleOwner(), resource -> {
viewModel.deleteAdoption().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), "Deleted", Toast.LENGTH_SHORT).show();
navigateBack();
@@ -385,9 +292,6 @@ public class AdoptionDetailFragment extends Fragment {
}));
}
/**
* Navigates back to the previous fragment.
*/
private void navigateBack() {
NavHostFragment.findNavController(this).popBackStack();
}

View File

@@ -43,37 +43,28 @@ public class AppointmentDetailFragment extends Fragment {
private final Integer[] HOURS = {9, 10, 11, 12, 13, 14, 15, 16, 17};
private final Integer[] MINUTES = {0, 15, 30, 45};
private AppointmentDetailViewModel appointmentViewModel;
private AppointmentDetailViewModel viewModel;
private boolean isUpdatingUI = false;
/**
* Called when the fragment is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appointmentViewModel = new ViewModelProvider(this).get(AppointmentDetailViewModel.class);
viewModel = new ViewModelProvider(this).get(AppointmentDetailViewModel.class);
}
/**
* Creates and returns the view hierarchy with the fragment.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentAppointmentDetailBinding.inflate(inflater, container, false);
return binding.getRoot();
}
/**
* Called immediately after onCreateView has returned.
*/
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setupSpinners();
setupDatePicker();
observeViewModel();
appointmentViewModel.loadInitialFormData();
viewModel.loadInitialFormData();
handleArguments();
binding.btnApptBack.setOnClickListener(v -> navigateBack());
@@ -81,110 +72,83 @@ public class AppointmentDetailFragment extends Fragment {
binding.btnDeleteAppointment.setOnClickListener(v -> confirmDelete());
}
/**
* Called when the view previously created by onCreateView has been detached.
*/
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/**
* Configures the adapters and listeners for all spinners.
*/
private void setupSpinners() {
//Status Spinner is empty by default the date determines whats in here
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, new String[]{});
// Set up hour and minute spinners
String[] hours = new String[HOURS.length];
for (int i = 0; i < HOURS.length; i++)
hours[i] = DateTimeUtils.formatTime(HOURS[i], 0);
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerHour, hours);
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerMinute, new String[]{"00", "15", "30", "45"});
// Pet and Staff spinners disabled by until parent selection
UIUtils.setViewsEnabled(false, binding.spinnerPet, binding.spinnerStaff);
// Listener to notify ViewModel of customer selection
binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
appointmentViewModel.onCustomerSelected(position);
viewModel.onCustomerSelected(position);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
});
// Listener to notify ViewModel of store selection
binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
appointmentViewModel.onStoreSelected(position);
viewModel.onStoreSelected(position);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
});
// Listeners for other selections
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerService, p -> appointmentViewModel.onServiceSelected(p));
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerPet, p -> appointmentViewModel.onPetSelected(p));
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerStaff, p -> appointmentViewModel.onStaffSelected(p));
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerService, p -> viewModel.onServiceSelected(p));
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerPet, p -> viewModel.onPetSelected(p));
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerStaff, p -> viewModel.onStaffSelected(p));
// Listeners for time changes
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerHour, p -> notifyDateTimeStatusChange());
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerMinute, p -> notifyDateTimeStatusChange());
// Listener to notify ViewModel of status selection
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAppointmentStatus, p -> notifyDateTimeStatusChange());
}
/**
* Configures the date picker dialog for the appointment date field.
*/
private void setupDatePicker() {
binding.etAppointmentDate.setOnClickListener(v ->
UIUtils.showDatePicker(requireContext(), binding.etAppointmentDate, this::notifyDateTimeStatusChange));
}
/**
* Observes the ViewModel for UI state and list updates.
*/
private void observeViewModel() {
appointmentViewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
// Populate spinners when data arrives
appointmentViewModel.getCustomers().observe(getViewLifecycleOwner(), list ->
viewModel.getCustomers().observe(getViewLifecycleOwner(), list ->
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerCustomer, list, DropdownDTO::getLabel, "-- Select Customer --", preselectedCustomerId, DropdownDTO::getId));
appointmentViewModel.getStores().observe(getViewLifecycleOwner(), list ->
viewModel.getStores().observe(getViewLifecycleOwner(), list ->
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStore, list, DropdownDTO::getLabel, "-- Select Store --", preselectedStoreId, DropdownDTO::getId));
appointmentViewModel.getServices().observe(getViewLifecycleOwner(), list ->
viewModel.getServices().observe(getViewLifecycleOwner(), list ->
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerService, list, ServiceDTO::getServiceName, "-- Select Service --", preselectedServiceId, ServiceDTO::getServiceId));
appointmentViewModel.getCustomerPets().observe(getViewLifecycleOwner(), list ->
viewModel.getCustomerPets().observe(getViewLifecycleOwner(), list ->
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPet, list, DropdownDTO::getLabel, "-- Select Pet --", preselectedPetId, DropdownDTO::getId));
appointmentViewModel.getStoreEmployees().observe(getViewLifecycleOwner(), list ->
viewModel.getStoreEmployees().observe(getViewLifecycleOwner(), list ->
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff, list, DropdownDTO::getLabel, "-- Select Staff --", preselectedStaffId, DropdownDTO::getId));
}
/**
* Applies the ViewState provided by the ViewModel to the UI components.
*/
private void applyViewState(AppointmentDetailViewModel.ViewState state) {
isUpdatingUI = true;
// Mode specific UI
binding.tvApptMode.setText(state.isEditing ? "Edit Appointment" : "Add Appointment");
binding.tvAppointmentId.setText(DateTimeUtils.formatId(appointmentViewModel.getAppointmentId()));
binding.tvAppointmentId.setText(DateTimeUtils.formatId(viewModel.getAppointmentId()));
binding.tvAppointmentId.setVisibility(state.isEditing ? View.VISIBLE : View.GONE);
binding.btnDeleteAppointment.setVisibility(state.isDeleteVisible ? View.VISIBLE : View.GONE);
binding.btnSaveAppointment.setVisibility(state.isSaveVisible ? View.VISIBLE : View.GONE);
// Enabling/Disabling Views and Labels
UIUtils.setFieldEnabled(state.isCustomerEnabled, binding.spinnerCustomer, binding.tvLabelCustomer);
UIUtils.setFieldEnabled(state.isStoreEnabled, binding.spinnerStore, binding.tvLabelStore);
UIUtils.setFieldEnabled(state.isPetEnabled, binding.spinnerPet, binding.tvLabelPet);
@@ -195,7 +159,6 @@ public class AppointmentDetailFragment extends Fragment {
UIUtils.setViewsEnabled(state.isTimeEnabled, binding.spinnerMinute);
UIUtils.setViewsEnabled(state.isStatusEnabled, binding.spinnerAppointmentStatus);
// Update status options
Object selected = binding.spinnerAppointmentStatus.getSelectedItem();
String current = selected != null ? selected.toString() : "";
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, state.availableStatuses);
@@ -204,9 +167,6 @@ public class AppointmentDetailFragment extends Fragment {
isUpdatingUI = false;
}
/**
* Notifies the ViewModel that the date, time, or status has changed.
*/
private void notifyDateTimeStatusChange() {
if (isUpdatingUI) return;
@@ -214,27 +174,21 @@ public class AppointmentDetailFragment extends Fragment {
String time = buildTimeString();
Object selected = binding.spinnerAppointmentStatus.getSelectedItem();
String status = selected != null ? selected.toString() : "";
appointmentViewModel.onDateOrTimeChanged(date, time, status);
viewModel.onDateOrTimeChanged(date, time, status);
}
/**
* Handles arguments to determine if the fragment is in edit or add mode.
*/
private void handleArguments() {
Bundle a = getArguments();
if (a != null && a.containsKey("appointmentId")) {
appointmentViewModel.setAppointmentId(a.getLong("appointmentId"));
viewModel.setAppointmentId(a.getLong("appointmentId"));
loadAppointmentData();
} else {
appointmentViewModel.setAppointmentId(-1);
viewModel.setAppointmentId(-1);
}
}
/**
* Fetches specific appointment details from the backend using the ID.
*/
private void loadAppointmentData() {
appointmentViewModel.loadAppointment().observe(getViewLifecycleOwner(), resource -> {
viewModel.loadAppointment().observe(getViewLifecycleOwner(), resource -> {
if (resource == null || resource.status != Resource.Status.SUCCESS || resource.data == null) return;
AppointmentDTO a = resource.data;
preselectedPetId = a.getPetId() != null ? a.getPetId() : -1;
@@ -254,9 +208,6 @@ public class AppointmentDetailFragment extends Fragment {
});
}
/**
* Validates input and saves the appointment to the backend.
*/
private void saveAppointment() {
if (!validateRequiredFields()) return;
@@ -264,14 +215,14 @@ public class AppointmentDetailFragment extends Fragment {
String time = buildTimeString();
String status = binding.spinnerAppointmentStatus.getSelectedItem().toString().toUpperCase();
if (!appointmentViewModel.isValidFutureBooking(status, date, time)) {
if (!viewModel.isValidFutureBooking(status, date, time)) {
DialogUtils.showInfoDialog(requireContext(), "Invalid Time", "Booked appointments must be in the future.");
return;
}
appointmentViewModel.saveAppointment(date, time, status).observe(getViewLifecycleOwner(), resource -> {
viewModel.saveAppointment(date, time, status).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
AppointmentDetailViewModel.ViewState state = appointmentViewModel.getViewState().getValue();
AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue();
String message = (state != null && state.isEditing) ? "Updated" : "Saved";
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
navigateBack();
@@ -281,9 +232,6 @@ public class AppointmentDetailFragment extends Fragment {
});
}
/**
* Validates that all required fields are selected.
*/
private boolean validateRequiredFields() {
if (binding.spinnerCustomer.getSelectedItemPosition() == 0) return UIUtils.showToast(getContext(), "Select a customer");
if (binding.spinnerStore.getSelectedItemPosition() == 0) return UIUtils.showToast(getContext(), "Select a store");
@@ -293,24 +241,15 @@ public class AppointmentDetailFragment extends Fragment {
return true;
}
/**
* Builds a time string from the hour and minute spinners.
*/
private String buildTimeString() {
return DateTimeUtils.formatTime(HOURS[binding.spinnerHour.getSelectedItemPosition()], MINUTES[binding.spinnerMinute.getSelectedItemPosition()]);
}
/**
* Handles errors that occur during the saving process.
*/
private void handleSaveError(String errorMessage) {
if (errorMessage != null && errorMessage.toLowerCase().contains("not available")) showNoAvailabilityDialog();
else Toast.makeText(getContext(), errorMessage != null ? errorMessage : "Error saving", Toast.LENGTH_SHORT).show();
}
/**
* Shows a specialized dialog when a time slot is not available.
*/
private void showNoAvailabilityDialog() {
new androidx.appcompat.app.AlertDialog.Builder(requireContext())
.setTitle("No Availability")
@@ -319,26 +258,17 @@ public class AppointmentDetailFragment extends Fragment {
.setNegativeButton("Cancel Booking", (d, w) -> navigateBack()).show();
}
/**
* Shows a confirmation dialog and handles the deletion of an appointment.
*/
private void confirmDelete() {
DialogUtils.showDeleteConfirmDialog(requireContext(), "Appointment", () ->
appointmentViewModel.deleteAppointment().observe(getViewLifecycleOwner(), resource -> {
viewModel.deleteAppointment().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) navigateBack();
}));
}
/**
* Navigates back to the previous screen.
*/
private void navigateBack() {
NavHostFragment.findNavController(this).popBackStack();
}
/**
* Parses a time string and sets the hour and minute spinners.
*/
private void parseAndSetTimeSpinners(String time) {
int[] parsedTime = DateTimeUtils.parseTimeString(time);
if (parsedTime == null) return;

View File

@@ -17,16 +17,10 @@ import com.example.petstoremobile.databinding.FragmentInventoryDetailBinding;
import com.example.petstoremobile.dtos.DropdownDTO;
import com.example.petstoremobile.dtos.InventoryDTO;
import com.example.petstoremobile.dtos.ProductDTO;
import com.example.petstoremobile.dtos.StoreDTO;
import com.example.petstoremobile.utils.InputValidator;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.viewmodels.InventoryViewModel;
import com.example.petstoremobile.viewmodels.ProductViewModel;
import com.example.petstoremobile.viewmodels.StoreViewModel;
import java.util.ArrayList;
import java.util.List;
import com.example.petstoremobile.viewmodels.InventoryDetailViewModel;
import dagger.hilt.android.AndroidEntryPoint;
@@ -37,33 +31,17 @@ import dagger.hilt.android.AndroidEntryPoint;
public class InventoryDetailFragment extends Fragment {
private FragmentInventoryDetailBinding binding;
private InventoryDetailViewModel viewModel;
private InventoryViewModel inventoryViewModel;
private ProductViewModel productViewModel;
private StoreViewModel storeViewModel;
private boolean isEditing = false;
private long inventoryId = -1;
private long preselectedStoreId = -1;
private long preselectedProductId = -1;
private List<DropdownDTO> storeList = new ArrayList<>();
private List<ProductDTO> productList = new ArrayList<>();
/**
* Initializes the view models.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
inventoryViewModel = new ViewModelProvider(this).get(InventoryViewModel.class);
productViewModel = new ViewModelProvider(this).get(ProductViewModel.class);
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
viewModel = new ViewModelProvider(this).get(InventoryDetailViewModel.class);
}
/**
* Inflates the layout.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -71,13 +49,11 @@ public class InventoryDetailFragment extends Fragment {
return binding.getRoot();
}
/**
* Sets up UI components after the view is created.
*/
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
observeViewModel();
loadSpinnersData();
handleArguments();
@@ -86,64 +62,47 @@ public class InventoryDetailFragment extends Fragment {
binding.btnDeleteInventory.setOnClickListener(v -> confirmDelete());
}
private void observeViewModel() {
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner());
viewModel.getProductList().observe(getViewLifecycleOwner(), list -> refreshProductSpinner());
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/**
* Fetches required data for spinners from the backend.
*/
private void loadSpinnersData() {
loadStores();
loadProducts();
}
/**
* Loads the list of stores for the spinner.
*/
private void loadStores() {
storeViewModel.getStoreDropdowns().observe(getViewLifecycleOwner(), resource -> {
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
storeList = resource.data;
refreshStoreSpinner();
viewModel.setStoreList(resource.data);
}
});
viewModel.loadProducts().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
viewModel.setProductList(resource.data.getContent());
}
});
}
private void refreshStoreSpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryStore, storeList,
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryStore, viewModel.getStoreList().getValue(),
DropdownDTO::getLabel, "-- Select Store --",
preselectedStoreId, DropdownDTO::getId);
}
/**
* Loads the list of products for the spinner.
*/
private void loadProducts() {
productViewModel.getAllProducts(null, null, 0, 500, "prodName").observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
productList = resource.data.getContent();
refreshProductSpinner();
}
});
}
private void refreshProductSpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, productList,
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, viewModel.getProductList().getValue(),
ProductDTO::getProdName, "-- Select Product --",
preselectedProductId, ProductDTO::getProdId);
}
/**
* Handles fragment arguments to determine if we are in edit or add mode.
*/
private void handleArguments() {
Bundle args = getArguments();
if (args != null && args.containsKey("inventoryId")) {
isEditing = true;
inventoryId = args.getLong("inventoryId");
long inventoryId = args.getLong("inventoryId");
viewModel.setInventoryId(inventoryId);
binding.tvInventoryMode.setText("Edit Inventory");
binding.tvInventoryId.setText("Inventory ID: " + inventoryId);
@@ -153,7 +112,7 @@ public class InventoryDetailFragment extends Fragment {
loadInventoryData();
} else {
isEditing = false;
viewModel.setInventoryId(-1);
binding.tvInventoryMode.setText("Add Inventory");
binding.tvInventoryId.setVisibility(View.GONE);
binding.btnDeleteInventory.setVisibility(View.GONE);
@@ -161,11 +120,8 @@ public class InventoryDetailFragment extends Fragment {
}
}
/**
* Loads existing inventory data from the backend.
*/
private void loadInventoryData() {
inventoryViewModel.getInventoryById(inventoryId).observe(getViewLifecycleOwner(), resource -> {
viewModel.loadInventory().observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
InventoryDTO inv = resource.data;
@@ -181,9 +137,6 @@ public class InventoryDetailFragment extends Fragment {
});
}
/**
* Validates input and saves the current inventory item details to the backend.
*/
private void saveInventory() {
if (binding.spinnerInventoryStore.getSelectedItemPosition() == 0) {
Toast.makeText(getContext(), "Please select a store", Toast.LENGTH_SHORT).show();
@@ -200,38 +153,23 @@ public class InventoryDetailFragment extends Fragment {
}
int quantity = Integer.parseInt(binding.etQuantity.getText().toString().trim());
DropdownDTO store = storeList.get(binding.spinnerInventoryStore.getSelectedItemPosition() - 1);
ProductDTO product = productList.get(binding.spinnerInventoryProduct.getSelectedItemPosition() - 1);
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerInventoryStore.getSelectedItemPosition() - 1);
ProductDTO product = viewModel.getProductList().getValue().get(binding.spinnerInventoryProduct.getSelectedItemPosition() - 1);
InventoryDTO request = new InventoryDTO(product.getProdId(), store.getId(), quantity);
setButtonsEnabled(false);
if (isEditing) {
inventoryViewModel.updateInventory(inventoryId, request).observe(getViewLifecycleOwner(), resource -> {
setButtonsEnabled(true);
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), "Inventory updated", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
} else {
inventoryViewModel.createInventory(request).observe(getViewLifecycleOwner(), resource -> {
setButtonsEnabled(true);
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), "Inventory created", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
}
viewModel.saveInventory(request).observe(getViewLifecycleOwner(), resource -> {
setButtonsEnabled(true);
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), viewModel.isEditing() ? "Inventory updated" : "Inventory created", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
}
/**
* Shows a confirmation dialog before deleting an inventory item.
*/
private void confirmDelete() {
new AlertDialog.Builder(requireContext())
.setTitle("Delete inventory item?")
@@ -241,12 +179,9 @@ public class InventoryDetailFragment extends Fragment {
.show();
}
/**
* Sends a request to the API to delete the inventory item.
*/
private void deleteInventory() {
setButtonsEnabled(false);
inventoryViewModel.deleteInventory(inventoryId).observe(getViewLifecycleOwner(), resource -> {
viewModel.deleteInventory().observe(getViewLifecycleOwner(), resource -> {
setButtonsEnabled(true);
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), "Inventory deleted", Toast.LENGTH_SHORT).show();
@@ -257,16 +192,10 @@ public class InventoryDetailFragment extends Fragment {
});
}
/**
* Navigates back to the previous fragment.
*/
private void navigateBack() {
NavHostFragment.findNavController(this).popBackStack();
}
/**
* Enables or disables action buttons.
*/
private void setButtonsEnabled(boolean enabled) {
binding.btnSaveInventory.setEnabled(enabled);
binding.btnDeleteInventory.setEnabled(enabled);

View File

@@ -26,12 +26,8 @@ import com.example.petstoremobile.utils.InputValidator;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.CustomerViewModel;
import com.example.petstoremobile.viewmodels.PetViewModel;
import com.example.petstoremobile.viewmodels.StoreViewModel;
import com.example.petstoremobile.viewmodels.PetDetailViewModel;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import dagger.hilt.android.AndroidEntryPoint;
@@ -43,23 +39,15 @@ import dagger.hilt.android.AndroidEntryPoint;
public class PetDetailFragment extends Fragment {
private FragmentPetDetailBinding binding;
private long petId;
private boolean isEditing = false;
private PetViewModel viewModel;
private CustomerViewModel customerViewModel;
private StoreViewModel storeViewModel;
private List<DropdownDTO> customerList = new ArrayList<>();
private List<DropdownDTO> storeList = new ArrayList<>();
private PetDetailViewModel viewModel;
private Long selectedCustomerId = null;
private Long selectedStoreId = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(PetViewModel.class);
customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class);
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
viewModel = new ViewModelProvider(this).get(PetDetailViewModel.class);
}
@Override
@@ -74,8 +62,7 @@ public class PetDetailFragment extends Fragment {
super.onViewCreated(view, savedInstanceState);
setupSpinner();
loadCustomers();
loadStores();
observeViewModel();
handleArguments();
//set button click listeners
@@ -84,6 +71,23 @@ public class PetDetailFragment extends Fragment {
binding.btnDeletePet.setOnClickListener(v -> deletePet());
}
private void observeViewModel() {
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> updateCustomerSpinnerSelection());
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> updateStoreSpinnerSelection());
viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
viewModel.setCustomerList(resource.data);
}
});
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
viewModel.setStoreList(resource.data);
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -113,14 +117,14 @@ public class PetDetailFragment extends Fragment {
Long customerId = null;
int customerPos = binding.spinnerCustomer.getSelectedItemPosition();
if (customerPos > 0) { // 0 means no customer for pet
customerId = customerList.get(customerPos - 1).getId();
customerId = viewModel.getCustomerList().getValue().get(customerPos - 1).getId();
}
// Get selected store
Long storeId = null;
int storePos = binding.spinnerStore.getSelectedItemPosition();
if (storePos > 0) {
storeId = storeList.get(storePos - 1).getId();
storeId = viewModel.getStoreList().getValue().get(storePos - 1).getId();
}
// Validation: If status is Available, a store must be selected
@@ -150,31 +154,20 @@ public class PetDetailFragment extends Fragment {
petDTO.setCustomerId(customerId);
petDTO.setStoreId(storeId);
//check if the pet is being edited or added
if (isEditing) {
// Update existing pet
petDTO.setPetId(petId);
viewModel.updatePet(petId, petDTO).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
ActivityLogger.logChange(requireContext(), "Pet", "UPDATED", (int) petId);
viewModel.savePet(petDTO).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
if (viewModel.isEditing()) {
ActivityLogger.logChange(requireContext(), "Pet", "UPDATED", (int) viewModel.getPetId());
Toast.makeText(getContext(), "Pet updated successfully!", Toast.LENGTH_SHORT).show();
navigateToPetList();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
} else {
// Add new pet
viewModel.createPet(petDTO).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
} else {
ActivityLogger.log(requireContext(), "Added new Pet: " + name);
Toast.makeText(getContext(), "Pet added successfully!", Toast.LENGTH_SHORT).show();
navigateToPetList();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
}
navigateToPetList();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
}
/**
@@ -182,9 +175,9 @@ public class PetDetailFragment extends Fragment {
*/
private void deletePet() {
DialogUtils.showDeleteConfirmDialog(requireContext(), "Pet", () ->
viewModel.deletePet(petId).observe(getViewLifecycleOwner(), resource -> {
viewModel.deletePet().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
ActivityLogger.logChange(requireContext(), "Pet", "DELETED", (int) petId);
ActivityLogger.logChange(requireContext(), "Pet", "DELETED", (int) viewModel.getPetId());
Toast.makeText(getContext(), "Pet deleted successfully!", Toast.LENGTH_SHORT).show();
navigateToPetList();
} else if (resource.status == Resource.Status.ERROR) {
@@ -211,30 +204,23 @@ public class PetDetailFragment extends Fragment {
* Handles arguments passed to the fragment to determine if it's in edit or add mode.
*/
private void handleArguments() {
// Pet is being edited if the bundle contains a petId
if (getArguments() != null && getArguments().containsKey("petId")) {
// Get pet data from arguments and populate fields
isEditing = true;
petId = getArguments().getLong("petId");
long petId = getArguments().getLong("petId");
viewModel.setPetId(petId);
binding.tvMode.setText("Edit Pet");
binding.tvPetId.setText("ID: " + petId);
binding.tvPetId.setVisibility(View.VISIBLE);
binding.btnDeletePet.setVisibility(View.VISIBLE);
// Disable species and breed fields in edit mode
UIUtils.setViewsEnabled(false, binding.etPetSpecies, binding.etPetBreed);
loadPetData();
} else {
// Pet is being added
// Set default values for add a new pet
isEditing = false;
viewModel.setPetId(-1);
binding.tvMode.setText("Add Pet");
binding.tvPetId.setVisibility(View.GONE);
binding.btnDeletePet.setVisibility(View.GONE);
binding.btnSavePet.setText("Add");
// Enable species and breed fields in edit mode
UIUtils.setViewsEnabled(true, binding.etPetSpecies, binding.etPetBreed);
}
}
@@ -243,7 +229,7 @@ public class PetDetailFragment extends Fragment {
* Fetches specific pet details from the backend using the ID.
*/
private void loadPetData() {
viewModel.getPetById(petId).observe(getViewLifecycleOwner(), resource -> {
viewModel.loadPet().observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
PetDTO p = resource.data;
@@ -267,30 +253,6 @@ public class PetDetailFragment extends Fragment {
});
}
/**
* Fetches the list of customers and populates the spinner.
*/
private void loadCustomers() {
customerViewModel.getCustomerDropdowns().observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
customerList = resource.data;
updateCustomerSpinnerSelection();
}
});
}
/**
* Fetches the list of stores and populates the spinner.
*/
private void loadStores() {
storeViewModel.getStoreDropdowns().observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
storeList = resource.data;
updateStoreSpinnerSelection();
}
});
}
/**
* Updates the customer spinner with the current list and sets the selection if needed.
*/
@@ -298,7 +260,7 @@ public class PetDetailFragment extends Fragment {
SpinnerUtils.populateSpinner(
requireContext(),
binding.spinnerCustomer,
customerList,
viewModel.getCustomerList().getValue(),
DropdownDTO::getLabel,
"No Owner",
selectedCustomerId,
@@ -313,7 +275,7 @@ public class PetDetailFragment extends Fragment {
SpinnerUtils.populateSpinner(
requireContext(),
binding.spinnerStore,
storeList,
viewModel.getStoreList().getValue(),
DropdownDTO::getLabel,
"None",
selectedStoreId,
@@ -333,11 +295,9 @@ public class PetDetailFragment extends Fragment {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String status = parent.getItemAtPosition(position).toString();
// Clear any existing error icons when status changes
clearSpinnerError(binding.spinnerCustomer);
clearSpinnerError(binding.spinnerStore);
//Disable the customer spinner if the status is "Available"
if ("Available".equalsIgnoreCase(status)) {
binding.spinnerCustomer.setSelection(0);
UIUtils.setViewsEnabled(false, binding.spinnerCustomer);
@@ -345,7 +305,6 @@ public class PetDetailFragment extends Fragment {
UIUtils.setViewsEnabled(true, binding.spinnerCustomer);
}
//Disable the store spinner if the status is "Owned"
if ("Owned".equalsIgnoreCase(status)) {
binding.spinnerStore.setSelection(0);
UIUtils.setViewsEnabled(false, binding.spinnerStore);
@@ -360,9 +319,6 @@ public class PetDetailFragment extends Fragment {
});
}
/**
* Clears error messages from a Spinner's selected view.
*/
private void clearSpinnerError(Spinner spinner) {
View selectedView = spinner.getSelectedView();
if (selectedView instanceof TextView) {

View File

@@ -17,7 +17,7 @@ import com.example.petstoremobile.api.*;
import com.example.petstoremobile.api.auth.TokenManager;
import com.example.petstoremobile.databinding.FragmentProductDetailBinding;
import com.example.petstoremobile.dtos.*;
import com.example.petstoremobile.viewmodels.ProductViewModel;
import com.example.petstoremobile.viewmodels.ProductDetailViewModel;
import com.example.petstoremobile.utils.DialogUtils;
import com.example.petstoremobile.utils.FileUtils;
import com.example.petstoremobile.utils.GlideUtils;
@@ -31,7 +31,6 @@ import java.math.BigDecimal;
import java.util.*;
import javax.inject.Inject;
import javax.inject.Named;
import dagger.hilt.android.AndroidEntryPoint;
@@ -46,29 +45,22 @@ import okhttp3.RequestBody;
public class ProductDetailFragment extends Fragment {
private FragmentProductDetailBinding binding;
private ProductDetailViewModel viewModel;
private ImagePickerHelper imagePickerHelper;
private long prodId = -1;
private boolean isEditing = false;
private long preselectedCategoryId = -1;
private boolean hasImage = false;
private boolean isImageChanged = false;
private boolean isImageRemoved = false;
private List<CategoryDTO> categoryList = new ArrayList<>();
private Uri photoUri;
private ProductViewModel viewModel;
private ImagePickerHelper imagePickerHelper;
@Inject @Named("baseUrl") String baseUrl;
@Inject TokenManager tokenManager;
/**
* Initializes activity launchers and the ImagePickerHelper.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(ProductViewModel.class);
viewModel = new ViewModelProvider(this).get(ProductDetailViewModel.class);
imagePickerHelper = new ImagePickerHelper(this, "product_photo.jpg", new ImagePickerHelper.ImagePickerListener() {
@Override
@@ -95,9 +87,6 @@ public class ProductDetailFragment extends Fragment {
});
}
/**
* Inflates the layout.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -105,14 +94,11 @@ public class ProductDetailFragment extends Fragment {
return binding.getRoot();
}
/**
* Sets up UI components and listeners after the view is created.
*/
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
loadCategories();
observeViewModel();
handleArguments();
binding.btnProductBack.setOnClickListener(v -> navigateBack());
@@ -121,34 +107,33 @@ public class ProductDetailFragment extends Fragment {
binding.ivProductImage.setOnClickListener(v -> imagePickerHelper.showImagePickerDialog("Select Product Image", hasImage));
}
private void observeViewModel() {
viewModel.getCategoryList().observe(getViewLifecycleOwner(), list -> updateCategorySpinner());
viewModel.loadCategories().observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
viewModel.setCategoryList(resource.data.getContent());
}
});
}
private void updateCategorySpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, viewModel.getCategoryList().getValue(),
CategoryDTO::getCategoryName, "-- Select Category --",
preselectedCategoryId, CategoryDTO::getCategoryId);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/**
* Fetches all product categories for the selection spinner.
*/
private void loadCategories() {
viewModel.getAllCategories(0, 100).observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
categoryList = resource.data.getContent();
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, categoryList,
CategoryDTO::getCategoryName, "-- Select Category --",
preselectedCategoryId, CategoryDTO::getCategoryId);
}
});
}
/**
* Checks if the fragment was opened with existing product data for editing.
*/
private void handleArguments() {
Bundle a = getArguments();
if (a != null && a.containsKey("prodId")) {
isEditing = true;
prodId = a.getLong("prodId");
long prodId = a.getLong("prodId");
viewModel.setProdId(prodId);
binding.tvProductMode.setText("Edit Product");
binding.tvProductId.setText("ID: " + prodId);
binding.tvProductId.setVisibility(View.VISIBLE);
@@ -156,6 +141,7 @@ public class ProductDetailFragment extends Fragment {
loadProductData();
loadProductImage();
} else {
viewModel.setProdId(-1);
binding.tvProductMode.setText("Add Product");
binding.btnDeleteProduct.setVisibility(View.GONE);
binding.tvProductId.setVisibility(View.GONE);
@@ -163,11 +149,8 @@ public class ProductDetailFragment extends Fragment {
}
}
/**
* Loads the product data from the backend.
*/
private void loadProductData() {
viewModel.getProductById(prodId).observe(getViewLifecycleOwner(), resource -> {
viewModel.loadProduct().observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
ProductDTO p = resource.data;
@@ -175,24 +158,15 @@ public class ProductDetailFragment extends Fragment {
binding.etProductDesc.setText(p.getProdDesc());
binding.etProductPrice.setText(p.getProdPrice() != null ? p.getProdPrice().toString() : "");
preselectedCategoryId = p.getCategoryId() != null ? p.getCategoryId() : -1;
// Refresh spinner selection once data is loaded
if (!categoryList.isEmpty()) {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, categoryList,
CategoryDTO::getCategoryName, "-- Select Category --",
preselectedCategoryId, CategoryDTO::getCategoryId);
}
updateCategorySpinner();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Failed to load product: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
}
/**
* Loads the product image from the backend.
*/
private void loadProductImage() {
String imageUrl = baseUrl + String.format(Locale.US, ProductApi.PRODUCT_IMAGE_PATH, prodId);
String imageUrl = baseUrl + String.format(Locale.US, ProductApi.PRODUCT_IMAGE_PATH, viewModel.getProdId());
String token = tokenManager.getToken();
GlideUtils.loadImageWithToken(requireContext(), binding.ivProductImage, imageUrl, token, R.drawable.placeholder2, new GlideUtils.ImageLoadListener() {
@@ -208,12 +182,9 @@ public class ProductDetailFragment extends Fragment {
});
}
/**
* Performs image related actions (upload/delete) after product details are saved.
*/
private void performPendingImageActions(String successMsg) {
if (isImageRemoved) {
viewModel.deleteProductImage(prodId).observe(getViewLifecycleOwner(), resource -> {
viewModel.deleteProductImage().observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status != Resource.Status.LOADING) {
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show();
@@ -231,9 +202,6 @@ public class ProductDetailFragment extends Fragment {
}
}
/**
* Uploads the selected image file to the server.
*/
private void uploadProductImageAndNavigate(Uri uri, String successMsg) {
File file = FileUtils.getFileFromUri(requireContext(), uri);
if (file == null) {
@@ -245,7 +213,7 @@ public class ProductDetailFragment extends Fragment {
RequestBody requestFile = RequestBody.create(file, MediaType.parse(requireContext().getContentResolver().getType(uri)));
MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
viewModel.uploadProductImage(prodId, body).observe(getViewLifecycleOwner(), resource -> {
viewModel.uploadProductImage(body).observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status != Resource.Status.LOADING) {
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show();
@@ -257,9 +225,6 @@ public class ProductDetailFragment extends Fragment {
});
}
/**
* Validates input fields and saves product information to the backend.
*/
private void saveProduct() {
if (!InputValidator.isNotEmpty(binding.etProductName, "Product Name")) return;
@@ -276,39 +241,26 @@ public class ProductDetailFragment extends Fragment {
String desc = binding.etProductDesc.getText().toString().trim();
BigDecimal price = new BigDecimal(binding.etProductPrice.getText().toString().trim());
CategoryDTO category = categoryList.get(binding.spinnerProductCategory.getSelectedItemPosition() - 1);
CategoryDTO category = viewModel.getCategoryList().getValue().get(binding.spinnerProductCategory.getSelectedItemPosition() - 1);
ProductDTO dto = new ProductDTO(name, category.getCategoryId(), desc, price);
if (isEditing) {
viewModel.updateProduct(prodId, dto).observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status != Resource.Status.LOADING) {
if (resource.status == Resource.Status.SUCCESS) {
performPendingImageActions("Updated");
} else {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
viewModel.saveProduct(dto).observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status != Resource.Status.LOADING) {
if (resource.status == Resource.Status.SUCCESS) {
if (resource.data != null) {
viewModel.setProdId(resource.data.getProdId());
}
performPendingImageActions(viewModel.isEditing() ? "Updated" : "Saved");
} else {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
} else {
viewModel.createProduct(dto).observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status != Resource.Status.LOADING) {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
prodId = resource.data.getProdId();
performPendingImageActions("Saved");
} else {
Toast.makeText(getContext(), "Error saving: " + resource.message, Toast.LENGTH_SHORT).show();
}
}
});
}
}
});
}
/**
* Displays a confirmation dialog before deleting the product.
*/
private void confirmDelete() {
DialogUtils.showDeleteConfirmDialog(requireContext(), "Product", () ->
viewModel.deleteProduct(prodId).observe(getViewLifecycleOwner(), resource -> {
viewModel.deleteProduct().observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS) {
navigateBack();
} else if (resource != null && resource.status == Resource.Status.ERROR) {
@@ -317,9 +269,6 @@ public class ProductDetailFragment extends Fragment {
}));
}
/**
* Navigates back to the previous fragment.
*/
private void navigateBack() {
NavHostFragment.findNavController(this).popBackStack();
}

View File

@@ -15,9 +15,7 @@ import com.example.petstoremobile.utils.DialogUtils;
import com.example.petstoremobile.utils.InputValidator;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.viewmodels.ProductSupplierViewModel;
import com.example.petstoremobile.viewmodels.ProductViewModel;
import com.example.petstoremobile.viewmodels.SupplierViewModel;
import com.example.petstoremobile.viewmodels.ProductSupplierDetailViewModel;
import java.math.BigDecimal;
import java.util.*;
@@ -31,26 +29,15 @@ import dagger.hilt.android.AndroidEntryPoint;
public class ProductSupplierDetailFragment extends Fragment {
private FragmentProductSupplierDetailBinding binding;
private ProductSupplierDetailViewModel viewModel;
private boolean isEditing = false;
private long editProductId = -1;
private long editSupplierId = -1;
private long preselectedProductId = -1;
private long preselectedSupplierId = -1;
private List<ProductDTO> productList = new ArrayList<>();
private List<SupplierDTO> supplierList = new ArrayList<>();
private ProductSupplierViewModel psViewModel;
private ProductViewModel productViewModel;
private SupplierViewModel supplierViewModel;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
psViewModel = new ViewModelProvider(this).get(ProductSupplierViewModel.class);
productViewModel = new ViewModelProvider(this).get(ProductViewModel.class);
supplierViewModel = new ViewModelProvider(this).get(SupplierViewModel.class);
viewModel = new ViewModelProvider(this).get(ProductSupplierDetailViewModel.class);
}
@Override
@@ -63,6 +50,7 @@ public class ProductSupplierDetailFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
observeViewModel();
loadSpinnersData();
handleArguments();
@@ -71,81 +59,59 @@ public class ProductSupplierDetailFragment extends Fragment {
binding.btnDeletePS.setOnClickListener(v -> confirmDelete());
}
private void observeViewModel() {
viewModel.getProductList().observe(getViewLifecycleOwner(), list -> refreshProductSpinner());
viewModel.getSupplierList().observe(getViewLifecycleOwner(), list -> refreshSupplierSpinner());
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/**
* Fetches products and suppliers to populate the spinners.
*/
private void loadSpinnersData() {
loadProducts();
loadSuppliers();
}
/**
* Loads the list of products from the API.
*/
private void loadProducts() {
productViewModel.getAllProducts(null, null, 0, 200, "prodName").observe(getViewLifecycleOwner(), resource -> {
viewModel.loadProducts().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
productList = resource.data.getContent();
refreshProductSpinner();
viewModel.setProductList(resource.data.getContent());
}
});
viewModel.loadSuppliers().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
viewModel.setSupplierList(resource.data.getContent());
}
});
}
private void refreshProductSpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSProduct, productList,
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSProduct, viewModel.getProductList().getValue(),
ProductDTO::getProdName, "-- Select Product --",
preselectedProductId, ProductDTO::getProdId);
}
/**
* Loads the list of suppliers from the API.
*/
private void loadSuppliers() {
supplierViewModel.getAllSuppliers(0, 200, null, "supCompany").observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
supplierList = resource.data.getContent();
refreshSupplierSpinner();
}
});
}
private void refreshSupplierSpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSSupplier, supplierList,
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSSupplier, viewModel.getSupplierList().getValue(),
SupplierDTO::getSupCompany, "-- Select Supplier --",
preselectedSupplierId, SupplierDTO::getSupId);
}
/**
* Handles arguments to determine if the fragment is in edit or add mode.
*/
private void handleArguments() {
Bundle a = getArguments();
if (a != null && a.containsKey("productId") && a.containsKey("supplierId")) {
isEditing = true;
editProductId = a.getLong("productId");
editSupplierId = a.getLong("supplierId");
preselectedProductId = editProductId;
preselectedSupplierId = editSupplierId;
long productId = a.getLong("productId");
long supplierId = a.getLong("supplierId");
viewModel.setEditMode(productId, supplierId);
preselectedProductId = productId;
preselectedSupplierId = supplierId;
binding.tvPSMode.setText("Edit Product Supplier");
binding.btnDeletePS.setVisibility(View.VISIBLE);
} else {
binding.tvPSMode.setText("Add Product Supplier");
binding.btnDeletePS.setVisibility(View.GONE);
}
}
/**
* Validates input and saves the product-supplier to the backend.
*/
private void save() {
if (binding.spinnerPSProduct.getSelectedItemPosition() == 0) {
Toast.makeText(getContext(), "Select a product", Toast.LENGTH_SHORT).show(); return;
@@ -159,40 +125,25 @@ public class ProductSupplierDetailFragment extends Fragment {
return;
}
ProductDTO product = productList.get(binding.spinnerPSProduct.getSelectedItemPosition() - 1);
SupplierDTO supplier = supplierList.get(binding.spinnerPSSupplier.getSelectedItemPosition() - 1);
ProductDTO product = viewModel.getProductList().getValue().get(binding.spinnerPSProduct.getSelectedItemPosition() - 1);
SupplierDTO supplier = viewModel.getSupplierList().getValue().get(binding.spinnerPSSupplier.getSelectedItemPosition() - 1);
BigDecimal cost = new BigDecimal(binding.etPSCost.getText().toString().trim());
ProductSupplierDTO dto = new ProductSupplierDTO(
product.getProdId(), supplier.getSupId(), cost);
ProductSupplierDTO dto = new ProductSupplierDTO(product.getProdId(), supplier.getSupId(), cost);
if (isEditing) {
psViewModel.updateProductSupplier(editProductId, editSupplierId, dto).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), "Updated", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
} else {
psViewModel.createProductSupplier(dto).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), "Saved", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
}
viewModel.saveProductSupplier(dto).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), viewModel.isEditing() ? "Updated" : "Saved", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
}
/**
* Shows a confirmation dialog before deleting a product-supplier relationship.
*/
private void confirmDelete() {
DialogUtils.showDeleteConfirmDialog(requireContext(), "Product Supplier", () ->
psViewModel.deleteProductSupplier(editProductId, editSupplierId).observe(getViewLifecycleOwner(), resource -> {
viewModel.deleteProductSupplier().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), "Deleted", Toast.LENGTH_SHORT).show();
navigateBack();
@@ -202,9 +153,6 @@ public class ProductSupplierDetailFragment extends Fragment {
}));
}
/**
* Navigates back to the previous screen.
*/
private void navigateBack() {
NavHostFragment.findNavController(this).popBackStack();
}

View File

@@ -14,7 +14,7 @@ import androidx.navigation.fragment.NavHostFragment;
import com.example.petstoremobile.databinding.FragmentPurchaseOrderDetailBinding;
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.viewmodels.PurchaseOrderViewModel;
import com.example.petstoremobile.viewmodels.PurchaseOrderDetailViewModel;
import dagger.hilt.android.AndroidEntryPoint;
@@ -25,13 +25,13 @@ import dagger.hilt.android.AndroidEntryPoint;
public class PurchaseOrderDetailFragment extends Fragment {
private FragmentPurchaseOrderDetailBinding binding;
private PurchaseOrderViewModel viewModel;
private PurchaseOrderDetailViewModel viewModel;
private long purchaseOrderId;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(PurchaseOrderViewModel.class);
viewModel = new ViewModelProvider(this).get(PurchaseOrderDetailViewModel.class);
}
/**
@@ -67,7 +67,7 @@ public class PurchaseOrderDetailFragment extends Fragment {
}
private void loadPurchaseOrderData() {
viewModel.getPurchaseOrderById(purchaseOrderId).observe(getViewLifecycleOwner(), resource -> {
viewModel.loadPurchaseOrder(purchaseOrderId).observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
PurchaseOrderDTO po = resource.data;

View File

@@ -12,7 +12,8 @@ import androidx.navigation.fragment.NavHostFragment;
import com.example.petstoremobile.R;
import com.example.petstoremobile.databinding.FragmentRefundBinding;
import com.example.petstoremobile.dtos.SaleDTO;
import com.example.petstoremobile.viewmodels.SaleViewModel;
import com.example.petstoremobile.viewmodels.RefundViewModel;
import com.example.petstoremobile.utils.Resource;
import dagger.hilt.android.AndroidEntryPoint;
import java.math.BigDecimal;
import java.math.RoundingMode;
@@ -22,53 +23,23 @@ import java.util.*;
public class RefundFragment extends Fragment {
private FragmentRefundBinding binding;
private SaleViewModel saleViewModel;
private SaleDTO currentSale;
private List<SaleDTO> allSales = new ArrayList<>();
// Items available to refund (after accounting for previous refunds)
private List<RefundItem> availableItems = new ArrayList<>();
// Items user has added to refund cart
private List<RefundItem> refundCart = new ArrayList<>();
private RefundViewModel viewModel;
private final String[] PAYMENT_METHODS = {"Cash", "Card"};
// Inner class to track refund items
static class RefundItem {
long prodId;
String productName;
int quantity;
BigDecimal unitPrice;
RefundItem(long prodId, String productName, int quantity, BigDecimal unitPrice) {
this.prodId = prodId;
this.productName = productName;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
BigDecimal getTotal() {
return unitPrice != null
? unitPrice.multiply(BigDecimal.valueOf(quantity))
: BigDecimal.ZERO;
}
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentRefundBinding.inflate(inflater, container, false);
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
viewModel = new ViewModelProvider(this).get(RefundViewModel.class);
setupSpinner();
observeViewModel();
loadAllSales();
// Pre-fill sale ID if passed from SaleFragment
Bundle args = getArguments();
if (args != null && args.containsKey("saleId")) {
long saleId = args.getLong("saleId");
binding.etRefundSaleId.setText(String.valueOf(saleId));
// Auto-load after sales are fetched
binding.etRefundSaleId.setText(String.valueOf(args.getLong("saleId")));
}
binding.btnLoadSale.setOnClickListener(v -> loadSale());
@@ -83,27 +54,25 @@ public class RefundFragment extends Fragment {
android.R.layout.simple_spinner_item, PAYMENT_METHODS));
}
private void observeViewModel() {
viewModel.getAvailableItems().observe(getViewLifecycleOwner(), items -> renderOriginalItems());
viewModel.getRefundCart().observe(getViewLifecycleOwner(), cart -> {
renderRefundCart();
updateRefundTotal();
renderOriginalItems(); // Re-render to reflect quantities in cart
});
}
private void loadAllSales() {
saleViewModel.getAllSales(0, 1000, null, null, null, "saleDate,desc")
.observe(getViewLifecycleOwner(), resource -> {
if (resource != null) {
switch (resource.status) {
case SUCCESS:
if (resource.data != null) {
allSales = resource.data.getContent();
// Auto-load if saleId was pre-filled
Bundle args = getArguments();
if (args != null && args.containsKey("saleId")) {
loadSale();
}
}
break;
case ERROR:
Log.e("Refund", "Failed to load sales: " + resource.message);
break;
}
}
});
viewModel.loadAllSales().observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
viewModel.setAllSales(resource.data.getContent());
Bundle args = getArguments();
if (args != null && args.containsKey("saleId")) {
loadSale();
}
}
});
}
private void loadSale() {
@@ -120,11 +89,12 @@ public class RefundFragment extends Fragment {
return;
}
// Find sale in loaded list
SaleDTO found = null;
for (SaleDTO s : allSales) {
if (s.getSaleId() != null && s.getSaleId() == saleId) {
found = s; break;
if (viewModel.getAllSalesList() != null) {
for (SaleDTO s : viewModel.getAllSalesList()) {
if (s.getSaleId() != null && s.getSaleId() == saleId) {
found = s; break;
}
}
}
@@ -139,9 +109,9 @@ public class RefundFragment extends Fragment {
return;
}
currentSale = found;
viewModel.setCurrentSale(found);
SaleDTO currentSale = viewModel.getCurrentSale();
// Show sale info
binding.tvSaleInfo.setVisibility(View.VISIBLE);
binding.tvSaleInfo.setText("Sale #" + currentSale.getSaleId()
+ " | " + (currentSale.getSaleDate() != null
@@ -151,7 +121,6 @@ public class RefundFragment extends Fragment {
+ " | Total: $" + currentSale.getTotalAmount()
+ " | Payment: " + currentSale.getPaymentMethod());
// Pre-select payment method
if (currentSale.getPaymentMethod() != null) {
for (int i = 0; i < PAYMENT_METHODS.length; i++) {
if (PAYMENT_METHODS[i].equalsIgnoreCase(currentSale.getPaymentMethod())) {
@@ -160,85 +129,40 @@ public class RefundFragment extends Fragment {
}
}
// Build refundable items accounting for previous refunds
buildRefundableItems();
if (availableItems.isEmpty()) {
Toast.makeText(getContext(),
"This sale has no remaining refundable items", Toast.LENGTH_LONG).show();
if (viewModel.getAvailableItems().getValue() == null || viewModel.getAvailableItems().getValue().isEmpty()) {
Toast.makeText(getContext(), "This sale has no remaining refundable items", Toast.LENGTH_LONG).show();
return;
}
// Reset refund cart
refundCart.clear();
// Show cards
binding.cardOriginalItems.setVisibility(View.VISIBLE);
binding.cardRefundItems.setVisibility(View.VISIBLE);
binding.cardPayment.setVisibility(View.VISIBLE);
binding.btnProcessRefund.setVisibility(View.VISIBLE);
renderOriginalItems();
renderRefundCart();
updateRefundTotal();
}
private void buildRefundableItems() {
availableItems.clear();
if (currentSale.getItems() == null) return;
// Find all previous refunds for this sale
Map<Long, Integer> alreadyRefunded = new HashMap<>();
for (SaleDTO s : allSales) {
if (Boolean.TRUE.equals(s.getIsRefund())
&& currentSale.getSaleId().equals(s.getOriginalSaleId())
&& s.getItems() != null) {
for (SaleDTO.SaleItemDTO item : s.getItems()) {
if (item.getProdId() != null && item.getQuantity() != null) {
alreadyRefunded.merge(item.getProdId(),
Math.abs(item.getQuantity()), Integer::sum);
}
}
}
}
// Build available items
for (SaleDTO.SaleItemDTO item : currentSale.getItems()) {
if (item.getProdId() == null || item.getQuantity() == null) continue;
int refunded = alreadyRefunded.getOrDefault(item.getProdId(), 0);
int remaining = item.getQuantity() - refunded;
if (remaining > 0) {
availableItems.add(new RefundItem(
item.getProdId(),
item.getProductName() != null ? item.getProductName() : "Unknown",
remaining,
item.getUnitPrice()
));
}
}
}
private void renderOriginalItems() {
binding.llOriginalItems.removeAllViews();
List<RefundViewModel.RefundItem> available = viewModel.getAvailableItems().getValue();
if (available == null) return;
// Header
addTableHeader(binding.llOriginalItems);
for (RefundItem item : availableItems) {
// Calculate pending in cart
int pendingQty = 0;
for (RefundItem r : refundCart) {
if (r.prodId == item.prodId) { pendingQty = r.quantity; break; }
for (RefundViewModel.RefundItem item : available) {
int inCart = 0;
if (viewModel.getRefundCart().getValue() != null) {
for (RefundViewModel.RefundItem r : viewModel.getRefundCart().getValue()) {
if (r.prodId == item.prodId) { inCart = r.quantity; break; }
}
}
int displayQty = item.quantity - pendingQty;
int displayQty = item.quantity - inCart;
if (displayQty <= 0) continue;
LinearLayout row = buildItemRow(
item.productName,
displayQty,
item.unitPrice,
true, // show add button
() -> showQuantityDialog(item)
true,
() -> showQuantityDialog(item, displayQty)
);
binding.llOriginalItems.addView(row);
}
@@ -246,8 +170,9 @@ public class RefundFragment extends Fragment {
private void renderRefundCart() {
binding.llRefundItems.removeAllViews();
List<RefundViewModel.RefundItem> cart = viewModel.getRefundCart().getValue();
if (refundCart.isEmpty()) {
if (cart == null || cart.isEmpty()) {
TextView empty = new TextView(getContext());
empty.setText("No items added to refund yet");
empty.setTextColor(0xFF888780);
@@ -258,18 +183,13 @@ public class RefundFragment extends Fragment {
addTableHeader(binding.llRefundItems);
for (RefundItem item : refundCart) {
for (RefundViewModel.RefundItem item : cart) {
LinearLayout row = buildItemRow(
item.productName,
item.quantity,
item.unitPrice,
false, // show remove button
() -> {
refundCart.remove(item);
renderOriginalItems();
renderRefundCart();
updateRefundTotal();
}
false,
() -> viewModel.removeFromCart(item)
);
binding.llRefundItems.addView(row);
}
@@ -342,24 +262,10 @@ public class RefundFragment extends Fragment {
return row;
}
private void showQuantityDialog(RefundItem item) {
// Calculate how many are already in cart
int inCart = 0;
for (RefundItem r : refundCart) {
if (r.prodId == item.prodId) { inCart = r.quantity; break; }
}
int available = item.quantity - inCart;
if (available <= 0) {
Toast.makeText(getContext(), "All units already added to refund",
Toast.LENGTH_SHORT).show();
return;
}
// Build dialog
private void showQuantityDialog(RefundViewModel.RefundItem item, int available) {
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
builder.setTitle("Refund Quantity");
builder.setMessage("Product: " + item.productName
+ "\nAvailable: " + available);
builder.setMessage("Product: " + item.productName + "\nAvailable: " + available);
EditText input = new EditText(getContext());
input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER);
@@ -377,36 +283,15 @@ public class RefundFragment extends Fragment {
return;
}
if (qty <= 0) {
Toast.makeText(getContext(), "Quantity must be at least 1",
Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "Quantity must be at least 1", Toast.LENGTH_SHORT).show();
return;
}
if (qty > available) {
Toast.makeText(getContext(), "Cannot exceed " + available,
Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "Cannot exceed " + available, Toast.LENGTH_SHORT).show();
return;
}
// Add or merge into cart
boolean merged = false;
for (int i = 0; i < refundCart.size(); i++) {
if (refundCart.get(i).prodId == item.prodId) {
RefundItem existing = refundCart.get(i);
refundCart.set(i, new RefundItem(existing.prodId,
existing.productName,
existing.quantity + qty,
existing.unitPrice));
merged = true; break;
}
}
if (!merged) {
refundCart.add(new RefundItem(item.prodId, item.productName,
qty, item.unitPrice));
}
renderOriginalItems();
renderRefundCart();
updateRefundTotal();
viewModel.addToCart(item, qty);
});
builder.setNegativeButton("Cancel", null);
@@ -415,31 +300,31 @@ public class RefundFragment extends Fragment {
private void updateRefundTotal() {
BigDecimal total = BigDecimal.ZERO;
for (RefundItem item : refundCart) total = total.add(item.getTotal());
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));
}
private void processRefund() {
if (currentSale == null) {
if (viewModel.getCurrentSale() == null) {
Toast.makeText(getContext(), "Load a sale first", Toast.LENGTH_SHORT).show();
return;
}
if (refundCart.isEmpty()) {
Toast.makeText(getContext(), "Add at least one item to refund",
Toast.LENGTH_SHORT).show();
if (viewModel.getRefundCart().getValue() == null || viewModel.getRefundCart().getValue().isEmpty()) {
Toast.makeText(getContext(), "Add at least one item to refund", Toast.LENGTH_SHORT).show();
return;
}
String payment = PAYMENT_METHODS[binding.spinnerRefundPayment.getSelectedItemPosition()];
// Confirm dialog
BigDecimal total = BigDecimal.ZERO;
for (RefundItem item : refundCart) total = total.add(item.getTotal());
for (RefundViewModel.RefundItem item : viewModel.getRefundCart().getValue()) total = total.add(item.getTotal());
final BigDecimal finalTotal = total;
new AlertDialog.Builder(requireContext())
.setTitle("Confirm Refund")
.setMessage("Process refund for Sale #" + currentSale.getSaleId()
.setMessage("Process refund for Sale #" + viewModel.getCurrentSale().getSaleId()
+ "?\nRefund amount: $" + finalTotal.setScale(2, RoundingMode.HALF_UP))
.setPositiveButton("Yes", (d, w) -> submitRefund(payment))
.setNegativeButton("No", null)
@@ -447,41 +332,13 @@ public class RefundFragment extends Fragment {
}
private void submitRefund(String payment) {
// Build sale items list
List<SaleDTO.SaleItemDTO> items = new ArrayList<>();
for (RefundItem item : refundCart) {
// Backend expects negative quantity for refunds
items.add(new SaleDTO.SaleItemDTO(item.prodId, -item.quantity));
}
SaleDTO dto = new SaleDTO(
currentSale.getStoreId(),
payment,
items,
true, // isRefund = true
currentSale.getSaleId(), // originalSaleId
null // no customer needed
);
Log.d("REFUND", "Submitting refund for saleId=" + currentSale.getSaleId()
+ " items=" + items.size());
saleViewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
viewModel.submitRefund(payment).observe(getViewLifecycleOwner(), resource -> {
if (resource != null) {
switch (resource.status) {
case SUCCESS:
if (resource.data != null) {
Toast.makeText(getContext(),
"Refund #" + resource.data.getSaleId() + " processed successfully!",
Toast.LENGTH_LONG).show();
navigateBack();
}
break;
case ERROR:
Log.e("REFUND", "Error: " + resource.message);
Toast.makeText(getContext(), "Error: " + resource.message,
Toast.LENGTH_LONG).show();
break;
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), "Refund processed successfully!", Toast.LENGTH_LONG).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
}
}
});

View File

@@ -11,7 +11,7 @@ import androidx.navigation.fragment.NavHostFragment;
import com.example.petstoremobile.R;
import com.example.petstoremobile.databinding.FragmentSaleDetailBinding;
import com.example.petstoremobile.dtos.*;
import com.example.petstoremobile.viewmodels.*;
import com.example.petstoremobile.viewmodels.SaleDetailViewModel;
import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.utils.DialogUtils;
import com.example.petstoremobile.utils.Resource;
@@ -24,18 +24,7 @@ import java.util.*;
public class SaleDetailFragment extends Fragment {
private FragmentSaleDetailBinding binding;
private SaleViewModel saleViewModel;
private StoreViewModel storeViewModel;
private CustomerViewModel customerViewModel;
private ProductViewModel productViewModel;
private boolean viewOnly = false;
private long saleId = -1;
private List<DropdownDTO> storeList = new ArrayList<>();
private List<DropdownDTO> customerList = new ArrayList<>();
private List<ProductDTO> productList = new ArrayList<>();
private List<SaleDTO.SaleItemDTO> cartItems = new ArrayList<>();
private SaleDetailViewModel viewModel;
private final String[] PAYMENT_METHODS = { "Cash", "Card"};
@@ -43,17 +32,14 @@ public class SaleDetailFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentSaleDetailBinding.inflate(inflater, container, false);
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class);
productViewModel = new ViewModelProvider(this).get(ProductViewModel.class);
viewModel = new ViewModelProvider(this).get(SaleDetailViewModel.class);
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPaymentMethod, PAYMENT_METHODS);
observeViewModel();
handleArguments();
if (!viewOnly) {
if (!viewModel.isViewOnly()) {
loadData();
setupAddItem();
}
@@ -65,20 +51,42 @@ public class SaleDetailFragment extends Fragment {
return binding.getRoot();
}
private void observeViewModel() {
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleStore, list,
DropdownDTO::getLabel, "-- Select Store --", -1L, DropdownDTO::getId);
});
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleCustomer, list,
DropdownDTO::getLabel, "-- No Customer --", -1L, DropdownDTO::getId);
});
viewModel.getProductList().observe(getViewLifecycleOwner(), list -> {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleProduct, list,
ProductDTO::getProdName, "Select Product", -1L, ProductDTO::getProdId);
});
viewModel.getCartItems().observe(getViewLifecycleOwner(), items -> {
renderCartItems();
updateTotal();
});
}
private void handleArguments() {
Bundle a = getArguments();
if (a != null && a.containsKey("saleId")) {
saleId = a.getLong("saleId");
viewOnly = a.getBoolean("viewOnly", false);
long saleId = a.getLong("saleId");
boolean viewOnly = a.getBoolean("viewOnly", false);
viewModel.setSaleId(saleId, viewOnly);
binding.tvSaleMode.setText("Sale #" + saleId);
binding.tvSaleDetailId.setText("ID: " + saleId);
// Show refund button for existing non-refund sales
if (!a.getBoolean("isRefund", false)) {
binding.btnRefundSale.setVisibility(View.VISIBLE);
}
// Hide save and input controls for view only
if (viewOnly) {
binding.btnSaveSale.setVisibility(View.GONE);
UIUtils.setViewsEnabled(false,
@@ -89,9 +97,9 @@ public class SaleDetailFragment extends Fragment {
binding.llExtraInfo.setVisibility(View.VISIBLE);
}
// Load sale details
loadSaleDetails();
} else {
viewModel.setSaleId(-1, false);
binding.tvSaleMode.setText("New Sale");
binding.tvSaleDetailId.setVisibility(View.GONE);
binding.btnRefundSale.setVisibility(View.GONE);
@@ -100,82 +108,47 @@ public class SaleDetailFragment extends Fragment {
}
private void loadData() {
loadStores();
loadCustomers();
loadProducts();
}
private void loadStores() {
storeViewModel.getStoreDropdowns().observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
storeList = resource.data;
if (binding != null) {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleStore, storeList,
DropdownDTO::getLabel, "-- Select Store --", -1L, DropdownDTO::getId);
}
}
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) viewModel.setStoreList(resource.data);
});
}
private void loadCustomers() {
customerViewModel.getCustomerDropdowns().observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
customerList = resource.data;
if (binding != null) {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleCustomer, customerList,
DropdownDTO::getLabel, "-- No Customer --", -1L, DropdownDTO::getId);
}
}
viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) viewModel.setCustomerList(resource.data);
});
}
private void loadProducts() {
productViewModel.getAllProducts(null, null, 0, 200, null).observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
productList = resource.data.getContent();
if (binding != null) {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleProduct, productList,
ProductDTO::getProdName, "Select Product", -1L, ProductDTO::getProdId);
}
}
viewModel.loadProducts().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) viewModel.setProductList(resource.data.getContent());
});
}
private void loadSaleDetails() {
saleViewModel.getSaleById(saleId).observe(getViewLifecycleOwner(), resource -> {
viewModel.loadSaleDetails().observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
SaleDTO sale = resource.data;
if (binding != null) {
binding.tvSaleDetailTotal.setText("Total: $" + sale.getTotalAmount());
binding.tvSaleSubtotal.setText("$" + (sale.getSubtotalAmount() != null ? sale.getSubtotalAmount() : sale.getTotalAmount()));
if (sale.getCouponDiscountAmount() != null && sale.getCouponDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
binding.llCouponDiscount.setVisibility(View.VISIBLE);
binding.tvSaleCouponDiscount.setText("-$" + sale.getCouponDiscountAmount());
} else {
binding.llCouponDiscount.setVisibility(View.GONE);
}
binding.tvSaleDetailTotal.setText("Total: $" + sale.getTotalAmount());
binding.tvSaleSubtotal.setText("$" + (sale.getSubtotalAmount() != null ? sale.getSubtotalAmount() : sale.getTotalAmount()));
if (sale.getCouponDiscountAmount() != null && sale.getCouponDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
binding.llCouponDiscount.setVisibility(View.VISIBLE);
binding.tvSaleCouponDiscount.setText("-$" + sale.getCouponDiscountAmount());
} else {
binding.llCouponDiscount.setVisibility(View.GONE);
}
if (sale.getEmployeeDiscountAmount() != null && sale.getEmployeeDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
binding.llEmployeeDiscount.setVisibility(View.VISIBLE);
binding.tvSaleEmployeeDiscount.setText("-$" + sale.getEmployeeDiscountAmount());
} else {
binding.llEmployeeDiscount.setVisibility(View.GONE);
}
if (sale.getEmployeeDiscountAmount() != null && sale.getEmployeeDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
binding.llEmployeeDiscount.setVisibility(View.VISIBLE);
binding.tvSaleEmployeeDiscount.setText("-$" + sale.getEmployeeDiscountAmount());
} else {
binding.llEmployeeDiscount.setVisibility(View.GONE);
}
binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : "");
binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0));
binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : "");
binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0));
SpinnerUtils.setSelectionByValue(binding.spinnerPaymentMethod, sale.getPaymentMethod());
SpinnerUtils.setSelectionByValue(binding.spinnerPaymentMethod, sale.getPaymentMethod());
// Display items
if (sale.getItems() != null) {
binding.llSaleItems.removeAllViews();
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
addItemRow(item.getProductName(),
Math.abs(item.getQuantity()),
item.getUnitPrice());
}
if (sale.getItems() != null) {
binding.llSaleItems.removeAllViews();
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
addItemRow(item.getProductName(), Math.abs(item.getQuantity()), item.getUnitPrice());
}
}
}
@@ -194,31 +167,46 @@ public class SaleDetailFragment extends Fragment {
return;
}
int qty;
try {
qty = Integer.parseInt(qtyStr);
} catch (Exception e) {
try { qty = Integer.parseInt(qtyStr); }
catch (Exception e) {
binding.etSaleQuantity.setError("Invalid quantity");
return;
}
ProductDTO product = productList.get(binding.spinnerSaleProduct.getSelectedItemPosition() - 1);
ProductDTO product = viewModel.getProductList().getValue().get(binding.spinnerSaleProduct.getSelectedItemPosition() - 1);
// Check if product already in cart
for (SaleDTO.SaleItemDTO existing : cartItems) {
for (SaleDTO.SaleItemDTO existing : viewModel.getCartItems().getValue()) {
if (existing.getProdId().equals(product.getProdId())) {
Toast.makeText(getContext(), "Product already added", Toast.LENGTH_SHORT).show();
return;
}
}
SaleDTO.SaleItemDTO item = new SaleDTO.SaleItemDTO(product.getProdId(), qty);
cartItems.add(item);
addItemRow(product.getProdName(), qty, product.getProdPrice());
updateTotal();
viewModel.addToCart(new SaleDTO.SaleItemDTO(product.getProdId(), qty));
binding.etSaleQuantity.setText("");
});
}
private void renderCartItems() {
binding.llSaleItems.removeAllViews();
List<SaleDTO.SaleItemDTO> items = viewModel.getCartItems().getValue();
List<ProductDTO> products = viewModel.getProductList().getValue();
if (items == null || products == null) return;
for (SaleDTO.SaleItemDTO item : items) {
String name = "Unknown";
BigDecimal price = BigDecimal.ZERO;
for (ProductDTO p : products) {
if (p.getProdId().equals(item.getProdId())) {
name = p.getProdName();
price = p.getProdPrice();
break;
}
}
addItemRow(name, item.getQuantity(), price);
}
}
private void addItemRow(String name, int qty, BigDecimal price) {
if (getContext() == null) return;
LinearLayout row = new LinearLayout(getContext());
@@ -226,18 +214,15 @@ public class SaleDetailFragment extends Fragment {
row.setPadding(0, 8, 0, 8);
TextView tvName = new TextView(getContext());
tvName.setLayoutParams(new LinearLayout.LayoutParams(
0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f));
tvName.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f));
tvName.setText(name);
TextView tvQty = new TextView(getContext());
tvQty.setLayoutParams(new LinearLayout.LayoutParams(
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
tvQty.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
tvQty.setText("x" + qty);
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 : "");
row.addView(tvName);
@@ -247,16 +232,7 @@ public class SaleDetailFragment extends Fragment {
}
private void updateTotal() {
BigDecimal total = BigDecimal.ZERO;
for (SaleDTO.SaleItemDTO item : cartItems) {
for (ProductDTO p : productList) {
if (p.getProdId().equals(item.getProdId()) && p.getProdPrice() != null) {
total = total.add(p.getProdPrice()
.multiply(BigDecimal.valueOf(item.getQuantity())));
break;
}
}
}
BigDecimal total = viewModel.calculateSubtotal();
binding.tvSaleSubtotal.setText("$" + total);
binding.tvSaleDetailTotal.setText("Total: $" + total);
}
@@ -266,40 +242,28 @@ public class SaleDetailFragment extends Fragment {
Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show();
return;
}
if (cartItems.isEmpty()) {
if (viewModel.getCartItems().getValue() == null || viewModel.getCartItems().getValue().isEmpty()) {
Toast.makeText(getContext(), "Add at least one item", Toast.LENGTH_SHORT).show();
return;
}
DropdownDTO store = storeList.get(binding.spinnerSaleStore.getSelectedItemPosition() - 1);
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerSaleStore.getSelectedItemPosition() - 1);
String payment = PAYMENT_METHODS[binding.spinnerPaymentMethod.getSelectedItemPosition()];
// Optional customer
Long customerId = null;
if (binding.spinnerSaleCustomer.getSelectedItemPosition() > 0) {
customerId = customerList.get(binding.spinnerSaleCustomer.getSelectedItemPosition() - 1)
.getId();
customerId = viewModel.getCustomerList().getValue().get(binding.spinnerSaleCustomer.getSelectedItemPosition() - 1).getId();
}
SaleDTO dto = new SaleDTO(
store.getId(),
payment,
cartItems,
false,
null,
customerId);
SaleDTO dto = new SaleDTO(store.getId(), payment, viewModel.getCartItems().getValue(), false, null, customerId);
saleViewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
viewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
if (resource != null) {
switch (resource.status) {
case SUCCESS:
Toast.makeText(getContext(), "Sale saved!", Toast.LENGTH_SHORT).show();
navigateBack();
break;
case ERROR:
Log.e("SALE_SAVE", "Error: " + resource.message);
DialogUtils.showInfoDialog(requireContext(), "Save Error", resource.message);
break;
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), "Sale saved!", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
DialogUtils.showInfoDialog(requireContext(), "Save Error", resource.message);
}
}
});
@@ -309,7 +273,7 @@ public class SaleDetailFragment extends Fragment {
DialogUtils.showConfirmDialog(requireContext(), "Process Refund",
"Are you sure you want to process a refund for this sale?", () -> {
Bundle args = new Bundle();
args.putLong("saleId", saleId);
args.putLong("saleId", viewModel.getSaleId());
NavHostFragment.findNavController(this).navigate(R.id.nav_refund, args);
});
}

View File

@@ -20,7 +20,7 @@ import com.example.petstoremobile.utils.ActivityLogger;
import com.example.petstoremobile.utils.DialogUtils;
import com.example.petstoremobile.utils.InputValidator;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.viewmodels.ServiceViewModel;
import com.example.petstoremobile.viewmodels.ServiceDetailViewModel;
import dagger.hilt.android.AndroidEntryPoint;
@@ -31,15 +31,12 @@ import dagger.hilt.android.AndroidEntryPoint;
public class ServiceDetailFragment extends Fragment {
private FragmentServiceDetailBinding binding;
private long serviceId;
private boolean isEditing = false;
private ServiceViewModel viewModel;
private ServiceDetailViewModel viewModel;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(ServiceViewModel.class);
viewModel = new ViewModelProvider(this).get(ServiceDetailViewModel.class);
}
@Override
@@ -53,10 +50,8 @@ public class ServiceDetailFragment extends Fragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//get controls from layout and display the view depending on the mode
handleArguments();
//set button click listeners
binding.btnBack.setOnClickListener(v -> navigateBack());
binding.btnSaveService.setOnClickListener(v -> saveService());
binding.btnDeleteService.setOnClickListener(v -> deleteService());
@@ -68,63 +63,44 @@ public class ServiceDetailFragment extends Fragment {
binding = null;
}
/**
* Handles the saving of service data (adding or updating).
*/
private void saveService() {
// Validates all fields using InputValidator
if (!InputValidator.isNotEmpty(binding.etServiceName, "Service Name")) return;
if (!InputValidator.isNotEmpty(binding.etServiceDesc, "Description")) return;
if (!InputValidator.isPositiveInteger(binding.etServiceDuration, "Duration")) return;
if (!InputValidator.isPositiveDecimal(binding.etServicePrice, "Price")) return;
//get all the values from the fields
String name = binding.etServiceName.getText().toString().trim();
String desc = binding.etServiceDesc.getText().toString().trim();
int duration = Integer.parseInt(binding.etServiceDuration.getText().toString().trim());
double price = Double.parseDouble(binding.etServicePrice.getText().toString().trim());
//create a service object to send to the API
ServiceDTO serviceDTO = new ServiceDTO();
serviceDTO.setServiceName(name);
serviceDTO.setServiceDesc(desc);
serviceDTO.setServiceDuration(duration);
serviceDTO.setServicePrice(price);
//check if the service is being edited or added
if (isEditing) {
// Update existing service
serviceDTO.setServiceId(serviceId);
viewModel.updateService(serviceId, serviceDTO).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
ActivityLogger.logChange(requireContext(), "Service", "UPDATED", (int) serviceId);
viewModel.saveService(serviceDTO).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
if (viewModel.isEditing()) {
ActivityLogger.logChange(requireContext(), "Service", "UPDATED", (int) viewModel.getServiceId());
Toast.makeText(getContext(), "Service updated successfully!", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
} else {
viewModel.createService(serviceDTO).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
} else {
ActivityLogger.log(requireContext(), "Added new Service: " + name);
Toast.makeText(getContext(), "Service added successfully!", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
}
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
}
/**
* Displays a confirmation dialog and handles the deletion of a service.
*/
private void deleteService() {
DialogUtils.showDeleteConfirmDialog(requireContext(), "Service", () ->
viewModel.deleteService(serviceId).observe(getViewLifecycleOwner(), resource -> {
viewModel.deleteService().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
ActivityLogger.logChange(requireContext(), "Service", "DELETED", (int) serviceId);
ActivityLogger.logChange(requireContext(), "Service", "DELETED", (int) viewModel.getServiceId());
Toast.makeText(getContext(), "Service deleted successfully!", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
@@ -133,30 +109,20 @@ public class ServiceDetailFragment extends Fragment {
}));
}
/**
* Navigates back to the previous screen.
*/
private void navigateBack() {
NavHostFragment.findNavController(this).popBackStack();
}
/**
* Handles arguments passed to the fragment to determine if it's in edit or add mode.
*/
private void handleArguments() {
// Service is being edited if the bundle contains a serviceId
if (getArguments() != null && getArguments().containsKey("serviceId")) {
// Get service data from arguments and populate fields
isEditing = true;
serviceId = getArguments().getLong("serviceId");
long serviceId = getArguments().getLong("serviceId");
viewModel.setServiceId(serviceId);
binding.tvMode.setText("Edit Service");
binding.tvServiceId.setText("ID: " + serviceId);
binding.btnDeleteService.setVisibility(View.VISIBLE);
loadServiceData();
} else {
// Service is being added
// Set default values for add a new service
isEditing = false;
viewModel.setServiceId(-1);
binding.tvMode.setText("Add Service");
binding.tvServiceId.setVisibility(View.GONE);
binding.btnDeleteService.setVisibility(View.GONE);
@@ -164,11 +130,8 @@ public class ServiceDetailFragment extends Fragment {
}
}
/**
* Fetches specific service details from the backend using the ID.
*/
private void loadServiceData() {
viewModel.getServiceById(serviceId).observe(getViewLifecycleOwner(), resource -> {
viewModel.loadService().observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
ServiceDTO s = resource.data;

View File

@@ -1,7 +1,6 @@
package com.example.petstoremobile.fragments.listfragments.detailfragments;
import android.os.Bundle;
import android.util.Log;
import android.view.*;
import android.widget.*;
import androidx.annotation.NonNull;
@@ -12,16 +11,15 @@ import androidx.navigation.fragment.NavHostFragment;
import com.example.petstoremobile.R;
import com.example.petstoremobile.databinding.FragmentStaffDetailBinding;
import com.example.petstoremobile.dtos.EmployeeDTO;
import com.example.petstoremobile.viewmodels.EmployeeViewModel;
import com.example.petstoremobile.viewmodels.StaffDetailViewModel;
import com.example.petstoremobile.utils.Resource;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class StaffDetailFragment extends Fragment {
private FragmentStaffDetailBinding binding;
private EmployeeViewModel employeeViewModel;
private long employeeId = -1;
private boolean isEditing = false;
private StaffDetailViewModel viewModel;
private final String[] ROLES = {"STAFF", "ADMIN"};
private final String[] STATUSES = {"Active", "Inactive"};
@@ -30,7 +28,7 @@ public class StaffDetailFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentStaffDetailBinding.inflate(inflater, container, false);
employeeViewModel = new ViewModelProvider(this).get(EmployeeViewModel.class);
viewModel = new ViewModelProvider(this).get(StaffDetailViewModel.class);
setupSpinners();
handleArguments();
@@ -51,8 +49,8 @@ public class StaffDetailFragment extends Fragment {
private void handleArguments() {
Bundle a = getArguments();
if (a != null && a.getBoolean("isEditing", false)) {
isEditing = true;
employeeId = a.getLong("employeeId", -1);
long employeeId = a.getLong("employeeId", -1);
viewModel.setEmployeeId(employeeId, true);
binding.tvStaffMode.setText("Edit Staff Account");
binding.tvStaffId.setText("ID: " + employeeId);
@@ -64,7 +62,6 @@ public class StaffDetailFragment extends Fragment {
binding.etStaffPhone.setText(a.getString("phone", ""));
binding.btnDeleteStaff.setVisibility(View.VISIBLE);
// Pre-fill role
String role = a.getString("role", "STAFF");
for (int i = 0; i < ROLES.length; i++) {
if (ROLES[i].equals(role)) {
@@ -73,13 +70,11 @@ public class StaffDetailFragment extends Fragment {
}
}
// Pre-fill status
boolean active = a.getBoolean("active", true);
binding.spinnerStaffStatus.setSelection(active ? 0 : 1);
} else {
isEditing = false;
employeeId = -1;
viewModel.setEmployeeId(-1, false);
binding.tvStaffMode.setText("Add Staff Account");
binding.btnDeleteStaff.setVisibility(View.GONE);
binding.tvStaffId.setVisibility(View.GONE);
@@ -97,12 +92,11 @@ public class StaffDetailFragment extends Fragment {
String role = ROLES[binding.spinnerStaffRole.getSelectedItemPosition()];
boolean active = binding.spinnerStaffStatus.getSelectedItemPosition() == 0;
// Validation
if (username.isEmpty()) { binding.etStaffUsername.setError("Required"); return; }
if (!isEditing && password.isEmpty()) {
if (!viewModel.isEditing() && password.isEmpty()) {
binding.etStaffPassword.setError("Required for new account"); return;
}
if (!isEditing && password.length() < 6) {
if (!viewModel.isEditing() && password.length() < 6) {
binding.etStaffPassword.setError("At least 6 characters"); return;
}
if (firstName.isEmpty()) { binding.etStaffFirstName.setError("Required"); return; }
@@ -121,35 +115,16 @@ public class StaffDetailFragment extends Fragment {
active
);
if (isEditing && employeeId > 0) {
employeeViewModel.updateEmployee(employeeId, dto).observe(getViewLifecycleOwner(), resource -> {
if (resource != null) {
switch (resource.status) {
case SUCCESS:
Toast.makeText(getContext(), "Updated successfully", Toast.LENGTH_SHORT).show();
navigateBack();
break;
case ERROR:
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
break;
}
viewModel.saveEmployee(dto).observe(getViewLifecycleOwner(), resource -> {
if (resource != null) {
if (resource.status == Resource.Status.SUCCESS) {
Toast.makeText(getContext(), viewModel.isEditing() ? "Updated successfully" : "Staff account created", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
}
});
} else {
employeeViewModel.createEmployee(dto).observe(getViewLifecycleOwner(), resource -> {
if (resource != null) {
switch (resource.status) {
case SUCCESS:
Toast.makeText(getContext(), "Staff account created", Toast.LENGTH_SHORT).show();
navigateBack();
break;
case ERROR:
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
break;
}
}
});
}
}
});
}
private void confirmDelete() {
@@ -157,16 +132,13 @@ public class StaffDetailFragment extends Fragment {
.setTitle("Delete Staff Account?")
.setMessage("This will permanently delete this staff account.")
.setPositiveButton("Yes", (d, w) ->
employeeViewModel.deleteEmployee(employeeId).observe(getViewLifecycleOwner(), resource -> {
viewModel.deleteEmployee().observe(getViewLifecycleOwner(), resource -> {
if (resource != null) {
switch (resource.status) {
case SUCCESS:
navigateBack();
break;
case ERROR:
Toast.makeText(getContext(), "Delete failed: " + resource.message,
Toast.LENGTH_SHORT).show();
break;
if (resource.status == Resource.Status.SUCCESS) {
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Delete failed: " + resource.message,
Toast.LENGTH_SHORT).show();
}
}
}))

View File

@@ -20,7 +20,7 @@ import com.example.petstoremobile.utils.DialogUtils;
import com.example.petstoremobile.utils.InputValidator;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.SupplierViewModel;
import com.example.petstoremobile.viewmodels.SupplierDetailViewModel;
import dagger.hilt.android.AndroidEntryPoint;
@@ -31,15 +31,12 @@ import dagger.hilt.android.AndroidEntryPoint;
public class SupplierDetailFragment extends Fragment {
private FragmentSupplierDetailBinding binding;
private long supId;
private boolean isEditing = false;
private SupplierViewModel viewModel;
private SupplierDetailViewModel viewModel;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(SupplierViewModel.class);
viewModel = new ViewModelProvider(this).get(SupplierDetailViewModel.class);
}
@Override
@@ -53,12 +50,9 @@ public class SupplierDetailFragment extends Fragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Add phone number formatting (CA) and limit length to 14 characters
UIUtils.formatPhoneInput(binding.etSupPhone);
handleArguments();
//set button click listeners
binding.btnBack.setOnClickListener(v -> navigateBack());
binding.btnSaveSupplier.setOnClickListener(v -> saveSupplier());
binding.btnDeleteSupplier.setOnClickListener(v -> deleteSupplier());
@@ -70,25 +64,19 @@ public class SupplierDetailFragment extends Fragment {
binding = null;
}
/**
* Handles the saving of supplier data (adding or updating).
*/
private void saveSupplier() {
// Validates all fields using InputValidator
if (!InputValidator.isNotEmpty(binding.etSupCompany, "Company Name")) return;
if (!InputValidator.isNotEmpty(binding.etSupContactFirstName, "First Name")) return;
if (!InputValidator.isNotEmpty(binding.etSupContactLastName, "Last Name")) return;
if (!InputValidator.isValidEmail(binding.etSupEmail)) return;
if (!InputValidator.isValidPhone(binding.etSupPhone)) return;
//get all the values from the fields
String company = binding.etSupCompany.getText().toString().trim();
String firstName = binding.etSupContactFirstName.getText().toString().trim();
String lastName = binding.etSupContactLastName.getText().toString().trim();
String email = binding.etSupEmail.getText().toString().trim();
String phone = binding.etSupPhone.getText().toString().trim();
//create a supplier object to send to the API
SupplierDTO supplierDTO = new SupplierDTO();
supplierDTO.setSupCompany(company);
supplierDTO.setSupContactFirstName(firstName);
@@ -96,41 +84,27 @@ public class SupplierDetailFragment extends Fragment {
supplierDTO.setSupEmail(email);
supplierDTO.setSupPhone(phone);
//check if the supplier is being edited or added
if (isEditing) {
// Update existing supplier
supplierDTO.setSupId(supId);
viewModel.updateSupplier(supId, supplierDTO).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
ActivityLogger.logChange(requireContext(), "Supplier", "UPDATED", (int) supId);
viewModel.saveSupplier(supplierDTO).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
if (viewModel.isEditing()) {
ActivityLogger.logChange(requireContext(), "Supplier", "UPDATED", (int) viewModel.getSupId());
Toast.makeText(getContext(), "Supplier updated successfully!", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
} else {
// Add new supplier
viewModel.createSupplier(supplierDTO).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
} else {
ActivityLogger.log(requireContext(), "Added new Supplier: " + company);
Toast.makeText(getContext(), "Supplier added successfully!", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
}
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
}
});
}
/**
* Displays a confirmation dialog and handles the deletion of a supplier.
*/
private void deleteSupplier() {
DialogUtils.showDeleteConfirmDialog(requireContext(), "Supplier", () ->
viewModel.deleteSupplier(supId).observe(getViewLifecycleOwner(), resource -> {
viewModel.deleteSupplier().observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS) {
ActivityLogger.logChange(requireContext(), "Supplier", "DELETED", (int) supId);
ActivityLogger.logChange(requireContext(), "Supplier", "DELETED", (int) viewModel.getSupId());
Toast.makeText(getContext(), "Supplier deleted successfully!", Toast.LENGTH_SHORT).show();
navigateBack();
} else if (resource.status == Resource.Status.ERROR) {
@@ -139,31 +113,21 @@ public class SupplierDetailFragment extends Fragment {
}));
}
/**
* Navigates back to the previous screen.
*/
private void navigateBack() {
NavHostFragment.findNavController(this).popBackStack();
}
/**
* Handles arguments passed to the fragment to determine if it's in edit or add mode.
*/
private void handleArguments() {
// Supplier is being edited if the bundle contains a supId
if (getArguments() != null && getArguments().containsKey("supId")) {
// Get supplier data from arguments and populate fields
isEditing = true;
supId = getArguments().getLong("supId");
long supId = getArguments().getLong("supId");
viewModel.setSupId(supId);
binding.tvMode.setText("Edit Supplier");
binding.tvSupId.setText("ID: " + supId);
binding.tvSupId.setVisibility(View.VISIBLE);
binding.btnDeleteSupplier.setVisibility(View.VISIBLE);
loadSupplierData();
} else {
// Supplier is being added
// Set default values for add a new supplier
isEditing = false;
viewModel.setSupId(-1);
binding.tvMode.setText("Add Supplier");
binding.tvSupId.setVisibility(View.GONE);
binding.btnDeleteSupplier.setVisibility(View.GONE);
@@ -171,11 +135,8 @@ public class SupplierDetailFragment extends Fragment {
}
}
/**
* Fetches specific supplier details from the backend using the ID.
*/
private void loadSupplierData() {
viewModel.getSupplierById(supId).observe(getViewLifecycleOwner(), resource -> {
viewModel.loadSupplier().observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
SupplierDTO s = resource.data;

View File

@@ -0,0 +1,102 @@
package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.AdoptionDTO;
import com.example.petstoremobile.dtos.DropdownDTO;
import com.example.petstoremobile.repositories.AdoptionRepository;
import com.example.petstoremobile.repositories.CustomerRepository;
import com.example.petstoremobile.repositories.PetRepository;
import com.example.petstoremobile.repositories.StoreRepository;
import com.example.petstoremobile.utils.Resource;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@HiltViewModel
public class AdoptionDetailViewModel extends ViewModel {
private final AdoptionRepository adoptionRepository;
private final PetRepository petRepository;
private final CustomerRepository customerRepository;
private final StoreRepository storeRepository;
private long adoptionId = -1;
private boolean isEditing = false;
private final MutableLiveData<List<DropdownDTO>> petList = 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>> employeeList = new MutableLiveData<>(new ArrayList<>());
@Inject
public AdoptionDetailViewModel(AdoptionRepository adoptionRepository, PetRepository petRepository,
CustomerRepository customerRepository, StoreRepository storeRepository) {
this.adoptionRepository = adoptionRepository;
this.petRepository = petRepository;
this.customerRepository = customerRepository;
this.storeRepository = storeRepository;
}
public void setAdoptionId(long id) {
this.adoptionId = id;
this.isEditing = id != -1;
}
public long getAdoptionId() {
return adoptionId;
}
public boolean isEditing() {
return isEditing;
}
public LiveData<Resource<AdoptionDTO>> loadAdoption() {
return adoptionRepository.getAdoptionById(adoptionId);
}
public LiveData<Resource<List<DropdownDTO>>> loadPets() {
return petRepository.getAdoptionPets();
}
public LiveData<Resource<List<DropdownDTO>>> loadCustomers() {
return customerRepository.getCustomerDropdowns();
}
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
return storeRepository.getStoreDropdowns();
}
public LiveData<Resource<List<DropdownDTO>>> loadEmployees(Long storeId) {
return storeRepository.getStoreEmployees(storeId);
}
public LiveData<Resource<AdoptionDTO>> saveAdoption(AdoptionDTO dto) {
if (isEditing) {
return adoptionRepository.updateAdoption(adoptionId, dto);
} else {
return adoptionRepository.createAdoption(dto);
}
}
public LiveData<Resource<Void>> deleteAdoption() {
return adoptionRepository.deleteAdoption(adoptionId);
}
public void setPetList(List<DropdownDTO> list) { petList.setValue(list); }
public LiveData<List<DropdownDTO>> getPetList() { return petList; }
public void setCustomerList(List<DropdownDTO> list) { customerList.setValue(list); }
public LiveData<List<DropdownDTO>> getCustomerList() { return customerList; }
public void setStoreList(List<DropdownDTO> list) { storeList.setValue(list); }
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
public void setEmployeeList(List<DropdownDTO> list) { employeeList.setValue(list); }
public LiveData<List<DropdownDTO>> getEmployeeList() { return employeeList; }
}

View File

@@ -0,0 +1,79 @@
package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.DropdownDTO;
import com.example.petstoremobile.dtos.InventoryDTO;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.ProductDTO;
import com.example.petstoremobile.repositories.InventoryRepository;
import com.example.petstoremobile.repositories.ProductRepository;
import com.example.petstoremobile.repositories.StoreRepository;
import com.example.petstoremobile.utils.Resource;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@HiltViewModel
public class InventoryDetailViewModel extends ViewModel {
private final InventoryRepository inventoryRepository;
private final StoreRepository storeRepository;
private final ProductRepository productRepository;
private long inventoryId = -1;
private boolean isEditing = false;
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<List<ProductDTO>> productList = new MutableLiveData<>(new ArrayList<>());
@Inject
public InventoryDetailViewModel(InventoryRepository inventoryRepository, StoreRepository storeRepository, ProductRepository productRepository) {
this.inventoryRepository = inventoryRepository;
this.storeRepository = storeRepository;
this.productRepository = productRepository;
}
public void setInventoryId(long id) {
this.inventoryId = id;
this.isEditing = id != -1;
}
public long getInventoryId() { return inventoryId; }
public boolean isEditing() { return isEditing; }
public LiveData<Resource<InventoryDTO>> loadInventory() {
return inventoryRepository.getInventoryById(inventoryId);
}
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
return storeRepository.getStoreDropdowns();
}
public LiveData<Resource<PageResponse<ProductDTO>>> loadProducts() {
return productRepository.getAllProducts(null, null, 0, 500, "prodName");
}
public LiveData<Resource<InventoryDTO>> saveInventory(InventoryDTO dto) {
if (isEditing) {
return inventoryRepository.updateInventory(inventoryId, dto);
} else {
return inventoryRepository.createInventory(dto);
}
}
public LiveData<Resource<Void>> deleteInventory() {
return inventoryRepository.deleteInventory(inventoryId);
}
public void setStoreList(List<DropdownDTO> list) { storeList.setValue(list); }
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
public void setProductList(List<ProductDTO> list) { productList.setValue(list); }
public LiveData<List<ProductDTO>> getProductList() { return productList; }
}

View File

@@ -0,0 +1,94 @@
package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.DropdownDTO;
import com.example.petstoremobile.dtos.PetDTO;
import com.example.petstoremobile.repositories.CustomerRepository;
import com.example.petstoremobile.repositories.PetRepository;
import com.example.petstoremobile.repositories.StoreRepository;
import com.example.petstoremobile.utils.Resource;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@HiltViewModel
public class PetDetailViewModel extends ViewModel {
private final PetRepository petRepository;
private final CustomerRepository customerRepository;
private final StoreRepository storeRepository;
private final MutableLiveData<PetDTO> petState = new MutableLiveData<>();
private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
private long petId = -1;
private boolean isEditing = false;
@Inject
public PetDetailViewModel(PetRepository petRepository, CustomerRepository customerRepository, StoreRepository storeRepository) {
this.petRepository = petRepository;
this.customerRepository = customerRepository;
this.storeRepository = storeRepository;
}
public void setPetId(long id) {
this.petId = id;
this.isEditing = id != -1;
}
public long getPetId() {
return petId;
}
public boolean isEditing() {
return isEditing;
}
public LiveData<Resource<PetDTO>> loadPet() {
return petRepository.getPetById(petId);
}
public LiveData<Resource<List<DropdownDTO>>> loadCustomers() {
return customerRepository.getCustomerDropdowns();
}
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
return storeRepository.getStoreDropdowns();
}
public LiveData<Resource<PetDTO>> savePet(PetDTO petDTO) {
if (isEditing) {
petDTO.setPetId(petId);
return petRepository.updatePet(petId, petDTO);
} else {
return petRepository.createPet(petDTO);
}
}
public LiveData<Resource<Void>> deletePet() {
return petRepository.deletePet(petId);
}
public void setCustomerList(List<DropdownDTO> list) {
customerList.setValue(list);
}
public LiveData<List<DropdownDTO>> getCustomerList() {
return customerList;
}
public void setStoreList(List<DropdownDTO> list) {
storeList.setValue(list);
}
public LiveData<List<DropdownDTO>> getStoreList() {
return storeList;
}
}

View File

@@ -0,0 +1,85 @@
package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.CategoryDTO;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.ProductDTO;
import com.example.petstoremobile.repositories.CategoryRepository;
import com.example.petstoremobile.repositories.ProductRepository;
import com.example.petstoremobile.utils.Resource;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
import okhttp3.MultipartBody;
@HiltViewModel
public class ProductDetailViewModel extends ViewModel {
private final ProductRepository productRepository;
private final CategoryRepository categoryRepository;
private final MutableLiveData<List<CategoryDTO>> categoryList = new MutableLiveData<>(new ArrayList<>());
private long prodId = -1;
private boolean isEditing = false;
@Inject
public ProductDetailViewModel(ProductRepository productRepository, CategoryRepository categoryRepository) {
this.productRepository = productRepository;
this.categoryRepository = categoryRepository;
}
public void setProdId(long id) {
this.prodId = id;
this.isEditing = id != -1;
}
public long getProdId() {
return prodId;
}
public boolean isEditing() {
return isEditing;
}
public LiveData<Resource<PageResponse<CategoryDTO>>> loadCategories() {
return categoryRepository.getAllCategories(0, 100);
}
public LiveData<Resource<ProductDTO>> loadProduct() {
return productRepository.getProductById(prodId);
}
public LiveData<Resource<ProductDTO>> saveProduct(ProductDTO dto) {
if (isEditing) {
return productRepository.updateProduct(prodId, dto);
} else {
return productRepository.createProduct(dto);
}
}
public LiveData<Resource<Void>> deleteProduct() {
return productRepository.deleteProduct(prodId);
}
public LiveData<Resource<Void>> uploadProductImage(MultipartBody.Part image) {
return productRepository.uploadProductImage(prodId, image);
}
public LiveData<Resource<Void>> deleteProductImage() {
return productRepository.deleteProductImage(prodId);
}
public void setCategoryList(List<CategoryDTO> list) {
categoryList.setValue(list);
}
public LiveData<List<CategoryDTO>> getCategoryList() {
return categoryList;
}
}

View File

@@ -0,0 +1,78 @@
package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.ProductDTO;
import com.example.petstoremobile.dtos.ProductSupplierDTO;
import com.example.petstoremobile.dtos.SupplierDTO;
import com.example.petstoremobile.repositories.ProductRepository;
import com.example.petstoremobile.repositories.ProductSupplierRepository;
import com.example.petstoremobile.repositories.SupplierRepository;
import com.example.petstoremobile.utils.Resource;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@HiltViewModel
public class ProductSupplierDetailViewModel extends ViewModel {
private final ProductSupplierRepository psRepository;
private final ProductRepository productRepository;
private final SupplierRepository supplierRepository;
private boolean isEditing = false;
private long editProductId = -1;
private long editSupplierId = -1;
private final MutableLiveData<List<ProductDTO>> productList = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<List<SupplierDTO>> supplierList = new MutableLiveData<>(new ArrayList<>());
@Inject
public ProductSupplierDetailViewModel(ProductSupplierRepository psRepository, ProductRepository productRepository, SupplierRepository supplierRepository) {
this.psRepository = psRepository;
this.productRepository = productRepository;
this.supplierRepository = supplierRepository;
}
public void setEditMode(long productId, long supplierId) {
this.isEditing = true;
this.editProductId = productId;
this.editSupplierId = supplierId;
}
public boolean isEditing() { return isEditing; }
public long getEditProductId() { return editProductId; }
public long getEditSupplierId() { return editSupplierId; }
public LiveData<Resource<PageResponse<ProductDTO>>> loadProducts() {
return productRepository.getAllProducts(null, null, 0, 200, "prodName");
}
public LiveData<Resource<PageResponse<SupplierDTO>>> loadSuppliers() {
return supplierRepository.getAllSuppliers(0, 200, null, "supCompany");
}
public LiveData<Resource<ProductSupplierDTO>> saveProductSupplier(ProductSupplierDTO dto) {
if (isEditing) {
return psRepository.updateProductSupplier(editProductId, editSupplierId, dto);
} else {
return psRepository.createProductSupplier(dto);
}
}
public LiveData<Resource<Void>> deleteProductSupplier() {
return psRepository.deleteProductSupplier(editProductId, editSupplierId);
}
public void setProductList(List<ProductDTO> list) { productList.setValue(list); }
public LiveData<List<ProductDTO>> getProductList() { return productList; }
public void setSupplierList(List<SupplierDTO> list) { supplierList.setValue(list); }
public LiveData<List<SupplierDTO>> getSupplierList() { return supplierList; }
}

View File

@@ -0,0 +1,26 @@
package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
import com.example.petstoremobile.repositories.PurchaseOrderRepository;
import com.example.petstoremobile.utils.Resource;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@HiltViewModel
public class PurchaseOrderDetailViewModel extends ViewModel {
private final PurchaseOrderRepository repository;
@Inject
public PurchaseOrderDetailViewModel(PurchaseOrderRepository repository) {
this.repository = repository;
}
public LiveData<Resource<PurchaseOrderDTO>> loadPurchaseOrder(long id) {
return repository.getPurchaseOrderById(id);
}
}

View File

@@ -0,0 +1,167 @@
package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.SaleDTO;
import com.example.petstoremobile.repositories.SaleRepository;
import com.example.petstoremobile.utils.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@HiltViewModel
public class RefundViewModel extends ViewModel {
private final SaleRepository saleRepository;
private final MutableLiveData<List<SaleDTO>> allSales = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<SaleDTO> currentSale = new MutableLiveData<>();
private final MutableLiveData<List<RefundItem>> availableItems = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<List<RefundItem>> refundCart = new MutableLiveData<>(new ArrayList<>());
@Inject
public RefundViewModel(SaleRepository saleRepository) {
this.saleRepository = saleRepository;
}
public LiveData<Resource<PageResponse<SaleDTO>>> loadAllSales() {
return saleRepository.getAllSales(0, 1000, null, null, null, "saleDate,desc");
}
public void setAllSales(List<SaleDTO> sales) {
allSales.setValue(sales);
}
public List<SaleDTO> getAllSalesList() {
return allSales.getValue();
}
public void setCurrentSale(SaleDTO sale) {
currentSale.setValue(sale);
buildRefundableItems();
}
public SaleDTO getCurrentSale() {
return currentSale.getValue();
}
public LiveData<List<RefundItem>> getAvailableItems() {
return availableItems;
}
public LiveData<List<RefundItem>> getRefundCart() {
return refundCart;
}
private void buildRefundableItems() {
SaleDTO sale = currentSale.getValue();
List<SaleDTO> sales = allSales.getValue();
if (sale == null || sales == null || sale.getItems() == null) {
availableItems.setValue(new ArrayList<>());
return;
}
Map<Long, Integer> alreadyRefunded = new HashMap<>();
for (SaleDTO s : sales) {
if (Boolean.TRUE.equals(s.getIsRefund())
&& sale.getSaleId().equals(s.getOriginalSaleId())
&& s.getItems() != null) {
for (SaleDTO.SaleItemDTO item : s.getItems()) {
if (item.getProdId() != null && item.getQuantity() != null) {
alreadyRefunded.merge(item.getProdId(),
Math.abs(item.getQuantity()), Integer::sum);
}
}
}
}
List<RefundItem> items = new ArrayList<>();
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
if (item.getProdId() == null || item.getQuantity() == null) continue;
int refunded = alreadyRefunded.getOrDefault(item.getProdId(), 0);
int remaining = item.getQuantity() - refunded;
if (remaining > 0) {
items.add(new RefundItem(
item.getProdId(),
item.getProductName() != null ? item.getProductName() : "Unknown",
remaining,
item.getUnitPrice()
));
}
}
availableItems.setValue(items);
refundCart.setValue(new ArrayList<>());
}
public void addToCart(RefundItem item, int qty) {
List<RefundItem> cart = new ArrayList<>(refundCart.getValue());
boolean merged = false;
for (int i = 0; i < cart.size(); i++) {
if (cart.get(i).prodId == item.prodId) {
RefundItem existing = cart.get(i);
cart.set(i, new RefundItem(existing.prodId, existing.productName, existing.quantity + qty, existing.unitPrice));
merged = true;
break;
}
}
if (!merged) {
cart.add(new RefundItem(item.prodId, item.productName, qty, item.unitPrice));
}
refundCart.setValue(cart);
}
public void removeFromCart(RefundItem item) {
List<RefundItem> cart = new ArrayList<>(refundCart.getValue());
cart.remove(item);
refundCart.setValue(cart);
}
public LiveData<Resource<SaleDTO>> submitRefund(String paymentMethod) {
SaleDTO sale = currentSale.getValue();
List<RefundItem> cart = refundCart.getValue();
if (sale == null || cart == null || cart.isEmpty()) return null;
List<SaleDTO.SaleItemDTO> items = new ArrayList<>();
for (RefundItem item : cart) {
items.add(new SaleDTO.SaleItemDTO(item.prodId, -item.quantity));
}
SaleDTO dto = new SaleDTO(
sale.getStoreId(),
paymentMethod,
items,
true,
sale.getSaleId(),
null
);
return saleRepository.createSale(dto);
}
public static class RefundItem {
public long prodId;
public String productName;
public int quantity;
public BigDecimal unitPrice;
public RefundItem(long prodId, String productName, int quantity, BigDecimal unitPrice) {
this.prodId = prodId;
this.productName = productName;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
public BigDecimal getTotal() {
return unitPrice != null ? unitPrice.multiply(BigDecimal.valueOf(quantity)) : BigDecimal.ZERO;
}
}
}

View File

@@ -0,0 +1,109 @@
package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.DropdownDTO;
import com.example.petstoremobile.dtos.ProductDTO;
import com.example.petstoremobile.dtos.SaleDTO;
import com.example.petstoremobile.repositories.CustomerRepository;
import com.example.petstoremobile.repositories.ProductRepository;
import com.example.petstoremobile.repositories.SaleRepository;
import com.example.petstoremobile.repositories.StoreRepository;
import com.example.petstoremobile.utils.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@HiltViewModel
public class SaleDetailViewModel extends ViewModel {
private final SaleRepository saleRepository;
private final StoreRepository storeRepository;
private final CustomerRepository customerRepository;
private final ProductRepository productRepository;
private long saleId = -1;
private boolean viewOnly = false;
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<List<DropdownDTO>> customerList = 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<>());
@Inject
public SaleDetailViewModel(SaleRepository saleRepository, StoreRepository storeRepository,
CustomerRepository customerRepository, ProductRepository productRepository) {
this.saleRepository = saleRepository;
this.storeRepository = storeRepository;
this.customerRepository = customerRepository;
this.productRepository = productRepository;
}
public void setSaleId(long id, boolean viewOnly) {
this.saleId = id;
this.viewOnly = viewOnly;
}
public long getSaleId() { return saleId; }
public boolean isViewOnly() { return viewOnly; }
public LiveData<Resource<SaleDTO>> loadSaleDetails() {
return saleRepository.getSaleById(saleId);
}
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
return storeRepository.getStoreDropdowns();
}
public LiveData<Resource<List<DropdownDTO>>> loadCustomers() {
return customerRepository.getCustomerDropdowns();
}
public LiveData<Resource<com.example.petstoremobile.dtos.PageResponse<ProductDTO>>> loadProducts() {
return productRepository.getAllProducts(null, null, 0, 200, null);
}
public LiveData<Resource<SaleDTO>> createSale(SaleDTO sale) {
return saleRepository.createSale(sale);
}
public void setStoreList(List<DropdownDTO> list) { storeList.setValue(list); }
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
public void setCustomerList(List<DropdownDTO> list) { customerList.setValue(list); }
public LiveData<List<DropdownDTO>> getCustomerList() { return customerList; }
public void setProductList(List<ProductDTO> list) { productList.setValue(list); }
public LiveData<List<ProductDTO>> getProductList() { return productList; }
public void addToCart(SaleDTO.SaleItemDTO item) {
List<SaleDTO.SaleItemDTO> currentCart = new ArrayList<>(cartItems.getValue());
currentCart.add(item);
cartItems.setValue(currentCart);
}
public LiveData<List<SaleDTO.SaleItemDTO>> getCartItems() { return cartItems; }
public BigDecimal calculateSubtotal() {
BigDecimal total = BigDecimal.ZERO;
List<SaleDTO.SaleItemDTO> items = cartItems.getValue();
List<ProductDTO> products = productList.getValue();
if (items != null && products != null) {
for (SaleDTO.SaleItemDTO item : items) {
for (ProductDTO p : products) {
if (p.getProdId().equals(item.getProdId()) && p.getProdPrice() != null) {
total = total.add(p.getProdPrice().multiply(BigDecimal.valueOf(item.getQuantity())));
break;
}
}
}
}
return total;
}
}

View File

@@ -0,0 +1,54 @@
package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.ServiceDTO;
import com.example.petstoremobile.repositories.ServiceRepository;
import com.example.petstoremobile.utils.Resource;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@HiltViewModel
public class ServiceDetailViewModel extends ViewModel {
private final ServiceRepository repository;
private long serviceId = -1;
private boolean isEditing = false;
@Inject
public ServiceDetailViewModel(ServiceRepository repository) {
this.repository = repository;
}
public void setServiceId(long id) {
this.serviceId = id;
this.isEditing = id != -1;
}
public long getServiceId() {
return serviceId;
}
public boolean isEditing() {
return isEditing;
}
public LiveData<Resource<ServiceDTO>> loadService() {
return repository.getServiceById(serviceId);
}
public LiveData<Resource<ServiceDTO>> saveService(ServiceDTO dto) {
if (isEditing) {
dto.setServiceId(serviceId);
return repository.updateService(serviceId, dto);
} else {
return repository.createService(dto);
}
}
public LiveData<Resource<Void>> deleteService() {
return repository.deleteService(serviceId);
}
}

View File

@@ -0,0 +1,49 @@
package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.EmployeeDTO;
import com.example.petstoremobile.repositories.EmployeeRepository;
import com.example.petstoremobile.utils.Resource;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@HiltViewModel
public class StaffDetailViewModel extends ViewModel {
private final EmployeeRepository repository;
private long employeeId = -1;
private boolean isEditing = false;
@Inject
public StaffDetailViewModel(EmployeeRepository repository) {
this.repository = repository;
}
public void setEmployeeId(long id, boolean isEditing) {
this.employeeId = id;
this.isEditing = isEditing;
}
public long getEmployeeId() {
return employeeId;
}
public boolean isEditing() {
return isEditing;
}
public LiveData<Resource<EmployeeDTO>> saveEmployee(EmployeeDTO dto) {
if (isEditing && employeeId > 0) {
return repository.updateEmployee(employeeId, dto);
} else {
return repository.createEmployee(dto);
}
}
public LiveData<Resource<Void>> deleteEmployee() {
return repository.deleteEmployee(employeeId);
}
}

View File

@@ -0,0 +1,54 @@
package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.SupplierDTO;
import com.example.petstoremobile.repositories.SupplierRepository;
import com.example.petstoremobile.utils.Resource;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@HiltViewModel
public class SupplierDetailViewModel extends ViewModel {
private final SupplierRepository repository;
private long supId = -1;
private boolean isEditing = false;
@Inject
public SupplierDetailViewModel(SupplierRepository repository) {
this.repository = repository;
}
public void setSupId(long id) {
this.supId = id;
this.isEditing = id != -1;
}
public long getSupId() {
return supId;
}
public boolean isEditing() {
return isEditing;
}
public LiveData<Resource<SupplierDTO>> loadSupplier() {
return repository.getSupplierById(supId);
}
public LiveData<Resource<SupplierDTO>> saveSupplier(SupplierDTO dto) {
if (isEditing) {
dto.setSupId(supId);
return repository.updateSupplier(supId, dto);
} else {
return repository.createSupplier(dto);
}
}
public LiveData<Resource<Void>> deleteSupplier() {
return repository.deleteSupplier(supId);
}
}