Compare commits

...

45 Commits

Author SHA1 Message Date
Patrick Fic
b5a2be9392 Package updates. 2022-04-18 10:27:06 -07:00
Patrick Fic
df1e15be60 All package upgrades. 2022-04-13 17:46:46 -07:00
Patrick Fic
274b572915 Resolve vendor delete error message. 2022-04-13 17:04:52 -07:00
Patrick Fic
fa4aa5ca8f IO-1689 Resolve partner popup checks. 2022-04-13 16:53:31 -07:00
Patrick Fic
2b537b65dd Add migrations that were not created for previous tickets. 2022-04-13 16:32:51 -07:00
Patrick Fic
75b2cb18ca IO-1826 Refetch on convert if GST registrant. 2022-04-13 15:07:47 -07:00
Patrick Fic
5167668958 IO-1824 Allow posting to closed ROs. 2022-04-13 15:02:07 -07:00
Patrick Fic
1eacf43669 IO-1817 Delete existing bill line. 2022-04-13 14:49:33 -07:00
Patrick Fic
77ed64969e IO-1822 Add manual ATS calculation. 2022-04-13 12:34:31 -07:00
Patrick Fic
44ff032acc IO-1819 Allow private estimate lines to be modified. 2022-04-13 10:26:17 -07:00
Patrick Fic
542637edb2 Global search improvement. 2022-04-12 13:17:56 -07:00
Patrick Fic
f7971ed60c Add further days out for smart scheduling. 2022-04-12 08:56:34 -07:00
Patrick Fic
ca71b0479a IO-1394 Disable delete on vendor create. 2022-04-12 07:47:21 -07:00
Patrick Fic
515de38fe6 IO-1689 Remove partner and acct path popups. 2022-04-11 17:12:27 -07:00
Patrick Fic
113c62b36f Minor UI Changes for UB 2022-04-11 16:39:04 -07:00
Patrick Fic
c0ea5a9818 IO-1464 Edit assigned vendor on bill. 2022-04-11 13:34:43 -07:00
Patrick Fic
6b13dffe68 IO-1442 Update login error codes. 2022-04-11 13:28:51 -07:00
Patrick Fic
51d49be16e IO-1661 Add parts order comments presets. 2022-04-11 13:17:14 -07:00
Patrick Fic
04c3abd65f Merged in release/2022-04-08 (pull request #443)
Release/2022 04 08
2022-04-08 20:42:13 +00:00
Patrick Fic
29dab7312b IO-1810 Resolve time ticke tmodal issue. 2022-04-07 17:36:58 -07:00
Patrick Fic
01eb68fda1 IO-1589 2022-04-07 17:28:57 -07:00
Patrick Fic
a4c4329253 IO-1711 Cancel appointments from job 2022-04-07 15:22:27 -07:00
Patrick Fic
b738fc9007 IO-1751 Add parts order data to repair data screen. 2022-04-07 10:56:02 -07:00
Patrick Fic
5484358b32 Merged in release/2022-04-08 (pull request #441)
Autohouse update.
2022-04-07 15:00:32 +00:00
Patrick Fic
b48d7512a2 Autohouse update. 2022-04-07 07:59:54 -07:00
Patrick Fic
ae3226efb8 Merged in release/2022-04-08 (pull request #440)
Release/2022 04 08
2022-04-06 22:13:56 +00:00
Patrick Fic
da9ddfc419 Autohouse update. 2022-04-06 15:13:23 -07:00
Patrick Fic
901902c1d1 Missing translations for QBO in shop setup. 2022-04-04 14:07:16 -07:00
Patrick Fic
79bfb12063 Merged in release/2022-04-02 (pull request #439)
Test
2022-04-01 21:05:31 +00:00
Patrick Fic
c1bf112aac Resolve CI Issue. 2022-03-31 14:43:41 -07:00
Patrick Fic
ecfb3e91cd Package updates. 2022-03-31 14:25:20 -07:00
Patrick Fic
e25606070b Resolve Firefox Rendering issues. 2022-03-31 14:25:16 -07:00
Patrick Fic
28f0d9a4b2 IO-1805 QBO Modifications for No 1 group. 2022-03-31 12:50:06 -07:00
Patrick Fic
c125cd8ca2 Add end to autohouse export. 2022-03-30 14:45:09 -07:00
Patrick Fic
123f94a0f5 IO-1666 Sticky headers on production board. 2022-03-30 10:47:09 -07:00
Patrick Fic
d601617819 Add job total cehck for Autohouse. 2022-03-30 08:34:55 -07:00
Patrick Fic
80bd2dc6d8 IO-1794 Rsolve password reset issue. 2022-03-29 09:24:09 -07:00
Patrick Fic
45bd2d3281 IO-1778 Reconciliation modal UI updates. 2022-03-29 09:17:21 -07:00
Patrick Fic
b4304c743e IO-1793 Add detail drawer to production board view. 2022-03-29 08:31:15 -07:00
Patrick Fic
faf9fbb9d8 IO-1638 Parts Received % on prod list. 2022-03-29 08:14:20 -07:00
Patrick Fic
6d1581a4e1 IO-1790 Add invoice date to receivables export. 2022-03-28 16:17:40 -07:00
Patrick Fic
28f6c72de1 Resolve search on payments. 2022-03-28 13:31:16 -07:00
Patrick Fic
54577ac680 IO-1802 Add Lag Time RO to print center. 2022-03-25 15:32:23 -06:00
Patrick Fic
c912681793 Merged in release/2022-03-25 (pull request #434)
release/2022-03-25

Approved-by: Patrick Fic
2022-03-25 13:22:19 +00:00
Patrick Fic
89b515fff4 Merged in release/2022-03-25 (pull request #433)
Release/2022 03 25
2022-03-25 01:04:39 +00:00
101 changed files with 40506 additions and 12660 deletions

View File

@@ -1,21 +0,0 @@
{
"env": {
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"no-console": "off"
},
"settings": {},
"plugins": ["cypress"]
}

View File

@@ -2354,6 +2354,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>is_credit_memo_short</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> <concept_node>
<name>local_tax_rate</name> <name>local_tax_rate</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -3633,6 +3654,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>bill_allow_post_to_closed</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> <concept_node>
<name>bill_federal_tax_rate</name> <name>bill_federal_tax_rate</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4367,6 +4409,27 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<concept_node>
<name>last_name_first</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> <concept_node>
<name>lastnumberworkingdays</name> <name>lastnumberworkingdays</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4711,6 +4774,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>private</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> <concept_node>
<name>state</name> <name>state</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4818,6 +4902,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>md_parts_order_comment</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> <concept_node>
<name>md_payment_types</name> <name>md_payment_types</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -8822,6 +8927,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>qbo_departmentid</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>qbo_usa</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> <concept_node>
<name>rbac</name> <name>rbac</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20229,6 +20376,27 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<concept_node>
<name>auto_add_ats</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> <concept_node>
<name>ca_bc_pvrt</name> <name>ca_bc_pvrt</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -23131,6 +23299,27 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<concept_node>
<name>rate_ats</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> <concept_node>
<name>rate_la1</name> <name>rate_la1</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -26589,6 +26778,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>parts_received</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> <concept_node>
<name>parts_tax_rates</name> <name>parts_tax_rates</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -29881,6 +30091,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>cancelallappointments</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> <concept_node>
<name>closejob</name> <name>closejob</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -31189,6 +31420,37 @@
</folder_node> </folder_node>
</children> </children>
</folder_node> </folder_node>
<folder_node>
<name>owner</name>
<children>
<folder_node>
<name>labels</name>
<children>
<concept_node>
<name>noownerinfo</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>
</children>
</folder_node>
<folder_node> <folder_node>
<name>owners</name> <name>owners</name>
<children> <children>
@@ -32557,6 +32819,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>notyetordered</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> <concept_node>
<name>oec</name> <name>oec</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -34925,6 +35208,27 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<concept_node>
<name>lag_time_ro</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> <concept_node>
<name>mechanical_authorization</name> <name>mechanical_authorization</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -36487,6 +36791,58 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<folder_node>
<name>bodyshop</name>
<children>
<folder_node>
<name>labels</name>
<children>
<concept_node>
<name>qbo_departmentid</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>qbo_usa</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>
</children>
</folder_node>
<concept_node> <concept_node>
<name>cardsettings</name> <name>cardsettings</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -36844,6 +37200,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>stickyheader</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> <concept_node>
<name>sublets</name> <name>sublets</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -42938,6 +43315,63 @@
</folder_node> </folder_node>
</children> </children>
</folder_node> </folder_node>
<folder_node>
<name>users</name>
<children>
<folder_node>
<name>errors</name>
<children>
<folder_node>
<name>signinerror</name>
<children>
<concept_node>
<name>auth/user-not-found</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>auth/wrong-password</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>
</children>
</folder_node>
</children>
</folder_node>
<folder_node> <folder_node>
<name>vehicles</name> <name>vehicles</name>
<children> <children>

View File

@@ -3,83 +3,89 @@
"version": "0.2.1", "version": "0.2.1",
"private": true, "private": true,
"proxy": "http://localhost:4000", "proxy": "http://localhost:4000",
"browser": {
"fs": false,
"path": false,
"os": false
},
"dependencies": { "dependencies": {
"@apollo/client": "^3.5.6", "@apollo/client": "^3.5.10",
"@asseinfo/react-kanban": "^2.2.0", "@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^6.4.3", "@craco/craco": "^6.4.3",
"@fingerprintjs/fingerprintjs": "^3.3.1", "@fingerprintjs/fingerprintjs": "^3.3.3",
"@sentry/react": "^6.16.1", "@jsreport/browser-client": "^3.1.0",
"@sentry/tracing": "^6.16.1", "@sentry/react": "^6.19.6",
"@splitsoftware/splitio-react": "^1.3.0", "@sentry/tracing": "^6.19.6",
"@stripe/react-stripe-js": "^1.7.0", "@splitsoftware/splitio-react": "^1.4.0",
"@stripe/stripe-js": "^1.22.0", "@stripe/react-stripe-js": "^1.7.1",
"@tanem/react-nprogress": "^3.0.82", "@stripe/stripe-js": "^1.27.0",
"antd": "^4.17.4", "@tanem/react-nprogress": "^5.0.0",
"antd": "^4.19.5",
"apollo-link-logger": "^2.0.0", "apollo-link-logger": "^2.0.0",
"axios": "^0.24.0", "axios": "^0.26.1",
"craco-less": "^1.20.0", "craco-less": "^2.0.0",
"dinero.js": "^1.9.1", "dinero.js": "^1.9.1",
"dotenv": "^10.0.0", "dotenv": "^16.0.0",
"enquire-js": "^0.2.1", "enquire-js": "^0.2.1",
"env-cmd": "^10.1.0", "env-cmd": "^10.1.0",
"exifr": "^7.1.3", "exifr": "^7.1.3",
"firebase": "^9.6.1", "firebase": "^9.6.10",
"graphql": "^16.2.0", "graphql": "^16.3.0",
"i18next": "^21.6.3", "i18next": "^21.6.16",
"i18next-browser-languagedetector": "^6.1.2", "i18next-browser-languagedetector": "^6.1.4",
"jsoneditor": "^9.5.8", "jsoneditor": "^9.7.4",
"jsreport-browser-client-dist": "^1.3.0", "libphonenumber-js": "^1.9.51",
"libphonenumber-js": "^1.9.44", "logrocket": "^2.2.1",
"logrocket": "^2.1.2", "markerjs2": "^2.21.0",
"markerjs2": "^2.17.2",
"moment-business-days": "^1.2.0", "moment-business-days": "^1.2.0",
"moment-timezone": "^0.5.34", "moment-timezone": "^0.5.34",
"phone": "^3.1.10", "phone": "^3.1.15",
"preval.macro": "^5.0.0", "preval.macro": "^5.0.0",
"prop-types": "^15.7.2", "prop-types": "^15.8.1",
"query-string": "^7.0.1", "query-string": "^7.1.1",
"rc-queue-anim": "^2.0.0", "rc-queue-anim": "^2.0.0",
"rc-scroll-anim": "^2.7.6", "rc-scroll-anim": "^2.7.6",
"react": "^17.0.2", "react": "^18.0.0",
"react-big-calendar": "^0.38.2", "react-big-calendar": "^0.40.0",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-cookie": "^4.1.1", "react-cookie": "^4.1.1",
"react-dom": "^17.0.2", "react-dom": "^18.0.0",
"react-drag-listview": "^0.1.8", "react-drag-listview": "^0.1.9",
"react-grid-gallery": "^0.5.5", "react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.3.0", "react-grid-layout": "^1.3.4",
"react-i18next": "^11.15.1", "react-i18next": "^11.16.6",
"react-icons": "^4.3.1", "react-icons": "^4.3.1",
"react-number-format": "^4.9.0", "react-number-format": "^4.9.1",
"react-redux": "^7.2.6", "react-redux": "^7.2.8",
"react-resizable": "^3.0.4", "react-resizable": "^3.0.4",
"react-router-dom": "^5.3.0", "react-router-dom": "^5.3.0",
"react-scripts": "^4.0.3", "react-scripts": "^4.0.3",
"react-sticky": "^6.0.3",
"react-sublime-video": "^0.2.5", "react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.3", "react-virtualized": "^9.22.3",
"recharts": "^2.1.8", "recharts": "^2.1.9",
"redux": "^4.1.2", "redux": "^4.1.2",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"redux-saga": "^1.1.3", "redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.2", "redux-state-sync": "^3.1.2",
"reselect": "^4.1.5", "reselect": "^4.1.5",
"sass": "^1.45.0", "sass": "^1.50.0",
"socket.io-client": "^4.4.0", "socket.io-client": "^4.4.1",
"styled-components": "^5.3.3", "styled-components": "^5.3.5",
"subscriptions-transport-ws": "^0.11.0", "subscriptions-transport-ws": "^0.11.0",
"web-vitals": "^2.1.2", "web-vitals": "^2.1.4",
"workbox-background-sync": "^6.4.2", "workbox-background-sync": "^6.5.3",
"workbox-broadcast-update": "^6.4.2", "workbox-broadcast-update": "^6.5.3",
"workbox-cacheable-response": "^6.4.2", "workbox-cacheable-response": "^6.5.3",
"workbox-core": "^6.4.2", "workbox-core": "^6.5.3",
"workbox-expiration": "^6.4.2", "workbox-expiration": "^6.5.3",
"workbox-google-analytics": "^6.4.2", "workbox-google-analytics": "^6.5.3",
"workbox-navigation-preload": "^6.4.2", "workbox-navigation-preload": "^6.5.3",
"workbox-precaching": "^6.4.2", "workbox-precaching": "^6.5.3",
"workbox-range-requests": "^6.4.2", "workbox-range-requests": "^6.5.3",
"workbox-routing": "^6.4.2", "workbox-routing": "^6.5.3",
"workbox-strategies": "^6.4.2", "workbox-strategies": "^6.5.3",
"workbox-streams": "^6.4.2", "workbox-streams": "^6.5.3",
"yauzl": "^2.10.0" "yauzl": "^2.10.0"
}, },
"scripts": { "scripts": {
@@ -116,11 +122,11 @@
"react-error-overlay": "6.0.9" "react-error-overlay": "6.0.9"
}, },
"devDependencies": { "devDependencies": {
"@sentry/webpack-plugin": "^1.18.3", "@sentry/webpack-plugin": "^1.18.8",
"@testing-library/cypress": "^8.0.2", "@testing-library/cypress": "^8.0.2",
"cypress": "^9.1.1", "cypress": "^9.5.4",
"eslint-plugin-cypress": "^2.12.1", "eslint-plugin-cypress": "^2.12.1",
"react-error-overlay": "6.0.9", "react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.2" "source-map-explorer": "^2.5.2"
} }

View File

@@ -12,6 +12,7 @@ import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -76,14 +77,12 @@ export function AccountingPayablesTableComponent({
render: (text, record) => { render: (text, record) => {
return record.job.owner ? ( return record.job.owner ? (
<Link to={"/manage/owners/" + record.job.owner.id}> <Link to={"/manage/owners/" + record.job.owner.id}>
{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${ <OwnerNameDisplay ownerObject={record.job} />
record.job.ownr_co_nm || ""
}`}
</Link> </Link>
) : ( ) : (
<span>{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${ <span>
record.job.ownr_co_nm || "" <OwnerNameDisplay ownerObject={record.job} />
}`}</span> </span>
); );
}, },
}, },
@@ -162,10 +161,19 @@ export function AccountingPayablesTableComponent({
const dataSource = state.search const dataSource = state.search
? payments.filter( ? payments.filter(
(v) => (v) =>
(v.vendor.name || "") (v.paymentnum || "")
.toLowerCase() .toLowerCase()
.includes(state.search.toLowerCase()) || .includes(state.search.toLowerCase()) ||
(v.invoice_number || "") ((v.job && v.job.ro_number) || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
((v.job && v.job.ownr_fn) || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
((v.job && v.job.ownr_ln) || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
((v.job && v.job.ownr_co_nm) || "")
.toLowerCase() .toLowerCase()
.includes(state.search.toLowerCase()) .includes(state.search.toLowerCase())
) )

View File

@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort, dateSort } from "../../utils/sorters";
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component"; import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component"; import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
@@ -12,6 +12,9 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component"; import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { DateFormatter } from "../../utils/DateFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
@@ -62,6 +65,18 @@ export function AccountingReceivablesTableComponent({
sortOrder: sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order, state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
}, },
{
title: t("jobs.fields.date_invoiced"),
dataIndex: "date_invoiced",
key: "date_invoiced",
sorter: (a, b) => dateSort(a.date_invoiced, b.date_invoiced),
sortOrder:
state.sortedInfo.columnKey === "date_invoiced" &&
state.sortedInfo.order,
render: (text, record) => (
<DateFormatter>{record.date_invoiced}</DateFormatter>
),
},
{ {
title: t("jobs.fields.owner"), title: t("jobs.fields.owner"),
dataIndex: "owner", dataIndex: "owner",
@@ -72,14 +87,12 @@ export function AccountingReceivablesTableComponent({
render: (text, record) => { render: (text, record) => {
return record.owner ? ( return record.owner ? (
<Link to={"/manage/owners/" + record.owner.id}> <Link to={"/manage/owners/" + record.owner.id}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ <OwnerNameDisplay ownerObject={record} />
record.ownr_co_nm || ""
}`}
</Link> </Link>
) : ( ) : (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ <span>
record.ownr_co_nm || "" <OwnerNameDisplay ownerObject={record} />
}`}</span> </span>
); );
}, },
}, },

View File

@@ -14,6 +14,7 @@ import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useLocation, useHistory } from "react-router-dom"; import { useLocation, useHistory } from "react-router-dom";
import { import {
DELETE_BILL_LINE,
INSERT_NEW_BILL_LINES, INSERT_NEW_BILL_LINES,
UPDATE_BILL_LINE, UPDATE_BILL_LINE,
} from "../../graphql/bill-lines.queries"; } from "../../graphql/bill-lines.queries";
@@ -58,6 +59,7 @@ export function BillDetailEditcontainer({
const [update_bill] = useMutation(UPDATE_BILL); const [update_bill] = useMutation(UPDATE_BILL);
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES); const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
const [updateBillLine] = useMutation(UPDATE_BILL_LINE); const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1]) .filter((screen) => !!screen[1])
@@ -107,6 +109,20 @@ export function BillDetailEditcontainer({
}) })
); );
//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) => { billlines.forEach((billline) => {
const { deductedfromlbr, jobline, ...il } = billline; const { deductedfromlbr, jobline, ...il } = billline;
delete il.__typename; delete il.__typename;
@@ -142,6 +158,7 @@ export function BillDetailEditcontainer({
); );
} }
}); });
await Promise.all(updates); await Promise.all(updates);
insertAuditTrail({ insertAuditTrail({

View File

@@ -114,7 +114,7 @@ export function BillFormComponent({
<Form.Item <Form.Item
label={t("bills.fields.vendor")} label={t("bills.fields.vendor")}
name="vendorid" name="vendorid"
style={{ display: billEdit ? "none" : null }} // style={{ display: billEdit ? "none" : null }}
rules={[ rules={[
{ {
required: true, required: true,
@@ -229,6 +229,7 @@ export function BillFormComponent({
({ getFieldValue }) => ({ ({ getFieldValue }) => ({
validator(rule, value) { validator(rule, value) {
if ( if (
!bodyshop.bill_allow_post_to_closed &&
(job.status === bodyshop.md_ro_statuses.default_invoiced || (job.status === bodyshop.md_ro_statuses.default_invoiced ||
job.status === bodyshop.md_ro_statuses.default_exported || job.status === bodyshop.md_ro_statuses.default_exported ||
job.status === bodyshop.md_ro_statuses.default_void) && job.status === bodyshop.md_ro_statuses.default_void) &&

View File

@@ -7,6 +7,7 @@ import { selectSelectedConversation } from "../../redux/messaging/messaging.sele
import { TimeAgoFormatter } from "../../utils/DateFormatter"; import { TimeAgoFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter";
import "./chat-conversation-list.styles.scss"; import "./chat-conversation-list.styles.scss";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
selectedConversation: selectSelectedConversation, selectedConversation: selectSelectedConversation,
@@ -40,9 +41,9 @@ export function ChatConversationListComponent({
{item.job_conversations.length > 0 ? ( {item.job_conversations.length > 0 ? (
<div className="chat-name"> <div className="chat-name">
{item.job_conversations.map((j, idx) => ( {item.job_conversations.map((j, idx) => (
<div key={idx}>{`${j.job.ownr_fn || ""} ${ <div key={idx}>
j.job.ownr_ln || "" <OwnerNameDisplay ownerObject={j.job} />
} ${j.job.ownr_co_nm || ""} `}</div> </div>
))} ))}
</div> </div>
) : ( ) : (

View File

@@ -4,6 +4,7 @@ import React from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries"; import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function ChatConversationTitleTags({ jobConversations }) { export default function ChatConversationTitleTags({ jobConversations }) {
const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG); const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG);
@@ -45,9 +46,8 @@ export default function ChatConversationTitleTags({ jobConversations }) {
onClose={() => handleRemoveTag(item.job.id)} onClose={() => handleRemoveTag(item.job.id)}
> >
<Link to={`/manage/jobs/${item.job.id}`}> <Link to={`/manage/jobs/${item.job.id}`}>
{`${item.job.ro_number || "?"} | ${item.job.ownr_fn || ""} ${ {`${item.job.ro_number || "?"} | `}
item.job.ownr_ln || "" <OwnerNameDisplay ownerObject={item.job} />
} ${item.job.ownr_co_nm || ""}`}
</Link> </Link>
</Tag> </Tag>
))} ))}

View File

@@ -1,7 +1,8 @@
import { CloseCircleOutlined, LoadingOutlined } from "@ant-design/icons"; import { CloseCircleOutlined, LoadingOutlined } from "@ant-design/icons";
import { Select, Empty, Space } from "antd"; import { Empty, Select, Space } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
export default function ChatTagRoComponent({ export default function ChatTagRoComponent({
roOptions, roOptions,
@@ -27,9 +28,7 @@ export default function ChatTagRoComponent({
> >
{roOptions.map((item, idx) => ( {roOptions.map((item, idx) => (
<Select.Option key={item.id || idx}> <Select.Option key={item.id || idx}>
{` ${item.ro_number || ""} | ${item.ownr_fn || ""} ${ {` ${item.ro_number || ""} | ${OwnerNameDisplayFunction(item)}`}
item.ownr_ln || ""
} ${item.ownr_co_nm || ""}`}
</Select.Option> </Select.Option>
))} ))}
</Select> </Select>

View File

@@ -3,7 +3,7 @@ import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function ContractJobBlock({ job }) { export default function ContractJobBlock({ job }) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@@ -23,9 +23,7 @@ export default function ContractJobBlock({ job }) {
} ${(job && job.v_model_desc) || ""}`} } ${(job && job.v_model_desc) || ""}`}
</DataLabel> </DataLabel>
<DataLabel label={t("jobs.fields.owner")}> <DataLabel label={t("jobs.fields.owner")}>
{`${(job && job.ownr_fn) || ""} ${(job && job.ownr_ln) || ""} ${ <OwnerNameDisplay ownerObject={job} />
(job && job.ownr_co_nm) || ""
}`}
</DataLabel> </DataLabel>
</div> </div>
</Card> </Card>

View File

@@ -3,6 +3,7 @@ import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function ContractsJobsComponent({ export default function ContractsJobsComponent({
loading, loading,
@@ -43,17 +44,7 @@ export default function ContractsJobsComponent({
width: "25%", width: "25%",
sortOrder: sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => <OwnerNameDisplay ownerObject={record} />,
return record.owner ? (
<span>
{record.ownr_fn} {record.ownr_ln} {record.ownr_co_nm || ""}
</span>
) : (
<span>{`${record.ownr_fn} ${record.ownr_ln} ${
record.ownr_co_nm || ""
}`}</span>
);
},
}, },
{ {
title: t("jobs.fields.status"), title: t("jobs.fields.status"),

View File

@@ -12,17 +12,22 @@ import { setModalContext } from "../../redux/modals/modals.actions";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import moment from "moment"; import moment from "moment";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
setContractFinderContext: (context) => setContractFinderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "contractFinder" })), dispatch(setModalContext({ context: context, modal: "contractFinder" })),
}); });
export default connect(mapStateToProps, mapDispatchToProps)(ContractsList); export default connect(mapStateToProps, mapDispatchToProps)(ContractsList);
export function ContractsList({ export function ContractsList({
bodyshop,
loading, loading,
contracts, contracts,
refetch, refetch,
@@ -72,7 +77,9 @@ export function ContractsList({
sortOrder: sortOrder:
state.sortedInfo.columnKey === "driver_ln" && state.sortedInfo.order, state.sortedInfo.columnKey === "driver_ln" && state.sortedInfo.order,
render: (text, record) => render: (text, record) =>
`${record.driver_fn || ""} ${record.driver_ln || ""}`, bodyshop.last_name_first
? `${record.driver_ln || ""}, ${record.driver_fn || ""}`
: `${record.driver_fn || ""} ${record.driver_ln || ""}`,
}, },
{ {
title: t("contracts.labels.vehicle"), title: t("contracts.labels.vehicle"),

View File

@@ -1,9 +1,11 @@
import { Table, Button, Input, Card, Space } from "antd"; import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Input, Space, Table } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import { SyncOutlined } from "@ant-design/icons"; import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
export default function CourtesyCarsList({ loading, courtesycars, refetch }) { export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
@@ -97,9 +99,9 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
render: (text, record) => render: (text, record) =>
record.cccontracts.length === 1 ? ( record.cccontracts.length === 1 ? (
<Link to={`/manage/jobs/${record.cccontracts[0].job.id}`}> <Link to={`/manage/jobs/${record.cccontracts[0].job.id}`}>
{`${record.cccontracts[0].job.ro_number} - ${ {`${
record.cccontracts[0].job.ownr_fn || "" record.cccontracts[0].job.ro_number
} ${record.cccontracts[0].job.ownr_ln || ""} ${record.cccontracts[0].job.ownr_co_nm || ""}`} } - ${OwnerNameDisplayFunction(record.cccontracts[0].job)}`}
</Link> </Link>
) : null, ) : null,
}, },

View File

@@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next";
import { Link, useHistory, useLocation } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function CsiResponseListPaginated({ export default function CsiResponseListPaginated({
refetch, refetch,
@@ -48,14 +49,12 @@ export default function CsiResponseListPaginated({
render: (text, record) => { render: (text, record) => {
return record.job.owner ? ( return record.job.owner ? (
<Link to={"/manage/owners/" + record.job.owner.id}> <Link to={"/manage/owners/" + record.job.owner.id}>
{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${ <OwnerNameDisplay ownerObject={record.job} />
record.job.ownr_co_nm || ""
}`}
</Link> </Link>
) : ( ) : (
<span>{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${ <span>
record.job.ownr_co_nm || "" <OwnerNameDisplay ownerObject={record.job} />
}`}</span> </span>
); );
}, },
}, },

View File

@@ -1,17 +1,21 @@
import { useLazyQuery } from "@apollo/client"; import { useLazyQuery } from "@apollo/client";
import { LoadingOutlined } from "@ant-design/icons";
import { AutoComplete, Divider, Space } from "antd"; import { AutoComplete, Divider, Space } from "antd";
import _ from "lodash"; import _ from "lodash";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link, useHistory } from "react-router-dom";
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries"; import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
export default function GlobalSearch() { export default function GlobalSearch() {
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory();
const [callSearch, { error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY); const [callSearch, { loading, error, data }] =
useLazyQuery(GLOBAL_SEARCH_QUERY);
const executeSearch = (v) => { const executeSearch = (v) => {
if (v && v.variables.search && v.variables.search !== "") callSearch(v); if (v && v.variables.search && v.variables.search !== "") callSearch(v);
@@ -39,9 +43,9 @@ export default function GlobalSearch() {
<Space size="small" split={<Divider type="vertical" />}> <Space size="small" split={<Divider type="vertical" />}>
<strong>{job.ro_number || t("general.labels.na")}</strong> <strong>{job.ro_number || t("general.labels.na")}</strong>
<span>{`${job.status || ""}`}</span> <span>{`${job.status || ""}`}</span>
<span>{`${job.ownr_fn || ""} ${job.ownr_ln || ""} ${ <span>
job.ownr_co_nm || "" <OwnerNameDisplay ownerObject={job} />
}`}</span> </span>
<span>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${ <span>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
job.v_model_desc || "" job.v_model_desc || ""
}`}</span> }`}</span>
@@ -57,15 +61,13 @@ export default function GlobalSearch() {
options: data.search_owners.map((owner) => { options: data.search_owners.map((owner) => {
return { return {
key: owner.id, key: owner.id,
value: `${owner.ownr_fn || ""} ${owner.ownr_ln || ""} ${ value: OwnerNameDisplayFunction(owner),
owner.ownr_co_nm || ""
}`,
label: ( label: (
<Link to={`/manage/owners/${owner.id}`}> <Link to={`/manage/owners/${owner.id}`}>
<Space size="small" split={<Divider type="vertical" />} wrap> <Space size="small" split={<Divider type="vertical" />} wrap>
<span>{`${owner.ownr_fn || ""} ${owner.ownr_ln || ""} ${ <span>
owner.ownr_co_nm || "" <OwnerNameDisplay ownerObject={owner} />
}`}</span> </span>
<PhoneNumberFormatter> <PhoneNumberFormatter>
{owner.ownr_ph1} {owner.ownr_ph1}
</PhoneNumberFormatter> </PhoneNumberFormatter>
@@ -171,8 +173,13 @@ export default function GlobalSearch() {
<AutoComplete <AutoComplete
options={options} options={options}
onSearch={handleSearch} onSearch={handleSearch}
suffixIcon={loading && <LoadingOutlined spin />}
defaultActiveFirstOption
placeholder={t("general.labels.globalsearch")} placeholder={t("general.labels.globalsearch")}
allowClear allowClear
onSelect={(val, opt) => {
history.push(opt.label.props.to);
}}
></AutoComplete> ></AutoComplete>
); );
} }

View File

@@ -31,6 +31,7 @@ import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.
import ScheduleAtChange from "./job-at-change.component"; import ScheduleAtChange from "./job-at-change.component";
import ScheduleEventColor from "./schedule-event.color.component"; import ScheduleEventColor from "./schedule-event.color.component";
import ScheduleEventNote from "./schedule-event.note.component"; import ScheduleEventNote from "./schedule-event.note.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -73,9 +74,9 @@ export function ScheduleEventComponent({
</Space> </Space>
) : ( ) : (
<Space> <Space>
<strong>{`${(event.job && event.job.ownr_fn) || ""} ${ <strong>
(event.job && event.job.ownr_ln) || "" <OwnerNameDisplay ownerObject={event.job} />
}`}</strong> </strong>
<span style={{ margin: 4 }}> <span style={{ margin: 4 }}>
{`${(event.job && event.job.v_model_yr) || ""} ${ {`${(event.job && event.job.v_model_yr) || ""} ${
(event.job && event.job.v_make_desc) || "" (event.job && event.job.v_make_desc) || ""
@@ -256,9 +257,9 @@ export function ScheduleEventComponent({
<Space> <Space>
{event.note && <AlertFilled className="production-alert" />} {event.note && <AlertFilled className="production-alert" />}
<strong>{`${event.job.ro_number || t("general.labels.na")}`}</strong> <strong>{`${event.job.ro_number || t("general.labels.na")}`}</strong>
<span>{`${(event.job && event.job.ownr_fn) || ""} ${ <span>
(event.job && event.job.ownr_ln) || "" <OwnerNameDisplay ownerObject={event.job} />
} ${(event.job && event.job.ownr_co_nm) || ""}`}</span> </span>
</Space> </Space>
<Space> <Space>
<span> <span>

View File

@@ -0,0 +1,54 @@
import { useQuery } from "@apollo/client";
import { Row, Col, Timeline, Typography, Space, Divider, Skeleton } from "antd";
import React from "react";
import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries";
import { useTranslation } from "react-i18next";
import AlertComponent from "../alert/alert.component";
import { DateFormatter } from "../../utils/DateFormatter";
import { Link } from "react-router-dom";
export default function JobLinesExpander({ jobline, jobid }) {
const { t } = useTranslation();
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
joblineid: jobline.id,
},
});
if (loading) return <Skeleton />;
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<Row>
<Col md={24} lg={12}>
<Typography.Title level={4}>
{t("parts_orders.labels.parts_orders")}
</Typography.Title>
<Timeline>
{data.parts_order_lines.length > 0 ? (
data.parts_order_lines.map((line) => (
<Timeline.Item key={line.id}>
<Space split={<Divider type="vertical" />} wrap>
<Link
to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}
>
{line.parts_order.order_number}
</Link>
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
{line.parts_order.vendor.name}
</Space>
</Timeline.Item>
))
) : (
<Timeline.Item>
{t("parts_orders.labels.notyetordered")}
</Timeline.Item>
)}
</Timeline>
</Col>
<Col md={24} lg={12}></Col>
</Row>
);
}

View File

@@ -4,6 +4,8 @@ import {
SyncOutlined, SyncOutlined,
WarningFilled, WarningFilled,
EditFilled, EditFilled,
PlusCircleTwoTone,
MinusCircleTwoTone,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { import {
@@ -38,9 +40,11 @@ import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-re
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container"; import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import _ from "lodash"; import _ from "lodash";
import JobCreateIOU from "../job-create-iou/job-create-iou.component"; import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobLinesExpander from "./job-lines-expander.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser bodyshop: selectBodyshop,
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
technician: selectTechnician, technician: selectTechnician,
}); });
@@ -53,6 +57,7 @@ const mapDispatchToProps = (dispatch) => ({
}); });
export function JobLinesComponent({ export function JobLinesComponent({
bodyshop,
jobRO, jobRO,
technician, technician,
setPartsOrderContext, setPartsOrderContext,
@@ -72,6 +77,9 @@ export function JobLinesComponent({
filteredInfo: {}, filteredInfo: {},
}); });
const { t } = useTranslation(); const { t } = useTranslation();
const jobIsPrivate = bodyshop.md_ins_cos.find(
(c) => c.name === job.ins_co_nm
)?.private;
const columns = [ const columns = [
{ {
@@ -283,7 +291,7 @@ export function JobLinesComponent({
key: "actions", key: "actions",
render: (text, record) => ( render: (text, record) => (
<div> <div>
{record.manual_line && ( {(record.manual_line || jobIsPrivate) && (
<Space> <Space>
<Button <Button
disabled={jobRO} disabled={jobRO}
@@ -449,6 +457,19 @@ export function JobLinesComponent({
scroll={{ scroll={{
x: true, x: true,
}} }}
expandable={{
expandedRowRender: (record) => (
<JobLinesExpander jobline={record} jobid={job.id} />
),
rowExpandable: (record) => true,
//expandRowByClick: true,
expandIcon: ({ expanded, onExpand, record }) =>
expanded ? (
<MinusCircleTwoTone onClick={(e) => onExpand(record, e)} />
) : (
<PlusCircleTwoTone onClick={(e) => onExpand(record, e)} />
),
}}
onRow={(record, rowIndex) => { onRow={(record, rowIndex) => {
return { return {
onDoubleClick: (event) => { onDoubleClick: (event) => {

View File

@@ -216,6 +216,7 @@ export function JobLinesUpsertModalComponent({
rules={[ rules={[
({ getFieldValue }) => ({ ({ getFieldValue }) => ({
validator(rule, value) { validator(rule, value) {
console.log(value);
if (!value || getFieldValue("part_type") !== "PAE") { if (!value || getFieldValue("part_type") !== "PAE") {
return Promise.resolve(); return Promise.resolve();
} }
@@ -226,7 +227,10 @@ export function JobLinesUpsertModalComponent({
}), }),
({ getFieldValue }) => ({ ({ getFieldValue }) => ({
validator(rule, value) { validator(rule, value) {
if (!!getFieldValue("part_type") === !!value) { console.log(value, !!value);
if (
!!getFieldValue("part_type") === (!!value || value === 0)
) {
return Promise.resolve(); return Promise.resolve();
} }
return Promise.reject( return Promise.reject(

View File

@@ -22,7 +22,7 @@ export default function JobReconciliationBillsTable({
dataIndex: "line_desc", dataIndex: "line_desc",
key: "line_desc", key: "line_desc",
ellipsis: true, ellipsis: true,
minWidth: "65rem", width: "10rem",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order, state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
@@ -72,11 +72,11 @@ export default function JobReconciliationBillsTable({
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order, state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
}, },
{ {
title: t("bills.fields.is_credit_memo"), title: t("bills.fields.is_credit_memo_short"),
dataIndex: "is_credit_memo", dataIndex: "is_credit_memo",
key: "is_credit_memo", key: "is_credit_memo",
sorter: (a, b) => a.bill.is_credit_memo - b.bill.is_credit_memo, sorter: (a, b) => a.bill.is_credit_memo - b.bill.is_credit_memo,
width: "8rem", width: "3rem",
sortOrder: sortOrder:
state.sortedInfo.columnKey === "is_credit_memo" && state.sortedInfo.columnKey === "is_credit_memo" &&
state.sortedInfo.order, state.sortedInfo.order,

View File

@@ -9,6 +9,7 @@ import {
SEARCH_JOBS_FOR_AUTOCOMPLETE, SEARCH_JOBS_FOR_AUTOCOMPLETE,
} from "../../graphql/jobs.queries"; } from "../../graphql/jobs.queries";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
const { Option } = Select; const { Option } = Select;
const JobSearchSelect = ( const JobSearchSelect = (
@@ -86,11 +87,9 @@ const JobSearchSelect = (
<span> <span>
{`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${ {`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${
o.ro_number || t("general.labels.na") o.ro_number || t("general.labels.na")
} | ${o.ownr_ln || ""} ${o.ownr_fn || ""} ${ } | ${OwnerNameDisplayFunction(o)} | ${
o.ownr_co_nm ? ` ${o.ownr_co_num}` : "" o.v_model_yr || ""
}| ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${ } ${o.v_make_desc || ""} ${o.v_model_desc || ""}`}
o.v_model_desc || ""
}`}
</span> </span>
<Tag> <Tag>
<strong>{o.status}</strong> <strong>{o.status}</strong>

View File

@@ -18,7 +18,7 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import axios from "axios";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -53,6 +53,12 @@ export function JobsConvertButton({
variables: { jobId: job.id, ...values }, variables: { jobId: job.id, ...values },
}); });
if (values.ca_gst_registrant) {
await axios.post("/job/totalsssu", {
id: job.id,
});
}
if (!res.errors) { if (!res.errors) {
refetch(); refetch();
notification["success"]({ notification["success"]({

View File

@@ -1,6 +1,5 @@
import { import {
Col, Col,
Divider,
Form, Form,
Input, Input,
InputNumber, InputNumber,
@@ -23,8 +22,8 @@ import FormItemPhone, {
} from "../form-items-formatted/phone-form-item.component"; } from "../form-items-formatted/phone-form-item.component";
import Car from "../job-damage-visual/job-damage-visual.component"; import Car from "../job-damage-visual/job-damage-visual.component";
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component"; import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
import FormRow from "../layout-form-row/layout-form-row.component";
import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component"; import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
import FormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -220,15 +219,8 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
)} )}
</Col> </Col>
</Row> </Row>
<Divider
orientation="left"
type="horizontal"
style={{ marginTop: ".8rem", float: "right" }}
>
{t("jobs.forms.appraiserinfo")}
</Divider>
<FormRow noDivider> <FormRow header={t("jobs.forms.appraiserinfo")}>
<Form.Item label={t("jobs.fields.est_co_nm")} name="est_co_nm"> <Form.Item label={t("jobs.fields.est_co_nm")} name="est_co_nm">
<Input disabled={jobRO} /> <Input disabled={jobRO} />
</Form.Item> </Form.Item>

View File

@@ -7,6 +7,7 @@ import { connect } from "react-redux";
import { Link, useHistory } from "react-router-dom"; import { Link, useHistory } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { CANCEL_APPOINTMENTS_BY_JOB_ID } from "../../graphql/appointments.queries";
import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries"; import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
@@ -56,6 +57,7 @@ export function JobsDetailHeaderActions({
const [deleteJob] = useMutation(DELETE_JOB); const [deleteJob] = useMutation(DELETE_JOB);
const [updateJob] = useMutation(UPDATE_JOB); const [updateJob] = useMutation(UPDATE_JOB);
const [voidJob] = useMutation(VOID_JOB); const [voidJob] = useMutation(VOID_JOB);
const [cancelAllAppointments] = useMutation(CANCEL_APPOINTMENTS_BY_JOB_ID);
const jobInProduction = useMemo(() => { const jobInProduction = useMemo(() => {
return bodyshop.md_ro_statuses.production_statuses.includes(job.status); return bodyshop.md_ro_statuses.production_statuses.includes(job.status);
}, [job, bodyshop.md_ro_statuses.production_statuses]); }, [job, bodyshop.md_ro_statuses.production_statuses]);
@@ -121,6 +123,39 @@ export function JobsDetailHeaderActions({
> >
{t("jobs.actions.schedule")} {t("jobs.actions.schedule")}
</Menu.Item> </Menu.Item>
<Menu.Item
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
>
<Popconfirm
title={t("general.labels.areyousure")}
okText="Yes"
cancelText="No"
onClick={(e) => e.stopPropagation()}
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
onConfirm={async () => {
const jobUpdate = await cancelAllAppointments({
variables: {
jobid: job.id,
job: {
date_scheduled: null,
scheduled_in: null,
scheduled_completion: null,
status: bodyshop.md_ro_statuses.default_imported,
},
},
});
if (!jobUpdate.errors) {
notification["success"]({
message: t("appointments.successes.canceled"),
});
return;
}
}}
getPopupContainer={(trigger) => trigger.parentNode}
>
{t("menus.jobsactions.cancelallappointments")}
</Popconfirm>
</Menu.Item>
<Menu.Item <Menu.Item
disabled={ disabled={
!!job.intakechecklist || !!job.intakechecklist ||

View File

@@ -22,6 +22,7 @@ import "./jobs-detail-header.styles.scss";
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component"; import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component"; import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
@@ -63,10 +64,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""} const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""}
${job.v_make_desc || ""} ${job.v_make_desc || ""}
${job.v_model_desc || ""}`.trim(); ${job.v_model_desc || ""}`.trim();
console.log(
"🚀 ~ file: jobs-detail-header.component.jsx ~ line 64 ~ vehicleTitle", const ownerTitle = OwnerNameDisplayFunction(job).trim();
vehicleTitle.length
);
return ( return (
<Row gutter={[16, 16]} style={{ alignItems: "stretch" }}> <Row gutter={[16, 16]} style={{ alignItems: "stretch" }}>
<Col {...colSpan}> <Col {...colSpan}>
@@ -159,9 +159,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
style={{ height: "100%" }} style={{ height: "100%" }}
title={ title={
<Link to={disabled ? "#" : `/manage/owners/${job.owner.id}`}> <Link to={disabled ? "#" : `/manage/owners/${job.owner.id}`}>
{`${job.ownr_fn || ""} ${job.ownr_ln || ""} ${ {ownerTitle.length > 0
job.ownr_co_nm || "" ? ownerTitle
}`} : t("owner.labels.noownerinfo")}
</Link> </Link>
} }
> >

View File

@@ -88,6 +88,33 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
</Form.Item> </Form.Item>
<CABCpvrtCalculator form={form} disabled={jobRO} /> <CABCpvrtCalculator form={form} disabled={jobRO} />
</Space> </Space>
<Form.Item
label={t("jobs.fields.auto_add_ats")}
name="auto_add_ats"
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
nostyle
shouldUpdate={(prev, cur) => prev.auto_add_ats !== cur.auto_add_ats}
>
{() => {
if (form.getFieldValue("auto_add_ats"))
return (
<Form.Item
label={t("jobs.fields.rate_ats")}
name="rate_ats"
initialValue={bodyshop.shoprates.rate_atp}
>
<CurrencyInput disabled={jobRO} />
</Form.Item>
);
return null;
}}
</Form.Item>
</FormRow> </FormRow>
<FormRow> <FormRow>
<Form.Item <Form.Item
@@ -100,7 +127,13 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
label={t("jobs.fields.state_tax_rate")} label={t("jobs.fields.state_tax_rate")}
name="state_tax_rate" name="state_tax_rate"
> >
<InputNumber min={0} max={1} precision={2} disabled={jobRO} autoComplete="new-password"/> <InputNumber
min={0}
max={1}
precision={2}
disabled={jobRO}
autoComplete="new-password"
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.local_tax_rate")} label={t("jobs.fields.local_tax_rate")}

View File

@@ -4,6 +4,7 @@ import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import PhoneFormatter from "../../utils/PhoneFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function JobsFindModalComponent({ export default function JobsFindModalComponent({
selectedJob, selectedJob,
@@ -43,15 +44,12 @@ export default function JobsFindModalComponent({
render: (text, record) => { render: (text, record) => {
return record.owner ? ( return record.owner ? (
<Link to={"/manage/owners/" + record.owner.id}> <Link to={"/manage/owners/" + record.owner.id}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ <OwnerNameDisplay ownerObject={record} />
record.ownr_co_nm || ""
}`}
</Link> </Link>
) : ( ) : (
// t("jobs.errors.noowner") <span>
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ <OwnerNameDisplay ownerObject={record} />
record.ownr_co_nm || "" </span>
}`}</span>
); );
}, },
}, },

View File

@@ -10,7 +10,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import StartChatButton from "../chat-open-button/chat-open-button.component"; import StartChatButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -52,14 +52,12 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
render: (text, record) => { render: (text, record) => {
return record.ownerid ? ( return record.ownerid ? (
<Link to={"/manage/owners/" + record.ownerid}> <Link to={"/manage/owners/" + record.ownerid}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ <OwnerNameDisplay ownerObject={record} />
record.ownr_co_nm || ""
}`}
</Link> </Link>
) : ( ) : (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ <span>
record.ownr_co_nm || "" <OwnerNameDisplay ownerObject={record} />
}`}</span> </span>
); );
}, },
}, },

View File

@@ -18,6 +18,7 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -141,14 +142,12 @@ export function JobsList({ bodyshop }) {
to={"/manage/owners/" + record.owner.id} to={"/manage/owners/" + record.owner.id}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ <OwnerNameDisplay ownerObject={record} />
record.ownr_co_nm || ""
}`}
</Link> </Link>
) : ( ) : (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ <span>
record.ownr_co_nm || "" <OwnerNameDisplay ownerObject={record} />
}`}</span> </span>
); );
}, },
}, },

View File

@@ -6,6 +6,7 @@ import { QUERY_SEARCH_OWNER_BY_IDX } from "../../graphql/owners.queries";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import OwnerFindModalComponent from "./owner-find-modal.component"; import OwnerFindModalComponent from "./owner-find-modal.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
export default function OwnerFindModalContainer({ export default function OwnerFindModalContainer({
loading, loading,
@@ -30,9 +31,7 @@ export default function OwnerFindModalContainer({
useEffect(() => { useEffect(() => {
if (modalProps.visible && owner) { if (modalProps.visible && owner) {
const s = `${owner.ownr_fn || ""} ${owner.ownr_ln || ""} ${ const s = OwnerNameDisplayFunction(owner);
owner.ownr_co_nm || ""
}`;
setSearchText(s.trim()); setSearchText(s.trim());
callSearchowners({ variables: { search: s.trim() } }); callSearchowners({ variables: { search: s.trim() } });

View File

@@ -0,0 +1,47 @@
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { store } from "../../redux/store";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(OwnerNameDisplay);
export function OwnerNameDisplay({ bodyshop, ownerObject }) {
const emptyTest =
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 || ""
}`.trim();
return `${ownerObject.ownr_fn || ""} ${ownerObject.ownr_ln || ""} ${
ownerObject.ownr_co_nm || ""
}`.trim();
}
export function OwnerNameDisplayFunction(ownerObject) {
const emptyTest =
ownerObject.ownr_fn + ownerObject.ownr_ln + ownerObject.ownr_co_nm;
if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "")
return "N/A";
const rdxStore = store.getState();
if (rdxStore.user.bodyshop.last_name_first)
return `${ownerObject.ownr_ln || ""}, ${ownerObject.ownr_fn || ""} ${
ownerObject.ownr_co_nm || ""
}`.trim();
return `${ownerObject.ownr_fn || ""} ${ownerObject.ownr_ln || ""} ${
ownerObject.ownr_co_nm || ""
}`.trim();
}

View File

@@ -8,6 +8,7 @@ import {
SEARCH_OWNERS_FOR_AUTOCOMPLETE, SEARCH_OWNERS_FOR_AUTOCOMPLETE,
} from "../../graphql/owners.queries"; } from "../../graphql/owners.queries";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
const { Option } = Select; const { Option } = Select;
@@ -16,10 +17,8 @@ const OwnerSearchSelect = ({ value, onChange, onBlur, disabled }, ref) => {
SEARCH_OWNERS_FOR_AUTOCOMPLETE SEARCH_OWNERS_FOR_AUTOCOMPLETE
); );
const [ const [callIdSearch, { loading: idLoading, error: idError, data: idData }] =
callIdSearch, useLazyQuery(SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE);
{ loading: idLoading, error: idError, data: idData },
] = useLazyQuery(SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE);
const executeSearch = (v) => { const executeSearch = (v) => {
callSearch(v); callSearch(v);
@@ -78,9 +77,7 @@ const OwnerSearchSelect = ({ value, onChange, onBlur, disabled }, ref) => {
{theOptions {theOptions
? theOptions.map((o) => ( ? theOptions.map((o) => (
<Option key={o.id} value={o.id}> <Option key={o.id} value={o.id}>
{`${o.ownr_ln || ""} ${o.ownr_fn || ""} ${ {`${OwnerNameDisplayFunction(o)} | ${o.ownr_addr1 || ""} `}
o.ownr_co_nm ? ` ${o.ownr_co_num}` : ""
}| ${o.ownr_addr1 || ""} `}
</Option> </Option>
)) ))
: null} : null}

View File

@@ -3,6 +3,10 @@ import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import PhoneFormatter from "../../utils/PhoneFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
export default function OwnerTagPopoverComponent({ job }) { export default function OwnerTagPopoverComponent({ job }) {
const { t } = useTranslation(); const { t } = useTranslation();
const content = ( const content = (
@@ -10,9 +14,9 @@ export default function OwnerTagPopoverComponent({ job }) {
<Row> <Row>
<Col span={12}> <Col span={12}>
<Descriptions title={t("owners.labels.fromclaim")} column={1}> <Descriptions title={t("owners.labels.fromclaim")} column={1}>
<Descriptions.Item key="1" label={t("jobs.fields.owner")}>{`${ <Descriptions.Item key="1" label={t("jobs.fields.owner")}>
job.ownr_fn || "" <OwnerNameDisplay ownerObject={job} />
} ${job.ownr_ln || ""} ${job.ownr_co_nm || ""}`}</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph1")}> <Descriptions.Item key="2" label={t("jobs.fields.ownr_ph1")}>
<PhoneFormatter>{job.ownr_ph1 || ""}</PhoneFormatter> <PhoneFormatter>{job.ownr_ph1 || ""}</PhoneFormatter>
</Descriptions.Item> </Descriptions.Item>
@@ -31,11 +35,9 @@ export default function OwnerTagPopoverComponent({ job }) {
</Col> </Col>
<Col span={12}> <Col span={12}>
<Descriptions title={t("owners.labels.fromowner")} column={1}> <Descriptions title={t("owners.labels.fromowner")} column={1}>
<Descriptions.Item key="1" label={t("jobs.fields.owner")}>{`${ <Descriptions.Item key="1" label={t("jobs.fields.owner")}>
job.owner.ownr_fn || "" <OwnerNameDisplay ownerObject={job.owner} />
} ${job.owner.ownr_ln || ""} ${ </Descriptions.Item>
job.owner.ownr_co_nm || ""
}`}</Descriptions.Item>
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph1")}> <Descriptions.Item key="2" label={t("jobs.fields.ownr_ph1")}>
<PhoneFormatter>{job.owner.ownr_ph1 || ""}</PhoneFormatter> <PhoneFormatter>{job.owner.ownr_ph1 || ""}</PhoneFormatter>
</Descriptions.Item> </Descriptions.Item>
@@ -68,9 +70,7 @@ export default function OwnerTagPopoverComponent({ job }) {
<Popover placement="bottom" content={content}> <Popover placement="bottom" content={content}>
<Tag color="cyan"> <Tag color="cyan">
<Link to={`/manage/owners/${job.owner.id}`}> <Link to={`/manage/owners/${job.owner.id}`}>
{job.owner {job.owner ? OwnerNameDisplayFunction(job) : t("jobs.errors.noowner")}
? `${job.ownr_co_nm || ""}${job.ownr_fn || ""} ${job.ownr_ln || ""}`
: t("jobs.errors.noowner")}
</Link> </Link>
</Tag> </Tag>
</Popover> </Popover>

View File

@@ -5,6 +5,7 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link, useHistory, useLocation } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
import PhoneFormatter from "../../utils/PhoneFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function OwnersListComponent({ export default function OwnersListComponent({
loading, loading,
@@ -33,9 +34,7 @@ export default function OwnersListComponent({
key: "name", key: "name",
render: (text, record) => ( render: (text, record) => (
<Link to={"/manage/owners/" + record.id}> <Link to={"/manage/owners/" + record.id}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ <OwnerNameDisplay ownerObject={record} />
record.ownr_co_nm || ""
}`}
</Link> </Link>
), ),
}, },

View File

@@ -1,13 +1,10 @@
import { notification } from "antd";
import axios from "axios"; import axios from "axios";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { setPartnerVersion } from "../../redux/application/application.actions"; import { setPartnerVersion } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import {store} from '../../redux/store'
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -21,45 +18,48 @@ export default connect(
mapDispatchToProps mapDispatchToProps
)(PartnerPingComponent); )(PartnerPingComponent);
export function PartnerPingComponent({ bodyshop, setPartnerVersion }) { export function PartnerPingComponent({ bodyshop, }) {
const { t } = useTranslation();
useEffect(() => { useEffect(() => {
// Create an scoped async function in the hook // Create an scoped async function in the hook
async function checkPartnerStatus() {
if (!bodyshop) return;
try {
//if (process.env.NODE_ENV === "development") return;
const PartnerResponse = await axios.post("http://localhost:1337/ping/");
const { appver, qbpath } = PartnerResponse.data;
setPartnerVersion(appver);
console.log({ appver, qbpath });
if (
!qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
)
) {
notification["error"]({
title: "",
message: t("general.messages.noacctfilepath"),
});
}
} catch (error) {
console.log(error);
notification["error"]({
title: "",
message: t("general.messages.partnernotrunning"),
});
}
}
// Execute the created function directly // Execute the created function directly
checkPartnerStatus(); checkPartnerStatus(bodyshop);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [bodyshop]); }, [bodyshop]);
return <></>; return <></>;
} }
export async function checkPartnerStatus(bodyshop) {
if (!bodyshop) return;
try {
//if (process.env.NODE_ENV === "development") return;
const PartnerResponse = await axios.post("http://localhost:1337/ping/");
// const {
// appver, //qbpath
// } = PartnerResponse.data;
console.log(PartnerResponse.data)
store.dispatch(setPartnerVersion(PartnerResponse.data));
// if (
// checkAcctPath &&
// !qbpath &&
// !(
// bodyshop &&
// (bodyshop.cdk_dealerid ||
// bodyshop.pbs_serialnumber ||
// bodyshop.accountingconfig.qbo)
// )
// ) {
// notification["error"]({
// title: "",
// message: i18n.t("general.messages.noacctfilepath"),
// });
// }
} catch (error) {
console.log("ImEX Online Partner is not running.", error);
// notification["error"]({
// title: "",
// message: i18n.t("general.messages.partnernotrunning"),
// });
}
}

View File

@@ -425,8 +425,6 @@ export function PartsOrderListTableComponent({
placement="right" placement="right"
onClose={() => handleOnRowClick(null)} onClose={() => handleOnRowClick(null)}
visible={selectedpartsorder} visible={selectedpartsorder}
//getContainer={false}
style={{ position: "absolute" }}
closable closable
width={drawerPercentage} width={drawerPercentage}
> >

View File

@@ -1,4 +1,4 @@
import { DeleteFilled, WarningFilled } from "@ant-design/icons"; import { DeleteFilled, WarningFilled, DownOutlined } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useTreatments } from "@splitsoftware/splitio-react";
import { import {
Divider, Divider,
@@ -9,6 +9,8 @@ import {
Space, Space,
Tag, Tag,
Select, Select,
Menu,
Dropdown,
} from "antd"; } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -39,6 +41,7 @@ export function PartsOrderModalComponent({
isReturn, isReturn,
preferredMake, preferredMake,
job, job,
form,
}) { }) {
const [sendType, setSendType] = sendTypeState; const [sendType, setSendType] = sendTypeState;
const { OEConnection } = useTreatments( const { OEConnection } = useTreatments(
@@ -52,6 +55,21 @@ export function PartsOrderModalComponent({
bodyshop.imexshopid bodyshop.imexshopid
); );
const { t } = useTranslation(); const { t } = useTranslation();
const handleClick = ({ item, key, keyPath }) => {
form.setFieldsValue({ comments: item.props.value });
};
const menu = (
<div>
<Menu onClick={handleClick}>
{bodyshop.md_parts_order_comment.map((comment, idx) => (
<Menu.Item value={comment.comment} key={idx}>
{comment.label}
</Menu.Item>
))}
</Menu>
</div>
);
return ( return (
<div> <div>
@@ -243,7 +261,23 @@ export function PartsOrderModalComponent({
); );
}} }}
</Form.List> </Form.List>
<Form.Item name="comments" label={t("parts_orders.fields.comments")}> <Form.Item
name="comments"
label={
<Space>
{t("parts_orders.fields.comments")}
<Dropdown overlay={menu}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<DownOutlined />
</a>
</Dropdown>
</Space>
}
>
<Input.TextArea rows={3} /> <Input.TextArea rows={3} />
</Form.Item> </Form.Item>
<Radio.Group <Radio.Group

View File

@@ -346,6 +346,7 @@ export function PartsOrderModalContainer({
<LoadingSpinner /> <LoadingSpinner />
) : ( ) : (
<PartsOrderModalComponent <PartsOrderModalComponent
form={form}
vendorList={(data && data.vendors) || []} vendorList={(data && data.vendors) || []}
sendTypeState={sendTypeState} sendTypeState={sendTypeState}
isReturn={isReturn} isReturn={isReturn}

View File

@@ -14,6 +14,7 @@ import { alphaSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container"; import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const stripeTestEnv = process.env.REACT_APP_STRIPE_PUBLIC_KEY; //.includes("test"); const stripeTestEnv = process.env.REACT_APP_STRIPE_PUBLIC_KEY; //.includes("test");
@@ -78,14 +79,12 @@ export function PaymentsListPaginated({
render: (text, record) => { render: (text, record) => {
return record.job.owner ? ( return record.job.owner ? (
<Link to={"/manage/owners/" + record.job.owner.id}> <Link to={"/manage/owners/" + record.job.owner.id}>
{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${ <OwnerNameDisplay ownerObject={record} />
record.job.ownr_co_nm || ""
}`}
</Link> </Link>
) : ( ) : (
<span>{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${ <span>
record.job.ownr_co_nm || "" <OwnerNameDisplay ownerObject={record} />
}`}</span> </span>
); );
}, },
}, },

View File

@@ -13,6 +13,7 @@ import ProductionListColumnProductionNote from "../production-list-columns/produ
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component"; import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
import "./production-board-card.styles.scss"; import "./production-board-card.styles.scss";
import moment from "moment"; import moment from "moment";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function ProductionBoardCard( export default function ProductionBoardCard(
technician, technician,
@@ -61,20 +62,22 @@ export default function ProductionBoardCard(
<PauseCircleOutlined style={{ color: "orangered" }} /> <PauseCircleOutlined style={{ color: "orangered" }} />
)} )}
<span style={{ fontWeight: "bolder" }}> <span style={{ fontWeight: "bolder" }}>
{card.ro_number || t("general.labels.na")} <Link
to={
technician
? `/tech/joblookup?selected=${card.id}`
: `/manage/jobs/${card.id}`
}
>
{card.ro_number || t("general.labels.na")}
</Link>
</span> </span>
</Space> </Space>
} }
extra={ extra={
technician ? ( <Link to={{ search: `?selected=${card.id}` }}>
<Link to={`/tech/joblookup?selected=${card.id}`}> <EyeFilled />
<EyeFilled /> </Link>
</Link>
) : (
<Link to={`/manage/jobs/${card.id}`}>
<EyeFilled />
</Link>
)
} }
> >
<Row> <Row>
@@ -85,9 +88,9 @@ export default function ProductionBoardCard(
card.ownr_co_nm || "" card.ownr_co_nm || ""
}`}</div> }`}</div>
) : ( ) : (
<div className="ellipses">{`${card.ownr_ln || ""}, ${ <div className="ellipses">
card.ownr_fn || "" <OwnerNameDisplay ownerObject={card} />
} ${card.ownr_co_nm || ""}`}</div> </div>
)} )}
</Col> </Col>
)} )}

View File

@@ -131,6 +131,13 @@ export default function ProductionBoardKanbanCardSettings({
> >
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item
valuePropName="checked"
label={t("production.labels.stickyheader")}
name="stickyheader"
>
<Switch />
</Form.Item>
</Col> </Col>
</Row> </Row>
</Form> </Form>

View File

@@ -1,26 +1,27 @@
import { SyncOutlined } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client"; import { useApolloClient } from "@apollo/client";
import Board, { moveCard } from "@asseinfo/react-kanban"; import Board, { moveCard } from "@asseinfo/react-kanban";
//import "@asseinfo/react-kanban/dist/styles.css"; import { Button, Grid, notification, PageHeader, Space, Statistic } from "antd";
import "./production-board-kanban.styles.scss";
import { SyncOutlined } from "@ant-design/icons";
import { Grid, notification, Button, PageHeader, Space, Statistic } from "antd";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Sticky, StickyContainer } from "react-sticky";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { generate_UPDATE_JOB_KANBAN } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ProductionBoardCard from "../production-board-kanban-card/production-board-kanban-card.component";
import { createBoardData } from "./production-board-kanban.utils.js";
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component";
import styled from "styled-components"; import styled from "styled-components";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { generate_UPDATE_JOB_KANBAN } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
import ProductionBoardCard from "../production-board-kanban-card/production-board-kanban-card.component";
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component";
//import "@asseinfo/react-kanban/dist/styles.css";
import "./production-board-kanban.styles.scss";
import { createBoardData } from "./production-board-kanban.utils.js";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician, technician: selectTechnician,
@@ -183,9 +184,52 @@ export function ProductionBoardKanbanComponent({
: standardSizes[selectedBreakpoint[0]] : standardSizes[selectedBreakpoint[0]]
: "250"; : "250";
const stickyHeader = {
renderColumnHeader: ({ title }) => (
<Sticky>
{({
style,
// the following are also available but unused in this example
isSticky,
wasSticky,
distanceFromTop,
distanceFromBottom,
calculatedHeight,
}) => (
<div
className="react-kanban-column-header"
style={{ ...style, zIndex: "99", backgroundColor: "#ddd" }}
>
{title}
</div>
)}
</Sticky>
),
};
const cardSettings =
associationSettings &&
associationSettings.kanban_settings &&
Object.keys(associationSettings.kanban_settings).length > 0
? associationSettings.kanban_settings
: {
ats: true,
clm_no: true,
compact: false,
ownr_nm: true,
sublets: true,
ins_co_nm: true,
production_note: true,
employeeassignments: true,
scheduled_completion: true,
stickyheader: false,
};
return ( return (
<Container width={width}> <Container width={width}>
<IndefiniteLoading loading={isMoving} /> <IndefiniteLoading loading={isMoving} />
<PageHeader <PageHeader
title={ title={
<Space> <Space>
@@ -215,34 +259,19 @@ export function ProductionBoardKanbanComponent({
</Space> </Space>
} }
/> />
<ProductionListDetailComponent jobs={data} />
<Board <StickyContainer>
children={boardLanes} <Board
disableCardDrag={isMoving} style={{ height: "100%" }}
renderCard={(card) => children={boardLanes}
ProductionBoardCard( disableCardDrag={isMoving}
technician, {...(cardSettings.stickyheader && stickyHeader)}
card, renderCard={(card) =>
bodyshop, ProductionBoardCard(technician, card, bodyshop, cardSettings)
associationSettings && }
associationSettings.kanban_settings && onCardDragEnd={handleDragEnd}
Object.keys(associationSettings.kanban_settings).length > 0 />
? associationSettings.kanban_settings </StickyContainer>
: {
ats: true,
clm_no: true,
compact: false,
ownr_nm: true,
sublets: true,
ins_co_nm: true,
production_note: true,
employeeassignments: true,
scheduled_completion: true,
}
)
}
onCardDragEnd={handleDragEnd}
/>
</Container> </Container>
); );
} }

View File

@@ -21,6 +21,8 @@ import ProductionListColumnStatus from "./production-list-columns.status.compone
import ProductionListColumnCategory from "./production-list-columns.status.category"; import ProductionListColumnCategory from "./production-list-columns.status.category";
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component"; import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
import ProductionListColumnComment from "./production-list-columns.comment.component"; import ProductionListColumnComment from "./production-list-columns.comment.component";
import ProductionListColumnPartsReceived from "./production-list-columns.partsreceived.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const r = ({ technician, state, activeStatuses, bodyshop }) => { const r = ({ technician, state, activeStatuses, bodyshop }) => {
return [ return [
@@ -67,11 +69,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
dataIndex: "ownr", dataIndex: "ownr",
key: "ownr", key: "ownr",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <OwnerNameDisplay ownerObject={record} />,
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}</span>
),
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order, state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order,
@@ -81,6 +79,13 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
dataIndex: "vehicle", dataIndex: "vehicle",
key: "vehicle", key: "vehicle",
ellipsis: true, ellipsis: true,
sorter: (a, b) =>
alphaSort(
a.v_make_desc + a.v_model_desc,
b.v_make_desc + b.v_model_desc
),
sortOrder:
state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order,
render: (text, record) => ( render: (text, record) => (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ <span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || "" record.v_model_desc || ""
@@ -96,7 +101,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
sortOrder: sortOrder:
state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order, state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order,
render: (text, record) => ( render: (text, record) => (
<ProductionListDate record={record} field="actual_in" time/> <ProductionListDate record={record} field="actual_in" time />
), ),
}, },
{ {
@@ -477,6 +482,14 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
/> />
), ),
}, },
{
title: i18n.t("jobs.labels.parts_received"),
dataIndex: "parts_received",
key: "parts_received",
render: (text, record) => (
<ProductionListColumnPartsReceived record={record} />
),
},
]; ];
}; };
export default r; export default r;

View File

@@ -0,0 +1,41 @@
import { useMemo } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListColumnPartsReceived);
export function ProductionListColumnPartsReceived({ bodyshop, record }) {
const amount = useMemo(() => {
const amount = record.joblines_status.reduce(
(acc, val) => {
acc.total += val.count;
acc.received =
val.status === bodyshop.md_order_statuses.default_received
? acc.received + val.count
: acc.received;
return acc;
},
{ total: 0, received: 0 }
);
return {
...amount,
percent:
amount.total !== 0
? ((amount.received / amount.total) * 100).toFixed(0) + "%"
: "N/A",
};
}, [record, bodyshop.md_order_statuses]);
return `${amount.percent} (${amount.received}/${amount.total})`;
}

View File

@@ -17,7 +17,7 @@ import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component"; import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
import JobAtChange from "../job-at-change/job-at-change.component"; import JobAtChange from "../job-at-change/job-at-change.component";
import { PrinterFilled } from "@ant-design/icons"; import { PrinterFilled } from "@ant-design/icons";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
@@ -107,9 +107,7 @@ export function ProductionListDetail({ jobs, setPrintCenterContext }) {
{theJob.ins_co_nm || ""} {theJob.ins_co_nm || ""}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.owner")}> <Descriptions.Item label={t("jobs.fields.owner")}>
{`${theJob.ownr_fn || ""} ${theJob.ownr_ln || ""} ${ <OwnerNameDisplay ownerObject={theJob} />
theJob.ownr_co_nm || ""
}`}
<StartChatButton <StartChatButton
phone={data.jobs_by_pk.ownr_ph1} phone={data.jobs_by_pk.ownr_ph1}
jobid={data.jobs_by_pk.id} jobid={data.jobs_by_pk.id}

View File

@@ -88,12 +88,6 @@ export function ProductionListTable({
); );
const handleTableChange = (pagination, filters, sorter) => { const handleTableChange = (pagination, filters, sorter) => {
console.log(
"🚀 ~ file: production-list-table.component.jsx ~ line 91 ~ pagination, filters, sorter",
pagination,
filters,
sorter
);
setState({ setState({
...state, ...state,
filteredInfo: filters, filteredInfo: filters,

View File

@@ -9,7 +9,7 @@ import { DateTimeFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import "./schedule-production-list.styles.scss"; import "./schedule-production-list.styles.scss";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function ScheduleProductionList() { export default function ScheduleProductionList() {
const { t } = useTranslation(); const { t } = useTranslation();
const [callQuery, { loading, error, data }] = useLazyQuery( const [callQuery, { loading, error, data }] = useLazyQuery(
@@ -36,9 +36,7 @@ export default function ScheduleProductionList() {
<td> <td>
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link> <Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
</td> </td>
<td>{`${j.ownr_fn || ""} ${j.ownr_ln || ""} ${ <td><OwnerNameDisplay ownerObject={j} /></td>
j.ownr_co_nm || ""
}`}</td>
<td>{`${j.v_model_yr || ""} ${j.v_make_desc || ""} ${ <td>{`${j.v_model_yr || ""} ${j.v_make_desc || ""} ${
j.v_model_desc || "" j.v_model_desc || ""
}`}</td> }`}</td>

View File

@@ -163,6 +163,27 @@ export default function ShopInfoGeneral({ form }) {
> >
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item shouldUpdate noStyle>
{() => (
<Form.Item
label={t("bodyshop.labels.qbo_usa")}
shouldUpdate
valuePropName="checked"
name={["accountingconfig", "qbo_usa"]}
>
<Switch
disabled={!form.getFieldValue(["accountingconfig", "qbo"])}
/>
</Form.Item>
)}
</Form.Item>
<Form.Item
label={t("bodyshop.labels.qbo_departmentid")}
name={["accountingconfig", "qbo_departmentid"]}
>
<Input />
</Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.labels.accountingtiers")} label={t("bodyshop.labels.accountingtiers")}
rules={[ rules={[
@@ -499,7 +520,6 @@ export default function ShopInfoGeneral({ form }) {
> >
<CurrencyInput /> <CurrencyInput />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name={["attach_pdf_to_email"]} name={["attach_pdf_to_email"]}
label={t("bodyshop.fields.attach_pdf_to_email")} label={t("bodyshop.fields.attach_pdf_to_email")}
@@ -538,6 +558,13 @@ export default function ShopInfoGeneral({ form }) {
> >
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item
name={["bill_allow_post_to_closed"]}
label={t("bodyshop.fields.bill_allow_post_to_closed")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item <Form.Item
name={["md_ded_notes"]} name={["md_ded_notes"]}
label={t("bodyshop.fields.md_ded_notes")} label={t("bodyshop.fields.md_ded_notes")}
@@ -550,6 +577,13 @@ export default function ShopInfoGeneral({ form }) {
> >
<Select mode="tags" /> <Select mode="tags" />
</Form.Item> </Form.Item>
<Form.Item
name={["last_name_first"]}
label={t("bodyshop.fields.last_name_first")}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")}> <LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")}>
<Form.List name={["md_messaging_presets"]}> <Form.List name={["md_messaging_presets"]}>
@@ -784,14 +818,23 @@ export default function ShopInfoGeneral({ form }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Space> <Form.Item
<Form.Item label={t("bodyshop.fields.md_ins_co.zip")}
label={t("bodyshop.fields.md_ins_co.zip")} key={`${index}zip`}
key={`${index}zip`} name={[field.name, "zip"]}
name={[field.name, "zip"]} >
> <Input />
<Input /> </Form.Item>
</Form.Item> <Form.Item
label={t("bodyshop.fields.md_ins_co.private")}
key={`${index}private`}
name={[field.name, "private"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Space wrap>
<DeleteFilled <DeleteFilled
onClick={() => { onClick={() => {
remove(field.name); remove(field.name);
@@ -1284,6 +1327,72 @@ export default function ShopInfoGeneral({ form }) {
}} }}
</Form.List> </Form.List>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.fields.md_parts_order_comment")}>
<Form.List name={["md_parts_order_comment"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<Form.Item
label={t("general.labels.label")}
key={`${index}label`}
name={[field.name, "label"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("parts_orders.fields.comments")}
key={`${index}comment`}
name={[field.name, "comment"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
</LayoutFormRow>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("general.actions.add")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</LayoutFormRow>
</div> </div>
); );
} }

View File

@@ -81,7 +81,10 @@ export function SignInComponent({
/> />
</Form.Item> </Form.Item>
{signInError ? ( {signInError ? (
<AlertComponent type="error" message={signInError.message} /> <AlertComponent
type="error"
message={t(`users.errors.signinerror.${signInError.code}`)}
/>
) : null} ) : null}
<Button <Button
className="login-btn" className="login-btn"

View File

@@ -12,6 +12,7 @@ import AlertComponent from "../alert/alert.component";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component"; import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
technician: selectTechnician, technician: selectTechnician,
@@ -60,11 +61,9 @@ export function TechClockedInList({ technician }) {
<Card <Card
title={ title={
<Link to={`/tech/joblookup?selected=${ticket.job.id}`}> <Link to={`/tech/joblookup?selected=${ticket.job.id}`}>
{`${ticket.job.ro_number || t("general.labels.na")} ${ {`${
ticket.job.ownr_fn || "" ticket.job.ro_number || t("general.labels.na")
} ${ticket.job.ownr_ln || ""} ${ } ${OwnerNameDisplayFunction(ticket.job)}`}
ticket.job.ownr_co_nm || ""
}`}
</Link> </Link>
} }
actions={[ actions={[

View File

@@ -12,6 +12,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { onlyUnique } from "../../utils/arrayHelper"; import { onlyUnique } from "../../utils/arrayHelper";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -108,9 +109,9 @@ export function TechLookupJobsList({ bodyshop }) {
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ <span>
record.ownr_co_nm || "" <OwnerNameDisplay ownerObject={record} />
}`}</span> </span>
), ),
}, },
{ {

View File

@@ -237,7 +237,7 @@ export function TimeTicketModalComponent({
return Promise.reject( return Promise.reject(
t("timetickets.validation.clockoffwithoutclockon") t("timetickets.validation.clockoffwithoutclockon")
); );
if (!value.isSameOrAfter(clockon)) if (value && !value.isSameOrAfter(clockon))
return Promise.reject( return Promise.reject(
t("timetickets.validation.clockoffmustbeafterclockon") t("timetickets.validation.clockoffmustbeafterclockon")
); );

View File

@@ -7,6 +7,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component"; import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -34,9 +35,7 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
key: "owner", key: "owner",
render: (text, record) => ( render: (text, record) => (
<Link to={`/manage/owners/${record.owner.id}`}> <Link to={`/manage/owners/${record.owner.id}`}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ <OwnerNameDisplay ownerObject={record} />
record.ownr_co_nm || ""
}`}
</Link> </Link>
), ),
}, },

View File

@@ -25,6 +25,7 @@ export default function VendorsFormComponent({
formLoading, formLoading,
handleDelete, handleDelete,
responsibilityCenters, responsibilityCenters,
selectedvendor,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const client = useApolloClient(); const client = useApolloClient();
@@ -52,7 +53,12 @@ export default function VendorsFormComponent({
> >
{t("general.actions.save")} {t("general.actions.save")}
</Button> </Button>
<Button type="danger" onClick={handleDelete} loading={formLoading}> <Button
type="danger"
disabled={selectedvendor === "new"}
onClick={handleDelete}
loading={formLoading}
>
{t("general.actions.delete")} {t("general.actions.delete")}
</Button> </Button>

View File

@@ -39,30 +39,30 @@ function VendorsFormContainer({ refetch, bodyshop }) {
const [insertvendor] = useMutation(INSERT_NEW_VENDOR); const [insertvendor] = useMutation(INSERT_NEW_VENDOR);
const [deleteVendor] = useMutation(DELETE_VENDOR); const [deleteVendor] = useMutation(DELETE_VENDOR);
const handleDelete = () => { const handleDelete = async () => {
setFormLoading(true); setFormLoading(true);
deleteVendor({ const result = await deleteVendor({
variables: { id: selectedvendor }, variables: { id: selectedvendor },
refetchQueries: ["QUERY_ALL_VENDORS"], refetchQueries: ["QUERY_ALL_VENDORS"],
}) });
.then((r) => { console.log(result);
notification["success"]({ if (result.errors) {
message: t("vendors.successes.deleted"), notification["error"]({
}); message: t("vendors.errors.deleting"),
delete search.selectedvendor;
history.push({ search: queryString.stringify(search) });
if (refetch)
refetch().then((r) => {
form.resetFields();
});
setFormLoading(false);
})
.catch((error) => {
notification["error"]({
message: t("vendors.errors.deleting"),
});
setFormLoading(false);
}); });
} else {
notification["success"]({
message: t("vendors.successes.deleted"),
});
delete search.selectedvendor;
history.push({ search: queryString.stringify(search) });
if (refetch)
refetch().then((r) => {
form.resetFields();
});
}
setFormLoading(false);
}; };
const handleFinish = async (values) => { const handleFinish = async (values) => {
@@ -139,6 +139,7 @@ function VendorsFormContainer({ refetch, bodyshop }) {
formLoading={formLoading} formLoading={formLoading}
handleDelete={handleDelete} handleDelete={handleDelete}
responsibilityCenters={bodyshop.md_responsibility_centers || null} responsibilityCenters={bodyshop.md_responsibility_centers || null}
selectedvendor={selectedvendor}
/> />
) : ( ) : (
t("vendors.labels.noneselected") t("vendors.labels.noneselected")

View File

@@ -245,6 +245,27 @@ export const CANCEL_APPOINTMENT_BY_ID = gql`
} }
`; `;
export const CANCEL_APPOINTMENTS_BY_JOB_ID = gql`
mutation CANCEL_APPOINTMENTS_BY_JOB_ID($jobid: uuid!, $job: jobs_set_input) {
update_appointments(
where: { _and: { jobid: { _eq: $jobid }, arrived: { _eq: false } } }
_set: { canceled: true }
) {
returning {
id
canceled
}
}
update_jobs_by_pk(pk_columns: { id: $jobid }, _set: $job) {
date_scheduled
id
scheduled_in
scheduled_completion
status
}
}
`;
export const QUERY_APPOINTMENTS_BY_JOBID = gql` export const QUERY_APPOINTMENTS_BY_JOBID = gql`
query QUERY_APPOINTMENTS_BY_JOBID($jobid: uuid!) { query QUERY_APPOINTMENTS_BY_JOBID($jobid: uuid!) {
appointments(where: { jobid: { _eq: $jobid } }, order_by: { start: desc }) { appointments(where: { jobid: { _eq: $jobid } }, order_by: { start: desc }) {

View File

@@ -12,6 +12,13 @@ export const UPDATE_BILL_LINE = gql`
} }
} }
`; `;
export const DELETE_BILL_LINE = gql`
mutation DELETE_BILL_LINE($id: uuid!) {
delete_billlines_by_pk(id: $id) {
id
}
}
`;
export const INSERT_NEW_BILL_LINES = gql` export const INSERT_NEW_BILL_LINES = gql`
mutation INSERT_NEW_BILL_LINES($billLines: [billlines_insert_input!]!) { mutation INSERT_NEW_BILL_LINES($billLines: [billlines_insert_input!]!) {

View File

@@ -103,6 +103,9 @@ export const QUERY_BODYSHOP = gql`
timezone timezone
ss_configuration ss_configuration
md_from_emails md_from_emails
last_name_first
md_parts_order_comment
bill_allow_post_to_closed
employees { employees {
user_email user_email
id id
@@ -203,6 +206,9 @@ export const UPDATE_SHOP = gql`
timezone timezone
ss_configuration ss_configuration
md_from_emails md_from_emails
last_name_first
md_parts_order_comment
bill_allow_post_to_closed
employees { employees {
id id
first_name first_name

View File

@@ -146,6 +146,11 @@ export const QUERY_EXACT_JOB_IN_PRODUCTION = gql`
employee_refinish employee_refinish
employee_prep employee_prep
employee_csr employee_csr
joblines_status {
part_type
status
count
}
labhrs: joblines_aggregate( labhrs: joblines_aggregate(
where: { where: {
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
@@ -219,6 +224,11 @@ export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
employee_refinish employee_refinish
employee_prep employee_prep
employee_csr employee_csr
joblines_status {
part_type
status
count
}
labhrs: joblines_aggregate( labhrs: joblines_aggregate(
where: { where: {
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
@@ -294,6 +304,11 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
employee_prep employee_prep
employee_csr employee_csr
suspended suspended
joblines_status {
part_type
status
count
}
labhrs: joblines_aggregate( labhrs: joblines_aggregate(
where: { where: {
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
@@ -596,6 +611,8 @@ export const GET_JOB_BY_PK = gql`
ownerid ownerid
ded_note ded_note
materials materials
auto_add_ats
rate_ats
owner { owner {
id id
ownr_fn ownr_fn
@@ -706,20 +723,6 @@ export const GET_JOB_BY_PK = gql`
} }
} }
} }
parts_order_lines {
id
parts_order {
id
order_number
comments
order_date
user_email
vendor {
id
name
}
}
}
} }
payments { payments {
id id
@@ -2099,3 +2102,23 @@ export const DELETE_RELATED_RO = gql`
} }
} }
`; `;
export const GET_JOB_LINE_ORDERS = gql`
query GET_JOB_LINE_ORDERS($joblineid: uuid!) {
parts_order_lines(where: { job_line_id: { _eq: $joblineid } }) {
id
act_price
parts_order {
id
order_date
order_number
orderedby
return
comments
vendor {
id
name
}
}
}
}
`;

View File

@@ -3,7 +3,7 @@ import * as Sentry from "@sentry/react";
import "antd/dist/antd.less"; import "antd/dist/antd.less";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import { createRoot } from "react-dom/client";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { PersistGate } from "redux-persist/integration/react"; import { PersistGate } from "redux-persist/integration/react";
@@ -39,7 +39,9 @@ if (process.env.NODE_ENV !== "development") {
}); });
} }
ReactDOM.render( const container = document.getElementById("root");
const root = createRoot(container); // createRoot(container!) if you use TypeScript
root.render(
<Provider store={store}> <Provider store={store}>
<BrowserRouter> <BrowserRouter>
<PersistGate <PersistGate
@@ -49,48 +51,7 @@ ReactDOM.render(
<AppContainer /> <AppContainer />
</PersistGate> </PersistGate>
</BrowserRouter> </BrowserRouter>
</Provider>, </Provider>
document.getElementById("root")
); );
// const onServiceWorkerUpdate = (registration) => {
// console.log("onServiceWorkerUpdate", registration);
// const btn = (
// <Space flex>
// <Button
// onClick={async () => {
// window.open("https://imex-online.noticeable.news/", "_blank");
// }}
// >
// {i18n.t("general.actions.viewreleasenotes")}
// </Button>
// <Button
// type="primary"
// onClick={async () => {
// if (registration && registration.waiting) {
// await registration.unregister();
// // Makes Workbox call skipWaiting()
// registration.waiting.postMessage({ type: "SKIP_WAITING" });
// // Once the service worker is unregistered, we can reload the page to let
// // the browser download a fresh copy of our app (invalidating the cache)
// window.location.reload();
// }
// }}
// >
// {i18n.t("general.actions.refresh")}
// </Button>
// </Space>
// );
// notification.open({
// icon: <AlertOutlined />,
// message: i18n.t("general.messages.newversiontitle"),
// description: i18n.t("general.messages.newversionmessage"),
// duration: 0,
// btn,
// key: "updateavailable",
// });
// };
// serviceWorkerRegistration.register({ onUpdate: onServiceWorkerUpdate });
reportWebVitals(); reportWebVitals();

View File

@@ -5,16 +5,19 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import AccountingPayablesTable from "../../components/accounting-payables-table/accounting-payables-table.component"; import AccountingPayablesTable from "../../components/accounting-payables-table/accounting-payables-table.component";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import { checkPartnerStatus } from "../../components/partner-ping/partner-ping.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_BILLS_FOR_EXPORT } from "../../graphql/accounting.queries"; import { QUERY_BILLS_FOR_EXPORT } from "../../graphql/accounting.queries";
import { import {
setBreadcrumbs, setBreadcrumbs,
setSelectedHeader, setSelectedHeader,
} from "../../redux/application/application.actions"; } from "../../redux/application/application.actions";
import { selectPartnerVersion } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
partnerVersion: selectPartnerVersion,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -26,6 +29,7 @@ export function AccountingPayablesContainer({
bodyshop, bodyshop,
setBreadcrumbs, setBreadcrumbs,
setSelectedHeader, setSelectedHeader,
partnerVersion,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -38,7 +42,8 @@ export function AccountingPayablesContainer({
label: t("titles.bc.accounting-payables"), label: t("titles.bc.accounting-payables"),
}, },
]); ]);
}, [t, setBreadcrumbs, setSelectedHeader]); checkPartnerStatus(bodyshop, true);
}, [t, setBreadcrumbs, setSelectedHeader, bodyshop]);
const { loading, error, data } = useQuery(QUERY_BILLS_FOR_EXPORT, { const { loading, error, data } = useQuery(QUERY_BILLS_FOR_EXPORT, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
@@ -47,9 +52,24 @@ export function AccountingPayablesContainer({
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
const noPath =
!partnerVersion?.qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
);
return ( return (
<div> <div>
<RbacWrapper action="accounting:payables"> <RbacWrapper action="accounting:payables">
{noPath && (
<AlertComponent
type="error"
message={t("general.messages.noacctfilepath")}
/>
)}
<AccountingPayablesTable <AccountingPayablesTable
loadaing={loading} loadaing={loading}
bills={data ? data.bills : []} bills={data ? data.bills : []}

View File

@@ -12,9 +12,12 @@ import {
} from "../../redux/application/application.actions"; } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { checkPartnerStatus } from "../../components/partner-ping/partner-ping.component";
import { selectPartnerVersion } from "../../redux/application/application.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
partnerVersion: selectPartnerVersion,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -25,6 +28,7 @@ export function AccountingPaymentsContainer({
bodyshop, bodyshop,
setBreadcrumbs, setBreadcrumbs,
setSelectedHeader, setSelectedHeader,
partnerVersion,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -37,7 +41,8 @@ export function AccountingPaymentsContainer({
label: t("titles.bc.accounting-payments"), label: t("titles.bc.accounting-payments"),
}, },
]); ]);
}, [t, setBreadcrumbs, setSelectedHeader]); checkPartnerStatus(bodyshop, true);
}, [t, setBreadcrumbs, setSelectedHeader, bodyshop]);
const { loading, error, data } = useQuery(QUERY_PAYMENTS_FOR_EXPORT, { const { loading, error, data } = useQuery(QUERY_PAYMENTS_FOR_EXPORT, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
@@ -45,10 +50,23 @@ export function AccountingPaymentsContainer({
}); });
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
const noPath =
!partnerVersion?.qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
);
return ( return (
<div> <div>
<RbacWrapper action="accounting:payments"> <RbacWrapper action="accounting:payments">
{noPath && (
<AlertComponent
type="error"
message={t("general.messages.noacctfilepath")}
/>
)}
<AccountingPaymentsTable <AccountingPaymentsTable
loadaing={loading} loadaing={loading}
payments={data ? data.payments : []} payments={data ? data.payments : []}

View File

@@ -12,9 +12,12 @@ import {
} from "../../redux/application/application.actions"; } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { checkPartnerStatus } from "../../components/partner-ping/partner-ping.component";
import { selectPartnerVersion } from "../../redux/application/application.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
partnerVersion: selectPartnerVersion,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -25,6 +28,7 @@ export function AccountingReceivablesContainer({
bodyshop, bodyshop,
setBreadcrumbs, setBreadcrumbs,
setSelectedHeader, setSelectedHeader,
partnerVersion,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -37,7 +41,8 @@ export function AccountingReceivablesContainer({
label: t("titles.bc.accounting-receivables"), label: t("titles.bc.accounting-receivables"),
}, },
]); ]);
}, [t, setBreadcrumbs, setSelectedHeader]); checkPartnerStatus(bodyshop, true);
}, [t, setBreadcrumbs, setSelectedHeader, bodyshop]);
const { loading, error, data } = useQuery(QUERY_JOBS_FOR_EXPORT, { const { loading, error, data } = useQuery(QUERY_JOBS_FOR_EXPORT, {
variables: { variables: {
@@ -48,9 +53,25 @@ export function AccountingReceivablesContainer({
}); });
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
const noPath =
!partnerVersion?.qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
);
return ( return (
<div> <div>
<RbacWrapper action="accounting:receivables"> <RbacWrapper action="accounting:receivables">
{noPath && (
<AlertComponent
type="error"
message={t("general.messages.noacctfilepath")}
/>
)}
<AccountingReceivablesTable <AccountingReceivablesTable
loadaing={loading} loadaing={loading}
jobs={data ? data.jobs : []} jobs={data ? data.jobs : []}

View File

@@ -1,4 +1,4 @@
import { SyncOutlined } from "@ant-design/icons"; import { SyncOutlined, EditFilled } from "@ant-design/icons";
import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd"; import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd";
import queryString from "query-string"; import queryString from "query-string";
import React, { useState } from "react"; import React, { useState } from "react";
@@ -141,7 +141,9 @@ export function BillsListPage({
render: (text, record) => ( render: (text, record) => (
<Space wrap> <Space wrap>
<Link to={`/manage/bills?billid=${record.id}`}> <Link to={`/manage/bills?billid=${record.id}`}>
<Button>{t("bills.actions.edit")}</Button> <Button>
<EditFilled />
</Button>
</Link> </Link>
{ {
// <Button // <Button

View File

@@ -29,6 +29,7 @@ import {
setSelectedHeader, setSelectedHeader,
} from "../../redux/application/application.actions"; } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -143,9 +144,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
<Link to={`/manage/jobs/${data && data.jobs_by_pk.id}`}>{`${ <Link to={`/manage/jobs/${data && data.jobs_by_pk.id}`}>{`${
data && data.jobs_by_pk && data.jobs_by_pk.ro_number data && data.jobs_by_pk && data.jobs_by_pk.ro_number
}`}</Link> }`}</Link>
{` | ${data.jobs_by_pk.ownr_fn || ""} ${ {` | ${OwnerNameDisplayFunction(data.jobs_by_pk)} | ${
data.jobs_by_pk.ownr_ln || ""
} ${data.jobs_by_pk.ownr_co_nm || ""} | ${
data.jobs_by_pk.v_model_yr || "" data.jobs_by_pk.v_model_yr || ""
} ${data.jobs_by_pk.v_make_desc || ""} ${ } ${data.jobs_by_pk.v_make_desc || ""} ${
data.jobs_by_pk.v_model_desc || "" data.jobs_by_pk.v_model_desc || ""

View File

@@ -3,12 +3,19 @@ import React, { useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import JobsAvailableTableContainer from "../../components/jobs-available-table/jobs-available-table.container"; import JobsAvailableTableContainer from "../../components/jobs-available-table/jobs-available-table.container";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { import {
setBreadcrumbs, setBreadcrumbs,
setSelectedHeader, setSelectedHeader,
} from "../../redux/application/application.actions"; } from "../../redux/application/application.actions";
import { selectPartnerVersion } from "../../redux/application/application.selectors";
const mapStateToProps = createStructuredSelector({
partnerVersion: selectPartnerVersion,
});
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -16,6 +23,7 @@ const mapDispatchToProps = (dispatch) => ({
}); });
export function JobsAvailablePageContainer({ export function JobsAvailablePageContainer({
partnerVersion,
setBreadcrumbs, setBreadcrumbs,
setSelectedHeader, setSelectedHeader,
}) { }) {
@@ -40,9 +48,18 @@ export function JobsAvailablePageContainer({
</Link> </Link>
} }
/> />
{!partnerVersion && (
<AlertComponent
type="warning"
message={t("general.messages.partnernotrunning")}
/>
)}
<JobsAvailableTableContainer /> <JobsAvailableTableContainer />
</div> </div>
</RbacWrapper> </RbacWrapper>
); );
} }
export default connect(null, mapDispatchToProps)(JobsAvailablePageContainer); export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsAvailablePageContainer);

View File

@@ -6,6 +6,7 @@ import { createStructuredSelector } from "reselect";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import SpinComponent from "../../components/loading-spinner/loading-spinner.component"; import SpinComponent from "../../components/loading-spinner/loading-spinner.component";
import NotFound from "../../components/not-found/not-found.component"; import NotFound from "../../components/not-found/not-found.component";
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { GET_JOB_BY_PK, UPDATE_JOB } from "../../graphql/jobs.queries"; import { GET_JOB_BY_PK, UPDATE_JOB } from "../../graphql/jobs.queries";
import { import {
@@ -78,11 +79,10 @@ function JobsDetailPageContainer({
CreateRecentItem( CreateRecentItem(
jobId, jobId,
"job", "job",
`${data.jobs_by_pk.ro_number || t("general.labels.na")} | ${
data.jobs_by_pk.ownr_fn || "" `${
} ${data.jobs_by_pk.ownr_ln || ""} ${ data.jobs_by_pk.ro_number || t("general.labels.na")
data.jobs_by_pk.ownr_co_nm || "" } | ${OwnerNameDisplayFunction(data.jobs_by_pk)}`,
}`,
`/manage/jobs/${jobId}` `/manage/jobs/${jobId}`
) )
); );

View File

@@ -14,6 +14,7 @@ import {
import { CreateRecentItem } from "../../utils/create-recent-item"; import { CreateRecentItem } from "../../utils/create-recent-item";
import OwnersDetailComponent from "./owners-detail.page.component"; import OwnersDetailComponent from "./owners-detail.page.component";
import NotFound from "../../components/not-found/not-found.component"; import NotFound from "../../components/not-found/not-found.component";
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -38,11 +39,7 @@ export function OwnersDetailContainer({
useEffect(() => { useEffect(() => {
document.title = t("titles.owners-detail", { document.title = t("titles.owners-detail", {
name: data name: data ? OwnerNameDisplayFunction(data.owners_by_pk) : "",
? `${(data.owners_by_pk && data.owners_by_pk.ownr_fn) || ""} ${
(data.owners_by_pk && data.owners_by_pk.ownr_ln) || ""
} ${(data.owners_by_pk && data.owners_by_pk.ownr_co_nm) || ""}`
: "",
}); });
setSelectedHeader("owners"); setSelectedHeader("owners");
setBreadcrumbs([ setBreadcrumbs([
@@ -50,11 +47,7 @@ export function OwnersDetailContainer({
{ {
link: `/manage/owners/${ownerId}`, link: `/manage/owners/${ownerId}`,
label: t("titles.bc.owner-detail", { label: t("titles.bc.owner-detail", {
name: data name: data ? OwnerNameDisplayFunction(data.owners_by_pk) : "",
? `${(data.owners_by_pk && data.owners_by_pk.ownr_fn) || ""} ${
(data.owners_by_pk && data.owners_by_pk.ownr_ln) || ""
} ${(data.owners_by_pk && data.owners_by_pk.ownr_co_nm) || ""}`
: "",
}), }),
}, },
]); ]);
@@ -64,9 +57,7 @@ export function OwnersDetailContainer({
CreateRecentItem( CreateRecentItem(
ownerId, ownerId,
"owner", "owner",
`${data.owners_by_pk.ownr_fn || ""} ${ OwnerNameDisplayFunction(data.owners_by_pk),
data.owners_by_pk.ownr_ln || ""
} ${data.owners_by_pk.ownr_co_nm || ""}`,
`/manage/owners/${ownerId}` `/manage/owners/${ownerId}`
) )
); );

View File

@@ -17,6 +17,7 @@ import { alphaSort } from "../../utils/sorters";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import queryString from "query-string"; import queryString from "query-string";
import _ from "lodash"; import _ from "lodash";
import OwnerNameDisplay from "../../components/owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -120,14 +121,12 @@ export function PartsQueuePageComponent({ bodyshop }) {
render: (text, record) => { render: (text, record) => {
return record.ownerid ? ( return record.ownerid ? (
<Link to={"/manage/owners/" + record.ownerid}> <Link to={"/manage/owners/" + record.ownerid}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ <OwnerNameDisplay ownerObject={record} />
record.ownr_co_nm || ""
}`}
</Link> </Link>
) : ( ) : (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ <span>
record.ownr_co_nm || "" <OwnerNameDisplay ownerObject={record} />
}`}</span> </span>
); );
}, },
}, },

View File

@@ -7,6 +7,7 @@ import {
confirmPasswordReset, confirmPasswordReset,
signInWithEmailAndPassword, signInWithEmailAndPassword,
signOut, signOut,
sendPasswordResetEmail,
} from "firebase/auth"; } from "firebase/auth";
import { doc } from "firebase/firestore"; import { doc } from "firebase/firestore";
import i18next from "i18next"; import i18next from "i18next";
@@ -223,16 +224,16 @@ export function* signInSuccessSaga({ payload }) {
export function* onSendPasswordResetStart() { export function* onSendPasswordResetStart() {
yield takeLatest( yield takeLatest(
UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START, UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START,
sendPasswordResetEmail sendPasswordResetEmailSaga
); );
yield takeLatest( yield takeLatest(
UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN, UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN,
sendPasswordResetEmail sendPasswordResetEmailSaga
); );
} }
export function* sendPasswordResetEmail({ payload }) { export function* sendPasswordResetEmailSaga({ payload }) {
try { try {
yield sendPasswordResetEmail(payload, { yield sendPasswordResetEmail(auth, payload, {
url: "https://imex.online/passwordreset", url: "https://imex.online/passwordreset",
}); });
@@ -269,7 +270,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
const userEmail = yield select((state) => state.user.currentUser.email); const userEmail = yield select((state) => state.user.currentUser.email);
try { try {
console.log("Setting shop timezone."); console.log("Setting shop timezone.");
// moment.tz.setDefault(payload.timezone); // moment.tz.setDefault(payload.timezone);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }

View File

@@ -157,6 +157,7 @@
"federal_tax_rate": "Federal Tax Rate", "federal_tax_rate": "Federal Tax Rate",
"invoice_number": "Invoice Number", "invoice_number": "Invoice Number",
"is_credit_memo": "Credit Memo?", "is_credit_memo": "Credit Memo?",
"is_credit_memo_short": "CM",
"local_tax_rate": "Local Tax Rate", "local_tax_rate": "Local Tax Rate",
"ro_number": "RO Number", "ro_number": "RO Number",
"state_tax_rate": "Provincial/State Tax Rate", "state_tax_rate": "Provincial/State Tax Rate",
@@ -232,6 +233,7 @@
}, },
"appt_length": "Default Appointment Length", "appt_length": "Default Appointment Length",
"attach_pdf_to_email": "Attach PDF copy to sent emails?", "attach_pdf_to_email": "Attach PDF copy to sent emails?",
"bill_allow_post_to_closed": "Allow Bills to be posted to Closed Jobs",
"bill_federal_tax_rate": "Bills - Federal Tax Rate %", "bill_federal_tax_rate": "Bills - Federal Tax Rate %",
"bill_local_tax_rate": "Bill - Provincial/State Tax Rate %", "bill_local_tax_rate": "Bill - Provincial/State Tax Rate %",
"bill_state_tax_rate": "Bill - Provincial/State Tax Rate %", "bill_state_tax_rate": "Bill - Provincial/State Tax Rate %",
@@ -274,6 +276,7 @@
"mapa": "Job Costing - Paint Materials Hourly Cost Rate", "mapa": "Job Costing - Paint Materials Hourly Cost Rate",
"mash": "Job Costing - Shop Materials Hourly Cost Rate" "mash": "Job Costing - Shop Materials Hourly Cost Rate"
}, },
"last_name_first": "Display Owner Info as <Last>, <First>",
"lastnumberworkingdays": "Scoreboard - Last Number of Working Days", "lastnumberworkingdays": "Scoreboard - Last Number of Working Days",
"logo_img_footer_margin": "Footer Margin (px)", "logo_img_footer_margin": "Footer Margin (px)",
"logo_img_header_margin": "Header Margin (px)", "logo_img_header_margin": "Header Margin (px)",
@@ -293,12 +296,14 @@
"md_ins_co": { "md_ins_co": {
"city": "City", "city": "City",
"name": "Insurance Company Name", "name": "Insurance Company Name",
"private": "Private",
"state": "Province/State", "state": "Province/State",
"street1": "Street 1", "street1": "Street 1",
"street2": "Street 2", "street2": "Street 2",
"zip": "Zip/Postal Code" "zip": "Zip/Postal Code"
}, },
"md_jobline_presets": "Jobline Presets", "md_jobline_presets": "Jobline Presets",
"md_parts_order_comment": "Parts Orders Comments",
"md_payment_types": "Payment Types", "md_payment_types": "Payment Types",
"md_referral_sources": "Referral Sources", "md_referral_sources": "Referral Sources",
"messaginglabel": "Messaging Preset Label", "messaginglabel": "Messaging Preset Label",
@@ -539,6 +544,8 @@
"partslocations": "Parts Locations", "partslocations": "Parts Locations",
"printlater": "Print Later", "printlater": "Print Later",
"qbo": "Use QuickBooks Online?", "qbo": "Use QuickBooks Online?",
"qbo_departmentid": "QBO Department ID",
"qbo_usa": "QBO USA Compatibility",
"rbac": "Role Based Access Control", "rbac": "Role Based Access Control",
"responsibilitycenters": { "responsibilitycenters": {
"costs": "Cost Centers", "costs": "Cost Centers",
@@ -1079,7 +1086,7 @@
"mod_lbr_ty": "Labor Type", "mod_lbr_ty": "Labor Type",
"notes": "Notes", "notes": "Notes",
"oem_partno": "OEM Part #", "oem_partno": "OEM Part #",
"op_code_desc": "Operation Code Description", "op_code_desc": "Op Code Description",
"part_qty": "Qty.", "part_qty": "Qty.",
"part_type": "Part Type", "part_type": "Part Type",
"part_types": { "part_types": {
@@ -1239,6 +1246,7 @@
"08": "Left Rear Side", "08": "Left Rear Side",
"09": "Left Side" "09": "Left Side"
}, },
"auto_add_ats": "Automatically Add/Update ATS",
"ca_bc_pvrt": "PVRT", "ca_bc_pvrt": "PVRT",
"ca_customer_gst": "Customer Portion of GST", "ca_customer_gst": "Customer Portion of GST",
"ca_gst_registrant": "GST Registrant", "ca_gst_registrant": "GST Registrant",
@@ -1386,6 +1394,7 @@
"production_vars": { "production_vars": {
"note": "Production Note" "note": "Production Note"
}, },
"rate_ats": "ATS Rate",
"rate_la1": "LA1", "rate_la1": "LA1",
"rate_la2": "LA2", "rate_la2": "LA2",
"rate_la3": "LA3", "rate_la3": "LA3",
@@ -1563,6 +1572,7 @@
"override_header": "Override estimate header on import?", "override_header": "Override estimate header on import?",
"ownerassociation": "Owner Association", "ownerassociation": "Owner Association",
"parts": "Parts", "parts": "Parts",
"parts_received": "Parts Rec.",
"parts_tax_rates": "Parts Tax rates", "parts_tax_rates": "Parts Tax rates",
"partsfilter": "Parts Only", "partsfilter": "Parts Only",
"partssubletstotal": "Parts & Sublets Total", "partssubletstotal": "Parts & Sublets Total",
@@ -1755,6 +1765,7 @@
}, },
"jobsactions": { "jobsactions": {
"admin": "Admin", "admin": "Admin",
"cancelallappointments": "Cancel all appointments",
"closejob": "Close Job", "closejob": "Close Job",
"deletejob": "Delete Job", "deletejob": "Delete Job",
"duplicate": "Duplicate this Job", "duplicate": "Duplicate this Job",
@@ -1842,6 +1853,11 @@
"updated": "Note updated successfully." "updated": "Note updated successfully."
} }
}, },
"owner": {
"labels": {
"noownerinfo": "No owner information."
}
},
"owners": { "owners": {
"actions": { "actions": {
"update": "Update Selected Records" "update": "Update Selected Records"
@@ -1930,6 +1946,7 @@
"email": "Send by Email", "email": "Send by Email",
"inthisorder": "Parts in this Order", "inthisorder": "Parts in this Order",
"newpartsorder": "New Parts Order", "newpartsorder": "New Parts Order",
"notyetordered": "This part has not yet been ordered.",
"oec": "Order via OEC", "oec": "Order via OEC",
"orderhistory": "Order History", "orderhistory": "Order History",
"parts_orders": "Parts Orders", "parts_orders": "Parts Orders",
@@ -2078,6 +2095,7 @@
"labels": "Labels", "labels": "Labels",
"position": "Starting Position" "position": "Starting Position"
}, },
"lag_time_ro": "Lag Time",
"mechanical_authorization": "Mechanical Authorization", "mechanical_authorization": "Mechanical Authorization",
"mpi_animal_checklist": "MPI - Animal Checklist", "mpi_animal_checklist": "MPI - Animal Checklist",
"mpi_eglass_auth": "MPI - eGlass Auth", "mpi_eglass_auth": "MPI - eGlass Auth",
@@ -2170,6 +2188,12 @@
"ats": "Alternative Transportation", "ats": "Alternative Transportation",
"bodyhours": "B", "bodyhours": "B",
"bodypriority": "B/P", "bodypriority": "B/P",
"bodyshop": {
"labels": {
"qbo_departmentid": "QBO Department ID",
"qbo_usa": "QBO USA"
}
},
"cardsettings": "Card Settings", "cardsettings": "Card Settings",
"clm_no": "Claim Number", "clm_no": "Claim Number",
"comment": "Comment", "comment": "Comment",
@@ -2187,6 +2211,7 @@
"refinishhours": "R", "refinishhours": "R",
"scheduled_completion": "Scheduled Completion", "scheduled_completion": "Scheduled Completion",
"selectview": "Select a View", "selectview": "Select a View",
"stickyheader": "Sticky Header (BETA)",
"sublets": "Sublets", "sublets": "Sublets",
"totalhours": "Total Hrs ", "totalhours": "Total Hrs ",
"touchtime": "T/T", "touchtime": "T/T",
@@ -2553,6 +2578,14 @@
"passwordchanged": "Password changed successfully. " "passwordchanged": "Password changed successfully. "
} }
}, },
"users": {
"errors": {
"signinerror": {
"auth/user-not-found": "A user with this email does not exist.",
"auth/wrong-password": "The email and password combination you provided is incorrect."
}
}
},
"vehicles": { "vehicles": {
"errors": { "errors": {
"noaccess": "The vehicle does not exist or you do not have access to it.", "noaccess": "The vehicle does not exist or you do not have access to it.",

View File

@@ -157,6 +157,7 @@
"federal_tax_rate": "", "federal_tax_rate": "",
"invoice_number": "", "invoice_number": "",
"is_credit_memo": "", "is_credit_memo": "",
"is_credit_memo_short": "",
"local_tax_rate": "", "local_tax_rate": "",
"ro_number": "", "ro_number": "",
"state_tax_rate": "", "state_tax_rate": "",
@@ -232,6 +233,7 @@
}, },
"appt_length": "", "appt_length": "",
"attach_pdf_to_email": "", "attach_pdf_to_email": "",
"bill_allow_post_to_closed": "",
"bill_federal_tax_rate": "", "bill_federal_tax_rate": "",
"bill_local_tax_rate": "", "bill_local_tax_rate": "",
"bill_state_tax_rate": "", "bill_state_tax_rate": "",
@@ -274,6 +276,7 @@
"mapa": "", "mapa": "",
"mash": "" "mash": ""
}, },
"last_name_first": "",
"lastnumberworkingdays": "", "lastnumberworkingdays": "",
"logo_img_footer_margin": "", "logo_img_footer_margin": "",
"logo_img_header_margin": "", "logo_img_header_margin": "",
@@ -293,12 +296,14 @@
"md_ins_co": { "md_ins_co": {
"city": "", "city": "",
"name": "", "name": "",
"private": "",
"state": "", "state": "",
"street1": "", "street1": "",
"street2": "", "street2": "",
"zip": "" "zip": ""
}, },
"md_jobline_presets": "", "md_jobline_presets": "",
"md_parts_order_comment": "",
"md_payment_types": "", "md_payment_types": "",
"md_referral_sources": "", "md_referral_sources": "",
"messaginglabel": "", "messaginglabel": "",
@@ -539,6 +544,8 @@
"partslocations": "", "partslocations": "",
"printlater": "", "printlater": "",
"qbo": "", "qbo": "",
"qbo_departmentid": "",
"qbo_usa": "",
"rbac": "", "rbac": "",
"responsibilitycenters": { "responsibilitycenters": {
"costs": "", "costs": "",
@@ -1239,6 +1246,7 @@
"08": "", "08": "",
"09": "" "09": ""
}, },
"auto_add_ats": "",
"ca_bc_pvrt": "", "ca_bc_pvrt": "",
"ca_customer_gst": "", "ca_customer_gst": "",
"ca_gst_registrant": "", "ca_gst_registrant": "",
@@ -1386,6 +1394,7 @@
"production_vars": { "production_vars": {
"note": "" "note": ""
}, },
"rate_ats": "",
"rate_la1": "Tarifa LA1", "rate_la1": "Tarifa LA1",
"rate_la2": "Tarifa LA2", "rate_la2": "Tarifa LA2",
"rate_la3": "Tarifa LA3", "rate_la3": "Tarifa LA3",
@@ -1563,6 +1572,7 @@
"override_header": "¿Anular encabezado estimado al importar?", "override_header": "¿Anular encabezado estimado al importar?",
"ownerassociation": "", "ownerassociation": "",
"parts": "Partes", "parts": "Partes",
"parts_received": "",
"parts_tax_rates": "", "parts_tax_rates": "",
"partsfilter": "", "partsfilter": "",
"partssubletstotal": "", "partssubletstotal": "",
@@ -1755,6 +1765,7 @@
}, },
"jobsactions": { "jobsactions": {
"admin": "", "admin": "",
"cancelallappointments": "",
"closejob": "", "closejob": "",
"deletejob": "", "deletejob": "",
"duplicate": "", "duplicate": "",
@@ -1842,6 +1853,11 @@
"updated": "Nota actualizada con éxito." "updated": "Nota actualizada con éxito."
} }
}, },
"owner": {
"labels": {
"noownerinfo": ""
}
},
"owners": { "owners": {
"actions": { "actions": {
"update": "" "update": ""
@@ -1930,6 +1946,7 @@
"email": "Enviar por correo electrónico", "email": "Enviar por correo electrónico",
"inthisorder": "Partes en este pedido", "inthisorder": "Partes en este pedido",
"newpartsorder": "", "newpartsorder": "",
"notyetordered": "",
"oec": "", "oec": "",
"orderhistory": "Historial de pedidos", "orderhistory": "Historial de pedidos",
"parts_orders": "", "parts_orders": "",
@@ -2078,6 +2095,7 @@
"labels": "", "labels": "",
"position": "" "position": ""
}, },
"lag_time_ro": "",
"mechanical_authorization": "", "mechanical_authorization": "",
"mpi_animal_checklist": "", "mpi_animal_checklist": "",
"mpi_eglass_auth": "", "mpi_eglass_auth": "",
@@ -2170,6 +2188,12 @@
"ats": "", "ats": "",
"bodyhours": "", "bodyhours": "",
"bodypriority": "", "bodypriority": "",
"bodyshop": {
"labels": {
"qbo_departmentid": "",
"qbo_usa": ""
}
},
"cardsettings": "", "cardsettings": "",
"clm_no": "", "clm_no": "",
"comment": "", "comment": "",
@@ -2187,6 +2211,7 @@
"refinishhours": "", "refinishhours": "",
"scheduled_completion": "", "scheduled_completion": "",
"selectview": "", "selectview": "",
"stickyheader": "",
"sublets": "", "sublets": "",
"totalhours": "", "totalhours": "",
"touchtime": "", "touchtime": "",
@@ -2553,6 +2578,14 @@
"passwordchanged": "" "passwordchanged": ""
} }
}, },
"users": {
"errors": {
"signinerror": {
"auth/user-not-found": "",
"auth/wrong-password": ""
}
}
},
"vehicles": { "vehicles": {
"errors": { "errors": {
"noaccess": "El vehículo no existe o usted no tiene acceso a él.", "noaccess": "El vehículo no existe o usted no tiene acceso a él.",

View File

@@ -157,6 +157,7 @@
"federal_tax_rate": "", "federal_tax_rate": "",
"invoice_number": "", "invoice_number": "",
"is_credit_memo": "", "is_credit_memo": "",
"is_credit_memo_short": "",
"local_tax_rate": "", "local_tax_rate": "",
"ro_number": "", "ro_number": "",
"state_tax_rate": "", "state_tax_rate": "",
@@ -232,6 +233,7 @@
}, },
"appt_length": "", "appt_length": "",
"attach_pdf_to_email": "", "attach_pdf_to_email": "",
"bill_allow_post_to_closed": "",
"bill_federal_tax_rate": "", "bill_federal_tax_rate": "",
"bill_local_tax_rate": "", "bill_local_tax_rate": "",
"bill_state_tax_rate": "", "bill_state_tax_rate": "",
@@ -274,6 +276,7 @@
"mapa": "", "mapa": "",
"mash": "" "mash": ""
}, },
"last_name_first": "",
"lastnumberworkingdays": "", "lastnumberworkingdays": "",
"logo_img_footer_margin": "", "logo_img_footer_margin": "",
"logo_img_header_margin": "", "logo_img_header_margin": "",
@@ -293,12 +296,14 @@
"md_ins_co": { "md_ins_co": {
"city": "", "city": "",
"name": "", "name": "",
"private": "",
"state": "", "state": "",
"street1": "", "street1": "",
"street2": "", "street2": "",
"zip": "" "zip": ""
}, },
"md_jobline_presets": "", "md_jobline_presets": "",
"md_parts_order_comment": "",
"md_payment_types": "", "md_payment_types": "",
"md_referral_sources": "", "md_referral_sources": "",
"messaginglabel": "", "messaginglabel": "",
@@ -539,6 +544,8 @@
"partslocations": "", "partslocations": "",
"printlater": "", "printlater": "",
"qbo": "", "qbo": "",
"qbo_departmentid": "",
"qbo_usa": "",
"rbac": "", "rbac": "",
"responsibilitycenters": { "responsibilitycenters": {
"costs": "", "costs": "",
@@ -1239,6 +1246,7 @@
"08": "", "08": "",
"09": "" "09": ""
}, },
"auto_add_ats": "",
"ca_bc_pvrt": "", "ca_bc_pvrt": "",
"ca_customer_gst": "", "ca_customer_gst": "",
"ca_gst_registrant": "", "ca_gst_registrant": "",
@@ -1386,6 +1394,7 @@
"production_vars": { "production_vars": {
"note": "" "note": ""
}, },
"rate_ats": "",
"rate_la1": "Taux LA1", "rate_la1": "Taux LA1",
"rate_la2": "Taux LA2", "rate_la2": "Taux LA2",
"rate_la3": "Taux LA3", "rate_la3": "Taux LA3",
@@ -1563,6 +1572,7 @@
"override_header": "Remplacer l'en-tête d'estimation à l'importation?", "override_header": "Remplacer l'en-tête d'estimation à l'importation?",
"ownerassociation": "", "ownerassociation": "",
"parts": "les pièces", "parts": "les pièces",
"parts_received": "",
"parts_tax_rates": "", "parts_tax_rates": "",
"partsfilter": "", "partsfilter": "",
"partssubletstotal": "", "partssubletstotal": "",
@@ -1755,6 +1765,7 @@
}, },
"jobsactions": { "jobsactions": {
"admin": "", "admin": "",
"cancelallappointments": "",
"closejob": "", "closejob": "",
"deletejob": "", "deletejob": "",
"duplicate": "", "duplicate": "",
@@ -1842,6 +1853,11 @@
"updated": "Remarque mise à jour avec succès." "updated": "Remarque mise à jour avec succès."
} }
}, },
"owner": {
"labels": {
"noownerinfo": ""
}
},
"owners": { "owners": {
"actions": { "actions": {
"update": "" "update": ""
@@ -1930,6 +1946,7 @@
"email": "Envoyé par email", "email": "Envoyé par email",
"inthisorder": "Pièces dans cette commande", "inthisorder": "Pièces dans cette commande",
"newpartsorder": "", "newpartsorder": "",
"notyetordered": "",
"oec": "", "oec": "",
"orderhistory": "Historique des commandes", "orderhistory": "Historique des commandes",
"parts_orders": "", "parts_orders": "",
@@ -2078,6 +2095,7 @@
"labels": "", "labels": "",
"position": "" "position": ""
}, },
"lag_time_ro": "",
"mechanical_authorization": "", "mechanical_authorization": "",
"mpi_animal_checklist": "", "mpi_animal_checklist": "",
"mpi_eglass_auth": "", "mpi_eglass_auth": "",
@@ -2170,6 +2188,12 @@
"ats": "", "ats": "",
"bodyhours": "", "bodyhours": "",
"bodypriority": "", "bodypriority": "",
"bodyshop": {
"labels": {
"qbo_departmentid": "",
"qbo_usa": ""
}
},
"cardsettings": "", "cardsettings": "",
"clm_no": "", "clm_no": "",
"comment": "", "comment": "",
@@ -2187,6 +2211,7 @@
"refinishhours": "", "refinishhours": "",
"scheduled_completion": "", "scheduled_completion": "",
"selectview": "", "selectview": "",
"stickyheader": "",
"sublets": "", "sublets": "",
"totalhours": "", "totalhours": "",
"touchtime": "", "touchtime": "",
@@ -2553,6 +2578,14 @@
"passwordchanged": "" "passwordchanged": ""
} }
}, },
"users": {
"errors": {
"signinerror": {
"auth/user-not-found": "",
"auth/wrong-password": ""
}
}
},
"vehicles": { "vehicles": {
"errors": { "errors": {
"noaccess": "Le véhicule n'existe pas ou vous n'y avez pas accès.", "noaccess": "Le véhicule n'existe pas ou vous n'y avez pas accès.",

View File

@@ -1,7 +1,7 @@
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
import { notification } from "antd"; import { notification } from "antd";
import axios from "axios"; import axios from "axios";
import jsreport from "jsreport-browser-client-dist"; import jsreport from "@jsreport/browser-client";
import _ from "lodash"; import _ from "lodash";
import moment from "moment"; import moment from "moment";
import { auth } from "../firebase/firebase.utils"; import { auth } from "../firebase/firebase.utils";

View File

@@ -464,6 +464,14 @@ export const TemplateList = (type, context) => {
disabled: false, disabled: false,
group: "post", group: "post",
}, },
lag_time_ro: {
title: i18n.t("printcenter.jobs.lag_time_ro"),
description: "CASL Authorization",
subject: i18n.t("printcenter.jobs.lag_time_ro"),
key: "lag_time_ro",
disabled: false,
group: "ro",
},
} }
: {}), : {}),
...(!type || type === "job_special" ...(!type || type === "job_special"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
{}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."bodyshops" add column "last_name_first" boolean
-- not null default 'false';

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "last_name_first" boolean
not null default 'false';

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."bodyshops" add column "md_parts_order_comment" jsonb
-- not null default jsonb_build_array();

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "md_parts_order_comment" jsonb
not null default jsonb_build_array();

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."bodyshops" add column "bill_allow_post_to_closed" boolean
-- not null default 'false';

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "bill_allow_post_to_closed" boolean
not null default 'false';

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."jobs" add column "auto_add_ats" boolean
-- null default 'false';

View File

@@ -0,0 +1,2 @@
alter table "public"."jobs" add column "auto_add_ats" boolean
null default 'false';

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."jobs" add column "rate_ats" numeric
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."jobs" add column "rate_ats" numeric
null;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

5325
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,8 @@
"version": "0.0.1", "version": "0.0.1",
"license": "UNLICENSED", "license": "UNLICENSED",
"engines": { "engines": {
"node": "12.22.6", "node": "16.14.2",
"npm": "7.17.0" "npm": "8.6.0"
}, },
"scripts": { "scripts": {
"setup": "yarn && cd client && yarn", "setup": "yarn && cd client && yarn",

View File

@@ -611,6 +611,32 @@ exports.default = function ({
} }
} }
//QB USA with GST
//This was required for the No. 1 Collision Group.
if (
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
bodyshop.accountingconfig.qbo_usa &&
bodyshop.region_config.includes("CA_")
) {
InvoiceLineAdd.push({
DetailType: "SalesItemLineDetail",
Amount: Dinero(jobs_by_pk.job_totals.totals.federal_tax).toFormat(
DineroQbFormat
),
SalesItemLineDetail: {
...(jobs_by_pk.class
? { ClassRef: { value: classes[jobs_by_pk.class] } }
: {}),
ItemRef: {
value:
items[bodyshop.md_responsibility_centers.taxes.federal.accountitem],
},
Qty: 1,
},
});
}
return InvoiceLineAdd; return InvoiceLineAdd;
}; };
@@ -626,7 +652,7 @@ const findTaxCode = ({ local, state, federal }, taxcode) => {
} else if (t.length > 1) { } else if (t.length > 1) {
return "Multiple Tax Codes Match"; return "Multiple Tax Codes Match";
} else { } else {
return "No Tax Code Matches"; return "";
} }
}; };
exports.findTaxCode = findTaxCode; exports.findTaxCode = findTaxCode;

View File

@@ -174,6 +174,55 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor) {
req req
); );
const lines = bill.billlines.map((il) =>
generateBillLine(
il,
accounts,
bill.job.class,
bill.job.bodyshop.md_responsibility_centers.sales_tax_codes,
classes,
taxCodes,
bill.job.bodyshop.md_responsibility_centers.costs
)
);
//QB USA with GST
//This was required for the No. 1 Collision Group.
if (
bill.job.bodyshop.accountingconfig &&
bill.job.bodyshop.accountingconfig.qbo &&
bill.job.bodyshop.accountingconfig.qbo_usa &&
bill.job.bodyshop.region_config.includes("CA_")
) {
lines.push({
DetailType: "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail: {
...(bill.job.class
? { ClassRef: { value: classes[bill.job.class] } }
: {}),
AccountRef: {
value:
accounts[
bill.job.bodyshop.md_responsibility_centers.taxes.federal
.accountdesc
],
},
},
Amount: Dinero({
amount: Math.round(
bill.billlines.reduce((acc, val) => {
return acc + val.actual_cost * val.quantity;
}, 0) * 100
),
})
.percentage(bill.federal_tax_rate)
.toFormat(DineroQbFormat),
});
}
const billQbo = { const billQbo = {
VendorRef: { VendorRef: {
value: vendor.Id, value: vendor.Id,
@@ -192,17 +241,7 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor) {
//...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}), //...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),
PrivateNote: `RO ${bill.job.ro_number || ""}`, PrivateNote: `RO ${bill.job.ro_number || ""}`,
Line: bill.billlines.map((il) => Line: lines,
generateBillLine(
il,
accounts,
bill.job.class,
bill.job.bodyshop.md_responsibility_centers.sales_tax_codes,
classes,
taxCodes,
bill.job.bodyshop.md_responsibility_centers.costs
)
),
}; };
logger.log("qbo-payable-objectlog", "DEBUG", req.user.email, bill.id, { logger.log("qbo-payable-objectlog", "DEBUG", req.user.email, bill.id, {
billQbo, billQbo,
@@ -282,7 +321,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) {
url: urlBuilder( url: urlBuilder(
qbo_realmId, qbo_realmId,
"query", "query",
`select * From Account where AccountType = 'Cost of Goods Sold'` `select * From Account where AccountType in ('Cost of Goods Sold', 'Other Current Liability')`
), ),
method: "POST", method: "POST",
headers: { headers: {

View File

@@ -455,6 +455,25 @@ async function InsertInvoice(
CustomerRef: { CustomerRef: {
value: parentTierRef.Id, value: parentTierRef.Id,
}, },
...(bodyshop.accountingconfig.qbo_departmentid &&
bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && {
DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid },
}),
...(bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
bodyshop.accountingconfig.qbo_usa &&
bodyshop.region_config.includes("CA_") && {
TxnTaxDetail: {
TxnTaxCodeRef: {
value:
taxCodes[
bodyshop.md_responsibility_centers.taxes.state.accountitem
],
},
},
}),
...(bodyshop.accountingconfig.printlater ...(bodyshop.accountingconfig.printlater
? { PrintStatus: "NeedToPrint" } ? { PrintStatus: "NeedToPrint" }
: {}), : {}),

View File

@@ -39,7 +39,7 @@ exports.default = async (req, res) => {
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS); const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
const specificShopIds = req.body.bodyshopIds; // ['uuid] const specificShopIds = req.body.bodyshopIds; // ['uuid]
const start = req.body.start; //YYYY-MM-DD const { start, end } = req.body; //YYYY-MM-DD
const allxmlsToUpload = []; const allxmlsToUpload = [];
const allErrors = []; const allErrors = [];
try { try {
@@ -58,6 +58,7 @@ exports.default = async (req, res) => {
start: start start: start
? moment(start).startOf("day") ? moment(start).startOf("day")
: moment().subtract(3, "days").startOf("day"), : moment().subtract(3, "days").startOf("day"),
...(end && { end: moment(end).startOf("day") }),
} }
); );
@@ -167,7 +168,6 @@ exports.default = async (req, res) => {
sendServerEmail({ sendServerEmail({
subject: `Autohouse Report ${moment().format("MM-DD-YY")}`, subject: `Autohouse Report ${moment().format("MM-DD-YY")}`,
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
Uploaded: ${JSON.stringify( Uploaded: ${JSON.stringify(
allxmlsToUpload.map((x) => x.filename), allxmlsToUpload.map((x) => x.filename),
null, null,
@@ -184,6 +184,11 @@ exports.default = async (req, res) => {
const CreateRepairOrderTag = (job, errorCallback) => { const CreateRepairOrderTag = (job, errorCallback) => {
//Level 2 //Level 2
if (!job.job_totals) {
errorCallback({ job, error: { toString: () => "No job totals for RO." } });
return {};
}
const repairCosts = CreateCosts(job); const repairCosts = CreateCosts(job);
try { try {
@@ -749,9 +754,14 @@ const CreateCosts = (job) => {
return { return {
PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => { PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => {
if (key !== defaultCosts.PAS && key !== defaultCosts.PASL) if (
key !== defaultCosts.PAS &&
key !== defaultCosts.PASL &&
key !== defaultCosts.MAPA &&
key !== defaultCosts.MASH &&
key !== defaultCosts.TOW
)
return acc.add(billTotalsByCostCenters[key]); return acc.add(billTotalsByCostCenters[key]);
return acc; return acc;
}, Dinero()), }, Dinero()),
PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add( PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add(

View File

@@ -20,6 +20,38 @@ mutation UNARCHIVE_CONVERSATION($id: uuid!) {
} }
`; `;
exports.INSERT_NEW_JOB_LINE = `
mutation INSERT_NEW_JOB_LINE($lineInput: [joblines_insert_input!]!) {
insert_joblines(objects: $lineInput) {
returning {
id
}
}
}
`;
exports.UPDATE_JOB_LINE = `
mutation UPDATE_JOB_LINE($lineId: uuid!, $line: joblines_set_input!) {
update_joblines(where: { id: { _eq: $lineId } }, _set: $line) {
returning {
id
notes
mod_lbr_ty
part_qty
db_price
act_price
line_desc
line_no
oem_partno
notes
location
status
removed
}
}
}
`;
exports.RECEIVE_MESSAGE = ` exports.RECEIVE_MESSAGE = `
mutation RECEIVE_MESSAGE($msg: [messages_insert_input!]!) { mutation RECEIVE_MESSAGE($msg: [messages_insert_input!]!) {
insert_messages(objects: $msg) { insert_messages(objects: $msg) {
@@ -111,6 +143,11 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
ro_number ro_number
clm_total clm_total
clm_no clm_no
v_model_yr
v_model_desc
v_make_desc
v_vin
plate_no
ownerid ownerid
ownr_ln ownr_ln
ownr_fn ownr_fn
@@ -176,6 +213,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
bodyshops(where: {associations: {active: {_eq: true}}}) { bodyshops(where: {associations: {active: {_eq: true}}}) {
id id
md_responsibility_centers md_responsibility_centers
region_config
accountingconfig accountingconfig
md_ins_cos md_ins_cos
timezone timezone
@@ -389,6 +427,8 @@ query QUERY_BILLS_FOR_PAYABLES_EXPORT($bills: [uuid!]!) {
bodyshop{ bodyshop{
md_responsibility_centers md_responsibility_centers
timezone timezone
region_config
accountingconfig
} }
} }
billlines{ billlines{
@@ -550,7 +590,7 @@ exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employee
} }
}`; }`;
exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshopid: uuid!) { exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshopid: uuid!, $end: timestamptz) {
bodyshops_by_pk(id: $bodyshopid){ bodyshops_by_pk(id: $bodyshopid){
id id
@@ -568,7 +608,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
jc_hourly_rates jc_hourly_rates
timezone timezone
} }
jobs(where: {_and: [{converted: {_eq: true}}, {updated_at: {_gt: $start}}, {shopid: {_eq: $bodyshopid}}]}) { jobs(where: {_and: [{converted: {_eq: true}}, {updated_at: {_gt: $start}}, {updated_at: {_lte: $end}}, {shopid: {_eq: $bodyshopid}}]}) {
id id
ro_number ro_number
status status
@@ -962,6 +1002,8 @@ exports.GET_JOB_BY_PK = ` query GET_JOB_BY_PK($id: uuid!) {
ca_bc_pvrt ca_bc_pvrt
ca_customer_gst ca_customer_gst
materials materials
auto_add_ats
rate_ats
joblines(where: { removed: { _eq: false } }){ joblines(where: { removed: { _eq: false } }){
id id
line_no line_no

View File

@@ -24,7 +24,7 @@ exports.totalsSsu = async function (req, res) {
}); });
const newTotals = await TotalsServerSide( const newTotals = await TotalsServerSide(
{ body: { job: job.jobs_by_pk } }, { body: { job: job.jobs_by_pk, client: client } },
res, res,
true true
); );
@@ -53,7 +53,9 @@ exports.totalsSsu = async function (req, res) {
//IMPORTANT*** These two functions MUST be mirrrored. //IMPORTANT*** These two functions MUST be mirrrored.
async function TotalsServerSide(req, res) { async function TotalsServerSide(req, res) {
const { job } = req.body; const { job, client } = req.body;
await AutoAddAtsIfRequired({ job: job, client: client });
try { try {
let ret = { let ret = {
parts: CalculatePartsTotals(job.joblines), parts: CalculatePartsTotals(job.joblines),
@@ -78,6 +80,16 @@ async function Totals(req, res) {
jobid: job.id, jobid: job.id,
}); });
const BearerToken = req.headers.authorization;
const { id } = req.body;
logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
Authorization: BearerToken,
},
});
await AutoAddAtsIfRequired({ job, client });
try { try {
let ret = { let ret = {
parts: CalculatePartsTotals(job.joblines), parts: CalculatePartsTotals(job.joblines),
@@ -96,6 +108,83 @@ async function Totals(req, res) {
} }
} }
async function AutoAddAtsIfRequired({ job, client }) {
//Check if ATS should be automatically added.
if (job.auto_add_ats) {
//Get the total sum of hours that should be the ATS amount.
//Check to see if an ATS line exists.
let atsLineIndex = null;
const atsHours = job.joblines.reduce((acc, val, index) => {
if (val.line_desc && val.line_desc.toLowerCase() === "ats amount") {
atsLineIndex = index;
}
if (
val.mod_lbr_ty !== "LA1" &&
val.mod_lbr_ty !== "LA2" &&
val.mod_lbr_ty !== "LA3" &&
val.mod_lbr_ty !== "LA4" &&
val.mod_lbr_ty !== "LAU" &&
val.mod_lbr_ty !== "LAG" &&
val.mod_lbr_ty !== "LAS" &&
val.mod_lbr_ty !== "LAA"
) {
acc = acc + val.mod_lb_hrs;
}
return acc;
}, 0);
const atsAmount = atsHours * (job.rate_ats || 0);
//If it does, update it in place, and make sure it is updated for local calculations.
if (atsLineIndex === null) {
const newAtsLine = {
jobid: job.id,
alt_partm: null,
line_no: 35,
unq_seq: 0,
line_ind: "E",
line_desc: "ATS Amount",
line_ref: 0.0,
part_type: null,
oem_partno: null,
db_price: 0.0,
act_price: atsAmount,
part_qty: 1,
mod_lbr_ty: null,
db_hrs: 0.0,
mod_lb_hrs: 0.0,
lbr_op: "OP13",
lbr_amt: 0.0,
op_code_desc: "ADDITIONAL COSTS",
status: null,
location: null,
tax_part: true,
db_ref: null,
manual_line: true,
prt_dsmk_p: 0.0,
prt_dsmk_m: 0.0,
};
const result = await client.request(queries.INSERT_NEW_JOB_LINE, {
lineInput: [newAtsLine],
});
job.joblines.push(newAtsLine);
}
//If it does not, create one for local calculations and insert it.
else {
const result = await client.request(queries.UPDATE_JOB_LINE, {
line: { act_price: atsAmount },
lineId: job.joblines[atsLineIndex].id,
});
job.joblines[atsLineIndex].act_price = atsAmount;
}
console.log(job.jobLines);
}
}
function CalculateRatesTotals(ratesList) { function CalculateRatesTotals(ratesList) {
const jobLines = ratesList.joblines.filter((jl) => !jl.removed); const jobLines = ratesList.joblines.filter((jl) => !jl.removed);

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