diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bad499ef..320ffb4a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -66,6 +66,10 @@
+
+
@@ -94,6 +98,8 @@
android:name=".ui.online.groups.creategroup.CreateGroupActivity"
android:windowSoftInputMode="adjustResize|stateHidden" />
+
+
diff --git a/app/src/main/java/org/apache/fineract/couchbase/DocumentType.kt b/app/src/main/java/org/apache/fineract/couchbase/DocumentType.kt
index 9e32fd45..b30b814d 100644
--- a/app/src/main/java/org/apache/fineract/couchbase/DocumentType.kt
+++ b/app/src/main/java/org/apache/fineract/couchbase/DocumentType.kt
@@ -6,5 +6,6 @@ package org.apache.fineract.couchbase
enum class DocumentType(val value: String) {
GROUP("Group"),
- CUSTOMER("customer")
+ CUSTOMER("customer"),
+ PRODUCT("Product")
}
\ No newline at end of file
diff --git a/app/src/main/java/org/apache/fineract/data/models/loan/TermRange.kt b/app/src/main/java/org/apache/fineract/data/models/loan/TermRange.kt
index 5a472c27..ea24ca6e 100644
--- a/app/src/main/java/org/apache/fineract/data/models/loan/TermRange.kt
+++ b/app/src/main/java/org/apache/fineract/data/models/loan/TermRange.kt
@@ -1,12 +1,16 @@
package org.apache.fineract.data.models.loan
+import android.os.Parcelable
import com.google.gson.annotations.SerializedName
+import kotlinx.android.parcel.Parcelize
/**
* @author Rajan Maurya
* On 12/07/17.
*/
+
+@Parcelize
data class TermRange(
@SerializedName("temporalUnit") var temporalUnit: String?,
@SerializedName("maximum") var maximum: Double?
-)
+) : Parcelable
diff --git a/app/src/main/java/org/apache/fineract/data/models/product/BalanceRange.kt b/app/src/main/java/org/apache/fineract/data/models/product/BalanceRange.kt
index 80c90149..c37953da 100644
--- a/app/src/main/java/org/apache/fineract/data/models/product/BalanceRange.kt
+++ b/app/src/main/java/org/apache/fineract/data/models/product/BalanceRange.kt
@@ -1,13 +1,16 @@
package org.apache.fineract.data.models.product
+import android.os.Parcelable
import com.google.gson.annotations.SerializedName
+import kotlinx.android.parcel.Parcelize
/**
* @author Rajan Maurya
* On 20/07/17.
*/
+@Parcelize
data class BalanceRange (
@SerializedName("minimum") var minimum: Double? = null,
@SerializedName("maximum") var maximum: Double? = null
-)
+) : Parcelable
diff --git a/app/src/main/java/org/apache/fineract/data/models/product/InterestBasis.kt b/app/src/main/java/org/apache/fineract/data/models/product/InterestBasis.kt
index a3f39b4b..7781c526 100644
--- a/app/src/main/java/org/apache/fineract/data/models/product/InterestBasis.kt
+++ b/app/src/main/java/org/apache/fineract/data/models/product/InterestBasis.kt
@@ -1,5 +1,7 @@
package org.apache.fineract.data.models.product
+import kotlinx.android.parcel.Parcelize
+
/**
* @author Rajan Maurya
* On 20/07/17.
diff --git a/app/src/main/java/org/apache/fineract/data/models/product/InterestRange.kt b/app/src/main/java/org/apache/fineract/data/models/product/InterestRange.kt
index f5882f18..7b5ad8e7 100644
--- a/app/src/main/java/org/apache/fineract/data/models/product/InterestRange.kt
+++ b/app/src/main/java/org/apache/fineract/data/models/product/InterestRange.kt
@@ -1,13 +1,16 @@
package org.apache.fineract.data.models.product
+import android.os.Parcelable
import com.google.gson.annotations.SerializedName
+import kotlinx.android.parcel.Parcelize
/**
* @author Rajan Maurya
* On 20/07/17.
*/
+@Parcelize
data class InterestRange (
@SerializedName("minimum") var minimum: Double? = null,
@SerializedName("maximum") var maximum: Double? = null
-)
+) : Parcelable
diff --git a/app/src/main/java/org/apache/fineract/data/models/product/Product.kt b/app/src/main/java/org/apache/fineract/data/models/product/Product.kt
index c1e6431c..2fe4b6b0 100644
--- a/app/src/main/java/org/apache/fineract/data/models/product/Product.kt
+++ b/app/src/main/java/org/apache/fineract/data/models/product/Product.kt
@@ -1,6 +1,10 @@
package org.apache.fineract.data.models.product
+import android.os.Parcelable
import com.google.gson.annotations.SerializedName
+import kotlinx.android.parcel.Parcelize
+import kotlinx.android.parcel.RawValue
+import org.apache.fineract.couchbase.DocumentType
import org.apache.fineract.data.models.loan.AccountAssignment
import org.apache.fineract.data.models.loan.TermRange
import java.util.ArrayList
@@ -10,22 +14,24 @@ import java.util.ArrayList
* On 20/07/17.
*/
+@Parcelize
data class Product (
- @SerializedName("identifier") val identifier: String? = null,
- @SerializedName("name") val name: String? = null,
- @SerializedName("termRange") val termRange: TermRange? = null,
- @SerializedName("balanceRange") val balanceRange: BalanceRange? = null,
- @SerializedName("interestRange") val interestRange: InterestRange? = null,
- @SerializedName("interestBasis") val interestBasis: InterestBasis? = null,
- @SerializedName("patternPackage") val patternPackage: String? = null,
- @SerializedName("description") val description: String? = null,
- @SerializedName("currencyCode") val currencyCode: String? = null,
- @SerializedName("minorCurrencyUnitDigits") val minorCurrencyUnitDigits: Int = 0,
- @SerializedName("accountAssignments") val accountAssignments: List =
+ @SerializedName("identifier") var identifier: String? = null,
+ @SerializedName("name") var name: String? = null,
+ @SerializedName("termRange") var termRange: TermRange? = null,
+ @SerializedName("balanceRange") var balanceRange: BalanceRange? = null,
+ @SerializedName("interestRange") var interestRange: InterestRange? = null,
+ @SerializedName("interestBasis") var interestBasis: InterestBasis? = null,
+ @SerializedName("patternPackage") var patternPackage: String? = null,
+ @SerializedName("description") var description: String? = null,
+ @SerializedName("currencyCode") var currencyCode: String? = null,
+ @SerializedName("minorCurrencyUnitDigits") var minorCurrencyUnitDigits: Int = 0,
+ @SerializedName("accountAssignments") var accountAssignments: List =
ArrayList(),
- @SerializedName("parameters") val parameters: String? = null,
- @SerializedName("createdOn") val createdOn: String? = null,
- @SerializedName("createdBy") val createdBy: String? = null,
- @SerializedName("lastModifiedOn") val lastModifiedOn: String? = null,
- @SerializedName("lastModifiedBy") val lastModifiedBy: String? = null
-)
+ @SerializedName("parameters") var parameters: String? = null,
+ @SerializedName("createdOn") var createdOn: String? = null,
+ @SerializedName("createdBy") var createdBy: String? = null,
+ @SerializedName("lastModifiedOn") var lastModifiedOn: String? = null,
+ @SerializedName("lastModifiedBy") var lastModifiedBy: String? = null,
+ var documentType: String = DocumentType.PRODUCT.value
+) : Parcelable
\ No newline at end of file
diff --git a/app/src/main/java/org/apache/fineract/injection/component/ActivityComponent.java b/app/src/main/java/org/apache/fineract/injection/component/ActivityComponent.java
index 7e8d2ec0..1dd37350 100644
--- a/app/src/main/java/org/apache/fineract/injection/component/ActivityComponent.java
+++ b/app/src/main/java/org/apache/fineract/injection/component/ActivityComponent.java
@@ -59,7 +59,13 @@
import org.apache.fineract.ui.online.review.AddLoanReviewFragment;
import org.apache.fineract.ui.online.roles.roleslist.RolesFragment;
import org.apache.fineract.ui.online.teller.TellerFragment;
-import org.apache.fineract.ui.product.ProductFragment;
+import org.apache.fineract.ui.product.createproduct.AddAccountAssignmentsFragment;
+import org.apache.fineract.ui.product.createproduct.CreateProductActivity;
+import org.apache.fineract.ui.product.createproduct.ProductDetailsStepFragment;
+import org.apache.fineract.ui.product.createproduct.ProductReviewStepFragment;
+import org.apache.fineract.ui.product.productlist.ProductFragment;
+import org.apache.fineract.ui.product.productdetails.ProductDetailsActivity;
+import org.apache.fineract.ui.product.productdetails.ProductDetailsFragment;
import dagger.Subcomponent;
@@ -159,5 +165,17 @@ public interface ActivityComponent {
void inject(GroupDetailsFragment groupDetailsFragment);
void inject(GroupTasksBottomSheetFragment groupTasksBottomSheetFragment);
+
+ void inject(ProductDetailsActivity productDetailsActivity);
+
+ void inject(ProductDetailsFragment productDetailsFragment);
+
+ void inject(CreateProductActivity createProductActivity);
+
+ void inject(ProductDetailsStepFragment productDetailsStepFragment);
+
+ void inject(AddAccountAssignmentsFragment addAccountAssignmentsFragment);
+
+ void inject(ProductReviewStepFragment productReviewStepFragment);
}
diff --git a/app/src/main/java/org/apache/fineract/ui/adapters/ProductAccountAssignmentsAdapter.kt b/app/src/main/java/org/apache/fineract/ui/adapters/ProductAccountAssignmentsAdapter.kt
new file mode 100644
index 00000000..8819a88c
--- /dev/null
+++ b/app/src/main/java/org/apache/fineract/ui/adapters/ProductAccountAssignmentsAdapter.kt
@@ -0,0 +1,77 @@
+package org.apache.fineract.ui.adapters
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.android.synthetic.main.account_assignments_item.view.*
+import org.apache.fineract.R
+import org.apache.fineract.data.models.loan.AccountAssignment
+import javax.inject.Inject
+
+class ProductAccountAssignmentsAdapter @Inject constructor() :
+ RecyclerView.Adapter() {
+
+ private var accountAssignmentsList = ArrayList()
+ private var onItemClickListener: OnItemClickListener? = null
+ private var isReview = false
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder.from(parent)
+ }
+
+ override fun getItemCount(): Int = accountAssignmentsList.size
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.bind(accountAssignmentsList[position], onItemClickListener, isReview)
+ }
+
+ fun setOnItemClickListener(onItemClickListener: OnItemClickListener?) {
+ this.onItemClickListener = onItemClickListener
+ }
+
+ fun submitList(list: ArrayList) {
+ accountAssignmentsList = list
+ notifyDataSetChanged()
+ }
+
+ fun setReview(isReview: Boolean) {
+ this.isReview = isReview
+ }
+
+ class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView) {
+
+ fun bind(accountAssignment: AccountAssignment, onItemClickListener: OnItemClickListener?, isReview: Boolean) {
+ if (isReview) {
+ itemView.ivEditProductAccountAss.visibility = View.GONE
+ itemView.ivDeleteProductAccountAss.visibility = View.GONE
+ } else {
+ itemView.ivEditProductAccountAss.visibility = View.VISIBLE
+ itemView.ivDeleteProductAccountAss.visibility = View.VISIBLE
+ }
+ itemView.tvDesignator.text = accountAssignment.designator
+ itemView.tvAccountIdentifier.text = accountAssignment.accountIdentifier
+ itemView.tvLedgerIdentifier.text = accountAssignment.ledgerIdentifier
+ itemView.ivDeleteProductAccountAss.setOnClickListener {
+ onItemClickListener?.onDeleteClicked(adapterPosition)
+ }
+ itemView.ivEditProductAccountAss.setOnClickListener {
+ onItemClickListener?.onEditClicked(adapterPosition)
+ }
+ }
+
+ companion object {
+ fun from(parent: ViewGroup): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(parent.context)
+ .inflate(R.layout.account_assignments_item, parent, false)
+ )
+ }
+ }
+ }
+
+ interface OnItemClickListener {
+ fun onEditClicked(position: Int)
+ fun onDeleteClicked(position: Int)
+ }
+}
diff --git a/app/src/main/java/org/apache/fineract/ui/adapters/ProductAdapter.kt b/app/src/main/java/org/apache/fineract/ui/adapters/ProductAdapter.kt
index 7f06886f..701c2072 100644
--- a/app/src/main/java/org/apache/fineract/ui/adapters/ProductAdapter.kt
+++ b/app/src/main/java/org/apache/fineract/ui/adapters/ProductAdapter.kt
@@ -5,11 +5,13 @@ import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.LinearLayout
import android.widget.TextView
import kotlinx.android.synthetic.main.item_product.view.*
import org.apache.fineract.R
import org.apache.fineract.data.models.product.Product
import org.apache.fineract.injection.ApplicationContext
+import org.apache.fineract.ui.base.OnItemClickListener
import org.apache.fineract.utils.DateUtils
import javax.inject.Inject
@@ -17,6 +19,7 @@ class ProductAdapter @Inject constructor(@ApplicationContext val context: Contex
: RecyclerView.Adapter() {
private var products: List = ArrayList()
+ lateinit var onItemClickListener: OnItemClickListener
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@@ -50,11 +53,26 @@ class ProductAdapter @Inject constructor(@ApplicationContext val context: Contex
notifyDataSetChanged()
}
- class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ fun setItemClickListener(onItemClickListener: OnItemClickListener) {
+ this.onItemClickListener = onItemClickListener
+ }
+
+ inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
val tvProductIdentifier: TextView = itemView.tv_product_identifier
val tvModifiedBy: TextView = itemView.tv_modified_by
val tvModifiedOn: TextView = itemView.tv_modified_on
val tvName: TextView = itemView.tv_name
+ val llProduct: LinearLayout = itemView.ll_product
+
+ init {
+ llProduct.setOnClickListener(this)
+ }
+
+ override fun onClick(p0: View?) {
+ if (onItemClickListener != null) {
+ onItemClickListener.onItemClick(p0, adapterPosition)
+ }
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/apache/fineract/ui/online/DashboardActivity.java b/app/src/main/java/org/apache/fineract/ui/online/DashboardActivity.java
index 7f0460d9..a4090646 100644
--- a/app/src/main/java/org/apache/fineract/ui/online/DashboardActivity.java
+++ b/app/src/main/java/org/apache/fineract/ui/online/DashboardActivity.java
@@ -32,7 +32,7 @@
import org.apache.fineract.ui.online.launcher.LauncherActivity;
import org.apache.fineract.ui.online.roles.roleslist.RolesFragment;
import org.apache.fineract.ui.online.teller.TellerFragment;
-import org.apache.fineract.ui.product.ProductFragment;
+import org.apache.fineract.ui.product.productlist.ProductFragment;
import org.apache.fineract.utils.MaterialDialog;
import javax.inject.Inject;
diff --git a/app/src/main/java/org/apache/fineract/ui/product/ProductAction.kt b/app/src/main/java/org/apache/fineract/ui/product/ProductAction.kt
new file mode 100644
index 00000000..bef5616b
--- /dev/null
+++ b/app/src/main/java/org/apache/fineract/ui/product/ProductAction.kt
@@ -0,0 +1,6 @@
+package org.apache.fineract.ui.product
+
+enum class ProductAction {
+ EDIT,
+ CREATE
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/apache/fineract/ui/product/ProductFragment.kt b/app/src/main/java/org/apache/fineract/ui/product/ProductFragment.kt
deleted file mode 100644
index aa46396c..00000000
--- a/app/src/main/java/org/apache/fineract/ui/product/ProductFragment.kt
+++ /dev/null
@@ -1,174 +0,0 @@
-package org.apache.fineract.ui.product
-
-import android.app.SearchManager
-import android.content.Context
-import android.os.Bundle
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.appcompat.widget.SearchView
-import android.text.TextUtils
-import android.view.*
-import androidx.recyclerview.widget.RecyclerView
-import kotlinx.android.synthetic.main.fragment_product.*
-import kotlinx.android.synthetic.main.layout_exception_handler.*
-import org.apache.fineract.R
-import org.apache.fineract.data.models.product.Product
-import org.apache.fineract.ui.adapters.ProductAdapter
-import org.apache.fineract.ui.base.FineractBaseActivity
-import org.apache.fineract.ui.base.FineractBaseFragment
-import java.util.*
-import javax.inject.Inject
-
-
-class ProductFragment : FineractBaseFragment(), ProductContract.View,
- SwipeRefreshLayout.OnRefreshListener {
-
- @Inject
- lateinit var productPresenter: ProductPresenter
-
- @Inject
- lateinit var productAdapter: ProductAdapter
-
- lateinit var productList: List
-
- companion object {
- fun newInstance() = ProductFragment().apply {
- val args = Bundle()
- arguments = args
- }
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setHasOptionsMenu(true)
- productList = ArrayList()
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View? {
- // Inflate the layout for this fragment
- val rootView = inflater.inflate(R.layout.fragment_product, container, false)
- (activity as FineractBaseActivity).activityComponent.inject(this)
- productPresenter.attachView(this)
- initializeFineractUIErrorHandler(activity, rootView)
- return rootView
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- showUserInterface()
-
- btn_try_again.setOnClickListener {
- layoutError.visibility = View.GONE
- productPresenter.getProductsPage()
- }
-
- productPresenter.getProductsPage()
- }
-
- override fun showUserInterface() {
-
- setToolbarTitle(getString(R.string.products))
- val layoutManager = LinearLayoutManager(activity)
- layoutManager.orientation = RecyclerView.VERTICAL
- rvProduct.layoutManager = layoutManager
- rvProduct.setHasFixedSize(true)
- rvProduct.adapter = productAdapter
-
- swipeContainer.setColorSchemeColors(*activity!!
- .resources.getIntArray(R.array.swipeRefreshColors))
- swipeContainer.setOnRefreshListener(this)
-
- }
-
- override fun onRefresh() {
- productPresenter.getProductsPage()
- }
-
- override fun showProduct(products: List) {
- showRecyclerView(true)
- this.productList = products
- productAdapter.setProductsList(products)
- }
-
-
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- super.onCreateOptionsMenu(menu, inflater)
- inflater.inflate(R.menu.menu_product_search, menu)
- setUpSearchInterface(menu)
- }
-
- private fun setUpSearchInterface(menu: Menu?) {
-
- val searchManager = activity?.getSystemService(Context.SEARCH_SERVICE) as? SearchManager
- val searchView = menu?.findItem(R.id.product_search)?.actionView as? SearchView
-
- searchView?.setSearchableInfo(searchManager?.getSearchableInfo(activity?.componentName))
-
- searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
- override fun onQueryTextSubmit(query: String): Boolean {
- productPresenter.searchProduct(productList, query)
- return false
- }
-
- override fun onQueryTextChange(newText: String): Boolean {
- if (TextUtils.isEmpty(newText)) {
- showRecyclerView(true)
- productAdapter.setProductsList(productList)
-
- } else {
- productPresenter.searchProduct(productList, newText)
- }
-
- return false
- }
- })
-
- }
-
- override fun showEmptyProduct() {
- showRecyclerView(false)
- showFineractEmptyUI(getString(R.string.products), getString(R.string.products),
- R.drawable.ic_person_outline_black_24dp)
- }
-
- override fun showRecyclerView(status: Boolean) {
- if (status) {
- rvProduct.visibility = View.VISIBLE
- layoutError.visibility = View.GONE
- } else {
- rvProduct.visibility = View.GONE
- layoutError.visibility = View.VISIBLE
- }
- }
-
- override fun showProgressbar() {
- swipeContainer.isRefreshing = true
- }
-
- override fun hideProgressbar() {
- swipeContainer.isRefreshing = false
- }
-
- override fun searchedProduct(products: List) {
- //showRecyclerView(true)
- productAdapter.setProductsList(products)
- }
-
- override fun showNoInternetConnection() {
- showRecyclerView(false)
- showNoInternetConnection()
- }
-
- override fun showError(message: String) {
- showRecyclerView(false)
- showFineractErrorUI(getString(R.string.products))
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- productPresenter.detachView()
- }
-
-}
diff --git a/app/src/main/java/org/apache/fineract/ui/product/createproduct/AddAccountAssignmentsFragment.kt b/app/src/main/java/org/apache/fineract/ui/product/createproduct/AddAccountAssignmentsFragment.kt
new file mode 100644
index 00000000..7b18f3e3
--- /dev/null
+++ b/app/src/main/java/org/apache/fineract/ui/product/createproduct/AddAccountAssignmentsFragment.kt
@@ -0,0 +1,230 @@
+package org.apache.fineract.ui.product.createproduct
+
+import android.content.DialogInterface
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import butterknife.ButterKnife
+import butterknife.OnClick
+import butterknife.Optional
+import com.stepstone.stepper.Step
+import com.stepstone.stepper.VerificationError
+import com.wajahatkarim3.easyvalidation.core.view_ktx.validator
+import kotlinx.android.synthetic.main.fragment_step_add_account_assignments.*
+import kotlinx.android.synthetic.main.fragment_step_add_account_assignments.view.*
+import org.apache.fineract.R
+import org.apache.fineract.data.models.loan.AccountAssignment
+import org.apache.fineract.ui.adapters.ProductAccountAssignmentsAdapter
+import org.apache.fineract.ui.base.FineractBaseFragment
+import org.apache.fineract.ui.online.groups.GroupAction
+import org.apache.fineract.ui.product.ProductAction
+import org.apache.fineract.utils.Constants
+import org.apache.fineract.utils.MaterialDialog
+import org.apache.fineract.utils.Utils
+import javax.inject.Inject
+
+/*
+ * Created by Varun Jain on 15th August 2021
+ */
+
+class AddAccountAssignmentsFragment : FineractBaseFragment(), Step,
+ ProductAccountAssignmentsAdapter.OnItemClickListener {
+
+ lateinit var rootView: View
+ var accountAssignments: ArrayList = ArrayList()
+ private var currentAction = ProductAction.CREATE
+ private var editItemPosition = 0
+ private lateinit var productAction: ProductAction
+
+ @Inject
+ lateinit var productAccountAssignmentsAdapter: ProductAccountAssignmentsAdapter
+
+ companion object {
+ fun newInstance(productAction: ProductAction) = AddAccountAssignmentsFragment().apply {
+ val bundle = Bundle().apply {
+ putSerializable(Constants.PRODUCT_ACTION, productAction)
+ }
+ arguments = bundle
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ arguments?.getSerializable(Constants.PRODUCT_ACTION)?.let {
+ productAction = it as ProductAction
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ rootView =
+ inflater.inflate(R.layout.fragment_step_add_account_assignments, container, false)
+ ButterKnife.bind(this, rootView)
+ (activity as CreateProductActivity).activityComponent.inject(this)
+ rootView.rvProductAccountAssignments.adapter = productAccountAssignmentsAdapter
+ productAccountAssignmentsAdapter.setOnItemClickListener(this)
+ if (productAction == ProductAction.EDIT) {
+ showDataOnViews()
+ }
+ return rootView
+ }
+
+ private fun showDataOnViews() {
+ val product = (activity as CreateProductActivity).getProduct()
+ accountAssignments = product.accountAssignments as ArrayList
+ if (accountAssignments.size == 0) {
+ showRecyclerView(false)
+ } else {
+ showRecyclerView(true)
+ }
+ productAccountAssignmentsAdapter.submitList(accountAssignments)
+ }
+
+ private fun showRecyclerView(showRecyclerView: Boolean) {
+ if (showRecyclerView) {
+ rootView.rvProductAccountAssignments.visibility = View.VISIBLE
+ rootView.tvAddProductAccountAssignments.visibility = View.GONE
+ } else {
+ rootView.rvProductAccountAssignments.visibility = View.GONE
+ rootView.tvAddProductAccountAssignments.visibility = View.VISIBLE
+ }
+ }
+
+
+ override fun verifyStep(): VerificationError? {
+ if (accountAssignments.size == 0) {
+ Toast.makeText(
+ context,
+ getString(R.string.error_product_atleast_1_account_assignment),
+ Toast.LENGTH_SHORT
+ ).show()
+ return VerificationError("")
+ }
+ (activity as CreateProductActivity).setAccountAssignments(accountAssignments)
+ return null
+ }
+
+ override fun onSelected() {
+ }
+
+ override fun onError(p0: VerificationError) {
+ }
+
+ @Optional
+ @OnClick(R.id.ibAddAccountAssignment)
+ fun showAddLeaderView() {
+ showAddAccountAssignmentsView(ProductAction.CREATE, null)
+ }
+
+ private fun showAddAccountAssignmentsView(
+ action: ProductAction,
+ accountAssignment: AccountAssignment?
+ ) {
+ currentAction = action
+ llAddProductAccountAssignments.visibility = View.VISIBLE
+ when (action) {
+ ProductAction.EDIT -> {
+ btnAddProductAccountAssignment.text = getString(R.string.update)
+ etDesignator.setText(accountAssignment?.designator)
+ etAccountIdentifier.setText(accountAssignment?.accountIdentifier)
+ etLedgerIdentifier.setText(accountAssignment?.ledgerIdentifier)
+ }
+ ProductAction.CREATE -> {
+ btnAddProductAccountAssignment.text = getString(R.string.add)
+ }
+ }
+ }
+
+ @Optional
+ @OnClick(R.id.btnAddProductAccountAssignment)
+ fun addAccountAssignment() {
+ if ( validateAccountIdentifier() || validateDesignator() || validateLedgerIdentifier()) {
+ if (currentAction == ProductAction.CREATE) {
+ accountAssignments.add(
+ AccountAssignment(
+ etDesignator.text.toString(),
+ etAccountIdentifier.text.toString(),
+ etLedgerIdentifier.text.toString()
+ )
+ )
+ } else {
+ accountAssignments[editItemPosition].designator = etDesignator.text.toString()
+ accountAssignments[editItemPosition].accountIdentifier =
+ etAccountIdentifier.text.toString()
+ accountAssignments[editItemPosition].ledgerIdentifier =
+ etLedgerIdentifier.text.toString()
+ }
+ etDesignator.text.clear()
+ etAccountIdentifier.text.clear()
+ etLedgerIdentifier.text.clear()
+ llAddProductAccountAssignments.visibility = View.GONE
+ Utils.hideKeyboard(context, etDesignator)
+ Utils.hideKeyboard(context, etAccountIdentifier)
+ Utils.hideKeyboard(context, etLedgerIdentifier)
+ showRecyclerView(true)
+ productAccountAssignmentsAdapter.submitList(accountAssignments)
+
+ }
+ }
+
+ private fun validateDesignator() : Boolean {
+ return etDesignator.validator()
+ .nonEmpty().addErrorCallback { etDesignator.error = it }.check()
+ }
+
+
+ private fun validateAccountIdentifier() : Boolean {
+ return etAccountIdentifier.validator()
+ .nonEmpty().addErrorCallback { etAccountIdentifier.error = it }.check()
+ }
+
+
+ private fun validateLedgerIdentifier() : Boolean {
+ return etLedgerIdentifier.validator()
+ .nonEmpty().addErrorCallback { etLedgerIdentifier.error = it }.check()
+ }
+
+ @Optional
+ @OnClick(R.id.btnCancelProductAccountAssignments)
+ fun cancelProductAccountAssignmentAddition() {
+ etLedgerIdentifier.text.clear()
+ etDesignator.text.clear()
+ etAccountIdentifier.text.clear()
+ llAddProductAccountAssignments.visibility = View.GONE
+ }
+
+ override fun onEditClicked(position: Int) {
+ editItemPosition = position
+ showAddAccountAssignmentsView(ProductAction.EDIT, accountAssignments[position])
+ }
+
+ override fun onDeleteClicked(position: Int) {
+ MaterialDialog.Builder().init(context).apply {
+ setTitle(getString(R.string.dialog_title_confirm_deletion))
+ setMessage(
+ getString(
+ R.string.dialog_message_confirm_name_deletion,
+ accountAssignments[position].designator
+ )
+ )
+ setPositiveButton(
+ getString(R.string.delete)
+ ) { dialog: DialogInterface?, _ ->
+ accountAssignments.removeAt(position)
+ productAccountAssignmentsAdapter.submitList(accountAssignments)
+ if (accountAssignments.size == 0) {
+ showRecyclerView(false)
+ }
+ dialog?.dismiss()
+ }
+ setNegativeButton(getString(R.string.dialog_action_cancel))
+ createMaterialDialog()
+ }.run { show() }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/apache/fineract/ui/product/createproduct/CreateProductActivity.kt b/app/src/main/java/org/apache/fineract/ui/product/createproduct/CreateProductActivity.kt
new file mode 100644
index 00000000..f76dd06e
--- /dev/null
+++ b/app/src/main/java/org/apache/fineract/ui/product/createproduct/CreateProductActivity.kt
@@ -0,0 +1,177 @@
+package org.apache.fineract.ui.product.createproduct
+
+import android.os.Bundle
+import android.view.View
+import android.widget.Toast
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProviders
+import com.stepstone.stepper.StepperLayout
+import com.stepstone.stepper.VerificationError
+import kotlinx.android.synthetic.main.activity_create_product.*
+import org.apache.fineract.R
+import org.apache.fineract.data.Status
+import org.apache.fineract.data.models.loan.AccountAssignment
+import org.apache.fineract.data.models.loan.TermRange
+import org.apache.fineract.data.models.product.BalanceRange
+import org.apache.fineract.data.models.product.InterestBasis
+import org.apache.fineract.data.models.product.InterestRange
+import org.apache.fineract.data.models.product.Product
+import org.apache.fineract.ui.base.FineractBaseActivity
+import org.apache.fineract.ui.base.Toaster
+import org.apache.fineract.ui.product.ProductAction
+import org.apache.fineract.ui.product.viewmodel.ProductViewModel
+import org.apache.fineract.ui.product.viewmodel.ProductViewModelFactory
+import org.apache.fineract.utils.Constants
+import javax.inject.Inject
+
+/*
+ * Created by Varun Jain on 14th August 2021
+ */
+
+class CreateProductActivity : FineractBaseActivity(), StepperLayout.StepperListener {
+
+ private var product = Product()
+ private var productAction = ProductAction.CREATE
+
+ @Inject
+ lateinit var productViewModelFactory: ProductViewModelFactory
+
+ lateinit var productViewModel: ProductViewModel
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_create_product)
+ activityComponent.inject(this)
+ productAction = intent.getSerializableExtra(Constants.PRODUCT_ACTION) as ProductAction
+ when (productAction) {
+ ProductAction.EDIT -> {
+ setToolbarTitle(getString(R.string.edit_product))
+ intent?.extras?.getParcelable(Constants.PRODUCT)?.let {
+ product = it
+ }
+ }
+ ProductAction.CREATE -> {
+ setToolbarTitle(getString(R.string.create_product))
+ }
+ }
+ productViewModel =
+ ViewModelProviders.of(this, productViewModelFactory).get(ProductViewModel::class.java)
+ subscribeUI()
+ showBackButton()
+ slCreateProduct.adapter = CreateProductAdapter(supportFragmentManager, this, productAction)
+ slCreateProduct.setOffscreenPageLimit(slCreateProduct.adapter.count)
+ slCreateProduct.setListener(this)
+ }
+
+ private fun subscribeUI() {
+ productViewModel.status.observe(this, Observer { status ->
+ when (status) {
+ Status.LOADING -> {
+ if (productAction == ProductAction.CREATE) {
+ showMifosProgressDialog(getString(R.string.create_product))
+ } else {
+ showMifosProgressDialog(getString(R.string.updating_product_please_wait))
+ }
+ }
+ Status.ERROR -> {
+ hideMifosProgressDialog()
+ if (productAction == ProductAction.CREATE) {
+ Toaster.show(
+ findViewById(android.R.id.content),
+ R.string.error_while_creating_product,
+ Toast.LENGTH_SHORT
+ )
+ } else {
+ Toaster.show(
+ findViewById(android.R.id.content),
+ R.string.error_while_updating_group,
+ Toast.LENGTH_SHORT
+ )
+ }
+ }
+ Status.DONE -> {
+ hideMifosProgressDialog()
+ if (productAction == ProductAction.CREATE) {
+ Toast.makeText(
+ this,
+ getString(
+ R.string.product_identifier_created_successfully,
+ product.identifier
+ ),
+ Toast.LENGTH_SHORT
+ ).show()
+ } else {
+ Toast.makeText(
+ this,
+ getString(
+ R.string.product_identifier_updated_successfully,
+ product.identifier
+ ),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ finish()
+ }
+ }
+ })
+ }
+
+ fun setProductDetails(
+ identifier: String,
+ name: String,
+ desc: String,
+ currentCode: String,
+ minorCurrencyUnitDigits: Int,
+ parameters: String,
+ patternPackage: String
+ ) {
+ product.identifier = identifier
+ product.name = name
+ product.description = desc
+ product.currencyCode = currentCode
+ product.minorCurrencyUnitDigits = minorCurrencyUnitDigits
+ product.parameters = parameters
+ product.patternPackage = patternPackage
+ }
+
+ fun setTermRange(temporalUnit: String, maxTermRange: Double) {
+ product.termRange = TermRange(temporalUnit, maxTermRange)
+ }
+
+ fun setBalanceRange(minBalanceRange: Double, maxBalanceRange: Double) {
+ product.balanceRange = BalanceRange(minBalanceRange, maxBalanceRange)
+ }
+
+ fun setInterestRange(minInterestRange: Double, maxInterestRange: Double) {
+ product.interestRange = InterestRange(minInterestRange, maxInterestRange)
+ }
+
+ fun setInterestBasis(interestBasis: InterestBasis) {
+ product.interestBasis = interestBasis
+ }
+
+ fun setAccountAssignments(accountAssignments: List) {
+ product.accountAssignments = accountAssignments
+ }
+
+ fun getProduct(): Product {
+ return product
+ }
+
+ override fun onCompleted(p0: View?) {
+ when (productAction) {
+ ProductAction.CREATE -> {
+ productViewModel.createProduct(product)
+ }
+ ProductAction.EDIT -> product.identifier?.let {
+ productViewModel.updateProduct(product)
+ }
+ }
+ }
+
+ override fun onError(p0: VerificationError?) {}
+
+ override fun onStepSelected(p0: Int) {}
+
+ override fun onReturn() {}
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/apache/fineract/ui/product/createproduct/CreateProductAdapter.kt b/app/src/main/java/org/apache/fineract/ui/product/createproduct/CreateProductAdapter.kt
new file mode 100644
index 00000000..c416c57a
--- /dev/null
+++ b/app/src/main/java/org/apache/fineract/ui/product/createproduct/CreateProductAdapter.kt
@@ -0,0 +1,40 @@
+package org.apache.fineract.ui.product.createproduct
+
+import android.content.Context
+import androidx.fragment.app.FragmentManager
+import com.stepstone.stepper.Step
+import com.stepstone.stepper.adapter.AbstractFragmentStepAdapter
+import com.stepstone.stepper.viewmodel.StepViewModel
+import org.apache.fineract.R
+import org.apache.fineract.ui.product.ProductAction
+
+
+/*
+ * Created by Varun Jain on 15th August 2021
+ */
+
+
+class CreateProductAdapter constructor(fm: FragmentManager,
+ context: Context,
+ val productAction: ProductAction)
+ : AbstractFragmentStepAdapter(fm, context) {
+
+ private var createProductSteps = context.resources.getStringArray(R.array.create_product_steps)
+
+ override fun getCount(): Int {
+ return 3
+ }
+
+ override fun createStep(p0: Int): Step? {
+ when (p0) {
+ 0 -> return ProductDetailsStepFragment.newInstance(productAction)
+ 1 -> return AddAccountAssignmentsFragment.newInstance(productAction)
+ 2 -> return ProductReviewStepFragment.newInstance()
+ }
+ return null
+ }
+
+ override fun getViewModel(position: Int): StepViewModel {
+ return StepViewModel.Builder(context).setTitle(createProductSteps[position]).create()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/apache/fineract/ui/product/createproduct/ProductDetailsStepFragment.kt b/app/src/main/java/org/apache/fineract/ui/product/createproduct/ProductDetailsStepFragment.kt
new file mode 100644
index 00000000..7113d1ba
--- /dev/null
+++ b/app/src/main/java/org/apache/fineract/ui/product/createproduct/ProductDetailsStepFragment.kt
@@ -0,0 +1,239 @@
+package org.apache.fineract.ui.product.createproduct
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.stepstone.stepper.Step
+import com.stepstone.stepper.VerificationError
+import com.wajahatkarim3.easyvalidation.core.view_ktx.validator
+import kotlinx.android.synthetic.main.fragment_step_product_details.*
+import kotlinx.android.synthetic.main.fragment_step_product_details.view.*
+import org.apache.fineract.R
+import org.apache.fineract.data.models.product.InterestBasis
+import org.apache.fineract.ui.base.FineractBaseFragment
+import org.apache.fineract.ui.product.ProductAction
+import org.apache.fineract.utils.Constants
+
+
+/*
+ * Created by Varun Jain on 14th August 2021
+ */
+
+class ProductDetailsStepFragment : FineractBaseFragment(), Step {
+
+ lateinit var rootView: View
+ private lateinit var productAction: ProductAction
+
+ companion object {
+ fun newInstance(productAction: ProductAction) = ProductDetailsStepFragment().apply {
+ val bundle = Bundle().apply {
+ putSerializable(Constants.PRODUCT_ACTION, productAction)
+ }
+ arguments = bundle
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ arguments?.getSerializable(Constants.PRODUCT_ACTION)?.let {
+ productAction = it as ProductAction
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ rootView = inflater.inflate(R.layout.fragment_step_product_details, container, false)
+ if (productAction == ProductAction.EDIT) {
+ populateViews()
+ }
+ return rootView
+ }
+
+ private fun populateViews() {
+ val product = (activity as CreateProductActivity).getProduct()
+ rootView.etIdentifier.setText(product.identifier)
+ rootView.etIdentifier.isEnabled = false
+ rootView.etName.setText(product.name)
+ rootView.etPatternPackage.setText(product.patternPackage)
+ rootView.etDescription.setText(product.description)
+ rootView.etCurrencyCode.setText(product.currencyCode)
+ rootView.etParameters.setText(product.parameters)
+ rootView.etMinorCurrencyUnitDigits.setText(product.minorCurrencyUnitDigits.toString())
+ rootView.spinnerInterestBasis.setSelection(getIndexFromInterestBasisType(product.interestBasis))
+ rootView.etTermRangeMax.setText(product.termRange?.maximum.toString())
+ rootView.etTemporalUnit.setText(product.termRange?.temporalUnit)
+ rootView.etBalanceRangeMinimum.setText(product.balanceRange?.minimum.toString())
+ rootView.etMaximumBalanceRange.setText(product.balanceRange?.maximum.toString())
+ rootView.etMinInterestRange.setText(product.interestRange?.minimum.toString())
+ rootView.etMaximumInterestRange.setText(product.interestRange?.maximum.toString())
+ }
+
+
+ private fun getIndexFromInterestBasisType(interestBasis: InterestBasis?): Int {
+ return when (interestBasis) {
+ InterestBasis.BEGINNING_BALANCE -> 0
+ InterestBasis.CURRENT_BALANCE -> 1
+ else -> return 0
+ }
+ }
+
+ private fun getInterestBasisTypeFromIndex(index: Int): InterestBasis {
+ return when (index) {
+ 0 -> InterestBasis.BEGINNING_BALANCE
+ 1 -> InterestBasis.CURRENT_BALANCE
+ else -> InterestBasis.BEGINNING_BALANCE
+ }
+ }
+
+ override fun verifyStep(): VerificationError? {
+ if (!validateBalanceRangeMax() || !validateName() || !validateIdentifier() || !validatePatternPackage() || !validateTermRangeMax() || !validateInterestRangeMax() || !validateInterestRangeMin() || !validateBalanceRangeMax() || !validateBalanceRangeMin() || !validateDescription() || !validateCurrencyCode() || !validateMinorCurrencyUnitDigits() || !validateParameters()) {
+ return VerificationError(null)
+ }
+ (activity as CreateProductActivity)
+ .setProductDetails(
+ etIdentifier.text.toString(),
+ etName.text.toString(),
+ etDescription.text.toString(),
+ etCurrencyCode.text.toString(),
+ etMinorCurrencyUnitDigits.text.toString().toInt(),
+ etParameters.text.toString(),
+ etPatternPackage.text.toString()
+ )
+ (activity as CreateProductActivity)
+ .setTermRange(etTemporalUnit.text.toString(), etTermRangeMax.text.toString().toDouble())
+ (activity as CreateProductActivity)
+ .setBalanceRange(
+ etBalanceRangeMinimum.text.toString().toDouble(),
+ etMaximumBalanceRange.text.toString().toDouble()
+ )
+ (activity as CreateProductActivity)
+ .setInterestRange(
+ etMinInterestRange.text.toString().toDouble(),
+ etMaximumInterestRange.text.toString().toDouble()
+ )
+ (activity as CreateProductActivity)
+ .setInterestBasis(getInterestBasisTypeFromIndex(spinnerInterestBasis.selectedItemPosition))
+ return null
+ }
+
+ private fun validateIdentifier(): Boolean {
+ return etIdentifier.validator()
+ .nonEmpty()
+ .minLength(5)
+ .addErrorCallback {
+ etIdentifier.error = it
+ }
+ .check()
+ }
+
+ private fun validateName(): Boolean {
+ return etName.validator()
+ .nonEmpty()
+ .minLength(5)
+ .addErrorCallback {
+ etName.error = it
+ }
+ .check()
+ }
+
+ private fun validatePatternPackage(): Boolean {
+ return etPatternPackage.validator()
+ .minLength(5)
+ .nonEmpty()
+ .addErrorCallback {
+ etPatternPackage.error = it
+ }
+ .check()
+ }
+
+ private fun validateDescription(): Boolean {
+ return etDescription.validator()
+ .minLength(5)
+ .nonEmpty()
+ .addErrorCallback {
+ etDescription.error = it
+ }
+ .check()
+ }
+
+ private fun validateCurrencyCode(): Boolean {
+ return etCurrencyCode.validator()
+ .minLength(5)
+ .nonEmpty()
+ .addErrorCallback {
+ etCurrencyCode.error = it
+ }
+ .check()
+ }
+
+ private fun validateMinorCurrencyUnitDigits(): Boolean {
+ return etMinorCurrencyUnitDigits.validator()
+ .nonEmpty()
+ .addErrorCallback {
+ etMinorCurrencyUnitDigits.error = it
+ }
+ .check()
+ }
+
+ private fun validateParameters(): Boolean {
+ return etParameters.validator()
+ .minLength(5)
+ .addErrorCallback {
+ etParameters.error = it
+ }
+ .check()
+ }
+
+ private fun validateInterestRangeMax(): Boolean {
+ return etMaximumInterestRange.validator()
+ .nonEmpty()
+ .addErrorCallback {
+ etMaximumInterestRange.error = it
+ }
+ .check()
+ }
+
+ private fun validateInterestRangeMin(): Boolean {
+ return etMinInterestRange.validator()
+ .nonEmpty()
+ .addErrorCallback {
+ etMinInterestRange.error = it
+ }
+ .check()
+ }
+
+ private fun validateBalanceRangeMin(): Boolean {
+ return etBalanceRangeMinimum.validator()
+ .nonEmpty()
+ .addErrorCallback {
+ etBalanceRangeMinimum.error = it
+ }
+ .check()
+ }
+
+ private fun validateBalanceRangeMax(): Boolean {
+ return etMaximumBalanceRange.validator()
+ .nonEmpty()
+ .addErrorCallback {
+ etMaximumBalanceRange.error = it
+ }
+ .check()
+ }
+
+ private fun validateTermRangeMax(): Boolean {
+ return etTermRangeMax.validator()
+ .nonEmpty()
+ .addErrorCallback {
+ etTermRangeMax.error = it
+ }
+ .check()
+ }
+
+ override fun onSelected() {}
+
+ override fun onError(p0: VerificationError) {}
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/apache/fineract/ui/product/createproduct/ProductReviewStepFragment.kt b/app/src/main/java/org/apache/fineract/ui/product/createproduct/ProductReviewStepFragment.kt
new file mode 100644
index 00000000..a9462685
--- /dev/null
+++ b/app/src/main/java/org/apache/fineract/ui/product/createproduct/ProductReviewStepFragment.kt
@@ -0,0 +1,80 @@
+package org.apache.fineract.ui.product.createproduct
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.stepstone.stepper.Step
+import com.stepstone.stepper.VerificationError
+import kotlinx.android.synthetic.main.fragment_step_group_review.*
+import kotlinx.android.synthetic.main.fragment_step_product_review.*
+import kotlinx.android.synthetic.main.fragment_step_product_review.view.*
+import org.apache.fineract.R
+import org.apache.fineract.data.models.loan.AccountAssignment
+import org.apache.fineract.data.models.product.Product
+import org.apache.fineract.ui.adapters.ProductAccountAssignmentsAdapter
+import org.apache.fineract.ui.base.FineractBaseFragment
+import javax.inject.Inject
+
+
+/*
+ * Created by Varun Jain on 15th August 2021
+ */
+
+class ProductReviewStepFragment : FineractBaseFragment(), Step{
+
+ lateinit var rootView: View
+
+ @Inject
+ lateinit var productAccountAssignmentsAdapter: ProductAccountAssignmentsAdapter
+
+ companion object {
+ fun newInstance(): ProductReviewStepFragment {
+ return ProductReviewStepFragment()
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ rootView = inflater.inflate(R.layout.fragment_step_product_review, container, false)
+ (activity as CreateProductActivity).activityComponent.inject(this)
+ rootView.rvProductAccountAssignmentsStepReview.adapter = productAccountAssignmentsAdapter
+ productAccountAssignmentsAdapter.setReview(true)
+
+ return rootView
+ }
+
+ override fun verifyStep(): VerificationError? {
+ return null
+ }
+
+ override fun onSelected() {
+ populateView((activity as CreateProductActivity).getProduct())
+ }
+
+ private fun populateView(product: Product) {
+ tvProductIdentifier.text = product.identifier
+ tvProductName.text = product.name
+ tvPatternPackageProduct.text = product.patternPackage
+ tvProductDescription.text = product.description
+ tvProductCurrencyCode.text = product.currencyCode
+ tvProductMinorCurrencyUnit.text = product.minorCurrencyUnitDigits.toString()
+ tvParameters.text = product.parameters
+ tvInterestBasis.text = product.interestBasis.toString()
+ product.accountAssignments?.let {
+ productAccountAssignmentsAdapter.submitList(it as ArrayList)
+ }
+ tvMinProductBalanceRange.text = product.balanceRange?.minimum.toString()
+ tvMaxProductBalanceRange.text = product.balanceRange?.maximum.toString()
+ tvMinInterestRange.text = product.interestRange?.minimum.toString()
+ tvMaxInterestRange.text = product.interestRange?.maximum.toString()
+ tvProductTemporalUnit.text = product.termRange?.temporalUnit.toString()
+ tvMaximumTermRange.text = product.termRange?.maximum.toString()
+ }
+
+ override fun onError(p0: VerificationError) {
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/apache/fineract/ui/product/productdetails/ProductDetailsActivity.kt b/app/src/main/java/org/apache/fineract/ui/product/productdetails/ProductDetailsActivity.kt
new file mode 100644
index 00000000..c52988b8
--- /dev/null
+++ b/app/src/main/java/org/apache/fineract/ui/product/productdetails/ProductDetailsActivity.kt
@@ -0,0 +1,21 @@
+package org.apache.fineract.ui.product.productdetails
+
+import android.os.Bundle
+import org.apache.fineract.R
+import org.apache.fineract.ui.base.FineractBaseActivity
+import org.apache.fineract.utils.Constants
+
+/*
+ * Created by Varun Jain on 07th August 2021
+ */
+
+class ProductDetailsActivity : FineractBaseActivity(){
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_toolbar_container)
+
+ replaceFragment(ProductDetailsFragment.newInstance(intent.getParcelableExtra(Constants.PRODUCT)), false, R.id.container)
+ showBackButton()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/apache/fineract/ui/product/productdetails/productdetailsfragment.kt b/app/src/main/java/org/apache/fineract/ui/product/productdetails/productdetailsfragment.kt
new file mode 100644
index 00000000..1e944b90
--- /dev/null
+++ b/app/src/main/java/org/apache/fineract/ui/product/productdetails/productdetailsfragment.kt
@@ -0,0 +1,110 @@
+package org.apache.fineract.ui.product.productdetails
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.*
+import android.widget.Toast
+import butterknife.ButterKnife
+import kotlinx.android.synthetic.main.fragment_product_details.*
+import kotlinx.android.synthetic.main.fragment_product_details.view.*
+import org.apache.fineract.R
+import org.apache.fineract.data.models.loan.AccountAssignment
+import org.apache.fineract.data.models.product.Product
+import org.apache.fineract.ui.adapters.ProductAccountAssignmentsAdapter
+import org.apache.fineract.ui.base.FineractBaseActivity
+import org.apache.fineract.ui.base.FineractBaseFragment
+import org.apache.fineract.ui.base.Toaster
+import org.apache.fineract.ui.product.ProductAction
+import org.apache.fineract.ui.product.createproduct.CreateProductActivity
+import org.apache.fineract.utils.Constants
+import org.apache.fineract.utils.Utils
+import javax.inject.Inject
+
+/*
+ * Created by Varun Jain on 07th August 2021
+ */
+
+class ProductDetailsFragment : FineractBaseFragment() {
+
+ lateinit var rootView: View
+ lateinit var product: Product
+
+ @Inject
+ lateinit var productAccountAssignmentsAdapter: ProductAccountAssignmentsAdapter
+
+ companion object {
+ fun newInstance(product: Product): ProductDetailsFragment {
+ val fragment = ProductDetailsFragment()
+ val args = Bundle()
+ args.putParcelable(Constants.PRODUCT, product)
+ fragment.arguments = args
+ return fragment
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ product = arguments?.get(Constants.PRODUCT) as Product
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ rootView = inflater.inflate(R.layout.fragment_product_details, container, false)
+ (activity as FineractBaseActivity).activityComponent.inject(this)
+ ButterKnife.bind(this, rootView)
+ rootView.rvProductAccountAssignments.adapter = productAccountAssignmentsAdapter
+ productAccountAssignmentsAdapter.setReview(true)
+ setToolbarTitle(product.identifier)
+ setHasOptionsMenu(true)
+ return rootView
+ }
+
+ override fun onPrepareOptionsMenu(menu: Menu) {
+ super.onPrepareOptionsMenu(menu)
+ Utils.setToolbarIconColor(context, menu, R.color.white)
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ super.onCreateOptionsMenu(menu, inflater)
+ inflater.inflate(R.menu.menu_product_details, menu)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.menu_product_details -> {
+ val intent = Intent(activity, CreateProductActivity::class.java).apply {
+ putExtra(Constants.PRODUCT, product)
+ putExtra(Constants.PRODUCT_ACTION, ProductAction.EDIT)
+ }
+ startActivity(intent)
+ activity!!.finish()
+ }
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+ tvIdentifierProduct.text = product.identifier
+ tvNameProduct.text = product.name
+ tvPatternPackageProduct.text = product.patternPackage
+ tvProductTemporalUnit.text = product.termRange?.temporalUnit
+ tvMaximumTermRange.text = product.termRange?.maximum.toString()
+ tvMinProductBalanceRange.text = product.balanceRange?.minimum.toString()
+ tvMaxProductBalanceRange.text = product.balanceRange?.maximum.toString()
+ tvMaximumTermRange.text = product.balanceRange?.maximum.toString()
+ tvMaxInterestRange.text = product.interestRange?.maximum.toString()
+ tvMinInterestRange.text = product.interestRange?.minimum.toString()
+ tvProductDescription.text = product.description
+ tvInterestBasis.text = product.interestBasis.toString()
+ tvProductCurrencyCode.text = product.currencyCode.toString()
+ tvProductMinorCurrencyUnit.text = product.minorCurrencyUnitDigits.toString()
+ tvProductParams.text = product.parameters.toString()
+ product.accountAssignments?.let {
+ productAccountAssignmentsAdapter.submitList(it as ArrayList)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/apache/fineract/ui/product/productlist/ProductFragment.kt b/app/src/main/java/org/apache/fineract/ui/product/productlist/ProductFragment.kt
new file mode 100644
index 00000000..5d32ec3d
--- /dev/null
+++ b/app/src/main/java/org/apache/fineract/ui/product/productlist/ProductFragment.kt
@@ -0,0 +1,171 @@
+package org.apache.fineract.ui.product.productlist
+
+import android.app.SearchManager
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.appcompat.widget.SearchView
+import android.text.TextUtils
+import android.view.*
+import androidx.lifecycle.ViewModelProviders
+import butterknife.ButterKnife
+import kotlinx.android.synthetic.main.fragment_product.*
+import kotlinx.android.synthetic.main.layout_exception_handler.*
+import org.apache.fineract.R
+import org.apache.fineract.data.models.product.Product
+import org.apache.fineract.ui.adapters.ProductAdapter
+import org.apache.fineract.ui.base.FineractBaseActivity
+import org.apache.fineract.ui.base.FineractBaseFragment
+import org.apache.fineract.ui.base.OnItemClickListener
+import org.apache.fineract.ui.product.productdetails.ProductDetailsActivity
+import org.apache.fineract.ui.product.viewmodel.ProductViewModel
+import org.apache.fineract.ui.product.viewmodel.ProductViewModelFactory
+import org.apache.fineract.utils.Constants
+import javax.inject.Inject
+import kotlin.collections.ArrayList
+import androidx.lifecycle.Observer
+import butterknife.OnClick
+import org.apache.fineract.ui.online.groups.GroupAction
+import org.apache.fineract.ui.online.groups.creategroup.CreateGroupActivity
+import org.apache.fineract.ui.product.ProductAction
+import org.apache.fineract.ui.product.createproduct.CreateProductActivity
+
+class ProductFragment : FineractBaseFragment(), OnItemClickListener, SwipeRefreshLayout.OnRefreshListener {
+
+ lateinit var rootView: View
+
+ lateinit var viewModel: ProductViewModel
+
+ @Inject
+ lateinit var productAdapter: ProductAdapter
+
+ @Inject
+ lateinit var productViewModelFactory: ProductViewModelFactory
+
+ lateinit var productList: ArrayList
+
+ companion object {
+ fun newInstance() = ProductFragment().apply {
+ val args = Bundle()
+ arguments = args
+ }
+ }
+
+ val searchedProduct: (ArrayList) -> Unit = { products ->
+ productAdapter.setProductsList(products)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ rootView = inflater.inflate(R.layout.fragment_product, container, false)
+ (activity as FineractBaseActivity).activityComponent.inject(this)
+ initializeFineractUIErrorHandler(activity, rootView)
+ viewModel =
+ ViewModelProviders.of(this, productViewModelFactory).get(ProductViewModel::class.java)
+ return rootView
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ ButterKnife.bind(this, rootView)
+ productAdapter.setItemClickListener(this)
+
+ btn_try_again.setOnClickListener {
+ layoutError.visibility = View.GONE
+ viewModel.getProducts()
+ }
+
+ swipeContainer.setColorSchemeColors(*activity!!
+ .resources.getIntArray(R.array.swipeRefreshColors))
+ swipeContainer.setOnRefreshListener(this)
+
+ rvProduct.adapter = productAdapter
+ rvProduct.layoutManager = LinearLayoutManager(context)
+ }
+
+ override fun onStart() {
+ super.onStart()
+ viewModel.getProducts()?.observe(this, Observer {
+ it?.let {
+ productList = it
+ productAdapter.setProductsList(it)
+ }
+ })
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ super.onCreateOptionsMenu(menu, inflater)
+ inflater.inflate(R.menu.menu_product_search, menu)
+ setUpSearchInterface(menu)
+ }
+
+ private fun setUpSearchInterface(menu: Menu?) {
+
+ val searchManager = activity?.getSystemService(Context.SEARCH_SERVICE) as? SearchManager
+ val searchView = menu?.findItem(R.id.product_search)?.actionView as? SearchView
+
+ searchView?.setSearchableInfo(searchManager?.getSearchableInfo(activity?.componentName))
+
+ searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String): Boolean {
+ viewModel.searchProducts(productList, query, searchedProduct)
+ return false
+ }
+
+ override fun onQueryTextChange(newText: String): Boolean {
+ if (TextUtils.isEmpty(newText)) {
+ productAdapter.setProductsList(productList)
+ }
+ viewModel.searchProducts(productList, newText, searchedProduct)
+ return true
+ }
+ })
+ }
+
+ override fun onItemClick(childView: View?, position: Int) {
+ val intent = Intent(context, ProductDetailsActivity::class.java).apply {
+ putExtra(Constants.PRODUCT, productList[position])
+ }
+ startActivity(intent)
+
+ }
+
+ override fun onItemLongPress(childView: View?, position: Int) {}
+
+ @OnClick(R.id.fabAddProduct)
+ fun addGroup() {
+ val intent = Intent(activity, CreateProductActivity::class.java).apply {
+ putExtra(Constants.PRODUCT_ACTION, ProductAction.CREATE)
+ }
+ startActivity(intent)
+ }
+
+ override fun onRefresh() {
+ hideProgressbar()
+ viewModel.getProducts()?.observe(this, Observer {
+ it?.let {
+ productList = it
+ productAdapter.setProductsList(productList)
+ }
+ })
+// hideProgressbar()
+ }
+
+ fun showProgressbar() {
+ swipeContainer.isRefreshing = true
+ }
+
+ fun hideProgressbar() {
+ swipeContainer.isRefreshing = false
+ }
+}
diff --git a/app/src/main/java/org/apache/fineract/ui/product/viewmodel/ProductViewModel.kt b/app/src/main/java/org/apache/fineract/ui/product/viewmodel/ProductViewModel.kt
new file mode 100644
index 00000000..622194ee
--- /dev/null
+++ b/app/src/main/java/org/apache/fineract/ui/product/viewmodel/ProductViewModel.kt
@@ -0,0 +1,146 @@
+package org.apache.fineract.ui.product.viewmodel
+
+import android.annotation.SuppressLint
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.couchbase.lite.Expression
+import io.reactivex.Observable
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+import kotlinx.coroutines.*
+import org.apache.fineract.couchbase.DocumentType
+import org.apache.fineract.couchbase.SynchronizationManager
+import org.apache.fineract.data.Status
+import org.apache.fineract.data.datamanager.api.DataManagerAnonymous
+import org.apache.fineract.data.datamanager.api.DataManagerProduct
+import org.apache.fineract.data.local.PreferencesHelper
+import org.apache.fineract.data.models.customer.Country
+import org.apache.fineract.data.models.product.Product
+import org.apache.fineract.data.models.product.ProductPage
+import org.apache.fineract.utils.DateUtils
+import org.apache.fineract.utils.serializeToMap
+import org.apache.fineract.utils.toDataClass
+
+/*
+ * Created by Varun Jain on 11/August/2021
+*/
+
+
+class ProductViewModel constructor(private val synchronizationManager: SynchronizationManager,
+ private val dataManagerAnonymous: DataManagerAnonymous,
+ private val preferencesHelper: PreferencesHelper,
+ private val dataManagerProduct: DataManagerProduct
+) : ViewModel() {
+
+ var productList = MutableLiveData>()
+ private var viewModelJob = Job()
+
+ // Create a Coroutine scope using a job to be able to cancel when needed
+ private val uiScope = CoroutineScope(viewModelJob + Dispatchers.IO)
+ private var _status = MutableLiveData()
+ val status: LiveData
+ get() = _status
+
+ @SuppressLint("CheckResult")
+ fun getProducts(): MutableLiveData>? {
+ val expression = Expression.property("documentType")
+ .equalTo(Expression.string(DocumentType.PRODUCT.value))
+ .and(Expression.property("identifier").notEqualTo(Expression.string("null")))
+ val hashMapList = synchronizationManager.getDocuments(expression)
+ if (hashMapList?.isEmpty() == null) {
+ return null
+ }
+ val list = arrayListOf()
+ for (map in hashMapList){
+ list.add(map.toDataClass())
+ }
+ productList.value = list
+ return productList
+ }
+
+ fun searchProducts(products: ArrayList, query: String, searchedProduct: (ArrayList) -> Unit) {
+ searchedProduct(ArrayList(Observable.fromIterable(products).filter { product -> product.identifier?.toLowerCase()?.contains(query.toLowerCase()).toString().toBoolean() }.toList().blockingGet()))
+ }
+
+ @SuppressLint("CheckResult")
+ fun createProduct(product: Product) {
+ uiScope.launch {
+ withContext(Dispatchers.Main) {
+ try {
+ _status.value = Status.LOADING
+ product.createdBy = preferencesHelper.userName
+ product.createdOn = DateUtils.getCurrentDate()
+ product.lastModifiedBy = preferencesHelper.userName
+ product.lastModifiedOn = DateUtils.getCurrentDate()
+ synchronizationManager.saveDocument(product.identifier!!, product.serializeToMap())
+ _status.value = Status.DONE
+ } catch (exception: Exception) {
+ _status.value = Status.ERROR
+ }
+ }
+ }
+ }
+
+ fun updateProduct(product: Product) {
+ uiScope.launch {
+ withContext(Dispatchers.Main) {
+ try {
+ _status.value = Status.LOADING
+ product.createdBy = preferencesHelper.userName
+ product.createdOn = DateUtils.getCurrentDate()
+ product.lastModifiedBy = preferencesHelper.userName
+ product.lastModifiedOn = DateUtils.getCurrentDate()
+ synchronizationManager.updateDocument(product.identifier!!, product.serializeToMap())
+ _status.value = Status.DONE
+ } catch (exception: Exception) {
+ _status.value = Status.ERROR
+ }
+ }
+ }
+ }
+
+ @SuppressLint("CheckResult")
+ fun getCountries(): MutableLiveData> {
+ val countries = MutableLiveData>()
+ dataManagerAnonymous.countries.subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe {
+ countries.value = it
+ }
+ return countries
+ }
+
+ fun getCountryNames(countries: List): List {
+ return Observable.fromIterable(countries).map { country: Country -> country.name }.toList().blockingGet()
+ }
+
+ fun getCountryCode(countries: List, countryName: String): String? {
+ for (country in countries) {
+ if (country.name == countryName) {
+ return country.alphaCode
+ }
+ }
+ return null
+ }
+
+ fun isCountryValid(countries: List, countryName: String): Boolean {
+ for (country in countries) {
+ if (country.name == countryName) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * When the [ViewModel] is finished, we cancel our coroutine [viewModelJob], which tells the
+ * Retrofit service to stop.
+ */
+ override fun onCleared() {
+ super.onCleared()
+ viewModelJob.cancel()
+ synchronizationManager.closeDatabase()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/apache/fineract/ui/product/viewmodel/ProductViewModelFactory.kt b/app/src/main/java/org/apache/fineract/ui/product/viewmodel/ProductViewModelFactory.kt
new file mode 100644
index 00000000..dbbe15c7
--- /dev/null
+++ b/app/src/main/java/org/apache/fineract/ui/product/viewmodel/ProductViewModelFactory.kt
@@ -0,0 +1,29 @@
+package org.apache.fineract.ui.product.viewmodel
+
+import android.content.Context
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import org.apache.fineract.couchbase.SynchronizationManager
+import org.apache.fineract.data.datamanager.api.DataManagerAnonymous
+import org.apache.fineract.data.datamanager.api.DataManagerProduct
+import org.apache.fineract.data.local.PreferencesHelper
+import org.apache.fineract.injection.ApplicationContext
+import javax.inject.Inject
+
+/*
+ * Created by Varun Jain on 11/August/2021
+*/
+
+class ProductViewModelFactory @Inject constructor(@ApplicationContext var context: Context,
+ private val synchronizationManager: SynchronizationManager,
+ private val dataManagerAnonymous: DataManagerAnonymous,
+ private val preferencesHelper: PreferencesHelper,
+ private val dataManagerProduct: DataManagerProduct
+)
+ : ViewModelProvider.NewInstanceFactory() {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+ return ProductViewModel(synchronizationManager, dataManagerAnonymous, preferencesHelper, dataManagerProduct) as T
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/apache/fineract/utils/Constants.kt b/app/src/main/java/org/apache/fineract/utils/Constants.kt
index 072c90f3..f141ef71 100644
--- a/app/src/main/java/org/apache/fineract/utils/Constants.kt
+++ b/app/src/main/java/org/apache/fineract/utils/Constants.kt
@@ -5,11 +5,13 @@ package org.apache.fineract.utils
* Created by saksham on 23/June/2019
*/
-object Constants {
+object Constants {
const val GROUP = "group"
const val GROUP_ACTION = "group_action"
const val DATABASE_NAME = "fineract-cn-mobile"
const val BASIC_AUTH_KEY = "basic_auth_key"
const val GATEWAY_USER_NAME = "fineract-cn"
const val GATEWAY_PASSWORD = "password"
+ const val PRODUCT = "product"
+ const val PRODUCT_ACTION = "product_action"
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/account_assignments_item.xml b/app/src/main/res/layout/account_assignments_item.xml
new file mode 100644
index 00000000..3ce03d07
--- /dev/null
+++ b/app/src/main/res/layout/account_assignments_item.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_create_product.xml b/app/src/main/res/layout/activity_create_product.xml
new file mode 100644
index 00000000..d4469fa4
--- /dev/null
+++ b/app/src/main/res/layout/activity_create_product.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_product.xml b/app/src/main/res/layout/fragment_product.xml
index 2b279d34..66d5d12b 100644
--- a/app/src/main/res/layout/fragment_product.xml
+++ b/app/src/main/res/layout/fragment_product.xml
@@ -1,6 +1,7 @@
@@ -17,8 +18,19 @@
android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/layout_padding_30dp"
android:layout_weight="1" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_step_add_account_assignments.xml b/app/src/main/res/layout/fragment_step_add_account_assignments.xml
new file mode 100644
index 00000000..74514f8b
--- /dev/null
+++ b/app/src/main/res/layout/fragment_step_add_account_assignments.xml
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_step_product_details.xml b/app/src/main/res/layout/fragment_step_product_details.xml
new file mode 100644
index 00000000..0d28c8d3
--- /dev/null
+++ b/app/src/main/res/layout/fragment_step_product_details.xml
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_step_product_review.xml b/app/src/main/res/layout/fragment_step_product_review.xml
new file mode 100644
index 00000000..a7a9cd43
--- /dev/null
+++ b/app/src/main/res/layout/fragment_step_product_review.xml
@@ -0,0 +1,328 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_product_details.xml b/app/src/main/res/menu/menu_product_details.xml
new file mode 100644
index 00000000..79d33109
--- /dev/null
+++ b/app/src/main/res/menu/menu_product_details.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b25c3ac7..dcb368a2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -230,6 +230,7 @@
Create Group
No Group Member Added
No Group Leader Added
+ No Account Assignments
Small Text
@@ -325,6 +326,7 @@
Failed to refresh customers
Group should have atleast 1 Member
Group should have atleast 1 Leader
+ Product should have atleast 1 Account assignment
OK
@@ -449,11 +451,22 @@
- Review
+
+ - Product Details
+ - Add Account assignments
+ - Review
+
+
- Customer payroll
- Payroll allocations
+
+ - BEGINNING_BALANCE
+ - CURRENT_BALANCE
+
+
- weeks
- months
@@ -544,4 +557,25 @@
"Updating group status, please wait... "
Error while updating group status
Error while creating group
+ Designator
+ Ledger Identifier
+ Pattern package
+ Term Range
+ Temporal Unit
+ Maximum
+ Balance Range
+ Minimum
+ Interest Range
+ Account Assignments
+ Currency Code
+ Minor Currency Unit Digits
+ Parameters
+ Interest Basis
+ Create Product
+ Edit Product
+ Updating Product, please wait...
+ Error while creating product
+ Error while updating product
+ Product %1$s created successfully
+ Product %1$s updated successfully