PageSourceSearch

https://urbanplates.com/assets/js/entities/order.js

js urbanplates.com collected 2026-09-25 23:52:48 UTC 27,128 bytes, 853 lines download raw bytes

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.