Merge branch 'AttachmentsToChat'
This commit is contained in:
@@ -34,6 +34,9 @@
|
|||||||
android:name=".activities.HomeActivity"
|
android:name=".activities.HomeActivity"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".activities.ForgotPasswordActivity"
|
||||||
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".activities.MainActivity"
|
android:name=".activities.MainActivity"
|
||||||
android:exported="true">
|
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);
|
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 != null && resource.status != Resource.Status.LOADING) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
tokenManager.saveUserId(resource.data.getId());
|
tokenManager.saveUserId(resource.data.getId());
|
||||||
|
tokenManager.savePrimaryStoreId(resource.data.getStoreId());
|
||||||
}
|
}
|
||||||
Toast.makeText(this, "Login successful", Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, "Login successful", Toast.LENGTH_SHORT).show();
|
||||||
startActivity(new Intent(this, HomeActivity.class));
|
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("status") String status,
|
||||||
@Query("storeId") Long storeId,
|
@Query("storeId") Long storeId,
|
||||||
@Query("date") String date,
|
@Query("date") String date,
|
||||||
@Query("employeeId") Long employeeId);
|
@Query("employeeId") Long employeeId,
|
||||||
|
@Query("sort") String sort);
|
||||||
|
|
||||||
@GET("api/v1/adoptions/{id}")
|
@GET("api/v1/adoptions/{id}")
|
||||||
Call<AdoptionDTO> getAdoptionById(@Path("id") Long id);
|
Call<AdoptionDTO> getAdoptionById(@Path("id") Long id);
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ public interface AppointmentApi {
|
|||||||
@Query("status") String status,
|
@Query("status") String status,
|
||||||
@Query("storeId") Long storeId,
|
@Query("storeId") Long storeId,
|
||||||
@Query("date") String date,
|
@Query("date") String date,
|
||||||
@Query("employeeId") Long employeeId);
|
@Query("employeeId") Long employeeId,
|
||||||
|
@Query("sort") String sort);
|
||||||
|
|
||||||
@GET("api/v1/appointments/{id}")
|
@GET("api/v1/appointments/{id}")
|
||||||
Call<AppointmentDTO> getAppointmentById(@Path("id") Long id);
|
Call<AppointmentDTO> getAppointmentById(@Path("id") Long id);
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ import com.example.petstoremobile.dtos.PageResponse;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
|
import retrofit2.http.Body;
|
||||||
|
import retrofit2.http.DELETE;
|
||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.POST;
|
||||||
|
import retrofit2.http.PUT;
|
||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
//api calls to get customers
|
|
||||||
public interface CustomerApi {
|
public interface CustomerApi {
|
||||||
|
|
||||||
@GET("api/v1/customers")
|
@GET("api/v1/customers")
|
||||||
@@ -20,6 +23,15 @@ public interface CustomerApi {
|
|||||||
@GET("api/v1/customers/{customerId}")
|
@GET("api/v1/customers/{customerId}")
|
||||||
Call<CustomerDTO> getCustomerById(@Path("customerId") Long 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")
|
@GET("api/v1/dropdowns/customers")
|
||||||
Call<List<DropdownDTO>> getCustomerDropdowns();
|
Call<List<DropdownDTO>> getCustomerDropdowns();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ public interface PetApi {
|
|||||||
@GET("api/v1/dropdowns/pets")
|
@GET("api/v1/dropdowns/pets")
|
||||||
Call<List<DropdownDTO>> getPetDropdowns();
|
Call<List<DropdownDTO>> getPetDropdowns();
|
||||||
|
|
||||||
|
@GET("api/v1/dropdowns/pet-species")
|
||||||
|
Call<List<DropdownDTO>> getPetSpeciesDropdowns();
|
||||||
|
|
||||||
// Get pet by id
|
// Get pet by id
|
||||||
@GET("api/v1/pets/{id}")
|
@GET("api/v1/pets/{id}")
|
||||||
Call<PetDTO> getPetById(@Path("id") Long 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 ROLE_KEY = "role";
|
||||||
private static final String PREFS_NAME = "auth_prefs";
|
private static final String PREFS_NAME = "auth_prefs";
|
||||||
private static final String USER_ID_KEY = "user_id";
|
private static final String USER_ID_KEY = "user_id";
|
||||||
|
private static final String PRIMARY_STORE_ID_KEY = "primary_store_id";
|
||||||
|
|
||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
|
|
||||||
@@ -54,6 +55,19 @@ public class TokenManager {
|
|||||||
prefs.edit().putLong(USER_ID_KEY, userId).apply();
|
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
|
//Check if logged in
|
||||||
public boolean isLoggedIn() {
|
public boolean isLoggedIn() {
|
||||||
return getToken() != null;
|
return getToken() != null;
|
||||||
|
|||||||
@@ -5,61 +5,72 @@ import com.google.gson.annotations.SerializedName;
|
|||||||
public class CustomerDTO {
|
public class CustomerDTO {
|
||||||
@SerializedName("id")
|
@SerializedName("id")
|
||||||
private Long customerId;
|
private Long customerId;
|
||||||
|
private String username;
|
||||||
private String firstName;
|
private String firstName;
|
||||||
private String lastName;
|
private String lastName;
|
||||||
|
private String fullName;
|
||||||
private String email;
|
private String email;
|
||||||
|
private String phone;
|
||||||
|
private Boolean active;
|
||||||
|
private Integer loyaltyPoints;
|
||||||
|
private Long primaryStoreId;
|
||||||
private String createdAt;
|
private String createdAt;
|
||||||
private String updatedAt;
|
private String updatedAt;
|
||||||
|
private String password;
|
||||||
|
|
||||||
public Long getCustomerId() {
|
public CustomerDTO() {}
|
||||||
return customerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCustomerId(Long customerId) {
|
public CustomerDTO(String username, String password, String firstName, String lastName,
|
||||||
this.customerId = customerId;
|
String email, String phone) {
|
||||||
}
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
public String getFirstName() {
|
|
||||||
return firstName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFirstName(String firstName) {
|
|
||||||
this.firstName = firstName;
|
this.firstName = firstName;
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastName() {
|
|
||||||
return lastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastName(String lastName) {
|
|
||||||
this.lastName = lastName;
|
this.lastName = lastName;
|
||||||
}
|
|
||||||
|
|
||||||
public String getEmail() {
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEmail(String email) {
|
|
||||||
this.email = 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() {
|
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() {
|
public String getEmail() { return email; }
|
||||||
return createdAt;
|
public void setEmail(String email) { this.email = email; }
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(String createdAt) {
|
public String getPhone() { return phone; }
|
||||||
this.createdAt = createdAt;
|
public void setPhone(String phone) { this.phone = phone; }
|
||||||
}
|
|
||||||
|
|
||||||
public String getUpdatedAt() {
|
public Boolean getActive() { return active; }
|
||||||
return updatedAt;
|
public void setActive(Boolean active) { this.active = active; }
|
||||||
}
|
|
||||||
|
|
||||||
public void setUpdatedAt(String updatedAt) {
|
public Integer getLoyaltyPoints() { return loyaltyPoints; }
|
||||||
this.updatedAt = updatedAt;
|
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;
|
return couponId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCouponId(Long couponId) {
|
||||||
|
this.couponId = couponId;
|
||||||
|
}
|
||||||
|
|
||||||
public Integer getPointsEarned() {
|
public Integer getPointsEarned() {
|
||||||
return pointsEarned;
|
return pointsEarned;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ public class ListFragment extends Fragment {
|
|||||||
binding.drawerPurchaseOrderView.setOnClickListener(v -> navigateTo(R.id.nav_purchase_order));
|
binding.drawerPurchaseOrderView.setOnClickListener(v -> navigateTo(R.id.nav_purchase_order));
|
||||||
binding.drawerSale.setOnClickListener(v -> navigateTo(R.id.nav_sale));
|
binding.drawerSale.setOnClickListener(v -> navigateTo(R.id.nav_sale));
|
||||||
binding.drawerStaff.setOnClickListener(v -> navigateTo(R.id.nav_staff));
|
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.drawerAnalytics.setOnClickListener(v -> navigateTo(R.id.nav_analytics));
|
||||||
binding.drawerCoupons.setOnClickListener(v -> navigateTo(R.id.nav_coupon));
|
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);
|
android.content.Intent serviceIntent = new android.content.Intent(requireContext(), ChatNotificationService.class);
|
||||||
requireContext().stopService(serviceIntent);
|
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
|
//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);
|
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);
|
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.R;
|
||||||
import com.example.petstoremobile.adapters.AdoptionAdapter;
|
import com.example.petstoremobile.adapters.AdoptionAdapter;
|
||||||
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.databinding.FragmentAdoptionBinding;
|
import com.example.petstoremobile.databinding.FragmentAdoptionBinding;
|
||||||
import com.example.petstoremobile.dtos.AdoptionDTO;
|
import com.example.petstoremobile.dtos.AdoptionDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
@@ -38,6 +39,8 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -52,6 +55,8 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
private boolean isMonthMode = false;
|
private boolean isMonthMode = false;
|
||||||
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||||
|
|
||||||
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -117,7 +122,7 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
loadAdoptions();
|
loadAdoptions();
|
||||||
viewModel.loadStores();
|
if (!isStaff()) viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleCalendarMode() {
|
private void toggleCalendarMode() {
|
||||||
@@ -128,8 +133,18 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilterAdoption, binding.layoutFilterAdoption,
|
if (isStaff()) {
|
||||||
binding.etSearchAdoption, binding.spinnerStatusAdoption, binding.spinnerStoreAdoption);
|
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() {
|
private void setupCalendar() {
|
||||||
@@ -195,10 +210,15 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
String query = binding.etSearchAdoption.getText().toString().trim();
|
String query = binding.etSearchAdoption.getText().toString().trim();
|
||||||
String status = binding.spinnerStatusAdoption.getSelectedItem() != null ? binding.spinnerStatusAdoption.getSelectedItem().toString() : "All Statuses";
|
String status = binding.spinnerStatusAdoption.getSelectedItem() != null ? binding.spinnerStatusAdoption.getSelectedItem().toString() : "All Statuses";
|
||||||
|
|
||||||
Long storeId = null;
|
Long storeId;
|
||||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
if (isStaff()) {
|
||||||
if (binding.spinnerStoreAdoption.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
storeId = tokenManager.getPrimaryStoreId();
|
||||||
storeId = stores.get(binding.spinnerStoreAdoption.getSelectedItemPosition() - 1).getStoreId();
|
} 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;
|
String selectedDateString = null;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.adapters.AppointmentAdapter;
|
import com.example.petstoremobile.adapters.AppointmentAdapter;
|
||||||
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.databinding.FragmentAppointmentBinding;
|
import com.example.petstoremobile.databinding.FragmentAppointmentBinding;
|
||||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
@@ -39,6 +40,8 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -52,6 +55,8 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
private AuthViewModel authViewModel;
|
private AuthViewModel authViewModel;
|
||||||
private BulkDeleteHandler bulkDeleteHandler;
|
private BulkDeleteHandler bulkDeleteHandler;
|
||||||
|
|
||||||
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
private CalendarDay selectedCalendarDay;
|
private CalendarDay selectedCalendarDay;
|
||||||
private boolean isMonthMode = false;
|
private boolean isMonthMode = false;
|
||||||
private Long currentUserId = null;
|
private Long currentUserId = null;
|
||||||
@@ -126,7 +131,7 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
loadAppointmentData();
|
loadAppointmentData();
|
||||||
viewModel.loadStores();
|
if (!isStaff()) viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleCalendarMode() {
|
private void toggleCalendarMode() {
|
||||||
@@ -151,8 +156,13 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchAppointment,
|
if (isStaff()) {
|
||||||
binding.spinnerStatus, binding.spinnerStore);
|
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() {
|
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() {
|
private void loadAppointmentData() {
|
||||||
String query = binding.etSearchAppointment.getText().toString().trim();
|
String query = binding.etSearchAppointment.getText().toString().trim();
|
||||||
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
||||||
|
|
||||||
Long storeId = null;
|
Long storeId;
|
||||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
if (isStaff()) {
|
||||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
storeId = tokenManager.getPrimaryStoreId();
|
||||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
} 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;
|
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.R;
|
||||||
import com.example.petstoremobile.adapters.InventoryAdapter;
|
import com.example.petstoremobile.adapters.InventoryAdapter;
|
||||||
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.databinding.FragmentInventoryBinding;
|
import com.example.petstoremobile.databinding.FragmentInventoryBinding;
|
||||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
@@ -27,6 +28,8 @@ import com.example.petstoremobile.utils.SpinnerUtils;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -38,6 +41,8 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
private InventoryListViewModel viewModel;
|
private InventoryListViewModel viewModel;
|
||||||
private BulkDeleteHandler bulkDeleteHandler;
|
private BulkDeleteHandler bulkDeleteHandler;
|
||||||
|
|
||||||
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -99,7 +104,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
viewModel.loadStores();
|
if (!isStaff()) viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -109,7 +114,16 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupFilterToggle() {
|
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() {
|
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() : "";
|
String query = binding.etSearchInventory != null ? binding.etSearchInventory.getText().toString().trim() : "";
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
|
|
||||||
Long storeId = null;
|
Long storeId;
|
||||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
if (isStaff()) {
|
||||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
storeId = tokenManager.getPrimaryStoreId();
|
||||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
} 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);
|
viewModel.loadInventory(reset, query, storeId);
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
binding.swipeRefreshPet.setRefreshing(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() {
|
private void setupBulkDelete() {
|
||||||
@@ -107,12 +112,23 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
loadPetData();
|
loadPetData();
|
||||||
viewModel.loadStores();
|
viewModel.loadSpecies();
|
||||||
|
if (!isStaff()) viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPet,
|
if (isStaff()) {
|
||||||
binding.spinnerStatus, binding.spinnerSpecies, binding.spinnerStore);
|
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() {
|
private void setupSearch() {
|
||||||
@@ -125,8 +141,8 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupSpeciesFilter() {
|
private void setupSpeciesFilter() {
|
||||||
String[] species = {"All Species", "Dog", "Cat", "Bird", "Rabbit", "Fish", "Hamster"};
|
String[] initial = {"All Species"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerSpecies, species, this::loadPetData);
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerSpecies, initial, this::loadPetData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
@@ -141,11 +157,16 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
String query = binding.etSearchPet.getText().toString().trim();
|
String query = binding.etSearchPet.getText().toString().trim();
|
||||||
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
||||||
String species = binding.spinnerSpecies.getSelectedItem() != null ? binding.spinnerSpecies.getSelectedItem().toString() : "All Species";
|
String species = binding.spinnerSpecies.getSelectedItem() != null ? binding.spinnerSpecies.getSelectedItem().toString() : "All Species";
|
||||||
|
|
||||||
Long storeId = null;
|
Long storeId;
|
||||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
if (isStaff()) {
|
||||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
storeId = tokenManager.getPrimaryStoreId();
|
||||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
} 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);
|
viewModel.loadPets(query, status, species, storeId);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.adapters.PurchaseOrderAdapter;
|
import com.example.petstoremobile.adapters.PurchaseOrderAdapter;
|
||||||
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.databinding.FragmentPurchaseOrderBinding;
|
import com.example.petstoremobile.databinding.FragmentPurchaseOrderBinding;
|
||||||
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
@@ -24,6 +25,8 @@ import com.example.petstoremobile.viewmodels.PurchaseOrderListViewModel;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -35,6 +38,8 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
private PurchaseOrderAdapter adapter;
|
private PurchaseOrderAdapter adapter;
|
||||||
private PurchaseOrderListViewModel viewModel;
|
private PurchaseOrderListViewModel viewModel;
|
||||||
|
|
||||||
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -79,11 +84,20 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
loadData();
|
loadData();
|
||||||
viewModel.loadStores();
|
if (!isStaff()) viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupFilterToggle() {
|
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() {
|
private void setupSearch() {
|
||||||
@@ -108,10 +122,15 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
String query = binding.etSearchPO != null ? binding.etSearchPO.getText().toString().trim() : "";
|
String query = binding.etSearchPO != null ? binding.etSearchPO.getText().toString().trim() : "";
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
|
|
||||||
Long storeId = null;
|
Long storeId;
|
||||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
if (isStaff()) {
|
||||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
storeId = tokenManager.getPrimaryStoreId();
|
||||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
} 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);
|
viewModel.loadPurchaseOrders(query, storeId);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.adapters.SaleAdapter;
|
import com.example.petstoremobile.adapters.SaleAdapter;
|
||||||
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.databinding.FragmentSaleBinding;
|
import com.example.petstoremobile.databinding.FragmentSaleBinding;
|
||||||
import com.example.petstoremobile.dtos.SaleDTO;
|
import com.example.petstoremobile.dtos.SaleDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
@@ -25,6 +26,8 @@ import com.example.petstoremobile.viewmodels.SaleListViewModel;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -35,6 +38,8 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
private SaleAdapter adapter;
|
private SaleAdapter adapter;
|
||||||
private SaleListViewModel viewModel;
|
private SaleListViewModel viewModel;
|
||||||
|
|
||||||
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -87,12 +92,22 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
viewModel.loadStores();
|
if (!isStaff()) viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale,
|
if (isStaff()) {
|
||||||
binding.spinnerPaymentMethod, binding.spinnerStore, binding.spinnerRefundStatus);
|
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() {
|
private void setupStoreFilter() {
|
||||||
@@ -149,10 +164,15 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
paymentMethod = (String) binding.spinnerPaymentMethod.getSelectedItem();
|
paymentMethod = (String) binding.spinnerPaymentMethod.getSelectedItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
Long storeId = null;
|
Long storeId;
|
||||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
if (isStaff()) {
|
||||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
storeId = tokenManager.getPrimaryStoreId();
|
||||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
} 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;
|
Boolean isRefund = null;
|
||||||
|
|||||||
@@ -17,11 +17,14 @@ import com.example.petstoremobile.utils.InputValidator;
|
|||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.viewmodels.AdoptionDetailViewModel;
|
import com.example.petstoremobile.viewmodels.AdoptionDetailViewModel;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +37,8 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
private AdoptionDetailViewModel viewModel;
|
private AdoptionDetailViewModel viewModel;
|
||||||
private boolean isUpdatingUI = false;
|
private boolean isUpdatingUI = false;
|
||||||
|
|
||||||
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -82,7 +87,7 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
|
|
||||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
||||||
AdoptionDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
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,
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionStore, list,
|
||||||
DropdownDTO::getLabel, "-- Select Store --", storeId, DropdownDTO::getId);
|
DropdownDTO::getLabel, "-- Select Store --", storeId, DropdownDTO::getId);
|
||||||
});
|
});
|
||||||
@@ -113,6 +118,7 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
binding.spinnerAdoptionCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
binding.spinnerAdoptionCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
if (isUpdatingUI) return;
|
||||||
viewModel.onCustomerSelected(position);
|
viewModel.onCustomerSelected(position);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
@@ -122,6 +128,7 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
binding.spinnerAdoptionStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
binding.spinnerAdoptionStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
if (isUpdatingUI) return;
|
||||||
viewModel.onStoreSelected(position);
|
viewModel.onStoreSelected(position);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
@@ -210,9 +217,15 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
if (employees != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee,
|
if (employees != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee,
|
||||||
employees, DropdownDTO::getLabel, "-- Select Staff --", state.selectedEmployeeId, DropdownDTO::getId);
|
employees, DropdownDTO::getLabel, "-- Select Staff --", state.selectedEmployeeId, DropdownDTO::getId);
|
||||||
|
|
||||||
|
if (isStaff()) UIUtils.setViewsEnabled(false, binding.spinnerAdoptionStore);
|
||||||
|
|
||||||
isUpdatingUI = false;
|
isUpdatingUI = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isStaff() {
|
||||||
|
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||||
|
}
|
||||||
|
|
||||||
private void saveAdoption() {
|
private void saveAdoption() {
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionCustomer, "Customer")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionCustomer, "Customer")) return;
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionPet, "Pet")) 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) {}
|
try { fee = new BigDecimal(feeStr); } catch (NumberFormatException ignored) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
DropdownDTO customer = viewModel.getCustomerList().getValue().get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1);
|
List<DropdownDTO> customerListVal = viewModel.getCustomerList().getValue();
|
||||||
DropdownDTO pet = viewModel.getPetList().getValue().get(binding.spinnerAdoptionPet.getSelectedItemPosition() - 1);
|
List<DropdownDTO> petListVal = viewModel.getPetList().getValue();
|
||||||
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerAdoptionStore.getSelectedItemPosition() - 1);
|
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;
|
Long employeeId = null;
|
||||||
if (binding.spinnerAdoptionEmployee.getSelectedItemPosition() > 0 && viewModel.getEmployeeList().getValue() != 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.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.viewmodels.AppointmentDetailViewModel;
|
import com.example.petstoremobile.viewmodels.AppointmentDetailViewModel;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,6 +46,8 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
private AppointmentDetailViewModel viewModel;
|
private AppointmentDetailViewModel viewModel;
|
||||||
private boolean isUpdatingUI = false;
|
private boolean isUpdatingUI = false;
|
||||||
|
|
||||||
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -89,6 +94,7 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
if (isUpdatingUI) return;
|
||||||
viewModel.onCustomerSelected(position);
|
viewModel.onCustomerSelected(position);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
@@ -98,6 +104,7 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
if (isUpdatingUI) return;
|
||||||
viewModel.onStoreSelected(position);
|
viewModel.onStoreSelected(position);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
@@ -129,7 +136,7 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
|
|
||||||
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||||
AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
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);
|
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,
|
if (staff != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff,
|
||||||
staff, DropdownDTO::getLabel, "-- Select Staff --", state.selectedStaffId, DropdownDTO::getId);
|
staff, DropdownDTO::getLabel, "-- Select Staff --", state.selectedStaffId, DropdownDTO::getId);
|
||||||
|
|
||||||
|
if (isStaff()) binding.spinnerStore.setEnabled(false);
|
||||||
|
|
||||||
isUpdatingUI = false;
|
isUpdatingUI = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isStaff() {
|
||||||
|
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||||
|
}
|
||||||
|
|
||||||
private void notifyDateTimeStatusChange() {
|
private void notifyDateTimeStatusChange() {
|
||||||
if (isUpdatingUI) return;
|
if (isUpdatingUI) return;
|
||||||
|
|
||||||
@@ -248,7 +261,12 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
|
|
||||||
String date = binding.etAppointmentDate.getText().toString().trim();
|
String date = binding.etAppointmentDate.getText().toString().trim();
|
||||||
String time = buildTimeString();
|
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)) {
|
if (!viewModel.isValidFutureBooking(status, date, time)) {
|
||||||
DialogUtils.showInfoDialog(requireContext(), "Invalid Time", "Booked appointments must be in the future.");
|
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.R;
|
||||||
import com.example.petstoremobile.databinding.FragmentCouponDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentCouponDetailBinding;
|
||||||
import com.example.petstoremobile.dtos.CouponDTO;
|
import com.example.petstoremobile.dtos.CouponDTO;
|
||||||
|
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
@@ -95,6 +96,9 @@ public class CouponDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadCouponDetails() {
|
private void loadCouponDetails() {
|
||||||
|
binding.tvCouponId.setText(DateTimeUtils.formatId(couponId));
|
||||||
|
binding.tvCouponId.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
viewModel.loadCoupon(couponId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadCoupon(couponId).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
CouponDTO coupon = resource.data;
|
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.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
@@ -17,6 +18,7 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.databinding.FragmentPetDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentPetDetailBinding;
|
||||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.PetDTO;
|
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.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.PetDetailViewModel;
|
import com.example.petstoremobile.viewmodels.PetDetailViewModel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,6 +48,8 @@ public class PetDetailFragment extends Fragment {
|
|||||||
private PetDetailViewModel viewModel;
|
private PetDetailViewModel viewModel;
|
||||||
private boolean isUpdatingUI = false;
|
private boolean isUpdatingUI = false;
|
||||||
|
|
||||||
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -81,8 +88,19 @@ public class PetDetailFragment extends Fragment {
|
|||||||
|
|
||||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
||||||
PetDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
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);
|
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() {
|
private void savePet() {
|
||||||
if (!InputValidator.isNotEmpty(binding.etPetName, "Pet Name")) return;
|
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.isNotEmpty(binding.etPetBreed, "Breed")) return;
|
||||||
if (!InputValidator.isPositiveInteger(binding.etPetAge, "Age")) return;
|
if (!InputValidator.isPositiveInteger(binding.etPetAge, "Age")) return;
|
||||||
if (!InputValidator.isPositiveDecimal(binding.etPetPrice, "Price")) return;
|
if (!InputValidator.isPositiveDecimal(binding.etPetPrice, "Price")) return;
|
||||||
|
|
||||||
String name = binding.etPetName.getText().toString().trim();
|
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();
|
String breed = binding.etPetBreed.getText().toString().trim();
|
||||||
int age = Integer.parseInt(binding.etPetAge.getText().toString().trim());
|
int age = Integer.parseInt(binding.etPetAge.getText().toString().trim());
|
||||||
double price = Double.parseDouble(binding.etPetPrice.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.setCustomerId(customerId);
|
||||||
petDTO.setStoreId(storeId);
|
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 -> {
|
viewModel.savePet(petDTO).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
setLoading(resource.status == Resource.Status.LOADING);
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
@@ -202,7 +237,6 @@ public class PetDetailFragment extends Fragment {
|
|||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
PetDTO p = resource.data;
|
PetDTO p = resource.data;
|
||||||
binding.etPetName.setText(p.getPetName());
|
binding.etPetName.setText(p.getPetName());
|
||||||
binding.etPetSpecies.setText(p.getPetSpecies());
|
|
||||||
binding.etPetBreed.setText(p.getPetBreed());
|
binding.etPetBreed.setText(p.getPetBreed());
|
||||||
binding.etPetAge.setText(String.valueOf(p.getPetAge()));
|
binding.etPetAge.setText(String.valueOf(p.getPetAge()));
|
||||||
if (p.getPetPrice() != null) {
|
if (p.getPetPrice() != null) {
|
||||||
@@ -241,6 +275,11 @@ public class PetDetailFragment extends Fragment {
|
|||||||
private void setupSpinner() {
|
private void setupSpinner() {
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus, new String[]{});
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus, new String[]{});
|
||||||
|
|
||||||
|
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerPetSpecies, p -> {
|
||||||
|
if (isUpdatingUI) return;
|
||||||
|
viewModel.onSpeciesSelected(p);
|
||||||
|
});
|
||||||
|
|
||||||
binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
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.btnDeletePet.setVisibility(state.isDeleteVisible ? View.VISIBLE : View.GONE);
|
||||||
binding.btnSavePet.setText(state.saveButtonText);
|
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.isBreedEnabled, binding.etPetBreed);
|
||||||
UIUtils.setViewsEnabled(state.isCustomerEnabled, binding.spinnerCustomer);
|
UIUtils.setViewsEnabled(state.isCustomerEnabled, binding.spinnerCustomer);
|
||||||
UIUtils.setViewsEnabled(state.isStoreEnabled, binding.spinnerStore);
|
UIUtils.setViewsEnabled(state.isStoreEnabled, binding.spinnerStore);
|
||||||
@@ -301,6 +340,13 @@ public class PetDetailFragment extends Fragment {
|
|||||||
updateCustomerSpinnerSelection(state.selectedCustomerId);
|
updateCustomerSpinnerSelection(state.selectedCustomerId);
|
||||||
updateStoreSpinnerSelection(state.selectedStoreId);
|
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) {
|
if (!state.isCustomerEnabled && binding.spinnerCustomer.getSelectedItemPosition() != 0) {
|
||||||
binding.spinnerCustomer.setSelection(0);
|
binding.spinnerCustomer.setSelection(0);
|
||||||
}
|
}
|
||||||
@@ -308,9 +354,22 @@ public class PetDetailFragment extends Fragment {
|
|||||||
binding.spinnerStore.setSelection(0);
|
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;
|
isUpdatingUI = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isStaff() {
|
||||||
|
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||||
|
}
|
||||||
|
|
||||||
private void clearSpinnerError(Spinner spinner) {
|
private void clearSpinnerError(Spinner spinner) {
|
||||||
View selectedView = spinner.getSelectedView();
|
View selectedView = spinner.getSelectedView();
|
||||||
if (selectedView instanceof TextView) {
|
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.InputValidator;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -27,6 +31,8 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
private FragmentSaleDetailBinding binding;
|
private FragmentSaleDetailBinding binding;
|
||||||
private SaleDetailViewModel viewModel;
|
private SaleDetailViewModel viewModel;
|
||||||
|
|
||||||
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
private final String[] PAYMENT_METHODS = { "Cash", "Card"};
|
private final String[] PAYMENT_METHODS = { "Cash", "Card"};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -42,11 +48,13 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
|
|
||||||
if (viewModel.isViewOnly()) {
|
if (viewModel.isViewOnly()) {
|
||||||
binding.llAddItemRow.setVisibility(View.GONE);
|
binding.llAddItemRow.setVisibility(View.GONE);
|
||||||
|
binding.llCouponInput.setVisibility(View.GONE);
|
||||||
binding.btnSaveSale.setVisibility(View.GONE);
|
binding.btnSaveSale.setVisibility(View.GONE);
|
||||||
UIUtils.setViewsEnabled(false, binding.spinnerSaleStore, binding.spinnerSaleCustomer, binding.spinnerPaymentMethod);
|
UIUtils.setViewsEnabled(false, binding.spinnerSaleStore, binding.spinnerSaleCustomer, binding.spinnerPaymentMethod);
|
||||||
} else {
|
} else {
|
||||||
loadData();
|
loadData();
|
||||||
setupAddItem();
|
setupAddItem();
|
||||||
|
setupCoupon();
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.btnSaleBack.setOnClickListener(v -> navigateBack());
|
binding.btnSaleBack.setOnClickListener(v -> navigateBack());
|
||||||
@@ -56,10 +64,18 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isStaff() {
|
||||||
|
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||||
|
}
|
||||||
|
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
Long selectedStoreId = isStaff() ? tokenManager.getPrimaryStoreId() : -1L;
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleStore, list,
|
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 -> {
|
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> {
|
||||||
@@ -185,13 +201,77 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
if (sale.getItems() != null) {
|
if (sale.getItems() != null) {
|
||||||
binding.llSaleItems.removeAllViews();
|
binding.llSaleItems.removeAllViews();
|
||||||
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
|
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() {
|
private void setupAddItem() {
|
||||||
binding.btnAddItem.setOnClickListener(v -> {
|
binding.btnAddItem.setOnClickListener(v -> {
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerSaleProduct, "Product")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerSaleProduct, "Product")) return;
|
||||||
@@ -228,15 +308,16 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
break;
|
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;
|
if (getContext() == null) return;
|
||||||
LinearLayout row = new LinearLayout(getContext());
|
LinearLayout row = new LinearLayout(getContext());
|
||||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
row.setPadding(0, 8, 0, 8);
|
row.setPadding(0, 8, 0, 8);
|
||||||
|
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
|
||||||
|
|
||||||
TextView tvName = new TextView(getContext());
|
TextView tvName = new TextView(getContext());
|
||||||
tvName.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f));
|
tvName.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f));
|
||||||
@@ -253,12 +334,34 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
row.addView(tvName);
|
row.addView(tvName);
|
||||||
row.addView(tvQty);
|
row.addView(tvQty);
|
||||||
row.addView(tvPrice);
|
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);
|
binding.llSaleItems.addView(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTotal() {
|
private void updateTotal() {
|
||||||
BigDecimal total = viewModel.calculateSubtotal();
|
BigDecimal subtotal = viewModel.calculateSubtotal();
|
||||||
binding.tvSaleSubtotal.setText("$" + total);
|
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);
|
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);
|
SaleDTO dto = new SaleDTO(store.getId(), payment, viewModel.getCartItems().getValue(), false, null, customerId);
|
||||||
|
dto.setCouponId(viewModel.getAppliedCouponId());
|
||||||
|
|
||||||
viewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ public class AdoptionRepository extends BaseRepository {
|
|||||||
/**
|
/**
|
||||||
* Retrieves a paginated list of all adoptions from the API.
|
* 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) {
|
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));
|
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.
|
* 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) {
|
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));
|
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));
|
return executeCall(couponApi.getCouponById(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<CouponDTO>> getCouponByCode(String code) {
|
||||||
|
return executeCall(couponApi.getCouponByCode(code));
|
||||||
|
}
|
||||||
|
|
||||||
public LiveData<Resource<CouponDTO>> createCoupon(CouponDTO coupon) {
|
public LiveData<Resource<CouponDTO>> createCoupon(CouponDTO coupon) {
|
||||||
return executeCall(couponApi.createCoupon(coupon));
|
return executeCall(couponApi.createCoupon(coupon));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,24 +23,27 @@ public class CustomerRepository extends BaseRepository {
|
|||||||
this.customerApi = customerApi;
|
this.customerApi = customerApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a paginated list of all customers from the API.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<CustomerDTO>>> getAllCustomers(int page, int size) {
|
public LiveData<Resource<PageResponse<CustomerDTO>>> getAllCustomers(int page, int size) {
|
||||||
return executeCall(customerApi.getAllCustomers(page, size));
|
return executeCall(customerApi.getAllCustomers(page, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a specific customer by their ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<CustomerDTO>> getCustomerById(Long id) {
|
public LiveData<Resource<CustomerDTO>> getCustomerById(Long id) {
|
||||||
return executeCall(customerApi.getCustomerById(id));
|
return executeCall(customerApi.getCustomerById(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public LiveData<Resource<CustomerDTO>> createCustomer(CustomerDTO dto) {
|
||||||
* Retrieves a list of customer dropdowns from the API.
|
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() {
|
public LiveData<Resource<List<DropdownDTO>>> getCustomerDropdowns() {
|
||||||
return executeCall(customerApi.getCustomerDropdowns());
|
return executeCall(customerApi.getCustomerDropdowns());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ public class PetRepository extends BaseRepository {
|
|||||||
return executeCall(petApi.getPetDropdowns());
|
return executeCall(petApi.getPetDropdowns());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> getPetSpeciesDropdowns() {
|
||||||
|
return executeCall(petApi.getPetSpeciesDropdowns());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves available pets for a specific store.
|
* 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.DateTimeUtils;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -71,18 +73,17 @@ public class AdoptionDetailViewModel extends ViewModel {
|
|||||||
state.saveButtonText = isEditing ? "Save" : "Add";
|
state.saveButtonText = isEditing ? "Save" : "Add";
|
||||||
state.isAdoptionIdVisible = isEditing;
|
state.isAdoptionIdVisible = isEditing;
|
||||||
state.isDeleteVisible = isEditing;
|
state.isDeleteVisible = isEditing;
|
||||||
state.isFeeEnabled = false; // fee is always read-only
|
state.isFeeEnabled = false;
|
||||||
if (!isEditing) {
|
if (!isEditing) {
|
||||||
state.isCustomerEnabled = true;
|
state.isCustomerEnabled = true;
|
||||||
state.isStoreEnabled = true;
|
state.isStoreEnabled = true;
|
||||||
state.isPetEnabled = false; // until customer selected
|
state.isPetEnabled = false;
|
||||||
state.isEmployeeEnabled = false; // until store selected
|
state.isEmployeeEnabled = false;
|
||||||
state.isDateEnabled = true;
|
state.isDateEnabled = true;
|
||||||
state.isStatusEnabled = true;
|
state.isStatusEnabled = true;
|
||||||
state.availableStatuses = new String[]{"Pending"};
|
state.availableStatuses = new String[]{"Pending"};
|
||||||
state.selectedStatus = "Pending";
|
state.selectedStatus = "Pending";
|
||||||
} else {
|
} else {
|
||||||
// edit: date-based logic applied after load
|
|
||||||
state.isCustomerEnabled = false;
|
state.isCustomerEnabled = false;
|
||||||
state.isStoreEnabled = false;
|
state.isStoreEnabled = false;
|
||||||
state.isPetEnabled = false;
|
state.isPetEnabled = false;
|
||||||
@@ -95,12 +96,12 @@ public class AdoptionDetailViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void loadInitialFormData(boolean isEditing) {
|
public void loadInitialFormData(boolean isEditing) {
|
||||||
// Pets are loaded dynamically based on store selection; no pre-load needed.
|
// 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) {
|
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||||
customerList.setValue(r.data);
|
customerList.setValue(r.data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
storeRepository.getStoreDropdowns().observeForever(r -> {
|
observeOnce(storeRepository.getStoreDropdowns(), r -> {
|
||||||
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||||
storeList.setValue(r.data);
|
storeList.setValue(r.data);
|
||||||
}
|
}
|
||||||
@@ -155,7 +156,7 @@ public class AdoptionDetailViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadAvailablePetsByStore(Long storeId) {
|
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) {
|
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||||
List<DropdownDTO> dropdowns = new ArrayList<>();
|
List<DropdownDTO> dropdowns = new ArrayList<>();
|
||||||
for (com.example.petstoremobile.dtos.PetDTO pet : r.data.getContent()) {
|
for (com.example.petstoremobile.dtos.PetDTO pet : r.data.getContent()) {
|
||||||
@@ -167,7 +168,7 @@ public class AdoptionDetailViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadPetPrice(Long petId) {
|
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) {
|
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||||
com.example.petstoremobile.dtos.PetDTO pet = r.data;
|
com.example.petstoremobile.dtos.PetDTO pet = r.data;
|
||||||
// In edit mode, add the pet to the list so the spinner can display its name
|
// 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) {
|
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) {
|
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||||
employeeList.setValue(r.data);
|
employeeList.setValue(r.data);
|
||||||
}
|
}
|
||||||
@@ -217,6 +218,10 @@ public class AdoptionDetailViewModel extends ViewModel {
|
|||||||
s.isEmployeeEnabled = true;
|
s.isEmployeeEnabled = true;
|
||||||
s.isDateEnabled = true;
|
s.isDateEnabled = true;
|
||||||
s.isStatusEnabled = 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() {
|
public LiveData<Resource<AdoptionDTO>> loadAdoption() {
|
||||||
MutableLiveData<Resource<AdoptionDTO>> result = new MutableLiveData<>();
|
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) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
AdoptionDTO a = resource.data;
|
AdoptionDTO a = resource.data;
|
||||||
String formattedStatus = DateTimeUtils.formatStatusFromBackend(
|
String formattedStatus = DateTimeUtils.formatStatusFromBackend(
|
||||||
@@ -248,10 +253,10 @@ public class AdoptionDetailViewModel extends ViewModel {
|
|||||||
String adoptionDate = a.getAdoptionDate() != null ? a.getAdoptionDate() : "";
|
String adoptionDate = a.getAdoptionDate() != null ? a.getAdoptionDate() : "";
|
||||||
|
|
||||||
updateViewState(state -> {
|
updateViewState(state -> {
|
||||||
state.selectedPetId = a.getPetId() != null ? a.getPetId() : -1;
|
state.selectedPetId = a.getPetId();
|
||||||
state.selectedCustomerId = a.getCustomerId() != null ? a.getCustomerId() : -1;
|
state.selectedCustomerId = a.getCustomerId();
|
||||||
state.selectedStoreId = a.getSourceStoreId() != null ? a.getSourceStoreId() : -1;
|
state.selectedStoreId = a.getSourceStoreId();
|
||||||
state.selectedEmployeeId = a.getEmployeeId() != null ? a.getEmployeeId() : -1;
|
state.selectedEmployeeId = a.getEmployeeId();
|
||||||
state.adoptionDate = adoptionDate;
|
state.adoptionDate = adoptionDate;
|
||||||
state.adoptionFee = a.getAdoptionFee() != null ? a.getAdoptionFee().toPlainString() : "";
|
state.adoptionFee = a.getAdoptionFee() != null ? a.getAdoptionFee().toPlainString() : "";
|
||||||
state.selectedStatus = formattedStatus;
|
state.selectedStatus = formattedStatus;
|
||||||
@@ -321,6 +326,21 @@ public class AdoptionDetailViewModel extends ViewModel {
|
|||||||
void run(T target);
|
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 static class ViewState {
|
||||||
public boolean isEditing = false;
|
public boolean isEditing = false;
|
||||||
public boolean isAdoptionIdVisible = 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.repositories.StoreRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -53,7 +55,7 @@ public class AdoptionListViewModel extends ViewModel {
|
|||||||
if ("All Statuses".equals(status)) status = null;
|
if ("All Statuses".equals(status)) status = null;
|
||||||
|
|
||||||
isLoading.setValue(true);
|
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 != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
List<AdoptionDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(adoptions.getValue());
|
List<AdoptionDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(adoptions.getValue());
|
||||||
@@ -70,13 +72,25 @@ public class AdoptionListViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadStores() {
|
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) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
stores.setValue(resource.data.getContent());
|
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) {
|
public LiveData<Resource<Void>> bulkDeleteAdoptions(List<String> ids) {
|
||||||
return adoptionRepository.bulkDeleteAdoptions(new BulkDeleteRequest(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.repositories.SaleRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -49,7 +51,7 @@ public class AnalyticsViewModel extends ViewModel {
|
|||||||
public void loadAnalytics() {
|
public void loadAnalytics() {
|
||||||
isLoading.setValue(true);
|
isLoading.setValue(true);
|
||||||
errorMessage.setValue(null);
|
errorMessage.setValue(null);
|
||||||
saleRepository.getAllSales(0, 2000, null, null, null, null, "saleDate,desc").observeForever(resource -> {
|
observeOnce(saleRepository.getAllSales(0, 2000, null, null, null, null, "saleDate,desc"), resource -> {
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
cachedSales = resource.data.getContent();
|
cachedSales = resource.data.getContent();
|
||||||
@@ -247,6 +249,18 @@ public class AnalyticsViewModel extends ViewModel {
|
|||||||
return "Daily Revenue (" + s + " – " + e + ")";
|
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 static class FilterState {
|
||||||
public String startDate = "";
|
public String startDate = "";
|
||||||
public String endDate = "";
|
public String endDate = "";
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import com.example.petstoremobile.repositories.StoreRepository;
|
|||||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -42,6 +44,7 @@ public class AppointmentDetailViewModel extends ViewModel {
|
|||||||
|
|
||||||
private long appointmentId = -1;
|
private long appointmentId = -1;
|
||||||
private boolean isOriginallyCancel = false;
|
private boolean isOriginallyCancel = false;
|
||||||
|
private boolean isOriginallyCompletedOrMissed = false;
|
||||||
private Long currentCustomerId;
|
private Long currentCustomerId;
|
||||||
private Long currentStoreId;
|
private Long currentStoreId;
|
||||||
private Long currentPetId;
|
private Long currentPetId;
|
||||||
@@ -71,14 +74,14 @@ public class AppointmentDetailViewModel extends ViewModel {
|
|||||||
* Loads initial dropdown data for customers, stores, and services.
|
* Loads initial dropdown data for customers, stores, and services.
|
||||||
*/
|
*/
|
||||||
public void loadInitialFormData() {
|
public void loadInitialFormData() {
|
||||||
customerRepository.getCustomerDropdowns().observeForever(r -> {
|
observeOnce(customerRepository.getCustomerDropdowns(), r -> {
|
||||||
if (r.status == Resource.Status.SUCCESS) customers.setValue(r.data);
|
if (r != null && r.status == Resource.Status.SUCCESS) customers.setValue(r.data);
|
||||||
});
|
});
|
||||||
storeRepository.getStoreDropdowns().observeForever(r -> {
|
observeOnce(storeRepository.getStoreDropdowns(), r -> {
|
||||||
if (r.status == Resource.Status.SUCCESS) stores.setValue(r.data);
|
if (r != null && r.status == Resource.Status.SUCCESS) stores.setValue(r.data);
|
||||||
});
|
});
|
||||||
serviceRepository.getAllServices(0, 200, null, "serviceName").observeForever(r -> {
|
observeOnce(serviceRepository.getAllServices(0, 200, null, "serviceName"), r -> {
|
||||||
if (r.status == Resource.Status.SUCCESS && r.data != null) services.setValue(r.data.getContent());
|
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.
|
* Loads the list of pets for a specific customer.
|
||||||
*/
|
*/
|
||||||
private void loadPetsForCustomer(Long customerId) {
|
private void loadPetsForCustomer(Long customerId) {
|
||||||
petRepository.getCustomerPets(customerId).observeForever(r -> {
|
observeOnce(petRepository.getCustomerPets(customerId), r -> {
|
||||||
if (r.status == Resource.Status.SUCCESS) customerPets.setValue(r.data);
|
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.
|
* Loads the list of employees for a specific store.
|
||||||
*/
|
*/
|
||||||
private void loadEmployeesForStore(Long storeId) {
|
private void loadEmployeesForStore(Long storeId) {
|
||||||
storeRepository.getStoreEmployees(storeId).observeForever(r -> {
|
observeOnce(storeRepository.getStoreEmployees(storeId), r -> {
|
||||||
if (r.status == Resource.Status.SUCCESS) storeEmployees.setValue(r.data);
|
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() {
|
public LiveData<Resource<AppointmentDTO>> loadAppointment() {
|
||||||
MutableLiveData<Resource<AppointmentDTO>> result = new MutableLiveData<>();
|
MutableLiveData<Resource<AppointmentDTO>> result = new MutableLiveData<>();
|
||||||
repository.getAppointmentById(appointmentId).observeForever(resource -> {
|
observeOnce(repository.getAppointmentById(appointmentId), resource -> {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
AppointmentDTO a = resource.data;
|
AppointmentDTO a = resource.data;
|
||||||
isOriginallyCancel = "CANCELLED".equalsIgnoreCase(a.getAppointmentStatus());
|
isOriginallyCancel = "CANCELLED".equalsIgnoreCase(a.getAppointmentStatus());
|
||||||
|
isOriginallyCompletedOrMissed = "COMPLETED".equalsIgnoreCase(a.getAppointmentStatus())
|
||||||
|
|| "MISSED".equalsIgnoreCase(a.getAppointmentStatus());
|
||||||
currentCustomerId = a.getCustomerId();
|
currentCustomerId = a.getCustomerId();
|
||||||
currentStoreId = a.getStoreId();
|
currentStoreId = a.getStoreId();
|
||||||
currentPetId = a.getPetId();
|
currentPetId = a.getPetId();
|
||||||
@@ -282,21 +287,18 @@ public class AppointmentDetailViewModel extends ViewModel {
|
|||||||
public void onDateOrTimeChanged(String date, String time, String currentStatus) {
|
public void onDateOrTimeChanged(String date, String time, String currentStatus) {
|
||||||
updateViewState(s -> {
|
updateViewState(s -> {
|
||||||
s.availableStatuses = calculateAvailableStatuses(s.isEditing, date, time, currentStatus);
|
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);
|
java.util.List<String> available = java.util.Arrays.asList(s.availableStatuses);
|
||||||
if (!currentStatus.isEmpty() && available.contains(currentStatus)) {
|
if (!currentStatus.isEmpty() && available.contains(currentStatus)) {
|
||||||
s.selectedStatus = currentStatus;
|
s.selectedStatus = currentStatus;
|
||||||
} else if (!available.contains(s.selectedStatus) && s.availableStatuses.length > 0) {
|
} else if (!available.contains(s.selectedStatus) && s.availableStatuses.length > 0) {
|
||||||
s.selectedStatus = s.availableStatuses[0];
|
s.selectedStatus = s.availableStatuses[0];
|
||||||
}
|
}
|
||||||
boolean isPast = DateTimeUtils.isDateTimeInPast(date, time);
|
|
||||||
|
|
||||||
if (isOriginallyCancel) {
|
if (isOriginallyCancel) {
|
||||||
s.isPast = true;
|
s.isPast = true;
|
||||||
setAllFieldsEnabled(s, false);
|
setAllFieldsEnabled(s, false);
|
||||||
s.isStatusEnabled = false;
|
s.isStatusEnabled = false;
|
||||||
s.isSaveVisible = false;
|
s.isSaveVisible = false;
|
||||||
} else if (isPast) {
|
} else if (isOriginallyCompletedOrMissed) {
|
||||||
s.isPast = true;
|
s.isPast = true;
|
||||||
setAllFieldsEnabled(s, false);
|
setAllFieldsEnabled(s, false);
|
||||||
s.isStatusEnabled = true;
|
s.isStatusEnabled = true;
|
||||||
@@ -322,7 +324,7 @@ public class AppointmentDetailViewModel extends ViewModel {
|
|||||||
if (!isEditing) return new String[]{"Booked"};
|
if (!isEditing) return new String[]{"Booked"};
|
||||||
if (date == null || date.isEmpty()) return new String[]{};
|
if (date == null || date.isEmpty()) return new String[]{};
|
||||||
if (isOriginallyCancel) return new String[]{"Cancelled"};
|
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"};
|
return new String[]{"Booked", "Cancelled"};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,8 +357,8 @@ public class AppointmentDetailViewModel extends ViewModel {
|
|||||||
s.isCustomerEnabled = true;
|
s.isCustomerEnabled = true;
|
||||||
s.isStoreEnabled = true;
|
s.isStoreEnabled = true;
|
||||||
s.isServiceEnabled = true;
|
s.isServiceEnabled = true;
|
||||||
s.isPetEnabled = false; // until customer selected
|
s.isPetEnabled = false;
|
||||||
s.isStaffEnabled = false; // until store selected
|
s.isStaffEnabled = false;
|
||||||
s.availableStatuses = new String[]{"Booked"};
|
s.availableStatuses = new String[]{"Booked"};
|
||||||
s.selectedStatus = "Booked";
|
s.selectedStatus = "Booked";
|
||||||
}
|
}
|
||||||
@@ -385,6 +387,19 @@ public class AppointmentDetailViewModel extends ViewModel {
|
|||||||
void run(T t);
|
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.
|
* 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.repositories.StoreRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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) {
|
public void loadAppointments(String query, String status, Long storeId, String date, Long employeeId) {
|
||||||
isLoading.setValue(true);
|
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 != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
appointments.setValue(resource.data.getContent());
|
appointments.setValue(resource.data.getContent());
|
||||||
@@ -52,13 +54,25 @@ public class AppointmentListViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadStores() {
|
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) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
stores.setValue(resource.data.getContent());
|
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) {
|
public LiveData<Resource<Void>> bulkDeleteAppointments(List<String> ids) {
|
||||||
return appointmentRepository.bulkDeleteAppointments(new BulkDeleteRequest(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.repositories.CouponRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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) {
|
public void loadCoupons(int page, int size, Boolean active, String discountType, String sort) {
|
||||||
isLoading.setValue(true);
|
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 != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
coupons.setValue(resource.data.getContent());
|
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) {
|
public LiveData<Resource<Void>> bulkDeleteCoupons(List<Long> ids) {
|
||||||
return repository.bulkDeleteCoupons(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.repositories.StoreRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -51,7 +53,7 @@ public class InventoryListViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isLoading.setValue(true);
|
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 != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
List<InventoryDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(inventory.getValue());
|
List<InventoryDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(inventory.getValue());
|
||||||
@@ -68,13 +70,25 @@ public class InventoryListViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadStores() {
|
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) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
stores.setValue(resource.data.getContent());
|
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) {
|
public LiveData<Resource<Void>> bulkDeleteInventory(List<String> ids) {
|
||||||
return inventoryRepository.bulkDeleteInventory(new BulkDeleteRequest(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.repositories.StoreRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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>> customerList = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<List<DropdownDTO>> storeList = 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<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
||||||
|
|
||||||
private long petId = -1;
|
private long petId = -1;
|
||||||
private Long selectedCustomerId = null;
|
private Long selectedCustomerId = null;
|
||||||
private Long selectedStoreId = null;
|
private Long selectedStoreId = null;
|
||||||
|
private String selectedSpecies = null;
|
||||||
|
private boolean isOriginallyOwnedOrAdopted = false;
|
||||||
|
private Long originalCustomerId = null;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PetDetailViewModel(PetRepository petRepository, CustomerRepository customerRepository, StoreRepository storeRepository) {
|
public PetDetailViewModel(PetRepository petRepository, CustomerRepository customerRepository, StoreRepository storeRepository) {
|
||||||
@@ -45,17 +51,23 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadInitialFormData() {
|
public void loadInitialFormData() {
|
||||||
customerRepository.getCustomerDropdowns().observeForever(resource -> {
|
observeOnce(customerRepository.getCustomerDropdowns(), resource -> {
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
customerList.setValue(resource.data);
|
customerList.setValue(resource.data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
storeRepository.getStoreDropdowns().observeForever(resource -> {
|
observeOnce(storeRepository.getStoreDropdowns(), resource -> {
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
storeList.setValue(resource.data);
|
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) {
|
public void setPetId(long id) {
|
||||||
@@ -72,6 +84,14 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
return current != null && current.isEditing;
|
return current != null && current.isEditing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isOriginallyOwnedOrAdopted() {
|
||||||
|
return isOriginallyOwnedOrAdopted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getOriginalCustomerId() {
|
||||||
|
return originalCustomerId;
|
||||||
|
}
|
||||||
|
|
||||||
public LiveData<ViewState> getViewState() {
|
public LiveData<ViewState> getViewState() {
|
||||||
return viewState;
|
return viewState;
|
||||||
}
|
}
|
||||||
@@ -87,6 +107,16 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
updateViewState(state -> state.selectedCustomerId = selectedCustomerId);
|
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) {
|
public void onStoreSelected(int position) {
|
||||||
List<DropdownDTO> list = storeList.getValue();
|
List<DropdownDTO> list = storeList.getValue();
|
||||||
if (position > 0 && list != null && position <= list.size()) {
|
if (position > 0 && list != null && position <= list.size()) {
|
||||||
@@ -123,8 +153,10 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
if (!isEditing) {
|
if (!isEditing) {
|
||||||
selectedCustomerId = null;
|
selectedCustomerId = null;
|
||||||
selectedStoreId = null;
|
selectedStoreId = null;
|
||||||
|
selectedSpecies = null;
|
||||||
state.selectedCustomerId = null;
|
state.selectedCustomerId = null;
|
||||||
state.selectedStoreId = null;
|
state.selectedStoreId = null;
|
||||||
|
state.selectedSpecies = null;
|
||||||
state.selectedStatus = STATUS_AVAILABLE;
|
state.selectedStatus = STATUS_AVAILABLE;
|
||||||
state.isCustomerEnabled = false;
|
state.isCustomerEnabled = false;
|
||||||
state.isStoreEnabled = true;
|
state.isStoreEnabled = true;
|
||||||
@@ -134,15 +166,20 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
|
|
||||||
public LiveData<Resource<PetDTO>> loadPet() {
|
public LiveData<Resource<PetDTO>> loadPet() {
|
||||||
MutableLiveData<Resource<PetDTO>> result = new MutableLiveData<>();
|
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) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
PetDTO pet = resource.data;
|
PetDTO pet = resource.data;
|
||||||
selectedCustomerId = pet.getCustomerId();
|
selectedCustomerId = pet.getCustomerId();
|
||||||
selectedStoreId = pet.getStoreId();
|
selectedStoreId = pet.getStoreId();
|
||||||
|
selectedSpecies = pet.getPetSpecies();
|
||||||
|
isOriginallyOwnedOrAdopted = STATUS_OWNED.equalsIgnoreCase(pet.getPetStatus())
|
||||||
|
|| STATUS_ADOPTED.equalsIgnoreCase(pet.getPetStatus());
|
||||||
|
originalCustomerId = pet.getCustomerId();
|
||||||
|
|
||||||
updateViewState(state -> {
|
updateViewState(state -> {
|
||||||
state.selectedCustomerId = selectedCustomerId;
|
state.selectedCustomerId = selectedCustomerId;
|
||||||
state.selectedStoreId = selectedStoreId;
|
state.selectedStoreId = selectedStoreId;
|
||||||
|
state.selectedSpecies = selectedSpecies;
|
||||||
state.selectedStatus = normalizeStatus(pet.getPetStatus());
|
state.selectedStatus = normalizeStatus(pet.getPetStatus());
|
||||||
applyStatusRules(state, false);
|
applyStatusRules(state, false);
|
||||||
});
|
});
|
||||||
@@ -174,6 +211,10 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
return storeList;
|
return storeList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LiveData<List<DropdownDTO>> getSpeciesList() {
|
||||||
|
return speciesList;
|
||||||
|
}
|
||||||
|
|
||||||
public LiveData<Boolean> getIsLoading() {
|
public LiveData<Boolean> getIsLoading() {
|
||||||
return isLoading;
|
return isLoading;
|
||||||
}
|
}
|
||||||
@@ -227,6 +268,17 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
void run(T target);
|
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 static class ViewState {
|
||||||
public boolean isEditing = false;
|
public boolean isEditing = false;
|
||||||
@@ -240,6 +292,7 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
public String saveButtonText = "Add";
|
public String saveButtonText = "Add";
|
||||||
public String[] availableStatuses = new String[]{STATUS_AVAILABLE, STATUS_ADOPTED, STATUS_OWNED};
|
public String[] availableStatuses = new String[]{STATUS_AVAILABLE, STATUS_ADOPTED, STATUS_OWNED};
|
||||||
public String selectedStatus = STATUS_AVAILABLE;
|
public String selectedStatus = STATUS_AVAILABLE;
|
||||||
|
public String selectedSpecies = null;
|
||||||
public Long selectedCustomerId = null;
|
public Long selectedCustomerId = null;
|
||||||
public Long selectedStoreId = null;
|
public Long selectedStoreId = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData;
|
|||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
import com.example.petstoremobile.dtos.PetDTO;
|
import com.example.petstoremobile.dtos.PetDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
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.repositories.StoreRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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<PetDTO>> pets = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<List<StoreDTO>> stores = 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);
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -36,6 +40,7 @@ public class PetListViewModel extends ViewModel {
|
|||||||
|
|
||||||
public LiveData<List<PetDTO>> getPets() { return pets; }
|
public LiveData<List<PetDTO>> getPets() { return pets; }
|
||||||
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||||
|
public LiveData<List<String>> getSpeciesOptions() { return speciesOptions; }
|
||||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
|
||||||
public void loadPets(String query, String status, String species, Long storeId) {
|
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;
|
if ("All Species".equals(species)) species = null;
|
||||||
|
|
||||||
isLoading.setValue(true);
|
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 != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
pets.setValue(resource.data.getContent());
|
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() {
|
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) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
stores.setValue(resource.data.getContent());
|
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) {
|
public LiveData<Resource<Void>> bulkDeletePets(List<String> ids) {
|
||||||
return petRepository.bulkDeletePets(new BulkDeleteRequest(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.repositories.ProductRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ public class ProductListViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void loadProducts(String query, Long categoryId) {
|
public void loadProducts(String query, Long categoryId) {
|
||||||
isLoading.setValue(true);
|
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 != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
products.setValue(resource.data.getContent());
|
products.setValue(resource.data.getContent());
|
||||||
@@ -51,10 +53,22 @@ public class ProductListViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadCategories() {
|
public void loadCategories() {
|
||||||
productRepository.getCategoryDropdowns().observeForever(resource -> {
|
observeOnce(productRepository.getCategoryDropdowns(), resource -> {
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
categories.setValue(resource.data);
|
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.repositories.SupplierRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -45,7 +47,7 @@ public class ProductSupplierListViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void loadProductSuppliers(String query, Long productId, Long supplierId) {
|
public void loadProductSuppliers(String query, Long productId, Long supplierId) {
|
||||||
isLoading.setValue(true);
|
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 != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
productSuppliers.setValue(resource.data.getContent());
|
productSuppliers.setValue(resource.data.getContent());
|
||||||
@@ -58,19 +60,31 @@ public class ProductSupplierListViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadFilterData() {
|
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) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
products.setValue(resource.data.getContent());
|
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) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
suppliers.setValue(resource.data.getContent());
|
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) {
|
public LiveData<Resource<Void>> bulkDeleteProductSuppliers(List<String> ids) {
|
||||||
return psRepository.bulkDeleteProductSuppliers(new BulkDeleteRequest(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.repositories.StoreRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ public class PurchaseOrderListViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void loadPurchaseOrders(String query, Long storeId) {
|
public void loadPurchaseOrders(String query, Long storeId) {
|
||||||
isLoading.setValue(true);
|
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 != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
purchaseOrders.setValue(resource.data.getContent());
|
purchaseOrders.setValue(resource.data.getContent());
|
||||||
@@ -51,10 +53,22 @@ public class PurchaseOrderListViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadStores() {
|
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) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
stores.setValue(resource.data.getContent());
|
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.MutableLiveData;
|
||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.CouponDTO;
|
||||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import com.example.petstoremobile.dtos.SaleDTO;
|
import com.example.petstoremobile.dtos.SaleDTO;
|
||||||
|
import com.example.petstoremobile.repositories.CouponRepository;
|
||||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||||
import com.example.petstoremobile.repositories.ProductRepository;
|
import com.example.petstoremobile.repositories.ProductRepository;
|
||||||
import com.example.petstoremobile.repositories.SaleRepository;
|
import com.example.petstoremobile.repositories.SaleRepository;
|
||||||
@@ -27,6 +29,7 @@ public class SaleDetailViewModel extends ViewModel {
|
|||||||
private final StoreRepository storeRepository;
|
private final StoreRepository storeRepository;
|
||||||
private final CustomerRepository customerRepository;
|
private final CustomerRepository customerRepository;
|
||||||
private final ProductRepository productRepository;
|
private final ProductRepository productRepository;
|
||||||
|
private final CouponRepository couponRepository;
|
||||||
|
|
||||||
private long saleId = -1;
|
private long saleId = -1;
|
||||||
private boolean viewOnly = false;
|
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<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<List<ProductDTO>> productList = 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<List<SaleDTO.SaleItemDTO>> cartItems = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<CouponDTO> appliedCoupon = new MutableLiveData<>(null);
|
||||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SaleDetailViewModel(SaleRepository saleRepository, StoreRepository storeRepository,
|
public SaleDetailViewModel(SaleRepository saleRepository, StoreRepository storeRepository,
|
||||||
CustomerRepository customerRepository, ProductRepository productRepository) {
|
CustomerRepository customerRepository, ProductRepository productRepository,
|
||||||
|
CouponRepository couponRepository) {
|
||||||
this.saleRepository = saleRepository;
|
this.saleRepository = saleRepository;
|
||||||
this.storeRepository = storeRepository;
|
this.storeRepository = storeRepository;
|
||||||
this.customerRepository = customerRepository;
|
this.customerRepository = customerRepository;
|
||||||
this.productRepository = productRepository;
|
this.productRepository = productRepository;
|
||||||
|
this.couponRepository = couponRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSaleId(long id, boolean viewOnly) {
|
public void setSaleId(long id, boolean viewOnly) {
|
||||||
@@ -89,8 +95,46 @@ public class SaleDetailViewModel extends ViewModel {
|
|||||||
cartItems.setValue(currentCart);
|
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<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() {
|
public BigDecimal calculateSubtotal() {
|
||||||
BigDecimal total = BigDecimal.ZERO;
|
BigDecimal total = BigDecimal.ZERO;
|
||||||
List<SaleDTO.SaleItemDTO> items = cartItems.getValue();
|
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.repositories.StoreRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -51,7 +53,7 @@ public class SaleListViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isLoading.setValue(true);
|
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 != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
List<SaleDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(sales.getValue());
|
List<SaleDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(sales.getValue());
|
||||||
@@ -68,10 +70,22 @@ public class SaleListViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadStores() {
|
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) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
stores.setValue(resource.data.getContent());
|
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.repositories.ServiceRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
@@ -55,7 +57,7 @@ public class ServiceDetailViewModel extends ViewModel {
|
|||||||
|
|
||||||
public LiveData<Resource<ServiceDTO>> loadService() {
|
public LiveData<Resource<ServiceDTO>> loadService() {
|
||||||
MutableLiveData<Resource<ServiceDTO>> result = new MutableLiveData<>();
|
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) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
ServiceDTO service = resource.data;
|
ServiceDTO service = resource.data;
|
||||||
updateViewState(state -> {
|
updateViewState(state -> {
|
||||||
@@ -106,6 +108,18 @@ public class ServiceDetailViewModel extends ViewModel {
|
|||||||
void run(T target);
|
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 static class ViewState {
|
||||||
public boolean isEditing = false;
|
public boolean isEditing = false;
|
||||||
public boolean isDeleteVisible = 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.repositories.ServiceRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -46,7 +48,7 @@ public class ServiceListViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isLoading.setValue(true);
|
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 != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
List<ServiceDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(services.getValue());
|
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) {
|
public LiveData<Resource<Void>> bulkDeleteServices(List<String> ids) {
|
||||||
return repository.bulkDeleteServices(new BulkDeleteRequest(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.repositories.StoreRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -42,7 +44,7 @@ public class StaffListViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void loadStaff() {
|
public void loadStaff() {
|
||||||
isLoading.setValue(true);
|
isLoading.setValue(true);
|
||||||
repository.getAllEmployees(0, 100).observeForever(resource -> {
|
observeOnce(repository.getAllEmployees(0, 100), resource -> {
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
employees.setValue(resource.data.getContent());
|
employees.setValue(resource.data.getContent());
|
||||||
@@ -56,13 +58,25 @@ public class StaffListViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadStores() {
|
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) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
stores.setValue(resource.data.getContent());
|
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) {
|
public void filter(String query, Long storeId, String status) {
|
||||||
this.lastQuery = query;
|
this.lastQuery = query;
|
||||||
this.lastStoreId = storeId;
|
this.lastStoreId = storeId;
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import com.example.petstoremobile.dtos.SupplierDTO;
|
|||||||
import com.example.petstoremobile.repositories.SupplierRepository;
|
import com.example.petstoremobile.repositories.SupplierRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
@@ -55,7 +57,7 @@ public class SupplierDetailViewModel extends ViewModel {
|
|||||||
|
|
||||||
public LiveData<Resource<SupplierDTO>> loadSupplier() {
|
public LiveData<Resource<SupplierDTO>> loadSupplier() {
|
||||||
MutableLiveData<Resource<SupplierDTO>> result = new MutableLiveData<>();
|
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) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
SupplierDTO s = resource.data;
|
SupplierDTO s = resource.data;
|
||||||
updateViewState(state -> {
|
updateViewState(state -> {
|
||||||
@@ -99,6 +101,18 @@ public class SupplierDetailViewModel extends ViewModel {
|
|||||||
void run(T target);
|
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 static class ViewState {
|
||||||
public boolean isEditing = false;
|
public boolean isEditing = false;
|
||||||
public boolean isDeleteVisible = 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.repositories.SupplierRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -33,7 +35,7 @@ public class SupplierListViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void loadSuppliers(String query) {
|
public void loadSuppliers(String query) {
|
||||||
isLoading.setValue(true);
|
isLoading.setValue(true);
|
||||||
repository.getAllSuppliers(0, 100, query, "supCompany").observeForever(resource -> {
|
observeOnce(repository.getAllSuppliers(0, 100, query, "supCompany"), resource -> {
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
suppliers.setValue(resource.data.getContent());
|
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) {
|
public LiveData<Resource<Void>> bulkDeleteSuppliers(List<String> ids) {
|
||||||
return repository.bulkDeleteSuppliers(new BulkDeleteRequest(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:textColor="@color/white"
|
||||||
android:textSize="16sp"/>
|
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>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -60,6 +60,17 @@
|
|||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
android:layout_marginBottom="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 -->
|
<!-- Coupon Code -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
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"/>
|
android:textSize="15sp"/>
|
||||||
</LinearLayout>
|
</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
|
<LinearLayout
|
||||||
android:id="@+id/drawerServices"
|
android:id="@+id/drawerServices"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -345,6 +362,7 @@
|
|||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="15sp"/>
|
android:textSize="15sp"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -95,14 +95,11 @@
|
|||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<EditText
|
<Spinner
|
||||||
android:id="@+id/etPetSpecies"
|
android:id="@+id/spinnerPetSpecies"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="e.g. Dog, Cat, Bird"
|
android:layout_marginBottom="16dp"/>
|
||||||
android:inputType="text"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
@@ -262,6 +262,66 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"/>
|
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
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
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"
|
android:label="Staff Details"
|
||||||
tools:layout="@layout/fragment_staff_detail" />
|
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
|
<fragment
|
||||||
android:id="@+id/nav_analytics"
|
android:id="@+id/nav_analytics"
|
||||||
android:name="com.example.petstoremobile.fragments.listfragments.AnalyticsFragment"
|
android:name="com.example.petstoremobile.fragments.listfragments.AnalyticsFragment"
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ public class SaleResponse {
|
|||||||
private String employeeName;
|
private String employeeName;
|
||||||
private Long storeId;
|
private Long storeId;
|
||||||
private String storeName;
|
private String storeName;
|
||||||
|
private Long customerId;
|
||||||
|
private String customerName;
|
||||||
private BigDecimal totalAmount;
|
private BigDecimal totalAmount;
|
||||||
private BigDecimal subtotalAmount;
|
private BigDecimal subtotalAmount;
|
||||||
private BigDecimal couponDiscountAmount;
|
private BigDecimal couponDiscountAmount;
|
||||||
@@ -77,6 +79,22 @@ public class SaleResponse {
|
|||||||
this.storeName = storeName;
|
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() {
|
public BigDecimal getTotalAmount() {
|
||||||
return totalAmount;
|
return totalAmount;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -20,6 +21,8 @@ import java.util.List;
|
|||||||
@Service
|
@Service
|
||||||
public class SaleService {
|
public class SaleService {
|
||||||
|
|
||||||
|
private static final BigDecimal EMPLOYEE_DISCOUNT_PERCENT = new BigDecimal("0.10");
|
||||||
|
|
||||||
private final SaleRepository saleRepository;
|
private final SaleRepository saleRepository;
|
||||||
private final ProductRepository productRepository;
|
private final ProductRepository productRepository;
|
||||||
private final StoreRepository storeRepository;
|
private final StoreRepository storeRepository;
|
||||||
@@ -76,6 +79,7 @@ public class SaleService {
|
|||||||
if (request.getCouponId() != null) {
|
if (request.getCouponId() != null) {
|
||||||
Coupon coupon = couponRepository.findById(request.getCouponId())
|
Coupon coupon = couponRepository.findById(request.getCouponId())
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Coupon not found with id: " + request.getCouponId()));
|
.orElseThrow(() -> new ResourceNotFoundException("Coupon not found with id: " + request.getCouponId()));
|
||||||
|
validateCoupon(coupon);
|
||||||
sale.setCoupon(coupon);
|
sale.setCoupon(coupon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,8 +89,9 @@ public class SaleService {
|
|||||||
sale.setCart(cart);
|
sale.setCart(cart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
User customer = null;
|
||||||
if (request.getCustomerId() != 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()));
|
.orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
|
||||||
sale.setCustomer(customer);
|
sale.setCustomer(customer);
|
||||||
}
|
}
|
||||||
@@ -97,7 +102,7 @@ public class SaleService {
|
|||||||
sale.setOriginalSale(originalSale);
|
sale.setOriginalSale(originalSale);
|
||||||
}
|
}
|
||||||
|
|
||||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
BigDecimal subtotalAmount = BigDecimal.ZERO;
|
||||||
List<SaleItem> saleItems = new ArrayList<>();
|
List<SaleItem> saleItems = new ArrayList<>();
|
||||||
|
|
||||||
if (sale.getIsRefund() && sale.getOriginalSale() != null) {
|
if (sale.getIsRefund() && sale.getOriginalSale() != null) {
|
||||||
@@ -145,9 +150,11 @@ public class SaleService {
|
|||||||
saleItem.setUnitPrice(unitPrice);
|
saleItem.setUnitPrice(unitPrice);
|
||||||
|
|
||||||
saleItems.add(saleItem);
|
saleItems.add(saleItem);
|
||||||
totalAmount = totalAmount.add(itemTotal);
|
subtotalAmount = subtotalAmount.add(itemTotal);
|
||||||
}
|
}
|
||||||
totalAmount = totalAmount.negate();
|
subtotalAmount = subtotalAmount.negate();
|
||||||
|
sale.setSubtotalAmount(subtotalAmount);
|
||||||
|
sale.setTotalAmount(subtotalAmount);
|
||||||
} else {
|
} else {
|
||||||
for (var itemRequest : request.getItems()) {
|
for (var itemRequest : request.getItems()) {
|
||||||
Product product = productRepository.findById(itemRequest.getProdId())
|
Product product = productRepository.findById(itemRequest.getProdId())
|
||||||
@@ -174,17 +181,77 @@ public class SaleService {
|
|||||||
saleItem.setUnitPrice(unitPrice);
|
saleItem.setUnitPrice(unitPrice);
|
||||||
|
|
||||||
saleItems.add(saleItem);
|
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.setItems(saleItems);
|
||||||
|
|
||||||
Sale savedSale = saleRepository.save(sale);
|
Sale savedSale = saleRepository.save(sale);
|
||||||
return mapToResponse(savedSale);
|
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) {
|
private SaleResponse mapToResponse(Sale sale) {
|
||||||
SaleResponse response = new SaleResponse();
|
SaleResponse response = new SaleResponse();
|
||||||
response.setSaleId(sale.getSaleId());
|
response.setSaleId(sale.getSaleId());
|
||||||
@@ -197,6 +264,11 @@ public class SaleService {
|
|||||||
response.setStoreName(sale.getStore().getStoreName());
|
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.setTotalAmount(sale.getTotalAmount());
|
||||||
response.setSubtotalAmount(sale.getSubtotalAmount());
|
response.setSubtotalAmount(sale.getSubtotalAmount());
|
||||||
response.setCouponDiscountAmount(sale.getCouponDiscountAmount());
|
response.setCouponDiscountAmount(sale.getCouponDiscountAmount());
|
||||||
|
|||||||
Reference in New Issue
Block a user