summaryrefslogtreecommitdiff
path: root/app/src/main/java/net/taler/merchantpos/order/OrderManager.kt
blob: 449d741f521a1bab044b4b9fd8ce5559f2804bd5 (plain)
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)
    }

}