Merge remote-tracking branch 'origin/master-AIO' into hotfix/IO-2969-Fonts-For-Production
This commit is contained in:
24
.dockerignore
Normal file
24
.dockerignore
Normal file
@@ -0,0 +1,24 @@
|
||||
# Directories to exclude
|
||||
.circleci
|
||||
.idea
|
||||
.platform
|
||||
.vscode
|
||||
_reference
|
||||
client
|
||||
redis/dockerdata
|
||||
hasura
|
||||
node_modules
|
||||
# Files to exclude
|
||||
.ebignore
|
||||
.editorconfig
|
||||
.eslintrc.json
|
||||
.gitignore
|
||||
.prettierrc.js
|
||||
Dockerfile
|
||||
README.MD
|
||||
bodyshop_translations.babel
|
||||
docker-compose.yml
|
||||
ecosystem.config.js
|
||||
|
||||
# Optional: Exclude logs and temporary files
|
||||
*.log
|
||||
0
.localstack/.gitkeep
Normal file
0
.localstack/.gitkeep
Normal file
@@ -1 +1,2 @@
|
||||
client_max_body_size 50M;
|
||||
client_body_buffer_size 5M;
|
||||
|
||||
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
@@ -14,6 +14,21 @@
|
||||
"request": "launch",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceRoot}/client/src"
|
||||
},
|
||||
{
|
||||
"name": "Attach to Node.js in Docker",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"address": "localhost",
|
||||
"port": 9229,
|
||||
"localRoot": "${workspaceFolder}",
|
||||
"remoteRoot": "/app",
|
||||
"protocol": "inspector",
|
||||
"restart": true,
|
||||
"sourceMaps": true,
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
47
Dockerfile
Normal file
47
Dockerfile
Normal file
@@ -0,0 +1,47 @@
|
||||
# Use Amazon Linux 2023 as the base image
|
||||
FROM amazonlinux:2023
|
||||
|
||||
# Install Git and Node.js (Amazon Linux 2023 uses the DNF package manager)
|
||||
RUN dnf install -y git \
|
||||
&& curl -sL https://rpm.nodesource.com/setup_20.x | bash - \
|
||||
&& dnf install -y nodejs \
|
||||
&& dnf clean all
|
||||
|
||||
|
||||
# Install dependencies required by node-canvas
|
||||
RUN dnf install -y \
|
||||
gcc \
|
||||
gcc-c++ \
|
||||
cairo-devel \
|
||||
pango-devel \
|
||||
libjpeg-turbo-devel \
|
||||
giflib-devel \
|
||||
libpng-devel \
|
||||
make \
|
||||
python3 \
|
||||
python3-pip \
|
||||
&& dnf clean all
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# This is because our test route uses a git commit hash
|
||||
RUN git config --global --add safe.directory /app
|
||||
|
||||
# Copy package.json and package-lock.json
|
||||
COPY package.json ./
|
||||
|
||||
# Install Nodemon
|
||||
RUN npm install -g nodemon
|
||||
|
||||
# Install dependencies
|
||||
RUN npm i --no-package-lock
|
||||
|
||||
# Copy the rest of your application code
|
||||
COPY . .
|
||||
|
||||
# Expose the port your app runs on (adjust if necessary)
|
||||
EXPOSE 4000 9229
|
||||
|
||||
# Start the application
|
||||
CMD ["nodemon", "--legacy-watch", "--inspect=0.0.0.0:9229", "server.js"]
|
||||
64
_reference/Documents/dockerreadme.md
Normal file
64
_reference/Documents/dockerreadme.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Setting up External Networking and Static IP for WSL2 using Hyper-V
|
||||
|
||||
This guide will walk you through the steps to configure your WSL2 (Windows Subsystem for Linux) instance to use an external Hyper-V virtual switch, enabling it to connect directly to your local network. Additionally, you'll learn how to assign a static IP address to your WSL2 instance.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Windows 11**
|
||||
2. **Docker Desktop For Windows (Latest Version)
|
||||
|
||||
# Docker Setup
|
||||
Inside the root of the project exists the `docker-compose.yaml` file, you can simply run
|
||||
`docker-compose up` to launch the backend.
|
||||
|
||||
Things to note:
|
||||
- When installing NPM packages, you will need to rebuild the `node-app` container
|
||||
- Making changes to the server files will restart the `node-app`
|
||||
|
||||
# Local Stack
|
||||
- LocalStack Front end (Optional) - https://apps.microsoft.com/detail/9ntrnft9zws2?hl=en-us&gl=US
|
||||
- http://localhost:4566/_aws/ses will allow you to see emails sent
|
||||
|
||||
# Docker Commands
|
||||
|
||||
## General `docker-compose` Commands:
|
||||
1. Bring up the services, force a rebuild of all services, and do not use the cache: `docker-compose up --build --no-cache`
|
||||
2. Start Containers in Detached Mode: This will run the containers in the background (detached mode): `docker-compose up -d`
|
||||
3. Stop and Remove Containers: Stops and removes the containers gracefully: `docker-compose down`
|
||||
4. Stop containers without removing them: `docker-compose stop`
|
||||
5. Remove Containers, Volumes, and Networks: `docker-compose down --volumes`
|
||||
6. Force rebuild of containers: `docker-compose build --no-cache`
|
||||
7. View running Containers: `docker-compose ps`
|
||||
8. View a specific containers logs: `docker-compose logs <container-name>`
|
||||
9. Scale services (multiple instances of a service): `docker-compose up --scale <container-name>=<instances number> -d`
|
||||
10. Watch a specific containers logs in realtime with timestamps: `docker-compose logs -f --timestamps <container-name>`
|
||||
|
||||
## Volume Management Commands
|
||||
1. List Docker volumes: `docker volume ls`
|
||||
2. Remove Unused volumes `docker volume prune`
|
||||
3. Remove specific volumes `docker volume rm <volume-name>`
|
||||
4. Inspect a volume: `docker volume inspect <volume-name>`
|
||||
|
||||
## Container Image Management Commands:
|
||||
1. List running containers: `docker ps`
|
||||
2. List all containers: `docker os -a`
|
||||
3. Remove Stopped containers: `docker container prune`
|
||||
4. Remove a specific container: `docker container rm <container-name>`
|
||||
5. Remove a specific image: `docker rmi <image-name>:<version>`
|
||||
6. Remove all unused images: `docker image prune -a`
|
||||
|
||||
## Network Management Commands:
|
||||
1. List networks: `docker network ls`
|
||||
2. Inspect a specific network: `docker network inspect <network-name>`
|
||||
3. Remove a specific network: `docker network rm <network-name>`
|
||||
4. Remove unused networks: `docker network prune`
|
||||
|
||||
## Debugging and maintenance:
|
||||
1. Enter a Running container: `docker exec -it <container name> /bin/bash` (could also be `/bin/sh` or for example `redis-cli` on a redis node)
|
||||
2. View container resource usage: `docker stats`
|
||||
3. Check Disk space used by Docker: `docker system df`
|
||||
4. Remove all unused Data (Nuclear option): `docker system prune`
|
||||
|
||||
## Specific examples
|
||||
1. To simulate a Clean state, one should run `docker system prune` followed by `docker volume prune -a`
|
||||
2. You can run `docker-compose up` without the `-d` option, and you will get what is identical to the experience you were used to, this includes being able to control-c and bring the entire stack down
|
||||
1
_reference/localEmailViewer/.gitignore
vendored
Normal file
1
_reference/localEmailViewer/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
7
_reference/localEmailViewer/README.md
Normal file
7
_reference/localEmailViewer/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
This will connect to your dockers local stack session and render the email in HTML.
|
||||
|
||||
```shell
|
||||
node index.js
|
||||
```
|
||||
|
||||
http://localhost:3334
|
||||
116
_reference/localEmailViewer/index.js
Normal file
116
_reference/localEmailViewer/index.js
Normal file
@@ -0,0 +1,116 @@
|
||||
// index.js
|
||||
|
||||
import express from 'express';
|
||||
import fetch from 'node-fetch';
|
||||
import {simpleParser} from 'mailparser';
|
||||
|
||||
const app = express();
|
||||
const PORT = 3334;
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
try {
|
||||
const response = await fetch('http://localhost:4566/_aws/ses');
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
const data = await response.json();
|
||||
const messagesHtml = await parseMessages(data.messages);
|
||||
res.send(renderHtml(messagesHtml));
|
||||
} catch (error) {
|
||||
console.error('Error fetching messages:', error);
|
||||
res.status(500).send('Error fetching messages');
|
||||
}
|
||||
});
|
||||
|
||||
async function parseMessages(messages) {
|
||||
const parsedMessages = await Promise.all(
|
||||
messages.map(async (message, index) => {
|
||||
try {
|
||||
const parsed = await simpleParser(message.RawData);
|
||||
return `
|
||||
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: lightgray">
|
||||
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: white">
|
||||
<div class="mb-2">
|
||||
<span class="font-bold text-lg">Message ${index + 1}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">From:</span> ${message.Source}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Region:</span> ${message.Region}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
|
||||
</div>
|
||||
</div>
|
||||
<div class="prose">
|
||||
${parsed.html || parsed.textAsHtml || 'No HTML content available'}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
console.error('Error parsing email:', error);
|
||||
return `
|
||||
<div class="bg-white shadow-md rounded-lg p-4 mb-6">
|
||||
<div class="mb-2">
|
||||
<span class="font-bold text-lg">Message ${index + 1}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">From:</span> ${message.Source}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Region:</span> ${message.Region}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
|
||||
</div>
|
||||
<div class="text-red-500">
|
||||
Error parsing email content
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
})
|
||||
);
|
||||
return parsedMessages.join('');
|
||||
}
|
||||
|
||||
function renderHtml(messagesHtml) {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Email Messages Viewer</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
body {
|
||||
background-color: #f3f4f6;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.prose {
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container bg-white shadow-lg rounded-lg p-6">
|
||||
<h1 class="text-2xl font-bold text-center mb-6">Email Messages Viewer</h1>
|
||||
<div id="messages-container">
|
||||
${messagesHtml}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on http://localhost:${PORT}`);
|
||||
});
|
||||
1214
_reference/localEmailViewer/package-lock.json
generated
Normal file
1214
_reference/localEmailViewer/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
_reference/localEmailViewer/package.json
Normal file
18
_reference/localEmailViewer/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "localemailviewer",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"express": "^4.21.1",
|
||||
"mailparser": "^3.7.1",
|
||||
"node-fetch": "^3.3.2"
|
||||
}
|
||||
}
|
||||
27
certs/id_rsa
Normal file
27
certs/id_rsa
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
|
||||
NhAAAAAwEAAQAAAQEAvNl5fuVmLNv72BZNxnTqX5CHf5Xi8UxjYaYxHITSCx7blnhpVYLd
|
||||
qXvcOWXzbsfjch/den73QiW4n2FYz75oGMhUGlOYzdWKA9I9Sj09Qy1R06RhwDiZGd5qaM
|
||||
swEeXpkNmi2u4Qd2kJeDfUQUigjC09V81O/vrniGtQAJScfiG/itdm+Ufn09Z4MYk0HWjq
|
||||
iDokNEskoEPsibYIrb+Q6vdtuPkZO+wU/smXhPtgw5ST6oQdmm/gVNsRg5XNzxrire+z1G
|
||||
WatnnVL3hPnnfpnf8W589dyms7GGJwhPerSGTN1bn0T4+9C69Cd7LBJtxiuFdRmdlGLLLP
|
||||
RR48Rur71wAAA9AEfVsdBH1bHQAAAAdzc2gtcnNhAAABAQC82Xl+5WYs2/vYFk3GdOpfkI
|
||||
d/leLxTGNhpjEchNILHtuWeGlVgt2pe9w5ZfNux+NyH916fvdCJbifYVjPvmgYyFQaU5jN
|
||||
1YoD0j1KPT1DLVHTpGHAOJkZ3mpoyzAR5emQ2aLa7hB3aQl4N9RBSKCMLT1XzU7++ueIa1
|
||||
AAlJx+Ib+K12b5R+fT1ngxiTQdaOqIOiQ0SySgQ+yJtgitv5Dq9224+Rk77BT+yZeE+2DD
|
||||
lJPqhB2ab+BU2xGDlc3PGuKt77PUZZq2edUveE+ed+md/xbnz13KazsYYnCE96tIZM3Vuf
|
||||
RPj70Lr0J3ssEm3GK4V1GZ2UYsss9FHjxG6vvXAAAAAwEAAQAAAQAQTosSLQbMmtY9S3e9
|
||||
yjyusdExcCTfhyQRu4MEHmfws+JsNMuLqbgwOVTD1AzYJQR7x0qdmDcLjCxL/uDnV16vvS
|
||||
Sd/Vf1dhnryIyoS29tzI0DRG94ZKq7tBvmHp1w/jRT4KcSVnovhW9e5Rs74+SRFhr06PKI
|
||||
S+wQOIv48Nwue9+QUMsMCpWgKXHx7SHNTHvnAfqdhi9O29SWlMA+v+mELZ5Cl+HU0UTt2I
|
||||
A1BxOe1N8FjN7KE2viJexsl3is1PuqMkpLl/wyHBJTVzUadl6DRALJQIm7/YO5goE72YOV
|
||||
Lpo27do3zjhC87dlKdATvZUzfKV0LuUVdxq/PNDZMUbBAAAAgQDShAqDZiDrdTUaGXfUVm
|
||||
QzcnVNbh2/KgZh4uux9QNHST562W6cnN7qxoRwVrM4BCOk1Kl73QQZW4nDvXX3PVC5j038
|
||||
8AXkcBHS9j9f4h72ue7D2jqlbHFa7aGU9zYgk9mbBF+GX3tDntkAIQjLtwOLfj1iiJ/clX
|
||||
mHFUAY1V4L8AAAAIEA3E4t/v0yU5D9AOI0r17UNYqfeyDoKAEDR4QbbFjO1l0kLnEJy7Zx
|
||||
Mhj18GilYg2y0P0v8dSM/oWXS8Hua2t5i9Exlv6gHhGlQ80mwYcVGIxewZ/pPeCPw0U+kt
|
||||
EKUjt09m9Oe7+6xHQsTBj9hY8/vqPmQwRalZFcLdhHiDiVKTcAAACBANtykaPXdVzEFx7D
|
||||
UOlsjVL7zM0EVOFXf9JJQ6BhazhmsEI2PYt3IpgGMo8cXkoUofAOIYjf421AabN1BqSO5J
|
||||
XTMxM0ZV3JmLLi804Mu9h1iFrVTBdLYOMJdc2VCo1EwHWpo9SXOyjxce/znvcIOU04aZhu
|
||||
TaPg816X+E+gw5JhAAAAFGRhdmVARGF2ZVJpY2hlci1JTUVYAQIDBAUG
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
1
certs/id_rsa.pub
Normal file
1
certs/id_rsa.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC82Xl+5WYs2/vYFk3GdOpfkId/leLxTGNhpjEchNILHtuWeGlVgt2pe9w5ZfNux+NyH916fvdCJbifYVjPvmgYyFQaU5jN1YoD0j1KPT1DLVHTpGHAOJkZ3mpoyzAR5emQ2aLa7hB3aQl4N9RBSKCMLT1XzU7++ueIa1AAlJx+Ib+K12b5R+fT1ngxiTQdaOqIOiQ0SySgQ+yJtgitv5Dq9224+Rk77BT+yZeE+2DDlJPqhB2ab+BU2xGDlc3PGuKt77PUZZq2edUveE+ed+md/xbnz13KazsYYnCE96tIZM3VufRPj70Lr0J3ssEm3GK4V1GZ2UYsss9FHjxG6vvX dave@DaveRicher-IMEX
|
||||
@@ -2,8 +2,6 @@ import { ApolloProvider } from "@apollo/client";
|
||||
import { SplitFactoryProvider, SplitSdk } from "@splitsoftware/splitio-react";
|
||||
import { ConfigProvider } from "antd";
|
||||
import enLocale from "antd/es/locale/en_US";
|
||||
import dayjs from "../utils/day";
|
||||
import "dayjs/locale/en";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||
@@ -19,8 +17,6 @@ if (import.meta.env.DEV) {
|
||||
Userpilot.initialize("NX-69145f08");
|
||||
}
|
||||
|
||||
dayjs.locale("en");
|
||||
|
||||
const config = {
|
||||
core: {
|
||||
authorizationKey: import.meta.env.VITE_APP_SPLIT_API,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { exportPageLimit } from "../../utils/config";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component";
|
||||
@@ -175,7 +175,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={dataSource}
|
||||
pagination={{ position: "top", pageSize: pageLimit }}
|
||||
pagination={{ position: "top", pageSize: exportPageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
onChange={handleTableChange}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { exportPageLimit } from "../../utils/config";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
@@ -177,7 +177,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={dataSource}
|
||||
pagination={{ position: "top", pageSize: pageLimit }}
|
||||
pagination={{ position: "top", pageSize: exportPageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
onChange={handleTableChange}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Button, Card, Input, Space, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { exportPageLimit } from "../../utils/config";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
|
||||
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import JobMarkSelectedExported from "../jobs-mark-selected-exported/jobs-mark-selected-exported";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
@@ -201,7 +201,7 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={dataSource}
|
||||
pagination={{ position: "top" }}
|
||||
pagination={{ position: "top", pageSize: exportPageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
onChange={handleTableChange}
|
||||
|
||||
@@ -7,10 +7,10 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CiecaSelect from "../../utils/Ciecaselect";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
|
||||
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -72,7 +72,14 @@ export function BillEnterModalLinesComponent({
|
||||
<BillLineSearchSelect
|
||||
disabled={disabled}
|
||||
options={lineData}
|
||||
style={{ width: "100%", minWidth: "10rem" }}
|
||||
style={{
|
||||
width: "20rem",
|
||||
maxWidth: "20rem",
|
||||
minWidth: "10rem",
|
||||
whiteSpace: "normal",
|
||||
height: "auto",
|
||||
minHeight: "32px" // default height of Ant Design inputs
|
||||
}}
|
||||
allowRemoved={form.getFieldValue("is_credit_memo") || false}
|
||||
onSelect={(value, opt) => {
|
||||
setFieldsValue({
|
||||
@@ -105,7 +112,7 @@ export function BillEnterModalLinesComponent({
|
||||
title: t("billlines.fields.line_desc"),
|
||||
dataIndex: "line_desc",
|
||||
editable: true,
|
||||
|
||||
width: "20rem",
|
||||
formItemProps: (field) => {
|
||||
return {
|
||||
key: `${field.index}line_desc`,
|
||||
@@ -119,7 +126,7 @@ export function BillEnterModalLinesComponent({
|
||||
]
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Input disabled={disabled} />
|
||||
formInput: (record, index) => <Input.TextArea disabled={disabled} autoSize />
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.quantity"),
|
||||
|
||||
@@ -11,7 +11,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
showSearch
|
||||
popupMatchSelectWidth={false}
|
||||
popupMatchSelectWidth={true}
|
||||
optionLabelProp={"name"}
|
||||
// optionFilterProp="line_desc"
|
||||
filterOption={(inputValue, option) => {
|
||||
@@ -43,7 +43,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim(),
|
||||
label: (
|
||||
<>
|
||||
<div style={{ whiteSpace: 'normal', wordBreak: 'break-word' }}>
|
||||
<span>
|
||||
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
@@ -57,7 +57,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
||||
<span style={{ float: "right", paddingleft: "1rem" }}>
|
||||
{item.act_price ? `$${item.act_price && item.act_price.toFixed(2)}` : ``}
|
||||
</span>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}))
|
||||
]}
|
||||
|
||||
@@ -45,7 +45,7 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh
|
||||
if (fcmToken) {
|
||||
setpollInterval(0);
|
||||
} else {
|
||||
setpollInterval(60000);
|
||||
setpollInterval(90000);
|
||||
}
|
||||
}, [fcmToken]);
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { WarningFilled } from "@ant-design/icons";
|
||||
import { Form, Input, InputNumber, Space } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import dayjs from "../../utils/day";
|
||||
//import ContractLicenseDecodeButton from "../contract-license-decode-button/contract-license-decode-button.component";
|
||||
import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
|
||||
import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
|
||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import {
|
||||
default as DateTimePicker,
|
||||
default as FormDateTimePicker
|
||||
} from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import InputPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
@@ -18,10 +20,10 @@ import ContractFormJobPrefill from "./contract-form-job-prefill.component";
|
||||
export default function ContractFormComponent({ form, create = false, selectedJobState, selectedCar }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<FormFieldsChanged form={form} />
|
||||
<>
|
||||
{!create && <FormFieldsChanged form={form} />}
|
||||
<LayoutFormRow>
|
||||
{create ? null : (
|
||||
{!create && (
|
||||
<Form.Item
|
||||
label={t("contracts.fields.status")}
|
||||
name="status"
|
||||
@@ -50,7 +52,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
<Form.Item label={t("contracts.fields.scheduledreturn")} name="scheduledreturn">
|
||||
<FormDateTimePicker />
|
||||
</Form.Item>
|
||||
{create ? null : (
|
||||
{!create && (
|
||||
<Form.Item label={t("contracts.fields.actualreturn")} name="actualreturn">
|
||||
<FormDateTimePicker />
|
||||
</Form.Item>
|
||||
@@ -122,7 +124,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
}}
|
||||
</Form.Item>
|
||||
)}
|
||||
{create ? null : (
|
||||
{!create && (
|
||||
<Form.Item label={t("contracts.fields.kmend")} name="kmend">
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
@@ -145,25 +147,21 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
>
|
||||
<CourtesyCarFuelSlider />
|
||||
</Form.Item>
|
||||
{create ? null : (
|
||||
{!create && (
|
||||
<Form.Item label={t("contracts.fields.fuelin")} name="fuelin" span={8}>
|
||||
<CourtesyCarFuelSlider />
|
||||
</Form.Item>
|
||||
)}
|
||||
</LayoutFormRow>
|
||||
<div>
|
||||
<Space wrap>
|
||||
{selectedJobState && (
|
||||
<div>
|
||||
<ContractFormJobPrefill jobId={selectedJobState && selectedJobState[0]} form={form} />
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
//<ContractLicenseDecodeButton form={form} />
|
||||
}
|
||||
</Space>
|
||||
</div>
|
||||
<LayoutFormRow header={t("contracts.labels.driverinformation")}>
|
||||
<Space wrap>
|
||||
{create && selectedJobState && (
|
||||
<ContractFormJobPrefill jobId={selectedJobState && selectedJobState[0]} form={form} />
|
||||
)}
|
||||
{/* {<ContractLicenseDecodeButton form={form} />} */}
|
||||
</Space>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow noDivider={true}>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.driver_dlnumber")}
|
||||
name="driver_dlnumber"
|
||||
@@ -183,9 +181,8 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
const dlExpiresBeforeReturn = dayjs(form.getFieldValue("driver_dlexpiry")).isBefore(
|
||||
dayjs(form.getFieldValue("scheduledreturn"))
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.driver_dlexpiry")}
|
||||
name="driver_dlexpiry"
|
||||
@@ -204,11 +201,10 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
<span>{t("contracts.labels.dlexpirebeforereturn")}</span>
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t("contracts.fields.driver_dlst")} name="driver_dlst">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
@@ -315,6 +311,6 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,13 +40,11 @@ export function DmsLogEvents({ socket, logs, bodyshop }) {
|
||||
|
||||
function LogLevelHierarchy(level) {
|
||||
switch (level) {
|
||||
case "TRACE":
|
||||
return "pink";
|
||||
case "DEBUG":
|
||||
return "orange";
|
||||
case "INFO":
|
||||
return "blue";
|
||||
case "WARNING":
|
||||
case "WARN":
|
||||
return "yellow";
|
||||
case "ERROR":
|
||||
return "red";
|
||||
|
||||
@@ -8,7 +8,7 @@ import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { acceptEula } from "../../redux/user/user.actions";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
import "./eula.styles.scss";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
@@ -208,7 +208,7 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
||||
{
|
||||
required: true,
|
||||
validator: (_, value) => {
|
||||
if (day(value).isSame(day(), "day")) {
|
||||
if (dayjs(value).isSame(dayjs(), "day")) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error(t("eula.messages.date_accepted")));
|
||||
|
||||
@@ -2,21 +2,38 @@ import { DatePicker } from "antd";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||
import dayjs from "../../utils/day";
|
||||
import { fuzzyMatchDate } from "./formats.js";
|
||||
|
||||
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => {
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const DateTimePicker = ({
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
id,
|
||||
onlyFuture,
|
||||
onlyToday,
|
||||
isDateOnly = false,
|
||||
bodyshop,
|
||||
...restProps
|
||||
}) => {
|
||||
const [isManualInput, setIsManualInput] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newDate) => {
|
||||
if (onChange) {
|
||||
onChange(newDate || null);
|
||||
onChange(bodyshop?.timezone && newDate ? dayjs(newDate).tz(bodyshop.timezone, true) : newDate);
|
||||
}
|
||||
setIsManualInput(false);
|
||||
},
|
||||
[onChange]
|
||||
[onChange, bodyshop?.timezone]
|
||||
);
|
||||
|
||||
const handleBlur = useCallback(
|
||||
@@ -102,4 +119,4 @@ DateTimePicker.propTypes = {
|
||||
isDateOnly: PropTypes.bool
|
||||
};
|
||||
|
||||
export default React.memo(DateTimePicker);
|
||||
export default connect(mapStateToProps, null)(DateTimePicker);
|
||||
|
||||
@@ -3,13 +3,15 @@ import axios from "axios";
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
|
||||
export default function GlobalSearchOs() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(false);
|
||||
|
||||
@@ -177,7 +179,18 @@ export default function GlobalSearchOs() {
|
||||
};
|
||||
|
||||
return (
|
||||
<AutoComplete options={data} onSearch={handleSearch} defaultActiveFirstOption onClear={() => setData([])}>
|
||||
<AutoComplete
|
||||
options={data}
|
||||
onSearch={handleSearch}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== "Enter") return;
|
||||
const firstUrlForSearch = data?.[0]?.options?.[0]?.label?.props?.to;
|
||||
if (!firstUrlForSearch) return;
|
||||
navigate(firstUrlForSearch);
|
||||
}}
|
||||
defaultActiveFirstOption
|
||||
onClear={() => setData([])}
|
||||
>
|
||||
<Input.Search
|
||||
size="large"
|
||||
placeholder={t("general.labels.globalsearch")}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AutoComplete, Divider, Input, Space } from "antd";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
@@ -13,6 +13,7 @@ import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.compon
|
||||
export default function GlobalSearch() {
|
||||
const { t } = useTranslation();
|
||||
const [callSearch, { loading, error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const executeSearch = (v) => {
|
||||
if (v && v.variables.search && v.variables.search !== "" && v.variables.search.length >= 3) callSearch(v);
|
||||
@@ -20,7 +21,6 @@ export default function GlobalSearch() {
|
||||
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
console.log("Handle Search");
|
||||
debouncedExecuteSearch({ variables: { search: value } });
|
||||
};
|
||||
|
||||
@@ -156,7 +156,17 @@ export default function GlobalSearch() {
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<AutoComplete options={options} onSearch={handleSearch} defaultActiveFirstOption>
|
||||
<AutoComplete
|
||||
options={options}
|
||||
onSearch={handleSearch}
|
||||
defaultActiveFirstOption
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== "Enter") return;
|
||||
const firstUrlForSearch = options?.[0]?.options?.[0]?.label?.props?.to;
|
||||
if (!firstUrlForSearch) return;
|
||||
navigate(firstUrlForSearch);
|
||||
}}
|
||||
>
|
||||
<Input.Search
|
||||
size="large"
|
||||
placeholder={t("general.labels.globalsearch")}
|
||||
|
||||
@@ -116,18 +116,15 @@ function Header({
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const deleteBetaCookie = () => {
|
||||
const cookieExists = document.cookie.split("; ").some((row) => row.startsWith(`betaSwitchImex=`));
|
||||
if (cookieExists) {
|
||||
const domain = window.location.hostname.split(".").slice(-2).join(".");
|
||||
document.cookie = `betaSwitchImex=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${domain}`;
|
||||
console.log(`betaSwitchImex cookie deleted`);
|
||||
} else {
|
||||
console.log(`betaSwitchImex cookie does not exist`);
|
||||
}
|
||||
};
|
||||
|
||||
deleteBetaCookie();
|
||||
// const deleteBetaCookie = () => {
|
||||
// const cookieExists = document.cookie.split("; ").some((row) => row.startsWith(`betaSwitchImex=`));
|
||||
// if (cookieExists) {
|
||||
// const domain = window.location.hostname.split(".").slice(-2).join(".");
|
||||
// document.cookie = `betaSwitchImex=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${domain}`;
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// deleteBetaCookie();
|
||||
|
||||
const accountingChildren = [];
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import ScheduleEventColor from "./schedule-event.color.component";
|
||||
import ScheduleEventNote from "./schedule-event.note.component";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -127,6 +128,9 @@ export function ScheduleEventComponent({
|
||||
{(event.job && event.job.alt_transport) || ""}
|
||||
<ScheduleAtChange job={event && event.job} />
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.comment")} valueStyle={{ overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||
<ProductionListColumnComment record={event && event.job} />
|
||||
</DataLabel>
|
||||
<ScheduleEventNote event={event} />
|
||||
</div>
|
||||
) : (
|
||||
@@ -316,6 +320,7 @@ export function ScheduleEventComponent({
|
||||
})`}
|
||||
|
||||
{event.job && event.job.alt_transport && <div style={{ margin: ".1rem" }}>{event.job.alt_transport}</div>}
|
||||
{event?.job?.comment && `C: ${event.job.comment}`}
|
||||
</Space>
|
||||
) : (
|
||||
<div
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import axios from "axios";
|
||||
import { Badge, Card, Space, Table, Tag } from "antd";
|
||||
import { gql, useQuery } from "@apollo/client";
|
||||
@@ -72,7 +72,7 @@ export function JobLifecycleComponent({ job, statuses, ...rest }) {
|
||||
dataIndex: "start",
|
||||
key: "start",
|
||||
render: (text) => DateTimeFormatterFunction(text),
|
||||
sorter: (a, b) => day(a.start).unix() - day(b.start).unix()
|
||||
sorter: (a, b) => dayjs(a.start).unix() - dayjs(b.start).unix()
|
||||
},
|
||||
{
|
||||
title: t("job_lifecycle.columns.relative_start"),
|
||||
@@ -90,7 +90,7 @@ export function JobLifecycleComponent({ job, statuses, ...rest }) {
|
||||
}
|
||||
return isEmpty(a.end) ? 1 : -1;
|
||||
}
|
||||
return day(a.end).unix() - day(b.end).unix();
|
||||
return dayjs(a.end).unix() - dayjs(b.end).unix();
|
||||
},
|
||||
render: (text) => (isEmpty(text) ? t("job_lifecycle.content.not_available") : DateTimeFormatterFunction(text))
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
||||
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Form, notification, Popover, Select, Space } from "antd";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -48,7 +48,7 @@ export function JobLineDispatchButton({
|
||||
const result = await dispatchLines({
|
||||
variables: {
|
||||
partsDispatch: {
|
||||
dispatched_at: day(),
|
||||
dispatched_at: dayjs(),
|
||||
employeeid: values.employeeid,
|
||||
jobid: job.id,
|
||||
dispatched_by: currentUser.email,
|
||||
@@ -138,7 +138,11 @@ export function JobLineDispatchButton({
|
||||
|
||||
return (
|
||||
<Popover open={visible} content={popMenu}>
|
||||
<Button disabled={selectedLines.length === 0 || jobRO || disabled} loading={loading} onClick={() => setVisible(true)}>
|
||||
<Button
|
||||
disabled={selectedLines.length === 0 || jobRO || disabled}
|
||||
loading={loading}
|
||||
onClick={() => setVisible(true)}
|
||||
>
|
||||
{t("joblines.actions.dispatchparts", { count: selectedLines.length })}
|
||||
</Button>
|
||||
</Popover>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { notification } from "antd";
|
||||
import Axios from "axios";
|
||||
import Dinero from "dinero.js";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -7,13 +10,10 @@ import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_NEW_JOB_LINE, UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectJobLineEditModal } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
||||
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
|
||||
import Axios from "axios";
|
||||
import Dinero from "dinero.js";
|
||||
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobLineEditModal: selectJobLineEditModal,
|
||||
@@ -82,6 +82,7 @@ function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bo
|
||||
variables: {
|
||||
lineId: jobLineEditModal.context.id,
|
||||
line: {
|
||||
...UndefinedToNull({
|
||||
...values,
|
||||
prt_dsmk_m: Dinero({
|
||||
amount: Math.round(values.act_price * 100)
|
||||
@@ -89,6 +90,7 @@ function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bo
|
||||
.percentage(Math.abs(values.prt_dsmk_p || 0))
|
||||
.multiply(values.prt_dsmk_p >= 0 ? 1 : -1)
|
||||
.toFormat(0.0)
|
||||
})
|
||||
}
|
||||
},
|
||||
refetchQueries: ["GET_LINE_TICKET_BY_PK"]
|
||||
|
||||
@@ -219,7 +219,7 @@ export function JobsExportAllButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled || jobIds?.length > 10}>
|
||||
{t("jobs.actions.exportselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Col, notification, Row, Table } from "antd";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_PARTS_DISPATCH_LINE } from "../../graphql/parts-dispatch.queries";
|
||||
@@ -11,7 +11,7 @@ export default function PartsDispatchExpander({ dispatch, job }) {
|
||||
const [updateDispatchLine] = useMutation(UPDATE_PARTS_DISPATCH_LINE);
|
||||
|
||||
const handleAccept = async ({ partsDispatchLineId }) => {
|
||||
const accepted_at = day();
|
||||
const accepted_at = dayjs();
|
||||
const result = await updateDispatchLine({
|
||||
variables: { id: partsDispatchLineId, line: { accepted_at } },
|
||||
optimisticResponse: {
|
||||
|
||||
@@ -242,7 +242,8 @@ export function PartsOrderListTableComponent({
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
render: (text, record) => recordActions(record, true)
|
||||
render: (text, record) => recordActions(record, true),
|
||||
id: "parts-order-list-table-actions"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ export function PayableExportAll({
|
||||
);
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled || billids?.length > 10}>
|
||||
{t("jobs.actions.exportselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -180,7 +180,7 @@ export function PaymentsExportAllButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled || paymentIds?.length > 10}>
|
||||
{t("jobs.actions.exportselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import Board from "./trello-board/index";
|
||||
import { Button, notification, Skeleton, Space } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { Button, notification, Skeleton, Space } from "antd";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { generate_UPDATE_JOB_KANBAN } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
@@ -15,14 +17,13 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
|
||||
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
|
||||
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
|
||||
import ProductionListPrint from "../production-list-table/production-list-print.component.jsx";
|
||||
import CardColorLegend from "./production-board-kanban-card-color-legend.component.jsx";
|
||||
import "./production-board-kanban.styles.scss";
|
||||
import { createBoardData } from "./production-board-kanban.utils.js";
|
||||
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import { defaultFilters, mergeWithDefaults } from "./settings/defaultKanbanSettings.js";
|
||||
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx";
|
||||
import Board from "./trello-board/index";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -185,7 +186,7 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
|
||||
const cardSettings = useMemo(() => {
|
||||
const kanbanSettings = associationSettings?.kanban_settings;
|
||||
return mergeWithDefaults(kanbanSettings);
|
||||
}, [associationSettings]);
|
||||
}, [associationSettings?.kanban_settings]);
|
||||
|
||||
const handleSettingsChange = () => {
|
||||
setFilter(defaultFilters);
|
||||
@@ -214,6 +215,7 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
|
||||
bodyshop={bodyshop}
|
||||
data={data}
|
||||
/>
|
||||
<ProductionListPrint />
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -1,15 +1,66 @@
|
||||
import InstanceRenderManager from "../../../utils/instanceRenderMgr.js";
|
||||
|
||||
const statisticsItems = [
|
||||
{ id: 0, name: "totalHrs", label: "total_hours_in_production" },
|
||||
{ id: 1, name: "totalAmountInProduction", label: "total_amount_in_production" },
|
||||
{ id: 2, name: "totalLAB", label: "total_lab_in_production" },
|
||||
{ id: 3, name: "totalLAR", label: "total_lar_in_production" },
|
||||
{ id: 4, name: "jobsInProduction", label: "jobs_in_production" },
|
||||
{ id: 5, name: "totalHrsOnBoard", label: "total_hours_on_board" },
|
||||
{ id: 6, name: "totalAmountOnBoard", label: "total_amount_on_board" },
|
||||
{ id: 7, name: "totalLABOnBoard", label: "total_lab_on_board" },
|
||||
{ id: 8, name: "totalLAROnBoard", label: "total_lar_on_board" },
|
||||
{ id: 9, name: "jobsOnBoard", label: "total_jobs_on_board" },
|
||||
{ id: 10, name: "tasksOnBoard", label: "tasks_on_board" },
|
||||
|
||||
{
|
||||
id: 5,
|
||||
name: "totalHrsOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_hours_in_view",
|
||||
rome: "total_hours_on_board",
|
||||
promanager: "total_hours_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "totalAmountOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_amount_in_view",
|
||||
rome: "total_amount_on_board",
|
||||
promanager: "total_amount_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "totalLABOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_lab_in_view",
|
||||
rome: "total_lab_on_board",
|
||||
promanager: "total_lab_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "totalLAROnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_lar_in_view",
|
||||
rome: "total_lar_on_board",
|
||||
promanager: "total_lar_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "jobsOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_jobs_in_view",
|
||||
rome: "total_jobs_on_board",
|
||||
promanager: "total_jobs_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "tasksOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "tasks_in_view",
|
||||
rome: "tasks_on_board",
|
||||
promanager: "tasks_on_board"
|
||||
})
|
||||
},
|
||||
{ id: 11, name: "tasksInProduction", label: "tasks_in_production" }
|
||||
];
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ export function ProductionListColumnStatus({ record, bodyshop, insertAuditTrail
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSetStatus = async (e) => {
|
||||
if (bodyshop.md_ro_statuses.production_statuses.includes(record.status) && !bodyshop.md_ro_statuses.post_production_statuses.includes(record.status)) {
|
||||
logImEXEvent("production_change_status");
|
||||
// e.stopPropagation();
|
||||
setLoading(true);
|
||||
@@ -38,8 +39,8 @@ export function ProductionListColumnStatus({ record, bodyshop, insertAuditTrail
|
||||
operation: AuditTrailMapping.jobstatuschange(key),
|
||||
type: "jobstatuschange"
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const menu = {
|
||||
|
||||
@@ -457,7 +457,8 @@ export function ProductionListConfigManager({
|
||||
value={activeView}
|
||||
disabled={open || isAddingNewProfile} // Disable the Select box when the popover is open or adding a new profile
|
||||
>
|
||||
{bodyshop.production_config
|
||||
{bodyshop?.production_config &&
|
||||
bodyshop.production_config
|
||||
.slice()
|
||||
.sort((a, b) =>
|
||||
a.name === t("production.constants.main_profile")
|
||||
|
||||
@@ -51,8 +51,8 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
|
||||
const initialColumnsRef = useRef(
|
||||
(initialStateRef.current &&
|
||||
bodyshop.production_config
|
||||
.find((p) => p.name === defaultView)
|
||||
bodyshop?.production_config
|
||||
?.find((p) => p.name === defaultView)
|
||||
?.columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({
|
||||
@@ -76,7 +76,7 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
const { t } = useTranslation();
|
||||
|
||||
const matchingColumnConfig = useMemo(() => {
|
||||
return bodyshop.production_config.find((p) => p.name === defaultView);
|
||||
return bodyshop?.production_config?.find((p) => p.name === defaultView);
|
||||
}, [bodyshop.production_config, defaultView]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -120,14 +120,6 @@ var formats = {
|
||||
};
|
||||
|
||||
const localizer = (dayjsLib) => {
|
||||
// load dayjs plugins
|
||||
dayjsLib.extend(isBetween);
|
||||
dayjsLib.extend(isSameOrAfter);
|
||||
dayjsLib.extend(isSameOrBefore);
|
||||
dayjsLib.extend(localeData);
|
||||
dayjsLib.extend(localizedFormat);
|
||||
dayjsLib.extend(minMax);
|
||||
dayjsLib.extend(utc);
|
||||
var locale = function locale(dj, c) {
|
||||
return c ? dj.locale(c) : dj;
|
||||
};
|
||||
@@ -137,6 +129,7 @@ const localizer = (dayjsLib) => {
|
||||
|
||||
//TODO This was the issue entirely...
|
||||
// var dayjs = dayjsLib.tz ? dayjsLib.tz : dayjsLib;
|
||||
|
||||
var dayjs = dayjsLib;
|
||||
|
||||
function getTimezoneOffset(date) {
|
||||
|
||||
@@ -20,6 +20,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
problemJobs: selectProblemJobs
|
||||
});
|
||||
|
||||
const localizer = local(dayjs);
|
||||
|
||||
export function ScheduleCalendarWrapperComponent({
|
||||
|
||||
@@ -4,12 +4,12 @@ import ScoreboardChart from "../scoreboard-chart/scoreboard-chart.component";
|
||||
import ScoreboardLastDays from "../scoreboard-last-days/scoreboard-last-days.component";
|
||||
import ScoreboardTargetsTable from "../scoreboard-targets-table/scoreboard-targets-table.component";
|
||||
|
||||
import { useApolloClient, useQuery } from "@apollo/client";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { GET_BLOCKED_DAYS, QUERY_SCOREBOARD } from "../../graphql/scoreboard.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import dayjs from "../../utils/day";
|
||||
import { useApolloClient, useQuery } from "@apollo/client";
|
||||
import { GET_BLOCKED_DAYS, QUERY_SCOREBOARD } from "../../graphql/scoreboard.queries";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -26,7 +26,7 @@ export function ScoreboardDisplayComponent({ bodyshop }) {
|
||||
start: dayjs().startOf("month"),
|
||||
end: dayjs().endOf("month")
|
||||
},
|
||||
pollInterval: 60000
|
||||
pollInterval: 60000*5
|
||||
});
|
||||
|
||||
const { data } = scoreboardSubscription;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Col, Row } from "antd";
|
||||
import _ from "lodash";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import dayjs from "../../utils/day";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||
@@ -86,7 +86,7 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
pollInterval: 60000,
|
||||
pollInterval: 60000*5,
|
||||
skip: !fixedPeriods
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Col, Row } from "antd";
|
||||
import _ from "lodash";
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, { useMemo } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries";
|
||||
import dayjs from "../../utils/day";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||
@@ -68,7 +68,7 @@ export default function ScoreboardTimeTickets() {
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
pollInterval: 60000,
|
||||
pollInterval: 60000*5,
|
||||
skip: !fixedPeriods
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { Button, Card, Space, Switch, Table } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import dayjs from "../../utils/day";
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
CheckCircleOutlined,
|
||||
@@ -15,9 +8,16 @@ import {
|
||||
PlusCircleFilled,
|
||||
SyncOutlined
|
||||
} from "@ant-design/icons";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
|
||||
import { Button, Card, Space, Switch, Table } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
/**
|
||||
* Task List Component
|
||||
@@ -140,6 +140,17 @@ function TaskListComponent({
|
||||
render: (text, record) => <DateTimeFormatter>{record.created_at}</DateTimeFormatter>
|
||||
});
|
||||
|
||||
columns.push({
|
||||
title: t("tasks.fields.created_by"),
|
||||
dataIndex: "created_by",
|
||||
key: "created_by",
|
||||
width: "8%",
|
||||
defaultSortOrder: "descend",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "created_by" && sortorder,
|
||||
render: (text, record) => record.created_by
|
||||
});
|
||||
|
||||
if (!onlyMine) {
|
||||
columns.push({
|
||||
title: t("tasks.fields.assigned_to"),
|
||||
@@ -155,65 +166,70 @@ function TaskListComponent({
|
||||
});
|
||||
}
|
||||
|
||||
if (showRo) {
|
||||
columns.push({
|
||||
title: t("tasks.fields.job.ro_number"),
|
||||
dataIndex: ["job", "ro_number"],
|
||||
key: "job.ro_number",
|
||||
width: "8%",
|
||||
render: (text, record) =>
|
||||
record.job ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}?tab=tasks`}>{record.job.ro_number || t("general.labels.na")}</Link>
|
||||
) : (
|
||||
t("general.labels.na")
|
||||
)
|
||||
});
|
||||
title: t("tasks.fields.related_items"),
|
||||
key: "related_items",
|
||||
width: "12%",
|
||||
render: (text, record) => {
|
||||
const items = [];
|
||||
|
||||
// Job
|
||||
if (showRo && record.job) {
|
||||
items.push(
|
||||
<Link key="job" to={`/manage/jobs/${record.job.id}?tab=tasks`}>
|
||||
{t("tasks.fields.job.ro_number")}: {record.job.ro_number}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
if (showRo && !record.job) {
|
||||
items.push(`${t("tasks.fields.job.ro_number")}: ${t("general.labels.na")}`);
|
||||
}
|
||||
|
||||
// Jobline
|
||||
if (record.jobline?.line_desc) {
|
||||
items.push(
|
||||
<span key="jobline">
|
||||
{t("tasks.fields.jobline")}: {record.jobline.line_desc}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Parts Order
|
||||
if (record.parts_order) {
|
||||
const { order_number, vendor } = record.parts_order;
|
||||
const partsOrderText =
|
||||
order_number && vendor?.name ? `${order_number} - ${vendor.name}` : t("general.labels.na");
|
||||
items.push(
|
||||
<Link
|
||||
key="parts_order"
|
||||
to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}
|
||||
>
|
||||
{t("tasks.fields.parts_order")}: {partsOrderText}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// Bill
|
||||
if (record.bill) {
|
||||
const { invoice_number, vendor } = record.bill;
|
||||
const billText = invoice_number && vendor?.name ? `${invoice_number} - ${vendor.name}` : t("general.labels.na");
|
||||
items.push(
|
||||
<Link key="bill" to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
|
||||
{t("tasks.fields.bill")}: {billText}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return items.length > 0 ? <Space direction="vertical">{items}</Space> : null;
|
||||
}
|
||||
});
|
||||
|
||||
columns.push(
|
||||
{
|
||||
title: t("tasks.fields.jobline"),
|
||||
dataIndex: ["jobline", "id"],
|
||||
key: "jobline.id",
|
||||
width: "8%",
|
||||
render: (text, record) => record?.jobline?.line_desc || ""
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.parts_order"),
|
||||
dataIndex: ["parts_order", "id"],
|
||||
key: "part_order.id",
|
||||
width: "8%",
|
||||
render: (text, record) =>
|
||||
record.parts_order ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}>
|
||||
{record.parts_order.order_number && record.parts_order.vendor && record.parts_order.vendor.name
|
||||
? `${record.parts_order.order_number} - ${record.parts_order.vendor.name}`
|
||||
: t("general.labels.na")}
|
||||
</Link>
|
||||
) : (
|
||||
""
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.bill"),
|
||||
dataIndex: ["bill", "id"],
|
||||
key: "bill.id",
|
||||
width: "10%",
|
||||
render: (text, record) =>
|
||||
record.bill ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
|
||||
{record.bill.invoice_number && record.bill.vendor && record.bill.vendor.name
|
||||
? `${record.bill.invoice_number} - ${record.bill.vendor.name}`
|
||||
: t("general.labels.na")}
|
||||
</Link>
|
||||
) : (
|
||||
""
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.title"),
|
||||
dataIndex: "title",
|
||||
key: "title",
|
||||
minWidth: "20%",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "title" && sortorder
|
||||
},
|
||||
@@ -247,7 +263,7 @@ function TaskListComponent({
|
||||
{
|
||||
title: t("tasks.fields.actions"),
|
||||
key: "toggleCompleted",
|
||||
width: "5%",
|
||||
width: "8%",
|
||||
render: (text, record) => (
|
||||
<Space direction="horizontal">
|
||||
<Button
|
||||
|
||||
@@ -39,7 +39,7 @@ export function TimeTicketList({
|
||||
extra
|
||||
}) {
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
sortedInfo: { columnKey: 'date', order: 'descend' },
|
||||
filteredInfo: { text: "" }
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Form, Modal, notification, Space } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import dayjs from "../../utils/day";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Form, Modal, notification, Space } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -11,9 +11,9 @@ import { INSERT_NEW_TIME_TICKET, UPDATE_TIME_TICKET } from "../../graphql/timeti
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectTimeTicket } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import TimeTicketModalComponent from "./time-ticket-modal.component";
|
||||
import dayjs from "../../utils/day";
|
||||
import TimeTicketsCommitToggleComponent from "../time-tickets-commit-toggle/time-tickets-commit-toggle.component";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import TimeTicketModalComponent from "./time-ticket-modal.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
timeTicketModal: selectTimeTicket,
|
||||
@@ -87,7 +87,7 @@ export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible,
|
||||
if (enterAgain) {
|
||||
//Capture the existing information and repopulate it.
|
||||
|
||||
const prev = form.getFieldsValue(["date", "employeeid"]);
|
||||
const prev = form.getFieldsValue(["date", "employeeid", "flat_rate"]);
|
||||
|
||||
form.resetFields();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -29,7 +29,7 @@ export function TimeTicketsCommit({ bodyshop, currentUser, timeticket, disabled,
|
||||
? { commited_by: null, committed_at: null }
|
||||
: {
|
||||
commited_by: currentUser.email,
|
||||
committed_at: day()
|
||||
committed_at: dayjs()
|
||||
};
|
||||
|
||||
const result = await updateTimeTicket({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -34,7 +34,7 @@ export function TimeTicketsCommit({
|
||||
timeticketIds: timetickets.map((ticket) => ticket.id),
|
||||
timeticket: {
|
||||
commited_by: currentUser.email,
|
||||
committed_at: day()
|
||||
committed_at: dayjs()
|
||||
}
|
||||
},
|
||||
update(cache) {
|
||||
@@ -47,7 +47,7 @@ export function TimeTicketsCommit({
|
||||
return {
|
||||
...ticket,
|
||||
commited_by: currentUser.email,
|
||||
committed_at: day()
|
||||
committed_at: dayjs()
|
||||
};
|
||||
}
|
||||
return ticket;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import _ from "lodash";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -49,7 +49,7 @@ export function TtApproveButton({
|
||||
})),
|
||||
approvalIds: selectedTickets,
|
||||
approvalUpdate: {
|
||||
approved_at: day(),
|
||||
approved_at: dayjs(),
|
||||
approved_by: currentUser.email
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
|
||||
v_model_desc
|
||||
est_ct_fn
|
||||
est_ct_ln
|
||||
comment
|
||||
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
|
||||
aggregate {
|
||||
sum {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { gql } from "@apollo/client";
|
||||
|
||||
export const QUERY_TICKETS_BY_JOBID = gql`
|
||||
query QUERY_TICKETS_BY_JOBID($jobid: uuid!) {
|
||||
timetickets(where: { jobid: { _eq: $jobid } }, order_by: { date: desc_nulls_first }) {
|
||||
timetickets(where: { jobid: { _eq: $jobid } }) {
|
||||
actualhrs
|
||||
cost_center
|
||||
ciecacode
|
||||
@@ -26,7 +26,7 @@ export const QUERY_TICKETS_BY_JOBID = gql`
|
||||
|
||||
export const QUERY_TIME_TICKETS_IN_RANGE = gql`
|
||||
query QUERY_TIME_TICKETS_IN_RANGE($start: date!, $end: date!) {
|
||||
timetickets(where: { date: { _gte: $start, _lte: $end } }, order_by: { date: desc_nulls_first }) {
|
||||
timetickets(where: { date: { _gte: $start, _lte: $end } }) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
clockoff
|
||||
@@ -69,7 +69,6 @@ export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
|
||||
) {
|
||||
timetickets(
|
||||
where: { date: { _gte: $start, _lte: $end }, employeeid: { _eq: $employeeid } }
|
||||
order_by: { date: desc_nulls_first }
|
||||
) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
@@ -101,7 +100,6 @@ export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
|
||||
}
|
||||
fixedperiod: timetickets(
|
||||
where: { date: { _gte: $fixedStart, _lte: $fixedEnd }, employeeid: { _eq: $employeeid } }
|
||||
order_by: { date: desc_nulls_first }
|
||||
) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
@@ -145,7 +143,6 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
|
||||
) {
|
||||
timetickets(
|
||||
where: { date: { _gte: $start, _lte: $end }, cost_center: { _neq: "timetickets.labels.shift" } }
|
||||
order_by: { date: desc_nulls_first }
|
||||
) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
@@ -180,7 +177,6 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
|
||||
}
|
||||
fixedperiod: timetickets(
|
||||
where: { date: { _gte: $fixedStart, _lte: $fixedEnd }, cost_center: { _neq: "timetickets.labels.shift" } }
|
||||
order_by: { date: desc_nulls_first }
|
||||
) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
@@ -335,7 +331,6 @@ export const UPDATE_TIME_TICKETS = gql`
|
||||
export const QUERY_ACTIVE_TIME_TICKETS = gql`
|
||||
query QUERY_ACTIVE_TIME_TICKETS($employeeId: uuid) {
|
||||
timetickets(
|
||||
order_by: { date: desc_nulls_first }
|
||||
where: {
|
||||
_and: {
|
||||
clockoff: { _is_null: true }
|
||||
|
||||
@@ -71,7 +71,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
...logs,
|
||||
{
|
||||
timestamp: new Date(),
|
||||
level: "WARNING",
|
||||
level: "WARN",
|
||||
message: "Reconnected to CDK Export Service"
|
||||
}
|
||||
];
|
||||
@@ -123,10 +123,9 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
socket.emit("set-log-level", value);
|
||||
}}
|
||||
>
|
||||
<Select.Option key="TRACE">TRACE</Select.Option>
|
||||
<Select.Option key="DEBUG">DEBUG</Select.Option>
|
||||
<Select.Option key="INFO">INFO</Select.Option>
|
||||
<Select.Option key="WARNING">WARNING</Select.Option>
|
||||
<Select.Option key="WARN">WARN</Select.Option>
|
||||
<Select.Option key="ERROR">ERROR</Select.Option>
|
||||
</Select>
|
||||
<Button onClick={() => setLogs([])}>Clear Logs</Button>
|
||||
|
||||
@@ -90,7 +90,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
...logs,
|
||||
{
|
||||
timestamp: new Date(),
|
||||
level: "WARNING",
|
||||
level: "warn",
|
||||
message: "Reconnected to CDK Export Service"
|
||||
}
|
||||
];
|
||||
@@ -173,10 +173,9 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
socket.emit("set-log-level", value);
|
||||
}}
|
||||
>
|
||||
<Select.Option key="TRACE">TRACE</Select.Option>
|
||||
<Select.Option key="DEBUG">DEBUG</Select.Option>
|
||||
<Select.Option key="INFO">INFO</Select.Option>
|
||||
<Select.Option key="WARNING">WARNING</Select.Option>
|
||||
<Select.Option key="WARN">WARN</Select.Option>
|
||||
<Select.Option key="ERROR">ERROR</Select.Option>
|
||||
</Select>
|
||||
<Button onClick={() => setLogs([])}>Clear Logs</Button>
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
} from "../../firebase/firebase.utils";
|
||||
import { QUERY_EULA } from "../../graphql/bodyshop.queries";
|
||||
import client from "../../utils/GraphQLClient";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import {
|
||||
checkInstanceId,
|
||||
@@ -96,7 +96,7 @@ export function* isUserAuthenticated() {
|
||||
const eulaQuery = yield client.query({
|
||||
query: QUERY_EULA,
|
||||
variables: {
|
||||
now: day()
|
||||
now: dayjs()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -314,8 +314,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
try {
|
||||
const userEmail = yield select((state) => state.user.currentUser.email);
|
||||
try {
|
||||
//console.log("Setting shop timezone.");
|
||||
// dayjs.tz.setDefault(payload.timezone);
|
||||
dayjs.tz.setDefault(payload.timezone);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
"jobclosedwithbypass": "Job was invoiced using the master bypass password. ",
|
||||
"jobconverted": "Job converted and assigned number {{ro_number}}.",
|
||||
"jobdelivery": "Job intake completed. Status set to {{status}}. Actual completion is {{actual_completion}}.",
|
||||
"jobexported": "",
|
||||
"jobexported": "Job has been exported",
|
||||
"jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.",
|
||||
"jobimported": "Job imported.",
|
||||
"jobinproductionchange": "Job production status set to {{inproduction}}",
|
||||
@@ -2849,15 +2849,21 @@
|
||||
"jobs_in_production": "Jobs in Production",
|
||||
"tasks_in_production": "Tasks in Production",
|
||||
"tasks_on_board": "Tasks on Board",
|
||||
"tasks_in_view": "Tasks in View",
|
||||
"total_amount_in_production": "Dollars in Production",
|
||||
"total_amount_on_board": "Dollars on Board",
|
||||
"total_amount_in_view": "Dollars in View",
|
||||
"total_hours_in_production": "Hours in Production",
|
||||
"total_hours_on_board": "Hours on Board",
|
||||
"total_hours_in_view": "Hours in View",
|
||||
"total_jobs_on_board": "Jobs on Board",
|
||||
"total_jobs_in_view": "Jobs in View",
|
||||
"total_lab_in_production": "Body Hours in Production",
|
||||
"total_lab_on_board": "Body Hours on Board",
|
||||
"total_lab_in_view": "Body Hours in View",
|
||||
"total_lar_in_production": "Refinish Hours in Production",
|
||||
"total_lar_on_board": "Refinish Hours on Board"
|
||||
"total_lar_on_board": "Refinish Hours on Board",
|
||||
"total_lar_in_view": "Refinish Hours in View"
|
||||
},
|
||||
"statistics_title": "Statistics"
|
||||
},
|
||||
@@ -2869,15 +2875,21 @@
|
||||
"tasks": "Tasks",
|
||||
"tasks_in_production": "Tasks in Production",
|
||||
"tasks_on_board": "Tasks on Board",
|
||||
"tasks_in_view": "Tasks in View",
|
||||
"total_amount_in_production": "Dollars in Production",
|
||||
"total_amount_on_board": "Dollars on Board",
|
||||
"total_amount_in_view": "Dollars in View",
|
||||
"total_hours_in_production": "Hours in Production",
|
||||
"total_hours_on_board": "Hours on Board",
|
||||
"total_hours_in_view": "Hours in View",
|
||||
"total_jobs_on_board": "Jobs on Board",
|
||||
"total_jobs_in_view": "Jobs in View",
|
||||
"total_lab_in_production": "Body Hours in Production",
|
||||
"total_lab_on_board": "Body Hours on Board",
|
||||
"total_lab_in_view": "Body Hours in View",
|
||||
"total_lar_in_production": "Refinish Hours in Production",
|
||||
"total_lar_on_board": "Refinish Hours on Board"
|
||||
"total_lar_on_board": "Refinish Hours on Board",
|
||||
"total_lar_in_view": "Refinish Hours in View"
|
||||
},
|
||||
"successes": {
|
||||
"removed": "Job removed from production."
|
||||
@@ -3032,6 +3044,7 @@
|
||||
"production_by_target_date": "Production by Target Date",
|
||||
"production_by_technician": "Production by Technician",
|
||||
"production_by_technician_one": "Production filtered by Technician",
|
||||
"production_not_production_status": "Production not in Production Status",
|
||||
"production_over_time": "Production Level over Time",
|
||||
"psr_by_make": "Percent of Sales by Vehicle Make",
|
||||
"purchase_return_ratio_grouped_by_vendor_detail": "Purchase & Return Ratio by Vendor (Detail)",
|
||||
@@ -3174,6 +3187,7 @@
|
||||
"billid": "Bill",
|
||||
"completed": "Completed",
|
||||
"created_at": "Created At",
|
||||
"created_by": "Created By",
|
||||
"description": "Description",
|
||||
"due_date": "Due Date",
|
||||
"job": {
|
||||
@@ -3190,6 +3204,7 @@
|
||||
"medium": "Medium"
|
||||
},
|
||||
"priority": "Priority",
|
||||
"related_items": "Related Items",
|
||||
"remind_at": "Remind At",
|
||||
"title": "Title"
|
||||
},
|
||||
|
||||
@@ -2849,15 +2849,21 @@
|
||||
"jobs_in_production": "",
|
||||
"tasks_in_production": "",
|
||||
"tasks_on_board": "",
|
||||
"tasks_in_view": "",
|
||||
"total_amount_in_production": "",
|
||||
"total_amount_on_board": "",
|
||||
"total_amount_in_view": "",
|
||||
"total_hours_in_production": "",
|
||||
"total_hours_on_board": "",
|
||||
"total_hours_in_view": "",
|
||||
"total_jobs_on_board": "",
|
||||
"total_jobs_in_view": "",
|
||||
"total_lab_in_production": "",
|
||||
"total_lab_on_board": "",
|
||||
"total_lab_in_view": "",
|
||||
"total_lar_in_production": "",
|
||||
"total_lar_on_board": ""
|
||||
"total_lar_on_board": "",
|
||||
"total_lar_in_view": ""
|
||||
},
|
||||
"statistics_title": ""
|
||||
},
|
||||
@@ -2869,15 +2875,21 @@
|
||||
"tasks": "",
|
||||
"tasks_in_production": "",
|
||||
"tasks_on_board": "",
|
||||
"tasks_in_view": "",
|
||||
"total_amount_in_production": "",
|
||||
"total_amount_on_board": "",
|
||||
"total_amount_in_view": "",
|
||||
"total_hours_in_production": "",
|
||||
"total_hours_on_board": "",
|
||||
"total_hours_in_view": "",
|
||||
"total_jobs_on_board": "",
|
||||
"total_jobs_in_view": "",
|
||||
"total_lab_in_production": "",
|
||||
"total_lab_on_board": "",
|
||||
"total_lab_in_view": "",
|
||||
"total_lar_in_production": "",
|
||||
"total_lar_on_board": ""
|
||||
"total_lar_on_board": "",
|
||||
"total_lar_in_view": ""
|
||||
},
|
||||
"successes": {
|
||||
"removed": ""
|
||||
@@ -3032,6 +3044,7 @@
|
||||
"production_by_target_date": "",
|
||||
"production_by_technician": "",
|
||||
"production_by_technician_one": "",
|
||||
"production_not_production_status": "",
|
||||
"production_over_time": "",
|
||||
"psr_by_make": "",
|
||||
"purchase_return_ratio_grouped_by_vendor_detail": "",
|
||||
@@ -3174,6 +3187,7 @@
|
||||
"billid": "",
|
||||
"completed": "",
|
||||
"created_at": "",
|
||||
"created_by": "",
|
||||
"description": "",
|
||||
"due_date": "",
|
||||
"job": {
|
||||
@@ -3190,6 +3204,7 @@
|
||||
"medium": ""
|
||||
},
|
||||
"priority": "",
|
||||
"related_items": "",
|
||||
"remind_at": "",
|
||||
"title": ""
|
||||
},
|
||||
|
||||
@@ -2849,15 +2849,21 @@
|
||||
"jobs_in_production": "",
|
||||
"tasks_in_production": "",
|
||||
"tasks_on_board": "",
|
||||
"tasks_in_view": "",
|
||||
"total_amount_in_production": "",
|
||||
"total_amount_on_board": "",
|
||||
"total_amount_in_view": "",
|
||||
"total_hours_in_production": "",
|
||||
"total_hours_on_board": "",
|
||||
"total_hours_in_view": "",
|
||||
"total_jobs_on_board": "",
|
||||
"total_jobs_in_view": "",
|
||||
"total_lab_in_production": "",
|
||||
"total_lab_on_board": "",
|
||||
"total_lab_in_view": "",
|
||||
"total_lar_in_production": "",
|
||||
"total_lar_on_board": ""
|
||||
"total_lar_on_board": "",
|
||||
"total_lar_in_view": ""
|
||||
},
|
||||
"statistics_title": ""
|
||||
},
|
||||
@@ -2869,15 +2875,21 @@
|
||||
"tasks": "",
|
||||
"tasks_in_production": "",
|
||||
"tasks_on_board": "",
|
||||
"tasks_in_view": "",
|
||||
"total_amount_in_production": "",
|
||||
"total_amount_on_board": "",
|
||||
"total_amount_in_view": "",
|
||||
"total_hours_in_production": "",
|
||||
"total_hours_on_board": "",
|
||||
"total_hours_in_view": "",
|
||||
"total_jobs_on_board": "",
|
||||
"total_jobs_in_view": "",
|
||||
"total_lab_in_production": "",
|
||||
"total_lab_on_board": "",
|
||||
"total_lab_in_view": "",
|
||||
"total_lar_in_production": "",
|
||||
"total_lar_on_board": ""
|
||||
"total_lar_on_board": "",
|
||||
"total_lar_in_view": ""
|
||||
},
|
||||
"successes": {
|
||||
"removed": ""
|
||||
@@ -3032,6 +3044,7 @@
|
||||
"production_by_target_date": "",
|
||||
"production_by_technician": "",
|
||||
"production_by_technician_one": "",
|
||||
"production_not_production_status": "",
|
||||
"production_over_time": "",
|
||||
"psr_by_make": "",
|
||||
"purchase_return_ratio_grouped_by_vendor_detail": "",
|
||||
@@ -3174,6 +3187,7 @@
|
||||
"billid": "",
|
||||
"completed": "",
|
||||
"created_at": "",
|
||||
"created_by": "",
|
||||
"description": "",
|
||||
"due_date": "",
|
||||
"job": {
|
||||
@@ -3190,6 +3204,7 @@
|
||||
"medium": ""
|
||||
},
|
||||
"priority": "",
|
||||
"related_items": "",
|
||||
"remind_at": "",
|
||||
"title": ""
|
||||
},
|
||||
|
||||
@@ -2333,6 +2333,14 @@ export const TemplateList = (type, context) => {
|
||||
key: "production_by_technician",
|
||||
//idtype: "vendor",
|
||||
disabled: false
|
||||
},
|
||||
production_not_production_status: {
|
||||
title: i18n.t("reportcenter.templates.production_not_production_status"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.production_not_production_status"),
|
||||
key: "production_not_production_status",
|
||||
//idtype: "vendor",
|
||||
disabled: false
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Sometimes referred to as PageSize, this variable controls the amount of records
|
||||
// to show on one page during pagination.
|
||||
export const pageLimit = 50;
|
||||
export const exportPageLimit = 10;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import "dayjs/locale/en";
|
||||
import dayjsBusinessDays from "dayjs-business-days2";
|
||||
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
|
||||
import updateLocale from "dayjs/plugin/updateLocale";
|
||||
@@ -64,4 +65,6 @@ dayjs.extend(minMax);
|
||||
dayjs.extend(isBetween);
|
||||
dayjs.extend(dayjsBusinessDays);
|
||||
|
||||
dayjs.locale("en");
|
||||
|
||||
export default dayjs;
|
||||
|
||||
@@ -2,5 +2,5 @@ import { store } from "../redux/store";
|
||||
|
||||
export function CreateExplorerLinkForJob({ jobid }) {
|
||||
const bodyshop = store.getState().user.bodyshop;
|
||||
return `imexmedia://${bodyshop.localmediaservernetwork}\\Jobs\\${jobid}`;
|
||||
return `imexmedia://`.concat(encodeURIComponent(`${bodyshop.localmediaservernetwork}\\Jobs\\${jobid}`));
|
||||
}
|
||||
|
||||
180
docker-compose.yml
Normal file
180
docker-compose.yml
Normal file
@@ -0,0 +1,180 @@
|
||||
#############################
|
||||
# Ports Exposed
|
||||
# 4000 - Imex Node API
|
||||
# 4556 - LocalStack (Local AWS)
|
||||
# 3333 - SocketIO Admin-UI (Optional)
|
||||
# 3334 - Redis-Insights (Optional)
|
||||
#############################
|
||||
|
||||
services:
|
||||
|
||||
# Redis Node 1
|
||||
redis-node-1:
|
||||
build:
|
||||
context: ./redis
|
||||
container_name: redis-node-1
|
||||
hostname: redis-node-1
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- redis-cluster-net
|
||||
volumes:
|
||||
- redis-node-1-data:/data
|
||||
- redis-lock:/redis-lock
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# Redis Node 2
|
||||
redis-node-2:
|
||||
build:
|
||||
context: ./redis
|
||||
container_name: redis-node-2
|
||||
hostname: redis-node-2
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- redis-cluster-net
|
||||
volumes:
|
||||
- redis-node-2-data:/data
|
||||
- redis-lock:/redis-lock
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# Redis Node 3
|
||||
redis-node-3:
|
||||
build:
|
||||
context: ./redis
|
||||
container_name: redis-node-3
|
||||
hostname: redis-node-3
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- redis-cluster-net
|
||||
volumes:
|
||||
- redis-node-3-data:/data
|
||||
- redis-lock:/redis-lock
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# LocalStack: Used to emulate AWS services locally, currently setup for SES
|
||||
# Notes: Set the ENV Debug to 1 for additional logging
|
||||
localstack:
|
||||
image: localstack/localstack
|
||||
container_name: localstack
|
||||
hostname: localstack
|
||||
networks:
|
||||
- redis-cluster-net
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- SERVICES=ses,secretsmanager,cloudwatch,logs
|
||||
- DEBUG=0
|
||||
- AWS_ACCESS_KEY_ID=test
|
||||
- AWS_SECRET_ACCESS_KEY=test
|
||||
- AWS_DEFAULT_REGION=ca-central-1
|
||||
- EXTRA_CORS_ALLOWED_HEADERS=Authorization,Content-Type
|
||||
- EXTRA_CORS_ALLOWED_ORIGINS=*
|
||||
- EXTRA_CORS_EXPOSE_HEADERS=Authorization,Content-Type
|
||||
ports:
|
||||
- "4566:4566"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 20s
|
||||
|
||||
# AWS-CLI - Used in conjunction with LocalStack to set required permission to send emails
|
||||
aws-cli:
|
||||
image: amazon/aws-cli
|
||||
container_name: aws-cli
|
||||
hostname: aws-cli
|
||||
networks:
|
||||
- redis-cluster-net
|
||||
depends_on:
|
||||
localstack:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- './localstack:/tmp/localstack'
|
||||
- './certs:/tmp/certs'
|
||||
|
||||
environment:
|
||||
- AWS_ACCESS_KEY_ID=test
|
||||
- AWS_SECRET_ACCESS_KEY=test
|
||||
- AWS_DEFAULT_REGION=ca-central-1
|
||||
entrypoint: /bin/sh -c
|
||||
command: >
|
||||
"
|
||||
aws --endpoint-url=http://localstack:4566 ses verify-domain-identity --domain imex.online --region ca-central-1
|
||||
aws --endpoint-url=http://localstack:4566 ses verify-email-identity --email-address noreply@imex.online --region ca-central-1
|
||||
aws --endpoint-url=http://localstack:4566 secretsmanager create-secret --name CHATTER_PRIVATE_KEY --secret-string file:///tmp/certs/id_rsa
|
||||
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
|
||||
"
|
||||
# Node App: The Main IMEX API
|
||||
node-app:
|
||||
build:
|
||||
context: .
|
||||
container_name: node-app
|
||||
hostname: imex-api
|
||||
networks:
|
||||
- redis-cluster-net
|
||||
env_file:
|
||||
- .env.development
|
||||
depends_on:
|
||||
redis-node-1:
|
||||
condition: service_healthy
|
||||
redis-node-2:
|
||||
condition: service_healthy
|
||||
redis-node-3:
|
||||
condition: service_healthy
|
||||
localstack:
|
||||
condition: service_healthy
|
||||
aws-cli:
|
||||
condition: service_completed_successfully
|
||||
ports:
|
||||
- "4000:4000"
|
||||
- "9229:9229"
|
||||
volumes:
|
||||
- .:/app
|
||||
- node-app-npm-cache:/app/node_modules
|
||||
|
||||
# ## Optional Container to Observe SocketIO data
|
||||
# socketio-admin-ui:
|
||||
# image: maitrungduc1410/socket.io-admin-ui
|
||||
# container_name: socketio-admin-ui
|
||||
# networks:
|
||||
# - redis-cluster-net
|
||||
# ports:
|
||||
# - "3333:80"
|
||||
|
||||
# ##Optional Container to Observe Redis Cluster Data
|
||||
# redis-insight:
|
||||
# image: redislabs/redisinsight:latest
|
||||
# container_name: redis-insight
|
||||
# hostname: redis-insight
|
||||
# restart: unless-stopped
|
||||
# ports:
|
||||
# - "3334:5540"
|
||||
# networks:
|
||||
# - redis-cluster-net
|
||||
# volumes:
|
||||
# - redis-insight-data:/db
|
||||
|
||||
networks:
|
||||
redis-cluster-net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
node-app-npm-cache:
|
||||
redis-node-1-data:
|
||||
redis-node-2-data:
|
||||
redis-node-3-data:
|
||||
redis-lock:
|
||||
redis-insight-data:
|
||||
@@ -69,7 +69,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
jobline:
|
||||
job:
|
||||
@@ -180,7 +179,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -387,7 +385,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -504,7 +501,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bill:
|
||||
job:
|
||||
@@ -671,7 +667,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
_and:
|
||||
- job:
|
||||
@@ -1285,7 +1280,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
courtesycar:
|
||||
bodyshop:
|
||||
@@ -1526,7 +1520,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -1786,7 +1779,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -1920,7 +1912,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
_or:
|
||||
- job:
|
||||
@@ -2105,7 +2096,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
employee_team:
|
||||
bodyshop:
|
||||
@@ -2268,7 +2258,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
employee:
|
||||
bodyshop:
|
||||
@@ -2449,7 +2438,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -2696,7 +2684,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -2808,7 +2795,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
conversation:
|
||||
bodyshop:
|
||||
@@ -3123,7 +3109,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
@@ -4232,7 +4217,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -4248,41 +4232,41 @@
|
||||
enable_manual: false
|
||||
update:
|
||||
columns:
|
||||
- clm_no
|
||||
- v_make_desc
|
||||
- date_next_contact
|
||||
- status
|
||||
- employee_csr
|
||||
- employee_prep
|
||||
- clm_total
|
||||
- suspended
|
||||
- employee_body
|
||||
- ro_number
|
||||
- actual_in
|
||||
- ownr_co_nm
|
||||
- v_model_yr
|
||||
- comment
|
||||
- job_totals
|
||||
- v_vin
|
||||
- ownr_fn
|
||||
- scheduled_completion
|
||||
- special_coverage_policy
|
||||
- v_color
|
||||
- ca_gst_registrant
|
||||
- scheduled_delivery
|
||||
- actual_delivery
|
||||
- actual_completion
|
||||
- kanbanparent
|
||||
- est_ct_fn
|
||||
- alt_transport
|
||||
- v_model_desc
|
||||
- clm_no
|
||||
- v_make_desc
|
||||
- date_next_contact
|
||||
- status
|
||||
- employee_csr
|
||||
- actual_in
|
||||
- v_model_yr
|
||||
- comment
|
||||
- job_totals
|
||||
- ownr_fn
|
||||
- v_color
|
||||
- ca_gst_registrant
|
||||
- employee_refinish
|
||||
- ownr_ph1
|
||||
- date_last_contacted
|
||||
- alt_transport
|
||||
- inproduction
|
||||
- est_ct_ln
|
||||
- production_vars
|
||||
- category
|
||||
- v_model_desc
|
||||
- date_invoiced
|
||||
- est_co_nm
|
||||
- ownr_ln
|
||||
@@ -4295,6 +4279,12 @@
|
||||
- name: event-secret
|
||||
value_from_env: EVENT_SECRET
|
||||
request_transform:
|
||||
body:
|
||||
action: transform
|
||||
template: |-
|
||||
{
|
||||
"data": {{$body?.event?.data?.new}}
|
||||
}
|
||||
method: POST
|
||||
query_params: {}
|
||||
template_engine: Kriti
|
||||
@@ -4496,7 +4486,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
conversation:
|
||||
bodyshop:
|
||||
@@ -4670,7 +4659,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
@@ -4805,7 +4793,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -5110,7 +5097,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
parts_order:
|
||||
job:
|
||||
@@ -5243,7 +5229,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
@@ -5419,7 +5404,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
@@ -5559,7 +5543,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -5670,7 +5653,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
_or:
|
||||
- parentjob_rel:
|
||||
@@ -5760,7 +5742,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
@@ -6045,7 +6026,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -6541,7 +6521,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -6698,7 +6677,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
|
||||
35
nodemon.json
Normal file
35
nodemon.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"watch": [
|
||||
"server.js",
|
||||
"server"
|
||||
],
|
||||
"ignore": [
|
||||
"client",
|
||||
".circleci",
|
||||
".platform",
|
||||
".idea",
|
||||
".vscode",
|
||||
"_reference",
|
||||
"firebase",
|
||||
"hasura",
|
||||
"logs",
|
||||
"redis",
|
||||
".dockerignore",
|
||||
".ebignore",
|
||||
".editorconfig",
|
||||
".env.development",
|
||||
".env.development.rome",
|
||||
".eslintrc.json",
|
||||
".gitignore",
|
||||
".npmmrc",
|
||||
".prettierrc.js",
|
||||
"bodyshop_translations.babel",
|
||||
"Dockerfile",
|
||||
"ecosystem.config.js",
|
||||
"job-totals-testing-util.js",
|
||||
"nodemon.json",
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"setadmin.js"
|
||||
]
|
||||
}
|
||||
2052
package-lock.json
generated
2052
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@@ -19,9 +19,11 @@
|
||||
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.654.0",
|
||||
"@aws-sdk/client-ses": "^3.654.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.654.0",
|
||||
"@aws-sdk/client-cloudwatch-logs": "^3.679.0",
|
||||
"@aws-sdk/client-elasticache": "^3.675.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.675.0",
|
||||
"@aws-sdk/client-ses": "^3.675.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.675.0",
|
||||
"@opensearch-project/opensearch": "^2.12.0",
|
||||
"@socket.io/admin-ui": "^0.5.1",
|
||||
"@socket.io/redis-adapter": "^8.3.0",
|
||||
@@ -31,39 +33,41 @@
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.20.3",
|
||||
"canvas": "^2.11.2",
|
||||
"chart.js": "^4.4.4",
|
||||
"cloudinary": "^2.5.0",
|
||||
"chart.js": "^4.4.5",
|
||||
"cloudinary": "^2.5.1",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "2.8.5",
|
||||
"csrf": "^3.1.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.21.0",
|
||||
"firebase-admin": "^12.5.0",
|
||||
"express": "^4.21.1",
|
||||
"firebase-admin": "^12.6.0",
|
||||
"graphql": "^16.9.0",
|
||||
"graphql-request": "^6.1.0",
|
||||
"graylog2": "^0.2.1",
|
||||
"inline-css": "^4.0.2",
|
||||
"intuit-oauth": "^4.1.2",
|
||||
"json-2-csv": "^5.5.5",
|
||||
"ioredis": "^5.4.1",
|
||||
"json-2-csv": "^5.5.6",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"moment-timezone": "^0.5.46",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"node-mailjet": "^6.0.6",
|
||||
"node-persist": "^4.0.3",
|
||||
"nodemailer": "^6.9.15",
|
||||
"phone": "^3.1.50",
|
||||
"phone": "^3.1.51",
|
||||
"recursive-diff": "^1.0.9",
|
||||
"redis": "^4.7.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"soap": "^1.1.4",
|
||||
"soap": "^1.1.5",
|
||||
"socket.io": "^4.8.0",
|
||||
"socket.io-adapter": "^2.5.5",
|
||||
"ssh2-sftp-client": "^10.0.3",
|
||||
"twilio": "^4.23.0",
|
||||
"uuid": "^10.0.0",
|
||||
"winston": "^3.15.0",
|
||||
"winston-cloudwatch": "^6.3.0",
|
||||
"xml2js": "^0.6.2",
|
||||
"xmlbuilder2": "^3.1.1"
|
||||
},
|
||||
|
||||
20
redis/Dockerfile
Normal file
20
redis/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Use the official Redis image
|
||||
FROM redis:7.0-alpine
|
||||
|
||||
# Copy the Redis configuration file
|
||||
COPY redis.conf /usr/local/etc/redis/redis.conf
|
||||
|
||||
# Copy the entrypoint script
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
|
||||
# Make the entrypoint script executable
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
# Debugging step: List contents of /usr/local/bin
|
||||
RUN ls -l /usr/local/bin
|
||||
|
||||
# Expose Redis ports
|
||||
EXPOSE 6379 16379
|
||||
|
||||
# Set the entrypoint
|
||||
ENTRYPOINT ["entrypoint.sh"]
|
||||
36
redis/entrypoint.sh
Normal file
36
redis/entrypoint.sh
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
|
||||
LOCKFILE="/redis-lock/redis-cluster-init.lock"
|
||||
|
||||
# Start Redis server in the background
|
||||
redis-server /usr/local/etc/redis/redis.conf &
|
||||
|
||||
# Wait for Redis server to start
|
||||
sleep 5
|
||||
|
||||
# Only initialize the cluster if the lock file doesn't exist
|
||||
if [ ! -f "$LOCKFILE" ]; then
|
||||
echo "Initializing Redis Cluster..."
|
||||
|
||||
# Create lock file to prevent further initialization attempts
|
||||
touch "$LOCKFILE"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Lock file created successfully at $LOCKFILE."
|
||||
else
|
||||
echo "Failed to create lock file."
|
||||
fi
|
||||
|
||||
# Initialize the Redis cluster
|
||||
yes yes | redis-cli --cluster create \
|
||||
redis-node-1:6379 \
|
||||
redis-node-2:6379 \
|
||||
redis-node-3:6379 \
|
||||
--cluster-replicas 0
|
||||
|
||||
echo "Redis Cluster initialized."
|
||||
else
|
||||
echo "Cluster already initialized, skipping initialization."
|
||||
fi
|
||||
|
||||
# Keep the container running
|
||||
tail -f /dev/null
|
||||
6
redis/redis.conf
Normal file
6
redis/redis.conf
Normal file
@@ -0,0 +1,6 @@
|
||||
bind 0.0.0.0
|
||||
port 6379
|
||||
cluster-enabled yes
|
||||
cluster-config-file nodes.conf
|
||||
cluster-node-timeout 5000
|
||||
appendonly yes
|
||||
203
server.js
203
server.js
@@ -1,26 +1,32 @@
|
||||
const express = require("express");
|
||||
const cors = require("cors");
|
||||
const bodyParser = require("body-parser");
|
||||
// Load environment variables THIS MUST BE AT THE TOP
|
||||
const path = require("path");
|
||||
const compression = require("compression");
|
||||
const cookieParser = require("cookie-parser");
|
||||
const http = require("http");
|
||||
const { Server } = require("socket.io");
|
||||
const { createClient } = require("redis");
|
||||
const { createAdapter } = require("@socket.io/redis-adapter");
|
||||
const logger = require("./server/utils/logger");
|
||||
const { redisSocketEvents } = require("./server/web-sockets/redisSocketEvents");
|
||||
const { instrument } = require("@socket.io/admin-ui");
|
||||
|
||||
const { isString, isEmpty } = require("lodash");
|
||||
const applyRedisHelpers = require("./server/utils/redisHelpers");
|
||||
const applyIOHelpers = require("./server/utils/ioHelpers");
|
||||
|
||||
// Load environment variables
|
||||
require("dotenv").config({
|
||||
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
||||
});
|
||||
|
||||
const cors = require("cors");
|
||||
const http = require("http");
|
||||
const Redis = require("ioredis");
|
||||
const express = require("express");
|
||||
const bodyParser = require("body-parser");
|
||||
const compression = require("compression");
|
||||
const cookieParser = require("cookie-parser");
|
||||
const { Server } = require("socket.io");
|
||||
const { createAdapter } = require("@socket.io/redis-adapter");
|
||||
const { instrument } = require("@socket.io/admin-ui");
|
||||
const { isString, isEmpty } = require("lodash");
|
||||
|
||||
const logger = require("./server/utils/logger");
|
||||
const { applyRedisHelpers } = require("./server/utils/redisHelpers");
|
||||
const { applyIOHelpers } = require("./server/utils/ioHelpers");
|
||||
const { redisSocketEvents } = require("./server/web-sockets/redisSocketEvents");
|
||||
const { ElastiCacheClient, DescribeCacheClustersCommand } = require("@aws-sdk/client-elasticache");
|
||||
const { default: InstanceManager } = require("./server/utils/instanceMgr");
|
||||
|
||||
const CLUSTER_RETRY_BASE_DELAY = 100;
|
||||
const CLUSTER_RETRY_MAX_DELAY = 5000;
|
||||
const CLUSTER_RETRY_JITTER = 100;
|
||||
|
||||
/**
|
||||
* CORS Origin for Socket.IO
|
||||
* @type {string[][]}
|
||||
@@ -49,16 +55,16 @@ const SOCKETIO_CORS_ORIGIN = [
|
||||
"https://old.imex.online",
|
||||
"https://www.old.imex.online",
|
||||
"https://wsadmin.imex.online",
|
||||
"https://www.wsadmin.imex.online",
|
||||
"http://localhost:3333",
|
||||
"https://localhost:3333"
|
||||
"https://www.wsadmin.imex.online"
|
||||
];
|
||||
|
||||
const SOCKETIO_CORS_ORIGIN_DEV = ["http://localhost:3333", "https://localhost:3333"];
|
||||
|
||||
/**
|
||||
* Middleware for Express app
|
||||
* @param app
|
||||
*/
|
||||
const applyMiddleware = (app) => {
|
||||
const applyMiddleware = ({ app }) => {
|
||||
app.use(compression());
|
||||
app.use(cookieParser());
|
||||
app.use(bodyParser.json({ limit: "50mb" }));
|
||||
@@ -76,7 +82,7 @@ const applyMiddleware = (app) => {
|
||||
* Route groupings for Express app
|
||||
* @param app
|
||||
*/
|
||||
const applyRoutes = (app) => {
|
||||
const applyRoutes = ({ app }) => {
|
||||
app.use("/", require("./server/routes/miscellaneousRoutes"));
|
||||
app.use("/notifications", require("./server/routes/notificationsRoutes"));
|
||||
app.use("/render", require("./server/routes/renderRoutes"));
|
||||
@@ -102,37 +108,130 @@ const applyRoutes = (app) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch Redis nodes from AWS ElastiCache
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
const getRedisNodesFromAWS = async () => {
|
||||
const client = new ElastiCacheClient({
|
||||
region: InstanceManager({
|
||||
imex: "ca-central-1",
|
||||
rome: "us-east-2"
|
||||
})
|
||||
});
|
||||
|
||||
const params = {
|
||||
ReplicationGroupId: process.env.REDIS_CLUSTER_ID,
|
||||
ShowCacheNodeInfo: true
|
||||
};
|
||||
|
||||
try {
|
||||
// Fetch the cache clusters associated with the replication group
|
||||
const command = new DescribeCacheClustersCommand(params);
|
||||
const response = await client.send(command);
|
||||
const cacheClusters = response.CacheClusters;
|
||||
|
||||
return cacheClusters.flatMap((cluster) =>
|
||||
cluster.CacheNodes.map((node) => `${node.Endpoint.Address}:${node.Endpoint.Port}`)
|
||||
);
|
||||
} catch (err) {
|
||||
logger.log(`Error fetching Redis nodes from AWS: ${err.message}`, "ERROR", "redis", "api");
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to Redis Cluster
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
const connectToRedisCluster = async () => {
|
||||
let redisServers;
|
||||
|
||||
if (isString(process.env?.REDIS_CLUSTER_ID) && !isEmpty(process.env?.REDIS_CLUSTER_ID)) {
|
||||
// Fetch Redis nodes from AWS if AWS environment variables are present
|
||||
redisServers = await getRedisNodesFromAWS();
|
||||
} else {
|
||||
// Use the Dockerized Redis cluster in development
|
||||
if (isEmpty(process.env?.REDIS_URL) || !isString(process.env?.REDIS_URL)) {
|
||||
logger.log(`No or Malformed REDIS_URL present.`, "ERROR", "redis", "api");
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
redisServers = JSON.parse(process.env.REDIS_URL);
|
||||
} catch (error) {
|
||||
logger.log(`Failed to parse REDIS_URL: ${error.message}. Exiting...`, "ERROR", "redis", "api");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const clusterRetryStrategy = (times) => {
|
||||
const delay =
|
||||
Math.min(CLUSTER_RETRY_BASE_DELAY + times * 50, CLUSTER_RETRY_MAX_DELAY) + Math.random() * CLUSTER_RETRY_JITTER;
|
||||
logger.log(`Redis cluster not yet ready. Retrying in ${delay.toFixed(2)}ms`, "WARN", "redis", "api");
|
||||
return delay;
|
||||
};
|
||||
|
||||
const redisCluster = new Redis.Cluster(redisServers, {
|
||||
clusterRetryStrategy,
|
||||
enableAutoPipelining: true,
|
||||
enableReadyCheck: true,
|
||||
redisOptions: {
|
||||
// connectTimeout: 10000, // Timeout for connecting in ms
|
||||
// idleTimeoutMillis: 30000, // Close idle connections after 30s
|
||||
// maxRetriesPerRequest: 5 // Retry a maximum of 5 times per request
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
redisCluster.on("ready", () => {
|
||||
logger.log(`Redis cluster connection established.`, "INFO", "redis", "api");
|
||||
resolve(redisCluster);
|
||||
});
|
||||
|
||||
redisCluster.on("error", (err) => {
|
||||
logger.log(`Redis cluster connection failed: ${err.message}`, "ERROR", "redis", "api");
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply Redis to the server
|
||||
* @param server
|
||||
* @param app
|
||||
*/
|
||||
const applySocketIO = async (server, app) => {
|
||||
// Redis client setup for Pub/Sub and Key-Value Store
|
||||
const pubClient = createClient({ url: process.env.REDIS_URL || "redis://localhost:6379" });
|
||||
const applySocketIO = async ({ server, app }) => {
|
||||
const redisCluster = await connectToRedisCluster();
|
||||
|
||||
// Handle errors
|
||||
redisCluster.on("error", (err) => {
|
||||
logger.log(`Redis ERROR`, "ERROR", "redis", "api");
|
||||
});
|
||||
|
||||
const pubClient = redisCluster;
|
||||
const subClient = pubClient.duplicate();
|
||||
|
||||
pubClient.on("error", (err) => logger.log(`Redis pubClient error: ${err}`, "ERROR", "redis"));
|
||||
subClient.on("error", (err) => logger.log(`Redis subClient error: ${err}`, "ERROR", "redis"));
|
||||
|
||||
try {
|
||||
await Promise.all([pubClient.connect(), subClient.connect()]);
|
||||
logger.log(`[${process.env.NODE_ENV}] Connected to Redis`, "INFO", "redis", "api");
|
||||
} catch (redisError) {
|
||||
logger.log("Failed to connect to Redis", "ERROR", "redis", redisError);
|
||||
}
|
||||
|
||||
process.on("SIGINT", async () => {
|
||||
logger.log("Closing Redis connections...", "INFO", "redis", "api");
|
||||
try {
|
||||
await Promise.all([pubClient.disconnect(), subClient.disconnect()]);
|
||||
process.exit(0);
|
||||
logger.log("Redis connections closed. Process will exit.", "INFO", "redis", "api");
|
||||
} catch (error) {
|
||||
logger.log(`Error closing Redis connections: ${error.message}`, "ERROR", "redis", "api");
|
||||
}
|
||||
});
|
||||
|
||||
const ioRedis = new Server(server, {
|
||||
path: "/wss",
|
||||
adapter: createAdapter(pubClient, subClient),
|
||||
cors: {
|
||||
origin: SOCKETIO_CORS_ORIGIN,
|
||||
origin:
|
||||
process.env?.NODE_ENV === "development"
|
||||
? [...SOCKETIO_CORS_ORIGIN, ...SOCKETIO_CORS_ORIGIN_DEV]
|
||||
: SOCKETIO_CORS_ORIGIN,
|
||||
methods: ["GET", "POST"],
|
||||
credentials: true,
|
||||
exposedHeaders: ["set-cookie"]
|
||||
@@ -140,13 +239,14 @@ const applySocketIO = async (server, app) => {
|
||||
});
|
||||
|
||||
if (isString(process.env.REDIS_ADMIN_PASS) && !isEmpty(process.env.REDIS_ADMIN_PASS)) {
|
||||
logger.log(`[${process.env.NODE_ENV}] Initializing Redis Admin UI....`, "INFO", "redis", "api");
|
||||
logger.log(`Initializing Redis Admin UI....`, "INFO", "redis", "api");
|
||||
instrument(ioRedis, {
|
||||
auth: {
|
||||
type: "basic",
|
||||
username: "admin",
|
||||
password: process.env.REDIS_ADMIN_PASS
|
||||
},
|
||||
|
||||
mode: process.env.REDIS_ADMIN_MODE || "development"
|
||||
});
|
||||
}
|
||||
@@ -161,19 +261,22 @@ const applySocketIO = async (server, app) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.use((req, res, next) => {
|
||||
Object.assign(req, {
|
||||
const api = {
|
||||
pubClient,
|
||||
io,
|
||||
ioRedis
|
||||
});
|
||||
ioRedis,
|
||||
redisCluster
|
||||
};
|
||||
|
||||
app.use((req, res, next) => {
|
||||
Object.assign(req, api);
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
Object.assign(module.exports, { io, pubClient, ioRedis });
|
||||
Object.assign(module.exports, api);
|
||||
|
||||
return { pubClient, io, ioRedis };
|
||||
return api;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -186,22 +289,22 @@ const main = async () => {
|
||||
|
||||
const server = http.createServer(app);
|
||||
|
||||
const { pubClient, ioRedis } = await applySocketIO(server, app);
|
||||
const api = applyRedisHelpers(pubClient, app, logger);
|
||||
const ioHelpers = applyIOHelpers(app, api, ioRedis, logger);
|
||||
const { pubClient, ioRedis } = await applySocketIO({ server, app });
|
||||
const redisHelpers = applyRedisHelpers({ pubClient, app, logger });
|
||||
const ioHelpers = applyIOHelpers({ app, redisHelpers, ioRedis, logger });
|
||||
|
||||
// Legacy Socket Events
|
||||
require("./server/web-sockets/web-socket");
|
||||
|
||||
applyMiddleware(app);
|
||||
applyRoutes(app);
|
||||
redisSocketEvents(ioRedis, api, ioHelpers);
|
||||
applyMiddleware({ app });
|
||||
applyRoutes({ app });
|
||||
redisSocketEvents({ io: ioRedis, redisHelpers, ioHelpers, logger });
|
||||
|
||||
try {
|
||||
await server.listen(port);
|
||||
logger.log(`[${process.env.NODE_ENV}] Server started on port ${port}`, "INFO", "api");
|
||||
logger.log(`Server started on port ${port}`, "INFO", "api");
|
||||
} catch (error) {
|
||||
logger.log(`[${process.env.NODE_ENV}] Server failed to start on port ${port}`, "ERROR", "api", error);
|
||||
logger.log(`Server failed to start on port ${port}`, "ERROR", "api", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ axios.interceptors.request.use((x) => {
|
||||
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
|
||||
x.url
|
||||
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
|
||||
console.log(printable);
|
||||
//console.log(printable);
|
||||
|
||||
CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
|
||||
CdkBase.createJsonEvent(socket, "SILLY", `Raw Request: ${printable}`, x.data);
|
||||
|
||||
return x;
|
||||
});
|
||||
@@ -35,8 +35,8 @@ axios.interceptors.response.use((x) => {
|
||||
const socket = x.config.socket;
|
||||
|
||||
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
|
||||
console.log(printable);
|
||||
CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
|
||||
//console.log(printable);
|
||||
CdkBase.createJsonEvent(socket, "SILLY", `Raw Response: ${printable}`, x.data);
|
||||
|
||||
return x;
|
||||
});
|
||||
@@ -181,7 +181,7 @@ async function QueryBillData(socket, billids) {
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.request(queries.GET_PBS_AP_ALLOCATIONS, { billids: billids });
|
||||
CdkBase.createLogEvent(socket, "TRACE", `Bill data query result ${JSON.stringify(result, null, 2)}`);
|
||||
CdkBase.createLogEvent(socket, "SILLY", `Bill data query result ${JSON.stringify(result, null, 2)}`);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ axios.interceptors.request.use((x) => {
|
||||
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
|
||||
x.url
|
||||
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
|
||||
console.log(printable);
|
||||
//console.log(printable);
|
||||
|
||||
CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
|
||||
CdkBase.createJsonEvent(socket, "SILLY", `Raw Request: ${printable}`, x.data);
|
||||
|
||||
return x;
|
||||
});
|
||||
@@ -37,8 +37,8 @@ axios.interceptors.response.use((x) => {
|
||||
const socket = x.config.socket;
|
||||
|
||||
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
|
||||
console.log(printable);
|
||||
CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
|
||||
//console.log(printable);
|
||||
CdkBase.createJsonEvent(socket, "SILLY", `Raw Response: ${printable}`, x.data);
|
||||
|
||||
return x;
|
||||
});
|
||||
@@ -118,7 +118,7 @@ async function CheckForErrors(socket, response) {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Successful response from DMS. ${response.Message || ""}`);
|
||||
} else {
|
||||
CdkBase.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`);
|
||||
CdkBase.createLogEvent(socket, "TRACE", `Error received from DMS: ${JSON.stringify(response)}`);
|
||||
CdkBase.createLogEvent(socket, "SILLY", `Error received from DMS: ${JSON.stringify(response)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ async function QueryJobData(socket, jobid) {
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.request(queries.QUERY_JOBS_FOR_PBS_EXPORT, { id: jobid });
|
||||
CdkBase.createLogEvent(socket, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
CdkBase.createLogEvent(socket, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
return result.jobs_by_pk;
|
||||
}
|
||||
|
||||
@@ -611,7 +611,7 @@ async function InsertFailedExportLog(socket, error) {
|
||||
bodyshopid: socket.JobData.bodyshop.id,
|
||||
jobid: socket.JobData.id,
|
||||
successful: false,
|
||||
message: [error],
|
||||
message: JSON.stringify(error),
|
||||
useremail: socket.user.email
|
||||
}
|
||||
});
|
||||
|
||||
@@ -63,7 +63,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
|
||||
);
|
||||
|
||||
if (!account) {
|
||||
logger.log("qbxml-receivables-no-account", "warn", null, jobline.id, null);
|
||||
logger.log("qbxml-receivables-no-account", "warn", null, jobline.id);
|
||||
throw new Error(
|
||||
`A matching account does not exist for the part allocation. Center: ${jobline.profitcenter_part}`
|
||||
);
|
||||
@@ -207,7 +207,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
|
||||
});
|
||||
|
||||
if (!hasMapaLine && jobs_by_pk.job_totals.rates.mapa.total.amount > 0) {
|
||||
// console.log("Adding MAPA Line Manually.");
|
||||
// //console.log("Adding MAPA Line Manually.");
|
||||
const mapaAccountName = responsibilityCenters.defaults.profits.MAPA;
|
||||
|
||||
const mapaAccount = responsibilityCenters.profits.find((c) => c.name === mapaAccountName);
|
||||
@@ -272,12 +272,12 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
|
||||
});
|
||||
}
|
||||
} else {
|
||||
//console.log("NO MAPA ACCOUNT FOUND!!");
|
||||
////console.log("NO MAPA ACCOUNT FOUND!!");
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasMashLine && jobs_by_pk.job_totals.rates.mash.total.amount > 0) {
|
||||
// console.log("Adding MASH Line Manually.");
|
||||
// //console.log("Adding MASH Line Manually.");
|
||||
|
||||
const mashAccountName = responsibilityCenters.defaults.profits.MASH;
|
||||
|
||||
@@ -341,7 +341,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// console.log("NO MASH ACCOUNT FOUND!!");
|
||||
// //console.log("NO MASH ACCOUNT FOUND!!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,7 +795,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
|
||||
: taxCodes[taxAccountCode];
|
||||
for (let tyCounter = 1; tyCounter <= 5; tyCounter++) {
|
||||
const taxAmount = Dinero(job_totals.totals.us_sales_tax_breakdown[`ty${tyCounter}Tax`]);
|
||||
console.log(`Tax ${tyCounter}`, taxAmount.toFormat());
|
||||
//console.log(`Tax ${tyCounter}`, taxAmount.toFormat());
|
||||
if (taxAmount.getAmount() > 0) {
|
||||
if (qbo) {
|
||||
InvoiceLineAdd.push({
|
||||
|
||||
@@ -45,7 +45,7 @@ exports.default = async (req, res) => {
|
||||
const BearerToken = req.BearerToken;
|
||||
const client = req.userGraphQLClient;
|
||||
|
||||
logger.log("qbo-payable-create", "DEBUG", req.user.email, billsToQuery);
|
||||
logger.log("qbo-payable-create", "DEBUG", req.user.email, null, { billsToQuery });
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: BearerToken })
|
||||
@@ -91,6 +91,12 @@ exports.default = async (req, res) => {
|
||||
|
||||
ret.push({ billid: bill.id, success: true });
|
||||
} catch (error) {
|
||||
logger.log("qbo-paybles-create-error", "ERROR", req.user.email, null, {
|
||||
error:
|
||||
(error && error.authResponse && error.authResponse.body) ||
|
||||
error.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") ||
|
||||
(error && error.message)
|
||||
});
|
||||
ret.push({
|
||||
billid: bill.id,
|
||||
success: false,
|
||||
@@ -121,8 +127,8 @@ exports.default = async (req, res) => {
|
||||
|
||||
res.status(200).json(ret);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
logger.log("qbo-payable-create-error", "ERROR", req.user.email, {
|
||||
//console.log(error);
|
||||
logger.log("qbo-payable-create-error", "ERROR", req.user.email, null, {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
@@ -49,7 +49,7 @@ exports.default = async (req, res) => {
|
||||
const BearerToken = req.BearerToken;
|
||||
const client = req.userGraphQLClient;
|
||||
|
||||
logger.log("qbo-payment-create", "DEBUG", req.user.email, paymentsToQuery);
|
||||
logger.log("qbo-payment-create", "DEBUG", req.user.email, null, { paymentsToQuery });
|
||||
|
||||
const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
|
||||
payments: paymentsToQuery
|
||||
@@ -152,7 +152,7 @@ exports.default = async (req, res) => {
|
||||
|
||||
ret.push({ paymentid: payment.id, success: true });
|
||||
} catch (error) {
|
||||
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
|
||||
logger.log("qbo-payment-create-error", "ERROR", req.user.email, null, {
|
||||
error: (error && error.authResponse && error.authResponse.body) || (error && error.message)
|
||||
});
|
||||
//Add the export log error.
|
||||
@@ -182,8 +182,8 @@ exports.default = async (req, res) => {
|
||||
|
||||
res.status(200).json(ret);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
|
||||
//console.log(error);
|
||||
logger.log("qbo-payment-create-error", "ERROR", req.user.email, null, {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
@@ -185,7 +185,7 @@ exports.default = async (req, res) => {
|
||||
error?.response?.data ||
|
||||
error?.message
|
||||
});
|
||||
console.log(error);
|
||||
//console.log(error);
|
||||
logger.log("qbo-receivable-create-error", "ERROR", req.user.email, {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
@@ -211,7 +211,7 @@ exports.default = async (req, res) => {
|
||||
|
||||
res.status(200).json(ret);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
//console.log(error);
|
||||
logger.log("qbo-receivable-create-error", "ERROR", req.user.email, {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
|
||||
@@ -42,9 +42,10 @@ exports.default = async (req, res) => {
|
||||
//For each invoice.
|
||||
res.status(200).json(QbXmlToExecute);
|
||||
} catch (error) {
|
||||
logger.log("qbxml-payable-error", "ERROR", req.user.email, req.body.billsToQuery, {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
logger.log("qbxml-payable-error", "ERROR", req?.user?.email, null, {
|
||||
billsToQuery: req?.body?.billsToQuery,
|
||||
error: error?.message,
|
||||
stack: error?.stack
|
||||
});
|
||||
res.status(400).send(JSON.stringify(error));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ exports.default = async (req, res) => {
|
||||
const client = req.userGraphQLClient;
|
||||
|
||||
try {
|
||||
logger.log("qbxml-payments-create", "DEBUG", req.user.email, req.body.paymentsToQuery, null);
|
||||
logger.log("qbxml-payments-create", "DEBUG", req?.user?.email, null, {
|
||||
paymentsToQuery: req.body?.paymentsToQuery
|
||||
});
|
||||
|
||||
const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
|
||||
payments: paymentsToQuery
|
||||
@@ -62,7 +64,8 @@ exports.default = async (req, res) => {
|
||||
|
||||
res.status(200).json(QbXmlToExecute);
|
||||
} catch (error) {
|
||||
logger.log("qbxml-payments-error", "error", req.user.email, req.body.paymentsToQuery, {
|
||||
logger.log("qbxml-payments-error", "error", req?.user?.email, null, {
|
||||
paymentsToQuery: req.body?.paymentsToQuery,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
@@ -23,7 +23,9 @@ exports.default = async (req, res) => {
|
||||
const client = req.userGraphQLClient;
|
||||
|
||||
try {
|
||||
logger.log("qbxml-receivables-create", "DEBUG", req.user.email, req.body.jobIds, null);
|
||||
logger.log("qbxml-receivables-create", "DEBUG", req?.user?.email, null, {
|
||||
jobIds: req?.body?.jobIds
|
||||
});
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: BearerToken })
|
||||
@@ -74,7 +76,8 @@ exports.default = async (req, res) => {
|
||||
|
||||
res.status(200).json(QbXmlToExecute);
|
||||
} catch (error) {
|
||||
logger.log("qbxml-receivables-error", "error", req.user.email, req.body.jobIds, {
|
||||
logger.log("qbxml-receivables-error", "error", req?.user?.email, null, {
|
||||
jobIds: req.body?.jobIds,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ require("dotenv").config({
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
|
||||
exports.createAssociation = async (req, res) => {
|
||||
logger.log("admin-create-association", "ADMIN", req.user.email, null, {
|
||||
logger.log("admin-create-association", "debug", req.user.email, null, {
|
||||
request: req.body,
|
||||
ioadmin: true
|
||||
});
|
||||
@@ -31,7 +31,7 @@ exports.createAssociation = async (req, res) => {
|
||||
res.json(result);
|
||||
};
|
||||
exports.createShop = async (req, res) => {
|
||||
logger.log("admin-create-shop", "ADMIN", req.user.email, null, {
|
||||
logger.log("admin-create-shop", "debug", req.user.email, null, {
|
||||
request: req.body,
|
||||
ioadmin: true
|
||||
});
|
||||
@@ -64,7 +64,7 @@ exports.createShop = async (req, res) => {
|
||||
}
|
||||
};
|
||||
exports.updateCounter = async (req, res) => {
|
||||
logger.log("admin-update-counter", "ADMIN", req.user.email, null, {
|
||||
logger.log("admin-update-counter", "debug", req.user.email, null, {
|
||||
request: req.body,
|
||||
ioadmin: true
|
||||
});
|
||||
@@ -91,7 +91,7 @@ exports.updateCounter = async (req, res) => {
|
||||
}
|
||||
};
|
||||
exports.updateShop = async (req, res) => {
|
||||
logger.log("admin-update-shop", "ADMIN", req.user.email, null, {
|
||||
logger.log("admin-update-shop", "debug", req.user.email, null, {
|
||||
request: req.body,
|
||||
ioadmin: true
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user