1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
package net.taler.merchantpos.order
import android.util.Log
import androidx.annotation.UiThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations.map
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import net.taler.merchantpos.Amount.Companion.fromString
import net.taler.merchantpos.CombinedLiveData
import net.taler.merchantpos.config.ConfigurationReceiver
import net.taler.merchantpos.order.RestartState.DISABLED
import net.taler.merchantpos.order.RestartState.ENABLED
import net.taler.merchantpos.order.RestartState.UNDO
import org.json.JSONObject
enum class RestartState { ENABLED, DISABLED, UNDO }
class OrderManager(private val mapper: ObjectMapper) : ConfigurationReceiver {
companion object {
val TAG = OrderManager::class.java.simpleName
}
private val productsByCategory = HashMap<Category, ArrayList<ConfigProduct>>()
private val mOrder = MutableLiveData<Order>()
private val newOrder // an empty order containing only available categories
get() = Order(productsByCategory.keys.map { it.id to it }.toMap())
internal val order: LiveData<Order> = mOrder
internal val orderTotal: LiveData<Double> = map(mOrder) { it.total }
private val mProducts = MutableLiveData<List<ConfigProduct>>()
internal val products: LiveData<List<ConfigProduct>> = mProducts
private val mCategories = MutableLiveData<List<Category>>()
internal val categories: LiveData<List<Category>> = mCategories
private var undoOrder: Order? = null
private val mRestartState = MutableLiveData<RestartState>().apply { value = DISABLED }
internal val restartState: LiveData<RestartState> = mRestartState
private val mSelectedOrderLine = MutableLiveData<OrderLine>()
internal val modifyOrderAllowed =
CombinedLiveData(restartState, mSelectedOrderLine) { restartState, selectedOrderLine ->
restartState != DISABLED && selectedOrderLine != null
}
@Suppress("BlockingMethodInNonBlockingContext") // run on Dispatchers.Main
override suspend fun onConfigurationReceived(json: JSONObject, currency: String): Boolean {
// parse categories
val categoriesStr = json.getJSONArray("categories").toString()
val categoriesType = object : TypeReference<List<Category>>() {}
val categories: List<Category> = mapper.readValue(categoriesStr, categoriesType)
if (categories.isEmpty()) {
Log.e(TAG, "No valid category found.")
return false
}
// pre-select the first category
categories[0].selected = true
// parse products (live data gets updated in setCurrentCategory())
val productsStr = json.getJSONArray("products").toString()
val productsType = object : TypeReference<List<ConfigProduct>>() {}
val products: List<ConfigProduct> = mapper.readValue(productsStr, productsType)
// group products by categories
productsByCategory.clear()
products.forEach { product ->
val productCurrency = fromString(product.price).currency
if (productCurrency != currency) {
Log.e(TAG, "Product $product has currency $productCurrency, $currency expected")
return false
}
product.categories.forEach { categoryId ->
val category = categories.find { it.id == categoryId }
if (category == null) {
Log.e(TAG, "Product $product has unknown category $categoryId")
return false
}
if (productsByCategory.containsKey(category)) {
productsByCategory[category]?.add(product)
} else {
productsByCategory[category] = ArrayList<ConfigProduct>().apply { add(product) }
}
}
}
return if (productsByCategory.size > 0) {
mCategories.postValue(categories)
mProducts.postValue(productsByCategory[categories[0]])
true
} else {
false
}
}
internal fun setCurrentCategory(category: Category) {
val newCategories = categories.value?.apply {
forEach { if (it.selected) it.selected = false }
category.selected = true
}
mCategories.postValue(newCategories)
mProducts.postValue(productsByCategory[category])
}
@UiThread
internal fun addProduct(product: ConfigProduct) {
val order = mOrder.value ?: newOrder
mOrder.value = order + product
mRestartState.value = ENABLED
}
@UiThread
internal fun removeProduct(product: ConfigProduct) {
val order = mOrder.value ?: throw IllegalStateException()
val modifiedOrder = order - product
mOrder.value = modifiedOrder
mRestartState.value = if (modifiedOrder.products.isEmpty()) DISABLED else ENABLED
}
@UiThread
internal fun restartOrUndo() {
if (restartState.value == UNDO) {
mOrder.value = undoOrder
mRestartState.value = ENABLED
undoOrder = null
} else {
undoOrder = mOrder.value
mOrder.value = newOrder
mRestartState.value = UNDO
}
}
@UiThread
fun selectOrderLine(orderLine: OrderLine?) {
mSelectedOrderLine.value = orderLine
}
@UiThread
fun increaseSelectedOrderLine() {
val orderLine = mSelectedOrderLine.value ?: throw IllegalStateException()
addProduct(orderLine.first)
}
@UiThread
fun decreaseSelectedOrderLine() {
val orderLine = mSelectedOrderLine.value ?: throw IllegalStateException()
removeProduct(orderLine.first)
}
}
|