diff --git a/client/src/components/global-search/global-search-os.component.jsx b/client/src/components/global-search/global-search-os.component.jsx
new file mode 100644
index 000000000..8f1cb596a
--- /dev/null
+++ b/client/src/components/global-search/global-search-os.component.jsx
@@ -0,0 +1,216 @@
+import { AutoComplete, Divider, Input, Space } from "antd";
+import axios from "axios";
+import _ from "lodash";
+import React, { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { Link, useHistory } from "react-router-dom";
+import PhoneNumberFormatter from "../../utils/PhoneFormatter";
+import OwnerNameDisplay, {
+ OwnerNameDisplayFunction,
+} from "../owner-name-display/owner-name-display.component";
+import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
+
+export default function GlobalSearchOs() {
+ const { t } = useTranslation();
+ const history = useHistory();
+ const [loading, setLoading] = useState(false);
+ const [data, setData] = useState(false);
+
+ const executeSearch = async (v) => {
+ if (v && v && v !== "" && v.length >= 3) {
+ try {
+ setLoading(true);
+ const searchData = await axios.post("/search", {
+ search: v,
+ });
+
+ const resultsByType = {
+ payments: [],
+ jobs: [],
+ bills: [],
+ owners: [],
+ vehicles: [],
+ };
+
+ searchData.data.hits.hits.forEach((hit) => {
+ resultsByType[hit._index].push(hit._source);
+ });
+ setData([
+ {
+ label: renderTitle(t("menus.header.search.jobs")),
+ options: resultsByType.jobs.map((job) => {
+ return {
+ key: job.id,
+ value: job.ro_number,
+ label: (
+
+ }>
+ {job.ro_number || t("general.labels.na")}
+ {`${job.status || ""}`}
+
+
+
+ {`${job.v_model_yr || ""} ${
+ job.v_make_desc || ""
+ } ${job.v_model_desc || ""}`}
+ {`${job.clm_no || ""}`}
+
+
+ ),
+ };
+ }),
+ },
+ {
+ label: renderTitle(t("menus.header.search.owners")),
+ options: resultsByType.owners.map((owner) => {
+ return {
+ key: owner.id,
+ value: OwnerNameDisplayFunction(owner),
+ label: (
+
+ }
+ wrap
+ >
+
+
+
+
+ {owner.ownr_ph1}
+
+
+ {owner.ownr_ph2}
+
+
+
+ ),
+ };
+ }),
+ },
+ {
+ label: renderTitle(t("menus.header.search.vehicles")),
+ options: resultsByType.vehicles.map((vehicle) => {
+ return {
+ key: vehicle.id,
+ value: `${vehicle.v_model_yr || ""} ${
+ vehicle.v_make_desc || ""
+ } ${vehicle.v_model_desc || ""}`,
+ label: (
+
+ }>
+
+ {`${vehicle.v_model_yr || ""} ${
+ vehicle.v_make_desc || ""
+ } ${vehicle.v_model_desc || ""}`}
+
+ {vehicle.plate_no || ""}
+
+
+ {vehicle.v_vin || ""}
+
+
+
+
+ ),
+ };
+ }),
+ },
+ {
+ label: renderTitle(t("menus.header.search.payments")),
+ options: resultsByType.payments.map((payment) => {
+ return {
+ key: payment.id,
+ value: `${payment.job?.ro_number} ${payment.amount}`,
+ label: (
+
+ }>
+ {payment.paymentnum}
+ {payment.job?.ro_number}
+ {payment.memo || ""}
+ {payment.amount || ""}
+ {payment.transactionid || ""}
+
+
+ ),
+ };
+ }),
+ },
+ {
+ label: renderTitle(t("menus.header.search.bills")),
+ options: resultsByType.bills.map((bill) => {
+ return {
+ key: bill.id,
+ value: `${bill.invoice_number} - ${bill.vendor.name}`,
+ label: (
+
+ }>
+ {bill.invoice_number}
+ {bill.vendor.name}
+ {bill.date}
+
+
+ ),
+ };
+ }),
+ },
+ // {
+ // label: renderTitle(t("menus.header.search.phonebook")),
+ // options: resultsByType.search_phonebook.map((pb) => {
+ // return {
+ // key: pb.id,
+ // value: `${pb.firstname || ""} ${pb.lastname || ""} ${
+ // pb.company || ""
+ // }`,
+ // label: (
+ //
+ // }>
+ // {`${pb.firstname || ""} ${pb.lastname || ""} ${
+ // pb.company || ""
+ // }`}
+ // {pb.phone1}
+ // {pb.email}
+ //
+ //
+ // ),
+ // };
+ // }),
+ // },
+ ]);
+ } catch (error) {
+ console.log("Error while fetching search results", error);
+ } finally {
+ setLoading(false);
+ }
+ }
+ };
+ const debouncedExecuteSearch = _.debounce(executeSearch, 750);
+
+ const handleSearch = (value) => {
+ console.log("Handle Search");
+ debouncedExecuteSearch(value);
+ };
+
+ const renderTitle = (title) => {
+ return {title};
+ };
+
+ return (
+ {
+ history.push(opt.label.props.to);
+ }}
+ >
+
+
+ );
+}
diff --git a/client/src/pages/jobs-all/jobs-all.container.jsx b/client/src/pages/jobs-all/jobs-all.container.jsx
index 539271e7d..a36745a0e 100644
--- a/client/src/pages/jobs-all/jobs-all.container.jsx
+++ b/client/src/pages/jobs-all/jobs-all.container.jsx
@@ -39,7 +39,7 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) {
...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}),
order: [
{
- [sortcolumn || "created_at"]:
+ [sortcolumn || "ro_number"]:
sortorder && sortorder !== "false"
? sortorder === "descend"
? "desc"
diff --git a/hasura/migrations/1682522234404_create_index_jobs_idx_status_hash/down.sql b/hasura/migrations/1682522234404_create_index_jobs_idx_status_hash/down.sql
new file mode 100644
index 000000000..9a9ec807f
--- /dev/null
+++ b/hasura/migrations/1682522234404_create_index_jobs_idx_status_hash/down.sql
@@ -0,0 +1 @@
+DROP INDEX IF EXISTS "public"."jobs_idx_status_hash";
diff --git a/hasura/migrations/1682522234404_create_index_jobs_idx_status_hash/up.sql b/hasura/migrations/1682522234404_create_index_jobs_idx_status_hash/up.sql
new file mode 100644
index 000000000..1343f6695
--- /dev/null
+++ b/hasura/migrations/1682522234404_create_index_jobs_idx_status_hash/up.sql
@@ -0,0 +1,2 @@
+CREATE INDEX "jobs_idx_status_hash" on
+ "public"."jobs" using hash ("status");
diff --git a/hasura/migrations/1682522667065_create_index_idx_jobs_ronumber_btree/down.sql b/hasura/migrations/1682522667065_create_index_idx_jobs_ronumber_btree/down.sql
new file mode 100644
index 000000000..63b6b51e7
--- /dev/null
+++ b/hasura/migrations/1682522667065_create_index_idx_jobs_ronumber_btree/down.sql
@@ -0,0 +1 @@
+DROP INDEX IF EXISTS "public"."idx_jobs_ronumber_btree";
diff --git a/hasura/migrations/1682522667065_create_index_idx_jobs_ronumber_btree/up.sql b/hasura/migrations/1682522667065_create_index_idx_jobs_ronumber_btree/up.sql
new file mode 100644
index 000000000..0d93a227c
--- /dev/null
+++ b/hasura/migrations/1682522667065_create_index_idx_jobs_ronumber_btree/up.sql
@@ -0,0 +1,2 @@
+CREATE INDEX "idx_jobs_ronumber_btree" on
+ "public"."jobs" using btree ("ro_number");
diff --git a/os-loader.js b/os-loader.js
new file mode 100644
index 000000000..d5a2c52a1
--- /dev/null
+++ b/os-loader.js
@@ -0,0 +1,237 @@
+const Dinero = require("dinero.js");
+
+//const client = require("../graphql-client/graphql-client").client;
+const _ = require("lodash");
+const GraphQLClient = require("graphql-request").GraphQLClient;
+const logger = require("./server/utils/logger");
+
+const path = require("path");
+const client = require("./server/graphql-client/graphql-client").client;
+require("dotenv").config({
+ path: path.resolve(
+ process.cwd(),
+ `.env.${process.env.NODE_ENV || "development"}`
+ ),
+});
+const { Client, Connection } = require("@opensearch-project/opensearch");
+const { defaultProvider } = require("@aws-sdk/credential-provider-node");
+const aws4 = require("aws4");
+const { gql } = require("graphql-request");
+const gqlclient = require("./server/graphql-client/graphql-client").client;
+// const osClient = new Client({
+// node: `https://imex:Wl0d8k@!@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com/`,
+// });
+
+var host = process.env.OPEN_SEARCH_HOST; // e.g. https://my-domain.region.es.amazonaws.com
+const createAwsConnector = (credentials, region) => {
+ class AmazonConnection extends Connection {
+ buildRequestObject(params) {
+ const request = super.buildRequestObject(params);
+ request.service = "es";
+ request.region = region;
+ request.headers = request.headers || {};
+ request.headers["host"] = request.hostname;
+
+ return aws4.sign(request, credentials);
+ }
+ }
+ return {
+ Connection: AmazonConnection,
+ };
+};
+
+const getClient = async () => {
+ const credentials = await defaultProvider()();
+ return new Client({
+ ...createAwsConnector(credentials, "ca-central-1"),
+ node: host,
+ });
+};
+
+async function OpenSearchUpdateHandler(req, res) {
+ try {
+ var osClient = await getClient();
+ // const osClient = new Client({
+ // node: `https://imex:password@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com`,
+ // });
+
+ //Clear out all current documents
+ // const deleteResult = await osClient.deleteByQuery({
+ // index: ["*"], // ["jobs", "payments", "bills", "vehicles", "owners"],
+ // body: {
+ // query: {
+ // match_all: {},
+ // },
+ // },
+ // });
+
+ // return;
+
+ var batchSize = 1000;
+ var promiseQueue = [];
+
+ //Jobs Load.
+ const jobsData = await gqlclient.request(`query{jobs{
+ id
+ bodyshopid:shopid
+ ro_number
+ clm_no
+ ownr_fn
+ ownr_ln
+ status
+ ownr_co_nm
+ v_model_yr
+ v_make_desc
+ v_model_desc
+ }}`);
+ for (let i = 0; i <= jobsData.jobs.length / batchSize; i++) {
+ const slicedArray = jobsData.jobs.slice(
+ i * batchSize,
+ i * batchSize + batchSize
+ );
+ const bulkOperation = [];
+ slicedArray.forEach((job) => {
+ bulkOperation.push({ index: { _index: "jobs", _id: job.id } });
+ bulkOperation.push(job);
+ });
+ promiseQueue.push(bulkOperation);
+ }
+
+ //Owner Load
+ const ownersData = await gqlclient.request(`{
+ owners {
+ id
+ bodyshopid: shopid
+ ownr_fn
+ ownr_ln
+ ownr_co_nm
+ ownr_ph1
+ ownr_ph2
+ }
+ }
+ `);
+ for (let i = 0; i <= ownersData.owners.length / batchSize; i++) {
+ const slicedArray = ownersData.owners.slice(
+ i * batchSize,
+ i * batchSize + batchSize
+ );
+ const bulkOperation = [];
+ slicedArray.forEach((owner) => {
+ bulkOperation.push({ index: { _index: "owners", _id: owner.id } });
+ bulkOperation.push(owner);
+ });
+ promiseQueue.push(bulkOperation);
+ }
+
+ //Vehicles
+ const vehiclesData = await gqlclient.request(`{
+ vehicles {
+ id
+ bodyshopid: shopid
+v_model_yr
+v_model_desc
+v_make_desc
+v_color
+v_vin
+plate_no
+ }
+ }
+ `);
+ for (let i = 0; i <= vehiclesData.vehicles.length / batchSize; i++) {
+ const slicedArray = vehiclesData.vehicles.slice(
+ i * batchSize,
+ i * batchSize + batchSize
+ );
+ const bulkOperation = [];
+ slicedArray.forEach((vehicle) => {
+ bulkOperation.push({ index: { _index: "vehicles", _id: vehicle.id } });
+ bulkOperation.push(vehicle);
+ });
+ promiseQueue.push(bulkOperation);
+ }
+
+ //payments
+ const paymentsData = await gqlclient.request(`{
+ payments {
+ id
+ amount
+ paymentnum
+ memo
+ transactionid
+ job {
+ id
+ ro_number
+ bodyshopid: shopid
+ }
+ }
+ }
+
+ `);
+ for (let i = 0; i <= paymentsData.payments.length / batchSize; i++) {
+ const slicedArray = paymentsData.payments.slice(
+ i * batchSize,
+ i * batchSize + batchSize
+ );
+ const bulkOperation = [];
+ slicedArray.forEach((payment) => {
+ bulkOperation.push({ index: { _index: "payments", _id: payment.id } });
+ bulkOperation.push({
+ ..._.omit(payment, ["job"]),
+ bodyshopid: payment.job.id,
+ });
+ });
+ promiseQueue.push(bulkOperation);
+ }
+
+ //bills
+ const billsData = await gqlclient.request(`{
+ bills {
+ id
+ total
+ invoice_number
+ date
+ vendor {
+ name
+ id
+ }
+ job {
+ ro_number
+ id
+ bodyshopid: shopid
+ }
+ }
+ }
+
+ `);
+ for (let i = 0; i <= billsData.bills.length / batchSize; i++) {
+ const slicedArray = billsData.bills.slice(
+ i * batchSize,
+ i * batchSize + batchSize
+ );
+ const bulkOperation = [];
+ slicedArray.forEach((bill) => {
+ bulkOperation.push({ index: { _index: "bills", _id: bill.id } });
+ bulkOperation.push({
+ ..._.omit(bill, ["job"]),
+ bodyshopid: bill.job.id,
+ });
+ });
+ promiseQueue.push(bulkOperation);
+ }
+
+ //Load the entire queue.
+ for (const queueItem of promiseQueue) {
+ const insertJobsBulk = await osClient.bulk({ body: queueItem });
+
+ console.log(
+ ` ${insertJobsBulk.body.items.length} Records inserted in ${insertJobsBulk.body.took}.`
+ );
+ if (insertJobsBulk.body.errors)
+ console.error("*** Error while inserting.");
+ }
+ } catch (error) {
+ console.log(error);
+ }
+}
+
+OpenSearchUpdateHandler();