1/*************************** 2 * About This File 3 ***************************/ 4 5/** 6 * @module entities/order 7 * @description This module contains an Order class that handles order-related functionalities. 8 */ 9 10/*************************** 11 * Imports 12 ***************************/ 13 14import {novadineQuery} from "../api/novadine-query.js"; 15import {Preferences} from "@capacitor/preferences"; 16import App from "../app.js"; 17import {Logger} from "../utils/logger.js"; 18import {EventService} from "../services/event-service.js"; 19import {captureError} from "../utils/logging.js"; 20import {AuthenticationService} from "../services/authentication-service.js"; 21import config from "../config.js"; 22 23/*************************** 24 * Type Definitions 25 ***************************/ 26 27/** 28 * @typedef {Object} NewOrderPayload 29 * @description Payload for creating a new order. 30 * @property {number} customer_id - The ID of the customer placing the order. 31 * @property {number|null} [address_id=null] - The ID of the address for the order. 32 * @property {string} store_id - The ID of the store where the order is placed. 33 * @property {number} service_type_id - The ID of the service type for the order. 34 * @property {boolean} [is_asap=true] - Whether the order is for ASAP service. 35 **/ 36 37/** 38 * @typedef {ItemPayload[]} UpdateOrderItemsPayload 39 */ 40 41/** 42 * @typedef ItemPayload 43 * @description Payload for an item in the order. 44 * @property {number} [orderItemId] - The ID of the order item to update if updating an existing item. 45 * @property {number} menu_id - The ID of the menu item to update. 46 * @property {number} item_id - The ID of the item to update. 47 * @property {number} category_id - The ID of the category to which the item belongs. 48 * @property {number} quantity - The quantity of the item to update. 49 * @property {Object} [child_items] - An object containing child items to update. 50 */ 51 52/** 53 * @typedef SavedCardPaymentPayload 54 * @description Payload for a saved card payment. 55 * @property {number} pay_proc_id - The payment processor ID. 56 * @property {number} cc_account_id - The ID of the saved credit card account. 57 * @property {number} [security_code] - Credit card CVV/security code. 58 * @property {string[]} [payer_authentication.known_providers] - List of supported authentication providers. 59 */ 60 61/** 62 * @typedef {Object} NewCardPaymentPayload 63 * @description Payload for a new card payment. 64 * @property {number} pay_proc_id - The payment processor ID. 65 * @property {string} security_code - Credit card CVV/security code. 66 * @property {string} card_number - Credit card number. 67 * @property {string} expiration_month - Card expiration month (MM format). 68 * @property {string} expiration_year - Card expiration year (2-digit or 4-digit format). 69 * @property {string} first_name - First name of the cardholder. 70 * @property {string} last_name - Last name of the cardholder. 71 * @property {string} company - Name of the company (optional). 72 * @property {string} address1 - Primary street address. 73 * @property {string} address2 - Secondary address line (e.g., unit or suite). 74 * @property {string} city - City of the billing address. 75 * @property {string} state - State of the billing address. 76 * @property {string} zip_code - ZIP or postal code. 77 * @property {boolean} should_save - Indicates whether the card should be saved. 78 * @property {Object} payer_authentication - Payer authentication options. 79 * @property {string[]} payer_authentication.known_providers - List of supported authentication providers. 80 */ 81 82 83/** 84 * @typedef {Object} OrderData 85 **/ 86 87/*************************** 88 * Contents 89 ***************************/ 90 91/** 92 * Fetches an order by its ID. 93 * @param ID 94 * @returns {Promise<{cookies: Object, response: Object, status: number}>} 95 */ 96export async function fetchOrder(ID) { 97 return await novadineQuery(`orders/${ID}`); 98} 99 100/** 101 * @class Order 102 * @description 103 * Represents an order and contains methods to handle order-related functionalities. 104 * Instances of this class are **immutable** and **frozen** upon creation. 105 * All properties are read-only. Attempts to modify the instance or its 106 * properties will fail silently or throw in strict mode. 107 */ 108export class Order { 109 /** 110 * Set the event for the order 111 * @param {String} eventId 112 * @returns {Promise<void>} 113 */ 114 static async setEvent(eventId) { 115 await Preferences.set({key: "order_event_id", value: eventId.toLowerCase()}); 116 } 117 118 /** 119 * Gets an event, if any for the order 120 * @returns {Promise<any|null>} 121 */ 122 static async getEvent() { 123 const {value} = await Preferences.get({key: "order_event_id"}); 124 125 if (!value) return null; 126 127 const event = await config.get(`events/${value}`); 128 129 if (!event) return null; 130 131 return event; 132 } 133 134 /** 135 * Stores the order details 136 * @param order_id 137 * @param order_date - We cache menus based on order date since the menu on Thanksgiving is different from somebody placing an order for today. 138 * @returns {Promise<void>} 139 */ 140 static async storeDetails({order_id, order_date}) { 141 await Promise.all([ 142 Preferences.set({key: "order_id", value: order_id}), 143 Preferences.set({key: "order_date", value: order_date}), 144 Preferences.set({key: "order_flow_error_count", value: "0"}) 145 ]); 146 } 147 148 /** 149 * Deletes order details from storage 150 * @returns {Promise<void>} 151 */
152 static async deleteStoredDetails() { 153 await Promise.all([ 154 Preferences.remove({key: "order_id"}), 155 Preferences.remove({key: "order_date"}), 156 Preferences.remove({key: "order_flow_error_count"}), 157 Preferences.remove({key: "order_event_id"}) 158 ]); 159 160 window.Sentry?.setContext("order", null); 161 } 162 163 /** 164 * Increments the error count associated with the order 165 * @returns {Promise<void>} 166 */ 167 static async incrementOrderFlowErrorCount() { 168 const {value: orderId} = await Preferences.get({key: "order_id"}); 169 if (!orderId) return; 170 171 const count = await this.getOrderFlowErrorCount(); 172 173 await Preferences.set({ 174 key: "order_flow_error_count", 175 value: String(count + 1) 176 }); 177 } 178 179 /** 180 * Checks if the order flow encountered any errors 181 * @returns {Promise<boolean>} 182 */ 183 static async hasOrderFlowErrors() { 184 return await Order.getOrderFlowErrorCount() > 0; 185 } 186 187 /** 188 * Gets the errors that have occurred while the order has been stored 189 * @returns {Promise<*>} 190 */ 191 static async getOrderFlowErrorCount() { 192 const {value} = await Preferences.get({ 193 key: "order_flow_error_count" 194 }); 195 196 return Number(value || 0); 197 } 198 199 /** 200 * Creates a new order. 201 * @param {NewOrderPayload} payload - The payload for the new order. 202 * @returns {Promise<Order>} - A promise that resolves to the created order as an Order class instance. 203 * @throws {Error} - Throws an error if the order creation fails. 204 */ 205 static async create(payload) { 206 try { 207 const {response} = await novadineQuery("orders/new", { 208 method: "POST", 209 payload: { 210 ...payload, 211 platform: App.getPlatformID() 212 }, 213 }); 214 215 await Order.storeDetails({ 216 order_id: response.order.order_id.toString(), 217 order_date: response.order.ready_time 218 }); 219 220 // track start order event, wrapped in try/catch because analytics failures should not interfere with critical state 221 try { 222 await EventService.send("start_order", {}, 200); 223 } catch (eventServiceError) { 224 console.warn(eventServiceError); 225 } 226 227 return new Order(response); 228 } catch (error) { 229 try { 230 await Order.deleteStoredDetails(); 231 } catch (rollbackError) { 232 console.error(rollbackError); 233 await captureError(rollbackError); 234 } 235 236 // Bubble original error to caller 237 throw error; 238 } 239 } 240 241 /** 242 * Gets the payment status for the order. 243 * @description 244 * There are 2 possible 200 success responses. 245 * - A payment that is successfully processed will return the details of the order. 246 * - An errors array, meaning something went wrong with the payment, or the status is not available. 247 * @param {string} token - The payment token returned from the submitPayment method. 248 * @param {number} payProcId - The payment processor ID. 249 * @returns {Promise<NovadineQueryResponse>} - A promise that resolves to the response of the payment status check. 250 * @throws {Error} - Throws an error if the payment status request fails. 251 */ 252 static async getPaymentStatus(token, payProcId) { 253 return await novadineQuery(`orders/payment/${token}?pay_proc_id=${payProcId}`); 254 } 255 256 /** 257 * Polls for the payment status of an order. 258 * @param {string} token - The payment token. 259 * @param {number} payProcId - The payment processor ID. 260 * @param {Object} [options] - Options for polling. 261 * @returns {Promise<NovadineQueryResponse>} - A promise that resolves to the order if payment is successful, or null if not. 262 * @throws {Error} - Throws an error if polling fails after the maximum number of attempts. 263 */ 264 static async pollForPaymentStatus(token, payProcId, options = {}) { 265 // Default options 266 options = { 267 interval: 1000, // Default polling interval in milliseconds 268 maxAttempts: 10, // Default maximum number of attempts 269 ...options, 270 }; 271 272 let attempts = 0; 273 let statusError = null; 274 275 while (attempts < options.maxAttempts) { 276 try { 277 const response = await Order.getPaymentStatus(token, payProcId); 278 const {response: paymentStatus} = response; 279 280 // If the response contains an order, we can assume the payment was successful 281 if (paymentStatus.hasOwnProperty("order")) { 282 return response; 283 } 284 } catch (error) { 285 statusError = error; 286 console.warn(`Error polling payment status for token ${token}:`, error); 287 } 288 289 // Wait for the specified interval before the next attempt 290 await new Promise(resolve => setTimeout(resolve, options.interval)); 291 292 attempts++; 293 } 294 295 throw new Error(`Payment unsuccessful. ${statusError}`); 296 } 297 298 299 /** 300 * Gets the card type ID from the type name. 301 * @param name - The card type name. e.g., "Visa", "Mastercard", etc. 302 * @returns {*|null} 303 */ 304 static getCardTypeIdByName(name) { 305 const cardTypes = { 306 "Visa": 7, 307 "Mastercard": 8, 308 "Discover": 9, 309 "Amex": 10, 310 }; 311 312 return cardTypes[name] || null; 313 } 314 315 /** 316 * Creates a new Order instance. 317 * @param {Object<OrderData>} data 318 */ 319 constructor(data) { 320 for (const [key, value] of Object.entries(data)) { 321 // Define each property 322 this[key] = value; 323 } 324 } 325 326 /** 327 * Get the menu url for the order 328 * @returns {string} 329 */ 330 getMenuUrl() { 331 return `/order/menu/index.html?store_id=${this.restaurant.store_id}&menu_id=76&service_type_id=${this.service_type_id}`; 332 } 333 334 /** 335 * Get the item page url used to edit an item already on the order 336 * @param {Object} item 337 * @param {"cart"|"checkout"} source 338 * @returns {string} 339 */ 340 getItemEditUrl(item, source) { 341 const url = new URL("/order/menu/item/index.html", window.location.origin); 342 url.searchParams.set("store_id", this.restaurant.store_id); 343 url.searchParams.set("service_type_id", this.service_type_id); 344 url.searchParams.set("menu_id", item.menu_id); 345 url.searchParams.set("category_id", item.category_id); 346 url.searchParams.set("category_name", item.category ?? ""); 347 url.searchParams.set("item_id", item.item_id); 348 url.searchParams.set("edit_order_item_id", item.order_item_id); 349 url.searchParams.set("edit_item_source", source); 350 return `${url.pathname}${url.search}`; 351 } 352 353 /** 354 * Add comments for the Restaurant 355 * @param comments 356 * @returns {Promise<{cookies: Object, response: Object, status: number}>} 357 */ 358 async addCommentForRestaurant(comments) { 359 return await novadineQuery(`orders/${this.id}`, { 360 method: "PUT", 361 payload: { 362 order: { 363 comments_to_restaurant: comments 364 } 365 } 366 }); 367 } 368 369 /** 370 * Place the order 371 * @param payment 372 * @returns {Promise<void>} 373 */ 374 async place(payment = {}) { 375 let payload; 376 let payProcId; 377 378 // Process saved card payment 379 if (payment.cardTypeId && payment.cardAccountId) { 380 const {response: paymentMethods} = await this.getPaymentMethods(); 381 const method = paymentMethods.find(pm => pm.cc_type_id === payment.cardTypeId); 382
383 if (!method) { 384 throw new Error("Payment method not accepted"); 385 } 386 387 payProcId = method.pay_proc_id; 388 389 payload = { 390 pay_proc_id: payProcId, 391 cc_account_id: payment.cardAccountId 392 } 393 } 394 // Process token payment (Tokenized card, Apple Pay, etc..) 395 else if (payment.cardToken && payment.payProcId && payment.cardTypeId) { 396 payProcId = payment.payProcId; 397 398 payload = { 399 pay_proc_id: payProcId, 400 card_token: payment.cardToken, 401 card_type_id: payment.cardTypeId 402 } 403 } else { 404 throw new Error("Invalid payment payload"); 405 } 406 407 const {response: paymentResponse} = await this.submitPayment(payload, this.charges.remaining_cents); 408 409 // Poll payment status if order is not returned in response from payment submission 410 if (!paymentResponse.hasOwnProperty("order")) { 411 await Order.pollForPaymentStatus(paymentResponse.pending_payment.token, payProcId); 412 } 413 414 try { 415 await Order.deleteStoredDetails(); 416 } catch (e) { 417 Logger.error("Error clearing order metadata from preferences", e); 418 await captureError(e); 419 } 420 } 421 422 /** 423 * Submits a payment and returns the order state. 424 * If the payment is not immediately completed, it polls until finished. 425 * 426 * @async 427 * @param {Object} payment 428 * @param {number} payment.cc_account_id 429 * @param {number} payment.pay_proc_id 430 * @param {number} payment.amount 431 * @returns {Promise<Object>} Payment response or order state 432 */ 433 async processPayment(payment) { 434 // Submit the payment 435 const response = await this.submitPayment({ 436 cc_account_id: payment.cc_account_id, 437 pay_proc_id: payment.pay_proc_id, 438 }, payment.amount); 439 440 const {response: paymentResponse} = response; 441 442 if (paymentResponse.hasOwnProperty("order")) { 443 return response; 444 } 445 446 // Poll for the payment status and return the response which also contains the order on success 447 return Order.pollForPaymentStatus(paymentResponse.pending_payment.token, payment.pay_proc_id); 448 } 449 450 /** 451 * Polls the receipt status for the order. 452 * @param options 453 * @returns {Promise<void>} 454 */ 455 async pollReceiptStatus(options = {}) { 456 // Default options 457 options = { 458 intervalMs: options?.intervalMs || 3000, // Default polling interval in milliseconds 459 maxAttempts: options?.maxAttempts || 3, // Default maximum number of attempts 460 stopOnError: options?.stopOnError || false, // Whether to stop polling on error 461 } 462 463 // Run first attempt 464 Logger.info(`[Receipt Polling] Polling receipt status for order ${this.id}...`); 465 let attempt = 0; 466 467 const check = async () => { 468 try { 469 const {response} = await this.getReceiptStatus(); 470 if (response?.ok === true) { 471 Logger.info(`[Receipt Polling] Receipt is ready for order ${this.id}.`); 472 return true; 473 } 474 } catch (e) { 475 // Re-throw error if stopOnError is true but not for the expected message 476 if (options.stopOnError && !e?.message.includes("Showing receipt is not allowed:")) { 477 throw e; 478 } 479 Logger.warn(`[Receipt Polling] Error polling receipt status for order ${this.id} on attempt ${attempt + 1}: ${e.message}`); 480 } 481 return false; 482 } 483 484 // First check 485 if (await check()) return; 486 487 // Update attempt count 488 attempt++; 489 490 // Continue polling until max attempts reached 491 while (attempt < options.maxAttempts) { 492 if (await check()) return; 493 attempt++; 494 if (attempt < options.maxAttempts) { 495 await new Promise(resolve => setTimeout(resolve, options.intervalMs)); 496 } 497 } 498 499 // Max attempts reached 500 if (attempt >= options.maxAttempts) {
501 Logger.warn("[Receipt Polling] Max attempts reached, cannot show receipt"); 502 throw new Error("[Receipt Polling] Max attempts reached"); 503 } 504 } 505 506 /** 507 * Gets the receipt status for the order. 508 * @returns {Promise<{cookies: Object, response: Object, status: number}>} 509 */ 510 async getReceiptStatus() { 511 return await novadineQuery(`orders/${this.order.order_id}/actions/show_receipt?customer_id=${this.order.customer_id}`); 512 } 513 514 /** 515 * Removes an item from the order. 516 * @param orderItemId 517 * @returns {Promise<{cookies: Object, response: Object, status: number}>} 518 */ 519 async removeItem(orderItemId) { 520 return await novadineQuery(`orders/${this.id}/items/${orderItemId}`, { 521 method: "DELETE" 522 }); 523 } 524 525 /** 526 * Updates the items in the order. 527 * @param {UpdateOrderItemsPayload} payload - The payload containing the items to update. 528 * @returns {Promise<NovadineQueryResponse>} - A promise that resolves to the response of the update operation. 529 * @throws {Error} - Throws an error if the update operation fails. 530 */ 531 async updateItems(payload) { 532 return await novadineQuery(`orders/${this.id}/items`, { 533 method: "POST", 534 payload: { 535 customer_id: this.order.customer_id, 536 items: payload, 537 }, 538 }); 539 } 540 541 /** 542 * Edits the order with the given payload. 543 * @param payload 544 * @returns {Promise<{cookies: Object, response: Object, status: number}>} 545 */ 546 async edit(payload) { 547 return await novadineQuery(`orders/new`, { 548 method: "POST", 549 payload: { 550 store_id: this.restaurant.store_id, 551 customer_id: this.customer.customer_id, 552 order_id: this.id, 553 service_type_id: this.service_type_id, 554 ...payload, 555 } 556 }); 557 } 558 559 /** 560 * Sets the tip amount for the order. 561 * @param amountCents 562 * @returns {Promise<{cookies: Object, response: Object, status: number}>} 563 */ 564 async setTip(amountCents) { 565 return await novadineQuery(`orders/${this.order.order_id}`, { 566 method: "PUT", 567 payload: { 568 order: { 569 charges: { 570 tip_cents: amountCents, 571 } 572 } 573 }, 574 }); 575 } 576 577 /** 578 * Adds a discount code to the order. 579 * @param code 580 * @returns {Promise<{cookies: Object, response: Object, status: number}>} 581 */ 582 async addDiscount(code) { 583 return await novadineQuery(`orders/${this.order.order_id}/discount`, { 584 method: "POST", 585 payload: { 586 customer_id: this.order.customer_id, 587 discount_code: code, 588 }, 589 }); 590 } 591 592 /** 593 * Removes a discount from the order. 594 * @param id 595 * @returns {Promise<{cookies: Object, response: Object, status: number}>} 596 */ 597 async removeDiscount(id) { 598 return await novadineQuery(`orders/${this.order.order_id}/discount?customer_id=${this.order.customer_id}&discount_id=${id}`, { 599 method: "DELETE" 600 }); 601 } 602 603 /** 604 * Cancels the order. 605 * @returns {Promise<NovadineQueryResponse>} - A promise that resolves to the response of the cancellation operation. 606 * @throws {Error} - Throws an error if the cancellation fails. 607 */ 608 async cancel() { 609 return await novadineQuery(`orders/${this.order.order_id}?customer_id=${this.customer.customer_id}`, { 610 method: "DELETE" 611 }); 612 } 613 614 /** 615 * Submits the payment for the order. 616 * @param {NewCardPaymentPayload|SavedCardPaymentPayload} card - The card information. 617 * @param {number} [amount=0] - The amount to pay in cents. Defaults to 0. 618 * @returns {Promise<NovadineQueryResponse>} - A promise that resolves to the response of the payment submission. This jekyll-jekyll-includes a payment token you can use to check the status of the payment. 619 * @throws {Error} - Throws an error if the update operation fails. 620 */ 621 async submitPayment(card, amount = 0) { 622 return await novadineQuery(`orders/${this.order.order_id}/payment`, { 623 method: "POST", 624 payload: { 625 ...card, 626 payment_cents: amount, 627 }, 628 }) 629 } 630 631 /** 632 * Gets the payment methods available for the order. 633 * @returns {Promise<{cookies: Object, response: Object, status: number}>} 634 */ 635 async getPaymentMethods() { 636 return await novadineQuery(`orders/${this.order.order_id}/payment_methods`); 637 } 638 639 /** 640 * Gets the coupons applied to the order that are "Plate Pass". 641 * @returns {*} 642 */ 643 getPlatePassCoupons() { 644 return this.coupons.filter(coupon => coupon.name.includes("Plate Pass") && Math.abs(coupon.applied_value) > 0); 645 } 646 647 /** 648 * Gets the coupons applied to the order that are not "Plate Pass". 649 * @returns {Array} 650 */ 651 getNonPlatePassCoupons() { 652 return this.coupons.filter(coupon => !coupon.name.includes("Plate Pass") && Math.abs(coupon.applied_value) > 0); 653 } 654 655 /** 656 * Check if the order is in a partially paid state 657 * @returns {boolean} 658 */ 659 isPartiallyPaid() { 660 return this.status_id === 7; 661 } 662 663 /** 664 * Returns items available for display, for example in the cart or on the confirmation page 665 * @returns {*[]} 666 */ 667 get display_items() { 668 const filteredItems = [ 669 15201, // Surcharge, 670 18252 // $0 Dining Credit item: required as a placeholder so discounts can be applied to a empty cart 671 ]; 672 673 return this.items.filter(item => !filteredItems.includes(item.item_id)); 674 } 675 676 /** 677 * Gets the service type name for the order. 678 * e.g., "Delivery", "Pickup", etc. 679 * @returns {string} 680 */ 681 get service_type() { 682 return this.order.service_type; 683 } 684 685 /** 686 * Gets the service group for the order. 687 * e.g., "catering", etc. 688 * @returns {string} 689 */ 690 get service_group() { 691 return this.order.service_group; 692 } 693 694 /** 695 * Gets whether the order is for ASAP service. 696 * @returns {boolean} 697 */ 698 get is_asap() { 699 return this.order.is_asap; 700 } 701 702 /** 703 * Gets the order ID. 704 * @returns {Number} 705 */ 706 get id() { 707 return this.order.order_id; 708 } 709 710 /** 711 * Gets the ordering status 712 * @return {*} 713 */ 714 get ordering_status() { 715 return this.order.ordering_status; 716 } 717 718 /** 719 * Gets the tracking URL for the order. 720 * @returns {string|null} 721 */ 722 get tracking_url() { 723 return this.order?.tracking_url || null; 724 } 725 726 /** 727 * Gets the table number for the order. 728 * @returns {string|null} 729 */ 730 get table_number() { 731 return this.order?.table_number || null; 732 } 733 734 /** 735 * Gets the estimated dropoff timestamp for the order. 736 * @returns {string|null} 737 */ 738 get est_dropoff_ts() { 739 return this.order?.est_dropoff_ts || null; 740 } 741 742 /** 743 * Gets the comments to restaurant for the order. 744 * @returns {string|null} 745 */ 746 get comments_to_restaurant() { 747 return this.order?.comments_to_restaurant || null; 748 } 749 750 /** 751 * Gets the customer id on the order 752 * @return {*} 753 */ 754 get customer_id() { 755 return this.order.customer.customer_id; 756 } 757 758 /** 759 * Gets the status ID for the order. 760 * @returns {Number} 761 */ 762 get status_id() { 763 return this.order.status_id; 764 } 765 766 /** 767 * Gets the service type ID for the order. 768 * @returns {Number} 769 */ 770 get service_type_id() { 771 return Number(this.order.service_type_id); 772 } 773 774 /** 775 * Gets the display name for the service type. 776 * @returns {String} 777 */ 778 get service_type_display_name() { 779 return this.order.service_type_display_name; 780 } 781 782 /** 783 * Gets the charges associated with the order. 784 * @returns {Object} 785 */ 786 get charges() { 787 return this.order.charges; 788 } 789 790 /** 791 * Gets the order items 792 * @returns {Array} 793 */ 794 get items() { 795 return this.order.items; 796 } 797 798 /** 799 * Gets the ready time for the order. 800 * @returns {String} 801 */ 802 get ready_time() { 803 return this.order.ready_time; 804 } 805 806 /** 807 * Get any comments to the restaurant 808 * @returns {string|null|HTMLElement|*} 809 */ 810 get comments_to_restaurant() { 811 return this.order.comments_to_restaurant; 812 } 813 814 /** 815 * Gets the tip percentages for the order. 816 * @returns {Array} 817 */ 818 get tip_percentages() { 819 return this.order.tip_percentages; 820 } 821 822 /** 823 * Get the max tip amount for the order. 824 * @returns {*} 825 */ 826 get max_tip_cents() { 827 return this.order.max_tip_cents; 828 } 829 830 /** 831 * Gets the suggested ready time for the order 832 * @returns {*} 833 */ 834 get suggested_ready_time() { 835 return this.order.suggested_ready_time; 836 } 837 838 /** 839 * Gets any errors on the order 840 * @returns {Object} 841 */ 842 get errors() { 843 return this.order.errors ?? {}; 844 } 845 846 /** 847 * Gets the service type display name for the order 848 * @returns {String|*} 849 */ 850 get service_type_display_name() { 851 return this.order.service_type_display_name; 852 } 853}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.