Merge remote-tracking branch 'origin/feature/IO-2460-Node-18-Server' into rome/test

This commit is contained in:
Patrick Fic
2023-11-22 15:58:10 -08:00
26 changed files with 7680 additions and 5936 deletions

View File

@@ -8,13 +8,13 @@ orbs:
jobs: jobs:
api-deploy: api-deploy:
docker: docker:
- image: "cimg/base:stable" - image: cimg/node:18.18.2
steps: steps:
- checkout - checkout
- eb/setup - eb/setup
- run: - run:
command: | command: |
eb init imex-online-production-api -r ca-central-1 -p "Node.js 16 running on 64bit Amazon Linux 2" eb init imex-online-production-api -r ca-central-1 -p "Node.js 18 running on 64bit Amazon Linux 2"
eb status --verbose eb status --verbose
eb deploy eb deploy
eb status eb status

2
.gitignore vendored
View File

@@ -118,3 +118,5 @@ logs/oAuthClient-log.log
.node-persist/** .node-persist/**
/*.env.* /*.env.*
.idea/*
.idea

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
legacy-peer-deps=true

View File

@@ -89,8 +89,8 @@
"analyze": "source-map-explorer 'build/static/js/*.js'", "analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "craco start", "start": "craco start",
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build", "build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
"build:test": "env-cmd -f .env.test yarn run build", "build:test": "env-cmd -f .env.test npm run build",
"build-deploy:test": "yarn run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'", "build-deploy:test": "npm run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
"buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build", "buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
"test": "cypress open", "test": "cypress open",
"eject": "react-scripts eject", "eject": "react-scripts eject",

View File

@@ -2,10 +2,10 @@ import { useQuery } from "@apollo/client";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ContractJobsComponent from "./contract-jobs.component"; import ContractJobsComponent from "./contract-jobs.component";
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -15,6 +15,7 @@ export function ContractJobsContainer({ selectedJobState, bodyshop }) {
const { loading, error, data } = useQuery(QUERY_ALL_ACTIVE_JOBS, { const { loading, error, data } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: { variables: {
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open"], statuses: bodyshop.md_ro_statuses.active_statuses || ["Open"],
isConverted: true,
}, },
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",

View File

@@ -4,14 +4,14 @@ import queryString from "query-string";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link, useHistory, useLocation } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
import { setModalContext } from "../../redux/modals/modals.actions";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import ContractsFindModalContainer from "../contracts-find-modal/contracts-find-modal.container"; import ContractsFindModalContainer from "../contracts-find-modal/contracts-find-modal.container";
import { setModalContext } from "../../redux/modals/modals.actions";
import moment from "moment";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import moment from "moment";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({

View File

@@ -34,6 +34,18 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
{/* <FormFieldsChanged form={form} /> */} {/* <FormFieldsChanged form={form} /> */}
<LayoutFormRow header={t("courtesycars.labels.vehicle")}> <LayoutFormRow header={t("courtesycars.labels.vehicle")}>
<Form.Item
label={t("courtesycars.fields.year")}
name="year"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item <Form.Item
label={t("courtesycars.fields.make")} label={t("courtesycars.fields.make")}
name="make" name="make"
@@ -58,18 +70,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item
label={t("courtesycars.fields.year")}
name="year"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item <Form.Item
label={t("courtesycars.fields.plate")} label={t("courtesycars.fields.plate")}
name="plate" name="plate"
@@ -216,12 +216,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
<Form.Item <Form.Item
label={t("courtesycars.fields.nextservicekm")} label={t("courtesycars.fields.nextservicekm")}
name="nextservicekm" name="nextservicekm"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
@@ -229,12 +223,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
<Form.Item <Form.Item
label={t("courtesycars.fields.nextservicedate")} label={t("courtesycars.fields.nextservicedate")}
name="nextservicedate" name="nextservicedate"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
> >
<FormDatePicker /> <FormDatePicker />
</Form.Item> </Form.Item>
@@ -283,12 +271,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
<Form.Item <Form.Item
label={t("courtesycars.fields.registrationexpires")} label={t("courtesycars.fields.registrationexpires")}
name="registrationexpires" name="registrationexpires"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
> >
<FormDatePicker /> <FormDatePicker />
</Form.Item> </Form.Item>
@@ -330,9 +312,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
<FormDatePicker /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
shouldUpdate={(p, c) => shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}
p.insuranceexpires !== c.insuranceexpires
}
> >
{() => { {() => {
const expires = form.getFieldValue("insuranceexpires"); const expires = form.getFieldValue("insuranceexpires");

View File

@@ -174,6 +174,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
(c.year || "").toLowerCase().includes(searchText.toLowerCase()) || (c.year || "").toLowerCase().includes(searchText.toLowerCase()) ||
(c.make || "").toLowerCase().includes(searchText.toLowerCase()) || (c.make || "").toLowerCase().includes(searchText.toLowerCase()) ||
(c.model || "").toLowerCase().includes(searchText.toLowerCase()) || (c.model || "").toLowerCase().includes(searchText.toLowerCase()) ||
(c.plate || "").toLowerCase().includes(searchText.toLowerCase()) ||
(t(c.status) || "").toLowerCase().includes(searchText.toLowerCase()) (t(c.status) || "").toLowerCase().includes(searchText.toLowerCase())
) )
: courtesycars; : courtesycars;

View File

@@ -54,6 +54,7 @@ export default function GlobalSearchOs() {
job.v_make_desc || "" job.v_make_desc || ""
} ${job.v_model_desc || ""}`}</span> } ${job.v_model_desc || ""}`}</span>
<span>{`${job.clm_no || ""}`}</span> <span>{`${job.clm_no || ""}`}</span>
<span>{`${job.plate_no || ""}`}</span>
</Space> </Space>
</Link> </Link>
), ),

View File

@@ -1,6 +1,6 @@
import { useMutation } from "@apollo/client";
import { notification, Select, Space } from "antd"; import { notification, Select, Space } from "antd";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -56,8 +56,10 @@ export function JobLineLocationPopup({ bodyshop, jobline, disabled }) {
<LoadingSpinner loading={loading}> <LoadingSpinner loading={loading}>
<Select <Select
autoFocus autoFocus
allowClear
dropdownMatchSelectWidth={100} dropdownMatchSelectWidth={100}
value={location} value={location}
onClear={() => setLocation(null)}
onSelect={handleChange} onSelect={handleChange}
onBlur={handleSave} onBlur={handleSave}
> >

View File

@@ -1,8 +1,8 @@
import { import {
BranchesOutlined,
ExclamationCircleFilled, ExclamationCircleFilled,
PauseCircleOutlined, PauseCircleOutlined,
SyncOutlined, SyncOutlined,
BranchesOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd"; import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
@@ -14,8 +14,8 @@ import { Link, useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries"; import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { onlyUnique } from "../../utils/arrayHelper";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { onlyUnique } from "../../utils/arrayHelper";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component";
@@ -46,6 +46,7 @@ export function JobsReadyList({ bodyshop }) {
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: { variables: {
statuses: readyStatuses, statuses: readyStatuses,
isConverted: true,
}, },
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",

View File

@@ -25,6 +25,7 @@ export function TechLookupJobsList({ bodyshop }) {
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: { variables: {
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
isConverted: true,
}, },
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",

View File

@@ -1,9 +1,9 @@
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
export const QUERY_ALL_ACTIVE_JOBS = gql` export const QUERY_ALL_ACTIVE_JOBS = gql`
query QUERY_ALL_ACTIVE_JOBS($statuses: [String!]!) { query QUERY_ALL_ACTIVE_JOBS($statuses: [String!]!, $isConverted: Boolean) {
jobs( jobs(
where: { status: { _in: $statuses } } where: { status: { _in: $statuses }, converted: { _eq: $isConverted } }
order_by: { created_at: desc } order_by: { created_at: desc }
) { ) {
iouparent iouparent
@@ -432,7 +432,6 @@ export const QUERY_JOB_COSTING_DETAILS = gql`
status status
ca_bc_pvrt ca_bc_pvrt
ca_customer_gst ca_customer_gst
joblines(where: { removed: { _eq: false } }) { joblines(where: { removed: { _eq: false } }) {
id id
unq_seq unq_seq
@@ -1058,7 +1057,6 @@ export const QUERY_TECH_JOB_DETAILS = gql`
actual_completion actual_completion
actual_delivery actual_delivery
actual_in actual_in
id id
ins_co_nm ins_co_nm
clm_no clm_no
@@ -1643,7 +1641,6 @@ export const QUERY_ALL_JOB_FIELDS = gql`
clm_title clm_title
clm_total clm_total
clm_zip clm_zip
cust_pr cust_pr
ded_amt ded_amt
ded_status ded_status
@@ -1726,7 +1723,6 @@ export const QUERY_ALL_JOB_FIELDS = gql`
parts_tax_rates parts_tax_rates
pay_amt pay_amt
pay_chknm pay_chknm
pay_type pay_type
payee_nms payee_nms
plate_no plate_no
@@ -2134,6 +2130,7 @@ export const QUERY_JOB_EXPORT_DMS = gql`
} }
} }
`; `;
export const QUERY_RELATED_ROS = gql` export const QUERY_RELATED_ROS = gql`
query QUERY_RELATED_ROS($jobid: uuid!) { query QUERY_RELATED_ROS($jobid: uuid!) {
relatedjobs( relatedjobs(
@@ -2155,6 +2152,7 @@ export const QUERY_RELATED_ROS = gql`
} }
} }
`; `;
export const INSERT_RELATED_ROS = gql` export const INSERT_RELATED_ROS = gql`
mutation INSERT_RELATED_ROS($relationship: relatedjobs_insert_input!) { mutation INSERT_RELATED_ROS($relationship: relatedjobs_insert_input!) {
insert_relatedjobs_one(object: $relationship) { insert_relatedjobs_one(object: $relationship) {
@@ -2172,6 +2170,7 @@ export const INSERT_RELATED_ROS = gql`
} }
} }
`; `;
export const DELETE_RELATED_RO = gql` export const DELETE_RELATED_RO = gql`
mutation DELETE_RELATED_RO($relationshipid: uuid!) { mutation DELETE_RELATED_RO($relationshipid: uuid!) {
delete_relatedjobs_by_pk(id: $relationshipid) { delete_relatedjobs_by_pk(id: $relationshipid) {
@@ -2179,6 +2178,7 @@ export const DELETE_RELATED_RO = gql`
} }
} }
`; `;
export const GET_JOB_LINE_ORDERS = gql` export const GET_JOB_LINE_ORDERS = gql`
query GET_JOB_LINE_ORDERS($joblineid: uuid!) { query GET_JOB_LINE_ORDERS($joblineid: uuid!) {
billlines(where: { joblineid: { _eq: $joblineid } }) { billlines(where: { joblineid: { _eq: $joblineid } }) {

View File

@@ -4089,6 +4089,7 @@
- name: event-secret - name: event-secret
value_from_env: EVENT_SECRET value_from_env: EVENT_SECRET
request_transform: request_transform:
method: POST
query_params: {} query_params: {}
template_engine: Kriti template_engine: Kriti
url: '{{$base_url}}/job/statustransition' url: '{{$base_url}}/job/statustransition'

View File

@@ -0,0 +1,38 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE OR REPLACE FUNCTION public.search_cccontracts(search text)
-- RETURNS SETOF cccontracts
-- LANGUAGE plpgsql
-- STABLE
-- AS $function$
-- BEGIN
-- IF search = '' THEN
-- RETURN query
-- SELECT
-- *
-- FROM
-- cccontracts c;
-- ELSE
-- RETURN query
-- SELECT
-- contracts.*
-- FROM
-- courtesycars cars,
-- jobs jobs,
-- cccontracts contracts
-- WHERE (jobs.ro_number ILIKE '%' || search || '%'
-- OR jobs.ownr_fn ILIKE '%' || search || '%'
-- OR jobs.ownr_ln ILIKE '%' || search || '%'
-- OR jobs.ownr_co_nm ILIKE '%' || search || '%'
-- OR (cast(contracts.agreementnumber as text)) ILIKE '%' || search || '%'
-- OR contracts.driver_fn ILIKE '%' || search || '%'
-- OR contracts.driver_ln ILIKE '%' || search || '%'
-- OR cars.fleetnumber ILIKE '%' || search || '%'
-- OR cars.make ILIKE '%' || search || '%'
-- OR cars.model ILIKE '%' || search || '%'
-- OR cars.plate ILIKE '%' || search || '%')
-- AND contracts.jobid = jobs.id
-- AND contracts.courtesycarid = cars.id;
-- END IF;
-- END
-- $function$;

View File

@@ -0,0 +1,36 @@
CREATE OR REPLACE FUNCTION public.search_cccontracts(search text)
RETURNS SETOF cccontracts
LANGUAGE plpgsql
STABLE
AS $function$
BEGIN
IF search = '' THEN
RETURN query
SELECT
*
FROM
cccontracts c;
ELSE
RETURN query
SELECT
contracts.*
FROM
courtesycars cars,
jobs jobs,
cccontracts contracts
WHERE (jobs.ro_number ILIKE '%' || search || '%'
OR jobs.ownr_fn ILIKE '%' || search || '%'
OR jobs.ownr_ln ILIKE '%' || search || '%'
OR jobs.ownr_co_nm ILIKE '%' || search || '%'
OR (cast(contracts.agreementnumber as text)) ILIKE '%' || search || '%'
OR contracts.driver_fn ILIKE '%' || search || '%'
OR contracts.driver_ln ILIKE '%' || search || '%'
OR cars.fleetnumber ILIKE '%' || search || '%'
OR cars.make ILIKE '%' || search || '%'
OR cars.model ILIKE '%' || search || '%'
OR cars.plate ILIKE '%' || search || '%')
AND contracts.jobid = jobs.id
AND contracts.courtesycarid = cars.id;
END IF;
END
$function$;

7156
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,65 +3,64 @@
"version": "0.0.1", "version": "0.0.1",
"license": "UNLICENSED", "license": "UNLICENSED",
"engines": { "engines": {
"node": ">=16.0.0", "node": ">=18.0.0",
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"scripts": { "scripts": {
"setup": "rm -rf node_modules && yarn && cd client && rm -rf node_modules && yarn", "setup": "rm -rf node_modules && npm i && cd client && rm -rf node_modules && npm i",
"admin": "cd admin && yarn start", "admin": "cd admin && npm start",
"client": "cd client && yarn start", "client": "cd client && npm start",
"server": "nodemon server.js", "server": "nodemon server.js",
"build": "cd client && yarn run build", "build": "cd client && npm run build",
"dev": "concurrently --kill-others-on-fail \"yarn run server\" \"yarn run client\"", "dev": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\"",
"deva": "concurrently --kill-others-on-fail \"yarn run server\" \"yarn run client\" \"yarn run admin\"", "deva": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\" \"npm run admin\"",
"start": "node server.js" "start": "node server.js"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-secrets-manager": "^3.388.0", "@aws-sdk/client-secrets-manager": "^3.454.0",
"@aws-sdk/credential-provider-node": "^3.319.0", "@aws-sdk/client-ses": "^3.454.0",
"@opensearch-project/opensearch": "^2.2.1", "@aws-sdk/credential-provider-node": "^3.451.0",
"aws-sdk": "^2.1326.0", "@opensearch-project/opensearch": "^2.4.0",
"aws4": "^1.12.0", "aws4": "^1.12.0",
"axios": "^0.27.2", "axios": "^1.6.2",
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"cloudinary": "^1.34.0", "cloudinary": "^1.41.0",
"compression": "^1.7.4", "compression": "^1.7.4",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"cors": "2.8.5", "cors": "2.8.5",
"csrf": "^3.1.0", "csrf": "^3.1.0",
"dinero.js": "^1.9.1", "dinero.js": "^1.9.1",
"dotenv": "16.0.3", "dotenv": "^16.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"firebase-admin": "^11.5.0", "firebase-admin": "^11.11.0",
"graphql": "^16.6.0", "graphql": "^16.8.1",
"graphql-request": "^4.2.0", "graphql-request": "^6.1.0",
"graylog2": "^0.2.1", "graylog2": "^0.2.1",
"inline-css": "^4.0.2", "inline-css": "^4.0.2",
"intuit-oauth": "^4.0.0", "intuit-oauth": "^4.0.0",
"json-2-csv": "^3.19.0", "json-2-csv": "^5.0.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.29.4", "moment": "^2.29.4",
"moment-timezone": "^0.5.41", "moment-timezone": "^0.5.41",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"node-mailjet": "^6.0.2", "node-mailjet": "^6.0.4",
"node-persist": "^3.1.3", "node-persist": "^3.1.3",
"node-quickbooks": "^2.0.41", "node-quickbooks": "^2.0.43",
"nodemailer": "^6.9.1", "nodemailer": "^6.9.7",
"phone": "^3.1.35", "phone": "^3.1.41",
"query-string": "^7.1.1",
"recursive-diff": "^1.0.9", "recursive-diff": "^1.0.9",
"soap": "^1.0.0", "soap": "^1.0.0",
"socket.io": "^4.6.1", "socket.io": "^4.7.2",
"ssh2-sftp-client": "^9.0.4", "ssh2-sftp-client": "^9.1.0",
"stripe": "^9.15.0", "stripe": "^14.5.0",
"twilio": "^4.8.0", "twilio": "^4.19.0",
"uuid": "^9.0.0", "uuid": "^9.0.1",
"xml2js": "^0.4.23", "xml2js": "^0.6.2",
"xmlbuilder2": "^3.0.2" "xmlbuilder2": "^3.1.1"
}, },
"devDependencies": { "devDependencies": {
"concurrently": "^7.3.0", "concurrently": "^8.2.2",
"source-map-explorer": "^2.5.2" "source-map-explorer": "^2.5.2"
} }
} }

View File

@@ -5,8 +5,8 @@ const path = require("path");
const compression = require("compression"); const compression = require("compression");
const twilio = require("twilio"); const twilio = require("twilio");
const logger = require("./server/utils/logger"); const logger = require("./server/utils/logger");
var fb = require("./server/firebase/firebase-handler"); const fb = require("./server/firebase/firebase-handler");
var cookieParser = require("cookie-parser"); const cookieParser = require("cookie-parser");
const multer = require("multer"); const multer = require("multer");
const upload = multer(); const upload = multer();
//var enforce = require("express-sslify"); //var enforce = require("express-sslify");

View File

@@ -9,7 +9,7 @@ const logger = require("../../utils/logger");
const OAuthClient = require("intuit-oauth"); const OAuthClient = require("intuit-oauth");
const client = require("../../graphql-client/graphql-client").client; const client = require("../../graphql-client/graphql-client").client;
const queries = require("../../graphql-client/queries"); const queries = require("../../graphql-client/queries");
const queryString = require("query-string"); const {parse, stringify} = require("querystring");
const oauthClient = new OAuthClient({ const oauthClient = new OAuthClient({
clientId: process.env.QBO_CLIENT_ID, clientId: process.env.QBO_CLIENT_ID,
@@ -30,7 +30,8 @@ if (process.env.NODE_ENV === "production") {
} }
exports.default = async (req, res) => { exports.default = async (req, res) => {
const params = queryString.parse(req.url.split("?").reverse()[0]); const queryString = req.url.split("?").reverse()[0];
const params = parse(queryString);
try { try {
logger.log("qbo-callback-create-token", "DEBUG", params.state, null, null); logger.log("qbo-callback-create-token", "DEBUG", params.state, null, null);
const authResponse = await oauthClient.createToken(req.url); const authResponse = await oauthClient.createToken(req.url);
@@ -58,7 +59,7 @@ exports.default = async (req, res) => {
); );
res.redirect( res.redirect(
`${url}/manage/accounting/qbo?${queryString.stringify(params)}` `${url}/manage/accounting/qbo?${stringify(params)}`
); );
} }
} catch (e) { } catch (e) {

View File

@@ -7,12 +7,16 @@ require("dotenv").config({
}); });
const axios = require("axios"); const axios = require("axios");
let nodemailer = require("nodemailer"); let nodemailer = require("nodemailer");
let aws = require("aws-sdk"); let aws = require("@aws-sdk/client-ses");
let { defaultProvider } = require("@aws-sdk/credential-provider-node");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
const client = require("../graphql-client/graphql-client").client; const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries"); const queries = require("../graphql-client/queries");
const ses = new aws.SES({ const ses = new aws.SES({
// The key apiVersion is no longer supported in v3, and can be removed.
// @deprecated The client uses the "latest" apiVersion.
apiVersion: "latest", apiVersion: "latest",
region: "us-east-2", region: "us-east-2",
@@ -48,7 +52,6 @@ exports.sendServerEmail = async function ({ subject, text }) {
} catch (error) { } catch (error) {
console.log(error); console.log(error);
logger.log("server-email-failure", "error", null, null, error); logger.log("server-email-failure", "error", null, null, error);
res.status(500).json(error);
} }
}; };
exports.sendTaskEmail = async function ({ to, subject, text, attachments }) { exports.sendTaskEmail = async function ({ to, subject, text, attachments }) {

View File

@@ -1,6 +1,6 @@
"use strict"; "use strict";
const AWS = require("aws-sdk"); const awsSecretManager = require("@aws-sdk/client-secrets-manager");
class SecretsManager { class SecretsManager {
/** /**
@@ -8,11 +8,10 @@ class SecretsManager {
*/ */
static async getSecret(secretName, region) { static async getSecret(secretName, region) {
const config = { region: region }; const config = { region: region };
let secretsManager = new AWS.SecretsManager(config); let secretsManager = new awsSecretManager.SecretsManager(config);
try { try {
let secretValue = await secretsManager let secretValue = await secretsManager
.getSecretValue({ SecretId: secretName }) .getSecretValue({ SecretId: secretName });
.promise();
if ("SecretString" in secretValue) { if ("SecretString" in secretValue) {
return secretValue.SecretString; return secretValue.SecretString;
} else { } else {

View File

@@ -1,121 +1,122 @@
const Dinero = require("dinero.js");
const queries = require("../graphql-client/queries"); const queries = require("../graphql-client/queries");
//const client = require("../graphql-client/graphql-client").client; const {pick} = require("lodash");
const _ = require("lodash");
const GraphQLClient = require("graphql-request").GraphQLClient; const GraphQLClient = require("graphql-request").GraphQLClient;
const logger = require("../utils/logger"); const logger = require("../utils/logger");
//const client = require("../graphql-client/graphql-client").client;
const path = require("path"); const path = require("path");
const client = require("../graphql-client/graphql-client").client; const client = require("../graphql-client/graphql-client").client;
require("dotenv").config({ require("dotenv").config({
path: path.resolve( path: path.resolve(
process.cwd(), process.cwd(),
`.env.${process.env.NODE_ENV || "development"}` `.env.${process.env.NODE_ENV || "development"}`
), ),
}); });
const { Client, Connection } = require("@opensearch-project/opensearch"); const {Client, Connection} = require("@opensearch-project/opensearch");
const { defaultProvider } = require("@aws-sdk/credential-provider-node"); const {defaultProvider} = require("@aws-sdk/credential-provider-node");
const aws4 = require("aws4"); const aws4 = require("aws4");
const { gql } = require("graphql-request"); const {gql} = require("graphql-request");
var host = process.env.OPEN_SEARCH_HOST; var host = process.env.OPEN_SEARCH_HOST;
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); 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 { return {
Connection: AmazonConnection, Connection: AmazonConnection,
}; };
}; };
const getClient = async () => { const getClient = async () => {
const credentials = await defaultProvider()(); const credentials = await defaultProvider()();
return new Client({ return new Client({
...createAwsConnector(credentials, "ca-central-1"), ...createAwsConnector(credentials, "ca-central-1"),
node: host, node: host,
}); });
}; };
async function OpenSearchUpdateHandler(req, res) { async function OpenSearchUpdateHandler(req, res) {
if (req.headers["event-secret"] !== process.env.EVENT_SECRET) { if (req.headers["event-secret"] !== process.env.EVENT_SECRET) {
res.status(401).send("Unauthorized"); res.status(401).send("Unauthorized");
return; return;
} }
try { try {
var osClient = await getClient(); var osClient = await getClient();
// const osClient = new Client({ // const osClient = new Client({
// node: `https://imex:<password>@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com/`, // node: `https://imex:<password>@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com/`,
// }); // });
if (req.body.event.op === "DELETE") { if (req.body.event.op === "DELETE") {
let response; let response;
response = await osClient.delete({ response = await osClient.delete({
id: req.body.event.data.old.id, id: req.body.event.data.old.id,
index: req.body.table.name, index: req.body.table.name,
}); });
res.status(200).json(response.body); res.status(200).json(response.body);
} else { } else {
let document; let document;
switch (req.body.table.name) { switch (req.body.table.name) {
case "jobs": case "jobs":
document = _.pick(req.body.event.data.new, [ document = pick(req.body.event.data.new, [
"id", "id",
"bodyshopid", "bodyshopid",
"clm_no", "clm_no",
"clm_total", "clm_total",
"comment", "comment",
"ins_co_nm", "ins_co_nm",
"owner_owing", "owner_owing",
"ownr_co_nm", "ownr_co_nm",
"ownr_fn", "ownr_fn",
"ownr_ln", "ownr_ln",
"ownr_ph1", "ownr_ph1",
"ownr_ph2", "ownr_ph2",
"plate_no", "plate_no",
"ro_number", "ro_number",
"status", "status",
"v_model_yr", "v_model_yr",
"v_make_desc", "v_make_desc",
"v_model_desc", "v_model_desc",
"v_vin", "v_vin",
]); ]);
document.bodyshopid = req.body.event.data.new.shopid; document.bodyshopid = req.body.event.data.new.shopid;
break; break;
case "vehicles": case "vehicles":
document = _.pick(req.body.event.data.new, [ document = pick(req.body.event.data.new, [
"id", "id",
"v_model_yr", "v_model_yr",
"v_model_desc", "v_model_desc",
"v_make_desc", "v_make_desc",
"v_color", "v_color",
"v_vin", "v_vin",
"plate_no", "plate_no",
]); ]);
document.bodyshopid = req.body.event.data.new.shopid; document.bodyshopid = req.body.event.data.new.shopid;
break; break;
case "owners": case "owners":
document = _.pick(req.body.event.data.new, [ document = pick(req.body.event.data.new, [
"id", "id",
"ownr_fn", "ownr_fn",
"ownr_ln", "ownr_ln",
"ownr_co_nm", "ownr_co_nm",
"ownr_ph1", "ownr_ph1",
"ownr_ph2", "ownr_ph2",
]); ]);
document.bodyshopid = req.body.event.data.new.shopid; document.bodyshopid = req.body.event.data.new.shopid;
break; break;
case "bills": case "bills":
const bill = await client.request( const bill = await client.request(
`query ADMIN_GET_BILL_BY_ID($billId: uuid!) { `query ADMIN_GET_BILL_BY_ID($billId: uuid!) {
bills_by_pk(id: $billId) { bills_by_pk(id: $billId) {
id id
job { job {
@@ -130,26 +131,26 @@ async function OpenSearchUpdateHandler(req, res) {
} }
} }
`, `,
{ billId: req.body.event.data.new.id } {billId: req.body.event.data.new.id}
); );
document = { document = {
..._.pick(req.body.event.data.new, [ ...pick(req.body.event.data.new, [
"id", "id",
"date", "date",
"exported", "exported",
"exported_at", "exported_at",
"invoice_number", "invoice_number",
"is_credit_memo", "is_credit_memo",
"total", "total",
]), ]),
...bill.bills_by_pk, ...bill.bills_by_pk,
bodyshopid: bill.bills_by_pk.job.shopid, bodyshopid: bill.bills_by_pk.job.shopid,
}; };
break; break;
case "payments": case "payments":
//Query to get the job and RO number //Query to get the job and RO number
const payment = await client.request( const payment = await client.request(
`query ADMIN_GET_PAYMENT_BY_ID($paymentId: uuid!) { `query ADMIN_GET_PAYMENT_BY_ID($paymentId: uuid!) {
payments_by_pk(id: $paymentId) { payments_by_pk(id: $paymentId) {
id id
job { job {
@@ -170,154 +171,156 @@ async function OpenSearchUpdateHandler(req, res) {
} }
} }
`, `,
{ paymentId: req.body.event.data.new.id } {paymentId: req.body.event.data.new.id}
); );
document = { document = {
..._.pick(req.body.event.data.new, [ ...pick(req.body.event.data.new, [
"id", "id",
"amount", "amount",
"created_at", "created_at",
"date", "date",
"exportedat", "exportedat",
"memo", "memo",
"payer", "payer",
"paymentnum", "paymentnum",
"transactionid", "transactionid",
"type", "type",
]), ]),
...payment.payments_by_pk, ...payment.payments_by_pk,
bodyshopid: payment.payments_by_pk.job.shopid, bodyshopid: payment.payments_by_pk.job.shopid,
}; };
break; break;
} }
const payload = { const payload = {
id: req.body.event.data.new.id, id: req.body.event.data.new.id,
index: req.body.table.name, index: req.body.table.name,
body: document, body: document,
}; };
let response; let response;
response = await osClient.index(payload); response = await osClient.index(payload);
console.log(response.body); console.log(response.body);
res.status(200).json(response.body); res.status(200).json(response.body);
}
} catch (error) {
res.status(400).json(JSON.stringify(error));
} finally {
} }
} catch (error) {
res.status(400).json(JSON.stringify(error));
} finally {
}
} }
async function OpensearchSearchHandler(req, res) { async function OpenSearchSearchHandler(req, res) {
try { try {
const { search, bodyshopid, index } = req.body; const {search, bodyshopid, index} = req.body;
if (!req.user) {
res.sendStatus(401);
return;
}
logger.log("os-search", "DEBUG", req.user.email, null, {
search,
});
const BearerToken = req.headers.authorization; if (!req.user) {
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { res.sendStatus(401);
headers: { return;
Authorization: BearerToken, }
},
});
const assocs = await client logger.log("os-search", "DEBUG", req.user.email, null, {
.setHeaders({ Authorization: BearerToken }) search,
.request(queries.ACTIVE_SHOP_BY_USER, { });
user: req.user.email,
});
if (assocs.length === 0) { const BearerToken = req.headers.authorization;
res.sendStatus(401); const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
} headers: {
Authorization: BearerToken,
var osClient = await getClient();
const { body } = await osClient.search({
...(index ? { index } : {}),
body: {
size: 100,
query: {
bool: {
must: [
{
match: {
bodyshopid: assocs.associations[0].shopid,
},
},
{
bool: {
should: [
{
multi_match: {
query: search,
type: "cross_fields",
fields: ["*ownr_fn", "*ownr_ln"],
},
},
{
multi_match: {
query: search,
type: "most_fields",
fields: [
"*v_model_yr",
"*v_make_desc^2",
"*v_model_desc^3",
],
},
},
{
query_string: {
query: `*${search}*`,
fields: [
"*ro_number^20",
"*clm_no^14",
"*v_vin^12",
"*plate_no^12",
"*ownr_ln^10",
"transactionid^10",
"paymentnum^10",
"invoice_number^10",
"*ownr_fn^8",
"*ownr_co_nm^8",
"*ownr_ph1^8",
"*ownr_ph2^8",
"*",
],
},
},
],
minimum_should_match: 1,
},
},
],
},
},
sort: [
{
_score: {
order: "desc",
}, },
}, });
],
},
});
res.json(body); const assocs = await client
} catch (error) { .setHeaders({Authorization: BearerToken})
console.log(error); .request(queries.ACTIVE_SHOP_BY_USER, {
logger.log("os-search-error", "ERROR", req.user.email, null, { user: req.user.email,
error: JSON.stringify(error), });
});
res.status(400).json(error); if (assocs.length === 0) {
} finally { res.sendStatus(401);
} }
const osClient = await getClient();
const {body} = await osClient.search({
...(index ? {index} : {}),
body: {
size: 100,
query: {
bool: {
must: [
{
match: {
bodyshopid: assocs.associations[0].shopid,
},
},
{
bool: {
should: [
{
multi_match: {
query: search,
type: "cross_fields",
fields: ["*ownr_fn", "*ownr_ln"],
},
},
{
multi_match: {
query: search,
type: "most_fields",
fields: [
"*v_model_yr",
"*v_make_desc^2",
"*v_model_desc^3",
],
},
},
{
query_string: {
query: `*${search}*`,
// Weighted Fields
fields: [
"*ro_number^20",
"*clm_no^14",
"*v_vin^12",
"*plate_no^12",
"*ownr_ln^10",
"transactionid^10",
"paymentnum^10",
"invoice_number^10",
"*ownr_fn^8",
"*ownr_co_nm^8",
"*ownr_ph1^8",
"*ownr_ph2^8",
"*",
],
},
},
],
minimum_should_match: 1,
},
},
],
},
},
sort: [
{
_score: {
order: "desc",
},
},
],
},
});
res.json(body);
} catch (error) {
console.log(error);
logger.log("os-search-error", "ERROR", req.user.email, null, {
error: JSON.stringify(error),
});
res.status(400).json(error);
} finally {
}
} }
exports.handler = OpenSearchUpdateHandler; exports.handler = OpenSearchUpdateHandler;
exports.search = OpensearchSearchHandler; exports.search = OpenSearchSearchHandler;

View File

@@ -1,79 +1,46 @@
const GraphQLClient = require("graphql-request").GraphQLClient;
const path = require("path"); const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
require("dotenv").config({ require("dotenv").config({
path: path.resolve( path: path.resolve(
process.cwd(), process.cwd(),
`.env.${process.env.NODE_ENV || "development"}` `.env.${process.env.NODE_ENV || "development"}`
), ),
}); });
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
exports.payment = async (req, res) => { const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const { amount, stripe_acct_id } = req.body;
try { const processor = async (req, res) => {
await stripe.paymentIntents const {amount, stripe_acct_id} = req.body;
.create(
{ try {
payment_method_types: ["card"], await stripe.paymentIntents
amount: amount, .create(
currency: "cad", {
application_fee_amount: 50, payment_method_types: ["card"],
}, amount: amount,
{ currency: "cad",
stripeAccount: stripe_acct_id, application_fee_amount: 50,
} },
) {
.then(function (paymentIntent) { stripeAccount: stripe_acct_id,
try { }
return res.send({ )
clientSecret: paymentIntent.client_secret, .then(function (paymentIntent) {
}); try {
} catch (err) { return res.send({
return res.status(500).send({ clientSecret: paymentIntent.client_secret,
error: err.message, });
}); } catch (err) {
} return res.status(500).send({
}); error: err.message,
} catch (error) { });
console.log("error", error); }
res.status(400).send(error); });
} } catch (error) {
console.log("error", error);
res.status(400).send(error);
}
}; };
exports.mobile_payment = async (req, res) => { exports.payment = processor;
const { amount, stripe_acct_id } = req.body; exports.mobile_payment = processor;
try {
await stripe.paymentIntents
.create(
{
//Pull the amounts from the payment request.
payment_method_types: ["card"],
amount: amount,
currency: "cad",
application_fee_amount: 50,
},
{
stripeAccount: stripe_acct_id,
}
)
.then(function (paymentIntent) {
try {
return res.send({
clientSecret: paymentIntent.client_secret,
});
} catch (err) {
return res.status(500).send({
error: err.message,
});
}
});
} catch (error) {
console.log("error", error);
res.status(400).send(error);
}
};

View File

@@ -1,92 +1,84 @@
const path = require("path"); const path = require("path");
require("dotenv").config({ require("dotenv").config({
path: path.resolve( path: path.resolve(
process.cwd(), process.cwd(),
`.env.${process.env.NODE_ENV || "development"}` `.env.${process.env.NODE_ENV || "development"}`
), ),
}); });
const axios = require("axios");
const client = require("../graphql-client/graphql-client").client; const client = require("../graphql-client/graphql-client").client;
const emailer = require("../email/sendemail"); const emailer = require("../email/sendemail");
const logger = require("../utils/logger");
const moment = require("moment-timezone"); const moment = require("moment-timezone");
const converter = require("json-2-csv");
exports.taskHandler = async (req, res) => { exports.taskHandler = async (req, res) => {
try { try {
const { bodyshopid, query, variables, text, to, subject, timezone } = const {bodyshopid, query, variables, text, to, subject, timezone} = req.body;
req.body;
//Run the query
//Check the variables to see if they are an object. //Check the variables to see if they are an object.
Object.keys(variables).forEach((key) => { Object.keys(variables).forEach((key) => {
if (typeof variables[key] === "object") { if (typeof variables[key] === "object") {
if (variables[key].function) { if (variables[key].function) {
variables[key] = functionMapper(variables[key].function, timezone); variables[key] = functionMapper(variables[key].function, timezone);
} }
} }
}); });
const response = await client.request(query, variables); const response = await client.request(query, variables);
//Massage the data const rootElement = response[Object.keys(response)[0]]; //This element should always be an array.
//Send the email
const rootElement = response[Object.keys(response)[0]]; //This element shoudl always be an array. const csv = converter.json2csv(rootElement, {emptyFieldValue: ""});
let converter = require("json-2-csv");
converter.json2csv(
rootElement,
(err, csv) => {
if (err) {
res.status(500).json(err);
}
emailer.sendTaskEmail({ emailer.sendTaskEmail({
to, to,
subject, subject,
text, text,
attachments: [{ filename: "query.csv", content: csv }], attachments: [{filename: "query.csv", content: csv}],
}).catch(err => {
console.error('Errors sending CSV Email.')
}); });
res.status(200).send(csv);
}, return res.status(200).send(csv);
{ emptyFieldValue: "" } } catch (error) {
); res.status(500).json({error: error.message, stack: error.stackTrace});
} catch (error) { }
res.status(500).json({ error: error.message, stack: error.stackTrace });
}
}; };
const isoformat = "YYYY-MM-DD"; const isoFormat = "YYYY-MM-DD";
function functionMapper(f, timezone) {
switch (f) {
case "date.today":
return moment().tz(timezone).format(isoformat);
case "date.now":
return moment().tz(timezone);
case "date.yesterday":
return moment().tz(timezone).subtract(1, "day").format(isoformat);
case "date.3daysago":
return moment().tz(timezone).subtract(3, "days").format(isoformat);
case "date.7daysago":
return moment().tz(timezone).subtract(7, "days").format(isoformat);
case "date.tomorrow":
return moment().tz(timezone).add(1, "day").format(isoformat);
case "date.3daysfromnow":
return moment().tz(timezone).add(3, "days").format(isoformat);
case "date.7daysfromnow":
return moment().tz(timezone).add(7, "days").format(isoformat);
case "date.yesterdaytz":
return moment().tz(timezone).subtract(1, "day");
case "date.3daysagotz":
return moment().tz(timezone).subtract(3, "days");
case "date.7daysagotz":
return moment().tz(timezone).subtract(7, "days");
case "date.tomorrowtz":
return moment().tz(timezone).add(1, "day");
case "date.3daysfromnowtz":
return moment().tz(timezone).add(3, "days");
case "date.7daysfromnowtz":
return moment().tz(timezone).add(7, "days");
case "date.now": function functionMapper(f, timezone) {
return moment().tz(timezone); switch (f) {
default: case "date.today":
return f; return moment().tz(timezone).format(isoFormat);
} case "date.now":
return moment().tz(timezone);
case "date.yesterday":
return moment().tz(timezone).subtract(1, "day").format(isoFormat);
case "date.3daysago":
return moment().tz(timezone).subtract(3, "days").format(isoFormat);
case "date.7daysago":
return moment().tz(timezone).subtract(7, "days").format(isoFormat);
case "date.tomorrow":
return moment().tz(timezone).add(1, "day").format(isoFormat);
case "date.3daysfromnow":
return moment().tz(timezone).add(3, "days").format(isoFormat);
case "date.7daysfromnow":
return moment().tz(timezone).add(7, "days").format(isoFormat);
case "date.yesterdaytz":
return moment().tz(timezone).subtract(1, "day");
case "date.3daysagotz":
return moment().tz(timezone).subtract(3, "days");
case "date.7daysagotz":
return moment().tz(timezone).subtract(7, "days");
case "date.tomorrowtz":
return moment().tz(timezone).add(1, "day");
case "date.3daysfromnowtz":
return moment().tz(timezone).add(3, "days");
case "date.7daysfromnowtz":
return moment().tz(timezone).add(7, "days");
case "date.now":
return moment().tz(timezone);
default:
return f;
}
} }

5441
yarn.lock

File diff suppressed because it is too large Load Diff