OrderManager.kt (7113B)
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.categories.forEach { category -> 66 productsByCategory[category] = ArrayList() 67 } 68 posConfig.products.forEach { product -> 69 val productCurrency = product.price.currency 70 if (productCurrency != currency) { 71 Log.e(TAG, "Product $product has currency $productCurrency, $currency expected") 72 return context.getString( 73 R.string.config_error_currency, product.description, productCurrency, currency 74 ) 75 } 76 product.categories.forEach { categoryId -> 77 val category = posConfig.categories.find { it.id == categoryId } ?: let { 78 Log.e(TAG, "Product $product has unknown category $categoryId") 79 unknownCategory 80 } 81 82 productsByCategory.getOrPut(category) { ArrayList() }.add(product) 83 } 84 } 85 this.currency = currency 86 mCategories.postValue(posConfig.categories + 87 if(productsByCategory.containsKey(unknownCategory)) { 88 listOf(unknownCategory) 89 } else { 90 emptyList() 91 }) 92 mProducts.postValue(productsByCategory[posConfig.categories[0]] ?: emptyList()) 93 orders.clear() 94 orderCounter = 0 95 orders[0] = MutableLiveOrder(0, currency, productsByCategory) 96 mCurrentOrderId.postValue(0) 97 return null // success, no error string 98 } 99 100 @UiThread 101 internal fun getOrder(orderId: Int): LiveOrder { 102 return orders[orderId] ?: throw IllegalArgumentException("Order not found: $orderId") 103 } 104 105 @UiThread 106 internal fun nextOrder() { 107 val currentId = currentOrderId.value!! 108 var foundCurrentOrder = false 109 var nextId: Int? = null 110 for (orderId in orders.keys) { 111 if (foundCurrentOrder) { 112 nextId = orderId 113 break 114 } 115 if (orderId == currentId) foundCurrentOrder = true 116 } 117 if (nextId == null) { 118 nextId = ++orderCounter 119 orders[nextId] = MutableLiveOrder(nextId, currency, productsByCategory) 120 } 121 val currentOrder = order(currentId) 122 if (currentOrder.isEmpty()) orders.remove(currentId) 123 else currentOrder.lastAddedProduct = null // not needed anymore and it would get selected 124 mCurrentOrderId.value = requireNotNull(nextId) 125 } 126 127 @UiThread 128 internal fun previousOrder() { 129 val currentId = currentOrderId.value!! 130 var previousId: Int? = null 131 var foundCurrentOrder = false 132 for (orderId in orders.keys) { 133 if (orderId == currentId) { 134 foundCurrentOrder = true 135 break 136 } 137 previousId = orderId 138 } 139 if (previousId == null || !foundCurrentOrder) { 140 throw AssertionError("Could not find previous order for $currentId") 141 } 142 val currentOrder = order(currentId) 143 // remove current order if empty, or lastAddedProduct as it is not needed anymore 144 // and would get selected when navigating back instead of last selection 145 if (currentOrder.isEmpty()) orders.remove(currentId) 146 else currentOrder.lastAddedProduct = null 147 mCurrentOrderId.value = requireNotNull(previousId) 148 } 149 150 fun hasPreviousOrder(currentOrderId: Int): Boolean { 151 return currentOrderId != orders.keys.first() 152 } 153 154 fun hasNextOrder(currentOrderId: Int) = order(currentOrderId).restartState.map { state -> 155 state == ENABLED || currentOrderId != orders.keys.last() 156 } 157 158 internal fun setCurrentCategory(category: Category) { 159 val newCategories = categories.value?.apply { 160 forEach { if (it.selected) it.selected = false } 161 category.selected = true 162 } 163 mCategories.postValue(newCategories ?: emptyList()) 164 mProducts.postValue(productsByCategory[category]) 165 } 166 167 @UiThread 168 internal fun addProduct(orderId: Int, product: ConfigProduct) { 169 order(orderId).addProduct(product) 170 } 171 172 @UiThread 173 internal fun onOrderPaid(orderId: Int) { 174 if (currentOrderId.value == orderId) { 175 if (hasPreviousOrder(orderId)) previousOrder() 176 else nextOrder() 177 } 178 orders.remove(orderId) 179 } 180 181 private fun order(orderId: Int): MutableLiveOrder { 182 return orders[orderId] ?: throw IllegalStateException() 183 } 184 185 }