Sales bug fix

This commit is contained in:
Alex
2026-04-10 05:56:05 -06:00
parent 79261274f6
commit dff379c99d
15 changed files with 126 additions and 23 deletions

View File

@@ -19,6 +19,7 @@ public interface SaleApi {
@Query("q") String query, @Query("q") String query,
@Query("paymentMethod") String paymentMethod, @Query("paymentMethod") String paymentMethod,
@Query("storeId") Long storeId, @Query("storeId") Long storeId,
@Query("isRefund") Boolean isRefund,
@Query("sort") String sort); @Query("sort") String sort);
@GET("api/v1/sales/{id}") @GET("api/v1/sales/{id}")

View File

@@ -9,6 +9,8 @@ public class SaleDTO {
private String saleDate; private String saleDate;
private Long employeeId; private Long employeeId;
private String employeeName; private String employeeName;
private Long customerId;
private String customerName;
private Long storeId; private Long storeId;
private String storeName; private String storeName;
private BigDecimal totalAmount; private BigDecimal totalAmount;
@@ -25,9 +27,6 @@ public class SaleDTO {
private List<SaleItemDTO> items; private List<SaleItemDTO> items;
private String createdAt; private String createdAt;
// Request fields
private Long customerId;
// Constructor for create request // Constructor for create request
public SaleDTO(Long storeId, String paymentMethod, List<SaleItemDTO> items, public SaleDTO(Long storeId, String paymentMethod, List<SaleItemDTO> items,
Boolean isRefund, Long originalSaleId, Long customerId) { Boolean isRefund, Long originalSaleId, Long customerId) {
@@ -119,6 +118,10 @@ public class SaleDTO {
return customerId; return customerId;
} }
public String getCustomerName() {
return customerName;
}
// Nested SaleItemDTO // Nested SaleItemDTO
public static class SaleItemDTO { public static class SaleItemDTO {
private Long saleItemId; private Long saleItemId;

View File

@@ -51,6 +51,7 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
setupSearch(); setupSearch();
setupStoreFilter(); setupStoreFilter();
setupPaymentMethodFilter(); setupPaymentMethodFilter();
setupRefundStatusFilter();
setupSwipeRefresh(); setupSwipeRefresh();
setupFilterToggle(); setupFilterToggle();
observeViewModel(); observeViewModel();
@@ -75,7 +76,7 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
viewModel.getStores().observe(getViewLifecycleOwner(), list -> { viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, list, SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, list,
StoreDTO::getStoreName, "Stores", null, StoreDTO::getStoreId); StoreDTO::getStoreName, "All Stores", null, StoreDTO::getStoreId);
}); });
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> { viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
@@ -91,7 +92,7 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
private void setupFilterToggle() { private void setupFilterToggle() {
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale, UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale,
binding.spinnerPaymentMethod, binding.spinnerStore); binding.spinnerPaymentMethod, binding.spinnerStore, binding.spinnerRefundStatus);
} }
private void setupStoreFilter() { private void setupStoreFilter() {
@@ -99,10 +100,15 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
} }
private void setupPaymentMethodFilter() { private void setupPaymentMethodFilter() {
String[] paymentMethods = {"Payments", "Cash", "Card"}; String[] paymentMethods = {"All Payments", "Cash", "Card"};
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerPaymentMethod, paymentMethods, () -> loadSales(true)); SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerPaymentMethod, paymentMethods, () -> loadSales(true));
} }
private void setupRefundStatusFilter() {
String[] refundStatuses = {"All Status", "Sale", "Refund"};
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerRefundStatus, refundStatuses, () -> loadSales(true));
}
private void setupRecyclerView() { private void setupRecyclerView() {
adapter = new SaleAdapter(saleList, this); adapter = new SaleAdapter(saleList, this);
binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext())); binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext()));
@@ -149,7 +155,12 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId(); storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
} }
viewModel.loadSales(reset, query, paymentMethod, storeId); Boolean isRefund = null;
if (binding.spinnerRefundStatus.getSelectedItemPosition() > 0) {
isRefund = binding.spinnerRefundStatus.getSelectedItemPosition() == 2;
}
viewModel.loadSales(reset, query, paymentMethod, storeId, isRefund);
} }
@Override @Override
@@ -159,6 +170,7 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
Bundle args = new Bundle(); Bundle args = new Bundle();
if (sale.getSaleId() != null) { if (sale.getSaleId() != null) {
args.putLong("saleId", sale.getSaleId()); args.putLong("saleId", sale.getSaleId());
args.putBoolean("viewOnly", true);
} }
if (sale.getIsRefund() != null) { if (sale.getIsRefund() != null) {
args.putBoolean("isRefund", sale.getIsRefund()); args.putBoolean("isRefund", sale.getIsRefund());

View File

@@ -40,7 +40,11 @@ public class SaleDetailFragment extends Fragment {
observeViewModel(); observeViewModel();
handleArguments(); handleArguments();
if (!viewModel.isViewOnly()) { if (viewModel.isViewOnly()) {
binding.llAddItemRow.setVisibility(View.GONE);
binding.btnSaveSale.setVisibility(View.GONE);
UIUtils.setViewsEnabled(false, binding.spinnerSaleStore, binding.spinnerSaleCustomer, binding.spinnerPaymentMethod);
} else {
loadData(); loadData();
setupAddItem(); setupAddItem();
} }
@@ -84,8 +88,9 @@ public class SaleDetailFragment extends Fragment {
binding.tvSaleMode.setText("Sale #" + saleId); binding.tvSaleMode.setText("Sale #" + saleId);
binding.tvSaleDetailId.setText("ID: " + saleId); binding.tvSaleDetailId.setText("ID: " + saleId);
if (!a.getBoolean("isRefund", false)) { boolean isRefund = a.getBoolean("isRefund", false);
binding.btnRefundSale.setVisibility(View.VISIBLE); if (isRefund) {
binding.btnRefundSale.setVisibility(View.GONE);
} }
if (viewOnly) { if (viewOnly) {
@@ -96,6 +101,16 @@ public class SaleDetailFragment extends Fragment {
binding.spinnerPaymentMethod); binding.spinnerPaymentMethod);
binding.llAddItemRow.setVisibility(View.GONE); binding.llAddItemRow.setVisibility(View.GONE);
binding.llExtraInfo.setVisibility(View.VISIBLE); binding.llExtraInfo.setVisibility(View.VISIBLE);
binding.llCustomerInfo.setVisibility(View.VISIBLE);
binding.tvCustomerLabel.setVisibility(View.GONE);
binding.spinnerSaleCustomer.setVisibility(View.GONE);
binding.spinnerSaleStore.setVisibility(View.GONE);
binding.spinnerPaymentMethod.setVisibility(View.GONE);
binding.tvSaleStore.setVisibility(View.VISIBLE);
binding.tvSalePaymentMethod.setVisibility(View.VISIBLE);
// Show refund button only if it's not already a refund
binding.btnRefundSale.setVisibility(isRefund ? View.GONE : View.VISIBLE);
} }
loadSaleDetails(); loadSaleDetails();
@@ -157,6 +172,13 @@ public class SaleDetailFragment extends Fragment {
binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : ""); binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : "");
binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0)); binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0));
binding.tvSaleStore.setText(sale.getStoreName() != null ? sale.getStoreName() : "");
binding.tvSaleCustomer.setText(sale.getCustomerName() != null ? sale.getCustomerName() : "No Customer");
binding.tvSalePaymentMethod.setText(sale.getPaymentMethod() != null ? sale.getPaymentMethod() : "");
if (sale.getIsRefund() != null && sale.getIsRefund()) {
binding.btnRefundSale.setVisibility(View.GONE);
}
SpinnerUtils.setSelectionByValue(binding.spinnerPaymentMethod, sale.getPaymentMethod()); SpinnerUtils.setSelectionByValue(binding.spinnerPaymentMethod, sale.getPaymentMethod());

View File

@@ -73,7 +73,9 @@ public class StaffDetailFragment extends Fragment {
} }
private void refreshStoreSpinner() { private void refreshStoreSpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaffStore, viewModel.getStoreList().getValue(), List<DropdownDTO> list = viewModel.getStoreList().getValue();
if (list == null) return;
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaffStore, list,
DropdownDTO::getLabel, "-- Select Store --", DropdownDTO::getLabel, "-- Select Store --",
preselectedStoreId, DropdownDTO::getId); preselectedStoreId, DropdownDTO::getId);
} }

View File

@@ -20,8 +20,8 @@ public class SaleRepository extends BaseRepository {
this.saleApi = saleApi; this.saleApi = saleApi;
} }
public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size, String query, String paymentMethod, Long storeId, String sortBy) { public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size, String query, String paymentMethod, Long storeId, Boolean isRefund, String sortBy) {
return executeCall(saleApi.getAllSales(page, size, query, paymentMethod, storeId, sortBy)); return executeCall(saleApi.getAllSales(page, size, query, paymentMethod, storeId, isRefund, sortBy));
} }
public LiveData<Resource<SaleDTO>> getSaleById(Long id) { public LiveData<Resource<SaleDTO>> getSaleById(Long id) {

View File

@@ -49,7 +49,7 @@ public class AnalyticsViewModel extends ViewModel {
public void loadAnalytics() { public void loadAnalytics() {
isLoading.setValue(true); isLoading.setValue(true);
errorMessage.setValue(null); errorMessage.setValue(null);
saleRepository.getAllSales(0, 2000, null, null, null, "saleDate,desc").observeForever(resource -> { saleRepository.getAllSales(0, 2000, null, null, null, null, "saleDate,desc").observeForever(resource -> {
if (resource != null) { if (resource != null) {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
cachedSales = resource.data.getContent(); cachedSales = resource.data.getContent();

View File

@@ -34,7 +34,7 @@ public class RefundViewModel extends ViewModel {
} }
public LiveData<Resource<PageResponse<SaleDTO>>> loadAllSales() { public LiveData<Resource<PageResponse<SaleDTO>>> loadAllSales() {
return saleRepository.getAllSales(0, 1000, null, null, null, "saleDate,desc"); return saleRepository.getAllSales(0, 1000, null, null, null, null, "saleDate,desc");
} }
public void setAllSales(List<SaleDTO> sales) { public void setAllSales(List<SaleDTO> sales) {

View File

@@ -42,7 +42,7 @@ public class SaleListViewModel extends ViewModel {
public LiveData<Boolean> getIsLoading() { return isLoading; } public LiveData<Boolean> getIsLoading() { return isLoading; }
public boolean isLastPage() { return isLastPage; } public boolean isLastPage() { return isLastPage; }
public void loadSales(boolean reset, String query, String paymentMethod, Long storeId) { public void loadSales(boolean reset, String query, String paymentMethod, Long storeId, Boolean isRefund) {
if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; if (isLoading.getValue() != null && isLoading.getValue() && !reset) return;
if (reset) { if (reset) {
@@ -51,7 +51,7 @@ public class SaleListViewModel extends ViewModel {
} }
isLoading.setValue(true); isLoading.setValue(true);
saleRepository.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, "saleDate,desc").observeForever(resource -> { saleRepository.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, isRefund, "saleDate,desc").observeForever(resource -> {
if (resource != null) { if (resource != null) {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
List<SaleDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(sales.getValue()); List<SaleDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(sales.getValue());

View File

@@ -10,6 +10,7 @@ import com.example.petstoremobile.repositories.EmployeeRepository;
import com.example.petstoremobile.repositories.StoreRepository; import com.example.petstoremobile.repositories.StoreRepository;
import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.Resource;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
@@ -20,7 +21,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
public class StaffDetailViewModel extends ViewModel { public class StaffDetailViewModel extends ViewModel {
private final EmployeeRepository repository; private final EmployeeRepository repository;
private final StoreRepository storeRepository; private final StoreRepository storeRepository;
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(); private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
private long employeeId = -1; private long employeeId = -1;
private boolean isEditing = false; private boolean isEditing = false;

View File

@@ -117,8 +117,25 @@
android:background="@drawable/bg_spinner" android:background="@drawable/bg_spinner"
android:paddingStart="12dp" android:paddingStart="12dp"
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:layout_marginEnd="4dp"
android:layout_marginStart="4dp"/> android:layout_marginStart="4dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp"
android:gravity="center_vertical">
<Spinner
android:id="@+id/spinnerRefundStatus"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_weight="1"
android:background="@drawable/bg_spinner"
android:paddingStart="12dp"
android:paddingEnd="8dp"
android:layout_marginEnd="4dp"/>
<Button <Button
android:id="@+id/btnOpenRefund" android:id="@+id/btnOpenRefund"

View File

@@ -83,6 +83,40 @@
android:layout_marginBottom="16dp"/> android:layout_marginBottom="16dp"/>
<TextView <TextView
android:id="@+id/tvSaleStore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="@color/text_dark"
android:layout_marginBottom="16dp"
android:visibility="gone"/>
<LinearLayout
android:id="@+id/llCustomerInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Customer"
android:textColor="@color/text_dark"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<TextView
android:id="@+id/tvSaleCustomer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="@color/text_dark"
android:layout_marginBottom="16dp"/>
</LinearLayout>
<TextView
android:id="@+id/tvCustomerLabel"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Customer (Optional)" android:text="Customer (Optional)"
@@ -111,6 +145,15 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp"/> android:layout_marginBottom="16dp"/>
<TextView
android:id="@+id/tvSalePaymentMethod"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="@color/text_dark"
android:layout_marginBottom="16dp"
android:visibility="gone"/>
<LinearLayout <LinearLayout
android:id="@+id/llExtraInfo" android:id="@+id/llExtraInfo"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -27,8 +27,9 @@ public class SaleController {
@RequestParam(required = false) String q, @RequestParam(required = false) String q,
@RequestParam(required = false) String paymentMethod, @RequestParam(required = false) String paymentMethod,
@RequestParam(required = false) Long storeId, @RequestParam(required = false) Long storeId,
@RequestParam(required = false) Boolean isRefund,
Pageable pageable) { Pageable pageable) {
return ResponseEntity.ok(saleService.getAllSales(q, paymentMethod, storeId, pageable)); return ResponseEntity.ok(saleService.getAllSales(q, paymentMethod, storeId, isRefund, pageable));
} }
@GetMapping("/{id}") @GetMapping("/{id}")

View File

@@ -20,8 +20,9 @@ public interface SaleRepository extends JpaRepository<Sale, Long> {
"LOWER(s.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))" + "LOWER(s.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))" +
")) AND " + ")) AND " +
"(:paymentMethod IS NULL OR LOWER(s.paymentMethod) = LOWER(:paymentMethod)) AND " + "(:paymentMethod IS NULL OR LOWER(s.paymentMethod) = LOWER(:paymentMethod)) AND " +
"(:isRefund IS NULL OR s.isRefund = :isRefund) AND " +
"(:storeId IS NULL OR s.store.storeId = :storeId)") "(:storeId IS NULL OR s.store.storeId = :storeId)")
Page<Sale> searchSales(@Param("q") String query, @Param("paymentMethod") String paymentMethod, @Param("storeId") Long storeId, Pageable pageable); Page<Sale> searchSales(@Param("q") String query, @Param("paymentMethod") String paymentMethod, @Param("storeId") Long storeId, @Param("isRefund") Boolean isRefund, Pageable pageable);
List<Sale> findByOriginalSaleSaleId(Long originalSaleId); List<Sale> findByOriginalSaleSaleId(Long originalSaleId);
} }

View File

@@ -39,8 +39,8 @@ public class SaleService {
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Page<SaleResponse> getAllSales(String query, String paymentMethod, Long storeId, Pageable pageable) { public Page<SaleResponse> getAllSales(String query, String paymentMethod, Long storeId, Boolean isRefund, Pageable pageable) {
Page<Sale> sales = saleRepository.searchSales(normalizeFilter(query), normalizeFilter(paymentMethod), storeId, pageable); Page<Sale> sales = saleRepository.searchSales(normalizeFilter(query), normalizeFilter(paymentMethod), storeId, isRefund, pageable);
return sales.map(this::mapToResponse); return sales.map(this::mapToResponse);
} }