00000000001// legacy.js — Byte Burrito, 2019–present. Please don't add anything here.
1// (Everyone has added something here.)
2
3const TAX_RATE = 0.0875;
4const MAX_DISTANCE_KM = 8;
5const OPEN_HOUR = 11;
6const CLOSE_HOUR = 23;
7const RUSH_START = 12;
8const RUSH_END = 14;
9
10function isOpen(now) {
11 const hour = now.getHours();
12 return hour >= OPEN_HOUR && hour < CLOSE_HOUR;
13}
14
15function isLunchRush(now) {
16 const hour = now.getHours();
17 return hour >= RUSH_START && hour < RUSH_END;
18}
19
20function formatPrice(cents) {
21 const dollars = Math.floor(cents / 100);
22 const rest = String(cents % 100).padStart(2, "0");
23 return "$" + dollars + "." + rest;
24}
25
26function subtotal(items) {
27 let sum = 0;
28 for (const item of items) {
29 sum += item.price * item.qty;
30 }
31 return sum;
32}
33
34function taxFor(amount) {
35 return Math.round(amount * TAX_RATE);
36}
37
38function totalFor(order) {
39 const base = subtotal(order.items);
40 const tax = taxFor(base);
41 const tip = order.tip || 0;
42 return base + tax + tip;
43}
44
45function validateOrder(order) {
46 if (!order.items || order.items.length === 0) return "empty";
47 if (order.items.some((i) => i.qty <= 0)) return "bad quantity";
48 if (!order.address) return "no address";
49 if (!isOpen(new Date())) return "closed";
50 return null;
51}
52
53function applyCoupon(order, code) {
54 const coupon = COUPONS[code];
55 if (!coupon) return order;
56 if (coupon.expires < Date.now()) return order;
57 const off = Math.round(subtotal(order.items) * coupon.percent);
58 return { ...order, discount: off };
59}
60
61function studentDiscount(order, user) {
62 if (!user.isStudent) return 0;
63 return Math.round(subtotal(order.items) * 0.1);
64}
65
66function distanceKm(a, b) {
67 const dLat = (b.lat - a.lat) * 111;
68 const dLng = (b.lng - a.lng) * 111 * Math.cos((a.lat * Math.PI) / 180);
69 return Math.sqrt(dLat * dLat + dLng * dLng);
70}
71
72function nearestRider(riders, address) {
73 let best = null;
74 for (const rider of riders) {
75 if (!rider.online) continue;
76 const d = distanceKm(rider.position, address.position);
77 if (d > MAX_DISTANCE_KM) continue;
78 if (!best || d < best.distance) best = { rider, distance: d };
79 }
80 return best;
81}
82
83function assignRider(order, riders) {
84 const match = nearestRider(riders, order.address);
85 if (!match) return refund(order, "No rider nearby, sorry!");
86 order.riderId = match.rider.id;
87 order.eta = Math.ceil(match.distance * 4) + 10;
88 notify(match.rider.phone, "New order: " + order.id);
89 return order;
90}
91
92function etaMinutes(order, now) {
93 const rush = isLunchRush(now) ? 10 : 0;
94 return order.eta + rush;
95}
96
97function refund(order, reason) {
98 order.status = "refunded";
99 order.refundReason = reason;
100 payments.refund(order.paymentId, totalFor(order));
101 notify(order.customerPhone, "Refunded: " + reason);
102 return order;
103}
104
105function markDelivered(order, now) {
106 order.status = "delivered";
107 order.deliveredAt = now.toISOString();
108 const late = now - new Date(order.placedAt) > order.eta * 60000;
109 if (late) order.lateBy = Math.round((now - new Date(order.placedAt)) / 60000 - order.eta);
110 return order;
111}
112
113function kitchenTicket(order) {
114 const lines = order.items.map((i) => i.qty + " x " + i.name);
115 lines.unshift("ORDER " + order.id);
116 if (order.notes) lines.push("NOTE: " + order.notes);
117 return lines.join("\n");
118}
119
120function printTicket(order) {
121 const ticket = kitchenTicket(order);
122 printer.print(ticket);
123 log("printed", order.id);
124}
125
126function emailReceipt(order, user) {
127 const body = renderReceipt(order);
128 mailer.send(user.email, "Your Byte Burrito receipt", body);
129}
130
131function menuItem(id) {
132 const item = MENU.find((m) => m.id === id);
133 if (!item) throw new Error("not on the menu: " + id);
134 return item;
135}
136
137function isSpicy(id) {
138 return menuItem(id).spicy === true;
139}
140
141function happyHourPrice(item, now) {
142 const hour = now.getHours();
143 if (hour >= 17 && hour < 19) return Math.round(item.price * 0.8);
144 return item.price;
145}
146
147function priceList(now) {
148 return MENU.map((item) => ({
149 id: item.id,
150 name: item.name,
151 price: formatPrice(happyHourPrice(item, now)),
152 }));
153}
154
155function placeOrder(cart, user, now) {
156 const order = { id: newId(), items: cart.items, address: user.address, placedAt: now.toISOString() };
157 const fee = deliveryFee(order, distanceKm(KITCHEN, order.address));
158 const problem = validateOrder(order);
159 if (problem) throw new Error(problem);
160 order.discount = studentDiscount(order, user);
161 order.total = totalFor(order) - order.discount + fee;
162 return order;
163}
164
165function deliveryFee(order, km) {
166 const base = km <= 2 ? 0 : 1.5;
167 const perKm = km > 2 ? (km - 2) * 0.4 : 0;
168 const rush = isLunchRush(new Date()) ? 1 : 0;
169 return Math.round((base + perKm + rush) * 100) / 100;
170}
171
172function newId() {
173 return "BB-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 6);
174}
175
176function log(event, ...details) {
177 console.log("[" + new Date().toISOString() + "] " + event, ...details);
178}
179
180function notify(phone, text) {
181 if (!phone) return;
182 sms.send(phone, text.slice(0, 160));
183}
184
185function saveUser(user) {
186 db.users.put(user.id, user);
187}
188
189function loadUser(id) {
190 return db.users.get(id);
191}
192
193function saveOrder(order) {
194 db.orders.put(order.id, order);
195 log("saved", order.id);
196}
197
198function loadOrder(id) {
199 return db.orders.get(id);
200}
201
202function ordersToday(now) {
203 const day = now.toISOString().slice(0, 10);
204 return db.orders.all().filter((o) => o.placedAt.startsWith(day));
205}
206
207function revenueToday(now) {
208 return ordersToday(now).reduce((sum, o) => sum + o.total, 0);
209}
210
211function busiestHour(now) {
212 const counts = new Array(24).fill(0);
213 for (const o of ordersToday(now)) counts[new Date(o.placedAt).getHours()]++;
214 return counts.indexOf(Math.max(...counts));
215}
216
217function ridersOnline(riders) {
218 return riders.filter((r) => r.online).length;
219}
220
221function riderReport(riders, now) {
222 const online = ridersOnline(riders);
223 const rush = isLunchRush(now);
224 return { online, rush, short: rush && online < 6 };
225}
226
227function weatherDelay(weather) {
228 if (weather === "rain") return 8;
229 if (weather === "snow") return 20;
230 return 0;
231}
232
233function adjustEta(order, weather) {
234 order.eta = order.eta + weatherDelay(weather);
235 return order;
236}
237
238function cancelOrder(order, reason) {
239 if (order.status === "delivered") return order;
240 order.status = "cancelled";
241 return refund(order, reason);
242}
243
244function reorder(previous, user, now) {
245 const cart = { items: previous.items.map((i) => ({ ...i })) };
246 return placeOrder(cart, user, now);
247}
248
249function favourite(user) {
250 const counts = {};
251 for (const o of db.orders.byUser(user.id)) {
252 for (const i of o.items) counts[i.id] = (counts[i.id] || 0) + i.qty;
253 }
254 return Object.keys(counts).sort((a, b) => counts[b] - counts[a])[0];
255}
256
257module.exports = {
258 isOpen,
259 placeOrder,
260 assignRider,
261 markDelivered,
262 printTicket,
263 emailReceipt,
264 priceList,
265 revenueToday,
266 riderReport,
267 cancelOrder,
268 reorder,
269};