Compare commits
2 Commits
release/20
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f070af7ab | ||
|
|
bc4b6fa007 |
@@ -19,6 +19,3 @@ npx deadfile ./src/index.js --exclude build templates
|
||||
hasura migrate create "Init" --from-server --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
||||
hasura migrate apply --version "1620771761757" --skip-execution --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
||||
hasura migrate status --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
||||
|
||||
Generate the license file:
|
||||
$ generate-license-file --input package.json --output third-party-licenses.txt --overwrite
|
||||
|
||||
@@ -1149,48 +1149,6 @@
|
||||
<folder_node>
|
||||
<name>fields</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>cc</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>contents</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>created</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -1233,48 +1191,6 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>subject</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>to</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>useremail</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -11233,27 +11149,6 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>dateinpast</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>dlexpirebeforereturn</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -12475,27 +12370,6 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>leasereturn</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>out</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -14861,27 +14735,6 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>vacationadded</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
@@ -27448,27 +27301,6 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>emailaudit</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>employeeassignments</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -27511,27 +27343,6 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>estimator</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>existing_jobs</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -32732,48 +32543,6 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>systemnotes</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>usernotes</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
@@ -32930,27 +32699,6 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>saving</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>selectexistingornew</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -33355,27 +33103,6 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>tax_number</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
@@ -40660,27 +40387,6 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>parts_not_recieved_vendor</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>payments_by_date</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -40933,27 +40639,6 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>psr_by_make</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>purchases_by_cost_center_detail</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -41384,69 +41069,6 @@
|
||||
<folder_node>
|
||||
<name>labels</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>employeevacation</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>intake</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>manual</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>manualevent</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -41724,48 +41346,6 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>efficiencyoverperiod</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>entries</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>jobs</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.6.9",
|
||||
"@apollo/client": "^3.6.6",
|
||||
"@asseinfo/react-kanban": "^2.2.0",
|
||||
"@craco/craco": "^6.4.5",
|
||||
"@craco/craco": "^6.4.3",
|
||||
"@fingerprintjs/fingerprintjs": "^3.3.3",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@sentry/react": "^7.7.0",
|
||||
"@sentry/tracing": "^7.7.0",
|
||||
"@splitsoftware/splitio-react": "^1.6.0",
|
||||
"@stripe/react-stripe-js": "^1.9.0",
|
||||
"@stripe/stripe-js": "^1.32.0",
|
||||
"@tanem/react-nprogress": "^5.0.8",
|
||||
"antd": "^4.22.3",
|
||||
"@sentry/react": "^7.1.1",
|
||||
"@sentry/tracing": "^7.1.1",
|
||||
"@splitsoftware/splitio-react": "^1.4.1",
|
||||
"@stripe/react-stripe-js": "^1.8.1",
|
||||
"@stripe/stripe-js": "^1.31.0",
|
||||
"@tanem/react-nprogress": "^5.0.1",
|
||||
"antd": "^4.21.0",
|
||||
"apollo-link-logger": "^2.0.0",
|
||||
"axios": "^0.27.2",
|
||||
"craco-less": "^1.20.0",
|
||||
@@ -24,33 +24,33 @@
|
||||
"enquire-js": "^0.2.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
"firebase": "^9.9.1",
|
||||
"firebase": "^9.8.2",
|
||||
"graphql": "^16.5.0",
|
||||
"i18next": "^21.8.14",
|
||||
"i18next": "^21.8.9",
|
||||
"i18next-browser-languagedetector": "^6.1.4",
|
||||
"jsoneditor": "^9.9.0",
|
||||
"jsoneditor": "^9.8.0",
|
||||
"jsreport-browser-client-dist": "^1.3.0",
|
||||
"libphonenumber-js": "^1.10.9",
|
||||
"logrocket": "^3.0.1",
|
||||
"markerjs2": "^2.22.0",
|
||||
"libphonenumber-js": "^1.10.6",
|
||||
"logrocket": "^3.0.0",
|
||||
"markerjs2": "^2.21.4",
|
||||
"moment-business-days": "^1.2.0",
|
||||
"moment-timezone": "^0.5.34",
|
||||
"normalize-url": "^7.0.3",
|
||||
"phone": "^3.1.23",
|
||||
"phone": "^3.1.20",
|
||||
"preval.macro": "^5.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^7.1.1",
|
||||
"rc-queue-anim": "^2.0.0",
|
||||
"rc-scroll-anim": "^2.7.6",
|
||||
"react": "^17.0.2",
|
||||
"react-big-calendar": "^1.5.0",
|
||||
"react-big-calendar": "^0.40.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^4.1.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-drag-listview": "^0.2.1",
|
||||
"react-grid-gallery": "^0.5.5",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-i18next": "^11.18.1",
|
||||
"react-i18next": "^11.17.0",
|
||||
"react-icons": "^4.4.0",
|
||||
"react-number-format": "^4.9.3",
|
||||
"react-redux": "^7.2.8",
|
||||
@@ -60,13 +60,13 @@
|
||||
"react-sticky": "^6.0.3",
|
||||
"react-sublime-video": "^0.2.5",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"recharts": "^2.1.12",
|
||||
"recharts": "^2.1.10",
|
||||
"redux": "^4.2.0",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.1.3",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"redux-state-sync": "^3.1.2",
|
||||
"reselect": "^4.1.6",
|
||||
"sass": "^1.54.0",
|
||||
"sass": "^1.51.0",
|
||||
"socket.io-client": "^4.5.1",
|
||||
"styled-components": "^5.3.5",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
@@ -119,9 +119,9 @@
|
||||
"react-error-overlay": "6.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/webpack-plugin": "^1.19.0",
|
||||
"@testing-library/cypress": "^8.0.3",
|
||||
"cypress": "^10.3.1",
|
||||
"@sentry/webpack-plugin": "^1.18.9",
|
||||
"@testing-library/cypress": "^8.0.2",
|
||||
"cypress": "^9.6.1",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"react-error-overlay": "6.0.11",
|
||||
"redux-logger": "^3.0.6",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -142,9 +142,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Update row highlighting on production board.
|
||||
.ant-table-tbody > tr.ant-table-row:hover > td {
|
||||
background: #eaeaea !important;
|
||||
}
|
||||
@@ -4,8 +4,6 @@ import { useQuery } from "@apollo/client";
|
||||
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import EmailAuditTrailListComponent from "./email-audit-trail-list.component";
|
||||
import { Card, Row } from "antd";
|
||||
|
||||
export default function AuditTrailListContainer({ recordId }) {
|
||||
const { loading, error, data } = useQuery(QUERY_AUDIT_TRAIL, {
|
||||
@@ -20,20 +18,10 @@ export default function AuditTrailListContainer({ recordId }) {
|
||||
{error ? (
|
||||
<AlertComponent type="error" message={error.message} />
|
||||
) : (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Card>
|
||||
<AuditTrailListComponent
|
||||
loading={loading}
|
||||
data={data ? data.audit_trail : []}
|
||||
/>
|
||||
</Card>
|
||||
<Card>
|
||||
<EmailAuditTrailListComponent
|
||||
loading={loading}
|
||||
data={data ? data.audit_trail : []}
|
||||
/>
|
||||
</Card>
|
||||
</Row>
|
||||
<AuditTrailListComponent
|
||||
loading={loading}
|
||||
data={data ? data.audit_trail : null}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { Table } from "antd";
|
||||
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";
|
||||
|
||||
export default function EmailAuditTrailListComponent({ loading, data }) {
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: {},
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
const columns = [
|
||||
{
|
||||
title: t("audit.fields.created"),
|
||||
dataIndex: " created",
|
||||
key: " created",
|
||||
width: "10%",
|
||||
render: (text, record) => (
|
||||
<DateTimeFormatter>{record.created}</DateTimeFormatter>
|
||||
),
|
||||
sorter: (a, b) => a.created - b.created,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "created" && state.sortedInfo.order,
|
||||
},
|
||||
|
||||
{
|
||||
title: t("audit.fields.useremail"),
|
||||
dataIndex: "useremail",
|
||||
key: "useremail",
|
||||
width: "10%",
|
||||
sorter: (a, b) => alphaSort(a.useremail, b.useremail),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "useremail" && state.sortedInfo.order,
|
||||
},
|
||||
];
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
xs: { span: 12 },
|
||||
sm: { span: 5 },
|
||||
},
|
||||
wrapperCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 12 },
|
||||
},
|
||||
};
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
return (
|
||||
<Table
|
||||
{...formItemLayout}
|
||||
loading={loading}
|
||||
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={data}
|
||||
onChange={handleTableChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Form, PageHeader, Popconfirm, Space } from "antd";
|
||||
import moment from "moment";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
DELETE_BILL_LINE,
|
||||
INSERT_NEW_BILL_LINES,
|
||||
UPDATE_BILL_LINE
|
||||
} from "../../graphql/bill-lines.queries";
|
||||
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import BillFormContainer from "../bill-form/bill-form.container";
|
||||
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
||||
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
|
||||
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
|
||||
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import BillDetailEditReturn from "./bill-detail-edit-return.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPartsOrderContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(BillDetailEditcontainer);
|
||||
|
||||
export function BillDetailEditcontainer({
|
||||
setPartsOrderContext,
|
||||
insertAuditTrail,
|
||||
bodyshop,
|
||||
}) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [updateLoading, setUpdateLoading] = useState(false);
|
||||
const [update_bill] = useMutation(UPDATE_BILL);
|
||||
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
|
||||
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
|
||||
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
|
||||
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
|
||||
variables: { billid: search.billid },
|
||||
skip: !!!search.billid,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
//It's got a previously deducted bill line!
|
||||
if (
|
||||
data.bills_by_pk.billlines.filter((b) => b.deductedfromlbr).length > 0 ||
|
||||
form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length >
|
||||
0
|
||||
)
|
||||
setVisible(true);
|
||||
else {
|
||||
form.submit();
|
||||
}
|
||||
};
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
setUpdateLoading(true);
|
||||
//let adjustmentsToInsert = {};
|
||||
|
||||
const { billlines, upload, ...bill } = values;
|
||||
const updates = [];
|
||||
updates.push(
|
||||
update_bill({
|
||||
variables: { billId: search.billid, bill: bill },
|
||||
})
|
||||
);
|
||||
|
||||
billlines.forEach((l) => {
|
||||
delete l.selected;
|
||||
});
|
||||
|
||||
//Find bill lines that were deleted.
|
||||
const deletedJobLines = [];
|
||||
|
||||
data.bills_by_pk.billlines.forEach((a) => {
|
||||
const matchingRecord = billlines.find((b) => b.id === a.id);
|
||||
if (!matchingRecord) {
|
||||
deletedJobLines.push(a);
|
||||
}
|
||||
});
|
||||
|
||||
deletedJobLines.forEach((d) => {
|
||||
updates.push(deleteBillLine({ variables: { id: d.id } }));
|
||||
});
|
||||
|
||||
billlines.forEach((billline) => {
|
||||
const { deductedfromlbr, inventories, jobline, ...il } = billline;
|
||||
delete il.__typename;
|
||||
|
||||
if (il.id) {
|
||||
updates.push(
|
||||
updateBillLine({
|
||||
variables: {
|
||||
billLineId: il.id,
|
||||
billLine: {
|
||||
...il,
|
||||
deductedfromlbr: deductedfromlbr,
|
||||
joblineid: il.joblineid === "noline" ? null : il.joblineid,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
} else {
|
||||
//It's a new line, have to insert it.
|
||||
updates.push(
|
||||
insertBillLine({
|
||||
variables: {
|
||||
billLines: [
|
||||
{
|
||||
...il,
|
||||
deductedfromlbr: deductedfromlbr,
|
||||
billid: search.billid,
|
||||
joblineid: il.joblineid === "noline" ? null : il.joblineid,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(updates);
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: bill.jobid,
|
||||
billid: search.billid,
|
||||
operation: AuditTrailMapping.billupdated(bill.invoice_number),
|
||||
});
|
||||
|
||||
await refetch();
|
||||
form.setFieldsValue(transformData(data));
|
||||
form.resetFields();
|
||||
setVisible(false);
|
||||
setUpdateLoading(false);
|
||||
};
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
||||
|
||||
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && <LoadingSkeleton />}
|
||||
{data && (
|
||||
<>
|
||||
<PageHeader
|
||||
title={
|
||||
data &&
|
||||
`${data.bills_by_pk.invoice_number} - ${data.bills_by_pk.vendor.name}`
|
||||
}
|
||||
extra={
|
||||
<Space>
|
||||
<BillDetailEditReturn data={data} />
|
||||
|
||||
<Popconfirm
|
||||
visible={visible}
|
||||
onConfirm={() => form.submit()}
|
||||
onCancel={() => setVisible(false)}
|
||||
okButtonProps={{ loading: updateLoading }}
|
||||
title={t("bills.labels.editadjwarning")}
|
||||
>
|
||||
<Button
|
||||
htmlType="submit"
|
||||
disabled={exported}
|
||||
onClick={handleSave}
|
||||
loading={updateLoading}
|
||||
type="primary"
|
||||
>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
|
||||
<BillMarkExportedButton bill={data && data.bills_by_pk} />
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleFinish}
|
||||
initialValues={transformData(data)}
|
||||
layout="vertical"
|
||||
>
|
||||
<BillFormContainer form={form} billEdit disabled={exported} />
|
||||
|
||||
{bodyshop.uselocalmediaserver ? (
|
||||
<JobsDocumentsLocalGallery
|
||||
job={{ id: data ? data.bills_by_pk.jobid : null }}
|
||||
invoice_number={data ? data.bills_by_pk.invoice_number : null}
|
||||
vendorid={data ? data.bills_by_pk.vendorid : null}
|
||||
/>
|
||||
) : (
|
||||
<JobDocumentsGallery
|
||||
jobId={data ? data.bills_by_pk.jobid : null}
|
||||
billId={search.billid}
|
||||
documentsList={data ? data.bills_by_pk.documents : []}
|
||||
billsCallback={refetch}
|
||||
/>
|
||||
)}
|
||||
</Form>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const transformData = (data) => {
|
||||
return data
|
||||
? {
|
||||
...data.bills_by_pk,
|
||||
|
||||
billlines: data.bills_by_pk.billlines.map((i) => {
|
||||
return {
|
||||
...i,
|
||||
joblineid: !!i.joblineid ? i.joblineid : "noline",
|
||||
applicable_taxes: {
|
||||
federal:
|
||||
(i.applicable_taxes && i.applicable_taxes.federal) || false,
|
||||
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
|
||||
local: (i.applicable_taxes && i.applicable_taxes.local) || false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null,
|
||||
}
|
||||
: {};
|
||||
};
|
||||
@@ -1,185 +0,0 @@
|
||||
import { Button, Checkbox, Form, Modal } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPartsOrderContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(BillDetailEditReturn);
|
||||
|
||||
export function BillDetailEditReturn({
|
||||
setPartsOrderContext,
|
||||
insertAuditTrail,
|
||||
bodyshop,
|
||||
data,
|
||||
disabled,
|
||||
}) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const handleFinish = ({ billlines }) => {
|
||||
const selectedLines = billlines.filter((l) => l.selected).map((l) => l.id);
|
||||
|
||||
setPartsOrderContext({
|
||||
actions: {},
|
||||
context: {
|
||||
jobId: data.bills_by_pk.jobid,
|
||||
vendorId: data.bills_by_pk.vendorid,
|
||||
returnFromBill: data.bills_by_pk.id,
|
||||
invoiceNumber: data.bills_by_pk.invoice_number,
|
||||
linesToOrder: data.bills_by_pk.billlines
|
||||
.filter((l) => selectedLines.includes(l.id))
|
||||
.map((i) => {
|
||||
return {
|
||||
line_desc: i.line_desc,
|
||||
// db_price: i.actual_price,
|
||||
act_price: i.actual_price,
|
||||
cost: i.actual_cost,
|
||||
quantity: i.quantity,
|
||||
joblineid: i.joblineid,
|
||||
oem_partno: i.jobline && i.jobline.oem_partno,
|
||||
part_type: i.jobline && i.jobline.part_type,
|
||||
};
|
||||
}),
|
||||
isReturn: true,
|
||||
},
|
||||
});
|
||||
delete search.billid;
|
||||
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
setVisible(false);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (visible === false) form.resetFields();
|
||||
}, [visible, form]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(false)}
|
||||
destroyOnClose
|
||||
title={t("bills.actions.return")}
|
||||
onOk={() => form.submit()}
|
||||
>
|
||||
<Form
|
||||
initialValues={data && data.bills_by_pk}
|
||||
onFinish={handleFinish}
|
||||
form={form}
|
||||
>
|
||||
<Form.List name={["billlines"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
<table style={{ tableLayout: "auto", width: "100%" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<Checkbox
|
||||
onChange={(e) => {
|
||||
form.setFieldsValue({
|
||||
billlines: form
|
||||
.getFieldsValue()
|
||||
.billlines.map((b) => ({
|
||||
...b,
|
||||
selected: e.target.checked,
|
||||
})),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td>{t("billlines.fields.line_desc")}</td>
|
||||
<td>{t("billlines.fields.quantity")}</td>
|
||||
<td>{t("billlines.fields.actual_price")}</td>
|
||||
<td>{t("billlines.fields.actual_cost")}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{fields.map((field, index) => (
|
||||
<tr key={field.key}>
|
||||
<td>
|
||||
<Form.Item
|
||||
// label={t("joblines.fields.selected")}
|
||||
key={`${index}selected`}
|
||||
name={[field.name, "selected"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
</td>
|
||||
<td>
|
||||
<Form.Item
|
||||
// label={t("joblines.fields.line_desc")}
|
||||
key={`${index}line_desc`}
|
||||
name={[field.name, "line_desc"]}
|
||||
>
|
||||
<ReadOnlyFormItemComponent />
|
||||
</Form.Item>
|
||||
</td>
|
||||
<td>
|
||||
<Form.Item
|
||||
// label={t("joblines.fields.quantity")}
|
||||
key={`${index}quantity`}
|
||||
name={[field.name, "quantity"]}
|
||||
>
|
||||
<ReadOnlyFormItemComponent />
|
||||
</Form.Item>
|
||||
</td>
|
||||
<td>
|
||||
<Form.Item
|
||||
// label={t("joblines.fields.actual_price")}
|
||||
key={`${index}actual_price`}
|
||||
name={[field.name, "actual_price"]}
|
||||
>
|
||||
<ReadOnlyFormItemComponent type="currency" />
|
||||
</Form.Item>
|
||||
</td>
|
||||
<td>
|
||||
<Form.Item
|
||||
// label={t("joblines.fields.actual_cost")}
|
||||
key={`${index}actual_cost`}
|
||||
name={[field.name, "actual_cost"]}
|
||||
>
|
||||
<ReadOnlyFormItemComponent type="currency" />
|
||||
</Form.Item>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
</Form>
|
||||
</Modal>
|
||||
<Button
|
||||
disabled={data.bills_by_pk.is_credit_memo || disabled}
|
||||
onClick={() => {
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
{t("bills.actions.return")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,68 @@
|
||||
import { Drawer, Grid } from "antd";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Drawer,
|
||||
Form,
|
||||
Grid,
|
||||
PageHeader,
|
||||
Popconfirm,
|
||||
Space,
|
||||
} from "antd";
|
||||
import moment from "moment";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import BillDetailEditComponent from "./bill-detail-edit-component";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
DELETE_BILL_LINE,
|
||||
INSERT_NEW_BILL_LINES,
|
||||
UPDATE_BILL_LINE,
|
||||
} from "../../graphql/bill-lines.queries";
|
||||
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import BillFormContainer from "../bill-form/bill-form.container";
|
||||
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
||||
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
|
||||
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
|
||||
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
|
||||
export default function BillDetailEditcontainer() {
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPartsOrderContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(BillDetailEditcontainer);
|
||||
|
||||
export function BillDetailEditcontainer({
|
||||
setPartsOrderContext,
|
||||
insertAuditTrail,
|
||||
bodyshop,
|
||||
}) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [updateLoading, setUpdateLoading] = useState(false);
|
||||
const [update_bill] = useMutation(UPDATE_BILL);
|
||||
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
|
||||
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
|
||||
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
|
||||
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
@@ -24,6 +80,114 @@ export default function BillDetailEditcontainer() {
|
||||
? bpoints[selectedBreakpoint[0]]
|
||||
: "100%";
|
||||
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
|
||||
variables: { billid: search.billid },
|
||||
skip: !!!search.billid,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
//It's got a previously deducted bill line!
|
||||
if (
|
||||
data.bills_by_pk.billlines.filter((b) => b.deductedfromlbr).length > 0 ||
|
||||
form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length >
|
||||
0
|
||||
)
|
||||
setVisible(true);
|
||||
else {
|
||||
form.submit();
|
||||
}
|
||||
};
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
setUpdateLoading(true);
|
||||
//let adjustmentsToInsert = {};
|
||||
|
||||
const { billlines, upload, ...bill } = values;
|
||||
const updates = [];
|
||||
updates.push(
|
||||
update_bill({
|
||||
variables: { billId: search.billid, bill: bill },
|
||||
})
|
||||
);
|
||||
|
||||
//Find bill lines that were deleted.
|
||||
const deletedJobLines = [];
|
||||
|
||||
data.bills_by_pk.billlines.forEach((a) => {
|
||||
const matchingRecord = billlines.find((b) => b.id === a.id);
|
||||
if (!matchingRecord) {
|
||||
deletedJobLines.push(a);
|
||||
}
|
||||
});
|
||||
|
||||
deletedJobLines.forEach((d) => {
|
||||
updates.push(deleteBillLine({ variables: { id: d.id } }));
|
||||
});
|
||||
|
||||
billlines.forEach((billline) => {
|
||||
const { deductedfromlbr, inventories, jobline, ...il } = billline;
|
||||
delete il.__typename;
|
||||
|
||||
if (il.id) {
|
||||
updates.push(
|
||||
updateBillLine({
|
||||
variables: {
|
||||
billLineId: il.id,
|
||||
billLine: {
|
||||
...il,
|
||||
deductedfromlbr: deductedfromlbr,
|
||||
joblineid: il.joblineid === "noline" ? null : il.joblineid,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
} else {
|
||||
//It's a new line, have to insert it.
|
||||
updates.push(
|
||||
insertBillLine({
|
||||
variables: {
|
||||
billLines: [
|
||||
{
|
||||
...il,
|
||||
deductedfromlbr: deductedfromlbr,
|
||||
billid: search.billid,
|
||||
joblineid: il.joblineid === "noline" ? null : il.joblineid,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(updates);
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: bill.jobid,
|
||||
billid: search.billid,
|
||||
operation: AuditTrailMapping.billupdated(bill.invoice_number),
|
||||
});
|
||||
|
||||
await refetch();
|
||||
form.setFieldsValue(transformData(data));
|
||||
form.resetFields();
|
||||
setVisible(false);
|
||||
setUpdateLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (search.billid && data) {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [form, search.billid, data]);
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
||||
|
||||
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
width={drawerPercentage}
|
||||
@@ -31,10 +195,119 @@ export default function BillDetailEditcontainer() {
|
||||
delete search.billid;
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
}}
|
||||
destroyOnClose
|
||||
visible={search.billid}
|
||||
>
|
||||
<BillDetailEditComponent />
|
||||
{loading && <LoadingSkeleton />}
|
||||
{!loading && (
|
||||
<>
|
||||
<PageHeader
|
||||
title={
|
||||
data &&
|
||||
`${data.bills_by_pk.invoice_number} - ${data.bills_by_pk.vendor.name}`
|
||||
}
|
||||
extra={
|
||||
<Space>
|
||||
<Button
|
||||
disabled={data.bills_by_pk.is_credit_memo}
|
||||
onClick={() => {
|
||||
delete search.billid;
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
setPartsOrderContext({
|
||||
actions: {},
|
||||
context: {
|
||||
jobId: data.bills_by_pk.jobid,
|
||||
vendorId: data.bills_by_pk.vendorid,
|
||||
returnFromBill: data.bills_by_pk.id,
|
||||
invoiceNumber: data.bills_by_pk.invoice_number,
|
||||
linesToOrder: data.bills_by_pk.billlines.map((i) => {
|
||||
return {
|
||||
line_desc: i.line_desc,
|
||||
// db_price: i.actual_price,
|
||||
act_price: i.actual_price,
|
||||
cost: i.actual_cost,
|
||||
quantity: i.quantity,
|
||||
joblineid: i.joblineid,
|
||||
oem_partno: i.jobline && i.jobline.oem_partno,
|
||||
part_type: i.jobline && i.jobline.part_type,
|
||||
};
|
||||
}),
|
||||
isReturn: true,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("bills.actions.return")}
|
||||
</Button>
|
||||
|
||||
<Popconfirm
|
||||
visible={visible}
|
||||
onConfirm={() => form.submit()}
|
||||
onCancel={() => setVisible(false)}
|
||||
okButtonProps={{ loading: updateLoading }}
|
||||
title={t("bills.labels.editadjwarning")}
|
||||
>
|
||||
<Button
|
||||
htmlType="submit"
|
||||
disabled={exported}
|
||||
onClick={handleSave}
|
||||
loading={updateLoading}
|
||||
type="primary"
|
||||
>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
|
||||
<BillMarkExportedButton bill={data && data.bills_by_pk} />
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleFinish}
|
||||
initialValues={transformData(data)}
|
||||
layout="vertical"
|
||||
>
|
||||
<BillFormContainer form={form} billEdit disabled={exported} />
|
||||
|
||||
{bodyshop.uselocalmediaserver ? (
|
||||
<JobsDocumentsLocalGallery
|
||||
job={{ id: data ? data.bills_by_pk.jobid : null }}
|
||||
invoice_number={data ? data.bills_by_pk.invoice_number : null}
|
||||
vendorid={data ? data.bills_by_pk.vendorid : null}
|
||||
/>
|
||||
) : (
|
||||
<JobDocumentsGallery
|
||||
jobId={data ? data.bills_by_pk.jobid : null}
|
||||
billId={search.billid}
|
||||
documentsList={data ? data.bills_by_pk.documents : []}
|
||||
billsCallback={refetch}
|
||||
/>
|
||||
)}
|
||||
</Form>
|
||||
</>
|
||||
)}
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
const transformData = (data) => {
|
||||
return data
|
||||
? {
|
||||
...data.bills_by_pk,
|
||||
|
||||
billlines: data.bills_by_pk.billlines.map((i) => {
|
||||
return {
|
||||
...i,
|
||||
joblineid: !!i.joblineid ? i.joblineid : "noline",
|
||||
applicable_taxes: {
|
||||
federal:
|
||||
(i.applicable_taxes && i.applicable_taxes.federal) || false,
|
||||
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
|
||||
local: (i.applicable_taxes && i.applicable_taxes.local) || false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null,
|
||||
}
|
||||
: {};
|
||||
};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import {
|
||||
Button, Form,
|
||||
Button,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Table,
|
||||
Tooltip
|
||||
Tooltip,
|
||||
} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -17,8 +17,9 @@ import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CiecaSelect from "../../utils/Ciecaselect";
|
||||
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
|
||||
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
|
||||
@@ -27,10 +27,6 @@ const BillLineSearchSelect = (
|
||||
option.oem_partno
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())) ||
|
||||
(option.alt_partno &&
|
||||
option.alt_partno
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())) ||
|
||||
(option.act_price &&
|
||||
option.act_price.toString().startsWith(inputValue.toString()))
|
||||
);
|
||||
@@ -52,17 +48,14 @@ const BillLineSearchSelect = (
|
||||
line_desc={item.line_desc}
|
||||
part_qty={item.part_qty}
|
||||
oem_partno={item.oem_partno}
|
||||
alt_partno={item.alt_partno}
|
||||
act_price={item.act_price}
|
||||
style={{
|
||||
...(item.removed ? { textDecoration: "line-through" } : {}),
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
|
||||
</span>
|
||||
<span>{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}`}</span>
|
||||
<span style={{ float: "right", paddingleft: "1rem" }}>
|
||||
{item.act_price
|
||||
? `$${item.act_price && item.act_price.toFixed(2)}`
|
||||
|
||||
@@ -12,7 +12,6 @@ import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
|
||||
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
|
||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -59,15 +58,39 @@ export function BillsListTableComponent({
|
||||
</Button>
|
||||
)}
|
||||
<BillDeleteButton bill={record} />
|
||||
<BillDetailEditReturnComponent
|
||||
data={{ bills_by_pk: { ...record, jobid: job.id } }}
|
||||
<Button
|
||||
disabled={
|
||||
record.is_credit_memo ||
|
||||
record.vendorid === bodyshop.inhousevendorid ||
|
||||
jobRO
|
||||
}
|
||||
/>
|
||||
|
||||
onClick={() => {
|
||||
setPartsOrderContext({
|
||||
actions: {},
|
||||
context: {
|
||||
jobId: job.id,
|
||||
vendorId: record.vendorid,
|
||||
returnFromBill: record.id,
|
||||
invoiceNumber: record.invoice_number,
|
||||
linesToOrder: record.billlines.map((i) => {
|
||||
return {
|
||||
line_desc: i.line_desc,
|
||||
// db_price: i.actual_price,
|
||||
act_price: i.actual_price,
|
||||
cost: i.actual_cost,
|
||||
quantity: i.quantity,
|
||||
joblineid: i.joblineid,
|
||||
oem_partno: i.jobline && i.jobline.oem_partno,
|
||||
part_type: i.jobline && i.jobline.part_type,
|
||||
};
|
||||
}),
|
||||
isReturn: true,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("bills.actions.return")}
|
||||
</Button>
|
||||
{record.isinhouse && (
|
||||
<PrintWrapperComponent
|
||||
templateObject={{
|
||||
|
||||
@@ -9,7 +9,6 @@ import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component";
|
||||
import JobDocumentsLocalGalleryExternal from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -59,24 +58,17 @@ export function ChatMediaSelector({
|
||||
{selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
|
||||
<div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
|
||||
) : null}
|
||||
{!bodyshop.uselocalmediaserver && data && (
|
||||
{data && (
|
||||
<JobDocumentsGalleryExternal
|
||||
data={data ? data.documents : []}
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
/>
|
||||
)}
|
||||
{bodyshop.uselocalmediaserver && visible && (
|
||||
<JobDocumentsLocalGalleryExternal
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
jobId={
|
||||
conversation.job_conversations[0] &&
|
||||
conversation.job_conversations[0].jobid
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (bodyshop.uselocalmediaserver) return null;
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
|
||||
@@ -279,80 +279,31 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
<Form.Item label={t("courtesycars.fields.notes")} name="notes">
|
||||
<Input.TextArea />
|
||||
</Form.Item>
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.registrationexpires")}
|
||||
name="registrationexpires"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
shouldUpdate={(p, c) =>
|
||||
p.registrationexpires !== c.registrationexpires
|
||||
}
|
||||
>
|
||||
{() => {
|
||||
const expires = form.getFieldValue("registrationexpires");
|
||||
|
||||
const dateover = expires && moment(expires).isBefore(moment());
|
||||
|
||||
if (dateover)
|
||||
return (
|
||||
<Space direction="vertical" style={{ color: "tomato" }}>
|
||||
<span>
|
||||
<WarningFilled style={{ marginRight: ".3rem" }} />
|
||||
{t("contracts.labels.dateinpast")}
|
||||
</span>
|
||||
</Space>
|
||||
);
|
||||
|
||||
return <></>;
|
||||
}}
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.insuranceexpires")}
|
||||
name="insuranceexpires"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
shouldUpdate={(p, c) =>
|
||||
p.insuranceexpires !== c.insuranceexpires
|
||||
}
|
||||
>
|
||||
{() => {
|
||||
const expires = form.getFieldValue("insuranceexpires");
|
||||
|
||||
const dateover = expires && moment(expires).isBefore(moment());
|
||||
|
||||
if (dateover)
|
||||
return (
|
||||
<Space direction="vertical" style={{ color: "tomato" }}>
|
||||
<span>
|
||||
<WarningFilled style={{ marginRight: ".3rem" }} />
|
||||
{t("contracts.labels.dateinpast")}
|
||||
</span>
|
||||
</Space>
|
||||
);
|
||||
|
||||
return <></>;
|
||||
}}
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.registrationexpires")}
|
||||
name="registrationexpires"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.insuranceexpires")}
|
||||
name="insuranceexpires"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("courtesycars.fields.dailycost")} name="dailycost">
|
||||
<CurrencyInput />
|
||||
</Form.Item>
|
||||
|
||||
@@ -34,9 +34,6 @@ const CourtesyCarStatusComponent = ({ value, onChange }, ref) => {
|
||||
<Option value="courtesycars.status.sold">
|
||||
{t("courtesycars.status.sold")}
|
||||
</Option>
|
||||
<Option value="courtesycars.status.leasereturn">
|
||||
{t("courtesycars.status.leasereturn")}
|
||||
</Option>
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -52,10 +52,6 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
text: t("courtesycars.status.sold"),
|
||||
value: "courtesycars.status.sold",
|
||||
},
|
||||
{
|
||||
text: t("courtesycars.status.leasereturn"),
|
||||
value: "courtesycars.status.leasereturn",
|
||||
},
|
||||
],
|
||||
onFilter: (value, record) => value.includes(record.status),
|
||||
sortOrder:
|
||||
|
||||
@@ -5,15 +5,12 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
|
||||
import { selectEmailConfig } from "../../redux/email/email.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component";
|
||||
import JobsDocumentsLocalGalleryExternalComponent from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
emailConfig: selectEmailConfig,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
@@ -28,7 +25,6 @@ export function EmailDocumentsComponent({
|
||||
emailConfig,
|
||||
form,
|
||||
selectedMediaState,
|
||||
bodyshop,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -56,18 +52,12 @@ export function EmailDocumentsComponent({
|
||||
10485760 - new Blob([form.getFieldValue("html")]).size ? (
|
||||
<div style={{ color: "red" }}>{t("general.errors.sizelimit")}</div>
|
||||
) : null}
|
||||
{!bodyshop.uselocalmediaserver && data && (
|
||||
{data && (
|
||||
<JobDocumentsGalleryExternal
|
||||
data={data ? data.documents : []}
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
/>
|
||||
)}
|
||||
{bodyshop.uselocalmediaserver && (
|
||||
<JobsDocumentsLocalGalleryExternalComponent
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
jobId={emailConfig.jobid}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -160,13 +160,14 @@ export function EmailOverlayComponent({
|
||||
</Form.Item>
|
||||
|
||||
<Tabs>
|
||||
<Tabs.TabPane tab={t("emails.labels.documents")} key="documents">
|
||||
<EmailDocumentsComponent
|
||||
selectedMediaState={selectedMediaState}
|
||||
form={form}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
|
||||
{!bodyshop.uselocalmediaserver && (
|
||||
<Tabs.TabPane tab={t("emails.labels.documents")} key="documents">
|
||||
<EmailDocumentsComponent
|
||||
selectedMediaState={selectedMediaState}
|
||||
form={form}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
)}
|
||||
<Tabs.TabPane tab={t("emails.labels.attachments")} key="attachments">
|
||||
{bodyshop.uselocalmediaserver && emailConfig.jobid && (
|
||||
<a href={CreateExplorerLinkForJob({ jobid: emailConfig.jobid })}>
|
||||
|
||||
@@ -77,9 +77,6 @@ export function EmailOverlayContainer({
|
||||
setSending(true);
|
||||
try {
|
||||
await axios.post("/sendemail", {
|
||||
bodyshopid: bodyshop.id,
|
||||
jobid: emailConfig.jobid,
|
||||
|
||||
...defaultEmailFrom,
|
||||
ReplyTo: {
|
||||
Email: from,
|
||||
@@ -184,11 +181,10 @@ export function EmailOverlayContainer({
|
||||
loading: sending,
|
||||
disabled:
|
||||
selectedMedia &&
|
||||
(selectedMedia
|
||||
( (selectedMedia
|
||||
.filter((s) => s.isSelected)
|
||||
.reduce((acc, val) => (acc = acc + val.size), 0) >=
|
||||
10485760 - new Blob([form.getFieldValue("html")]).size ||
|
||||
selectedMedia.filter((s) => s.isSelected).length > 10),
|
||||
10485760 - new Blob([form.getFieldValue("html")]).size) || selectedMedia.filter((s) => s.isSelected).length > 10),
|
||||
}}
|
||||
>
|
||||
<Form layout="vertical" form={form} onFinish={handleFinish}>
|
||||
|
||||
@@ -5,7 +5,7 @@ import AlertComponent from "../alert/alert.component";
|
||||
import { Prompt, useLocation } from "react-router-dom";
|
||||
import "./form-fields-changed.styles.scss";
|
||||
|
||||
export default function FormsFieldChanged({ form, skipPrompt }) {
|
||||
export default function FormsFieldChanged({ form }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleReset = () => {
|
||||
@@ -25,7 +25,7 @@ export default function FormsFieldChanged({ form, skipPrompt }) {
|
||||
return (
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
<Prompt
|
||||
when={skipPrompt ? false : true}
|
||||
when={true}
|
||||
message={(location) => {
|
||||
if (loc.pathname === location.pathname) return false;
|
||||
return t("general.messages.unsavedchangespopup");
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Button, Card, Col, Row, Table, Tag } from "antd";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { Card, Table } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobAuditTrail);
|
||||
|
||||
export function JobAuditTrail({ currentUser, jobId }) {
|
||||
export default function JobAuditTrail({ jobId }) {
|
||||
const { t } = useTranslation();
|
||||
const { loading, data, refetch } = useQuery(QUERY_AUDIT_TRAIL, {
|
||||
const { loading, data } = useQuery(QUERY_AUDIT_TRAIL, {
|
||||
variables: { jobid: jobId },
|
||||
skip: !jobId,
|
||||
fetchPolicy: "network-only",
|
||||
@@ -45,104 +34,15 @@ export function JobAuditTrail({ currentUser, jobId }) {
|
||||
key: "operation",
|
||||
},
|
||||
];
|
||||
const emailColumns = [
|
||||
{
|
||||
title: t("audit.fields.created"),
|
||||
dataIndex: " created_at",
|
||||
key: " created_at",
|
||||
|
||||
render: (text, record) => (
|
||||
<DateTimeFormatter>{record.created_at}</DateTimeFormatter>
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
title: t("audit.fields.useremail"),
|
||||
dataIndex: "useremail",
|
||||
key: "useremail",
|
||||
},
|
||||
|
||||
{
|
||||
title: t("audit.fields.to"),
|
||||
dataIndex: "to",
|
||||
key: "to",
|
||||
|
||||
render: (text, record) =>
|
||||
record.to &&
|
||||
record.to.map((email, idx) => <Tag key={idx}>{email}</Tag>),
|
||||
},
|
||||
{
|
||||
title: t("audit.fields.cc"),
|
||||
dataIndex: "cc",
|
||||
key: "cc",
|
||||
|
||||
render: (text, record) =>
|
||||
record.cc &&
|
||||
record.cc.map((email, idx) => <Tag key={idx}>{email}</Tag>),
|
||||
},
|
||||
{
|
||||
title: t("audit.fields.subject"),
|
||||
dataIndex: "subject",
|
||||
key: "subject",
|
||||
},
|
||||
...(currentUser?.email.includes("@imex.")
|
||||
? [
|
||||
{
|
||||
title: t("audit.fields.contents"),
|
||||
dataIndex: "contents",
|
||||
key: "contents",
|
||||
width: "10%",
|
||||
render: (text, record) => (
|
||||
<Button
|
||||
onClick={() => {
|
||||
var win = window.open(
|
||||
"",
|
||||
"Title",
|
||||
"toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=780,height=400,"
|
||||
);
|
||||
win.document.body.innerHTML = record.contents;
|
||||
}}
|
||||
>
|
||||
Preview
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Card
|
||||
title={t("jobs.labels.audit")}
|
||||
extra={
|
||||
<Button
|
||||
onClick={() => {
|
||||
refetch();
|
||||
}}
|
||||
>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={data ? data.audit_trail : []}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Card title={t("jobs.labels.emailaudit")}>
|
||||
<Table
|
||||
loading={loading}
|
||||
columns={emailColumns}
|
||||
rowKey="id"
|
||||
dataSource={data ? data.email_audit_trail : []}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Card title={t("jobs.labels.audit")}>
|
||||
<Table
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={data ? data.audit_trail : []}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,10 +115,7 @@ export function JobLinesComponent({
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
|
||||
ellipsis: true,
|
||||
render: (text, record) =>
|
||||
`${record.oem_partno || ""} ${
|
||||
record.alt_partno ? `(${record.alt_partno})` : ""
|
||||
}`.trim(),
|
||||
render: (text, record) => record.oem_partno,
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.op_code_desc"),
|
||||
@@ -144,26 +141,11 @@ export function JobLinesComponent({
|
||||
filters: [
|
||||
{
|
||||
text: t("jobs.labels.partsfilter"),
|
||||
value: [
|
||||
"PAN",
|
||||
"PAC",
|
||||
"PAR",
|
||||
"PAL",
|
||||
"PAA",
|
||||
"PAM",
|
||||
"PAP",
|
||||
"PAS",
|
||||
"PASL",
|
||||
"PAG",
|
||||
],
|
||||
value: ["PAN", "PAP", "PAL", "PAA", "PAS", "PASL"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAN"),
|
||||
value: ["PAN"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAP"),
|
||||
value: ["PAP"],
|
||||
value: ["PAN", "PAP"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAL"),
|
||||
@@ -173,29 +155,9 @@ export function JobLinesComponent({
|
||||
text: t("joblines.fields.part_types.PAA"),
|
||||
value: ["PAA"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAG"),
|
||||
value: ["PAG"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAS"),
|
||||
value: ["PAS"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PASL"),
|
||||
value: ["PASL"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAC"),
|
||||
value: ["PAC"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAR"),
|
||||
value: ["PAR"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAM"),
|
||||
value: ["PAM"],
|
||||
value: ["PAS", "PASL"],
|
||||
},
|
||||
],
|
||||
onFilter: (value, record) => value.includes(record.part_type),
|
||||
@@ -381,11 +343,7 @@ export function JobLinesComponent({
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
filteredInfo: filters,
|
||||
sortedInfo: sorter,
|
||||
}));
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
const handleMark = (e) => {
|
||||
@@ -503,12 +461,7 @@ export function JobLinesComponent({
|
||||
context: {
|
||||
jobId: job.id,
|
||||
job: job,
|
||||
linesToOrder: selectedLines.map((l) => ({
|
||||
...l,
|
||||
oem_partno: `${l.oem_partno || ""} ${
|
||||
l.alt_partno ? `(${l.alt_partno})` : ""
|
||||
}`.trim(),
|
||||
})),
|
||||
linesToOrder: selectedLines,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -521,24 +474,12 @@ export function JobLinesComponent({
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setState((state) => ({
|
||||
setState({
|
||||
...state,
|
||||
filteredInfo: {
|
||||
...state.filteredInfo,
|
||||
part_type: [
|
||||
"PAN",
|
||||
"PAC",
|
||||
"PAR",
|
||||
"PAL",
|
||||
"PAA",
|
||||
"PAM",
|
||||
"PAP",
|
||||
"PAS",
|
||||
"PASL",
|
||||
"PAG",
|
||||
],
|
||||
part_type: ["PAN,PAC,PAR,PAL,PAA,PAM,PAP,PAS,PASL,PAG"],
|
||||
},
|
||||
}));
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FilterFilled /> {t("jobs.actions.filterpartsonly")}
|
||||
|
||||
@@ -66,8 +66,10 @@ export function JobLineConvertToLabor({
|
||||
const newAdjustments = _.cloneDeep(
|
||||
existingAdjustments.data.jobs_by_pk.lbr_adjustments
|
||||
);
|
||||
const adjustment = calculateAdjustment({ mod_lbr_ty, job, jobline });
|
||||
newAdjustments[mod_lbr_ty] = (newAdjustments[mod_lbr_ty] || 0) + adjustment;
|
||||
|
||||
newAdjustments[mod_lbr_ty] =
|
||||
(newAdjustments[mod_lbr_ty] || 0) +
|
||||
calculateAdjustment({ mod_lbr_ty, job, jobline });
|
||||
|
||||
const jobUpdate = client.mutate({
|
||||
mutation: UPDATE_JOB,
|
||||
@@ -81,13 +83,7 @@ export function JobLineConvertToLabor({
|
||||
mutation: UPDATE_JOB_LINE,
|
||||
variables: {
|
||||
lineId: jobline.id,
|
||||
line: {
|
||||
convertedtolbr: true,
|
||||
convertedtolbr_data: {
|
||||
mod_lbr_ty: mod_lbr_ty,
|
||||
mod_lb_hrs: adjustment,
|
||||
},
|
||||
},
|
||||
line: { convertedtolbr: true },
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Form, notification } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
@@ -38,8 +37,6 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
||||
setLoading(true);
|
||||
const result = await updateJob({
|
||||
variables: { jobId: job.id, job: values },
|
||||
refetchQueries: ['GET_JOB_BY_PK'],
|
||||
awaitRefetchQueries:true
|
||||
});
|
||||
|
||||
const changedAuditFields = form.getFieldsValue(
|
||||
@@ -68,8 +65,6 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
||||
}),
|
||||
});
|
||||
}
|
||||
form.resetFields();
|
||||
form.resetFields();
|
||||
setLoading(false);
|
||||
//Get the owner details, populate it all back into the job.
|
||||
};
|
||||
@@ -92,7 +87,6 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
||||
: null,
|
||||
}}
|
||||
>
|
||||
<FormFieldsChanged form={form} />
|
||||
<LayoutFormRow header={t("jobs.forms.estdates")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.date_estimated")}
|
||||
|
||||
@@ -95,7 +95,7 @@ mutation UNVOID_JOB($jobId: uuid!) {
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.admin_jobunvoid(),
|
||||
operation: AuditTrailMapping.admin_unvoicejob(),
|
||||
});
|
||||
} else {
|
||||
notification["error"]({
|
||||
|
||||
@@ -45,11 +45,9 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
|
||||
return acc + generateUpdateQuery(value, idx);
|
||||
}, "");
|
||||
|
||||
const removeQueries = existingLines
|
||||
.filter((l) => !l.manual_line)
|
||||
.reduce((acc, value, idx) => {
|
||||
return acc + generateRemoveQuery(value, idx);
|
||||
}, "");
|
||||
const removeQueries = existingLines.reduce((acc, value, idx) => {
|
||||
return acc + generateRemoveQuery(value, idx);
|
||||
}, "");
|
||||
console.log(insertQueries, updateQueries, removeQueries);
|
||||
|
||||
if ((insertQueries + updateQueries + removeQueries).trim() === "") {
|
||||
|
||||
@@ -205,7 +205,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseLines);
|
||||
|
||||
const HasBeenConvertedTolabor = ({ value }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
console.log(value);
|
||||
if (!value) return null;
|
||||
return (
|
||||
<Tooltip title={t("joblines.labels.convertedtolabor")}>
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Form,
|
||||
Input,
|
||||
Menu,
|
||||
notification,
|
||||
Popover,
|
||||
Select,
|
||||
Space,
|
||||
} from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobsDetailHeaderAddEvent);
|
||||
|
||||
export function JobsDetailHeaderAddEvent({ bodyshop, jobid, ...props }) {
|
||||
const { t } = useTranslation();
|
||||
const [insertAppointment] = useMutation(INSERT_MANUAL_APPT);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [visibility, setVisibility] = useState(false);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
logImEXEvent("schedule_manual_event");
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
insertAppointment({
|
||||
variables: {
|
||||
apt: { ...values, isintake: false, jobid, bodyshopid: bodyshop.id },
|
||||
},
|
||||
refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"],
|
||||
});
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("appointments.successes.created"),
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setVisibility(false);
|
||||
}
|
||||
};
|
||||
|
||||
const overlay = (
|
||||
<Card>
|
||||
<div>
|
||||
<Form form={form} layout="vertical" onFinish={handleFinish}>
|
||||
<Form.Item
|
||||
label={t("appointments.fields.title")}
|
||||
name="title"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("appointments.fields.note")} name="note">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("appointments.fields.start")}
|
||||
name="start"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormDateTimePickerComponent
|
||||
onBlur={() => {
|
||||
const start = form.getFieldValue("start");
|
||||
form.setFieldsValue({ end: start.add(30, "minutes") });
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("appointments.fields.end")}
|
||||
name="end"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
async validator(rule, value) {
|
||||
if (value) {
|
||||
const { start } = form.getFieldsValue();
|
||||
if (moment(start).isAfter(moment(value))) {
|
||||
return Promise.reject(
|
||||
t("employees.labels.endmustbeafterstart")
|
||||
);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<FormDateTimePickerComponent />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("appointments.fields.color")} name="color">
|
||||
<Select>
|
||||
{bodyshop.appt_colors.map((col, idx) => (
|
||||
<Select.Option key={idx} value={col.color.hex}>
|
||||
{col.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Space wrap>
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
<Button onClick={() => setVisibility(false)}>
|
||||
{t("general.actions.cancel")}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
const handleClick = (e) => {
|
||||
setVisibility(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover content={overlay} visible={visibility}>
|
||||
<Menu.Item {...props} onClick={handleClick}>
|
||||
{t("appointments.labels.manualevent")}
|
||||
</Menu.Item>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent";
|
||||
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
||||
import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component";
|
||||
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
|
||||
@@ -422,7 +421,6 @@ export function JobsDetailHeaderActions({
|
||||
</Popconfirm>
|
||||
</Menu.Item>
|
||||
)}
|
||||
<JobsDetailHeaderActionsAddevent jobid={job.id} />
|
||||
{!jobRO && job.converted && (
|
||||
<Menu.Item>
|
||||
<Popconfirm
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Card, Col, Row, Space, Tag } from "antd";
|
||||
import {
|
||||
WarningFilled,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
WarningFilled,
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Col, Row, Space, Tag } from "antd";
|
||||
import React from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -13,17 +13,17 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
import JobAltTransportChange from "../job-at-change/job-at-change.component";
|
||||
import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container";
|
||||
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
|
||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
|
||||
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
import "./jobs-detail-header.styles.scss";
|
||||
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
|
||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
@@ -56,6 +56,12 @@ const colSpan = {
|
||||
export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const jobInPostProduction = useMemo(() => {
|
||||
return bodyshop.md_ro_statuses.post_production_statuses.includes(
|
||||
job.status
|
||||
);
|
||||
}, [job.status, bodyshop.md_ro_statuses.post_production_statuses]);
|
||||
|
||||
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""}
|
||||
${job.v_make_desc || ""}
|
||||
${job.v_model_desc || ""}`.trim();
|
||||
@@ -123,10 +129,11 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
</DataLabel>
|
||||
)}
|
||||
|
||||
<DataLabel label={t("jobs.fields.production_vars.note")}>
|
||||
<ProductionListColumnProductionNote record={job} />
|
||||
</DataLabel>
|
||||
|
||||
{(job.inproduction || jobInPostProduction) && (
|
||||
<DataLabel label={t("jobs.fields.production_vars.note")}>
|
||||
<ProductionListColumnProductionNote record={job} />
|
||||
</DataLabel>
|
||||
)}
|
||||
<Space>
|
||||
{job.special_coverage_policy && (
|
||||
<Tag color="tomato">
|
||||
@@ -174,11 +181,6 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
<DataLabel key="4" label={t("owners.fields.ownr_ea")}>
|
||||
{job.ownr_ea || ""}
|
||||
</DataLabel>
|
||||
{job.owner?.tax_number && (
|
||||
<DataLabel key="5" label={t("owners.fields.tax_number")}>
|
||||
{job.owner?.tax_number || ""}
|
||||
</DataLabel>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SyncOutlined, FileExcelFilled } from "@ant-design/icons";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { Button, Card, Space } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import Gallery from "react-grid-gallery";
|
||||
@@ -58,24 +58,6 @@ export function JobsDocumentsLocalGallery({
|
||||
}
|
||||
}, [job, invoice_number, getJobMedia, getBillMedia]);
|
||||
|
||||
const jobMedia =
|
||||
allMedia && allMedia[job.id]
|
||||
? allMedia[job.id].reduce(
|
||||
(acc, val) => {
|
||||
if (
|
||||
val.type &&
|
||||
val.type.mime &&
|
||||
val.type.mime.startsWith("image")
|
||||
) {
|
||||
acc.images.push(val);
|
||||
} else {
|
||||
acc.other.push(val);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ images: [], other: [] }
|
||||
)
|
||||
: { images: [], other: [] };
|
||||
return (
|
||||
<div>
|
||||
<Space wrap>
|
||||
@@ -108,7 +90,7 @@ export function JobsDocumentsLocalGallery({
|
||||
</Card>
|
||||
<Card title={t("jobs.labels.documents-images")}>
|
||||
<Gallery
|
||||
images={jobMedia.images}
|
||||
images={(allMedia && allMedia[job.id]) || []}
|
||||
backdropClosesModal={true}
|
||||
onSelectImage={(index, image) => {
|
||||
toggleMediaSelected({ jobid: job.id, filename: image.filename });
|
||||
@@ -122,31 +104,6 @@ export function JobsDocumentsLocalGallery({
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
<Card title={t("jobs.labels.documents-other")}>
|
||||
<Gallery
|
||||
images={jobMedia.other}
|
||||
backdropClosesModal={true}
|
||||
enableLightbox={false}
|
||||
thumbnailStyle={() => {
|
||||
return {
|
||||
backgroundImage: <FileExcelFilled />,
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
cursor: "pointer",
|
||||
};
|
||||
}}
|
||||
onClickThumbnail={(index) => {
|
||||
window.open(
|
||||
jobMedia.other[index].src,
|
||||
"_blank",
|
||||
"toolbar=0,location=0,menubar=0"
|
||||
);
|
||||
}}
|
||||
onSelectImage={(index, image) => {
|
||||
toggleMediaSelected({ jobid: job.id, filename: image.filename });
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import React, { useEffect } from "react";
|
||||
import Gallery from "react-grid-gallery";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
getJobMedia,
|
||||
toggleMediaSelected,
|
||||
} from "../../redux/media/media.actions";
|
||||
import { selectAllMedia } from "../../redux/media/media.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
allMedia: selectAllMedia,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
getJobMedia: (id) => dispatch(getJobMedia(id)),
|
||||
|
||||
toggleMediaSelected: ({ jobid, filename }) =>
|
||||
dispatch(toggleMediaSelected({ jobid, filename })),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobDocumentsLocalGalleryExternal);
|
||||
|
||||
function JobDocumentsLocalGalleryExternal({
|
||||
jobId,
|
||||
externalMediaState,
|
||||
getJobMedia,
|
||||
toggleMediaSelected,
|
||||
allMedia,
|
||||
}) {
|
||||
const [galleryImages, setgalleryImages] = externalMediaState;
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if ( jobId) {
|
||||
getJobMedia(jobId);
|
||||
}
|
||||
}, [jobId, getJobMedia]);
|
||||
|
||||
useEffect(() => {
|
||||
let documents =
|
||||
allMedia && allMedia[jobId]
|
||||
? allMedia[jobId].reduce((acc, val) => {
|
||||
if (
|
||||
val.type &&
|
||||
val.type.mime &&
|
||||
val.type.mime.startsWith("image")
|
||||
) {
|
||||
acc.push(val);
|
||||
}
|
||||
return acc;
|
||||
}, [])
|
||||
: [];
|
||||
|
||||
setgalleryImages(documents);
|
||||
}, [allMedia, jobId, setgalleryImages, t]);
|
||||
|
||||
return (
|
||||
<div className="clearfix">
|
||||
<Gallery
|
||||
images={galleryImages}
|
||||
backdropClosesModal={true}
|
||||
onSelectImage={(index, image) => {
|
||||
setgalleryImages(
|
||||
galleryImages.map((g, idx) =>
|
||||
index === idx ? { ...g, isSelected: !g.isSelected } : g
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -77,12 +77,6 @@ export function JobsList({ bodyshop }) {
|
||||
(j.v_model_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.est_ct_fn || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.est_ct_ln || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.v_make_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase())
|
||||
@@ -270,32 +264,6 @@ export function JobsList({ bodyshop }) {
|
||||
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.estimator"),
|
||||
dataIndex: "jobs.labels.estimator",
|
||||
key: "jobs.labels.estimator",
|
||||
ellipsis: true,
|
||||
responsive: ["xl"],
|
||||
filterSearch: true,
|
||||
filters:
|
||||
(jobs &&
|
||||
jobs
|
||||
.map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim())
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "N/A",
|
||||
value: [s],
|
||||
};
|
||||
})) ||
|
||||
[],
|
||||
onFilter: (value, record) =>
|
||||
value.includes(
|
||||
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
|
||||
),
|
||||
render: (text, record) =>
|
||||
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.comment"),
|
||||
dataIndex: "comment",
|
||||
|
||||
@@ -14,7 +14,6 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import NoteUpsertModal from "../note-upsert-modal/note-upsert-modal.container";
|
||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||
@@ -41,8 +40,6 @@ export function JobNotesComponent({
|
||||
relatedRos,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [filter, setFilter] = useLocalStorage("filter_job_notes_icons", null);
|
||||
|
||||
const Templates = TemplateList("job_special", {
|
||||
ro_number,
|
||||
});
|
||||
@@ -53,18 +50,6 @@ export function JobNotesComponent({
|
||||
dataIndex: "icons",
|
||||
key: "icons",
|
||||
width: 80,
|
||||
filteredValue: filter?.icons || null,
|
||||
filters: [
|
||||
{
|
||||
text: t("notes.labels.usernotes"),
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
text: t("notes.labels.systemnotes"),
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
onFilter: (value, record) => record.audit === value,
|
||||
render: (text, record) => (
|
||||
<span>
|
||||
{record.critical ? (
|
||||
@@ -146,10 +131,6 @@ export function JobNotesComponent({
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setFilter(filters);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LayoutFormRow>
|
||||
@@ -185,7 +166,6 @@ export function JobNotesComponent({
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={data}
|
||||
onChange={handleTableChange}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -89,12 +89,6 @@ export function JobsReadyList({ bodyshop }) {
|
||||
(j.v_model_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.est_ct_fn || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.est_ct_ln || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.v_make_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase())
|
||||
@@ -282,32 +276,6 @@ export function JobsReadyList({ bodyshop }) {
|
||||
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.estimator"),
|
||||
dataIndex: "jobs.labels.estimator",
|
||||
key: "jobs.labels.estimator",
|
||||
ellipsis: true,
|
||||
responsive: ["xl"],
|
||||
filterSearch: true,
|
||||
filters:
|
||||
(jobs &&
|
||||
jobs
|
||||
.map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim())
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "N/A",
|
||||
value: [s],
|
||||
};
|
||||
})) ||
|
||||
[],
|
||||
onFilter: (value, record) =>
|
||||
value.includes(
|
||||
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
|
||||
),
|
||||
render: (text, record) =>
|
||||
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.comment"),
|
||||
dataIndex: "comment",
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { EditFilled } from "@ant-design/icons";
|
||||
import { Card, Col, Row, Space, Table, Typography } from "antd";
|
||||
import { Card, Col, Row, Space, Table } from "antd";
|
||||
import _ from "lodash";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import LaborAllocationsAdjustmentEdit from "../labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component";
|
||||
import "./labor-allocations-table.styles.scss";
|
||||
@@ -45,10 +44,10 @@ export function LaborAllocationsTable({
|
||||
if (!jobId) setTotals([]);
|
||||
}, [joblines, timetickets, bodyshop, adjustments, jobId]);
|
||||
|
||||
const convertedLines = useMemo(
|
||||
() => joblines && joblines.filter((j) => j.convertedtolbr),
|
||||
[joblines]
|
||||
);
|
||||
// const convertedLines = useMemo(
|
||||
// () => joblines && joblines.filter((j) => j.convertedtolbr),
|
||||
// [joblines]
|
||||
// );
|
||||
|
||||
const columns = [
|
||||
{
|
||||
@@ -121,86 +120,57 @@ export function LaborAllocationsTable({
|
||||
),
|
||||
},
|
||||
];
|
||||
const convertedTableCols = [
|
||||
{
|
||||
title: t("joblines.fields.line_desc"),
|
||||
dataIndex: "line_desc",
|
||||
key: "line_desc",
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.op_code_desc"),
|
||||
dataIndex: "op_code_desc",
|
||||
key: "op_code_desc",
|
||||
ellipsis: true,
|
||||
render: (text, record) =>
|
||||
`${record.op_code_desc || ""}${
|
||||
record.alt_partm ? ` ${record.alt_partm}` : ""
|
||||
}`,
|
||||
},
|
||||
// const convertedTableCols = [
|
||||
// {
|
||||
// title: t("joblines.fields.line_desc"),
|
||||
// dataIndex: "line_desc",
|
||||
// key: "line_desc",
|
||||
// ellipsis: true,
|
||||
// },
|
||||
// {
|
||||
// title: t("joblines.fields.op_code_desc"),
|
||||
// dataIndex: "op_code_desc",
|
||||
// key: "op_code_desc",
|
||||
// ellipsis: true,
|
||||
// render: (text, record) =>
|
||||
// `${record.op_code_desc || ""}${
|
||||
// record.alt_partm ? ` ${record.alt_partm}` : ""
|
||||
// }`,
|
||||
// },
|
||||
|
||||
{
|
||||
title: t("joblines.fields.act_price"),
|
||||
dataIndex: "act_price",
|
||||
key: "act_price",
|
||||
ellipsis: true,
|
||||
render: (text, record) => (
|
||||
<>
|
||||
<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>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.part_qty"),
|
||||
dataIndex: "part_qty",
|
||||
key: "part_qty",
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.mod_lbr_ty"),
|
||||
dataIndex: "conv_mod_lbr_ty",
|
||||
key: "conv_mod_lbr_ty",
|
||||
render: (text, record) =>
|
||||
record.convertedtolbr_data && record.convertedtolbr_data.mod_lbr_ty,
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.mod_lb_hrs"),
|
||||
dataIndex: "conv_mod_lb_hrs",
|
||||
key: "conv_mod_lb_hrs",
|
||||
render: (text, record) =>
|
||||
record.convertedtolbr_data &&
|
||||
record.convertedtolbr_data.mod_lb_hrs &&
|
||||
record.convertedtolbr_data.mod_lb_hrs.toFixed(1),
|
||||
},
|
||||
];
|
||||
// {
|
||||
// title: t("joblines.fields.act_price"),
|
||||
// dataIndex: "act_price",
|
||||
// key: "act_price",
|
||||
// ellipsis: true,
|
||||
// render: (text, record) => (
|
||||
// <>
|
||||
// <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>
|
||||
// ) : (
|
||||
// <></>
|
||||
// )}
|
||||
// </>
|
||||
// ),
|
||||
// },
|
||||
// {
|
||||
// title: t("joblines.fields.part_qty"),
|
||||
// dataIndex: "part_qty",
|
||||
// key: "part_qty",
|
||||
// },
|
||||
// ];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
const summary =
|
||||
totals &&
|
||||
totals.reduce(
|
||||
(acc, val) => {
|
||||
acc.hrs_total += val.total;
|
||||
acc.hrs_claimed += val.claimed;
|
||||
acc.adjustments += val.adjustments;
|
||||
acc.difference += val.difference;
|
||||
return acc;
|
||||
},
|
||||
{ hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0 }
|
||||
);
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
@@ -214,45 +184,26 @@ export function LaborAllocationsTable({
|
||||
scroll={{
|
||||
x: true,
|
||||
}}
|
||||
summary={() => (
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell>
|
||||
<Typography.Title level={4}>
|
||||
{t("general.labels.totals")}
|
||||
</Typography.Title>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{summary.hrs_total.toFixed(1)}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{summary.hrs_claimed.toFixed(1)}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{summary.adjustments.toFixed(1)}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{summary.difference.toFixed(1)}
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
)}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
{convertedLines && convertedLines.length > 0 && (
|
||||
<Col span={24}>
|
||||
<Card title={t("jobs.labels.convertedtolabor")}>
|
||||
<Table
|
||||
columns={convertedTableCols}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
dataSource={convertedLines}
|
||||
scroll={{
|
||||
x: true,
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
)}
|
||||
{
|
||||
// convertedLines && convertedLines.length > 0 && (
|
||||
// <Col span={24}>
|
||||
// <Card title={t("jobs.labels.convertedtolabor")}>
|
||||
// <Table
|
||||
// columns={convertedTableCols}
|
||||
// rowKey="id"
|
||||
// pagination={false}
|
||||
// dataSource={convertedLines}
|
||||
// scroll={{
|
||||
// x: true,
|
||||
// }}
|
||||
// />
|
||||
// </Card>
|
||||
// </Col>
|
||||
// )
|
||||
}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,17 +6,12 @@ export const CalculateAllocationsTotals = (
|
||||
timetickets,
|
||||
adjustments = []
|
||||
) => {
|
||||
console.log(
|
||||
"🚀 ~ file: labor-allocations-table.utility.js ~ line 9 ~ adjustments",
|
||||
adjustments
|
||||
);
|
||||
const responsibilitycenters = bodyshop.md_responsibility_centers;
|
||||
const jobCodes = joblines.map((item) => item.mod_lbr_ty);
|
||||
//.filter((value, index, self) => self.indexOf(value) === index && !!value);
|
||||
const ticketCodes = timetickets.map((item) => item.ciecacode);
|
||||
//.filter((value, index, self) => self.indexOf(value) === index && !!value);
|
||||
const adjustmentCodes = Object.keys(adjustments);
|
||||
const allCodes = [...jobCodes, ...ticketCodes, ...adjustmentCodes].filter(
|
||||
const allCodes = [...jobCodes, ...ticketCodes].filter(
|
||||
(value, index, self) => self.indexOf(value) === index && !!value
|
||||
);
|
||||
|
||||
@@ -39,7 +34,7 @@ export const CalculateAllocationsTotals = (
|
||||
}, 0),
|
||||
};
|
||||
|
||||
r.difference = r.total + r.adjustments - r.claimed;
|
||||
r.difference = (r.total + r.adjustments - r.claimed).toFixed(2);
|
||||
acc.push(r);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
@@ -94,12 +94,6 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("owners.fields.tax_number")}
|
||||
name="tax_number"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Form.Item label={t("owners.fields.note")} name="note">
|
||||
<Input.TextArea rows={4} />
|
||||
|
||||
@@ -20,10 +20,9 @@ function OwnerDetailFormContainer({ owner, refetch }) {
|
||||
if (!!result.errors) {
|
||||
notification["error"]({
|
||||
message: t("owners.errors.saving", {
|
||||
error: JSON.stringify(result.errors),
|
||||
message: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,24 +12,24 @@ export default connect(mapStateToProps, mapDispatchToProps)(OwnerNameDisplay);
|
||||
|
||||
export function OwnerNameDisplay({ bodyshop, ownerObject }) {
|
||||
const emptyTest =
|
||||
ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm;
|
||||
ownerObject.ownr_fn + ownerObject.ownr_ln + ownerObject.ownr_co_nm;
|
||||
|
||||
if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "")
|
||||
return "N/A";
|
||||
|
||||
if (bodyshop.last_name_first)
|
||||
return `${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${
|
||||
ownerObject?.ownr_co_nm || ""
|
||||
return `${ownerObject.ownr_ln || ""}, ${ownerObject.ownr_fn || ""} ${
|
||||
ownerObject.ownr_co_nm || ""
|
||||
}`.trim();
|
||||
|
||||
return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${
|
||||
return `${ownerObject.ownr_fn || ""} ${ownerObject.ownr_ln || ""} ${
|
||||
ownerObject.ownr_co_nm || ""
|
||||
}`.trim();
|
||||
}
|
||||
|
||||
export function OwnerNameDisplayFunction(ownerObject, forceFirstLast = false) {
|
||||
const emptyTest =
|
||||
ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm;
|
||||
ownerObject.ownr_fn + ownerObject.ownr_ln + ownerObject.ownr_co_nm;
|
||||
|
||||
if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "")
|
||||
return "N/A";
|
||||
@@ -37,11 +37,11 @@ export function OwnerNameDisplayFunction(ownerObject, forceFirstLast = false) {
|
||||
const rdxStore = store.getState();
|
||||
|
||||
if (rdxStore.user.bodyshop.last_name_first && !forceFirstLast)
|
||||
return `${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${
|
||||
ownerObject?.ownr_co_nm || ""
|
||||
return `${ownerObject.ownr_ln || ""}, ${ownerObject.ownr_fn || ""} ${
|
||||
ownerObject.ownr_co_nm || ""
|
||||
}`.trim();
|
||||
|
||||
return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${
|
||||
ownerObject?.ownr_co_nm || ""
|
||||
return `${ownerObject.ownr_fn || ""} ${ownerObject.ownr_ln || ""} ${
|
||||
ownerObject.ownr_co_nm || ""
|
||||
}`.trim();
|
||||
}
|
||||
|
||||
@@ -238,8 +238,10 @@ export function ProductionListTable({
|
||||
sticky
|
||||
pagination={false}
|
||||
size="small"
|
||||
{...(Production_List_Status_Colors.treatment === "on" && {
|
||||
onRow: (record, index) => {
|
||||
className="production-list-table"
|
||||
onRow={
|
||||
Production_List_Status_Colors.treatment === "on" &&
|
||||
((record, index) => {
|
||||
if (!bodyshop.md_ro_statuses.production_colors) return null;
|
||||
|
||||
const color = bodyshop.md_ro_statuses.production_colors.find(
|
||||
@@ -253,8 +255,8 @@ export function ProductionListTable({
|
||||
backgroundColor: `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
|
||||
},
|
||||
};
|
||||
},
|
||||
})}
|
||||
})
|
||||
}
|
||||
components={{
|
||||
header: {
|
||||
cell: ResizeableTitle,
|
||||
|
||||
@@ -39,12 +39,7 @@ export function ScheduleCalendarHeaderComponent({
|
||||
const ATSToday = useMemo(() => {
|
||||
if (!events) return [];
|
||||
return _.groupBy(
|
||||
events.filter(
|
||||
(e) =>
|
||||
!e.vacation &&
|
||||
e.isintake &&
|
||||
moment(date).isSame(moment(e.start), "day")
|
||||
),
|
||||
events.filter((e) => moment(date).isSame(moment(e.start), "day")),
|
||||
"job.alt_transport"
|
||||
);
|
||||
}, [events, date]);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { Button, Card, Checkbox, Col, PageHeader, Row, Space } from "antd";
|
||||
import { t } from "i18next";
|
||||
import React, { useMemo } from "react";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import { Button, Card, Col, PageHeader, Row, Space } from "antd";
|
||||
import React from "react";
|
||||
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
|
||||
import ScheduleModal from "../schedule-job-modal/schedule-job-modal.container";
|
||||
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
||||
@@ -10,23 +8,6 @@ import ScheduleProductionList from "../schedule-production-list/schedule-product
|
||||
import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verify-integrity.component";
|
||||
|
||||
export default function ScheduleCalendarComponent({ data, refetch }) {
|
||||
const [filter, setFilter] = useLocalStorage("filter_events", {
|
||||
intake: true,
|
||||
manual: true,
|
||||
employeevacation: true,
|
||||
});
|
||||
const filteredData = useMemo(() => {
|
||||
return data.filter(
|
||||
(d) =>
|
||||
d.block ||
|
||||
(filter.intake && d.isintake) ||
|
||||
(filter.manual && !d.isintake && d.block === false) ||
|
||||
(d.__typename === "employee_vacation" &&
|
||||
filter.employeevacation &&
|
||||
!!d.employee)
|
||||
);
|
||||
}, [data, filter]);
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<ScheduleModal />
|
||||
@@ -35,30 +16,6 @@ export default function ScheduleCalendarComponent({ data, refetch }) {
|
||||
<PageHeader
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Checkbox
|
||||
checked={filter?.intake}
|
||||
onChange={(e) => {
|
||||
setFilter({ ...filter, intake: e.target.checked });
|
||||
}}
|
||||
>
|
||||
{t("schedule.labels.intake")}
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
checked={filter?.manual}
|
||||
onChange={(e) => {
|
||||
setFilter({ ...filter, manual: e.target.checked });
|
||||
}}
|
||||
>
|
||||
{t("schedule.labels.manual")}
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
checked={filter?.employeevacation}
|
||||
onChange={(e) => {
|
||||
setFilter({ ...filter, employeevacation: e.target.checked });
|
||||
}}
|
||||
>
|
||||
{t("schedule.labels.employeevacation")}
|
||||
</Checkbox>
|
||||
<ScheduleVerifyIntegrity />
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -78,7 +35,7 @@ export default function ScheduleCalendarComponent({ data, refetch }) {
|
||||
<Col span={24}>
|
||||
<Card>
|
||||
<ScheduleCalendarWrapperComponent
|
||||
data={filteredData}
|
||||
data={data}
|
||||
refetch={refetch}
|
||||
style={{ height: "100rem" }}
|
||||
/>
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Button, Card, Input, Modal, Space, Table, Typography } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { Dropdown, Button, Table, Space, Card, Input } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { QUERY_SCOREBOARD_PAGINATED } from "../../graphql/scoreboard.queries";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import ScoreboardEntryEdit from "../scoreboard-entry-edit/scoreboard-entry-edit.component";
|
||||
import ScoreboardRemoveButton from "../scoreboard-remove-button/scorebard-remove-button.component";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import ScoreboardEntryEdit from "../scoreboard-entry-edit/scoreboard-entry-edit.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
export default function ScoreboardJobsList({ scoreBoardlist }) {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
visible: false,
|
||||
search: "",
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const { loading, error, data, refetch } = useQuery(
|
||||
QUERY_SCOREBOARD_PAGINATED,
|
||||
{
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
skip: !state.visible,
|
||||
variables: {
|
||||
search: state.search !== "" ? `%${state.search}%` : null,
|
||||
offset: state.current ? (state.current - 1) * state.pageSize : 0,
|
||||
limit: state.pageSize,
|
||||
order: [
|
||||
{
|
||||
date: "desc",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
const jobs = scoreBoardlist
|
||||
? searchText === ""
|
||||
? scoreBoardlist
|
||||
: scoreBoardlist.filter(
|
||||
(sb) =>
|
||||
(sb.job.ro_number || "")
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(sb.job.ownr_co_nm || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(sb.job.ownr_fn || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(sb.job.ownr_ln || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(sb.job.v_model_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(sb.job.v_make_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase())
|
||||
)
|
||||
: [];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
@@ -97,69 +97,35 @@ export default function ScoreboardJobsList({ scoreBoardlist }) {
|
||||
},
|
||||
];
|
||||
|
||||
const overlay = (
|
||||
<Card
|
||||
style={{ maxWidth: "90vw", padding: "1rem" }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
extra={
|
||||
<Input.Search
|
||||
placeholder={t("general.labels.search")}
|
||||
onChange={(e) => {
|
||||
setSearchText(e.target.value);
|
||||
}}
|
||||
value={searchText}
|
||||
enterButton
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
pagination={false}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={jobs}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
visible={state.visible}
|
||||
destroyOnClose
|
||||
width="80%"
|
||||
cancelButtonProps={{ style: { display: "none" } }}
|
||||
onCancel={() =>
|
||||
setState((state) => ({
|
||||
...state,
|
||||
visible: false,
|
||||
current: 1,
|
||||
search: "",
|
||||
}))
|
||||
}
|
||||
>
|
||||
{error && (
|
||||
<AlertComponent type="error" message={JSON.stringify(error)} />
|
||||
)}
|
||||
<Card
|
||||
extra={
|
||||
<Space align="middle" wrap>
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<Typography.Title level={4}>
|
||||
{t("general.labels.searchresults", { search: state.search })}
|
||||
</Typography.Title>
|
||||
<Input.Search
|
||||
placeholder={t("jobs.fields.ro_number")}
|
||||
allowClear
|
||||
onSearch={(value) => {
|
||||
setState((state) => ({ ...state, search: value }));
|
||||
}}
|
||||
//value={state.search}
|
||||
enterButton
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={data ? data.scoreboard : []}
|
||||
loading={loading}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(tableArgs) =>
|
||||
setState((state) => ({ ...state, ...tableArgs }))
|
||||
}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: state.pageSize || 10,
|
||||
current: state.current || 1,
|
||||
total: data ? data.scoreboard_aggregate.aggregate.count : 0,
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</Modal>
|
||||
<Button
|
||||
onClick={() => setState((state) => ({ ...state, visible: true }))}
|
||||
>
|
||||
{t("scoreboard.labels.entries")}
|
||||
</Button>
|
||||
</>
|
||||
<Dropdown trigger={["click"]} overlay={overlay}>
|
||||
<Button>Jobs</Button>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ export default function ScoreboardRemoveButton({ scoreboardId }) {
|
||||
setLoading(true);
|
||||
const result = await deleteScoreboardEntry({
|
||||
variables: { sbId: scoreboardId },
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: ["QUERY_SCOREBOARD_PAGINATED"],
|
||||
});
|
||||
|
||||
if (!!result.errors) {
|
||||
|
||||
@@ -80,7 +80,6 @@ export default function ScoreboardTimeTickets() {
|
||||
totalThisMonth: 0,
|
||||
totalLastMonth: 0,
|
||||
totalOverPeriod: 0,
|
||||
actualTotalOverPeriod: 0,
|
||||
employees: {},
|
||||
};
|
||||
data.fixedperiod.forEach((ticket) => {
|
||||
@@ -93,7 +92,6 @@ export default function ScoreboardTimeTickets() {
|
||||
totalThisMonth: 0,
|
||||
totalLastMonth: 0,
|
||||
totalOverPeriod: 0,
|
||||
actualTotalOverPeriod: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -184,9 +182,6 @@ export default function ScoreboardTimeTickets() {
|
||||
ret.employees[ticket.employee.employee_number].totalOverPeriod =
|
||||
ret.employees[ticket.employee.employee_number].totalOverPeriod +
|
||||
ticket.productivehrs;
|
||||
ret.employees[ticket.employee.employee_number].actualTotalOverPeriod =
|
||||
ret.employees[ticket.employee.employee_number]
|
||||
.actualTotalOverPeriod + (ticket.actualhrs || 0);
|
||||
|
||||
if (!totals.employees[ticket.employee.employee_number])
|
||||
totals.employees[ticket.employee.employee_number] = {
|
||||
@@ -224,7 +219,7 @@ export default function ScoreboardTimeTickets() {
|
||||
roundObject(ret);
|
||||
roundObject(totals);
|
||||
roundObject(ret2);
|
||||
|
||||
console.log(ret);
|
||||
return {
|
||||
fixed: ret,
|
||||
timeperiod: {
|
||||
|
||||
@@ -19,6 +19,7 @@ export default connect(
|
||||
|
||||
export function ScoreboardTicketsStats({ data, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
console.log(data);
|
||||
const columns = [
|
||||
{
|
||||
title: t("employees.fields.employee_number"),
|
||||
@@ -56,16 +57,6 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
|
||||
key: "totalOverPeriod",
|
||||
sorter: (a, b) => a.totalOverPeriod - b.totalOverPeriod,
|
||||
},
|
||||
{
|
||||
title: t("scoreboard.labels.efficiencyoverperiod"),
|
||||
dataIndex: "efficiencyoverperiod",
|
||||
key: "efficiencyoverperiod",
|
||||
render: (text, record) =>
|
||||
`${(
|
||||
(record.totalOverPeriod / (record.actualTotalOverPeriod || .1)) *
|
||||
100
|
||||
).toFixed(1)} %`,
|
||||
},
|
||||
];
|
||||
|
||||
const tableData = data
|
||||
@@ -121,7 +112,6 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
|
||||
<Col md={24} lg={20}>
|
||||
<Table
|
||||
columns={columns}
|
||||
rowKey='employee_number'
|
||||
dataSource={tableData}
|
||||
id="employee_number"
|
||||
scroll={{ y: "300px" }}
|
||||
|
||||
@@ -43,10 +43,9 @@ export default function ShopEmployeeAddVacation({ employee }) {
|
||||
});
|
||||
} else {
|
||||
notification["success"]({
|
||||
message: t("employees.successes.vacationadded"),
|
||||
message: t("employees.successes.added"),
|
||||
});
|
||||
}
|
||||
form.resetFields();
|
||||
setLoading(false);
|
||||
setVisibility(false);
|
||||
};
|
||||
|
||||
@@ -1494,7 +1494,7 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}
|
||||
|
||||
const ReceivableCustomFieldSelect = (
|
||||
<Select allowClear>
|
||||
<Select>
|
||||
<Select.Option value="v_vin">VIN</Select.Option>
|
||||
<Select.Option value="clm_no">Claim No.</Select.Option>
|
||||
<Select.Option value="ded_amt">Deductible Amount</Select.Option>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
|
||||
export default function VehicleVinDisplay({ children }) {
|
||||
if (!children) return null;
|
||||
|
||||
console.log(children);
|
||||
if (typeof children !== "string" || children.length !== 17) return children;
|
||||
const vin = children.trim();
|
||||
|
||||
|
||||
@@ -1,77 +1,75 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
|
||||
query QUERY_ALL_ACTIVE_APPOINTMENTS(
|
||||
$start: timestamptz!
|
||||
$end: timestamptz!
|
||||
$startd: date!
|
||||
$endd: date!
|
||||
) {
|
||||
employee_vacation(
|
||||
where: { _or: [{ start: { _gte: $startd } },
|
||||
{ end: { _lte: $endd } },
|
||||
{_and:[{start:{_lte: $startd}},{end:{_gte:$endd}}]}] }
|
||||
query QUERY_ALL_ACTIVE_APPOINTMENTS(
|
||||
$start: timestamptz!
|
||||
$end: timestamptz!
|
||||
$startd: date!
|
||||
$endd: date!
|
||||
) {
|
||||
id
|
||||
start
|
||||
end
|
||||
employee {
|
||||
employee_vacation(
|
||||
where: { _or: [{ start: { _gte: $startd } }, { end: { _lte: $endd } }] }
|
||||
) {
|
||||
id
|
||||
last_name
|
||||
first_name
|
||||
start
|
||||
end
|
||||
employee {
|
||||
id
|
||||
last_name
|
||||
first_name
|
||||
}
|
||||
}
|
||||
}
|
||||
appointments(
|
||||
where: {
|
||||
canceled: { _eq: false }
|
||||
end: { _lte: $end }
|
||||
start: { _gte: $start }
|
||||
}
|
||||
) {
|
||||
start
|
||||
id
|
||||
end
|
||||
arrived
|
||||
title
|
||||
isintake
|
||||
block
|
||||
color
|
||||
note
|
||||
job {
|
||||
alt_transport
|
||||
ro_number
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ea
|
||||
clm_total
|
||||
appointments(
|
||||
where: {
|
||||
canceled: { _eq: false }
|
||||
end: { _lte: $end }
|
||||
start: { _gte: $start }
|
||||
}
|
||||
) {
|
||||
start
|
||||
id
|
||||
clm_no
|
||||
ins_co_nm
|
||||
v_model_yr
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
labhrs: joblines_aggregate(
|
||||
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
||||
) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
end
|
||||
arrived
|
||||
title
|
||||
isintake
|
||||
block
|
||||
color
|
||||
note
|
||||
job {
|
||||
alt_transport
|
||||
ro_number
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ea
|
||||
clm_total
|
||||
id
|
||||
clm_no
|
||||
ins_co_nm
|
||||
v_model_yr
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
labhrs: joblines_aggregate(
|
||||
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
||||
) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
}
|
||||
}
|
||||
}
|
||||
larhrs: joblines_aggregate(
|
||||
where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }
|
||||
) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
larhrs: joblines_aggregate(
|
||||
where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }
|
||||
) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -13,20 +13,6 @@ export const QUERY_AUDIT_TRAIL = gql`
|
||||
created
|
||||
bodyshopid
|
||||
}
|
||||
email_audit_trail(
|
||||
where: { jobid: { _eq: $jobid } }
|
||||
order_by: { created_at: desc }
|
||||
) {
|
||||
cc
|
||||
contents
|
||||
created_at
|
||||
id
|
||||
jobid
|
||||
noteid
|
||||
subject
|
||||
to
|
||||
useremail
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ export const GET_ALL_JOBLINES_BY_PK = gql`
|
||||
notes
|
||||
location
|
||||
tax_part
|
||||
manual_line
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -50,7 +49,6 @@ export const GET_LINE_TICKET_BY_PK = gql`
|
||||
lbr_amt
|
||||
op_code_desc
|
||||
convertedtolbr
|
||||
convertedtolbr_data
|
||||
}
|
||||
timetickets(where: { jobid: { _eq: $id } }) {
|
||||
actualhrs
|
||||
@@ -181,7 +179,6 @@ export const UPDATE_JOB_LINE = gql`
|
||||
status
|
||||
removed
|
||||
convertedtolbr
|
||||
convertedtolbr_data
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +195,6 @@ export const GET_JOB_LINES_TO_ENTER_BILL = gql`
|
||||
line_desc
|
||||
part_type
|
||||
oem_partno
|
||||
alt_partno
|
||||
db_price
|
||||
act_price
|
||||
part_qty
|
||||
@@ -208,7 +204,6 @@ export const GET_JOB_LINES_TO_ENTER_BILL = gql`
|
||||
lbr_op
|
||||
lbr_amt
|
||||
op_code_desc
|
||||
alt_partno
|
||||
}
|
||||
jobs_by_pk(id: $id) {
|
||||
id
|
||||
|
||||
@@ -40,8 +40,6 @@ export const QUERY_ALL_ACTIVE_JOBS = gql`
|
||||
updated_at
|
||||
ded_amt
|
||||
suspended
|
||||
est_ct_fn
|
||||
est_ct_ln
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -623,7 +621,6 @@ export const GET_JOB_BY_PK = gql`
|
||||
ownr_ctry
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
tax_number
|
||||
}
|
||||
labor_rate_desc
|
||||
rate_la1
|
||||
@@ -687,7 +684,6 @@ export const GET_JOB_BY_PK = gql`
|
||||
line_ref
|
||||
part_type
|
||||
oem_partno
|
||||
alt_partno
|
||||
db_price
|
||||
act_price
|
||||
part_qty
|
||||
@@ -859,7 +855,6 @@ export const QUERY_JOB_CARD_DETAILS = gql`
|
||||
id
|
||||
allow_text_message
|
||||
preferred_contact
|
||||
tax_number
|
||||
}
|
||||
vehicleid
|
||||
v_model_yr
|
||||
@@ -1912,7 +1907,6 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
||||
profitcenter_part
|
||||
prt_dsmk_p
|
||||
convertedtolbr
|
||||
convertedtolbr_data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,6 @@ export const QUERY_OWNER_BY_ID = gql`
|
||||
ownr_zip
|
||||
preferred_contact
|
||||
note
|
||||
tax_number
|
||||
jobs {
|
||||
id
|
||||
ro_number
|
||||
|
||||
@@ -88,41 +88,3 @@ export const GET_BLOCKED_DAYS = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_SCOREBOARD_PAGINATED = gql`
|
||||
query QUERY_SCOREBOARD_PAGINATED(
|
||||
$search: String
|
||||
$offset: Int
|
||||
$limit: Int
|
||||
$order: [scoreboard_order_by!]
|
||||
) {
|
||||
scoreboard(
|
||||
where: { job: { ro_number: { _ilike: $search } } }
|
||||
offset: $offset
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
) {
|
||||
id
|
||||
jobid
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
invoice_date
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
}
|
||||
date
|
||||
bodyhrs
|
||||
painthrs
|
||||
}
|
||||
scoreboard_aggregate(where: { job: { ro_number: { _ilike: $search } } }) {
|
||||
aggregate {
|
||||
count(distinct: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -79,7 +79,6 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
|
||||
date
|
||||
id
|
||||
rate
|
||||
actualhrs
|
||||
productivehrs
|
||||
memo
|
||||
jobid
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import React from "react";
|
||||
import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
|
||||
import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";
|
||||
|
||||
export default function TechLookupContainer() {
|
||||
return (
|
||||
<div>
|
||||
<RbacWrapperComponent action="jobs:list-active">
|
||||
<TechLookupJobsList />
|
||||
<TechLookupJobsDrawer />
|
||||
</RbacWrapperComponent>
|
||||
<TechLookupJobsList />
|
||||
<TechLookupJobsDrawer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,9 +17,7 @@ const sentryReduxEnhancer = Sentry.createReduxEnhancer({
|
||||
const sagaMiddleWare = createSagaMiddleware();
|
||||
|
||||
const reduxSyncConfig = {
|
||||
whitelist: [
|
||||
"ADD_RECENT_ITEM", //"SET_SHOP_DETAILS"
|
||||
],
|
||||
whitelist: ["ADD_RECENT_ITEM", "SET_SHOP_DETAILS"],
|
||||
};
|
||||
|
||||
const middlewares = [
|
||||
|
||||
@@ -269,7 +269,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
try {
|
||||
const userEmail = yield select((state) => state.user.currentUser.email);
|
||||
try {
|
||||
//console.log("Setting shop timezone.");
|
||||
console.log("Setting shop timezone.");
|
||||
// moment.tz.setDefault(payload.timezone);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
@@ -82,12 +82,8 @@
|
||||
},
|
||||
"audit": {
|
||||
"fields": {
|
||||
"cc": "CC",
|
||||
"contents": "Contents",
|
||||
"created": "Time",
|
||||
"operation": "Operation",
|
||||
"subject": "Subject",
|
||||
"to": "To",
|
||||
"useremail": "User",
|
||||
"values": "Values"
|
||||
}
|
||||
@@ -682,7 +678,6 @@
|
||||
"refuelqty": "Refuel qty.?"
|
||||
},
|
||||
"correctdataonform": "Please review the information above. If any of it is not correct, you can fix it later.",
|
||||
"dateinpast": "Date is in the past.",
|
||||
"dlexpirebeforereturn": "The driver's license expires before the car is expected to return. ",
|
||||
"driverinformation": "Driver's Information",
|
||||
"findcontract": "Find Contract",
|
||||
@@ -757,7 +752,6 @@
|
||||
"status": {
|
||||
"in": "Available",
|
||||
"inservice": "In Service",
|
||||
"leasereturn": "Lease Returned",
|
||||
"out": "Rented",
|
||||
"sold": "Sold"
|
||||
},
|
||||
@@ -926,8 +920,7 @@
|
||||
},
|
||||
"successes": {
|
||||
"delete": "Employee deleted successfully.",
|
||||
"save": "Employee saved successfully.",
|
||||
"vacationadded": "Employee vacation added."
|
||||
"save": "Employee saved successfully."
|
||||
},
|
||||
"validation": {
|
||||
"unique_employee_number": "You must enter a unique employee number."
|
||||
@@ -1618,10 +1611,8 @@
|
||||
"documents-images": "Images",
|
||||
"documents-other": "Other Documents",
|
||||
"duplicateconfirm": "Are you sure you want to duplicate this job? Some elements of this job will not be duplicated.",
|
||||
"emailaudit": "Email Audit Trail",
|
||||
"employeeassignments": "Employee Assignments",
|
||||
"estimatelines": "Estimate Lines",
|
||||
"estimator": "Estimator",
|
||||
"existing_jobs": "Existing Jobs",
|
||||
"federal_tax_amt": "Federal Taxes",
|
||||
"gpdollars": "$ G.P.",
|
||||
@@ -1925,9 +1916,7 @@
|
||||
"labels": {
|
||||
"addtorelatedro": "Add to Related ROs",
|
||||
"newnoteplaceholder": "Add a note...",
|
||||
"notetoadd": "Note to Add",
|
||||
"systemnotes": "System Notes",
|
||||
"usernotes": "User Notes"
|
||||
"notetoadd": "Note to Add"
|
||||
},
|
||||
"successes": {
|
||||
"create": "Note created successfully.",
|
||||
@@ -1946,7 +1935,6 @@
|
||||
},
|
||||
"errors": {
|
||||
"noaccess": "The record does not exist or you do not have access to it. ",
|
||||
"saving": "Error saving owner. {{error}}.",
|
||||
"selectexistingornew": "Select an existing owner record or create a new one. "
|
||||
},
|
||||
"fields": {
|
||||
@@ -1967,8 +1955,7 @@
|
||||
"ownr_st": "Province/State",
|
||||
"ownr_title": "Title",
|
||||
"ownr_zip": "Zip/Postal Code",
|
||||
"preferred_contact": "Preferred Contact Method",
|
||||
"tax_number": "Tax Number"
|
||||
"preferred_contact": "Preferred Contact Method"
|
||||
},
|
||||
"forms": {
|
||||
"address": "Address",
|
||||
@@ -2411,7 +2398,6 @@
|
||||
"open_orders_status": "Open Orders by Status",
|
||||
"parts_backorder": "IOU Parts List",
|
||||
"parts_not_recieved": "Parts Not Received",
|
||||
"parts_not_recieved_vendor": "Parts Not Received by Vendor",
|
||||
"payments_by_date": "Payments by Date",
|
||||
"payments_by_date_type": "Payments by Date and Type",
|
||||
"production_by_category": "Production by Category",
|
||||
@@ -2424,7 +2410,6 @@
|
||||
"production_by_target_date": "Production by Target Date",
|
||||
"production_by_technician": "Production by Technician",
|
||||
"production_by_technician_one": "Production filtered by Technician",
|
||||
"psr_by_make": "Percent of Sales by Vehicle Make",
|
||||
"purchases_by_cost_center_detail": "Purchases by Cost Center (Detail)",
|
||||
"purchases_by_cost_center_summary": "Purchases by Cost Center (Summary)",
|
||||
"purchases_by_date_range_detail": "Purchases by Date - Detail",
|
||||
@@ -2449,9 +2434,6 @@
|
||||
},
|
||||
"schedule": {
|
||||
"labels": {
|
||||
"employeevacation": "Employee Vacations",
|
||||
"intake": "Intake Events",
|
||||
"manual": "Manual Events",
|
||||
"manualevent": "Add Manual Event"
|
||||
}
|
||||
},
|
||||
@@ -2474,8 +2456,6 @@
|
||||
"calendarperiod": "Periods based on calendar weeks/months.",
|
||||
"dailyactual": "Actual (D)",
|
||||
"dailytarget": "Daily",
|
||||
"efficiencyoverperiod": "Efficiency over Selected Dates",
|
||||
"entries": "Scoreboard Entries",
|
||||
"jobs": "Jobs",
|
||||
"lastmonth": "Last Month",
|
||||
"lastweek": "Last Week",
|
||||
|
||||
@@ -82,12 +82,8 @@
|
||||
},
|
||||
"audit": {
|
||||
"fields": {
|
||||
"cc": "",
|
||||
"contents": "",
|
||||
"created": "",
|
||||
"operation": "",
|
||||
"subject": "",
|
||||
"to": "",
|
||||
"useremail": "",
|
||||
"values": ""
|
||||
}
|
||||
@@ -682,7 +678,6 @@
|
||||
"refuelqty": ""
|
||||
},
|
||||
"correctdataonform": "",
|
||||
"dateinpast": "",
|
||||
"dlexpirebeforereturn": "",
|
||||
"driverinformation": "",
|
||||
"findcontract": "",
|
||||
@@ -757,7 +752,6 @@
|
||||
"status": {
|
||||
"in": "",
|
||||
"inservice": "",
|
||||
"leasereturn": "",
|
||||
"out": "",
|
||||
"sold": ""
|
||||
},
|
||||
@@ -926,8 +920,7 @@
|
||||
},
|
||||
"successes": {
|
||||
"delete": "Empleado eliminado con éxito.",
|
||||
"save": "Empleado guardado con éxito.",
|
||||
"vacationadded": ""
|
||||
"save": "Empleado guardado con éxito."
|
||||
},
|
||||
"validation": {
|
||||
"unique_employee_number": ""
|
||||
@@ -1618,10 +1611,8 @@
|
||||
"documents-images": "",
|
||||
"documents-other": "",
|
||||
"duplicateconfirm": "",
|
||||
"emailaudit": "",
|
||||
"employeeassignments": "",
|
||||
"estimatelines": "",
|
||||
"estimator": "",
|
||||
"existing_jobs": "Empleos existentes",
|
||||
"federal_tax_amt": "",
|
||||
"gpdollars": "",
|
||||
@@ -1925,9 +1916,7 @@
|
||||
"labels": {
|
||||
"addtorelatedro": "",
|
||||
"newnoteplaceholder": "Agrega una nota...",
|
||||
"notetoadd": "",
|
||||
"systemnotes": "",
|
||||
"usernotes": ""
|
||||
"notetoadd": ""
|
||||
},
|
||||
"successes": {
|
||||
"create": "Nota creada con éxito.",
|
||||
@@ -1946,7 +1935,6 @@
|
||||
},
|
||||
"errors": {
|
||||
"noaccess": "El registro no existe o no tiene acceso a él.",
|
||||
"saving": "",
|
||||
"selectexistingornew": ""
|
||||
},
|
||||
"fields": {
|
||||
@@ -1967,8 +1955,7 @@
|
||||
"ownr_st": "Provincia del estado",
|
||||
"ownr_title": "Título",
|
||||
"ownr_zip": "código postal",
|
||||
"preferred_contact": "Método de Contacto Preferido",
|
||||
"tax_number": ""
|
||||
"preferred_contact": "Método de Contacto Preferido"
|
||||
},
|
||||
"forms": {
|
||||
"address": "",
|
||||
@@ -2411,7 +2398,6 @@
|
||||
"open_orders_status": "",
|
||||
"parts_backorder": "",
|
||||
"parts_not_recieved": "",
|
||||
"parts_not_recieved_vendor": "",
|
||||
"payments_by_date": "",
|
||||
"payments_by_date_type": "",
|
||||
"production_by_category": "",
|
||||
@@ -2424,7 +2410,6 @@
|
||||
"production_by_target_date": "",
|
||||
"production_by_technician": "",
|
||||
"production_by_technician_one": "",
|
||||
"psr_by_make": "",
|
||||
"purchases_by_cost_center_detail": "",
|
||||
"purchases_by_cost_center_summary": "",
|
||||
"purchases_by_date_range_detail": "",
|
||||
@@ -2449,9 +2434,6 @@
|
||||
},
|
||||
"schedule": {
|
||||
"labels": {
|
||||
"employeevacation": "",
|
||||
"intake": "",
|
||||
"manual": "",
|
||||
"manualevent": ""
|
||||
}
|
||||
},
|
||||
@@ -2474,8 +2456,6 @@
|
||||
"calendarperiod": "",
|
||||
"dailyactual": "",
|
||||
"dailytarget": "",
|
||||
"efficiencyoverperiod": "",
|
||||
"entries": "",
|
||||
"jobs": "",
|
||||
"lastmonth": "",
|
||||
"lastweek": "",
|
||||
|
||||
@@ -82,12 +82,8 @@
|
||||
},
|
||||
"audit": {
|
||||
"fields": {
|
||||
"cc": "",
|
||||
"contents": "",
|
||||
"created": "",
|
||||
"operation": "",
|
||||
"subject": "",
|
||||
"to": "",
|
||||
"useremail": "",
|
||||
"values": ""
|
||||
}
|
||||
@@ -682,7 +678,6 @@
|
||||
"refuelqty": ""
|
||||
},
|
||||
"correctdataonform": "",
|
||||
"dateinpast": "",
|
||||
"dlexpirebeforereturn": "",
|
||||
"driverinformation": "",
|
||||
"findcontract": "",
|
||||
@@ -757,7 +752,6 @@
|
||||
"status": {
|
||||
"in": "",
|
||||
"inservice": "",
|
||||
"leasereturn": "",
|
||||
"out": "",
|
||||
"sold": ""
|
||||
},
|
||||
@@ -926,8 +920,7 @@
|
||||
},
|
||||
"successes": {
|
||||
"delete": "L'employé a bien été supprimé.",
|
||||
"save": "L'employé a enregistré avec succès.",
|
||||
"vacationadded": ""
|
||||
"save": "L'employé a enregistré avec succès."
|
||||
},
|
||||
"validation": {
|
||||
"unique_employee_number": ""
|
||||
@@ -1618,10 +1611,8 @@
|
||||
"documents-images": "",
|
||||
"documents-other": "",
|
||||
"duplicateconfirm": "",
|
||||
"emailaudit": "",
|
||||
"employeeassignments": "",
|
||||
"estimatelines": "",
|
||||
"estimator": "",
|
||||
"existing_jobs": "Emplois existants",
|
||||
"federal_tax_amt": "",
|
||||
"gpdollars": "",
|
||||
@@ -1925,9 +1916,7 @@
|
||||
"labels": {
|
||||
"addtorelatedro": "",
|
||||
"newnoteplaceholder": "Ajouter une note...",
|
||||
"notetoadd": "",
|
||||
"systemnotes": "",
|
||||
"usernotes": ""
|
||||
"notetoadd": ""
|
||||
},
|
||||
"successes": {
|
||||
"create": "Remarque créée avec succès.",
|
||||
@@ -1946,7 +1935,6 @@
|
||||
},
|
||||
"errors": {
|
||||
"noaccess": "L'enregistrement n'existe pas ou vous n'y avez pas accès.",
|
||||
"saving": "",
|
||||
"selectexistingornew": ""
|
||||
},
|
||||
"fields": {
|
||||
@@ -1967,8 +1955,7 @@
|
||||
"ownr_st": "Etat / Province",
|
||||
"ownr_title": "Titre",
|
||||
"ownr_zip": "Zip / code postal",
|
||||
"preferred_contact": "Méthode de contact préférée",
|
||||
"tax_number": ""
|
||||
"preferred_contact": "Méthode de contact préférée"
|
||||
},
|
||||
"forms": {
|
||||
"address": "",
|
||||
@@ -2411,7 +2398,6 @@
|
||||
"open_orders_status": "",
|
||||
"parts_backorder": "",
|
||||
"parts_not_recieved": "",
|
||||
"parts_not_recieved_vendor": "",
|
||||
"payments_by_date": "",
|
||||
"payments_by_date_type": "",
|
||||
"production_by_category": "",
|
||||
@@ -2424,7 +2410,6 @@
|
||||
"production_by_target_date": "",
|
||||
"production_by_technician": "",
|
||||
"production_by_technician_one": "",
|
||||
"psr_by_make": "",
|
||||
"purchases_by_cost_center_detail": "",
|
||||
"purchases_by_cost_center_summary": "",
|
||||
"purchases_by_date_range_detail": "",
|
||||
@@ -2449,9 +2434,6 @@
|
||||
},
|
||||
"schedule": {
|
||||
"labels": {
|
||||
"employeevacation": "",
|
||||
"intake": "",
|
||||
"manual": "",
|
||||
"manualevent": ""
|
||||
}
|
||||
},
|
||||
@@ -2474,8 +2456,6 @@
|
||||
"calendarperiod": "",
|
||||
"dailyactual": "",
|
||||
"dailytarget": "",
|
||||
"efficiencyoverperiod": "",
|
||||
"entries": "",
|
||||
"jobs": "",
|
||||
"lastmonth": "",
|
||||
"lastweek": "",
|
||||
|
||||
@@ -40,7 +40,6 @@ const AuditTrailMapping = {
|
||||
i18n.t("audit_trail.messages.admin_jobmarkforreexport"),
|
||||
admin_jobmarkexported: () =>
|
||||
i18n.t("audit_trail.messages.admin_jobmarkexported"),
|
||||
|
||||
};
|
||||
|
||||
export default AuditTrailMapping;
|
||||
|
||||
@@ -1448,19 +1448,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "purchases",
|
||||
},
|
||||
parts_not_recieved_vendor: {
|
||||
title: i18n.t("reportcenter.templates.parts_not_recieved_vendor"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.parts_not_recieved_vendor"),
|
||||
key: "parts_not_recieved_vendor",
|
||||
idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.parts_orders"),
|
||||
field: i18n.t("parts_orders.fields.order_date"),
|
||||
},
|
||||
group: "purchases",
|
||||
},
|
||||
scoreboard_detail: {
|
||||
title: i18n.t("reportcenter.templates.scoreboard_detail"),
|
||||
description: "",
|
||||
@@ -1559,18 +1546,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "jobs",
|
||||
},
|
||||
psr_by_make: {
|
||||
title: i18n.t("reportcenter.templates.psr_by_make"),
|
||||
subject: i18n.t("reportcenter.templates.psr_by_make"),
|
||||
key: "psr_by_make",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_invoiced"),
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(!type || type === "courtesycarcontract"
|
||||
|
||||
5350
client/yarn.lock
5350
client/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -752,13 +752,6 @@
|
||||
table:
|
||||
schema: public
|
||||
name: documents
|
||||
- name: email_audit_trails
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: bodyshopid
|
||||
table:
|
||||
schema: public
|
||||
name: email_audit_trail
|
||||
- name: employees
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
@@ -1834,93 +1827,6 @@
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
- table:
|
||||
schema: public
|
||||
name: email_audit_trail
|
||||
object_relationships:
|
||||
- name: bodyshop
|
||||
using:
|
||||
foreign_key_constraint_on: bodyshopid
|
||||
- name: job
|
||||
using:
|
||||
foreign_key_constraint_on: jobid
|
||||
- name: user
|
||||
using:
|
||||
foreign_key_constraint_on: useremail
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
check:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- cc
|
||||
- to
|
||||
- contents
|
||||
- subject
|
||||
- useremail
|
||||
- created_at
|
||||
- updated_at
|
||||
- bodyshopid
|
||||
- id
|
||||
- jobid
|
||||
- noteid
|
||||
backend_only: false
|
||||
select_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- cc
|
||||
- to
|
||||
- contents
|
||||
- subject
|
||||
- useremail
|
||||
- created_at
|
||||
- updated_at
|
||||
- bodyshopid
|
||||
- id
|
||||
- jobid
|
||||
- noteid
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
update_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- cc
|
||||
- to
|
||||
- contents
|
||||
- subject
|
||||
- useremail
|
||||
- created_at
|
||||
- updated_at
|
||||
- bodyshopid
|
||||
- id
|
||||
- jobid
|
||||
- noteid
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
check: null
|
||||
- table:
|
||||
schema: public
|
||||
name: employee_vacation
|
||||
@@ -2479,7 +2385,6 @@
|
||||
- bett_type
|
||||
- cert_part
|
||||
- convertedtolbr
|
||||
- convertedtolbr_data
|
||||
- created_at
|
||||
- db_hrs
|
||||
- db_price
|
||||
@@ -2544,7 +2449,6 @@
|
||||
- bett_type
|
||||
- cert_part
|
||||
- convertedtolbr
|
||||
- convertedtolbr_data
|
||||
- created_at
|
||||
- db_hrs
|
||||
- db_price
|
||||
@@ -2620,7 +2524,6 @@
|
||||
- bett_type
|
||||
- cert_part
|
||||
- convertedtolbr
|
||||
- convertedtolbr_data
|
||||
- created_at
|
||||
- db_hrs
|
||||
- db_price
|
||||
@@ -2820,13 +2723,6 @@
|
||||
table:
|
||||
schema: public
|
||||
name: documents
|
||||
- name: email_audit_trails
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: jobid
|
||||
table:
|
||||
schema: public
|
||||
name: email_audit_trail
|
||||
- name: exportlogs
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
@@ -4116,7 +4012,6 @@
|
||||
- ownr_zip
|
||||
- preferred_contact
|
||||
- shopid
|
||||
- tax_number
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
@@ -4152,7 +4047,6 @@
|
||||
- ownr_zip
|
||||
- preferred_contact
|
||||
- shopid
|
||||
- tax_number
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
@@ -5077,13 +4971,6 @@
|
||||
table:
|
||||
schema: public
|
||||
name: audit_trail
|
||||
- name: email_audit_trails
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: useremail
|
||||
table:
|
||||
schema: public
|
||||
name: email_audit_trail
|
||||
- name: exportlogs
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."joblines" add column "convertedtolbr_data" jsonb
|
||||
-- not null default jsonb_build_object();
|
||||
@@ -1,2 +0,0 @@
|
||||
alter table "public"."joblines" add column "convertedtolbr_data" jsonb
|
||||
not null default jsonb_build_object();
|
||||
@@ -1,4 +0,0 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."owners" add column "tax_number" text
|
||||
-- null;
|
||||
@@ -1,2 +0,0 @@
|
||||
alter table "public"."owners" add column "tax_number" text
|
||||
null;
|
||||
@@ -1 +0,0 @@
|
||||
DROP TABLE "public"."email_audit_trail";
|
||||
@@ -1,18 +0,0 @@
|
||||
CREATE TABLE "public"."email_audit_trail" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "bodyshopid" uuid NOT NULL, "jobid" uuid, "noteid" uuid, "to" jsonb NOT NULL DEFAULT jsonb_build_array(), "cc" jsonb NOT NULL DEFAULT jsonb_build_array(), "subject" text, "contents" text, "useremail" text NOT NULL, PRIMARY KEY ("id") );
|
||||
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
_new record;
|
||||
BEGIN
|
||||
_new := NEW;
|
||||
_new."updated_at" = NOW();
|
||||
RETURN _new;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER "set_public_email_audit_trail_updated_at"
|
||||
BEFORE UPDATE ON "public"."email_audit_trail"
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
|
||||
COMMENT ON TRIGGER "set_public_email_audit_trail_updated_at" ON "public"."email_audit_trail"
|
||||
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
@@ -1 +0,0 @@
|
||||
DROP TABLE "public"."email_audit_trail";
|
||||
@@ -1,18 +0,0 @@
|
||||
CREATE TABLE "public"."email_audit_trail" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "bodyshopid" uuid NOT NULL, "jobid" uuid, "noteid" uuid, "to" jsonb NOT NULL DEFAULT jsonb_build_array(), "cc" jsonb NOT NULL DEFAULT jsonb_build_array(), "subject" text, "contents" text, "useremail" text NOT NULL, PRIMARY KEY ("id") );
|
||||
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
_new record;
|
||||
BEGIN
|
||||
_new := NEW;
|
||||
_new."updated_at" = NOW();
|
||||
RETURN _new;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER "set_public_email_audit_trail_updated_at"
|
||||
BEFORE UPDATE ON "public"."email_audit_trail"
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
|
||||
COMMENT ON TRIGGER "set_public_email_audit_trail_updated_at" ON "public"."email_audit_trail"
|
||||
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
@@ -1 +0,0 @@
|
||||
alter table "public"."email_audit_trail" drop constraint "email_audit_trail_bodyshopid_fkey";
|
||||
@@ -1,5 +0,0 @@
|
||||
alter table "public"."email_audit_trail"
|
||||
add constraint "email_audit_trail_bodyshopid_fkey"
|
||||
foreign key ("bodyshopid")
|
||||
references "public"."bodyshops"
|
||||
("id") on update cascade on delete cascade;
|
||||
@@ -1 +0,0 @@
|
||||
alter table "public"."email_audit_trail" drop constraint "email_audit_trail_jobid_fkey";
|
||||
@@ -1,5 +0,0 @@
|
||||
alter table "public"."email_audit_trail"
|
||||
add constraint "email_audit_trail_jobid_fkey"
|
||||
foreign key ("jobid")
|
||||
references "public"."jobs"
|
||||
("id") on update cascade on delete cascade;
|
||||
@@ -1 +0,0 @@
|
||||
alter table "public"."email_audit_trail" drop constraint "email_audit_trail_useremail_fkey";
|
||||
@@ -1,5 +0,0 @@
|
||||
alter table "public"."email_audit_trail"
|
||||
add constraint "email_audit_trail_useremail_fkey"
|
||||
foreign key ("useremail")
|
||||
references "public"."users"
|
||||
("email") on update cascade on delete cascade;
|
||||
30
package.json
30
package.json
@@ -17,11 +17,11 @@
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws-sdk": "^2.1181.0",
|
||||
"aws-sdk": "^2.1136.0",
|
||||
"axios": "^0.27.2",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.20.0",
|
||||
"cloudinary": "^1.30.1",
|
||||
"cloudinary": "^1.30.0",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "2.8.5",
|
||||
@@ -29,33 +29,35 @@
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "16.0.1",
|
||||
"express": "^4.18.1",
|
||||
"firebase-admin": "^11.0.0",
|
||||
"firebase-admin": "^10.2.0",
|
||||
"graphql": "^16.5.0",
|
||||
"graphql-request": "^4.2.0",
|
||||
"graylog2": "^0.2.1",
|
||||
"inline-css": "^4.0.1",
|
||||
"inline-css": "^3.0.0",
|
||||
"intuit-oauth": "^4.0.0",
|
||||
"json-2-csv": "^3.17.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"moment": "^2.29.3",
|
||||
"moment-timezone": "^0.5.34",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"node-mailjet": "^5.1.0",
|
||||
"multer": "^1.4.4",
|
||||
"node-mailjet": "^3.4.1",
|
||||
"node-quickbooks": "^2.0.39",
|
||||
"nodemailer": "^6.7.7",
|
||||
"phone": "^3.1.23",
|
||||
"nodemailer": "^6.7.5",
|
||||
"phone": "^3.1.17",
|
||||
"query-string": "^7.1.1",
|
||||
"soap": "^0.45.0",
|
||||
"soap": "^0.43.0",
|
||||
"socket.io": "^4.5.0",
|
||||
"ssh2-sftp-client": "^9.0.2",
|
||||
"stripe": "^9.15.0",
|
||||
"twilio": "^3.80.0",
|
||||
"ssh2-sftp-client": "^8.0.0",
|
||||
"stripe": "^9.1.0",
|
||||
"twilio": "^3.77.0",
|
||||
"uuid": "^8.3.2",
|
||||
"xml2js": "^0.4.23",
|
||||
"xmlbuilder2": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^7.3.0",
|
||||
"concurrently": "^6.3.0",
|
||||
"eslint": "^7.31.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"source-map-explorer": "^2.5.2"
|
||||
}
|
||||
}
|
||||
|
||||
12
server.js
12
server.js
@@ -172,18 +172,6 @@ app.post(
|
||||
fb.validateAdmin,
|
||||
adm.createShop
|
||||
);
|
||||
app.post(
|
||||
"/adm/updateshop",
|
||||
fb.validateFirebaseIdToken,
|
||||
fb.validateAdmin,
|
||||
adm.updateShop
|
||||
);
|
||||
app.post(
|
||||
"/adm/updatecounter",
|
||||
fb.validateFirebaseIdToken,
|
||||
fb.validateAdmin,
|
||||
adm.updateCounter
|
||||
);
|
||||
|
||||
//Stripe Processing
|
||||
var stripe = require("./server/stripe/payment");
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const DineroQbFormat = require("./accounting-constants").DineroQbFormat;
|
||||
|
||||
const Dinero = require("dinero.js");
|
||||
const { DiscountNotAlreadyCounted } = require("../job/job-totals");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
exports.default = function ({
|
||||
@@ -38,22 +37,23 @@ exports.default = function ({
|
||||
amount: Math.round((jobline.act_price || 0) * 100),
|
||||
}).multiply(jobline.part_qty || 1);
|
||||
|
||||
// console.log("Have a part discount", jobline);
|
||||
DineroAmount = DineroAmount.add(
|
||||
((jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0) ||
|
||||
(jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(jobline, jobs_by_pk.joblines)
|
||||
? jobline.prt_dsmk_m
|
||||
if (
|
||||
(jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0) ||
|
||||
((jobline.db_ref === "900511" ||
|
||||
jobline.db_ref === "900510" ||
|
||||
jobline.db_ref === "900500") &&
|
||||
jobline.prt_dsmk_m &&
|
||||
jobline.prt_dsmk_m !== 0)
|
||||
) {
|
||||
// console.log("Have a part discount", jobline);
|
||||
DineroAmount = DineroAmount.add(
|
||||
jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0
|
||||
? Dinero({ amount: Math.round(jobline.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(jobline.act_price * 100),
|
||||
})
|
||||
.multiply(jobline.part_qty || 0)
|
||||
.percentage(Math.abs(jobline.prt_dsmk_p || 0))
|
||||
.multiply(jobline.prt_dsmk_p > 0 ? 1 : -1)
|
||||
: Dinero()
|
||||
);
|
||||
|
||||
: DineroAmount.percentage(
|
||||
Math.abs(jobline.prt_dsmk_p || 0)
|
||||
).multiply(jobline.prt_dsmk_p > 0 ? 1 : -1)
|
||||
);
|
||||
}
|
||||
const account = responsibilityCenters.profits.find(
|
||||
(i) => jobline.profitcenter_part.toLowerCase() === i.name.toLowerCase()
|
||||
);
|
||||
@@ -82,11 +82,7 @@ exports.default = function ({
|
||||
state:
|
||||
jobs_by_pk.state_tax_rate === 0
|
||||
? false
|
||||
: jobline.db_ref === "900511" ||
|
||||
jobline.db_ref === "900510" ||
|
||||
(jobline.mod_lb_hrs === 0 && //Extending IO-1375 as a part of IO-2023
|
||||
jobline.act_price > 0 &&
|
||||
jobline.lbr_op === "OP14")
|
||||
: jobline.db_ref === "900511" || jobline.db_ref === "900510"
|
||||
? true
|
||||
: jobline.tax_part,
|
||||
},
|
||||
|
||||
@@ -414,11 +414,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) {
|
||||
});
|
||||
setNewRefreshToken(req.user.email, items);
|
||||
const taxCodes = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(
|
||||
qbo_realmId,
|
||||
"query",
|
||||
`select * From TaxCode where active=true`
|
||||
),
|
||||
url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -66,55 +66,3 @@ exports.createShop = async (req, res) => {
|
||||
res.status(500).json(error);
|
||||
}
|
||||
};
|
||||
exports.updateCounter = async (req, res) => {
|
||||
logger.log("admin-update-counter", "ADMIN", req.user.email, null, {
|
||||
request: req.body,
|
||||
ioadmin: true,
|
||||
});
|
||||
const { id, counter } = req.body;
|
||||
|
||||
try {
|
||||
const result = await client.request(
|
||||
`mutation UPDATE_COUNTER($id: uuid!, $counter: counters_set_input!) {
|
||||
update_counters_by_pk(pk_columns: { id: $id }, _set: $counter) {
|
||||
id
|
||||
countertype
|
||||
count
|
||||
prefix
|
||||
}
|
||||
}`,
|
||||
{
|
||||
id,
|
||||
counter,
|
||||
}
|
||||
);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json(error);
|
||||
}
|
||||
};
|
||||
exports.updateShop = async (req, res) => {
|
||||
logger.log("admin-update-shop", "ADMIN", req.user.email, null, {
|
||||
request: req.body,
|
||||
ioadmin: true,
|
||||
});
|
||||
const { id, bodyshop } = req.body;
|
||||
|
||||
try {
|
||||
const result = await client.request(
|
||||
`mutation UPDATE_BODYSHOP($id: uuid!, $bodyshop: bodyshops_set_input!) {
|
||||
update_bodyshops_by_pk(pk_columns: { id: $id }, _set: $bodyshop) {
|
||||
id
|
||||
|
||||
}
|
||||
}`,
|
||||
{
|
||||
id,
|
||||
bodyshop,
|
||||
}
|
||||
);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,7 +12,6 @@ const CdkBase = require("../web-sockets/web-socket");
|
||||
|
||||
const Dinero = require("dinero.js");
|
||||
const _ = require("lodash");
|
||||
const { DiscountNotAlreadyCounted } = require("../job/job-totals");
|
||||
|
||||
exports.default = async function (socket, jobid) {
|
||||
try {
|
||||
@@ -71,20 +70,23 @@ exports.default = async function (socket, jobid) {
|
||||
amount: Math.round(val.act_price * 100),
|
||||
}).multiply(val.part_qty || 1);
|
||||
|
||||
DineroAmount = DineroAmount.add(
|
||||
((val.prt_dsmk_m && val.prt_dsmk_m !== 0) ||
|
||||
(val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(val, job.joblines)
|
||||
? val.prt_dsmk_m
|
||||
if (
|
||||
(val.prt_dsmk_p && val.prt_dsmk_p !== 0) ||
|
||||
((val.db_ref === "900511" ||
|
||||
val.db_ref === "900510" ||
|
||||
val.db_ref === "900500") &&
|
||||
val.prt_dsmk_m &&
|
||||
val.prt_dsmk_m !== 0)
|
||||
) {
|
||||
// console.log("Have a part discount", val);
|
||||
DineroAmount = DineroAmount.add(
|
||||
val.prt_dsmk_m && val.prt_dsmk_m !== 0
|
||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(val.act_price * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
: Dinero()
|
||||
);
|
||||
: DineroAmount.percentage(Math.abs(val.prt_dsmk_p || 0)).multiply(
|
||||
val.prt_dsmk_p > 0 ? 1 : -1
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
acc[val.profitcenter_part] =
|
||||
acc[val.profitcenter_part].add(DineroAmount);
|
||||
|
||||
@@ -431,7 +431,7 @@ async function QueryDmsCustomerById(socket, JobData, CustomerId) {
|
||||
|
||||
async function QueryDmsCustomerByName(socket, JobData) {
|
||||
const ownerName = (
|
||||
JobData.ownr_co_nm && JobData.ownr_co_nm !== ""
|
||||
JobData.ownr_co_nm
|
||||
? JobData.ownr_co_nm
|
||||
: `${JobData.ownr_ln},${JobData.ownr_fn}`
|
||||
).replace(replaceSpecialRegex, "");
|
||||
@@ -725,7 +725,7 @@ async function InsertDmsVehicle(socket) {
|
||||
manufacturer: {},
|
||||
vehicle: {
|
||||
deliveryDate: moment()
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
.format("YYYYMMDD"),
|
||||
licensePlateNo: socket.JobData.plate_no,
|
||||
make: socket.txEnvelope.dms_make,
|
||||
@@ -854,7 +854,7 @@ async function UpdateDmsVehicle(socket) {
|
||||
socket.DMSVeh.dealer.inServiceDate ||
|
||||
socket.txEnvelope.inservicedate
|
||||
)
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
.toISOString(),
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -57,10 +57,10 @@ exports.default = async (req, res) => {
|
||||
TransmitDateTime: moment().format(momentFormat), // Omitted from ARMS docs
|
||||
},
|
||||
EventInfo: {
|
||||
AssignmentEvent: {
|
||||
CreateDateTime:
|
||||
job.asgn_date && moment(job.asgn_date).format(momentFormat),
|
||||
},
|
||||
// AssignmentEvent: {
|
||||
// CreateDateTime:
|
||||
// job.asgn_date && moment(job.asgn_date).format(momentFormat),
|
||||
// },
|
||||
// EstimateEvent: {
|
||||
// UploadDateTime: moment().format(momentFormat),
|
||||
// },
|
||||
@@ -258,9 +258,9 @@ exports.default = async (req, res) => {
|
||||
VINNum: job.v_vin,
|
||||
},
|
||||
},
|
||||
License: {
|
||||
LicensePlateNum: job.plate_no,
|
||||
},
|
||||
// License: {
|
||||
// LicensePlateNum: job.plate_no,
|
||||
// },
|
||||
VehicleDesc: {
|
||||
//ProductionDate: "2009-10",
|
||||
ModelYear:
|
||||
@@ -845,7 +845,7 @@ exports.default = async (req, res) => {
|
||||
|
||||
const entegralResponse =
|
||||
await entegralSoapClient.RepairOrderFolderAddRqAsync(
|
||||
[jobsToPush[0]],
|
||||
jobsToPush,
|
||||
function (err, result, rawResponse, soapHeader, rawRequest) {
|
||||
fs.writeFileSync(`./logs/arms-request.xml`, rawRequest);
|
||||
fs.writeFileSync(`./logs/arms-response.xml`, rawResponse);
|
||||
@@ -910,13 +910,10 @@ function GetDocumentstatus(job, bodyshop) {
|
||||
function GetRepairStatusCode(job) {
|
||||
return "25";
|
||||
}
|
||||
|
||||
function GetProductionStageCode(job, bodyshop) {
|
||||
const result = (bodyshop.features?.entegral).find(
|
||||
(k) => k.status === job.status
|
||||
);
|
||||
|
||||
return result?.code || "33";
|
||||
if (bodyshop.md_ro_statuses.post_production_statuses.includes(job.status))
|
||||
return "8D";
|
||||
return "33";
|
||||
}
|
||||
|
||||
function isEmpty(obj) {
|
||||
|
||||
@@ -9,9 +9,6 @@ const axios = require("axios");
|
||||
let nodemailer = require("nodemailer");
|
||||
let aws = require("aws-sdk");
|
||||
const logger = require("../utils/logger");
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
const queries = require("../graphql-client/queries");
|
||||
|
||||
const ses = new aws.SES({
|
||||
apiVersion: "latest",
|
||||
|
||||
@@ -144,11 +141,7 @@ exports.sendEmail = async (req, res) => {
|
||||
subject: req.body.subject,
|
||||
// info,
|
||||
});
|
||||
logEmail(req, {
|
||||
to: req.body.to,
|
||||
cc: req.body.cc,
|
||||
subject: req.body.subject,
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true, //response: info
|
||||
});
|
||||
@@ -161,12 +154,7 @@ exports.sendEmail = async (req, res) => {
|
||||
subject: req.body.subject,
|
||||
error: err,
|
||||
});
|
||||
logEmail(req, {
|
||||
to: req.body.to,
|
||||
cc: req.body.cc,
|
||||
subject: req.body.subject,
|
||||
bodyshopid: req.body.bodyshopid,
|
||||
});
|
||||
|
||||
res.status(500).json({ success: false, error: err });
|
||||
}
|
||||
}
|
||||
@@ -178,27 +166,3 @@ async function getImage(imageUrl) {
|
||||
let raw = Buffer.from(image.data).toString("base64");
|
||||
return "data:" + image.headers["content-type"] + ";base64," + raw;
|
||||
}
|
||||
|
||||
async function logEmail(req, email) {
|
||||
try {
|
||||
await client.request(queries.INSERT_EMAIL_AUDIT, {
|
||||
email: {
|
||||
to: email.to,
|
||||
cc: email.cc,
|
||||
subject: email.subject,
|
||||
bodyshopid: req.body.bodyshopid,
|
||||
useremail: req.user.email,
|
||||
contents: req.body.html,
|
||||
jobid: req.body.jobid,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.log("email-log-error", "error", req.user.email, null, {
|
||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||
to: req.body.to,
|
||||
cc: req.body.cc,
|
||||
subject: req.body.subject,
|
||||
// info,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,9 +209,6 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
|
||||
prt_dsmk_p
|
||||
prt_dsmk_m
|
||||
tax_part
|
||||
line_ref
|
||||
unq_seq
|
||||
lbr_op
|
||||
}
|
||||
}
|
||||
bodyshops(where: {associations: {active: {_eq: true}}}) {
|
||||
@@ -405,8 +402,6 @@ query QUERY_JOBS_FOR_PBS_EXPORT($id: uuid!) {
|
||||
profitcenter_part
|
||||
db_ref
|
||||
prt_dsmk_p
|
||||
unq_seq
|
||||
line_ref
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1117,7 +1112,6 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
|
||||
joblines(where: { removed: { _eq: false } }) {
|
||||
id
|
||||
db_ref
|
||||
line_ref
|
||||
unq_seq
|
||||
line_ind
|
||||
tax_part
|
||||
@@ -1226,7 +1220,6 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
|
||||
id
|
||||
db_ref
|
||||
unq_seq
|
||||
line_ref
|
||||
line_ind
|
||||
tax_part
|
||||
line_desc
|
||||
@@ -1322,8 +1315,6 @@ exports.GET_ENTEGRAL_SHOPS = `query GET_AUTOHOUSE_SHOPS {
|
||||
entegral_id
|
||||
md_responsibility_centers
|
||||
imexshopid
|
||||
timezone
|
||||
features
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -1452,8 +1443,7 @@ exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
|
||||
op_code_desc
|
||||
profitcenter_labor
|
||||
profitcenter_part
|
||||
line_ref
|
||||
unq_seq
|
||||
prt_dsmk_p
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1604,10 +1594,3 @@ exports.INSERT_EXPORT_LOG = `
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
exports.INSERT_EMAIL_AUDIT = `mutation INSERT_EMAIL_AUDIT($email: email_audit_trail_insert_input!) {
|
||||
insert_email_audit_trail_one(object: $email) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -4,7 +4,6 @@ const queries = require("../graphql-client/queries");
|
||||
const _ = require("lodash");
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
const logger = require("../utils/logger");
|
||||
const { DiscountNotAlreadyCounted } = require("./job-totals");
|
||||
// Dinero.defaultCurrency = "USD";
|
||||
// Dinero.globalLocale = "en-CA";
|
||||
Dinero.globalRoundingMode = "HALF_EVEN";
|
||||
@@ -219,7 +218,7 @@ async function JobCostingMulti(req, res) {
|
||||
(multiSummary.summaryData.totalLaborGp.getAmount() /
|
||||
multiSummary.summaryData.totalLaborSales.getAmount()) *
|
||||
100
|
||||
).toFixed(1);
|
||||
).toFixed(2);
|
||||
multiSummary.summaryData.totalLaborGppercentFormatted = formatGpPercent(
|
||||
multiSummary.summaryData.totalLaborGppercent
|
||||
);
|
||||
@@ -228,7 +227,7 @@ async function JobCostingMulti(req, res) {
|
||||
(multiSummary.summaryData.totalPartsGp.getAmount() /
|
||||
multiSummary.summaryData.totalPartsSales.getAmount()) *
|
||||
100
|
||||
).toFixed(1);
|
||||
).toFixed(2);
|
||||
|
||||
multiSummary.summaryData.totalPartsGppercentFormatted = formatGpPercent(
|
||||
multiSummary.summaryData.totalPartsGppercent
|
||||
@@ -238,7 +237,7 @@ async function JobCostingMulti(req, res) {
|
||||
(multiSummary.summaryData.totalAdditionalGp.getAmount() /
|
||||
multiSummary.summaryData.totalAdditionalSales.getAmount()) *
|
||||
100
|
||||
).toFixed(1);
|
||||
).toFixed(2);
|
||||
|
||||
multiSummary.summaryData.totalAdditionalGppercentFormatted =
|
||||
formatGpPercent(multiSummary.summaryData.totalAdditionalGppercent);
|
||||
@@ -247,7 +246,7 @@ async function JobCostingMulti(req, res) {
|
||||
(multiSummary.summaryData.totalSubletGp.getAmount() /
|
||||
multiSummary.summaryData.totalSubletSales.getAmount()) *
|
||||
100
|
||||
).toFixed(1);
|
||||
).toFixed(2);
|
||||
|
||||
multiSummary.summaryData.totalSubletGppercentFormatted = formatGpPercent(
|
||||
multiSummary.summaryData.totalSubletGppercent
|
||||
@@ -257,7 +256,7 @@ async function JobCostingMulti(req, res) {
|
||||
(multiSummary.summaryData.gpdollars.getAmount() /
|
||||
multiSummary.summaryData.totalSales.getAmount()) *
|
||||
100
|
||||
).toFixed(1);
|
||||
).toFixed(2);
|
||||
|
||||
multiSummary.summaryData.gppercentFormatted = formatGpPercent(
|
||||
multiSummary.summaryData.gppercent
|
||||
@@ -283,7 +282,7 @@ async function JobCostingMulti(req, res) {
|
||||
(
|
||||
(c.gpdollars_dinero.getAmount() / c.sales_dinero.getAmount()) *
|
||||
100
|
||||
).toFixed(1)
|
||||
).toFixed(2)
|
||||
),
|
||||
};
|
||||
});
|
||||
@@ -309,8 +308,7 @@ function GenerateCostingData(job) {
|
||||
job.bodyshop.md_responsibility_centers.defaults.profits;
|
||||
const allCenters = _.union(
|
||||
job.bodyshop.md_responsibility_centers.profits.map((p) => p.name),
|
||||
job.bodyshop.md_responsibility_centers.costs.map((p) => p.name),
|
||||
["Unknown"]
|
||||
job.bodyshop.md_responsibility_centers.costs.map((p) => p.name)
|
||||
);
|
||||
|
||||
const materialsHours = { mapaHrs: 0, mashHrs: 0 };
|
||||
@@ -332,15 +330,12 @@ function GenerateCostingData(job) {
|
||||
}
|
||||
if (val.mod_lbr_ty) {
|
||||
const laborProfitCenter =
|
||||
val.profitcenter_labor ||
|
||||
defaultProfits[val.mod_lbr_ty] ||
|
||||
"Unknown";
|
||||
val.profitcenter_labor || defaultProfits[val.mod_lbr_ty] || "?";
|
||||
|
||||
if (laborProfitCenter === "Unknown")
|
||||
if (laborProfitCenter === "?")
|
||||
console.log("Unknown type", val.line_desc, val.mod_lbr_ty);
|
||||
|
||||
const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`;
|
||||
|
||||
const laborAmount = Dinero({
|
||||
amount: Math.round((job[rateName] || 0) * 100),
|
||||
}).multiply(val.mod_lb_hrs || 0);
|
||||
@@ -349,19 +344,6 @@ function GenerateCostingData(job) {
|
||||
acc.labor[laborProfitCenter] =
|
||||
acc.labor[laborProfitCenter].add(laborAmount);
|
||||
|
||||
if (
|
||||
val.mod_lb_hrs === 0 &&
|
||||
val.act_price > 0 &&
|
||||
val.lbr_op === "OP14"
|
||||
) {
|
||||
//Scenario where SGI may pay out hours using a part price.
|
||||
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(
|
||||
Dinero({
|
||||
amount: Math.round((val.act_price || 0) * 100),
|
||||
}).multiply(val.part_qty)
|
||||
);
|
||||
}
|
||||
|
||||
if (val.mod_lbr_ty === "LAR") {
|
||||
materialsHours.mapaHrs += val.mod_lb_hrs || 0;
|
||||
}
|
||||
@@ -377,9 +359,9 @@ function GenerateCostingData(job) {
|
||||
val.part_type !== "PASL"
|
||||
) {
|
||||
const partsProfitCenter =
|
||||
val.profitcenter_part || defaultProfits[val.part_type] || "Unknown";
|
||||
val.profitcenter_part || defaultProfits[val.part_type] || "?";
|
||||
|
||||
if (partsProfitCenter === "Unknown")
|
||||
if (partsProfitCenter === "?")
|
||||
console.log("Unknown type", val.line_desc, val.part_type);
|
||||
|
||||
if (!partsProfitCenter)
|
||||
@@ -393,18 +375,14 @@ function GenerateCostingData(job) {
|
||||
})
|
||||
.multiply(val.part_qty || 1)
|
||||
.add(
|
||||
((val.prt_dsmk_m && val.prt_dsmk_m !== 0) ||
|
||||
(val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(val, job.joblines)
|
||||
? val.prt_dsmk_m
|
||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(val.act_price * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
: Dinero()
|
||||
val.prt_dsmk_m && val.prt_dsmk_m !== 0
|
||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(val.act_price * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
);
|
||||
if (!acc.parts[partsProfitCenter])
|
||||
acc.parts[partsProfitCenter] = Dinero();
|
||||
@@ -417,9 +395,9 @@ function GenerateCostingData(job) {
|
||||
(val.part_type === "PAS" || val.part_type === "PASL")
|
||||
) {
|
||||
const partsProfitCenter =
|
||||
val.profitcenter_part || defaultProfits[val.part_type] || "Unknown";
|
||||
val.profitcenter_part || defaultProfits[val.part_type] || "?";
|
||||
|
||||
if (partsProfitCenter === "Unknown")
|
||||
if (partsProfitCenter === "?")
|
||||
console.log("Unknown type", val.line_desc, val.part_type);
|
||||
|
||||
if (!partsProfitCenter)
|
||||
@@ -433,18 +411,14 @@ function GenerateCostingData(job) {
|
||||
})
|
||||
.multiply(val.part_qty || 1)
|
||||
.add(
|
||||
((val.prt_dsmk_m && val.prt_dsmk_m !== 0) ||
|
||||
(val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(val, job.joblines)
|
||||
? val.prt_dsmk_m
|
||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(val.act_price * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
: Dinero()
|
||||
val.prt_dsmk_m && val.prt_dsmk_m !== 0
|
||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(val.act_price * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
);
|
||||
if (!acc.sublet[partsProfitCenter])
|
||||
acc.sublet[partsProfitCenter] = Dinero();
|
||||
@@ -459,20 +433,17 @@ function GenerateCostingData(job) {
|
||||
const partsProfitCenter =
|
||||
val.profitcenter_part ||
|
||||
getAdditionalCostCenter(val, defaultProfits) ||
|
||||
"Unknown";
|
||||
"?";
|
||||
|
||||
if (partsProfitCenter === "Unknown") {
|
||||
if (partsProfitCenter === "?") {
|
||||
console.log("Unknown type", val.line_desc, val.part_type);
|
||||
}
|
||||
const partsAmount = Dinero({
|
||||
amount: Math.round((val.act_price || 0) * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 1)
|
||||
.add(
|
||||
((val.prt_dsmk_m && val.prt_dsmk_m !== 0) ||
|
||||
(val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(val, job.joblines)
|
||||
? val.prt_dsmk_m
|
||||
} else {
|
||||
const partsAmount = Dinero({
|
||||
amount: Math.round((val.act_price || 0) * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 1)
|
||||
.add(
|
||||
val.prt_dsmk_m && val.prt_dsmk_m !== 0
|
||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(val.act_price * 100),
|
||||
@@ -480,13 +451,13 @@ function GenerateCostingData(job) {
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
: Dinero()
|
||||
);
|
||||
);
|
||||
|
||||
if (!acc.additional[partsProfitCenter])
|
||||
acc.additional[partsProfitCenter] = Dinero();
|
||||
acc.additional[partsProfitCenter] =
|
||||
acc.additional[partsProfitCenter].add(partsAmount);
|
||||
if (!acc.additional[partsProfitCenter])
|
||||
acc.additional[partsProfitCenter] = Dinero();
|
||||
acc.additional[partsProfitCenter] =
|
||||
acc.additional[partsProfitCenter].add(partsAmount);
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
@@ -759,9 +730,9 @@ function GenerateCostingData(job) {
|
||||
.add(sale_sublet);
|
||||
const gpdollars = totalSales.subtract(costs);
|
||||
const gppercent = (
|
||||
(gpdollars.getAmount() / Math.abs(totalSales.getAmount())) *
|
||||
(gpdollars.getAmount() / totalSales.getAmount()) *
|
||||
100
|
||||
).toFixed(1);
|
||||
).toFixed(2);
|
||||
|
||||
//Push summary data to avoid extra loop.
|
||||
summaryData.totalLaborSales = summaryData.totalLaborSales.add(sale_labor);
|
||||
@@ -852,7 +823,7 @@ function GenerateCostingData(job) {
|
||||
(summaryData.totalLaborGp.getAmount() /
|
||||
summaryData.totalLaborSales.getAmount()) *
|
||||
100
|
||||
).toFixed(1);
|
||||
).toFixed(2);
|
||||
summaryData.totalLaborGppercentFormatted = formatGpPercent(
|
||||
summaryData.totalLaborGppercent
|
||||
);
|
||||
@@ -864,7 +835,7 @@ function GenerateCostingData(job) {
|
||||
(summaryData.totalPartsGp.getAmount() /
|
||||
summaryData.totalPartsSales.getAmount()) *
|
||||
100
|
||||
).toFixed(1);
|
||||
).toFixed(2);
|
||||
summaryData.totalPartsGppercentFormatted = formatGpPercent(
|
||||
summaryData.totalPartsGppercent
|
||||
);
|
||||
@@ -875,7 +846,7 @@ function GenerateCostingData(job) {
|
||||
(summaryData.totalAdditionalGp.getAmount() /
|
||||
summaryData.totalAdditionalSales.getAmount()) *
|
||||
100
|
||||
).toFixed(1);
|
||||
).toFixed(2);
|
||||
summaryData.totalAdditionalGppercentFormatted = formatGpPercent(
|
||||
summaryData.totalAdditionalGppercent
|
||||
);
|
||||
@@ -886,7 +857,7 @@ function GenerateCostingData(job) {
|
||||
(summaryData.totalSubletGp.getAmount() /
|
||||
summaryData.totalSubletSales.getAmount()) *
|
||||
100
|
||||
).toFixed(1);
|
||||
).toFixed(2);
|
||||
summaryData.totalSubletGppercentFormatted = formatGpPercent(
|
||||
summaryData.totalSubletGppercent
|
||||
);
|
||||
@@ -895,10 +866,9 @@ function GenerateCostingData(job) {
|
||||
summaryData.totalCost
|
||||
);
|
||||
summaryData.gppercent = (
|
||||
(summaryData.gpdollars.getAmount() /
|
||||
Math.abs(summaryData.totalSales.getAmount())) *
|
||||
(summaryData.gpdollars.getAmount() / summaryData.totalSales.getAmount()) *
|
||||
100
|
||||
).toFixed(1);
|
||||
).toFixed(2);
|
||||
|
||||
if (isNaN(summaryData.gppercent)) summaryData.gppercentFormatted = 0;
|
||||
else if (!isFinite(summaryData.gppercent))
|
||||
|
||||
@@ -362,27 +362,28 @@ function CalculateRatesTotals(ratesList) {
|
||||
}
|
||||
|
||||
function CalculatePartsTotals(jobLines) {
|
||||
const jl = jobLines.filter((jl) => !jl.removed);
|
||||
|
||||
const ret = jl.reduce(
|
||||
(acc, value) => {
|
||||
switch (value.part_type) {
|
||||
case "PAS":
|
||||
case "PASL":
|
||||
return {
|
||||
...acc,
|
||||
sublets: {
|
||||
...acc.sublets,
|
||||
subtotal: acc.sublets.subtotal.add(
|
||||
Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
})
|
||||
.multiply(value.part_qty || 0)
|
||||
.add(
|
||||
((value.prt_dsmk_m && value.prt_dsmk_m !== 0) ||
|
||||
(value.prt_dsmk_p && value.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(value, jl)
|
||||
? value.prt_dsmk_m
|
||||
const ret = jobLines
|
||||
.filter((jl) => !jl.removed)
|
||||
.reduce(
|
||||
(acc, value) => {
|
||||
switch (value.part_type) {
|
||||
case "PAS":
|
||||
case "PASL":
|
||||
return {
|
||||
...acc,
|
||||
sublets: {
|
||||
...acc.sublets,
|
||||
subtotal: acc.sublets.subtotal.add(
|
||||
Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
})
|
||||
.multiply(value.part_qty || 0)
|
||||
.add(
|
||||
(value.db_ref === "900511" ||
|
||||
value.db_ref === "900510" ||
|
||||
value.db_ref === "900500") &&
|
||||
value.prt_dsmk_m &&
|
||||
value.prt_dsmk_m !== 0
|
||||
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
@@ -390,28 +391,28 @@ function CalculatePartsTotals(jobLines) {
|
||||
.multiply(value.part_qty || 0)
|
||||
.percentage(Math.abs(value.prt_dsmk_p || 0))
|
||||
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
|
||||
: Dinero()
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
default:
|
||||
if (
|
||||
!value.part_type &&
|
||||
value.db_ref !== "900510" &&
|
||||
value.db_ref !== "900511"
|
||||
)
|
||||
return acc;
|
||||
return {
|
||||
...acc,
|
||||
parts: {
|
||||
...acc.parts,
|
||||
prt_dsmk_total: acc.parts.prt_dsmk_total.add(
|
||||
((value.prt_dsmk_m && value.prt_dsmk_m !== 0) ||
|
||||
(value.prt_dsmk_p && value.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(value, jl)
|
||||
? value.prt_dsmk_m
|
||||
default:
|
||||
if (
|
||||
!value.part_type &&
|
||||
value.db_ref !== "900510" &&
|
||||
value.db_ref !== "900511"
|
||||
)
|
||||
return acc;
|
||||
return {
|
||||
...acc,
|
||||
parts: {
|
||||
...acc.parts,
|
||||
prt_dsmk_total: acc.parts.prt_dsmk_total.add(
|
||||
(value.db_ref === "900511" ||
|
||||
value.db_ref === "900510" ||
|
||||
value.db_ref === "900500") &&
|
||||
value.prt_dsmk_m &&
|
||||
value.prt_dsmk_m !== 0
|
||||
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
@@ -419,45 +420,47 @@ function CalculatePartsTotals(jobLines) {
|
||||
.multiply(value.part_qty || 0)
|
||||
.percentage(Math.abs(value.prt_dsmk_p || 0))
|
||||
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
|
||||
: Dinero()
|
||||
),
|
||||
...(value.part_type
|
||||
? {
|
||||
list: {
|
||||
...acc.parts.list,
|
||||
[value.part_type]:
|
||||
acc.parts.list[value.part_type] &&
|
||||
acc.parts.list[value.part_type].total
|
||||
? {
|
||||
total: acc.parts.list[value.part_type].total.add(
|
||||
Dinero({
|
||||
),
|
||||
...(value.part_type
|
||||
? {
|
||||
list: {
|
||||
...acc.parts.list,
|
||||
[value.part_type]:
|
||||
acc.parts.list[value.part_type] &&
|
||||
acc.parts.list[value.part_type].total
|
||||
? {
|
||||
total: acc.parts.list[
|
||||
value.part_type
|
||||
].total.add(
|
||||
Dinero({
|
||||
amount: Math.round(
|
||||
(value.act_price || 0) * 100
|
||||
),
|
||||
}).multiply(value.part_qty || 0)
|
||||
),
|
||||
}
|
||||
: {
|
||||
total: Dinero({
|
||||
amount: Math.round(
|
||||
(value.act_price || 0) * 100
|
||||
),
|
||||
}).multiply(value.part_qty || 0)
|
||||
),
|
||||
}
|
||||
: {
|
||||
total: Dinero({
|
||||
amount: Math.round(
|
||||
(value.act_price || 0) * 100
|
||||
),
|
||||
}).multiply(value.part_qty || 0),
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
subtotal: acc.parts.subtotal
|
||||
.add(
|
||||
Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
}).multiply(value.part_qty || 0)
|
||||
)
|
||||
.add(
|
||||
((value.prt_dsmk_m && value.prt_dsmk_m !== 0) ||
|
||||
(value.prt_dsmk_p && value.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(value, jl)
|
||||
? value.prt_dsmk_m
|
||||
}).multiply(value.part_qty || 0),
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
subtotal: acc.parts.subtotal
|
||||
.add(
|
||||
Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
}).multiply(value.part_qty || 0)
|
||||
)
|
||||
.add(
|
||||
(value.db_ref === "900511" ||
|
||||
value.db_ref === "900510" ||
|
||||
value.db_ref === "900500") &&
|
||||
value.prt_dsmk_m &&
|
||||
value.prt_dsmk_m !== 0
|
||||
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
@@ -465,26 +468,25 @@ function CalculatePartsTotals(jobLines) {
|
||||
.multiply(value.part_qty || 0)
|
||||
.percentage(Math.abs(value.prt_dsmk_p || 0))
|
||||
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
|
||||
: Dinero()
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
parts: {
|
||||
list: {},
|
||||
prt_dsmk_total: Dinero(),
|
||||
subtotal: Dinero({ amount: 0 }),
|
||||
total: Dinero({ amount: 0 }),
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
sublets: {
|
||||
subtotal: Dinero({ amount: 0 }),
|
||||
{
|
||||
parts: {
|
||||
list: {},
|
||||
prt_dsmk_total: Dinero(),
|
||||
subtotal: Dinero({ amount: 0 }),
|
||||
total: Dinero({ amount: 0 }),
|
||||
},
|
||||
sublets: {
|
||||
subtotal: Dinero({ amount: 0 }),
|
||||
|
||||
total: Dinero({ amount: 0 }),
|
||||
},
|
||||
}
|
||||
);
|
||||
total: Dinero({ amount: 0 }),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
parts: {
|
||||
@@ -704,16 +706,7 @@ function CalculateTaxesTotals(job, otherTotals) {
|
||||
exports.default = Totals;
|
||||
|
||||
function DiscountNotAlreadyCounted(jobline, joblines) {
|
||||
if (
|
||||
//If it's not a discount line, then it definitely hasn't been counted yet.
|
||||
jobline.db_ref !== "900510" &&
|
||||
jobline.db_ref !== "900511"
|
||||
)
|
||||
return true;
|
||||
|
||||
if (jobline.db_ref !== "900510") return true;
|
||||
const ParentLine = joblines.find((j) => j.unq_seq === jobline.line_ref);
|
||||
|
||||
return ParentLine && !(ParentLine.prt_dsmk_m && ParentLine.prt_dsmk_m !== 0);
|
||||
}
|
||||
|
||||
exports.DiscountNotAlreadyCounted = DiscountNotAlreadyCounted;
|
||||
|
||||
Reference in New Issue
Block a user