/* * This file is part of GNU Taler * (C) 2020 Taler Systems S.A. * * GNU Taler is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 3, or (at your option) any later version. * * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * GNU Taler; see the file COPYING. If not, see */ package net.taler.merchantpos.order import androidx.core.os.LocaleListCompat import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL import com.fasterxml.jackson.annotation.JsonProperty import net.taler.merchantpos.Amount import java.util.* import java.util.Locale.LanguageRange import kotlin.collections.ArrayList import kotlin.collections.HashMap data class Category( val id: Int, val name: String, @JsonProperty("name_i18n") val nameI18n: Map? ) { var selected: Boolean = false val localizedName: String get() = getLocalizedString(nameI18n, name) } @JsonInclude(NON_NULL) abstract class Product { @get:JsonProperty("product_id") abstract val productId: String? abstract val description: String @get:JsonProperty("description_i18n") abstract val descriptionI18n: Map? abstract val price: String @get:JsonProperty("delivery_location") abstract val location: String? abstract val image: String? @get:JsonIgnore val localizedDescription: String get() = getLocalizedString(descriptionI18n, description) } data class ConfigProduct( @JsonIgnore val id: String = UUID.randomUUID().toString(), override val productId: String?, override val description: String, override val descriptionI18n: Map?, override val price: String, override val location: String?, override val image: String?, val categories: List, @JsonIgnore val quantity: Int = 0 ) : Product() { val priceAsDouble by lazy { Amount.fromString(price).amount.toDouble() } override fun equals(other: Any?) = other is ConfigProduct && id == other.id override fun hashCode() = id.hashCode() } data class ContractProduct( override val productId: String?, override val description: String, override val descriptionI18n: Map?, override val price: String, override val location: String?, override val image: String?, val quantity: Int ) : Product() { constructor(product: ConfigProduct) : this( product.productId, product.description, product.descriptionI18n, product.price, product.location, product.image, product.quantity ) } private fun getLocalizedString(map: Map?, default: String): String { // just return the default, if it is the only element if (map == null) return default // create a priority list of language ranges from system locales val locales = LocaleListCompat.getDefault() val priorityList = ArrayList(locales.size()) for (i in 0 until locales.size()) { priorityList.add(LanguageRange(locales[i].toLanguageTag())) } // create a list of locales available in the given map val availableLocales = map.keys.mapNotNull { if (it == "_") return@mapNotNull null val list = it.split("_") when (list.size) { 1 -> Locale(list[0]) 2 -> Locale(list[0], list[1]) 3 -> Locale(list[0], list[1], list[2]) else -> null } } val match = Locale.lookup(priorityList, availableLocales) return match?.toString()?.let { map[it] } ?: default } data class Order(val id: Int, val availableCategories: Map) { val products = ArrayList() val title: String = id.toString() val summary: String get() { if (products.size == 1) return products[0].description return getCategoryQuantities().map { (category: Category, quantity: Int) -> "$quantity x ${category.localizedName}" }.joinToString() } val total: Double get() { var total = 0.0 products.forEach { product -> val price = product.priceAsDouble total += price * product.quantity } return total } val totalAsString: String get() = String.format("%.2f", total) operator fun plus(product: ConfigProduct): Order { val i = products.indexOf(product) if (i == -1) { products.add(product.copy(quantity = 1)) } else { val quantity = products[i].quantity products[i] = products[i].copy(quantity = quantity + 1) } return this } operator fun minus(product: ConfigProduct): Order { val i = products.indexOf(product) if (i == -1) return this val quantity = products[i].quantity if (quantity <= 1) { products.remove(product) } else { products[i] = products[i].copy(quantity = quantity - 1) } return this } private fun getCategoryQuantities(): HashMap { val categories = HashMap() products.forEach { product -> val categoryId = product.categories[0] val category = availableCategories.getValue(categoryId) val oldQuantity = categories[category] ?: 0 categories[category] = oldQuantity + product.quantity } return categories } /** * Returns a map of i18n summaries for each locale present in *all* given [Category]s * or null if there's no locale that fulfills this criteria. */ val summaryI18n: Map? get() { if (products.size == 1) return products[0].descriptionI18n val categoryQuantities = getCategoryQuantities() // get all available locales val availableLocales = categoryQuantities.mapNotNull { (category, _) -> val nameI18n = category.nameI18n // if one category doesn't have locales, we can return null here already nameI18n?.keys ?: return null }.flatten().toHashSet() // remove all locales not supported by all categories categoryQuantities.forEach { (category, _) -> // category.nameI18n should be non-null now availableLocales.retainAll(category.nameI18n!!.keys) if (availableLocales.isEmpty()) return null } return availableLocales.map { locale -> Pair( locale, categoryQuantities.map { (category, quantity) -> // category.nameI18n should be non-null now "$quantity x ${category.nameI18n!![locale]}" }.joinToString() ) }.toMap() } }