taler-android

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

PosConfig.kt (6477B)


      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.config
     18 
     19 import android.os.Build.VERSION.SDK_INT
     20 import kotlinx.serialization.SerialName
     21 import kotlinx.serialization.Serializable
     22 import net.taler.common.Amount
     23 import net.taler.common.ContractProduct
     24 import net.taler.common.OrderProduct
     25 import net.taler.common.TalerUtils
     26 import net.taler.common.Tax
     27 import net.taler.merchantlib.MerchantConfig
     28 import java.math.RoundingMode
     29 import java.util.UUID
     30 
     31 sealed class Config {
     32     abstract fun isValid(): Boolean
     33     abstract fun hasPassword(): Boolean
     34     abstract fun savePassword(): Boolean
     35 
     36     /**
     37      * JSON config URL + user/password
     38      *
     39      * @Deprecated("Use New instead")
     40      */
     41     /*
     42     data class Old(
     43         val configUrl: String,
     44         val username: String,
     45         val password: String,
     46         val savePassword: Boolean,
     47     ): Config() {
     48         override fun isValid() = configUrl.isNotBlank()
     49         override fun hasPassword() = password.isNotBlank()
     50         override fun savePassword() = savePassword
     51     }
     52     */
     53 
     54     /**
     55      * Merchant URL + access token
     56      */
     57     data class New(
     58         val merchantUrl: String,
     59         val accessToken: String,
     60         val savePassword: Boolean,
     61     ): Config() {
     62         override fun isValid() = merchantUrl.isNotBlank()
     63         override fun hasPassword() = accessToken.isNotBlank()
     64         override fun savePassword() = savePassword
     65     }
     66 }
     67 
     68 @Serializable
     69 data class PosConfig(
     70     @SerialName("config")
     71     val merchantConfig: MerchantConfig? = null ,
     72     val categories: List<Category>,
     73     val products: List<ConfigProduct>
     74 )
     75 
     76 @Serializable
     77 data class Category(
     78     val id: Int,
     79     val name: String,
     80     @SerialName("name_i18n")
     81     val nameI18n: Map<String, String>? = null
     82 ) {
     83     var selected: Boolean = false
     84     val localizedName: String
     85         get() = if (SDK_INT >= 26) TalerUtils.getLocalizedString(nameI18n, name) else name
     86 }
     87 
     88 @Serializable
     89 data class ConfigProduct(
     90     val id: String = UUID.randomUUID().toString(),
     91     @SerialName("product_id")
     92     override val productId: String? = null,
     93     @SerialName("product_name")
     94     override val productName: String? = null,
     95     override val description: String,
     96     @SerialName("description_i18n")
     97     override val descriptionI18n: Map<String, String>? = null,
     98     override val price: Amount,
     99     @SerialName("prices_are_net")
    100     val pricesAreNet: Boolean = false,
    101     @SerialName("delivery_location")
    102     override val location: String? = null,
    103     override val image: String? = null,
    104     override val taxes: Set<Tax>? = null,
    105     val categories: List<Int>,
    106     val quantity: Int = 0,
    107     @SerialName("total_stock")
    108     val totalStock: Int? = null,
    109     @SerialName("unit_total_stock")
    110     val unitTotalStock: String? = null,
    111     @SerialName("total_sold")
    112     val totalSold: Int? = null,
    113     @SerialName("unit_total_sold")
    114     val unitTotalSold: String? = null,
    115     @SerialName("total_lost")
    116     val totalLost: Int? = null,
    117     @SerialName("unit_total_lost")
    118     val unitTotalLost: String? = null,
    119     val availableToSell: Boolean = true,
    120     val remainingStock: Int? = null,
    121     val currencyMismatch: Boolean = false,
    122 ) : OrderProduct() {
    123     val totalPrice: Amount
    124         get() = (price * quantity).withSpec(price.spec)
    125     private val normalizedProductName: String?
    126         get() = productName?.sanitizeVisibleText()?.takeIf { it.isNotEmpty() }
    127     private val normalizedDescription: String
    128         get() = localizedDescription.sanitizeVisibleText()
    129     val displayName: String
    130         get() = normalizedProductName ?: normalizedDescription
    131     val displayDescription: String?
    132         get() = normalizedProductName
    133             ?.takeIf { it != normalizedDescription }
    134             ?.let { normalizedDescription.takeIf(String::isNotEmpty) }
    135     val displayPrice: String
    136         get() = price.toString()
    137     val stableKey: String
    138         get() = listOf(
    139             productId?.trim().orEmpty(),
    140             productName?.trim().orEmpty(),
    141             description.trim(),
    142             price.currency,
    143             price.value.toString(),
    144             price.fraction.toString(),
    145             categories.joinToString(","),
    146         ).joinToString("|")
    147     val stockLimit: Int?
    148         get() {
    149             if (totalStock == -1 || unitTotalStock == "-1") return null
    150             val total = totalStock
    151                 ?: unitTotalStock?.toBigDecimalOrNull()
    152                     ?.setScale(0, RoundingMode.DOWN)
    153                     ?.toInt()
    154                 ?: return null
    155             val sold = totalSold
    156                 ?: unitTotalSold?.toBigDecimalOrNull()
    157                     ?.setScale(0, RoundingMode.DOWN)
    158                     ?.toInt()
    159                 ?: 0
    160             val lost = totalLost
    161                 ?: unitTotalLost?.toBigDecimalOrNull()
    162                     ?.setScale(0, RoundingMode.DOWN)
    163                     ?.toInt()
    164                 ?: 0
    165             return (total - sold - lost).coerceAtLeast(0)
    166         }
    167 
    168     fun toContractProduct() = ContractProduct(
    169         productId = productId,
    170         productName = productName ?: description,
    171         description = description,
    172         descriptionI18n = descriptionI18n?.takeIf { it.isNotEmpty() },
    173         price = price,
    174         pricesAreNet = pricesAreNet,
    175         location = null,
    176         image = image?.takeIf { it.isNotBlank() },
    177         taxes = taxes?.takeIf { it.isNotEmpty() },
    178         quantity = quantity
    179     )
    180 }
    181 
    182 private fun String.sanitizeVisibleText(): String {
    183     return filterNot { char ->
    184         when (Character.getType(char)) {
    185             Character.FORMAT.toInt(),
    186             Character.CONTROL.toInt(),
    187             Character.SURROGATE.toInt(),
    188             Character.PRIVATE_USE.toInt(),
    189             Character.UNASSIGNED.toInt() -> true
    190             else -> false
    191         }
    192     }.trim()
    193 }