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 }