Added the catalogue models and seed to support phase 3 of the POC prototype in the Persistence package.
This commit is contained in:
1 parent
43dcc03da6
commit
b1ceed7790
20 files changed
+2035
-8
No files matched your search
@@ -0,0 +1,224 @@
|
||||
import Foundation
|
||||
|
||||
/// A `Sendable` snapshot of a sale record: what was agreed, and how far its handshake has come.
|
||||
///
|
||||
/// The handshake has five steps — agreed, paid, sent, received, rated — each stamped by the person who took it. Payment is settled between
|
||||
/// buyer and seller off-platform; the record keeps the amounts and the stamps.
|
||||
public struct Sale: Sendable, Equatable {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// The record's number, shown as `CCN-48213`.
|
||||
public let number: Int
|
||||
/// The identifier of the conversation the sale was agreed in.
|
||||
public let conversationID: UUID
|
||||
/// The listing sold.
|
||||
public let listing: Listing
|
||||
/// The account buying.
|
||||
public let buyer: User
|
||||
/// The account selling: the owner of the listing's shop.
|
||||
public let seller: User
|
||||
/// The agreed price, in euro cents.
|
||||
public let priceCents: Int
|
||||
/// The asking price at the moment of agreement, in euro cents.
|
||||
public let askedCents: Int
|
||||
/// The agreed shipping cost, in euro cents.
|
||||
public let shippingCents: Int
|
||||
/// The condition the seller declared, frozen at the moment of agreement.
|
||||
public let declared: Declaration
|
||||
/// When the sale was agreed.
|
||||
public let agreedAt: Date
|
||||
/// When the buyer marked the sale paid.
|
||||
public let paidAt: Date?
|
||||
/// How the buyer says they paid.
|
||||
public let paymentNote: String?
|
||||
/// When the seller marked the copy sent.
|
||||
public let sentAt: Date?
|
||||
/// The carrier and tracking the seller gave.
|
||||
public let shippingNote: String?
|
||||
/// When the buyer marked the copy received.
|
||||
public let receivedAt: Date?
|
||||
/// The stars the buyer gave the seller, out of five.
|
||||
public let buyerRating: Int?
|
||||
/// The stars the seller gave the buyer, out of five.
|
||||
public let sellerRating: Int?
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
public init(
|
||||
number: Int,
|
||||
conversationID: UUID,
|
||||
listing: Listing,
|
||||
buyer: User,
|
||||
seller: User,
|
||||
priceCents: Int,
|
||||
askedCents: Int,
|
||||
shippingCents: Int,
|
||||
declared: Declaration,
|
||||
agreedAt: Date,
|
||||
paidAt: Date? = nil,
|
||||
paymentNote: String? = nil,
|
||||
sentAt: Date? = nil,
|
||||
shippingNote: String? = nil,
|
||||
receivedAt: Date? = nil,
|
||||
buyerRating: Int? = nil,
|
||||
sellerRating: Int? = nil
|
||||
) {
|
||||
self.number = number
|
||||
self.conversationID = conversationID
|
||||
self.listing = listing
|
||||
self.buyer = buyer
|
||||
self.seller = seller
|
||||
self.priceCents = priceCents
|
||||
self.askedCents = askedCents
|
||||
self.shippingCents = shippingCents
|
||||
self.declared = declared
|
||||
self.agreedAt = agreedAt
|
||||
self.paidAt = paidAt
|
||||
self.paymentNote = paymentNote
|
||||
self.sentAt = sentAt
|
||||
self.shippingNote = shippingNote
|
||||
self.receivedAt = receivedAt
|
||||
self.buyerRating = buyerRating
|
||||
self.sellerRating = sellerRating
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Structs
|
||||
|
||||
public extension Sale {
|
||||
/// The condition a seller declared, frozen when the sale was agreed: a problem report is only ever for what is not in it.
|
||||
struct Declaration: Sendable, Equatable {
|
||||
/// The grade or condition, e.g. `CGC 9.8`.
|
||||
public let grade: String
|
||||
/// The page quality, or `nil` when not stated.
|
||||
public let pageQuality: String?
|
||||
/// The printing in full.
|
||||
public let printing: String
|
||||
/// The seller's notes on the copy.
|
||||
public let notes: String
|
||||
|
||||
public init(
|
||||
grade: String,
|
||||
pageQuality: String?,
|
||||
printing: String,
|
||||
notes: String
|
||||
) {
|
||||
self.grade = grade
|
||||
self.pageQuality = pageQuality
|
||||
self.printing = printing
|
||||
self.notes = notes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Enumerations
|
||||
|
||||
public extension Sale {
|
||||
/// Something a party does to move the handshake on.
|
||||
enum Action: Sendable, Equatable {
|
||||
/// The buyer says they paid, and how.
|
||||
case markPaid(note: String)
|
||||
/// The seller says the copy is on its way, and how.
|
||||
case markSent(note: String)
|
||||
/// The buyer says the copy arrived.
|
||||
case markReceived
|
||||
/// Either party rates the other, out of five.
|
||||
case rate(stars: Int)
|
||||
}
|
||||
|
||||
/// A party to a sale.
|
||||
enum Role: Sendable {
|
||||
case buyer
|
||||
case seller
|
||||
}
|
||||
|
||||
/// A step of the handshake, in order.
|
||||
enum Step: Int, CaseIterable, Comparable, Sendable {
|
||||
case agreed = 1
|
||||
case paid
|
||||
case sent
|
||||
case received
|
||||
case rated
|
||||
|
||||
public static func < (lhs: Self, rhs: Self) -> Bool {
|
||||
lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Extensions
|
||||
|
||||
public extension Sale {
|
||||
|
||||
// MARK: Computed
|
||||
|
||||
/// The last step taken. Rated counts once both parties have rated.
|
||||
var completed: Step {
|
||||
if buyerRating != nil, sellerRating != nil {
|
||||
return .rated
|
||||
}
|
||||
|
||||
if receivedAt != nil {
|
||||
return .received
|
||||
}
|
||||
|
||||
if sentAt != nil {
|
||||
return .sent
|
||||
}
|
||||
|
||||
return paidAt != nil ? .paid : .agreed
|
||||
}
|
||||
|
||||
/// The step waited on, or `nil` once the handshake is done.
|
||||
var next: Step? {
|
||||
.init(rawValue: completed.rawValue + 1)
|
||||
}
|
||||
|
||||
/// The agreed price and shipping together, in euro cents.
|
||||
var totalCents: Int {
|
||||
priceCents + shippingCents
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
/// Whether a party may take an action now.
|
||||
///
|
||||
/// Each step waits for the one before it and is taken once, by the party it belongs to: the buyer pays and receives, the seller sends.
|
||||
/// Ratings unlock only after the copy is received, so every rating maps to a finished sale.
|
||||
/// - Parameters:
|
||||
/// - action: the action to take.
|
||||
/// - role: the party taking it.
|
||||
func allows(
|
||||
_ action: Action,
|
||||
by role: Role
|
||||
) -> Bool {
|
||||
switch action {
|
||||
case .markPaid: role == .buyer && completed == .agreed
|
||||
case .markSent: role == .seller && completed == .paid
|
||||
case .markReceived: role == .buyer && completed == .sent
|
||||
case .rate(let stars): receivedAt != nil && (1...5).contains(stars) && rating(by: role) == nil
|
||||
}
|
||||
}
|
||||
|
||||
/// The stars a party gave, or `nil` when they have not rated yet.
|
||||
func rating(
|
||||
by role: Role
|
||||
) -> Int? {
|
||||
role == .buyer ? buyerRating : sellerRating
|
||||
}
|
||||
|
||||
/// The party an account is to the sale, or `nil` when it is neither.
|
||||
/// - Parameter userID: the account's identifier.
|
||||
func role(
|
||||
of userID: UUID
|
||||
) -> Role? {
|
||||
if userID == buyer.id {
|
||||
return .buyer
|
||||
}
|
||||
|
||||
return userID == seller.id ? .seller : nil
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user