Matua Doc

Matua Doc

OOP Challenge

Learning intentions

In this lesson, you will build a small sales workflow for a video rental store using objects and collection functions.

  • Use map to transform rental data into receipt data.
  • Use filter to select only overdue cases for billing.
  • Use reduce to total overdue fee income.
  • Build your own object that conforms to CustomStringConvertible, Equatable, and Comparable.
  • Prepare your objects for storage by conforming to Hashable and Codable.

Scenario

You are helping a local video rental store prepare end-of-week records.

  • Each rental has a customer, a video, and return status.
  • The store charges a flat $2.00 overdue fee when a rental is not returned on time.
  • Your goal is to generate receipts, find overdue customers, total overdue income, and save billing records.

Use this code as your starting point for all five tasks.

import Foundation

struct Video: Identifiable {
    let id: UUID
    let title: String
    let dailyRate: Double
}

struct Customer: Identifiable {
    let id: UUID
    let name: String
    let address: String
}

struct VideoRental {
    let videoID: Video.ID
    let customerID: Customer.ID
    let dayIssued: Int
    let dayToReturn: Int
    let wasReturned: Bool
}

struct Receipt {
    let videoID: Video.ID
    let customerID: Customer.ID
    let pricePaid: Double
    let overdueFeeCharged: Bool
}

let videos: [Video] = [
    Video(id: UUID(), title: "The Matrix", dailyRate: 4.50),
    Video(id: UUID(), title: "Toy Story", dailyRate: 3.00),
    Video(id: UUID(), title: "Spirited Away", dailyRate: 4.00),
    Video(id: UUID(), title: "Interstellar", dailyRate: 5.00),
    Video(id: UUID(), title: "Moana", dailyRate: 3.50)
]

let customers: [Customer] = [
    Customer(id: UUID(), name: "Aroha Ngata", address: "14 Kowhai Street"),
    Customer(id: UUID(), name: "Liam Patel", address: "8 Tui Avenue"),
    Customer(id: UUID(), name: "Mia Thompson", address: "22 Rimu Road"),
    Customer(id: UUID(), name: "Noah Wiremu", address: "3 Pukeko Lane"),
    Customer(id: UUID(), name: "Eva Chen", address: "11 Nikau Place")
]

let rentals: [VideoRental] = [
    VideoRental(videoID: videos[0].id,
                customerID: customers[0].id,
                dayIssued: 1, dayToReturn: 3,
                wasReturned: true),
    VideoRental(videoID: videos[1].id,
                customerID: customers[1].id,
                dayIssued: 2, dayToReturn: 4,
                wasReturned: false),
    VideoRental(videoID: videos[2].id,
                customerID: customers[2].id,
                dayIssued: 2, dayToReturn: 5,
                wasReturned: true),
    VideoRental(videoID: videos[3].id,
                customerID: customers[3].id,
                dayIssued: 3, dayToReturn: 6,
                wasReturned: false),
    VideoRental(videoID: videos[4].id,
                customerID: customers[4].id,
                dayIssued: 4, dayToReturn: 6,
                wasReturned: true)
]

Task A

Complete the following:

  1. Use map to build a [Receipt] from rentals.

  2. Use a for loop to print each receipt in this format: Receipt | Customer: <name> | Video: <title> | Base: $<price> | Overdue: <Yes/No>

  3. Format money to 2 decimal places.

Task B

Complete the following:

  1. Use filter to keep only receipts where overdueFeeCharged == true.

  2. Use a loop to print a mailing list in this format: Send overdue notice to: <customer name>, <address>

  3. Use the customers data (via customerID) to find addresses.

Task C

Complete the following:

  1. Use reduce on the overdueReceipts array.
  2. Add $2.00 for each overdue receipt.
  3. Print the final total in this format: Total overdue fees collected: $<amount>

Task D

Complete the following:

  1. Create CustomerBill with the required fields and protocol conformances.
    • Properties: customer: Customer, receipt: Receipt — it accepts the actual objects as arguments, not just the ID
    • Protocol conformance: CustomStringConvertible, Equatable, Sortable
  2. Use map to convert overdue receipts into [CustomerBill].
  3. Sort the bills array from highest fee to lowest using .sorted().reversed().
  4. Print only the first bill to verify your description output.

Use this letter format for description:

Kia ora <customer name>,

Our records show that "<video title>" was overdue.
Base rental paid: $<base price>
Overdue fee now due: $<fee amount>

Please pay this amount at your earliest convenience.
Store Billing Team

Task E

Complete the following:

  1. Add Hashable, Codable conformance to all five model types.
  2. Encode [CustomerBill] using JSONEncoder.
  3. Save to customer_bills.json.
  4. Read the file back and print it to confirm the JSON contains all bill data.

Use this code to store your data to a JSON file.

let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]

do {
    let data = try encoder.encode(bills)
    let url = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
        .appendingPathComponent("customer_bills.json")

    try data.write(to: url)
    print("Saved JSON to: \(url.path)")

    let readBack = try String(contentsOf: url, encoding: .utf8)
    print(readBack)
} catch {
    print("JSON save/load failed: \(error)")
}

Extension challenge for Super Players!

  1. Add one more field to Receipt called daysOverdue and change the fee rule from flat $2.00 to $2.00 per day overdue.
  2. Update your map, filter, reduce, and CustomerBill logic to use the new fee rule.