import Foundation /// A `Sendable` snapshot of a buyer's offer on a listing. public struct Offer: Sendable, Equatable { // MARK: Constants /// The share of the asking price under which an offer is declined outright. public static let floorShare = 0.8 /// How long an offer binds its buyer: 48 hours. public static let lifetime: TimeInterval = 172_800 // MARK: Properties /// The offer's identifier. public let id: UUID /// The identifier of the conversation the offer was made in. public let conversationID: UUID /// The listing offered on. public let listing: Listing /// The account offering. public let buyer: User /// The amount offered, in euro cents. public let amountCents: Int /// Where the offer stands; an open offer past its expiry reads as expired. public let status: Status /// When the offer was made. public let createdAt: Date /// When the offer stops binding its buyer. public let expiresAt: Date // MARK: Initializers public init( id: UUID, conversationID: UUID, listing: Listing, buyer: User, amountCents: Int, status: Status, createdAt: Date, expiresAt: Date ) { self.id = id self.conversationID = conversationID self.listing = listing self.buyer = buyer self.amountCents = amountCents self.status = status self.createdAt = createdAt self.expiresAt = expiresAt } } // MARK: - Enumerations public extension Offer { /// Where an offer stands. enum Status: String, Sendable { /// Waiting for the seller's answer. case open /// Accepted: a sale record was opened. case accepted /// Declined by the seller. case declined /// Left unanswered past its expiry, or overtaken by a sale of the listing. case expired } } // MARK: - Extensions public extension Offer { // MARK: Methods /// The lowest offer a listing's seller hears, in euro cents: ``floorShare`` of the asking price, rounded up to a whole euro. /// - Parameter askingCents: the listing's asking price, in euro cents. static func floor( forAsking askingCents: Int ) -> Int { Int((Double(askingCents) * floorShare / 100).rounded(.up)) * 100 } }