OrderManager.kt (7334B)
1 /* 2 * This file is part of GNU Taler 3 * (C) 2020 Taler Systems S.A. 4 * 5 * GNU Taler is free software; you can redistribute it and/or modify it under the 6 * terms of the GNU General Public License as published by the Free Software 7 * Foundation; either version 3, or (at your option) any later version. 8 * 9 * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY 10 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License along with 14 * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 package net.taler.merchantpos.order 18 19 import android.content.Context 20 import android.util.Log 21 import androidx.annotation.UiThread 22 import androidx.lifecycle.LiveData 23 import androidx.lifecycle.MutableLiveData 24 import androidx.lifecycle.map 25 import net.taler.merchantpos.R 26 import net.taler.merchantpos.config.Category 27 import net.taler.merchantpos.config.ConfigProduct 28 import net.taler.merchantpos.config.ConfigurationReceiver 29 import net.taler.merchantpos.config.PosConfig 30 import net.taler.merchantpos.order.RestartState.ENABLED 31 32 class OrderManager(private val context: Context) : ConfigurationReceiver { 33 34 companion object { 35 val TAG: String = OrderManager::class.java.simpleName 36 } 37 38 private lateinit var currency: String 39 private var orderCounter: Int = 0 40 private val mCurrentOrderId = MutableLiveData<Int>() 41 internal val currentOrderId: LiveData<Int> = mCurrentOrderId 42 43 private val productsByCategory = HashMap<Category, ArrayList<ConfigProduct>>() 44 45 private val orders = LinkedHashMap<Int, MutableLiveOrder>() 46 47 private val mProducts = MutableLiveData<List<ConfigProduct>>() 48 internal val products: LiveData<List<ConfigProduct>> = mProducts 49 50 private val mCategories = MutableLiveData<List<Category>>() 51 internal val categories: LiveData<List<Category>> = mCategories 52 53 override suspend fun onConfigurationReceived(posConfig: PosConfig, currency: String): String? { 54 // parse categories 55 if (posConfig.categories.isEmpty()) { 56 Log.e(TAG, "No valid category found.") 57 return context.getString(R.string.config_error_category) 58 } 59 // pre-select the first category 60 posConfig.categories[0].selected = true 61 62 // group products by categories 63 productsByCategory.clear() 64 val unknownCategory = Category(-1, context.getString(R.string.product_category_uncategorized)) 65 posConfig.products.forEach { product -> 66 val productCurrency = product.price.currency 67 if (productCurrency != currency) { 68 Log.e(TAG, "Product $product has currency $productCurrency, $currency expected") 69 return context.getString( 70 R.string.config_error_currency, product.description, productCurrency, currency 71 ) 72 } 73 product.categories.forEach { categoryId -> 74 val category = posConfig.categories.find { it.id == categoryId } ?: let { 75 Log.e(TAG, "Product $product has unknown category $categoryId") 76 unknownCategory 77 } 78 79 if (productsByCategory.containsKey(category)) { 80 productsByCategory[category]?.add(product) 81 } else { 82 productsByCategory[category] = ArrayList<ConfigProduct>().apply { add(product) } 83 } 84 } 85 } 86 return if (productsByCategory.size > 0) { 87 this.currency = currency 88 mCategories.postValue(posConfig.categories + 89 if(productsByCategory.containsKey(unknownCategory)) { 90 listOf(unknownCategory) 91 } else { 92 emptyList() 93 }) 94 mProducts.postValue(productsByCategory[posConfig.categories[0]]) 95 orders.clear() 96 orderCounter = 0 97 orders[0] = MutableLiveOrder(0, currency, productsByCategory) 98 mCurrentOrderId.postValue(0) 99 null // success, no error string 100 } else context.getString(R.string.config_error_product_zero) 101 } 102 103 @UiThread 104 internal fun getOrder(orderId: Int): LiveOrder { 105 return orders[orderId] ?: throw IllegalArgumentException("Order not found: $orderId") 106 } 107 108 @UiThread 109 internal fun nextOrder() { 110 val currentId = currentOrderId.value!! 111 var foundCurrentOrder = false 112 var nextId: Int? = null 113 for (orderId in orders.keys) { 114 if (foundCurrentOrder) { 115 nextId = orderId 116 break 117 } 118 if (orderId == currentId) foundCurrentOrder = true 119 } 120 if (nextId == null) { 121 nextId = ++orderCounter 122 orders[nextId] = MutableLiveOrder(nextId, currency, productsByCategory) 123 } 124 val currentOrder = order(currentId) 125 if (currentOrder.isEmpty()) orders.remove(currentId) 126 else currentOrder.lastAddedProduct = null // not needed anymore and it would get selected 127 mCurrentOrderId.value = requireNotNull(nextId) 128 } 129 130 @UiThread 131 internal fun previousOrder() { 132 val currentId = currentOrderId.value!! 133 var previousId: Int? = null 134 var foundCurrentOrder = false 135 for (orderId in orders.keys) { 136 if (orderId == currentId) { 137 foundCurrentOrder = true 138 break 139 } 140 previousId = orderId 141 } 142 if (previousId == null || !foundCurrentOrder) { 143 throw AssertionError("Could not find previous order for $currentId") 144 } 145 val currentOrder = order(currentId) 146 // remove current order if empty, or lastAddedProduct as it is not needed anymore 147 // and would get selected when navigating back instead of last selection 148 if (currentOrder.isEmpty()) orders.remove(currentId) 149 else currentOrder.lastAddedProduct = null 150 mCurrentOrderId.value = requireNotNull(previousId) 151 } 152 153 fun hasPreviousOrder(currentOrderId: Int): Boolean { 154 return currentOrderId != orders.keys.first() 155 } 156 157 fun hasNextOrder(currentOrderId: Int) = order(currentOrderId).restartState.map { state -> 158 state == ENABLED || currentOrderId != orders.keys.last() 159 } 160 161 internal fun setCurrentCategory(category: Category) { 162 val newCategories = categories.value?.apply { 163 forEach { if (it.selected) it.selected = false } 164 category.selected = true 165 } 166 mCategories.postValue(newCategories ?: emptyList()) 167 mProducts.postValue(productsByCategory[category]) 168 } 169 170 @UiThread 171 internal fun addProduct(orderId: Int, product: ConfigProduct) { 172 order(orderId).addProduct(product) 173 } 174 175 @UiThread 176 internal fun onOrderPaid(orderId: Int) { 177 if (currentOrderId.value == orderId) { 178 if (hasPreviousOrder(orderId)) previousOrder() 179 else nextOrder() 180 } 181 orders.remove(orderId) 182 } 183 184 private fun order(orderId: Int): MutableLiveOrder { 185 return orders[orderId] ?: throw IllegalStateException() 186 } 187 188 }