taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

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 }