@@ -2,9 +2,8 @@ version: 2.1
|
|||||||
orbs:
|
orbs:
|
||||||
#snyk: snyk/snyk@0.0.8
|
#snyk: snyk/snyk@0.0.8
|
||||||
#cypress: cypress-io/cypress@1.23.0
|
#cypress: cypress-io/cypress@1.23.0
|
||||||
aws-s3: circleci/aws-s3@2.0.0
|
aws-s3: circleci/aws-s3@4.0.0
|
||||||
eb: circleci/aws-elastic-beanstalk@1.0.2
|
eb: circleci/aws-elastic-beanstalk@2.0.1
|
||||||
jira: circleci/jira@1.3.1
|
|
||||||
jobs:
|
jobs:
|
||||||
api-deploy:
|
api-deploy:
|
||||||
docker:
|
docker:
|
||||||
@@ -18,7 +17,6 @@ jobs:
|
|||||||
eb status --verbose
|
eb status --verbose
|
||||||
eb deploy
|
eb deploy
|
||||||
eb status
|
eb status
|
||||||
- jira/notify
|
|
||||||
|
|
||||||
hasura-migrate:
|
hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
@@ -48,31 +46,21 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
path: ~/repo
|
path: ~/repo
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
name: Restore Yarn Package Cache
|
|
||||||
keys:
|
|
||||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
command: npm i
|
||||||
- save_cache:
|
|
||||||
name: Save Yarn Package Cache
|
|
||||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
paths:
|
|
||||||
- ~/.cache/yarn
|
|
||||||
|
|
||||||
- run: yarn run build
|
- run: npm run build
|
||||||
|
|
||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
to: "s3://imex-online-production/"
|
to: "s3://imex-online-production/"
|
||||||
- jira/notify
|
arguments: "--exclude '*.map'"
|
||||||
|
|
||||||
app-beta-build:
|
app-beta-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:18.18.2
|
||||||
resource_class: large
|
resource_class: xlarge
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -87,7 +75,7 @@ jobs:
|
|||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
to: "s3://imex-online-beta/"
|
to: "s3://imex-online-beta/"
|
||||||
- jira/notify
|
arguments: "--exclude '*.map'"
|
||||||
|
|
||||||
test-hasura-migrate:
|
test-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
@@ -118,31 +106,21 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
path: ~/repo
|
path: ~/repo
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
name: Restore Yarn Package Cache
|
|
||||||
keys:
|
|
||||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
command: npm i
|
||||||
- save_cache:
|
|
||||||
name: Save Yarn Package Cache
|
|
||||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
paths:
|
|
||||||
- ~/.cache/yarn
|
|
||||||
|
|
||||||
- run: yarn run build:test
|
- run: npm run build:test
|
||||||
|
|
||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
to: "s3://imex-online-test/"
|
to: "s3://imex-online-test/"
|
||||||
- jira/notify
|
arguments: "--exclude '*.map'"
|
||||||
|
|
||||||
test-app-beta-build:
|
test-app-beta-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:18.18.2
|
||||||
resource_class: large
|
resource_class: snaptsoft/pfic
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -158,7 +136,7 @@ jobs:
|
|||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
to: "s3://imex-online-test-beta/"
|
to: "s3://imex-online-test-beta/"
|
||||||
- jira/notify
|
arguments: "--exclude '*.map'"
|
||||||
|
|
||||||
admin-app-build:
|
admin-app-build:
|
||||||
docker:
|
docker:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
GENERATE_SOURCEMAP=false
|
GENERATE_SOURCEMAP=true
|
||||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
|
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
|
||||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
|
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
|
||||||
REACT_APP_GA_CODE=231103507
|
REACT_APP_GA_CODE=231103507
|
||||||
|
|||||||
3
client/.gitignore
vendored
Normal file
3
client/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
# Sentry Config File
|
||||||
|
.sentryclirc
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
// craco.config.js
|
// craco.config.js
|
||||||
const TerserPlugin = require("terser-webpack-plugin");
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
const CracoLessPlugin = require("craco-less");
|
const CracoLessPlugin = require("craco-less");
|
||||||
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
|
|
||||||
const {convertLegacyToken} = require('@ant-design/compatible/lib');
|
const {convertLegacyToken} = require('@ant-design/compatible/lib');
|
||||||
const {theme} = require('antd/lib');
|
const {theme} = require('antd/lib');
|
||||||
|
|
||||||
@@ -14,21 +13,21 @@ const v4Token = convertLegacyToken(mapToken);
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: [
|
||||||
{
|
// {
|
||||||
plugin: SentryWebpackPlugin,
|
// plugin: SentryWebpackPlugin,
|
||||||
options: {
|
// options: {
|
||||||
// sentry-cli configuration
|
// // sentry-cli configuration
|
||||||
authToken:
|
// authToken:
|
||||||
"6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
|
// "6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
|
||||||
org: "snapt-software",
|
// org: "snapt-software",
|
||||||
project: "imexonline",
|
// project: "imexonline",
|
||||||
release: process.env.REACT_APP_GIT_SHA,
|
// release: process.env.REACT_APP_GIT_SHA,
|
||||||
|
//
|
||||||
// webpack-specific configuration
|
// // webpack-specific configuration
|
||||||
include: ".",
|
// include: ".",
|
||||||
ignore: ["node_modules", "webpack.config.js"],
|
// ignore: ["node_modules", "webpack.config.js"],
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
plugin: CracoLessPlugin,
|
plugin: CracoLessPlugin,
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
592
client/package-lock.json
generated
592
client/package-lock.json
generated
@@ -10,14 +10,15 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/compatible": "^5.1.2",
|
"@ant-design/compatible": "^5.1.2",
|
||||||
"@ant-design/pro-layout": "^7.17.16",
|
"@ant-design/pro-layout": "^7.17.16",
|
||||||
"@apollo/client": "^3.8.10",
|
"@apollo/client": "^3.9.0",
|
||||||
"@asseinfo/react-kanban": "^2.2.0",
|
"@asseinfo/react-kanban": "^2.2.0",
|
||||||
"@craco/craco": "^7.1.0",
|
"@craco/craco": "^7.1.0",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.2.1",
|
"@fingerprintjs/fingerprintjs": "^4.2.2",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.1.0",
|
"@reduxjs/toolkit": "^2.1.0",
|
||||||
"@sentry/react": "^7.95.0",
|
"@sentry/cli": "^2.27.0",
|
||||||
"@sentry/tracing": "^7.95.0",
|
"@sentry/react": "^7.99.0",
|
||||||
|
"@sentry/tracing": "^7.99.0",
|
||||||
"@splitsoftware/splitio-react": "^1.11.0",
|
"@splitsoftware/splitio-react": "^1.11.0",
|
||||||
"@tanem/react-nprogress": "^5.0.51",
|
"@tanem/react-nprogress": "^5.0.51",
|
||||||
"@vitejs/plugin-legacy": "^5.2.0",
|
"@vitejs/plugin-legacy": "^5.2.0",
|
||||||
@@ -26,25 +27,26 @@
|
|||||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||||
"antd": "^5.13.2",
|
"antd": "^5.13.2",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.1",
|
||||||
|
"apollo-link-sentry": "^3.3.0",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.6.5",
|
||||||
"consola": "^3.2.3",
|
"consola": "^3.2.3",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"dayjs-business-days2": "^1.2.2",
|
"dayjs-business-days2": "^1.2.2",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^16.4.0",
|
"dotenv": "^16.4.1",
|
||||||
"enquire-js": "^0.2.1",
|
"enquire-js": "^0.2.1",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"esbuild": "^0.19.11",
|
"esbuild": "^0.19.11",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"firebase": "^10.7.2",
|
"firebase": "^10.7.2",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"i18next": "^23.7.18",
|
"i18next": "^23.8.1",
|
||||||
"i18next-browser-languagedetector": "^7.0.2",
|
"i18next-browser-languagedetector": "^7.0.2",
|
||||||
"jsoneditor": "^10.0.0",
|
"jsoneditor": "^10.0.0",
|
||||||
"jsreport-browser-client-dist": "^1.3.0",
|
"jsreport-browser-client-dist": "^1.3.0",
|
||||||
"libphonenumber-js": "^1.10.54",
|
"libphonenumber-js": "^1.10.54",
|
||||||
"logrocket": "^7.0.0",
|
"logrocket": "^7.0.0",
|
||||||
"markerjs2": "^2.31.4",
|
"markerjs2": "^2.32.0",
|
||||||
"normalize-url": "^8.0.0",
|
"normalize-url": "^8.0.0",
|
||||||
"phone": "^3.1.42",
|
"phone": "^3.1.42",
|
||||||
"preval.macro": "^5.0.0",
|
"preval.macro": "^5.0.0",
|
||||||
@@ -53,9 +55,9 @@
|
|||||||
"rc-queue-anim": "^2.0.0",
|
"rc-queue-anim": "^2.0.0",
|
||||||
"rc-scroll-anim": "^2.7.6",
|
"rc-scroll-anim": "^2.7.6",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-big-calendar": "^1.8.6",
|
"react-big-calendar": "^1.8.7",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-cookie": "^7.0.1",
|
"react-cookie": "^7.0.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-drag-listview": "^2.0.0",
|
"react-drag-listview": "^2.0.0",
|
||||||
"react-grid-gallery": "^1.0.0",
|
"react-grid-gallery": "^1.0.0",
|
||||||
@@ -78,13 +80,13 @@
|
|||||||
"redux-saga": "^1.3.0",
|
"redux-saga": "^1.3.0",
|
||||||
"redux-state-sync": "^3.1.4",
|
"redux-state-sync": "^3.1.4",
|
||||||
"reselect": "^5.1.0",
|
"reselect": "^5.1.0",
|
||||||
"sass": "^1.69.7",
|
"sass": "^1.70.0",
|
||||||
"socket.io-client": "^4.7.4",
|
"socket.io-client": "^4.7.4",
|
||||||
"styled-components": "^6.1.8",
|
"styled-components": "^6.1.8",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
"terser-webpack-plugin": "^5.3.10",
|
"terser-webpack-plugin": "^5.3.10",
|
||||||
"vite-plugin-svgr": "^4.2.0",
|
"vite-plugin-svgr": "^4.2.0",
|
||||||
"web-vitals": "^3.5.1",
|
"web-vitals": "^3.5.2",
|
||||||
"workbox-core": "^7.0.0",
|
"workbox-core": "^7.0.0",
|
||||||
"workbox-expiration": "^7.0.0",
|
"workbox-expiration": "^7.0.0",
|
||||||
"workbox-navigation-preload": "^7.0.0",
|
"workbox-navigation-preload": "^7.0.0",
|
||||||
@@ -98,7 +100,7 @@
|
|||||||
"@babel/preset-react": "^7.23.3",
|
"@babel/preset-react": "^7.23.3",
|
||||||
"@emotion/babel-plugin": "^11.11.0",
|
"@emotion/babel-plugin": "^11.11.0",
|
||||||
"@emotion/react": "^11.11.3",
|
"@emotion/react": "^11.11.3",
|
||||||
"@sentry/webpack-plugin": "^2.10.2",
|
"@sentry/webpack-plugin": "^2.10.3",
|
||||||
"@swc/core": "^1.3.104",
|
"@swc/core": "^1.3.104",
|
||||||
"@swc/plugin-styled-components": "^1.5.108",
|
"@swc/plugin-styled-components": "^1.5.108",
|
||||||
"@testing-library/cypress": "^10.0.1",
|
"@testing-library/cypress": "^10.0.1",
|
||||||
@@ -106,7 +108,7 @@
|
|||||||
"browserslist-to-esbuild": "^2.1.1",
|
"browserslist-to-esbuild": "^2.1.1",
|
||||||
"craco-less": "^3.0.1",
|
"craco-less": "^3.0.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cypress": "^13.6.2",
|
"cypress": "^13.6.4",
|
||||||
"eslint-plugin-cypress": "^2.15.1",
|
"eslint-plugin-cypress": "^2.15.1",
|
||||||
"memfs": "^4.6.0",
|
"memfs": "^4.6.0",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
@@ -294,17 +296,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@apollo/client": {
|
"node_modules/@apollo/client": {
|
||||||
"version": "3.8.10",
|
"version": "3.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.8.10.tgz",
|
"resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.9.1.tgz",
|
||||||
"integrity": "sha512-p/22RZ8ehHyvySnC20EHPPe0gdu8Xp6ZCiXOfdEe1ZORw5cUteD/TLc66tfKv8qu8NLIfbiWoa+6s70XnKvxqg==",
|
"integrity": "sha512-gpnIwMrEZ4n811gy2Ksz51Ax4zjAR5O5Tg1vlKs70Eig5Qj7kGGfnWfH5B37LSCIXanRyuMgVbQx+Aty5WruBA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@graphql-typed-document-node/core": "^3.1.1",
|
"@graphql-typed-document-node/core": "^3.1.1",
|
||||||
|
"@wry/caches": "^1.0.0",
|
||||||
"@wry/equality": "^0.5.6",
|
"@wry/equality": "^0.5.6",
|
||||||
"@wry/trie": "^0.5.0",
|
"@wry/trie": "^0.5.0",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
"hoist-non-react-statics": "^3.3.2",
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
"optimism": "^0.18.0",
|
"optimism": "^0.18.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
|
"rehackt": "0.0.3",
|
||||||
"response-iterator": "^0.2.6",
|
"response-iterator": "^0.2.6",
|
||||||
"symbol-observable": "^4.0.0",
|
"symbol-observable": "^4.0.0",
|
||||||
"ts-invariant": "^0.10.3",
|
"ts-invariant": "^0.10.3",
|
||||||
@@ -312,7 +316,7 @@
|
|||||||
"zen-observable-ts": "^1.2.5"
|
"zen-observable-ts": "^1.2.5"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
"graphql": "^15.0.0 || ^16.0.0",
|
||||||
"graphql-ws": "^5.5.5",
|
"graphql-ws": "^5.5.5",
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
@@ -2878,9 +2882,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fingerprintjs/fingerprintjs": {
|
"node_modules/@fingerprintjs/fingerprintjs": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@fingerprintjs/fingerprintjs/-/fingerprintjs-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@fingerprintjs/fingerprintjs/-/fingerprintjs-4.2.2.tgz",
|
||||||
"integrity": "sha512-uW+GVUNTgCXbVPEbgnbf5Aor22e1dyYR0JRwdUiZBaikfxr7KlhV9y0aahA1FB99fEeQVvhCEvTcPIFSYTy9Pw==",
|
"integrity": "sha512-scD+pDgNZW78LuFAr7ms2yxmDx2NWC4+K5iiOjPT2ZlTlHFbLsORUzLJI2rcKicxxLtHbvf3A7BU1drVr4iHGg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.4.1"
|
"tslib": "^2.4.1"
|
||||||
}
|
}
|
||||||
@@ -4086,36 +4090,99 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/feedback": {
|
"node_modules/@sentry-internal/feedback": {
|
||||||
"version": "7.95.0",
|
"version": "7.99.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.95.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.99.0.tgz",
|
||||||
"integrity": "sha512-UWNUUg+OrhV58/ChQNY0yLCCYdEI2cIYVwq1riZ+AFU9Z2ZyCxEoSRVLh7c5tnCPLbKAPR+VvlEsSjMqucAKnA==",
|
"integrity": "sha512-exIO1o+bE0MW4z30FxC0cYzJ4ZHSMlDPMHCBDPzU+MWGQc/fb8s58QUrx5Dnm6HTh9G3H+YlroCxIo9u0GSwGQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/core": "7.95.0",
|
"@sentry/core": "7.99.0",
|
||||||
"@sentry/types": "7.95.0",
|
"@sentry/types": "7.99.0",
|
||||||
"@sentry/utils": "7.95.0"
|
"@sentry/utils": "7.99.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/replay-canvas": {
|
"node_modules/@sentry-internal/feedback/node_modules/@sentry/core": {
|
||||||
"version": "7.95.0",
|
"version": "7.99.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.95.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.99.0.tgz",
|
||||||
"integrity": "sha512-nK+VYLnhlxLnnEUPMnzN8BSCFXPy2dQ5OVFO+fiTNPlGCbxsq8O9SnkX6E+u1oPLSA/151s2B3VxIasR8bXJAQ==",
|
"integrity": "sha512-vOAtzcAXEUtS/oW7wi3wMkZ3hsb5Ch96gKyrrj/mXdOp2zrcwdNV6N9/pawq2E9P/7Pw8AXw4CeDZztZrjQLuA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/core": "7.95.0",
|
"@sentry/types": "7.99.0",
|
||||||
"@sentry/replay": "7.95.0",
|
"@sentry/utils": "7.99.0"
|
||||||
"@sentry/types": "7.95.0",
|
},
|
||||||
"@sentry/utils": "7.95.0"
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry-internal/feedback/node_modules/@sentry/types": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-94qwOw4w40sAs5mCmzcGyj8ZUu/KhnWnuMZARRq96k+SjRW/tHFAOlIdnFSrt3BLPvSOK7R3bVAskZQ0N4FTmA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry-internal/feedback/node_modules/@sentry/utils": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-cYZy5WNTkWs5GgggGnjfGqC44CWir0pAv4GVVSx0fsup4D4pMKBJPrtub15f9uC+QkUf3vVkqwpBqeFxtmJQTQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "7.99.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry-internal/replay-canvas": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-PoIkfusToDq0snfl2M6HJx/1KJYtXxYhQplrn11kYadO04SdG0XGXf4h7wBTMEQ7LDEAtQyvsOu4nEQtTO3YjQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/core": "7.99.0",
|
||||||
|
"@sentry/replay": "7.99.0",
|
||||||
|
"@sentry/types": "7.99.0",
|
||||||
|
"@sentry/utils": "7.99.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/core": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-vOAtzcAXEUtS/oW7wi3wMkZ3hsb5Ch96gKyrrj/mXdOp2zrcwdNV6N9/pawq2E9P/7Pw8AXw4CeDZztZrjQLuA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "7.99.0",
|
||||||
|
"@sentry/utils": "7.99.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/types": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-94qwOw4w40sAs5mCmzcGyj8ZUu/KhnWnuMZARRq96k+SjRW/tHFAOlIdnFSrt3BLPvSOK7R3bVAskZQ0N4FTmA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/utils": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-cYZy5WNTkWs5GgggGnjfGqC44CWir0pAv4GVVSx0fsup4D4pMKBJPrtub15f9uC+QkUf3vVkqwpBqeFxtmJQTQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "7.99.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sentry-internal/tracing": {
|
"node_modules/@sentry-internal/tracing": {
|
||||||
"version": "7.95.0",
|
"version": "7.95.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.95.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.95.0.tgz",
|
||||||
"integrity": "sha512-YKiLPMnEgTsTh7u/W1Zep9HtV1rJqAetqJ4ekaIxyUUB6ppi6V00MacSjb01o++fwlNNDYFxNpJlgQqNPqsCNA==",
|
"integrity": "sha512-YKiLPMnEgTsTh7u/W1Zep9HtV1rJqAetqJ4ekaIxyUUB6ppi6V00MacSjb01o++fwlNNDYFxNpJlgQqNPqsCNA==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/core": "7.95.0",
|
"@sentry/core": "7.95.0",
|
||||||
"@sentry/types": "7.95.0",
|
"@sentry/types": "7.95.0",
|
||||||
@@ -4126,17 +4193,61 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/browser": {
|
"node_modules/@sentry/browser": {
|
||||||
"version": "7.95.0",
|
"version": "7.99.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.95.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.99.0.tgz",
|
||||||
"integrity": "sha512-J+bAryc/7StRZSeaJByLTnEslWu7uXaZ1zERQF17sZbRxvbyaYbAO7cqSUKZ1gK3p9QfrkqzNl7xA8thg4FtHQ==",
|
"integrity": "sha512-bgfoUv3wkwwLgN5YUOe0ibB3y268ZCnamZh6nLFqnY/UBKC1+FXWFdvzVON/XKUm62LF8wlpCybOf08ebNj2yg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/feedback": "7.95.0",
|
"@sentry-internal/feedback": "7.99.0",
|
||||||
"@sentry-internal/replay-canvas": "7.95.0",
|
"@sentry-internal/replay-canvas": "7.99.0",
|
||||||
"@sentry-internal/tracing": "7.95.0",
|
"@sentry-internal/tracing": "7.99.0",
|
||||||
"@sentry/core": "7.95.0",
|
"@sentry/core": "7.99.0",
|
||||||
"@sentry/replay": "7.95.0",
|
"@sentry/replay": "7.99.0",
|
||||||
"@sentry/types": "7.95.0",
|
"@sentry/types": "7.99.0",
|
||||||
"@sentry/utils": "7.95.0"
|
"@sentry/utils": "7.99.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/browser/node_modules/@sentry-internal/tracing": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-z3JQhHjoM1KdM20qrHwRClKJrNLr2CcKtCluq7xevLtXHJWNAQQbafnWD+Aoj85EWXBzKt9yJMv2ltcXJ+at+w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/core": "7.99.0",
|
||||||
|
"@sentry/types": "7.99.0",
|
||||||
|
"@sentry/utils": "7.99.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/browser/node_modules/@sentry/core": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-vOAtzcAXEUtS/oW7wi3wMkZ3hsb5Ch96gKyrrj/mXdOp2zrcwdNV6N9/pawq2E9P/7Pw8AXw4CeDZztZrjQLuA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "7.99.0",
|
||||||
|
"@sentry/utils": "7.99.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/browser/node_modules/@sentry/types": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-94qwOw4w40sAs5mCmzcGyj8ZUu/KhnWnuMZARRq96k+SjRW/tHFAOlIdnFSrt3BLPvSOK7R3bVAskZQ0N4FTmA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/browser/node_modules/@sentry/utils": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-cYZy5WNTkWs5GgggGnjfGqC44CWir0pAv4GVVSx0fsup4D4pMKBJPrtub15f9uC+QkUf3vVkqwpBqeFxtmJQTQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "7.99.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -4162,10 +4273,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli": {
|
"node_modules/@sentry/cli": {
|
||||||
"version": "2.26.0",
|
"version": "2.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.27.0.tgz",
|
||||||
"integrity": "sha512-WRrY9nkjLLUvyo+l8KE0x0Q+0NtCd2U8HYJzh3kyJHyyfKWiSH7ZhExcsb2MoSIjlzbKjjrIJzxhklZABkidDw==",
|
"integrity": "sha512-pc0opd71W8lGhYvmB1keQtJkarxzCS9f9ErKYv6TfXOOX6drvwkyA6vD/6xEnpzyvqGAuGRU4T4sEeLD3irwUQ==",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"https-proxy-agent": "^5.0.0",
|
"https-proxy-agent": "^5.0.0",
|
||||||
@@ -4181,23 +4291,115 @@
|
|||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@sentry/cli-darwin": "2.26.0",
|
"@sentry/cli-darwin": "2.27.0",
|
||||||
"@sentry/cli-linux-arm": "2.26.0",
|
"@sentry/cli-linux-arm": "2.27.0",
|
||||||
"@sentry/cli-linux-arm64": "2.26.0",
|
"@sentry/cli-linux-arm64": "2.27.0",
|
||||||
"@sentry/cli-linux-i686": "2.26.0",
|
"@sentry/cli-linux-i686": "2.27.0",
|
||||||
"@sentry/cli-linux-x64": "2.26.0",
|
"@sentry/cli-linux-x64": "2.27.0",
|
||||||
"@sentry/cli-win32-i686": "2.26.0",
|
"@sentry/cli-win32-i686": "2.27.0",
|
||||||
"@sentry/cli-win32-x64": "2.26.0"
|
"@sentry/cli-win32-x64": "2.27.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli-win32-x64": {
|
"node_modules/@sentry/cli-darwin": {
|
||||||
"version": "2.26.0",
|
"version": "2.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.27.0.tgz",
|
||||||
"integrity": "sha512-r3ZaxdHGC6OyJhOxO5ADzAitpGcgT/PkqQzOzKXBOebHj5jzwY27JWjdNhpT6sJZDII13HxqwISRedVWftZgRw==",
|
"integrity": "sha512-/DOZlN5rK19g7YP2OaVNauQhUrRfJ88RDr6qURFiqdxYHDc3isPFGHZJmeZBTwOnDDepyZb4XLaOyfwvAOxHig==",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/cli-linux-arm": {
|
||||||
|
"version": "2.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.27.0.tgz",
|
||||||
|
"integrity": "sha512-JmMQ9zgFhkZUEN5WIYuJisu4Jif/ThRHDjbsbXBRbUkkgRn88hgUfg299djMvlZZxjpl3K9AEua+1TIUeQd0Sg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux",
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/cli-linux-arm64": {
|
||||||
|
"version": "2.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.27.0.tgz",
|
||||||
|
"integrity": "sha512-f+zuB9XGfB8pNamNgSDhqsavuLuzi6saZxbr3uQf30bA5AESI5hspOd1zPcidOORCVZxiPzQe3+T7avBI1XLuw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux",
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/cli-linux-i686": {
|
||||||
|
"version": "2.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.27.0.tgz",
|
||||||
|
"integrity": "sha512-/4eyz7jnYp20mZqNtpvCEBkxFW0nEjEZRo2BiASQ5/7K8CmoJRe1vhpDA0WOfzi1zTFIfpdE1/RZm2CjHS6DHQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x86",
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux",
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/cli-linux-x64": {
|
||||||
|
"version": "2.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.27.0.tgz",
|
||||||
|
"integrity": "sha512-ptu7wXecnYssihzHlxEOaqbFHWmNEfbepBKGXTdWK2kC+D51+7yHsR9xRdThwVID1bisFgjAveKmBQjmKuXjHQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux",
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/cli-win32-i686": {
|
||||||
|
"version": "2.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.27.0.tgz",
|
||||||
|
"integrity": "sha512-Db4/xmdE5qV4Aq7Yc8vRw22Y46JJdGMdsMsl5jIf0GVSQPgO23O/2uTiDGpPOdeq91K9EtvpH1zQfDLIfLMaXw==",
|
||||||
|
"cpu": [
|
||||||
|
"x86",
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/cli-win32-x64": {
|
||||||
|
"version": "2.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.27.0.tgz",
|
||||||
|
"integrity": "sha512-q7y/BH4iGfs0TD5PXh2Q8oqnTbOIufoT1NWJcKqvZcOiqCLK3PNUiq7xUeX1PMTrFYAh3Bm6EekOnMavqvbGmg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
@@ -4210,6 +4412,7 @@
|
|||||||
"version": "7.95.0",
|
"version": "7.95.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.95.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.95.0.tgz",
|
||||||
"integrity": "sha512-z+ffO6jK/ZUxnRbBGmnj5sOouKZ4mvRY0KJa33kbyqcmeiJKrN81M7Ecj1IJUCamo/6RqX0GCwDDxgUPZZZBwA==",
|
"integrity": "sha512-z+ffO6jK/ZUxnRbBGmnj5sOouKZ4mvRY0KJa33kbyqcmeiJKrN81M7Ecj1IJUCamo/6RqX0GCwDDxgUPZZZBwA==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/types": "7.95.0",
|
"@sentry/types": "7.95.0",
|
||||||
"@sentry/utils": "7.95.0"
|
"@sentry/utils": "7.95.0"
|
||||||
@@ -4234,14 +4437,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/react": {
|
"node_modules/@sentry/react": {
|
||||||
"version": "7.95.0",
|
"version": "7.99.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.95.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.99.0.tgz",
|
||||||
"integrity": "sha512-L1WVoRJ14uUBhnpVURVO3G7szuA2hk5pzNWMGjNhE7I+ERdfYjgRS5FMnjcsZsnyNtELe/56jLCBirKqwWZJIQ==",
|
"integrity": "sha512-RtHwgzMHJhzJfSQpVG0SDPQYMTGDX3Q37/YWI59S4ALMbSW4/F6n/eQAvGVYZKbh2UCSqgFuRWaXOYkSZT17wA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/browser": "7.95.0",
|
"@sentry/browser": "7.99.0",
|
||||||
"@sentry/core": "7.95.0",
|
"@sentry/core": "7.99.0",
|
||||||
"@sentry/types": "7.95.0",
|
"@sentry/types": "7.99.0",
|
||||||
"@sentry/utils": "7.95.0",
|
"@sentry/utils": "7.99.0",
|
||||||
"hoist-non-react-statics": "^3.3.2"
|
"hoist-non-react-statics": "^3.3.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -4251,26 +4454,145 @@
|
|||||||
"react": "15.x || 16.x || 17.x || 18.x"
|
"react": "15.x || 16.x || 17.x || 18.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/replay": {
|
"node_modules/@sentry/react/node_modules/@sentry/core": {
|
||||||
"version": "7.95.0",
|
"version": "7.99.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.95.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.99.0.tgz",
|
||||||
"integrity": "sha512-WAAEvWCXoNC7hfPI1fNoxODB9UzUYhXtscrWIm89Mn3AAi8haayVNNiUWKVj6+Vpt87C+/sb9UsERwdPWpl/Sw==",
|
"integrity": "sha512-vOAtzcAXEUtS/oW7wi3wMkZ3hsb5Ch96gKyrrj/mXdOp2zrcwdNV6N9/pawq2E9P/7Pw8AXw4CeDZztZrjQLuA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/tracing": "7.95.0",
|
"@sentry/types": "7.99.0",
|
||||||
"@sentry/core": "7.95.0",
|
"@sentry/utils": "7.99.0"
|
||||||
"@sentry/types": "7.95.0",
|
},
|
||||||
"@sentry/utils": "7.95.0"
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/react/node_modules/@sentry/types": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-94qwOw4w40sAs5mCmzcGyj8ZUu/KhnWnuMZARRq96k+SjRW/tHFAOlIdnFSrt3BLPvSOK7R3bVAskZQ0N4FTmA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/react/node_modules/@sentry/utils": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-cYZy5WNTkWs5GgggGnjfGqC44CWir0pAv4GVVSx0fsup4D4pMKBJPrtub15f9uC+QkUf3vVkqwpBqeFxtmJQTQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "7.99.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/replay": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-gyN/I2WpQrLAZDT+rScB/0jnFL2knEVBo8U8/OVt8gNP20Pq8T/rDZKO/TG0cBfvULDUbJj2P4CJryn2p/O2rA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry-internal/tracing": "7.99.0",
|
||||||
|
"@sentry/core": "7.99.0",
|
||||||
|
"@sentry/types": "7.99.0",
|
||||||
|
"@sentry/utils": "7.99.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/tracing": {
|
"node_modules/@sentry/replay/node_modules/@sentry-internal/tracing": {
|
||||||
"version": "7.95.0",
|
"version": "7.99.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.95.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.99.0.tgz",
|
||||||
"integrity": "sha512-0vhTwd2j1Il+stpPH/cwcVuqzMlW9Ixzm5IYMKwjyoauHJIaG9b71NIG7xMmO7gOfcdxh14NbebHgqdyqDrCGw==",
|
"integrity": "sha512-z3JQhHjoM1KdM20qrHwRClKJrNLr2CcKtCluq7xevLtXHJWNAQQbafnWD+Aoj85EWXBzKt9yJMv2ltcXJ+at+w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/tracing": "7.95.0"
|
"@sentry/core": "7.99.0",
|
||||||
|
"@sentry/types": "7.99.0",
|
||||||
|
"@sentry/utils": "7.99.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/replay/node_modules/@sentry/core": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-vOAtzcAXEUtS/oW7wi3wMkZ3hsb5Ch96gKyrrj/mXdOp2zrcwdNV6N9/pawq2E9P/7Pw8AXw4CeDZztZrjQLuA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "7.99.0",
|
||||||
|
"@sentry/utils": "7.99.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/replay/node_modules/@sentry/types": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-94qwOw4w40sAs5mCmzcGyj8ZUu/KhnWnuMZARRq96k+SjRW/tHFAOlIdnFSrt3BLPvSOK7R3bVAskZQ0N4FTmA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/replay/node_modules/@sentry/utils": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-cYZy5WNTkWs5GgggGnjfGqC44CWir0pAv4GVVSx0fsup4D4pMKBJPrtub15f9uC+QkUf3vVkqwpBqeFxtmJQTQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "7.99.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/tracing": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-Cf622gSeamiSsi0JEj3PTXnq019OymaCrGf91x1d6OPyJ5jAXdlNuhw7NkqCEw8euIhhULuS81l5nGfBrgjj9Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry-internal/tracing": "7.99.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/tracing/node_modules/@sentry-internal/tracing": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-z3JQhHjoM1KdM20qrHwRClKJrNLr2CcKtCluq7xevLtXHJWNAQQbafnWD+Aoj85EWXBzKt9yJMv2ltcXJ+at+w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/core": "7.99.0",
|
||||||
|
"@sentry/types": "7.99.0",
|
||||||
|
"@sentry/utils": "7.99.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/tracing/node_modules/@sentry/core": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-vOAtzcAXEUtS/oW7wi3wMkZ3hsb5Ch96gKyrrj/mXdOp2zrcwdNV6N9/pawq2E9P/7Pw8AXw4CeDZztZrjQLuA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "7.99.0",
|
||||||
|
"@sentry/utils": "7.99.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/tracing/node_modules/@sentry/types": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-94qwOw4w40sAs5mCmzcGyj8ZUu/KhnWnuMZARRq96k+SjRW/tHFAOlIdnFSrt3BLPvSOK7R3bVAskZQ0N4FTmA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/tracing/node_modules/@sentry/utils": {
|
||||||
|
"version": "7.99.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.99.0.tgz",
|
||||||
|
"integrity": "sha512-cYZy5WNTkWs5GgggGnjfGqC44CWir0pAv4GVVSx0fsup4D4pMKBJPrtub15f9uC+QkUf3vVkqwpBqeFxtmJQTQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "7.99.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -4280,6 +4602,7 @@
|
|||||||
"version": "7.95.0",
|
"version": "7.95.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.95.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.95.0.tgz",
|
||||||
"integrity": "sha512-ouU7NsEcrwmcnXHMNBGmKZEmKMzmgPGoBydZn1gukCI67Ci71fAYpPNrbtmjai6+jtsY21o45rVLqExru2sdfw==",
|
"integrity": "sha512-ouU7NsEcrwmcnXHMNBGmKZEmKMzmgPGoBydZn1gukCI67Ci71fAYpPNrbtmjai6+jtsY21o45rVLqExru2sdfw==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
@@ -4288,6 +4611,7 @@
|
|||||||
"version": "7.95.0",
|
"version": "7.95.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.95.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.95.0.tgz",
|
||||||
"integrity": "sha512-0zget8AOaQWLIEA9cTx/qiQQYpx2x0UfnaW5xRmQg12QGTSngo/cUm9O04zuHw5gpBBGG0ocMDHxwwr+UCCBiw==",
|
"integrity": "sha512-0zget8AOaQWLIEA9cTx/qiQQYpx2x0UfnaW5xRmQg12QGTSngo/cUm9O04zuHw5gpBBGG0ocMDHxwwr+UCCBiw==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/types": "7.95.0"
|
"@sentry/types": "7.95.0"
|
||||||
},
|
},
|
||||||
@@ -5123,7 +5447,6 @@
|
|||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "4"
|
"debug": "4"
|
||||||
},
|
},
|
||||||
@@ -5311,6 +5634,22 @@
|
|||||||
"@apollo/client": "^3.0.0"
|
"@apollo/client": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/apollo-link-sentry": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-link-sentry/-/apollo-link-sentry-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-wLffWmo5sRw3rHN1Ck6azM0oxObvtaBBf3AC8cLX4SxhyjmkRIagGDji6CFkyAhxupPz0b9/H1u4Ocx+63lNug==",
|
||||||
|
"dependencies": {
|
||||||
|
"deepmerge": "^4.2.2",
|
||||||
|
"dot-prop": "^6.0.0",
|
||||||
|
"tslib": "^2.0.3",
|
||||||
|
"zen-observable-ts": "^1.2.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@apollo/client": "^3.2.3",
|
||||||
|
"@sentry/browser": "^7.41.0",
|
||||||
|
"graphql": "15 - 16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/arch": {
|
"node_modules/arch": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz",
|
||||||
@@ -6669,9 +7008,9 @@
|
|||||||
"integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw=="
|
"integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw=="
|
||||||
},
|
},
|
||||||
"node_modules/cypress": {
|
"node_modules/cypress": {
|
||||||
"version": "13.6.3",
|
"version": "13.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.4.tgz",
|
||||||
"integrity": "sha512-d/pZvgwjAyZsoyJ3FOsJT5lDsqnxQ/clMqnNc++rkHjbkkiF2h9s0JsZSyyH4QXhVFW3zPFg82jD25roFLOdZA==",
|
"integrity": "sha512-pYJjCfDYB+hoOoZuhysbbYhEmNW7DEDsqn+ToCLwuVowxUXppIWRr7qk4TVRIU471ksfzyZcH+mkoF0CQUKnpw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -7003,6 +7342,14 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/deepmerge": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/define-data-property": {
|
"node_modules/define-data-property": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
|
||||||
@@ -7159,10 +7506,24 @@
|
|||||||
"tslib": "^2.0.3"
|
"tslib": "^2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dot-prop": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
|
||||||
|
"dependencies": {
|
||||||
|
"is-obj": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.4.0",
|
"version": "16.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.1.tgz",
|
||||||
"integrity": "sha512-WvImr5kpN5NGNn7KaDjJnLTh5rDVLZiDf/YLA8T1ZEZEBZNEDOE+mnkS0PVjPax8ZxBP5zC5SLMB3/9VV5de9g==",
|
"integrity": "sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -8263,7 +8624,6 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"agent-base": "6",
|
"agent-base": "6",
|
||||||
"debug": "4"
|
"debug": "4"
|
||||||
@@ -8291,9 +8651,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/i18next": {
|
"node_modules/i18next": {
|
||||||
"version": "23.7.18",
|
"version": "23.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.7.18.tgz",
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.8.2.tgz",
|
||||||
"integrity": "sha512-b9N2KjRCYQNlUvE1Kc83g8knyUkL5NiZQOp9BsTR/v/LXk6Fzz+doOzTg2/826XK28mCgBkYLNAtixjE58qpCw==",
|
"integrity": "sha512-Z84zyEangrlERm0ZugVy4bIt485e/H8VecGUZkZWrH7BDePG6jT73QdL9EA1tRTTVVMpry/MgWIP1FjEn0DRXA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
@@ -8790,6 +9150,14 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-obj": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-path-inside": {
|
"node_modules/is-path-inside": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
|
||||||
@@ -9636,9 +10004,9 @@
|
|||||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
|
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
|
||||||
},
|
},
|
||||||
"node_modules/markerjs2": {
|
"node_modules/markerjs2": {
|
||||||
"version": "2.31.5",
|
"version": "2.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/markerjs2/-/markerjs2-2.31.5.tgz",
|
"resolved": "https://registry.npmjs.org/markerjs2/-/markerjs2-2.32.0.tgz",
|
||||||
"integrity": "sha512-fOk0IPfqmwdmKlcaf9qfD8SnBBEzJtgl6NVcDOQpUJAcyyk6/wkA4m410NQ8mD7B42WdjkAzAGj2BMxHsSYthQ=="
|
"integrity": "sha512-B9bk63dGS5NvP/68yPAK2F44bo3un3fpN96+0ZwCzQPzu1ZdMG7Ns6QYx0iTpx3QsmdwIr3D1lZTwsmiiVUfug=="
|
||||||
},
|
},
|
||||||
"node_modules/material-colors": {
|
"node_modules/material-colors": {
|
||||||
"version": "1.2.6",
|
"version": "1.2.6",
|
||||||
@@ -11105,7 +11473,6 @@
|
|||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
@@ -12033,9 +12400,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-big-calendar": {
|
"node_modules/react-big-calendar": {
|
||||||
"version": "1.8.6",
|
"version": "1.8.7",
|
||||||
"resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-1.8.6.tgz",
|
"resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-1.8.7.tgz",
|
||||||
"integrity": "sha512-/weukU7kfaowMgC634Vtzc3eNAEpTPxmK9aVEpRQ6gIrTUlL8E+xpgWZ4ouc8HdwRDD0nJD14po5NGbTOFwbtg==",
|
"integrity": "sha512-qgxQyPIYoWM+TLbGNlEUIEyNE6USAAc7LEAhtUKzJgoP4XsgpnqXJ46Ub298mRzMRM6crRweh3AywDjl5GbHnQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.7",
|
"@babel/runtime": "^7.20.7",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
@@ -12082,9 +12449,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-cookie": {
|
"node_modules/react-cookie": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.0.2.tgz",
|
||||||
"integrity": "sha512-SmDjy2TFp+vS6BGOqW7HyaWKyJzVmIH74uP3mxq6kswlwLJEBtIbhkrioozdvQL9r81yprHYFQkSmcO4HiXPdA==",
|
"integrity": "sha512-UnW1rZw1VibRdTvV8Ksr0BKKZoajeUxYLE89sIygDeyQgtz6ik89RHOM+3kib36G9M7HxheORggPoLk5DxAK7Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/hoist-non-react-statics": "^3.3.5",
|
"@types/hoist-non-react-statics": "^3.3.5",
|
||||||
"hoist-non-react-statics": "^3.3.2",
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
@@ -12691,6 +13058,23 @@
|
|||||||
"jsesc": "bin/jsesc"
|
"jsesc": "bin/jsesc"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rehackt": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/rehackt/-/rehackt-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-aBRHudKhOWwsTvCbSoinzq+Lej/7R8e8UoPvLZo5HirZIIBLGAgdG7SL9QpdcBoQ7+3QYPi3lRLknAzXBlhZ7g==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/remark-parse": {
|
"node_modules/remark-parse": {
|
||||||
"version": "11.0.0",
|
"version": "11.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
||||||
@@ -15169,9 +15553,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/web-vitals": {
|
"node_modules/web-vitals": {
|
||||||
"version": "3.5.1",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.2.tgz",
|
||||||
"integrity": "sha512-xQ9lvIpfLxUj0eSmT79ZjRoU5wIRfIr7pNukL7ZE4EcWZSmfZQqOlhuAGfkVa3EFmzPHZhWhXfm2i5ys+THVPg=="
|
"integrity": "sha512-c0rhqNcHXRkY/ogGDJQxZ9Im9D19hDihbzSQJrsioex+KnFgmMzBiy57Z1EjkhX/+OjyBpclDCzz2ITtjokFmg=="
|
||||||
},
|
},
|
||||||
"node_modules/webpack-merge": {
|
"node_modules/webpack-merge": {
|
||||||
"version": "5.10.0",
|
"version": "5.10.0",
|
||||||
|
|||||||
@@ -4,20 +4,23 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": "18.18.2"
|
"node": "18.18.2"
|
||||||
},
|
},
|
||||||
|
"type": "commonjs",
|
||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "http://localhost:4000",
|
"proxy": "http://localhost:4000",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/compatible": "^5.1.2",
|
"@ant-design/compatible": "^5.1.2",
|
||||||
"@ant-design/pro-layout": "^7.17.16",
|
"@ant-design/pro-layout": "^7.17.16",
|
||||||
"@apollo/client": "^3.8.10",
|
"@apollo/client": "^3.9.0",
|
||||||
"@asseinfo/react-kanban": "^2.2.0",
|
"@asseinfo/react-kanban": "^2.2.0",
|
||||||
"@craco/craco": "^7.1.0",
|
"@craco/craco": "^7.1.0",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.2.1",
|
"@fingerprintjs/fingerprintjs": "^4.2.2",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.1.0",
|
"@reduxjs/toolkit": "^2.1.0",
|
||||||
"@sentry/react": "^7.95.0",
|
"@sentry/cli": "^2.27.0",
|
||||||
"@sentry/tracing": "^7.95.0",
|
"@sentry/react": "^7.99.0",
|
||||||
|
"@sentry/tracing": "^7.99.0",
|
||||||
"@splitsoftware/splitio-react": "^1.11.0",
|
"@splitsoftware/splitio-react": "^1.11.0",
|
||||||
|
"apollo-link-sentry": "^3.3.0",
|
||||||
"@tanem/react-nprogress": "^5.0.51",
|
"@tanem/react-nprogress": "^5.0.51",
|
||||||
"@vitejs/plugin-legacy": "^5.2.0",
|
"@vitejs/plugin-legacy": "^5.2.0",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
@@ -30,20 +33,20 @@
|
|||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"dayjs-business-days2": "^1.2.2",
|
"dayjs-business-days2": "^1.2.2",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^16.4.0",
|
"dotenv": "^16.4.1",
|
||||||
"enquire-js": "^0.2.1",
|
"enquire-js": "^0.2.1",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"esbuild": "^0.19.11",
|
"esbuild": "^0.19.11",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"firebase": "^10.7.2",
|
"firebase": "^10.7.2",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"i18next": "^23.7.18",
|
"i18next": "^23.8.1",
|
||||||
"i18next-browser-languagedetector": "^7.0.2",
|
"i18next-browser-languagedetector": "^7.0.2",
|
||||||
"jsoneditor": "^10.0.0",
|
"jsoneditor": "^10.0.0",
|
||||||
"jsreport-browser-client-dist": "^1.3.0",
|
"jsreport-browser-client-dist": "^1.3.0",
|
||||||
"libphonenumber-js": "^1.10.54",
|
"libphonenumber-js": "^1.10.54",
|
||||||
"logrocket": "^7.0.0",
|
"logrocket": "^7.0.0",
|
||||||
"markerjs2": "^2.31.4",
|
"markerjs2": "^2.32.0",
|
||||||
"normalize-url": "^8.0.0",
|
"normalize-url": "^8.0.0",
|
||||||
"phone": "^3.1.42",
|
"phone": "^3.1.42",
|
||||||
"preval.macro": "^5.0.0",
|
"preval.macro": "^5.0.0",
|
||||||
@@ -52,9 +55,9 @@
|
|||||||
"rc-queue-anim": "^2.0.0",
|
"rc-queue-anim": "^2.0.0",
|
||||||
"rc-scroll-anim": "^2.7.6",
|
"rc-scroll-anim": "^2.7.6",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-big-calendar": "^1.8.6",
|
"react-big-calendar": "^1.8.7",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-cookie": "^7.0.1",
|
"react-cookie": "^7.0.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-drag-listview": "^2.0.0",
|
"react-drag-listview": "^2.0.0",
|
||||||
"react-grid-gallery": "^1.0.0",
|
"react-grid-gallery": "^1.0.0",
|
||||||
@@ -77,13 +80,13 @@
|
|||||||
"redux-saga": "^1.3.0",
|
"redux-saga": "^1.3.0",
|
||||||
"redux-state-sync": "^3.1.4",
|
"redux-state-sync": "^3.1.4",
|
||||||
"reselect": "^5.1.0",
|
"reselect": "^5.1.0",
|
||||||
"sass": "^1.69.7",
|
"sass": "^1.70.0",
|
||||||
"socket.io-client": "^4.7.4",
|
"socket.io-client": "^4.7.4",
|
||||||
"styled-components": "^6.1.8",
|
"styled-components": "^6.1.8",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
"terser-webpack-plugin": "^5.3.10",
|
"terser-webpack-plugin": "^5.3.10",
|
||||||
"vite-plugin-svgr": "^4.2.0",
|
"vite-plugin-svgr": "^4.2.0",
|
||||||
"web-vitals": "^3.5.1",
|
"web-vitals": "^3.5.2",
|
||||||
"workbox-core": "^7.0.0",
|
"workbox-core": "^7.0.0",
|
||||||
"workbox-expiration": "^7.0.0",
|
"workbox-expiration": "^7.0.0",
|
||||||
"workbox-navigation-preload": "^7.0.0",
|
"workbox-navigation-preload": "^7.0.0",
|
||||||
@@ -95,14 +98,15 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"build": "cross-env-shell VITE_APP_GIT_SHA=\\\"`git rev-parse --short HEAD`\\\" vite build",
|
"build": "cross-env-shell VITE_APP_GIT_SHA=\\\"`git rev-parse --short HEAD`\\\" vite build && npm run sentry:sourcemaps",
|
||||||
"build:test": "env-cmd -f .env.test npm run build",
|
"build:test": "env-cmd -f .env.test npm run build",
|
||||||
"build-deploy:test": "npm 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": "cross-env-shell VITE_APP_GIT_SHA=\\\"`git rev-parse --short HEAD`\\\" vite build",
|
"buildcra": "cross-env-shell VITE_APP_GIT_SHA=\\\"`git rev-parse --short HEAD`\\\" vite build",
|
||||||
"test": "cypress open",
|
"test": "cypress open",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
|
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
|
||||||
"eulaize": "node src/utils/eulaize.js",
|
"eulaize": "node src/utils/eulaize.js",
|
||||||
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular ."
|
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org imex --project imexonline ./build && sentry-cli sourcemaps upload --org imex --project imexonline ./build"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@@ -139,7 +143,7 @@
|
|||||||
"browserslist-to-esbuild": "^2.1.1",
|
"browserslist-to-esbuild": "^2.1.1",
|
||||||
"craco-less": "^3.0.1",
|
"craco-less": "^3.0.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cypress": "^13.6.2",
|
"cypress": "^13.6.4",
|
||||||
"eslint-plugin-cypress": "^2.15.1",
|
"eslint-plugin-cypress": "^2.15.1",
|
||||||
"memfs": "^4.6.0",
|
"memfs": "^4.6.0",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {useTranslation} from "react-i18next";
|
|||||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||||
import client from "../utils/GraphQLClient";
|
import client from "../utils/GraphQLClient";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
|
|
||||||
import themeProvider from "./themeProvider";
|
import themeProvider from "./themeProvider";
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ const config = {
|
|||||||
export const factory = SplitSdk(config);
|
export const factory = SplitSdk(config);
|
||||||
|
|
||||||
|
|
||||||
export default function AppContainer() {
|
function AppContainer() {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -48,3 +49,5 @@ export default function AppContainer() {
|
|||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Sentry.withProfiler(AppContainer);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import TechPageContainer from "../pages/tech/tech.page.container";
|
|||||||
import {setOnline} from "../redux/application/application.actions";
|
import {setOnline} from "../redux/application/application.actions";
|
||||||
import {selectOnline} from "../redux/application/application.selectors";
|
import {selectOnline} from "../redux/application/application.selectors";
|
||||||
import {checkUserSession} from "../redux/user/user.actions";
|
import {checkUserSession} from "../redux/user/user.actions";
|
||||||
import {selectBodyshop, selectCurrentUser,} from "../redux/user/user.selectors";
|
import {selectBodyshop, selectCurrentEula, selectCurrentUser,} from "../redux/user/user.selectors";
|
||||||
import PrivateRoute from "../components/PrivateRoute";
|
import PrivateRoute from "../components/PrivateRoute";
|
||||||
import "./App.styles.scss";
|
import "./App.styles.scss";
|
||||||
import handleBeta from "../utils/betaHandler";
|
import handleBeta from "../utils/betaHandler";
|
||||||
@@ -37,13 +37,14 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
online: selectOnline,
|
online: selectOnline,
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
|
currentEula: selectCurrentEula
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
checkUserSession: () => dispatch(checkUserSession()),
|
checkUserSession: () => dispatch(checkUserSession()),
|
||||||
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function App({bodyshop, checkUserSession, currentUser, online, setOnline}) {
|
export function App({bodyshop, checkUserSession, currentUser, online, setOnline, currentEula}) {
|
||||||
const client = useSplitClient().client;
|
const client = useSplitClient().client;
|
||||||
const [listenersAdded, setListenersAdded] = useState(false)
|
const [listenersAdded, setListenersAdded] = useState(false)
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
@@ -122,7 +123,7 @@ export function App({bodyshop, checkUserSession, currentUser, online, setOnline}
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!currentUser.eulaIsAccepted) {
|
if (currentEula && !currentUser.eulaIsAccepted) {
|
||||||
return <Eula/>
|
return <Eula/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Button, Col, Collapse, Result, Row, Space } from "antd";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { withTranslation } from "react-i18next";
|
import { withTranslation } from "react-i18next";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import {
|
import {
|
||||||
@@ -138,7 +138,6 @@ class ErrorBoundary extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default Sentry.withErrorBoundary(
|
||||||
mapStateToProps,
|
connect(mapStateToProps, mapDispatchToProps)(withTranslation()(ErrorBoundary))
|
||||||
mapDispatchToProps
|
);
|
||||||
)(withTranslation()(ErrorBoundary));
|
|
||||||
|
|||||||
@@ -1,36 +1,42 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
import React, {useCallback, useEffect, useRef, useState} from "react";
|
||||||
import {Button, Card, Checkbox, Col, Form, Input, Modal, notification, Row, Space} from "antd";
|
import {Button, Card, Checkbox, Col, Form, Input, Modal, notification, Row} from "antd";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
import { createStructuredSelector } from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import { selectCurrentEula, selectCurrentUser } from "../../redux/user/user.selectors";
|
import {selectCurrentEula, selectCurrentUser} from "../../redux/user/user.selectors";
|
||||||
import { connect } from "react-redux";
|
import {connect} from "react-redux";
|
||||||
import { FormDatePicker } from "../form-date-picker/form-date-picker.component";
|
import {FormDatePicker} from "../form-date-picker/form-date-picker.component";
|
||||||
import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries";
|
import {INSERT_EULA_ACCEPTANCE} from "../../graphql/user.queries";
|
||||||
import { useMutation } from "@apollo/client";
|
import {useMutation} from "@apollo/client";
|
||||||
import { acceptEula } from "../../redux/user/user.actions";
|
import {acceptEula} from "../../redux/user/user.actions";
|
||||||
import { useTranslation } from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import day from '../../utils/day';
|
import day from '../../utils/day';
|
||||||
|
|
||||||
import './eula.styles.scss';
|
import './eula.styles.scss';
|
||||||
|
|
||||||
const Eula = ({ currentEula, currentUser, acceptEula }) => {
|
const Eula = ({currentEula, currentUser, acceptEula}) => {
|
||||||
const [formReady, setFormReady] = useState(false);
|
const [formReady, setFormReady] = useState(false);
|
||||||
const [hasEverScrolledToBottom, setHasEverScrolledToBottom] = useState(false);
|
const [hasEverScrolledToBottom, setHasEverScrolledToBottom] = useState(false);
|
||||||
const [insertEulaAcceptance] = useMutation(INSERT_EULA_ACCEPTANCE);
|
const [insertEulaAcceptance] = useMutation(INSERT_EULA_ACCEPTANCE);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const markdownCardRef = useRef(null);
|
const markdownCardRef = useRef(null);
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
const [api, contextHolder] = notification.useNotification();
|
const [api, contextHolder] = notification.useNotification();
|
||||||
|
|
||||||
const handleScroll = (e) => {
|
const handleScroll = useCallback((e) => {
|
||||||
const bottom = e.target.scrollHeight - 100 <= e.target.scrollTop + e.target.clientHeight;
|
const bottom = e.target.scrollHeight - 100 <= e.target.scrollTop + e.target.clientHeight;
|
||||||
if (bottom && !hasEverScrolledToBottom) {
|
if (bottom && !hasEverScrolledToBottom) {
|
||||||
setHasEverScrolledToBottom(true);
|
setHasEverScrolledToBottom(true);
|
||||||
|
} else if (e.target.scrollHeight <= e.target.clientHeight && !hasEverScrolledToBottom) {
|
||||||
|
setHasEverScrolledToBottom(true);
|
||||||
}
|
}
|
||||||
};
|
}, [hasEverScrolledToBottom, setHasEverScrolledToBottom]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleScroll({target: markdownCardRef.current});
|
||||||
|
}, [handleScroll]);
|
||||||
|
|
||||||
const handleChange = useCallback(() => {
|
const handleChange = useCallback(() => {
|
||||||
form.validateFields({ validateOnly: true })
|
form.validateFields({validateOnly: true})
|
||||||
.then(() => setFormReady(hasEverScrolledToBottom))
|
.then(() => setFormReady(hasEverScrolledToBottom))
|
||||||
.catch(() => setFormReady(false));
|
.catch(() => setFormReady(false));
|
||||||
}, [form, hasEverScrolledToBottom]);
|
}, [form, hasEverScrolledToBottom]);
|
||||||
@@ -39,25 +45,33 @@ const Eula = ({ currentEula, currentUser, acceptEula }) => {
|
|||||||
handleChange();
|
handleChange();
|
||||||
}, [handleChange, hasEverScrolledToBottom, form]);
|
}, [handleChange, hasEverScrolledToBottom, form]);
|
||||||
|
|
||||||
const onFinish = async ({ acceptTerms, ...formValues }) => {
|
const onFinish = async ({acceptTerms, ...formValues}) => {
|
||||||
const eulaId = currentEula.id;
|
const eulaId = currentEula.id;
|
||||||
const useremail = currentUser.email;
|
const useremail = currentUser.email;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { accepted_terms, ...otherFormValues } = formValues;
|
const {accepted_terms, ...otherFormValues} = formValues;
|
||||||
|
|
||||||
|
// Trim the values of the fields before submitting
|
||||||
|
const trimmedFormValues = Object.entries(otherFormValues).reduce((acc, [key, value]) => {
|
||||||
|
acc[key] = typeof value === 'string' ? value.trim() : value;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
await insertEulaAcceptance({
|
await insertEulaAcceptance({
|
||||||
variables: {
|
variables: {
|
||||||
eulaAcceptance: {
|
eulaAcceptance: {
|
||||||
eulaid: eulaId,
|
eulaid: eulaId,
|
||||||
useremail,
|
useremail,
|
||||||
...otherFormValues,
|
...otherFormValues,
|
||||||
|
...trimmedFormValues,
|
||||||
date_accepted: new Date(),
|
date_accepted: new Date(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
acceptEula();
|
acceptEula();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
api.error({
|
api.error({
|
||||||
message: t('eula.errors.acceptance.message'),
|
message: t('eula.errors.acceptance.message'),
|
||||||
description: t('eula.errors.acceptance.description'),
|
description: t('eula.errors.acceptance.description'),
|
||||||
placement: 'bottomRight',
|
placement: 'bottomRight',
|
||||||
@@ -93,46 +107,54 @@ const Eula = ({ currentEula, currentUser, acceptEula }) => {
|
|||||||
)}
|
)}
|
||||||
closable={false}
|
closable={false}
|
||||||
>
|
>
|
||||||
<Space direction='vertical'>
|
<Card type='inner' className='eula-markdown-card' onScroll={handleScroll} ref={markdownCardRef}>
|
||||||
<Card type='inner' className='eula-markdown-card' onScroll={handleScroll} ref={markdownCardRef}>
|
<div id='markdowndiv' className='eula-markdown-div'>
|
||||||
<div id='markdowndiv' className='eula-markdown-div'>
|
<Markdown children={currentEula?.content?.replace(/\\n|\\r|\\n\\r|\\r\\n/g, '\n')}/>
|
||||||
<Markdown children={currentEula?.content?.replace(/\\n/g, '\n')} />
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
|
|
||||||
|
<EulaFormComponent form={form} handleChange={handleChange} onFinish={onFinish} t={t}/>
|
||||||
|
|
||||||
|
{!hasEverScrolledToBottom && (
|
||||||
|
<Card className='eula-never-scrolled' type='inner'>
|
||||||
|
<h3>{t('eula.content.never_scrolled')}</h3>
|
||||||
</Card>
|
</Card>
|
||||||
<EulaFormComponent form={form} handleChange={handleChange} onFinish={onFinish} t={t} />
|
)}
|
||||||
{!hasEverScrolledToBottom && (
|
|
||||||
<Card className='eula-never-scrolled' type='inner'>
|
|
||||||
<h3>{t('eula.content.never_scrolled')}</h3>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
const EulaFormComponent = ({form, handleChange, onFinish, t}) => (
|
||||||
<Card type='inner' title={t('eula.titles.upper_card')}>
|
<Card type='inner' title={t('eula.titles.upper_card')} style={{marginTop: '10px'}}>
|
||||||
<Form id='tosForm' onChange={handleChange} onFinish={onFinish} form={form}>
|
<Form id='tosForm' onChange={handleChange} onFinish={onFinish} form={form}>
|
||||||
<Row gutter={24}>
|
<Row gutter={24}>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('eula.labels.first_name')}
|
label={t('eula.labels.first_name')}
|
||||||
name="first_name"
|
name="first_name"
|
||||||
rules={[{ required: true, message: t('eula.messages.first_name') }]}
|
rules={[{
|
||||||
|
required: true,
|
||||||
|
validator: (_, value) =>
|
||||||
|
value.trim() !== '' ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.first_name'))),
|
||||||
|
},]}
|
||||||
>
|
>
|
||||||
<Input placeholder={t('eula.labels.first_name')}
|
<Input placeholder={t('eula.labels.first_name')}
|
||||||
aria-label={t('eula.labels.first_name')} />
|
aria-label={t('eula.labels.first_name')}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('eula.labels.last_name')}
|
label={t('eula.labels.last_name')}
|
||||||
name="last_name"
|
name="last_name"
|
||||||
rules={[{ required: true, message: t('eula.messages.last_name') }]}
|
rules={[{
|
||||||
|
required: true,
|
||||||
|
validator: (_, value) =>
|
||||||
|
value.trim() !== '' ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.last_name'))),
|
||||||
|
}]}
|
||||||
>
|
>
|
||||||
<Input placeholder={t('eula.labels.last_name')}
|
<Input placeholder={t('eula.labels.last_name')}
|
||||||
aria-label={t('eula.labels.last_name')} />
|
aria-label={t('eula.labels.last_name')}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -141,10 +163,13 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('eula.labels.business_name')}
|
label={t('eula.labels.business_name')}
|
||||||
name="business_name"
|
name="business_name"
|
||||||
rules={[{ required: true, message: t('eula.messages.business_name') }]}
|
rules={[{
|
||||||
|
required: true,
|
||||||
|
validator: (_, value) =>
|
||||||
|
value.trim() !== '' ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.business_name'))),
|
||||||
|
}]}
|
||||||
>
|
>
|
||||||
<Input placeholder={t('eula.labels.business_name')}
|
<Input placeholder={t('eula.labels.business_name')} aria-label={t('eula.labels.business_name')}/>
|
||||||
aria-label={t('eula.labels.business_name')} />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
@@ -159,7 +184,7 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input placeholder={t('eula.labels.phone_number')}
|
<Input placeholder={t('eula.labels.phone_number')}
|
||||||
aria-label={t('eula.labels.phone_number')} />
|
aria-label={t('eula.labels.phone_number')}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -169,7 +194,7 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
|||||||
label={t('eula.labels.address')}
|
label={t('eula.labels.address')}
|
||||||
name="address"
|
name="address"
|
||||||
>
|
>
|
||||||
<Input placeholder={t('eula.labels.address')} aria-label={t('eula.labels.address')} />
|
<Input placeholder={t('eula.labels.address')} aria-label={t('eula.labels.address')}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
@@ -177,8 +202,8 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
|||||||
label={t('eula.labels.date_accepted')}
|
label={t('eula.labels.date_accepted')}
|
||||||
name="date_accepted"
|
name="date_accepted"
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true },
|
|
||||||
{
|
{
|
||||||
|
required: true,
|
||||||
validator: (_, value) => {
|
validator: (_, value) => {
|
||||||
if (day(value).isSame(day(), 'day')) {
|
if (day(value).isSame(day(), 'day')) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -188,8 +213,7 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker onChange={handleChange} onlyToday
|
<FormDatePicker onChange={handleChange} onlyToday aria-label={t('eula.labels.date_accepted')}/>
|
||||||
aria-label={t('eula.labels.date_accepted')} />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -200,13 +224,13 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
|||||||
valuePropName="checked"
|
valuePropName="checked"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
|
required: true,
|
||||||
validator: (_, value) =>
|
validator: (_, value) =>
|
||||||
value ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.accepted_terms'))),
|
value ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.accepted_terms'))),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox aria-label={t('eula.labels.accepted_terms')}>{t('eula.labels.accepted_terms')}</Checkbox>
|
||||||
aria-label={t('eula.labels.accepted_terms')}>{t('eula.labels.accepted_terms')}</Checkbox>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
246
client/src/components/job-lifecycle/job-lifecycle.component.jsx
Normal file
246
client/src/components/job-lifecycle/job-lifecycle.component.jsx
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
import React, {useCallback, useEffect, useState} from 'react';
|
||||||
|
import day from '../../utils/day';
|
||||||
|
import axios from 'axios';
|
||||||
|
import {Badge, Card, Space, Table, Tag} from 'antd';
|
||||||
|
import {gql, useQuery} from "@apollo/client";
|
||||||
|
import {DateTimeFormatterFunction} from "../../utils/DateFormatter";
|
||||||
|
import {isEmpty} from "lodash";
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
|
||||||
|
require('./job-lifecycle.styles.scss');
|
||||||
|
|
||||||
|
// show text on bar if text can fit
|
||||||
|
export function JobLifecycleComponent({job, statuses, ...rest}) {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [lifecycleData, setLifecycleData] = useState(null);
|
||||||
|
const {t} = useTranslation(); // Used for tracking external state changes.
|
||||||
|
|
||||||
|
const {data} = useQuery(gql`
|
||||||
|
query get_job_test($id: uuid!){
|
||||||
|
jobs_by_pk(id:$id){
|
||||||
|
id
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, {
|
||||||
|
variables: {
|
||||||
|
id: job.id
|
||||||
|
},
|
||||||
|
fetchPolicy: 'cache-only'
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the lifecycle data for the job.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const getLifecycleData = useCallback(async () => {
|
||||||
|
if (job && job.id && statuses && statuses.statuses) {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await axios.post("/job/lifecycle", {
|
||||||
|
jobids: job.id,
|
||||||
|
statuses: statuses.statuses
|
||||||
|
});
|
||||||
|
const data = response.data.transition[job.id];
|
||||||
|
setLifecycleData(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`${t('job_lifecycle.errors.fetch')}: ${err.message}`);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [job, statuses, t]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data) return;
|
||||||
|
setTimeout(() => {
|
||||||
|
getLifecycleData().catch(err => console.error(`${t('job_lifecycle.errors.fetch')}: ${err.message}`));
|
||||||
|
}, 500);
|
||||||
|
}, [data, getLifecycleData, t]);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.value'),
|
||||||
|
dataIndex: 'value',
|
||||||
|
key: 'value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.start'),
|
||||||
|
dataIndex: 'start',
|
||||||
|
key: 'start',
|
||||||
|
render: (text) => DateTimeFormatterFunction(text),
|
||||||
|
sorter: (a, b) => day(a.start).unix() - day(b.start).unix(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.relative_start'),
|
||||||
|
dataIndex: 'start_readable',
|
||||||
|
key: 'start_readable',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.end'),
|
||||||
|
dataIndex: 'end',
|
||||||
|
key: 'end',
|
||||||
|
sorter: (a, b) => {
|
||||||
|
if (isEmpty(a.end) || isEmpty(b.end)) {
|
||||||
|
if (isEmpty(a.end) && isEmpty(b.end)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return isEmpty(a.end) ? 1 : -1;
|
||||||
|
}
|
||||||
|
return day(a.end).unix() - day(b.end).unix();
|
||||||
|
},
|
||||||
|
render: (text) => isEmpty(text) ? t('job_lifecycle.content.not_available') : DateTimeFormatterFunction(text)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.relative_end'),
|
||||||
|
dataIndex: 'end_readable',
|
||||||
|
key: 'end_readable',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.duration'),
|
||||||
|
dataIndex: 'duration_readable',
|
||||||
|
key: 'duration_readable',
|
||||||
|
sorter: (a, b) => a.duration - b.duration,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card loading={loading} title={t('job_lifecycle.content.title')}>
|
||||||
|
{!loading ? (
|
||||||
|
lifecycleData && lifecycleData.lifecycle && lifecycleData.durations ? (
|
||||||
|
<Space direction='vertical' style={{width: '100%'}}>
|
||||||
|
<Card
|
||||||
|
type='inner'
|
||||||
|
title={(
|
||||||
|
<Space direction='horizontal' size='small'>
|
||||||
|
<Badge status='processing' count={lifecycleData.durations.totalStatuses}/>
|
||||||
|
{t('job_lifecycle.content.title_durations')}
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
)}
|
||||||
|
style={{width: '100%'}}
|
||||||
|
>
|
||||||
|
<div id="bar-container" style={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
height: '100px',
|
||||||
|
textAlign: 'center',
|
||||||
|
borderRadius: '5px',
|
||||||
|
borderWidth: '5px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderColor: '#f0f2f5',
|
||||||
|
margin: 0,
|
||||||
|
padding: 0
|
||||||
|
}}>
|
||||||
|
{lifecycleData.durations.summations.map((key, index, array) => {
|
||||||
|
const isFirst = index === 0;
|
||||||
|
const isLast = index === array.length - 1;
|
||||||
|
return (
|
||||||
|
<div key={key.status} style={{
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
|
||||||
|
borderTop: '1px solid #f0f2f5',
|
||||||
|
borderBottom: '1px solid #f0f2f5',
|
||||||
|
borderLeft: isFirst ? '1px solid #f0f2f5' : undefined,
|
||||||
|
borderRight: isLast ? '1px solid #f0f2f5' : undefined,
|
||||||
|
|
||||||
|
backgroundColor: key.color,
|
||||||
|
width: `${key.percentage}%`
|
||||||
|
}}
|
||||||
|
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
>
|
||||||
|
|
||||||
|
{key.percentage > 15 ?
|
||||||
|
<>
|
||||||
|
<div>{key.roundedPercentage}</div>
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: '#f0f2f5',
|
||||||
|
borderRadius: '5px',
|
||||||
|
paddingRight: '2px',
|
||||||
|
paddingLeft: '2px',
|
||||||
|
fontSize: '0.8rem',
|
||||||
|
}}>
|
||||||
|
{key.status}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Card type='inner' title={t('job_lifecycle.content.legend_title')}
|
||||||
|
style={{marginTop: '10px'}}>
|
||||||
|
<div>
|
||||||
|
{lifecycleData.durations.summations.map((key) => (
|
||||||
|
<Tag key={key.status} color={key.color} style={{width: '13vh', padding: '4px', margin: '4px'}}>
|
||||||
|
<div
|
||||||
|
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#f0f2f5',
|
||||||
|
color: '#000',
|
||||||
|
padding: '4px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}>
|
||||||
|
{key.status} ({key.roundedPercentage})
|
||||||
|
</div>
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
{(lifecycleData?.durations?.humanReadableTotal) ||
|
||||||
|
(lifecycleData.lifecycle[0] && lifecycleData.lifecycle[0].value && lifecycleData?.durations?.totalCurrentStatusDuration?.humanReadable) ?
|
||||||
|
<Card style={{marginTop: '10px'}}>
|
||||||
|
<ul>
|
||||||
|
{lifecycleData.durations && lifecycleData.durations.humanReadableTotal &&
|
||||||
|
<li>
|
||||||
|
<span
|
||||||
|
style={{fontWeight: 'bold'}}>{t('job_lifecycle.content.previous_status_accumulated_time')}:</span> {lifecycleData.durations.humanReadableTotal}
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
{lifecycleData.lifecycle[0] && lifecycleData.lifecycle[0].value && lifecycleData?.durations?.totalCurrentStatusDuration?.humanReadable &&
|
||||||
|
<li>
|
||||||
|
<span
|
||||||
|
style={{fontWeight: 'bold'}}>{t('job_lifecycle.content.current_status_accumulated_time')} ({lifecycleData.lifecycle[0].value}):</span> {lifecycleData.durations.totalCurrentStatusDuration.humanReadable}
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</Card>
|
||||||
|
: null}
|
||||||
|
</Card>
|
||||||
|
<Card type='inner' title={(
|
||||||
|
<>
|
||||||
|
<Space direction="horizontal" size="small">
|
||||||
|
<Badge status='processing' count={lifecycleData.lifecycle.length}/>
|
||||||
|
{t('job_lifecycle.content.title_transitions')}
|
||||||
|
</Space>
|
||||||
|
</>
|
||||||
|
)}>
|
||||||
|
<Table style={{
|
||||||
|
overflow: 'auto',
|
||||||
|
width: '100%',
|
||||||
|
}} columns={columns} dataSource={lifecycleData.lifecycle}/>
|
||||||
|
</Card>
|
||||||
|
</Space>
|
||||||
|
) : (
|
||||||
|
<Card type='inner' style={{textAlign: 'center'}}>
|
||||||
|
{t('job_lifecycle.content.data_unavailable')}
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Card type='inner' title={t('job_lifecycle.content.title_loading')}>
|
||||||
|
{t('job_lifecycle.content.loading')}
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JobLifecycleComponent;
|
||||||
@@ -51,12 +51,14 @@ export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown menu={statusMenu} trigger={["click"]} key="changestatus">
|
<>
|
||||||
<Button shape="round">
|
<Dropdown menu={statusMenu} trigger={["click"]} key="changestatus">
|
||||||
<span>{job.status}</span>
|
<Button shape="round">
|
||||||
|
<span>{job.status}</span>
|
||||||
|
|
||||||
<DownCircleFilled />
|
<DownCircleFilled />
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,18 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, notification } from "antd";
|
import { Button, Space, notification } from "antd";
|
||||||
import { gql } from "@apollo/client";
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
DELETE_DELIVERY_CHECKLIST,
|
||||||
|
DELETE_INTAKE_CHECKLIST,
|
||||||
|
} from "../../graphql/jobs.queries";
|
||||||
|
|
||||||
export default function JobAdminDeleteIntake({ job }) {
|
export default function JobAdminDeleteIntake({ job }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [deleteIntake] = useMutation(gql`
|
|
||||||
mutation DELETE_INTAKE($jobId: uuid!) {
|
|
||||||
update_jobs_by_pk(
|
|
||||||
pk_columns: { id: $jobId }
|
|
||||||
_set: { intakechecklist: null }
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
intakechecklist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const [DELETE_DELIVERY] = useMutation(gql`
|
const [deleteIntake] = useMutation(DELETE_INTAKE_CHECKLIST);
|
||||||
mutation DELETE_DELIVERY($jobId: uuid!) {
|
const [deleteDelivery] = useMutation(DELETE_DELIVERY_CHECKLIST);
|
||||||
update_jobs_by_pk(
|
|
||||||
pk_columns: { id: $jobId }
|
|
||||||
_set: { deliverchecklist: null }
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
deliverchecklist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const handleDelete = async (values) => {
|
const handleDelete = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -50,7 +34,7 @@ export default function JobAdminDeleteIntake({ job }) {
|
|||||||
|
|
||||||
const handleDeleteDelivery = async (values) => {
|
const handleDeleteDelivery = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await DELETE_DELIVERY({
|
const result = await deleteDelivery({
|
||||||
variables: { jobId: job.id },
|
variables: { jobId: job.id },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -68,12 +52,22 @@ export default function JobAdminDeleteIntake({ job }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button loading={loading} onClick={handleDelete}>
|
<Space wrap>
|
||||||
{t("jobs.labels.deleteintake")}
|
<Button
|
||||||
</Button>
|
loading={loading}
|
||||||
<Button loading={loading} onClick={handleDeleteDelivery}>
|
onClick={handleDelete}
|
||||||
{t("jobs.labels.deletedelivery")}
|
disabled={!job.intakechecklist}
|
||||||
</Button>
|
>
|
||||||
|
{t("jobs.labels.deleteintake")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
loading={loading}
|
||||||
|
onClick={handleDeleteDelivery}
|
||||||
|
disabled={!job.deliverychecklist}
|
||||||
|
>
|
||||||
|
{t("jobs.labels.deletedelivery")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { gql, useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, notification } from "antd";
|
import { Button, Space, notification } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@@ -7,6 +7,11 @@ import dayjs from "../../utils/day";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||||
|
import {
|
||||||
|
MARK_JOB_AS_EXPORTED,
|
||||||
|
MARK_JOB_AS_UNINVOICED,
|
||||||
|
MARK_JOB_FOR_REEXPORT,
|
||||||
|
} from "../../graphql/jobs.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
@@ -35,58 +40,18 @@ export function JobAdminMarkReexport({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||||
const [markJobForReexport] = useMutation(gql`
|
|
||||||
mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!) {
|
|
||||||
update_jobs_by_pk(
|
|
||||||
pk_columns: { id: $jobId }
|
|
||||||
_set: { date_exported: null
|
|
||||||
status: "${bodyshop.md_ro_statuses.default_invoiced}"
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
date_exported
|
|
||||||
status
|
|
||||||
date_invoiced
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const [markJobExported] = useMutation(gql`
|
const [markJobForReexport] = useMutation(MARK_JOB_FOR_REEXPORT);
|
||||||
mutation MARK_JOB_AS_EXPORTED($jobId: uuid!, $date_exported: timestamptz!) {
|
const [markJobExported] = useMutation(MARK_JOB_AS_EXPORTED);
|
||||||
update_jobs_by_pk(
|
const [markJobUninvoiced] = useMutation(MARK_JOB_AS_UNINVOICED);
|
||||||
pk_columns: { id: $jobId }
|
|
||||||
_set: { date_exported: $date_exported
|
|
||||||
status: "${bodyshop.md_ro_statuses.default_exported}"
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
date_exported
|
|
||||||
date_invoiced
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
const [markJobUninvoiced] = useMutation(gql`
|
|
||||||
mutation MARK_JOB_AS_UNINVOICED($jobId: uuid!, ) {
|
|
||||||
update_jobs_by_pk(
|
|
||||||
pk_columns: { id: $jobId }
|
|
||||||
_set: { date_exported: null
|
|
||||||
date_invoiced: null
|
|
||||||
status: "${bodyshop.md_ro_statuses.default_delivered}"
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
date_exported
|
|
||||||
date_invoiced
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const handleMarkForExport = async () => {
|
const handleMarkForExport = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await markJobForReexport({
|
const result = await markJobForReexport({
|
||||||
variables: { jobId: job.id },
|
variables: {
|
||||||
|
jobId: job.id,
|
||||||
|
default_invoiced: bodyshop.md_ro_statuses.default_invoiced,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.errors) {
|
if (!result.errors) {
|
||||||
@@ -108,7 +73,11 @@ export function JobAdminMarkReexport({
|
|||||||
const handleMarkExported = async () => {
|
const handleMarkExported = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await markJobExported({
|
const result = await markJobExported({
|
||||||
variables: { jobId: job.id, date_exported: dayjs() },
|
variables: {
|
||||||
|
jobId: job.id,
|
||||||
|
date_exported: dayjs(),
|
||||||
|
default_exported: bodyshop.md_ro_statuses.default_exported,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await insertExportLog({
|
await insertExportLog({
|
||||||
@@ -144,7 +113,10 @@ export function JobAdminMarkReexport({
|
|||||||
const handleUninvoice = async () => {
|
const handleUninvoice = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await markJobUninvoiced({
|
const result = await markJobUninvoiced({
|
||||||
variables: { jobId: job.id },
|
variables: {
|
||||||
|
jobId: job.id,
|
||||||
|
default_delivered: bodyshop.md_ro_statuses.default_delivered,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.errors) {
|
if (!result.errors) {
|
||||||
@@ -165,27 +137,29 @@ export function JobAdminMarkReexport({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Space wrap>
|
||||||
loading={loading}
|
<Button
|
||||||
disabled={!job.date_exported}
|
loading={loading}
|
||||||
onClick={handleMarkForExport}
|
disabled={!job.date_exported}
|
||||||
>
|
onClick={handleMarkForExport}
|
||||||
{t("jobs.labels.markforreexport")}
|
>
|
||||||
</Button>
|
{t("jobs.labels.markforreexport")}
|
||||||
<Button
|
</Button>
|
||||||
loading={loading}
|
<Button
|
||||||
disabled={job.date_exported}
|
loading={loading}
|
||||||
onClick={handleMarkExported}
|
disabled={job.date_exported}
|
||||||
>
|
onClick={handleMarkExported}
|
||||||
{t("jobs.actions.markasexported")}
|
>
|
||||||
</Button>
|
{t("jobs.actions.markasexported")}
|
||||||
<Button
|
</Button>
|
||||||
loading={loading}
|
<Button
|
||||||
disabled={!job.date_invoiced || job.date_exported}
|
loading={loading}
|
||||||
onClick={handleUninvoice}
|
disabled={!job.date_invoiced || job.date_exported}
|
||||||
>
|
onClick={handleUninvoice}
|
||||||
{t("jobs.actions.uninvoice")}
|
>
|
||||||
</Button>
|
{t("jobs.actions.uninvoice")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { useMutation } from "@apollo/client";
|
||||||
|
import { Switch, notification } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { UPDATE_REMOVE_FROM_AR } from "../../graphql/jobs.queries";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
insertAuditTrail: ({ jobid, operation }) =>
|
||||||
|
dispatch(insertAuditTrail({ jobid, operation })),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminRemoveAR);
|
||||||
|
|
||||||
|
export function JobsAdminRemoveAR({ insertAuditTrail, job }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [switchValue, setSwitchValue] = useState(job.remove_from_ar);
|
||||||
|
|
||||||
|
const [mutationUpdateRemoveFromAR] = useMutation(UPDATE_REMOVE_FROM_AR);
|
||||||
|
|
||||||
|
const handleChange = async (value) => {
|
||||||
|
setLoading(true);
|
||||||
|
const result = await mutationUpdateRemoveFromAR({
|
||||||
|
variables: { jobId: job.id, remove_from_ar: value },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.errors) {
|
||||||
|
notification["success"]({ message: t("jobs.successes.save") });
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: job.id,
|
||||||
|
operation: AuditTrailMapping.admin_job_remove_from_ar(value),
|
||||||
|
});
|
||||||
|
setSwitchValue(value);
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("jobs.errors.saving", {
|
||||||
|
error: JSON.stringify(result.errors),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<div style={{ marginRight: "10px" }}>
|
||||||
|
{t("jobs.labels.remove_from_ar")}:
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Switch
|
||||||
|
checked={switchValue}
|
||||||
|
loading={loading}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { gql, useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, notification } from "antd";
|
import { Button, notification } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
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";
|
||||||
|
import { UNVOID_JOB } from "../../graphql/jobs.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
@@ -29,66 +30,17 @@ export function JobsAdminUnvoid({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [updateJob] = useMutation(gql`
|
const [mutationUnvoidJob] = useMutation(UNVOID_JOB);
|
||||||
mutation UNVOID_JOB($jobId: uuid!) {
|
|
||||||
update_jobs_by_pk(pk_columns: {id: $jobId}, _set: {voided: false, status: "${
|
|
||||||
bodyshop.md_ro_statuses.default_imported
|
|
||||||
}", date_void: null}) {
|
|
||||||
id
|
|
||||||
date_void
|
|
||||||
voided
|
|
||||||
status
|
|
||||||
}
|
|
||||||
insert_notes(objects: {jobid: $jobId, audit: true, created_by: "${
|
|
||||||
currentUser.email
|
|
||||||
}", text: "${t("jobs.labels.unvoidnote")}"}) {
|
|
||||||
returning {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
`);
|
|
||||||
|
|
||||||
// const result = await voidJob({
|
|
||||||
// variables: {
|
|
||||||
// jobId: job.id,
|
|
||||||
// job: {
|
|
||||||
// status: bodyshop.md_ro_statuses.default_void,
|
|
||||||
// voided: true,
|
|
||||||
// },
|
|
||||||
// note: [
|
|
||||||
// {
|
|
||||||
// jobid: job.id,
|
|
||||||
// created_by: currentUser.email,
|
|
||||||
// audit: true,
|
|
||||||
// text: t("jobs.labels.voidnote", {
|
|
||||||
// date: dayjs().format("MM/DD/yyy"),
|
|
||||||
// time: dayjs().format("hh:mm a"),
|
|
||||||
// }),
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (!!!result.errors) {
|
|
||||||
// notification["success"]({
|
|
||||||
// message: t("jobs.successes.voided"),
|
|
||||||
// });
|
|
||||||
// //go back to jobs list.
|
|
||||||
// history.push(`/manage/`);
|
|
||||||
// } else {
|
|
||||||
// notification["error"]({
|
|
||||||
// message: t("jobs.errors.voiding", {
|
|
||||||
// error: JSON.stringify(result.errors),
|
|
||||||
// }),
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
const handleUpdate = async (values) => {
|
const handleUpdate = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await updateJob({
|
const result = await mutationUnvoidJob({
|
||||||
variables: { jobId: job.id },
|
variables: {
|
||||||
|
jobId: job.id,
|
||||||
|
default_imported: bodyshop.md_ro_statuses.default_imported,
|
||||||
|
currentUserEmail: currentUser.email,
|
||||||
|
text: t("jobs.labels.unvoidnote"),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.errors) {
|
if (!result.errors) {
|
||||||
@@ -110,8 +62,10 @@ mutation UNVOID_JOB($jobId: uuid!) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button loading={loading} disabled={!job.voided} onClick={handleUpdate}>
|
<>
|
||||||
{t("jobs.actions.unvoid")}
|
<Button loading={loading} disabled={!job.voided} onClick={handleUpdate}>
|
||||||
</Button>
|
{t("jobs.actions.unvoid")}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -624,7 +624,7 @@ export function JobsDetailHeaderActions({
|
|||||||
{
|
{
|
||||||
key: 'cancelallappointments',
|
key: 'cancelallappointments',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if ( job.status !== bodyshop.md_ro_statuses.default_scheduled) {
|
if (job.status !== bodyshop.md_ro_statuses.default_scheduled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showCancelScheduleModal()
|
showCancelScheduleModal()
|
||||||
@@ -713,12 +713,7 @@ export function JobsDetailHeaderActions({
|
|||||||
menuItems.push({
|
menuItems.push({
|
||||||
key: 'cccontract',
|
key: 'cccontract',
|
||||||
disabled: jobRO || !job.converted,
|
disabled: jobRO || !job.converted,
|
||||||
label: <Link
|
label: <Link state={{jobId: job.id}} to='/manage/courtesycars/contracts/new'>
|
||||||
to={{
|
|
||||||
pathname: "/manage/courtesycars/contracts/new",
|
|
||||||
state: {jobId: job.id},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("menus.jobsactions.newcccontract")}
|
{t("menus.jobsactions.newcccontract")}
|
||||||
</Link>
|
</Link>
|
||||||
});
|
});
|
||||||
@@ -993,10 +988,11 @@ export function JobsDetailHeaderActions({
|
|||||||
onOk={handleCancelScheduleOK}
|
onOk={handleCancelScheduleOK}
|
||||||
onCancel={handleCancelScheduleModalCancel}
|
onCancel={handleCancelScheduleModalCancel}
|
||||||
footer={[
|
footer={[
|
||||||
<Button form="cancelScheduleForm" key="back" onClick={handleCancelScheduleModalCancel}>
|
<Button form="cancelScheduleForm" key="back" onClick={handleCancelScheduleModalCancel}>
|
||||||
{t("general.actions.cancel")}
|
{t("general.actions.cancel")}
|
||||||
</Button>,
|
</Button>,
|
||||||
<Button form="cancelScheduleForm" htmlType="submit" key="submit" type="primary" loading={loading} onClick={handleCancelScheduleOK}>
|
<Button form="cancelScheduleForm" htmlType="submit" key="submit" type="primary" loading={loading}
|
||||||
|
onClick={handleCancelScheduleOK}>
|
||||||
{t("appointments.actions.cancel")}
|
{t("appointments.actions.cancel")}
|
||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
@@ -1004,7 +1000,7 @@ export function JobsDetailHeaderActions({
|
|||||||
<Form
|
<Form
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
id="cancelScheduleForm"
|
id="cancelScheduleForm"
|
||||||
onFinish={s =>{
|
onFinish={s => {
|
||||||
console.log(s);
|
console.log(s);
|
||||||
handleLostSaleFinish(s);
|
handleLostSaleFinish(s);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ export function PayableExportAll({
|
|||||||
|
|
||||||
if (bodyshop.pbs_serialnumber)
|
if (bodyshop.pbs_serialnumber)
|
||||||
return (
|
return (
|
||||||
<Link to={{ state: { billids }, pathname: `/manage/dmsap` }}>
|
<Link to='/manage/dmsap' state={{ billids }}>
|
||||||
<Button>{t("jobs.actions.export")}</Button>
|
<Button>{t("jobs.actions.export")}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ export function PayableExportButton({
|
|||||||
|
|
||||||
if (bodyshop.pbs_serialnumber)
|
if (bodyshop.pbs_serialnumber)
|
||||||
return (
|
return (
|
||||||
<Link to={{ state: { billids: [billId] }, pathname: `/manage/dmsap` }}>
|
<Link to='/manage/dmsap' state={{billids: [billId]}}>
|
||||||
<Button>{t("jobs.actions.export")}</Button>
|
<Button>{t("jobs.actions.export")}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export function ProductionBoardKanbanComponent({
|
|||||||
// console.log("==> New Card is somewhere in the middle");
|
// console.log("==> New Card is somewhere in the middle");
|
||||||
movedCardNewKanbanParent = newChildCard.kanbanparent;
|
movedCardNewKanbanParent = newChildCard.kanbanparent;
|
||||||
} else {
|
} else {
|
||||||
throw new Error("==> !!!!!!Couldn't find a parent.!!!! <==");
|
console.log("==> !!!!!!Couldn't find a parent.!!!! <==");
|
||||||
}
|
}
|
||||||
const newChildCardNewParent = newChildCard ? card.id : null;
|
const newChildCardNewParent = newChildCard ? card.id : null;
|
||||||
const update = await client.mutate({
|
const update = await client.mutate({
|
||||||
@@ -154,7 +154,7 @@ export function ProductionBoardKanbanComponent({
|
|||||||
0
|
0
|
||||||
)
|
)
|
||||||
.toFixed(1);
|
.toFixed(1);
|
||||||
const totalLAB = data
|
const totalLAB = data
|
||||||
.reduce(
|
.reduce(
|
||||||
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||||
0
|
0
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
|
|||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const start = values.dates[0];
|
const start = values.dates ? values.dates[0] : null;
|
||||||
const end = values.dates[1];
|
const end = values.dates ? values.dates[1] : null;
|
||||||
const { id } = values;
|
const { id } = values;
|
||||||
|
|
||||||
await GenerateDocument(
|
await GenerateDocument(
|
||||||
@@ -239,20 +239,30 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
|
|||||||
else return null;
|
else return null;
|
||||||
}}
|
}}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item style={{ margin: 0, padding: 0 }} dependencies={["key"]}>
|
||||||
name="dates"
|
{() => {
|
||||||
label={t("reportcenter.labels.dates")}
|
const key = form.getFieldValue("key");
|
||||||
rules={[
|
const datedisable = Templates[key] && Templates[key].datedisable;
|
||||||
{
|
if (datedisable !== true) {
|
||||||
required: true,
|
return (
|
||||||
//message: t("general.validation.required"),
|
<Form.Item
|
||||||
},
|
name="dates"
|
||||||
]}
|
label={t("reportcenter.labels.dates")}
|
||||||
>
|
rules={[
|
||||||
<DatePicker.RangePicker
|
{
|
||||||
format="MM/DD/YYYY"
|
required: true,
|
||||||
presets={DatePIckerRanges}
|
//message: t("general.validation.required"),
|
||||||
/>
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<DatePicker.RangePicker
|
||||||
|
format="MM/DD/YYYY"
|
||||||
|
ranges={DatePIckerRanges}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
} else return null;
|
||||||
|
}}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item style={{ margin: 0, padding: 0 }} dependencies={["key"]}>
|
<Form.Item style={{ margin: 0, padding: 0 }} dependencies={["key"]}>
|
||||||
{() => {
|
{() => {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import {useQuery} from "@apollo/client";
|
||||||
import { Col, Row } from "antd";
|
import {Col, Row} from "antd";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import React, { useMemo } from "react";
|
import React, {useMemo} from "react";
|
||||||
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";
|
||||||
import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries";
|
import {QUERY_TIME_TICKETS_IN_RANGE_SB} from "../../graphql/timetickets.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 LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||||
@@ -16,396 +16,397 @@ import ScoreboardTicketsStats from "./scoreboard-timetickets.stats.component";
|
|||||||
import ScoreboardTimeticketsTargetsTable from "./scoreboard-timetickets.targets-table.component";
|
import ScoreboardTimeticketsTargetsTable from "./scoreboard-timetickets.targets-table.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(ScoreboardTimeTicketsStats);
|
)(ScoreboardTimeTicketsStats);
|
||||||
|
|
||||||
export function ScoreboardTimeTicketsStats({ bodyshop }) {
|
export function ScoreboardTimeTicketsStats({bodyshop}) {
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
const startDate = dayjs().startOf("month")
|
const startDate = dayjs().startOf("month")
|
||||||
const endDate = dayjs().endOf("month");
|
const endDate = dayjs().endOf("month");
|
||||||
|
|
||||||
const fixedPeriods = useMemo(() => {
|
const fixedPeriods = useMemo(() => {
|
||||||
const endOfThisMonth = dayjs().endOf("month");
|
const endOfThisMonth = dayjs().endOf("month");
|
||||||
const startofthisMonth = dayjs().startOf("month");
|
const startofthisMonth = dayjs().startOf("month");
|
||||||
|
|
||||||
const endOfLastmonth = dayjs().subtract(1, "month").endOf("month");
|
const endOfLastmonth = dayjs().subtract(1, "month").endOf("month");
|
||||||
const startOfLastmonth = dayjs().subtract(1, "month").startOf("month");
|
const startOfLastmonth = dayjs().subtract(1, "month").startOf("month");
|
||||||
|
|
||||||
const endOfThisWeek = dayjs().endOf("week");
|
const endOfThisWeek = dayjs().endOf("week");
|
||||||
const startOfThisWeek = dayjs().startOf("week");
|
const startOfThisWeek = dayjs().startOf("week");
|
||||||
|
|
||||||
const endOfLastWeek = dayjs().subtract(1, "week").endOf("week");
|
const endOfLastWeek = dayjs().subtract(1, "week").endOf("week");
|
||||||
const startOfLastWeek = dayjs().subtract(1, "week").startOf("week");
|
const startOfLastWeek = dayjs().subtract(1, "week").startOf("week");
|
||||||
|
|
||||||
const endOfPriorWeek = dayjs().subtract(2, "week").endOf("week");
|
const endOfPriorWeek = dayjs().subtract(2, "week").endOf("week");
|
||||||
const startOfPriorWeek = dayjs().subtract(2, "week").startOf("week");
|
const startOfPriorWeek = dayjs().subtract(2, "week").startOf("week");
|
||||||
|
|
||||||
const allDates = [
|
const allDates = [
|
||||||
endOfThisMonth,
|
endOfThisMonth,
|
||||||
startofthisMonth,
|
startofthisMonth,
|
||||||
endOfLastmonth,
|
endOfLastmonth,
|
||||||
startOfLastmonth,
|
startOfLastmonth,
|
||||||
endOfThisWeek,
|
endOfThisWeek,
|
||||||
startOfThisWeek,
|
startOfThisWeek,
|
||||||
endOfLastWeek,
|
endOfLastWeek,
|
||||||
startOfLastWeek,
|
startOfLastWeek,
|
||||||
endOfPriorWeek,
|
endOfPriorWeek,
|
||||||
startOfPriorWeek,
|
startOfPriorWeek,
|
||||||
];
|
];
|
||||||
const start = dayjs.min(allDates);
|
const start = dayjs.min(allDates);
|
||||||
const end = dayjs.max(allDates);
|
const end = dayjs.max(allDates);
|
||||||
return {
|
return {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
endOfThisMonth,
|
endOfThisMonth,
|
||||||
startofthisMonth,
|
startofthisMonth,
|
||||||
endOfLastmonth,
|
endOfLastmonth,
|
||||||
startOfLastmonth,
|
startOfLastmonth,
|
||||||
endOfThisWeek,
|
endOfThisWeek,
|
||||||
startOfThisWeek,
|
startOfThisWeek,
|
||||||
endOfLastWeek,
|
endOfLastWeek,
|
||||||
startOfLastWeek,
|
startOfLastWeek,
|
||||||
endOfPriorWeek,
|
endOfPriorWeek,
|
||||||
startOfPriorWeek,
|
startOfPriorWeek,
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE_SB, {
|
const {loading, error, data} = useQuery(QUERY_TIME_TICKETS_IN_RANGE_SB, {
|
||||||
variables: {
|
variables: {
|
||||||
start: startDate.format("YYYY-MM-DD"),
|
start: startDate.format("YYYY-MM-DD"),
|
||||||
end: endDate.format("YYYY-MM-DD"),
|
end: endDate.format("YYYY-MM-DD"),
|
||||||
fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
|
fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
|
||||||
fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
|
fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
|
||||||
jobStart: startDate,
|
jobStart: startDate,
|
||||||
jobEnd: endDate,
|
jobEnd: endDate,
|
||||||
},
|
|
||||||
fetchPolicy: "network-only",
|
|
||||||
nextFetchPolicy: "network-only",
|
|
||||||
pollInterval: 60000,
|
|
||||||
skip: !fixedPeriods,
|
|
||||||
});
|
|
||||||
|
|
||||||
const calculatedData = useMemo(() => {
|
|
||||||
if (!data) return [];
|
|
||||||
const ret = {
|
|
||||||
totalThisWeek: 0,
|
|
||||||
totalThisWeekLAB: 0,
|
|
||||||
totalThisWeekLAR: 0,
|
|
||||||
totalLastWeek: 0,
|
|
||||||
totalLastWeekLAB: 0,
|
|
||||||
totalLastWeekLAR: 0,
|
|
||||||
totalPriorWeek: 0,
|
|
||||||
totalPriorWeekLAB: 0,
|
|
||||||
totalPriorWeekLAR: 0,
|
|
||||||
totalThisMonth: 0,
|
|
||||||
totalThisMonthLAB: 0,
|
|
||||||
totalThisMonthLAR: 0,
|
|
||||||
totalLastMonth: 0,
|
|
||||||
totalLastMonthLAB: 0,
|
|
||||||
totalLastMonthLAR: 0,
|
|
||||||
actualTotalOverPeriod: 0,
|
|
||||||
actualTotalOverPeriodLAB: 0,
|
|
||||||
actualTotalOverPeriodLAR: 0,
|
|
||||||
totalEffieciencyOverPeriod: 0,
|
|
||||||
totalEffieciencyOverPeriodLAB: 0,
|
|
||||||
totalEffieciencyOverPeriodLAR: 0,
|
|
||||||
seperatedThisWeek: {
|
|
||||||
sunday: {
|
|
||||||
total: 0,
|
|
||||||
lab: 0,
|
|
||||||
lar: 0,
|
|
||||||
},
|
},
|
||||||
monday: {
|
fetchPolicy: "network-only",
|
||||||
total: 0,
|
nextFetchPolicy: "network-only",
|
||||||
lab: 0,
|
pollInterval: 60000,
|
||||||
lar: 0,
|
skip: !fixedPeriods,
|
||||||
},
|
|
||||||
tuesday: {
|
|
||||||
total: 0,
|
|
||||||
lab: 0,
|
|
||||||
lar: 0,
|
|
||||||
},
|
|
||||||
wednesday: {
|
|
||||||
total: 0,
|
|
||||||
lab: 0,
|
|
||||||
lar: 0,
|
|
||||||
},
|
|
||||||
thursday: {
|
|
||||||
total: 0,
|
|
||||||
lab: 0,
|
|
||||||
lar: 0,
|
|
||||||
},
|
|
||||||
friday: {
|
|
||||||
total: 0,
|
|
||||||
lab: 0,
|
|
||||||
lar: 0,
|
|
||||||
},
|
|
||||||
saturday: {
|
|
||||||
total: 0,
|
|
||||||
lab: 0,
|
|
||||||
lar: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
data.fixedperiod.forEach((ticket) => {
|
|
||||||
const ticketDate = dayjs(ticket.date);
|
|
||||||
if (
|
|
||||||
ticketDate.isBetween(
|
|
||||||
fixedPeriods.startOfThisWeek,
|
|
||||||
fixedPeriods.endOfThisWeek,
|
|
||||||
undefined,
|
|
||||||
"[]"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs;
|
|
||||||
if (ticket.ciecacode !== "LAR")
|
|
||||||
ret.totalThisWeekLAB = ret.totalThisWeekLAB + ticket.productivehrs;
|
|
||||||
if (ticket.ciecacode === "LAR")
|
|
||||||
ret.totalThisWeekLAR = ret.totalThisWeekLAR + ticket.productivehrs;
|
|
||||||
|
|
||||||
//Seperate out to Day of Week
|
|
||||||
ret.seperatedThisWeek[
|
|
||||||
dayjs(ticket.date).format("dddd").toLowerCase()
|
|
||||||
].total =
|
|
||||||
ret.seperatedThisWeek[
|
|
||||||
dayjs(ticket.date).format("dddd").toLowerCase()
|
|
||||||
].total + ticket.productivehrs;
|
|
||||||
if (ticket.ciecacode !== "LAR")
|
|
||||||
ret.seperatedThisWeek[
|
|
||||||
dayjs(ticket.date).format("dddd").toLowerCase()
|
|
||||||
].lab =
|
|
||||||
ret.seperatedThisWeek[
|
|
||||||
dayjs(ticket.date).format("dddd").toLowerCase()
|
|
||||||
].lab + ticket.productivehrs;
|
|
||||||
if (ticket.ciecacode === "LAR")
|
|
||||||
ret.seperatedThisWeek[
|
|
||||||
dayjs(ticket.date).format("dddd").toLowerCase()
|
|
||||||
].lar =
|
|
||||||
ret.seperatedThisWeek[
|
|
||||||
dayjs(ticket.date).format("dddd").toLowerCase()
|
|
||||||
].lar + ticket.productivehrs;
|
|
||||||
} else if (
|
|
||||||
ticketDate.isBetween(
|
|
||||||
fixedPeriods.startOfLastWeek,
|
|
||||||
fixedPeriods.endOfLastWeek,
|
|
||||||
undefined,
|
|
||||||
"[]"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs;
|
|
||||||
if (ticket.ciecacode !== "LAR")
|
|
||||||
ret.totalLastWeekLAB = ret.totalLastWeekLAB + ticket.productivehrs;
|
|
||||||
if (ticket.ciecacode === "LAR")
|
|
||||||
ret.totalLastWeekLAR = ret.totalLastWeekLAR + ticket.productivehrs;
|
|
||||||
} else if (
|
|
||||||
ticketDate.isBetween(
|
|
||||||
fixedPeriods.startOfPriorWeek,
|
|
||||||
fixedPeriods.endOfPriorWeek,
|
|
||||||
undefined,
|
|
||||||
"[]"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
ret.totalPriorWeek = ret.totalPriorWeek + ticket.productivehrs;
|
|
||||||
if (ticket.ciecacode !== "LAR")
|
|
||||||
ret.totalPriorWeekLAB = ret.totalPriorWeekLAB + ticket.productivehrs;
|
|
||||||
if (ticket.ciecacode === "LAR")
|
|
||||||
ret.totalPriorWeekLAR = ret.totalPriorWeekLAR + ticket.productivehrs;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
ticketDate.isBetween(
|
|
||||||
fixedPeriods.startofthisMonth,
|
|
||||||
fixedPeriods.endOfThisMonth,
|
|
||||||
undefined,
|
|
||||||
"[]"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs;
|
|
||||||
ret.actualTotalOverPeriod =
|
|
||||||
ret.actualTotalOverPeriod + (ticket.actualhrs || 0);
|
|
||||||
if (ticket.ciecacode !== "LAR") {
|
|
||||||
ret.totalThisMonthLAB = ret.totalThisMonthLAB + ticket.productivehrs;
|
|
||||||
ret.actualTotalOverPeriodLAB =
|
|
||||||
ret.actualTotalOverPeriodLAB + (ticket.actualhrs || 0);
|
|
||||||
}
|
|
||||||
if (ticket.ciecacode === "LAR") {
|
|
||||||
ret.totalThisMonthLAR = ret.totalThisMonthLAR + ticket.productivehrs;
|
|
||||||
ret.actualTotalOverPeriodLAR =
|
|
||||||
ret.actualTotalOverPeriodLAR + (ticket.actualhrs || 0);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
ticketDate.isBetween(
|
|
||||||
fixedPeriods.startOfLastmonth,
|
|
||||||
fixedPeriods.endOfLastmonth,
|
|
||||||
undefined,
|
|
||||||
"[]"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs;
|
|
||||||
if (ticket.ciecacode !== "LAR")
|
|
||||||
ret.totalLastMonthLAB = ret.totalLastMonthLAB + ticket.productivehrs;
|
|
||||||
if (ticket.ciecacode === "LAR")
|
|
||||||
ret.totalLastMonthLAR = ret.totalLastMonthLAR + ticket.productivehrs;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ret.totalEffieciencyOverPeriod = ret.actualTotalOverPeriod
|
const calculatedData = useMemo(() => {
|
||||||
? (ret.totalThisMonth / ret.actualTotalOverPeriod) * 100
|
if (!data) return [];
|
||||||
: 0;
|
const ret = {
|
||||||
ret.totalEffieciencyOverPeriodLAB = ret.actualTotalOverPeriodLAB
|
totalThisWeek: 0,
|
||||||
? (ret.totalThisMonthLAB / ret.actualTotalOverPeriodLAB) * 100
|
totalThisWeekLAB: 0,
|
||||||
: 0;
|
totalThisWeekLAR: 0,
|
||||||
ret.totalEffieciencyOverPeriodLAR = ret.actualTotalOverPeriodLAR
|
totalLastWeek: 0,
|
||||||
? (ret.totalThisMonthLAR / ret.actualTotalOverPeriodLAR) * 100
|
totalLastWeekLAB: 0,
|
||||||
: 0;
|
totalLastWeekLAR: 0,
|
||||||
|
totalPriorWeek: 0,
|
||||||
|
totalPriorWeekLAB: 0,
|
||||||
|
totalPriorWeekLAR: 0,
|
||||||
|
totalThisMonth: 0,
|
||||||
|
totalThisMonthLAB: 0,
|
||||||
|
totalThisMonthLAR: 0,
|
||||||
|
totalLastMonth: 0,
|
||||||
|
totalLastMonthLAB: 0,
|
||||||
|
totalLastMonthLAR: 0,
|
||||||
|
actualTotalOverPeriod: 0,
|
||||||
|
actualTotalOverPeriodLAB: 0,
|
||||||
|
actualTotalOverPeriodLAR: 0,
|
||||||
|
totalEffieciencyOverPeriod: 0,
|
||||||
|
totalEffieciencyOverPeriodLAB: 0,
|
||||||
|
totalEffieciencyOverPeriodLAR: 0,
|
||||||
|
seperatedThisWeek: {
|
||||||
|
sunday: {
|
||||||
|
total: 0,
|
||||||
|
lab: 0,
|
||||||
|
lar: 0,
|
||||||
|
},
|
||||||
|
monday: {
|
||||||
|
total: 0,
|
||||||
|
lab: 0,
|
||||||
|
lar: 0,
|
||||||
|
},
|
||||||
|
tuesday: {
|
||||||
|
total: 0,
|
||||||
|
lab: 0,
|
||||||
|
lar: 0,
|
||||||
|
},
|
||||||
|
wednesday: {
|
||||||
|
total: 0,
|
||||||
|
lab: 0,
|
||||||
|
lar: 0,
|
||||||
|
},
|
||||||
|
thursday: {
|
||||||
|
total: 0,
|
||||||
|
lab: 0,
|
||||||
|
lar: 0,
|
||||||
|
},
|
||||||
|
friday: {
|
||||||
|
total: 0,
|
||||||
|
lab: 0,
|
||||||
|
lar: 0,
|
||||||
|
},
|
||||||
|
saturday: {
|
||||||
|
total: 0,
|
||||||
|
lab: 0,
|
||||||
|
lar: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
roundObject(ret);
|
data.fixedperiod.forEach((ticket) => {
|
||||||
|
const ticketDate = dayjs(ticket.date);
|
||||||
|
if (
|
||||||
|
ticketDate.isBetween(
|
||||||
|
fixedPeriods.startOfThisWeek,
|
||||||
|
fixedPeriods.endOfThisWeek,
|
||||||
|
undefined,
|
||||||
|
"[]"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs;
|
||||||
|
if (ticket.ciecacode !== "LAR")
|
||||||
|
ret.totalThisWeekLAB = ret.totalThisWeekLAB + ticket.productivehrs;
|
||||||
|
if (ticket.ciecacode === "LAR")
|
||||||
|
ret.totalThisWeekLAR = ret.totalThisWeekLAR + ticket.productivehrs;
|
||||||
|
|
||||||
const ticketsGroupedByDate = _.groupBy(data.timetickets, "date");
|
//Seperate out to Day of Week
|
||||||
|
ret.seperatedThisWeek[
|
||||||
const listOfDays = Utils.ListOfDaysInCurrentMonth();
|
dayjs(ticket.date).format("dddd").toLowerCase()
|
||||||
|
].total =
|
||||||
const combinedData = [],
|
ret.seperatedThisWeek[
|
||||||
labData = [],
|
dayjs(ticket.date).format("dddd").toLowerCase()
|
||||||
larData = [];
|
].total + ticket.productivehrs;
|
||||||
var acc_comb = 0;
|
if (ticket.ciecacode !== "LAR")
|
||||||
var acc_lab = 0;
|
ret.seperatedThisWeek[
|
||||||
var acc_lar = 0;
|
dayjs(ticket.date).format("dddd").toLowerCase()
|
||||||
|
].lab =
|
||||||
listOfDays.forEach((day) => {
|
ret.seperatedThisWeek[
|
||||||
const r = {
|
dayjs(ticket.date).format("dddd").toLowerCase()
|
||||||
date: dayjs(day).format("MM/DD"),
|
].lab + ticket.productivehrs;
|
||||||
actualhrs: 0,
|
if (ticket.ciecacode === "LAR")
|
||||||
productivehrs: 0,
|
ret.seperatedThisWeek[
|
||||||
};
|
dayjs(ticket.date).format("dddd").toLowerCase()
|
||||||
|
].lar =
|
||||||
const combined = {
|
ret.seperatedThisWeek[
|
||||||
accTargetHrs: _.round(
|
dayjs(ticket.date).format("dddd").toLowerCase()
|
||||||
Utils.AsOfDateTargetHours(
|
].lar + ticket.productivehrs;
|
||||||
bodyshop.scoreboard_target.dailyBodyTarget +
|
} else if (
|
||||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
ticketDate.isBetween(
|
||||||
day
|
fixedPeriods.startOfLastWeek,
|
||||||
) +
|
fixedPeriods.endOfLastWeek,
|
||||||
(bodyshop.scoreboard_target.dailyBodyTarget +
|
undefined,
|
||||||
bodyshop.scoreboard_target.dailyPaintTarget),
|
"[]"
|
||||||
1
|
)
|
||||||
),
|
) {
|
||||||
accHrs: 0,
|
ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs;
|
||||||
};
|
if (ticket.ciecacode !== "LAR")
|
||||||
const lab = {
|
ret.totalLastWeekLAB = ret.totalLastWeekLAB + ticket.productivehrs;
|
||||||
accTargetHrs: _.round(
|
if (ticket.ciecacode === "LAR")
|
||||||
Utils.AsOfDateTargetHours(
|
ret.totalLastWeekLAR = ret.totalLastWeekLAR + ticket.productivehrs;
|
||||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
} else if (
|
||||||
day
|
ticketDate.isBetween(
|
||||||
) + bodyshop.scoreboard_target.dailyBodyTarget,
|
fixedPeriods.startOfPriorWeek,
|
||||||
1
|
fixedPeriods.endOfPriorWeek,
|
||||||
),
|
undefined,
|
||||||
accHrs: 0,
|
"[]"
|
||||||
};
|
)
|
||||||
const lar = {
|
) {
|
||||||
accTargetHrs: _.round(
|
ret.totalPriorWeek = ret.totalPriorWeek + ticket.productivehrs;
|
||||||
Utils.AsOfDateTargetHours(
|
if (ticket.ciecacode !== "LAR")
|
||||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
ret.totalPriorWeekLAB = ret.totalPriorWeekLAB + ticket.productivehrs;
|
||||||
day
|
if (ticket.ciecacode === "LAR")
|
||||||
) + bodyshop.scoreboard_target.dailyPaintTarget,
|
ret.totalPriorWeekLAR = ret.totalPriorWeekLAR + ticket.productivehrs;
|
||||||
1
|
}
|
||||||
),
|
if (
|
||||||
accHrs: 0,
|
ticketDate.isBetween(
|
||||||
};
|
fixedPeriods.startofthisMonth,
|
||||||
|
fixedPeriods.endOfThisMonth,
|
||||||
if (ticketsGroupedByDate[day]) {
|
undefined,
|
||||||
ticketsGroupedByDate[day].forEach((ticket) => {
|
"[]"
|
||||||
r.actualhrs = r.actualhrs + ticket.actualhrs;
|
)
|
||||||
r.productivehrs = r.productivehrs + ticket.productivehrs;
|
) {
|
||||||
acc_comb = acc_comb + ticket.productivehrs;
|
ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs;
|
||||||
|
ret.actualTotalOverPeriod =
|
||||||
if (ticket.ciecacode !== "LAR")
|
ret.actualTotalOverPeriod + (ticket.actualhrs || 0);
|
||||||
acc_lab = acc_lab + ticket.productivehrs;
|
if (ticket.ciecacode !== "LAR") {
|
||||||
if (ticket.ciecacode === "LAR")
|
ret.totalThisMonthLAB = ret.totalThisMonthLAB + ticket.productivehrs;
|
||||||
acc_lar = acc_lar + ticket.productivehrs;
|
ret.actualTotalOverPeriodLAB =
|
||||||
|
ret.actualTotalOverPeriodLAB + (ticket.actualhrs || 0);
|
||||||
|
}
|
||||||
|
if (ticket.ciecacode === "LAR") {
|
||||||
|
ret.totalThisMonthLAR = ret.totalThisMonthLAR + ticket.productivehrs;
|
||||||
|
ret.actualTotalOverPeriodLAR =
|
||||||
|
ret.actualTotalOverPeriodLAR + (ticket.actualhrs || 0);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
ticketDate.isBetween(
|
||||||
|
fixedPeriods.startOfLastmonth,
|
||||||
|
fixedPeriods.endOfLastmonth,
|
||||||
|
undefined,
|
||||||
|
"[]"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs;
|
||||||
|
if (ticket.ciecacode !== "LAR")
|
||||||
|
ret.totalLastMonthLAB = ret.totalLastMonthLAB + ticket.productivehrs;
|
||||||
|
if (ticket.ciecacode === "LAR")
|
||||||
|
ret.totalLastMonthLAR = ret.totalLastMonthLAR + ticket.productivehrs;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
combined.accHrs = acc_comb;
|
|
||||||
lab.accHrs = acc_lab;
|
|
||||||
lar.accHrs = acc_lar;
|
|
||||||
|
|
||||||
combinedData.push({ ...r, ...combined });
|
ret.totalEffieciencyOverPeriod = ret.actualTotalOverPeriod
|
||||||
labData.push({ ...r, ...lab });
|
? (ret.totalThisMonth / ret.actualTotalOverPeriod) * 100
|
||||||
larData.push({ ...r, ...lar });
|
: 0;
|
||||||
});
|
ret.totalEffieciencyOverPeriodLAB = ret.actualTotalOverPeriodLAB
|
||||||
|
? (ret.totalThisMonthLAB / ret.actualTotalOverPeriodLAB) * 100
|
||||||
|
: 0;
|
||||||
|
ret.totalEffieciencyOverPeriodLAR = ret.actualTotalOverPeriodLAR
|
||||||
|
? (ret.totalThisMonthLAR / ret.actualTotalOverPeriodLAR) * 100
|
||||||
|
: 0;
|
||||||
|
|
||||||
const jobData = {};
|
roundObject(ret);
|
||||||
|
|
||||||
data.jobs.forEach((job) => {
|
const ticketsGroupedByDate = _.groupBy(data.timetickets, "date");
|
||||||
job.tthrs = job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
jobData.tthrs = data.jobs
|
const listOfDays = Utils.ListOfDaysInCurrentMonth();
|
||||||
.reduce((acc, val) => acc + val.tthrs, 0)
|
|
||||||
.toFixed(1);
|
|
||||||
|
|
||||||
jobData.count = data.jobs.length.toFixed(0);
|
const combinedData = [],
|
||||||
|
labData = [],
|
||||||
|
larData = [];
|
||||||
|
var acc_comb = 0;
|
||||||
|
var acc_lab = 0;
|
||||||
|
var acc_lar = 0;
|
||||||
|
|
||||||
return {
|
listOfDays.forEach((day) => {
|
||||||
fixed: ret,
|
const r = {
|
||||||
combinedData: combinedData,
|
date: dayjs(day).format("MM/DD"),
|
||||||
labData: labData,
|
actualhrs: 0,
|
||||||
larData: larData,
|
productivehrs: 0,
|
||||||
jobData: jobData,
|
};
|
||||||
};
|
|
||||||
}, [fixedPeriods, data, bodyshop]);
|
|
||||||
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
const combined = {
|
||||||
if (loading) return <LoadingSpinner />;
|
accTargetHrs: _.round(
|
||||||
return (
|
Utils.AsOfDateTargetHours(
|
||||||
<Row gutter={[16, 16]}>
|
bodyshop.scoreboard_target.dailyBodyTarget +
|
||||||
<Col span={24}>
|
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||||
<ScoreboardTimeticketsTargetsTable />
|
day
|
||||||
</Col>
|
) +
|
||||||
<Col span={24}>
|
(bodyshop.scoreboard_target.dailyBodyTarget +
|
||||||
<ScoreboardTicketsStats
|
bodyshop.scoreboard_target.dailyPaintTarget),
|
||||||
data={calculatedData.fixed}
|
1
|
||||||
jobData={calculatedData.jobData}
|
),
|
||||||
/>
|
accHrs: 0,
|
||||||
</Col>
|
};
|
||||||
<Col span={24}>
|
const lab = {
|
||||||
<ScoreboardTimeTicketsChart
|
accTargetHrs: _.round(
|
||||||
data={calculatedData.combinedData}
|
Utils.AsOfDateTargetHours(
|
||||||
chartTitle={t("scoreboard.labels.combinedcharttitle")}
|
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||||
/>
|
day
|
||||||
</Col>
|
) + bodyshop.scoreboard_target.dailyBodyTarget,
|
||||||
<Col span={12}>
|
1
|
||||||
<ScoreboardTimeTicketsChart
|
),
|
||||||
data={calculatedData.labData}
|
accHrs: 0,
|
||||||
chartTitle={t("scoreboard.labels.bodycharttitle")}
|
};
|
||||||
/>
|
const lar = {
|
||||||
</Col>
|
accTargetHrs: _.round(
|
||||||
<Col span={12}>
|
Utils.AsOfDateTargetHours(
|
||||||
<ScoreboardTimeTicketsChart
|
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||||
data={calculatedData.larData}
|
day
|
||||||
chartTitle={t("scoreboard.labels.refinishcharttitle")}
|
) + bodyshop.scoreboard_target.dailyPaintTarget,
|
||||||
/>
|
1
|
||||||
</Col>
|
),
|
||||||
</Row>
|
accHrs: 0,
|
||||||
);
|
};
|
||||||
|
|
||||||
|
if (ticketsGroupedByDate[day]) {
|
||||||
|
ticketsGroupedByDate[day].forEach((ticket) => {
|
||||||
|
r.actualhrs = r.actualhrs + ticket.actualhrs;
|
||||||
|
r.productivehrs = r.productivehrs + ticket.productivehrs;
|
||||||
|
acc_comb = acc_comb + ticket.productivehrs;
|
||||||
|
|
||||||
|
if (ticket.ciecacode !== "LAR")
|
||||||
|
acc_lab = acc_lab + ticket.productivehrs;
|
||||||
|
if (ticket.ciecacode === "LAR")
|
||||||
|
acc_lar = acc_lar + ticket.productivehrs;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
combined.accHrs = acc_comb;
|
||||||
|
lab.accHrs = acc_lab;
|
||||||
|
lar.accHrs = acc_lar;
|
||||||
|
|
||||||
|
combinedData.push({...r, ...combined});
|
||||||
|
labData.push({...r, ...lab});
|
||||||
|
larData.push({...r, ...lar});
|
||||||
|
});
|
||||||
|
|
||||||
|
const jobData = {};
|
||||||
|
|
||||||
|
const dataJobs = data.jobs.map((job) => ({
|
||||||
|
...job,
|
||||||
|
tthrs: job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
|
||||||
|
}));
|
||||||
|
|
||||||
|
jobData.tthrs = dataJobs
|
||||||
|
.reduce((acc, val) => acc + val.tthrs, 0)
|
||||||
|
.toFixed(1);
|
||||||
|
|
||||||
|
jobData.count = dataJobs.length.toFixed(0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
fixed: ret,
|
||||||
|
combinedData: combinedData,
|
||||||
|
labData: labData,
|
||||||
|
larData: larData,
|
||||||
|
jobData: jobData,
|
||||||
|
};
|
||||||
|
}, [fixedPeriods, data, bodyshop]);
|
||||||
|
|
||||||
|
if (error) return <AlertComponent message={error.message} type="error"/>;
|
||||||
|
if (loading) return <LoadingSpinner/>;
|
||||||
|
return (
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col span={24}>
|
||||||
|
<ScoreboardTimeticketsTargetsTable/>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<ScoreboardTicketsStats
|
||||||
|
data={calculatedData.fixed}
|
||||||
|
jobData={calculatedData.jobData}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<ScoreboardTimeTicketsChart
|
||||||
|
data={calculatedData.combinedData}
|
||||||
|
chartTitle={t("scoreboard.labels.combinedcharttitle")}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<ScoreboardTimeTicketsChart
|
||||||
|
data={calculatedData.labData}
|
||||||
|
chartTitle={t("scoreboard.labels.bodycharttitle")}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<ScoreboardTimeTicketsChart
|
||||||
|
data={calculatedData.larData}
|
||||||
|
chartTitle={t("scoreboard.labels.refinishcharttitle")}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function roundObject(inputObj) {
|
function roundObject(inputObj) {
|
||||||
for (var key of Object.keys(inputObj)) {
|
for (var key of Object.keys(inputObj)) {
|
||||||
if (typeof inputObj[key] === "number") {
|
if (typeof inputObj[key] === "number") {
|
||||||
inputObj[key] = inputObj[key].toFixed(1);
|
inputObj[key] = inputObj[key].toFixed(1);
|
||||||
} else if (Array.isArray(inputObj[key])) {
|
} else if (Array.isArray(inputObj[key])) {
|
||||||
inputObj[key].forEach((item) => roundObject(item));
|
inputObj[key].forEach((item) => roundObject(item));
|
||||||
} else if (typeof inputObj[key] === "object") {
|
} else if (typeof inputObj[key] === "object") {
|
||||||
roundObject(inputObj[key]);
|
roundObject(inputObj[key]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
import { onError } from "@apollo/client/link/error";
|
import { onError } from "@apollo/client/link/error";
|
||||||
//https://stackoverflow.com/questions/57163454/refreshing-a-token-with-apollo-client-firebase-auth
|
//https://stackoverflow.com/questions/57163454/refreshing-a-token-with-apollo-client-firebase-auth
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
|
|
||||||
const errorLink = onError(
|
const errorLink = onError(
|
||||||
({ graphQLErrors, networkError, operation, forward }) => {
|
({ graphQLErrors, networkError, operation, forward }) => {
|
||||||
if (graphQLErrors)
|
if (graphQLErrors) {
|
||||||
graphQLErrors.forEach(({ message, locations, path }) =>
|
graphQLErrors.forEach(({ message, locations, path }) => {
|
||||||
console.log(
|
console.log(
|
||||||
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
|
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
|
||||||
)
|
);
|
||||||
);
|
Sentry.captureException({ message, locations, path });
|
||||||
|
});
|
||||||
|
}
|
||||||
if (networkError)
|
if (networkError)
|
||||||
console.log(`[Network error]: ${JSON.stringify(networkError)}`);
|
console.log(`[Network error]: ${JSON.stringify(networkError)}`);
|
||||||
console.log(operation.getContext());
|
console.log(operation.getContext());
|
||||||
|
return forward(operation);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -545,147 +545,166 @@ export const QUERY_JOB_COSTING_DETAILS = gql`
|
|||||||
export const GET_JOB_BY_PK = gql`
|
export const GET_JOB_BY_PK = gql`
|
||||||
query GET_JOB_BY_PK($id: uuid!) {
|
query GET_JOB_BY_PK($id: uuid!) {
|
||||||
jobs_by_pk(id: $id) {
|
jobs_by_pk(id: $id) {
|
||||||
updated_at
|
actual_completion
|
||||||
|
actual_delivery
|
||||||
|
actual_in
|
||||||
|
adjustment_bottom_line
|
||||||
|
area_of_damage
|
||||||
|
auto_add_ats
|
||||||
|
available_jobs {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
alt_transport
|
||||||
|
ca_bc_pvrt
|
||||||
|
ca_customer_gst
|
||||||
|
ca_gst_registrant
|
||||||
|
category
|
||||||
|
cccontracts {
|
||||||
|
agreementnumber
|
||||||
|
courtesycar {
|
||||||
|
fleetnumber
|
||||||
|
id
|
||||||
|
make
|
||||||
|
model
|
||||||
|
plate
|
||||||
|
year
|
||||||
|
}
|
||||||
|
id
|
||||||
|
scheduledreturn
|
||||||
|
start
|
||||||
|
status
|
||||||
|
}
|
||||||
|
cieca_ttl
|
||||||
|
class
|
||||||
|
clm_no
|
||||||
|
clm_total
|
||||||
|
comment
|
||||||
|
converted
|
||||||
|
csiinvites {
|
||||||
|
completedon
|
||||||
|
id
|
||||||
|
}
|
||||||
|
date_estimated
|
||||||
|
date_exported
|
||||||
|
date_invoiced
|
||||||
|
date_last_contacted
|
||||||
|
date_lost_sale
|
||||||
|
date_next_contact
|
||||||
|
date_open
|
||||||
|
date_rentalresp
|
||||||
|
date_repairstarted
|
||||||
|
date_scheduled
|
||||||
|
date_towin
|
||||||
|
date_void
|
||||||
|
ded_amt
|
||||||
|
ded_note
|
||||||
|
ded_status
|
||||||
|
deliverchecklist
|
||||||
|
depreciation_taxes
|
||||||
|
driveable
|
||||||
|
employee_body
|
||||||
employee_body_rel {
|
employee_body_rel {
|
||||||
id
|
id
|
||||||
first_name
|
first_name
|
||||||
last_name
|
last_name
|
||||||
}
|
}
|
||||||
employee_refinish_rel {
|
employee_csr
|
||||||
id
|
|
||||||
first_name
|
|
||||||
last_name
|
|
||||||
}
|
|
||||||
employee_prep_rel {
|
|
||||||
id
|
|
||||||
first_name
|
|
||||||
last_name
|
|
||||||
}
|
|
||||||
employee_csr_rel {
|
employee_csr_rel {
|
||||||
id
|
id
|
||||||
first_name
|
first_name
|
||||||
last_name
|
last_name
|
||||||
}
|
}
|
||||||
employee_csr
|
|
||||||
employee_prep
|
employee_prep
|
||||||
|
employee_prep_rel {
|
||||||
|
id
|
||||||
|
first_name
|
||||||
|
last_name
|
||||||
|
}
|
||||||
employee_refinish
|
employee_refinish
|
||||||
employee_body
|
employee_refinish_rel {
|
||||||
alt_transport
|
id
|
||||||
intakechecklist
|
first_name
|
||||||
invoice_final_note
|
last_name
|
||||||
comment
|
}
|
||||||
loss_desc
|
est_co_nm
|
||||||
kmin
|
est_ct_fn
|
||||||
kmout
|
est_ct_ln
|
||||||
referral_source
|
est_ea
|
||||||
referral_source_extra
|
est_ph1
|
||||||
unit_number
|
federal_tax_rate
|
||||||
po_number
|
id
|
||||||
special_coverage_policy
|
|
||||||
scheduled_delivery
|
|
||||||
converted
|
|
||||||
lbr_adjustments
|
|
||||||
ro_number
|
|
||||||
po_number
|
|
||||||
clm_total
|
|
||||||
inproduction
|
inproduction
|
||||||
vehicleid
|
|
||||||
plate_no
|
|
||||||
plate_st
|
|
||||||
v_vin
|
|
||||||
v_model_yr
|
|
||||||
v_model_desc
|
|
||||||
v_make_desc
|
|
||||||
v_color
|
|
||||||
vehicleid
|
|
||||||
driveable
|
|
||||||
towin
|
|
||||||
loss_of_use
|
|
||||||
lost_sale_reason
|
|
||||||
vehicle {
|
|
||||||
id
|
|
||||||
plate_no
|
|
||||||
plate_st
|
|
||||||
v_vin
|
|
||||||
v_model_yr
|
|
||||||
v_model_desc
|
|
||||||
v_make_desc
|
|
||||||
v_color
|
|
||||||
notes
|
|
||||||
v_paint_codes
|
|
||||||
jobs {
|
|
||||||
id
|
|
||||||
ro_number
|
|
||||||
status
|
|
||||||
clm_no
|
|
||||||
}
|
|
||||||
}
|
|
||||||
available_jobs {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
ins_co_id
|
|
||||||
policy_no
|
|
||||||
loss_date
|
|
||||||
clm_no
|
|
||||||
area_of_damage
|
|
||||||
ins_co_nm
|
|
||||||
ins_addr1
|
ins_addr1
|
||||||
ins_city
|
ins_city
|
||||||
|
ins_co_id
|
||||||
|
ins_co_nm
|
||||||
ins_ct_ln
|
ins_ct_ln
|
||||||
ins_ct_fn
|
ins_ct_fn
|
||||||
ins_ea
|
ins_ea
|
||||||
ins_ph1
|
ins_ph1
|
||||||
est_co_nm
|
intakechecklist
|
||||||
est_ct_fn
|
invoice_final_note
|
||||||
est_ct_ln
|
|
||||||
est_ph1
|
|
||||||
est_ea
|
|
||||||
selling_dealer
|
|
||||||
servicing_dealer
|
|
||||||
selling_dealer_contact
|
|
||||||
servicing_dealer_contact
|
|
||||||
regie_number
|
|
||||||
scheduled_completion
|
|
||||||
id
|
|
||||||
ded_amt
|
|
||||||
ded_status
|
|
||||||
depreciation_taxes
|
|
||||||
other_amount_payable
|
|
||||||
towing_payable
|
|
||||||
storage_payable
|
|
||||||
adjustment_bottom_line
|
|
||||||
federal_tax_rate
|
|
||||||
state_tax_rate
|
|
||||||
local_tax_rate
|
|
||||||
tax_tow_rt
|
|
||||||
tax_str_rt
|
|
||||||
tax_paint_mat_rt
|
|
||||||
tax_shop_mat_rt
|
|
||||||
tax_sub_rt
|
|
||||||
tax_lbr_rt
|
|
||||||
tax_levies_rt
|
|
||||||
parts_tax_rates
|
|
||||||
job_totals
|
|
||||||
ownr_fn
|
|
||||||
ownr_ln
|
|
||||||
ownr_co_nm
|
|
||||||
ownr_ea
|
|
||||||
ownr_addr1
|
|
||||||
ownr_addr2
|
|
||||||
ownr_city
|
|
||||||
ownr_st
|
|
||||||
ownr_zip
|
|
||||||
ownr_ctry
|
|
||||||
ownr_ph1
|
|
||||||
ownr_ph2
|
|
||||||
production_vars
|
|
||||||
ca_gst_registrant
|
|
||||||
ownerid
|
|
||||||
ded_note
|
|
||||||
materials
|
|
||||||
auto_add_ats
|
|
||||||
rate_ats
|
|
||||||
iouparent
|
iouparent
|
||||||
|
job_totals
|
||||||
|
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
|
||||||
|
act_price
|
||||||
|
ah_detail_line
|
||||||
|
alt_partm
|
||||||
|
alt_partno
|
||||||
|
billlines(limit: 1, order_by: { bill: { date: desc } }) {
|
||||||
|
actual_cost
|
||||||
|
actual_price
|
||||||
|
bill {
|
||||||
|
id
|
||||||
|
invoice_number
|
||||||
|
vendor {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
joblineid
|
||||||
|
id
|
||||||
|
quantity
|
||||||
|
}
|
||||||
|
convertedtolbr
|
||||||
|
critical
|
||||||
|
db_hrs
|
||||||
|
db_price
|
||||||
|
db_ref
|
||||||
|
id
|
||||||
|
ioucreated
|
||||||
|
lbr_amt
|
||||||
|
lbr_op
|
||||||
|
line_desc
|
||||||
|
line_ind
|
||||||
|
line_no
|
||||||
|
line_ref
|
||||||
|
location
|
||||||
|
manual_line
|
||||||
|
mod_lb_hrs
|
||||||
|
mod_lbr_ty
|
||||||
|
notes
|
||||||
|
oem_partno
|
||||||
|
op_code_desc
|
||||||
|
part_qty
|
||||||
|
part_type
|
||||||
|
prt_dsmk_m
|
||||||
|
prt_dsmk_p
|
||||||
|
status
|
||||||
|
tax_part
|
||||||
|
unq_seq
|
||||||
|
}
|
||||||
|
kmin
|
||||||
|
kmout
|
||||||
|
labor_rate_desc
|
||||||
|
lbr_adjustments
|
||||||
|
local_tax_rate
|
||||||
|
loss_date
|
||||||
|
loss_desc
|
||||||
|
loss_of_use
|
||||||
|
lost_sale_reason
|
||||||
|
materials
|
||||||
|
other_amount_payable
|
||||||
owner {
|
owner {
|
||||||
id
|
id
|
||||||
ownr_fn
|
ownr_fn
|
||||||
@@ -702,7 +721,40 @@ export const GET_JOB_BY_PK = gql`
|
|||||||
ownr_ph2
|
ownr_ph2
|
||||||
tax_number
|
tax_number
|
||||||
}
|
}
|
||||||
labor_rate_desc
|
owner_owing
|
||||||
|
ownerid
|
||||||
|
ownr_addr1
|
||||||
|
ownr_addr2
|
||||||
|
ownr_ctry
|
||||||
|
ownr_city
|
||||||
|
ownr_co_nm
|
||||||
|
ownr_ea
|
||||||
|
ownr_fn
|
||||||
|
ownr_ln
|
||||||
|
ownr_ph1
|
||||||
|
ownr_ph2
|
||||||
|
ownr_st
|
||||||
|
ownr_zip
|
||||||
|
parts_tax_rates
|
||||||
|
payments {
|
||||||
|
amount
|
||||||
|
created_at
|
||||||
|
date
|
||||||
|
exportedat
|
||||||
|
id
|
||||||
|
jobid
|
||||||
|
memo
|
||||||
|
payer
|
||||||
|
paymentnum
|
||||||
|
transactionid
|
||||||
|
type
|
||||||
|
}
|
||||||
|
plate_no
|
||||||
|
plate_st
|
||||||
|
po_number
|
||||||
|
policy_no
|
||||||
|
production_vars
|
||||||
|
rate_ats
|
||||||
rate_la1
|
rate_la1
|
||||||
rate_la2
|
rate_la2
|
||||||
rate_la3
|
rate_la3
|
||||||
@@ -726,121 +778,64 @@ export const GET_JOB_BY_PK = gql`
|
|||||||
rate_mapa
|
rate_mapa
|
||||||
rate_mash
|
rate_mash
|
||||||
rate_matd
|
rate_matd
|
||||||
actual_in
|
regie_number
|
||||||
federal_tax_rate
|
referral_source
|
||||||
local_tax_rate
|
referral_source_extra
|
||||||
state_tax_rate
|
remove_from_ar
|
||||||
|
ro_number
|
||||||
scheduled_completion
|
scheduled_completion
|
||||||
scheduled_in
|
|
||||||
actual_completion
|
|
||||||
scheduled_delivery
|
scheduled_delivery
|
||||||
actual_delivery
|
scheduled_in
|
||||||
date_estimated
|
selling_dealer
|
||||||
date_open
|
servicing_dealer
|
||||||
date_scheduled
|
selling_dealer_contact
|
||||||
date_invoiced
|
servicing_dealer_contact
|
||||||
date_last_contacted
|
special_coverage_policy
|
||||||
date_lost_sale
|
state_tax_rate
|
||||||
date_next_contact
|
|
||||||
date_towin
|
|
||||||
date_rentalresp
|
|
||||||
date_exported
|
|
||||||
date_repairstarted
|
|
||||||
date_void
|
|
||||||
status
|
status
|
||||||
owner_owing
|
storage_payable
|
||||||
tax_registration_number
|
|
||||||
class
|
|
||||||
category
|
|
||||||
deliverchecklist
|
|
||||||
voided
|
|
||||||
ca_bc_pvrt
|
|
||||||
ca_customer_gst
|
|
||||||
suspended
|
suspended
|
||||||
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
|
tax_lbr_rt
|
||||||
|
tax_levies_rt
|
||||||
|
tax_paint_mat_rt
|
||||||
|
tax_registration_number
|
||||||
|
tax_shop_mat_rt
|
||||||
|
tax_str_rt
|
||||||
|
tax_sub_rt
|
||||||
|
tax_tow_rt
|
||||||
|
towin
|
||||||
|
towing_payable
|
||||||
|
unit_number
|
||||||
|
updated_at
|
||||||
|
v_vin
|
||||||
|
v_model_yr
|
||||||
|
v_model_desc
|
||||||
|
v_make_desc
|
||||||
|
v_color
|
||||||
|
vehicleid
|
||||||
|
vehicle {
|
||||||
id
|
id
|
||||||
alt_partm
|
jobs {
|
||||||
line_no
|
clm_no
|
||||||
unq_seq
|
id
|
||||||
line_ind
|
ro_number
|
||||||
line_desc
|
status
|
||||||
line_ref
|
}
|
||||||
part_type
|
|
||||||
oem_partno
|
|
||||||
alt_partno
|
|
||||||
db_price
|
|
||||||
act_price
|
|
||||||
part_qty
|
|
||||||
mod_lbr_ty
|
|
||||||
db_hrs
|
|
||||||
mod_lb_hrs
|
|
||||||
lbr_op
|
|
||||||
lbr_amt
|
|
||||||
op_code_desc
|
|
||||||
status
|
|
||||||
notes
|
notes
|
||||||
location
|
plate_no
|
||||||
tax_part
|
plate_st
|
||||||
db_ref
|
v_color
|
||||||
manual_line
|
v_make_desc
|
||||||
prt_dsmk_p
|
v_model_desc
|
||||||
prt_dsmk_m
|
v_model_yr
|
||||||
ioucreated
|
v_paint_codes
|
||||||
convertedtolbr
|
v_vin
|
||||||
ah_detail_line
|
|
||||||
critical
|
|
||||||
billlines(limit: 1, order_by: { bill: { date: desc } }) {
|
|
||||||
id
|
|
||||||
quantity
|
|
||||||
actual_cost
|
|
||||||
actual_price
|
|
||||||
joblineid
|
|
||||||
bill {
|
|
||||||
id
|
|
||||||
invoice_number
|
|
||||||
vendor {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
payments {
|
|
||||||
id
|
|
||||||
jobid
|
|
||||||
amount
|
|
||||||
payer
|
|
||||||
paymentnum
|
|
||||||
created_at
|
|
||||||
transactionid
|
|
||||||
memo
|
|
||||||
date
|
|
||||||
type
|
|
||||||
exportedat
|
|
||||||
}
|
|
||||||
cccontracts {
|
|
||||||
id
|
|
||||||
status
|
|
||||||
start
|
|
||||||
scheduledreturn
|
|
||||||
agreementnumber
|
|
||||||
courtesycar {
|
|
||||||
id
|
|
||||||
make
|
|
||||||
model
|
|
||||||
year
|
|
||||||
plate
|
|
||||||
fleetnumber
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cieca_ttl
|
|
||||||
csiinvites {
|
|
||||||
id
|
|
||||||
completedon
|
|
||||||
}
|
}
|
||||||
|
voided
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const GET_JOB_RECONCILIATION_BY_PK = gql`
|
export const GET_JOB_RECONCILIATION_BY_PK = gql`
|
||||||
query GET_JOB_RECONCILIATION_BY_PK($id: uuid!) {
|
query GET_JOB_RECONCILIATION_BY_PK($id: uuid!) {
|
||||||
bills(where: { jobid: { _eq: $id } }) {
|
bills(where: { jobid: { _eq: $id } }) {
|
||||||
@@ -905,6 +900,7 @@ export const GET_JOB_RECONCILIATION_BY_PK = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const QUERY_JOB_CARD_DETAILS = gql`
|
export const QUERY_JOB_CARD_DETAILS = gql`
|
||||||
query QUERY_JOB_CARD_DETAILS($id: uuid!) {
|
query QUERY_JOB_CARD_DETAILS($id: uuid!) {
|
||||||
jobs_by_pk(id: $id) {
|
jobs_by_pk(id: $id) {
|
||||||
@@ -2225,3 +2221,120 @@ export const GET_JOB_LINE_ORDERS = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const UPDATE_REMOVE_FROM_AR = gql`
|
||||||
|
mutation UPDATE_REMOVE_FROM_AR($jobId: uuid!, $remove_from_ar: Boolean!) {
|
||||||
|
update_jobs_by_pk(
|
||||||
|
pk_columns: { id: $jobId }
|
||||||
|
_set: { remove_from_ar: $remove_from_ar }
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
remove_from_ar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const UNVOID_JOB = gql`
|
||||||
|
mutation UNVOID_JOB(
|
||||||
|
$jobId: uuid!
|
||||||
|
$default_imported: String!
|
||||||
|
$currentUserEmail: String!
|
||||||
|
$text: String!
|
||||||
|
) {
|
||||||
|
update_jobs_by_pk(
|
||||||
|
pk_columns: { id: $jobId }
|
||||||
|
_set: { voided: false, status: $default_imported, date_void: null }
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
date_void
|
||||||
|
voided
|
||||||
|
status
|
||||||
|
}
|
||||||
|
insert_notes(
|
||||||
|
objects: {
|
||||||
|
jobid: $jobId
|
||||||
|
audit: true
|
||||||
|
created_by: $currentUserEmail
|
||||||
|
text: $text
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
returning {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DELETE_INTAKE_CHECKLIST = gql`
|
||||||
|
mutation DELETE_INTAKE($jobId: uuid!) {
|
||||||
|
update_jobs_by_pk(
|
||||||
|
pk_columns: { id: $jobId }
|
||||||
|
_set: { intakechecklist: null }
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
intakechecklist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DELETE_DELIVERY_CHECKLIST = gql`
|
||||||
|
mutation DELETE_DELIVERY($jobId: uuid!) {
|
||||||
|
update_jobs_by_pk(
|
||||||
|
pk_columns: { id: $jobId }
|
||||||
|
_set: { deliverchecklist: null }
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
deliverchecklist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const MARK_JOB_FOR_REEXPORT = gql`
|
||||||
|
mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!, $default_invoiced: String!) {
|
||||||
|
update_jobs_by_pk(
|
||||||
|
pk_columns: { id: $jobId }
|
||||||
|
_set: { date_exported: null, status: $default_invoiced }
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
date_exported
|
||||||
|
status
|
||||||
|
date_invoiced
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const MARK_JOB_AS_EXPORTED = gql`
|
||||||
|
mutation MARK_JOB_AS_EXPORTED(
|
||||||
|
$jobId: uuid!
|
||||||
|
$date_exported: timestamptz!
|
||||||
|
$default_exported: String!
|
||||||
|
) {
|
||||||
|
update_jobs_by_pk(
|
||||||
|
pk_columns: { id: $jobId }
|
||||||
|
_set: { date_exported: $date_exported, status: $default_exported }
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
date_exported
|
||||||
|
date_invoiced
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const MARK_JOB_AS_UNINVOICED = gql`
|
||||||
|
mutation MARK_JOB_AS_UNINVOICED($jobId: uuid!, $default_delivered: String!) {
|
||||||
|
update_jobs_by_pk(
|
||||||
|
pk_columns: { id: $jobId }
|
||||||
|
_set: {
|
||||||
|
date_exported: null
|
||||||
|
date_invoiced: null
|
||||||
|
status: $default_delivered
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
date_exported
|
||||||
|
date_invoiced
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import reportWebVitals from "./reportWebVitals";
|
|||||||
import "./translations/i18n";
|
import "./translations/i18n";
|
||||||
import "./utils/CleanAxios";
|
import "./utils/CleanAxios";
|
||||||
import { ConfigProvider } from "antd";
|
import { ConfigProvider } from "antd";
|
||||||
|
|
||||||
//import { BrowserTracing } from "@sentry/tracing";
|
//import { BrowserTracing } from "@sentry/tracing";
|
||||||
//import "antd/dist/antd.css";
|
//import "antd/dist/antd.css";
|
||||||
// import "antd/dist/antd.less";
|
// import "antd/dist/antd.less";
|
||||||
@@ -25,24 +26,20 @@ if (import.meta.env.PROD) {
|
|||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: "https://fd7e89369b6b4bdc9c6c4c9f22fa4ee4@o492140.ingest.sentry.io/5651027",
|
dsn: "https://fd7e89369b6b4bdc9c6c4c9f22fa4ee4@o492140.ingest.sentry.io/5651027",
|
||||||
ignoreErrors: [
|
ignoreErrors: [
|
||||||
|
"ResizeObserver loop",
|
||||||
|
"Module specifier, 'fs' does not start",
|
||||||
|
"Module specifier, 'zlib' does not start with",
|
||||||
],
|
],
|
||||||
integrations: [
|
integrations: [
|
||||||
// new BrowserTracing(),
|
Sentry.replayIntegration({
|
||||||
// new Sentry.Integrations.Breadcrumbs({ console: true }),
|
maskAllText: false,
|
||||||
// new Sentry.Replay(),
|
blockAllMedia: true,
|
||||||
|
}),
|
||||||
|
new Sentry.BrowserTracing(),
|
||||||
],
|
],
|
||||||
// This sets the sample rate to be 10%. You may want this to be 100% while
|
tracesSampleRate: 1.0,
|
||||||
// in development and sample at a lower rate in production
|
replaysOnErrorSampleRate: 1.0,
|
||||||
// replaysSessionSampleRate: 0.1,
|
|
||||||
// // If the entire session is not sampled, use the below sample rate to sample
|
|
||||||
// // sessions when an error occurs.
|
|
||||||
// replaysOnErrorSampleRate: 1.0,
|
|
||||||
environment: import.meta.env.MODE,
|
environment: import.meta.env.MODE,
|
||||||
// tracesSampleRate: 0.2,
|
|
||||||
// We recommend adjusting this value in production, or using tracesSampler
|
|
||||||
// for finer control
|
|
||||||
// tracesSampleRate: 0.5,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ import { useParams } from "react-router-dom";
|
|||||||
import AlertComponent from "../../components/alert/alert.component";
|
import AlertComponent from "../../components/alert/alert.component";
|
||||||
import JobCalculateTotals from "../../components/job-calculate-totals/job-calculate-totals.component";
|
import JobCalculateTotals from "../../components/job-calculate-totals/job-calculate-totals.component";
|
||||||
import ScoreboardAddButton from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component";
|
import ScoreboardAddButton from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component";
|
||||||
|
import JobsAdminStatus from "../../components/jobs-admin-change-status/jobs-admin-change.status.component";
|
||||||
import JobsAdminClass from "../../components/jobs-admin-class/jobs-admin-class.component";
|
import JobsAdminClass from "../../components/jobs-admin-class/jobs-admin-class.component";
|
||||||
import JobsAdminDatesChange from "../../components/jobs-admin-dates/jobs-admin-dates.component";
|
import JobsAdminDatesChange from "../../components/jobs-admin-dates/jobs-admin-dates.component";
|
||||||
import JobsAdminDeleteIntake from "../../components/jobs-admin-delete-intake/jobs-admin-delete-intake.component";
|
import JobsAdminDeleteIntake from "../../components/jobs-admin-delete-intake/jobs-admin-delete-intake.component";
|
||||||
import JobsAdminMarkReexport from "../../components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component";
|
import JobsAdminMarkReexport from "../../components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component";
|
||||||
import JobAdminOwnerReassociate from "../../components/jobs-admin-owner-reassociate/jobs-admin-owner-reassociate.component";
|
import JobAdminOwnerReassociate from "../../components/jobs-admin-owner-reassociate/jobs-admin-owner-reassociate.component";
|
||||||
|
import JobsAdminRemoveAR from "../../components/jobs-admin-remove-ar/jobs-admin-remove-ar.component";
|
||||||
import JobsAdminUnvoid from "../../components/jobs-admin-unvoid/jobs-admin-unvoid.component";
|
import JobsAdminUnvoid from "../../components/jobs-admin-unvoid/jobs-admin-unvoid.component";
|
||||||
import JobAdminVehicleReassociate from "../../components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component";
|
import JobAdminVehicleReassociate from "../../components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component";
|
||||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||||
import JobsAdminStatus from "../../components/jobs-admin-change-status/jobs-admin-change.status.component";
|
|
||||||
|
|
||||||
import NotFound from "../../components/not-found/not-found.component";
|
import NotFound from "../../components/not-found/not-found.component";
|
||||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||||
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
|
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
|
||||||
@@ -104,6 +104,7 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
|
|||||||
<JobsAdminMarkReexport job={data ? data.jobs_by_pk : {}} />
|
<JobsAdminMarkReexport job={data ? data.jobs_by_pk : {}} />
|
||||||
<JobsAdminUnvoid job={data ? data.jobs_by_pk : {}} />
|
<JobsAdminUnvoid job={data ? data.jobs_by_pk : {}} />
|
||||||
<JobsAdminStatus job={data ? data.jobs_by_pk : {}} />
|
<JobsAdminStatus job={data ? data.jobs_by_pk : {}} />
|
||||||
|
<JobsAdminRemoveAR job={data ? data.jobs_by_pk : {}} />
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import UndefinedToNull from "../../utils/undefinedtonull";
|
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||||
import { DateTimeFormat } from "./../../utils/DateFormatter";
|
import { DateTimeFormat } from "./../../utils/DateFormatter";
|
||||||
|
import JobLifecycleComponent from "../../components/job-lifecycle/job-lifecycle.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -305,6 +306,12 @@ export function JobsDetailPage({
|
|||||||
label: t("menus.jobsdetail.labor"),
|
label: t("menus.jobsdetail.labor"),
|
||||||
children: <JobsDetailLaborContainer job={job} jobId={job.id} />,
|
children: <JobsDetailLaborContainer job={job} jobId={job.id} />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'lifecycle',
|
||||||
|
icon: <BarsOutlined />,
|
||||||
|
label: t('menus.jobsdetail.lifecycle'),
|
||||||
|
children: <JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses} />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "dates",
|
key: "dates",
|
||||||
icon: <CalendarFilled />,
|
icon: <CalendarFilled />,
|
||||||
|
|||||||
@@ -99,6 +99,7 @@
|
|||||||
},
|
},
|
||||||
"audit_trail": {
|
"audit_trail": {
|
||||||
"messages": {
|
"messages": {
|
||||||
|
"admin_job_remove_from_ar": "ADMIN: Remove from AR updated to: {{status}}",
|
||||||
"admin_jobmarkexported": "ADMIN: Job marked as exported.",
|
"admin_jobmarkexported": "ADMIN: Job marked as exported.",
|
||||||
"admin_jobmarkforreexport": "ADMIN: Job marked for re-export.",
|
"admin_jobmarkforreexport": "ADMIN: Job marked for re-export.",
|
||||||
"admin_jobuninvoice": "ADMIN: Job has been uninvoiced.",
|
"admin_jobuninvoice": "ADMIN: Job has been uninvoiced.",
|
||||||
@@ -112,11 +113,11 @@
|
|||||||
"jobassignmentchange": "Employee {{name}} assigned to {{operation}}",
|
"jobassignmentchange": "Employee {{name}} assigned to {{operation}}",
|
||||||
"jobassignmentremoved": "Employee assignment removed for {{operation}}",
|
"jobassignmentremoved": "Employee assignment removed for {{operation}}",
|
||||||
"jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.",
|
"jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.",
|
||||||
"jobinvoiced": "Job has been invoiced.",
|
|
||||||
"jobconverted": "Job converted and assigned number {{ro_number}}.",
|
"jobconverted": "Job converted and assigned number {{ro_number}}.",
|
||||||
"jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.",
|
"jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.",
|
||||||
"jobimported": "Job imported.",
|
"jobimported": "Job imported.",
|
||||||
"jobinproductionchange": "Job production status set to {{inproduction}}",
|
"jobinproductionchange": "Job production status set to {{inproduction}}",
|
||||||
|
"jobinvoiced": "Job has been invoiced.",
|
||||||
"jobioucreated": "IOU Created.",
|
"jobioucreated": "IOU Created.",
|
||||||
"jobmodifylbradj": "Labor adjustments modified {{mod_lbr_ty}} / {{hours}}.",
|
"jobmodifylbradj": "Labor adjustments modified {{mod_lbr_ty}} / {{hours}}.",
|
||||||
"jobnoteadded": "Note added to Job.",
|
"jobnoteadded": "Note added to Job.",
|
||||||
@@ -254,7 +255,6 @@
|
|||||||
"saving": "Error encountered while saving. {{message}}"
|
"saving": "Error encountered while saving. {{message}}"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
|
|
||||||
"address1": "Address 1",
|
"address1": "Address 1",
|
||||||
"address2": "Address 2",
|
"address2": "Address 2",
|
||||||
"appt_alt_transport": "Appointment Alternative Transportation Options",
|
"appt_alt_transport": "Appointment Alternative Transportation Options",
|
||||||
@@ -331,6 +331,9 @@
|
|||||||
"md_ded_notes": "Deductible Notes",
|
"md_ded_notes": "Deductible Notes",
|
||||||
"md_email_cc": "Auto Email CC: $t(printcenter.subjects.jobs.{{template}})",
|
"md_email_cc": "Auto Email CC: $t(printcenter.subjects.jobs.{{template}})",
|
||||||
"md_from_emails": "Additional From Emails",
|
"md_from_emails": "Additional From Emails",
|
||||||
|
"md_functionality_toggles": {
|
||||||
|
"parts_queue_toggle": "Auto Add Imported/Supplemented Jobs to Parts Queue"
|
||||||
|
},
|
||||||
"md_hour_split": {
|
"md_hour_split": {
|
||||||
"paint": "Paint Hour Split",
|
"paint": "Paint Hour Split",
|
||||||
"prep": "Prep Hour Split"
|
"prep": "Prep Hour Split"
|
||||||
@@ -353,9 +356,6 @@
|
|||||||
},
|
},
|
||||||
"md_payment_types": "Payment Types",
|
"md_payment_types": "Payment Types",
|
||||||
"md_referral_sources": "Referral Sources",
|
"md_referral_sources": "Referral Sources",
|
||||||
"md_functionality_toggles": {
|
|
||||||
"parts_queue_toggle": "Auto Add Imported/Supplemented Jobs to Parts Queue"
|
|
||||||
},
|
|
||||||
"md_tasks_presets": {
|
"md_tasks_presets": {
|
||||||
"hourstype": "",
|
"hourstype": "",
|
||||||
"memo": "",
|
"memo": "",
|
||||||
@@ -473,6 +473,7 @@
|
|||||||
"editaccess": "Users -> Edit access"
|
"editaccess": "Users -> Edit access"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
|
||||||
"responsibilitycenter": "Responsibility Center",
|
"responsibilitycenter": "Responsibility Center",
|
||||||
"responsibilitycenter_accountdesc": "Account Description",
|
"responsibilitycenter_accountdesc": "Account Description",
|
||||||
"responsibilitycenter_accountitem": "Item",
|
"responsibilitycenter_accountitem": "Item",
|
||||||
@@ -814,6 +815,10 @@
|
|||||||
"usage": "Usage",
|
"usage": "Usage",
|
||||||
"vehicle": "Vehicle Description"
|
"vehicle": "Vehicle Description"
|
||||||
},
|
},
|
||||||
|
"readiness": {
|
||||||
|
"notready": "Not Ready",
|
||||||
|
"ready": "Ready"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"in": "Available",
|
"in": "Available",
|
||||||
"inservice": "In Service",
|
"inservice": "In Service",
|
||||||
@@ -823,10 +828,6 @@
|
|||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"saved": "Courtesy Car saved successfully."
|
"saved": "Courtesy Car saved successfully."
|
||||||
},
|
|
||||||
"readiness": {
|
|
||||||
"notready": "Not Ready",
|
|
||||||
"ready": "Ready"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"csi": {
|
"csi": {
|
||||||
@@ -1248,6 +1249,31 @@
|
|||||||
"updated": "Inventory line updated."
|
"updated": "Inventory line updated."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"job_lifecycle": {
|
||||||
|
"columns": {
|
||||||
|
"duration": "Duration",
|
||||||
|
"end": "End",
|
||||||
|
"relative_end": "Relative End",
|
||||||
|
"relative_start": "Relative Start",
|
||||||
|
"start": "Start",
|
||||||
|
"value": "Value"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"current_status_accumulated_time": "Current Status Accumulated Time",
|
||||||
|
"data_unavailable": " There is currently no Lifecycle data for this Job.",
|
||||||
|
"legend_title": "Legend",
|
||||||
|
"loading": "Loading Job Timelines....",
|
||||||
|
"not_available": "N/A",
|
||||||
|
"previous_status_accumulated_time": "Previous Status Accumulated Time",
|
||||||
|
"title": "Job Lifecycle Component",
|
||||||
|
"title_durations": "Historical Status Durations",
|
||||||
|
"title_loading": "Loading",
|
||||||
|
"title_transitions": "Transitions"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fetch": "Error getting Job Lifecycle Data"
|
||||||
|
}
|
||||||
|
},
|
||||||
"job_payments": {
|
"job_payments": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"goback": "Go Back",
|
"goback": "Go Back",
|
||||||
@@ -1868,6 +1894,7 @@
|
|||||||
},
|
},
|
||||||
"reconciliationheader": "Parts & Sublet Reconciliation",
|
"reconciliationheader": "Parts & Sublet Reconciliation",
|
||||||
"relatedros": "Related ROs",
|
"relatedros": "Related ROs",
|
||||||
|
"remove_from_ar": "Remove from AR",
|
||||||
"returntotals": "Return Totals",
|
"returntotals": "Return Totals",
|
||||||
"rosaletotal": "RO Parts Total",
|
"rosaletotal": "RO Parts Total",
|
||||||
"sale_additional": "Sales - Additional",
|
"sale_additional": "Sales - Additional",
|
||||||
@@ -2047,6 +2074,7 @@
|
|||||||
"general": "General",
|
"general": "General",
|
||||||
"insurance": "Insurance Information",
|
"insurance": "Insurance Information",
|
||||||
"labor": "Labor",
|
"labor": "Labor",
|
||||||
|
"lifecycle": "Lifecycle",
|
||||||
"partssublet": "Parts & Bills",
|
"partssublet": "Parts & Bills",
|
||||||
"rates": "Rates",
|
"rates": "Rates",
|
||||||
"repairdata": "Repair Data",
|
"repairdata": "Repair Data",
|
||||||
@@ -2598,6 +2626,7 @@
|
|||||||
},
|
},
|
||||||
"templates": {
|
"templates": {
|
||||||
"anticipated_revenue": "Anticipated Revenue",
|
"anticipated_revenue": "Anticipated Revenue",
|
||||||
|
"ar_aging": "AR Aging",
|
||||||
"attendance_detail": "Attendance (All Employees)",
|
"attendance_detail": "Attendance (All Employees)",
|
||||||
"attendance_employee": "Employee Attendance",
|
"attendance_employee": "Employee Attendance",
|
||||||
"attendance_summary": "Attendance Summary (All Employees)",
|
"attendance_summary": "Attendance Summary (All Employees)",
|
||||||
@@ -2954,8 +2983,8 @@
|
|||||||
"shop-templates": "Shop Templates | $t(titles.app)",
|
"shop-templates": "Shop Templates | $t(titles.app)",
|
||||||
"shop_vendors": "Vendors | $t(titles.app)",
|
"shop_vendors": "Vendors | $t(titles.app)",
|
||||||
"techconsole": "Technician Console | $t(titles.app)",
|
"techconsole": "Technician Console | $t(titles.app)",
|
||||||
"techjoblookup": "Technician Job Lookup | $t(titles.app)",
|
|
||||||
"techjobclock": "Technician Job Clock | $t(titles.app)",
|
"techjobclock": "Technician Job Clock | $t(titles.app)",
|
||||||
|
"techjoblookup": "Technician Job Lookup | $t(titles.app)",
|
||||||
"techshiftclock": "Technician Shift Clock | $t(titles.app)",
|
"techshiftclock": "Technician Shift Clock | $t(titles.app)",
|
||||||
"temporarydocs": "Temporary Documents | $t(titles.app)",
|
"temporarydocs": "Temporary Documents | $t(titles.app)",
|
||||||
"timetickets": "Time Tickets | $t(titles.app)",
|
"timetickets": "Time Tickets | $t(titles.app)",
|
||||||
|
|||||||
@@ -99,6 +99,7 @@
|
|||||||
},
|
},
|
||||||
"audit_trail": {
|
"audit_trail": {
|
||||||
"messages": {
|
"messages": {
|
||||||
|
"admin_job_remove_from_ar": "",
|
||||||
"admin_jobmarkexported": "",
|
"admin_jobmarkexported": "",
|
||||||
"admin_jobmarkforreexport": "",
|
"admin_jobmarkforreexport": "",
|
||||||
"admin_jobuninvoice": "",
|
"admin_jobuninvoice": "",
|
||||||
@@ -112,11 +113,11 @@
|
|||||||
"jobassignmentchange": "",
|
"jobassignmentchange": "",
|
||||||
"jobassignmentremoved": "",
|
"jobassignmentremoved": "",
|
||||||
"jobchecklist": "",
|
"jobchecklist": "",
|
||||||
"jobinvoiced": "",
|
|
||||||
"jobconverted": "",
|
"jobconverted": "",
|
||||||
"jobfieldchanged": "",
|
"jobfieldchanged": "",
|
||||||
"jobimported": "",
|
"jobimported": "",
|
||||||
"jobinproductionchange": "",
|
"jobinproductionchange": "",
|
||||||
|
"jobinvoiced": "",
|
||||||
"jobioucreated": "",
|
"jobioucreated": "",
|
||||||
"jobmodifylbradj": "",
|
"jobmodifylbradj": "",
|
||||||
"jobnoteadded": "",
|
"jobnoteadded": "",
|
||||||
@@ -254,13 +255,9 @@
|
|||||||
"saving": ""
|
"saving": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"ReceivableCustomField": "",
|
|
||||||
"address1": "",
|
"address1": "",
|
||||||
"address2": "",
|
"address2": "",
|
||||||
"appt_alt_transport": "",
|
"appt_alt_transport": "",
|
||||||
"md_functionality_toggles": {
|
|
||||||
"parts_queue_toggle": ""
|
|
||||||
},
|
|
||||||
"appt_colors": {
|
"appt_colors": {
|
||||||
"color": "",
|
"color": "",
|
||||||
"label": ""
|
"label": ""
|
||||||
@@ -334,6 +331,9 @@
|
|||||||
"md_ded_notes": "",
|
"md_ded_notes": "",
|
||||||
"md_email_cc": "",
|
"md_email_cc": "",
|
||||||
"md_from_emails": "",
|
"md_from_emails": "",
|
||||||
|
"md_functionality_toggles": {
|
||||||
|
"parts_queue_toggle": ""
|
||||||
|
},
|
||||||
"md_hour_split": {
|
"md_hour_split": {
|
||||||
"paint": "",
|
"paint": "",
|
||||||
"prep": ""
|
"prep": ""
|
||||||
@@ -473,6 +473,7 @@
|
|||||||
"editaccess": ""
|
"editaccess": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ReceivableCustomField": "",
|
||||||
"responsibilitycenter": "",
|
"responsibilitycenter": "",
|
||||||
"responsibilitycenter_accountdesc": "",
|
"responsibilitycenter_accountdesc": "",
|
||||||
"responsibilitycenter_accountitem": "",
|
"responsibilitycenter_accountitem": "",
|
||||||
@@ -814,6 +815,10 @@
|
|||||||
"usage": "",
|
"usage": "",
|
||||||
"vehicle": ""
|
"vehicle": ""
|
||||||
},
|
},
|
||||||
|
"readiness": {
|
||||||
|
"notready": "",
|
||||||
|
"ready": ""
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"in": "",
|
"in": "",
|
||||||
"inservice": "",
|
"inservice": "",
|
||||||
@@ -823,10 +828,6 @@
|
|||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"saved": ""
|
"saved": ""
|
||||||
},
|
|
||||||
"readiness": {
|
|
||||||
"notready": "",
|
|
||||||
"ready": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"csi": {
|
"csi": {
|
||||||
@@ -1248,6 +1249,31 @@
|
|||||||
"updated": ""
|
"updated": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"job_lifecycle": {
|
||||||
|
"columns": {
|
||||||
|
"duration": "",
|
||||||
|
"end": "",
|
||||||
|
"relative_end": "",
|
||||||
|
"relative_start": "",
|
||||||
|
"start": "",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"current_status_accumulated_time": "",
|
||||||
|
"data_unavailable": "",
|
||||||
|
"legend_title": "",
|
||||||
|
"loading": "",
|
||||||
|
"not_available": "",
|
||||||
|
"previous_status_accumulated_time": "",
|
||||||
|
"title": "",
|
||||||
|
"title_durations": "",
|
||||||
|
"title_loading": "",
|
||||||
|
"title_transitions": ""
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fetch": "Error al obtener los datos del ciclo de vida del trabajo"
|
||||||
|
}
|
||||||
|
},
|
||||||
"job_payments": {
|
"job_payments": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"goback": "",
|
"goback": "",
|
||||||
@@ -1868,6 +1894,7 @@
|
|||||||
},
|
},
|
||||||
"reconciliationheader": "",
|
"reconciliationheader": "",
|
||||||
"relatedros": "",
|
"relatedros": "",
|
||||||
|
"remove_from_ar": "",
|
||||||
"returntotals": "",
|
"returntotals": "",
|
||||||
"rosaletotal": "",
|
"rosaletotal": "",
|
||||||
"sale_additional": "",
|
"sale_additional": "",
|
||||||
@@ -2047,6 +2074,7 @@
|
|||||||
"general": "",
|
"general": "",
|
||||||
"insurance": "",
|
"insurance": "",
|
||||||
"labor": "Labor",
|
"labor": "Labor",
|
||||||
|
"lifecycle": "",
|
||||||
"partssublet": "Piezas / Subarrendamiento",
|
"partssublet": "Piezas / Subarrendamiento",
|
||||||
"rates": "",
|
"rates": "",
|
||||||
"repairdata": "Datos de reparación",
|
"repairdata": "Datos de reparación",
|
||||||
@@ -2598,6 +2626,7 @@
|
|||||||
},
|
},
|
||||||
"templates": {
|
"templates": {
|
||||||
"anticipated_revenue": "",
|
"anticipated_revenue": "",
|
||||||
|
"ar_aging": "",
|
||||||
"attendance_detail": "",
|
"attendance_detail": "",
|
||||||
"attendance_employee": "",
|
"attendance_employee": "",
|
||||||
"attendance_summary": "",
|
"attendance_summary": "",
|
||||||
@@ -2954,8 +2983,8 @@
|
|||||||
"shop-templates": "",
|
"shop-templates": "",
|
||||||
"shop_vendors": "Vendedores | $t(titles.app)",
|
"shop_vendors": "Vendedores | $t(titles.app)",
|
||||||
"techconsole": "$t(titles.app)",
|
"techconsole": "$t(titles.app)",
|
||||||
"techjoblookup": "$t(titles.app)",
|
|
||||||
"techjobclock": "$t(titles.app)",
|
"techjobclock": "$t(titles.app)",
|
||||||
|
"techjoblookup": "$t(titles.app)",
|
||||||
"techshiftclock": "$t(titles.app)",
|
"techshiftclock": "$t(titles.app)",
|
||||||
"temporarydocs": "",
|
"temporarydocs": "",
|
||||||
"timetickets": "",
|
"timetickets": "",
|
||||||
|
|||||||
@@ -99,6 +99,7 @@
|
|||||||
},
|
},
|
||||||
"audit_trail": {
|
"audit_trail": {
|
||||||
"messages": {
|
"messages": {
|
||||||
|
"admin_job_remove_from_ar": "",
|
||||||
"admin_jobmarkexported": "",
|
"admin_jobmarkexported": "",
|
||||||
"admin_jobmarkforreexport": "",
|
"admin_jobmarkforreexport": "",
|
||||||
"admin_jobuninvoice": "",
|
"admin_jobuninvoice": "",
|
||||||
@@ -112,11 +113,11 @@
|
|||||||
"jobassignmentchange": "",
|
"jobassignmentchange": "",
|
||||||
"jobassignmentremoved": "",
|
"jobassignmentremoved": "",
|
||||||
"jobchecklist": "",
|
"jobchecklist": "",
|
||||||
"jobinvoiced": "",
|
|
||||||
"jobconverted": "",
|
"jobconverted": "",
|
||||||
"jobfieldchanged": "",
|
"jobfieldchanged": "",
|
||||||
"jobimported": "",
|
"jobimported": "",
|
||||||
"jobinproductionchange": "",
|
"jobinproductionchange": "",
|
||||||
|
"jobinvoiced": "",
|
||||||
"jobioucreated": "",
|
"jobioucreated": "",
|
||||||
"jobmodifylbradj": "",
|
"jobmodifylbradj": "",
|
||||||
"jobnoteadded": "",
|
"jobnoteadded": "",
|
||||||
@@ -254,7 +255,6 @@
|
|||||||
"saving": ""
|
"saving": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"ReceivableCustomField": "",
|
|
||||||
"address1": "",
|
"address1": "",
|
||||||
"address2": "",
|
"address2": "",
|
||||||
"appt_alt_transport": "",
|
"appt_alt_transport": "",
|
||||||
@@ -331,13 +331,13 @@
|
|||||||
"md_ded_notes": "",
|
"md_ded_notes": "",
|
||||||
"md_email_cc": "",
|
"md_email_cc": "",
|
||||||
"md_from_emails": "",
|
"md_from_emails": "",
|
||||||
|
"md_functionality_toggles": {
|
||||||
|
"parts_queue_toggle": ""
|
||||||
|
},
|
||||||
"md_hour_split": {
|
"md_hour_split": {
|
||||||
"paint": "",
|
"paint": "",
|
||||||
"prep": ""
|
"prep": ""
|
||||||
},
|
},
|
||||||
"md_functionality_toggles": {
|
|
||||||
"parts_queue_toggle": ""
|
|
||||||
},
|
|
||||||
"md_ins_co": {
|
"md_ins_co": {
|
||||||
"city": "",
|
"city": "",
|
||||||
"name": "",
|
"name": "",
|
||||||
@@ -473,6 +473,7 @@
|
|||||||
"editaccess": ""
|
"editaccess": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ReceivableCustomField": "",
|
||||||
"responsibilitycenter": "",
|
"responsibilitycenter": "",
|
||||||
"responsibilitycenter_accountdesc": "",
|
"responsibilitycenter_accountdesc": "",
|
||||||
"responsibilitycenter_accountitem": "",
|
"responsibilitycenter_accountitem": "",
|
||||||
@@ -814,6 +815,10 @@
|
|||||||
"usage": "",
|
"usage": "",
|
||||||
"vehicle": ""
|
"vehicle": ""
|
||||||
},
|
},
|
||||||
|
"readiness": {
|
||||||
|
"notready": "",
|
||||||
|
"ready": ""
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"in": "",
|
"in": "",
|
||||||
"inservice": "",
|
"inservice": "",
|
||||||
@@ -823,10 +828,6 @@
|
|||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"saved": ""
|
"saved": ""
|
||||||
},
|
|
||||||
"readiness": {
|
|
||||||
"notready": "",
|
|
||||||
"ready": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"csi": {
|
"csi": {
|
||||||
@@ -1248,6 +1249,31 @@
|
|||||||
"updated": ""
|
"updated": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"job_lifecycle": {
|
||||||
|
"columns": {
|
||||||
|
"duration": "",
|
||||||
|
"end": "",
|
||||||
|
"relative_end": "",
|
||||||
|
"relative_start": "",
|
||||||
|
"start": "",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"current_status_accumulated_time": "",
|
||||||
|
"data_unavailable": "",
|
||||||
|
"legend_title": "",
|
||||||
|
"loading": "",
|
||||||
|
"not_available": "",
|
||||||
|
"previous_status_accumulated_time": "",
|
||||||
|
"title": "",
|
||||||
|
"title_durations": "",
|
||||||
|
"title_loading": "",
|
||||||
|
"title_transitions": ""
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches"
|
||||||
|
}
|
||||||
|
},
|
||||||
"job_payments": {
|
"job_payments": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"goback": "",
|
"goback": "",
|
||||||
@@ -1868,6 +1894,7 @@
|
|||||||
},
|
},
|
||||||
"reconciliationheader": "",
|
"reconciliationheader": "",
|
||||||
"relatedros": "",
|
"relatedros": "",
|
||||||
|
"remove_from_ar": "",
|
||||||
"returntotals": "",
|
"returntotals": "",
|
||||||
"rosaletotal": "",
|
"rosaletotal": "",
|
||||||
"sale_additional": "",
|
"sale_additional": "",
|
||||||
@@ -2047,6 +2074,7 @@
|
|||||||
"general": "",
|
"general": "",
|
||||||
"insurance": "",
|
"insurance": "",
|
||||||
"labor": "La main d'oeuvre",
|
"labor": "La main d'oeuvre",
|
||||||
|
"lifecycle": "",
|
||||||
"partssublet": "Pièces / Sous-location",
|
"partssublet": "Pièces / Sous-location",
|
||||||
"rates": "",
|
"rates": "",
|
||||||
"repairdata": "Données de réparation",
|
"repairdata": "Données de réparation",
|
||||||
@@ -2598,6 +2626,7 @@
|
|||||||
},
|
},
|
||||||
"templates": {
|
"templates": {
|
||||||
"anticipated_revenue": "",
|
"anticipated_revenue": "",
|
||||||
|
"ar_aging": "",
|
||||||
"attendance_detail": "",
|
"attendance_detail": "",
|
||||||
"attendance_employee": "",
|
"attendance_employee": "",
|
||||||
"attendance_summary": "",
|
"attendance_summary": "",
|
||||||
@@ -2954,8 +2983,8 @@
|
|||||||
"shop-templates": "",
|
"shop-templates": "",
|
||||||
"shop_vendors": "Vendeurs | $t(titles.app)",
|
"shop_vendors": "Vendeurs | $t(titles.app)",
|
||||||
"techconsole": "$t(titles.app)",
|
"techconsole": "$t(titles.app)",
|
||||||
"techjoblookup": "$t(titles.app)",
|
|
||||||
"techjobclock": "$t(titles.app)",
|
"techjobclock": "$t(titles.app)",
|
||||||
|
"techjoblookup": "$t(titles.app)",
|
||||||
"techshiftclock": "$t(titles.app)",
|
"techshiftclock": "$t(titles.app)",
|
||||||
"temporarydocs": "",
|
"temporarydocs": "",
|
||||||
"timetickets": "",
|
"timetickets": "",
|
||||||
|
|||||||
@@ -1,54 +1,56 @@
|
|||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
|
|
||||||
const AuditTrailMapping = {
|
const AuditTrailMapping = {
|
||||||
alertToggle: (status) => i18n.t("audit_trail.messages.alerttoggle", { status }),
|
admin_job_remove_from_ar: (status) =>
|
||||||
|
i18n.t("audit_trail.messages.admin_job_remove_from_ar", { status }),
|
||||||
|
admin_jobfieldchange: (field, value) =>
|
||||||
|
"ADMIN: " +
|
||||||
|
i18n.t("audit_trail.messages.jobfieldchanged", { field, value }),
|
||||||
|
admin_jobmarkexported: () =>
|
||||||
|
i18n.t("audit_trail.messages.admin_jobmarkexported"),
|
||||||
|
admin_jobmarkforreexport: () =>
|
||||||
|
i18n.t("audit_trail.messages.admin_jobmarkforreexport"),
|
||||||
|
admin_jobstatuschange: (status) =>
|
||||||
|
"ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", { status }),
|
||||||
|
admin_jobuninvoice: () => i18n.t("audit_trail.messages.admin_jobuninvoice"),
|
||||||
|
admin_jobunvoid: () => i18n.t("audit_trail.messages.admin_jobunvoid"),
|
||||||
|
alertToggle: (status) =>
|
||||||
|
i18n.t("audit_trail.messages.alerttoggle", { status }),
|
||||||
appointmentcancel: (lost_sale_reason) =>
|
appointmentcancel: (lost_sale_reason) =>
|
||||||
i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }),
|
i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }),
|
||||||
appointmentinsert: (start) =>
|
appointmentinsert: (start) =>
|
||||||
i18n.t("audit_trail.messages.appointmentinsert", { start }),
|
i18n.t("audit_trail.messages.appointmentinsert", { start }),
|
||||||
jobstatuschange: (status) =>
|
|
||||||
i18n.t("audit_trail.messages.jobstatuschange", { status }),
|
|
||||||
admin_jobstatuschange: (status) =>
|
|
||||||
"ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", { status }),
|
|
||||||
jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"),
|
|
||||||
jobimported: () => i18n.t("audit_trail.messages.jobimported"),
|
|
||||||
jobinvoiced: () =>
|
|
||||||
i18n.t("audit_trail.messages.jobinvoiced"),
|
|
||||||
jobconverted: (ro_number) =>
|
|
||||||
i18n.t("audit_trail.messages.jobconverted", { ro_number }),
|
|
||||||
jobfieldchange: (field, value) =>
|
|
||||||
i18n.t("audit_trail.messages.jobfieldchanged", { field, value }),
|
|
||||||
admin_jobfieldchange: (field, value) =>
|
|
||||||
"ADMIN: " +
|
|
||||||
i18n.t("audit_trail.messages.jobfieldchanged", { field, value }),
|
|
||||||
jobspartsorder: (order_number) =>
|
|
||||||
i18n.t("audit_trail.messages.jobspartsorder", { order_number }),
|
|
||||||
jobspartsreturn: (order_number) =>
|
|
||||||
i18n.t("audit_trail.messages.jobspartsreturn", { order_number }),
|
|
||||||
jobmodifylbradj: ({ mod_lbr_ty, hours }) =>
|
|
||||||
i18n.t("audit_trail.messages.jobmodifylbradj", { mod_lbr_ty, hours }),
|
|
||||||
billposted: (invoice_number) =>
|
billposted: (invoice_number) =>
|
||||||
i18n.t("audit_trail.messages.billposted", { invoice_number }),
|
i18n.t("audit_trail.messages.billposted", { invoice_number }),
|
||||||
billupdated: (invoice_number) =>
|
billupdated: (invoice_number) =>
|
||||||
i18n.t("audit_trail.messages.billupdated", { invoice_number }),
|
i18n.t("audit_trail.messages.billupdated", { invoice_number }),
|
||||||
|
failedpayment: () => i18n.t("audit_trail.messages.failedpayment"),
|
||||||
jobassignmentchange: (operation, name) =>
|
jobassignmentchange: (operation, name) =>
|
||||||
i18n.t("audit_trail.messages.jobassignmentchange", { operation, name }),
|
i18n.t("audit_trail.messages.jobassignmentchange", { operation, name }),
|
||||||
jobassignmentremoved: (operation) =>
|
jobassignmentremoved: (operation) =>
|
||||||
i18n.t("audit_trail.messages.jobassignmentremoved", { operation }),
|
i18n.t("audit_trail.messages.jobassignmentremoved", { operation }),
|
||||||
jobinproductionchange: (inproduction) =>
|
|
||||||
i18n.t("audit_trail.messages.jobinproductionchange", { inproduction }),
|
|
||||||
jobchecklist: (type, inproduction, status) =>
|
jobchecklist: (type, inproduction, status) =>
|
||||||
i18n.t("audit_trail.messages.jobchecklist", { type, inproduction, status }),
|
i18n.t("audit_trail.messages.jobchecklist", { type, inproduction, status }),
|
||||||
|
jobconverted: (ro_number) =>
|
||||||
|
i18n.t("audit_trail.messages.jobconverted", { ro_number }),
|
||||||
|
jobfieldchange: (field, value) =>
|
||||||
|
i18n.t("audit_trail.messages.jobfieldchanged", { field, value }),
|
||||||
|
jobimported: () => i18n.t("audit_trail.messages.jobimported"),
|
||||||
|
jobinproductionchange: (inproduction) =>
|
||||||
|
i18n.t("audit_trail.messages.jobinproductionchange", { inproduction }),
|
||||||
|
jobinvoiced: () => i18n.t("audit_trail.messages.jobinvoiced"),
|
||||||
|
jobmodifylbradj: ({ mod_lbr_ty, hours }) =>
|
||||||
|
i18n.t("audit_trail.messages.jobmodifylbradj", { mod_lbr_ty, hours }),
|
||||||
jobnoteadded: () => i18n.t("audit_trail.messages.jobnoteadded"),
|
jobnoteadded: () => i18n.t("audit_trail.messages.jobnoteadded"),
|
||||||
jobnoteupdated: () => i18n.t("audit_trail.messages.jobnoteupdated"),
|
|
||||||
jobnotedeleted: () => i18n.t("audit_trail.messages.jobnotedeleted"),
|
jobnotedeleted: () => i18n.t("audit_trail.messages.jobnotedeleted"),
|
||||||
admin_jobunvoid: () => i18n.t("audit_trail.messages.admin_jobunvoid"),
|
jobnoteupdated: () => i18n.t("audit_trail.messages.jobnoteupdated"),
|
||||||
admin_jobuninvoice: () => i18n.t("audit_trail.messages.admin_jobuninvoice"),
|
jobspartsorder: (order_number) =>
|
||||||
admin_jobmarkforreexport: () =>
|
i18n.t("audit_trail.messages.jobspartsorder", { order_number }),
|
||||||
i18n.t("audit_trail.messages.admin_jobmarkforreexport"),
|
jobspartsreturn: (order_number) =>
|
||||||
admin_jobmarkexported: () =>
|
i18n.t("audit_trail.messages.jobspartsreturn", { order_number }),
|
||||||
i18n.t("audit_trail.messages.admin_jobmarkexported"),
|
jobstatuschange: (status) =>
|
||||||
failedpayment: () => i18n.t("audit_trail.messages.failedpayment"),
|
i18n.t("audit_trail.messages.jobstatuschange", { status }),
|
||||||
|
jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuditTrailMapping;
|
export default AuditTrailMapping;
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ export function DateTimeFormatter(props) {
|
|||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
export function DateTimeFormatterFunction(date) {
|
||||||
|
return dayjs(date).format("MM/DD/YYYY hh:mm a");
|
||||||
|
}
|
||||||
export function TimeFormatter(props) {
|
export function TimeFormatter(props) {
|
||||||
return props.children
|
return props.children
|
||||||
? dayjs(props.children).format(props.format ? props.format : "hh:mm a")
|
? dayjs(props.children).format(props.format ? props.format : "hh:mm a")
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import apolloLogger from "apollo-link-logger";
|
|||||||
//import axios from "axios";
|
//import axios from "axios";
|
||||||
import { auth } from "../firebase/firebase.utils";
|
import { auth } from "../firebase/firebase.utils";
|
||||||
import errorLink from "../graphql/apollo-error-handling";
|
import errorLink from "../graphql/apollo-error-handling";
|
||||||
|
import { SentryLink } from "apollo-link-sentry";
|
||||||
|
|
||||||
//import { store } from "../redux/store";
|
//import { store } from "../redux/store";
|
||||||
const httpLink = new HttpLink({
|
const httpLink = new HttpLink({
|
||||||
uri: import.meta.env.VITE_APP_GRAPHQL_ENDPOINT,
|
uri: import.meta.env.VITE_APP_GRAPHQL_ENDPOINT,
|
||||||
@@ -105,18 +107,30 @@ const link = split(
|
|||||||
const authLink = setContext((_, { headers }) => {
|
const authLink = setContext((_, { headers }) => {
|
||||||
return (
|
return (
|
||||||
auth.currentUser &&
|
auth.currentUser &&
|
||||||
auth.currentUser.getIdToken().then((token) => {
|
auth.currentUser
|
||||||
if (token) {
|
.getIdToken()
|
||||||
return {
|
.then((token) => {
|
||||||
headers: {
|
if (token) {
|
||||||
...headers,
|
return {
|
||||||
authorization: token ? `Bearer ${token}` : "",
|
headers: {
|
||||||
},
|
...headers,
|
||||||
};
|
authorization: token ? `Bearer ${token}` : "",
|
||||||
} else {
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Authentication error. Unable to add authorization token because it was empty."
|
||||||
|
);
|
||||||
|
return { headers };
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(
|
||||||
|
"Authentication error. Unable to add authorization token.",
|
||||||
|
error.message
|
||||||
|
);
|
||||||
return { headers };
|
return { headers };
|
||||||
}
|
})
|
||||||
})
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -138,8 +152,10 @@ if (import.meta.env.DEV) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
middlewares.push(
|
middlewares.push(
|
||||||
roundTripLink.concat(
|
new SentryLink().concat(
|
||||||
retryLink.concat(errorLink.concat(authLink.concat(link)))
|
roundTripLink.concat(
|
||||||
|
retryLink.concat(errorLink.concat(authLink.concat(link)))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -2039,6 +2039,15 @@ export const TemplateList = (type, context) => {
|
|||||||
},
|
},
|
||||||
group: "jobs",
|
group: "jobs",
|
||||||
},
|
},
|
||||||
|
ar_aging: {
|
||||||
|
title: i18n.t("reportcenter.templates.ar_aging"),
|
||||||
|
subject: i18n.t("reportcenter.templates.ar_aging"),
|
||||||
|
key: "ar_aging",
|
||||||
|
//idtype: "vendor",
|
||||||
|
disabled: false,
|
||||||
|
datedisable: true,
|
||||||
|
group: "customers",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(!type || type === "courtesycarcontract"
|
...(!type || type === "courtesycarcontract"
|
||||||
|
|||||||
@@ -2676,6 +2676,31 @@
|
|||||||
- table:
|
- table:
|
||||||
name: job_ar_schema
|
name: job_ar_schema
|
||||||
schema: public
|
schema: public
|
||||||
|
object_relationships:
|
||||||
|
- name: job
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: id
|
||||||
|
select_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- ro_number
|
||||||
|
- clm_total
|
||||||
|
- total_payments
|
||||||
|
- balance
|
||||||
|
- date_invoiced
|
||||||
|
- shopid
|
||||||
|
filter:
|
||||||
|
job:
|
||||||
|
bodyshop:
|
||||||
|
associations:
|
||||||
|
_and:
|
||||||
|
- active:
|
||||||
|
_eq: true
|
||||||
|
- user:
|
||||||
|
authid:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
- table:
|
- table:
|
||||||
name: job_conversations
|
name: job_conversations
|
||||||
schema: public
|
schema: public
|
||||||
@@ -4164,6 +4189,11 @@
|
|||||||
- name: job_status_transition
|
- name: job_status_transition
|
||||||
definition:
|
definition:
|
||||||
enable_manual: true
|
enable_manual: true
|
||||||
|
insert:
|
||||||
|
columns: '*'
|
||||||
|
update:
|
||||||
|
columns:
|
||||||
|
- status
|
||||||
retry_conf:
|
retry_conf:
|
||||||
interval_sec: 10
|
interval_sec: 10
|
||||||
num_retries: 0
|
num_retries: 0
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."job_ar_schema" add column "shopid" uuid
|
||||||
|
-- null;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."job_ar_schema" add column "shopid" uuid
|
||||||
|
null;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table "public"."job_ar_schema" drop constraint "job_ar_schema_id_fkey";
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
alter table "public"."job_ar_schema"
|
||||||
|
add constraint "job_ar_schema_id_fkey"
|
||||||
|
foreign key ("id")
|
||||||
|
references "public"."jobs"
|
||||||
|
("id") on update restrict on delete restrict;
|
||||||
35
hasura/migrations/1705952926623_run_sql_migration/down.sql
Normal file
35
hasura/migrations/1705952926623_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- CREATE OR REPLACE FUNCTION public.jobs_ar_summary ()
|
||||||
|
-- RETURNS SETOF job_ar_schema
|
||||||
|
-- LANGUAGE plpgsql
|
||||||
|
-- STABLE
|
||||||
|
-- AS $function$
|
||||||
|
-- BEGIN
|
||||||
|
--
|
||||||
|
-- RETURN query
|
||||||
|
-- select
|
||||||
|
-- j.id,
|
||||||
|
-- j.ro_number,
|
||||||
|
-- j.clm_total,
|
||||||
|
-- coalesce (p.total_payments,0) as total_payments,
|
||||||
|
-- j.clm_total - coalesce (p.total_payments,0) as balance,
|
||||||
|
-- j.date_invoiced,
|
||||||
|
-- j.shopid
|
||||||
|
-- from
|
||||||
|
-- jobs j
|
||||||
|
-- left join (
|
||||||
|
-- select
|
||||||
|
-- p.jobid,
|
||||||
|
-- coalesce (sum(p.amount),0) as total_payments
|
||||||
|
-- from
|
||||||
|
-- payments p
|
||||||
|
-- group by
|
||||||
|
-- p.jobid
|
||||||
|
-- ) p on
|
||||||
|
-- j.id = p.jobid
|
||||||
|
-- where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) > 0;
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- END
|
||||||
|
-- $function$;
|
||||||
33
hasura/migrations/1705952926623_run_sql_migration/up.sql
Normal file
33
hasura/migrations/1705952926623_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
CREATE OR REPLACE FUNCTION public.jobs_ar_summary ()
|
||||||
|
RETURNS SETOF job_ar_schema
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
STABLE
|
||||||
|
AS $function$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
RETURN query
|
||||||
|
select
|
||||||
|
j.id,
|
||||||
|
j.ro_number,
|
||||||
|
j.clm_total,
|
||||||
|
coalesce (p.total_payments,0) as total_payments,
|
||||||
|
j.clm_total - coalesce (p.total_payments,0) as balance,
|
||||||
|
j.date_invoiced,
|
||||||
|
j.shopid
|
||||||
|
from
|
||||||
|
jobs j
|
||||||
|
left join (
|
||||||
|
select
|
||||||
|
p.jobid,
|
||||||
|
coalesce (sum(p.amount),0) as total_payments
|
||||||
|
from
|
||||||
|
payments p
|
||||||
|
group by
|
||||||
|
p.jobid
|
||||||
|
) p on
|
||||||
|
j.id = p.jobid
|
||||||
|
where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) > 0;
|
||||||
|
|
||||||
|
|
||||||
|
END
|
||||||
|
$function$;
|
||||||
35
hasura/migrations/1706207204357_run_sql_migration/down.sql
Normal file
35
hasura/migrations/1706207204357_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- CREATE OR REPLACE FUNCTION public.jobs_ar_summary ()
|
||||||
|
-- RETURNS SETOF job_ar_schema
|
||||||
|
-- LANGUAGE plpgsql
|
||||||
|
-- STABLE
|
||||||
|
-- AS $function$
|
||||||
|
-- BEGIN
|
||||||
|
--
|
||||||
|
-- RETURN query
|
||||||
|
-- select
|
||||||
|
-- j.id,
|
||||||
|
-- j.ro_number,
|
||||||
|
-- j.clm_total,
|
||||||
|
-- coalesce (p.total_payments,0) as total_payments,
|
||||||
|
-- j.clm_total - coalesce (p.total_payments,0) as balance,
|
||||||
|
-- j.date_invoiced,
|
||||||
|
-- j.shopid
|
||||||
|
-- from
|
||||||
|
-- jobs j
|
||||||
|
-- left join (
|
||||||
|
-- select
|
||||||
|
-- p.jobid,
|
||||||
|
-- coalesce (sum(p.amount),0) as total_payments
|
||||||
|
-- from
|
||||||
|
-- payments p
|
||||||
|
-- group by
|
||||||
|
-- p.jobid
|
||||||
|
-- ) p on
|
||||||
|
-- j.id = p.jobid
|
||||||
|
-- where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0;
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- END
|
||||||
|
-- $function$;
|
||||||
33
hasura/migrations/1706207204357_run_sql_migration/up.sql
Normal file
33
hasura/migrations/1706207204357_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
CREATE OR REPLACE FUNCTION public.jobs_ar_summary ()
|
||||||
|
RETURNS SETOF job_ar_schema
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
STABLE
|
||||||
|
AS $function$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
RETURN query
|
||||||
|
select
|
||||||
|
j.id,
|
||||||
|
j.ro_number,
|
||||||
|
j.clm_total,
|
||||||
|
coalesce (p.total_payments,0) as total_payments,
|
||||||
|
j.clm_total - coalesce (p.total_payments,0) as balance,
|
||||||
|
j.date_invoiced,
|
||||||
|
j.shopid
|
||||||
|
from
|
||||||
|
jobs j
|
||||||
|
left join (
|
||||||
|
select
|
||||||
|
p.jobid,
|
||||||
|
coalesce (sum(p.amount),0) as total_payments
|
||||||
|
from
|
||||||
|
payments p
|
||||||
|
group by
|
||||||
|
p.jobid
|
||||||
|
) p on
|
||||||
|
j.id = p.jobid
|
||||||
|
where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0;
|
||||||
|
|
||||||
|
|
||||||
|
END
|
||||||
|
$function$;
|
||||||
35
hasura/migrations/1706207267558_run_sql_migration/down.sql
Normal file
35
hasura/migrations/1706207267558_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- CREATE OR REPLACE FUNCTION public.jobs_ar_summary ()
|
||||||
|
-- RETURNS SETOF job_ar_schema
|
||||||
|
-- LANGUAGE plpgsql
|
||||||
|
-- STABLE
|
||||||
|
-- AS $function$
|
||||||
|
-- BEGIN
|
||||||
|
--
|
||||||
|
-- RETURN query
|
||||||
|
-- select
|
||||||
|
-- j.id,
|
||||||
|
-- j.ro_number,
|
||||||
|
-- j.clm_total,
|
||||||
|
-- coalesce (p.total_payments,0) as total_payments,
|
||||||
|
-- j.clm_total - coalesce (p.total_payments,0) as balance,
|
||||||
|
-- j.date_invoiced,
|
||||||
|
-- j.shopid
|
||||||
|
-- from
|
||||||
|
-- jobs j
|
||||||
|
-- left join (
|
||||||
|
-- select
|
||||||
|
-- p.jobid,
|
||||||
|
-- coalesce (sum(p.amount),0) as total_payments
|
||||||
|
-- from
|
||||||
|
-- payments p
|
||||||
|
-- group by
|
||||||
|
-- p.jobid
|
||||||
|
-- ) p on
|
||||||
|
-- j.id = p.jobid
|
||||||
|
-- where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0;
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- END
|
||||||
|
-- $function$;
|
||||||
33
hasura/migrations/1706207267558_run_sql_migration/up.sql
Normal file
33
hasura/migrations/1706207267558_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
CREATE OR REPLACE FUNCTION public.jobs_ar_summary ()
|
||||||
|
RETURNS SETOF job_ar_schema
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
STABLE
|
||||||
|
AS $function$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
RETURN query
|
||||||
|
select
|
||||||
|
j.id,
|
||||||
|
j.ro_number,
|
||||||
|
j.clm_total,
|
||||||
|
coalesce (p.total_payments,0) as total_payments,
|
||||||
|
j.clm_total - coalesce (p.total_payments,0) as balance,
|
||||||
|
j.date_invoiced,
|
||||||
|
j.shopid
|
||||||
|
from
|
||||||
|
jobs j
|
||||||
|
left join (
|
||||||
|
select
|
||||||
|
p.jobid,
|
||||||
|
coalesce (sum(p.amount),0) as total_payments
|
||||||
|
from
|
||||||
|
payments p
|
||||||
|
group by
|
||||||
|
p.jobid
|
||||||
|
) p on
|
||||||
|
j.id = p.jobid
|
||||||
|
where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0;
|
||||||
|
|
||||||
|
|
||||||
|
END
|
||||||
|
$function$;
|
||||||
47574
new_bodyshop_translations.babel
Normal file
47574
new_bodyshop_translations.babel
Normal file
File diff suppressed because it is too large
Load Diff
1669
package-lock.json
generated
1669
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -8,6 +8,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"setup": "rm -rf node_modules && npm i && cd client && rm -rf node_modules && npm i",
|
"setup": "rm -rf node_modules && npm i && cd client && rm -rf node_modules && npm i",
|
||||||
|
"setup:win": "rimraf node_modules && npm i && cd client && rimraf node_modules && npm i",
|
||||||
"admin": "cd admin && npm start",
|
"admin": "cd admin && npm start",
|
||||||
"client": "cd client && npm start",
|
"client": "cd client && npm start",
|
||||||
"server": "nodemon server.js",
|
"server": "nodemon server.js",
|
||||||
@@ -17,21 +18,21 @@
|
|||||||
"start": "node server.js"
|
"start": "node server.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-secrets-manager": "^3.490.0",
|
"@aws-sdk/client-secrets-manager": "^3.503.0",
|
||||||
"@aws-sdk/client-ses": "^3.490.0",
|
"@aws-sdk/client-ses": "^3.503.0",
|
||||||
"@aws-sdk/credential-provider-node": "^3.490.0",
|
"@aws-sdk/credential-provider-node": "^3.503.0",
|
||||||
"@opensearch-project/opensearch": "^2.5.0",
|
"@opensearch-project/opensearch": "^2.5.0",
|
||||||
"aws4": "^1.12.0",
|
"aws4": "^1.12.0",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.6.5",
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"cloudinary": "^1.41.2",
|
"cloudinary": "^2.0.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.3.1",
|
"dotenv": "^16.4.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"firebase-admin": "^12.0.0",
|
"firebase-admin": "^12.0.0",
|
||||||
"graphql": "^16.8.1",
|
"graphql": "^16.8.1",
|
||||||
@@ -49,9 +50,10 @@
|
|||||||
"node-quickbooks": "^2.0.43",
|
"node-quickbooks": "^2.0.43",
|
||||||
"nodemailer": "^6.9.8",
|
"nodemailer": "^6.9.8",
|
||||||
"phone": "^3.1.42",
|
"phone": "^3.1.42",
|
||||||
|
"rimraf": "^5.0.5",
|
||||||
"soap": "^1.0.0",
|
"soap": "^1.0.0",
|
||||||
"socket.io": "^4.7.4",
|
"socket.io": "^4.7.4",
|
||||||
"ssh2-sftp-client": "^9.1.0",
|
"ssh2-sftp-client": "^10.0.3",
|
||||||
"stripe": "^14.11.0",
|
"stripe": "^14.11.0",
|
||||||
"twilio": "^4.20.0",
|
"twilio": "^4.20.0",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
|
|||||||
343
server.js
343
server.js
@@ -1,299 +1,94 @@
|
|||||||
|
// Import core modules
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
const cors = require("cors");
|
const cors = require("cors");
|
||||||
const bodyParser = require("body-parser");
|
const bodyParser = require("body-parser");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const compression = require("compression");
|
const compression = require("compression");
|
||||||
const twilio = require("twilio");
|
|
||||||
const logger = require("./server/utils/logger");
|
|
||||||
const fb = require("./server/firebase/firebase-handler");
|
|
||||||
const cookieParser = require("cookie-parser");
|
const cookieParser = require("cookie-parser");
|
||||||
const multer = require("multer");
|
const http = require("http");
|
||||||
const upload = multer();
|
const {Server} = require("socket.io");
|
||||||
//var enforce = require("express-sslify");
|
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
||||||
process.cwd(),
|
|
||||||
`.env.${process.env.NODE_ENV || "development"}`
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Import custom utilities and handlers
|
||||||
|
const logger = require("./server/utils/logger");
|
||||||
|
|
||||||
|
// Express app and server setup
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = process.env.PORT || 5000;
|
const port = process.env.PORT || 5000;
|
||||||
//const port = 5000;
|
|
||||||
|
|
||||||
const http = require("http");
|
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
const { Server } = require("socket.io");
|
|
||||||
const io = new Server(server, {
|
const io = new Server(server, {
|
||||||
path: "/ws",
|
path: "/ws",
|
||||||
cors: {
|
cors: {
|
||||||
origin: [
|
origin: [
|
||||||
"https://test.imex.online",
|
"https://test.imex.online",
|
||||||
"https://www.test.imex.online",
|
"https://www.test.imex.online",
|
||||||
"http://localhost:3000",
|
"http://localhost:3000",
|
||||||
"https://imex.online",
|
"https://imex.online",
|
||||||
"https://www.imex.online",
|
"https://www.imex.online",
|
||||||
"https://beta.test.imex.online",
|
"https://beta.test.imex.online",
|
||||||
"https://www.beta.test.imex.online",
|
"https://www.beta.test.imex.online",
|
||||||
"https://beta.imex.online",
|
"https://beta.imex.online",
|
||||||
"https://www.beta.imex.online",
|
"https://www.beta.imex.online",
|
||||||
],
|
],
|
||||||
methods: ["GET", "POST"],
|
methods: ["GET", "POST"],
|
||||||
credentials: true,
|
credentials: true,
|
||||||
exposedHeaders: ["set-cookie"],
|
exposedHeaders: ["set-cookie"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
exports.io = io;
|
exports.io = io;
|
||||||
|
|
||||||
require("./server/web-sockets/web-socket");
|
require("./server/web-sockets/web-socket");
|
||||||
// app.set('trust proxy', true)
|
|
||||||
// app.use(fb.validateFirebaseIdToken);
|
|
||||||
|
// Middleware
|
||||||
app.use(compression());
|
app.use(compression());
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.use(bodyParser.json({ limit: "50mb" }));
|
app.use(bodyParser.json({limit: "50mb"}));
|
||||||
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
|
app.use(bodyParser.urlencoded({limit: "50mb", extended: true}));
|
||||||
// app.use(enforce.HTTPS({ trustProtoHeader: true }));
|
app.use(cors({credentials: true, exposedHeaders: ["set-cookie"]}));
|
||||||
app.use(
|
|
||||||
cors({ credentials: true, exposedHeaders: ["set-cookie"] })
|
|
||||||
// cors({
|
|
||||||
// credentials: true,
|
|
||||||
// origin: [
|
|
||||||
// "https://test.imex.online",
|
|
||||||
// "http://localhost:3000",
|
|
||||||
// "https://imex.online",
|
|
||||||
// ],
|
|
||||||
// })
|
|
||||||
);
|
|
||||||
|
|
||||||
//Email Based Paths.
|
// Helper middleware
|
||||||
var sendEmail = require("./server/email/sendemail.js");
|
app.use((req, res, next) => {
|
||||||
app.post("/sendemail", fb.validateFirebaseIdToken, sendEmail.sendEmail);
|
req.logger = logger;
|
||||||
app.post("/emailbounce", bodyParser.text(), sendEmail.emailBounce);
|
next();
|
||||||
|
|
||||||
//Test route to ensure Express is responding.
|
|
||||||
app.get("/test", async function (req, res) {
|
|
||||||
const commit = require("child_process").execSync(
|
|
||||||
"git rev-parse --short HEAD"
|
|
||||||
);
|
|
||||||
// console.log(app.get('trust proxy'));
|
|
||||||
// console.log("remoteAddress", req.socket.remoteAddress);
|
|
||||||
// console.log("X-Forwarded-For", req.header('x-forwarded-for'));
|
|
||||||
logger.log("test-api-status", "DEBUG", "api", { commit });
|
|
||||||
// sendEmail.sendServerEmail({
|
|
||||||
// subject: `API Check - ${process.env.NODE_ENV}`,
|
|
||||||
// text: `Server API check has come in. Remote IP: ${req.socket.remoteAddress}, X-Forwarded-For: ${req.header('x-forwarded-for')}`,
|
|
||||||
// });
|
|
||||||
sendEmail.sendServerEmail({
|
|
||||||
subject: `API Check - ${process.env.NODE_ENV}`,
|
|
||||||
text: `Server API check has come in.`,
|
|
||||||
});
|
|
||||||
res.status(200).send(`OK - ${commit}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//Accounting Qbxml
|
// Route groupings
|
||||||
const accountQbxml = require("./server/accounting/qbxml/qbxml");
|
app.use('/', require("./server/routes/miscellaneousRoutes"));
|
||||||
app.post(
|
app.use("/notifications", require("./server/routes/notificationsRoutes"));
|
||||||
"/accounting/qbxml/receivables",
|
app.use("/render", require("./server/routes/renderRoutes"));
|
||||||
fb.validateFirebaseIdToken,
|
app.use('/mixdata', require("./server/routes/mixDataRoutes"));
|
||||||
accountQbxml.receivables
|
app.use('/accounting', require("./server/routes/accountingRoutes"));
|
||||||
);
|
app.use('/qbo', require("./server/routes/qboRoutes"));
|
||||||
app.post(
|
app.use('/media', require("./server/routes/mediaRoutes"));
|
||||||
"/accounting/qbxml/payables",
|
app.use('/sms', require("./server/routes/smsRoutes"));
|
||||||
fb.validateFirebaseIdToken,
|
app.use('/job', require("./server/routes/jobRoutes"));
|
||||||
accountQbxml.payables
|
app.use('/scheduling', require("./server/routes/schedulingRoutes"));
|
||||||
);
|
app.use('/utils', require("./server/routes/utilRoutes"));
|
||||||
app.post(
|
app.use('/data', require("./server/routes/dataRoutes"));
|
||||||
"/accounting/qbxml/payments",
|
app.use('/adm', require("./server/routes/adminRoutes"));
|
||||||
fb.validateFirebaseIdToken,
|
app.use('/tech', require("./server/routes/techRoutes"));
|
||||||
accountQbxml.payments
|
app.use('/intellipay', require("./server/routes/intellipayRoutes"));
|
||||||
);
|
app.use('/cdk', require("./server/routes/cdkRoutes"));
|
||||||
|
|
||||||
//Cloudinary Media Paths
|
// Default route for forbidden access
|
||||||
var media = require("./server/media/media");
|
app.get("/", (req, res) => {
|
||||||
app.post(
|
res.status(200).send("Access Forbidden.");
|
||||||
"/media/sign",
|
|
||||||
fb.validateFirebaseIdToken,
|
|
||||||
media.createSignedUploadURL
|
|
||||||
);
|
|
||||||
app.post("/media/download", fb.validateFirebaseIdToken, media.downloadFiles);
|
|
||||||
app.post("/media/rename", fb.validateFirebaseIdToken, media.renameKeys);
|
|
||||||
app.post("/media/delete", fb.validateFirebaseIdToken, media.deleteFiles);
|
|
||||||
|
|
||||||
//SMS/Twilio Paths
|
|
||||||
var smsReceive = require("./server/sms/receive");
|
|
||||||
app.post(
|
|
||||||
"/sms/receive",
|
|
||||||
twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }),
|
|
||||||
smsReceive.receive
|
|
||||||
);
|
|
||||||
var smsSend = require("./server/sms/send");
|
|
||||||
app.post("/sms/send", fb.validateFirebaseIdToken, smsSend.send);
|
|
||||||
var smsStatus = require("./server/sms/status");
|
|
||||||
app.post(
|
|
||||||
"/sms/status",
|
|
||||||
twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }),
|
|
||||||
smsStatus.status
|
|
||||||
);
|
|
||||||
app.post(
|
|
||||||
"/sms/markConversationRead",
|
|
||||||
fb.validateFirebaseIdToken,
|
|
||||||
smsStatus.markConversationRead
|
|
||||||
);
|
|
||||||
|
|
||||||
var job = require("./server/job/job");
|
|
||||||
app.post("/job/totals", fb.validateFirebaseIdToken, job.totals);
|
|
||||||
app.post(
|
|
||||||
"/job/statustransition",
|
|
||||||
// fb.validateFirebaseIdToken,
|
|
||||||
job.statustransition
|
|
||||||
);
|
|
||||||
app.post("/job/totalsssu", fb.validateFirebaseIdToken, job.totalsSsu);
|
|
||||||
app.post("/job/costing", fb.validateFirebaseIdToken, job.costing);
|
|
||||||
app.post("/job/costingmulti", fb.validateFirebaseIdToken, job.costingmulti);
|
|
||||||
var partsScan = require("./server/parts-scan/parts-scan");
|
|
||||||
app.post("/job/partsscan", fb.validateFirebaseIdToken, partsScan.partsScan);
|
|
||||||
//Scheduling
|
|
||||||
var scheduling = require("./server/scheduling/scheduling-job");
|
|
||||||
app.post("/scheduling/job", fb.validateFirebaseIdToken, scheduling.job);
|
|
||||||
|
|
||||||
//Handlebars Paths for Email/Report Rendering
|
|
||||||
// var renderHandlebars = require("./server/render/renderHandlebars");
|
|
||||||
// app.post("/render", fb.validateFirebaseIdToken, renderHandlebars.render);
|
|
||||||
var inlineCss = require("./server/render/inlinecss");
|
|
||||||
app.post("/render/inlinecss", fb.validateFirebaseIdToken, inlineCss.inlinecss);
|
|
||||||
|
|
||||||
// app.post(
|
|
||||||
// "/notifications/send",
|
|
||||||
|
|
||||||
// fb.sendNotification
|
|
||||||
// );
|
|
||||||
app.post("/notifications/subscribe", fb.validateFirebaseIdToken, fb.subscribe);
|
|
||||||
app.post(
|
|
||||||
"/notifications/unsubscribe",
|
|
||||||
fb.validateFirebaseIdToken,
|
|
||||||
fb.unsubscribe
|
|
||||||
);
|
|
||||||
app.post("/adm/updateuser", fb.validateFirebaseIdToken, fb.updateUser);
|
|
||||||
app.post("/adm/getuser", fb.validateFirebaseIdToken, fb.getUser);
|
|
||||||
app.post("/adm/createuser", fb.validateFirebaseIdToken, fb.createUser);
|
|
||||||
const adm = require("./server/admin/adminops");
|
|
||||||
app.post(
|
|
||||||
"/adm/createassociation",
|
|
||||||
fb.validateFirebaseIdToken,
|
|
||||||
fb.validateAdmin,
|
|
||||||
adm.createAssociation
|
|
||||||
);
|
|
||||||
app.post(
|
|
||||||
"/adm/createshop",
|
|
||||||
fb.validateFirebaseIdToken,
|
|
||||||
fb.validateAdmin,
|
|
||||||
adm.createShop
|
|
||||||
);
|
|
||||||
app.post(
|
|
||||||
"/adm/updateshop",
|
|
||||||
fb.validateFirebaseIdToken,
|
|
||||||
fb.validateAdmin,
|
|
||||||
adm.updateShop
|
|
||||||
);
|
|
||||||
app.post(
|
|
||||||
"/adm/updatecounter",
|
|
||||||
fb.validateFirebaseIdToken,
|
|
||||||
fb.validateAdmin,
|
|
||||||
adm.updateCounter
|
|
||||||
);
|
|
||||||
|
|
||||||
//Stripe Processing
|
|
||||||
// var stripe = require("./server/stripe/payment");
|
|
||||||
// app.post("/stripe/payment", fb.validateFirebaseIdToken, stripe.payment);
|
|
||||||
// app.post(
|
|
||||||
// "/stripe/mobilepayment",
|
|
||||||
// fb.validateFirebaseIdToken,
|
|
||||||
// stripe.mobile_payment
|
|
||||||
// );
|
|
||||||
|
|
||||||
//Tech Console
|
|
||||||
var tech = require("./server/tech/tech");
|
|
||||||
app.post("/tech/login", fb.validateFirebaseIdToken, tech.techLogin);
|
|
||||||
|
|
||||||
var utils = require("./server/utils/utils");
|
|
||||||
app.post("/utils/time", utils.servertime);
|
|
||||||
app.post("/utils/jsr", fb.validateFirebaseIdToken, utils.jsrAuth);
|
|
||||||
var qbo = require("./server/accounting/qbo/qbo");
|
|
||||||
app.post("/qbo/authorize", fb.validateFirebaseIdToken, qbo.authorize);
|
|
||||||
app.get("/qbo/callback", qbo.callback);
|
|
||||||
app.post("/qbo/receivables", fb.validateFirebaseIdToken, qbo.receivables);
|
|
||||||
app.post("/qbo/payables", fb.validateFirebaseIdToken, qbo.payables);
|
|
||||||
app.post("/qbo/payments", fb.validateFirebaseIdToken, qbo.payments);
|
|
||||||
|
|
||||||
var data = require("./server/data/data");
|
|
||||||
app.post("/data/ah", data.autohouse);
|
|
||||||
app.post("/data/cc", data.claimscorp);
|
|
||||||
app.post("/data/kaizen", data.kaizen);
|
|
||||||
app.post("/record-handler/arms", data.arms);
|
|
||||||
|
|
||||||
var taskHandler = require("./server/tasks/tasks");
|
|
||||||
app.post("/taskHandler", fb.validateFirebaseIdToken, taskHandler.taskHandler);
|
|
||||||
|
|
||||||
var mixdataUpload = require("./server/mixdata/mixdata");
|
|
||||||
|
|
||||||
app.post(
|
|
||||||
"/mixdata/upload",
|
|
||||||
fb.validateFirebaseIdToken,
|
|
||||||
upload.any(),
|
|
||||||
mixdataUpload.mixdataUpload
|
|
||||||
);
|
|
||||||
|
|
||||||
var intellipay = require("./server/intellipay/intellipay");
|
|
||||||
app.post(
|
|
||||||
"/intellipay/lightbox_credentials",
|
|
||||||
fb.validateFirebaseIdToken,
|
|
||||||
intellipay.lightbox_credentials
|
|
||||||
);
|
|
||||||
|
|
||||||
app.post(
|
|
||||||
"/intellipay/payment_refund",
|
|
||||||
fb.validateFirebaseIdToken,
|
|
||||||
intellipay.payment_refund
|
|
||||||
);
|
|
||||||
|
|
||||||
app.post(
|
|
||||||
"/intellipay/generate_payment_url",
|
|
||||||
fb.validateFirebaseIdToken,
|
|
||||||
intellipay.generate_payment_url
|
|
||||||
);
|
|
||||||
|
|
||||||
app.post(
|
|
||||||
"/intellipay/postback",
|
|
||||||
// fb.validateFirebaseIdToken,
|
|
||||||
intellipay.postback
|
|
||||||
);
|
|
||||||
|
|
||||||
var ioevent = require("./server/ioevent/ioevent");
|
|
||||||
app.post("/ioevent", ioevent.default);
|
|
||||||
// app.post("/newlog", (req, res) => {
|
|
||||||
// const { message, type, user, record, object } = req.body;
|
|
||||||
// logger.log(message, type, user, record, object);
|
|
||||||
// });
|
|
||||||
|
|
||||||
var os = require("./server/opensearch/os-handler");
|
|
||||||
app.post(
|
|
||||||
"/opensearch", //fb.validateFirebaseIdToken,
|
|
||||||
os.handler
|
|
||||||
);
|
|
||||||
app.post("/search", fb.validateFirebaseIdToken, os.search);
|
|
||||||
|
|
||||||
var cdkGetMake = require("./server/cdk/cdk-get-makes");
|
|
||||||
app.post("/cdk/getvehicles", fb.validateFirebaseIdToken, cdkGetMake.default);
|
|
||||||
|
|
||||||
app.get("/", async function (req, res) {
|
|
||||||
res.status(200).send("Access Forbidden.");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(port, (error) => {
|
const main = async () => {
|
||||||
if (error) throw error;
|
await server.listen(port);
|
||||||
logger.log(
|
}
|
||||||
`[${process.env.NODE_ENV || "DEVELOPMENT"}] Server running on port ${port}`,
|
|
||||||
"INFO",
|
// Start server
|
||||||
"api"
|
main()
|
||||||
);
|
.then(() => {
|
||||||
});
|
logger.log(`[${process.env.NODE_ENV || "DEVELOPMENT"}] Server started on port ${port}`, "INFO", "api");
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.log(`[${process.env.NODE_ENV || "DEVELOPMENT"}] Server failed to start on port ${port}`, "ERROR", "api", error);
|
||||||
|
});
|
||||||
@@ -166,7 +166,7 @@ async function CheckForErrors(socket, response) {
|
|||||||
CdkBase.createLogEvent(
|
CdkBase.createLogEvent(
|
||||||
socket,
|
socket,
|
||||||
"DEBUG",
|
"DEBUG",
|
||||||
`Succesful response from DMS. ${response.Message || ""}`
|
`Successful response from DMS. ${response.Message || ""}`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
CdkBase.createLogEvent(
|
CdkBase.createLogEvent(
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ const {
|
|||||||
} = require("./qbo-callback");
|
} = require("./qbo-callback");
|
||||||
const OAuthClient = require("intuit-oauth");
|
const OAuthClient = require("intuit-oauth");
|
||||||
const moment = require("moment-timezone");
|
const moment = require("moment-timezone");
|
||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
|
||||||
const findTaxCode = require("../qb-receivables-lines").findTaxCode;
|
const findTaxCode = require("../qb-receivables-lines").findTaxCode;
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
|
|
||||||
const oauthClient = new OAuthClient({
|
const oauthClient = new OAuthClient({
|
||||||
clientId: process.env.QBO_CLIENT_ID,
|
clientId: process.env.QBO_CLIENT_ID,
|
||||||
clientSecret: process.env.QBO_SECRET,
|
clientSecret: process.env.QBO_SECRET,
|
||||||
@@ -30,29 +30,31 @@ exports.default = async (req, res) => {
|
|||||||
redirectUri: process.env.QBO_REDIRECT_URI,
|
redirectUri: process.env.QBO_REDIRECT_URI,
|
||||||
logging: true,
|
logging: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//Fetch the API Access Tokens & Set them for the session.
|
//Fetch the API Access Tokens & Set them for the session.
|
||||||
const response = await apiGqlClient.request(queries.GET_QBO_AUTH, {
|
const response = await apiGqlClient.request(queries.GET_QBO_AUTH, {
|
||||||
email: req.user.email,
|
email: req.user.email,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { qbo_realmId } = response.associations[0];
|
const { qbo_realmId } = response.associations[0];
|
||||||
|
|
||||||
oauthClient.setToken(response.associations[0].qbo_auth);
|
oauthClient.setToken(response.associations[0].qbo_auth);
|
||||||
|
|
||||||
if (!qbo_realmId) {
|
if (!qbo_realmId) {
|
||||||
res.status(401).json({ error: "No company associated." });
|
res.status(401).json({ error: "No company associated." });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await refreshOauthToken(oauthClient, req);
|
await refreshOauthToken(oauthClient, req);
|
||||||
|
|
||||||
const BearerToken = req.headers.authorization;
|
|
||||||
const { bills: billsToQuery, elgen } = req.body;
|
const { bills: billsToQuery, elgen } = req.body;
|
||||||
//Query Job Info
|
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
const BearerToken = req.BearerToken;
|
||||||
headers: {
|
const client = req.userGraphQLClient;
|
||||||
Authorization: BearerToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
logger.log("qbo-payable-create", "DEBUG", req.user.email, billsToQuery);
|
logger.log("qbo-payable-create", "DEBUG", req.user.email, billsToQuery);
|
||||||
|
|
||||||
const result = await client
|
const result = await client
|
||||||
.setHeaders({ Authorization: BearerToken })
|
.setHeaders({ Authorization: BearerToken })
|
||||||
.request(queries.QUERY_BILLS_FOR_PAYABLES_EXPORT, {
|
.request(queries.QUERY_BILLS_FOR_PAYABLES_EXPORT, {
|
||||||
|
|||||||
@@ -51,15 +51,13 @@ exports.default = async (req, res) => {
|
|||||||
}
|
}
|
||||||
await refreshOauthToken(oauthClient, req);
|
await refreshOauthToken(oauthClient, req);
|
||||||
|
|
||||||
const BearerToken = req.headers.authorization;
|
|
||||||
const { payments: paymentsToQuery, elgen } = req.body;
|
const { payments: paymentsToQuery, elgen } = req.body;
|
||||||
//Query Job Info
|
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
const BearerToken = req.BearerToken;
|
||||||
headers: {
|
const client = req.userGraphQLClient;
|
||||||
Authorization: BearerToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
logger.log("qbo-payment-create", "DEBUG", req.user.email, paymentsToQuery);
|
logger.log("qbo-payment-create", "DEBUG", req.user.email, paymentsToQuery);
|
||||||
|
|
||||||
const result = await client
|
const result = await client
|
||||||
.setHeaders({ Authorization: BearerToken })
|
.setHeaders({ Authorization: BearerToken })
|
||||||
.request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
|
.request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ const {
|
|||||||
const OAuthClient = require("intuit-oauth");
|
const OAuthClient = require("intuit-oauth");
|
||||||
const CreateInvoiceLines = require("../qb-receivables-lines").default;
|
const CreateInvoiceLines = require("../qb-receivables-lines").default;
|
||||||
const moment = require("moment-timezone");
|
const moment = require("moment-timezone");
|
||||||
|
|
||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
|
||||||
const { generateOwnerTier } = require("../qbxml/qbxml-utils");
|
const { generateOwnerTier } = require("../qbxml/qbxml-utils");
|
||||||
const { createMultiQbPayerLines } = require("../qb-receivables-lines");
|
const { createMultiQbPayerLines } = require("../qb-receivables-lines");
|
||||||
|
|
||||||
@@ -46,15 +44,14 @@ exports.default = async (req, res) => {
|
|||||||
|
|
||||||
await refreshOauthToken(oauthClient, req);
|
await refreshOauthToken(oauthClient, req);
|
||||||
|
|
||||||
const BearerToken = req.headers.authorization;
|
|
||||||
const { jobIds, elgen } = req.body;
|
const { jobIds, elgen } = req.body;
|
||||||
//Query Job Info
|
//Query Job Info
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
|
||||||
headers: {
|
const BearerToken = req.BearerToken;
|
||||||
Authorization: BearerToken,
|
const client = req.userGraphQLClient;
|
||||||
},
|
|
||||||
});
|
|
||||||
logger.log("qbo-receivable-create", "DEBUG", req.user.email, jobIds);
|
logger.log("qbo-receivable-create", "DEBUG", req.user.email, jobIds);
|
||||||
|
|
||||||
const result = await client
|
const result = await client
|
||||||
.setHeaders({ Authorization: BearerToken })
|
.setHeaders({ Authorization: BearerToken })
|
||||||
.request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, {
|
.request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, {
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ const path = require("path");
|
|||||||
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
|
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
|
||||||
const queries = require("../../graphql-client/queries");
|
const queries = require("../../graphql-client/queries");
|
||||||
const Dinero = require("dinero.js");
|
const Dinero = require("dinero.js");
|
||||||
var builder = require("xmlbuilder2");
|
const builder = require("xmlbuilder2");
|
||||||
const QbXmlUtils = require("./qbxml-utils");
|
const QbXmlUtils = require("./qbxml-utils");
|
||||||
const moment = require("moment-timezone");
|
const moment = require("moment-timezone");
|
||||||
const logger = require("../../utils/logger");
|
const logger = require('../../utils/logger');
|
||||||
|
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
@@ -15,14 +16,10 @@ require("dotenv").config({
|
|||||||
});
|
});
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
const BearerToken = req.headers.authorization;
|
|
||||||
const { bills: billsToQuery } = req.body;
|
const { bills: billsToQuery } = req.body;
|
||||||
|
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
const BearerToken = req.BearerToken;
|
||||||
headers: {
|
const client = req.userGraphQLClient;
|
||||||
Authorization: BearerToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log(
|
logger.log(
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
|
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
|
||||||
const queries = require("../../graphql-client/queries");
|
const queries = require("../../graphql-client/queries");
|
||||||
const Dinero = require("dinero.js");
|
const Dinero = require("dinero.js");
|
||||||
var builder = require("xmlbuilder2");
|
const builder = require("xmlbuilder2");
|
||||||
const moment = require("moment-timezone");
|
const moment = require("moment-timezone");
|
||||||
const QbXmlUtils = require("./qbxml-utils");
|
const QbXmlUtils = require("./qbxml-utils");
|
||||||
const QbxmlReceivables = require("./qbxml-receivables");
|
const QbxmlReceivables = require("./qbxml-receivables");
|
||||||
const logger = require("../../utils/logger");
|
const logger = require('../../utils/logger');
|
||||||
|
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(
|
||||||
@@ -19,14 +18,10 @@ require("dotenv").config({
|
|||||||
const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils;
|
const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils;
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
const BearerToken = req.headers.authorization;
|
|
||||||
const { payments: paymentsToQuery } = req.body;
|
const { payments: paymentsToQuery } = req.body;
|
||||||
|
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
const BearerToken = req.BearerToken;
|
||||||
headers: {
|
const client = req.userGraphQLClient;
|
||||||
Authorization: BearerToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log(
|
logger.log(
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
|
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
|
||||||
const queries = require("../../graphql-client/queries");
|
const queries = require("../../graphql-client/queries");
|
||||||
const Dinero = require("dinero.js");
|
const Dinero = require("dinero.js");
|
||||||
const moment = require("moment-timezone");
|
const moment = require("moment-timezone");
|
||||||
var builder = require("xmlbuilder2");
|
const builder = require("xmlbuilder2");
|
||||||
const QbXmlUtils = require("./qbxml-utils");
|
const QbXmlUtils = require("./qbxml-utils");
|
||||||
const logger = require("../../utils/logger");
|
|
||||||
const CreateInvoiceLines = require("../qb-receivables-lines").default;
|
const CreateInvoiceLines = require("../qb-receivables-lines").default;
|
||||||
|
const logger = require('../../utils/logger');
|
||||||
|
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(
|
||||||
@@ -20,14 +19,10 @@ Dinero.globalRoundingMode = "HALF_EVEN";
|
|||||||
const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils;
|
const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils;
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
const BearerToken = req.headers.authorization;
|
|
||||||
const { jobIds } = req.body;
|
const { jobIds } = req.body;
|
||||||
|
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
const BearerToken = req.BearerToken;
|
||||||
headers: {
|
const client = req.userGraphQLClient;
|
||||||
Authorization: BearerToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log(
|
logger.log(
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ require("dotenv").config({
|
|||||||
`.env.${process.env.NODE_ENV || "development"}`
|
`.env.${process.env.NODE_ENV || "development"}`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
|
||||||
const soap = require("soap");
|
const soap = require("soap");
|
||||||
const queries = require("../graphql-client/queries");
|
const queries = require("../graphql-client/queries");
|
||||||
|
|
||||||
@@ -34,16 +33,11 @@ const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
|
|||||||
exports.default = async function ReloadCdkMakes(req, res) {
|
exports.default = async function ReloadCdkMakes(req, res) {
|
||||||
const { bodyshopid, cdk_dealerid } = req.body;
|
const { bodyshopid, cdk_dealerid } = req.body;
|
||||||
try {
|
try {
|
||||||
const BearerToken = req.headers.authorization;
|
|
||||||
//Query all CDK Models
|
//Query all CDK Models
|
||||||
const newList = await GetCdkMakes(req, cdk_dealerid);
|
const newList = await GetCdkMakes(req, cdk_dealerid);
|
||||||
|
|
||||||
//Clear out the existing records
|
const BearerToken = req.BearerToken;
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
const client = req.userGraphQLClient;
|
||||||
headers: {
|
|
||||||
Authorization: BearerToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const deleteResult = await client
|
const deleteResult = await client
|
||||||
.setHeaders({ Authorization: BearerToken })
|
.setHeaders({ Authorization: BearerToken })
|
||||||
|
|||||||
@@ -1,269 +1,269 @@
|
|||||||
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 axios = require("axios");
|
||||||
let nodemailer = require("nodemailer");
|
let nodemailer = require("nodemailer");
|
||||||
let aws = require("@aws-sdk/client-ses");
|
let aws = require("@aws-sdk/client-ses");
|
||||||
let { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
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.
|
// The key apiVersion is no longer supported in v3, and can be removed.
|
||||||
// @deprecated The client uses the "latest" apiVersion.
|
// @deprecated The client uses the "latest" apiVersion.
|
||||||
apiVersion: "latest",
|
apiVersion: "latest",
|
||||||
region: "ca-central-1",
|
region: "ca-central-1",
|
||||||
defaultProvider
|
defaultProvider
|
||||||
});
|
});
|
||||||
|
|
||||||
let transporter = nodemailer.createTransport({
|
let transporter = nodemailer.createTransport({
|
||||||
SES: { ses, aws },
|
SES: {ses, aws},
|
||||||
});
|
});
|
||||||
|
|
||||||
exports.sendServerEmail = async function ({ subject, text }) {
|
exports.sendServerEmail = async function ({subject, text}) {
|
||||||
if (process.env.NODE_ENV === undefined) return;
|
if (process.env.NODE_ENV === undefined) return;
|
||||||
try {
|
try {
|
||||||
transporter.sendMail(
|
transporter.sendMail(
|
||||||
{
|
|
||||||
from: `ImEX Online API - ${process.env.NODE_ENV} <noreply@imex.online>`,
|
|
||||||
to: ["patrick@imexsystems.ca", "support@thinkimex.com"],
|
|
||||||
subject: subject,
|
|
||||||
text: text,
|
|
||||||
ses: {
|
|
||||||
// optional extra arguments for SendRawEmail
|
|
||||||
Tags: [
|
|
||||||
{
|
{
|
||||||
Name: "tag_name",
|
from: `ImEX Online API - ${process.env.NODE_ENV} <noreply@imex.online>`,
|
||||||
Value: "tag_value",
|
to: ["patrick@imexsystems.ca", "support@thinkimex.com"],
|
||||||
|
subject: subject,
|
||||||
|
text: text,
|
||||||
|
ses: {
|
||||||
|
// optional extra arguments for SendRawEmail
|
||||||
|
Tags: [
|
||||||
|
{
|
||||||
|
Name: "tag_name",
|
||||||
|
Value: "tag_value",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
(err, info) => {
|
||||||
},
|
console.log(err || info);
|
||||||
},
|
}
|
||||||
(err, info) => {
|
);
|
||||||
console.log(err || info);
|
} catch (error) {
|
||||||
}
|
console.log(error);
|
||||||
);
|
logger.log("server-email-failure", "error", null, null, error);
|
||||||
} catch (error) {
|
}
|
||||||
console.log(error);
|
|
||||||
logger.log("server-email-failure", "error", null, null, error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
exports.sendTaskEmail = async function ({ to, subject, text, attachments }) {
|
exports.sendTaskEmail = async function ({to, subject, text, attachments}) {
|
||||||
try {
|
try {
|
||||||
transporter.sendMail(
|
transporter.sendMail(
|
||||||
{
|
{
|
||||||
from: `ImEX Online <noreply@imex.online>`,
|
from: `ImEX Online <noreply@imex.online>`,
|
||||||
to: to,
|
to: to,
|
||||||
subject: subject,
|
subject: subject,
|
||||||
text: text,
|
text: text,
|
||||||
attachments: attachments || null,
|
attachments: attachments || null,
|
||||||
},
|
},
|
||||||
(err, info) => {
|
(err, info) => {
|
||||||
console.log(err || info);
|
console.log(err || info);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} 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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.sendEmail = async (req, res) => {
|
exports.sendEmail = async (req, res) => {
|
||||||
logger.log("send-email", "DEBUG", req.user.email, null, {
|
logger.log("send-email", "DEBUG", req.user.email, null, {
|
||||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||||
replyTo: req.body.ReplyTo.Email,
|
replyTo: req.body.ReplyTo.Email,
|
||||||
to: req.body.to,
|
to: req.body.to,
|
||||||
cc: req.body.cc,
|
cc: req.body.cc,
|
||||||
subject: req.body.subject,
|
subject: req.body.subject,
|
||||||
});
|
});
|
||||||
|
|
||||||
let downloadedMedia = [];
|
let downloadedMedia = [];
|
||||||
if (req.body.media && req.body.media.length > 0) {
|
if (req.body.media && req.body.media.length > 0) {
|
||||||
downloadedMedia = await Promise.all(
|
downloadedMedia = await Promise.all(
|
||||||
req.body.media.map((m) => {
|
req.body.media.map((m) => {
|
||||||
try {
|
try {
|
||||||
return getImage(m);
|
return getImage(m);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("send-email-error", "ERROR", req.user.email, null, {
|
logger.log("send-email-error", "ERROR", req.user.email, null, {
|
||||||
|
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||||
|
replyTo: req.body.ReplyTo.Email,
|
||||||
|
to: req.body.to,
|
||||||
|
cc: req.body.cc,
|
||||||
|
subject: req.body.subject,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
transporter.sendMail(
|
||||||
|
{
|
||||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||||
replyTo: req.body.ReplyTo.Email,
|
replyTo: req.body.ReplyTo.Email,
|
||||||
to: req.body.to,
|
to: req.body.to,
|
||||||
cc: req.body.cc,
|
cc: req.body.cc,
|
||||||
subject: req.body.subject,
|
subject: req.body.subject,
|
||||||
error,
|
attachments:
|
||||||
});
|
[
|
||||||
|
...((req.body.attachments &&
|
||||||
|
req.body.attachments.map((a) => {
|
||||||
|
return {
|
||||||
|
filename: a.filename,
|
||||||
|
path: a.path,
|
||||||
|
};
|
||||||
|
})) ||
|
||||||
|
[]),
|
||||||
|
...downloadedMedia.map((a) => {
|
||||||
|
return {
|
||||||
|
path: a,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
] || null,
|
||||||
|
html: req.body.html,
|
||||||
|
ses: {
|
||||||
|
// optional extra arguments for SendRawEmail
|
||||||
|
Tags: [
|
||||||
|
{
|
||||||
|
Name: "tag_name",
|
||||||
|
Value: "tag_value",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(err, info) => {
|
||||||
|
console.log(err || info);
|
||||||
|
if (info) {
|
||||||
|
logger.log("send-email-success", "DEBUG", req.user.email, null, {
|
||||||
|
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||||
|
replyTo: req.body.ReplyTo.Email,
|
||||||
|
to: req.body.to,
|
||||||
|
cc: req.body.cc,
|
||||||
|
subject: req.body.subject,
|
||||||
|
// info,
|
||||||
|
});
|
||||||
|
logEmail(req, {
|
||||||
|
to: req.body.to,
|
||||||
|
cc: req.body.cc,
|
||||||
|
subject: req.body.subject,
|
||||||
|
messageId: info.response,
|
||||||
|
});
|
||||||
|
res.json({
|
||||||
|
success: true, //response: info
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.log("send-email-failure", "ERROR", req.user.email, null, {
|
||||||
|
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||||
|
replyTo: req.body.ReplyTo.Email,
|
||||||
|
to: req.body.to,
|
||||||
|
cc: req.body.cc,
|
||||||
|
subject: req.body.subject,
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
logEmail(req, {
|
||||||
|
to: req.body.to,
|
||||||
|
cc: req.body.cc,
|
||||||
|
subject: req.body.subject,
|
||||||
|
bodyshopid: req.body.bodyshopid,
|
||||||
|
});
|
||||||
|
res.status(500).json({success: false, error: err});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
transporter.sendMail(
|
|
||||||
{
|
|
||||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
|
||||||
replyTo: req.body.ReplyTo.Email,
|
|
||||||
to: req.body.to,
|
|
||||||
cc: req.body.cc,
|
|
||||||
subject: req.body.subject,
|
|
||||||
attachments:
|
|
||||||
[
|
|
||||||
...((req.body.attachments &&
|
|
||||||
req.body.attachments.map((a) => {
|
|
||||||
return {
|
|
||||||
filename: a.filename,
|
|
||||||
path: a.path,
|
|
||||||
};
|
|
||||||
})) ||
|
|
||||||
[]),
|
|
||||||
...downloadedMedia.map((a) => {
|
|
||||||
return {
|
|
||||||
path: a,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
] || null,
|
|
||||||
html: req.body.html,
|
|
||||||
ses: {
|
|
||||||
// optional extra arguments for SendRawEmail
|
|
||||||
Tags: [
|
|
||||||
{
|
|
||||||
Name: "tag_name",
|
|
||||||
Value: "tag_value",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
(err, info) => {
|
|
||||||
console.log(err || info);
|
|
||||||
if (info) {
|
|
||||||
logger.log("send-email-success", "DEBUG", req.user.email, null, {
|
|
||||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
|
||||||
replyTo: req.body.ReplyTo.Email,
|
|
||||||
to: req.body.to,
|
|
||||||
cc: req.body.cc,
|
|
||||||
subject: req.body.subject,
|
|
||||||
// info,
|
|
||||||
});
|
|
||||||
logEmail(req, {
|
|
||||||
to: req.body.to,
|
|
||||||
cc: req.body.cc,
|
|
||||||
subject: req.body.subject,
|
|
||||||
messageId: info.response,
|
|
||||||
});
|
|
||||||
res.json({
|
|
||||||
success: true, //response: info
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logger.log("send-email-failure", "ERROR", req.user.email, null, {
|
|
||||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
|
||||||
replyTo: req.body.ReplyTo.Email,
|
|
||||||
to: req.body.to,
|
|
||||||
cc: req.body.cc,
|
|
||||||
subject: req.body.subject,
|
|
||||||
error: err,
|
|
||||||
});
|
|
||||||
logEmail(req, {
|
|
||||||
to: req.body.to,
|
|
||||||
cc: req.body.cc,
|
|
||||||
subject: req.body.subject,
|
|
||||||
bodyshopid: req.body.bodyshopid,
|
|
||||||
});
|
|
||||||
res.status(500).json({ success: false, error: err });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getImage(imageUrl) {
|
async function getImage(imageUrl) {
|
||||||
let image = await axios.get(imageUrl, { responseType: "arraybuffer" });
|
let image = await axios.get(imageUrl, {responseType: "arraybuffer"});
|
||||||
let raw = Buffer.from(image.data).toString("base64");
|
let raw = Buffer.from(image.data).toString("base64");
|
||||||
return "data:" + image.headers["content-type"] + ";base64," + raw;
|
return "data:" + image.headers["content-type"] + ";base64," + raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logEmail(req, email) {
|
async function logEmail(req, email) {
|
||||||
try {
|
try {
|
||||||
const insertresult = await client.request(queries.INSERT_EMAIL_AUDIT, {
|
const insertresult = await client.request(queries.INSERT_EMAIL_AUDIT, {
|
||||||
email: {
|
email: {
|
||||||
to: email.to,
|
to: email.to,
|
||||||
cc: email.cc,
|
cc: email.cc,
|
||||||
subject: email.subject,
|
subject: email.subject,
|
||||||
bodyshopid: req.body.bodyshopid,
|
bodyshopid: req.body.bodyshopid,
|
||||||
useremail: req.user.email,
|
useremail: req.user.email,
|
||||||
contents: req.body.html,
|
contents: req.body.html,
|
||||||
jobid: req.body.jobid,
|
jobid: req.body.jobid,
|
||||||
sesmessageid: email.messageId,
|
sesmessageid: email.messageId,
|
||||||
status: "Sent",
|
status: "Sent",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(insertresult);
|
console.log(insertresult);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("email-log-error", "error", req.user.email, null, {
|
logger.log("email-log-error", "error", req.user.email, null, {
|
||||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||||
to: req.body.to,
|
to: req.body.to,
|
||||||
cc: req.body.cc,
|
cc: req.body.cc,
|
||||||
subject: req.body.subject,
|
subject: req.body.subject,
|
||||||
// info,
|
// info,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.emailBounce = async function (req, res, next) {
|
exports.emailBounce = async function (req, res) {
|
||||||
try {
|
try {
|
||||||
const body = JSON.parse(req.body);
|
const body = JSON.parse(req.body);
|
||||||
if (body.Type === "SubscriptionConfirmation") {
|
if (body.Type === "SubscriptionConfirmation") {
|
||||||
logger.log("SNS-message", "DEBUG", "api", null, {
|
logger.log("SNS-message", "DEBUG", "api", null, {
|
||||||
body: req.body,
|
body: req.body,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
const message = JSON.parse(body.Message);
|
|
||||||
if (message.notificationType === "Bounce") {
|
|
||||||
let replyTo, subject, messageId;
|
|
||||||
message.mail.headers.forEach((header) => {
|
|
||||||
if (header.name === "Reply-To") {
|
|
||||||
replyTo = header.value;
|
|
||||||
} else if (header.name === "Subject") {
|
|
||||||
subject = header.value;
|
|
||||||
}
|
}
|
||||||
});
|
const message = JSON.parse(body.Message);
|
||||||
messageId = message.mail.messageId;
|
if (message.notificationType === "Bounce") {
|
||||||
if (replyTo === "noreply@imex.online") {
|
let replyTo, subject, messageId;
|
||||||
res.sendStatus(200);
|
message.mail.headers.forEach((header) => {
|
||||||
return;
|
if (header.name === "Reply-To") {
|
||||||
}
|
replyTo = header.value;
|
||||||
//If it's bounced, log it as bounced in audit log. Send an email to the user.
|
} else if (header.name === "Subject") {
|
||||||
const result = await client.request(queries.UPDATE_EMAIL_AUDIT, {
|
subject = header.value;
|
||||||
sesid: messageId,
|
}
|
||||||
status: "Bounced",
|
});
|
||||||
context: message.bounce?.bouncedRecipients,
|
messageId = message.mail.messageId;
|
||||||
});
|
if (replyTo === "noreply@imex.online") {
|
||||||
transporter.sendMail(
|
res.sendStatus(200);
|
||||||
{
|
return;
|
||||||
from: `ImEX Online <noreply@imex.online>`,
|
}
|
||||||
to: replyTo,
|
//If it's bounced, log it as bounced in audit log. Send an email to the user.
|
||||||
//bcc: "patrick@snapt.ca",
|
const result = await client.request(queries.UPDATE_EMAIL_AUDIT, {
|
||||||
subject: `ImEX Online Bounced Email - RE: ${subject}`,
|
sesid: messageId,
|
||||||
text: `ImEX Online has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error.
|
status: "Bounced",
|
||||||
|
context: message.bounce?.bouncedRecipients,
|
||||||
|
});
|
||||||
|
transporter.sendMail(
|
||||||
|
{
|
||||||
|
from: `ImEX Online <noreply@imex.online>`,
|
||||||
|
to: replyTo,
|
||||||
|
//bcc: "patrick@snapt.ca",
|
||||||
|
subject: `ImEX Online Bounced Email - RE: ${subject}`,
|
||||||
|
text: `ImEX Online has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error.
|
||||||
|
|
||||||
${body.bounce?.bouncedRecipients.map(
|
${body.bounce?.bouncedRecipients.map(
|
||||||
(r) =>
|
(r) =>
|
||||||
`Recipient: ${r.emailAddress} | Status: ${r.action} | Code: ${r.diagnosticCode}
|
`Recipient: ${r.emailAddress} | Status: ${r.action} | Code: ${r.diagnosticCode}
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
(err, info) => {
|
(err, info) => {
|
||||||
console.log("***", err || info);
|
console.log("***", err || info);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
} catch (error) {
|
||||||
|
logger.log("sns-error", "ERROR", "api", null, {
|
||||||
|
error: JSON.stringify(error),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
res.sendStatus(200);
|
||||||
logger.log("sns-error", "ERROR", "api", null, {
|
|
||||||
error: JSON.stringify(error),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
res.sendStatus(200);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,287 +1,215 @@
|
|||||||
var admin = require("firebase-admin");
|
const admin = require("firebase-admin");
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { auth } = require("firebase-admin");
|
const {auth} = require("firebase-admin");
|
||||||
|
|
||||||
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 = require("../graphql-client/graphql-client").client;
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
var serviceAccount = require(process.env.FIREBASE_ADMINSDK_JSON);
|
|
||||||
|
const serviceAccount = require(process.env.FIREBASE_ADMINSDK_JSON);
|
||||||
|
const adminEmail = require("../utils/adminEmail");
|
||||||
|
|
||||||
admin.initializeApp({
|
admin.initializeApp({
|
||||||
credential: admin.credential.cert(serviceAccount),
|
credential: admin.credential.cert(serviceAccount),
|
||||||
databaseURL: process.env.FIREBASE_DATABASE_URL,
|
databaseURL: process.env.FIREBASE_DATABASE_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
exports.admin = admin;
|
exports.admin = admin;
|
||||||
|
|
||||||
const adminEmail = [
|
|
||||||
"patrick@imex.dev",
|
|
||||||
//"patrick@imex.test",
|
|
||||||
"patrick@imex.prod",
|
|
||||||
"patrick@imexsystems.ca",
|
|
||||||
"patrick@thinkimex.com",
|
|
||||||
];
|
|
||||||
|
|
||||||
exports.createUser = async (req, res) => {
|
exports.createUser = async (req, res) => {
|
||||||
logger.log("admin-create-user", "ADMIN", req.user.email, null, {
|
logger.log("admin-create-user", "ADMIN", req.user.email, null, {
|
||||||
request: req.body,
|
request: req.body,
|
||||||
ioadmin: true,
|
ioadmin: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { email, displayName, password, shopid, authlevel } = req.body;
|
const {email, displayName, password, shopid, authlevel} = req.body;
|
||||||
try {
|
try {
|
||||||
const userRecord = await admin
|
const userRecord = await admin
|
||||||
.auth()
|
.auth()
|
||||||
.createUser({ email, displayName, password });
|
.createUser({email, displayName, password});
|
||||||
|
|
||||||
// See the UserRecord reference doc for the contents of userRecord.
|
// See the UserRecord reference doc for the contents of userRecord.
|
||||||
|
|
||||||
const result = await client.request(
|
const result = await client.request(
|
||||||
`
|
`
|
||||||
mutation INSERT_USER($user: users_insert_input!) {
|
mutation INSERT_USER($user: users_insert_input!) {
|
||||||
insert_users_one(object: $user) {
|
insert_users_one(object: $user) {
|
||||||
email
|
email
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
user: {
|
user: {
|
||||||
email: email.toLowerCase(),
|
email: email.toLowerCase(),
|
||||||
authid: userRecord.uid,
|
authid: userRecord.uid,
|
||||||
associations: {
|
associations: {
|
||||||
data: [{ shopid, authlevel, active: true }],
|
data: [{shopid, authlevel, active: true}],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
res.json({ userRecord, result });
|
res.json({userRecord, result});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("admin-update-user-error", "ERROR", req.user.email, null, {
|
logger.log("admin-update-user-error", "ERROR", req.user.email, null, {
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
res.status(500).json(error);
|
res.status(500).json(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.updateUser = (req, res) => {
|
exports.updateUser = (req, res) => {
|
||||||
logger.log("admin-update-user", "ADMIN", req.user.email, null, {
|
logger.log("admin-update-user", "ADMIN", req.user.email, null, {
|
||||||
request: req.body,
|
|
||||||
ioadmin: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
|
||||||
logger.log(
|
|
||||||
"admin-update-user-unauthorized",
|
|
||||||
"ERROR",
|
|
||||||
req.user.email,
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
request: req.body,
|
request: req.body,
|
||||||
user: req.user,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
res.sendStatus(404);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
admin
|
|
||||||
.auth()
|
|
||||||
.updateUser(
|
|
||||||
req.body.uid,
|
|
||||||
req.body.user
|
|
||||||
// {
|
|
||||||
// email: "modifiedUser@example.com",
|
|
||||||
// phoneNumber: "+11234567890",
|
|
||||||
// emailVerified: true,
|
|
||||||
// password: "newPassword",
|
|
||||||
// displayName: "Jane Doe",
|
|
||||||
// photoURL: "http://www.example.com/12345678/photo.png",
|
|
||||||
// disabled: true,
|
|
||||||
// }
|
|
||||||
)
|
|
||||||
.then((userRecord) => {
|
|
||||||
// See the UserRecord reference doc for the contents of userRecord.
|
|
||||||
|
|
||||||
logger.log("admin-update-user-success", "ADMIN", req.user.email, null, {
|
|
||||||
userRecord,
|
|
||||||
ioadmin: true,
|
ioadmin: true,
|
||||||
});
|
|
||||||
res.json(userRecord);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.log("admin-update-user-error", "ERROR", req.user.email, null, {
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
res.status(500).json(error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
||||||
|
logger.log(
|
||||||
|
"admin-update-user-unauthorized",
|
||||||
|
"ERROR",
|
||||||
|
req.user.email,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
request: req.body,
|
||||||
|
user: req.user,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
res.sendStatus(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
admin
|
||||||
|
.auth()
|
||||||
|
.updateUser(
|
||||||
|
req.body.uid,
|
||||||
|
req.body.user
|
||||||
|
// {
|
||||||
|
// email: "modifiedUser@example.com",
|
||||||
|
// phoneNumber: "+11234567890",
|
||||||
|
// emailVerified: true,
|
||||||
|
// password: "newPassword",
|
||||||
|
// displayName: "Jane Doe",
|
||||||
|
// photoURL: "http://www.example.com/12345678/photo.png",
|
||||||
|
// disabled: true,
|
||||||
|
// }
|
||||||
|
)
|
||||||
|
.then((userRecord) => {
|
||||||
|
// See the UserRecord reference doc for the contents of userRecord.
|
||||||
|
|
||||||
|
logger.log("admin-update-user-success", "ADMIN", req.user.email, null, {
|
||||||
|
userRecord,
|
||||||
|
ioadmin: true,
|
||||||
|
});
|
||||||
|
res.json(userRecord);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.log("admin-update-user-error", "ERROR", req.user.email, null, {
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
res.status(500).json(error);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getUser = (req, res) => {
|
exports.getUser = (req, res) => {
|
||||||
logger.log("admin-get-user", "ADMIN", req.user.email, null, {
|
logger.log("admin-get-user", "ADMIN", req.user.email, null, {
|
||||||
request: req.body,
|
|
||||||
ioadmin: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
|
||||||
logger.log(
|
|
||||||
"admin-update-user-unauthorized",
|
|
||||||
"ERROR",
|
|
||||||
req.user.email,
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
request: req.body,
|
request: req.body,
|
||||||
user: req.user,
|
ioadmin: true,
|
||||||
}
|
|
||||||
);
|
|
||||||
res.sendStatus(404);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
admin
|
|
||||||
.auth()
|
|
||||||
.getUser(req.body.uid)
|
|
||||||
.then((userRecord) => {
|
|
||||||
res.json(userRecord);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.log("admin-get-user-error", "ERROR", req.user.email, null, {
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
res.status(500).json(error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
||||||
|
logger.log(
|
||||||
|
"admin-update-user-unauthorized",
|
||||||
|
"ERROR",
|
||||||
|
req.user.email,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
request: req.body,
|
||||||
|
user: req.user,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
res.sendStatus(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
admin
|
||||||
|
.auth()
|
||||||
|
.getUser(req.body.uid)
|
||||||
|
.then((userRecord) => {
|
||||||
|
res.json(userRecord);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.log("admin-get-user-error", "ERROR", req.user.email, null, {
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
res.status(500).json(error);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.sendNotification = async (req, res) => {
|
exports.sendNotification = async (req, res) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Send a message to the device corresponding to the provided
|
// Send a message to the device corresponding to the provided
|
||||||
// registration token.
|
// registration token.
|
||||||
admin
|
admin
|
||||||
.messaging()
|
.messaging()
|
||||||
.send({
|
.send({
|
||||||
topic: "PRD_PATRICK-messaging",
|
topic: "PRD_PATRICK-messaging",
|
||||||
notification: {
|
notification: {
|
||||||
title: `ImEX Online Message - +16049992002`,
|
title: `ImEX Online Message - +16049992002`,
|
||||||
body: "Test Noti.",
|
body: "Test Noti.",
|
||||||
//imageUrl: "https://thinkimex.com/img/io-fcm.png",
|
//imageUrl: "https://thinkimex.com/img/io-fcm.png",
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
type: "messaging-inbound",
|
type: "messaging-inbound",
|
||||||
conversationid: "e0eb17c3-3a78-4e3f-b932-55ef35aa2297",
|
conversationid: "e0eb17c3-3a78-4e3f-b932-55ef35aa2297",
|
||||||
text: "Hello. ",
|
text: "Hello. ",
|
||||||
image_path: "",
|
image_path: "",
|
||||||
phone_num: "+16049992002",
|
phone_num: "+16049992002",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
// Response is a message ID string.
|
// Response is a message ID string.
|
||||||
console.log("Successfully sent message:", response);
|
console.log("Successfully sent message:", response);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log("Error sending message:", error);
|
console.log("Error sending message:", error);
|
||||||
});
|
});
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.subscribe = async (req, res) => {
|
exports.subscribe = async (req, res) => {
|
||||||
const result = await admin
|
const result = await admin
|
||||||
.messaging()
|
.messaging()
|
||||||
.subscribeToTopic(
|
.subscribeToTopic(
|
||||||
req.body.fcm_tokens,
|
req.body.fcm_tokens,
|
||||||
`${req.body.imexshopid}-${req.body.type}`
|
`${req.body.imexshopid}-${req.body.type}`
|
||||||
);
|
);
|
||||||
|
|
||||||
res.json(result);
|
res.json(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.unsubscribe = async (req, res) => {
|
exports.unsubscribe = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const result = await admin
|
const result = await admin
|
||||||
.messaging()
|
.messaging()
|
||||||
.unsubscribeFromTopic(
|
.unsubscribeFromTopic(
|
||||||
req.body.fcm_tokens,
|
req.body.fcm_tokens,
|
||||||
`${req.body.imexshopid}-${req.body.type}`
|
`${req.body.imexshopid}-${req.body.type}`
|
||||||
);
|
);
|
||||||
|
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.sendStatus(500);
|
res.sendStatus(500);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.validateFirebaseIdToken = async (req, res, next) => {
|
|
||||||
if (
|
|
||||||
(!req.headers.authorization ||
|
|
||||||
!req.headers.authorization.startsWith("Bearer ")) &&
|
|
||||||
!(req.cookies && req.cookies.__session)
|
|
||||||
) {
|
|
||||||
console.error("Unauthorized attempt. No authorization provided.");
|
|
||||||
res.status(403).send("Unauthorized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let idToken;
|
|
||||||
if (
|
|
||||||
req.headers.authorization &&
|
|
||||||
req.headers.authorization.startsWith("Bearer ")
|
|
||||||
) {
|
|
||||||
// console.log('Found "Authorization" header');
|
|
||||||
// Read the ID Token from the Authorization header.
|
|
||||||
idToken = req.headers.authorization.split("Bearer ")[1];
|
|
||||||
} else if (req.cookies) {
|
|
||||||
//console.log('Found "__session" cookie');
|
|
||||||
// Read the ID Token from cookie.
|
|
||||||
idToken = req.cookies.__session;
|
|
||||||
} else {
|
|
||||||
// No cookie
|
|
||||||
console.error("Unauthorized attempt. No cookie provided.");
|
|
||||||
logger.log("api-unauthorized-call", "WARN", null, null, {
|
|
||||||
req,
|
|
||||||
type: "no-cookie",
|
|
||||||
});
|
|
||||||
res.status(403).send("Unauthorized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
|
|
||||||
//console.log("ID Token correctly decoded", decodedIdToken);
|
|
||||||
req.user = decodedIdToken;
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
logger.log("api-unauthorized-call", "WARN", null, null, {
|
|
||||||
path: req.path,
|
|
||||||
body: req.body,
|
|
||||||
|
|
||||||
type: "unauthroized",
|
|
||||||
...error,
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(401).send("Unauthorized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.validateAdmin = async (req, res, next) => {
|
|
||||||
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
|
||||||
logger.log("admin-validation-failed", "ERROR", req.user.email, null, {
|
|
||||||
request: req.body,
|
|
||||||
user: req.user,
|
|
||||||
});
|
|
||||||
res.sendStatus(404);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Admin claims code.
|
//Admin claims code.
|
||||||
// const uid = "JEqqYlsadwPEXIiyRBR55fflfko1";
|
// const uid = "JEqqYlsadwPEXIiyRBR55fflfko1";
|
||||||
|
|||||||
@@ -518,6 +518,21 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = `
|
|||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
exports.QUERY_TRANSITIONS_BY_JOBID = `query QUERY_TRANSITIONS_BY_JOBID($jobids: [uuid!]!) {
|
||||||
|
transitions(where: {jobid: {_in: $jobids}}, order_by: {end: desc}) {
|
||||||
|
start
|
||||||
|
end
|
||||||
|
value
|
||||||
|
prev_value
|
||||||
|
next_value
|
||||||
|
duration
|
||||||
|
type
|
||||||
|
created_at
|
||||||
|
updated_at
|
||||||
|
jobid
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) {
|
exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) {
|
||||||
jobs_by_pk(id: $jobId) {
|
jobs_by_pk(id: $jobId) {
|
||||||
bodyshop {
|
bodyshop {
|
||||||
@@ -1974,12 +1989,20 @@ exports.UPDATE_OLD_TRANSITION = `mutation UPDATE_OLD_TRANSITION($jobid: uuid!, $
|
|||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
exports.INSERT_NEW_TRANSITION = `mutation INSERT_NEW_TRANSITION($newTransition: transitions_insert_input!, $oldTransitionId: uuid, $duration: numeric) {
|
exports.INSERT_NEW_TRANSITION = (
|
||||||
|
includeOldTransition
|
||||||
|
) => `mutation INSERT_NEW_TRANSITION($newTransition: transitions_insert_input!, ${
|
||||||
|
includeOldTransition ? `$oldTransitionId: uuid!, $duration: numeric` : ""
|
||||||
|
}) {
|
||||||
insert_transitions_one(object: $newTransition) {
|
insert_transitions_one(object: $newTransition) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
update_transitions(where: {id: {_eq: $oldTransitionId}}, _set: {duration: $duration}) {
|
${
|
||||||
|
includeOldTransition
|
||||||
|
? `update_transitions(where: {id: {_eq: $oldTransitionId}}, _set: {duration: $duration}) {
|
||||||
affected_rows
|
affected_rows
|
||||||
|
}`
|
||||||
|
: ""
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
68
server/job/job-lifecycle.js
Normal file
68
server/job/job-lifecycle.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
const _ = require("lodash");
|
||||||
|
const queries = require("../graphql-client/queries");
|
||||||
|
const moment = require("moment");
|
||||||
|
const durationToHumanReadable = require("../utils/durationToHumanReadable");
|
||||||
|
const calculateStatusDuration = require("../utils/calculateStatusDuration");
|
||||||
|
|
||||||
|
const jobLifecycle = async (req, res) => {
|
||||||
|
// Grab the jobids and statuses from the request body
|
||||||
|
const {
|
||||||
|
jobids,
|
||||||
|
statuses
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
if (!jobids) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: "Missing jobids"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const jobIDs = _.isArray(jobids) ? jobids : [jobids];
|
||||||
|
const client = req.userGraphQLClient;
|
||||||
|
const resp = await client.request(queries.QUERY_TRANSITIONS_BY_JOBID, {jobids: jobIDs,});
|
||||||
|
|
||||||
|
const transitions = resp.transitions;
|
||||||
|
|
||||||
|
if (!transitions) {
|
||||||
|
return res.status(200).json({
|
||||||
|
jobIDs,
|
||||||
|
transitions: []
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const transitionsByJobId = _.groupBy(resp.transitions, 'jobid');
|
||||||
|
|
||||||
|
const groupedTransitions = {};
|
||||||
|
|
||||||
|
for (let jobId in transitionsByJobId) {
|
||||||
|
let lifecycle = transitionsByJobId[jobId].map(transition => {
|
||||||
|
transition.start_readable = transition.start ? moment(transition.start).fromNow() : 'N/A';
|
||||||
|
transition.end_readable = transition.end ? moment(transition.end).fromNow() : 'N/A';
|
||||||
|
|
||||||
|
if (transition.duration) {
|
||||||
|
transition.duration_seconds = Math.round(transition.duration / 1000);
|
||||||
|
transition.duration_minutes = Math.round(transition.duration_seconds / 60);
|
||||||
|
let duration = moment.duration(transition.duration);
|
||||||
|
transition.duration_readable = durationToHumanReadable(duration);
|
||||||
|
} else {
|
||||||
|
transition.duration_seconds = 0;
|
||||||
|
transition.duration_minutes = 0;
|
||||||
|
transition.duration_readable = 'N/A';
|
||||||
|
}
|
||||||
|
return transition;
|
||||||
|
});
|
||||||
|
|
||||||
|
groupedTransitions[jobId] = {
|
||||||
|
lifecycle: lifecycle,
|
||||||
|
durations: calculateStatusDuration(lifecycle, statuses),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
jobIDs,
|
||||||
|
transition: groupedTransitions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = jobLifecycle;
|
||||||
@@ -9,24 +9,25 @@ const logger = require("../utils/logger");
|
|||||||
Dinero.globalRoundingMode = "HALF_EVEN";
|
Dinero.globalRoundingMode = "HALF_EVEN";
|
||||||
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"}`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
async function StatusTransition(req, res) {
|
async function StatusTransition(req, res) {
|
||||||
if (req.headers["event-secret"] !== process.env.EVENT_SECRET) {
|
|
||||||
res.status(401).send("Unauthorized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.sendStatus(200);
|
|
||||||
return;
|
|
||||||
const {
|
const {
|
||||||
id: jobid,
|
id: jobid,
|
||||||
status: value,
|
status: value,
|
||||||
shopid: bodyshopid,
|
shopid: bodyshopid,
|
||||||
} = req.body.event.data.new;
|
} = req.body.event.data.new;
|
||||||
|
|
||||||
|
// Create record OPEN on new item, enter state
|
||||||
|
// If change to SCHEDULE, update the last record and create a new record (update status and end time on old record, create a new record saying we came from previous status going to previous status
|
||||||
|
// (Timeline)
|
||||||
|
// Final status is exported, there is no end date as there is no further transition (has no end date)
|
||||||
try {
|
try {
|
||||||
const { update_transitions } = await client.request(
|
const { update_transitions } = await client.request(
|
||||||
queries.UPDATE_OLD_TRANSITION,
|
queries.UPDATE_OLD_TRANSITION,
|
||||||
@@ -47,27 +48,36 @@ async function StatusTransition(req, res) {
|
|||||||
: new Date(update_transitions.returning[0].end) -
|
: new Date(update_transitions.returning[0].end) -
|
||||||
new Date(update_transitions.returning[0].start);
|
new Date(update_transitions.returning[0].start);
|
||||||
|
|
||||||
const resp2 = await client.request(queries.INSERT_NEW_TRANSITION, {
|
const resp2 = await client.request(
|
||||||
oldTransitionId:
|
queries.INSERT_NEW_TRANSITION(update_transitions.affected_rows > 0),
|
||||||
update_transitions.affected_rows === 0
|
{
|
||||||
? null
|
...(update_transitions.affected_rows > 0
|
||||||
: update_transitions.returning[0].id,
|
? {
|
||||||
duration,
|
oldTransitionId:
|
||||||
newTransition: {
|
update_transitions.affected_rows === 0
|
||||||
bodyshopid: bodyshopid,
|
? null
|
||||||
jobid: jobid,
|
: update_transitions.returning[0].id,
|
||||||
start:
|
duration,
|
||||||
update_transitions.affected_rows === 0
|
}
|
||||||
? new Date()
|
: {}),
|
||||||
: update_transitions.returning[0].end,
|
newTransition: {
|
||||||
prev_value:
|
bodyshopid: bodyshopid,
|
||||||
update_transitions.affected_rows === 0
|
jobid: jobid,
|
||||||
? null
|
start:
|
||||||
: update_transitions.returning[0].value,
|
update_transitions.affected_rows === 0
|
||||||
value: value,
|
? new Date()
|
||||||
type: "status",
|
: update_transitions.returning[0].end,
|
||||||
},
|
prev_value:
|
||||||
});
|
update_transitions.affected_rows === 0
|
||||||
|
? null
|
||||||
|
: update_transitions.returning[0].value,
|
||||||
|
value: value,
|
||||||
|
type: "status",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.log("job-transition-update-result", "DEBUG", null, jobid, resp2);
|
||||||
|
|
||||||
//Check to see if there is an existing status transition record.
|
//Check to see if there is an existing status transition record.
|
||||||
//Query using Job ID, start is not null, end is null.
|
//Query using Job ID, start is not null, end is null.
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
const Dinero = require("dinero.js");
|
const Dinero = require("dinero.js");
|
||||||
const queries = require("../graphql-client/queries");
|
const queries = require("../graphql-client/queries");
|
||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
const logger = require('../utils/logger');
|
||||||
const logger = require("../utils/logger");
|
|
||||||
// Dinero.defaultCurrency = "USD";
|
// Dinero.defaultCurrency = "USD";
|
||||||
// Dinero.globalLocale = "en-CA";
|
// Dinero.globalLocale = "en-CA";
|
||||||
Dinero.globalRoundingMode = "HALF_EVEN";
|
Dinero.globalRoundingMode = "HALF_EVEN";
|
||||||
|
|
||||||
exports.totalsSsu = async function (req, res) {
|
exports.totalsSsu = async function (req, res) {
|
||||||
const BearerToken = req.headers.authorization;
|
|
||||||
const { id } = req.body;
|
const { id } = req.body;
|
||||||
|
|
||||||
|
const BearerToken = req.BearerToken;
|
||||||
|
const client = req.userGraphQLClient;
|
||||||
|
|
||||||
logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null);
|
logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null);
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
|
||||||
headers: {
|
|
||||||
Authorization: BearerToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const job = await client
|
const job = await client
|
||||||
@@ -75,21 +73,19 @@ async function TotalsServerSide(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function Totals(req, res) {
|
async function Totals(req, res) {
|
||||||
const { job } = req.body;
|
const { job, id } = req.body;
|
||||||
|
|
||||||
|
const logger = req.logger;
|
||||||
|
const client = req.userGraphQLClient;
|
||||||
|
|
||||||
logger.log("job-totals", "DEBUG", req.user.email, job.id, {
|
logger.log("job-totals", "DEBUG", req.user.email, job.id, {
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const BearerToken = req.headers.authorization;
|
|
||||||
const { id } = req.body;
|
|
||||||
logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null);
|
logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null);
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
|
||||||
headers: {
|
|
||||||
Authorization: BearerToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await AutoAddAtsIfRequired({ job, client });
|
await AutoAddAtsIfRequired({ job, client });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let ret = {
|
let ret = {
|
||||||
parts: CalculatePartsTotals(job.joblines),
|
parts: CalculatePartsTotals(job.joblines),
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ exports.totalsSsu = require("./job-totals").totalsSsu;
|
|||||||
exports.costing = require("./job-costing").JobCosting;
|
exports.costing = require("./job-costing").JobCosting;
|
||||||
exports.costingmulti = require("./job-costing").JobCostingMulti;
|
exports.costingmulti = require("./job-costing").JobCostingMulti;
|
||||||
exports.statustransition = require("./job-status-transition").statustransition;
|
exports.statustransition = require("./job-status-transition").statustransition;
|
||||||
|
exports.lifecycle = require('./job-lifecycle');
|
||||||
20
server/middleware/eventAuthorizationMIddleware.js
Normal file
20
server/middleware/eventAuthorizationMIddleware.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the event secret is correct
|
||||||
|
* It adds the following properties to the request object:
|
||||||
|
* - req.isEventAuthorized - Returns true if the event secret is correct
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param next
|
||||||
|
*/
|
||||||
|
function eventAuthorizationMiddleware(req, res, next) {
|
||||||
|
if (req.headers["event-secret"] !== process.env.EVENT_SECRET) {
|
||||||
|
return res.status(401).send("Unauthorized");
|
||||||
|
}
|
||||||
|
|
||||||
|
req.isEventAuthorized = true;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = eventAuthorizationMiddleware;
|
||||||
26
server/middleware/validateAdminMiddleware.js
Normal file
26
server/middleware/validateAdminMiddleware.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
const logger = require("../utils/logger");
|
||||||
|
const adminEmail = require("../utils/adminEmail");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate admin middleware
|
||||||
|
* It adds the following properties to the request object:
|
||||||
|
* - req.isAdmin - returns true if the user passed an admin check
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param next
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
const validateAdminMiddleware = (req, res, next) => {
|
||||||
|
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
||||||
|
logger.log("admin-validation-failed", "ERROR", req.user.email, null, {
|
||||||
|
request: req.body,
|
||||||
|
user: req.user,
|
||||||
|
});
|
||||||
|
return res.sendStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.isAdmin = true;
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = validateAdminMiddleware;
|
||||||
69
server/middleware/validateFirebaseIdTokenMiddleware.js
Normal file
69
server/middleware/validateFirebaseIdTokenMiddleware.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
const logger = require("../utils/logger");
|
||||||
|
const admin = require("firebase-admin");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware to validate Firebase ID Tokens.
|
||||||
|
* This middleware is used to protect API endpoints from unauthorized access.
|
||||||
|
* It adds the following properties to the request object:
|
||||||
|
* - req.user - the decoded Firebase ID Token
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param next
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const validateFirebaseIdTokenMiddleware = async (req, res, next) => {
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
!req.headers.authorization ||
|
||||||
|
!req.headers.authorization.startsWith("Bearer ")) &&
|
||||||
|
!(req.cookies && req.cookies.__session
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
console.error("Unauthorized attempt. No authorization provided.");
|
||||||
|
return res.status(403).send("Unauthorized");
|
||||||
|
}
|
||||||
|
|
||||||
|
let idToken;
|
||||||
|
|
||||||
|
if (
|
||||||
|
req.headers.authorization &&
|
||||||
|
req.headers.authorization.startsWith("Bearer ")
|
||||||
|
) {
|
||||||
|
// console.log('Found "Authorization" header');
|
||||||
|
// Read the ID Token from the Authorization header.
|
||||||
|
idToken = req.headers.authorization.split("Bearer ")[1];
|
||||||
|
} else if (req.cookies) {
|
||||||
|
//console.log('Found "__session" cookie');
|
||||||
|
// Read the ID Token from cookie.
|
||||||
|
idToken = req.cookies.__session;
|
||||||
|
} else {
|
||||||
|
// No cookie
|
||||||
|
console.error("Unauthorized attempt. No cookie provided.");
|
||||||
|
logger.log("api-unauthorized-call", "WARN", null, null, {
|
||||||
|
req,
|
||||||
|
type: "no-cookie",
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(403).send("Unauthorized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
|
||||||
|
//console.log("ID Token correctly decoded", decodedIdToken);
|
||||||
|
req.user = decodedIdToken;
|
||||||
|
next();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.log("api-unauthorized-call", "WARN", null, null, {
|
||||||
|
path: req.path,
|
||||||
|
body: req.body,
|
||||||
|
|
||||||
|
type: "unauthroized",
|
||||||
|
...error,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(401).send("Unauthorized");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = validateFirebaseIdTokenMiddleware;
|
||||||
24
server/middleware/withUserGraphQLClientMiddleware.js
Normal file
24
server/middleware/withUserGraphQLClientMiddleware.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const {GraphQLClient} = require("graphql-request");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware to add a GraphQL Client to the request object
|
||||||
|
* Adds the following to the request object:
|
||||||
|
* req.userGraphQLClient - GraphQL Client with user Bearer Token
|
||||||
|
* req.BearerToken - Bearer Token
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param next
|
||||||
|
*/
|
||||||
|
const withUserGraphQLClientMiddleware = (req, res, next) => {
|
||||||
|
const BearerToken = req.headers.authorization;
|
||||||
|
req.userGraphQLClient = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||||
|
headers: {
|
||||||
|
Authorization: BearerToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
req.BearerToken = BearerToken;
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = withUserGraphQLClientMiddleware;
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const _ = require("lodash");
|
const _ = require("lodash");
|
||||||
const logger = require("../utils/logger");
|
|
||||||
const xml2js = require("xml2js");
|
const xml2js = require("xml2js");
|
||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
|
||||||
const queries = require("../graphql-client/queries");
|
const queries = require("../graphql-client/queries");
|
||||||
|
const logger = require('../utils/logger');
|
||||||
|
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(
|
||||||
@@ -15,13 +14,10 @@ require("dotenv").config({
|
|||||||
exports.mixdataUpload = async (req, res) => {
|
exports.mixdataUpload = async (req, res) => {
|
||||||
const { bodyshopid } = req.body;
|
const { bodyshopid } = req.body;
|
||||||
|
|
||||||
const BearerToken = req.headers.authorization;
|
const client = req.userGraphQLClient;
|
||||||
|
|
||||||
logger.log("job-mixdata-upload", "DEBUG", req.user.email, null, null);
|
logger.log("job-mixdata-upload", "DEBUG", req.user.email, null, null);
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
|
||||||
headers: {
|
|
||||||
Authorization: BearerToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const element of req.files) {
|
for (const element of req.files) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ require("dotenv").config({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
|
||||||
//const client = require("../graphql-client/graphql-client").client;
|
//const client = require("../graphql-client/graphql-client").client;
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
const queries = require("../graphql-client/queries");
|
const queries = require("../graphql-client/queries");
|
||||||
@@ -15,10 +14,6 @@ const {getClient} = require('../../libs/awsUtils');
|
|||||||
|
|
||||||
|
|
||||||
async function OpenSearchUpdateHandler(req, res) {
|
async function OpenSearchUpdateHandler(req, res) {
|
||||||
if (req.headers["event-secret"] !== process.env.EVENT_SECRET) {
|
|
||||||
res.status(401).send("Unauthorized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const osClient = await getClient();
|
const osClient = await getClient();
|
||||||
@@ -186,12 +181,8 @@ async function OpenSearchSearchHandler(req, res) {
|
|||||||
search,
|
search,
|
||||||
});
|
});
|
||||||
|
|
||||||
const BearerToken = req.headers.authorization;
|
const BearerToken = req.BearerToken;
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
const client = req.userGraphQLClient;
|
||||||
headers: {
|
|
||||||
Authorization: BearerToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const assocs = await client
|
const assocs = await client
|
||||||
.setHeaders({Authorization: BearerToken})
|
.setHeaders({Authorization: BearerToken})
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
const Dinero = require("dinero.js");
|
const Dinero = require("dinero.js");
|
||||||
const queries = require("../graphql-client/queries");
|
const queries = require("../graphql-client/queries");
|
||||||
|
const logger = require('../utils/logger');
|
||||||
const { job } = require("../scheduling/scheduling-job");
|
const { job } = require("../scheduling/scheduling-job");
|
||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
|
||||||
const logger = require("../utils/logger");
|
|
||||||
const _ = require("lodash");
|
const _ = require("lodash");
|
||||||
|
|
||||||
// Dinero.defaultCurrency = "USD";
|
// Dinero.defaultCurrency = "USD";
|
||||||
// Dinero.globalLocale = "en-CA";
|
// Dinero.globalLocale = "en-CA";
|
||||||
|
|
||||||
exports.partsScan = async function (req, res) {
|
exports.partsScan = async function (req, res) {
|
||||||
const BearerToken = req.headers.authorization;
|
|
||||||
const { jobid } = req.body;
|
const { jobid } = req.body;
|
||||||
|
|
||||||
|
const BearerToken = req.BearerToken;
|
||||||
|
const client = req.userGraphQLClient;
|
||||||
|
|
||||||
logger.log("job-parts-scan", "DEBUG", req.user?.email, jobid, null);
|
logger.log("job-parts-scan", "DEBUG", req.user?.email, jobid, null);
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
|
||||||
headers: {
|
|
||||||
Authorization: BearerToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//Query all jobline data using the user's authorization.
|
//Query all jobline data using the user's authorization.
|
||||||
|
|||||||
13
server/routes/accountingRoutes.js
Normal file
13
server/routes/accountingRoutes.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
const {payments, payables, receivables} = require("../accounting/qbxml/qbxml");
|
||||||
|
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware");
|
||||||
|
|
||||||
|
router.use(validateFirebaseIdTokenMiddleware);
|
||||||
|
|
||||||
|
router.post('/qbxml/receivables', withUserGraphQLClientMiddleware, receivables);
|
||||||
|
router.post('/qbxml/payables', withUserGraphQLClientMiddleware, payables);
|
||||||
|
router.post('/qbxml/payments', withUserGraphQLClientMiddleware, payments);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
18
server/routes/adminRoutes.js
Normal file
18
server/routes/adminRoutes.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const fb = require('../firebase/firebase-handler');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
const {createAssociation, createShop, updateShop, updateCounter} = require("../admin/adminops");
|
||||||
|
const validateAdminMiddleware = require("../middleware/validateAdminMiddleware");
|
||||||
|
|
||||||
|
router.use(validateFirebaseIdTokenMiddleware);
|
||||||
|
|
||||||
|
router.post('/createassociation', validateAdminMiddleware, createAssociation);
|
||||||
|
router.post('/createshop', validateAdminMiddleware, createShop);
|
||||||
|
router.post('/updateshop', validateAdminMiddleware, updateShop);
|
||||||
|
router.post('/updatecounter', validateAdminMiddleware, updateCounter);
|
||||||
|
router.post('/updateuser', fb.updateUser);
|
||||||
|
router.post('/getuser', fb.getUser);
|
||||||
|
router.post('/createuser', fb.createUser);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
11
server/routes/cdkRoutes.js
Normal file
11
server/routes/cdkRoutes.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const cdkGetMake = require('../cdk/cdk-get-makes');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware");
|
||||||
|
|
||||||
|
router.use(validateFirebaseIdTokenMiddleware);
|
||||||
|
|
||||||
|
router.post('/getvehicles', withUserGraphQLClientMiddleware, cdkGetMake.default);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
9
server/routes/dataRoutes.js
Normal file
9
server/routes/dataRoutes.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const {autohouse, claimscorp, kaizen} = require('../data/data');
|
||||||
|
|
||||||
|
router.post('/ah', autohouse);
|
||||||
|
router.post('/cc', claimscorp);
|
||||||
|
router.post('/kaizen', kaizen);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
11
server/routes/intellipayRoutes.js
Normal file
11
server/routes/intellipayRoutes.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
const {lightbox_credentials, payment_refund, generate_payment_url, postback} = require("../intellipay/intellipay");
|
||||||
|
|
||||||
|
router.post('/lightbox_credentials', validateFirebaseIdTokenMiddleware, lightbox_credentials);
|
||||||
|
router.post('/payment_refund', validateFirebaseIdTokenMiddleware, payment_refund);
|
||||||
|
router.post('/generate_payment_url', validateFirebaseIdTokenMiddleware, generate_payment_url);
|
||||||
|
router.post('/postback', postback);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
18
server/routes/jobRoutes.js
Normal file
18
server/routes/jobRoutes.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const job = require('../job/job');
|
||||||
|
const {partsScan} = require('../parts-scan/parts-scan');
|
||||||
|
const eventAuthorizationMiddleware = require('../middleware/eventAuthorizationMIddleware');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
const {totals, statustransition, totalsSsu, costing, lifecycle, costingmulti} = require("../job/job");
|
||||||
|
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware");
|
||||||
|
|
||||||
|
router.post('/totals', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, totals);
|
||||||
|
router.post('/statustransition', eventAuthorizationMiddleware, statustransition);
|
||||||
|
router.post('/totalsssu', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware,totalsSsu);
|
||||||
|
router.post('/costing', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware,costing);
|
||||||
|
router.post('/lifecycle', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, lifecycle);
|
||||||
|
router.post('/costingmulti', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, costingmulti);
|
||||||
|
router.post('/partsscan', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, partsScan);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
13
server/routes/mediaRoutes.js
Normal file
13
server/routes/mediaRoutes.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const {createSignedUploadURL, downloadFiles, renameKeys, deleteFiles} = require('../media/media');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
|
||||||
|
router.use(validateFirebaseIdTokenMiddleware);
|
||||||
|
|
||||||
|
router.post('/sign', createSignedUploadURL);
|
||||||
|
router.post('/download', downloadFiles);
|
||||||
|
router.post('/rename', renameKeys);
|
||||||
|
router.post('/delete', deleteFiles);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
51
server/routes/miscellaneousRoutes.js
Normal file
51
server/routes/miscellaneousRoutes.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const logger = require("../../server/utils/logger");
|
||||||
|
const sendEmail = require("../email/sendemail");
|
||||||
|
const data = require("../data/data");
|
||||||
|
const bodyParser = require("body-parser");
|
||||||
|
const ioevent = require("../ioevent/ioevent");
|
||||||
|
const taskHandler = require("../tasks/tasks");
|
||||||
|
const os = require("../opensearch/os-handler");
|
||||||
|
const eventAuthorizationMiddleware = require("../middleware/eventAuthorizationMIddleware");
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware");
|
||||||
|
|
||||||
|
//Test route to ensure Express is responding.
|
||||||
|
router.get("/test", async function (req, res) {
|
||||||
|
const commit = require("child_process").execSync(
|
||||||
|
"git rev-parse --short HEAD"
|
||||||
|
);
|
||||||
|
// console.log(app.get('trust proxy'));
|
||||||
|
// console.log("remoteAddress", req.socket.remoteAddress);
|
||||||
|
// console.log("X-Forwarded-For", req.header('x-forwarded-for'));
|
||||||
|
logger.log("test-api-status", "DEBUG", "api", {commit});
|
||||||
|
// sendEmail.sendServerEmail({
|
||||||
|
// subject: `API Check - ${process.env.NODE_ENV}`,
|
||||||
|
// text: `Server API check has come in. Remote IP: ${req.socket.remoteAddress}, X-Forwarded-For: ${req.header('x-forwarded-for')}`,
|
||||||
|
// });
|
||||||
|
sendEmail.sendServerEmail({
|
||||||
|
subject: `API Check - ${process.env.NODE_ENV}`,
|
||||||
|
text: `Server API check has come in.`,
|
||||||
|
});
|
||||||
|
res.status(200).send(`OK - ${commit}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Search
|
||||||
|
router.post("/search", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, os.search);
|
||||||
|
router.post("/opensearch", eventAuthorizationMiddleware, os.handler);
|
||||||
|
|
||||||
|
|
||||||
|
// IO Events
|
||||||
|
router.post('/ioevent', ioevent.default);
|
||||||
|
|
||||||
|
// Email
|
||||||
|
router.post('/sendemail', validateFirebaseIdTokenMiddleware, sendEmail.sendEmail);
|
||||||
|
router.post('/emailbounce', bodyParser.text(), sendEmail.emailBounce);
|
||||||
|
|
||||||
|
// Handlers
|
||||||
|
router.post('/record-handler/arms', data.arms);
|
||||||
|
router.post("/taskHandler", validateFirebaseIdTokenMiddleware, taskHandler.taskHandler);
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
11
server/routes/mixDataRoutes.js
Normal file
11
server/routes/mixDataRoutes.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const multer = require('multer');
|
||||||
|
const upload = multer();
|
||||||
|
const {mixdataUpload} = require('../mixdata/mixdata');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware");
|
||||||
|
|
||||||
|
router.post('/upload', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, upload.any(), mixdataUpload);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
11
server/routes/notificationsRoutes.js
Normal file
11
server/routes/notificationsRoutes.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
const {subscribe, unsubscribe} = require("../firebase/firebase-handler");
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.use(validateFirebaseIdTokenMiddleware);
|
||||||
|
|
||||||
|
router.post('/subscribe', subscribe);
|
||||||
|
router.post('/unsubscribe', unsubscribe);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
14
server/routes/qboRoutes.js
Normal file
14
server/routes/qboRoutes.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const {authorize, callback, receivables, payables, payments} = require('../accounting/qbo/qbo');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); // Assuming you have a qbo module for handling QuickBooks Online related functionalities
|
||||||
|
|
||||||
|
// Define the routes for QuickBooks Online
|
||||||
|
router.post('/authorize', validateFirebaseIdTokenMiddleware, authorize);
|
||||||
|
router.get('/callback', callback);
|
||||||
|
router.post('/receivables', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, receivables);
|
||||||
|
router.post('/payables', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, payables);
|
||||||
|
router.post('/payments', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, payments);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
9
server/routes/renderRoutes.js
Normal file
9
server/routes/renderRoutes.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const {inlinecss} = require('../render/inlinecss');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
|
||||||
|
// Define the route for inline CSS rendering
|
||||||
|
router.post('/inlinecss', validateFirebaseIdTokenMiddleware, inlinecss);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
9
server/routes/schedulingRoutes.js
Normal file
9
server/routes/schedulingRoutes.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const {job} = require('../scheduling/scheduling-job');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware");
|
||||||
|
|
||||||
|
router.post('/job', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, job);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
17
server/routes/smsRoutes.js
Normal file
17
server/routes/smsRoutes.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const twilio = require('twilio');
|
||||||
|
const {receive} = require('../sms/receive');
|
||||||
|
const {send} = require('../sms/send');
|
||||||
|
const {status, markConversationRead} = require('../sms/status');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
|
||||||
|
// Twilio Webhook Middleware for production
|
||||||
|
const twilioWebhookMiddleware = twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" });
|
||||||
|
|
||||||
|
router.post('/receive', twilioWebhookMiddleware, receive);
|
||||||
|
router.post('/send', validateFirebaseIdTokenMiddleware, send);
|
||||||
|
router.post('/status', twilioWebhookMiddleware, status);
|
||||||
|
router.post('/markConversationRead', validateFirebaseIdTokenMiddleware, markConversationRead);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
8
server/routes/techRoutes.js
Normal file
8
server/routes/techRoutes.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const {techLogin} = require('../tech/tech');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
|
||||||
|
router.post('/login', validateFirebaseIdTokenMiddleware, techLogin);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
9
server/routes/utilRoutes.js
Normal file
9
server/routes/utilRoutes.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const {servertime, jsrAuth} = require('../utils/utils');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
|
||||||
|
router.post('/time', servertime);
|
||||||
|
router.post('/jsr', validateFirebaseIdTokenMiddleware, jsrAuth);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const queries = require("../graphql-client/queries");
|
const queries = require("../graphql-client/queries");
|
||||||
const Dinero = require("dinero.js");
|
const Dinero = require("dinero.js");
|
||||||
@@ -14,17 +13,14 @@ require("dotenv").config({
|
|||||||
});
|
});
|
||||||
|
|
||||||
exports.job = async (req, res) => {
|
exports.job = async (req, res) => {
|
||||||
const BearerToken = req.headers.authorization;
|
|
||||||
const { jobId } = req.body;
|
const { jobId } = req.body;
|
||||||
|
|
||||||
|
const BearerToken = req.BearerToken;
|
||||||
|
const client = req.userGraphQLClient;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log("smart-scheduling-start", "DEBUG", req.user.email, jobId, null);
|
logger.log("smart-scheduling-start", "DEBUG", req.user.email, jobId, null);
|
||||||
|
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
|
||||||
headers: {
|
|
||||||
Authorization: BearerToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await client
|
const result = await client
|
||||||
.setHeaders({ Authorization: BearerToken })
|
.setHeaders({ Authorization: BearerToken })
|
||||||
.request(queries.QUERY_UPCOMING_APPOINTMENTS, {
|
.request(queries.QUERY_UPCOMING_APPOINTMENTS, {
|
||||||
|
|||||||
13
server/utils/adminEmail.js
Normal file
13
server/utils/adminEmail.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* List of admin email addresses
|
||||||
|
* @type {string[]}
|
||||||
|
*/
|
||||||
|
const adminEmail = [
|
||||||
|
"patrick@imex.dev",
|
||||||
|
//"patrick@imex.test",
|
||||||
|
"patrick@imex.prod",
|
||||||
|
"patrick@imexsystems.ca",
|
||||||
|
"patrick@thinkimex.com",
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = adminEmail;
|
||||||
100
server/utils/calculateStatusDuration.js
Normal file
100
server/utils/calculateStatusDuration.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
const durationToHumanReadable = require("./durationToHumanReadable");
|
||||||
|
const moment = require("moment");
|
||||||
|
const _ = require("lodash");
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
const getColor = (key) => {
|
||||||
|
const hash = crypto.createHash('sha256');
|
||||||
|
hash.update(key);
|
||||||
|
const hashedKey = hash.digest('hex');
|
||||||
|
const num = parseInt(hashedKey, 16);
|
||||||
|
return '#' + (num % 16777215).toString(16).padStart(6, '0');
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateStatusDuration = (transitions, statuses) => {
|
||||||
|
let statusDuration = {};
|
||||||
|
let totalDuration = 0;
|
||||||
|
let totalCurrentStatusDuration = null;
|
||||||
|
let summations = [];
|
||||||
|
|
||||||
|
transitions.forEach((transition, index) => {
|
||||||
|
let duration = transition.duration;
|
||||||
|
totalDuration += duration;
|
||||||
|
if (transition.start && !transition.end) {
|
||||||
|
const startMoment = moment(transition.start);
|
||||||
|
const nowMoment = moment();
|
||||||
|
const duration = moment.duration(nowMoment.diff(startMoment));
|
||||||
|
totalCurrentStatusDuration = {
|
||||||
|
value: duration.asMilliseconds(),
|
||||||
|
humanReadable: durationToHumanReadable(duration)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transition.prev_value) {
|
||||||
|
statusDuration[transition.value] = {
|
||||||
|
value: duration,
|
||||||
|
humanReadable: transition.duration_readable
|
||||||
|
};
|
||||||
|
} else if (!transition.next_value) {
|
||||||
|
if (statusDuration[transition.value]) {
|
||||||
|
statusDuration[transition.value].value += duration;
|
||||||
|
statusDuration[transition.value].humanReadable = transition.duration_readable;
|
||||||
|
} else {
|
||||||
|
statusDuration[transition.value] = {
|
||||||
|
value: duration,
|
||||||
|
humanReadable: transition.duration_readable
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (statusDuration[transition.value]) {
|
||||||
|
statusDuration[transition.value].value += duration;
|
||||||
|
statusDuration[transition.value].humanReadable = transition.duration_readable;
|
||||||
|
} else {
|
||||||
|
statusDuration[transition.value] = {
|
||||||
|
value: duration,
|
||||||
|
humanReadable: transition.duration_readable
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate the percentage for each status
|
||||||
|
// Calculate the percentage for each status
|
||||||
|
let totalPercentage = 0;
|
||||||
|
const statusKeys = Object.keys(statusDuration);
|
||||||
|
statusKeys.forEach((status, index) => {
|
||||||
|
if (index !== statusKeys.length - 1) {
|
||||||
|
const percentage = (statusDuration[status].value / totalDuration) * 100;
|
||||||
|
totalPercentage += percentage;
|
||||||
|
statusDuration[status].percentage = percentage;
|
||||||
|
} else {
|
||||||
|
statusDuration[status].percentage = 100 - totalPercentage;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let [status, {value, humanReadable}] of Object.entries(statusDuration)) {
|
||||||
|
if (status !== 'total') {
|
||||||
|
summations.push({
|
||||||
|
status,
|
||||||
|
value,
|
||||||
|
humanReadable,
|
||||||
|
percentage: statusDuration[status].percentage,
|
||||||
|
color: getColor(status),
|
||||||
|
roundedPercentage: `${Math.round(statusDuration[status].percentage)}%`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const humanReadableTotal = durationToHumanReadable(moment.duration(totalDuration));
|
||||||
|
|
||||||
|
return {
|
||||||
|
summations: _.isArray(statuses) && !_.isEmpty(statuses) ? summations.sort((a, b) => {
|
||||||
|
return statuses.indexOf(a.status) - statuses.indexOf(b.status);
|
||||||
|
}) : summations,
|
||||||
|
totalStatuses: summations.length,
|
||||||
|
total: totalDuration,
|
||||||
|
totalCurrentStatusDuration,
|
||||||
|
humanReadableTotal
|
||||||
|
};
|
||||||
|
}
|
||||||
|
module.exports = calculateStatusDuration;
|
||||||
22
server/utils/durationToHumanReadable.js
Normal file
22
server/utils/durationToHumanReadable.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
const durationToHumanReadable = (duration) => {
|
||||||
|
if (!duration) return 'N/A';
|
||||||
|
|
||||||
|
let parts = [];
|
||||||
|
|
||||||
|
let years = duration.years();
|
||||||
|
let months = duration.months();
|
||||||
|
let days = duration.days();
|
||||||
|
let hours = duration.hours();
|
||||||
|
let minutes = duration.minutes();
|
||||||
|
let seconds = duration.seconds();
|
||||||
|
|
||||||
|
if (years) parts.push(years + ' year' + (years > 1 ? 's' : ''));
|
||||||
|
if (months) parts.push(months + ' month' + (months > 1 ? 's' : ''));
|
||||||
|
if (days) parts.push(days + ' day' + (days > 1 ? 's' : ''));
|
||||||
|
if (hours) parts.push(hours + ' hour' + (hours > 1 ? 's' : ''));
|
||||||
|
if (minutes) parts.push(minutes + ' minute' + (minutes > 1 ? 's' : ''));
|
||||||
|
if (seconds) parts.push(seconds + ' second' + (seconds > 1 ? 's' : ''));
|
||||||
|
|
||||||
|
return parts.join(', ');
|
||||||
|
}
|
||||||
|
module.exports = durationToHumanReadable;
|
||||||
Reference in New Issue
Block a user