0000000000155// legacy.js — Byte Burrito, 2019–present. Please don't add anything here.
154// (Everyone has added something here.)
153
152const TAX_RATE = 0.0875;
151const MAX_DISTANCE_KM = 8;
150const OPEN_HOUR = 11;
149const CLOSE_HOUR = 23;
148const RUSH_START = 12;
147const RUSH_END = 14;
146
145function isOpen(now) {
144 const hour = now.getHours();
143 return hour >= OPEN_HOUR && hour < CLOSE_HOUR;
142}
141
140function isLunchRush(now) {
139 const hour = now.getHours();
138 return hour >= RUSH_START && hour < RUSH_END;
137}
136
135function formatPrice(cents) {
134 const dollars = Math.floor(cents / 100);
133 const rest = String(cents % 100).padStart(2, "0");
132 return "$" + dollars + "." + rest;
131}
130
129function subtotal(items) {
128 let sum = 0;
127 for (const item of items) {
126 sum += item.price * item.qty;
125 }
124 return sum;
123}
122
121function taxFor(amount) {
120 return Math.round(amount * TAX_RATE);
119}
118
117function totalFor(order) {
116 const base = subtotal(order.items);
115 const tax = taxFor(base);
114 const tip = order.tip || 0;
113 return base + tax + tip;
112}
111
110function validateOrder(order) {
109 if (!order.items || order.items.length === 0) return "empty";
108 if (order.items.some((i) => i.qty <= 0)) return "bad quantity";
107 if (!order.address) return "no address";
106 if (!isOpen(new Date())) return "closed";
105 return null;
104}
103
102function applyCoupon(order, code) {
101 const coupon = COUPONS[code];
100 if (!coupon) return order;
99 if (coupon.expires < Date.now()) return order;
98 const off = Math.round(subtotal(order.items) * coupon.percent);
97 return { ...order, discount: off };
96}
95
94function studentDiscount(order, user) {
93 if (!user.isStudent) return 0;
92 return Math.round(subtotal(order.items) * 0.1);
91}
90
89function distanceKm(a, b) {
88 const dLat = (b.lat - a.lat) * 111;
87 const dLng = (b.lng - a.lng) * 111 * Math.cos((a.lat * Math.PI) / 180);
86 return Math.sqrt(dLat * dLat + dLng * dLng);
85}
84
83function nearestRider(riders, address) {
82 let best = null;
81 for (const rider of riders) {
80 if (!rider.online) continue;
79 const d = distanceKm(rider.position, address.position);
78 if (d > MAX_DISTANCE_KM) continue;
77 if (!best || d < best.distance) best = { rider, distance: d };
76 }
75 return best;
74}
73
72function assignRider(order, riders) {
71 const match = nearestRider(riders, order.address);
70 if (!match) return refund(order, "No rider nearby, sorry!");
69 order.riderId = match.rider.id;
68 order.eta = Math.ceil(match.distance * 4) + 10;
67 notify(match.rider.phone, "New order: " + order.id);
66 return order;
65}
64
63function etaMinutes(order, now) {
62 const rush = isLunchRush(now) ? 10 : 0;
61 return order.eta + rush;
60}
59
58function refund(order, reason) {
57 order.status = "refunded";
56 order.refundReason = reason;
55 payments.refund(order.paymentId, totalFor(order));
54 notify(order.customerPhone, "Refunded: " + reason);
53 return order;
52}
51
50function markDelivered(order, now) {
49 order.status = "delivered";
48 order.deliveredAt = now.toISOString();
47 const late = now - new Date(order.placedAt) > order.eta * 60000;
46 if (late) order.lateBy = Math.round((now - new Date(order.placedAt)) / 60000 - order.eta);
45 return order;
44}
43
42function kitchenTicket(order) {
41 const lines = order.items.map((i) => i.qty + " x " + i.name);
40 lines.unshift("ORDER " + order.id);
39 if (order.notes) lines.push("NOTE: " + order.notes);
38 return lines.join("\n");
37}
36
35function printTicket(order) {
34 const ticket = kitchenTicket(order);
33 printer.print(ticket);
32 log("printed", order.id);
31}
30
29function emailReceipt(order, user) {
28 const body = renderReceipt(order);
27 mailer.send(user.email, "Your Byte Burrito receipt", body);
26}
25
24function menuItem(id) {
23 const item = MENU.find((m) => m.id === id);
22 if (!item) throw new Error("not on the menu: " + id);
21 return item;
20}
19
18function isSpicy(id) {
17 return menuItem(id).spicy === true;
16}
15
14function happyHourPrice(item, now) {
13 const hour = now.getHours();
12 if (hour >= 17 && hour < 19) return Math.round(item.price * 0.8);
11 return item.price;
10}
9
8function priceList(now) {
7 return MENU.map((item) => ({
6 id: item.id,
5 name: item.name,
4 price: formatPrice(happyHourPrice(item, now)),
3 }));
2}
1
156function placeOrder(cart, user, now) {
1 const order = { id: newId(), items: cart.items, address: user.address, placedAt: now.toISOString() };
2 const fee = deliveryFee(order, distanceKm(KITCHEN, order.address));
3 const problem = validateOrder(order);
4 if (problem) throw new Error(problem);
5 order.discount = studentDiscount(order, user);
6 order.total = totalFor(order) - order.discount + fee;
7 return order;
8}
9
10function deliveryFee(order, km) {
11 const base = km <= 2 ? 0 : 1.5;
12 const perKm = km > 2 ? (km - 2) * 0.4 : 0;
13 const rush = isLunchRush(new Date()) ? 1 : 0;
14 return Math.round((base + perKm + rush) * 100) / 100;
15}
16
17function newId() {
18 return "BB-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 6);
19}
20
21function log(event, ...details) {
22 console.log("[" + new Date().toISOString() + "] " + event, ...details);
23}
24
25function notify(phone, text) {
26 if (!phone) return;
27 sms.send(phone, text.slice(0, 160));
28}
29
30function saveUser(user) {
31 db.users.put(user.id, user);
32}
33
34function loadUser(id) {
35 return db.users.get(id);
36}
37
38function saveOrder(order) {
39 db.orders.put(order.id, order);
40 log("saved", order.id);
41}
42
43function loadOrder(id) {
44 return db.orders.get(id);
45}
46
47function ordersToday(now) {
48 const day = now.toISOString().slice(0, 10);
49 return db.orders.all().filter((o) => o.placedAt.startsWith(day));
50}
51
52function revenueToday(now) {
53 return ordersToday(now).reduce((sum, o) => sum + o.total, 0);
54}
55
56function busiestHour(now) {
57 const counts = new Array(24).fill(0);
58 for (const o of ordersToday(now)) counts[new Date(o.placedAt).getHours()]++;
59 return counts.indexOf(Math.max(...counts));
60}
61
62function ridersOnline(riders) {
63 return riders.filter((r) => r.online).length;
64}
65
66function riderReport(riders, now) {
67 const online = ridersOnline(riders);
68 const rush = isLunchRush(now);
69 return { online, rush, short: rush && online < 6 };
70}
71
72function weatherDelay(weather) {
73 if (weather === "rain") return 8;
74 if (weather === "snow") return 20;
75 return 0;
76}
77
78function adjustEta(order, weather) {
79 order.eta = order.eta + weatherDelay(weather);
80 return order;
81}
82
83function cancelOrder(order, reason) {
84 if (order.status === "delivered") return order;
85 order.status = "cancelled";
86 return refund(order, reason);
87}
88
89function reorder(previous, user, now) {
90 const cart = { items: previous.items.map((i) => ({ ...i })) };
91 return placeOrder(cart, user, now);
92}
93
94function favourite(user) {
95 const counts = {};
96 for (const o of db.orders.byUser(user.id)) {
97 for (const i of o.items) counts[i.id] = (counts[i.id] || 0) + i.qty;
98 }
99 return Object.keys(counts).sort((a, b) => counts[b] - counts[a])[0];
100}
101
102module.exports = {
103 isOpen,
104 placeOrder,
105 assignRider,
106 markDelivered,
107 printTicket,
108 emailReceipt,
109 priceList,
110 revenueToday,
111 riderReport,
112 cancelOrder,
113 reorder,
114};