Added filter by store for inventory in back end and added search to inventory
This commit is contained in:
@@ -12,7 +12,10 @@ public interface PurchaseOrderApi {
|
||||
@GET("api/v1/purchase-orders")
|
||||
Call<PageResponse<PurchaseOrderDTO>> getAllPurchaseOrders(
|
||||
@Query("page") int page,
|
||||
@Query("size") int size);
|
||||
@Query("size") int size,
|
||||
@Query("query") String query,
|
||||
@Query("storeId") Long storeId,
|
||||
@Query("sort") String sort);
|
||||
|
||||
@GET("api/v1/purchase-orders/{id}")
|
||||
Call<PurchaseOrderDTO> getPurchaseOrderById(@Path("id") Long id);
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.*;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -16,10 +21,15 @@ import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.PurchaseOrderAdapter;
|
||||
import com.example.petstoremobile.databinding.FragmentPurchaseOrderBinding;
|
||||
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.viewmodels.PurchaseOrderViewModel;
|
||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@@ -29,21 +39,23 @@ public class PurchaseOrderFragment extends Fragment
|
||||
|
||||
private FragmentPurchaseOrderBinding binding;
|
||||
private List<PurchaseOrderDTO> poList = new ArrayList<>();
|
||||
private List<PurchaseOrderDTO> filteredList = new ArrayList<>();
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
private PurchaseOrderAdapter adapter;
|
||||
private PurchaseOrderViewModel viewModel;
|
||||
private StoreViewModel storeViewModel;
|
||||
|
||||
/**
|
||||
* Initializes the fragment and its associated PurchaseOrderViewModel.
|
||||
* Initializes the fragment and its associated ViewModels.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(PurchaseOrderViewModel.class);
|
||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the fragment's UI components, including RecyclerView, search, and swipe-to-refresh.
|
||||
* Sets up the fragment's UI components, including RecyclerView, filters, and swipe-to-refresh.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
@@ -52,8 +64,9 @@ public class PurchaseOrderFragment extends Fragment
|
||||
|
||||
setupRecyclerView();
|
||||
setupSearch();
|
||||
setupStoreFilter();
|
||||
setupSwipeRefresh();
|
||||
loadData();
|
||||
setupFilterToggle();
|
||||
|
||||
binding.btnHamburgerPO.setOnClickListener(v -> {
|
||||
Fragment parent = getParentFragment();
|
||||
@@ -75,12 +88,32 @@ public class PurchaseOrderFragment extends Fragment
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the RecyclerView with a layout manager and adapter for purchase orders.
|
||||
* Reloads data every time the fragment becomes visible.
|
||||
*/
|
||||
private void setupRecyclerView() {
|
||||
adapter = new PurchaseOrderAdapter(filteredList, this);
|
||||
binding.recyclerViewPO.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.recyclerViewPO.setAdapter(adapter);
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadData();
|
||||
loadStoreData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the filter toggle button to show/hide the filter layout.
|
||||
*/
|
||||
private void setupFilterToggle() {
|
||||
binding.btnToggleFilter.setOnClickListener(v -> {
|
||||
if (binding.layoutFilter.getVisibility() == View.GONE) {
|
||||
binding.layoutFilter.setVisibility(View.VISIBLE);
|
||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
|
||||
} else {
|
||||
binding.layoutFilter.setVisibility(View.GONE);
|
||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search);
|
||||
|
||||
// Reset filters when closing
|
||||
binding.etSearchPO.setText("");
|
||||
binding.spinnerStore.setSelection(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,18 +121,49 @@ public class PurchaseOrderFragment extends Fragment
|
||||
*/
|
||||
private void setupSearch() {
|
||||
binding.etSearchPO.addTextChangedListener(new TextWatcher() {
|
||||
public void beforeTextChanged(CharSequence s, int a, int b, int c) {
|
||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
loadData();
|
||||
}
|
||||
@Override public void afterTextChanged(Editable s) {}
|
||||
});
|
||||
}
|
||||
|
||||
public void afterTextChanged(Editable s) {
|
||||
/**
|
||||
* Configures the store filter spinner.
|
||||
*/
|
||||
private void setupStoreFilter() {
|
||||
binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
loadData();
|
||||
}
|
||||
@Override public void onNothingSelected(AdapterView<?> parent) {}
|
||||
});
|
||||
}
|
||||
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
filter(s.toString());
|
||||
/**
|
||||
* Fetches store data to populate the store filter.
|
||||
*/
|
||||
private void loadStoreData() {
|
||||
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
storeList = resource.data.getContent();
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, storeList,
|
||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the RecyclerView with a layout manager and adapter.
|
||||
*/
|
||||
private void setupRecyclerView() {
|
||||
adapter = new PurchaseOrderAdapter(poList, this);
|
||||
binding.recyclerViewPO.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.recyclerViewPO.setAdapter(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the SwipeRefreshLayout to allow manual reloading of purchase order data.
|
||||
*/
|
||||
@@ -108,30 +172,18 @@ public class PurchaseOrderFragment extends Fragment
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the purchase order list based on the search query.
|
||||
*/
|
||||
private void filter(String query) {
|
||||
filteredList.clear();
|
||||
if (query.isEmpty()) {
|
||||
filteredList.addAll(poList);
|
||||
} else {
|
||||
String lower = query.toLowerCase();
|
||||
for (PurchaseOrderDTO po : poList) {
|
||||
if ((po.getSupplierName() != null && po.getSupplierName().toLowerCase().contains(lower))
|
||||
|| (po.getStatus() != null && po.getStatus().toLowerCase().contains(lower))) {
|
||||
filteredList.add(po);
|
||||
}
|
||||
}
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all purchase order data from the server through the ViewModel and updates the UI.
|
||||
* Fetches purchase order data from the server with active filters and updates the UI.
|
||||
*/
|
||||
private void loadData() {
|
||||
//Load all purchase orders from the backend using viewModel
|
||||
viewModel.getAllPurchaseOrders(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
String query = binding.etSearchPO != null ? binding.etSearchPO.getText().toString().trim() : "";
|
||||
if (query.isEmpty()) query = null;
|
||||
|
||||
Long storeId = null;
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
|
||||
storeId = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
}
|
||||
|
||||
viewModel.getAllPurchaseOrders(0, 100, query, storeId, "purchaseOrderId,desc").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
|
||||
// Check the status to see if the resource is loaded and display the data
|
||||
@@ -146,7 +198,7 @@ public class PurchaseOrderFragment extends Fragment
|
||||
if (resource.data != null) {
|
||||
poList.clear();
|
||||
poList.addAll(resource.data.getContent());
|
||||
filter(binding.etSearchPO != null ? binding.etSearchPO.getText().toString() : "");
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
@@ -164,7 +216,7 @@ public class PurchaseOrderFragment extends Fragment
|
||||
*/
|
||||
private void openDetail(int position) {
|
||||
Bundle args = new Bundle();
|
||||
PurchaseOrderDTO po = filteredList.get(position);
|
||||
PurchaseOrderDTO po = poList.get(position);
|
||||
args.putLong("purchaseOrderId", po.getPurchaseOrderId());
|
||||
NavHostFragment.findNavController(this).navigate(R.id.nav_purchase_order_detail, args);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ public class PurchaseOrderRepository extends BaseRepository {
|
||||
/**
|
||||
* Retrieves a paginated list of all purchase orders from the API.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<PurchaseOrderDTO>>> getAllPurchaseOrders(int page, int size) {
|
||||
return executeCall(api.getAllPurchaseOrders(page, size));
|
||||
public LiveData<Resource<PageResponse<PurchaseOrderDTO>>> getAllPurchaseOrders(int page, int size, String query, Long storeId, String sort) {
|
||||
return executeCall(api.getAllPurchaseOrders(page, size, query, storeId, sort));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,8 +24,8 @@ public class PurchaseOrderViewModel extends ViewModel {
|
||||
/**
|
||||
* Fetches a paginated list of all purchase orders.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<PurchaseOrderDTO>>> getAllPurchaseOrders(int page, int size) {
|
||||
return repository.getAllPurchaseOrders(page, size);
|
||||
public LiveData<Resource<PageResponse<PurchaseOrderDTO>>> getAllPurchaseOrders(int page, int size, String query, Long storeId, String sort) {
|
||||
return repository.getAllPurchaseOrders(page, size, query, storeId, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background_grey">
|
||||
@@ -27,27 +28,78 @@
|
||||
android:contentDescription="Open menu"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Purchase Orders"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
android:textStyle="bold"
|
||||
android:layout_marginStart="8dp"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnToggleFilter"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@android:drawable/ic_menu_search"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
app:tint="@color/white"
|
||||
android:contentDescription="Toggle filter"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSearchPO"
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutFilter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="Search by supplier or status..."
|
||||
android:inputType="text"
|
||||
android:drawableStart="@android:drawable/ic_menu_search"
|
||||
android:drawablePadding="8dp"
|
||||
android:background="@android:color/white"
|
||||
android:padding="12dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:visibility="gone"
|
||||
android:background="@color/primary_dark"
|
||||
android:elevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:background="@drawable/bg_search_bar"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:src="@android:drawable/ic_menu_search"
|
||||
android:alpha="0.6"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSearchPO"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="Search purchase orders..."
|
||||
android:inputType="text"
|
||||
android:background="@android:color/transparent"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textColorHint="#99000000"
|
||||
android:textSize="14sp"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerStore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_spinner"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshPO"
|
||||
|
||||
@@ -22,8 +22,9 @@ public class PurchaseOrderController {
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<PurchaseOrderResponse>> getAllPurchaseOrders(
|
||||
@RequestParam(required = false) String q,
|
||||
@RequestParam(required = false) Long storeId,
|
||||
Pageable pageable) {
|
||||
return ResponseEntity.ok(purchaseOrderService.getAllPurchaseOrders(q, pageable));
|
||||
return ResponseEntity.ok(purchaseOrderService.getAllPurchaseOrders(q, storeId, pageable));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.springframework.stereotype.Repository;
|
||||
public interface PurchaseOrderRepository extends JpaRepository<PurchaseOrder, Long> {
|
||||
|
||||
@Query("SELECT po FROM PurchaseOrder po WHERE " +
|
||||
"LOWER(po.supplier.supCompany) LIKE LOWER(CONCAT('%', :q, '%'))")
|
||||
Page<PurchaseOrder> searchPurchaseOrders(@Param("q") String query, Pageable pageable);
|
||||
"(:q IS NULL OR LOWER(po.supplier.supCompany) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||
"(:storeId IS NULL OR po.store.storeId = :storeId)")
|
||||
Page<PurchaseOrder> searchPurchaseOrders(@Param("q") String query, @Param("storeId") Long storeId, Pageable pageable);
|
||||
}
|
||||
|
||||
@@ -18,13 +18,9 @@ public class PurchaseOrderService {
|
||||
this.purchaseOrderRepository = purchaseOrderRepository;
|
||||
}
|
||||
|
||||
public Page<PurchaseOrderResponse> getAllPurchaseOrders(String query, Pageable pageable) {
|
||||
Page<PurchaseOrder> purchaseOrders;
|
||||
if (query != null && !query.trim().isEmpty()) {
|
||||
purchaseOrders = purchaseOrderRepository.searchPurchaseOrders(query, pageable);
|
||||
} else {
|
||||
purchaseOrders = purchaseOrderRepository.findAll(pageable);
|
||||
}
|
||||
public Page<PurchaseOrderResponse> getAllPurchaseOrders(String query, Long storeId, Pageable pageable) {
|
||||
String normalizedQuery = normalizeFilter(query);
|
||||
Page<PurchaseOrder> purchaseOrders = purchaseOrderRepository.searchPurchaseOrders(normalizedQuery, storeId, pageable);
|
||||
return purchaseOrders.map(this::mapToResponse);
|
||||
}
|
||||
|
||||
@@ -34,6 +30,14 @@ public class PurchaseOrderService {
|
||||
return mapToResponse(purchaseOrder);
|
||||
}
|
||||
|
||||
private String normalizeFilter(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = value.trim();
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
|
||||
private PurchaseOrderResponse mapToResponse(PurchaseOrder purchaseOrder) {
|
||||
StoreLocation store = purchaseOrder.getStore();
|
||||
return new PurchaseOrderResponse(
|
||||
|
||||
Reference in New Issue
Block a user