Merge branch 'AttachmentsToChat'
This commit is contained in:
@@ -34,6 +34,9 @@
|
||||
android:name=".activities.HomeActivity"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".activities.ForgotPasswordActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
android:exported="true">
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.example.petstoremobile.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import com.example.petstoremobile.databinding.ActivityForgotPasswordBinding;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class ForgotPasswordActivity extends AppCompatActivity {
|
||||
|
||||
private ActivityForgotPasswordBinding binding;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
EdgeToEdge.enable(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = ActivityForgotPasswordBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.forgotPasswordRoot, (v, insets) -> {
|
||||
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
||||
return insets;
|
||||
});
|
||||
|
||||
binding.btnSubmit.setOnClickListener(v -> {
|
||||
if (InputValidator.isValidEmail(binding.etEmail)) {
|
||||
String email = binding.etEmail.getText().toString().trim();
|
||||
// TODO: Implement password reset logic here
|
||||
Toast.makeText(this, "If this email is linked, a reset email will be sent.", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
binding.btnBackToLogin.setOnClickListener(v -> finish());
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,11 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
performLogin(username, password);
|
||||
});
|
||||
|
||||
// Set click listener for forgot password link
|
||||
binding.tvForgotPassword.setOnClickListener(v -> {
|
||||
startActivity(new Intent(this, ForgotPasswordActivity.class));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,6 +138,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
tokenManager.saveUserId(resource.data.getId());
|
||||
tokenManager.savePrimaryStoreId(resource.data.getStoreId());
|
||||
}
|
||||
Toast.makeText(this, "Login successful", Toast.LENGTH_SHORT).show();
|
||||
startActivity(new Intent(this, HomeActivity.class));
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.example.petstoremobile.adapters;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.api.UserApi;
|
||||
import com.example.petstoremobile.databinding.ItemCustomerBinding;
|
||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||
import com.example.petstoremobile.utils.GlideUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CustomerAdapter extends RecyclerView.Adapter<CustomerAdapter.CustomerViewHolder> {
|
||||
|
||||
private List<CustomerDTO> list;
|
||||
private OnCustomerClickListener listener;
|
||||
private String baseUrl;
|
||||
private String token;
|
||||
|
||||
public interface OnCustomerClickListener {
|
||||
void onCustomerClick(int position);
|
||||
}
|
||||
|
||||
public CustomerAdapter(List<CustomerDTO> list, OnCustomerClickListener listener) {
|
||||
this.list = list;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }
|
||||
public void setToken(String token) { this.token = token; }
|
||||
|
||||
public static class CustomerViewHolder extends RecyclerView.ViewHolder {
|
||||
final ItemCustomerBinding binding;
|
||||
|
||||
public CustomerViewHolder(@NonNull ItemCustomerBinding binding) {
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CustomerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
ItemCustomerBinding binding = ItemCustomerBinding.inflate(
|
||||
LayoutInflater.from(parent.getContext()), parent, false);
|
||||
return new CustomerViewHolder(binding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull CustomerViewHolder holder, int position) {
|
||||
CustomerDTO c = list.get(position);
|
||||
ItemCustomerBinding b = holder.binding;
|
||||
|
||||
b.tvCustomerFullName.setText(c.getFullName() != null ? c.getFullName() : "");
|
||||
b.tvCustomerUsername.setText("@" + (c.getUsername() != null ? c.getUsername() : ""));
|
||||
b.tvCustomerEmail.setText(c.getEmail() != null ? c.getEmail() : "");
|
||||
|
||||
int points = c.getLoyaltyPoints() != null ? c.getLoyaltyPoints() : 0;
|
||||
b.tvCustomerLoyalty.setText(points + " pts");
|
||||
|
||||
boolean active = Boolean.TRUE.equals(c.getActive());
|
||||
b.tvCustomerStatus.setText(active ? "Active" : "Inactive");
|
||||
b.tvCustomerStatus.setTextColor(active ? Color.parseColor("#4CAF50") : Color.parseColor("#F44336"));
|
||||
|
||||
if (baseUrl != null && c.getCustomerId() != null) {
|
||||
String imageUrl = baseUrl + String.format(UserApi.AVATAR_PATH, c.getCustomerId());
|
||||
GlideUtils.loadImageWithTokenCircle(holder.itemView.getContext(), b.ivCustomerProfile, imageUrl, token, R.drawable.placeholder);
|
||||
} else {
|
||||
b.ivCustomerProfile.setImageResource(R.drawable.placeholder);
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(v -> listener.onCustomerClick(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() { return list.size(); }
|
||||
}
|
||||
@@ -24,7 +24,8 @@ public interface AdoptionApi {
|
||||
@Query("status") String status,
|
||||
@Query("storeId") Long storeId,
|
||||
@Query("date") String date,
|
||||
@Query("employeeId") Long employeeId);
|
||||
@Query("employeeId") Long employeeId,
|
||||
@Query("sort") String sort);
|
||||
|
||||
@GET("api/v1/adoptions/{id}")
|
||||
Call<AdoptionDTO> getAdoptionById(@Path("id") Long id);
|
||||
|
||||
@@ -24,7 +24,8 @@ public interface AppointmentApi {
|
||||
@Query("status") String status,
|
||||
@Query("storeId") Long storeId,
|
||||
@Query("date") String date,
|
||||
@Query("employeeId") Long employeeId);
|
||||
@Query("employeeId") Long employeeId,
|
||||
@Query("sort") String sort);
|
||||
|
||||
@GET("api/v1/appointments/{id}")
|
||||
Call<AppointmentDTO> getAppointmentById(@Path("id") Long id);
|
||||
|
||||
@@ -7,11 +7,14 @@ import com.example.petstoremobile.dtos.PageResponse;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.DELETE;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.PUT;
|
||||
import retrofit2.http.Path;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
//api calls to get customers
|
||||
public interface CustomerApi {
|
||||
|
||||
@GET("api/v1/customers")
|
||||
@@ -20,6 +23,15 @@ public interface CustomerApi {
|
||||
@GET("api/v1/customers/{customerId}")
|
||||
Call<CustomerDTO> getCustomerById(@Path("customerId") Long customerId);
|
||||
|
||||
@PUT("api/v1/customers/{customerId}")
|
||||
Call<CustomerDTO> updateCustomer(@Path("customerId") Long customerId, @Body CustomerDTO customer);
|
||||
|
||||
@DELETE("api/v1/customers/{customerId}")
|
||||
Call<Void> deleteCustomer(@Path("customerId") Long customerId);
|
||||
|
||||
@POST("api/v1/auth/register")
|
||||
Call<CustomerDTO> registerCustomer(@Body CustomerDTO customer);
|
||||
|
||||
@GET("api/v1/dropdowns/customers")
|
||||
Call<List<DropdownDTO>> getCustomerDropdowns();
|
||||
}
|
||||
@@ -47,6 +47,9 @@ public interface PetApi {
|
||||
@GET("api/v1/dropdowns/pets")
|
||||
Call<List<DropdownDTO>> getPetDropdowns();
|
||||
|
||||
@GET("api/v1/dropdowns/pet-species")
|
||||
Call<List<DropdownDTO>> getPetSpeciesDropdowns();
|
||||
|
||||
// Get pet by id
|
||||
@GET("api/v1/pets/{id}")
|
||||
Call<PetDTO> getPetById(@Path("id") Long id);
|
||||
|
||||
@@ -15,6 +15,7 @@ public class TokenManager {
|
||||
private static final String ROLE_KEY = "role";
|
||||
private static final String PREFS_NAME = "auth_prefs";
|
||||
private static final String USER_ID_KEY = "user_id";
|
||||
private static final String PRIMARY_STORE_ID_KEY = "primary_store_id";
|
||||
|
||||
private SharedPreferences prefs;
|
||||
|
||||
@@ -54,6 +55,19 @@ public class TokenManager {
|
||||
prefs.edit().putLong(USER_ID_KEY, userId).apply();
|
||||
}
|
||||
|
||||
public void savePrimaryStoreId(Long storeId) {
|
||||
if (storeId != null) {
|
||||
prefs.edit().putLong(PRIMARY_STORE_ID_KEY, storeId).apply();
|
||||
} else {
|
||||
prefs.edit().remove(PRIMARY_STORE_ID_KEY).apply();
|
||||
}
|
||||
}
|
||||
|
||||
public Long getPrimaryStoreId() {
|
||||
long id = prefs.getLong(PRIMARY_STORE_ID_KEY, -1L);
|
||||
return id == -1L ? null : id;
|
||||
}
|
||||
|
||||
//Check if logged in
|
||||
public boolean isLoggedIn() {
|
||||
return getToken() != null;
|
||||
|
||||
@@ -5,61 +5,72 @@ import com.google.gson.annotations.SerializedName;
|
||||
public class CustomerDTO {
|
||||
@SerializedName("id")
|
||||
private Long customerId;
|
||||
private String username;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String fullName;
|
||||
private String email;
|
||||
private String phone;
|
||||
private Boolean active;
|
||||
private Integer loyaltyPoints;
|
||||
private Long primaryStoreId;
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
private String password;
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
public CustomerDTO() {}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
public CustomerDTO(String username, String password, String firstName, String lastName,
|
||||
String email, String phone) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public Long getCustomerId() { return customerId; }
|
||||
public void setCustomerId(Long customerId) { this.customerId = customerId; }
|
||||
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
|
||||
public String getFirstName() { return firstName; }
|
||||
public void setFirstName(String firstName) { this.firstName = firstName; }
|
||||
|
||||
public String getLastName() { return lastName; }
|
||||
public void setLastName(String lastName) { this.lastName = lastName; }
|
||||
|
||||
public String getFullName() {
|
||||
return firstName + " " + lastName;
|
||||
if (fullName != null) return fullName;
|
||||
String f = firstName != null ? firstName : "";
|
||||
String l = lastName != null ? lastName : "";
|
||||
return (f + " " + l).trim();
|
||||
}
|
||||
public void setFullName(String fullName) { this.fullName = fullName; }
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
|
||||
public void setCreatedAt(String createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
public Boolean getActive() { return active; }
|
||||
public void setActive(Boolean active) { this.active = active; }
|
||||
|
||||
public void setUpdatedAt(String updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
public Integer getLoyaltyPoints() { return loyaltyPoints; }
|
||||
public void setLoyaltyPoints(Integer loyaltyPoints) { this.loyaltyPoints = loyaltyPoints; }
|
||||
|
||||
public Long getPrimaryStoreId() { return primaryStoreId; }
|
||||
public void setPrimaryStoreId(Long primaryStoreId) { this.primaryStoreId = primaryStoreId; }
|
||||
|
||||
public String getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(String createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
public String getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(String updatedAt) { this.updatedAt = updatedAt; }
|
||||
|
||||
public String getPassword() { return password; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
}
|
||||
|
||||
@@ -102,6 +102,10 @@ public class SaleDTO {
|
||||
return couponId;
|
||||
}
|
||||
|
||||
public void setCouponId(Long couponId) {
|
||||
this.couponId = couponId;
|
||||
}
|
||||
|
||||
public Integer getPointsEarned() {
|
||||
return pointsEarned;
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ public class ListFragment extends Fragment {
|
||||
binding.drawerPurchaseOrderView.setOnClickListener(v -> navigateTo(R.id.nav_purchase_order));
|
||||
binding.drawerSale.setOnClickListener(v -> navigateTo(R.id.nav_sale));
|
||||
binding.drawerStaff.setOnClickListener(v -> navigateTo(R.id.nav_staff));
|
||||
binding.drawerCustomers.setOnClickListener(v -> navigateTo(R.id.nav_customer));
|
||||
binding.drawerAnalytics.setOnClickListener(v -> navigateTo(R.id.nav_analytics));
|
||||
binding.drawerCoupons.setOnClickListener(v -> navigateTo(R.id.nav_coupon));
|
||||
|
||||
|
||||
@@ -160,7 +160,8 @@ public class ProfileFragment extends Fragment {
|
||||
android.content.Intent serviceIntent = new android.content.Intent(requireContext(), ChatNotificationService.class);
|
||||
requireContext().stopService(serviceIntent);
|
||||
|
||||
tokenManager.clearLoginData(); // clear the token for next login
|
||||
// clear the token for next login
|
||||
tokenManager.clearLoginData();
|
||||
//get the intent to the main activity and clear the back stack so the back button won't allow the user to go back to the previous screen
|
||||
android.content.Intent intent = new android.content.Intent(getActivity(), MainActivity.class);
|
||||
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP | android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.AdoptionAdapter;
|
||||
import com.example.petstoremobile.api.auth.TokenManager;
|
||||
import com.example.petstoremobile.databinding.FragmentAdoptionBinding;
|
||||
import com.example.petstoremobile.dtos.AdoptionDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
@@ -38,6 +39,8 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -52,6 +55,8 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
private boolean isMonthMode = false;
|
||||
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||
|
||||
@Inject TokenManager tokenManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -117,7 +122,7 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadAdoptions();
|
||||
viewModel.loadStores();
|
||||
if (!isStaff()) viewModel.loadStores();
|
||||
}
|
||||
|
||||
private void toggleCalendarMode() {
|
||||
@@ -128,8 +133,18 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
}
|
||||
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilterAdoption, binding.layoutFilterAdoption,
|
||||
binding.etSearchAdoption, binding.spinnerStatusAdoption, binding.spinnerStoreAdoption);
|
||||
if (isStaff()) {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilterAdoption, binding.layoutFilterAdoption,
|
||||
binding.etSearchAdoption, binding.spinnerStatusAdoption);
|
||||
binding.spinnerStoreAdoption.setVisibility(View.GONE);
|
||||
} else {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilterAdoption, binding.layoutFilterAdoption,
|
||||
binding.etSearchAdoption, binding.spinnerStatusAdoption, binding.spinnerStoreAdoption);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStaff() {
|
||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||
}
|
||||
|
||||
private void setupCalendar() {
|
||||
@@ -195,10 +210,15 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
String query = binding.etSearchAdoption.getText().toString().trim();
|
||||
String status = binding.spinnerStatusAdoption.getSelectedItem() != null ? binding.spinnerStatusAdoption.getSelectedItem().toString() : "All Statuses";
|
||||
|
||||
Long storeId = null;
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStoreAdoption.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStoreAdoption.getSelectedItemPosition() - 1).getStoreId();
|
||||
Long storeId;
|
||||
if (isStaff()) {
|
||||
storeId = tokenManager.getPrimaryStoreId();
|
||||
} else {
|
||||
storeId = null;
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStoreAdoption.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStoreAdoption.getSelectedItemPosition() - 1).getStoreId();
|
||||
}
|
||||
}
|
||||
|
||||
String selectedDateString = null;
|
||||
|
||||
@@ -17,6 +17,7 @@ import android.view.ViewGroup;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.AppointmentAdapter;
|
||||
import com.example.petstoremobile.api.auth.TokenManager;
|
||||
import com.example.petstoremobile.databinding.FragmentAppointmentBinding;
|
||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
@@ -39,6 +40,8 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -52,6 +55,8 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
private AuthViewModel authViewModel;
|
||||
private BulkDeleteHandler bulkDeleteHandler;
|
||||
|
||||
@Inject TokenManager tokenManager;
|
||||
|
||||
private CalendarDay selectedCalendarDay;
|
||||
private boolean isMonthMode = false;
|
||||
private Long currentUserId = null;
|
||||
@@ -126,7 +131,7 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadAppointmentData();
|
||||
viewModel.loadStores();
|
||||
if (!isStaff()) viewModel.loadStores();
|
||||
}
|
||||
|
||||
private void toggleCalendarMode() {
|
||||
@@ -151,8 +156,13 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
}
|
||||
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchAppointment,
|
||||
binding.spinnerStatus, binding.spinnerStore);
|
||||
if (isStaff()) {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchAppointment, binding.spinnerStatus);
|
||||
binding.spinnerStore.setVisibility(View.GONE);
|
||||
} else {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchAppointment,
|
||||
binding.spinnerStatus, binding.spinnerStore);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupCalendar() {
|
||||
@@ -227,14 +237,23 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStaff() {
|
||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||
}
|
||||
|
||||
private void loadAppointmentData() {
|
||||
String query = binding.etSearchAppointment.getText().toString().trim();
|
||||
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
||||
|
||||
Long storeId = null;
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
Long storeId;
|
||||
if (isStaff()) {
|
||||
storeId = tokenManager.getPrimaryStoreId();
|
||||
} else {
|
||||
storeId = null;
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
}
|
||||
}
|
||||
|
||||
String selectedDateString = null;
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.*;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.CustomerAdapter;
|
||||
import com.example.petstoremobile.api.auth.TokenManager;
|
||||
import com.example.petstoremobile.databinding.FragmentCustomerBinding;
|
||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.CustomerListViewModel;
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class CustomerFragment extends Fragment implements CustomerAdapter.OnCustomerClickListener {
|
||||
|
||||
private FragmentCustomerBinding binding;
|
||||
private CustomerListViewModel viewModel;
|
||||
private List<CustomerDTO> customerList = new ArrayList<>();
|
||||
private CustomerAdapter adapter;
|
||||
|
||||
@Inject @Named("baseUrl") String baseUrl;
|
||||
@Inject TokenManager tokenManager;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
binding = FragmentCustomerBinding.inflate(inflater, container, false);
|
||||
viewModel = new ViewModelProvider(this).get(CustomerListViewModel.class);
|
||||
|
||||
setupRecyclerView();
|
||||
setupSearch();
|
||||
setupStatusFilter();
|
||||
setupSwipeRefresh();
|
||||
observeViewModel();
|
||||
|
||||
viewModel.loadCustomers();
|
||||
|
||||
binding.fabAddCustomer.setOnClickListener(v -> openDetail(-1));
|
||||
|
||||
UIUtils.setupHamburgerMenu(binding.btnHamburgerCustomer, this);
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilterCustomer, binding.layoutFilterCustomer,
|
||||
binding.etSearchCustomer, binding.spinnerStatusCustomer);
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getFilteredCustomers().observe(getViewLifecycleOwner(), list -> {
|
||||
customerList.clear();
|
||||
customerList.addAll(list);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.swipeRefreshCustomer.setRefreshing(loading);
|
||||
});
|
||||
}
|
||||
|
||||
private void setupRecyclerView() {
|
||||
adapter = new CustomerAdapter(customerList, this);
|
||||
adapter.setBaseUrl(baseUrl);
|
||||
adapter.setToken(tokenManager.getToken());
|
||||
binding.recyclerViewCustomer.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.recyclerViewCustomer.setAdapter(adapter);
|
||||
}
|
||||
|
||||
private void setupStatusFilter() {
|
||||
String[] statuses = {"All Statuses", "Active", "Inactive"};
|
||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusCustomer, statuses, this::applyFilters);
|
||||
}
|
||||
|
||||
private void setupSearch() {
|
||||
UIUtils.attachSearch(binding.etSearchCustomer, this::applyFilters);
|
||||
}
|
||||
|
||||
private void applyFilters() {
|
||||
String query = binding.etSearchCustomer.getText().toString().trim();
|
||||
String status = binding.spinnerStatusCustomer.getSelectedItem() != null ?
|
||||
binding.spinnerStatusCustomer.getSelectedItem().toString() : "All Statuses";
|
||||
viewModel.filter(query, status);
|
||||
}
|
||||
|
||||
private void setupSwipeRefresh() {
|
||||
binding.swipeRefreshCustomer.setOnRefreshListener(viewModel::loadCustomers);
|
||||
}
|
||||
|
||||
private void openDetail(int position) {
|
||||
Bundle args = new Bundle();
|
||||
if (position != -1) {
|
||||
CustomerDTO c = customerList.get(position);
|
||||
args.putLong("customerId", c.getCustomerId() != null ? c.getCustomerId() : -1);
|
||||
args.putString("username", c.getUsername() != null ? c.getUsername() : "");
|
||||
args.putString("firstName", c.getFirstName() != null ? c.getFirstName() : "");
|
||||
args.putString("lastName", c.getLastName() != null ? c.getLastName() : "");
|
||||
args.putString("email", c.getEmail() != null ? c.getEmail() : "");
|
||||
args.putString("phone", c.getPhone() != null ? c.getPhone() : "");
|
||||
args.putBoolean("active", Boolean.TRUE.equals(c.getActive()));
|
||||
args.putInt("loyaltyPoints", c.getLoyaltyPoints() != null ? c.getLoyaltyPoints() : 0);
|
||||
args.putBoolean("isEditing", true);
|
||||
}
|
||||
NavHostFragment.findNavController(this).navigate(R.id.nav_customer_detail, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCustomerClick(int position) {
|
||||
openDetail(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.InventoryAdapter;
|
||||
import com.example.petstoremobile.api.auth.TokenManager;
|
||||
import com.example.petstoremobile.databinding.FragmentInventoryBinding;
|
||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
@@ -27,6 +28,8 @@ import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -38,6 +41,8 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
private InventoryListViewModel viewModel;
|
||||
private BulkDeleteHandler bulkDeleteHandler;
|
||||
|
||||
@Inject TokenManager tokenManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -99,7 +104,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
viewModel.loadStores();
|
||||
if (!isStaff()) viewModel.loadStores();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,7 +114,16 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
}
|
||||
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchInventory, binding.spinnerStore);
|
||||
if (isStaff()) {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchInventory);
|
||||
binding.spinnerStore.setVisibility(View.GONE);
|
||||
} else {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchInventory, binding.spinnerStore);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStaff() {
|
||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||
}
|
||||
|
||||
private void setupSearch() {
|
||||
@@ -150,10 +164,15 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
String query = binding.etSearchInventory != null ? binding.etSearchInventory.getText().toString().trim() : "";
|
||||
if (query.isEmpty()) query = null;
|
||||
|
||||
Long storeId = null;
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
Long storeId;
|
||||
if (isStaff()) {
|
||||
storeId = tokenManager.getPrimaryStoreId();
|
||||
} else {
|
||||
storeId = null;
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.loadInventory(reset, query, storeId);
|
||||
|
||||
@@ -88,6 +88,11 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.swipeRefreshPet.setRefreshing(loading);
|
||||
});
|
||||
|
||||
viewModel.getSpeciesOptions().observe(getViewLifecycleOwner(), options -> {
|
||||
String[] arr = options.toArray(new String[0]);
|
||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerSpecies, arr, this::loadPetData);
|
||||
});
|
||||
}
|
||||
|
||||
private void setupBulkDelete() {
|
||||
@@ -107,12 +112,23 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadPetData();
|
||||
viewModel.loadStores();
|
||||
viewModel.loadSpecies();
|
||||
if (!isStaff()) viewModel.loadStores();
|
||||
}
|
||||
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPet,
|
||||
binding.spinnerStatus, binding.spinnerSpecies, binding.spinnerStore);
|
||||
if (isStaff()) {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPet,
|
||||
binding.spinnerStatus, binding.spinnerSpecies);
|
||||
binding.spinnerStore.setVisibility(View.GONE);
|
||||
} else {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPet,
|
||||
binding.spinnerStatus, binding.spinnerSpecies, binding.spinnerStore);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStaff() {
|
||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||
}
|
||||
|
||||
private void setupSearch() {
|
||||
@@ -125,8 +141,8 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
}
|
||||
|
||||
private void setupSpeciesFilter() {
|
||||
String[] species = {"All Species", "Dog", "Cat", "Bird", "Rabbit", "Fish", "Hamster"};
|
||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerSpecies, species, this::loadPetData);
|
||||
String[] initial = {"All Species"};
|
||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerSpecies, initial, this::loadPetData);
|
||||
}
|
||||
|
||||
private void setupStoreFilter() {
|
||||
@@ -142,10 +158,15 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
||||
String species = binding.spinnerSpecies.getSelectedItem() != null ? binding.spinnerSpecies.getSelectedItem().toString() : "All Species";
|
||||
|
||||
Long storeId = null;
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
Long storeId;
|
||||
if (isStaff()) {
|
||||
storeId = tokenManager.getPrimaryStoreId();
|
||||
} else {
|
||||
storeId = null;
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.loadPets(query, status, species, storeId);
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.PurchaseOrderAdapter;
|
||||
import com.example.petstoremobile.api.auth.TokenManager;
|
||||
import com.example.petstoremobile.databinding.FragmentPurchaseOrderBinding;
|
||||
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
@@ -24,6 +25,8 @@ import com.example.petstoremobile.viewmodels.PurchaseOrderListViewModel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -35,6 +38,8 @@ public class PurchaseOrderFragment extends Fragment
|
||||
private PurchaseOrderAdapter adapter;
|
||||
private PurchaseOrderListViewModel viewModel;
|
||||
|
||||
@Inject TokenManager tokenManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -79,11 +84,20 @@ public class PurchaseOrderFragment extends Fragment
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadData();
|
||||
viewModel.loadStores();
|
||||
if (!isStaff()) viewModel.loadStores();
|
||||
}
|
||||
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPO, binding.spinnerStore);
|
||||
if (isStaff()) {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPO);
|
||||
binding.spinnerStore.setVisibility(View.GONE);
|
||||
} else {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPO, binding.spinnerStore);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStaff() {
|
||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||
}
|
||||
|
||||
private void setupSearch() {
|
||||
@@ -108,10 +122,15 @@ public class PurchaseOrderFragment extends Fragment
|
||||
String query = binding.etSearchPO != null ? binding.etSearchPO.getText().toString().trim() : "";
|
||||
if (query.isEmpty()) query = null;
|
||||
|
||||
Long storeId = null;
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
Long storeId;
|
||||
if (isStaff()) {
|
||||
storeId = tokenManager.getPrimaryStoreId();
|
||||
} else {
|
||||
storeId = null;
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.loadPurchaseOrders(query, storeId);
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.widget.Toast;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.SaleAdapter;
|
||||
import com.example.petstoremobile.api.auth.TokenManager;
|
||||
import com.example.petstoremobile.databinding.FragmentSaleBinding;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
@@ -25,6 +26,8 @@ import com.example.petstoremobile.viewmodels.SaleListViewModel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -35,6 +38,8 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
||||
private SaleAdapter adapter;
|
||||
private SaleListViewModel viewModel;
|
||||
|
||||
@Inject TokenManager tokenManager;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -87,12 +92,22 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
viewModel.loadStores();
|
||||
if (!isStaff()) viewModel.loadStores();
|
||||
}
|
||||
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale,
|
||||
binding.spinnerPaymentMethod, binding.spinnerStore, binding.spinnerRefundStatus);
|
||||
if (isStaff()) {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale,
|
||||
binding.spinnerPaymentMethod, binding.spinnerRefundStatus);
|
||||
binding.spinnerStore.setVisibility(View.GONE);
|
||||
} else {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale,
|
||||
binding.spinnerPaymentMethod, binding.spinnerStore, binding.spinnerRefundStatus);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStaff() {
|
||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||
}
|
||||
|
||||
private void setupStoreFilter() {
|
||||
@@ -149,10 +164,15 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
||||
paymentMethod = (String) binding.spinnerPaymentMethod.getSelectedItem();
|
||||
}
|
||||
|
||||
Long storeId = null;
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
Long storeId;
|
||||
if (isStaff()) {
|
||||
storeId = tokenManager.getPrimaryStoreId();
|
||||
} else {
|
||||
storeId = null;
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
}
|
||||
}
|
||||
|
||||
Boolean isRefund = null;
|
||||
|
||||
@@ -17,11 +17,14 @@ 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.api.auth.TokenManager;
|
||||
import com.example.petstoremobile.viewmodels.AdoptionDetailViewModel;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
/**
|
||||
@@ -34,6 +37,8 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
private AdoptionDetailViewModel viewModel;
|
||||
private boolean isUpdatingUI = false;
|
||||
|
||||
@Inject TokenManager tokenManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -82,7 +87,7 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
|
||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
||||
AdoptionDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||
Long storeId = state != null ? state.selectedStoreId : null;
|
||||
Long storeId = isStaff() ? tokenManager.getPrimaryStoreId() : (state != null ? state.selectedStoreId : null);
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionStore, list,
|
||||
DropdownDTO::getLabel, "-- Select Store --", storeId, DropdownDTO::getId);
|
||||
});
|
||||
@@ -113,6 +118,7 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
binding.spinnerAdoptionCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (isUpdatingUI) return;
|
||||
viewModel.onCustomerSelected(position);
|
||||
}
|
||||
@Override
|
||||
@@ -122,6 +128,7 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
binding.spinnerAdoptionStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (isUpdatingUI) return;
|
||||
viewModel.onStoreSelected(position);
|
||||
}
|
||||
@Override
|
||||
@@ -210,9 +217,15 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
if (employees != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee,
|
||||
employees, DropdownDTO::getLabel, "-- Select Staff --", state.selectedEmployeeId, DropdownDTO::getId);
|
||||
|
||||
if (isStaff()) UIUtils.setViewsEnabled(false, binding.spinnerAdoptionStore);
|
||||
|
||||
isUpdatingUI = false;
|
||||
}
|
||||
|
||||
private boolean isStaff() {
|
||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||
}
|
||||
|
||||
private void saveAdoption() {
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionCustomer, "Customer")) return;
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionPet, "Pet")) return;
|
||||
@@ -225,9 +238,16 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
try { fee = new BigDecimal(feeStr); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
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);
|
||||
List<DropdownDTO> customerListVal = viewModel.getCustomerList().getValue();
|
||||
List<DropdownDTO> petListVal = viewModel.getPetList().getValue();
|
||||
List<DropdownDTO> storeListVal = viewModel.getStoreList().getValue();
|
||||
if (customerListVal == null || petListVal == null || storeListVal == null) {
|
||||
Toast.makeText(getContext(), "Data not loaded yet, please try again", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
DropdownDTO customer = customerListVal.get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1);
|
||||
DropdownDTO pet = petListVal.get(binding.spinnerAdoptionPet.getSelectedItemPosition() - 1);
|
||||
DropdownDTO store = storeListVal.get(binding.spinnerAdoptionStore.getSelectedItemPosition() - 1);
|
||||
|
||||
Long employeeId = null;
|
||||
if (binding.spinnerAdoptionEmployee.getSelectedItemPosition() > 0 && viewModel.getEmployeeList().getValue() != null) {
|
||||
|
||||
@@ -23,10 +23,13 @@ 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.api.auth.TokenManager;
|
||||
import com.example.petstoremobile.viewmodels.AppointmentDetailViewModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
/**
|
||||
@@ -43,6 +46,8 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
private AppointmentDetailViewModel viewModel;
|
||||
private boolean isUpdatingUI = false;
|
||||
|
||||
@Inject TokenManager tokenManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -89,6 +94,7 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (isUpdatingUI) return;
|
||||
viewModel.onCustomerSelected(position);
|
||||
}
|
||||
@Override
|
||||
@@ -98,6 +104,7 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (isUpdatingUI) return;
|
||||
viewModel.onStoreSelected(position);
|
||||
}
|
||||
@Override
|
||||
@@ -129,7 +136,7 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
|
||||
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||
AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||
Long id = state != null ? state.selectedStoreId : null;
|
||||
Long id = isStaff() ? tokenManager.getPrimaryStoreId() : (state != null ? state.selectedStoreId : null);
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStore, list, DropdownDTO::getLabel, "-- Select Store --", id, DropdownDTO::getId);
|
||||
});
|
||||
|
||||
@@ -201,9 +208,15 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
if (staff != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff,
|
||||
staff, DropdownDTO::getLabel, "-- Select Staff --", state.selectedStaffId, DropdownDTO::getId);
|
||||
|
||||
if (isStaff()) binding.spinnerStore.setEnabled(false);
|
||||
|
||||
isUpdatingUI = false;
|
||||
}
|
||||
|
||||
private boolean isStaff() {
|
||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||
}
|
||||
|
||||
private void notifyDateTimeStatusChange() {
|
||||
if (isUpdatingUI) return;
|
||||
|
||||
@@ -248,7 +261,12 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
|
||||
String date = binding.etAppointmentDate.getText().toString().trim();
|
||||
String time = buildTimeString();
|
||||
String status = binding.spinnerAppointmentStatus.getSelectedItem().toString().toUpperCase();
|
||||
Object selectedStatus = binding.spinnerAppointmentStatus.getSelectedItem();
|
||||
if (selectedStatus == null) {
|
||||
Toast.makeText(getContext(), "Please select a status", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
String status = selectedStatus.toString().toUpperCase();
|
||||
|
||||
if (!viewModel.isValidFutureBooking(status, date, time)) {
|
||||
DialogUtils.showInfoDialog(requireContext(), "Invalid Time", "Booked appointments must be in the future.");
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.navigation.Navigation;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.databinding.FragmentCouponDetailBinding;
|
||||
import com.example.petstoremobile.dtos.CouponDTO;
|
||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
@@ -95,6 +96,9 @@ public class CouponDetailFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void loadCouponDetails() {
|
||||
binding.tvCouponId.setText(DateTimeUtils.formatId(couponId));
|
||||
binding.tvCouponId.setVisibility(View.VISIBLE);
|
||||
|
||||
viewModel.loadCoupon(couponId).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
CouponDTO coupon = resource.data;
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import com.example.petstoremobile.databinding.FragmentCustomerDetailBinding;
|
||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.CustomerDetailViewModel;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class CustomerDetailFragment extends Fragment {
|
||||
|
||||
private FragmentCustomerDetailBinding binding;
|
||||
private CustomerDetailViewModel viewModel;
|
||||
|
||||
private final String[] STATUSES = {"Active", "Inactive"};
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
binding = FragmentCustomerDetailBinding.inflate(inflater, container, false);
|
||||
viewModel = new ViewModelProvider(this).get(CustomerDetailViewModel.class);
|
||||
|
||||
setupSpinners();
|
||||
handleArguments();
|
||||
|
||||
binding.btnCustomerBack.setOnClickListener(v -> navigateBack());
|
||||
binding.btnSaveCustomer.setOnClickListener(v -> save());
|
||||
binding.btnDeleteCustomer.setOnClickListener(v -> confirmDelete());
|
||||
|
||||
UIUtils.formatPhoneInput(binding.etCustomerPhone);
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void setupSpinners() {
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerCustomerStatus, STATUSES);
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
Bundle a = getArguments();
|
||||
if (a != null && a.getBoolean("isEditing", false)) {
|
||||
long customerId = a.getLong("customerId", -1);
|
||||
viewModel.setCustomerId(customerId, true);
|
||||
|
||||
binding.tvCustomerMode.setText("Edit Customer");
|
||||
binding.tvCustomerId.setText("ID: " + customerId);
|
||||
binding.tvCustomerId.setVisibility(View.VISIBLE);
|
||||
binding.btnDeleteCustomer.setVisibility(View.VISIBLE);
|
||||
|
||||
// Hide password field in edit mode
|
||||
binding.tvCustomerPasswordLabel.setVisibility(View.GONE);
|
||||
binding.etCustomerPassword.setVisibility(View.GONE);
|
||||
|
||||
// Show loyalty points
|
||||
binding.tvLoyaltyPointsLabel.setVisibility(View.VISIBLE);
|
||||
binding.tvCustomerLoyaltyPoints.setVisibility(View.VISIBLE);
|
||||
|
||||
loadCustomerData(customerId);
|
||||
} else {
|
||||
viewModel.setCustomerId(-1, false);
|
||||
binding.tvCustomerMode.setText("Add Customer");
|
||||
binding.btnDeleteCustomer.setVisibility(View.GONE);
|
||||
binding.tvCustomerId.setVisibility(View.GONE);
|
||||
binding.tvLoyaltyPointsLabel.setVisibility(View.GONE);
|
||||
binding.tvCustomerLoyaltyPoints.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadCustomerData(long id) {
|
||||
viewModel.loadCustomer(id).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
CustomerDTO c = resource.data;
|
||||
binding.etCustomerUsername.setText(c.getUsername() != null ? c.getUsername() : "");
|
||||
binding.etCustomerFirstName.setText(c.getFirstName() != null ? c.getFirstName() : "");
|
||||
binding.etCustomerLastName.setText(c.getLastName() != null ? c.getLastName() : "");
|
||||
binding.etCustomerEmail.setText(c.getEmail() != null ? c.getEmail() : "");
|
||||
binding.etCustomerPhone.setText(c.getPhone() != null ? c.getPhone() : "");
|
||||
binding.spinnerCustomerStatus.setSelection(Boolean.TRUE.equals(c.getActive()) ? 0 : 1);
|
||||
int pts = c.getLoyaltyPoints() != null ? c.getLoyaltyPoints() : 0;
|
||||
binding.tvCustomerLoyaltyPoints.setText(String.valueOf(pts));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void save() {
|
||||
if (!InputValidator.isNotEmpty(binding.etCustomerUsername, "Username")) return;
|
||||
|
||||
if (!viewModel.isEditing()) {
|
||||
if (!InputValidator.isNotEmpty(binding.etCustomerPassword, "Password")) return;
|
||||
String pass = binding.etCustomerPassword.getText().toString();
|
||||
if (pass.length() < 6) {
|
||||
binding.etCustomerPassword.setError("At least 6 characters");
|
||||
binding.etCustomerPassword.requestFocus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!InputValidator.isNotEmpty(binding.etCustomerFirstName, "First Name")) return;
|
||||
if (!InputValidator.isNotEmpty(binding.etCustomerLastName, "Last Name")) return;
|
||||
if (!InputValidator.isValidEmail(binding.etCustomerEmail)) return;
|
||||
if (!InputValidator.isValidPhone(binding.etCustomerPhone)) return;
|
||||
|
||||
String username = binding.etCustomerUsername.getText().toString().trim();
|
||||
String password = viewModel.isEditing() ? null : binding.etCustomerPassword.getText().toString().trim();
|
||||
String firstName = binding.etCustomerFirstName.getText().toString().trim();
|
||||
String lastName = binding.etCustomerLastName.getText().toString().trim();
|
||||
String email = binding.etCustomerEmail.getText().toString().trim();
|
||||
String phone = binding.etCustomerPhone.getText().toString().trim();
|
||||
boolean active = binding.spinnerCustomerStatus.getSelectedItemPosition() == 0;
|
||||
|
||||
CustomerDTO dto = new CustomerDTO(username, password, firstName, lastName, email, phone);
|
||||
dto.setFullName(firstName + " " + lastName);
|
||||
dto.setActive(active);
|
||||
|
||||
viewModel.saveCustomer(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(),
|
||||
viewModel.isEditing() ? "Updated successfully" : "Customer created",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void confirmDelete() {
|
||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Customer", () ->
|
||||
viewModel.deleteCustomer().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void navigateBack() {
|
||||
NavHostFragment.findNavController(this).popBackStack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
@@ -17,6 +18,7 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.api.auth.TokenManager;
|
||||
import com.example.petstoremobile.databinding.FragmentPetDetailBinding;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
@@ -29,8 +31,11 @@ import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.PetDetailViewModel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
/**
|
||||
@@ -43,6 +48,8 @@ public class PetDetailFragment extends Fragment {
|
||||
private PetDetailViewModel viewModel;
|
||||
private boolean isUpdatingUI = false;
|
||||
|
||||
@Inject TokenManager tokenManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -81,8 +88,19 @@ public class PetDetailFragment extends Fragment {
|
||||
|
||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
||||
PetDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||
Long selectedStoreId = state != null ? state.selectedStoreId : null;
|
||||
boolean staffCreating = isStaff() && (state == null || !state.isEditing);
|
||||
boolean storeEnabled = state == null || state.isStoreEnabled;
|
||||
Long selectedStoreId = (staffCreating && storeEnabled) ? tokenManager.getPrimaryStoreId() : (state != null ? state.selectedStoreId : null);
|
||||
updateStoreSpinnerSelection(selectedStoreId);
|
||||
if (staffCreating && storeEnabled) UIUtils.setViewsEnabled(false, binding.spinnerStore);
|
||||
});
|
||||
|
||||
viewModel.getSpeciesList().observe(getViewLifecycleOwner(), list -> {
|
||||
PetDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||
String selectedSpecies = state != null ? state.selectedSpecies : null;
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPetSpecies, list,
|
||||
DropdownDTO::getLabel, "-- Select Species --", null, DropdownDTO::getId);
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerPetSpecies, selectedSpecies);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,13 +118,16 @@ public class PetDetailFragment extends Fragment {
|
||||
|
||||
private void savePet() {
|
||||
if (!InputValidator.isNotEmpty(binding.etPetName, "Pet Name")) return;
|
||||
if (!InputValidator.isNotEmpty(binding.etPetSpecies, "Species")) return;
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerPetSpecies, "Species")) return;
|
||||
if (!InputValidator.isNotEmpty(binding.etPetBreed, "Breed")) return;
|
||||
if (!InputValidator.isPositiveInteger(binding.etPetAge, "Age")) return;
|
||||
if (!InputValidator.isPositiveDecimal(binding.etPetPrice, "Price")) return;
|
||||
|
||||
String name = binding.etPetName.getText().toString().trim();
|
||||
String species = binding.etPetSpecies.getText().toString().trim();
|
||||
List<DropdownDTO> speciesOptions = viewModel.getSpeciesList().getValue();
|
||||
String species = (speciesOptions != null && binding.spinnerPetSpecies.getSelectedItemPosition() > 0)
|
||||
? speciesOptions.get(binding.spinnerPetSpecies.getSelectedItemPosition() - 1).getLabel()
|
||||
: "";
|
||||
String breed = binding.etPetBreed.getText().toString().trim();
|
||||
int age = Integer.parseInt(binding.etPetAge.getText().toString().trim());
|
||||
double price = Double.parseDouble(binding.etPetPrice.getText().toString().trim());
|
||||
@@ -143,6 +164,20 @@ public class PetDetailFragment extends Fragment {
|
||||
petDTO.setCustomerId(customerId);
|
||||
petDTO.setStoreId(storeId);
|
||||
|
||||
boolean ownerChanged = !java.util.Objects.equals(viewModel.getOriginalCustomerId(), customerId);
|
||||
if (!isStaff() && viewModel.isEditing() && viewModel.isOriginallyOwnedOrAdopted() && ownerChanged) {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("Change Owner")
|
||||
.setMessage("Are you sure you want to change the owner of this pet?")
|
||||
.setPositiveButton("Yes", (d, w) -> performSave(petDTO, name))
|
||||
.setNegativeButton("Cancel", null)
|
||||
.show();
|
||||
} else {
|
||||
performSave(petDTO, name);
|
||||
}
|
||||
}
|
||||
|
||||
private void performSave(PetDTO petDTO, String name) {
|
||||
viewModel.savePet(petDTO).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
@@ -202,7 +237,6 @@ public class PetDetailFragment extends Fragment {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
PetDTO p = resource.data;
|
||||
binding.etPetName.setText(p.getPetName());
|
||||
binding.etPetSpecies.setText(p.getPetSpecies());
|
||||
binding.etPetBreed.setText(p.getPetBreed());
|
||||
binding.etPetAge.setText(String.valueOf(p.getPetAge()));
|
||||
if (p.getPetPrice() != null) {
|
||||
@@ -241,6 +275,11 @@ public class PetDetailFragment extends Fragment {
|
||||
private void setupSpinner() {
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus, new String[]{});
|
||||
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerPetSpecies, p -> {
|
||||
if (isUpdatingUI) return;
|
||||
viewModel.onSpeciesSelected(p);
|
||||
});
|
||||
|
||||
binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
@@ -290,7 +329,7 @@ public class PetDetailFragment extends Fragment {
|
||||
binding.btnDeletePet.setVisibility(state.isDeleteVisible ? View.VISIBLE : View.GONE);
|
||||
binding.btnSavePet.setText(state.saveButtonText);
|
||||
|
||||
UIUtils.setViewsEnabled(state.isSpeciesEnabled, binding.etPetSpecies);
|
||||
UIUtils.setViewsEnabled(state.isSpeciesEnabled, binding.spinnerPetSpecies);
|
||||
UIUtils.setViewsEnabled(state.isBreedEnabled, binding.etPetBreed);
|
||||
UIUtils.setViewsEnabled(state.isCustomerEnabled, binding.spinnerCustomer);
|
||||
UIUtils.setViewsEnabled(state.isStoreEnabled, binding.spinnerStore);
|
||||
@@ -301,6 +340,13 @@ public class PetDetailFragment extends Fragment {
|
||||
updateCustomerSpinnerSelection(state.selectedCustomerId);
|
||||
updateStoreSpinnerSelection(state.selectedStoreId);
|
||||
|
||||
List<DropdownDTO> species = viewModel.getSpeciesList().getValue();
|
||||
if (species != null) {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPetSpecies, species,
|
||||
DropdownDTO::getLabel, "-- Select Species --", null, DropdownDTO::getId);
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerPetSpecies, state.selectedSpecies);
|
||||
}
|
||||
|
||||
if (!state.isCustomerEnabled && binding.spinnerCustomer.getSelectedItemPosition() != 0) {
|
||||
binding.spinnerCustomer.setSelection(0);
|
||||
}
|
||||
@@ -308,9 +354,22 @@ public class PetDetailFragment extends Fragment {
|
||||
binding.spinnerStore.setSelection(0);
|
||||
}
|
||||
|
||||
if (isStaff() && !state.isEditing && state.isStoreEnabled) {
|
||||
updateStoreSpinnerSelection(tokenManager.getPrimaryStoreId());
|
||||
UIUtils.setViewsEnabled(false, binding.spinnerStore);
|
||||
}
|
||||
|
||||
if (isStaff() && state.isEditing && viewModel.isOriginallyOwnedOrAdopted()) {
|
||||
UIUtils.setViewsEnabled(false, binding.spinnerCustomer, binding.spinnerPetStatus, binding.spinnerStore);
|
||||
}
|
||||
|
||||
isUpdatingUI = false;
|
||||
}
|
||||
|
||||
private boolean isStaff() {
|
||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||
}
|
||||
|
||||
private void clearSpinnerError(Spinner spinner) {
|
||||
View selectedView = spinner.getSelectedView();
|
||||
if (selectedView instanceof TextView) {
|
||||
|
||||
@@ -17,6 +17,10 @@ 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.api.auth.TokenManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
@@ -27,6 +31,8 @@ public class SaleDetailFragment extends Fragment {
|
||||
private FragmentSaleDetailBinding binding;
|
||||
private SaleDetailViewModel viewModel;
|
||||
|
||||
@Inject TokenManager tokenManager;
|
||||
|
||||
private final String[] PAYMENT_METHODS = { "Cash", "Card"};
|
||||
|
||||
@Override
|
||||
@@ -42,11 +48,13 @@ public class SaleDetailFragment extends Fragment {
|
||||
|
||||
if (viewModel.isViewOnly()) {
|
||||
binding.llAddItemRow.setVisibility(View.GONE);
|
||||
binding.llCouponInput.setVisibility(View.GONE);
|
||||
binding.btnSaveSale.setVisibility(View.GONE);
|
||||
UIUtils.setViewsEnabled(false, binding.spinnerSaleStore, binding.spinnerSaleCustomer, binding.spinnerPaymentMethod);
|
||||
} else {
|
||||
loadData();
|
||||
setupAddItem();
|
||||
setupCoupon();
|
||||
}
|
||||
|
||||
binding.btnSaleBack.setOnClickListener(v -> navigateBack());
|
||||
@@ -56,10 +64,18 @@ public class SaleDetailFragment extends Fragment {
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private boolean isStaff() {
|
||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
||||
Long selectedStoreId = isStaff() ? tokenManager.getPrimaryStoreId() : -1L;
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleStore, list,
|
||||
DropdownDTO::getLabel, "-- Select Store --", -1L, DropdownDTO::getId);
|
||||
DropdownDTO::getLabel, "-- Select Store --", selectedStoreId, DropdownDTO::getId);
|
||||
if (isStaff()) {
|
||||
binding.spinnerSaleStore.setEnabled(false);
|
||||
}
|
||||
});
|
||||
|
||||
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> {
|
||||
@@ -185,13 +201,77 @@ public class SaleDetailFragment extends Fragment {
|
||||
if (sale.getItems() != null) {
|
||||
binding.llSaleItems.removeAllViews();
|
||||
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
|
||||
addItemRow(item.getProductName(), Math.abs(item.getQuantity()), item.getUnitPrice());
|
||||
addItemRow(item.getProductName(), Math.abs(item.getQuantity()), item.getUnitPrice(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupCoupon() {
|
||||
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();
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
viewModel.lookupCoupon(code).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
CouponDTO coupon = resource.data;
|
||||
if (Boolean.FALSE.equals(coupon.getActive())) {
|
||||
showCouponError("This coupon is no longer active.");
|
||||
return;
|
||||
}
|
||||
if (coupon.getMinOrderAmount() != null &&
|
||||
viewModel.calculateSubtotal().compareTo(coupon.getMinOrderAmount()) < 0) {
|
||||
showCouponError("Minimum order of $" + coupon.getMinOrderAmount() + " required.");
|
||||
return;
|
||||
}
|
||||
viewModel.setAppliedCoupon(coupon);
|
||||
applyAppliedCouponUI(coupon);
|
||||
updateTotal();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
showCouponError("Invalid coupon code.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
binding.btnClearCoupon.setOnClickListener(v -> {
|
||||
viewModel.clearCoupon();
|
||||
binding.etCouponCode.setText("");
|
||||
binding.tvCouponInfo.setVisibility(View.GONE);
|
||||
binding.btnClearCoupon.setVisibility(View.GONE);
|
||||
binding.btnApplyCoupon.setVisibility(View.VISIBLE);
|
||||
binding.etCouponCode.setEnabled(true);
|
||||
binding.llCouponDiscount.setVisibility(View.GONE);
|
||||
updateTotal();
|
||||
});
|
||||
}
|
||||
|
||||
private void applyAppliedCouponUI(CouponDTO coupon) {
|
||||
String info;
|
||||
if ("PERCENTAGE".equalsIgnoreCase(coupon.getDiscountType())) {
|
||||
info = coupon.getDiscountValue().stripTrailingZeros().toPlainString() + "% off applied";
|
||||
} else {
|
||||
info = "$" + coupon.getDiscountValue() + " off applied";
|
||||
}
|
||||
binding.tvCouponInfo.setText(info);
|
||||
binding.tvCouponInfo.setTextColor(0xFF4CAF50);
|
||||
binding.tvCouponInfo.setVisibility(View.VISIBLE);
|
||||
binding.btnClearCoupon.setVisibility(View.VISIBLE);
|
||||
binding.btnApplyCoupon.setVisibility(View.GONE);
|
||||
binding.etCouponCode.setEnabled(false);
|
||||
}
|
||||
|
||||
private void showCouponError(String message) {
|
||||
binding.tvCouponInfo.setText(message);
|
||||
binding.tvCouponInfo.setTextColor(0xFFE53935);
|
||||
binding.tvCouponInfo.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void setupAddItem() {
|
||||
binding.btnAddItem.setOnClickListener(v -> {
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerSaleProduct, "Product")) return;
|
||||
@@ -228,15 +308,16 @@ public class SaleDetailFragment extends Fragment {
|
||||
break;
|
||||
}
|
||||
}
|
||||
addItemRow(name, item.getQuantity(), price);
|
||||
addItemRow(name, item.getQuantity(), price, item.getProdId());
|
||||
}
|
||||
}
|
||||
|
||||
private void addItemRow(String name, int qty, BigDecimal price) {
|
||||
private void addItemRow(String name, int qty, BigDecimal price, Long prodId) {
|
||||
if (getContext() == null) return;
|
||||
LinearLayout row = new LinearLayout(getContext());
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
row.setPadding(0, 8, 0, 8);
|
||||
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
|
||||
|
||||
TextView tvName = new TextView(getContext());
|
||||
tvName.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f));
|
||||
@@ -253,12 +334,34 @@ public class SaleDetailFragment extends Fragment {
|
||||
row.addView(tvName);
|
||||
row.addView(tvQty);
|
||||
row.addView(tvPrice);
|
||||
|
||||
if (prodId != null) {
|
||||
Button btnRemove = new Button(getContext());
|
||||
btnRemove.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||
btnRemove.setText("✕");
|
||||
btnRemove.setTextSize(12f);
|
||||
btnRemove.setBackgroundTintList(android.content.res.ColorStateList.valueOf(0xFFE53935));
|
||||
btnRemove.setTextColor(0xFFFFFFFF);
|
||||
btnRemove.setPadding(16, 4, 16, 4);
|
||||
btnRemove.setOnClickListener(v -> viewModel.removeFromCart(prodId));
|
||||
row.addView(btnRemove);
|
||||
}
|
||||
|
||||
binding.llSaleItems.addView(row);
|
||||
}
|
||||
|
||||
private void updateTotal() {
|
||||
BigDecimal total = viewModel.calculateSubtotal();
|
||||
binding.tvSaleSubtotal.setText("$" + total);
|
||||
BigDecimal subtotal = viewModel.calculateSubtotal();
|
||||
BigDecimal discount = viewModel.calculateDiscount();
|
||||
BigDecimal total = subtotal.subtract(discount);
|
||||
binding.tvSaleSubtotal.setText("$" + subtotal);
|
||||
if (discount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
binding.llCouponDiscount.setVisibility(View.VISIBLE);
|
||||
binding.tvSaleCouponDiscount.setText("-$" + discount);
|
||||
} else {
|
||||
binding.llCouponDiscount.setVisibility(View.GONE);
|
||||
}
|
||||
binding.tvSaleDetailTotal.setText("Total: $" + total);
|
||||
}
|
||||
|
||||
@@ -279,6 +382,7 @@ public class SaleDetailFragment extends Fragment {
|
||||
}
|
||||
|
||||
SaleDTO dto = new SaleDTO(store.getId(), payment, viewModel.getCartItems().getValue(), false, null, customerId);
|
||||
dto.setCouponId(viewModel.getAppliedCouponId());
|
||||
|
||||
viewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
|
||||
@@ -24,8 +24,8 @@ public class AdoptionRepository extends BaseRepository {
|
||||
/**
|
||||
* Retrieves a paginated list of all adoptions from the API.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<AdoptionDTO>>> getAllAdoptions(int page, int size, String query, String status, Long storeId, String date, Long employeeId) {
|
||||
return executeCall(adoptionApi.getAllAdoptions(page, size, query, status, storeId, date, employeeId));
|
||||
public LiveData<Resource<PageResponse<AdoptionDTO>>> getAllAdoptions(int page, int size, String query, String status, Long storeId, String date, Long employeeId, String sort) {
|
||||
return executeCall(adoptionApi.getAllAdoptions(page, size, query, status, storeId, date, employeeId, sort));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,8 +24,8 @@ public class AppointmentRepository extends BaseRepository {
|
||||
/**
|
||||
* Retrieves a paginated list of all appointments from the API with filtering.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<AppointmentDTO>>> getAllAppointments(int page, int size, String query, String status, Long storeId, String date, Long employeeId) {
|
||||
return executeCall(appointmentApi.getAllAppointments(page, size, query, status, storeId, date, employeeId));
|
||||
public LiveData<Resource<PageResponse<AppointmentDTO>>> getAllAppointments(int page, int size, String query, String status, Long storeId, String date, Long employeeId, String sort) {
|
||||
return executeCall(appointmentApi.getAllAppointments(page, size, query, status, storeId, date, employeeId, sort));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,10 @@ public class CouponRepository extends BaseRepository {
|
||||
return executeCall(couponApi.getCouponById(id));
|
||||
}
|
||||
|
||||
public LiveData<Resource<CouponDTO>> getCouponByCode(String code) {
|
||||
return executeCall(couponApi.getCouponByCode(code));
|
||||
}
|
||||
|
||||
public LiveData<Resource<CouponDTO>> createCoupon(CouponDTO coupon) {
|
||||
return executeCall(couponApi.createCoupon(coupon));
|
||||
}
|
||||
|
||||
@@ -23,23 +23,26 @@ public class CustomerRepository extends BaseRepository {
|
||||
this.customerApi = customerApi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a paginated list of all customers from the API.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<CustomerDTO>>> getAllCustomers(int page, int size) {
|
||||
return executeCall(customerApi.getAllCustomers(page, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a specific customer by their ID.
|
||||
*/
|
||||
public LiveData<Resource<CustomerDTO>> getCustomerById(Long id) {
|
||||
return executeCall(customerApi.getCustomerById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of customer dropdowns from the API.
|
||||
*/
|
||||
public LiveData<Resource<CustomerDTO>> createCustomer(CustomerDTO dto) {
|
||||
return executeCall(customerApi.registerCustomer(dto));
|
||||
}
|
||||
|
||||
public LiveData<Resource<CustomerDTO>> updateCustomer(Long id, CustomerDTO dto) {
|
||||
return executeCall(customerApi.updateCustomer(id, dto));
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> deleteCustomer(Long id) {
|
||||
return executeCall(customerApi.deleteCustomer(id));
|
||||
}
|
||||
|
||||
public LiveData<Resource<List<DropdownDTO>>> getCustomerDropdowns() {
|
||||
return executeCall(customerApi.getCustomerDropdowns());
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ public class PetRepository extends BaseRepository {
|
||||
return executeCall(petApi.getPetDropdowns());
|
||||
}
|
||||
|
||||
public LiveData<Resource<List<DropdownDTO>>> getPetSpeciesDropdowns() {
|
||||
return executeCall(petApi.getPetSpeciesDropdowns());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves available pets for a specific store.
|
||||
*/
|
||||
|
||||
@@ -13,6 +13,8 @@ import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -71,18 +73,17 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
state.saveButtonText = isEditing ? "Save" : "Add";
|
||||
state.isAdoptionIdVisible = isEditing;
|
||||
state.isDeleteVisible = isEditing;
|
||||
state.isFeeEnabled = false; // fee is always read-only
|
||||
state.isFeeEnabled = false;
|
||||
if (!isEditing) {
|
||||
state.isCustomerEnabled = true;
|
||||
state.isStoreEnabled = true;
|
||||
state.isPetEnabled = false; // until customer selected
|
||||
state.isEmployeeEnabled = false; // until store selected
|
||||
state.isPetEnabled = false;
|
||||
state.isEmployeeEnabled = false;
|
||||
state.isDateEnabled = true;
|
||||
state.isStatusEnabled = true;
|
||||
state.availableStatuses = new String[]{"Pending"};
|
||||
state.selectedStatus = "Pending";
|
||||
} else {
|
||||
// edit: date-based logic applied after load
|
||||
state.isCustomerEnabled = false;
|
||||
state.isStoreEnabled = false;
|
||||
state.isPetEnabled = false;
|
||||
@@ -95,12 +96,12 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
|
||||
public void loadInitialFormData(boolean isEditing) {
|
||||
// Pets are loaded dynamically based on store selection; no pre-load needed.
|
||||
customerRepository.getCustomerDropdowns().observeForever(r -> {
|
||||
observeOnce(customerRepository.getCustomerDropdowns(), r -> {
|
||||
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||
customerList.setValue(r.data);
|
||||
}
|
||||
});
|
||||
storeRepository.getStoreDropdowns().observeForever(r -> {
|
||||
observeOnce(storeRepository.getStoreDropdowns(), r -> {
|
||||
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||
storeList.setValue(r.data);
|
||||
}
|
||||
@@ -155,7 +156,7 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
private void loadAvailablePetsByStore(Long storeId) {
|
||||
petRepository.getAvailablePetsByStore(storeId).observeForever(r -> {
|
||||
observeOnce(petRepository.getAvailablePetsByStore(storeId), r -> {
|
||||
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||
List<DropdownDTO> dropdowns = new ArrayList<>();
|
||||
for (com.example.petstoremobile.dtos.PetDTO pet : r.data.getContent()) {
|
||||
@@ -167,7 +168,7 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
private void loadPetPrice(Long petId) {
|
||||
petRepository.getPetById(petId).observeForever(r -> {
|
||||
observeOnce(petRepository.getPetById(petId), r -> {
|
||||
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||
com.example.petstoremobile.dtos.PetDTO pet = r.data;
|
||||
// In edit mode, add the pet to the list so the spinner can display its name
|
||||
@@ -185,7 +186,7 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
private void loadEmployeesForStore(Long storeId) {
|
||||
storeRepository.getStoreEmployees(storeId).observeForever(r -> {
|
||||
observeOnce(storeRepository.getStoreEmployees(storeId), r -> {
|
||||
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||
employeeList.setValue(r.data);
|
||||
}
|
||||
@@ -217,6 +218,10 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
s.isEmployeeEnabled = true;
|
||||
s.isDateEnabled = true;
|
||||
s.isStatusEnabled = true;
|
||||
} else {
|
||||
// Date cleared: disable everything except the date field so user can pick again
|
||||
setAllEditableFieldsEnabled(s, false);
|
||||
s.isDateEnabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -240,7 +245,7 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
|
||||
public LiveData<Resource<AdoptionDTO>> loadAdoption() {
|
||||
MutableLiveData<Resource<AdoptionDTO>> result = new MutableLiveData<>();
|
||||
adoptionRepository.getAdoptionById(adoptionId).observeForever(resource -> {
|
||||
observeOnce(adoptionRepository.getAdoptionById(adoptionId), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
AdoptionDTO a = resource.data;
|
||||
String formattedStatus = DateTimeUtils.formatStatusFromBackend(
|
||||
@@ -248,10 +253,10 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
String adoptionDate = a.getAdoptionDate() != null ? a.getAdoptionDate() : "";
|
||||
|
||||
updateViewState(state -> {
|
||||
state.selectedPetId = a.getPetId() != null ? a.getPetId() : -1;
|
||||
state.selectedCustomerId = a.getCustomerId() != null ? a.getCustomerId() : -1;
|
||||
state.selectedStoreId = a.getSourceStoreId() != null ? a.getSourceStoreId() : -1;
|
||||
state.selectedEmployeeId = a.getEmployeeId() != null ? a.getEmployeeId() : -1;
|
||||
state.selectedPetId = a.getPetId();
|
||||
state.selectedCustomerId = a.getCustomerId();
|
||||
state.selectedStoreId = a.getSourceStoreId();
|
||||
state.selectedEmployeeId = a.getEmployeeId();
|
||||
state.adoptionDate = adoptionDate;
|
||||
state.adoptionFee = a.getAdoptionFee() != null ? a.getAdoptionFee().toPlainString() : "";
|
||||
state.selectedStatus = formattedStatus;
|
||||
@@ -321,6 +326,21 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
void run(T target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Observes a LiveData once, removing the observer after the first response.
|
||||
* */
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static class ViewState {
|
||||
public boolean isEditing = false;
|
||||
public boolean isAdoptionIdVisible = false;
|
||||
|
||||
@@ -11,6 +11,8 @@ import com.example.petstoremobile.repositories.AdoptionRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -53,7 +55,7 @@ public class AdoptionListViewModel extends ViewModel {
|
||||
if ("All Statuses".equals(status)) status = null;
|
||||
|
||||
isLoading.setValue(true);
|
||||
adoptionRepository.getAllAdoptions(currentPage, PAGE_SIZE, query, status, storeId, date, employeeId).observeForever(resource -> {
|
||||
observeOnce(adoptionRepository.getAllAdoptions(currentPage, PAGE_SIZE, query, status, storeId, date, employeeId, "adoptionId,desc"), resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
List<AdoptionDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(adoptions.getValue());
|
||||
@@ -70,13 +72,25 @@ public class AdoptionListViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public void loadStores() {
|
||||
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||
observeOnce(storeRepository.getAllStores(0, 100), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
stores.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeleteAdoptions(List<String> ids) {
|
||||
return adoptionRepository.bulkDeleteAdoptions(new BulkDeleteRequest(ids));
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import com.example.petstoremobile.repositories.SaleRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
@@ -49,7 +51,7 @@ public class AnalyticsViewModel extends ViewModel {
|
||||
public void loadAnalytics() {
|
||||
isLoading.setValue(true);
|
||||
errorMessage.setValue(null);
|
||||
saleRepository.getAllSales(0, 2000, null, null, null, null, "saleDate,desc").observeForever(resource -> {
|
||||
observeOnce(saleRepository.getAllSales(0, 2000, null, null, null, null, "saleDate,desc"), resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
cachedSales = resource.data.getContent();
|
||||
@@ -247,6 +249,18 @@ public class AnalyticsViewModel extends ViewModel {
|
||||
return "Daily Revenue (" + s + " – " + e + ")";
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static class FilterState {
|
||||
public String startDate = "";
|
||||
public String endDate = "";
|
||||
|
||||
@@ -15,6 +15,8 @@ import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -42,6 +44,7 @@ public class AppointmentDetailViewModel extends ViewModel {
|
||||
|
||||
private long appointmentId = -1;
|
||||
private boolean isOriginallyCancel = false;
|
||||
private boolean isOriginallyCompletedOrMissed = false;
|
||||
private Long currentCustomerId;
|
||||
private Long currentStoreId;
|
||||
private Long currentPetId;
|
||||
@@ -71,14 +74,14 @@ public class AppointmentDetailViewModel extends ViewModel {
|
||||
* Loads initial dropdown data for customers, stores, and services.
|
||||
*/
|
||||
public void loadInitialFormData() {
|
||||
customerRepository.getCustomerDropdowns().observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) customers.setValue(r.data);
|
||||
observeOnce(customerRepository.getCustomerDropdowns(), r -> {
|
||||
if (r != null && r.status == Resource.Status.SUCCESS) customers.setValue(r.data);
|
||||
});
|
||||
storeRepository.getStoreDropdowns().observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) stores.setValue(r.data);
|
||||
observeOnce(storeRepository.getStoreDropdowns(), r -> {
|
||||
if (r != null && r.status == Resource.Status.SUCCESS) stores.setValue(r.data);
|
||||
});
|
||||
serviceRepository.getAllServices(0, 200, null, "serviceName").observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS && r.data != null) services.setValue(r.data.getContent());
|
||||
observeOnce(serviceRepository.getAllServices(0, 200, null, "serviceName"), r -> {
|
||||
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) services.setValue(r.data.getContent());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -206,8 +209,8 @@ public class AppointmentDetailViewModel extends ViewModel {
|
||||
* Loads the list of pets for a specific customer.
|
||||
*/
|
||||
private void loadPetsForCustomer(Long customerId) {
|
||||
petRepository.getCustomerPets(customerId).observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) customerPets.setValue(r.data);
|
||||
observeOnce(petRepository.getCustomerPets(customerId), r -> {
|
||||
if (r != null && r.status == Resource.Status.SUCCESS) customerPets.setValue(r.data);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -215,8 +218,8 @@ public class AppointmentDetailViewModel extends ViewModel {
|
||||
* Loads the list of employees for a specific store.
|
||||
*/
|
||||
private void loadEmployeesForStore(Long storeId) {
|
||||
storeRepository.getStoreEmployees(storeId).observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) storeEmployees.setValue(r.data);
|
||||
observeOnce(storeRepository.getStoreEmployees(storeId), r -> {
|
||||
if (r != null && r.status == Resource.Status.SUCCESS) storeEmployees.setValue(r.data);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -227,10 +230,12 @@ public class AppointmentDetailViewModel extends ViewModel {
|
||||
*/
|
||||
public LiveData<Resource<AppointmentDTO>> loadAppointment() {
|
||||
MutableLiveData<Resource<AppointmentDTO>> result = new MutableLiveData<>();
|
||||
repository.getAppointmentById(appointmentId).observeForever(resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
observeOnce(repository.getAppointmentById(appointmentId), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
AppointmentDTO a = resource.data;
|
||||
isOriginallyCancel = "CANCELLED".equalsIgnoreCase(a.getAppointmentStatus());
|
||||
isOriginallyCompletedOrMissed = "COMPLETED".equalsIgnoreCase(a.getAppointmentStatus())
|
||||
|| "MISSED".equalsIgnoreCase(a.getAppointmentStatus());
|
||||
currentCustomerId = a.getCustomerId();
|
||||
currentStoreId = a.getStoreId();
|
||||
currentPetId = a.getPetId();
|
||||
@@ -282,21 +287,18 @@ public class AppointmentDetailViewModel extends ViewModel {
|
||||
public void onDateOrTimeChanged(String date, String time, String currentStatus) {
|
||||
updateViewState(s -> {
|
||||
s.availableStatuses = calculateAvailableStatuses(s.isEditing, date, time, currentStatus);
|
||||
// Keep selectedStatus if still valid; prefer explicit currentStatus from UI if valid
|
||||
java.util.List<String> available = java.util.Arrays.asList(s.availableStatuses);
|
||||
if (!currentStatus.isEmpty() && available.contains(currentStatus)) {
|
||||
s.selectedStatus = currentStatus;
|
||||
} else if (!available.contains(s.selectedStatus) && s.availableStatuses.length > 0) {
|
||||
s.selectedStatus = s.availableStatuses[0];
|
||||
}
|
||||
boolean isPast = DateTimeUtils.isDateTimeInPast(date, time);
|
||||
|
||||
if (isOriginallyCancel) {
|
||||
s.isPast = true;
|
||||
setAllFieldsEnabled(s, false);
|
||||
s.isStatusEnabled = false;
|
||||
s.isSaveVisible = false;
|
||||
} else if (isPast) {
|
||||
} else if (isOriginallyCompletedOrMissed) {
|
||||
s.isPast = true;
|
||||
setAllFieldsEnabled(s, false);
|
||||
s.isStatusEnabled = true;
|
||||
@@ -322,7 +324,7 @@ public class AppointmentDetailViewModel extends ViewModel {
|
||||
if (!isEditing) return new String[]{"Booked"};
|
||||
if (date == null || date.isEmpty()) return new String[]{};
|
||||
if (isOriginallyCancel) return new String[]{"Cancelled"};
|
||||
if (DateTimeUtils.isDateTimeInPast(date, currentTime)) return new String[]{"Completed", "Missed"};
|
||||
if (isOriginallyCompletedOrMissed) return new String[]{"Completed", "Missed"};
|
||||
return new String[]{"Booked", "Cancelled"};
|
||||
}
|
||||
|
||||
@@ -355,8 +357,8 @@ public class AppointmentDetailViewModel extends ViewModel {
|
||||
s.isCustomerEnabled = true;
|
||||
s.isStoreEnabled = true;
|
||||
s.isServiceEnabled = true;
|
||||
s.isPetEnabled = false; // until customer selected
|
||||
s.isStaffEnabled = false; // until store selected
|
||||
s.isPetEnabled = false;
|
||||
s.isStaffEnabled = false;
|
||||
s.availableStatuses = new String[]{"Booked"};
|
||||
s.selectedStatus = "Booked";
|
||||
}
|
||||
@@ -385,6 +387,19 @@ public class AppointmentDetailViewModel extends ViewModel {
|
||||
void run(T t);
|
||||
}
|
||||
|
||||
/** Observes a LiveData once, removing the observer after the first non-loading response. */
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A Class to show the states of Appointment Detail Fragment.
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,8 @@ import com.example.petstoremobile.repositories.AppointmentRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -39,7 +41,7 @@ public class AppointmentListViewModel extends ViewModel {
|
||||
|
||||
public void loadAppointments(String query, String status, Long storeId, String date, Long employeeId) {
|
||||
isLoading.setValue(true);
|
||||
appointmentRepository.getAllAppointments(0, 500, query, status, storeId, date, employeeId).observeForever(resource -> {
|
||||
observeOnce(appointmentRepository.getAllAppointments(0, 500, query, status, storeId, date, employeeId, "appointmentId,desc"), resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
appointments.setValue(resource.data.getContent());
|
||||
@@ -52,13 +54,25 @@ public class AppointmentListViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public void loadStores() {
|
||||
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||
observeOnce(storeRepository.getAllStores(0, 100), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
stores.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeleteAppointments(List<String> ids) {
|
||||
return appointmentRepository.bulkDeleteAppointments(new BulkDeleteRequest(ids));
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.repositories.CouponRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -32,7 +34,7 @@ public class CouponListViewModel extends ViewModel {
|
||||
|
||||
public void loadCoupons(int page, int size, Boolean active, String discountType, String sort) {
|
||||
isLoading.setValue(true);
|
||||
repository.getAllCoupons(page, size, active, discountType, sort).observeForever(resource -> {
|
||||
observeOnce(repository.getAllCoupons(page, size, active, discountType, sort), resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
coupons.setValue(resource.data.getContent());
|
||||
@@ -44,6 +46,18 @@ public class CouponListViewModel extends ViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeleteCoupons(List<Long> ids) {
|
||||
return repository.bulkDeleteCoupons(ids);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class CustomerDetailViewModel extends ViewModel {
|
||||
private final CustomerRepository repository;
|
||||
|
||||
private long customerId = -1;
|
||||
private boolean isEditing = false;
|
||||
|
||||
@Inject
|
||||
public CustomerDetailViewModel(CustomerRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public void setCustomerId(long id, boolean isEditing) {
|
||||
this.customerId = id;
|
||||
this.isEditing = isEditing;
|
||||
}
|
||||
|
||||
public long getCustomerId() { return customerId; }
|
||||
public boolean isEditing() { return isEditing; }
|
||||
|
||||
public LiveData<Resource<CustomerDTO>> loadCustomer(long id) {
|
||||
return repository.getCustomerById(id);
|
||||
}
|
||||
|
||||
public LiveData<Resource<CustomerDTO>> saveCustomer(CustomerDTO dto) {
|
||||
if (isEditing && customerId > 0) {
|
||||
return repository.updateCustomer(customerId, dto);
|
||||
} else {
|
||||
return repository.createCustomer(dto);
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> deleteCustomer() {
|
||||
return repository.deleteCustomer(customerId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class CustomerListViewModel extends ViewModel {
|
||||
private final CustomerRepository repository;
|
||||
|
||||
private final MutableLiveData<List<CustomerDTO>> customers = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<CustomerDTO>> filteredCustomers = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
private String lastQuery = "";
|
||||
private String lastStatus = "All Statuses";
|
||||
|
||||
@Inject
|
||||
public CustomerListViewModel(CustomerRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public LiveData<List<CustomerDTO>> getFilteredCustomers() { return filteredCustomers; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
|
||||
public void loadCustomers() {
|
||||
isLoading.setValue(true);
|
||||
observeOnce(repository.getAllCustomers(0, 200), resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
customers.setValue(resource.data.getContent());
|
||||
filter(lastQuery, lastStatus);
|
||||
isLoading.setValue(false);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void filter(String query, String status) {
|
||||
this.lastQuery = query;
|
||||
this.lastStatus = status;
|
||||
|
||||
List<CustomerDTO> all = customers.getValue();
|
||||
if (all == null) return;
|
||||
|
||||
List<CustomerDTO> filtered = new ArrayList<>();
|
||||
String lowerQuery = query.toLowerCase();
|
||||
|
||||
for (CustomerDTO c : all) {
|
||||
boolean matchesQuery = query.isEmpty() ||
|
||||
(c.getFullName() != null && c.getFullName().toLowerCase().contains(lowerQuery)) ||
|
||||
(c.getUsername() != null && c.getUsername().toLowerCase().contains(lowerQuery)) ||
|
||||
(c.getEmail() != null && c.getEmail().toLowerCase().contains(lowerQuery)) ||
|
||||
(c.getPhone() != null && c.getPhone().toLowerCase().contains(lowerQuery));
|
||||
|
||||
boolean matchesStatus = status.equals("All Statuses") ||
|
||||
(status.equals("Active") && Boolean.TRUE.equals(c.getActive())) ||
|
||||
(status.equals("Inactive") && Boolean.FALSE.equals(c.getActive()));
|
||||
|
||||
if (matchesQuery && matchesStatus) {
|
||||
filtered.add(c);
|
||||
}
|
||||
}
|
||||
filteredCustomers.setValue(filtered);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import com.example.petstoremobile.repositories.InventoryRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -51,7 +53,7 @@ public class InventoryListViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
isLoading.setValue(true);
|
||||
inventoryRepository.getAllInventory(query, storeId, currentPage, PAGE_SIZE, "product.prodName").observeForever(resource -> {
|
||||
observeOnce(inventoryRepository.getAllInventory(query, storeId, currentPage, PAGE_SIZE, "product.prodName"), resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
List<InventoryDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(inventory.getValue());
|
||||
@@ -68,13 +70,25 @@ public class InventoryListViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public void loadStores() {
|
||||
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||
observeOnce(storeRepository.getAllStores(0, 100), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
stores.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeleteInventory(List<String> ids) {
|
||||
return inventoryRepository.bulkDeleteInventory(new BulkDeleteRequest(ids));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import com.example.petstoremobile.repositories.PetRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -30,12 +32,16 @@ public class PetDetailViewModel extends ViewModel {
|
||||
|
||||
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>> speciesList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
||||
|
||||
private long petId = -1;
|
||||
private Long selectedCustomerId = null;
|
||||
private Long selectedStoreId = null;
|
||||
private String selectedSpecies = null;
|
||||
private boolean isOriginallyOwnedOrAdopted = false;
|
||||
private Long originalCustomerId = null;
|
||||
|
||||
@Inject
|
||||
public PetDetailViewModel(PetRepository petRepository, CustomerRepository customerRepository, StoreRepository storeRepository) {
|
||||
@@ -45,17 +51,23 @@ public class PetDetailViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public void loadInitialFormData() {
|
||||
customerRepository.getCustomerDropdowns().observeForever(resource -> {
|
||||
observeOnce(customerRepository.getCustomerDropdowns(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
customerList.setValue(resource.data);
|
||||
}
|
||||
});
|
||||
|
||||
storeRepository.getStoreDropdowns().observeForever(resource -> {
|
||||
observeOnce(storeRepository.getStoreDropdowns(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
storeList.setValue(resource.data);
|
||||
}
|
||||
});
|
||||
|
||||
observeOnce(petRepository.getPetSpeciesDropdowns(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
speciesList.setValue(resource.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setPetId(long id) {
|
||||
@@ -72,6 +84,14 @@ public class PetDetailViewModel extends ViewModel {
|
||||
return current != null && current.isEditing;
|
||||
}
|
||||
|
||||
public boolean isOriginallyOwnedOrAdopted() {
|
||||
return isOriginallyOwnedOrAdopted;
|
||||
}
|
||||
|
||||
public Long getOriginalCustomerId() {
|
||||
return originalCustomerId;
|
||||
}
|
||||
|
||||
public LiveData<ViewState> getViewState() {
|
||||
return viewState;
|
||||
}
|
||||
@@ -87,6 +107,16 @@ public class PetDetailViewModel extends ViewModel {
|
||||
updateViewState(state -> state.selectedCustomerId = selectedCustomerId);
|
||||
}
|
||||
|
||||
public void onSpeciesSelected(int position) {
|
||||
List<DropdownDTO> list = speciesList.getValue();
|
||||
if (position > 0 && list != null && position <= list.size()) {
|
||||
selectedSpecies = list.get(position - 1).getLabel();
|
||||
} else {
|
||||
selectedSpecies = null;
|
||||
}
|
||||
updateViewState(state -> state.selectedSpecies = selectedSpecies);
|
||||
}
|
||||
|
||||
public void onStoreSelected(int position) {
|
||||
List<DropdownDTO> list = storeList.getValue();
|
||||
if (position > 0 && list != null && position <= list.size()) {
|
||||
@@ -123,8 +153,10 @@ public class PetDetailViewModel extends ViewModel {
|
||||
if (!isEditing) {
|
||||
selectedCustomerId = null;
|
||||
selectedStoreId = null;
|
||||
selectedSpecies = null;
|
||||
state.selectedCustomerId = null;
|
||||
state.selectedStoreId = null;
|
||||
state.selectedSpecies = null;
|
||||
state.selectedStatus = STATUS_AVAILABLE;
|
||||
state.isCustomerEnabled = false;
|
||||
state.isStoreEnabled = true;
|
||||
@@ -134,15 +166,20 @@ public class PetDetailViewModel extends ViewModel {
|
||||
|
||||
public LiveData<Resource<PetDTO>> loadPet() {
|
||||
MutableLiveData<Resource<PetDTO>> result = new MutableLiveData<>();
|
||||
petRepository.getPetById(petId).observeForever(resource -> {
|
||||
observeOnce(petRepository.getPetById(petId), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
PetDTO pet = resource.data;
|
||||
selectedCustomerId = pet.getCustomerId();
|
||||
selectedStoreId = pet.getStoreId();
|
||||
selectedSpecies = pet.getPetSpecies();
|
||||
isOriginallyOwnedOrAdopted = STATUS_OWNED.equalsIgnoreCase(pet.getPetStatus())
|
||||
|| STATUS_ADOPTED.equalsIgnoreCase(pet.getPetStatus());
|
||||
originalCustomerId = pet.getCustomerId();
|
||||
|
||||
updateViewState(state -> {
|
||||
state.selectedCustomerId = selectedCustomerId;
|
||||
state.selectedStoreId = selectedStoreId;
|
||||
state.selectedSpecies = selectedSpecies;
|
||||
state.selectedStatus = normalizeStatus(pet.getPetStatus());
|
||||
applyStatusRules(state, false);
|
||||
});
|
||||
@@ -174,6 +211,10 @@ public class PetDetailViewModel extends ViewModel {
|
||||
return storeList;
|
||||
}
|
||||
|
||||
public LiveData<List<DropdownDTO>> getSpeciesList() {
|
||||
return speciesList;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getIsLoading() {
|
||||
return isLoading;
|
||||
}
|
||||
@@ -227,6 +268,17 @@ public class PetDetailViewModel extends ViewModel {
|
||||
void run(T target);
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static class ViewState {
|
||||
public boolean isEditing = false;
|
||||
@@ -240,6 +292,7 @@ public class PetDetailViewModel extends ViewModel {
|
||||
public String saveButtonText = "Add";
|
||||
public String[] availableStatuses = new String[]{STATUS_AVAILABLE, STATUS_ADOPTED, STATUS_OWNED};
|
||||
public String selectedStatus = STATUS_AVAILABLE;
|
||||
public String selectedSpecies = null;
|
||||
public Long selectedCustomerId = null;
|
||||
public Long selectedStoreId = null;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
@@ -12,6 +13,8 @@ import com.example.petstoremobile.repositories.PetRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -26,6 +29,7 @@ public class PetListViewModel extends ViewModel {
|
||||
|
||||
private final MutableLiveData<List<PetDTO>> pets = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<String>> speciesOptions = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
@Inject
|
||||
@@ -36,6 +40,7 @@ public class PetListViewModel extends ViewModel {
|
||||
|
||||
public LiveData<List<PetDTO>> getPets() { return pets; }
|
||||
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||
public LiveData<List<String>> getSpeciesOptions() { return speciesOptions; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
|
||||
public void loadPets(String query, String status, String species, Long storeId) {
|
||||
@@ -43,7 +48,7 @@ public class PetListViewModel extends ViewModel {
|
||||
if ("All Species".equals(species)) species = null;
|
||||
|
||||
isLoading.setValue(true);
|
||||
petRepository.getAllPets(0, 100, query, status, species, storeId, null, "petName").observeForever(resource -> {
|
||||
observeOnce(petRepository.getAllPets(0, 100, query, status, species, storeId, null, "petName"), resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
pets.setValue(resource.data.getContent());
|
||||
@@ -55,14 +60,39 @@ public class PetListViewModel extends ViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
public void loadSpecies() {
|
||||
observeOnce(petRepository.getPetSpeciesDropdowns(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
List<String> labels = new ArrayList<>();
|
||||
labels.add("All Species");
|
||||
for (DropdownDTO dto : resource.data) {
|
||||
labels.add(dto.getLabel());
|
||||
}
|
||||
speciesOptions.setValue(labels);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadStores() {
|
||||
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||
observeOnce(storeRepository.getAllStores(0, 100), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
stores.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeletePets(List<String> ids) {
|
||||
return petRepository.bulkDeletePets(new BulkDeleteRequest(ids));
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import com.example.petstoremobile.repositories.CategoryRepository;
|
||||
import com.example.petstoremobile.repositories.ProductRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -38,7 +40,7 @@ public class ProductListViewModel extends ViewModel {
|
||||
|
||||
public void loadProducts(String query, Long categoryId) {
|
||||
isLoading.setValue(true);
|
||||
productRepository.getAllProducts(query, categoryId, 0, 100, "prodName").observeForever(resource -> {
|
||||
observeOnce(productRepository.getAllProducts(query, categoryId, 0, 100, "prodName"), resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
products.setValue(resource.data.getContent());
|
||||
@@ -51,10 +53,22 @@ public class ProductListViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public void loadCategories() {
|
||||
productRepository.getCategoryDropdowns().observeForever(resource -> {
|
||||
observeOnce(productRepository.getCategoryDropdowns(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
categories.setValue(resource.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import com.example.petstoremobile.repositories.ProductSupplierRepository;
|
||||
import com.example.petstoremobile.repositories.SupplierRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -45,7 +47,7 @@ public class ProductSupplierListViewModel extends ViewModel {
|
||||
|
||||
public void loadProductSuppliers(String query, Long productId, Long supplierId) {
|
||||
isLoading.setValue(true);
|
||||
psRepository.getAllProductSuppliers(0, 100, query, productId, supplierId, "productName").observeForever(resource -> {
|
||||
observeOnce(psRepository.getAllProductSuppliers(0, 100, query, productId, supplierId, "productName"), resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
productSuppliers.setValue(resource.data.getContent());
|
||||
@@ -58,19 +60,31 @@ public class ProductSupplierListViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public void loadFilterData() {
|
||||
productRepository.getAllProducts(null, null, 0, 100, "prodName").observeForever(resource -> {
|
||||
observeOnce(productRepository.getAllProducts(null, null, 0, 100, "prodName"), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
products.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
|
||||
supplierRepository.getAllSuppliers(0, 100, null, "supCompany").observeForever(resource -> {
|
||||
observeOnce(supplierRepository.getAllSuppliers(0, 100, null, "supCompany"), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
suppliers.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeleteProductSuppliers(List<String> ids) {
|
||||
return psRepository.bulkDeleteProductSuppliers(new BulkDeleteRequest(ids));
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import com.example.petstoremobile.repositories.PurchaseOrderRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -38,7 +40,7 @@ public class PurchaseOrderListViewModel extends ViewModel {
|
||||
|
||||
public void loadPurchaseOrders(String query, Long storeId) {
|
||||
isLoading.setValue(true);
|
||||
purchaseOrderRepository.getAllPurchaseOrders(0, 100, query, storeId, "purchaseOrderId,desc").observeForever(resource -> {
|
||||
observeOnce(purchaseOrderRepository.getAllPurchaseOrders(0, 100, query, storeId, "purchaseOrderId,desc"), resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
purchaseOrders.setValue(resource.data.getContent());
|
||||
@@ -51,10 +53,22 @@ public class PurchaseOrderListViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public void loadStores() {
|
||||
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||
observeOnce(storeRepository.getAllStores(0, 100), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
stores.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.CouponDTO;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import com.example.petstoremobile.repositories.CouponRepository;
|
||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||
import com.example.petstoremobile.repositories.ProductRepository;
|
||||
import com.example.petstoremobile.repositories.SaleRepository;
|
||||
@@ -27,6 +29,7 @@ public class SaleDetailViewModel extends ViewModel {
|
||||
private final StoreRepository storeRepository;
|
||||
private final CustomerRepository customerRepository;
|
||||
private final ProductRepository productRepository;
|
||||
private final CouponRepository couponRepository;
|
||||
|
||||
private long saleId = -1;
|
||||
private boolean viewOnly = false;
|
||||
@@ -35,15 +38,18 @@ public class SaleDetailViewModel extends ViewModel {
|
||||
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<>());
|
||||
private final MutableLiveData<CouponDTO> appliedCoupon = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
@Inject
|
||||
public SaleDetailViewModel(SaleRepository saleRepository, StoreRepository storeRepository,
|
||||
CustomerRepository customerRepository, ProductRepository productRepository) {
|
||||
CustomerRepository customerRepository, ProductRepository productRepository,
|
||||
CouponRepository couponRepository) {
|
||||
this.saleRepository = saleRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
this.customerRepository = customerRepository;
|
||||
this.productRepository = productRepository;
|
||||
this.couponRepository = couponRepository;
|
||||
}
|
||||
|
||||
public void setSaleId(long id, boolean viewOnly) {
|
||||
@@ -89,8 +95,46 @@ public class SaleDetailViewModel extends ViewModel {
|
||||
cartItems.setValue(currentCart);
|
||||
}
|
||||
|
||||
public void removeFromCart(Long prodId) {
|
||||
List<SaleDTO.SaleItemDTO> currentCart = new ArrayList<>(cartItems.getValue());
|
||||
currentCart.removeIf(item -> item.getProdId().equals(prodId));
|
||||
cartItems.setValue(currentCart);
|
||||
}
|
||||
|
||||
public LiveData<List<SaleDTO.SaleItemDTO>> getCartItems() { return cartItems; }
|
||||
|
||||
public LiveData<Resource<CouponDTO>> lookupCoupon(String code) {
|
||||
return couponRepository.getCouponByCode(code);
|
||||
}
|
||||
|
||||
public void setAppliedCoupon(CouponDTO coupon) {
|
||||
appliedCoupon.setValue(coupon);
|
||||
}
|
||||
|
||||
public void clearCoupon() {
|
||||
appliedCoupon.setValue(null);
|
||||
}
|
||||
|
||||
public LiveData<CouponDTO> getAppliedCoupon() {
|
||||
return appliedCoupon;
|
||||
}
|
||||
|
||||
public Long getAppliedCouponId() {
|
||||
CouponDTO coupon = appliedCoupon.getValue();
|
||||
return coupon != null ? coupon.getCouponId() : null;
|
||||
}
|
||||
|
||||
public BigDecimal calculateDiscount() {
|
||||
CouponDTO coupon = appliedCoupon.getValue();
|
||||
if (coupon == null || coupon.getDiscountValue() == null) return BigDecimal.ZERO;
|
||||
BigDecimal subtotal = calculateSubtotal();
|
||||
if ("PERCENTAGE".equalsIgnoreCase(coupon.getDiscountType())) {
|
||||
return subtotal.multiply(coupon.getDiscountValue()).divide(BigDecimal.valueOf(100), 2, java.math.RoundingMode.HALF_UP);
|
||||
} else {
|
||||
return coupon.getDiscountValue().min(subtotal);
|
||||
}
|
||||
}
|
||||
|
||||
public BigDecimal calculateSubtotal() {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
List<SaleDTO.SaleItemDTO> items = cartItems.getValue();
|
||||
|
||||
@@ -11,6 +11,8 @@ import com.example.petstoremobile.repositories.SaleRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -51,7 +53,7 @@ public class SaleListViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
isLoading.setValue(true);
|
||||
saleRepository.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, isRefund, "saleDate,desc").observeForever(resource -> {
|
||||
observeOnce(saleRepository.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, isRefund, "saleDate,desc"), resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
List<SaleDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(sales.getValue());
|
||||
@@ -68,10 +70,22 @@ public class SaleListViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public void loadStores() {
|
||||
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||
observeOnce(storeRepository.getAllStores(0, 100), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
stores.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.example.petstoremobile.dtos.ServiceDTO;
|
||||
import com.example.petstoremobile.repositories.ServiceRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
@@ -55,7 +57,7 @@ public class ServiceDetailViewModel extends ViewModel {
|
||||
|
||||
public LiveData<Resource<ServiceDTO>> loadService() {
|
||||
MutableLiveData<Resource<ServiceDTO>> result = new MutableLiveData<>();
|
||||
repository.getServiceById(serviceId).observeForever(resource -> {
|
||||
observeOnce(repository.getServiceById(serviceId), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
ServiceDTO service = resource.data;
|
||||
updateViewState(state -> {
|
||||
@@ -106,6 +108,18 @@ public class ServiceDetailViewModel extends ViewModel {
|
||||
void run(T target);
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static class ViewState {
|
||||
public boolean isEditing = false;
|
||||
public boolean isDeleteVisible = false;
|
||||
|
||||
@@ -10,6 +10,8 @@ import com.example.petstoremobile.dtos.ServiceDTO;
|
||||
import com.example.petstoremobile.repositories.ServiceRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -46,7 +48,7 @@ public class ServiceListViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
isLoading.setValue(true);
|
||||
repository.getAllServices(currentPage, PAGE_SIZE, query, "serviceName").observeForever(resource -> {
|
||||
observeOnce(repository.getAllServices(currentPage, PAGE_SIZE, query, "serviceName"), resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
List<ServiceDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(services.getValue());
|
||||
@@ -62,6 +64,18 @@ public class ServiceListViewModel extends ViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeleteServices(List<String> ids) {
|
||||
return repository.bulkDeleteServices(new BulkDeleteRequest(ids));
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import com.example.petstoremobile.repositories.EmployeeRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -42,7 +44,7 @@ public class StaffListViewModel extends ViewModel {
|
||||
|
||||
public void loadStaff() {
|
||||
isLoading.setValue(true);
|
||||
repository.getAllEmployees(0, 100).observeForever(resource -> {
|
||||
observeOnce(repository.getAllEmployees(0, 100), resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
employees.setValue(resource.data.getContent());
|
||||
@@ -56,13 +58,25 @@ public class StaffListViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public void loadStores() {
|
||||
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||
observeOnce(storeRepository.getAllStores(0, 100), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
stores.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void filter(String query, Long storeId, String status) {
|
||||
this.lastQuery = query;
|
||||
this.lastStoreId = storeId;
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.example.petstoremobile.dtos.SupplierDTO;
|
||||
import com.example.petstoremobile.repositories.SupplierRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
@@ -55,7 +57,7 @@ public class SupplierDetailViewModel extends ViewModel {
|
||||
|
||||
public LiveData<Resource<SupplierDTO>> loadSupplier() {
|
||||
MutableLiveData<Resource<SupplierDTO>> result = new MutableLiveData<>();
|
||||
repository.getSupplierById(supId).observeForever(resource -> {
|
||||
observeOnce(repository.getSupplierById(supId), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
SupplierDTO s = resource.data;
|
||||
updateViewState(state -> {
|
||||
@@ -99,6 +101,18 @@ public class SupplierDetailViewModel extends ViewModel {
|
||||
void run(T target);
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static class ViewState {
|
||||
public boolean isEditing = false;
|
||||
public boolean isDeleteVisible = false;
|
||||
|
||||
@@ -9,6 +9,8 @@ import com.example.petstoremobile.dtos.SupplierDTO;
|
||||
import com.example.petstoremobile.repositories.SupplierRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -33,7 +35,7 @@ public class SupplierListViewModel extends ViewModel {
|
||||
|
||||
public void loadSuppliers(String query) {
|
||||
isLoading.setValue(true);
|
||||
repository.getAllSuppliers(0, 100, query, "supCompany").observeForever(resource -> {
|
||||
observeOnce(repository.getAllSuppliers(0, 100, query, "supCompany"), resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
suppliers.setValue(resource.data.getContent());
|
||||
@@ -45,6 +47,18 @@ public class SupplierListViewModel extends ViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||
liveData.observeForever(new Observer<Resource<T>>() {
|
||||
@Override
|
||||
public void onChanged(Resource<T> resource) {
|
||||
if (resource == null || resource.status != Resource.Status.LOADING) {
|
||||
liveData.removeObserver(this);
|
||||
}
|
||||
handler.onChanged(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeleteSuppliers(List<String> ids) {
|
||||
return repository.bulkDeleteSuppliers(new BulkDeleteRequest(ids));
|
||||
}
|
||||
|
||||
93
android/app/src/main/res/layout/activity_forgot_password.xml
Normal file
93
android/app/src/main/res/layout/activity_forgot_password.xml
Normal file
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/forgot_password_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/primary_dark"
|
||||
android:padding="32dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/petstore_logo" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Forgot Password"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Enter your email to reset your password"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="14sp"
|
||||
android:textAlignment="center"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/white"
|
||||
android:padding="24dp"
|
||||
android:elevation="4dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Email Address"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etEmail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="example@email.com"
|
||||
android:inputType="textEmailAddress"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSubmit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Send Reset Link"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBackToLogin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Back to Login"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:textColor="@color/accent_blue"
|
||||
android:layout_marginTop="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="0.5"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -109,6 +109,18 @@
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvForgotPassword"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Forgot your password?"
|
||||
android:textColor="@color/accent_blue"
|
||||
android:textSize="14sp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -60,6 +60,17 @@
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCouponId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<!-- Coupon Code -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
131
android/app/src/main/res/layout/fragment_customer.xml
Normal file
131
android/app/src/main/res/layout/fragment_customer.xml
Normal file
@@ -0,0 +1,131 @@
|
||||
<?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">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/headerCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnHamburgerCustomer"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/baseline_menu_36"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Open menu"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Customers"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginStart="8dp"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnToggleFilterCustomer"
|
||||
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>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutFilterCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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/etSearchCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="Search customers..."
|
||||
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/spinnerStatusCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:background="@drawable/bg_spinner"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:layout_marginTop="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerViewCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp"/>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabAddCustomer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:contentDescription="Add Customer"
|
||||
app:srcCompat="@android:drawable/ic_input_add"
|
||||
app:tint="@color/white"/>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
252
android/app/src/main/res/layout/fragment_customer_detail.xml
Normal file
252
android/app/src/main/res/layout/fragment_customer_detail.xml
Normal file
@@ -0,0 +1,252 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCustomerMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Customer"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeleteCustomer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCustomerId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<!-- Username -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Username"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etCustomerUsername"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter username"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Password -->
|
||||
<TextView
|
||||
android:id="@+id/tvCustomerPasswordLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Password"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etCustomerPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter password (min 6 characters)"
|
||||
android:inputType="textPassword"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- First Name -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="First Name"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etCustomerFirstName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter first name"
|
||||
android:inputType="textPersonName"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Last Name -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Last Name"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etCustomerLastName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter last name"
|
||||
android:inputType="textPersonName"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Email -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Email"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etCustomerEmail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter email"
|
||||
android:inputType="textEmailAddress"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Phone -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Phone"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etCustomerPhone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter phone number"
|
||||
android:inputType="phone"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Status -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Status"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerCustomerStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Loyalty Points (read-only) -->
|
||||
<TextView
|
||||
android:id="@+id/tvLoyaltyPointsLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Loyalty Points"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCustomerLoyaltyPoints"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCustomerBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSaveCustomer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Save"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="@color/accent_coral"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -146,6 +146,23 @@
|
||||
android:textSize="15sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/drawerCustomers"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Customers"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/drawerServices"
|
||||
android:layout_width="match_parent"
|
||||
@@ -345,6 +362,7 @@
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -95,14 +95,11 @@
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPetSpecies"
|
||||
<Spinner
|
||||
android:id="@+id/spinnerPetSpecies"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="e.g. Dog, Cat, Bird"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -262,6 +262,66 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llCouponInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="12dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Coupon Code"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etCouponCode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:hint="Enter coupon code"
|
||||
android:inputType="textCapCharacters"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnApplyCoupon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Apply"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnClearCoupon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Clear"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"
|
||||
android:layout_marginStart="4dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCouponInfo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
|
||||
117
android/app/src/main/res/layout/item_customer.xml
Normal file
117
android/app/src/main/res/layout/item_customer.xml
Normal file
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout 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="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="@color/white">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/ivCustomerProfile"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/placeholder"
|
||||
app:shapeAppearanceOverlay="@style/CircleImageView"
|
||||
app:strokeWidth="2dp"
|
||||
app:strokeColor="#BDBDBD"
|
||||
android:padding="2dp"
|
||||
android:contentDescription="Customer Profile Image" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCustomerFullName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="Full Name"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCustomerLoyalty"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:text="0 pts"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="11sp"
|
||||
android:background="#FF9800" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCustomerUsername"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="@username"
|
||||
android:textColor="#888888"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginTop="4dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCustomerEmail"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="email@example.com"
|
||||
android:textColor="#888888"
|
||||
android:textSize="13sp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCustomerStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Active"
|
||||
android:textColor="@color/accent_coral"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#F0F0F0"
|
||||
android:layout_marginTop="12dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -151,6 +151,18 @@
|
||||
android:label="Staff Details"
|
||||
tools:layout="@layout/fragment_staff_detail" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_customer"
|
||||
android:name="com.example.petstoremobile.fragments.listfragments.CustomerFragment"
|
||||
android:label="Customers"
|
||||
tools:layout="@layout/fragment_customer" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_customer_detail"
|
||||
android:name="com.example.petstoremobile.fragments.listfragments.detailfragments.CustomerDetailFragment"
|
||||
android:label="Customer Details"
|
||||
tools:layout="@layout/fragment_customer_detail" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_analytics"
|
||||
android:name="com.example.petstoremobile.fragments.listfragments.AnalyticsFragment"
|
||||
|
||||
@@ -12,6 +12,8 @@ public class SaleResponse {
|
||||
private String employeeName;
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
private Long customerId;
|
||||
private String customerName;
|
||||
private BigDecimal totalAmount;
|
||||
private BigDecimal subtotalAmount;
|
||||
private BigDecimal couponDiscountAmount;
|
||||
@@ -77,6 +79,22 @@ public class SaleResponse {
|
||||
this.storeName = storeName;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public String getCustomerName() {
|
||||
return customerName;
|
||||
}
|
||||
|
||||
public void setCustomerName(String customerName) {
|
||||
this.customerName = customerName;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalAmount() {
|
||||
return totalAmount;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -20,6 +21,8 @@ import java.util.List;
|
||||
@Service
|
||||
public class SaleService {
|
||||
|
||||
private static final BigDecimal EMPLOYEE_DISCOUNT_PERCENT = new BigDecimal("0.10");
|
||||
|
||||
private final SaleRepository saleRepository;
|
||||
private final ProductRepository productRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
@@ -76,6 +79,7 @@ public class SaleService {
|
||||
if (request.getCouponId() != null) {
|
||||
Coupon coupon = couponRepository.findById(request.getCouponId())
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Coupon not found with id: " + request.getCouponId()));
|
||||
validateCoupon(coupon);
|
||||
sale.setCoupon(coupon);
|
||||
}
|
||||
|
||||
@@ -85,8 +89,9 @@ public class SaleService {
|
||||
sale.setCart(cart);
|
||||
}
|
||||
|
||||
User customer = null;
|
||||
if (request.getCustomerId() != null) {
|
||||
User customer = userRepository.findById(request.getCustomerId())
|
||||
customer = userRepository.findById(request.getCustomerId())
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
|
||||
sale.setCustomer(customer);
|
||||
}
|
||||
@@ -97,7 +102,7 @@ public class SaleService {
|
||||
sale.setOriginalSale(originalSale);
|
||||
}
|
||||
|
||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||
BigDecimal subtotalAmount = BigDecimal.ZERO;
|
||||
List<SaleItem> saleItems = new ArrayList<>();
|
||||
|
||||
if (sale.getIsRefund() && sale.getOriginalSale() != null) {
|
||||
@@ -145,9 +150,11 @@ public class SaleService {
|
||||
saleItem.setUnitPrice(unitPrice);
|
||||
|
||||
saleItems.add(saleItem);
|
||||
totalAmount = totalAmount.add(itemTotal);
|
||||
subtotalAmount = subtotalAmount.add(itemTotal);
|
||||
}
|
||||
totalAmount = totalAmount.negate();
|
||||
subtotalAmount = subtotalAmount.negate();
|
||||
sale.setSubtotalAmount(subtotalAmount);
|
||||
sale.setTotalAmount(subtotalAmount);
|
||||
} else {
|
||||
for (var itemRequest : request.getItems()) {
|
||||
Product product = productRepository.findById(itemRequest.getProdId())
|
||||
@@ -174,17 +181,77 @@ public class SaleService {
|
||||
saleItem.setUnitPrice(unitPrice);
|
||||
|
||||
saleItems.add(saleItem);
|
||||
totalAmount = totalAmount.add(itemTotal);
|
||||
subtotalAmount = subtotalAmount.add(itemTotal);
|
||||
}
|
||||
sale.setSubtotalAmount(subtotalAmount);
|
||||
|
||||
BigDecimal couponDiscount = calculateCouponDiscount(sale.getCoupon(), subtotalAmount);
|
||||
sale.setCouponDiscountAmount(couponDiscount);
|
||||
|
||||
BigDecimal employeeDiscount = calculateEmployeeDiscount(customer, subtotalAmount.subtract(couponDiscount));
|
||||
sale.setEmployeeDiscountAmount(employeeDiscount);
|
||||
|
||||
BigDecimal finalTotal = subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount);
|
||||
sale.setTotalAmount(finalTotal.max(BigDecimal.ZERO));
|
||||
|
||||
sale.setPointsEarned(sale.getTotalAmount().setScale(0, RoundingMode.FLOOR).intValue());
|
||||
if (customer != null) {
|
||||
customer.setLoyaltyPoints(customer.getLoyaltyPoints() + sale.getPointsEarned());
|
||||
userRepository.save(customer);
|
||||
}
|
||||
}
|
||||
|
||||
sale.setTotalAmount(totalAmount);
|
||||
sale.setItems(saleItems);
|
||||
|
||||
Sale savedSale = saleRepository.save(sale);
|
||||
return mapToResponse(savedSale);
|
||||
}
|
||||
|
||||
private void validateCoupon(Coupon coupon) {
|
||||
if (!Boolean.TRUE.equals(coupon.getActive())) {
|
||||
throw new BusinessException("Coupon is not active");
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (coupon.getStartsAt() != null && now.isBefore(coupon.getStartsAt())) {
|
||||
throw new BusinessException("Coupon has not started yet");
|
||||
}
|
||||
if (coupon.getEndsAt() != null && now.isAfter(coupon.getEndsAt())) {
|
||||
throw new BusinessException("Coupon has expired");
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal calculateCouponDiscount(Coupon coupon, BigDecimal subtotal) {
|
||||
if (coupon == null || subtotal.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
if (coupon.getMinOrderAmount() != null && subtotal.compareTo(coupon.getMinOrderAmount()) < 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
BigDecimal discount = BigDecimal.ZERO;
|
||||
String type = coupon.getDiscountType().trim().toUpperCase();
|
||||
if ("PERCENTAGE".equals(type) || "PERCENT".equals(type)) {
|
||||
discount = subtotal.multiply(coupon.getDiscountValue().divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP));
|
||||
} else if ("FIXED".equals(type)) {
|
||||
discount = coupon.getDiscountValue();
|
||||
}
|
||||
|
||||
return discount.min(subtotal).setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
private BigDecimal calculateEmployeeDiscount(User customer, BigDecimal remainingAmount) {
|
||||
if (customer == null || remainingAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
if (customer.getRole() == User.Role.STAFF || customer.getRole() == User.Role.ADMIN) {
|
||||
return remainingAmount.multiply(EMPLOYEE_DISCOUNT_PERCENT).setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private SaleResponse mapToResponse(Sale sale) {
|
||||
SaleResponse response = new SaleResponse();
|
||||
response.setSaleId(sale.getSaleId());
|
||||
@@ -197,6 +264,11 @@ public class SaleService {
|
||||
response.setStoreName(sale.getStore().getStoreName());
|
||||
}
|
||||
|
||||
if (sale.getCustomer() != null) {
|
||||
response.setCustomerId(sale.getCustomer().getId());
|
||||
response.setCustomerName(sale.getCustomer().getFirstName() + " " + sale.getCustomer().getLastName());
|
||||
}
|
||||
|
||||
response.setTotalAmount(sale.getTotalAmount());
|
||||
response.setSubtotalAmount(sale.getSubtotalAmount());
|
||||
response.setCouponDiscountAmount(sale.getCouponDiscountAmount());
|
||||
|
||||
Reference in New Issue
Block a user