import Foundation struct AppOrderSummary: Decodable, Identifiable { let id: String let orderId: String? let realId: String? let storeId: String? let shortId: String? let total: Double? let status: String? let statusDetailed: String? let statusLabel: String? let nextAction: String? let paymentStatus: String? let paymentMethod: String? let deliveryType: String? let storeName: String? let storePhone: String? let storeLogoURL: String? let createdAt: String? let updatedAt: String? enum CodingKeys: String, CodingKey { case id case orderId case realId case storeId case store_id case shortId case total case status case statusDetailed case statusLabel case nextAction case paymentStatus case paymentMethod case deliveryType case storeName case storePhone case storeLogo case store_logo case logo case storeImage case store_image case storeImageUrl case logoUrl case date case createdAt case updatedAt } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) orderId = ApiService.decodeFlexibleString(from: container, keys: [.orderId]) id = ApiService.decodeFlexibleString(from: container, keys: [.orderId, .realId, .id]) ?? UUID().uuidString realId = ApiService.decodeFlexibleString(from: container, keys: [.realId]) storeId = ApiService.decodeFlexibleString(from: container, keys: [.storeId, .store_id]) shortId = ApiService.decodeFlexibleString(from: container, keys: [.shortId]) total = ApiService.decodeFlexibleDouble(from: container, keys: [.total]) status = ApiService.decodeFlexibleString(from: container, keys: [.status]) statusDetailed = ApiService.decodeFlexibleString(from: container, keys: [.statusDetailed]) statusLabel = ApiService.decodeFlexibleString(from: container, keys: [.statusLabel]) nextAction = ApiService.decodeFlexibleString(from: container, keys: [.nextAction]) paymentStatus = ApiService.decodeFlexibleString(from: container, keys: [.paymentStatus]) paymentMethod = ApiService.decodeFlexibleString(from: container, keys: [.paymentMethod]) deliveryType = ApiService.decodeFlexibleString(from: container, keys: [.deliveryType]) storeName = ApiService.decodeFlexibleString(from: container, keys: [.storeName]) storePhone = ApiService.decodeFlexibleString(from: container, keys: [.storePhone]) storeLogoURL = ApiService.decodeFlexibleString(from: container, keys: [.storeLogo, .store_logo, .logo, .storeImage, .store_image, .storeImageUrl, .logoUrl]) let fallbackDate = ApiService.decodeFlexibleString(from: container, keys: [.date]) createdAt = ApiService.decodeFlexibleString(from: container, keys: [.createdAt]) ?? fallbackDate updatedAt = ApiService.decodeFlexibleString(from: container, keys: [.updatedAt]) ?? fallbackDate } } struct PublicOrderResult: Codable, Identifiable { let id: String let shortId: String? let realId: String? let storeId: String? let status: String? let paymentStatus: String? let paymentConfirmed: Bool? let paymentMethod: String? let paymentMethodCode: String? let paymentPayload: CreateOrderPaymentPayload? let payment: CreateOrderPaymentInfo? let nextAction: String? let deliveryType: String? let deliveryTypeLabel: String? let subtotal: Double? let deliveryFee: Double? let discount: Double? let total: Double? let storeName: String? let storePhone: String? let storeLogoURL: String? let createdAt: String? let updatedAt: String? let otp: String? let customerOtp: String? let confirmOtp: String? let cancellationReason: String? let fullAddress: String? let deliveryAddress: PublicOrderDeliveryAddress? let review: PublicOrderReview? let items: [PublicOrderItem] let timeline: [PublicOrderTimelineEvent] enum CodingKeys: String, CodingKey { case id case shortId case realId case storeId case store_id case status case paymentStatus case paymentConfirmed case paymentMethod case paymentMethodCode case paymentPayload case payment case nextAction case deliveryType case deliveryTypeLabel case subtotal case subTotal case itemsTotal case deliveryFee case delivery_fee case fee case discount case desconto case couponDiscount case total case storeName case storePhone case storeLogo case store_logo case logo case storeImage case store_image case storeImageUrl case logoUrl case createdAt case updatedAt case otp case customerOtp case confirmOtp case cancellationReason case fullAddress case address case deliveryAddress case delivery_address case customerAddress case customer_address case review case orderReview case items case timeline case history case orderedAt } init( id: String, shortId: String? = nil, realId: String? = nil, storeId: String? = nil, status: String? = nil, paymentStatus: String? = nil, paymentConfirmed: Bool? = nil, paymentMethod: String? = nil, paymentMethodCode: String? = nil, paymentPayload: CreateOrderPaymentPayload? = nil, payment: CreateOrderPaymentInfo? = nil, nextAction: String? = nil, deliveryType: String? = nil, deliveryTypeLabel: String? = nil, subtotal: Double? = nil, deliveryFee: Double? = nil, discount: Double? = nil, total: Double? = nil, storeName: String? = nil, storePhone: String? = nil, storeLogoURL: String? = nil, createdAt: String? = nil, updatedAt: String? = nil, otp: String? = nil, customerOtp: String? = nil, confirmOtp: String? = nil, cancellationReason: String? = nil, fullAddress: String? = nil, deliveryAddress: PublicOrderDeliveryAddress? = nil, review: PublicOrderReview? = nil, items: [PublicOrderItem] = [], timeline: [PublicOrderTimelineEvent] = [] ) { self.id = id self.shortId = shortId self.realId = realId self.storeId = storeId self.status = status self.paymentStatus = paymentStatus self.paymentConfirmed = paymentConfirmed self.paymentMethod = paymentMethod self.paymentMethodCode = paymentMethodCode self.paymentPayload = paymentPayload self.payment = payment self.nextAction = nextAction self.deliveryType = deliveryType self.deliveryTypeLabel = deliveryTypeLabel self.subtotal = subtotal self.deliveryFee = deliveryFee self.discount = discount self.total = total self.storeName = storeName self.storePhone = storePhone self.storeLogoURL = storeLogoURL self.createdAt = createdAt self.updatedAt = updatedAt self.otp = otp self.customerOtp = customerOtp self.confirmOtp = confirmOtp self.cancellationReason = cancellationReason self.fullAddress = fullAddress self.deliveryAddress = deliveryAddress self.review = review self.items = items self.timeline = timeline } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = ApiService.decodeFlexibleString(from: container, keys: [.id]) ?? UUID().uuidString shortId = ApiService.decodeFlexibleString(from: container, keys: [.shortId]) realId = ApiService.decodeFlexibleString(from: container, keys: [.realId]) storeId = ApiService.decodeFlexibleString(from: container, keys: [.storeId, .store_id]) status = ApiService.decodeFlexibleString(from: container, keys: [.status]) paymentStatus = ApiService.decodeFlexibleString(from: container, keys: [.paymentStatus]) paymentConfirmed = try? container.decode(Bool.self, forKey: .paymentConfirmed) paymentMethod = ApiService.decodeFlexibleString(from: container, keys: [.paymentMethod]) paymentMethodCode = ApiService.decodeFlexibleString(from: container, keys: [.paymentMethodCode]) nextAction = ApiService.decodeFlexibleString(from: container, keys: [.nextAction]) payment = try? container.decode(CreateOrderPaymentInfo.self, forKey: .payment) if let objectPayload = try? container.decode(CreateOrderPaymentPayload.self, forKey: .paymentPayload) { paymentPayload = objectPayload } else if let stringPayload = try? container.decode(String.self, forKey: .paymentPayload) { paymentPayload = CreateOrderPaymentPayload( copyPaste: stringPayload, qrCodeImage: nil, expirationDate: nil ) } else { paymentPayload = nil } deliveryType = ApiService.decodeFlexibleString(from: container, keys: [.deliveryType]) deliveryTypeLabel = ApiService.decodeFlexibleString(from: container, keys: [.deliveryTypeLabel]) subtotal = ApiService.decodeFlexibleDouble(from: container, keys: [.subtotal, .subTotal, .itemsTotal]) deliveryFee = ApiService.decodeFlexibleDouble(from: container, keys: [.deliveryFee, .delivery_fee, .fee]) discount = ApiService.decodeFlexibleDouble(from: container, keys: [.discount, .desconto, .couponDiscount]) total = ApiService.decodeFlexibleDouble(from: container, keys: [.total]) storeName = ApiService.decodeFlexibleString(from: container, keys: [.storeName]) storePhone = ApiService.decodeFlexibleString(from: container, keys: [.storePhone]) storeLogoURL = ApiService.decodeFlexibleString(from: container, keys: [.storeLogo, .store_logo, .logo, .storeImage, .store_image, .storeImageUrl, .logoUrl]) createdAt = ApiService.decodeFlexibleString(from: container, keys: [.createdAt, .orderedAt]) updatedAt = ApiService.decodeFlexibleString(from: container, keys: [.updatedAt]) otp = ApiService.decodeFlexibleString(from: container, keys: [.otp]) customerOtp = ApiService.decodeFlexibleString(from: container, keys: [.customerOtp]) confirmOtp = ApiService.decodeFlexibleString(from: container, keys: [.confirmOtp]) cancellationReason = ApiService.decodeFlexibleString(from: container, keys: [.cancellationReason]) fullAddress = ApiService.decodeFlexibleString(from: container, keys: [.fullAddress]) deliveryAddress = (try? container.decode(PublicOrderDeliveryAddress.self, forKey: .address)) ?? (try? container.decode(PublicOrderDeliveryAddress.self, forKey: .deliveryAddress)) ?? (try? container.decode(PublicOrderDeliveryAddress.self, forKey: .delivery_address)) ?? (try? container.decode(PublicOrderDeliveryAddress.self, forKey: .customerAddress)) ?? (try? container.decode(PublicOrderDeliveryAddress.self, forKey: .customer_address)) review = (try? container.decode(PublicOrderReview.self, forKey: .review)) ?? (try? container.decode(PublicOrderReview.self, forKey: .orderReview)) items = (try? container.decode([PublicOrderItem].self, forKey: .items)) ?? [] timeline = (try? container.decode([PublicOrderTimelineEvent].self, forKey: .timeline)) ?? (try? container.decode([PublicOrderTimelineEvent].self, forKey: .history)) ?? [] } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encodeIfPresent(shortId, forKey: .shortId) try container.encodeIfPresent(realId, forKey: .realId) try container.encodeIfPresent(storeId, forKey: .storeId) try container.encodeIfPresent(status, forKey: .status) try container.encodeIfPresent(paymentStatus, forKey: .paymentStatus) try container.encodeIfPresent(paymentConfirmed, forKey: .paymentConfirmed) try container.encodeIfPresent(paymentMethod, forKey: .paymentMethod) try container.encodeIfPresent(paymentMethodCode, forKey: .paymentMethodCode) try container.encodeIfPresent(paymentPayload, forKey: .paymentPayload) try container.encodeIfPresent(payment, forKey: .payment) try container.encodeIfPresent(nextAction, forKey: .nextAction) try container.encodeIfPresent(deliveryType, forKey: .deliveryType) try container.encodeIfPresent(deliveryTypeLabel, forKey: .deliveryTypeLabel) try container.encodeIfPresent(subtotal, forKey: .subtotal) try container.encodeIfPresent(deliveryFee, forKey: .deliveryFee) try container.encodeIfPresent(discount, forKey: .discount) try container.encodeIfPresent(total, forKey: .total) try container.encodeIfPresent(storeName, forKey: .storeName) try container.encodeIfPresent(storePhone, forKey: .storePhone) try container.encodeIfPresent(createdAt, forKey: .createdAt) try container.encodeIfPresent(updatedAt, forKey: .updatedAt) try container.encodeIfPresent(otp, forKey: .otp) try container.encodeIfPresent(customerOtp, forKey: .customerOtp) try container.encodeIfPresent(confirmOtp, forKey: .confirmOtp) try container.encodeIfPresent(cancellationReason, forKey: .cancellationReason) try container.encodeIfPresent(fullAddress, forKey: .fullAddress) try container.encodeIfPresent(deliveryAddress, forKey: .address) try container.encodeIfPresent(review, forKey: .review) try container.encode(items, forKey: .items) try container.encode(timeline, forKey: .timeline) } var displayOtpCode: String? { let values = [customerOtp, otp, confirmOtp] for value in values { let trimmed = (value ?? "").trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.isEmpty == false { return trimmed } } return nil } var isInDeliveryRoute: Bool { let normalized = (status ?? "").uppercased() if normalized.contains("OUT_FOR_DELIVERY") { return true } if normalized.contains("EM_ROTA") { return true } if normalized.contains("ON_ROUTE") { return true } if normalized.contains("ROTA") { return true } return false } var isFinalStatus: Bool { let normalized = (status ?? "").uppercased() return normalized == "COMPLETED" || normalized == "CANCELED" || normalized == "REFUNDED" } var isPaymentConfirmed: Bool { if let paymentConfirmed { return paymentConfirmed } let payment = (paymentStatus ?? "").uppercased() let currentStatus = (status ?? "").uppercased() if Self.looksConfirmed(payment) || Self.looksConfirmed(currentStatus) { return true } // Fallback: alguns ambientes atualizam apenas a timeline primeiro. if timeline.contains(where: { event in let statusValue = (event.status ?? "").uppercased() let messageValue = (event.message ?? "").uppercased() return Self.looksConfirmed(statusValue) || Self.looksConfirmed(messageValue) }) { return true } return false } private static func looksConfirmed(_ value: String) -> Bool { if value.isEmpty { return false } if value.contains("PENDING") || value.contains("AWAIT") { return false } if value.contains("FAILED") || value.contains("ERROR") { return false } if value.contains("CANCEL") || value.contains("REFUND") { return false } if value.contains("CONFIRM") { return true } if value.contains("APPROV") { return true } if value.contains("PAID") { return true } if value.contains("RECEIV") { return true } return value == "SUCCESS" || value == "DONE" } } struct PublicOrderDeliveryAddress: Codable { let label: String? let street: String? let number: String? let neighborhood: String? let city: String? let state: String? let zip: String? let complement: String? enum CodingKeys: String, CodingKey { case label case street case address case number case neighborhood case district case city case state case zip case zipCode case zipcode case complement } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) label = ApiService.decodeFlexibleString(from: container, keys: [.label]) street = ApiService.decodeFlexibleString(from: container, keys: [.street, .address]) number = ApiService.decodeFlexibleString(from: container, keys: [.number]) neighborhood = ApiService.decodeFlexibleString(from: container, keys: [.neighborhood, .district]) city = ApiService.decodeFlexibleString(from: container, keys: [.city]) state = ApiService.decodeFlexibleString(from: container, keys: [.state]) zip = ApiService.decodeFlexibleString(from: container, keys: [.zip, .zipCode, .zipcode]) complement = ApiService.decodeFlexibleString(from: container, keys: [.complement]) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(label, forKey: .label) try container.encodeIfPresent(street, forKey: .street) try container.encodeIfPresent(number, forKey: .number) try container.encodeIfPresent(neighborhood, forKey: .neighborhood) try container.encodeIfPresent(city, forKey: .city) try container.encodeIfPresent(state, forKey: .state) try container.encodeIfPresent(zip, forKey: .zip) try container.encodeIfPresent(complement, forKey: .complement) } } struct PublicOrderReview: Codable { let id: String? let orderId: String? let rate: Int? let message: String? let orderRate: Int? let orderComment: String? let orderPositiveTags: [String]? let orderImprovementTags: [String]? let deliverySentiment: String? let deliveryPositiveTags: [String]? let deliveryNegativeTags: [String]? let appNps: Int? let platform: String? let date: String? enum CodingKeys: String, CodingKey { case id case orderId case rate case message case orderRate case orderComment case orderPositiveTags case orderImprovementTags case itemFeedback case improvementFeedback case deliverySentiment case deliveryFeedback case deliveryPositiveTags case deliveryNegativeTags case appNps case app_nps case platform case date } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = ApiService.decodeFlexibleString(from: container, keys: [.id]) orderId = ApiService.decodeFlexibleString(from: container, keys: [.orderId]) rate = ApiService.decodeFlexibleInt(from: container, keys: [.rate]) message = ApiService.decodeFlexibleString(from: container, keys: [.message]) orderRate = ApiService.decodeFlexibleInt(from: container, keys: [.orderRate, .rate]) orderComment = ApiService.decodeFlexibleString(from: container, keys: [.orderComment, .message]) orderPositiveTags = Self.decodeStringList( from: container, keys: [.orderPositiveTags, .itemFeedback] ) orderImprovementTags = Self.decodeStringList( from: container, keys: [.orderImprovementTags, .improvementFeedback] ) deliverySentiment = ApiService.decodeFlexibleString(from: container, keys: [.deliverySentiment, .deliveryFeedback]) deliveryPositiveTags = Self.decodeStringList(from: container, keys: [.deliveryPositiveTags]) deliveryNegativeTags = Self.decodeStringList(from: container, keys: [.deliveryNegativeTags]) appNps = Self.decodeNps(from: container, keys: [.appNps, .app_nps]) platform = ApiService.decodeFlexibleString(from: container, keys: [.platform]) date = ApiService.decodeFlexibleString(from: container, keys: [.date]) } private static func decodeStringList( from container: KeyedDecodingContainer, keys: [CodingKeys] ) -> [String]? { for key in keys { if let list = try? container.decode([String].self, forKey: key) { return list } if let single = try? container.decode(String.self, forKey: key) { let normalized = single.trimmingCharacters(in: .whitespacesAndNewlines) if normalized.isEmpty == false { return [normalized] } } } return nil } private static func decodeNps( from container: KeyedDecodingContainer, keys: [CodingKeys] ) -> Int? { for key in keys { if let value = try? container.decode(Int.self, forKey: key) { return value } if let value = try? container.decode(Double.self, forKey: key) { return Int(value.rounded()) } if let raw = try? container.decode(String.self, forKey: key) { let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.isEmpty { continue } if let asInt = Int(trimmed) { return asInt } let normalized = trimmed.replacingOccurrences(of: ",", with: ".") if let asDouble = Double(normalized) { return Int(asDouble.rounded()) } } } return nil } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(id, forKey: .id) try container.encodeIfPresent(orderId, forKey: .orderId) try container.encodeIfPresent(rate, forKey: .rate) try container.encodeIfPresent(message, forKey: .message) try container.encodeIfPresent(orderRate, forKey: .orderRate) try container.encodeIfPresent(orderComment, forKey: .orderComment) try container.encodeIfPresent(orderPositiveTags, forKey: .orderPositiveTags) try container.encodeIfPresent(orderImprovementTags, forKey: .orderImprovementTags) try container.encodeIfPresent(deliverySentiment, forKey: .deliverySentiment) try container.encodeIfPresent(deliveryPositiveTags, forKey: .deliveryPositiveTags) try container.encodeIfPresent(deliveryNegativeTags, forKey: .deliveryNegativeTags) try container.encodeIfPresent(appNps, forKey: .appNps) try container.encodeIfPresent(platform, forKey: .platform) try container.encodeIfPresent(date, forKey: .date) } } struct PublicOrderItem: Codable, Identifiable { let id: String let productId: String? let name: String? let qty: Int? let price: Double? enum CodingKeys: String, CodingKey { case id case productId case name case qty case quantity case price } init(id: String = UUID().uuidString, productId: String? = nil, name: String?, qty: Int?, price: Double?) { self.id = id self.productId = productId self.name = name self.qty = qty self.price = price } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = (try? container.decode(String.self, forKey: .id)) ?? UUID().uuidString productId = try? container.decode(String.self, forKey: .productId) name = try? container.decode(String.self, forKey: .name) qty = ApiService.decodeFlexibleInt(from: container, keys: [.qty, .quantity]) price = ApiService.decodeFlexibleDouble(from: container, keys: [.price]) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encodeIfPresent(productId, forKey: .productId) try container.encodeIfPresent(name, forKey: .name) try container.encodeIfPresent(qty, forKey: .qty) try container.encodeIfPresent(price, forKey: .price) } } struct PublicOrderTimelineEvent: Codable, Identifiable { let id: String let status: String? let label: String? let active: Bool? let completed: Bool? let message: String? let time: String? let date: String? enum CodingKeys: String, CodingKey { case id case status case label case active case completed case message case event case time case date case createdAt case updatedAt } init( id: String = UUID().uuidString, status: String?, label: String? = nil, active: Bool? = nil, completed: Bool? = nil, message: String?, time: String?, date: String? = nil ) { self.id = id self.status = status self.label = label self.active = active self.completed = completed self.message = message self.time = time self.date = date } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = (try? container.decode(String.self, forKey: .id)) ?? UUID().uuidString status = try? container.decode(String.self, forKey: .status) label = try? container.decode(String.self, forKey: .label) active = try? container.decode(Bool.self, forKey: .active) completed = try? container.decode(Bool.self, forKey: .completed) message = (try? container.decode(String.self, forKey: .message)) ?? (try? container.decode(String.self, forKey: .event)) time = (try? container.decode(String.self, forKey: .time)) ?? (try? container.decode(String.self, forKey: .createdAt)) ?? (try? container.decode(String.self, forKey: .updatedAt)) date = try? container.decode(String.self, forKey: .date) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encodeIfPresent(status, forKey: .status) try container.encodeIfPresent(label, forKey: .label) try container.encodeIfPresent(active, forKey: .active) try container.encodeIfPresent(completed, forKey: .completed) try container.encodeIfPresent(message, forKey: .message) try container.encodeIfPresent(time, forKey: .time) try container.encodeIfPresent(date, forKey: .date) } } struct OrderRealtimeUpdate: Decodable { let id: String? let shortId: String? let storeId: String? let userId: String? let status: String? let paymentStatus: String? let updatedAt: String? enum CodingKeys: String, CodingKey { case id case shortId case storeId case userId case status case paymentStatus case updatedAt } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = ApiService.decodeFlexibleString(from: container, keys: [.id]) shortId = ApiService.decodeFlexibleString(from: container, keys: [.shortId]) storeId = ApiService.decodeFlexibleString(from: container, keys: [.storeId]) userId = ApiService.decodeFlexibleString(from: container, keys: [.userId]) status = ApiService.decodeFlexibleString(from: container, keys: [.status]) paymentStatus = ApiService.decodeFlexibleString(from: container, keys: [.paymentStatus]) updatedAt = ApiService.decodeFlexibleString(from: container, keys: [.updatedAt]) } } extension CreateOrderResult { func asPublicOrderResult() -> PublicOrderResult { PublicOrderResult( id: id ?? UUID().uuidString, shortId: shortId, status: status, paymentStatus: paymentStatus, paymentConfirmed: paymentConfirmed, paymentMethod: paymentMethod, paymentPayload: paymentPayload, payment: payment ) } }