taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

Order.swift (4399B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2026 Bohdan, Volodymyr Potuzhnyi
      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 import Foundation
     18 
     19 struct Order {
     20     let id: Int
     21     let currency: String
     22     let availableCategories: [Int: Category]
     23     var products: [ConfigProduct]
     24 
     25     var title: String { String(id) }
     26 
     27     var summary: String {
     28         if products.count == 1 { return products[0].description }
     29         let catQuantities = getCategoryQuantities()
     30         return catQuantities.map { (cat, qty) in
     31             "\(qty) x \(cat.localizedName)"
     32         }.joined(separator: ", ")
     33     }
     34 
     35     var summaryI18n: [String: String]? {
     36         if products.count == 1 { return products[0].descriptionI18n }
     37         let catQuantities = getCategoryQuantities()
     38         var availableLocales: Set<String>?
     39         for (cat, _) in catQuantities {
     40             guard let nameI18n = cat.nameI18n else { return nil }
     41             if availableLocales == nil {
     42                 availableLocales = Set(nameI18n.keys)
     43             } else {
     44                 availableLocales = availableLocales!.intersection(nameI18n.keys)
     45             }
     46             if availableLocales?.isEmpty == true { return nil }
     47         }
     48         guard let locales = availableLocales, !locales.isEmpty else { return nil }
     49         var result: [String: String] = [:]
     50         for locale in locales {
     51             result[locale] = catQuantities.map { (cat, qty) in
     52                 "\(qty) x \(cat.nameI18n![locale]!)"
     53             }.joined(separator: ", ")
     54         }
     55         return result
     56     }
     57 
     58     var total: Amount {
     59         var result = Amount.zero(currency)
     60         for product in products {
     61             result = result + (product.price * product.quantity)
     62         }
     63         return result
     64     }
     65 
     66     func adding(_ product: ConfigProduct) -> Order {
     67         var updated = products
     68         if let i = updated.firstIndex(where: { $0.id == product.id }) {
     69             updated[i].quantity += 1
     70         } else {
     71             var newProduct = product
     72             newProduct.quantity = 1
     73             updated.append(newProduct)
     74         }
     75         return Order(id: id, currency: currency, availableCategories: availableCategories, products: updated)
     76     }
     77 
     78     func removing(_ product: ConfigProduct) -> Order {
     79         var updated = products
     80         guard let i = updated.firstIndex(where: { $0.id == product.id }) else { return self }
     81         if updated[i].quantity <= 1 {
     82             updated.remove(at: i)
     83         } else {
     84             updated[i].quantity -= 1
     85         }
     86         return Order(id: id, currency: currency, availableCategories: availableCategories, products: updated)
     87     }
     88 
     89     private func getCategoryQuantities() -> [(Category, Int)] {
     90         var result: [(Category, Int)] = []
     91         var seen: [Int: Int] = [:]
     92         for product in products {
     93             guard let catId = product.categories.first,
     94                   let cat = availableCategories[catId] else { continue }
     95             if let idx = seen[catId] {
     96                 result[idx].1 += product.quantity
     97             } else {
     98                 seen[catId] = result.count
     99                 result.append((cat, product.quantity))
    100             }
    101         }
    102         return result
    103     }
    104 
    105     func toContractTerms(includeProducts: Bool = true) -> ContractTerms {
    106         ContractTerms(
    107             summary: summary,
    108             summaryI18n: summaryI18n,
    109             amount: total,
    110             products: includeProducts ? products.map { $0.toContractProduct() } : nil
    111         )
    112     }
    113 }
    114 
    115 struct ContractTerms: Codable {
    116     let summary: String
    117     let summaryI18n: [String: String]?
    118     let amount: Amount
    119     let products: [ContractProduct]?
    120 
    121     enum CodingKeys: String, CodingKey {
    122         case summary
    123         case summaryI18n = "summary_i18n"
    124         case amount
    125         case products
    126     }
    127 }