Compare commits

..

1 Commits

Author SHA1 Message Date
Patrick Fic
92103df2a9 IO-2348 add http auth for media. 2023-07-11 15:46:58 -07:00
342 changed files with 16731 additions and 52155 deletions

View File

@@ -8,13 +8,13 @@ orbs:
jobs:
api-deploy:
docker:
- image: cimg/node:18.18.2
- image: "cimg/base:stable"
steps:
- checkout
- eb/setup
- run:
command: |
eb init imex-online-production-api -r ca-central-1 -p "Node.js 18 running on 64bit Amazon Linux 2"
eb init imex-online-production-api -r ca-central-1 -p "Node.js 16 running on 64bit Amazon Linux 2"
eb status --verbose
eb deploy
eb status
@@ -68,120 +68,6 @@ jobs:
from: build
to: "s3://imex-online-production/"
- jira/notify
rome-api-deploy:
docker:
- image: "cimg/base:stable"
steps:
- checkout
- eb/setup
- run:
command: |
eb init romeonline-productionapi -r us-east-2 -p "Node.js 18 running on 64bit Amazon Linux 2"
eb status --verbose
eb deploy
eb status
- jira/notify
rome-hasura-migrate:
docker:
- image: cimg/node:16.15.0
parameters:
secret:
type: string
default: $HASURA_PROD_SECRET
working_directory: ~/repo/hasura
steps:
- checkout:
path: ~/repo
- run:
name: Execute migration
command: |
npm install hasura-cli -g
hasura migrate apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
rome-app-build:
docker:
- image: cimg/node:16.15.0
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-{{ checksum "yarn.lock" }}
- run:
name: Install Dependencies
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
- save_cache:
name: Save Yarn Package Cache
key: yarn-packages-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
- run: yarn run build
- aws-s3/sync:
from: build
to: "s3://rome-online-production/"
- jira/notify
test-rome-hasura-migrate:
docker:
- image: cimg/node:16.15.0
parameters:
secret:
type: string
default: $HASURA_ROME_TEST_SECRET
working_directory: ~/repo/hasura
steps:
- checkout:
path: ~/repo
- run:
name: Execute migration
command: |
npm install hasura-cli -g
echo ${HASURA_TEST_SECRET}
hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
test-rome-app-build:
docker:
- image: cimg/node:16.15.0
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-{{ checksum "yarn.lock" }}
- run:
name: Install Dependencies
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
- save_cache:
name: Save Yarn Package Cache
key: yarn-packages-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
- run: yarn run build:test
- aws-s3/sync:
from: build
to: "s3://rome-online-test/"
- jira/notify
test-hasura-migrate:
docker:
@@ -279,19 +165,6 @@ workflows:
filters:
branches:
only: master
- rome-api-deploy:
filters:
branches:
only: rome/master
- rome-app-build:
filters:
branches:
only: rome/master
- rome-hasura-migrate:
secret: ${HASURA_PROD_SECRET}
filters:
branches:
only: rome/master
- test-app-build:
filters:
branches:
@@ -301,15 +174,6 @@ workflows:
filters:
branches:
only: test
- test-rome-app-build:
filters:
branches:
only: rome/test
- test-rome-hasura-migrate:
secret: ${HASURA_ROME_TEST_SECRET}
filters:
branches:
only: rome/test
#- admin-app-build:
#filters:
#branches:

4
.gitignore vendored
View File

@@ -117,6 +117,4 @@ logs/oAuthClient-log.log
.node-persist/**
/*.env.*
.idea/*
.idea
/*.env.*

1
.npmrc
View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,13 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
REACT_APP_GA_CODE=231099835
REACT_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
REACT_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
REACT_APP_CLOUDINARY_API_KEY=957865933348715
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=http://localhost:4000
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
REACT_APP_COUNTRY=USA
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc

View File

@@ -1,13 +1,14 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.romeonline.io/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.romeonline.io/v1/graphql
GENERATE_SOURCEMAP=false
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
REACT_APP_GA_CODE=231103507
REACT_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
REACT_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU","authDomain":"imex-prod.firebaseapp.com","databaseURL":"https://imex-prod.firebaseio.com","projectId":"imex-prod","storageBucket":"imex-prod.appspot.com","messagingSenderId":"253497221485","appId":"1:253497221485:web:3c81c483b94db84b227a64","measurementId":"G-NTWBKG2L0M"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
REACT_APP_CLOUDINARY_API_KEY=473322739956866
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BMgZT1NZztW2DsJl8Mg2L04hgY9FzAg6b8fbzgNAfww2VDzH3VE63Ot9EaP_U7KWS2JT-7HPHaw0T_Tw_5vkZc8'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/
REACT_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
REACT_APP_AXIOS_BASE_API_URL=https://api.imex.online/
REACT_APP_REPORTS_SERVER_URL=https://reports.imex.online
REACT_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk

View File

@@ -1,14 +1,14 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.romeonline.io/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.romeonline.io/v1/graphql
REACT_APP_GA_CODE=231103507
REACT_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.bodyshop.app/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.bodyshop.app/v1/graphql
REACT_APP_GA_CODE=231099835
REACT_APP_FIREBASE_CONFIG={ "apiKey":"AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", "authDomain":"imex-test.firebaseapp.com", "projectId":"imex-test", "storageBucket":"imex-test.appspot.com", "messagingSenderId":"991923618608", "appId":"1:991923618608:web:633437569cdad78299bef5", "measurementId":"G-TW0XLZEH18"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
REACT_APP_CLOUDINARY_API_KEY=473322739956866
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.test.romeonline.io/
REACT_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
REACT_APP_AXIOS_BASE_API_URL=https://api.test.imex.online/
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_IS_TEST=true
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc

View File

@@ -12,7 +12,7 @@ module.exports = {
authToken:
"6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
org: "snapt-software",
project: "rome-online",
project: "imexonline",
release: process.env.REACT_APP_GIT_SHA,
// webpack-specific configuration
@@ -27,7 +27,7 @@ module.exports = {
lessOptions: {
modifyVars: {
...(process.env.NODE_ENV === "development"
? { "@primary-color": "#B22234" }
? { "@primary-color": "#a51d1d" }
: {
//"@primary-color": "#1DA57A"
}),

24522
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
"@apollo/client": "^3.7.9",
"@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^7.0.0",
"@fingerprintjs/fingerprintjs": "^3.4.2",
"@fingerprintjs/fingerprintjs": "^3.3.3",
"@jsreport/browser-client": "^3.1.0",
"@sentry/react": "^7.40.0",
"@sentry/tracing": "^7.40.0",
@@ -89,8 +89,8 @@
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "craco start",
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
"build:test": "env-cmd -f .env.test npm run build",
"build-deploy:test": "npm run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
"build:test": "env-cmd -f .env.test yarn run build",
"build-deploy:test": "yarn run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
"buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
"test": "cypress open",
"eject": "react-scripts eject",

View File

@@ -28,17 +28,6 @@ switch (this.location.hostname) {
// measurementId: "${config.measurementId}",
};
break;
case "romeonline.io":
firebaseConfig = {
apiKey: "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE",
authDomain: "rome-prod-1.firebaseapp.com",
projectId: "rome-prod-1",
storageBucket: "rome-prod-1.appspot.com",
messagingSenderId: "147786367145",
appId: "1:147786367145:web:9d4cba68071c3f29a8a9b8",
measurementId: "G-G8Z9DRHTZS",
};
break;
case "imex.online":
default:
firebaseConfig = {

View File

@@ -2,81 +2,23 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/ro-favicon.png" />
<link rel="icon" href="%PUBLIC_URL%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#002366" />
<meta name="description" content="Rome Online" />
<meta name="description" content="ImEX Online" />
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
<!--Use the below code snippet to provide real time updates to the live chat plugin without the need of copying and paste each time to your website when changes are made via PBX-->
<call-us-selector phonesystem-url=https://rometech.east.3cx.us:5001 party="LiveChat528346"></call-us-selector>
<!--Incase you don't want real time updates to the live chat plugin when options are changed, use the below code snippet. Please note that each time you change the settings you will need to copy and paste the snippet code to your website-->
<!--<call-us
phonesystem-url=https://rometech.east.3cx.us:5001
style="position:fixed;font-size:16px;line-height:17px;z-index: 99999;right: 20px; bottom: 20px;"
id="wp-live-chat-by-3CX"
minimized="true"
animation-style="noanimation"
party="LiveChat528346"
minimized-style="bubbleright"
allow-call="true"
allow-video="false"
allow-soundnotifications="true"
enable-mute="true"
enable-onmobile="true"
offline-enabled="true"
enable="true"
ignore-queueownership="false"
authentication="both"
show-operator-actual-name="true"
aknowledge-received="true"
gdpr-enabled="false"
message-userinfo-format="name"
message-dateformat="both"
lang="browser"
button-icon-type="default"
greeting-visibility="none"
greeting-offline-visibility="none"
chat-delay="2000"
enable-direct-call="true"
enable-ga="false"
></call-us>-->
<script defer src=https://downloads-global.3cx.com/downloads/livechatandtalk/v1/callus.js id="tcx-callus-js" charset="utf-8"></script>
<link rel="apple-touch-icon" href="logo192.png" />
<script type="text/javascript">
window.$crisp = [];
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
(function () {
d = document;
s = d.createElement("script");
s.src = "https://client.crisp.chat/l.js";
s.async = 1;
d.getElementsByTagName("head")[0].appendChild(s);
})();
</script>
<script>
!(function () {
"use strict";
@@ -135,7 +77,7 @@ enable-ga="false"
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Rome Online</title>
<title>ImEX Online</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -1,6 +1,6 @@
{
"short_name": "Rome Online",
"name": "Rome Online",
"short_name": "ImEX Online",
"name": "ImEX Online",
"description": "The ultimate bodyshop management system.",
"icons": [
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 B

View File

@@ -27,12 +27,6 @@ export default function AppContainer() {
//componentSize="small"
input={{ autoComplete: "new-password" }}
locale={enLocale}
theme={{
token: {
colorPrimary: "#326ade",
colorInfo: "#326ade"
},
}}
form={{
validateMessages: {
// eslint-disable-next-line no-template-curly-in-string

View File

@@ -77,9 +77,9 @@ export function App({
if (currentUser.authorized && bodyshop) {
client.setAttribute("imexshopid", bodyshop.imexshopid);
LogRocket.init("rome-online/rome-online");
if (client.getTreatment("LogRocket_Tracking") === "on") {
LogRocket.init("rome-online/rome-online");
console.log("LR Start");
LogRocket.init("gvfvfw/bodyshopapp");
}
}
}, [bodyshop, client, currentUser.authorized]);
@@ -109,7 +109,7 @@ export function App({
return (
<Switch>
<Suspense fallback={<LoadingSpinner />}>
<Suspense fallback={<LoadingSpinner message="ImEX Online" />}>
<ErrorBoundary>
<Route exact path="/" component={LandingPage} />
</ErrorBoundary>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -0,0 +1,49 @@
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { setEmailOptions } from "../../redux/email/email.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
});
function Test({ bodyshop, setEmailOptions }) {
return (
<div>
<button
onClick={() => {
setEmailOptions({
messageOptions: {
to: ["patrickwf@gmail.com"],
replyTo: bodyshop.email,
},
template: {
name: TemplateList().parts_order.key,
variables: {
id: "a7c2d4e1-f519-42a9-a071-c48cf0f22979",
},
},
});
}}
>
send email
</button>
<button
onClick={() => {
logImEXEvent("IMEXEVENT", { somethignArThare: 5 });
}}
>
Log an ImEX Event.
</button>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Test);

View File

@@ -1,63 +0,0 @@
{
"status": 24201299,
"custid": 19607899,
"paymentid": 24201299,
"response": "A",
"authcode": "498680",
"declinereason": "Approved",
"fee": 0,
"invoice": "",
"account": "john",
"amount": 1000,
"amountincludesfee": false,
"total": 1000,
"paymenttype": "C",
"methodhint": "VI ***1111",
"cardbrand": "Visa",
"cardnumdisplay": "***1111",
"receiptelements": {
"authcode": "498680",
"cust_srv_ph_num": "1-555-555-5555",
"rcpt_pg_ftr_txt": "Thank You\nPlease Come Again",
"rcpt_currency": "USD",
"responsecode": "A",
"rcpt_pay_mthd": "Visa",
"transid": "C00 915799",
"merch_disp_nm": "CP Devel Test",
"rcpt_input_mthd": "Keyed",
"rcpt_pg_hdr_txt": "Welcome!",
"rcpt_tran_time": "Thursday February 23 2023, 11:25:36 pm +08",
"rcpt_trans_type": "Normal Transaction (Sale)",
"message": "Approved",
"rcpt_dba_addr": "1234 Storefront Ave\nSome City, UT 84111",
"avsdata": "N",
"receiptrequirements": "S",
"rcpt_cardnum": "************1111",
"cv2result": "M",
"rfnd_policy_txt": "<b>No Refunds</b>\nStore Credit Only",
"labels": {
"tranref": "REF#",
"tid": "TID",
"validationcode": "ValCode",
"emvapplicationid": "AID",
"emvatc": "ATC",
"rcpt_pay_mthd": "Pay Method",
"transid": "TransID",
"rcpt_input_mthd": "IMode",
"emvtsi": "TSI",
"emvac": "AC",
"rcpt_trans_type": "TranType",
"emvapplicationname": "PApp",
"visarewards": "RewardsProg"
}
},
"receipttoken": "H4sIAAAAAAAAACXMTQuCMBgA4P/ynh3tw_3dBI/ipQ8NOtRN53QiblpBRfTfCzo/8LwhxGAdZCCwFYoJJFQjI2kvHdGu74lVkgmrWyWNhASW5jW7cB87yHjKKePGJODnxrrnMl7dDTKmEJlSOqV/_N30XPpyj2Eddq57_KKZ8FLzmh_G1VQnVfhjiXGK1XYTc/h8AVOkf4qUAAAA",
"call": "card_payment",
"nonce": "488b5568-b5c1-4f38-8b2f-3b050f3abb11P",
"hmac": "JyPAJ9Yx0SlYBTtqns1OxAFRt+xF3l2UiLPO5zTDRBE=",
"paymentreferenceid": "C19607899P24201299",
"cardnum": "...1111",
"email": "",
"nameOnCard": "John Allen",
"cardType": "visa"
}

View File

@@ -0,0 +1,9 @@
import React from "react";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
export default function Test() {
return (
<div>
<QboAuthorizeComponent />
</div>
);
}

View File

@@ -1,31 +0,0 @@
import { Button } from "antd";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setRefundPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "refund_payment" })),
});
function Test({ setRefundPaymentContext, refundPaymentModal }) {
console.log("refundPaymentModal", refundPaymentModal);
return (
<div>
<Button
onClick={() =>
setRefundPaymentContext({
context: {},
})
}
>
Open Modal
</Button>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Test);

View File

@@ -15,7 +15,6 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -211,7 +210,7 @@ export function AccountingPayablesTableComponent({
<Table
loading={loading}
dataSource={dataSource}
pagination={{ position: "top", pageSize: pageLimit }}
pagination={{ position: "top", pageSize: 50 }}
columns={columns}
rowKey="id"
onChange={handleTableChange}

View File

@@ -15,7 +15,6 @@ import PaymentExportButton from "../payment-export-button/payment-export-button.
import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component";
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -210,7 +209,7 @@ export function AccountingPayablesTableComponent({
<Table
loading={loading}
dataSource={dataSource}
pagination={{ position: "top", pageSize: pageLimit }}
pagination={{ position: "top", pageSize: 50 }}
columns={columns}
rowKey="id"
onChange={handleTableChange}

View File

@@ -4,7 +4,6 @@ import { alphaSort } from "../../utils/sorters";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { useTranslation } from "react-i18next";
import AuditTrailValuesComponent from "../audit-trail-values/audit-trail-values.component";
import {pageLimit} from "../../utils/config";
export default function AuditTrailListComponent({ loading, data }) {
const [state, setState] = useState({
@@ -75,7 +74,7 @@ export default function AuditTrailListComponent({ loading, data }) {
<Table
{...formItemLayout}
loading={loading}
pagination={{ position: "top", defaultPageSize: pageLimit }}
pagination={{ position: "top", defaultPageSize: 25 }}
columns={columns}
rowKey="id"
dataSource={data}

View File

@@ -3,7 +3,6 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import {pageLimit} from "../../utils/config";
export default function EmailAuditTrailListComponent({ loading, data }) {
const [state, setState] = useState({
@@ -54,7 +53,7 @@ export default function EmailAuditTrailListComponent({ loading, data }) {
<Table
{...formItemLayout}
loading={loading}
pagination={{ position: "top", defaultPageSize: pageLimit }}
pagination={{ position: "top", defaultPageSize: 25 }}
columns={columns}
rowKey="id"
dataSource={data}

View File

@@ -1,5 +1,4 @@
import { useApolloClient, useMutation } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Button, Checkbox, Form, Modal, Space, notification } from "antd";
import _ from "lodash";
import React, { useEffect, useMemo, useState } from "react";
@@ -38,8 +37,8 @@ const mapStateToProps = createStructuredSelector({
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
insertAuditTrail: ({ jobid, billid, operation }) =>
dispatch(insertAuditTrail({ jobid, billid, operation })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
const Templates = TemplateList("job_special");
@@ -64,19 +63,9 @@ function BillEnterModalContainer({
"enter_bill_generate_label",
false
);
const { Enhanced_Payroll } = useTreatments(
["Enhanced_Payroll"],
{},
bodyshop.imexshopid
);
const formValues = useMemo(() => {
return {
...billEnterModal.context.bill,
//Added as a part of IO-2436 for capturing parts price changes.
billlines: billEnterModal.context?.bill?.billlines?.map((line) => ({
...line,
original_actual_price: line.actual_price,
})),
jobid:
(billEnterModal.context.job && billEnterModal.context.job.id) || null,
federal_tax_rate:
@@ -109,7 +98,6 @@ function BillEnterModalContainer({
} = values;
let adjustmentsToInsert = {};
let payrollAdjustmentsToInsert = [];
const r1 = await insertBill({
variables: {
@@ -125,33 +113,14 @@ function BillEnterModalContainer({
lbr_adjustment,
location: lineLocation,
part_type,
create_ppc,
original_actual_price,
...restI
} = i;
if (Enhanced_Payroll.treatment === "on") {
if (
deductedfromlbr &&
true //payroll is on
) {
payrollAdjustmentsToInsert.push({
id: i.joblineid,
convertedtolbr: true,
convertedtolbr_data: {
mod_lb_hrs: lbr_adjustment.mod_lb_hrs * -1,
mod_lbr_ty: lbr_adjustment.mod_lbr_ty,
},
});
}
} else {
if (deductedfromlbr) {
adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] =
(adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) -
restI.actual_price / lbr_adjustment.rate;
}
if (deductedfromlbr) {
adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] =
(adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) -
restI.actual_price / lbr_adjustment.rate;
}
return {
...restI,
deductedfromlbr: deductedfromlbr,
@@ -174,23 +143,9 @@ function BillEnterModalContainer({
},
],
},
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"],
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
});
await Promise.all(
payrollAdjustmentsToInsert.map((li) => {
return updateJobLines({
variables: {
lineId: li.id,
line: {
convertedtolbr: li.convertedtolbr,
convertedtolbr_data: li.convertedtolbr_data,
},
},
});
})
);
const adjKeys = Object.keys(adjustmentsToInsert);
if (adjKeys.length > 0) {
//Query the adjustments, merge, and update them.
@@ -299,14 +254,6 @@ function BillEnterModalContainer({
location: li.location || location,
status:
bodyshop.md_order_statuses.default_received || "Received*",
//Added parts price changes.
...(li.create_ppc &&
li.original_actual_price !== li.actual_price
? {
act_price_before_ppc: li.original_actual_price,
act_price: li.actual_price,
}
: {}),
},
},
});
@@ -369,18 +316,16 @@ function BillEnterModalContainer({
insertAuditTrail({
jobid: values.jobid,
billid: billId,
operation: AuditTrailMapping.billposted(
r1.data.insert_bills.returning[0].invoice_number
),
operation: AuditTrailMapping.billposted(remainingValues.invoice_number),
});
if (enterAgain) {
// form.resetFields();
form.resetFields();
form.resetFields();
form.setFieldsValue({
...formValues,
billlines: [],
});
form.resetFields();
} else {
toggleModalVisible();
}
@@ -455,9 +400,6 @@ function BillEnterModalContainer({
setEnterAgain(false);
}}
>
<button onClick={() => console.log(form.getFieldsValue("billlines"))}>
get billlines
</button>
<BillFormContainer
form={form}
disableInvNumber={billEnterModal.context.disableInvNumber}

View File

@@ -1,6 +1,6 @@
import Icon, { UploadOutlined } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import { MdOpenInNew } from "react-icons/md";
import {
Alert,
Divider,
@@ -12,17 +12,14 @@ import {
Switch,
Upload,
} from "antd";
import moment from "moment";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { MdOpenInNew } from "react-icons/md";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
@@ -31,6 +28,8 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import BillFormLines from "./bill-form.lines.component";
import { CalculateBillTotal } from "./bill-form.totals.utility";
import { useTreatments } from "@splitsoftware/splitio-react";
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -50,7 +49,6 @@ export function BillFormComponent({
job,
loadOutstandingReturns,
loadInventory,
preferredMake,
}) {
const { t } = useTranslation();
const client = useApolloClient();
@@ -60,11 +58,6 @@ export function BillFormComponent({
{},
bodyshop.imexshopid
);
const { ClosingPeriod } = useTreatments(
["ClosingPeriod"],
{},
bodyshop.imexshopid
);
const handleVendorSelect = (props, opt) => {
setDiscount(opt.discount);
@@ -186,7 +179,6 @@ export function BillFormComponent({
<VendorSearchSelect
disabled={disabled}
options={vendorAutoCompleteOptions}
preferredMake={preferredMake}
onSelect={handleVendorSelect}
/>
</Form.Item>
@@ -267,37 +259,6 @@ export function BillFormComponent({
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
validator(rule, value) {
if (
ClosingPeriod.treatment === "on" &&
bodyshop.accountingconfig.ClosingPeriod
) {
if (
moment(value)
.startOf("day")
.isSameOrAfter(
moment(
bodyshop.accountingconfig.ClosingPeriod[0]
).startOf("day")
) &&
moment(value)
.startOf("day")
.isSameOrBefore(
moment(
bodyshop.accountingconfig.ClosingPeriod[1]
).endOf("day")
)
) {
return Promise.resolve();
} else {
return Promise.reject(t("bills.validation.closingperiod"));
}
} else {
return Promise.resolve();
}
},
}),
]}
>
<FormDatePicker disabled={disabled} />
@@ -366,15 +327,13 @@ export function BillFormComponent({
)}
</LayoutFormRow>
<LayoutFormRow>
{
// <Form.Item
// span={3}
// label={t("bills.fields.federal_tax_rate")}
// name="federal_tax_rate"
// >
// <CurrencyInput min={0} disabled={disabled} />
// </Form.Item>
}
<Form.Item
span={3}
label={t("bills.fields.federal_tax_rate")}
name="federal_tax_rate"
>
<CurrencyInput min={0} disabled={disabled} />
</Form.Item>
<Form.Item
span={3}
label={t("bills.fields.state_tax_rate")}
@@ -382,15 +341,13 @@ export function BillFormComponent({
>
<CurrencyInput min={0} disabled={disabled} />
</Form.Item>
{
// <Form.Item
// span={3}
// label={t("bills.fields.local_tax_rate")}
// name="local_tax_rate"
// >
// <CurrencyInput min={0} />
// </Form.Item>
}
<Form.Item
span={3}
label={t("bills.fields.local_tax_rate")}
name="local_tax_rate"
>
<CurrencyInput min={0} />
</Form.Item>
<Form.Item shouldUpdate span={15}>
{() => {
const values = form.getFieldsValue([
@@ -416,25 +373,21 @@ export function BillFormComponent({
value={totals.subtotal.toFormat()}
precision={2}
/>
{
// <Statistic
// title={t("bills.labels.federal_tax")}
// value={totals.federalTax.toFormat()}
// precision={2}
// />
}
<Statistic
title={t("bills.labels.federal_tax")}
value={totals.federalTax.toFormat()}
precision={2}
/>
<Statistic
title={t("bills.labels.state_tax")}
value={totals.stateTax.toFormat()}
precision={2}
/>
{
// <Statistic
// title={t("bills.labels.local_tax")}
// value={totals.localTax.toFormat()}
// precision={2}
// />
}
<Statistic
title={t("bills.labels.local_tax")}
value={totals.localTax.toFormat()}
precision={2}
/>
<Statistic
title={t("bills.labels.entered_total")}
value={totals.enteredTotal.toFormat()}

View File

@@ -1,16 +1,16 @@
import { useLazyQuery, useQuery } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
import { GET_JOB_LINES_TO_ENTER_BILL } from "../../graphql/jobs-lines.queries";
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component";
import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
import BillFormComponent from "./bill-form.component";
import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component";
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -59,7 +59,6 @@ export function BillFormContainer({
disableInvNumber={disableInvNumber}
loadOutstandingReturns={loadOutstandingReturns}
loadInventory={loadInventory}
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
/>
{!billEdit && (
<BillCmdReturnsTableComponent

View File

@@ -1,16 +1,14 @@
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Button,
Checkbox,
Form,
Button, Form,
Input,
InputNumber,
Select,
Space,
Switch,
Table,
Tooltip,
Tooltip
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -47,13 +45,6 @@ export function BillEnterModalLinesComponent({
{},
bodyshop && bodyshop.imexshopid
);
const { Enhanced_Payroll } = useTreatments(
["Enhanced_Payroll"],
{},
bodyshop.imexshopid
);
const columns = (remove) => {
return [
{
@@ -102,7 +93,6 @@ export function BillEnterModalLinesComponent({
line_desc: opt.line_desc,
quantity: opt.part_qty || 1,
actual_price: opt.cost,
original_actual_price: opt.cost,
cost_center: opt.part_type
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
? opt.part_type !== "PAE"
@@ -229,43 +219,6 @@ export function BillEnterModalLinesComponent({
}}
/>
),
additional: (record, index) => (
<Form.Item
dependencies={["billlines", record.name, "actual_price"]}
noStyle
>
{() => {
const billLine = getFieldValue(["billlines", record.name]);
const jobLine = lineData.find(
(line) => line.id === billLine?.joblineid
);
if (
!billEdit &&
billLine &&
jobLine &&
billLine?.actual_price !== jobLine?.act_price
) {
return (
<Space size="small">
<Form.Item
noStyle
label={t("joblines.fields.create_ppc")}
key={`${index}ppc`}
valuePropName="checked"
name={[record.name, "create_ppc"]}
>
<Checkbox />
</Form.Item>
{t("joblines.fields.create_ppc")}
</Space>
);
} else {
return null;
}
}}
</Form.Item>
),
},
{
title: t("billlines.fields.actual_cost"),
@@ -407,7 +360,7 @@ export function BillEnterModalLinesComponent({
},
formInput: (record, index) => <Switch disabled={disabled} />,
additional: (record, index) => (
<Form.Item shouldUpdate noStyle style={{ display: "inline-block" }}>
<Form.Item shouldUpdate style={{ display: "inline-block" }}>
{() => {
const price = getFieldValue([
"billlines",
@@ -422,31 +375,12 @@ export function BillEnterModalLinesComponent({
"rate",
]);
const billline = getFieldValue(["billlines", record.name]);
const jobline = lineData.find(
(line) => line.id === billline?.joblineid
);
const employeeTeamName = bodyshop.employee_teams.find(
(team) => team.id === jobline?.assigned_team
);
if (getFieldValue(["billlines", record.name, "deductedfromlbr"]))
return (
<div>
<Space>
{t("joblines.fields.assigned_team", {
name: employeeTeamName?.name,
})}
{`${jobline.mod_lb_hrs} units/${t(
`joblines.fields.lbr_types.${jobline.mod_lbr_ty}`
)}`}
</Space>
<Form.Item
label={t("joblines.fields.mod_lbr_ty")}
key={`${index}modlbrty`}
initialValue={jobline ? jobline.mod_lbr_ty : null}
rules={[
{
required: true,
@@ -500,44 +434,22 @@ export function BillEnterModalLinesComponent({
</Select.Option>
</Select>
</Form.Item>
{Enhanced_Payroll.treatment === "on" ? (
<Form.Item
label={t("billlines.labels.mod_lbr_adjustment")}
name={[record.name, "lbr_adjustment", "mod_lb_hrs"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber
precision={5}
min={0.01}
max={jobline ? jobline.mod_lb_hrs : 0}
/>
</Form.Item>
) : (
<Form.Item
label={t("jobs.labels.adjustmentrate")}
name={[record.name, "lbr_adjustment", "rate"]}
initialValue={bodyshop.default_adjustment_rate}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber precision={2} min={0.01} />
</Form.Item>
)}
<Space>
{price &&
adjustmentRate &&
`${(price / adjustmentRate).toFixed(1)} hrs`}
</Space>
<Form.Item
label={t("jobs.labels.adjustmentrate")}
name={[record.name, "lbr_adjustment", "rate"]}
initialValue={bodyshop.default_adjustment_rate}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber precision={2} min={0.01} />
</Form.Item>
{price &&
adjustmentRate &&
`${(price / adjustmentRate).toFixed(1)} hrs`}
</div>
);
return <></>;
@@ -545,21 +457,21 @@ export function BillEnterModalLinesComponent({
</Form.Item>
),
},
// {
// title: t("billlines.fields.federal_tax_applicable"),
// dataIndex: "applicable_taxes.federal",
// editable: true,
{
title: t("billlines.fields.federal_tax_applicable"),
dataIndex: "applicable_taxes.federal",
editable: true,
// formItemProps: (field) => {
// return {
// key: `${field.index}fedtax`,
// valuePropName: "checked",
// // initialValue: true,
// name: [field.name, "applicable_taxes", "federal"],
// };
// },
// formInput: (record, index) => <Switch disabled={disabled} />,
// },
formItemProps: (field) => {
return {
key: `${field.index}fedtax`,
valuePropName: "checked",
initialValue: true,
name: [field.name, "applicable_taxes", "federal"],
};
},
formInput: (record, index) => <Switch disabled={disabled} />,
},
{
title: t("billlines.fields.state_tax_applicable"),
dataIndex: "applicable_taxes.state",
@@ -574,20 +486,20 @@ export function BillEnterModalLinesComponent({
},
formInput: (record, index) => <Switch disabled={disabled} />,
},
// {
// title: t("billlines.fields.local_tax_applicable"),
// dataIndex: "applicable_taxes.local",
// editable: true,
{
title: t("billlines.fields.local_tax_applicable"),
dataIndex: "applicable_taxes.local",
editable: true,
// formItemProps: (field) => {
// return {
// key: `${field.index}localtax`,
// valuePropName: "checked",
// name: [field.name, "applicable_taxes", "local"],
// };
// },
// formInput: (record, index) => <Switch disabled={disabled} />,
// },
formItemProps: (field) => {
return {
key: `${field.index}localtax`,
valuePropName: "checked",
name: [field.name, "applicable_taxes", "local"],
};
},
formInput: (record, index) => <Switch disabled={disabled} />,
},
{
title: t("general.labels.actions"),
@@ -712,7 +624,7 @@ const EditableCell = ({
if (additional)
return (
<td {...restProps}>
<div size="small">
<Space size="small">
<Form.Item
name={dataIndex}
labelCol={{ span: 0 }}
@@ -721,7 +633,7 @@ const EditableCell = ({
{(formInput && formInput(record, record.name)) || children}
</Form.Item>
{additional && additional(record, record.name)}
</div>
</Space>
</td>
);
if (wrapper)

View File

@@ -19,14 +19,14 @@ export const CalculateBillTotal = (invoice) => {
}).multiply(i.quantity || 1);
subtotal = subtotal.add(itemTotal);
if (i.applicable_taxes?.federal) {
if (i.applicable_taxes.federal) {
federalTax = federalTax.add(
itemTotal.percentage(federal_tax_rate || 0)
);
}
if (i.applicable_taxes?.state)
if (i.applicable_taxes.state)
stateTax = stateTax.add(itemTotal.percentage(state_tax_rate || 0));
if (i.applicable_taxes?.local)
if (i.applicable_taxes.local)
localTax = localTax.add(itemTotal.percentage(local_tax_rate || 0));
}
});

View File

@@ -63,12 +63,6 @@ const BillLineSearchSelect = (
item.oem_partno ? ` - ${item.oem_partno}` : ""
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
</span>
{item.act_price === 0 && item.mod_lb_hrs > 0 && (
<span style={{ float: "right", paddingleft: "1rem" }}>
{`${item.mod_lb_hrs} units`}
</span>
)}
<span style={{ float: "right", paddingleft: "1rem" }}>
{item.act_price
? `$${item.act_price && item.act_price.toFixed(2)}`

View File

@@ -1,374 +0,0 @@
import { DeleteFilled } from "@ant-design/icons";
import { useLazyQuery, useMutation } from "@apollo/client";
import {
Button,
Card,
Col,
Form,
Input,
Row,
Space,
Spin,
Statistic,
notification,
} from "antd";
import axios from "axios";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
INSERT_PAYMENT_RESPONSE,
QUERY_RO_AND_OWNER_BY_JOB_PKS,
} from "../../graphql/payment_response.queries";
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectCardPayment } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
const mapStateToProps = createStructuredSelector({
cardPaymentModal: selectCardPayment,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
});
const CardPaymentModalComponent = ({
bodyshop,
cardPaymentModal,
toggleModalVisible,
insertAuditTrail,
}) => {
const { context } = cardPaymentModal;
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
const { t } = useTranslation();
const [, { data, refetch, queryLoading }] = useLazyQuery(
QUERY_RO_AND_OWNER_BY_JOB_PKS,
{
variables: { jobids: [context.jobid] },
skip: true,
}
);
console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data);
//Initialize the intellipay window.
const SetIntellipayCallbackFunctions = () => {
console.log("*** Set IntelliPay callback functions.");
window.intellipay.runOnClose(() => {
//window.intellipay.initialize();
});
window.intellipay.runOnApproval(async function (response) {
console.warn("*** Running On Approval Script ***");
form.setFieldValue("paymentResponse", response);
form.submit();
});
window.intellipay.runOnNonApproval(async function (response) {
// Mutate unsuccessful payment
const { payments } = form.getFieldsValue();
await insertPaymentResponse({
variables: {
paymentResponse: payments.map((payment) => ({
amount: payment.amount,
bodyshopid: bodyshop.id,
jobid: payment.jobid,
declinereason: response.declinereason,
ext_paymentid: response.paymentid.toString(),
successful: false,
response,
})),
},
});
payments.forEach((payment) =>
insertAuditTrail({
jobid: payment.jobid,
operation: AuditTrailMapping.failedpayment(),
})
);
});
};
const handleFinish = async (values) => {
try {
await insertPayment({
variables: {
paymentInput: values.payments.map((payment) => ({
amount: payment.amount,
transactionid: (values.paymentResponse.paymentid || "").toString(),
payer: t("payments.labels.customer"),
type: values.paymentResponse.cardbrand,
jobid: payment.jobid,
date: moment(Date.now()),
payment_responses: {
data: [
{
amount: payment.amount,
bodyshopid: bodyshop.id,
jobid: payment.jobid,
declinereason: values.paymentResponse.declinereason,
ext_paymentid: values.paymentResponse.paymentid.toString(),
successful: true,
response: values.paymentResponse,
},
],
},
})),
},
refetchQueries: ["GET_JOB_BY_PK"],
});
toggleModalVisible();
} catch (error) {
console.error(error);
notification.open({
type: "error",
message: t("payments.errors.inserting", { error: error.message }),
});
} finally {
setLoading(false);
}
};
const handleIntelliPayCharge = async () => {
setLoading(true);
//Validate
try {
await form.validateFields();
} catch (error) {
setLoading(false);
return;
}
try {
const response = await axios.post("/intellipay/lightbox_credentials", {
bodyshop,
refresh: !!window.intellipay,
});
if (window.intellipay) {
// eslint-disable-next-line no-eval
eval(response.data);
SetIntellipayCallbackFunctions();
window.intellipay.autoOpen();
} else {
var rg = document.createRange();
let node = rg.createContextualFragment(response.data);
document.documentElement.appendChild(node);
SetIntellipayCallbackFunctions();
window.intellipay.isAutoOpen = true;
window.intellipay.initialize();
}
} catch (error) {
notification.open({
type: "error",
message: t("job_payments.notifications.error.openingip"),
});
setLoading(false);
}
};
return (
<Card title="Card Payment">
<Spin spinning={loading}>
<Form
onFinish={handleFinish}
form={form}
layout="vertical"
initialValues={{
payments: context.jobid ? [{ jobid: context.jobid }] : [],
}}
>
<Form.List name={["payments"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<Row gutter={[16, 16]}>
<Col span={16}>
<Form.Item
key={`${index}jobid`}
label={t("jobs.fields.ro_number")}
name={[field.name, "jobid"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<JobSearchSelectComponent
notExported={false}
clm_no
/>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
key={`${index}amount`}
label={t("payments.fields.amount")}
name={[field.name, "amount"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<CurrencyFormItemComponent />
</Form.Item>
</Col>
<Col span={2}>
<DeleteFilled
style={{ margin: "1rem" }}
onClick={() => {
remove(field.name);
}}
/>
</Col>
</Row>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("general.actions.add")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item
shouldUpdate={(prevValues, curValues) =>
prevValues.payments?.map((p) => p?.jobid).join() !==
curValues.payments?.map((p) => p?.jobid).join()
}
>
{() => {
console.log("Updating the owner info section.");
//If all of the job ids have been fileld in, then query and update the IP field.
const { payments } = form.getFieldsValue();
if (
payments?.length > 0 &&
payments?.filter((p) => p?.jobid).length === payments?.length
) {
console.log("**Calling refetch.");
refetch({ jobids: payments.map((p) => p.jobid) });
}
console.log(
"Acc info",
data,
payments && data && data.jobs.length > 0
? data.jobs.map((j) => j.ro_number).join(", ")
: null
);
return (
<>
<Input
className="ipayfield"
data-ipayname="account"
//type="hidden"
value={
payments && data && data.jobs.length > 0
? data.jobs.map((j) => j.ro_number).join(", ")
: null
}
hidden
/>
<Input
className="ipayfield"
data-ipayname="email"
// type="hidden"
value={
payments && data && data.jobs.length > 0
? data.jobs.filter((j) => j.ownr_ea)[0]?.ownr_ea
: null
}
hidden
/>
</>
);
}}
</Form.Item>
<Form.Item
shouldUpdate={(prevValues, curValues) =>
prevValues.payments?.map((p) => p?.amount).join() !==
curValues.payments?.map((p) => p?.amount).join()
}
>
{() => {
const { payments } = form.getFieldsValue();
const totalAmountToCharge = payments?.reduce((acc, val) => {
return acc + (val?.amount || 0);
}, 0);
return (
<Space style={{ float: "right" }}>
<Statistic
title="Amount To Charge"
value={totalAmountToCharge}
precision={2}
/>
<Input
className="ipayfield"
data-ipayname="amount"
//type="hidden"
value={totalAmountToCharge?.toFixed(2)}
hidden
/>
<Button
type="primary"
// data-ipayname="submit"
className="ipayfield"
loading={queryLoading || loading}
disabled={!(totalAmountToCharge > 0)}
onClick={handleIntelliPayCharge}
>
{t("job_payments.buttons.proceedtopayment")}
</Button>
</Space>
);
}}
</Form.Item>
{/* Lightbox payment response when it is completed */}
<Form.Item name="paymentResponse" hidden>
<Input type="hidden" />
</Form.Item>
</Form>
</Spin>
</Card>
);
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(CardPaymentModalComponent);

View File

@@ -1,57 +0,0 @@
import { Button, Modal } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectCardPayment } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CardPaymentModalComponent from "./card-payment-modal.component.";
const mapStateToProps = createStructuredSelector({
cardPaymentModal: selectCardPayment,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
});
function CardPaymentModalContainer({
cardPaymentModal,
toggleModalVisible,
bodyshop,
}) {
const { visible } = cardPaymentModal;
const { t } = useTranslation();
const handleCancel = () => {
toggleModalVisible();
};
const handleOK = () => {
toggleModalVisible();
};
return (
<Modal
open={visible}
onOk={handleOK}
onCancel={handleCancel}
footer={[
<Button key="back" onClick={handleCancel}>
{t("job_payments.buttons.goback")}
</Button>,
]}
width="80%"
destroyOnClose
>
<CardPaymentModalComponent />
</Modal>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(CardPaymentModalContainer);

View File

@@ -1,5 +1,5 @@
import Icon from "@ant-design/icons";
import { Tooltip } from "antd";
import { Spin, Tooltip } from "antd";
import i18n from "i18next";
import moment from "moment";
import React, { useEffect, useRef } from "react";
@@ -12,6 +12,8 @@ import {
} from "react-virtualized";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import "./chat-message-list.styles.scss";
import { useState } from "react";
import axios from "axios";
export default function ChatMessageListComponent({ messages }) {
const virtualizedListRef = useRef(null);
@@ -95,9 +97,7 @@ const MessageRender = (message) => {
key={idx}
style={{ display: "flex", justifyContent: "center" }}
>
<a href={i} target="__blank">
<img alt="Received" className="message-img" src={i} />
</a>
<ImageDisplay src={i} />
</div>
))}
<div>{message.text}</div>
@@ -116,3 +116,21 @@ const StatusRender = (status) => {
return null;
}
};
const ImageDisplay = ({ src }) => {
const [state, setstate] = useState({ loading: true, url: null });
useEffect(() => {
axios
.post("/sms/fetchmedia", { mediaUrl: src })
.then(({ data }) => setstate({ loading: false, url: data }));
}, [src]);
if (state.loading === true) return <Spin />;
return (
<a href={i} target="__blank">
<img alt="Received" className="message-img" src={state.url} />
</a>
);
};

View File

@@ -8,23 +8,15 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { searchingForConversation } from "../../redux/messaging/messaging.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
searchingForConversation: searchingForConversation,
});
const mapDispatchToProps = (dispatch) => ({
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
});
export function ChatOpenButton({
bodyshop,
searchingForConversation,
phone,
jobid,
openChatByPhone,
}) {
export function ChatOpenButton({ bodyshop, phone, jobid, openChatByPhone }) {
const { t } = useTranslation();
if (!phone) return <></>;
@@ -37,7 +29,7 @@ export function ChatOpenButton({
onClick={(e) => {
e.stopPropagation();
const p = parsePhoneNumber(phone, "CA");
if (searchingForConversation) return; //This is to prevent finding the same thing twice.
if (p && p.isValid()) {
openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid });
} else {

View File

@@ -132,7 +132,7 @@ export function ChatPopupComponent({
onClick={() => toggleChatVisible()}
style={{ cursor: "pointer" }}
>
<MessageOutlined className="chat-popup-info-icon" />
<MessageOutlined />
<strong>{t("messaging.labels.messaging")}</strong>
</div>
)}

View File

@@ -13,9 +13,6 @@
height: 100%;
}
}
.chat-popup-info-icon {
margin-right: 5px;
}
@media only screen and (min-width: 992px) {
.chat-popup {

View File

@@ -59,14 +59,6 @@ export default function ContractsCarsComponent({
sortOrder:
state.sortedInfo.columnKey === "model" && state.sortedInfo.order,
},
{
title: t("courtesycars.fields.color"),
dataIndex: "color",
key: "color",
sorter: (a, b) => alphaSort(a.color, b.color),
sortOrder:
state.sortedInfo.columnKey === "color" && state.sortedInfo.order,
},
{
title: t("courtesycars.fields.plate"),
dataIndex: "plate",
@@ -101,9 +93,6 @@ export default function ContractsCarsComponent({
(cc.model || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
(cc.color || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
(cc.plate || "").toLowerCase().includes(state.search.toLowerCase())
);

View File

@@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next";
import { alphaSort } from "../../utils/sorters";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import {pageLimit} from "../../utils/config";
export default function ContractsJobsComponent({
loading,
@@ -176,7 +175,7 @@ export default function ContractsJobsComponent({
loading={loading}
pagination={{
position: "top",
defaultPageSize: pageLimit,
defaultPageSize: 10,
defaultCurrent: defaultCurrent,
}}
columns={columns}

View File

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

View File

@@ -4,16 +4,15 @@ import queryString from "query-string";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useHistory, useLocation } from "react-router-dom";
import { setModalContext } from "../../redux/modals/modals.actions";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import ContractsFindModalContainer from "../contracts-find-modal/contracts-find-modal.container";
import { setModalContext } from "../../redux/modals/modals.actions";
import moment from "moment";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import moment from "moment";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -210,7 +209,7 @@ export function ContractsList({
}}
pagination={{
position: "top",
pageSize: pageLimit,
pageSize: 25,
current: parseInt(page || 1),
total: total,
}}

View File

@@ -5,7 +5,6 @@ import { useTranslation } from "react-i18next";
import { Link, useHistory, useLocation } from "react-router-dom";
import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import {pageLimit} from "../../utils/config";
export default function CourtesyCarContractListComponent({
contracts,
@@ -90,7 +89,7 @@ export default function CourtesyCarContractListComponent({
scroll={{ x: true }}
pagination={{
position: "top",
pageSize: pageLimit,
pageSize: 25,
current: parseInt(page || 1),
total: totalContracts,
}}

View File

@@ -34,18 +34,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
{/* <FormFieldsChanged form={form} /> */}
<LayoutFormRow header={t("courtesycars.labels.vehicle")}>
<Form.Item
label={t("courtesycars.fields.year")}
name="year"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("courtesycars.fields.make")}
name="make"
@@ -70,6 +58,18 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
>
<Input />
</Form.Item>
<Form.Item
label={t("courtesycars.fields.year")}
name="year"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("courtesycars.fields.plate")}
name="plate"
@@ -118,7 +118,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
},
]}
>
<InputNumber min={0} precision={0} />
<InputNumber precision={0} />
</Form.Item>
<Form.Item
label={t("courtesycars.fields.fleetnumber")}
@@ -213,23 +213,49 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
>
<CourtesyCarStatus />
</Form.Item>
<Form.Item
label={t("courtesycars.fields.nextservicekm")}
name="nextservicekm"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber />
</Form.Item>
<div>
<Form.Item
label={t("courtesycars.fields.nextservicekm")}
name="nextservicekm"
label={t("courtesycars.fields.nextservicedate")}
name="nextservicedate"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber min={0} precision={0} />
<FormDatePicker />
</Form.Item>
<Form.Item
shouldUpdate={(p, c) =>
p.mileage !== c.mileage || p.nextservicekm !== c.nextservicekm
p.mileage !== c.mileage ||
p.nextservicedate !== c.nextservicedate ||
p.nextservicekm !== c.nextservicekm
}
>
{() => {
const nextservicedate = form.getFieldValue("nextservicedate");
const nextservicekm = form.getFieldValue("nextservicekm");
const mileageOver =
nextservicekm && nextservicekm <= form.getFieldValue("mileage");
if (mileageOver)
nextservicekm <= form.getFieldValue("mileage");
const dueForService =
nextservicedate && moment(nextservicedate).isBefore(moment());
if (mileageOver || dueForService)
return (
<Space direction="vertical" style={{ color: "tomato" }}>
<span>
@@ -237,36 +263,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
{t("contracts.labels.cardueforservice")}
</span>
<span>{`${nextservicekm} km`}</span>
</Space>
);
return <></>;
}}
</Form.Item>
</div>
<div>
<Form.Item
label={t("courtesycars.fields.nextservicedate")}
name="nextservicedate"
>
<FormDatePicker />
</Form.Item>
<Form.Item
shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}
>
{() => {
const nextservicedate = form.getFieldValue("nextservicedate");
const dueForService =
nextservicedate &&
moment(nextservicedate).endOf("day").isSameOrBefore(moment());
if (dueForService)
return (
<Space direction="vertical" style={{ color: "tomato" }}>
<span>
<WarningFilled style={{ marginRight: ".3rem" }} />
{t("contracts.labels.cardueforservice")}
</span>
<span>
<DateFormatter>{nextservicedate}</DateFormatter>
</span>
@@ -287,6 +283,12 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
<Form.Item
label={t("courtesycars.fields.registrationexpires")}
name="registrationexpires"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDatePicker />
</Form.Item>
@@ -298,8 +300,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
{() => {
const expires = form.getFieldValue("registrationexpires");
const dateover =
expires && moment(expires).endOf("day").isBefore(moment());
const dateover = expires && moment(expires).isBefore(moment());
if (dateover)
return (
@@ -329,13 +330,14 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
<FormDatePicker />
</Form.Item>
<Form.Item
shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}
shouldUpdate={(p, c) =>
p.insuranceexpires !== c.insuranceexpires
}
>
{() => {
const expires = form.getFieldValue("insuranceexpires");
const dateover =
expires && moment(expires).endOf("day").isBefore(moment());
const dateover = expires && moment(expires).isBefore(moment());
if (dateover)
return (

View File

@@ -9,15 +9,15 @@ import {
Table,
Tooltip,
} from "antd";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import moment from "moment";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
const [state, setState] = useState({
sortedInfo: {},
@@ -115,14 +115,6 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
sortOrder:
state.sortedInfo.columnKey === "model" && state.sortedInfo.order,
},
{
title: t("courtesycars.fields.color"),
dataIndex: "color",
key: "color",
sorter: (a, b) => alphaSort(a.color, b.color),
sortOrder:
state.sortedInfo.columnKey === "color" && state.sortedInfo.order,
},
{
title: t("courtesycars.fields.plate"),
dataIndex: "plate",
@@ -174,7 +166,6 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
(c.year || "").toLowerCase().includes(searchText.toLowerCase()) ||
(c.make || "").toLowerCase().includes(searchText.toLowerCase()) ||
(c.model || "").toLowerCase().includes(searchText.toLowerCase()) ||
(c.plate || "").toLowerCase().includes(searchText.toLowerCase()) ||
(t(c.status) || "").toLowerCase().includes(searchText.toLowerCase())
)
: courtesycars;

View File

@@ -7,7 +7,6 @@ import { Link, useHistory, useLocation } from "react-router-dom";
import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import {pageLimit} from "../../utils/config";
export default function CsiResponseListPaginated({
refetch,
@@ -107,7 +106,7 @@ export default function CsiResponseListPaginated({
loading={loading}
pagination={{
position: "top",
pageSize: pageLimit,
pageSize: 25,
current: parseInt(page || 1),
total: total,
}}

View File

@@ -6,7 +6,6 @@ import { alphaSort } from "../../../utils/sorters";
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
import Dinero from "dinero.js";
import DashboardRefreshRequired from "../refresh-required.component";
import {pageLimit} from "../../../utils/config";
export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
const { t } = useTranslation();
@@ -119,7 +118,7 @@ export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
<div style={{ height: "100%" }}>
<Table
onChange={handleTableChange}
pagination={{ position: "top", defaultPageSize: pageLimit }}
pagination={{ position: "top", defaultPageSize: 50 }}
columns={columns}
scroll={{ x: true, y: "calc(100% - 4em)" }}
rowKey="id"

View File

@@ -11,7 +11,6 @@ import { Link } from "react-router-dom";
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
import DashboardRefreshRequired from "../refresh-required.component";
import {pageLimit} from "../../../utils/config";
export default function DashboardScheduledInToday({ data, ...cardProps }) {
const { t } = useTranslation();
@@ -24,40 +23,36 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
const appt = []; // Flatten Data
data.scheduled_in_today.forEach((item) => {
if (item.job) {
var i = {
canceled: item.canceled,
id: item.id,
alt_transport: item.job.alt_transport,
clm_no: item.job.clm_no,
jobid: item.job.jobid,
ins_co_nm: item.job.ins_co_nm,
iouparent: item.job.iouparent,
ownerid: item.job.ownerid,
ownr_co_nm: item.job.ownr_co_nm,
ownr_ea: item.job.ownr_ea,
ownr_fn: item.job.ownr_fn,
ownr_ln: item.job.ownr_ln,
ownr_ph1: item.job.ownr_ph1,
ownr_ph2: item.job.ownr_ph2,
production_vars: item.job.production_vars,
ro_number: item.job.ro_number,
suspended: item.job.suspended,
v_make_desc: item.job.v_make_desc,
v_model_desc: item.job.v_model_desc,
v_model_yr: item.job.v_model_yr,
v_vin: item.job.v_vin,
vehicleid: item.job.vehicleid,
note: item.note,
start: moment(item.start).format("hh:mm a"),
title: item.title,
};
appt.push(i);
}
});
appt.sort(function (a, b) {
return new moment(a.start) - new moment(b.start);
var i = {
canceled: item.canceled,
id: item.id,
alt_transport: item.job.alt_transport,
clm_no: item.job.clm_no,
jobid: item.job.jobid,
ins_co_nm: item.job.ins_co_nm,
iouparent: item.job.iouparent,
ownerid: item.job.ownerid,
ownr_co_nm: item.job.ownr_co_nm,
ownr_ea: item.job.ownr_ea,
ownr_fn: item.job.ownr_fn,
ownr_ln: item.job.ownr_ln,
ownr_ph1: item.job.ownr_ph1,
ownr_ph2: item.job.ownr_ph2,
production_vars: item.job.production_vars,
ro_number: item.job.ro_number,
suspended: item.job.suspended,
v_make_desc: item.job.v_make_desc,
v_model_desc: item.job.v_model_desc,
v_model_yr: item.job.v_model_yr,
v_vin: item.job.v_vin,
vehicleid: item.job.vehicleid,
note: item.note,
start: moment(item.start).format("hh:mm a"),
title: item.title,
};
appt.push(i);
});
appt.sort ( function (a, b) { return new Date(a.start) - new Date(b.start); });
const columns = [
{
@@ -187,16 +182,11 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
};
return (
<Card
title={t("dashboard.titles.scheduledintoday", {
date: moment().startOf("day").format("MM/DD/YYYY"),
})}
{...cardProps}
>
<Card title={t("dashboard.titles.scheduledintoday", {date: moment().startOf("day").format("MM/DD/YYYY")})} {...cardProps}>
<div style={{ height: "100%" }}>
<Table
onChange={handleTableChange}
pagination={{ position: "top", defaultPageSize: pageLimit }}
pagination={{ position: "top", defaultPageSize: 50 }}
columns={columns}
scroll={{ x: true, y: "calc(100% - 2em)" }}
rowKey="id"

View File

@@ -11,7 +11,6 @@ import { Link } from "react-router-dom";
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
import DashboardRefreshRequired from "../refresh-required.component";
import {pageLimit} from "../../../utils/config";
export default function DashboardScheduledOutToday({ data, ...cardProps }) {
const { t } = useTranslation();
@@ -166,7 +165,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
<div style={{ height: "100%" }}>
<Table
onChange={handleTableChange}
pagination={{ position: "top", defaultPageSize: pageLimit }}
pagination={{ position: "top", defaultPageSize: 50 }}
columns={columns}
scroll={{ x: true, y: "calc(100% - 2em)" }}
rowKey="id"

View File

@@ -18,7 +18,7 @@ export default function DataLabel({
<div {...props} style={{ display: "flex" }}>
<div
style={{
// flex: 2,
flex: 2,
marginRight: ".2rem",
}}
>

View File

@@ -5,7 +5,6 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -118,7 +117,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
}
>
<Table
pagination={{ position: "top", defaultPageSize: pageLimit }}
pagination={{ position: "top", defaultPageSize: 50 }}
columns={columns}
rowKey={(record) => `${record.InvoiceNumber}${record.Account}`}
dataSource={allocationsSummary}

View File

@@ -6,7 +6,6 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import Dinero from "dinero.js";
import { SyncOutlined } from "@ant-design/icons";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -95,7 +94,7 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
<Alert type="warning" message={t("jobs.labels.dms.disablebillwip")} />
)}
<Table
pagination={{ position: "top", defaultPageSize: pageLimit }}
pagination={{ position: "top", defaultPageSize: 50 }}
columns={columns}
rowKey="center"
dataSource={allocationsSummary}

View File

@@ -191,13 +191,6 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
>
<Switch />
</Form.Item>
<Form.Item
name="dms_model_override"
label={t("jobs.fields.dms.dms_model_override")}
initialValue={false}
>
<Switch />
</Form.Item>
</Space>
</div>
)}

View File

@@ -109,7 +109,7 @@ export function EmailOverlayContainer({
]
: []),
],
media: selectedMedia.filter((m) => m.isSelected).map((m) => m.fullsize),
media: selectedMedia.filter((m) => m.isSelected).map((m) => m.src),
//attachments,
});
notification["success"]({ message: t("emails.successes.sent") });

View File

@@ -1,10 +1,10 @@
import { Select, Space, Tag } from "antd";
import React from "react";
import React, { forwardRef } from "react";
import { useTranslation } from "react-i18next";
const { Option } = Select;
//To be used as a form element only.
const EmployeeSearchSelect = ({ options, ...props }) => {
const EmployeeSearchSelect = ({ options, ...props }, ref) => {
const { t } = useTranslation();
return (
@@ -39,4 +39,4 @@ const EmployeeSearchSelect = ({ options, ...props }) => {
</Select>
);
};
export default EmployeeSearchSelect;
export default forwardRef(EmployeeSearchSelect);

View File

@@ -1,33 +0,0 @@
import { useQuery } from "@apollo/client";
import { Select } from "antd";
import React, { forwardRef } from "react";
import { QUERY_TEAMS } from "../../graphql/employee_teams.queries";
import AlertComponent from "../alert/alert.component";
//To be used as a form element only.
const EmployeeTeamSearchSelect = ({ ...props }, ref) => {
const { loading, error, data } = useQuery(QUERY_TEAMS);
if (error) return <AlertComponent message={JSON.stringify(error)} />;
return (
<Select
showSearch
allowClear
loading={loading}
style={{
width: 400,
}}
options={
data
? data.employee_teams.map((e) => ({
value: JSON.stringify(e),
label: e.name,
}))
: []
}
{...props}
/>
);
};
export default forwardRef(EmployeeTeamSearchSelect);

View File

@@ -40,22 +40,22 @@ class ErrorBoundary extends React.Component {
}
handleErrorSubmit = () => {
// window.$crisp.push([
// "do",
// "message:send",
// [
// "text",
// `I hit the following error: \n\n
// ${this.state.error.message}\n\n
// ${this.state.error.stack}\n\n
// URL:${window.location} as ${this.props.currentUser.email} for ${
// this.props.bodyshop && this.props.bodyshop.name
// }
// `,
// ],
// ]);
window.$crisp.push([
"do",
"message:send",
[
"text",
`I hit the following error: \n\n
${this.state.error.message}\n\n
${this.state.error.stack}\n\n
URL:${window.location} as ${this.props.currentUser.email} for ${
this.props.bodyshop && this.props.bodyshop.name
}
`,
],
]);
// window.$crisp.push(["do", "chat:open"]);
window.$crisp.push(["do", "chat:open"]);
// const errorDescription = `**Please add relevant details about what you were doing before you encountered this issue**
// ----

View File

@@ -65,17 +65,8 @@ export function FormDatePicker({
});
}
if (_a.isValid() && onChange) {
if (onlyFuture) {
if (moment().subtract(1, "day").isBefore(_a)) {
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
} else {
onChange(isDateOnly ? moment().format("YYYY-MM-DD") : moment());
}
} else {
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
}
}
if (_a.isValid() && onChange)
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
};
return (

View File

@@ -1,9 +1,9 @@
import React, { forwardRef } from "react";
//import DatePicker from "react-datepicker";
//import "react-datepicker/src/stylesheets/datepicker.scss";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import { TimePicker } from "antd";
import moment from "moment";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
//To be used as a form element only.
const DateTimePicker = (
@@ -26,21 +26,20 @@ const DateTimePicker = (
value={value}
onBlur={onBlur}
onChange={onChange}
onlyFuture={onlyFuture}
isDateOnly={false}
/>
<TimePicker
value={value ? moment(value) : null}
{...(onlyFuture && {
disabledDate: (d) => moment().isAfter(d),
})}
onChange={onChange}
showSecond={false}
minuteStep={15}
onBlur={onBlur}
format="hh:mm a"
{...restProps}
value={value ? moment(value) : null}
{...(onlyFuture && {
disabledDate: (d) => moment().isAfter(d),
})}
onChange={onChange}
showSecond={false}
minuteStep={15}
onBlur={onBlur}
format="hh:mm a"
{...restProps}
/>
</div>
);

View File

@@ -1,26 +1,9 @@
import Dinero from "dinero.js";
import React, { forwardRef } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
const ReadOnlyFormItem = (
{ bodyshop, value, type = "text", onChange },
ref
) => {
const ReadOnlyFormItem = ({ value, type = "text", onChange }, ref) => {
if (!value) return null;
switch (type) {
case "employee":
const emp = bodyshop.employees.find((e) => e.id === value);
return `${emp?.first_name} ${emp?.last_name}`;
case "text":
return <div>{value}</div>;
case "currency":
@@ -31,8 +14,4 @@ const ReadOnlyFormItem = (
return <div>{value}</div>;
}
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(forwardRef(ReadOnlyFormItem));
export default forwardRef(ReadOnlyFormItem);

View File

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

View File

@@ -1,9 +1,10 @@
import { useTreatments } from "@splitsoftware/splitio-react";
import Icon, {
BankFilled,
BarChartOutlined,
CarFilled,
CheckCircleOutlined,
ClockCircleFilled,
CheckCircleOutlined,
DashboardFilled,
DollarCircleFilled,
ExportOutlined,
@@ -25,7 +26,6 @@ import Icon, {
UnorderedListOutlined,
UserOutlined,
} from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Layout, Menu } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -70,8 +70,6 @@ const mapDispatchToProps = (dispatch) => ({
setReportCenterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "reportCenter" })),
signOutStart: () => dispatch(signOutStart()),
setCardPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
});
function Header({
@@ -85,7 +83,6 @@ function Header({
setPaymentContext,
setReportCenterContext,
recentItems,
setCardPaymentContext,
}) {
const { Simple_Inventory } = useTreatments(
["Simple_Inventory"],
@@ -97,11 +94,6 @@ function Header({
{},
bodyshop && bodyshop.imexshopid
);
const { ImEXPay } = useTreatments(
["ImEXPay"],
{},
bodyshop && bodyshop.imexshopid
);
const { t } = useTranslation();
@@ -248,44 +240,19 @@ function Header({
>
{t("menus.header.enterpayment")}
</Menu.Item>
{ImEXPay.treatment === "on" && (
<Menu.Item
key="entercardpayments"
onClick={() => {
setCardPaymentContext({
actions: {},
context: {},
});
}}
icon={<Icon component={FaCreditCard} />}
>
{t("menus.header.entercardpayment")}
</Menu.Item>
)}
<Menu.Divider key="div5" />
<Menu.Item key="timetickets" icon={<FieldTimeOutlined />}>
<Link to="/manage/timetickets">
{t("menus.header.timetickets")}
</Link>
</Menu.Item>
{bodyshop?.md_tasks_presets?.use_approvals && (
<Menu.Item key="ttapprovals" icon={<FieldTimeOutlined />}>
<Link to="/manage/ttapprovals">
{t("menus.header.ttapprovals")}
</Link>
</Menu.Item>
)}
<Menu.Item
key="entertimetickets"
icon={<Icon component={GiPlayerTime} />}
onClick={() => {
setTimeTicketContext({
actions: {},
context: {
created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
},
context: {},
});
}}
>
@@ -389,12 +356,20 @@ function Header({
<Menu.Item
key="help"
onClick={() => {
window.open("https://rometech.com/", "_blank");
window.open("https://help.imex.online/", "_blank");
}}
icon={<Icon component={QuestionCircleFilled} />}
>
{t("menus.header.help")}
</Menu.Item>
<Menu.Item
key="rescue"
onClick={() => {
window.open("https://imexrescue.com/", "_blank");
}}
>
{t("menus.header.rescueme")}
</Menu.Item>
<Menu.Item key="shiftclock">
<Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
</Menu.Item>

View File

@@ -11,7 +11,6 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import InventoryBillRo from "../inventory-bill-ro/inventory-bill-ro.component";
import InventoryLineDelete from "../inventory-line-delete/inventory-line-delete.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
@@ -214,7 +213,7 @@ export function JobsList({
loading={loading}
pagination={{
position: "top",
pageSize: pageLimit,
pageSize: 25,
current: parseInt(page || 1),
total: total,
}}

View File

@@ -11,7 +11,6 @@ import {
} from "../../redux/application/application.actions";
import AlertComponent from "../alert/alert.component";
import InventoryListPaginated from "./inventory-list.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
//bodyshop: selectBodyshop,
@@ -33,8 +32,8 @@ export function InventoryList({ setBreadcrumbs, setSelectedHeader }) {
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
offset: page ? (page - 1) * 25 : 0,
limit: 25,
consumedIsNull: showall === "true" ? null : true,
order: [
{

View File

@@ -5,7 +5,6 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR } from "../../graphql/vendors.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
@@ -14,14 +13,13 @@ import VendorSearchSelect from "../vendor-search-select/vendor-search-select.com
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
technician: selectTechnician,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(Jobd3RdPartyModal);
export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) {
export function Jobd3RdPartyModal({ bodyshop, jobId, job }) {
const [isModalVisible, setIsModalVisible] = useState(false);
const { t } = useTranslation();
const [form] = Form.useForm();
@@ -214,9 +212,7 @@ export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) {
]}
>
<Radio.Group>
{!technician ? (
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio>
) : null}
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio>
<Radio value={"p"}>{t("parts_orders.labels.print")}</Radio>
</Radio.Group>
</Form.Item>

View File

@@ -4,7 +4,6 @@ import {
Divider,
Dropdown,
Form,
Input,
Menu,
notification,
Popover,
@@ -30,13 +29,11 @@ import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
import ScheduleAtChange from "./job-at-change.component";
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 OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -61,44 +58,16 @@ export function ScheduleEventComponent({
const [visible, setVisible] = useState(false);
const history = useHistory();
const searchParams = queryString.parse(useLocation().search);
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
const [title, setTitle] = useState(event.title);
const blockContent = (
<Space direction="vertical" wrap>
<Input
value={title}
onChange={(e) => setTitle(e.currentTarget.value)}
onBlur={async () => {
await updateAppointment({
variables: {
appid: event.id,
app: {
title: title,
},
},
optimisticResponse: {
update_appointments: {
__typename: "appointments_mutation_response",
returning: [
{
...event,
title: title,
__typename: "appointments",
},
],
},
},
});
}}
/>
const blockContent = (
<div>
<Button
onClick={() => handleCancel({ id: event.id })}
disabled={event.arrived}
>
{t("appointments.actions.unblock")}
{t("appointments.actions.cancel")}
</Button>
</Space>
</div>
);
const popoverContent = (
@@ -239,56 +208,46 @@ export function ScheduleEventComponent({
<Button>{t("appointments.actions.sendreminder")}</Button>
</Dropdown>
) : null}
{event.arrived ? (
<Popover
trigger="click"
disabled={event.arrived}
content={
<Form
layout="vertical"
onFinish={({ lost_sale_reason }) => {
handleCancel({ id: event.id, lost_sale_reason });
}}
>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr,
}))}
/>
</Form.Item>
<Button htmlType="submit">
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
<Button
// onClick={() => handleCancel(event.id)}
disabled={event.arrived}
>
{t("appointments.actions.cancel")}
</Button>
) : (
<Popover
trigger="click"
disabled={event.arrived}
content={
<Form
layout="vertical"
onFinish={({ lost_sale_reason }) => {
handleCancel({ id: event.id, lost_sale_reason });
}}
>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr,
}))}
/>
</Form.Item>
<Button htmlType="submit">
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
<Button
// onClick={() => handleCancel(event.id)}
disabled={event.arrived}
>
{t("appointments.actions.cancel")}
</Button>
</Popover>
)}
</Popover>
{event.isintake ? (
<Button
disabled={event.arrived}

View File

@@ -2,16 +2,12 @@ import { useMutation } from "@apollo/client";
import { notification } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.queries";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import ScheduleEventComponent from "./schedule-event.component";
export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
const dispatch = useDispatch();
const { t } = useTranslation();
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
const [updateJob] = useMutation(UPDATE_JOB);
@@ -38,24 +34,16 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
const jobUpdate = await updateJob({
variables: {
jobId: event.job.id,
job: {
date_scheduled: null,
scheduled_in: null,
scheduled_completion: null,
lost_sale_reason,
date_lost_sale: new Date(),
status: bodyshop.md_ro_statuses.default_imported,
},
},
});
if (!jobUpdate.errors) {
dispatch(
insertAuditTrail({
jobid: event.job.id,
operation: AuditTrailMapping.appointmentcancel(lost_sale_reason),
})
);
}
if (!!jobUpdate.errors) {
notification["error"]({
message: t("jobs.errors.updating", {

View File

@@ -87,8 +87,7 @@ export default function JobBillsTotalComponent({
const totalPartsSublet = Dinero(totals.parts.parts.total)
.add(Dinero(totals.parts.sublets.total))
.add(Dinero(totals.additional.shipping))
.add(Dinero(totals.additional.towing))
.add(Dinero(totals.additional.additionalCosts));
.add(Dinero(totals.additional.towing));
const discrepancy = totalPartsSublet.subtract(billTotals);

View File

@@ -1,52 +1,47 @@
import { Button, notification } from "antd";
import Axios from "axios";
import React, { useState } from "react";
import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next";
export default function JobCalculateTotals({ job, disabled, refetch }) {
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import Dinero from "dinero.js";
export default function JobCalculateTotals({ job, disabled }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [updateJob] = useMutation(UPDATE_JOB);
const handleCalculate = async () => {
try {
setLoading(true);
setLoading(true);
const newTotals = (
await Axios.post("/job/totals", {
job: job,
})
).data;
await Axios.post("/job/totalsssu", {
id: job.id,
});
if (refetch) refetch();
// const result = await updateJob({
// refetchQueries: ["GET_JOB_BY_PK"],
// awaitRefetchQueries: true,
// variables: {
// jobId: job.id,
// job: {
// job_totals: newTotals,
// clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
// owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat(
// "0.00"
// ),
// },
// },
// });
// if (!!!result.errors) {
// notification["success"]({ message: t("jobs.successes.updated") });
// } else {
// notification["error"]({
// message: t("jobs.errors.updating", {
// error: JSON.stringify(result.errors),
// }),
// });
// }
} catch (error) {
const result = await updateJob({
refetchQueries: ["GET_JOB_BY_PK"],
awaitRefetchQueries: true,
variables: {
jobId: job.id,
job: {
job_totals: newTotals,
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat(
"0.00"
),
},
},
});
if (!!!result.errors) {
notification["success"]({ message: t("jobs.successes.updated") });
} else {
notification["error"]({
message: t("jobs.errors.updating", {
error: JSON.stringify(error),
error: JSON.stringify(result.errors),
}),
});
} finally {
setLoading(false);
}
setLoading(false);
};
return (

View File

@@ -1,6 +1,5 @@
import { useMutation } from "@apollo/client";
import { Button, Card, Form, Input, notification, Switch } from "antd";
import moment from "moment-business-days";
import queryString from "query-string";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -13,15 +12,16 @@ import {
MARK_LATEST_APPOINTMENT_ARRIVED,
} from "../../../../graphql/appointments.queries";
import { UPDATE_JOB } from "../../../../graphql/jobs.queries";
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
import { insertAuditTrail } from "../../../../redux/application/application.actions";
import {
selectBodyshop,
selectCurrentUser,
} from "../../../../redux/user/user.selectors";
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
import moment from "moment-business-days";
import { insertAuditTrail } from "../../../../redux/application/application.actions";
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -230,7 +230,6 @@ export function JobChecklistForm({
)),
scheduled_delivery:
job.scheduled_delivery && moment(job.scheduled_delivery),
production_vars: job.production_vars,
}),
...(type === "deliver" && {
removeFromProduction: true,

View File

@@ -3,7 +3,6 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { alphaSort } from "../../utils/sorters";
import Dinero from "dinero.js";
import {pageLimit} from "../../utils/config";
export default function JobCostingPartsTable({ data, summaryData }) {
const [searchText, setSearchText] = useState("");
const [state, setState] = useState({
@@ -99,7 +98,7 @@ export default function JobCostingPartsTable({ data, summaryData }) {
x: "50%", //y: "40rem"
}}
onChange={handleTableChange}
pagination={{ position: "top", defaultPageSize: pageLimit }}
pagination={{ position: "top", defaultPageSize: 50 }}
columns={columns}
rowKey="id"
dataSource={filteredData}

View File

@@ -32,9 +32,9 @@ const mapDispatchToProps = (dispatch) => ({
});
const span = {
lg: { span: 24 },
xl: { span: 12 },
xxl: { span: 8 },
sm: { span: 24 },
md: { span: 12 },
lg: { span: 8 },
};
export function JobDetailCards({ bodyshop, setPrintCenterContext }) {
@@ -137,6 +137,12 @@ export function JobDetailCards({ bodyshop, setPrintCenterContext }) {
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col {...span}>
<JobDetailCardsPartsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col {...span}>
<JobDetailCardsNotesComponent
loading={loading}
@@ -157,12 +163,6 @@ export function JobDetailCards({ bodyshop, setPrintCenterContext }) {
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col span={24}>
<JobDetailCardsPartsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Col>
</Row>
</Card>
) : null}

View File

@@ -1,119 +1,16 @@
import { Table } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component";
import PartsStatusPie from "../parts-status-pie/parts-status-pie.component";
import CardTemplate from "./job-detail-cards.template.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { onlyUnique } from "../../utils/arrayHelper";
import { alphaSort } from "../../utils/sorters";
import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component";
import JobLineStatusPopup from "../job-line-status-popup/job-line-status-popup.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
jobRO: selectJobReadOnly,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobDetailCardsPartsComponent);
export function JobDetailCardsPartsComponent({ loading, data, jobRO }) {
export default function JobDetailCardsPartsComponent({ loading, data }) {
const { t } = useTranslation();
const { joblines_status } = data;
const columns = [
{
title: t("joblines.fields.line_desc"),
dataIndex: "line_desc",
fixed: "left",
key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
onCell: (record) => ({
className: record.manual_line && "job-line-manual",
style: {
...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {}),
},
}),
width: "30%",
ellipsis: true,
},
{
title: t("joblines.fields.part_type"),
dataIndex: "part_type",
key: "part_type",
width: "15%",
sorter: (a, b) =>
alphaSort(
t(`joblines.fields.part_types.${a.part_type}`),
t(`joblines.fields.part_types.${b.part_type}`)
),
render: (text, record) =>
record.part_type
? t(`joblines.fields.part_types.${record.part_type}`)
: null,
},
{
title: t("joblines.fields.part_qty"),
dataIndex: "part_qty",
key: "part_qty",
width: "10%",
},
{
title: t("joblines.fields.notes"),
dataIndex: "notes",
key: "notes",
render: (text, record) => (
<JobLineNotePopup disabled={jobRO} jobline={record} />
),
},
{
title: t("joblines.fields.location"),
dataIndex: "location",
key: "location",
sorter: (a, b) => alphaSort(a.location, b.location),
render: (text, record) => (
<JobLineLocationPopup jobline={record} disabled={jobRO} />
),
},
{
title: t("joblines.fields.status"),
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
filters:
(data &&
data.joblines
?.map((l) => l.status)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Status*",
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.status),
render: (text, record) => (
<JobLineStatusPopup jobline={record} disabled={jobRO} />
),
},
];
return (
<div>
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
<PartsStatusPie joblines_status={joblines_status} />
<Table
key="id"
columns={columns}
dataSource={data ? data.joblines : []}
/>
</CardTemplate>
</div>
);

View File

@@ -8,18 +8,7 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander);
export function JobLinesExpander({ jobline, jobid, bodyshop }) {
export default function JobLinesExpander({ jobline, jobid }) {
const { t } = useTranslation();
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, {
fetchPolicy: "network-only",
@@ -34,7 +23,7 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
return (
<Row>
<Col md={24} lg={8}>
<Col md={24} lg={12}>
<Typography.Title level={4}>
{t("parts_orders.labels.parts_orders")}
</Typography.Title>
@@ -60,7 +49,7 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
)}
</Timeline>
</Col>
<Col md={24} lg={8}>
<Col md={24} lg={12}>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Timeline>
{data.billlines.length > 0 ? (
@@ -82,7 +71,7 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
</Col>
<Col span={4}>
<span>
{`${t("billlines.fields.actual_cost")}: `}
{`${t("billlines.fields.actual_cost")}: `}
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
</span>
</Col>
@@ -100,37 +89,6 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
)}
</Timeline>
</Col>
<Col md={24} lg={8}>
<Typography.Title level={4}>
{t("parts_dispatch.labels.parts_dispatch")}
</Typography.Title>
<Timeline>
{data.parts_dispatch_lines.length > 0 ? (
data.parts_dispatch_lines.map((line) => (
<Timeline.Item key={line.id}>
<Space split={<Divider type="vertical" />} wrap>
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>
{line.parts_dispatch.number}
</Link>
{
bodyshop.employees.find(
(e) => e.id === line.parts_dispatch.employeeid
)?.first_name
}
<Space>
{t("parts_dispatch_lines.fields.accepted_at")}
<DateFormatter>{line.accepted_at}</DateFormatter>
</Space>
</Space>
</Timeline.Item>
))
) : (
<Timeline.Item>
{t("parts_orders.labels.notyetordered")}
</Timeline.Item>
)}
</Timeline>
</Col>
</Row>
);
}

View File

@@ -1,97 +0,0 @@
import { useMutation } from "@apollo/client";
import { Button, Form, notification, Popover, Tooltip } from "antd";
import { t } from "i18next";
import React, { useState } from "react";
import { UPDATE_LINE_PPC } from "../../graphql/jobs-lines.queries";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
import axios from "axios";
export default function JobLinesPartPriceChange({ job, line, refetch }) {
const [loading, setLoading] = useState(false);
const [updatePartPrice] = useMutation(UPDATE_LINE_PPC);
const handleFinish = async (values) => {
try {
setLoading(true);
const result = await updatePartPrice({
variables: {
id: line.id,
jobline: {
act_price_before_ppc: line.act_price_before_ppc
? line.act_price_before_ppc
: line.act_price,
act_price: values.act_price,
},
},
});
await axios.post("/job/totalsssu", {
id: job.id,
});
if (result.errors) {
notification.open({
type: "error",
message: t("joblines.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
if (refetch) refetch();
} else {
notification.open({
type: "success",
message: t("joblines.successes.saved"),
});
}
} catch (error) {
notification.open({
type: "error",
message: t("joblines.errors.saving", { error: JSON.stringify(error) }),
});
} finally {
setLoading(false);
}
};
const popcontent = (
<Form layout="vertical" onFinish={handleFinish} initialValues={{ act_price: line.act_price }}>
<Form.Item
name="act_price"
label={t("jobs.labels.act_price_ppc")}
rules={[{ required: true }]}
>
<CurrencyFormItemComponent />
</Form.Item>
<Button loading={loading} htmlType="primary">
{t("general.actions.save")}
</Button>
</Form>
);
return (
<JobLineConvertToLabor jobline={line} job={job}>
<Popover trigger="click" disabled={line.manual_line} content={popcontent}>
<CurrencyFormatter>
{line.db_ref === "900510" || line.db_ref === "900511"
? line.prt_dsmk_m
: line.act_price}
</CurrencyFormatter>
{line.prt_dsmk_p && line.prt_dsmk_p !== 0 ? (
<span style={{ marginLeft: ".2rem" }}>{`(${line.prt_dsmk_p}%)`}</span>
) : (
<></>
)}
{line.act_price_before_ppc && line.act_price_before_ppc !== 0 ? (
<Tooltip title={t("jobs.labels.ppc")}>
<span style={{ marginLeft: ".2rem", color: "tomato" }}>
(
<CurrencyFormatter>{line.act_price_before_ppc}</CurrencyFormatter>
)
</span>
</Tooltip>
) : (
<></>
)}
</Popover>
</JobLineConvertToLabor>
);
}

View File

@@ -1,12 +1,12 @@
import {
DeleteFilled,
EditFilled,
FilterFilled,
HomeOutlined,
MinusCircleTwoTone,
PlusCircleTwoTone,
SyncOutlined,
WarningFilled,
EditFilled,
PlusCircleTwoTone,
MinusCircleTwoTone,
HomeOutlined,
} from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import {
@@ -29,6 +29,7 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { onlyUnique } from "../../utils/arrayHelper";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component";
import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component";
@@ -37,18 +38,13 @@ import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-re
// import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container";
// import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
// import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
import _ from "lodash";
import moment from "moment";
import { selectBodyshop } from "../../redux/user/user.selectors";
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobSendPartPriceChangeComponent from "../job-send-parts-price-change/job-send-parts-price-change.component";
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import _ from "lodash";
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobLinesExpander from "./job-lines-expander.component";
import JobLinesPartPriceChange from "./job-lines-part-price-change.component";
import JoblineTeamAssignment from "../job-line-team-assignment/job-line-team-assignmnent.component";
import JobLineDispatchButton from "../job-line-dispatch-button/job-line-dispatch-button.component";
import JobLineBulkAssignComponent from "../job-line-bulk-assign/job-line-bulk-assign.component";
import { useTreatments } from "@splitsoftware/splitio-react";
import { selectBodyshop } from "../../redux/user/user.selectors";
import moment from "moment";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -80,16 +76,8 @@ export function JobLinesComponent({
setBillEnterContext,
}) {
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
const { Enhanced_Payroll } = useTreatments(
["Enhanced_Payroll"],
{},
bodyshop.imexshopid
);
const [selectedLines, setSelectedLines] = useState([]);
console.log(
"🚀 ~ file: job-lines.component.jsx:89 ~ selectedLines:",
selectedLines
);
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: {},
@@ -133,21 +121,10 @@ export function JobLinesComponent({
sortOrder:
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
ellipsis: true,
onCell: (record) => ({
className: record.manual_line && "job-line-manual",
style: {
...(record.parts_dispatch_lines[0]?.accepted_at
? { boxShadow: " -.5em 0 0 #FFC107" }
: {}),
},
}),
render: (text, record) => (
<span class="ant-table-cell-content">
{`${record.oem_partno || ""} ${
record.alt_partno ? `(${record.alt_partno})` : ""
}`.trim()}
</span>
),
render: (text, record) =>
`${record.oem_partno || ""} ${
record.alt_partno ? `(${record.alt_partno})` : ""
}`.trim(),
},
{
title: t("joblines.fields.op_code_desc"),
@@ -243,7 +220,20 @@ export function JobLinesComponent({
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
ellipsis: true,
render: (text, record) => (
<JobLinesPartPriceChange line={record} job={job} refetch={refetch} />
<JobLineConvertToLabor jobline={record} job={job}>
<CurrencyFormatter>
{record.db_ref === "900510" || record.db_ref === "900511"
? record.prt_dsmk_m
: record.act_price}
</CurrencyFormatter>
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
<span
style={{ marginLeft: ".2rem" }}
>{`(${record.prt_dsmk_p}%)`}</span>
) : (
<></>
)}
</JobLineConvertToLabor>
),
},
{
@@ -296,23 +286,6 @@ export function JobLinesComponent({
state.sortedInfo.columnKey === "line_ind" && state.sortedInfo.order,
responsive: ["md"],
},
...(Enhanced_Payroll.treatment === "on"
? [
{
title: t("joblines.fields.assigned_team"),
dataIndex: "assigned_team",
key: "assigned_team",
render: (text, record) => (
<JoblineTeamAssignment
disabled={jobRO}
jobline={record}
jobId={job.id}
/>
),
},
]
: []),
{
title: t("joblines.fields.notes"),
dataIndex: "notes",
@@ -428,14 +401,10 @@ export function JobLinesComponent({
const markedTypes = [e.key];
if (e.key === "PAN") markedTypes.push("PAP");
if (e.key === "PAS") markedTypes.push("PASL");
setSelectedLines((selectedLines) =>
setSelectedLines(
_.uniq([
...selectedLines,
...jobLines.filter(
(item) =>
markedTypes.includes(item.part_type) ||
markedTypes.includes(item.mod_lbr_ty)
),
...jobLines.filter((item) => markedTypes.includes(item.part_type)),
])
);
}
@@ -448,21 +417,6 @@ export function JobLinesComponent({
<Menu.Item key="PAL">{t("joblines.fields.part_types.PAL")}</Menu.Item>
<Menu.Item key="PAS">{t("joblines.fields.part_types.PAS")}</Menu.Item>
<Menu.Divider />
<Menu.Item key="LAA">{t("joblines.fields.lbr_types.LAA")}</Menu.Item>
<Menu.Item key="LAB">{t("joblines.fields.lbr_types.LAB")}</Menu.Item>
<Menu.Item key="LAD">{t("joblines.fields.lbr_types.LAD")}</Menu.Item>
<Menu.Item key="LAE">{t("joblines.fields.lbr_types.LAE")}</Menu.Item>
<Menu.Item key="LAF">{t("joblines.fields.lbr_types.LAF")}</Menu.Item>
<Menu.Item key="LAG">{t("joblines.fields.lbr_types.LAG")}</Menu.Item>
<Menu.Item key="LAM">{t("joblines.fields.lbr_types.LAM")}</Menu.Item>
<Menu.Item key="LAR">{t("joblines.fields.lbr_types.LAR")}</Menu.Item>
<Menu.Item key="LAS">{t("joblines.fields.lbr_types.LAS")}</Menu.Item>
<Menu.Item key="LAU">{t("joblines.fields.lbr_types.LAU")}</Menu.Item>
<Menu.Item key="LA1">{t("joblines.fields.lbr_types.LA1")}</Menu.Item>
<Menu.Item key="LA2">{t("joblines.fields.lbr_types.LA2")}</Menu.Item>
<Menu.Item key="LA3">{t("joblines.fields.lbr_types.LA3")}</Menu.Item>
<Menu.Item key="LA4">{t("joblines.fields.lbr_types.LA2")}</Menu.Item>
<Menu.Divider />
<Menu.Item key="clear">{t("general.labels.clear")}</Menu.Item>
</Menu>
);
@@ -486,18 +440,6 @@ export function JobLinesComponent({
</Space>
</Tag>
)}
<JobLineDispatchButton
selectedLines={selectedLines}
setSelectedLines={setSelectedLines}
job={job}
/>
{Enhanced_Payroll.treatment === "on" && (
<JobLineBulkAssignComponent
selectedLines={selectedLines}
setSelectedLines={setSelectedLines}
job={job}
/>
)}
<Button
disabled={
(job && !job.converted) ||
@@ -506,6 +448,15 @@ export function JobLinesComponent({
technician
}
onClick={() => {
// setPartsOrderContext({
// actions: { refetch: refetch },
// context: {
// jobId: job.id,
// job: job,
// linesToOrder: selectedLines,
// },
// });
setBillEnterContext({
actions: { refetch: refetch },
context: {
@@ -612,9 +563,6 @@ export function JobLinesComponent({
>
{t("joblines.actions.new")}
</Button>
{bodyshop.region_config.toLowerCase().startsWith("us") && (
<JobSendPartPriceChangeComponent job={job} />
)}
<JobCreateIOU job={job} selectedJobLines={selectedLines} />
<Input.Search
placeholder={t("general.labels.search")}
@@ -666,17 +614,8 @@ export function JobLinesComponent({
onSelectAll: (selected, selectedRows, changeRows) => {
setSelectedLines(selectedRows);
},
onSelect: (record, selected, selectedRows, nativeEvent) => {
if (selected) {
setSelectedLines((selectedLines) =>
_.uniqBy([...selectedLines, record], "id")
);
} else {
setSelectedLines((selectedLines) =>
selectedLines.filter((l) => l.id !== record.id)
);
}
},
onSelect: (record, selected, selectedRows, nativeEvent) =>
setSelectedLines(selectedRows),
}}
/>
</div>

View File

@@ -1,149 +0,0 @@
import React, { useState } from "react";
import { useMutation } from "@apollo/client";
import { Button, Form, Popover, Select, Space, notification } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_LINE_BULK_ASSIGN } from "../../graphql/jobs-lines.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(mapStateToProps, mapDispatchToProps)(JoblineBulkAssign);
export function JoblineBulkAssign({
setSelectedLines,
selectedLines,
insertAuditTrail,
bodyshop,
jobRO,
job,
currentUser,
}) {
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const { t } = useTranslation();
const [assignLines] = useMutation(UPDATE_LINE_BULK_ASSIGN);
const handleConvert = async (values) => {
try {
setLoading(true);
const result = await assignLines({
variables: {
jobline: {
assigned_team: values.assigned_team,
},
ids: selectedLines.map((l) => l.id),
},
});
if (result.errors) {
notification.open({
type: "error",
message: t("parts_dispatch.errors.creating", {
error: JSON.stringify(result.errors),
}),
});
} else {
//Insert the audit trail here.
const teamName = bodyshop.employee_teams.find(
(et) => et.id === values.assigned_team
)?.name;
const hours = selectedLines.reduce(
(acc, val) => (acc += val.mod_lb_hrs),
0
);
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.assignedlinehours(
teamName,
hours.toFixed(1)
),
});
setSelectedLines([]);
setVisible(false);
}
} catch (error) {
notification.open({
type: "error",
message: t("parts_dispatch.errors.creating", {
error: error,
}),
});
} finally {
setLoading(false);
}
};
const popMenu = (
<div>
<Form layout="vertical" form={form} onFinish={handleConvert}>
<Form.Item
name={"assigned_team"}
label={t("joblines.fields.assigned_team")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
showSearch
style={{ width: 200 }}
optionFilterProp="children"
filterOption={(input, option) =>
option.props.children
.toLowerCase()
.indexOf(input.toLowerCase()) >= 0
}
>
{bodyshop.employee_teams.map((team) => (
<Select.Option value={team.id} key={team.id} name={team.name}>
{team.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Space wrap>
<Button type="danger" onClick={() => form.submit()} loading={loading}>
{t("general.actions.save")}
</Button>
<Button onClick={() => setVisible(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
</div>
);
return (
<Popover open={visible} content={popMenu}>
<Button
disabled={selectedLines.length === 0 || jobRO}
loading={loading}
onClick={() => setVisible(true)}
>
{t("joblines.actions.assign_team", { count: selectedLines.length })}
</Button>
</Popover>
);
}

View File

@@ -1,167 +0,0 @@
import React, { useState } from "react";
import { useMutation } from "@apollo/client";
import { Button, Form, Popover, Select, Space, notification } from "antd";
import moment from "moment";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_PARTS_DISPATCH } from "../../graphql/parts-dispatch.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobLineDispatchButton);
export function JobLineDispatchButton({
setSelectedLines,
selectedLines,
bodyshop,
jobRO,
job,
currentUser,
}) {
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const Templates = TemplateList("job_special", {
ro_number: job.ro_number,
});
const { t } = useTranslation();
const [dispatchLines] = useMutation(INSERT_PARTS_DISPATCH);
const handleConvert = async (values) => {
try {
setLoading(true);
//THIS HAS NOT YET BEEN TESTED. START BY FINISHING THIS FUNCTION.
const result = await dispatchLines({
variables: {
partsDispatch: {
dispatched_at: moment(),
employeeid: values.employeeid,
jobid: job.id,
dispatched_by: currentUser.email,
parts_dispatch_lines: {
data: selectedLines.map((l) => ({
joblineid: l.id,
quantity: l.part_qty,
})),
},
},
//joblineids: selectedLines.map((l) => l.id),
},
});
if (result.errors) {
notification.open({
type: "error",
message: t("parts_dispatch.errors.creating", {
error: result.errors,
}),
});
} else {
setSelectedLines([]);
await GenerateDocument(
{
name: Templates.parts_dispatch.key,
variables: {
id: result.data.insert_parts_dispatch_one.id,
},
},
{},
"p"
);
}
setVisible(false);
} catch (error) {
notification.open({
type: "error",
message: t("parts_dispatch.errors.creating", {
error: error,
}),
});
} finally {
setLoading(false);
}
};
const popMenu = (
<div>
<Form layout="vertical" form={form} onFinish={handleConvert}>
<Form.Item
name={"employeeid"}
label={t("timetickets.fields.employee")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
showSearch
style={{ width: 200 }}
optionFilterProp="children"
filterOption={(input, option) =>
option.props.children
.toLowerCase()
.indexOf(input.toLowerCase()) >= 0
}
>
{bodyshop.employees
.filter((emp) => emp.active)
.map((emp) => (
<Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`}
</Select.Option>
))}
</Select>
</Form.Item>
<Space wrap>
<Button
type="danger"
onClick={() => form.submit()}
loading={loading}
disabled={selectedLines.length === 0}
>
{t("general.actions.save")}
</Button>
<Button onClick={() => setVisible(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
</div>
);
return (
<Popover open={visible} content={popMenu}>
<Button
disabled={selectedLines.length === 0 || jobRO}
loading={loading}
onClick={() => setVisible(true)}
>
{t("joblines.actions.dispatchparts", { count: selectedLines.length })}
</Button>
</Popover>
);
}

View File

@@ -1,6 +1,6 @@
import { useMutation } from "@apollo/client";
import { notification, Select, Space } from "antd";
import { notification, Select } from "antd";
import React, { useEffect, useState } from "react";
import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -56,10 +56,8 @@ export function JobLineLocationPopup({ bodyshop, jobline, disabled }) {
<LoadingSpinner loading={loading}>
<Select
autoFocus
allowClear
dropdownMatchSelectWidth={100}
value={location}
onClear={() => setLocation(null)}
onSelect={handleChange}
onBlur={handleSave}
>
@@ -77,10 +75,7 @@ export function JobLineLocationPopup({ bodyshop, jobline, disabled }) {
style={{ width: "100%", minHeight: "2rem", cursor: "pointer" }}
onClick={() => !disabled && setEditing(true)}
>
<Space wrap>
{jobline.location}
{jobline.parts_dispatch_lines?.length > 0 && "-Disp"}
</Space>
{jobline.location}
</div>
);
}

View File

@@ -1,115 +0,0 @@
import { notification, Select } from "antd";
import React, { useEffect, useState } from "react";
import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function JoblineTeamAssignment({
bodyshop,
jobline,
disabled,
jobId,
insertAuditTrail,
}) {
const [editing, setEditing] = useState(false);
const [loading, setLoading] = useState(false);
const [assignedTeam, setAssignedTeam] = useState(jobline.assigned_team);
const [updateJob] = useMutation(UPDATE_JOB_LINE);
const { t } = useTranslation();
useEffect(() => {
if (editing) setAssignedTeam(jobline.assigned_team);
}, [editing, jobline.assigned_team]);
const handleChange = (e) => {
setAssignedTeam(e);
};
const handleSave = async (e) => {
setLoading(true);
const result = await updateJob({
variables: {
lineId: jobline.id,
line: { assigned_team: assignedTeam },
},
});
if (!!!result.errors) {
notification["success"]({ message: t("joblines.successes.saved") });
//insert the audit trail here.
const teamName = bodyshop.employee_teams.find(
(et) => et.id === assignedTeam
)?.name;
insertAuditTrail({
jobid: jobId,
operation: AuditTrailMapping.assignedlinehours(
teamName,
jobline.mod_lb_hrs
),
});
} else {
notification["error"]({
message: t("joblines.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
}
setLoading(false);
setEditing(false);
};
if (editing)
return (
<div>
<LoadingSpinner loading={loading}>
<Select
autoFocus
allowClear
dropdownMatchSelectWidth={100}
value={assignedTeam}
onSelect={handleChange}
onBlur={handleSave}
onClear={() => handleChange(null)}
>
{Object.values(bodyshop.employee_teams).map((s, idx) => (
<Select.Option key={idx} value={s.id}>
{s.name}
</Select.Option>
))}
</Select>
</LoadingSpinner>
</div>
);
const team = bodyshop.employee_teams.find(
(tm) => tm.id === jobline.assigned_team
);
return (
<div
style={{ width: "100%", minHeight: "1rem", cursor: "pointer" }}
onClick={() => !disabled && setEditing(true)}
>
{team?.name}
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(JoblineTeamAssignment);

View File

@@ -8,7 +8,7 @@ export default function JobLinesBillRefernece({ jobline }) {
return (
<div style={{ color: subletRequired && "tomato" }}>
{subletRequired && <WarningFilled />}
{`${billLine.actual_price.toFixed(2)} x ${billLine.quantity} (${
{`${(billLine.actual_price * billLine.quantity).toFixed(2)} (${
billLine.bill.vendor.name
} #${billLine.bill.invoice_number})`}
</div>

View File

@@ -1,26 +1,19 @@
import { EditFilled } from "@ant-design/icons";
import { Button, Card, Space, Table } from "antd";
import { EditFilled } from "@ant-design/icons";
import Dinero from "dinero.js";
import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import {
openChatByPhone,
setMessage,
} from "../../redux/messaging/messaging.actions";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort, dateSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import DataLabel from "../data-label/data-label.component";
import PaymentExpandedRowComponent from "../payment-expanded-row/payment-expanded-row.component";
import PaymentsGenerateLink from "../payments-generate-link/payments-generate-link.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -30,34 +23,20 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({
setPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "payment" })),
setCardPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
setMessage: (text) => dispatch(setMessage(text)),
});
export function JobPayments({
job,
jobRO,
bodyshop,
setMessage,
openChatByPhone,
setPaymentContext,
setCardPaymentContext,
refetch,
}) {
const { ImEXPay } = useTreatments(
["ImEXPay"],
{},
bodyshop && bodyshop.imexshopid
);
const { t } = useTranslation();
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: {},
});
const columns = [
{
title: t("payments.fields.date"),
@@ -170,21 +149,6 @@ export function JobPayments({
title={t("payments.labels.title")}
extra={
<Space wrap>
{ImEXPay.treatment === "on" && (
<>
<Button
onClick={() =>
setCardPaymentContext({
actions: { refetch },
context: { jobid: job.id, balance },
})
}
>
{t("menus.header.entercardpayment")}
</Button>
<PaymentsGenerateLink job={job} />
</>
)}
<Button
disabled={!job.converted}
onClick={() =>
@@ -196,7 +160,6 @@ export function JobPayments({
>
{t("menus.header.enterpayment")}
</Button>
<DataLabel
valueStyle={{ color: balance.getAmount() !== 0 ? "red" : "green" }}
label={t("payments.labels.balance")}
@@ -215,11 +178,6 @@ export function JobPayments({
scroll={{
x: true,
}}
expandable={{
expandedRowRender: (record) => (
<PaymentExpandedRowComponent record={record} bodyshop={bodyshop} />
),
}}
summary={() => (
<>
<Table.Summary.Row>

View File

@@ -1,18 +0,0 @@
import { Alert } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
export default function JobProfileDataWarning({ job }) {
const { t } = useTranslation();
let missingProfileInfo =
Object.keys(job.cieca_pft).length === 0 ||
Object.keys(job.cieca_pfl).length === 0 ||
Object.keys(job.materials).length === 0;
if (missingProfileInfo)
return (
<Alert type="error" message={t("jobs.labels.missingprofileinfo")}></Alert>
);
return null;
}

View File

@@ -19,8 +19,7 @@ export default function JobReconciliationModalComponent({ job, bills }) {
const jobLineData = job.joblines.filter(
(j) =>
(j.part_type !== "PAE" && j.act_price !== 0 && j.part_qty !== 0) ||
j.misc_amt !== 0 ||
(j.part_type !== null && j.part_type !== "PAE") ||
(j.line_desc &&
j.line_desc.toLowerCase().includes("towing") &&
j.lbr_op === "OP13") ||

View File

@@ -33,9 +33,7 @@ const JobSearchSelect = (
useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE);
const executeSearch = (v) => {
console.log(v);
if (v && v.variables?.search !== "" && v.variables.search.length >= 2)
callSearch(v);
if (v && v !== "") callSearch(v);
};
const debouncedExecuteSearch = _.debounce(executeSearch, 500);

View File

@@ -1,31 +0,0 @@
import { Button, notification } from "antd";
import axios from "axios";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
export default function JobSendPartPriceChangeComponent({ job }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
try {
const ppcData = await axios.post("/job/ppc", { jobid: job.id });
await axios.post("http://localhost:1337/ppc/", ppcData.data);
} catch (error) {
notification.open({
type: "error",
message: t("jobs.errors.partspricechange", {
error: JSON.stringify(error),
}),
});
} finally {
setLoading(false);
}
};
return (
<Button onClick={handleClick} loading={loading}>
{t("jobs.actions.sendpartspricechange")}
</Button>
);
}

View File

@@ -67,8 +67,7 @@ export function JobsTotalsTableComponent({ jobRO, currentUser, job }) {
<JobTotalsTableTotals job={job} />
</Card>
</Col>
{(currentUser.email.includes("@imex.") ||
currentUser.email.includes("@rome.")) && (
{currentUser.email.includes("@imex.") && (
<Col span={24}>
<Card title="DEVELOPMENT USE ONLY">
<JobCalculateTotals job={job} disabled={jobRO} />

View File

@@ -123,10 +123,11 @@ export default function JobTotalsTableLabor({ job }) {
<Space>
{t("jobs.labels.mapa")}
{job.materials &&
job.materials.MAPA &&
job.materials.MAPA.cal_maxdlr !== undefined &&
job.materials.mapa &&
job.materials.mapa.cal_maxdlr &&
job.materials.mapa.cal_maxdlr > 0 &&
t("jobs.labels.threshhold", {
amount: job.materials.MAPA.cal_maxdlr,
amount: job.materials.mapa.cal_maxdlr,
})}
</Space>
</Table.Summary.Cell>
@@ -147,10 +148,11 @@ export default function JobTotalsTableLabor({ job }) {
<Space wrap>
{t("jobs.labels.mash")}
{job.materials &&
job.materials.MASH &&
job.materials.MASH.cal_maxdlr !== undefined &&
job.materials.mash &&
job.materials.mash.cal_maxdlr &&
job.materials.mash.cal_maxdlr > 0 &&
t("jobs.labels.threshhold", {
amount: job.materials.MASH.cal_maxdlr,
amount: job.materials.mash.cal_maxdlr,
})}
</Space>
</Table.Summary.Cell>

View File

@@ -11,22 +11,6 @@ export default function JobTotalsTableParts({ job }) {
filteredInfo: {},
});
const insuranceAdjustments = useMemo(() => {
if (!job.job_totals) return [];
if (!job.job_totals?.parts?.adjustments) return [];
const adjs = [];
Object.keys(job.job_totals?.parts?.adjustments).forEach((key) => {
if (Dinero(job.job_totals?.parts?.adjustments[key]).getAmount() !== 0) {
adjs.push({
id: key,
amount: Dinero(job.job_totals.parts.adjustments[key]),
});
}
});
return adjs;
}, [job.job_totals]);
const data = useMemo(() => {
return Object.keys(job.job_totals.parts.parts.list)
.filter(
@@ -90,11 +74,11 @@ export default function JobTotalsTableParts({ job }) {
<Table.Summary.Cell>
{t("jobs.labels.prt_dsmk_total")}
</Table.Summary.Cell>
<Table.Summary.Cell align="right">
{Dinero(job.job_totals.parts.parts.prt_dsmk_total).toFormat()}
</Table.Summary.Cell>
</Table.Summary.Row>
<Table.Summary.Row>
<Table.Summary.Cell>
<strong>{t("jobs.labels.partstotal")}</strong>
@@ -106,24 +90,6 @@ export default function JobTotalsTableParts({ job }) {
</strong>
</Table.Summary.Cell>
</Table.Summary.Row>
{insuranceAdjustments.length > 0 && (
<Table.Summary.Row>
<Table.Summary.Cell colSpan={24}>
{t("jobs.labels.profileadjustments")}
</Table.Summary.Cell>
</Table.Summary.Row>
)}
{insuranceAdjustments.map((adj, idx) => (
<Table.Summary.Row key={idx}>
<Table.Summary.Cell>
{t(`jobs.fields.${adj.id.toLowerCase()}`)}
</Table.Summary.Cell>
<Table.Summary.Cell align="right">
{adj.amount.toFormat()}
</Table.Summary.Cell>
</Table.Summary.Row>
))}
</>
)}
/>

View File

@@ -28,109 +28,26 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
total: job.job_totals.totals.subtotal,
bold: true,
},
...(job.job_totals.totals.us_sales_tax_breakdown
{
key: t("jobs.labels.local_tax_amt"),
total: job.job_totals.totals.local_tax,
},
{
key: t("jobs.labels.state_tax_amt"),
total: job.job_totals.totals.state_tax,
},
...(bodyshop.region_config === "CA_BC"
? [
{
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty1?.tax_type1 ||
"T1"
} - ${[
job.cieca_pft.ty1_rate1,
job.cieca_pft.ty1_rate2,
job.cieca_pft.ty1_rate3,
job.cieca_pft.ty1_rate4,
job.cieca_pft.ty1_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty1Tax,
key: t("jobs.fields.ca_bc_pvrt"),
total: job.job_totals.additional.pvrt,
},
{
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty2?.tax_type2 ||
"T2"
} - ${[
job.cieca_pft.ty2_rate1,
job.cieca_pft.ty2_rate2,
job.cieca_pft.ty2_rate3,
job.cieca_pft.ty2_rate4,
job.cieca_pft.ty2_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty2Tax,
},
{
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty3?.tax_type3 ||
"T3"
} - ${[
job.cieca_pft.ty3_rate1,
job.cieca_pft.ty3_rate2,
job.cieca_pft.ty3_rate3,
job.cieca_pft.ty3_rate4,
job.cieca_pft.ty3_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty3Tax,
},
{
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty4?.tax_type4 ||
"T4"
} - ${[
job.cieca_pft.ty4_rate1,
job.cieca_pft.ty4_rate2,
job.cieca_pft.ty4_rate3,
job.cieca_pft.ty4_rate4,
job.cieca_pft.ty4_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty4Tax,
},
{
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty5?.tax_type5 ||
"TT"
} - ${[
job.cieca_pft.ty5_rate1,
job.cieca_pft.ty5_rate2,
job.cieca_pft.ty5_rate3,
job.cieca_pft.ty5_rate4,
job.cieca_pft.ty5_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty5Tax,
},
{
key: t("jobs.labels.total_sales_tax"),
bold: true,
total: Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty1Tax)
.add(
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty2Tax)
)
.add(
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty3Tax)
)
.add(
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty4Tax)
)
.add(
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty5Tax)
).toJSON(),
},
].filter((item) => item.total.amount !== 0)
: [
{
key: t("jobs.labels.state_tax_amt"),
total: job.job_totals.totals.state_tax,
},
]),
]
: []),
{
key: t("jobs.labels.federal_tax_amt"),
total: job.job_totals.totals.federal_tax,
},
{
key: t("jobs.labels.total_repairs"),
total: job.job_totals.totals.total_repairs,
@@ -140,10 +57,10 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
key: t("jobs.fields.ded_amt"),
total: job.job_totals.totals.custPayable.deductible,
},
// {
// key: t("jobs.fields.federal_tax_payable"),
// total: job.job_totals.totals.custPayable.federal_tax,
// },
{
key: t("jobs.fields.federal_tax_payable"),
total: job.job_totals.totals.custPayable.federal_tax,
},
{
key: t("jobs.fields.other_amount_payable"),
total: job.job_totals.totals.custPayable.other_customer_amount,
@@ -164,7 +81,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
bold: true,
},
];
}, [job.job_totals, job.cieca_pft, t, bodyshop.md_responsibility_centers]);
}, [job.job_totals, t, bodyshop.region_config]);
const columns = [
{

View File

@@ -1,19 +1,18 @@
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import { useMutation } from "@apollo/client";
import { Button, Form, notification } from "antd";
import moment from "moment";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import moment from "moment";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { DateTimeFormat } from "./../../utils/DateFormatter";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -39,8 +38,8 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
setLoading(true);
const result = await updateJob({
variables: { jobId: job.id, job: values },
refetchQueries: ["GET_JOB_BY_PK"],
awaitRefetchQueries: true,
refetchQueries: ['GET_JOB_BY_PK'],
awaitRefetchQueries:true
});
const changedAuditFields = form.getFieldsValue(
@@ -54,7 +53,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
operation: AuditTrailMapping.admin_jobfieldchange(
key,
changedAuditFields[key] instanceof moment
? DateTimeFormat(changedAuditFields[key])
? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a")
: changedAuditFields[key]
),
});
@@ -127,10 +126,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
<Form.Item label={t("jobs.fields.actual_in")} name="actual_in">
<DateTimePicker />
</Form.Item>
<Form.Item
label={t("jobs.fields.date_repairstarted")}
name="date_repairstarted"
>
<Form.Item label={t("jobs.fields.date_repairstarted")} name="date_repairstarted">
<DateTimePicker />
</Form.Item>
<Form.Item
@@ -177,15 +173,6 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
>
<DateTimePicker />
</Form.Item>
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
<DateTimePicker />
</Form.Item>
<Form.Item
label={t("jobs.fields.date_lost_sale")}
name="date_lost_sale"
>
<DateTimePicker />
</Form.Item>
</LayoutFormRow>
</Form>

View File

@@ -1,18 +1,19 @@
import { gql, useMutation } from "@apollo/client";
import { useMutation } from "@apollo/client";
import { Button, notification } from "antd";
import { gql } from "@apollo/client";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import moment from "moment";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import moment from "moment";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
@@ -149,10 +150,6 @@ export function JobAdminMarkReexport({
if (!result.errors) {
notification["success"]({ message: t("jobs.successes.save") });
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.admin_jobuninvoice(),
});
} else {
notification["error"]({
message: t("jobs.errors.saving", {

View File

@@ -33,9 +33,8 @@ export function JobsAdminUnvoid({
mutation UNVOID_JOB($jobId: uuid!) {
update_jobs_by_pk(pk_columns: {id: $jobId}, _set: {voided: false, status: "${
bodyshop.md_ro_statuses.default_imported
}", date_void: null}) {
}"}) {
id
date_void
voided
status
}

View File

@@ -1,6 +1,6 @@
import { GET_ALL_JOBLINES_BY_PK } from "../../graphql/jobs-lines.queries";
import { gql } from "@apollo/client";
import _ from "lodash";
import { GET_ALL_JOBLINES_BY_PK } from "../../graphql/jobs-lines.queries";
export const GetSupplementDelta = async (client, jobId, newLines) => {
const {
@@ -25,7 +25,7 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
//Found a relevant matching line. Add it to lines to update.
linesToUpdate.push({
id: existingLines[matchingIndex].id,
newData: { ...newLine, removed: false, act_price_before_ppc: null },
newData: { ...newLine, removed: false },
});
//Splice out item we found for performance.
@@ -50,7 +50,7 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
.reduce((acc, value, idx) => {
return acc + generateRemoveQuery(value, idx);
}, "");
//console.log(insertQueries, updateQueries, removeQueries);
console.log(insertQueries, updateQueries, removeQueries);
if ((insertQueries + updateQueries + removeQueries).trim() === "") {
return new Promise((resolve, reject) => {

View File

@@ -114,7 +114,7 @@ const headerFields = [
"ins_ct_ph",
"ins_ct_phx",
"loss_cat",
//AD2
//ad2
"clmt_ln",
"clmt_fn",
"clmt_title",
@@ -219,16 +219,7 @@ const headerFields = [
"loc_title",
"loc_ph",
"loc_phx",
"loc_ea",
//VEH
"plate_no",
"plate_st",
"v_vin",
"v_model_yr",
"v_make_desc",
"v_model_desc",
"v_options",
"v_color",
"loc_ea"
];
export default headerFields;

View File

@@ -3,12 +3,12 @@ import {
useApolloClient,
useLazyQuery,
useMutation,
useQuery,
useQuery
} from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Button, Col, Row, notification } from "antd";
import { Col, notification, Row } from "antd";
import Axios from "axios";
import _ from "lodash";
import Dinero from "dinero.js";
import moment from "moment";
import queryString from "query-string";
import React, { useCallback, useEffect, useState } from "react";
@@ -20,7 +20,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import {
DELETE_AVAILABLE_JOB,
QUERY_AVAILABLE_JOBS,
QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK,
QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK
} from "../../graphql/available-jobs.queries";
import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries";
import { INSERT_NEW_NOTE } from "../../graphql/notes.queries";
@@ -28,8 +28,9 @@ import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import {
selectBodyshop,
selectCurrentUser,
selectCurrentUser
} from "../../redux/user/user.selectors";
import confirmDialog from "../../utils/asyncConfirm";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import CriticalPartsScan from "../../utils/criticalPartsScan";
import AlertComponent from "../alert/alert.component";
@@ -72,8 +73,6 @@ export function JobsAvailableContainer({
const [selectedJob, setSelectedJob] = useState(null);
const [selectedOwner, setSelectedOwner] = useState(null);
const [partsQueueToggle, setPartsQueueToggle] = useState(bodyshop.md_functionality_toggles.parts_queue_toggle);
const [insertLoading, setInsertLoading] = useState(false);
const [insertNote] = useMutation(INSERT_NEW_NOTE);
@@ -91,15 +90,13 @@ export function JobsAvailableContainer({
const modalSearchState = useState("");
//Import Scenario
const onOwnerFindModalOk = async (lazyData) => {
const onOwnerFindModalOk = async () => {
logImEXEvent("job_import_new");
setOwnerModalVisible(false);
setInsertLoading(true);
const estData = replaceEmpty(
lazyData?.available_jobs_by_pk || estDataRaw.data.available_jobs_by_pk
);
const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk);
if (!(estData && estData.est_data)) {
//We don't have the right data. Error!
@@ -109,25 +106,21 @@ export function JobsAvailableContainer({
});
return;
}
// if (process.env.REACT_APP_COUNTRY === "USA") {
//Massage the CCC file set to remove duplicate UNQ_SEQ.
await ResolveCCCLineIssues(estData.est_data, bodyshop);
// } else {
//IO-539 Check for Parts Rate on PAL for SGI use case.
await CheckTaxRates(estData.est_data, bodyshop);
// }
// const newTotals = (
// await Axios.post("/job/totals", {
// job: {
// ...estData.est_data,
// joblines: estData.est_data.joblines.data,
// },
// })
// ).data;
const newTotals = (
await Axios.post("/job/totals", {
job: {
...estData.est_data,
joblines: estData.est_data.joblines.data,
},
})
).data;
let existingVehicles;
if (estData.est_data.v_vin) {
//There's vehicle data, need to double-check the VIN.
//There's vehicle data, need to double check the VIN.
existingVehicles = await client.query({
query: SEARCH_VEHICLE_BY_VIN,
variables: {
@@ -138,11 +131,10 @@ export function JobsAvailableContainer({
const newJob = {
...estData.est_data,
// clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
// owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
// job_totals: newTotals,
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
job_totals: newTotals,
date_open: moment(),
status: bodyshop.md_ro_statuses.default_imported,
notes: {
data: {
created_by: currentUser.email,
@@ -150,7 +142,7 @@ export function JobsAvailableContainer({
text: t("jobs.labels.importnote"),
},
},
queued_for_parts: partsQueueToggle,
queued_for_parts: true,
...(existingVehicles && existingVehicles.data.vehicles.length > 0
? { vehicleid: existingVehicles.data.vehicles[0].id, vehicle: null }
: {}),
@@ -164,57 +156,46 @@ export function JobsAvailableContainer({
delete newJob.vehicle;
}
if (typeof newJob.kmin === "string") {
newJob.kmin = null;
}
insertNewJob({
variables: {
job: newJob,
},
})
.then((r) => {
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(r.data.insert_jobs.returning[0].id);
}
notification["success"]({
message: t("jobs.successes.created"),
onClick: () => {
history.push(`/manage/jobs/${r.data.insert_jobs.returning[0].id}`);
},
});
//Job has been inserted. Clean up the available jobs record.
try {
const r = await insertNewJob({
variables: {
job: newJob,
},
});
await Axios.post("/job/totalsssu", {
id: r.data.insert_jobs.returning[0].id,
});
insertAuditTrail({
jobid: r.data.insert_jobs.returning[0].id,
operation: AuditTrailMapping.jobimported(),
});
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(r.data.insert_jobs.returning[0].id);
}
notification["success"]({
message: t("jobs.successes.created"),
onClick: () => {
history.push(`/manage/jobs/${r.data.insert_jobs.returning[0].id}`);
},
});
//Job has been inserted. Clean up the available jobs record.
insertAuditTrail({
jobid: r.data.insert_jobs.returning[0].id,
operation: AuditTrailMapping.jobimported(),
});
await deleteJob({
variables: { id: estData.id },
}).then((r) => {
deleteJob({
variables: { id: estData.id },
}).then((r) => {
refetch();
setInsertLoading(false);
});
})
.catch((r) => {
//error while inserting
notification["error"]({
message: t("jobs.errors.creating", { error: r.message }),
});
refetch();
setInsertLoading(false);
});
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
} catch (r) {
//error while inserting
notification["error"]({
message: t("jobs.errors.creating", { error: r.message }),
});
refetch();
setInsertLoading(false);
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
}
};
//Supplement scenario
//Suplement scenario
const onJobFindModalOk = async () => {
logImEXEvent("job_import_supplement");
@@ -234,7 +215,6 @@ export function JobsAvailableContainer({
let supp = replaceEmpty({ ...estData.est_data });
//IO-539 Check for Parts Rate on PAL for SGI use case.
await CheckTaxRates(supp, bodyshop);
await ResolveCCCLineIssues(supp, bodyshop);
delete supp.owner;
delete supp.vehicle;
@@ -267,14 +247,11 @@ export function JobsAvailableContainer({
// "0.00"
// ),
// job_totals: newTotals,
queued_for_parts: partsQueueToggle,
// queued_for_parts: true,
},
},
});
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
if (CriticalPartsScanning.treatment === "on") {
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id);
}
if (updateResult.errors) {
@@ -349,14 +326,12 @@ export function JobsAvailableContainer({
const onOwnerModalCancel = () => {
setOwnerModalVisible(false);
setSelectedOwner(null);
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
};
const onJobModalCancel = () => {
setJobModalVisible(false);
modalSearchState[1]("");
setSelectedJob(null);
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
};
const addJobAsNew = (record) => {
@@ -377,8 +352,6 @@ export function JobsAvailableContainer({
}, [addJobAsSupp, availableJobId, clm_no]);
if (error) return <AlertComponent type="error" message={error.message} />;
return (
<LoadingSpinner
loading={insertLoading}
@@ -388,14 +361,11 @@ export function JobsAvailableContainer({
loading={estDataRaw.loading}
error={estDataRaw.error}
owner={owner}
partsQueueToggle={partsQueueToggle}
setPartsQueueToggle={setPartsQueueToggle}
selectedOwner={selectedOwner}
setSelectedOwner={setSelectedOwner}
visible={ownerModalVisible}
onOk={onOwnerFindModalOk}
onCancel={onOwnerModalCancel}
/>
<JobsFindModalContainer
loading={estDataRaw.loading}
@@ -407,28 +377,7 @@ export function JobsAvailableContainer({
onOk={onJobFindModalOk}
onCancel={onJobModalCancel}
modalSearchState={modalSearchState}
partsQueueToggle={partsQueueToggle}
setPartsQueueToggle={setPartsQueueToggle}
/>
{currentUser.email.includes("@rome.") ||
currentUser.email.includes("@imex.") ? (
<Button
onClick={async () => {
for (const record of data.available_jobs) {
//Query the data
console.log("Start Job", record.id);
const { data } = await loadEstData({
variables: { id: record.id },
});
console.log("Query has been awaited and is complete");
await onOwnerFindModalOk(data);
}
}}
>
Add all jobs as new.
</Button>
) : null}
<Row gutter={[16, 16]}>
<Col span={24}>
<JobsAvailableTableComponent
@@ -460,158 +409,115 @@ function replaceEmpty(someObj, replaceValue = null) {
}
async function CheckTaxRates(estData, bodyshop) {
// //LKQ Check
// if (
// !estData.parts_tax_rates?.PAL ||
// estData.parts_tax_rates?.PAL?.prt_tax_rt === null ||
// estData.parts_tax_rates?.PAL?.prt_tax_rt === 0
// ) {
// const res = await confirmDialog(
// `Rome Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
// );
// if (res) {
// if (!estData.parts_tax_rates.PAL) {
// estData.parts_tax_rates.PAL = {
// prt_discp: 0,
// prt_mktyp: true,
// prt_mkupp: 0,
// prt_type: "PAL",
// };
// }
// estData.parts_tax_rates.PAL.prt_tax_rt =
// bodyshop.bill_tax_rates.state_tax_rate / 100;
// estData.parts_tax_rates.PAL.prt_tax_in = true;
// }
// }
// //PAC Check
// if (
// !estData.parts_tax_rates?.PAC ||
// estData.parts_tax_rates?.PAC?.prt_tax_rt === null ||
// estData.parts_tax_rates?.PAC?.prt_tax_rt === 0
// ) {
// const res = await confirmDialog(
// `Rome Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
// );
// if (res) {
// if (!estData.parts_tax_rates.PAC) {
// estData.parts_tax_rates.PAC = {
// prt_discp: 0,
// prt_mktyp: true,
// prt_mkupp: 0,
// prt_type: "PAC",
// };
// }
// estData.parts_tax_rates.PAC.prt_tax_rt =
// bodyshop.bill_tax_rates.state_tax_rate / 100;
// estData.parts_tax_rates.PAC.prt_tax_in = true;
// }
// }
//LKQ Check
if (
!estData.parts_tax_rates?.PAL ||
estData.parts_tax_rates?.PAL?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAL?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAL) {
estData.parts_tax_rates.PAL = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAL",
};
}
estData.parts_tax_rates.PAL.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAL.prt_tax_in = true;
}
}
//PAC Check
if (
!estData.parts_tax_rates?.PAC ||
estData.parts_tax_rates?.PAC?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAC?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAC) {
estData.parts_tax_rates.PAC = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAC",
};
}
estData.parts_tax_rates.PAC.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAC.prt_tax_in = true;
}
}
//PAM Check
if (!estData.parts_tax_rates?.PAM) {
estData.parts_tax_rates.PAM = estData.parts_tax_rates.PAC;
if (
!estData.parts_tax_rates?.PAM ||
estData.parts_tax_rates?.PAM?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAM?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAM) {
estData.parts_tax_rates.PAM = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAM",
};
}
estData.parts_tax_rates.PAM.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAM.prt_tax_in = true;
}
}
// //PAM Check
// if (
// !estData.parts_tax_rates?.PAM ||
// estData.parts_tax_rates?.PAM?.prt_tax_rt === null ||
// estData.parts_tax_rates?.PAM?.prt_tax_rt === 0
// ) {
// const res = await confirmDialog(
// `Rome Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
// );
// if (res) {
// if (!estData.parts_tax_rates.PAM) {
// estData.parts_tax_rates.PAM = {
// prt_discp: 0,
// prt_mktyp: true,
// prt_mkupp: 0,
// prt_type: "PAM",
// };
// }
// estData.parts_tax_rates.PAM.prt_tax_rt =
// bodyshop.bill_tax_rates.state_tax_rate / 100;
// estData.parts_tax_rates.PAM.prt_tax_in = true;
// }
// }
// if (
// !estData.parts_tax_rates?.PAR ||
// estData.parts_tax_rates?.PAR?.prt_tax_rt === null ||
// estData.parts_tax_rates?.PAR?.prt_tax_rt === 0
// ) {
// const res = await confirmDialog(
// `Rome Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
// );
// if (res) {
// if (!estData.parts_tax_rates.PAR) {
// estData.parts_tax_rates.PAR = {
// prt_discp: 0,
// prt_mktyp: true,
// prt_mkupp: 0,
// prt_type: "PAR",
// };
// }
// estData.parts_tax_rates.PAR.prt_tax_rt =
// bodyshop.bill_tax_rates.state_tax_rate / 100;
// estData.parts_tax_rates.PAR.prt_tax_in = true;
// }
// }
if (
!estData.parts_tax_rates?.PAR ||
estData.parts_tax_rates?.PAR?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAR?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAR) {
estData.parts_tax_rates.PAR = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAR",
};
}
estData.parts_tax_rates.PAR.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAR.prt_tax_in = true;
}
}
//IO-1387 If a sublet line is NOT R&R, use the labor tax. If it is, use the sublet tax rate.
//Currently limited to SK shops only.
if (bodyshop.region_config === "CA_SK") {
estData.joblines.data.forEach((jl, index) => {
if (
(jl.part_type === "PASL" || jl.part_type === "PAS") &&
jl.lbr_op !== "OP11"
) {
estData.joblines.data[index].tax_part = jl.lbr_tax;
}
//if (bodyshop.region_config === "CA_SK") {
estData.joblines.data.forEach((jl, index) => {
if (
(jl.part_type === "PASL" || jl.part_type === "PAS") &&
jl.lbr_op !== "OP11"
) {
estData.joblines.data[index].tax_part = jl.lbr_tax;
}
//Set markup lines and tax lines as taxable.
//900510 is a mark up. 900510 is a discount.
if (jl.db_ref === "900510") {
estData.joblines.data[index].tax_part = true;
}
});
}
}
async function ResolveCCCLineIssues(estData, bodyshop) {
//Find all misc amounts, populate them to the act price.
//TODO Ensure that this doesnt get violated
//This needs to be done before cleansing unq_seq since some misc prices could move over.
estData.joblines.data.forEach((line) => {
if (line.misc_amt && line.misc_amt !== 0) {
line.act_price = line.act_price + line.misc_amt;
line.tax_part = !!line.misc_tax;
//Set markup lines and tax lines as taxable.
//900510 is a mark up. 900510 is a discount.
if (jl.db_ref === "900510") {
estData.joblines.data[index].tax_part = true;
}
});
//Generate the list of duplicated UNQ_SEQ that will feed into the next section to scrub the lines.
const unqSeqHash = _.groupBy(estData.joblines.data, "unq_seq");
const duplicatedUnqSeq = Object.keys(unqSeqHash).filter(
(key) => unqSeqHash[key].length > 1
);
duplicatedUnqSeq.forEach((unq_seq) => {
//Keys are strings, convert to int.
const int_unq_seq = parseInt(unq_seq);
//When line splitting, the first line is always the non-refinish line. We will keep it as is.
//We will cleanse the second line, which is always the next line.
const nonRefLineIndex = estData.joblines.data.findIndex(
(line) => line.unq_seq === int_unq_seq
);
estData.joblines.data[nonRefLineIndex + 1] = {
...estData.joblines.data[nonRefLineIndex + 1],
part_type: null,
act_price: 0,
db_price: 0,
prt_dsmk_p: 0,
prt_dsmk_m: 0,
};
});
//}
}

View File

@@ -36,8 +36,6 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
ret.profitcenter_part = defaults.profits["MAPA"];
} else if (lineDesc.includes("ats amount")) {
ret.profitcenter_part = defaults.profits["ATS"];
} else if (jl.act_price > 0) {
ret.profitcenter_part = defaults.profits["PAO"];
} else {
ret.profitcenter_part = null;
}

View File

@@ -4,35 +4,22 @@ import axios from "axios";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { auth } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { useHistory } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
});
function updateJobCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
fields: {
jobs(existingJobs = []) {
return existingJobs.filter(
(jobRef) => jobRef.__ref.includes(items) === false
);
},
},
});
}
export function JobsCloseExportButton({
bodyshop,
currentUser,
@@ -114,9 +101,6 @@ export function JobsCloseExportButton({
//Check to see if any of them failed. If they didn't don't execute the update.
const failedTransactions = PartnerResponse.data.filter((r) => !r.success);
const successfulTransactions = PartnerResponse.data.filter(
(r) => r.success
);
if (failedTransactions.length > 0) {
//Uh oh. At least one was no good.
failedTransactions.forEach((ft) => {
@@ -175,15 +159,12 @@ export function JobsCloseExportButton({
},
});
if (!!!jobUpdateResponse.errors) {
if (!jobUpdateResponse.errors) {
notification.open({
type: "success",
key: "jobsuccessexport",
message: t("jobs.successes.exported"),
});
updateJobCache(
jobUpdateResponse.data.update_jobs.returning.map((job) => job.id)
);
} else {
notification["error"]({
message: t("jobs.errors.exporting", {
@@ -192,31 +173,13 @@ export function JobsCloseExportButton({
});
}
}
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
notification.open({
type: "success",
key: "jobsuccessexport",
message: t("jobs.successes.exported"),
});
updateJobCache([
...new Set(
successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
? "jobid"
: "id"
]
)
),
]);
}
if (setSelectedJobs) {
setSelectedJobs((selectedJobs) => {
return selectedJobs.filter((i) => i !== jobId);
});
}
}
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
setLoading(false);
};

View File

@@ -108,7 +108,7 @@ export function JobsConvertButton({
},
]}
>
<Select showSearch>
<Select>
{bodyshop.md_ins_cos.map((s, i) => (
<Select.Option key={i} value={s.name}>
{s.name}

Some files were not shown because too many files have changed in this diff Show More