taler-android

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

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 }