added loyaltypoint usage to sales unfinished still needs to work with the backend
This commit is contained in:
@@ -17,6 +17,8 @@ public class SaleDTO {
|
||||
private BigDecimal subtotalAmount;
|
||||
private BigDecimal couponDiscountAmount;
|
||||
private BigDecimal employeeDiscountAmount;
|
||||
private BigDecimal loyaltyDiscountAmount;
|
||||
private Integer pointsUsed;
|
||||
private String paymentMethod;
|
||||
private String channel;
|
||||
private Boolean isRefund;
|
||||
@@ -78,6 +80,22 @@ public class SaleDTO {
|
||||
return employeeDiscountAmount;
|
||||
}
|
||||
|
||||
public BigDecimal getLoyaltyDiscountAmount() {
|
||||
return loyaltyDiscountAmount;
|
||||
}
|
||||
|
||||
public void setLoyaltyDiscountAmount(BigDecimal loyaltyDiscountAmount) {
|
||||
this.loyaltyDiscountAmount = loyaltyDiscountAmount;
|
||||
}
|
||||
|
||||
public Integer getPointsUsed() {
|
||||
return pointsUsed;
|
||||
}
|
||||
|
||||
public void setPointsUsed(Integer pointsUsed) {
|
||||
this.pointsUsed = pointsUsed;
|
||||
}
|
||||
|
||||
public String getPaymentMethod() {
|
||||
return paymentMethod;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.example.petstoremobile.dtos.*;
|
||||
import com.example.petstoremobile.viewmodels.SaleDetailViewModel;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
@@ -81,7 +82,7 @@ public class SaleDetailFragment extends Fragment {
|
||||
if (isStaff()) {
|
||||
UIUtils.setViewsEnabled(false, binding.spinnerSaleStore);
|
||||
if (primaryStoreId == null) {
|
||||
Toast.makeText(requireContext(), "No store assigned to your account. Contact an admin.", Toast.LENGTH_LONG).show();
|
||||
UIUtils.showToast(requireContext(), "No store assigned to your account. Contact an admin.");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -100,6 +101,17 @@ public class SaleDetailFragment extends Fragment {
|
||||
renderCartItems();
|
||||
updateTotal();
|
||||
});
|
||||
|
||||
viewModel.getSelectedCustomerData().observe(getViewLifecycleOwner(), customer -> {
|
||||
if (customer != null && !viewModel.isViewOnly()) {
|
||||
binding.llLoyaltyPoints.setVisibility(View.VISIBLE);
|
||||
binding.tvAvailablePoints.setText("(Available: " + customer.getLoyaltyPoints() + ")");
|
||||
binding.cbUseLoyaltyPoints.setEnabled(customer.getLoyaltyPoints() >= 20);
|
||||
} else {
|
||||
binding.llLoyaltyPoints.setVisibility(View.GONE);
|
||||
binding.cbUseLoyaltyPoints.setChecked(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
@@ -109,8 +121,8 @@ public class SaleDetailFragment extends Fragment {
|
||||
boolean viewOnly = a.getBoolean("viewOnly", false);
|
||||
viewModel.setSaleId(saleId, viewOnly);
|
||||
|
||||
binding.tvSaleMode.setText("Sale #" + saleId);
|
||||
binding.tvSaleDetailId.setText("ID: " + saleId);
|
||||
binding.tvSaleMode.setText("Sale #" + DateTimeUtils.formatId(saleId));
|
||||
binding.tvSaleDetailId.setText("ID: " + DateTimeUtils.formatId(saleId));
|
||||
|
||||
boolean isRefund = a.getBoolean("isRefund", false);
|
||||
if (isRefund || isAdmin()) {
|
||||
@@ -176,23 +188,30 @@ public class SaleDetailFragment extends Fragment {
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
SaleDTO sale = resource.data;
|
||||
binding.tvSaleDetailTotal.setText("Total: $" + sale.getTotalAmount());
|
||||
binding.tvSaleSubtotal.setText("$" + (sale.getSubtotalAmount() != null ? sale.getSubtotalAmount() : sale.getTotalAmount()));
|
||||
binding.tvSaleDetailTotal.setText("Total: $" + String.format(Locale.getDefault(), "%.2f", sale.getTotalAmount()));
|
||||
binding.tvSaleSubtotal.setText("$" + String.format(Locale.getDefault(), "%.2f", (sale.getSubtotalAmount() != null ? sale.getSubtotalAmount() : sale.getTotalAmount())));
|
||||
|
||||
if (sale.getCouponDiscountAmount() != null && sale.getCouponDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||
binding.llCouponDiscount.setVisibility(View.VISIBLE);
|
||||
binding.tvSaleCouponDiscount.setText("-$" + sale.getCouponDiscountAmount());
|
||||
binding.tvSaleCouponDiscount.setText("-$" + String.format(Locale.getDefault(), "%.2f", 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());
|
||||
binding.tvSaleEmployeeDiscount.setText("-$" + String.format(Locale.getDefault(), "%.2f", sale.getEmployeeDiscountAmount()));
|
||||
} else {
|
||||
binding.llEmployeeDiscount.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (sale.getLoyaltyDiscountAmount() != null && sale.getLoyaltyDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||
binding.llLoyaltyDiscount.setVisibility(View.VISIBLE);
|
||||
binding.tvSaleLoyaltyDiscount.setText("-$" + String.format(Locale.getDefault(), "%.2f", sale.getLoyaltyDiscountAmount()));
|
||||
} else {
|
||||
binding.llLoyaltyDiscount.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : "—");
|
||||
binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0));
|
||||
binding.tvSaleStore.setText(sale.getStoreName() != null ? sale.getStoreName() : "—");
|
||||
@@ -219,7 +238,7 @@ public class SaleDetailFragment extends Fragment {
|
||||
binding.btnApplyCoupon.setOnClickListener(v -> {
|
||||
String code = binding.etCouponCode.getText().toString().trim();
|
||||
if (code.isEmpty()) {
|
||||
Toast.makeText(getContext(), "Enter a coupon code", Toast.LENGTH_SHORT).show();
|
||||
UIUtils.showToast(getContext(), "Enter a coupon code");
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
@@ -289,7 +308,7 @@ public class SaleDetailFragment extends Fragment {
|
||||
|
||||
for (SaleDTO.SaleItemDTO existing : viewModel.getCartItems().getValue()) {
|
||||
if (existing.getProdId().equals(product.getProdId())) {
|
||||
Toast.makeText(getContext(), "Product already added", Toast.LENGTH_SHORT).show();
|
||||
UIUtils.showToast(getContext(), "Product already added");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -297,6 +316,19 @@ public class SaleDetailFragment extends Fragment {
|
||||
viewModel.addToCart(new SaleDTO.SaleItemDTO(product.getProdId(), qty));
|
||||
binding.etSaleQuantity.setText("");
|
||||
});
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerSaleCustomer, p -> {
|
||||
if (p > 0) {
|
||||
Long id = viewModel.getCustomerList().getValue().get(p - 1).getId();
|
||||
viewModel.selectCustomer(id);
|
||||
} else {
|
||||
viewModel.selectCustomer(null);
|
||||
}
|
||||
});
|
||||
|
||||
binding.cbUseLoyaltyPoints.setOnCheckedChangeListener((v, checked) -> {
|
||||
viewModel.setUseLoyaltyPoints(checked);
|
||||
updateTotal();
|
||||
});
|
||||
}
|
||||
|
||||
private void renderCartItems() {
|
||||
@@ -336,7 +368,7 @@ public class SaleDetailFragment extends Fragment {
|
||||
|
||||
TextView tvPrice = new TextView(getContext());
|
||||
tvPrice.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||
tvPrice.setText(price != null ? "$" + price : "");
|
||||
tvPrice.setText(price != null ? "$" + String.format(Locale.getDefault(), "%.2f", price) : "");
|
||||
|
||||
row.addView(tvName);
|
||||
row.addView(tvQty);
|
||||
@@ -360,23 +392,35 @@ public class SaleDetailFragment extends Fragment {
|
||||
|
||||
private void updateTotal() {
|
||||
BigDecimal subtotal = viewModel.calculateSubtotal();
|
||||
BigDecimal discount = viewModel.calculateDiscount();
|
||||
BigDecimal total = subtotal.subtract(discount);
|
||||
binding.tvSaleSubtotal.setText("$" + subtotal);
|
||||
if (discount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
BigDecimal couponDiscount = viewModel.calculateCouponDiscount();
|
||||
BigDecimal loyaltyDiscount = viewModel.calculateLoyaltyDiscount();
|
||||
BigDecimal total = subtotal.subtract(couponDiscount).subtract(loyaltyDiscount);
|
||||
|
||||
binding.tvSaleSubtotal.setText("$" + String.format(Locale.getDefault(), "%.2f", subtotal));
|
||||
|
||||
if (couponDiscount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
binding.llCouponDiscount.setVisibility(View.VISIBLE);
|
||||
binding.tvSaleCouponDiscount.setText("-$" + discount);
|
||||
binding.tvSaleCouponDiscount.setText("-$" + String.format(Locale.getDefault(), "%.2f", couponDiscount));
|
||||
} else {
|
||||
binding.llCouponDiscount.setVisibility(View.GONE);
|
||||
}
|
||||
binding.tvSaleDetailTotal.setText("Total: $" + total);
|
||||
|
||||
if (loyaltyDiscount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
binding.llLoyaltyDiscount.setVisibility(View.VISIBLE);
|
||||
binding.tvLoyaltyDiscountLabel.setText("Loyalty Discount (" + viewModel.calculatePointsToUse() + " pts):");
|
||||
binding.tvSaleLoyaltyDiscount.setText("-$" + String.format(Locale.getDefault(), "%.2f", loyaltyDiscount));
|
||||
} else {
|
||||
binding.llLoyaltyDiscount.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
binding.tvSaleDetailTotal.setText("Total: $" + String.format(Locale.getDefault(), "%.2f", total));
|
||||
}
|
||||
|
||||
private void saveSale() {
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerSaleStore, "Store")) return;
|
||||
|
||||
if (viewModel.getCartItems().getValue() == null || viewModel.getCartItems().getValue().isEmpty()) {
|
||||
Toast.makeText(getContext(), "Add at least one item", Toast.LENGTH_SHORT).show();
|
||||
UIUtils.showToast(getContext(), "Add at least one item");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -390,12 +434,17 @@ public class SaleDetailFragment extends Fragment {
|
||||
|
||||
SaleDTO dto = new SaleDTO(store.getId(), payment, viewModel.getCartItems().getValue(), false, null, customerId);
|
||||
dto.setCouponId(viewModel.getAppliedCouponId());
|
||||
|
||||
if (Boolean.TRUE.equals(viewModel.getUseLoyaltyPoints().getValue())) {
|
||||
dto.setPointsUsed(viewModel.calculatePointsToUse());
|
||||
dto.setLoyaltyDiscountAmount(viewModel.calculateLoyaltyDiscount());
|
||||
}
|
||||
|
||||
viewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Sale saved!", Toast.LENGTH_SHORT).show();
|
||||
UIUtils.showToast(getContext(), "Sale saved!");
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
DialogUtils.showInfoDialog(requireContext(), "Save Error", resource.message);
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.CouponDTO;
|
||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
@@ -39,6 +40,8 @@ public class SaleDetailViewModel extends ViewModel {
|
||||
private final MutableLiveData<List<ProductDTO>> productList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<SaleDTO.SaleItemDTO>> cartItems = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<CouponDTO> appliedCoupon = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<CustomerDTO> selectedCustomerData = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<Boolean> useLoyaltyPoints = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
@Inject
|
||||
@@ -111,6 +114,34 @@ public class SaleDetailViewModel extends ViewModel {
|
||||
appliedCoupon.setValue(coupon);
|
||||
}
|
||||
|
||||
public void setUseLoyaltyPoints(boolean use) {
|
||||
useLoyaltyPoints.setValue(use);
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getUseLoyaltyPoints() {
|
||||
return useLoyaltyPoints;
|
||||
}
|
||||
|
||||
public LiveData<CustomerDTO> getSelectedCustomerData() {
|
||||
return selectedCustomerData;
|
||||
}
|
||||
|
||||
public void selectCustomer(Long customerId) {
|
||||
if (customerId == null) {
|
||||
selectedCustomerData.setValue(null);
|
||||
useLoyaltyPoints.setValue(false);
|
||||
return;
|
||||
}
|
||||
customerRepository.getCustomerById(customerId).observeForever(new androidx.lifecycle.Observer<Resource<CustomerDTO>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<CustomerDTO> resource) {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS) {
|
||||
selectedCustomerData.setValue(resource.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void clearCoupon() {
|
||||
appliedCoupon.setValue(null);
|
||||
}
|
||||
@@ -125,6 +156,10 @@ public class SaleDetailViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public BigDecimal calculateDiscount() {
|
||||
return calculateCouponDiscount().add(calculateLoyaltyDiscount());
|
||||
}
|
||||
|
||||
public BigDecimal calculateCouponDiscount() {
|
||||
CouponDTO coupon = appliedCoupon.getValue();
|
||||
if (coupon == null || coupon.getDiscountValue() == null) return BigDecimal.ZERO;
|
||||
BigDecimal subtotal = calculateSubtotal();
|
||||
@@ -135,6 +170,25 @@ public class SaleDetailViewModel extends ViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
public BigDecimal calculateLoyaltyDiscount() {
|
||||
if (Boolean.FALSE.equals(useLoyaltyPoints.getValue())) return BigDecimal.ZERO;
|
||||
CustomerDTO customer = selectedCustomerData.getValue();
|
||||
if (customer == null || customer.getLoyaltyPoints() == null || customer.getLoyaltyPoints() < 20) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
BigDecimal subtotalAfterCoupon = calculateSubtotal().subtract(calculateCouponDiscount());
|
||||
int maxPointsNeeded = subtotalAfterCoupon.multiply(BigDecimal.valueOf(20)).intValue();
|
||||
int pointsToUse = Math.min(customer.getLoyaltyPoints(), maxPointsNeeded);
|
||||
|
||||
return BigDecimal.valueOf(pointsToUse).multiply(BigDecimal.valueOf(0.05)).setScale(2, java.math.RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
public int calculatePointsToUse() {
|
||||
BigDecimal loyaltyDiscount = calculateLoyaltyDiscount();
|
||||
return loyaltyDiscount.divide(BigDecimal.valueOf(0.05), 0, java.math.RoundingMode.HALF_UP).intValue();
|
||||
}
|
||||
|
||||
public BigDecimal calculateSubtotal() {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
List<SaleDTO.SaleItemDTO> items = cartItems.getValue();
|
||||
|
||||
@@ -128,7 +128,30 @@
|
||||
android:id="@+id/spinnerSaleCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llLoyaltyPoints"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone">
|
||||
<CheckBox
|
||||
android:id="@+id/cbUseLoyaltyPoints"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Use Loyalty Points"/>
|
||||
<TextView
|
||||
android:id="@+id/tvAvailablePoints"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"
|
||||
android:text="(Available: 0)"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Payment Method -->
|
||||
<TextView
|
||||
@@ -390,6 +413,27 @@
|
||||
android:textColor="@color/status_adopted"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llLoyaltyDiscount"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="4dp"
|
||||
android:visibility="gone">
|
||||
<TextView
|
||||
android:id="@+id/tvLoyaltyDiscountLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Loyalty Discount:"/>
|
||||
<TextView
|
||||
android:id="@+id/tvSaleLoyaltyDiscount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="-$0.00"
|
||||
android:textColor="@color/status_adopted"/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleDetailTotal"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
Reference in New Issue
Block a user