Merged in feature/america (pull request #698)

Feature/america
This commit is contained in:
Patrick Fic
2023-03-28 16:55:36 +00:00
103 changed files with 15342 additions and 11288 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project be_version="2.7.1" version="1.2"> <babeledit_project version="1.2" be_version="2.7.1">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -3665,6 +3665,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>addpartsrule</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>addspeedprint</name> <name>addspeedprint</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4339,6 +4360,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>dms_control_override</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>dms_wip_acctnumber</name> <name>dms_wip_acctnumber</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4551,6 +4593,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>enforce_conversion_category</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>enforce_conversion_csr</name> <name>enforce_conversion_csr</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -5390,6 +5453,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>md_lost_sale_reasons</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_parts_order_comment</name> <name>md_parts_order_comment</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -5411,6 +5495,53 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<folder_node>
<name>md_parts_scan</name>
<children>
<concept_node>
<name>expression</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>flags</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>
<concept_node> <concept_node>
<name>md_payment_types</name> <name>md_payment_types</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -9588,6 +9719,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>partsscan</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>printlater</name> <name>printlater</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20737,6 +20889,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>sendpartspricechange</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>sendtodms</name> <name>sendtodms</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -21183,6 +21356,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>partspricechange</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>saving</name> <name>saving</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -24132,6 +24326,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>lost_sale_reason</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>ma2s</name> <name>ma2s</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -26383,6 +26598,27 @@
<folder_node> <folder_node>
<name>labels</name> <name>labels</name>
<children> <children>
<concept_node>
<name>act_price_ppc</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>actual_completion_inferred</name> <name>actual_completion_inferred</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -29017,6 +29253,27 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<concept_node>
<name>ppc</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>profileadjustments</name> <name>profileadjustments</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -40754,6 +41011,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>customer_list</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>cycle_time_analysis</name> <name>cycle_time_analysis</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -42140,6 +42418,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>production_over_time</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>psr_by_make</name> <name>psr_by_make</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -42161,6 +42460,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>purchase_return_ratio_grouped_by_vendor_detail</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>purchase_return_ratio_grouped_by_vendor_summary</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>purchases_by_cost_center_detail</name> <name>purchases_by_cost_center_detail</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -42696,6 +43037,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>ins_co_nm_filter</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>intake</name> <name>intake</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -8,7 +8,7 @@ REACT_APP_CLOUDINARY_API_KEY=957865933348715
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250 REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo' REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.imex.online/ REACT_APP_AXIOS_BASE_API_URL=http://localhost:4000
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
REACT_APP_COUNTRY=USA REACT_APP_COUNTRY=USA

View File

@@ -4,69 +4,69 @@
"private": true, "private": true,
"proxy": "http://localhost:4000", "proxy": "http://localhost:4000",
"dependencies": { "dependencies": {
"@apollo/client": "^3.6.9", "@apollo/client": "^3.7.9",
"@asseinfo/react-kanban": "^2.2.0", "@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^6.4.5", "@craco/craco": "^7.0.0",
"@fingerprintjs/fingerprintjs": "^3.3.3", "@fingerprintjs/fingerprintjs": "^3.3.3",
"@jsreport/browser-client": "^3.1.0", "@jsreport/browser-client": "^3.1.0",
"@sentry/react": "^7.28.1", "@sentry/react": "^7.40.0",
"@sentry/tracing": "^7.28.1", "@sentry/tracing": "^7.40.0",
"@splitsoftware/splitio-react": "^1.6.0", "@splitsoftware/splitio-react": "^1.8.1",
"@tanem/react-nprogress": "^5.0.8", "@tanem/react-nprogress": "^5.0.8",
"antd": "^4.22.3", "antd": "^4.24.8",
"apollo-link-logger": "^2.0.0", "apollo-link-logger": "^2.0.1",
"axios": "^0.27.2", "axios": "^1.3.4",
"craco-less": "^1.20.0", "craco-less": "^2.0.0",
"dinero.js": "^1.9.1", "dinero.js": "^1.9.1",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"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.9.1", "firebase": "^9.17.1",
"graphql": "^16.5.0", "graphql": "^16.6.0",
"i18next": "^21.8.14", "i18next": "^22.4.10",
"i18next-browser-languagedetector": "^6.1.4", "i18next-browser-languagedetector": "^7.0.1",
"jsoneditor": "^9.9.0", "jsoneditor": "^9.9.0",
"jsreport-browser-client-dist": "^1.3.0", "jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.10.9", "libphonenumber-js": "^1.10.21",
"logrocket": "^3.0.1", "logrocket": "^3.0.1",
"markerjs2": "^2.22.0", "markerjs2": "^2.28.1",
"moment-business-days": "^1.2.0", "moment-business-days": "^1.2.0",
"moment-timezone": "^0.5.34", "moment-timezone": "^0.5.41",
"normalize-url": "^7.0.3", "normalize-url": "^8.0.0",
"phone": "^3.1.23", "phone": "^3.1.35",
"preval.macro": "^5.0.0", "preval.macro": "^5.0.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"query-string": "^7.1.1", "query-string": "^7.1.3",
"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": "^17.0.2",
"react-big-calendar": "^1.5.0", "react-big-calendar": "^1.6.8",
"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": "^17.0.2",
"react-drag-listview": "^0.2.1", "react-drag-listview": "^0.2.1",
"react-grid-gallery": "^0.5.5", "react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.3.4", "react-grid-layout": "^1.3.4",
"react-i18next": "^11.18.1", "react-i18next": "^12.2.0",
"react-icons": "^4.4.0", "react-icons": "^4.7.1",
"react-number-format": "^4.9.3", "react-number-format": "^5.1.3",
"react-redux": "^7.2.8", "react-redux": "^8.0.5",
"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": "^5.0.1",
"react-sticky": "^6.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.12", "recharts": "^2.4.3",
"redux": "^4.2.0", "redux": "^4.2.1",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"redux-saga": "^1.1.3", "redux-saga": "^1.2.2",
"redux-state-sync": "^3.1.4", "redux-state-sync": "^3.1.4",
"reselect": "^4.1.6", "reselect": "^4.1.7",
"sass": "^1.54.0", "sass": "^1.58.3",
"socket.io-client": "^4.5.1", "socket.io-client": "^4.6.1",
"styled-components": "^5.3.5", "styled-components": "^5.3.6",
"subscriptions-transport-ws": "^0.11.0", "subscriptions-transport-ws": "^0.11.0",
"web-vitals": "^2.1.4", "web-vitals": "^2.1.4",
"workbox-background-sync": "^6.5.3", "workbox-background-sync": "^6.5.3",

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,258 @@
import React, { useEffect } from "react";
import axios from "axios";
import { useTranslation } from "react-i18next";
import { Button, Card, Form, Input, InputNumber, Row, Select } from "antd";
import moment from "moment";
import { useMutation, useQuery } from "@apollo/client";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
import {
INSERT_PAYMENT_RESPONSE,
QUERY_RO_AND_OWNER_BY_JOB_PK,
} from "../../graphql/payment_response.queries";
import DataLabel from "../data-label/data-label.component";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { connect } from "react-redux";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
});
const CardPaymentModalComponent = ({
bodyshop,
context,
toggleModalVisible,
insertAuditTrail,
}) => {
const [form] = Form.useForm();
const amount = Form.useWatch("amount", form);
const payer = Form.useWatch("payer", form);
const jobid = Form.useWatch("jobid", form);
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
const { t } = useTranslation();
const { data, refetch } = useQuery(QUERY_RO_AND_OWNER_BY_JOB_PK, {
variables: { jobid: context?.jobid ?? "" },
});
useEffect(() => {
axios.get("/intellipay/lightbox_credentials").then((response) => {
var rg = document.createRange();
let node = rg.createContextualFragment(response.data);
document.documentElement.appendChild(node);
window.intellipay.initialize();
window.intellipay.runOnClose(() => {
window.intellipay.initialize();
});
window.intellipay.runOnApproval(async function (response) {
form.setFieldValue("paymentResponse", response);
form.submit();
toggleModalVisible();
});
window.intellipay.runOnNonApproval(async function (response) {
// Mutate unsuccessful payment
await insertPaymentResponse({
variables: {
paymentResponse: {
amount: response.amount,
bodyshopid: bodyshop.id,
jobid: jobid || context.jobid,
declinereason: response.declinereason,
ext_paymentid: response.paymentid.toString(),
successful: false,
response,
},
},
});
// Insert failed payment to audit trail
insertAuditTrail({
jobid: jobid || context?.jobid,
operation: AuditTrailMapping.failedpayment(),
});
});
});
if (context?.jobid) {
form.setFieldValue("jobid", context.jobid);
}
form.setFieldValue("payer", t("payments.labels.customer"));
function handleEvents(...props) {
const operation = props[0].data.operation;
if (operation === "updateform") {
props[0].stopImmediatePropagation();
}
}
window.addEventListener("message", handleEvents, false);
return () => window.removeEventListener("message", handleEvents, false);
}, []);
const handleFinish = async (values) => {
const paymentResult = await insertPayment({
variables: {
paymentInput: {
amount: values.amount,
transactionid: values.paymentResponse.receiptelements.transid,
payer: values.payer,
type: values.paymentResponse.cardType,
jobid: values.jobid,
date: moment(Date.now()),
},
},
update(cache, { data }) {
cache.modify({
id: cache.identify({ id: jobid, __typename: "jobs" }),
fields: {
payments(payments) {
return [...data.insert_payments.returning, ...payments];
},
},
});
},
});
await insertPaymentResponse({
variables: {
paymentResponse: {
amount: values.amount,
bodyshopid: bodyshop.id,
paymentid: paymentResult.data.insert_payments.returning[0].id,
jobid: values.jobid,
declinereason: values.paymentResponse.declinereason,
ext_paymentid: values.paymentResponse.paymentid.toString(),
successful: true,
response: values.paymentResponse,
},
},
});
};
return (
<Card title="Card Payment">
<Form onFinish={handleFinish} form={form}>
<LayoutFormRow grow>
<Form.Item
name="jobid"
label={t("bills.fields.ro_number")}
rules={[
{
required: true,
// message: t("general.validation.required"),
},
]}
>
<JobSearchSelectComponent
disabled={context?.jobid}
notExported={false}
clm_no
onChange={(e) => {
refetch({ jobid: e });
}}
/>
</Form.Item>
</LayoutFormRow>
{/* Lighbox Input amount needs to be hidden */}
<Input
className="ipayfield"
data-ipayname="amount"
type="hidden"
value={amount}
hidden
/>
<Input
className="ipayfield"
data-ipayname="account"
type="hidden"
value={data?.jobs_by_pk.ro_number}
hidden
/>
<Input
className="ipayfield"
data-ipayname="email"
type="hidden"
value={data?.jobs_by_pk.owner.ownr_ea}
hidden
/>
{/* Lightbox payment response when it is completed */}
<Form.Item name="paymentResponse" hidden>
<Input type="hidden" value={amount} />
</Form.Item>
<LayoutFormRow grow>
<Form.Item
label={t("payments.fields.payer")}
name="payer"
rules={[
{
required: true,
// message: t("general.validation.required"),
},
]}
>
<Select>
<Select.Option value={t("payments.labels.customer")}>
{t("payments.labels.customer")}
</Select.Option>
<Select.Option value={t("payments.labels.insurance")}>
{t("payments.labels.insurance")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item
label="Amount"
name="amount"
rules={[
{
required: true,
// message: t("general.validation.required"),
},
]}
>
<InputNumber />
</Form.Item>
<Row justify="space-around">
<Button
type="primary"
data-ipayname="submit"
className="ipayfield"
disabled={!amount || !payer || !jobid}
>
{t("job_payments.buttons.proceedtopayment")}
</Button>
{context?.balance && (
<DataLabel
valueStyle={{
color: context?.balance.getAmount() !== 0 ? "red" : "green",
}}
label={t("payments.labels.balance")}
>
{context?.balance.toFormat()}
</DataLabel>
)}
</Row>
</LayoutFormRow>
</Form>
</Card>
);
};
export default connect(null, mapDispatchToProps)(CardPaymentModalComponent);

View File

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

View File

@@ -42,6 +42,7 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
MARK_MESSAGES_AS_READ_BY_CONVERSATION, MARK_MESSAGES_AS_READ_BY_CONVERSATION,
{ {
variables: { conversationId: selectedConversation }, variables: { conversationId: selectedConversation },
refetchQueries: ["UNREAD_CONVERSATION_COUNT"],
update(cache) { update(cache) {
cache.modify({ cache.modify({
id: cache.identify({ id: cache.identify({

View File

@@ -10,7 +10,10 @@ 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 { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { CONVERSATION_LIST_QUERY } from "../../graphql/conversations.queries"; import {
CONVERSATION_LIST_QUERY,
UNREAD_CONVERSATION_COUNT,
} from "../../graphql/conversations.queries";
import { toggleChatVisible } from "../../redux/messaging/messaging.actions"; import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
import { import {
selectChatVisible, selectChatVisible,
@@ -37,9 +40,17 @@ export function ChatPopupComponent({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [pollInterval, setpollInterval] = useState(0); const [pollInterval, setpollInterval] = useState(0);
const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
...(pollInterval > 0 ? { pollInterval } : {}),
});
const { loading, data, refetch, called } = useQuery(CONVERSATION_LIST_QUERY, { const { loading, data, refetch, called } = useQuery(CONVERSATION_LIST_QUERY, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
skip: !chatVisible,
...(pollInterval > 0 ? { pollInterval } : {}), ...(pollInterval > 0 ? { pollInterval } : {}),
}); });
@@ -57,12 +68,14 @@ export function ChatPopupComponent({
if (called && chatVisible) refetch(); if (called && chatVisible) refetch();
}, [chatVisible, called, refetch]); }, [chatVisible, called, refetch]);
const unreadCount = data // const unreadCount = data
? data.conversations.reduce( // ? data.conversations.reduce(
(acc, val) => val.messages_aggregate.aggregate.count + acc, // (acc, val) => val.messages_aggregate.aggregate.count + acc,
0 // 0
) // )
: 0; // : 0;
const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0;
return ( return (
<Badge count={unreadCount}> <Badge count={unreadCount}>

View File

@@ -4,7 +4,7 @@ import moment from "moment";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import ContractLicenseDecodeButton from "../contract-license-decode-button/contract-license-decode-button.component"; //import ContractLicenseDecodeButton from "../contract-license-decode-button/contract-license-decode-button.component";
import ContractStatusSelector from "../contract-status-select/contract-status-select.component"; import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component"; import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component"; import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
@@ -165,7 +165,9 @@ export default function ContractFormComponent({
/> />
</div> </div>
)} )}
<ContractLicenseDecodeButton form={form} /> {
//<ContractLicenseDecodeButton form={form} />
}
</Space> </Space>
</div> </div>
<LayoutFormRow header={t("contracts.labels.driverinformation")}> <LayoutFormRow header={t("contracts.labels.driverinformation")}>

View File

@@ -70,6 +70,8 @@ const mapDispatchToProps = (dispatch) => ({
setReportCenterContext: (context) => setReportCenterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "reportCenter" })), dispatch(setModalContext({ context: context, modal: "reportCenter" })),
signOutStart: () => dispatch(signOutStart()), signOutStart: () => dispatch(signOutStart()),
setCardPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
}); });
function Header({ function Header({
@@ -83,6 +85,7 @@ function Header({
setPaymentContext, setPaymentContext,
setReportCenterContext, setReportCenterContext,
recentItems, recentItems,
setCardPaymentContext,
}) { }) {
const { Simple_Inventory } = useTreatments( const { Simple_Inventory } = useTreatments(
["Simple_Inventory"], ["Simple_Inventory"],
@@ -240,6 +243,19 @@ function Header({
> >
{t("menus.header.enterpayment")} {t("menus.header.enterpayment")}
</Menu.Item> </Menu.Item>
{/* TODO: Enter Card Payment */}
<Menu.Item
key="entercardpayments"
onClick={() => {
setCardPaymentContext({
actions: {},
context: null,
});
}}
icon={<Icon component={FaCreditCard} />}
>
{t("menus.header.entercardpayment")}
</Menu.Item>
<Menu.Divider key="div5" /> <Menu.Divider key="div5" />
<Menu.Item key="timetickets" icon={<FieldTimeOutlined />}> <Menu.Item key="timetickets" icon={<FieldTimeOutlined />}>
<Link to="/manage/timetickets"> <Link to="/manage/timetickets">

View File

@@ -3,9 +3,11 @@ import {
Button, Button,
Divider, Divider,
Dropdown, Dropdown,
Form,
Menu, Menu,
notification, notification,
Popover, Popover,
Select,
Space, Space,
} from "antd"; } from "antd";
import parsePhoneNumber from "libphonenumber-js"; import parsePhoneNumber from "libphonenumber-js";
@@ -59,7 +61,10 @@ export function ScheduleEventComponent({
const blockContent = ( const blockContent = (
<div> <div>
<Button onClick={() => handleCancel(event.id)} disabled={event.arrived}> <Button
onClick={() => handleCancel({ id: event.id })}
disabled={event.arrived}
>
{t("appointments.actions.cancel")} {t("appointments.actions.cancel")}
</Button> </Button>
</div> </div>
@@ -203,10 +208,46 @@ export function ScheduleEventComponent({
<Button>{t("appointments.actions.sendreminder")}</Button> <Button>{t("appointments.actions.sendreminder")}</Button>
</Dropdown> </Dropdown>
) : null} ) : null}
<Popover
<Button onClick={() => handleCancel(event.id)} disabled={event.arrived}> trigger="click"
{t("appointments.actions.cancel")} disabled={event.arrived}
</Button> content={
<Form
layout="vertical"
onFinish={({ lost_sale_reason }) => {
handleCancel({ id: event.id, lost_sale_reason });
}}
>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr,
}))}
/>
</Form.Item>
<Button htmlType="submit">
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
<Button
// onClick={() => handleCancel(event.id)}
disabled={event.arrived}
>
{t("appointments.actions.cancel")}
</Button>
</Popover>
{event.isintake ? ( {event.isintake ? (
<Button <Button
disabled={event.arrived} disabled={event.arrived}
@@ -249,7 +290,7 @@ export function ScheduleEventComponent({
const RegularEvent = event.isintake ? ( const RegularEvent = event.isintake ? (
<Space <Space
wrap wrap
size='small' size="small"
style={{ style={{
backgroundColor: backgroundColor:
event.color && event.color.hex ? event.color.hex : event.color, event.color && event.color.hex ? event.color.hex : event.color,

View File

@@ -11,7 +11,7 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID); const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
const [updateJob] = useMutation(UPDATE_JOB); const [updateJob] = useMutation(UPDATE_JOB);
const handleCancel = async (id) => { const handleCancel = async ({ id, lost_sale_reason }) => {
logImEXEvent("schedule_cancel_appt"); logImEXEvent("schedule_cancel_appt");
const cancelAppt = await cancelAppointment({ const cancelAppt = await cancelAppointment({
@@ -38,7 +38,8 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
job: { job: {
date_scheduled: null, date_scheduled: null,
scheduled_in: null, scheduled_in: null,
scheduled_completion:null, scheduled_completion: null,
lost_sale_reason,
status: bodyshop.md_ro_statuses.default_imported, status: bodyshop.md_ro_statuses.default_imported,
}, },
}, },

View File

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

View File

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

View File

@@ -1,12 +1,12 @@
import { import {
DeleteFilled, DeleteFilled,
EditFilled,
FilterFilled, FilterFilled,
HomeOutlined,
MinusCircleTwoTone,
PlusCircleTwoTone,
SyncOutlined, SyncOutlined,
WarningFilled, WarningFilled,
EditFilled,
PlusCircleTwoTone,
MinusCircleTwoTone,
HomeOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { import {
@@ -29,7 +29,6 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectTechnician } from "../../redux/tech/tech.selectors";
import { onlyUnique } from "../../utils/arrayHelper"; import { onlyUnique } from "../../utils/arrayHelper";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component"; import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component";
import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component"; import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component";
@@ -38,13 +37,14 @@ import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-re
// import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container"; // import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container";
// import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container"; // import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
// import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container"; // import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.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 JobLinesExpander from "./job-lines-expander.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
import moment from "moment"; import moment from "moment";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component"; import { selectBodyshop } from "../../redux/user/user.selectors";
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobSendPartPriceChangeComponent from "../job-send-parts-price-change/job-send-parts-price-change.component";
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import JobLinesExpander from "./job-lines-expander.component";
import JobLinesPartPriceChange from "./job-lines-part-price-change.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -103,7 +103,12 @@ export function JobLinesComponent({
fixed: "left", fixed: "left",
key: "line_desc", key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
onCell: (record) => ({ className: record.manual_line && "job-line-manual" }), onCell: (record) => ({
className: record.manual_line && "job-line-manual",
style: {
...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {}),
},
}),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order, state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
ellipsis: true, ellipsis: true,
@@ -215,20 +220,7 @@ export function JobLinesComponent({
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order, state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => (
<JobLineConvertToLabor jobline={record} job={job}> <JobLinesPartPriceChange line={record} job={job} refetch={refetch} />
<CurrencyFormatter>
{record.db_ref === "900510" || record.db_ref === "900511"
? record.prt_dsmk_m
: record.act_price}
</CurrencyFormatter>
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
<span
style={{ marginLeft: ".2rem" }}
>{`(${record.prt_dsmk_p}%)`}</span>
) : (
<></>
)}
</JobLineConvertToLabor>
), ),
}, },
{ {
@@ -343,7 +335,7 @@ export function JobLinesComponent({
onClick={() => { onClick={() => {
setJobLineEditContext({ setJobLineEditContext({
actions: { refetch: refetch, submit: form && form.submit }, actions: { refetch: refetch, submit: form && form.submit },
context: record, context: { ...record, jobid: job.id },
}); });
}} }}
> >
@@ -443,15 +435,6 @@ export function JobLinesComponent({
technician technician
} }
onClick={() => { onClick={() => {
// setPartsOrderContext({
// actions: { refetch: refetch },
// context: {
// jobId: job.id,
// job: job,
// linesToOrder: selectedLines,
// },
// });
setBillEnterContext({ setBillEnterContext({
actions: { refetch: refetch }, actions: { refetch: refetch },
context: { context: {
@@ -558,6 +541,9 @@ export function JobLinesComponent({
> >
{t("joblines.actions.new")} {t("joblines.actions.new")}
</Button> </Button>
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<JobSendPartPriceChangeComponent job={job} />
)}
<JobCreateIOU job={job} selectedJobLines={selectedLines} /> <JobCreateIOU job={job} selectedJobLines={selectedLines} />
<Input.Search <Input.Search
placeholder={t("general.labels.search")} placeholder={t("general.labels.search")}

View File

@@ -14,8 +14,12 @@ import UndefinedToNull from "../../utils/undefinedtonull";
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component"; import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
import Axios from "axios"; import Axios from "axios";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import CriticalPartsScan from "../../utils/criticalPartsScan";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobLineEditModal: selectJobLineEditModal, jobLineEditModal: selectJobLineEditModal,
bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit")), toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit")),
@@ -24,7 +28,13 @@ const mapDispatchToProps = (dispatch) => ({
function JobLinesUpsertModalContainer({ function JobLinesUpsertModalContainer({
jobLineEditModal, jobLineEditModal,
toggleModalVisible, toggleModalVisible,
bodyshop,
}) { }) {
const { CriticalPartsScanning } = useTreatments(
["CriticalPartsScanning"],
{},
bodyshop.imexshopid
);
const { t } = useTranslation(); const { t } = useTranslation();
const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE); const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE);
const [updateJobLine] = useMutation(UPDATE_JOB_LINE); const [updateJobLine] = useMutation(UPDATE_JOB_LINE);
@@ -109,6 +119,9 @@ function JobLinesUpsertModalContainer({
} }
toggleModalVisible(); toggleModalVisible();
} }
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(jobLineEditModal.context.jobid);
}
setLoading(false); setLoading(false);
}; };

View File

@@ -14,6 +14,13 @@ import { alphaSort, dateSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import PaymentExpandedRowComponent from "../payment-expanded-row/payment-expanded-row.component";
import {
setMessage,
openChatByPhone,
} from "../../redux/messaging/messaging.actions";
import { parsePhoneNumber } from "libphonenumber-js";
import axios from "axios";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -23,13 +30,20 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPaymentContext: (context) => setPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "payment" })), dispatch(setModalContext({ context: context, modal: "payment" })),
setCardPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
setMessage: (text) => dispatch(setMessage(text)),
}); });
export function JobPayments({ export function JobPayments({
job, job,
jobRO, jobRO,
bodyshop, bodyshop,
setMessage,
openChatByPhone,
setPaymentContext, setPaymentContext,
setCardPaymentContext,
refetch, refetch,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -37,6 +51,8 @@ export function JobPayments({
sortedInfo: {}, sortedInfo: {},
filteredInfo: {}, filteredInfo: {},
}); });
const [generatingURL, setGeneratingtURL] = useState(false);
const columns = [ const columns = [
{ {
title: t("payments.fields.date"), title: t("payments.fields.date"),
@@ -149,6 +165,36 @@ export function JobPayments({
title={t("payments.labels.title")} title={t("payments.labels.title")}
extra={ extra={
<Space wrap> <Space wrap>
<Button
disabled={!job.converted}
loading={generatingURL}
onClick={async () => {
const p = parsePhoneNumber(job.ownr_ph1, "CA");
setGeneratingtURL(true);
const response = await axios.post(
"/intellipay/generate_payment_url",
{
amount: balance.getAmount(),
account: job.ro_number,
}
);
setGeneratingtURL(false);
openChatByPhone({
phone_num: p.formatInternational(),
jobid: job.id,
});
setMessage(
t("appointments.labels.smspaymentreminder", {
shopname: bodyshop.shopname,
amount: balance.toFormat(),
payment_link: response.data.shorUrl,
})
);
}}
>
{t("menus.header.paymentremindersms")}
</Button>
<Button <Button
disabled={!job.converted} disabled={!job.converted}
onClick={() => onClick={() =>
@@ -160,6 +206,16 @@ export function JobPayments({
> >
{t("menus.header.enterpayment")} {t("menus.header.enterpayment")}
</Button> </Button>
<Button
onClick={() =>
setCardPaymentContext({
actions: { refetch },
context: { jobid: job.id, balance },
})
}
>
{t("menus.header.entercardpayment")}
</Button>
<DataLabel <DataLabel
valueStyle={{ color: balance.getAmount() !== 0 ? "red" : "green" }} valueStyle={{ color: balance.getAmount() !== 0 ? "red" : "green" }}
label={t("payments.labels.balance")} label={t("payments.labels.balance")}
@@ -178,6 +234,11 @@ export function JobPayments({
scroll={{ scroll={{
x: true, x: true,
}} }}
expandable={{
expandedRowRender: (record) => (
<PaymentExpandedRowComponent record={record} />
),
}}
summary={() => ( summary={() => (
<> <>
<Table.Summary.Row> <Table.Summary.Row>

View File

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

View File

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

View File

@@ -5,9 +5,9 @@ import {
useMutation, useMutation,
useQuery, useQuery,
} from "@apollo/client"; } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Col, notification, Row } from "antd"; import { Col, notification, Row } from "antd";
import Axios from "axios"; import Axios from "axios";
import Dinero from "dinero.js";
import moment from "moment"; import moment from "moment";
import queryString from "query-string"; import queryString from "query-string";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
@@ -31,6 +31,7 @@ import {
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import confirmDialog from "../../utils/asyncConfirm"; import confirmDialog from "../../utils/asyncConfirm";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import CriticalPartsScan from "../../utils/criticalPartsScan";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component"; import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container"; import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
@@ -54,6 +55,11 @@ export function JobsAvailableContainer({
currentUser, currentUser,
insertAuditTrail, insertAuditTrail,
}) { }) {
const { CriticalPartsScanning } = useTreatments(
["CriticalPartsScanning"],
{},
bodyshop.imexshopid
);
const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, { const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
@@ -107,14 +113,14 @@ export function JobsAvailableContainer({
//IO-539 Check for Parts Rate on PAL for SGI use case. //IO-539 Check for Parts Rate on PAL for SGI use case.
await CheckTaxRates(estData.est_data, bodyshop); await CheckTaxRates(estData.est_data, bodyshop);
// } // }
const newTotals = ( // const newTotals = (
await Axios.post("/job/totals", { // await Axios.post("/job/totals", {
job: { // job: {
...estData.est_data, // ...estData.est_data,
joblines: estData.est_data.joblines.data, // joblines: estData.est_data.joblines.data,
}, // },
}) // })
).data; // ).data;
let existingVehicles; let existingVehicles;
if (estData.est_data.v_vin) { if (estData.est_data.v_vin) {
@@ -129,9 +135,9 @@ export function JobsAvailableContainer({
const newJob = { const newJob = {
...estData.est_data, ...estData.est_data,
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), // clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"), // owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
job_totals: newTotals, // job_totals: newTotals,
date_open: moment(), date_open: moment(),
notes: { notes: {
data: { data: {
@@ -160,6 +166,13 @@ export function JobsAvailableContainer({
}, },
}) })
.then((r) => { .then((r) => {
Axios.post("/job/totalsssu", {
id: r.data.insert_jobs.returning[0].id,
});
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(r.data.insert_jobs.returning[0].id);
}
notification["success"]({ notification["success"]({
message: t("jobs.successes.created"), message: t("jobs.successes.created"),
onClick: () => { onClick: () => {
@@ -210,6 +223,7 @@ export function JobsAvailableContainer({
let supp = replaceEmpty({ ...estData.est_data }); let supp = replaceEmpty({ ...estData.est_data });
//IO-539 Check for Parts Rate on PAL for SGI use case. //IO-539 Check for Parts Rate on PAL for SGI use case.
await CheckTaxRates(supp, bodyshop); await CheckTaxRates(supp, bodyshop);
await ResolveCCCLineIssues(supp, bodyshop);
delete supp.owner; delete supp.owner;
delete supp.vehicle; delete supp.vehicle;
@@ -246,7 +260,9 @@ export function JobsAvailableContainer({
}, },
}, },
}); });
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id);
}
if (updateResult.errors) { if (updateResult.errors) {
//error while inserting //error while inserting
notification["error"]({ notification["error"]({
@@ -498,20 +514,20 @@ async function CheckTaxRates(estData, bodyshop) {
//IO-1387 If a sublet line is NOT R&R, use the labor tax. If it is, use the sublet tax rate. //IO-1387 If a sublet line is NOT R&R, use the labor tax. If it is, use the sublet tax rate.
//Currently limited to SK shops only. //Currently limited to SK shops only.
if (bodyshop.region_config === "CA_SK") { if (bodyshop.region_config === "CA_SK") {
estData.joblines.data.forEach((jl, index) => { estData.joblines.data.forEach((jl, index) => {
if ( if (
(jl.part_type === "PASL" || jl.part_type === "PAS") && (jl.part_type === "PASL" || jl.part_type === "PAS") &&
jl.lbr_op !== "OP11" jl.lbr_op !== "OP11"
) { ) {
estData.joblines.data[index].tax_part = jl.lbr_tax; estData.joblines.data[index].tax_part = jl.lbr_tax;
} }
//Set markup lines and tax lines as taxable. //Set markup lines and tax lines as taxable.
//900510 is a mark up. 900510 is a discount. //900510 is a mark up. 900510 is a discount.
if (jl.db_ref === "900510") { if (jl.db_ref === "900510") {
estData.joblines.data[index].tax_part = true; estData.joblines.data[index].tax_part = true;
} }
}); });
} }
} }
@@ -544,8 +560,8 @@ async function ResolveCCCLineIssues(estData, bodyshop) {
); );
estData.joblines.data[nonRefLineIndex + 1] = { estData.joblines.data[nonRefLineIndex + 1] = {
...estData.joblines.data[nonRefLineIndex + 1], ...estData.joblines.data[nonRefLineIndex + 1],
act_price: null, act_price: 0,
db_price: null, db_price: 0,
prt_dsmk_p: 0, prt_dsmk_p: 0,
prt_dsmk_m: 0, prt_dsmk_m: 0,
}; };

View File

@@ -43,7 +43,7 @@ export function JobsConvertButton({
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const handleConvert = async ({ employee_csr, ...values }) => { const handleConvert = async ({ employee_csr, category, ...values }) => {
if (parentFormIsFieldsTouched()) { if (parentFormIsFieldsTouched()) {
alert(t("jobs.labels.savebeforeconversion")); alert(t("jobs.labels.savebeforeconversion"));
return; return;
@@ -55,6 +55,7 @@ export function JobsConvertButton({
job: { job: {
converted: true, converted: true,
...(bodyshop.enforce_conversion_csr ? { employee_csr } : {}), ...(bodyshop.enforce_conversion_csr ? { employee_csr } : {}),
...(bodyshop.enforce_conversion_category ? { category } : {}),
...values, ...values,
}, },
}, },
@@ -94,6 +95,7 @@ export function JobsConvertButton({
driveable: true, driveable: true,
towin: false, towin: false,
employee_csr: job.employee_csr, employee_csr: job.employee_csr,
category: job.category,
}} }}
> >
<Form.Item <Form.Item
@@ -197,6 +199,26 @@ export function JobsConvertButton({
</Select> </Select>
</Form.Item> </Form.Item>
)} )}
{bodyshop.enforce_conversion_category && (
<Form.Item
name={"category"}
label={t("jobs.fields.category")}
rules={[
{
required: bodyshop.enforce_conversion_category,
//message: t("general.validation.required"),
},
]}
>
<Select allowClear>
{bodyshop.md_categories.map((s) => (
<Select.Option key={s} value={s}>
{s}
</Select.Option>
))}
</Select>
</Form.Item>
)}
<Form.Item <Form.Item
label={t("jobs.fields.ca_gst_registrant")} label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant" name="ca_gst_registrant"

View File

@@ -9,7 +9,11 @@ const colSpan = {
lg: { span: 12 }, lg: { span: 12 },
}; };
export default function JobsCreateVehicleInfoComponent({ loading, vehicles }) { export default function JobsCreateVehicleInfoComponent({
loading,
vehicles,
form,
}) {
const [state, setState] = useContext(JobCreateContext); const [state, setState] = useContext(JobCreateContext);
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@@ -58,7 +62,7 @@ export default function JobsCreateVehicleInfoComponent({ loading, vehicles }) {
/> />
</Col> </Col>
<Col {...colSpan}> <Col {...colSpan}>
<JobsCreateVehicleInfoNewComponent /> <JobsCreateVehicleInfoNewComponent form={form}/>
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@@ -20,6 +20,7 @@ export default function JobsCreateVehicleInfoContainer({ form }) {
<JobsCreateVehicleInfoComponent <JobsCreateVehicleInfoComponent
loading={loading} loading={loading}
vehicles={data ? data.search_vehicles : null} vehicles={data ? data.search_vehicles : null}
form={form}
/> />
); );
} }

View File

@@ -4,8 +4,9 @@ import { useTranslation } from "react-i18next";
import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import JobsCreateVehicleInfoPredefined from "./jobs-create-vehicle-info.predefined.component";
export default function JobsCreateVehicleInfoNewComponent() { export default function JobsCreateVehicleInfoNewComponent({ form }) {
const [state] = useContext(JobCreateContext); const [state] = useContext(JobCreateContext);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -25,7 +26,7 @@ export default function JobsCreateVehicleInfoNewComponent() {
<Input disabled={!state.vehicle.new} /> <Input disabled={!state.vehicle.new} />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow grow> <LayoutFormRow grow noDivider>
<Form.Item <Form.Item
label={t("vehicles.fields.v_color")} label={t("vehicles.fields.v_color")}
name={["vehicle", "data", "v_color"]} name={["vehicle", "data", "v_color"]}
@@ -52,8 +53,9 @@ export default function JobsCreateVehicleInfoNewComponent() {
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow grow> <LayoutFormRow grow noDivider>
<Form.Item <Form.Item
span={10}
label={t("vehicles.fields.v_make_desc")} label={t("vehicles.fields.v_make_desc")}
name={["vehicle", "data", "v_make_desc"]} name={["vehicle", "data", "v_make_desc"]}
rules={[ rules={[
@@ -66,6 +68,7 @@ export default function JobsCreateVehicleInfoNewComponent() {
<Input disabled={!state.vehicle.new} /> <Input disabled={!state.vehicle.new} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
span={11}
label={t("vehicles.fields.v_model_desc")} label={t("vehicles.fields.v_model_desc")}
name={["vehicle", "data", "v_model_desc"]} name={["vehicle", "data", "v_model_desc"]}
rules={[ rules={[
@@ -77,6 +80,11 @@ export default function JobsCreateVehicleInfoNewComponent() {
> >
<Input disabled={!state.vehicle.new} /> <Input disabled={!state.vehicle.new} />
</Form.Item> </Form.Item>
<JobsCreateVehicleInfoPredefined
disabled={!state.vehicle.new}
form={form}
span={1}
/>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow header={t("vehicles.forms.registration")} grow> <LayoutFormRow header={t("vehicles.forms.registration")} grow>

View File

@@ -0,0 +1,81 @@
import { PlusOutlined, SearchOutlined } from "@ant-design/icons";
import { Button, Input, Popover, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import PredefinedVehicles from "./predefined-vehicles.js";
export default function JobsCreateVehicleInfoPredefined({ disabled, form }) {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const { t } = useTranslation();
const handleOpenChange = (newOpen) => {
setOpen(newOpen);
setSearch("");
};
const filteredPredefinedVehicles =
search === ""
? PredefinedVehicles
: PredefinedVehicles.filter(
(v) =>
v.make.toLowerCase().includes(search.toLowerCase()) ||
v.model.toLowerCase().includes(search.toLowerCase())
);
const popContent = () => (
<div>
<Table
size="small"
title={() => <Input.Search onSearch={(value) => setSearch(value)} />}
dataSource={filteredPredefinedVehicles}
columns={[
{
dataIndex: "make",
key: "make",
title: t("vehicles.fields.v_make_desc"),
},
{
dataIndex: "model",
key: "model",
title: t("vehicles.fields.v_model_desc"),
},
{
dataIndex: "select",
key: "select",
title: t("general.labels.actions"),
render: (value, record) => (
<Button
disabled={disabled}
onClick={() => {
form.setFieldsValue({
vehicle: {
data: {
v_make_desc: record.make,
v_model_desc: record.model,
},
},
});
setOpen(false);
setSearch("");
}}
>
<PlusOutlined />
</Button>
),
},
]}
/>
</div>
);
return (
<Popover
content={popContent}
trigger="click"
open={open}
placement="left"
onOpenChange={handleOpenChange}
destroyTooltipOnHide
>
<SearchOutlined style={{ cursor: "pointer" }} />
</Popover>
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -289,6 +289,12 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
> >
<Input disabled={jobRO} /> <Input disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item
label={t("jobs.fields.lost_sale_reason")}
name="lost_sale_reason"
>
<Input disabled={jobRO} allowClear />
</Form.Item>
</FormRow> </FormRow>
</div> </div>
); );

View File

@@ -1,6 +1,15 @@
import { DownCircleFilled } from "@ant-design/icons"; import { DownCircleFilled } from "@ant-design/icons";
import { useApolloClient, useMutation } from "@apollo/client"; import { useApolloClient, useMutation } from "@apollo/client";
import { Button, Dropdown, Menu, notification, Popconfirm } from "antd"; import {
Button,
Dropdown,
Form,
Menu,
notification,
Popconfirm,
Popover,
Select,
} from "antd";
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -38,6 +47,8 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setModalContext({ context: context, modal: "jobCosting" })), dispatch(setModalContext({ context: context, modal: "jobCosting" })),
setTimeTicketContext: (context) => setTimeTicketContext: (context) =>
dispatch(setModalContext({ context: context, modal: "timeTicket" })), dispatch(setModalContext({ context: context, modal: "timeTicket" })),
setCardPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
}); });
export function JobsDetailHeaderActions({ export function JobsDetailHeaderActions({
@@ -51,6 +62,7 @@ export function JobsDetailHeaderActions({
setJobCostingContext, setJobCostingContext,
jobRO, jobRO,
setTimeTicketContext, setTimeTicketContext,
setCardPaymentContext,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const client = useApolloClient(); const client = useApolloClient();
@@ -127,35 +139,63 @@ export function JobsDetailHeaderActions({
<Menu.Item <Menu.Item
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled} disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
> >
<Popconfirm <Popover
title={t("general.labels.areyousure")} trigger="click"
okText="Yes"
cancelText="No"
onClick={(e) => e.stopPropagation()}
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled} disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
onConfirm={async () => { content={
const jobUpdate = await cancelAllAppointments({ <Form
variables: { layout="vertical"
jobid: job.id, onFinish={async ({ lost_sale_reason }) => {
job: { const jobUpdate = await cancelAllAppointments({
date_scheduled: null, variables: {
scheduled_in: null, jobid: job.id,
scheduled_completion: null, job: {
status: bodyshop.md_ro_statuses.default_imported, date_scheduled: null,
}, scheduled_in: null,
}, scheduled_completion: null,
}); lost_sale_reason,
if (!jobUpdate.errors) { status: bodyshop.md_ro_statuses.default_imported,
notification["success"]({ },
message: t("appointments.successes.canceled"), },
}); });
return; if (!jobUpdate.errors) {
} notification["success"]({
}} message: t("appointments.successes.canceled"),
getPopupContainer={(trigger) => trigger.parentNode} });
return;
}
}}
>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr,
}))}
/>
</Form.Item>
<Button
htmlType="submit"
disabled={
job.status !== bodyshop.md_ro_statuses.default_scheduled
}
>
{t("appointments.actions.cancel")}
</Button>
</Form>
}
> >
{t("menus.jobsactions.cancelallappointments")} {t("menus.jobsactions.cancelallappointments")}
</Popconfirm> </Popover>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
disabled={ disabled={
@@ -221,6 +261,18 @@ export function JobsDetailHeaderActions({
> >
{t("menus.header.enterpayment")} {t("menus.header.enterpayment")}
</Menu.Item> </Menu.Item>
<Menu.Item
key="entercardpayments"
disabled={!job.converted}
onClick={() => {
setCardPaymentContext({
actions: {},
context: { jobid: job.id },
});
}}
>
{t("menus.header.entercardpayment")}
</Menu.Item>
<Menu.Item key="cccontract" disabled={jobRO || !job.converted}> <Menu.Item key="cccontract" disabled={jobRO || !job.converted}>
<Link <Link
to={{ to={{

View File

@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import cleanAxios from "../../utils/CleanAxios"; import cleanAxios from "../../utils/CleanAxios";
import formatBytes from "../../utils/formatbytes"; import formatBytes from "../../utils/formatbytes";
import yauzl from "yauzl"; //import yauzl from "yauzl";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useTreatments } from "@splitsoftware/splitio-react";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -69,44 +69,44 @@ export function JobsDocumentsDownloadButton({
setDownload(null); setDownload(null);
if (Direct_Media_Download.treatment === "on") { if (Direct_Media_Download.treatment === "on") {
try { try {
const parentDir = await window.showDirectoryPicker({ // const parentDir = await window.showDirectoryPicker({
id: "media", // id: "media",
startIn: "downloads", // startIn: "downloads",
}); // });
const directory = await parentDir.getDirectoryHandle(identifier, { // const directory = await parentDir.getDirectoryHandle(identifier, {
create: true, // create: true,
}); // });
yauzl.fromBuffer( // yauzl.fromBuffer(
Buffer.from(theDownloadedZip.data), // Buffer.from(theDownloadedZip.data),
{}, // {},
(err, zipFile) => { // (err, zipFile) => {
if (err) throw err; // if (err) throw err;
zipFile.on("entry", (entry) => { // zipFile.on("entry", (entry) => {
zipFile.openReadStream(entry, async (readErr, readStream) => { // zipFile.openReadStream(entry, async (readErr, readStream) => {
if (readErr) { // if (readErr) {
zipFile.close(); // zipFile.close();
throw readErr; // throw readErr;
} // }
if (err) throw err; // if (err) throw err;
let fileSystemHandle = await directory.getFileHandle( // let fileSystemHandle = await directory.getFileHandle(
entry.fileName, // entry.fileName,
{ // {
create: true, // create: true,
} // }
); // );
const writable = await fileSystemHandle.createWritable(); // const writable = await fileSystemHandle.createWritable();
readStream.on("data", async function (chunk) { // readStream.on("data", async function (chunk) {
await writable.write(chunk); // await writable.write(chunk);
}); // });
readStream.on("end", async function () { // readStream.on("end", async function () {
await writable.close(); // await writable.close();
}); // });
}); // });
}); // });
} // }
); // );
} catch (e) { } catch (e) {
console.log(e); console.log(e);
standardMediaDownload(theDownloadedZip.data); standardMediaDownload(theDownloadedZip.data);

View File

@@ -260,6 +260,19 @@ export function JobsList({ bodyshop }) {
dataIndex: "ins_co_nm", dataIndex: "ins_co_nm",
key: "ins_co_nm", key: "ins_co_nm",
ellipsis: true, ellipsis: true,
filters:
(jobs &&
jobs
.map((j) => j.ins_co_nm)
.filter(onlyUnique)
.map((s) => {
return {
text: s,
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.ins_co_nm),
responsive: ["md"], responsive: ["md"],
}, },
{ {

View File

@@ -272,6 +272,19 @@ export function JobsReadyList({ bodyshop }) {
dataIndex: "ins_co_nm", dataIndex: "ins_co_nm",
key: "ins_co_nm", key: "ins_co_nm",
ellipsis: true, ellipsis: true,
filters:
(jobs &&
jobs
.map((j) => j.ins_co_nm)
.filter(onlyUnique)
.map((s) => {
return {
text: s,
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.ins_co_nm),
responsive: ["md"], responsive: ["md"],
}, },
{ {

View File

@@ -1,15 +1,40 @@
import { Button, Form, notification, PageHeader } from "antd"; import { Button, Form, notification, PageHeader, Popconfirm } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { UPDATE_OWNER } from "../../graphql/owners.queries"; import { DELETE_OWNER, UPDATE_OWNER } from "../../graphql/owners.queries";
import OwnerDetailFormComponent from "./owner-detail-form.component"; import OwnerDetailFormComponent from "./owner-detail-form.component";
function OwnerDetailFormContainer({ owner, refetch }) { function OwnerDetailFormContainer({ owner, refetch }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const history = useHistory();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [updateOwner] = useMutation(UPDATE_OWNER); const [updateOwner] = useMutation(UPDATE_OWNER);
const [deleteOwner] = useMutation(DELETE_OWNER);
const handleDelete = async () => {
setLoading(true);
const result = await deleteOwner({
variables: { id: owner.id },
});
console.log(result);
if (result.errors) {
notification["error"]({
message: t("owners.errors.deleting", {
error: JSON.stringify(result.errors),
}),
});
setLoading(false);
} else {
notification["success"]({
message: t("owners.successes.delete"),
});
setLoading(false);
history.push(`/manage/owners`);
}
};
const handleFinish = async (values) => { const handleFinish = async (values) => {
setLoading(true); setLoading(true);
@@ -41,15 +66,29 @@ function OwnerDetailFormContainer({ owner, refetch }) {
<> <>
<PageHeader <PageHeader
title={t("menus.header.owners")} title={t("menus.header.owners")}
extra={ extra={[
<Popconfirm
trigger="click"
onConfirm={handleDelete}
disabled={owner.jobs.length !== 0}
title={t("owners.labels.deleteconfirm")}
>
<Button
type="danger"
loading={loading}
disabled={owner.jobs.length !== 0}
>
{t("general.actions.delete")}
</Button>
</Popconfirm>,
<Button <Button
type="primary" type="primary"
loading={loading} loading={loading}
onClick={() => form.submit()} onClick={() => form.submit()}
> >
{t("general.actions.save")} {t("general.actions.save")}
</Button> </Button>,
} ]}
/> />
<Form <Form
form={form} form={form}

View File

@@ -0,0 +1,171 @@
import React, { useState } from "react";
import { useMutation, useQuery } from "@apollo/client";
import {
GET_REFUNDABLE_AMOUNT_BY_JOBID,
INSERT_PAYMENT_RESPONSE,
QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID,
} from "../../graphql/payment_response.queries";
import { Button, Descriptions, InputNumber, Modal, notification } from "antd";
import moment from "moment";
import axios from "axios";
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
import { useTranslation } from "react-i18next";
const { confirm } = Modal;
const openNotificationWithIcon = (type, t) => {
notification[type]({
message: t("job_payments.notifications.error.title"),
description: t("job_payments.notifications.error.description"),
});
};
const PaymentExpandedRowComponent = ({ record }) => {
const [refundAmount, setRefundAmount] = useState(0);
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
const { t } = useTranslation();
const { loading, error, data } = useQuery(
QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID,
{
variables: {
paymentid: record.id,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
},
}
);
const { data: refundable_amount, refetch } = useQuery(
GET_REFUNDABLE_AMOUNT_BY_JOBID,
{
variables: {
jobid: record.jobid,
},
}
);
const insertPayments = async (payment_response, refund_response) => {
await insertPayment({
variables: {
paymentInput: {
amount: -refund_response.data.amount,
transactionid: payment_response.response.receiptelements.transid,
payer: record.payer,
type: "Refund",
jobid: payment_response.jobid,
date: moment(Date.now()),
},
},
update(cache, { data }) {
cache.modify({
id: cache.identify({
id: payment_response.jobid,
__typename: "jobs",
}),
fields: {
payments(payments) {
return [...data.insert_payments.returning, ...payments];
},
},
});
},
});
await insertPaymentResponse({
variables: {
paymentResponse: {
amount: -refund_response.data.amount,
bodyshopid: payment_response.bodyshopid,
paymentid: payment_response.paymentid,
jobid: payment_response.jobid,
declinereason: "Refund",
ext_paymentid: payment_response.ext_paymentid,
successful: true,
response: refund_response.data,
},
},
});
};
const showConfirm = (payment_response) => {
confirm({
title: "Do you want to refund payment?",
content:
"The payment will be refunded. Click OK to confirm and Cancel to dismiss.",
async onOk() {
const refundResponse = await axios.post("/intellipay/payment_refund", {
amount: refundAmount,
paymentid: payment_response.ext_paymentid,
});
if (refundResponse.data.status < 0) {
openNotificationWithIcon("error", t);
return;
}
insertPayments(payment_response, refundResponse);
// refetch refundable amount
refetch();
},
onCancel() {},
});
};
if (loading) return null;
if (error) return <p>Error loading data. Please Reload</p>;
const payment_response = data.payment_response[0];
const max_refundable_amount =
refundable_amount?.payment_response_aggregate.aggregate.sum.amount;
return (
<div>
<Descriptions
title={t("job_payments.titles.descriptions")}
contentStyle={{ fontWeight: "600" }}
column={4}
>
<Descriptions.Item label={t("job_payments.titles.payer")}>
{record.payer}
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.payername")}>
{payment_response?.response?.nameOnCard ?? ""}
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.amount")}>
{record.amount}
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.dateOfPayment")}>
{moment(record.created_at).format("YYYY-MM-DD HH:mm:ss")}
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.transactionid")}>
{record.transactionid}
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.paymentid")}>
{payment_response?.response?.paymentreferenceid ?? ""}
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.paymenttype")}>
{record.type}
</Descriptions.Item>
{payment_response && (
<Descriptions.Item label={t("job_payments.titles.refundamount")}>
<InputNumber
onChange={setRefundAmount}
max={max_refundable_amount}
min={0}
/>
<Button onClick={() => showConfirm(payment_response)}>
{t("job_payments.buttons.refundpayment")}
</Button>
</Descriptions.Item>
)}
</Descriptions>
</div>
);
};
export default PaymentExpandedRowComponent;

View File

@@ -7,14 +7,14 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { import {
INSERT_NEW_PAYMENT, INSERT_NEW_PAYMENT,
UPDATE_PAYMENT UPDATE_PAYMENT,
} from "../../graphql/payments.queries"; } from "../../graphql/payments.queries";
import { setEmailOptions } from "../../redux/email/email.actions"; import { setEmailOptions } from "../../redux/email/email.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectPayment } from "../../redux/modals/modals.selectors"; import { selectPayment } from "../../redux/modals/modals.selectors";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate"; import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
@@ -124,7 +124,11 @@ function PaymentModalContainer({
}; };
useEffect(() => { useEffect(() => {
if (visible) form.resetFields(); if (visible) {
form.resetFields();
form.resetFields();
form.setFieldsValue(context);
}
}, [visible, form, context]); }, [visible, form, context]);
useEffect(() => { useEffect(() => {
@@ -139,6 +143,7 @@ function PaymentModalContainer({
: t("payments.labels.edit") : t("payments.labels.edit")
} }
visible={visible} visible={visible}
destroyOnClose
okText={t("general.actions.save")} okText={t("general.actions.save")}
onOk={() => form.submit()} onOk={() => form.submit()}
width="50%" width="50%"

View File

@@ -55,6 +55,7 @@ export function ProductionListTable({
const assoc = bodyshop.associations.find( const assoc = bodyshop.associations.find(
(a) => a.useremail === currentUser.email (a) => a.useremail === currentUser.email
); );
if (assoc) { if (assoc) {
await updateDefaultProdView({ await updateDefaultProdView({
variables: { assocId: assoc.id, view: value }, variables: { assocId: assoc.id, view: value },

View File

@@ -39,7 +39,7 @@ export default function ProfileShopsComponent({
), ),
}, },
]; ];
console.log("🚀 ~ file: profile-shops.component.jsx:45 ~ data", data);
const filteredData = const filteredData =
search === "" search === ""
? data ? data

View File

@@ -3,7 +3,7 @@ import React from "react";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { import {
QUERY_ALL_ASSOCIATIONS, QUERY_ALL_ASSOCIATIONS,
UPDATE_ASSOCIATION, UPDATE_ACTIVE_ASSOCIATION,
} from "../../graphql/associations.queries"; } from "../../graphql/associations.queries";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ProfileShopsComponent from "./profile-shops.component"; import ProfileShopsComponent from "./profile-shops.component";
@@ -13,9 +13,13 @@ import { getToken } from "firebase/messaging";
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,
selectCurrentUser,
} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -25,14 +29,18 @@ export default connect(
mapDispatchToProps mapDispatchToProps
)(ProfileShopsContainer); )(ProfileShopsContainer);
export function ProfileShopsContainer({ bodyshop }) { export function ProfileShopsContainer({ bodyshop, currentUser }) {
const { loading, error, data } = useQuery(QUERY_ALL_ASSOCIATIONS, { const { loading, error, data } = useQuery(QUERY_ALL_ASSOCIATIONS, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: {
email: currentUser.email,
},
skip: !currentUser,
}); });
const [updateAssocation] = useMutation(UPDATE_ASSOCIATION); const [updateActiveAssociation] = useMutation(UPDATE_ACTIVE_ASSOCIATION);
const updateActiveShop = async (activeShopId) => { const updateActiveShop = async (newActiveAssocId) => {
logImEXEvent("profile_change_active_shop"); logImEXEvent("profile_change_active_shop");
try { try {
@@ -46,16 +54,12 @@ export function ProfileShopsContainer({ bodyshop }) {
} catch (error) { } catch (error) {
console.log("No FCM token. Skipping unsubscribe."); console.log("No FCM token. Skipping unsubscribe.");
} }
await Promise.all(
data.associations.map(async (record) => { await updateActiveAssociation({
await updateAssocation({ variables: {
variables: { newActiveAssocId: newActiveAssocId,
assocId: record.id, },
assocActive: record.id === activeShopId ? true : false, });
},
});
})
);
//Force window refresh. //Force window refresh.

View File

@@ -1,5 +1,14 @@
import { SyncOutlined } from "@ant-design/icons"; import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Checkbox, Col, PageHeader, Row, Space } from "antd"; import {
Button,
Card,
Checkbox,
Col,
PageHeader,
Row,
Select,
Space,
} from "antd";
import { t } from "i18next"; import { t } from "i18next";
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import useLocalStorage from "../../utils/useLocalStorage"; import useLocalStorage from "../../utils/useLocalStorage";
@@ -9,22 +18,39 @@ import ScheduleModal from "../schedule-job-modal/schedule-job-modal.container";
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component"; import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
import ScheduleProductionList from "../schedule-production-list/schedule-production-list.component"; import ScheduleProductionList from "../schedule-production-list/schedule-production-list.component";
import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verify-integrity.component"; import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verify-integrity.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScheduleCalendarComponent);
export default function ScheduleCalendarComponent({ data, refetch }) { export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
const [filter, setFilter] = useLocalStorage("filter_events", { const [filter, setFilter] = useLocalStorage("filter_events", {
intake: true, intake: true,
manual: true, manual: true,
employeevacation: true, employeevacation: true,
ins_co_nm: null,
}); });
const filteredData = useMemo(() => { const filteredData = useMemo(() => {
return data.filter( return data.filter(
(d) => (d) =>
d.block || (d.block ||
(filter.intake && d.isintake) || (filter.intake && d.isintake) ||
(filter.manual && !d.isintake && d.block === false) || (filter.manual && !d.isintake && d.block === false) ||
(d.__typename === "employee_vacation" && (d.__typename === "employee_vacation" &&
filter.employeevacation && filter.employeevacation &&
!!d.employee) !!d.employee)) &&
(filter.ins_co_nm && filter.ins_co_nm.length > 0
? filter.ins_co_nm.includes(d.job?.ins_co_nm)
: true)
); );
}, [data, filter]); }, [data, filter]);
@@ -37,6 +63,21 @@ export default function ScheduleCalendarComponent({ data, refetch }) {
extra={ extra={
<Space wrap> <Space wrap>
<ScheduleAtsSummary appointments={filteredData} /> <ScheduleAtsSummary appointments={filteredData} />
<Select
style={{ minWidth: "15rem" }}
mode="multiple"
placeholder={t("schedule.labels.ins_co_nm_filter")}
allowClear
onClear={() => setFilter({ ...filter, ins_co_nm: [] })}
value={filter?.ins_co_nm ? filter.ins_co_nm : []}
onChange={(e) => {
setFilter({ ...filter, ins_co_nm: e });
}}
options={bodyshop.md_ins_cos.map((i) => ({
label: i.name,
value: i.name,
}))}
/>
<Checkbox <Checkbox
checked={filter?.intake} checked={filter?.intake}
onChange={(e) => { onChange={(e) => {

View File

@@ -148,6 +148,7 @@ export function ScheduleJobModalContainer({
date_scheduled: new Date(), date_scheduled: new Date(),
scheduled_in: values.start, scheduled_in: values.start,
scheduled_completion: values.scheduled_completion, scheduled_completion: values.scheduled_completion,
lost_sale_reason: null,
}, },
}, },
}); });

View File

@@ -1,3 +1,4 @@
import { useTreatments } from "@splitsoftware/splitio-react";
import { Button, Card, Tabs } from "antd"; import { Button, Card, Tabs } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -8,6 +9,7 @@ import ShopInfoGeneral from "./shop-info.general.component";
import ShopInfoIntakeChecklistComponent from "./shop-info.intake.component"; import ShopInfoIntakeChecklistComponent from "./shop-info.intake.component";
import ShopInfoLaborRates from "./shop-info.laborrates.component"; import ShopInfoLaborRates from "./shop-info.laborrates.component";
import ShopInfoOrderStatusComponent from "./shop-info.orderstatus.component"; import ShopInfoOrderStatusComponent from "./shop-info.orderstatus.component";
import ShopInfoPartsScan from "./shop-info.parts-scan";
import ShopInfoRbacComponent from "./shop-info.rbac.component"; import ShopInfoRbacComponent from "./shop-info.rbac.component";
import ShopInfoResponsibilityCenterComponent from "./shop-info.responsibilitycenters.component"; import ShopInfoResponsibilityCenterComponent from "./shop-info.responsibilitycenters.component";
import ShopInfoROStatusComponent from "./shop-info.rostatus.component"; import ShopInfoROStatusComponent from "./shop-info.rostatus.component";
@@ -23,6 +25,11 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoComponent); export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoComponent);
export function ShopInfoComponent({ bodyshop, form, saveLoading }) { export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
const { CriticalPartsScanning } = useTreatments(
["CriticalPartsScanning"],
{},
bodyshop.imexshopid
);
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Card <Card
@@ -71,6 +78,11 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
<Tabs.TabPane key="laborrates" tab={t("bodyshop.labels.laborrates")}> <Tabs.TabPane key="laborrates" tab={t("bodyshop.labels.laborrates")}>
<ShopInfoLaborRates form={form} /> <ShopInfoLaborRates form={form} />
</Tabs.TabPane> </Tabs.TabPane>
{CriticalPartsScanning.treatment === "on" && (
<Tabs.TabPane key="partsscan" tab={t("bodyshop.labels.partsscan")}>
<ShopInfoPartsScan form={form} />
</Tabs.TabPane>
)}
</Tabs> </Tabs>
</Card> </Card>
); );

View File

@@ -473,6 +473,13 @@ export default function ShopInfoGeneral({ form }) {
> >
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item
name={["enforce_conversion_category"]}
label={t("bodyshop.fields.enforce_conversion_category")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item <Form.Item
name={["target_touchtime"]} name={["target_touchtime"]}
label={t("bodyshop.fields.target_touchtime")} label={t("bodyshop.fields.target_touchtime")}

View File

@@ -0,0 +1,81 @@
import { DeleteFilled } from "@ant-design/icons";
import { Button, Form, Input, Space } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
export default function ShopInfoPartsScan({ form }) {
const { t } = useTranslation();
return (
<div>
<LayoutFormRow header={t("bodyshop.labels.md_parts_scan")}>
<Form.List name={["md_parts_scan"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<Form.Item
label={t("bodyshop.fields.md_parts_scan.expression")}
key={`${index}expression`}
name={[field.name, "expression"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.md_parts_scan.flags")}
key={`${index}flags`}
name={[field.name, "flags"]}
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("bodyshop.actions.addpartsrule")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</LayoutFormRow>
</div>
);
}

View File

@@ -217,7 +217,9 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
{t("jobs.fields.ponumber")} {t("jobs.fields.ponumber")}
</Select.Option> </Select.Option>
<Select.Option value="account_number"> <Select.Option value="account_number">
{t("jobs.fields.dms.control_type.account_number")} {t(
"jobs.fields.dms.control_type.account_number"
)}
</Select.Option> </Select.Option>
</Select> </Select>
</Form.Item> </Form.Item>
@@ -423,6 +425,15 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<Input onBlur={handleBlur} /> <Input onBlur={handleBlur} />
</Form.Item> </Form.Item>
)} )}
{bodyshop.cdk_dealerid && (
<Form.Item
label={t("bodyshop.fields.dms.dms_control_override")}
key={`${index}dms_control_override`}
name={[field.name, "dms_control_override"]}
>
<Input onBlur={handleBlur} />
</Form.Item>
)}
<DeleteFilled <DeleteFilled
onClick={() => { onClick={() => {
@@ -546,6 +557,15 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<Input onBlur={handleBlur} /> <Input onBlur={handleBlur} />
</Form.Item> </Form.Item>
)} )}
{bodyshop.cdk_dealerid && (
<Form.Item
label={t("bodyshop.fields.dms.dms_control_override")}
key={`${index}dms_control_override`}
name={[field.name, "dms_control_override"]}
>
<Input onBlur={handleBlur} />
</Form.Item>
)}
<DeleteFilled <DeleteFilled
onClick={() => { onClick={() => {
remove(field.name); remove(field.name);

View File

@@ -76,6 +76,19 @@ export default function ShopInfoSchedulingComponent({ form }) {
> >
<InputNumber min={0} /> <InputNumber min={0} />
</Form.Item> </Form.Item>
<Form.Item
name={["md_lost_sale_reasons"]}
label={t("bodyshop.fields.md_lost_sale_reasons")}
rules={[
{
// required: true,
//message: t("general.validation.required"),
type: "array",
},
]}
>
<Select mode="tags" />
</Form.Item>
</LayoutFormRow> </LayoutFormRow>
<Divider orientation="left">{t("bodyshop.labels.workingdays")}</Divider> <Divider orientation="left">{t("bodyshop.labels.workingdays")}</Divider>
<Space wrap size="large"> <Space wrap size="large">

View File

@@ -42,7 +42,9 @@ export default function ShopUsersAuthEdit({ association }) {
</div> </div>
)} )}
{!visible && ( {!visible && (
<div style={{ cursor: "pointer" }} onClick={() => setVisible(true)}> <div
style={{ cursor: "pointer" }} //onClick={() => setVisible(true)}
>
{association.authlevel || t("general.labels.na")} {association.authlevel || t("general.labels.na")}
</div> </div>
)} )}

View File

@@ -82,9 +82,10 @@ export function TimeTicketModalComponent({
label={t("timetickets.fields.ro_number")} label={t("timetickets.fields.ro_number")}
rules={[ rules={[
{ {
required: required: !(
!form.getFieldValue("cost_center") === form.getFieldValue("cost_center") ===
"timetickets.labels.shift", "timetickets.labels.shift"
),
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
]} ]}

View File

@@ -1,16 +1,41 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Button, Form, notification, PageHeader } from "antd"; import { Button, Form, notification, PageHeader, Popconfirm } from "antd";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import VehicleDetailFormComponent from "./vehicle-detail-form.component"; import VehicleDetailFormComponent from "./vehicle-detail-form.component";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import moment from "moment"; import moment from "moment";
import { UPDATE_VEHICLE } from "../../graphql/vehicles.queries"; import { DELETE_VEHICLE, UPDATE_VEHICLE } from "../../graphql/vehicles.queries";
import { useHistory } from "react-router-dom";
function VehicleDetailFormContainer({ vehicle, refetch }) { function VehicleDetailFormContainer({ vehicle, refetch }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [updateVehicle] = useMutation(UPDATE_VEHICLE); const [updateVehicle] = useMutation(UPDATE_VEHICLE);
const [deleteVehicle] = useMutation(DELETE_VEHICLE);
const [form] = Form.useForm(); const [form] = Form.useForm();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const history = useHistory();
const handleDelete = async () => {
setLoading(true);
const result = await deleteVehicle({
variables: { id: vehicle.id },
});
console.log(result);
if (result.errors) {
notification["error"]({
message: t("vehicles.errors.deleting", {
error: JSON.stringify(result.errors),
}),
});
setLoading(false);
} else {
notification["success"]({
message: t("vehicles.successes.delete"),
});
setLoading(false);
history.push(`/manage/vehicles`);
}
};
const handleFinish = async (values) => { const handleFinish = async (values) => {
setLoading(true); setLoading(true);
@@ -40,15 +65,29 @@ function VehicleDetailFormContainer({ vehicle, refetch }) {
<> <>
<PageHeader <PageHeader
title={t("menus.header.vehicles")} title={t("menus.header.vehicles")}
extra={ extra={[
<Popconfirm
trigger="click"
onConfirm={handleDelete}
disabled={vehicle.jobs.length !== 0}
title={t("vehicles.labels.deleteconfirm")}
>
<Button
type="danger"
loading={loading}
disabled={vehicle.jobs.length !== 0}
>
{t("general.actions.delete")}
</Button>
</Popconfirm>,
<Button <Button
type="primary" type="primary"
loading={loading} loading={loading}
onClick={() => form.submit()} onClick={() => form.submit()}
> >
{t("general.actions.save")} {t("general.actions.save")}
</Button> </Button>,
} ]}
/> />
<Form <Form
onFinish={handleFinish} onFinish={handleFinish}

View File

@@ -268,6 +268,7 @@ export const CANCEL_APPOINTMENTS_BY_JOB_ID = gql`
scheduled_in scheduled_in
scheduled_completion scheduled_completion
status status
lost_sale_reason
} }
} }
`; `;

View File

@@ -1,8 +1,11 @@
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
export const QUERY_ALL_ASSOCIATIONS = gql` export const QUERY_ALL_ASSOCIATIONS = gql`
query QUERY_ALL_ASSOCIATIONS { query QUERY_ALL_ASSOCIATIONS($email: String) {
associations(order_by: { bodyshop: { shopname: asc } }) { associations(
where: { useremail: { _eq: $email } }
order_by: { bodyshop: { shopname: asc } }
) {
id id
active active
bodyshop { bodyshop {
@@ -27,6 +30,30 @@ export const UPDATE_ASSOCIATION = gql`
} }
} }
`; `;
export const UPDATE_ACTIVE_ASSOCIATION = gql`
mutation UPDATE_ACTIVE_ASSOCIATION($newActiveAssocId: uuid) {
nweActive: update_associations(
where: { id: { _eq: $newActiveAssocId } }
_set: { active: true }
) {
returning {
id
shopid
active
}
}
inactive: update_associations(
where: { id: { _neq: $newActiveAssocId } }
_set: { active: false }
) {
returning {
id
shopid
active
}
}
}
`;
export const UPDATE_ACTIVE_PROD_LIST_VIEW = gql` export const UPDATE_ACTIVE_PROD_LIST_VIEW = gql`
mutation UPDATE_ACTIVE_PROD_LIST_VIEW($assocId: uuid, $view: String) { mutation UPDATE_ACTIVE_PROD_LIST_VIEW($assocId: uuid, $view: String) {

View File

@@ -12,6 +12,7 @@ export const QUERY_BODYSHOP = gql`
query QUERY_BODYSHOP { query QUERY_BODYSHOP {
bodyshops(where: { associations: { active: { _eq: true } } }) { bodyshops(where: { associations: { active: { _eq: true } } }) {
associations { associations {
id
authlevel authlevel
useremail useremail
default_prod_list_view default_prod_list_view
@@ -112,6 +113,9 @@ export const QUERY_BODYSHOP = gql`
localmediaservernetwork localmediaservernetwork
localmediatoken localmediatoken
enforce_conversion_csr enforce_conversion_csr
md_lost_sale_reasons
md_parts_scan
enforce_conversion_category
employees { employees {
user_email user_email
id id
@@ -222,6 +226,9 @@ export const UPDATE_SHOP = gql`
localmediaservernetwork localmediaservernetwork
localmediatoken localmediatoken
enforce_conversion_csr enforce_conversion_csr
md_lost_sale_reasons
md_parts_scan
enforce_conversion_category
employees { employees {
id id
first_name first_name

View File

@@ -31,6 +31,18 @@ import { gql } from "@apollo/client";
// } // }
// `; // `;
export const UNREAD_CONVERSATION_COUNT = gql`
query UNREAD_CONVERSATION_COUNT {
messages_aggregate(
where: { read: { _eq: false }, isoutbound: { _eq: false } }
) {
aggregate {
count
}
}
}
`;
export const CONVERSATION_LIST_QUERY = gql` export const CONVERSATION_LIST_QUERY = gql`
query CONVERSATION_LIST_QUERY { query CONVERSATION_LIST_QUERY {
conversations( conversations(

View File

@@ -275,3 +275,14 @@ export const UPDATE_JOB_LINES_IOU = gql`
} }
} }
`; `;
export const UPDATE_LINE_PPC = gql`
mutation UPDATE_LINE_PPC($id: uuid!, $jobline: joblines_set_input) {
update_joblines_by_pk(pk_columns: { id: $id }, _set: $jobline) {
jobid
id
act_price_before_ppc
act_price
}
}
`;

View File

@@ -536,6 +536,7 @@ export const GET_JOB_BY_PK = gql`
driveable driveable
towin towin
loss_of_use loss_of_use
lost_sale_reason
vehicle { vehicle {
id id
plate_no plate_no
@@ -721,6 +722,8 @@ export const GET_JOB_BY_PK = gql`
ioucreated ioucreated
convertedtolbr convertedtolbr
ah_detail_line ah_detail_line
act_price_before_ppc
critical
billlines(limit: 1, order_by: { bill: { date: desc } }) { billlines(limit: 1, order_by: { bill: { date: desc } }) {
id id
quantity quantity
@@ -2060,6 +2063,7 @@ export const QUERY_JOB_EXPORT_DMS = gql`
ownr_fn ownr_fn
ownr_ln ownr_ln
ownr_co_nm ownr_co_nm
ins_co_nm
kmin kmin
kmout kmout
v_make_desc v_make_desc

View File

@@ -94,6 +94,14 @@ export const UPDATE_OWNER = gql`
} }
`; `;
export const DELETE_OWNER = gql`
mutation DELETE_OWNER($id: uuid!) {
delete_owners_by_pk(id: $id) {
id
}
}
`;
export const QUERY_ALL_OWNERS = gql` export const QUERY_ALL_OWNERS = gql`
query QUERY_ALL_OWNERS { query QUERY_ALL_OWNERS {
owners { owners {

View File

@@ -0,0 +1,55 @@
import { gql } from "@apollo/client";
export const INSERT_PAYMENT_RESPONSE = gql`
mutation INSERT_PAYMENT_RESPONSE(
$paymentResponse: [payment_response_insert_input!]!
) {
insert_payment_response(objects: $paymentResponse) {
returning {
id
}
}
}
`;
export const QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID = gql`
query QUERY_PAYMENT_RESPONSE_BY_PK($paymentid: uuid!) {
payment_response(where: { paymentid: { _eq: $paymentid } }) {
id
jobid
bodyshopid
paymentid
amount
declinereason
ext_paymentid
successful
response
}
}
`;
export const QUERY_RO_AND_OWNER_BY_JOB_PK = gql`
query QUERY_RO_AND_OWNER_BY_JOB_PK($jobid: uuid!) {
jobs_by_pk(id: $jobid) {
ro_number
owner {
ownr_fn
ownr_ln
ownr_ea
ownr_zip
}
}
}
`;
export const GET_REFUNDABLE_AMOUNT_BY_JOBID = gql`
query GET_REFUNDABLE_AMOUNT_BY_JOBID($jobid: uuid!) {
payment_response_aggregate(where: { jobid: { _eq: $jobid } }) {
aggregate {
sum {
amount
}
}
}
}
`;

View File

@@ -55,6 +55,14 @@ export const UPDATE_VEHICLE = gql`
} }
`; `;
export const DELETE_VEHICLE = gql`
mutation DELETE_VEHICLE($id: uuid!) {
delete_vehicles_by_pk(id: $id) {
id
}
}
`;
export const QUERY_ALL_VEHICLES = gql` export const QUERY_ALL_VEHICLES = gql`
query QUERY_ALL_VEHICLES { query QUERY_ALL_VEHICLES {
vehicles { vehicles {

View File

@@ -101,7 +101,10 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
]; ];
}); });
}); });
socket.on("connect_error", (err) => {
console.log(`connect_error due to ${err}`, err);
notification.error({ message: err.message });
});
socket.on("log-event", (payload) => { socket.on("log-event", (payload) => {
setLogs((logs) => { setLogs((logs) => {
return [...logs, payload]; return [...logs, payload];

View File

@@ -17,7 +17,7 @@ export default function JobsCreateComponent({ form }) {
const steps = [ const steps = [
{ {
title: t("jobs.labels.create.vehicleinfo"), title: t("jobs.labels.create.vehicleinfo"),
content: <JobsCreateVehicleInfoContainer />, content: <JobsCreateVehicleInfoContainer form={form} />,
validation: validation:
!!state.vehicle.new || !!state.vehicle.new ||
!!state.vehicle.selectedid || !!state.vehicle.selectedid ||

View File

@@ -16,7 +16,7 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component"; import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container"; import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component"; import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
import TestComponent from "../../components/_test/test.component"; import TestComponent from "../../components/_test/test.page";
import { requestForToken } from "../../firebase/firebase.utils"; import { requestForToken } from "../../firebase/firebase.utils";
import { import {
selectBodyshop, selectBodyshop,
@@ -31,6 +31,10 @@ const ManageRootPage = lazy(() =>
); );
const JobsPage = lazy(() => import("../jobs/jobs.page")); const JobsPage = lazy(() => import("../jobs/jobs.page"));
const CardPaymentModalContainer = lazy(() =>
import("../../components/card-payment-modal/card-payment-modal.container.")
);
const JobsDetailPage = lazy(() => const JobsDetailPage = lazy(() =>
import("../jobs-detail/jobs-detail.page.container") import("../jobs-detail/jobs-detail.page.container")
); );
@@ -195,6 +199,8 @@ export function Manage({ match, conflict, bodyshop }) {
> >
<PaymentModalContainer /> <PaymentModalContainer />
<CardPaymentModalContainer />
<BreadCrumbs /> <BreadCrumbs />
<BillEnterModalContainer /> <BillEnterModalContainer />
<JobCostingModal /> <JobCostingModal />

View File

@@ -25,6 +25,7 @@ const INITIAL_STATE = {
contractFinder: { ...baseModal }, contractFinder: { ...baseModal },
inventoryUpsert: { ...baseModal }, inventoryUpsert: { ...baseModal },
ca_bc_eftTableConvert: { ...baseModal }, ca_bc_eftTableConvert: { ...baseModal },
cardPayment: { ...baseModal },
}; };
const modalsReducer = (state = INITIAL_STATE, action) => { const modalsReducer = (state = INITIAL_STATE, action) => {

View File

@@ -79,3 +79,8 @@ export const selectCaBcEtfTableConvert = createSelector(
[selectModals], [selectModals],
(modals) => modals.ca_bc_eftTableConvert (modals) => modals.ca_bc_eftTableConvert
); );
export const selectCardPayment = createSelector(
[selectModals],
(modals) => modals.cardPayment
);

View File

@@ -230,6 +230,7 @@
"addapptcolor": "Add Appointment Color", "addapptcolor": "Add Appointment Color",
"addbucket": "Add Definition", "addbucket": "Add Definition",
"addpartslocation": "Add Parts Location", "addpartslocation": "Add Parts Location",
"addpartsrule": "Add Parts Scan Rule",
"addspeedprint": "Add Speed Print", "addspeedprint": "Add Speed Print",
"addtemplate": "Add Template", "addtemplate": "Add Template",
"newlaborrate": "New Labor Rate", "newlaborrate": "New Labor Rate",
@@ -270,6 +271,7 @@
"disablebillwip": "Disable bill WIP for A/P Posting", "disablebillwip": "Disable bill WIP for A/P Posting",
"disablecontactvehiclecreation": "Disable Contact & Vehicle Updates/Creation", "disablecontactvehiclecreation": "Disable Contact & Vehicle Updates/Creation",
"dms_acctnumber": "DMS Account #", "dms_acctnumber": "DMS Account #",
"dms_control_override": "Static Control # Override",
"dms_wip_acctnumber": "DMS W.I.P. Account #", "dms_wip_acctnumber": "DMS W.I.P. Account #",
"generic_customer_number": "Generic Customer Number", "generic_customer_number": "Generic Customer Number",
"itc_federal": "Federal Tax is ITC?", "itc_federal": "Federal Tax is ITC?",
@@ -281,6 +283,7 @@
}, },
"email": "General Shop Email", "email": "General Shop Email",
"enforce_class": "Enforce Class on Conversion?", "enforce_class": "Enforce Class on Conversion?",
"enforce_conversion_category": "Enforce Category on Conversion?",
"enforce_conversion_csr": "Enforce CSR on Conversion?", "enforce_conversion_csr": "Enforce CSR on Conversion?",
"enforce_referral": "Enforce Referrals", "enforce_referral": "Enforce Referrals",
"federal_tax_id": "Federal Tax ID (GST/HST)", "federal_tax_id": "Federal Tax ID (GST/HST)",
@@ -328,7 +331,12 @@
"zip": "Zip/Postal Code" "zip": "Zip/Postal Code"
}, },
"md_jobline_presets": "Jobline Presets", "md_jobline_presets": "Jobline Presets",
"md_lost_sale_reasons": "Lost Sale Reasons",
"md_parts_order_comment": "Parts Orders Comments", "md_parts_order_comment": "Parts Orders Comments",
"md_parts_scan": {
"expression": "RegEX Expression",
"flags": "Flags"
},
"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",
@@ -579,6 +587,7 @@
"notespresets": "Notes Presets", "notespresets": "Notes Presets",
"orderstatuses": "Order Statuses", "orderstatuses": "Order Statuses",
"partslocations": "Parts Locations", "partslocations": "Parts Locations",
"partsscan": "Critical Parts Scanning",
"printlater": "Print Later", "printlater": "Print Later",
"qbo": "Use QuickBooks Online?", "qbo": "Use QuickBooks Online?",
"qbo_departmentid": "QBO Department ID", "qbo_departmentid": "QBO Department ID",
@@ -1272,6 +1281,7 @@
"removefromproduction": "Remove from Production", "removefromproduction": "Remove from Production",
"schedule": "Schedule", "schedule": "Schedule",
"sendcsi": "Send CSI", "sendcsi": "Send CSI",
"sendpartspricechange": "Send Parts Price Change",
"sendtodms": "Send to DMS", "sendtodms": "Send to DMS",
"sync": "Sync", "sync": "Sync",
"uninvoice": "Uninvoice", "uninvoice": "Uninvoice",
@@ -1295,6 +1305,7 @@
"nojobselected": "No job is selected.", "nojobselected": "No job is selected.",
"noowner": "No owner associated.", "noowner": "No owner associated.",
"novehicle": "No vehicle associated.", "novehicle": "No vehicle associated.",
"partspricechange": "Error sending parts price change. {{error}}.",
"saving": "Error encountered while saving record.", "saving": "Error encountered while saving record.",
"scanimport": "Error importing job. {{message}}", "scanimport": "Error importing job. {{message}}",
"totalscalc": "Error while calculating new job totals.", "totalscalc": "Error while calculating new job totals.",
@@ -1446,6 +1457,7 @@
"loss_date": "Loss Date", "loss_date": "Loss Date",
"loss_desc": "Loss Description", "loss_desc": "Loss Description",
"loss_of_use": "Loss of Use", "loss_of_use": "Loss of Use",
"lost_sale_reason": "Lost Sale Reason",
"ma2s": "2 Stage Paint", "ma2s": "2 Stage Paint",
"ma3s": "3 Stage Pain", "ma3s": "3 Stage Pain",
"mabl": "MABL?", "mabl": "MABL?",
@@ -1562,6 +1574,7 @@
"scheddates": "Schedule Dates" "scheddates": "Schedule Dates"
}, },
"labels": { "labels": {
"act_price_ppc": "New Part Price",
"actual_completion_inferred": "$t(jobs.fields.actual_completion) inferred using $t(jobs.fields.scheduled_completion).", "actual_completion_inferred": "$t(jobs.fields.actual_completion) inferred using $t(jobs.fields.scheduled_completion).",
"actual_delivery_inferred": "$t(jobs.fields.actual_delivery) inferred using $t(jobs.fields.scheduled_delivery).", "actual_delivery_inferred": "$t(jobs.fields.actual_delivery) inferred using $t(jobs.fields.scheduled_delivery).",
"actual_in_inferred": "$t(jobs.fields.actual_in) inferred using $t(jobs.fields.scheduled_in).", "actual_in_inferred": "$t(jobs.fields.actual_in) inferred using $t(jobs.fields.scheduled_in).",
@@ -1698,6 +1711,7 @@
"partstotal": "This is the total of all parts and sublet amounts on the vehicle (some of these may require an in-house invoice).<br/>\nItems such as shop and paint materials, labor online lines, etc. are not included in this total.", "partstotal": "This is the total of all parts and sublet amounts on the vehicle (some of these may require an in-house invoice).<br/>\nItems such as shop and paint materials, labor online lines, etc. are not included in this total.",
"totalreturns": "The total <b>retail</b> amount of returns created for this job." "totalreturns": "The total <b>retail</b> amount of returns created for this job."
}, },
"ppc": "This line contains a part price change.",
"profileadjustments": "Profile Disc./Mkup (Already included above)", "profileadjustments": "Profile Disc./Mkup (Already included above)",
"prt_dsmk_total": "Line Item Adjustment", "prt_dsmk_total": "Line Item Adjustment",
"rates": "Rates", "rates": "Rates",
@@ -1981,6 +1995,7 @@
"update": "Update Selected Records" "update": "Update Selected Records"
}, },
"errors": { "errors": {
"deleting": "Error deleting owner. {{error}}.",
"noaccess": "The record does not exist or you do not have access to it. ", "noaccess": "The record does not exist or you do not have access to it. ",
"saving": "Error saving owner. {{error}}.", "saving": "Error saving owner. {{error}}.",
"selectexistingornew": "Select an existing owner record or create a new one. " "selectexistingornew": "Select an existing owner record or create a new one. "
@@ -2013,6 +2028,7 @@
}, },
"labels": { "labels": {
"create_new": "Create a new owner record.", "create_new": "Create a new owner record.",
"deleteconfirm": "Are you sure you want to delete this owner? This cannot be undone.",
"existing_owners": "Existing Owners", "existing_owners": "Existing Owners",
"fromclaim": "Current Claim", "fromclaim": "Current Claim",
"fromowner": "Historical Owner Record", "fromowner": "Historical Owner Record",
@@ -2020,6 +2036,7 @@
"updateowner": "Update Owner" "updateowner": "Update Owner"
}, },
"successes": { "successes": {
"delete": "Owner deleted successfully.",
"save": "Owner saved successfully." "save": "Owner saved successfully."
} }
}, },
@@ -2419,6 +2436,7 @@
"credits_not_received_date": "Credits not Received by Date", "credits_not_received_date": "Credits not Received by Date",
"credits_not_received_date_vendorid": "Credits not Received by Vendor", "credits_not_received_date_vendorid": "Credits not Received by Vendor",
"csi": "CSI Responses", "csi": "CSI Responses",
"customer_list": "Customer List",
"cycle_time_analysis": "Cycle Time Analysis", "cycle_time_analysis": "Cycle Time Analysis",
"estimates_written_converted": "Estimates Written/Converted", "estimates_written_converted": "Estimates Written/Converted",
"estimator_detail": "Jobs by Estimator (Detail)", "estimator_detail": "Jobs by Estimator (Detail)",
@@ -2485,6 +2503,7 @@
"production_by_target_date": "Production by Target Date", "production_by_target_date": "Production by Target Date",
"production_by_technician": "Production by Technician", "production_by_technician": "Production by Technician",
"production_by_technician_one": "Production filtered by Technician", "production_by_technician_one": "Production filtered by Technician",
"production_over_time": "Production Level over Time",
"psr_by_make": "Percent of Sales by Vehicle Make", "psr_by_make": "Percent of Sales by Vehicle Make",
"purchase_return_ratio_grouped_by_vendor_detail": "Purchase & Return Ratio by Vendor (Detail)", "purchase_return_ratio_grouped_by_vendor_detail": "Purchase & Return Ratio by Vendor (Detail)",
"purchase_return_ratio_grouped_by_vendor_summary": "Purchase & Return Ratio by Vendor (Summary)", "purchase_return_ratio_grouped_by_vendor_summary": "Purchase & Return Ratio by Vendor (Summary)",
@@ -2517,6 +2536,7 @@
"labels": { "labels": {
"atssummary": "ATS Summary", "atssummary": "ATS Summary",
"employeevacation": "Employee Vacations", "employeevacation": "Employee Vacations",
"ins_co_nm_filter": "Filter by Insurance Company",
"intake": "Intake Events", "intake": "Intake Events",
"manual": "Manual Events", "manual": "Manual Events",
"manualevent": "Add Manual Event" "manualevent": "Add Manual Event"
@@ -2777,6 +2797,7 @@
}, },
"vehicles": { "vehicles": {
"errors": { "errors": {
"deleting": "Error deleting vehicle. {{error}}.",
"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.",
"selectexistingornew": "Select an existing vehicle record or create a new one. ", "selectexistingornew": "Select an existing vehicle record or create a new one. ",
"validation": "Please ensure all fields are entered correctly.", "validation": "Please ensure all fields are entered correctly.",
@@ -2812,12 +2833,14 @@
"registration": "Registration" "registration": "Registration"
}, },
"labels": { "labels": {
"deleteconfirm": "Are you sure you want to delete this vehicle? This cannot be undone.",
"fromvehicle": "Historical Vehicle Record", "fromvehicle": "Historical Vehicle Record",
"novehinfo": "No Vehicle Information", "novehinfo": "No Vehicle Information",
"relatedjobs": "Related Jobs", "relatedjobs": "Related Jobs",
"updatevehicle": "Update Vehicle Information" "updatevehicle": "Update Vehicle Information"
}, },
"successes": { "successes": {
"delete": "Vehicle deleted successfully.",
"save": "Vehicle saved successfully." "save": "Vehicle saved successfully."
} }
}, },

View File

@@ -230,6 +230,7 @@
"addapptcolor": "", "addapptcolor": "",
"addbucket": "", "addbucket": "",
"addpartslocation": "", "addpartslocation": "",
"addpartsrule": "",
"addspeedprint": "", "addspeedprint": "",
"addtemplate": "", "addtemplate": "",
"newlaborrate": "", "newlaborrate": "",
@@ -270,6 +271,7 @@
"disablebillwip": "", "disablebillwip": "",
"disablecontactvehiclecreation": "", "disablecontactvehiclecreation": "",
"dms_acctnumber": "", "dms_acctnumber": "",
"dms_control_override": "",
"dms_wip_acctnumber": "", "dms_wip_acctnumber": "",
"generic_customer_number": "", "generic_customer_number": "",
"itc_federal": "", "itc_federal": "",
@@ -281,6 +283,7 @@
}, },
"email": "", "email": "",
"enforce_class": "", "enforce_class": "",
"enforce_conversion_category": "",
"enforce_conversion_csr": "", "enforce_conversion_csr": "",
"enforce_referral": "", "enforce_referral": "",
"federal_tax_id": "", "federal_tax_id": "",
@@ -328,7 +331,12 @@
"zip": "" "zip": ""
}, },
"md_jobline_presets": "", "md_jobline_presets": "",
"md_lost_sale_reasons": "",
"md_parts_order_comment": "", "md_parts_order_comment": "",
"md_parts_scan": {
"expression": "",
"flags": ""
},
"md_payment_types": "", "md_payment_types": "",
"md_referral_sources": "", "md_referral_sources": "",
"messaginglabel": "", "messaginglabel": "",
@@ -579,6 +587,7 @@
"notespresets": "", "notespresets": "",
"orderstatuses": "", "orderstatuses": "",
"partslocations": "", "partslocations": "",
"partsscan": "",
"printlater": "", "printlater": "",
"qbo": "", "qbo": "",
"qbo_departmentid": "", "qbo_departmentid": "",
@@ -1272,6 +1281,7 @@
"removefromproduction": "", "removefromproduction": "",
"schedule": "Programar", "schedule": "Programar",
"sendcsi": "", "sendcsi": "",
"sendpartspricechange": "",
"sendtodms": "", "sendtodms": "",
"sync": "", "sync": "",
"uninvoice": "", "uninvoice": "",
@@ -1295,6 +1305,7 @@
"nojobselected": "No hay trabajo seleccionado.", "nojobselected": "No hay trabajo seleccionado.",
"noowner": "Ningún propietario asociado.", "noowner": "Ningún propietario asociado.",
"novehicle": "No hay vehículo asociado.", "novehicle": "No hay vehículo asociado.",
"partspricechange": "",
"saving": "Se encontró un error al guardar el registro.", "saving": "Se encontró un error al guardar el registro.",
"scanimport": "", "scanimport": "",
"totalscalc": "", "totalscalc": "",
@@ -1446,6 +1457,7 @@
"loss_date": "Fecha de pérdida", "loss_date": "Fecha de pérdida",
"loss_desc": "", "loss_desc": "",
"loss_of_use": "", "loss_of_use": "",
"lost_sale_reason": "",
"ma2s": "", "ma2s": "",
"ma3s": "", "ma3s": "",
"mabl": "", "mabl": "",
@@ -1562,6 +1574,7 @@
"scheddates": "" "scheddates": ""
}, },
"labels": { "labels": {
"act_price_ppc": "",
"actual_completion_inferred": "", "actual_completion_inferred": "",
"actual_delivery_inferred": "", "actual_delivery_inferred": "",
"actual_in_inferred": "", "actual_in_inferred": "",
@@ -1698,6 +1711,7 @@
"partstotal": "", "partstotal": "",
"totalreturns": "" "totalreturns": ""
}, },
"ppc": "",
"profileadjustments": "", "profileadjustments": "",
"prt_dsmk_total": "", "prt_dsmk_total": "",
"rates": "Tarifas", "rates": "Tarifas",
@@ -1981,6 +1995,7 @@
"update": "" "update": ""
}, },
"errors": { "errors": {
"deleting": "",
"noaccess": "El registro no existe o no tiene acceso a él.", "noaccess": "El registro no existe o no tiene acceso a él.",
"saving": "", "saving": "",
"selectexistingornew": "" "selectexistingornew": ""
@@ -2013,6 +2028,7 @@
}, },
"labels": { "labels": {
"create_new": "Crea un nuevo registro de propietario.", "create_new": "Crea un nuevo registro de propietario.",
"deleteconfirm": "",
"existing_owners": "Propietarios existentes", "existing_owners": "Propietarios existentes",
"fromclaim": "", "fromclaim": "",
"fromowner": "", "fromowner": "",
@@ -2020,6 +2036,7 @@
"updateowner": "" "updateowner": ""
}, },
"successes": { "successes": {
"delete": "",
"save": "Propietario guardado con éxito." "save": "Propietario guardado con éxito."
} }
}, },
@@ -2419,6 +2436,7 @@
"credits_not_received_date": "", "credits_not_received_date": "",
"credits_not_received_date_vendorid": "", "credits_not_received_date_vendorid": "",
"csi": "", "csi": "",
"customer_list": "",
"cycle_time_analysis": "", "cycle_time_analysis": "",
"estimates_written_converted": "", "estimates_written_converted": "",
"estimator_detail": "", "estimator_detail": "",
@@ -2485,6 +2503,7 @@
"production_by_target_date": "", "production_by_target_date": "",
"production_by_technician": "", "production_by_technician": "",
"production_by_technician_one": "", "production_by_technician_one": "",
"production_over_time": "",
"psr_by_make": "", "psr_by_make": "",
"purchase_return_ratio_grouped_by_vendor_detail": "", "purchase_return_ratio_grouped_by_vendor_detail": "",
"purchase_return_ratio_grouped_by_vendor_summary": "", "purchase_return_ratio_grouped_by_vendor_summary": "",
@@ -2517,6 +2536,7 @@
"labels": { "labels": {
"atssummary": "", "atssummary": "",
"employeevacation": "", "employeevacation": "",
"ins_co_nm_filter": "",
"intake": "", "intake": "",
"manual": "", "manual": "",
"manualevent": "" "manualevent": ""
@@ -2777,6 +2797,7 @@
}, },
"vehicles": { "vehicles": {
"errors": { "errors": {
"deleting": "",
"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.",
"selectexistingornew": "", "selectexistingornew": "",
"validation": "Asegúrese de que todos los campos se ingresen correctamente.", "validation": "Asegúrese de que todos los campos se ingresen correctamente.",
@@ -2812,12 +2833,14 @@
"registration": "" "registration": ""
}, },
"labels": { "labels": {
"deleteconfirm": "",
"fromvehicle": "", "fromvehicle": "",
"novehinfo": "", "novehinfo": "",
"relatedjobs": "", "relatedjobs": "",
"updatevehicle": "" "updatevehicle": ""
}, },
"successes": { "successes": {
"delete": "",
"save": "Vehículo guardado con éxito." "save": "Vehículo guardado con éxito."
} }
}, },

View File

@@ -230,6 +230,7 @@
"addapptcolor": "", "addapptcolor": "",
"addbucket": "", "addbucket": "",
"addpartslocation": "", "addpartslocation": "",
"addpartsrule": "",
"addspeedprint": "", "addspeedprint": "",
"addtemplate": "", "addtemplate": "",
"newlaborrate": "", "newlaborrate": "",
@@ -270,6 +271,7 @@
"disablebillwip": "", "disablebillwip": "",
"disablecontactvehiclecreation": "", "disablecontactvehiclecreation": "",
"dms_acctnumber": "", "dms_acctnumber": "",
"dms_control_override": "",
"dms_wip_acctnumber": "", "dms_wip_acctnumber": "",
"generic_customer_number": "", "generic_customer_number": "",
"itc_federal": "", "itc_federal": "",
@@ -281,6 +283,7 @@
}, },
"email": "", "email": "",
"enforce_class": "", "enforce_class": "",
"enforce_conversion_category": "",
"enforce_conversion_csr": "", "enforce_conversion_csr": "",
"enforce_referral": "", "enforce_referral": "",
"federal_tax_id": "", "federal_tax_id": "",
@@ -328,7 +331,12 @@
"zip": "" "zip": ""
}, },
"md_jobline_presets": "", "md_jobline_presets": "",
"md_lost_sale_reasons": "",
"md_parts_order_comment": "", "md_parts_order_comment": "",
"md_parts_scan": {
"expression": "",
"flags": ""
},
"md_payment_types": "", "md_payment_types": "",
"md_referral_sources": "", "md_referral_sources": "",
"messaginglabel": "", "messaginglabel": "",
@@ -579,6 +587,7 @@
"notespresets": "", "notespresets": "",
"orderstatuses": "", "orderstatuses": "",
"partslocations": "", "partslocations": "",
"partsscan": "",
"printlater": "", "printlater": "",
"qbo": "", "qbo": "",
"qbo_departmentid": "", "qbo_departmentid": "",
@@ -1272,6 +1281,7 @@
"removefromproduction": "", "removefromproduction": "",
"schedule": "Programme", "schedule": "Programme",
"sendcsi": "", "sendcsi": "",
"sendpartspricechange": "",
"sendtodms": "", "sendtodms": "",
"sync": "", "sync": "",
"uninvoice": "", "uninvoice": "",
@@ -1295,6 +1305,7 @@
"nojobselected": "Aucun travail n'est sélectionné.", "nojobselected": "Aucun travail n'est sélectionné.",
"noowner": "Aucun propriétaire associé.", "noowner": "Aucun propriétaire associé.",
"novehicle": "Aucun véhicule associé.", "novehicle": "Aucun véhicule associé.",
"partspricechange": "",
"saving": "Erreur rencontrée lors de la sauvegarde de l'enregistrement.", "saving": "Erreur rencontrée lors de la sauvegarde de l'enregistrement.",
"scanimport": "", "scanimport": "",
"totalscalc": "", "totalscalc": "",
@@ -1446,6 +1457,7 @@
"loss_date": "Date de perte", "loss_date": "Date de perte",
"loss_desc": "", "loss_desc": "",
"loss_of_use": "", "loss_of_use": "",
"lost_sale_reason": "",
"ma2s": "", "ma2s": "",
"ma3s": "", "ma3s": "",
"mabl": "", "mabl": "",
@@ -1562,6 +1574,7 @@
"scheddates": "" "scheddates": ""
}, },
"labels": { "labels": {
"act_price_ppc": "",
"actual_completion_inferred": "", "actual_completion_inferred": "",
"actual_delivery_inferred": "", "actual_delivery_inferred": "",
"actual_in_inferred": "", "actual_in_inferred": "",
@@ -1698,6 +1711,7 @@
"partstotal": "", "partstotal": "",
"totalreturns": "" "totalreturns": ""
}, },
"ppc": "",
"profileadjustments": "", "profileadjustments": "",
"prt_dsmk_total": "", "prt_dsmk_total": "",
"rates": "Les taux", "rates": "Les taux",
@@ -1981,6 +1995,7 @@
"update": "" "update": ""
}, },
"errors": { "errors": {
"deleting": "",
"noaccess": "L'enregistrement n'existe pas ou vous n'y avez pas accès.", "noaccess": "L'enregistrement n'existe pas ou vous n'y avez pas accès.",
"saving": "", "saving": "",
"selectexistingornew": "" "selectexistingornew": ""
@@ -2013,6 +2028,7 @@
}, },
"labels": { "labels": {
"create_new": "Créez un nouvel enregistrement de propriétaire.", "create_new": "Créez un nouvel enregistrement de propriétaire.",
"deleteconfirm": "",
"existing_owners": "Propriétaires existants", "existing_owners": "Propriétaires existants",
"fromclaim": "", "fromclaim": "",
"fromowner": "", "fromowner": "",
@@ -2020,6 +2036,7 @@
"updateowner": "" "updateowner": ""
}, },
"successes": { "successes": {
"delete": "",
"save": "Le propriétaire a bien enregistré." "save": "Le propriétaire a bien enregistré."
} }
}, },
@@ -2419,6 +2436,7 @@
"credits_not_received_date": "", "credits_not_received_date": "",
"credits_not_received_date_vendorid": "", "credits_not_received_date_vendorid": "",
"csi": "", "csi": "",
"customer_list": "",
"cycle_time_analysis": "", "cycle_time_analysis": "",
"estimates_written_converted": "", "estimates_written_converted": "",
"estimator_detail": "", "estimator_detail": "",
@@ -2485,6 +2503,7 @@
"production_by_target_date": "", "production_by_target_date": "",
"production_by_technician": "", "production_by_technician": "",
"production_by_technician_one": "", "production_by_technician_one": "",
"production_over_time": "",
"psr_by_make": "", "psr_by_make": "",
"purchase_return_ratio_grouped_by_vendor_detail": "", "purchase_return_ratio_grouped_by_vendor_detail": "",
"purchase_return_ratio_grouped_by_vendor_summary": "", "purchase_return_ratio_grouped_by_vendor_summary": "",
@@ -2517,6 +2536,7 @@
"labels": { "labels": {
"atssummary": "", "atssummary": "",
"employeevacation": "", "employeevacation": "",
"ins_co_nm_filter": "",
"intake": "", "intake": "",
"manual": "", "manual": "",
"manualevent": "" "manualevent": ""
@@ -2777,6 +2797,7 @@
}, },
"vehicles": { "vehicles": {
"errors": { "errors": {
"deleting": "",
"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.",
"selectexistingornew": "", "selectexistingornew": "",
"validation": "Veuillez vous assurer que tous les champs sont correctement entrés.", "validation": "Veuillez vous assurer que tous les champs sont correctement entrés.",
@@ -2812,12 +2833,14 @@
"registration": "" "registration": ""
}, },
"labels": { "labels": {
"deleteconfirm": "",
"fromvehicle": "", "fromvehicle": "",
"novehinfo": "", "novehinfo": "",
"relatedjobs": "", "relatedjobs": "",
"updatevehicle": "" "updatevehicle": ""
}, },
"successes": { "successes": {
"delete": "",
"save": "Le véhicule a été enregistré avec succès." "save": "Le véhicule a été enregistré avec succès."
} }
}, },

View File

@@ -40,7 +40,7 @@ const AuditTrailMapping = {
i18n.t("audit_trail.messages.admin_jobmarkforreexport"), i18n.t("audit_trail.messages.admin_jobmarkforreexport"),
admin_jobmarkexported: () => admin_jobmarkexported: () =>
i18n.t("audit_trail.messages.admin_jobmarkexported"), i18n.t("audit_trail.messages.admin_jobmarkexported"),
failedpayment: () => i18n.t("audit_trail.messages.failedpayment"),
}; };
export default AuditTrailMapping; export default AuditTrailMapping;

View File

@@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import NumberFormat from "react-number-format"; import { NumericFormat } from "react-number-format";
export default function CurrencyFormatter(props) { export default function CurrencyFormatter(props) {
return ( return (
<NumberFormat <NumericFormat
thousandSeparator={true} thousandSeparator={true}
decimalScale={2} decimalScale={2}
fixedDecimalScale={true} fixedDecimalScale={true}

View File

@@ -142,7 +142,7 @@ middlewares.push(
const cache = new InMemoryCache({}); const cache = new InMemoryCache({});
export default new ApolloClient({ const client = new ApolloClient({
link: ApolloLink.from(middlewares), link: ApolloLink.from(middlewares),
cache, cache,
connectToDevTools: process.env.NODE_ENV !== "production", connectToDevTools: process.env.NODE_ENV !== "production",
@@ -161,3 +161,4 @@ export default new ApolloClient({
}, },
}, },
}); });
export default client;

View File

@@ -1841,6 +1841,34 @@ export const TemplateList = (type, context) => {
}, },
group: "purchases", group: "purchases",
}, },
production_over_time: {
title: i18n.t("reportcenter.templates.production_over_time"),
subject: i18n.t(
"reportcenter.templates.production_over_time"
),
key: "production_over_time",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.actual_in"),
},
group: "jobs",
},
customer_list: {
title: i18n.t("reportcenter.templates.customer_list"),
subject: i18n.t(
"reportcenter.templates.customer_list"
),
key: "customer_list",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
group: "customers",
},
} }
: {}), : {}),
...(!type || type === "courtesycarcontract" ...(!type || type === "courtesycarcontract"

View File

@@ -0,0 +1,12 @@
import axios from "axios";
import { notification } from "antd";
async function CriticalPartsScan(jobid) {
try {
await axios.post("/job/partsscan", { jobid });
} catch (error) {
notification.open({ type: "error", message: JSON.stringify(error) });
}
}
export default CriticalPartsScan;

File diff suppressed because it is too large Load Diff

View File

@@ -223,11 +223,9 @@
- kanban_settings - kanban_settings
- qbo_realmId - qbo_realmId
filter: filter:
bodyshop: user:
associations: authid:
user: _eq: X-Hasura-User-Id
authid:
_eq: X-Hasura-User-Id
check: null check: null
- table: - table:
name: audit_trail name: audit_trail
@@ -798,6 +796,13 @@
table: table:
name: owners name: owners
schema: public schema: public
- name: payment_responses
using:
foreign_key_constraint_on:
column: bodyshopid
table:
name: payment_response
schema: public
- name: phonebooks - name: phonebooks
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
@@ -855,6 +860,7 @@
- deliverchecklist - deliverchecklist
- email - email
- enforce_class - enforce_class
- enforce_conversion_category
- enforce_conversion_csr - enforce_conversion_csr
- enforce_referral - enforce_referral
- entegral_configuration - entegral_configuration
@@ -885,11 +891,13 @@
- md_ins_cos - md_ins_cos
- md_jobline_presets - md_jobline_presets
- md_labor_rates - md_labor_rates
- md_lost_sale_reasons
- md_messaging_presets - md_messaging_presets
- md_notes_presets - md_notes_presets
- md_order_statuses - md_order_statuses
- md_parts_locations - md_parts_locations
- md_parts_order_comment - md_parts_order_comment
- md_parts_scan
- md_payment_types - md_payment_types
- md_rbac - md_rbac
- md_referral_sources - md_referral_sources
@@ -952,6 +960,7 @@
- deliverchecklist - deliverchecklist
- email - email
- enforce_class - enforce_class
- enforce_conversion_category
- enforce_conversion_csr - enforce_conversion_csr
- enforce_referral - enforce_referral
- federal_tax_id - federal_tax_id
@@ -977,11 +986,13 @@
- md_ins_cos - md_ins_cos
- md_jobline_presets - md_jobline_presets
- md_labor_rates - md_labor_rates
- md_lost_sale_reasons
- md_messaging_presets - md_messaging_presets
- md_notes_presets - md_notes_presets
- md_order_statuses - md_order_statuses
- md_parts_locations - md_parts_locations
- md_parts_order_comment - md_parts_order_comment
- md_parts_scan
- md_payment_types - md_payment_types
- md_rbac - md_rbac
- md_referral_sources - md_referral_sources
@@ -2482,6 +2493,7 @@
_eq: true _eq: true
columns: columns:
- act_price - act_price
- act_price_before_ppc
- ah_detail_line - ah_detail_line
- alt_co_id - alt_co_id
- alt_overrd - alt_overrd
@@ -2548,6 +2560,7 @@
permission: permission:
columns: columns:
- act_price - act_price
- act_price_before_ppc
- ah_detail_line - ah_detail_line
- alt_co_id - alt_co_id
- alt_overrd - alt_overrd
@@ -2562,6 +2575,7 @@
- convertedtolbr - convertedtolbr
- convertedtolbr_data - convertedtolbr_data
- created_at - created_at
- critical
- db_hrs - db_hrs
- db_price - db_price
- db_ref - db_ref
@@ -2625,6 +2639,7 @@
permission: permission:
columns: columns:
- act_price - act_price
- act_price_before_ppc
- ah_detail_line - ah_detail_line
- alt_co_id - alt_co_id
- alt_overrd - alt_overrd
@@ -2639,6 +2654,7 @@
- convertedtolbr - convertedtolbr
- convertedtolbr_data - convertedtolbr_data
- created_at - created_at
- critical
- db_hrs - db_hrs
- db_price - db_price
- db_ref - db_ref
@@ -2903,6 +2919,13 @@
table: table:
name: parts_orders name: parts_orders
schema: public schema: public
- name: payment_responses
using:
foreign_key_constraint_on:
column: jobid
table:
name: payment_response
schema: public
- name: payments - name: payments
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
@@ -3382,6 +3405,7 @@
- loss_desc - loss_desc
- loss_of_use - loss_of_use
- loss_type - loss_type
- lost_sale_reason
- materials - materials
- other_amount_payable - other_amount_payable
- owner_owing - owner_owing
@@ -3656,6 +3680,7 @@
- loss_desc - loss_desc
- loss_of_use - loss_of_use
- loss_type - loss_type
- lost_sale_reason
- materials - materials
- other_amount_payable - other_amount_payable
- owner_owing - owner_owing
@@ -4502,6 +4527,63 @@
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
- active: - active:
_eq: true _eq: true
- table:
name: payment_response
schema: public
object_relationships:
- name: bodyshop
using:
foreign_key_constraint_on: bodyshopid
- name: job
using:
foreign_key_constraint_on: jobid
- name: payment
using:
foreign_key_constraint_on: paymentid
insert_permissions:
- role: user
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- amount
- bodyshopid
- declinereason
- ext_paymentid
- jobid
- paymentid
- response
- successful
select_permissions:
- role: user
permission:
columns:
- successful
- response
- amount
- declinereason
- ext_paymentid
- bodyshopid
- id
- jobid
- paymentid
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
allow_aggregations: true
- table: - table:
name: payments name: payments
schema: public schema: public
@@ -4517,6 +4599,13 @@
table: table:
name: exportlog name: exportlog
schema: public schema: public
- name: payment_responses
using:
foreign_key_constraint_on:
column: paymentid
table:
name: payment_response
schema: public
insert_permissions: insert_permissions:
- role: user - role: user
permission: permission:

View File

@@ -0,0 +1 @@
DROP TABLE "public"."payment_response";

View File

@@ -0,0 +1,2 @@
CREATE TABLE "public"."payment_response" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "bodyshopid" uuid NOT NULL, "jobid" uuid, "paymentid" uuid, "successful" boolean NOT NULL DEFAULT false, "ext_paymentid" text NOT NULL, "amount" numeric NOT NULL, "declinereason" text, "response" jsonb NOT NULL DEFAULT jsonb_build_object(), PRIMARY KEY ("id") , FOREIGN KEY ("bodyshopid") REFERENCES "public"."bodyshops"("id") ON UPDATE cascade ON DELETE cascade, FOREIGN KEY ("jobid") REFERENCES "public"."jobs"("id") ON UPDATE cascade ON DELETE cascade, FOREIGN KEY ("paymentid") REFERENCES "public"."payments"("id") ON UPDATE cascade ON DELETE cascade);
CREATE EXTENSION IF NOT EXISTS pgcrypto;

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_lost_sale_reasons" jsonb
-- not null default jsonb_build_array();

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "md_lost_sale_reasons" 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"."jobs" add column "lost_sale_reason" text
-- null;

View File

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

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_scan" jsonb
-- not null default jsonb_build_array();

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "md_parts_scan" 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"."joblines" add column "critical" boolean
-- not null default 'false';

View File

@@ -0,0 +1,2 @@
alter table "public"."joblines" add column "critical" 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 "enforce_conversion_category" boolean
-- not null default 'false';

View File

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

View File

@@ -0,0 +1 @@
alter table "public"."bodyshops" alter column "md_lost_sale_reasons" set default jsonb_build_array();

View File

@@ -0,0 +1 @@
alter table "public"."bodyshops" alter column "md_lost_sale_reasons" set default '["Scheduling Delay", "Backordered Parts", "Price", "Unknown"]';

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"."joblines" add column "act_price_before_ppc" numeric
-- null;

View File

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

View File

@@ -20,7 +20,7 @@ require("dotenv").config({
async function RunTheTest() { async function RunTheTest() {
const bodyshopids = ["6c63a820-542c-497e-8c82-0cc38fb2bbca"]; const bodyshopids = ["6c63a820-542c-497e-8c82-0cc38fb2bbca"];
const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImFlYjMxMjdiMjRjZTg2MDJjODEyNDUxZThmZTczZDU4MjkyMDg4N2MiLCJ0eXAiOiJKV1QifQ.eyJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6InVzZXIiLCJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbInVzZXIiXSwieC1oYXN1cmEtdXNlci1pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIifSwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL3JvbWUtcHJvZC0xIiwiYXVkIjoicm9tZS1wcm9kLTEiLCJhdXRoX3RpbWUiOjE2NzY0ODIxOTYsInVzZXJfaWQiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwic3ViIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiIsImlhdCI6MTY3NjU4NzYxNSwiZXhwIjoxNjc2NTkxMjE1LCJlbWFpbCI6InBhdHJpY2tAcm9tZS5kZXYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicGF0cmlja0Byb21lLmRldiJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.53cLDjRthvAWUOKjSmdMII78MxD1s-mkEbG9z9KVxLB18NsKS-iZMAfIZcYb-LXZGscH8O-jR0OSyMgXitc-mv6xYV6bAGcO7gUgxwMKqnbh9-pK_uyGQ5LQ-yxMG2F397ObJu3fyB1RZ1e8LRYkIpV9LwAm4XiHQdGAfYyFDA2fSOS-9x9k6im07hAYsEeIx2hNr-8vVaEpkCENF2JFpJ9qjtfp6pRnbwQY2VA8nsJly1oOz56GLhb5f1m2Ta22eVqAye9of5EXmNSTsvDkAv7Xs3NNuNbHu8fM76tAuKPniurMNV5VwJZX7RhsjFelmoUFFTVOj6JVL-Sw-vs65A`; const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ijk3OWVkMTU1OTdhYjM1Zjc4MjljZTc0NDMwN2I3OTNiN2ViZWIyZjAiLCJ0eXAiOiJKV1QifQ.eyJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6InVzZXIiLCJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbInVzZXIiXSwieC1oYXN1cmEtdXNlci1pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIifSwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL3JvbWUtcHJvZC0xIiwiYXVkIjoicm9tZS1wcm9kLTEiLCJhdXRoX3RpbWUiOjE2NzkzNDc4NzAsInVzZXJfaWQiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwic3ViIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiIsImlhdCI6MTY3OTk1NDk3MiwiZXhwIjoxNjc5OTU4NTcyLCJlbWFpbCI6InBhdHJpY2tAcm9tZS5kZXYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicGF0cmlja0Byb21lLmRldiJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.Dnq_xo5tffFf-LK0qD_iieUa_UYe4cJqOxcuJnRGH0aqirMMeQLRR4B_Z3pOsD3T20ML3qZMQNUKx-Ivz1mfyK_aA7_4GKtHRKOpIrAyssw_l5aXuCAEmC8iLQHDGvKi7Vp8LsTMPKqjJSjtaW2zuFqcIGrqncWkBMYSnCKjCFsKjryp35hQiIynAN1W0ajgjmFZHCy7hG1h4wFtLKNXEAGxWA0tE7m7ZZBZk3W7J3nMbYiMuGZfw0y2yYeILQGw3UW6sb9B2Jx2bAR3x-GWhPzQHNZEPolE-andm900cFgdph1z7eBE5P2udc2rp8JsAPdUdovt8ZImhCUeE5wD6g`;
const { jobs } = await client.request( const { jobs } = await client.request(
gql` gql`
query GET_JOBS($bodyshopids: [uuid!]!) { query GET_JOBS($bodyshopids: [uuid!]!) {
@@ -77,7 +77,7 @@ async function RunTheTest() {
const calcTotal = newjob.job_totals.totals.total_repairs.amount; const calcTotal = newjob.job_totals.totals.total_repairs.amount;
const ttlTotal = newjob.cieca_ttl.data.g_ttl_amt * 100; const ttlTotal = newjob.cieca_ttl.data.g_ttl_amt * 100;
result.difference = Math.abs(calcTotal - ttlTotal) / 100; result.difference = (calcTotal - ttlTotal) / 100;
if (Math.abs(calcTotal - ttlTotal) > 5) { if (Math.abs(calcTotal - ttlTotal) > 5) {
//Diff is greater than 5 cents. Fail it. //Diff is greater than 5 cents. Fail it.

5634
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,41 +17,41 @@
"start": "node server.js" "start": "node server.js"
}, },
"dependencies": { "dependencies": {
"aws-sdk": "^2.1181.0", "aws-sdk": "^2.1326.0",
"axios": "^0.27.2", "axios": "^0.27.2",
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"body-parser": "^1.20.0", "body-parser": "^1.20.2",
"cloudinary": "^1.30.1", "cloudinary": "^1.34.0",
"compression": "^1.7.4", "compression": "^1.7.4",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"cors": "2.8.5", "cors": "2.8.5",
"csrf": "^3.1.0", "csrf": "^3.1.0",
"dinero.js": "^1.9.1", "dinero.js": "^1.9.1",
"dotenv": "16.0.1", "dotenv": "16.0.3",
"express": "^4.18.1", "express": "^4.18.2",
"firebase-admin": "^11.0.0", "firebase-admin": "^11.5.0",
"graphql": "^16.5.0", "graphql": "^16.6.0",
"graphql-request": "^4.2.0", "graphql-request": "^4.2.0",
"graylog2": "^0.2.1", "graylog2": "^0.2.1",
"inline-css": "^4.0.1", "inline-css": "^4.0.2",
"intuit-oauth": "^4.0.0", "intuit-oauth": "^4.0.0",
"json-2-csv": "^3.17.1", "json-2-csv": "^3.19.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.29.4", "moment": "^2.29.4",
"moment-timezone": "^0.5.34", "moment-timezone": "^0.5.41",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"node-mailjet": "^5.1.0", "node-mailjet": "^6.0.2",
"node-persist": "^3.1.0", "node-persist": "^3.1.3",
"node-quickbooks": "^2.0.39", "node-quickbooks": "^2.0.41",
"nodemailer": "^6.7.7", "nodemailer": "^6.9.1",
"phone": "^3.1.23", "phone": "^3.1.35",
"query-string": "^7.1.1", "query-string": "^7.1.1",
"soap": "^0.45.0", "soap": "^1.0.0",
"socket.io": "^4.5.0", "socket.io": "^4.6.1",
"ssh2-sftp-client": "^9.0.2", "ssh2-sftp-client": "^9.0.4",
"stripe": "^9.15.0", "stripe": "^9.15.0",
"twilio": "^3.80.0", "twilio": "^4.8.0",
"uuid": "^8.3.2", "uuid": "^9.0.0",
"xml2js": "^0.4.23", "xml2js": "^0.4.23",
"xmlbuilder2": "^3.0.2" "xmlbuilder2": "^3.0.2"
}, },

View File

@@ -138,6 +138,11 @@ app.post("/job/totalsssu", fb.validateFirebaseIdToken, job.totalsSsu);
app.post("/job/costing", fb.validateFirebaseIdToken, job.costing); app.post("/job/costing", fb.validateFirebaseIdToken, job.costing);
app.post("/job/costingmulti", fb.validateFirebaseIdToken, job.costingmulti); app.post("/job/costingmulti", fb.validateFirebaseIdToken, job.costingmulti);
var ppc = require("./server/ccc/partspricechange");
app.post("/job/ppc", fb.validateFirebaseIdToken, ppc.generatePpc);
var partsScan = require("./server/parts-scan/parts-scan");
app.post("/job/partsscan", fb.validateFirebaseIdToken, partsScan.partsScan);
//Scheduling //Scheduling
var scheduling = require("./server/scheduling/scheduling-job"); var scheduling = require("./server/scheduling/scheduling-job");
app.post("/scheduling/job", fb.validateFirebaseIdToken, scheduling.job); app.post("/scheduling/job", fb.validateFirebaseIdToken, scheduling.job);
@@ -227,6 +232,31 @@ app.post(
mixdataUpload.mixdataUpload mixdataUpload.mixdataUpload
); );
var intellipay = require("./server/intellipay/intellipay");
app.get(
"/intellipay/lightbox_credentials",
fb.validateFirebaseIdToken,
intellipay.lightbox_credentials
);
app.post(
"/intellipay/payment_refund",
fb.validateFirebaseIdToken,
intellipay.payment_refund
);
app.post(
"/intellipay/generate_payment_url",
fb.validateFirebaseIdToken,
intellipay.generate_payment_url
);
app.post(
"/intellipay/postback",
// fb.validateFirebaseIdToken,
intellipay.postback
);
var ioevent = require("./server/ioevent/ioevent"); var ioevent = require("./server/ioevent/ioevent");
app.post("/ioevent", ioevent.default); app.post("/ioevent", ioevent.default);
app.post("/newlog", (req, res) => { app.post("/newlog", (req, res) => {

View File

@@ -0,0 +1,45 @@
const path = require("path");
const _ = require("lodash");
const logger = require("../utils/logger");
const queries = require("../graphql-client/queries");
const GraphQLClient = require("graphql-request").GraphQLClient;
const moment = require("moment-timezone");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
exports.generatePpc = async (req, res) => {
const { jobid } = req.body;
const BearerToken = req.headers.authorization;
logger.log("generate-ppc", "DEBUG", req.user.email, jobid, null);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
Authorization: BearerToken,
},
});
try {
const { jobs_by_pk: job } = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.GET_JOB_FOR_PPC, {
jobid: jobid,
});
const ReturnVal = {
...job,
trans_type: "P",
create_dt: moment().tz(job.bodyshop.timezone).format("yyyyMMDD"),
create_tm: moment().tz(job.bodyshop.timezone).format("HHmmSS"),
incl_est: true,
joblines: job.joblines.map((jl) => ({ ...jl, tran_code: 2 })),
};
res.json(ReturnVal);
} catch (error) {
res.status(400).Send(JSON.stringify(error));
}
};

View File

@@ -1092,7 +1092,13 @@ async function GenerateTransWips(socket) {
if (alloc.sale.getAmount() > 0 && !alloc.tax) { if (alloc.sale.getAmount() > 0 && !alloc.tax) {
const item = { const item = {
acct: alloc.profitCenter.dms_acctnumber, acct: alloc.profitCenter.dms_acctnumber,
cntl: socket.JobData.ro_number, cntl:
alloc.profitCenter.dms_control_override &&
alloc.profitCenter.dms_control_override !== null &&
alloc.profitCenter.dms_control_override !== undefined &&
alloc.profitCenter.dms_control_override?.trim() !== ""
? alloc.profitCenter.dms_control_override
: socket.JobData.ro_number,
cntl2: null, cntl2: null,
credtMemoNo: null, credtMemoNo: null,
postAmt: alloc.sale.multiply(-1).getAmount(), postAmt: alloc.sale.multiply(-1).getAmount(),
@@ -1109,7 +1115,13 @@ async function GenerateTransWips(socket) {
if (alloc.cost.getAmount() > 0 && !alloc.tax) { if (alloc.cost.getAmount() > 0 && !alloc.tax) {
const item = { const item = {
acct: alloc.costCenter.dms_acctnumber, acct: alloc.costCenter.dms_acctnumber,
cntl: socket.JobData.ro_number, cntl:
alloc.costCenter.dms_control_override &&
alloc.costCenter.dms_control_override !== null &&
alloc.costCenter.dms_control_override !== undefined &&
alloc.costCenter.dms_control_override?.trim() !== ""
? alloc.costCenter.dms_control_override
: socket.JobData.ro_number,
cntl2: null, cntl2: null,
credtMemoNo: null, credtMemoNo: null,
postAmt: alloc.cost.getAmount(), postAmt: alloc.cost.getAmount(),
@@ -1123,7 +1135,13 @@ async function GenerateTransWips(socket) {
const itemWip = { const itemWip = {
acct: alloc.costCenter.dms_wip_acctnumber, acct: alloc.costCenter.dms_wip_acctnumber,
cntl: socket.JobData.ro_number, cntl:
alloc.costCenter.dms_control_override &&
alloc.costCenter.dms_control_override !== null &&
alloc.costCenter.dms_control_override !== undefined &&
alloc.costCenter.dms_control_override?.trim() !== ""
? alloc.costCenter.dms_control_override
: socket.JobData.ro_number,
cntl2: null, cntl2: null,
credtMemoNo: null, credtMemoNo: null,
postAmt: alloc.cost.multiply(-1).getAmount(), postAmt: alloc.cost.multiply(-1).getAmount(),
@@ -1158,7 +1176,13 @@ async function GenerateTransWips(socket) {
if (alloc.sale.getAmount() > 0) { if (alloc.sale.getAmount() > 0) {
const item2 = { const item2 = {
acct: alloc.profitCenter.dms_acctnumber, acct: alloc.profitCenter.dms_acctnumber,
cntl: socket.JobData.ro_number, cntl:
alloc.profitCenter.dms_control_override &&
alloc.profitCenter.dms_control_override !== null &&
alloc.profitCenter.dms_control_override !== undefined &&
alloc.profitCenter.dms_control_override?.trim() !== ""
? alloc.profitCenter.dms_control_override
: socket.JobData.ro_number,
cntl2: null, cntl2: null,
credtMemoNo: null, credtMemoNo: null,
postAmt: alloc.sale.multiply(-1).getAmount(), postAmt: alloc.sale.multiply(-1).getAmount(),

View File

@@ -42,14 +42,16 @@ function pollFunc(fn, timeout, interval) {
pollFunc(getEntegralShopData, 0, 5 * 60 * 1000); //Set the metadata to refresh every 5 minutes. pollFunc(getEntegralShopData, 0, 5 * 60 * 1000); //Set the metadata to refresh every 5 minutes.
async function getEntegralShopData() { async function getEntegralShopData() {
await storage.init({ logging: true }); // await storage.init({ logging: true });
const { bodyshops } = await client.request(queries.GET_ENTEGRAL_SHOPS); // const { bodyshops } = await client.request(queries.GET_ENTEGRAL_SHOPS);
logger.log("set-entegral-shops-local-storage", "DEBUG", "API", null, null); // logger.log("set-entegral-shops-local-storage", "DEBUG", "API", null, null);
await storage.setItem("entegralShops", bodyshops); // await storage.setItem("entegralShops", bodyshops);
return true; //Continue execution. // return true; //Continue execution.
} }
exports.default = async (req, res) => { exports.default = async (req, res) => {
res.sendStatus(200);
return;
//Query for the List of Bodyshop Clients. //Query for the List of Bodyshop Clients.
const job = req.body.event.data.new; const job = req.body.event.data.new;
logger.log("arms-job-update", "DEBUG", "api", job.id, null); logger.log("arms-job-update", "DEBUG", "api", job.id, null);

View File

@@ -814,7 +814,11 @@ const CreateCosts = (job) => {
].add( ].add(
Dinero({ Dinero({
amount: Math.round((ticket_val.rate || 0) * 100), amount: Math.round((ticket_val.rate || 0) * 100),
}).multiply(ticket_val.actualhrs || ticket_val.productivehrs || 0) }).multiply(
(ticket_val.flat_rate
? ticket_val.productivehrs
: ticket_val.actualhrs) || 0
)
); );
return ticket_acc; return ticket_acc;

View File

@@ -775,6 +775,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
cost_center cost_center
actualhrs actualhrs
productivehrs productivehrs
flat_rate
} }
area_of_damage area_of_damage
employee_prep_rel { employee_prep_rel {
@@ -905,8 +906,9 @@ exports.UPDATE_JOB = `
} }
`; `;
exports.GET_JOB_BY_PK = ` query GET_JOB_BY_PK($id: uuid!) { exports.GET_JOB_BY_PK = `query GET_JOB_BY_PK($id: uuid!) {
jobs_by_pk(id: $id) { jobs_by_pk(id: $id) {
cieca_stl
updated_at updated_at
alt_transport alt_transport
intakechecklist intakechecklist
@@ -1072,6 +1074,8 @@ vehicle{
manual_line manual_line
prt_dsmk_p prt_dsmk_p
prt_dsmk_m prt_dsmk_m
misc_amt
misc_tax
parts_order_lines { parts_order_lines {
id id
parts_order { parts_order {
@@ -1718,3 +1722,43 @@ query GET_PBS_AP_ALLOCATIONS($billids: [uuid!]) {
} }
} }
`; `;
exports.GET_JOB_FOR_PPC = `query GET_JOB_FOR_PPC($jobid: uuid!) {
jobs_by_pk(id: $jobid) {
id
ciecaid
ro_number
joblines(where: {removed: {_eq: false}, act_price_before_ppc:{_is_null: false}}) {
id
act_price
unq_seq
}
bodyshop {
timezone
}
}
`
exports.QUERY_PARTS_SCAN = `query QUERY_PARTS_SCAN ($id: uuid!) {
jobs_by_pk(id: $id) {
bodyshop {
id
md_parts_scan
}
joblines(where: {removed: {_eq: false}}) {
id
line_desc
critical
}
}
}
`;
exports.UPDATE_PARTS_CRITICAL = `mutation UPDATE_PARTS_CRITICAL ($IdsToMarkCritical:[uuid!]!, $jobid: uuid!){
critical: update_joblines(where:{id:{_in:$IdsToMarkCritical}}, _set:{critical: true}){
affected_rows
}
notcritical: update_joblines(where:{id:{_nin:$IdsToMarkCritical}, jobid: {_eq: $jobid}}, _set:{critical: false}){
affected_rows
}
}`;

View File

@@ -0,0 +1,112 @@
const GraphQLClient = require("graphql-request").GraphQLClient;
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const qs = require("query-string");
const axios = require("axios");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const domain = process.env.NODE_ENV ? "secure" : "test";
const getShopCredentials = () => {
// add parametes for the request
// TODO: Implement retrieval logic later.
return {
merchantkey: "3B8068", //This should be dynamic
apikey: "Oepn2B.XqRgzAqHqvOOmYUxD2VW.vGSipi", //This should be dynamic
};
};
exports.lightbox_credentials = async (req, res) => {
//req.user contains firebase decoded credentials
// can add bodyshopid to req.body
//Server side query to get API credentials for that shop and generatae link
const shopCredentials = getShopCredentials();
try {
const options = {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
//TODO: Move these to environment variables/database.
data: qs.stringify({
...shopCredentials,
operatingenv:
process.env.NODE_ENV === undefined
? process.env.NODE_ENV
: "businessattended",
}),
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal`,
};
const response = await axios(options);
res.send(response.data);
} catch (error) {
console.log(error);
res.json({ error });
}
};
exports.payment_refund = async (req, res) => {
const shopCredentials = getShopCredentials();
try {
const options = {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
data: qs.stringify({
method: "payment_refund",
...shopCredentials,
paymentid: req.body.paymentid,
amount: req.body.amount,
}),
url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund`,
};
const response = await axios(options);
res.send(response.data);
} catch (error) {
console.log(error);
res.json({ error });
}
};
exports.generate_payment_url = async (req, res) => {
const shopCredentials = getShopCredentials();
try {
const options = {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
//TODO: Move these to environment variables/database.
data: qs.stringify({
...shopCredentials,
...req.body,
createshorturl: true,
}),
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=generate_lightbox_url`,
};
const response = await axios(options);
res.send(response.data);
} catch (error) {
console.log(error);
res.json({ error });
}
};
exports.postback = async (req, res) => {
console.log("postback as", req.body);
res.send({ message: "postback" });
};

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