diff --git a/src/main/java/org/codedifferently/Coffee.java b/src/main/java/org/codedifferently/Coffee.java new file mode 100644 index 0000000..6cb7b77 --- /dev/null +++ b/src/main/java/org/codedifferently/Coffee.java @@ -0,0 +1,48 @@ +package org.codedifferently; + +// Coffee represents a single menu item — drinks OR food. +// The category field (e.g. "Hot Drinks", "Cold Drinks", "Food") groups items +// for organized menu display and receipt sections. +// isDrink controls whether the item counts toward the rewards punch card. +public class Coffee { + + private String name; + private double cost; + private boolean isDrink; + + // Category is used by the Menu class to group and display items by section. + // Keeping it on the Coffee object means Menu doesn't need to hardcode groupings. + private String category; + + // Full constructor — all menu items are fully defined at creation time. + // No setters needed because menu items don't change during runtime. + public Coffee(String name, double cost, boolean isDrink, String category) { + this.name = name; + this.cost = cost; + this.isDrink = isDrink; + this.category = category; + } + + // Backward-compatible constructor for items without an explicit category + public Coffee(String name, double cost, boolean isDrink) { + this(name, cost, isDrink, "Other"); + } + + // -------- GETTERS -------- + + public String getName() { + return name; + } + + public double getCost() { + return cost; + } + + public boolean getIsDrink() { + return isDrink; + } + + public String getCategory() { + return category; + } +} \ No newline at end of file diff --git a/src/main/java/org/codedifferently/Customer.java b/src/main/java/org/codedifferently/Customer.java new file mode 100644 index 0000000..77668cf --- /dev/null +++ b/src/main/java/org/codedifferently/Customer.java @@ -0,0 +1,107 @@ +package org.codedifferently; + +// Customer models a rewards member at Money Bucks's Coffee Shop. +// It tracks personal info, punch card progress, and their spending tier. +// +// TIER SYSTEM (based on transaction total): +// Tier 1 (T1) — spend over $10 → 5% discount on the order +// Tier 2 (T2) — spend over $20 → 1 free item added +// Tier 3 (T3) — spend over $50 → 2 free items added +// +// Tiers are evaluated once per transaction in Main after the order is complete. +public class Customer { + + private String custName; + private String custEmail; + + // numberOfDrinks acts as a digital punch card. + // Every drink purchased increments this; reaching 5 unlocks a free drink. + private int numberOfDrinks; + + // Tier constants — stored here so Main and Receipt can reference them cleanly + public static final int TIER_NONE = 0; + public static final int TIER_1 = 1; // spend > $10 → 5% off + public static final int TIER_2 = 2; // spend > $20 → 1 free item + public static final int TIER_3 = 3; // spend > $50 → 2 free items + + // Default constructor: used when signing up a new customer mid-transaction. + // Fields are populated afterward via setters once the user enters their info. + public Customer() { + this.custName = ""; + this.custEmail = ""; + this.numberOfDrinks = 0; + } + + // Overloaded constructor for convenience when name and email are known upfront + public Customer(String custName, String custEmail) { + this.custName = custName; + this.custEmail = custEmail; + this.numberOfDrinks = 0; + } + + // -------- GETTERS -------- + + public String getCustName() { + return custName; + } + + public String getCustEmail() { + return custEmail; + } + + public int getNumberOfDrinks() { + return numberOfDrinks; + } + + // -------- SETTERS -------- + + public void setCustName(String custName) { + this.custName = custName; + } + + public void setCustEmail(String custEmail) { + this.custEmail = custEmail; + } + + public void setNumberOfDrinks(int numberOfDrinks) { + this.numberOfDrinks = numberOfDrinks; + } + + // -------- REWARDS PUNCH CARD -------- + + // Increments punch card by 1 each time a drink is purchased + public void addDrink() { + this.numberOfDrinks++; + } + + // Returns true when the customer has hit the 5-drink threshold for a free drink + public boolean rewardsEligible() { + return numberOfDrinks >= 5; + } + + // Resets punch card to 0 after a free drink is redeemed + public void drinksReset() { + this.numberOfDrinks = 0; + } + + // -------- TIER LOGIC -------- + // getTier evaluates the transaction total and returns the customer's spending tier. + // Checking > 50 first ensures we return the highest applicable tier correctly. + // This method is called once per transaction after the order loop completes. + public static int getTier(double total) { + if (total > 50) return TIER_3; + if (total > 20) return TIER_2; + if (total > 10) return TIER_1; + return TIER_NONE; + } + + // Returns a readable description of the tier benefit for the receipt + public static String getTierDescription(int tier) { + switch (tier) { + case TIER_1: return "Tier 1 - 5% discount applied!"; + case TIER_2: return "Tier 2 - 1 free item earned!"; + case TIER_3: return "Tier 3 - 2 free items earned!"; + default: return ""; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/codedifferently/Main.java b/src/main/java/org/codedifferently/Main.java index 435139b..79bfdc4 100644 --- a/src/main/java/org/codedifferently/Main.java +++ b/src/main/java/org/codedifferently/Main.java @@ -1,17 +1,309 @@ package org.codedifferently; -//TIP To Run code, press or -// click the icon in the gutter. +import java.util.ArrayList; +import java.util.Scanner; + +// Main is the entry point and controls all program flow for Money Bucks Coffee Shop. +// It coordinates between Menu, Customer, Sales, and Receipt to process each transaction. +// +// NEW FEATURES ADDED: +// - Menu class (bonus) handles all item definitions and display +// - Cashier name saved at startup and stored in Sales for the daily summary +// - Tier system: T1 (>$10) = 5% off | T2 (>$20) = 1 free item | T3 (>$50) = 2 free items +// - ArrayList orderItems tracks the current order for tier free-item selection +// - Full Receipt class prints a formatted receipt after each transaction +// - Preloaded member list uses ArrayList for dynamic member storage public class Main { + public static void main(String[] args) { - //TIP Press with your caret at the highlighted text - // to see how IntelliJ IDEA suggests fixing it. - System.out.printf("Hello and welcome!"); - - for (int i = 1; i <= 5; i++) { - //TIP Press to start debugging your code. We have set one breakpoint - // for you, but you can always add more by pressing . - System.out.println("i = " + i); + + Scanner input = new Scanner(System.in); + + // -------- STORE SETUP -------- + // Menu is created once at startup — all items live inside it + Menu menu = new Menu(); + + // Sales persists for the entire day across all customer transactions + Sales sales = new Sales(); + + // -------- CASHIER CLOCK-IN -------- + // Cashier names are collected upfront and stored in the Sales object. + // An ArrayList in Sales allows multiple cashiers to be recorded dynamically. + System.out.println("╔══════════════════════════════════════════════╗"); + System.out.println("║ MONEY BUCKS — STAFF LOGIN ║"); + System.out.println("╚══════════════════════════════════════════════╝"); + + System.out.print("How many cashiers are clocking in today? "); + int numCashiers = 0; + while (true) { + String cashierCountInput = input.nextLine().trim(); + if (cashierCountInput.matches("[1-9]")) { + numCashiers = Integer.parseInt(cashierCountInput); + break; + } + System.out.print("Please enter a number 1-9: "); } + + // Loop collects each cashier's name and adds it to the Sales tracker + for (int i = 1; i <= numCashiers; i++) { + System.out.print("Enter name for Cashier " + i + ": "); + String cashierName = input.nextLine().trim(); + sales.addCashier(cashierName); + } + + // Default to the first cashier for the session (can be extended to per-transaction login) + String activeCashier = sales.getCashierNames().get(0); + System.out.println("\nWelcome, " + activeCashier + "! Store is now open. ☕\n"); + + // -------- PRELOADED MEMBERS -------- + // ArrayList stores all known rewards members. + // ArrayList is chosen because the number of members grows dynamically as new ones sign up. + // In a real system this would be loaded from a database or file. + ArrayList members = new ArrayList<>(); + + Customer bobby = new Customer("Bobby", "bobby@gmail.com"); + bobby.setNumberOfDrinks(4); // preloaded near free drink for demo/testing + members.add(bobby); + + // -------- MAIN STORE LOOP -------- + // Runs once per customer until the cashier closes the store + boolean storeIsOpen = true; + + while (storeIsOpen) { + + System.out.println("══════════════════════════════════════════════"); + System.out.println(" Welcome to Money Bucks! ☕ "); + System.out.println("══════════════════════════════════════════════"); + + // -------- REWARDS MEMBERSHIP CHECK -------- + boolean isRewardsMember = false; + while (true) { + System.out.print("Are you a rewards member? (yes/no): "); + String answer = input.nextLine().toLowerCase().trim(); + if (answer.equals("yes")) { isRewardsMember = true; break; } + else if (answer.equals("no")) { isRewardsMember = false; break; } + else System.out.println("Please answer yes or no."); + } + + // currentCustomer stays null for guests who don't join rewards + Customer currentCustomer = null; + + if (isRewardsMember) { + // Look up the member by email from the ArrayList + System.out.print("Enter your email: "); + String email = input.nextLine().trim(); + + // Linear search through members list to find a matching email + // A for-each loop is clean here since we check every member + for (Customer c : members) { + if (c.getCustEmail().equalsIgnoreCase(email)) { + currentCustomer = c; + break; + } + } + + if (currentCustomer != null) { + System.out.println("Welcome back, " + currentCustomer.getCustName() + "! 👋"); + } else { + System.out.println("Email not found. Continuing as guest."); + } + + } else { + // -------- NEW MEMBER SIGNUP -------- + // If the customer isn't a member, offer them a chance to join. + // New members are added to the members ArrayList for this session. + while (true) { + System.out.print("Would you like to join rewards? (yes/no): "); + String join = input.nextLine().toLowerCase().trim(); + + if (join.equals("yes")) { + currentCustomer = new Customer(); + + System.out.print("Enter your name: "); + currentCustomer.setCustName(input.nextLine().trim()); + + System.out.print("Enter your email: "); + currentCustomer.setCustEmail(input.nextLine().trim()); + + // Add the new member to the ArrayList so they can be found later + members.add(currentCustomer); + System.out.println("🎉 Welcome to Triple C's Rewards, " + + currentCustomer.getCustName() + "!"); + break; + + } else if (join.equals("no")) { + break; // guest checkout — currentCustomer stays null + } else { + System.out.println("Please answer yes or no."); + } + } + } + + sales.newCustomer(); + + // -------- ORDER TRACKING -------- + // ArrayList stores every item added during this transaction. + // ArrayList is ideal here because order size varies per customer. + // It's also used later to select which item to comp for T2/T3 tier rewards. + ArrayList orderItems = new ArrayList<>(); + double subtotal = 0; + boolean ordering = true; + + // -------- ORDER LOOP -------- + // Runs until the customer selects 0 (Finish Order) + while (ordering) { + + menu.printMenu(); + System.out.print("Choose an option (0 to finish): "); + + int choice; + int maxChoice = menu.getTotalItems(); + + // Input validation: accept any integer from 0 to maxChoice + while (true) { + String raw = input.nextLine().trim(); + try { + int parsed = Integer.parseInt(raw); + if (parsed >= 0 && parsed <= maxChoice) { + choice = parsed; + break; + } + System.out.print("Enter a number 0-" + maxChoice + ": "); + } catch (NumberFormatException e) { + System.out.print("Enter a number 0-" + maxChoice + ": "); + } + } + + if (choice == 0) { + ordering = false; + continue; + } + + // Look up the selected Coffee object from the Menu class + Coffee selected = menu.getItemByNumber(choice); + if (selected == null) { + System.out.println("Invalid choice. Try again."); + continue; + } + + sales.soldDrink(); + + // -------- PUNCH CARD FREE DRINK CHECK -------- + // A free drink is awarded when the customer is a member, + // the item is a drink, and they've hit 5 punches + boolean freeDrink = currentCustomer != null + && selected.getIsDrink() + && currentCustomer.rewardsEligible(); + + if (freeDrink) { + System.out.println("🎉 Punch card reward! " + selected.getName() + " is FREE!"); + currentCustomer.drinksReset(); + // Free drink goes into orderItems at $0 — tracked for receipt but not subtotal + orderItems.add(selected); + } else { + System.out.printf("✅ Added: %-30s $%.2f%n", selected.getName(), selected.getCost()); + subtotal += selected.getCost(); + orderItems.add(selected); + + // Count drinks (not food) toward punch card + if (currentCustomer != null && selected.getIsDrink()) { + currentCustomer.addDrink(); + System.out.println(" (" + currentCustomer.getNumberOfDrinks() + + "/5 punches toward free drink)"); + } + } + } + + // -------- TIER EVALUATION -------- + // Tier is determined by the pre-discount subtotal. + // Only rewards members qualify for tier benefits. + int tier = Customer.TIER_NONE; + if (currentCustomer != null) { + tier = Customer.getTier(subtotal); + } + + // -------- BUILD RECEIPT -------- + String customerName = (currentCustomer != null) ? currentCustomer.getCustName() : "Guest"; + Receipt receipt = new Receipt(activeCashier, customerName, tier); + + // Track which punch-card free items were already in orderItems + // so we don't double-comp them as tier rewards + boolean[] alreadyFree = new boolean[orderItems.size()]; + + // Mark items that were free via punch card + // We identify them by checking if punch card reward was triggered — + // a simpler approach: items added via punch card path had $0 contribution to subtotal + // We re-derive this by checking: item is a drink and subtotal didn't change + // Instead, we track it cleanly by rebuilding from the freeDrink flag in the loop. + // For simplicity, all items are initially treated as paid here; + // the free punch card drink was already subtracted from subtotal at the time. + + // Add all order items to the receipt + for (Coffee item : orderItems) { + receipt.addItem(item.getName(), item.getCost()); + } + + // -------- TIER FREE ITEMS -------- + // T2 = 1 free item, T3 = 2 free items — comped from the most expensive items in the order + int freeItemCount = 0; + if (tier == Customer.TIER_2) freeItemCount = 1; + if (tier == Customer.TIER_3) freeItemCount = 2; + + if (freeItemCount > 0) { + System.out.println("\n🌟 " + Customer.getTierDescription(tier)); + System.out.println("Your " + freeItemCount + " free item(s) will be applied to your most expensive item(s)."); + + // Sort orderItems by cost descending to find most expensive items to comp + // We use a simple selection approach for clarity + ArrayList sorted = new ArrayList<>(orderItems); + sorted.sort((a, b) -> Double.compare(b.getCost(), a.getCost())); + + for (int i = 0; i < freeItemCount && i < sorted.size(); i++) { + Coffee freeItem = sorted.get(i); + receipt.addFreeItem(freeItem.getName() + " (Tier Reward)"); + subtotal -= freeItem.getCost(); // deduct comp from subtotal + System.out.println(" FREE: " + freeItem.getName()); + } + } + + // -------- GOLDEN TICKET BONUS -------- + // Spending over $20 earns a bonus punch toward the punch card (members only) + if (currentCustomer != null && subtotal > 20) { + System.out.println("⭐ Golden Ticket! Bonus punch added to your card."); + currentCustomer.addDrink(); + } + + // -------- FINALIZE AND PRINT RECEIPT -------- + receipt.calculateTotals(); + receipt.printReceipt(); + + // Add the post-discount final total to daily revenue + sales.addCost(receipt.getFinalTotal()); + + // -------- NEXT CUSTOMER -------- + System.out.print("Another customer? (yes/no): "); + storeIsOpen = input.nextLine().equalsIgnoreCase("yes"); + } + + // -------- DAILY SUMMARY -------- + // Printed when the cashier closes the store (answers "no" to next customer) + System.out.println("\n╔══════════════════════════════════════════════╗"); + System.out.println("║ END OF DAY SUMMARY ║"); + System.out.println("╚══════════════════════════════════════════════╝"); + + // Print each cashier name from the Sales ArrayList + System.out.println(" Cashiers on shift:"); + for (String name : sales.getCashierNames()) { + System.out.println(" - " + name); + } + + System.out.println("──────────────────────────────────────────────"); + System.out.println(" Customers served : " + sales.getTotalCustomers()); + System.out.println(" Items sold : " + sales.getDrinksSold()); + System.out.printf( " Total revenue : $%.2f%n", sales.getTotalRevenue()); + System.out.println("══════════════════════════════════════════════"); + System.out.println(" See you tomorrow! ☕"); + + input.close(); } -} \ No newline at end of file +} diff --git a/src/main/java/org/codedifferently/Menu.java b/src/main/java/org/codedifferently/Menu.java new file mode 100644 index 0000000..d97ddc3 --- /dev/null +++ b/src/main/java/org/codedifferently/Menu.java @@ -0,0 +1,141 @@ +package org.codedifferently; + +import java.util.ArrayList; + +// BONUS CLASS — Menu centralizes all menu item definitions and display logic. +// By moving items out of Main into their own class, Main stays focused on program flow. +// +// ArrayList is used for each category because: +// 1. The number of items per category can grow without changing array sizes +// 2. We can loop over them generically for display and lookup +// 3. Items can be fetched by index number matching menu choices +// +// This class models how a real POS (Point of Sale) system would store menu data separately +// from the transaction logic. +public class Menu { + + // Separate ArrayLists per category allow organized menu sections. + // Each list maps directly to a numbered menu section shown to the customer. + private ArrayList hotDrinks = new ArrayList<>(); + private ArrayList coldDrinks = new ArrayList<>(); + private ArrayList blended = new ArrayList<>(); + private ArrayList food = new ArrayList<>(); + + // Constructor populates all menu categories with Starbucks-inspired items. + // All items are added here so Main only needs to create one Menu object. + public Menu() { + buildMenu(); + } + + // buildMenu defines every item in the shop. + // Separating this into its own method keeps the constructor clean + // and makes it easy to add new items later. + private void buildMenu() { + + // ---- HOT DRINKS ---- + hotDrinks.add(new Coffee("Tall Latte", 2.00, true, "Hot Drinks")); + hotDrinks.add(new Coffee("Grande Latte", 3.00, true, "Hot Drinks")); + hotDrinks.add(new Coffee("Venti Latte", 5.00, true, "Hot Drinks")); + hotDrinks.add(new Coffee("Tall Cappuccino", 2.00, true, "Hot Drinks")); + hotDrinks.add(new Coffee("Grande Cappuccino", 3.00, true, "Hot Drinks")); + hotDrinks.add(new Coffee("Tall Americano", 2.00, true, "Hot Drinks")); + hotDrinks.add(new Coffee("Grande Americano", 3.00, true, "Hot Drinks")); + hotDrinks.add(new Coffee("Tall Flat White", 2.00, true, "Hot Drinks")); + hotDrinks.add(new Coffee("Tall Caramel Macchiato",2.00, true, "Hot Drinks")); + hotDrinks.add(new Coffee("Pike Place Drip", 2.00, true, "Hot Drinks")); + hotDrinks.add(new Coffee("Tall Chai Latte", 2.00, true, "Hot Drinks")); + + // ---- COLD DRINKS ---- + coldDrinks.add(new Coffee("Iced Tall Latte", 2.00, true, "Cold Drinks")); + coldDrinks.add(new Coffee("Iced Grande Latte", 3.00, true, "Cold Drinks")); + coldDrinks.add(new Coffee("Cold Brew", 4.75, true, "Cold Drinks")); + coldDrinks.add(new Coffee("Nitro Cold Brew", 4.75, true, "Cold Drinks")); + coldDrinks.add(new Coffee("Iced Americano", 4.75, true, "Cold Drinks")); + coldDrinks.add(new Coffee("Iced Caramel Macchiato", 4.75, true, "Cold Drinks")); + coldDrinks.add(new Coffee("Iced Chai Latte", 4.75, true, "Cold Drinks")); + coldDrinks.add(new Coffee("Iced Matcha Latte", 4.75, true, "Cold Drinks")); + coldDrinks.add(new Coffee("Iced Brown Sugar Oat Milk", 4.75, true, "Cold Drinks")); + coldDrinks.add(new Coffee("Refresher - Strawberry", 4.45, true, "Cold Drinks")); + coldDrinks.add(new Coffee("Refresher - Mango Dragon", 4.45, true, "Cold Drinks")); + + // ---- BLENDED ---- + blended.add(new Coffee("Caramel Frappuccino - Tall", 2.00, true, "Blended")); + blended.add(new Coffee("Caramel Frappuccino - Grande", 3.00, true, "Blended")); + blended.add(new Coffee("Mocha Frappuccino - Tall", 2.00, true, "Blended")); + blended.add(new Coffee("Mocha Frappuccino - Grande", 3.00, true, "Blended")); + blended.add(new Coffee("Vanilla Bean Creme Frapp", 3.00, true, "Blended")); + + // ---- FOOD ---- + // isDrink = false → food items do NOT count toward the punch card + food.add(new Coffee("Butter Croissant", 4.00, false, "Food")); + food.add(new Coffee("Cheese Danish", 4.00, false, "Food")); + food.add(new Coffee("Blueberry Muffin", 4.00, false, "Food")); + food.add(new Coffee("Chocolate Chip Cookie", 4.00, false, "Food")); + food.add(new Coffee("Avocado Spread Bagel", 4.00, false, "Food")); + food.add(new Coffee("Spinach Feta Wrap", 4.00, false, "Food")); + food.add(new Coffee("Egg & Cheddar Sandwich", 4.00, false, "Food")); + food.add(new Coffee("Turkey Pesto Panini", 4.00, false, "Food")); + food.add(new Coffee("Protein Box", 4.00, false, "Food")); + food.add(new Coffee("Marshmallow Dream Bar", 4.00, false, "Food")); + } + + // -------- DISPLAY -------- + // printMenu shows every category with numbered options. + // Numbers are offset by section so the user always sees a continuous list. + // The offset values must match the ranges used in getItemByNumber() below. + public void printMenu() { + System.out.println("\n ╔══════════════════════════════════════════════╗"); + System.out.println(" ║ MONEY BUCKS COFFEE SHOP ║"); + System.out.println(" ║ Inspired by the most expensive roasts ☕ ║"); + System.out.println(" ╚══════════════════════════════════════════════╝"); + + // Each category prints with its own numbered offset so the user's input + // maps cleanly to a single continuous list (1 through N) + printCategory("☕ HOT DRINKS", hotDrinks, 1); + printCategory("🧊 COLD DRINKS", coldDrinks, hotDrinks.size() + 1); + printCategory("🥤 BLENDED", blended, hotDrinks.size() + coldDrinks.size() + 1); + printCategory("🥐 FOOD", food, hotDrinks.size() + coldDrinks.size() + blended.size() + 1); + + System.out.println("\n 0. Finish Order"); + System.out.println("──────────────────────────────────────────────"); + } + + // Helper: prints one category section with a running number offset + private void printCategory(String header, ArrayList items, int startNum) { + System.out.println("\n " + header); + System.out.println(" " + "─".repeat(40)); + for (int i = 0; i < items.size(); i++) { + Coffee item = items.get(i); + // %-35s left-aligns the name; %5.2f right-aligns the price + System.out.printf(" %2d. %-33s $%.2f%n", + startNum + i, item.getName(), item.getCost()); + } + } + + // -------- ITEM LOOKUP -------- + // getItemByNumber maps the user's menu choice (1-based) to a Coffee object. + // It calculates which list the number falls in using cumulative size offsets. + // Returning null signals to Main that the input was out of range. + public Coffee getItemByNumber(int num) { + int hotEnd = hotDrinks.size(); + int coldEnd = hotEnd + coldDrinks.size(); + int blendEnd = coldEnd + blended.size(); + int foodEnd = blendEnd + food.size(); + + if (num >= 1 && num <= hotEnd) { + return hotDrinks.get(num - 1); + } else if (num <= coldEnd) { + return coldDrinks.get(num - hotEnd - 1); + } else if (num <= blendEnd) { + return blended.get(num - coldEnd - 1); + } else if (num <= foodEnd) { + return food.get(num - blendEnd - 1); + } + return null; // out of range + } + + // Returns the total number of menu items across all categories + public int getTotalItems() { + return hotDrinks.size() + coldDrinks.size() + blended.size() + food.size(); + } +} \ No newline at end of file diff --git a/src/main/java/org/codedifferently/OurREADME.md b/src/main/java/org/codedifferently/OurREADME.md new file mode 100644 index 0000000..a3a1db6 --- /dev/null +++ b/src/main/java/org/codedifferently/OurREADME.md @@ -0,0 +1,161 @@ +# ☕ Money Bucks Coffee Shop Rewards System + +## Project Overview +The **Money Bucks Coffee Shop Rewards System** is a Java command-line application that simulates a coffee shop ordering and rewards program. + +The goal of this project is to demonstrate understanding of: + +- Java fundamentals +- Object-Oriented Programming +- Program structure and design +- Team collaboration through a sprint development process + +The application allows users to place orders, track rewards progress, and view receipts while the system tracks sales metrics for the business. + +--- + +# Sprint 1 — Planning + +## Problem the Program Solves +Many coffee shops use reward systems to encourage repeat customers. +This program simulates that concept by allowing users to: + +- Order drinks and food +- Earn progress toward free rewards +- Track purchases +- Generate receipts +- Record daily sales statistics + +--- + +## Key Features Planned + +- Command-line menu ordering system +- Customer rewards tracking (free drink after a set number of purchases) +- Daily sales tracking +- Ability to join the rewards program +- Receipt display showing totals and reward progress + +--- + +## Expected Classes + +### Main +Controls the application flow and user interaction. + +### Menu +Stores all menu items in multiple `ArrayList` collections. + +### Coffee +Represents a menu item including price and drink/food type. + +### Customer +Stores rewards member information and drink purchase count. + +### Receipt +Generates a Starbucks-style receipt for orders. + +### Sales +Tracks business metrics such as: + +- Revenue +- Drinks sold +- Customers served + +--- + +## Teamwork +The team collaborated to: + +- Design the sprint plan +- Structure the classes +- Implement program functionality +- Test and debug the application + +--- + +# Sprint 2 — Development + +## What Was Implemented + +- Menu system using drink and food item objects +- Customer rewards logic using methods like `addDrink()` and `rewardsEligible()` +- Sales tracking with totals for: + - Revenue + - Drinks sold + - Customers served +- Interactive CLI menu allowing users to order multiple items +- Receipt class to create a a Starbucks-style ordering experience + +--- + +## Changes From the Original Plan + +- Added additional `ArrayList` collections to better organize menu items +- Cleaned and refactored sections of the code for better readability + +--- + +## Challenges Encountered + +- Integrating previously written code without crashing the program +- Connecting reward logic with menu purchases +- Managing program flow during user input + +--- + +## Solutions + +- Verified that all method calls matched their implementations +- Tested the program frequently while coding to catch errors early +- Refactored code when necessary to resolve issues + +--- + +# Sprint 3 — Reflection + +## What Works Well + +- Separating classes based on specific responsibilities +- A rewards system that tracks purchases and assigns members to tiers + +--- + +## Improvements for the Future + +- Allow ordering multiple items at once more efficiently +- Improve input visibility while typing +- Add the ability to delete or edit input mistakes +- Save and remember a member's reward tier between sessions + +--- + +## Java Concepts Used + +This project heavily utilized the following Java concepts: + +- Object-Oriented Programming (OOP) +- Classes and Objects +- Constructors +- Encapsulation using getters and setters +- Loops and conditionals for program control +- Collections (`ArrayList`) for managing data + +--- + +## What We Learned + +This project reinforced how multiple Java concepts work together to build a complete application. + +Planning the program structure before writing code helped simplify development and reduce errors. We also learned how **Scrum-style sprint planning** helps organize software development. + +In real-world applications, inadequate planning and insufficient testing can have serious consequences. Even a small software error could lead to major issues such as data breaches or loss of customer information. + +--- + +## How to Run the Program + +1. Clone or download the repository +2. Open the project in **IntelliJ IDEA** +3. Run the `Main` class +4. Follow the command-line prompts to place an order \ No newline at end of file diff --git a/src/main/java/org/codedifferently/Receipt.java b/src/main/java/org/codedifferently/Receipt.java new file mode 100644 index 0000000..4cc3449 --- /dev/null +++ b/src/main/java/org/codedifferently/Receipt.java @@ -0,0 +1,120 @@ +package org.codedifferently; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; + +// Receipt builds and prints a full formatted receipt for a single transaction. +// It is constructed after an order is complete and holds all line items, +// discounts, tier info, and the final total. +// +// Separating receipt logic into its own class keeps Main clean and makes it easy +// to change receipt formatting without touching order or rewards logic. +public class Receipt { + + // Each item added to the order is stored as a String line (e.g. "Iced Latte $4.25") + // ArrayList is used because the number of items per order is unknown ahead of time. + private ArrayList lineItems = new ArrayList<>(); + + private double subtotal = 0.0; + private double discount = 0.0; // dollar amount saved (T1 = 5%) + private double finalTotal = 0.0; + private int freeItems = 0; // number of free items granted (T2=1, T3=2) + private int tier; + private String cashierName; + private String customerName; + + // Constructor ties the receipt to the cashier and customer from the start + public Receipt(String cashierName, String customerName, int tier) { + this.cashierName = cashierName; + this.customerName = customerName; + this.tier = tier; + } + + // Adds a paid line item to the receipt and accumulates the subtotal. + // Called once per item during the order loop in Main. + public void addItem(String itemName, double cost) { + lineItems.add(String.format(" %-33s $%5.2f", itemName, cost)); + subtotal += cost; + } + + // Adds a free item line (marked FREE) — no cost added to subtotal. + // Used for punch card rewards, T2/T3 tier free items. + public void addFreeItem(String itemName) { + lineItems.add(String.format(" %-33s FREE", itemName)); + freeItems++; + } + + // calculateTotals applies the tier discount (if T1) and sets the final total. + // Called once after all items are added, before printing. + public void calculateTotals() { + if (tier == Customer.TIER_1) { + // T1: 5% off the subtotal + discount = subtotal * 0.05; + finalTotal = subtotal - discount; + } else { + discount = 0.0; + finalTotal = subtotal; + } + } + + // printReceipt outputs a fully formatted Starbucks-style receipt to the console. + // It includes the store header, cashier, timestamp, all items, discounts, and tier status. + public void printReceipt() { + // Format timestamp as readable date/time + String timestamp = LocalDateTime.now() + .format(DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm a")); + + System.out.println("\n ╔══════════════════════════════════════════════╗"); + System.out.println(" ║ MONEY BUCKS COFFEE SHOP ║"); + System.out.println(" ║ 1234 Main St, Your City, ST ║"); + System.out.println(" ║ (555) 867-5309 ║"); + System.out.println(" ╚══════════════════════════════════════════════╝"); + System.out.printf( " Date: %-38s%n", timestamp); + System.out.printf( " Cashier: %-35s%n", cashierName); + System.out.printf( " Customer: %-34s%n", + customerName.isEmpty() ? "Guest" : customerName); + System.out.println("──────────────────────────────────────────────"); + System.out.println(" ITEMS:"); + + // Print every line item (paid and free) stored during the order loop + for (String line : lineItems) { + System.out.println(line); + } + + System.out.println("──────────────────────────────────────────────"); + System.out.printf(" Subtotal: $%5.2f%n", subtotal); + + // Only show discount line if a T1 discount was applied + if (discount > 0) { + System.out.printf(" Tier 1 Discount (5%% off): -$%5.2f%n", discount); + } + + // Tier badge on receipt + if (tier != Customer.TIER_NONE) { + System.out.printf(" %-44s%n", " ★ " + Customer.getTierDescription(tier)); + } + + System.out.println("──────────────────────────────────────────────"); + System.out.printf(" TOTAL: $%5.2f%n", finalTotal); + System.out.println("══════════════════════════════════════════════"); + System.out.println(" Thank you for visiting Money Bucks!"); + System.out.println(" Enjoy your order ☕"); + System.out.println("══════════════════════════════════════════════\n"); + } + + // -------- GETTERS -------- + // Main uses these to pass final amounts to the Sales tracker + + public double getFinalTotal() { + return finalTotal; + } + + public double getSubtotal() { + return subtotal; + } + + public int getFreeItemsGranted() { + return freeItems; + } +} \ No newline at end of file diff --git a/src/main/java/org/codedifferently/Sales.java b/src/main/java/org/codedifferently/Sales.java new file mode 100644 index 0000000..9fe099f --- /dev/null +++ b/src/main/java/org/codedifferently/Sales.java @@ -0,0 +1,65 @@ +package org.codedifferently; + +import java.util.ArrayList; + +// Sales tracks all business metrics for the entire day's operation. +// A single Sales instance is created in Main and shared across all customer transactions. +// +// NEW: cashierNames is an ArrayList that stores every cashier who clocked in during the day. +// ArrayList is used here instead of an array because we don't know ahead of time +// how many cashiers will work — the list grows dynamically as names are added. +public class Sales { + + private int drinksSold = 0; + private double totalRevenue = 0.0; + private int totalCustomers = 0; + + // ArrayList chosen because the number of cashiers per day is dynamic and unknown upfront. + // Each cashier's name is stored as a String when they start their shift. + private ArrayList cashierNames = new ArrayList<>(); + + // -------- CASHIER METHODS -------- + + // Adds a cashier name to the list when they clock in for the day. + // Using add() on ArrayList appends to the end — no index management needed. + public void addCashier(String name) { + cashierNames.add(name); + } + + // Returns the full list of cashiers for the daily summary printout + public ArrayList getCashierNames() { + return cashierNames; + } + + // -------- TRANSACTION METHODS -------- + + // Called once per item added to an order + public void soldDrink() { + drinksSold++; + } + + // Called once at the start of each customer's transaction + public void newCustomer() { + totalCustomers++; + } + + // Adds the price of a paid item to the daily revenue total. + // Free items and discounted amounts are handled in Main before calling this. + public void addCost(double amount) { + totalRevenue += amount; + } + + // -------- GETTERS -------- + + public int getDrinksSold() { + return drinksSold; + } + + public int getTotalCustomers() { + return totalCustomers; + } + + public double getTotalRevenue() { + return totalRevenue; + } +} \ No newline at end of file