Compare commits

...

64 Commits

Author SHA1 Message Date
Dave Richer
31be51ef79 Merged release/AIO/2024-05-31 into feature/IO-2796-Header-IDS 2024-05-31 17:23:45 +00:00
Dave Richer
e4066c1570 - Add Additional IDS to headers
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-05-31 13:20:54 -04:00
Patrick Fic
ec480f2cb1 Resolve Bill Posting errors on Beta. 2024-05-29 14:08:49 -07:00
Patrick Fic
f5b30c9376 Resolve API Route. 2024-05-28 12:23:42 -07:00
Patrick Fic
412508fbcf Merge branch 'hotfix/AIO/2024-05-28' into master-AIO 2024-05-28 12:19:28 -07:00
Patrick Fic
dbfd9bce54 Resolve region for US intellipay. 2024-05-28 12:19:18 -07:00
Dave Richer
52e0558c79 Merged in release/AIO/2024-05-24 (pull request #1449)
Release/AIO/2024 05 24
2024-05-24 20:45:23 +00:00
Allan Carr
562d0b8641 Merged in feature/IO-2785-IO-Rescue-Header (pull request #1444)
IO-2785 AIO IO Header Rescue Link

Approved-by: Dave Richer
2024-05-23 19:47:27 +00:00
Patrick Fic
d48d6cdd91 Merge branch 'hotfix/AIO/2024-05-23' into release/AIO/2024-05-24 2024-05-23 11:40:35 -07:00
Patrick Fic
9363790541 Merge branch 'hotfix/AIO/2024-05-23' into master-AIO 2024-05-23 10:58:13 -07:00
Patrick Fic
2bceba948d Resolve postback logging. 2024-05-23 10:57:39 -07:00
Allan Carr
168d4246af IO-2785 AIO IO Header Rescue Link
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-05-22 11:43:19 -07:00
Patrick Fic
cdf6050ec2 Merge branch 'release/AIO/2024-05-17' into release/AIO/2024-05-24 2024-05-22 08:43:41 -07:00
Patrick Fic
f451155689 Add Firebase Service Worker to public. 2024-05-15 10:27:42 -07:00
Dave Richer
9f06e19346 Fix eslint regression
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-05-14 12:38:56 -04:00
Dave Richer
f4be3e9668 Fix issue with global search 2024-05-14 12:20:49 -04:00
Patrick Fic
ee613db0cb Merged in test-AIO (pull request #1443)
Resolve instance conflict for promanager.
2024-05-13 15:26:07 +00:00
Patrick Fic
55b892a74e Resolve instance conflict for promanager. 2024-05-13 08:25:45 -07:00
Patrick Fic
16754d9657 Merged in test-AIO (pull request #1442)
Test AIO
2024-05-10 22:36:17 +00:00
Patrick Fic
33db67122c Fix RBAC spread. 2024-05-10 15:35:26 -07:00
Patrick Fic
b9b88b0e23 Merged in release/AIO/2024-05-10 (pull request #1441)
Release/AIO/2024 05 10
2024-05-10 16:11:42 +00:00
Patrick Fic
992ad71910 Merge branch 'release/AIO/2024-05-10' into test-AIO 2024-05-10 08:33:31 -07:00
Patrick Fic
51d1f926c2 Improve spread for feature wrapped RBAC items. 2024-05-10 08:29:52 -07:00
Patrick Fic
c70447f337 Merge branch 'release/AIO/2024-05-10' into test-AIO 2024-05-10 08:08:32 -07:00
Patrick Fic
dc367e1a30 Add PAE Part Tax type for USA. 2024-05-10 08:08:15 -07:00
Patrick Fic
ee7997ffbc Merge branch 'release/AIO/2024-05-10' into test-AIO 2024-05-08 15:16:55 -07:00
Patrick Fic
2dbb5adbbb Merge branch 'feature/AIO/promanager' into release/AIO/2024-05-10 2024-05-08 15:16:47 -07:00
Patrick Fic
cc9f342575 Update expired page, remove RBAC, and prevent sign in. 2024-05-08 15:16:03 -07:00
Patrick Fic
252262f4a7 Merge branch 'master-AIO' into feature/AIO/promanager 2024-05-08 14:21:55 -07:00
Patrick Fic
f77a16648f Merged in release/AIO/2024-04-26 (pull request #1440)
Release/AIO/2024 04 26
2024-05-03 21:42:14 +00:00
Patrick Fic
d6e3c54b68 Merge branch 'release/AIO/2024-04-26' into test-AIO 2024-05-03 14:38:38 -07:00
Patrick Fic
7294e96a77 Merge branch 'release/2024-04-26' into release/AIO/2024-04-26 2024-05-03 14:38:04 -07:00
Allan Carr
a4f4f45251 Merged in feature/IO-2717-Paint-Material-from-Scale-for-CDK (pull request #1438)
IO-2717 CDK Use Scale Data for Paint Materials Cost
2024-05-02 05:53:35 +00:00
Allan Carr
acc91abc0c IO-2717 CDK Use Scale Data for Paint Materials Cost
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-05-01 13:26:24 -07:00
Patrick Fic
18d5176cb9 Merge branch 'release/2024-04-26' into release/AIO/2024-04-26 2024-05-01 11:49:09 -07:00
Patrick Fic
d6d6ced7a4 CC modifications. 2024-05-01 11:47:42 -07:00
Patrick Fic
52809cc849 Merge branch 'release/AIO/2024-04-26' into test-AIO 2024-04-29 12:10:03 -07:00
Patrick Fic
c525b7ea3f Place featurewrapped on bills in job line expander. 2024-04-29 12:06:42 -07:00
Patrick Fic
e799417aaf Remove header for partner interaction to comply with CORS. 2024-04-29 10:50:18 -07:00
Patrick Fic
ef077c2d48 Add render manager for part price changes. 2024-04-29 10:33:34 -07:00
Patrick Fic
e78b114544 Merge branch 'release/AIO/2024-04-26' into test-AIO 2024-04-26 08:18:25 -07:00
Patrick Fic
df170ddd27 Merge branch 'release/2024-04-26' into release/AIO/2024-04-26 2024-04-25 17:24:59 -07:00
Patrick Fic
17928741e3 Merge branch 'feature/IO-2766-intellipay-postback-refactor' into release/2024-04-26 2024-04-25 16:44:55 -07:00
Patrick Fic
a6b825ffdf Move intellipay to server side processing. 2024-04-25 16:43:49 -07:00
Patrick Fic
0d3bc0e95f Merge branch 'hotfix/AIO/2024-04-24' into release/AIO/2024-04-26 2024-04-25 08:19:00 -07:00
Patrick Fic
0f62a2d73d Merge branch 'hotfix/AIO/2024-04-24' into master-AIO 2024-04-24 11:13:22 -07:00
Patrick Fic
bcdd32f92f Merge branch 'hotfix/AIO/2024-04-24' into test-AIO 2024-04-24 10:52:18 -07:00
Patrick Fic
6865fcbffc Missing import instance 2024-04-24 10:51:09 -07:00
Patrick Fic
8e623c71a9 Merge branch 'hotfix/AIO/2024-04-24' into test-AIO 2024-04-24 10:17:40 -07:00
Patrick Fic
1e06502464 Correct instance manager for job totals in other files. 2024-04-24 10:17:28 -07:00
Allan Carr
f2914868e2 Merged in feature/IO-2761-Actual-Complete-in-Vehicle-Owner-Details (pull request #1434)
IO-2761 Actual Completion in Vehicle and Owners Job Details lists

Approved-by: Dave Richer
2024-04-23 17:40:10 +00:00
Allan Carr
561f0313f9 Merged in feature/IO-2762-Return-From-Bill-Reference (pull request #1435)
IO-2762 Return from Bill Reference in Parts Return Drawer

Approved-by: Dave Richer
2024-04-23 17:39:27 +00:00
Allan Carr
1e27c4e1ae Merged in feature/IO-2763-Job-Action-Button (pull request #1436)
IO-2763 Job Action Button

Approved-by: Dave Richer
2024-04-23 17:38:28 +00:00
Allan Carr
02a49efbea IO-2762 Return from Bill Reference in Parts Return Drawer
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-22 14:14:54 -07:00
Patrick Fic
5f2a5e1025 Merge branch 'release/AIO/2024-04-26' into test-AIO 2024-04-22 14:03:50 -07:00
Patrick Fic
42552e4f4f Promanager resolution & remove product fruit dev items. 2024-04-22 14:03:29 -07:00
Patrick Fic
f2af78f056 Merge branch 'release/AIO/2024-04-26' into test-AIO 2024-04-22 13:15:11 -07:00
Patrick Fic
269d55bde3 Add missing translations. 2024-04-22 13:14:58 -07:00
Patrick Fic
71f3dbbeb4 Merge branch 'release/AIO/2024-04-26' into test-AIO 2024-04-22 12:39:53 -07:00
Patrick Fic
73dfb74ed7 ProManager instance server updates. 2024-04-22 12:39:30 -07:00
Patrick Fic
7dd6baef33 Hardcore generic template for ProManager. 2024-04-22 11:28:49 -07:00
Patrick Fic
519b532091 Add download messages for partner. 2024-04-22 11:28:36 -07:00
Allan Carr
4fb9c37c0d IO-2761 Actual Completion in Vehicle and Owners Job Details lists
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-22 09:15:59 -07:00
Patrick Fic
c9b63be29f Hardcore generic template for ProManager. 2024-04-22 08:43:41 -07:00
47 changed files with 13826 additions and 11514 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,56 @@
// Scripts for firebase and firebase messaging
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js");
// Initialize the Firebase app in the service worker by passing the generated config
let firebaseConfig;
switch (this.location.hostname) {
case "localhost":
firebaseConfig = {
apiKey: "AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc",
authDomain: "imex-dev.firebaseapp.com",
databaseURL: "https://imex-dev.firebaseio.com",
projectId: "imex-dev",
storageBucket: "imex-dev.appspot.com",
messagingSenderId: "759548147434",
appId: "1:759548147434:web:e8239868a48ceb36700993",
measurementId: "G-K5XRBVVB4S",
};
break;
case "test.imex.online":
firebaseConfig = {
apiKey: "AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c",
authDomain: "imex-test.firebaseapp.com",
projectId: "imex-test",
storageBucket: "imex-test.appspot.com",
messagingSenderId: "991923618608",
appId: "1:991923618608:web:633437569cdad78299bef5",
// measurementId: "${config.measurementId}",
};
break;
case "imex.online":
default:
firebaseConfig = {
apiKey: "AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU",
authDomain: "imex-prod.firebaseapp.com",
databaseURL: "https://imex-prod.firebaseio.com",
projectId: "imex-prod",
storageBucket: "imex-prod.appspot.com",
messagingSenderId: "253497221485",
appId: "1:253497221485:web:3c81c483b94db84b227a64",
measurementId: "G-NTWBKG2L0M",
};
}
firebase.initializeApp(firebaseConfig);
// Retrieve firebase messaging
const messaging = firebase.messaging();
messaging.onBackgroundMessage(function (payload) {
// Customize notification here
const channel = new BroadcastChannel("imex-sw-messages");
channel.postMessage(payload);
//self.registration.showNotification(notificationTitle, notificationOptions);
});

View File

@@ -150,7 +150,7 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
<ProductFruits
workspaceCode={InstanceRenderMgr({
imex: null,
rome: null,
rome: "9BkbEseqNqxw8jUH",
promanager: "aoJoEifvezYI0Z0P"
})}
debug

View File

@@ -25,31 +25,27 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
);
}}
notFoundContent={"Removed."}
{...restProps}
>
<Select.Option key={null} value={"noline"} cost={0} line_desc={""}>
{t("billlines.labels.other")}
</Select.Option>
{options
? options.map((item) => (
<Option
disabled={allowRemoved ? false : item.removed}
key={item.id}
value={item.id}
cost={item.act_price ? item.act_price : 0}
part_type={item.part_type}
line_desc={item.line_desc}
part_qty={item.part_qty}
oem_partno={item.oem_partno}
alt_partno={item.alt_partno}
act_price={item.act_price}
style={{
...(item.removed ? { textDecoration: "line-through" } : {})
}}
name={`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
>
options={[
{ value: "noline", label: t("billlines.labels.other"), name: t("billlines.labels.other") },
...options.map((item) => ({
disabled: allowRemoved ? false : item.removed,
key: item.id,
value: item.id,
cost: item.act_price ? item.act_price : 0,
part_type: item.part_type,
line_desc: item.line_desc,
part_qty: item.part_qty,
oem_partno: item.oem_partno,
alt_partno: item.alt_partno,
act_price: item.act_price,
style: {
...(item.removed ? { textDecoration: "line-through" } : {})
},
name: `${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim(),
label: (
<>
<span>
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
@@ -60,14 +56,15 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
<span style={{ float: "right", paddingleft: "1rem" }}>{`${item.mod_lb_hrs} units`}</span>
)
})}
<span style={{ float: "right", paddingleft: "1rem" }}>
{item.act_price ? `$${item.act_price && item.act_price.toFixed(2)}` : ``}
</span>
</Option>
))
: null}
</Select>
</>
)
}))
]}
{...restProps}
></Select>
);
};
export default forwardRef(BillLineSearchSelect);

View File

@@ -1,14 +1,12 @@
import { DeleteFilled } from "@ant-design/icons";
import { useLazyQuery, useMutation } from "@apollo/client";
import { Button, Card, Col, Form, Input, notification, Row, Space, Spin, Statistic } from "antd";
import { Button, Card, Col, Form, Input, Row, Space, Spin, Statistic, notification } from "antd";
import axios from "axios";
import dayjs from "../../utils/day";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_PAYMENT_RESPONSE, QUERY_RO_AND_OWNER_BY_JOB_PKS } from "../../graphql/payment_response.queries";
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectCardPayment } from "../../redux/modals/modals.selectors";
@@ -28,12 +26,12 @@ const mapDispatchToProps = (dispatch) => ({
});
const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisible, insertAuditTrail }) => {
const { context } = cardPaymentModal;
const { context, actions } = cardPaymentModal;
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
// const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
const { t } = useTranslation();
@@ -42,7 +40,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
skip: true
});
console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data);
//Initialize the intellipay window.
const SetIntellipayCallbackFunctions = () => {
console.log("*** Set IntelliPay callback functions.");
@@ -51,16 +48,20 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
});
window.intellipay.runOnApproval(async function (response) {
console.warn("*** Running On Approval Script ***");
form.setFieldValue("paymentResponse", response);
form.submit();
//2024-04-25: Nothing is going to happen here anymore. We'll completely rely on the callback.
//Add a slight delay to allow the refetch to properly get the data.
setTimeout(() => {
if (actions && actions.refetch && typeof actions.refetch === "function")
actions.refetch();
setLoading(false);
toggleModalVisible();
}, 750);
});
window.intellipay.runOnNonApproval(async function (response) {
// Mutate unsuccessful payment
const { payments } = form.getFieldsValue();
await insertPaymentResponse({
variables: {
paymentResponse: payments.map((payment) => ({
@@ -85,50 +86,9 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
});
};
const handleFinish = async (values) => {
try {
await insertPayment({
variables: {
paymentInput: values.payments.map((payment) => ({
amount: payment.amount,
transactionid: (values.paymentResponse.paymentid || "").toString(),
payer: t("payments.labels.customer"),
type: values.paymentResponse.cardbrand,
jobid: payment.jobid,
date: dayjs(Date.now()),
payment_responses: {
data: [
{
amount: payment.amount,
bodyshopid: bodyshop.id,
jobid: payment.jobid,
declinereason: values.paymentResponse.declinereason,
ext_paymentid: values.paymentResponse.paymentid.toString(),
successful: true,
response: values.paymentResponse
}
]
}
}))
},
refetchQueries: ["GET_JOB_BY_PK"]
});
toggleModalVisible();
} catch (error) {
console.error(error);
notification.open({
type: "error",
message: t("payments.errors.inserting", { error: error.message })
});
} finally {
setLoading(false);
}
};
const handleIntelliPayCharge = async () => {
setLoading(true);
//Validate
try {
await form.validateFields();
@@ -140,7 +100,8 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
try {
const response = await axios.post("/intellipay/lightbox_credentials", {
bodyshop,
refresh: !!window.intellipay
refresh: !!window.intellipay,
paymentSplitMeta: form.getFieldsValue(),
});
if (window.intellipay) {
@@ -169,7 +130,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
<Card title="Card Payment">
<Spin spinning={loading}>
<Form
onFinish={handleFinish}
form={form}
layout="vertical"
initialValues={{
@@ -246,18 +206,14 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
}
>
{() => {
console.log("Updating the owner info section.");
//If all of the job ids have been fileld in, then query and update the IP field.
const { payments } = form.getFieldsValue();
if (payments?.length > 0 && payments?.filter((p) => p?.jobid).length === payments?.length) {
console.log("**Calling refetch.");
if (
payments?.length > 0 &&
payments?.filter((p) => p?.jobid).length === payments?.length
) {
refetch({ jobids: payments.map((p) => p.jobid) });
}
console.log(
"Acc info",
data,
payments && data && data.jobs.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null
);
return (
<>
<Input
@@ -300,6 +256,13 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
type="hidden"
value={totalAmountToCharge?.toFixed(2)}
/>
<Input
className="ipayfield"
data-ipayname="comment"
type="hidden"
value={btoa(JSON.stringify(payments))}
hidden
/>
<Button
type="primary"
// data-ipayname="submit"
@@ -314,11 +277,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
);
}}
</Form.Item>
{/* Lightbox payment response when it is completed */}
<Form.Item name="paymentResponse" hidden>
<Input type="hidden" />
</Form.Item>
</Form>
</Spin>
</Card>

View File

@@ -28,6 +28,7 @@ const CourtesyCarStatusComponent = ({ value, onChange }, ref) => {
<Option value="courtesycars.status.out">{t("courtesycars.status.out")}</Option>
<Option value="courtesycars.status.sold">{t("courtesycars.status.sold")}</Option>
<Option value="courtesycars.status.leasereturn">{t("courtesycars.status.leasereturn")}</Option>
<Option value="courtesycars.status.unavailable">{t("courtesycars.status.unavailable")}</Option>
</Select>
);
};

View File

@@ -61,7 +61,11 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
{
text: t("courtesycars.status.leasereturn"),
value: "courtesycars.status.leasereturn"
}
},
{
text: t("courtesycars.status.unavailable"),
value: "courtesycars.status.unavailable",
},
],
onFilter: (value, record) => record.status === value,
sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order,

View File

@@ -3,14 +3,13 @@ import axios from "axios";
import _ from "lodash";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";
import { Link } from "react-router-dom";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearchOs() {
const { t } = useTranslation();
const history = useNavigate();
const [loading, setLoading] = useState(false);
const [data, setData] = useState(false);
@@ -178,15 +177,7 @@ export default function GlobalSearchOs() {
};
return (
<AutoComplete
options={data}
onSearch={handleSearch}
defaultActiveFirstOption
onSelect={(val, opt) => {
history(opt.label.props.to);
}}
onClear={() => setData([])}
>
<AutoComplete options={data} onSearch={handleSearch} defaultActiveFirstOption onClear={() => setData([])}>
<Input.Search
size="large"
placeholder={t("general.labels.globalsearch")}

View File

@@ -3,7 +3,7 @@ import { AutoComplete, Divider, Input, Space } from "antd";
import _ from "lodash";
import React from "react";
import { useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";
import { Link } from "react-router-dom";
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import AlertComponent from "../alert/alert.component";
@@ -12,7 +12,6 @@ import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.compon
export default function GlobalSearch() {
const { t } = useTranslation();
const history = useNavigate();
const [callSearch, { loading, error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
const executeSearch = (v) => {
@@ -157,14 +156,7 @@ export default function GlobalSearch() {
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<AutoComplete
options={options}
onSearch={handleSearch}
defaultActiveFirstOption
onSelect={(val, opt) => {
history(opt.label.props.to);
}}
>
<AutoComplete options={options} onSearch={handleSearch} defaultActiveFirstOption>
<Input.Search
size="large"
placeholder={t("general.labels.globalsearch")}

View File

@@ -32,6 +32,7 @@ import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { BsKanban } from "react-icons/bs";
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
import { FiLogOut } from "react-icons/fi";
import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
import { IoBusinessOutline } from "react-icons/io5";
import { RiSurveyLine } from "react-icons/ri";
@@ -42,7 +43,6 @@ import { selectRecentItems, selectSelectedHeader } from "../../redux/application
import { setModalContext } from "../../redux/modals/modals.actions";
import { signOutStart } from "../../redux/user/user.actions";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { FiLogOut } from "react-icons/fi";
import { checkBeta, handleBeta, setBeta } from "../../utils/betaHandler";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
@@ -141,11 +141,13 @@ function Header({
accountingChildren.push(
{
key: "bills",
id: "header-accounting-bills",
icon: <Icon component={FaFileInvoiceDollar} />,
label: <Link to="/manage/bills">{t("menus.header.bills")}</Link>
},
{
key: "enterbills",
id: "header-accounting-enterbills",
icon: <Icon component={GiPayMoney} />,
label: t("menus.header.enterbills"),
onClick: () => {
@@ -165,6 +167,7 @@ function Header({
},
{
key: "inventory",
id: "header-accounting-inventory",
icon: <Icon component={FaFileInvoiceDollar} />,
label: <Link to="/manage/inventory">{t("menus.header.inventory")}</Link>
}
@@ -183,11 +186,13 @@ function Header({
},
{
key: "allpayments",
id: "header-accounting-allpayments",
icon: <BankFilled />,
label: <Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
},
{
key: "enterpayments",
id: "header-accounting-enterpayments",
icon: <Icon component={FaCreditCard} />,
label: t("menus.header.enterpayment"),
onClick: () => {
@@ -203,6 +208,7 @@ function Header({
if (ImEXPay.treatment === "on") {
accountingChildren.push({
key: "entercardpayments",
id: "header-accounting-entercardpayments",
icon: <Icon component={FaCreditCard} />,
label: t("menus.header.entercardpayment"),
onClick: () => {
@@ -227,6 +233,7 @@ function Header({
},
{
key: "timetickets",
id: "header-accounting-timetickets",
icon: <FieldTimeOutlined />,
label: <Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link>
}
@@ -235,6 +242,7 @@ function Header({
if (bodyshop?.md_tasks_presets?.use_approvals) {
accountingChildren.push({
key: "ttapprovals",
id: "header-accounting-ttapprovals",
icon: <FieldTimeOutlined />,
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
});
@@ -244,6 +252,7 @@ function Header({
key: "entertimetickets",
icon: <Icon component={GiPlayerTime} />,
label: t("menus.header.entertimeticket"),
id: "header-accounting-entertimetickets",
onClick: () => {
setTimeTicketContext({
actions: {},
@@ -264,6 +273,7 @@ function Header({
const accountingExportChildren = [
{
key: "receivables",
id: "header-accounting-receivables",
label: <Link to="/manage/accounting/receivables">{t("menus.header.accounting-receivables")}</Link>
}
];
@@ -271,6 +281,7 @@ function Header({
if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) || DmsAp.treatment === "on") {
accountingExportChildren.push({
key: "payables",
id: "header-accounting-payables",
label: <Link to="/manage/accounting/payables">{t("menus.header.accounting-payables")}</Link>
});
}
@@ -278,6 +289,7 @@ function Header({
if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber))) {
accountingExportChildren.push({
key: "payments",
id: "header-accounting-payments",
label: <Link to="/manage/accounting/payments">{t("menus.header.accounting-payments")}</Link>
});
}
@@ -288,6 +300,7 @@ function Header({
},
{
key: "exportlogs",
id: "header-accounting-exportlogs",
label: <Link to="/manage/accounting/exportlogs">{t("menus.header.export-logs")}</Link>
}
);
@@ -301,6 +314,7 @@ function Header({
) {
accountingChildren.push({
key: "accountingexport",
id: "header-accounting-export",
icon: <ExportOutlined />,
label: t("menus.header.export"),
children: accountingExportChildren
@@ -604,14 +618,22 @@ function Header({
);
}
},
// {
// key: 'rescue',
// icon: <Icon component={CarFilled}/>,
// label: t("menus.header.rescueme"),
// onClick: () => {
// window.open("https://imexrescue.com/", "_blank");
// }
// },
...(InstanceRenderManager({
imex: true,
rome: false,
promanager: false
})
? [
{
key: "rescue",
icon: <Icon component={CarFilled} />,
label: t("menus.header.rescueme"),
onClick: () => {
window.open("https://imexrescue.com/", "_blank");
}
}
]
: []),
...(InstanceRenderManager({
imex: true,

View File

@@ -12,6 +12,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { QUERY_JOBLINE_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
import TaskListContainer from "../task-list/task-list.container.jsx";
import FeatureWrapper from "../feature-wrapper/feature-wrapper.component.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -98,55 +99,59 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
</Row>
)
}))
: {
key: "dispatch-lines",
children: t("parts_orders.labels.notyetordered")
}
}
/>
</Col>
<Col md={24} lg={8}>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Timeline
items={
data.billlines.length > 0
? data.billlines.map((line) => ({
key: line.id,
children: (
<Row wrap>
<Col span={4}>
<Link to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}>
{line.bill.invoice_number}
</Link>
</Col>
<Col span={4}>
<span>
{`${t("billlines.fields.actual_price")}: `}
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter>
</span>
</Col>
<Col span={4}>
<span>
{`${t("billlines.fields.actual_cost")}: `}
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
</span>
</Col>
<Col span={4}>
<DateFormatter>{line.bill.date}</DateFormatter>
</Col>
<Col span={4}> {line.bill.vendor.name}</Col>
</Row>
)
}))
: [
{
key: "no-orders",
children: t("bills.labels.nobilllines")
key: "dispatch-lines",
children: t("parts_dispatch.labels.notyetdispatched")
}
]
}
/>
</Col>
<FeatureWrapper featureName="bills" noauth={() => null}>
<Col md={24} lg={8}>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Timeline
items={
data.billlines.length > 0
? data.billlines.map((line) => ({
key: line.id,
children: (
<Row wrap>
<Col span={4}>
<Link to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}>
{line.bill.invoice_number}
</Link>
</Col>
<Col span={4}>
<span>
{`${t("billlines.fields.actual_price")}: `}
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter>
</span>
</Col>
<Col span={4}>
<span>
{`${t("billlines.fields.actual_cost")}: `}
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
</span>
</Col>
<Col span={4}>
<DateFormatter>{line.bill.date}</DateFormatter>
</Col>
<Col span={4}> {line.bill.vendor.name}</Col>
</Row>
)
}))
: [
{
key: "no-orders",
children: t("bills.labels.nobilllines")
}
]
}
/>
</Col>
</FeatureWrapper>
<Col md={24} lg={24}>
<TaskListContainer
parentJobId={jobid}

View File

@@ -43,6 +43,7 @@ import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.con
import JobLinesExpander from "./job-lines-expander.component";
import JobLinesPartPriceChange from "./job-lines-part-price-change.component";
import { FaTasks } from "react-icons/fa";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -552,7 +553,7 @@ export function JobLinesComponent({
>
{t("joblines.actions.new")}
</Button>
{bodyshop.region_config.toLowerCase().startsWith("us") && <JobSendPartPriceChangeComponent job={job} />}
{InstanceRenderManager({ rome: <JobSendPartPriceChangeComponent job={job} /> })}
<JobCreateIOU job={job} selectedJobLines={selectedLines} />
<Input.Search
placeholder={t("general.labels.search")}

View File

@@ -1,7 +1,7 @@
import { DownCircleFilled } from "@ant-design/icons";
import { useApolloClient, useMutation } from "@apollo/client";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Button, Card, Dropdown, Form, Input, Modal, Popconfirm, Popover, Select, Space, notification } from "antd";
import { Button, Card, Dropdown, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } from "antd";
import axios from "axios";
import parsePhoneNumber from "libphonenumber-js";
import React, { useMemo, useState } from "react";
@@ -641,6 +641,7 @@ export function JobsDetailHeaderActions({
const menuItems = [
{
key: "schedule",
id: "job-actions-schedule",
disabled: !jobInPreProduction || !job.converted || jobRO,
label: t("jobs.actions.schedule"),
onClick: () => {
@@ -657,6 +658,7 @@ export function JobsDetailHeaderActions({
},
{
key: "cancelallappointments",
id: "job-actions-cancelallappointments",
onClick: () => {
if (job.status !== bodyshop.md_ro_statuses.default_scheduled) {
return;
@@ -670,6 +672,7 @@ export function JobsDetailHeaderActions({
imex: [
{
key: "intake",
id: "job-actions-intake",
disabled: !!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO,
label:
!!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO ? (
@@ -680,6 +683,7 @@ export function JobsDetailHeaderActions({
},
{
key: "deliver",
id: "job-actions-deliver",
disabled: !jobInProduction || jobRO,
label: !jobInProduction ? (
t("jobs.actions.deliver")
@@ -689,6 +693,7 @@ export function JobsDetailHeaderActions({
},
{
key: "checklist",
id: "job-actions-checklist",
disabled: !job.converted,
label: <Link to={`/manage/jobs/${job.id}/checklist`}>{t("jobs.actions.viewchecklist")}</Link>
}
@@ -697,6 +702,7 @@ export function JobsDetailHeaderActions({
promanager: [
{
key: "toggleproduction",
id: "job-actions-toggleproduction",
disabled: !job.converted || jobRO,
label: <JobsDetailHeaderActionsToggleProduction job={job} refetch={refetch} />
}
@@ -710,6 +716,7 @@ export function JobsDetailHeaderActions({
? [
{
key: "entertimetickets",
id: "job-actions-entertimetickets",
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
label: t("timetickets.actions.enter"),
onClick: () => {
@@ -733,6 +740,7 @@ export function JobsDetailHeaderActions({
if (bodyshop.md_tasks_presets.enable_tasks) {
menuItems.push({
key: "claimtimetickettasks",
id: "job-actions-claimtimetickettasks",
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
onClick: () => {
setTimeTicketTaskContext({
@@ -746,6 +754,7 @@ export function JobsDetailHeaderActions({
menuItems.push({
key: "enterpayments",
id: "job-actions-enterpayments",
disabled: !job.converted,
label: t("menus.header.enterpayment"),
onClick: () => {
@@ -761,13 +770,14 @@ export function JobsDetailHeaderActions({
if (ImEXPay.treatment === "on") {
menuItems.push({
key: "entercardpayments",
id: "job-actions-entercardpayments",
disabled: !job.converted,
label: t("menus.header.entercardpayment"),
onClick: () => {
logImEXEvent("job_header_enter_card_payment");
setCardPaymentContext({
actions: {},
actions: { refetch },
context: { jobid: job.id }
});
}
@@ -777,6 +787,7 @@ export function JobsDetailHeaderActions({
if (HasFeatureAccess({ featureName: "courtesycars", bodyshop })) {
menuItems.push({
key: "cccontract",
id: "job-actions-cccontract",
disabled: jobRO || !job.converted,
label: (
<Link state={{ jobId: job.id }} to="/manage/courtesycars/contracts/new">
@@ -788,6 +799,7 @@ export function JobsDetailHeaderActions({
menuItems.push({
key: "createtask",
id: "job-actions-createtask",
label: t("menus.header.create_task"),
onClick: () =>
setTaskUpsertContext({
@@ -800,12 +812,14 @@ export function JobsDetailHeaderActions({
job.inproduction
? {
key: "removefromproduction",
id: "job-actions-removefromproduction",
disabled: !job.converted,
label: t("jobs.actions.removefromproduction"),
onClick: () => AddToProduction(client, job.id, refetch, true)
}
: {
key: "addtoproduction",
id: "job-actions-addtoproduction",
disabled: !job.converted,
label: t("jobs.actions.addtoproduction"),
onClick: () => AddToProduction(client, job.id, refetch)
@@ -815,12 +829,14 @@ export function JobsDetailHeaderActions({
menuItems.push(
{
key: "togglesuspend",
id: "job-actions-togglesuspend",
onClick: handleSuspend,
label: job.suspended ? t("production.actions.unsuspend") : t("production.actions.suspend")
},
{
key: "toggleAlert",
onClick: handleAlertToggle,
id: "job-actions-togglealert",
label:
job.production_vars && job.production_vars.alert
? t("production.labels.alertoff")
@@ -832,6 +848,7 @@ export function JobsDetailHeaderActions({
children: [
{
key: "duplicate",
id: "job-actions-duplicate",
label: (
<Popconfirm
title={t("jobs.labels.duplicateconfirm")}
@@ -847,6 +864,7 @@ export function JobsDetailHeaderActions({
},
{
key: "duplicatenolines",
id: "job-actions-duplicatenolines",
label: (
<Popconfirm
title={t("jobs.labels.duplicateconfirm")}
@@ -870,6 +888,7 @@ export function JobsDetailHeaderActions({
? [
{
key: "postbills",
id: "job-actions-postbills",
disabled: !job.converted,
label: t("jobs.actions.postbills"),
onClick: () => {
@@ -888,6 +907,7 @@ export function JobsDetailHeaderActions({
{
key: "addtopartsqueue",
id: "job-actions-addtopartsqueue",
disabled: !job.converted || !jobInProduction || jobRO,
label: t("jobs.actions.addtopartsqueue"),
onClick: async () => {
@@ -913,6 +933,7 @@ export function JobsDetailHeaderActions({
},
{
key: "closejob",
id: "job-actions-closejob",
disabled: !jobInPostProduction,
label: !jobInPostProduction ? (
t("menus.jobsactions.closejob")
@@ -928,6 +949,7 @@ export function JobsDetailHeaderActions({
},
{
key: "admin",
id: "job-actions-admin",
label: (
<Link
to={{
@@ -949,6 +971,7 @@ export function JobsDetailHeaderActions({
) {
menuItems.push({
key: "exportcustdata",
id: "job-actions-exportcustdata",
disabled: !job.converted,
label: t("jobs.actions.exportcustdata"),
onClick: handleExportCustData
@@ -959,18 +982,21 @@ export function JobsDetailHeaderActions({
const children = [
{
key: "email",
id: "job-actions-email",
disabled: !!!job.ownr_ea,
label: t("general.labels.email"),
onClick: handleCreateCsi
},
{
key: "text",
id: "job-actions-text",
disabled: !!!job.ownr_ph1,
label: t("general.labels.text"),
onClick: handleCreateCsi
},
{
key: "generate",
id: "job-actions-generate",
disabled: job.csiinvites && job.csiinvites.length > 0,
label: t("jobs.actions.generatecsi"),
onClick: handleCreateCsi
@@ -1004,6 +1030,7 @@ export function JobsDetailHeaderActions({
}
menuItems.push({
key: "sendcsi",
id: "job-actions-sendcsi",
label: t("jobs.actions.sendcsi"),
disabled: !job.converted,
children
@@ -1012,6 +1039,7 @@ export function JobsDetailHeaderActions({
menuItems.push({
key: "jobcosting",
id: "job-actions-jobcosting",
disabled: !job.converted,
label: t("jobs.labels.jobcosting"),
onClick: () => {
@@ -1029,6 +1057,7 @@ export function JobsDetailHeaderActions({
if (job && !job.converted) {
menuItems.push({
key: "deletejob",
id: "job-actions-deletejob",
label: (
<Popconfirm
title={t("jobs.labels.deleteconfirm")}
@@ -1045,6 +1074,7 @@ export function JobsDetailHeaderActions({
menuItems.push({
key: "manualevent",
id: "job-actions-manualevent",
onClick: (e) => {
setVisibility(true);
},
@@ -1054,6 +1084,7 @@ export function JobsDetailHeaderActions({
if (!jobRO && job.converted) {
menuItems.push({
key: "voidjob",
id: "job-actions-voidjob",
label: (
<RbacWrapper action="jobs:void" noauth>
<Popconfirm

View File

@@ -18,8 +18,6 @@ import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import { setJoyRideSteps } from "../../redux/application/application.actions";
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
@@ -315,34 +313,6 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
title={t("titles.bc.jobs-active")}
extra={
<Space wrap>
{InstanceRenderManager({
promanager: (
<Button
onClick={() =>
setJoyRideSteps([
{
target: "#active-jobs-list",
content: "This is where you will see all work coming in and currently here."
},
{
target: "#header-jobs",
spotlightClicks: true,
disableOverlayClose: true,
content:
"The jobs menu lets you access additional pages to see more information. You can import new jobs, search all jobs, or manage your current production."
},
{
target: "#header-jobs-available",
content: "You can find jobs to import here."
}
])
}
>
Start Walk Through
</Button>
)
})}
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>

View File

@@ -6,7 +6,8 @@ import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, statusSort } from "../../utils/sorters";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component";
const mapStateToProps = createStructuredSelector({
@@ -75,7 +76,18 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
})),
onFilter: (value, record) => value.includes(record.status)
},
{
title: t("jobs.fields.actual_completion"),
dataIndex: "actual_completion",
key: "actual_completion",
render: (text, record) => (
<DateTimeFormatter>{record.actual_completion}</DateTimeFormatter>
),
sorter: (a, b) => dateSort(a.actual_completion, b.actual_completion),
sortOrder:
state.sortedInfo.columnKey === "actual_completion" &&
state.sortedInfo.order,
},
{
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",

View File

@@ -1,23 +1,24 @@
import { DeleteFilled, EyeFilled, SyncOutlined } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { useLazyQuery, useMutation } from "@apollo/client";
import { Button, Card, Checkbox, Drawer, Grid, Input, Popconfirm, Space, Table } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
import queryString from "query-string";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { QUERY_BILL_BY_PK } from "../../graphql/bills.queries";
import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters";
import DataLabel from "../data-label/data-label.component";
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component";
@@ -81,19 +82,46 @@ export function PartsOrderListTableComponent({
const [state, setState] = useState({
sortedInfo: {}
});
const [returnfrombill, setReturnFromBill] = useState();
const [billData, setBillData] = useState();
const search = queryString.parse(useLocation().search);
const selectedpartsorder = search.partsorderid;
const [searchText, setSearchText] = useState("");
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
const { refetch } = billsQuery;
useEffect(() => {
if (returnfrombill === null) {
setBillData(null);
} else {
const fetchData = async () => {
const result = await billQuery({
variables: { billid: returnfrombill }
});
setBillData(result.data);
};
fetchData();
}
}, [returnfrombill, billQuery]);
const recordActions = (record, showView = false) => (
<Space direction="horizontal" wrap>
{showView && (
<Button onClick={() => handleOnRowClick(record)}>
<Button
onClick={() => {
if (record.returnfrombill) {
setReturnFromBill(record.returnfrombill);
} else {
setReturnFromBill(null);
}
handleOnRowClick(record);
}}
>
<EyeFilled />
</Button>
)}
@@ -175,7 +203,7 @@ export function PartsOrderListTableComponent({
is_credit_memo: record.return,
billlines: record.parts_order_lines.map((pol) => {
return {
joblineid: pol.job_line_id,
joblineid: pol.job_line_id || "noline",
line_desc: pol.line_desc,
quantity: pol.quantity,
@@ -395,7 +423,14 @@ export function PartsOrderListTableComponent({
return (
<div>
<PageHeader title={record && `${record.vendor.name} - ${record.order_number}`} extra={recordActions(record)} />
<PageHeader
title={
billData
? `${record.vendor.name} - ${record.order_number} - ${t("bills.labels.returnfrombill")}: ${billData.bills_by_pk.invoice_number}`
: `${record.vendor.name} - ${record.order_number}`
}
extra={recordActions(record)}
/>
<Table
scroll={{
x: true //y: "50rem"

View File

@@ -119,8 +119,14 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
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
title={t("job_payments.titles.descriptions")}
contentStyle={{ fontWeight: "600" }}
column={4}
>
<Descriptions.Item label={t("job_payments.titles.hint")}>
{payment_response?.response?.methodhint}
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.payername")}>
{payment_response?.response?.nameOnCard ?? ""}
</Descriptions.Item>
@@ -132,7 +138,7 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
</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 ?? ""}
{payment_response?.ext_paymentid ?? ""}
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.paymenttype")}>{record.type}</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.paymentnum")}>{record.paymentnum}</Descriptions.Item>

View File

@@ -13,6 +13,7 @@ import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-forma
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import FeatureWrapper, { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
// TODO: Client Update, this might break
const timeZonesList = Intl.supportedValuesOf("timeZone");
const mapStateToProps = createStructuredSelector({
@@ -144,285 +145,289 @@ export function ShopInfoGeneral({ form, bodyshop }) {
<InputNumber min={0} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.accountingsetup")} id="accountingsetup">
<Form.Item label={t("bodyshop.labels.qbo")} valuePropName="checked" name={["accountingconfig", "qbo"]}>
<Switch />
</Form.Item>
{InstanceRenderManager({
imex: (
<Form.Item shouldUpdate noStyle>
{() => (
<FeatureWrapper featureName="export" noauth={() => null}>
<LayoutFormRow header={t("bodyshop.labels.accountingsetup")} id="accountingsetup">
<Form.Item label={t("bodyshop.labels.qbo")} valuePropName="checked" name={["accountingconfig", "qbo"]}>
<Switch />
</Form.Item>
{InstanceRenderManager({
imex: (
<Form.Item shouldUpdate noStyle>
{() => (
<Form.Item
label={t("bodyshop.labels.qbo_usa")}
shouldUpdate
valuePropName="checked"
name={["accountingconfig", "qbo_usa"]}
>
<Switch disabled={!form.getFieldValue(["accountingconfig", "qbo"])} />
</Form.Item>
)}
</Form.Item>
)
})}
<Form.Item label={t("bodyshop.labels.qbo_departmentid")} name={["accountingconfig", "qbo_departmentid"]}>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.labels.accountingtiers")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["accountingconfig", "tiers"]}
>
<Radio.Group>
<Radio value={2}>2</Radio>
<Radio value={3}>3</Radio>
</Radio.Group>
</Form.Item>
<Form.Item shouldUpdate>
{() => {
return (
<Form.Item
label={t("bodyshop.labels.qbo_usa")}
label={t("bodyshop.labels.2tiersetup")}
shouldUpdate
valuePropName="checked"
name={["accountingconfig", "qbo_usa"]}
rules={[
{
required: form.getFieldValue(["accountingconfig", "tiers"]) === 2
//message: t("general.validation.required"),
}
]}
name={["accountingconfig", "twotierpref"]}
>
<Switch disabled={!form.getFieldValue(["accountingconfig", "qbo"])} />
<Radio.Group disabled={form.getFieldValue(["accountingconfig", "tiers"]) === 3}>
<Radio value="name">{t("bodyshop.labels.2tiername")}</Radio>
<Radio value="source">{t("bodyshop.labels.2tiersource")}</Radio>
</Radio.Group>
</Form.Item>
)}
</Form.Item>
)
})}
<Form.Item label={t("bodyshop.labels.qbo_departmentid")} name={["accountingconfig", "qbo_departmentid"]}>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.labels.accountingtiers")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["accountingconfig", "tiers"]}
>
<Radio.Group>
<Radio value={2}>2</Radio>
<Radio value={3}>3</Radio>
</Radio.Group>
</Form.Item>
<Form.Item shouldUpdate>
{() => {
return (
);
}}
</Form.Item>
<Form.Item
label={t("bodyshop.labels.printlater")}
valuePropName="checked"
name={["accountingconfig", "printlater"]}
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.labels.emaillater")}
valuePropName="checked"
name={["accountingconfig", "emaillater"]}
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.inhousevendorid")}
name={"inhousevendorid"}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.default_adjustment_rate")}
name={"default_adjustment_rate"}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={2} />
</Form.Item>
{InstanceRenderManager({
imex: (
<Form.Item label={t("bodyshop.fields.federal_tax_id")} name="federal_tax_id">
<Input />
</Form.Item>
)
})}
<Form.Item label={t("bodyshop.fields.state_tax_id")} name="state_tax_id">
<Input />
</Form.Item>
{InstanceRenderManager({
imex: (
<Form.Item
label={t("bodyshop.labels.2tiersetup")}
shouldUpdate
label={t("bodyshop.fields.invoice_federal_tax_rate")}
name={["bill_tax_rates", "federal_tax_rate"]}
rules={[
{
required: form.getFieldValue(["accountingconfig", "tiers"]) === 2
required: true
//message: t("general.validation.required"),
}
]}
name={["accountingconfig", "twotierpref"]}
>
<Radio.Group disabled={form.getFieldValue(["accountingconfig", "tiers"]) === 3}>
<Radio value="name">{t("bodyshop.labels.2tiername")}</Radio>
<Radio value="source">{t("bodyshop.labels.2tiersource")}</Radio>
</Radio.Group>
<InputNumber />
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("bodyshop.labels.printlater")}
valuePropName="checked"
name={["accountingconfig", "printlater"]}
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.labels.emaillater")}
valuePropName="checked"
name={["accountingconfig", "emaillater"]}
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.inhousevendorid")}
name={"inhousevendorid"}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.default_adjustment_rate")}
name={"default_adjustment_rate"}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={2} />
</Form.Item>
{InstanceRenderManager({
imex: (
<Form.Item label={t("bodyshop.fields.federal_tax_id")} name="federal_tax_id">
<Input />
</Form.Item>
)
})}
<Form.Item label={t("bodyshop.fields.state_tax_id")} name="state_tax_id">
<Input />
</Form.Item>
{InstanceRenderManager({
imex: (
<Form.Item
label={t("bodyshop.fields.invoice_federal_tax_rate")}
name={["bill_tax_rates", "federal_tax_rate"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber />
</Form.Item>
)
})}
<Form.Item
label={t("bodyshop.fields.invoice_state_tax_rate")}
name={["bill_tax_rates", "state_tax_rate"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.invoice_local_tax_rate")}
name={["bill_tax_rates", "local_tax_rate"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber />
</Form.Item>
<Form.Item
name={["md_payment_types"]}
label={t("bodyshop.fields.md_payment_types")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select mode="tags" />
</Form.Item>
<Form.Item
name={["md_categories"]}
label={t("bodyshop.fields.md_categories")}
rules={[
{
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select mode="tags" />
</Form.Item>
<Form.Item name={["enforce_class"]} label={t("bodyshop.fields.enforce_class")} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField1"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 1 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField2"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 2 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField3"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 3 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["md_classes"]}
label={t("bodyshop.fields.md_classes")}
rules={[
({ getFieldValue }) => {
return {
required: getFieldValue("enforce_class"),
)
})}
<Form.Item
label={t("bodyshop.fields.invoice_state_tax_rate")}
name={["bill_tax_rates", "state_tax_rate"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.invoice_local_tax_rate")}
name={["bill_tax_rates", "local_tax_rate"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber />
</Form.Item>
<Form.Item
name={["md_payment_types"]}
label={t("bodyshop.fields.md_payment_types")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array"
};
}
]}
>
<Select mode="tags" />
</Form.Item>
{ClosingPeriod.treatment === "on" && (
<>
<Form.Item
name={["accountingconfig", "ClosingPeriod"]}
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
>
<DatePicker.RangePicker format="MM/DD/YYYY" presets={DatePickerRanges} />
</Form.Item>
</>
)}
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.scoreboardsetup")} id="scoreboardsetup">
<Form.Item
label={t("bodyshop.fields.dailypainttarget")}
name={["scoreboard_target", "dailyPaintTarget"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={0} />
</Form.Item>
}
]}
>
<Select mode="tags" />
</Form.Item>
<Form.Item
name={["md_categories"]}
label={t("bodyshop.fields.md_categories")}
rules={[
{
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select mode="tags" />
</Form.Item>
<Form.Item name={["enforce_class"]} label={t("bodyshop.fields.enforce_class")} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField1"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 1 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField2"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 2 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField3"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 3 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["md_classes"]}
label={t("bodyshop.fields.md_classes")}
rules={[
({ getFieldValue }) => {
return {
required: getFieldValue("enforce_class"),
//message: t("general.validation.required"),
type: "array"
};
}
]}
>
<Select mode="tags" />
</Form.Item>
{ClosingPeriod.treatment === "on" && (
<>
<Form.Item
name={["accountingconfig", "ClosingPeriod"]}
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
>
<DatePicker.RangePicker format="MM/DD/YYYY" presets={DatePickerRanges} />
</Form.Item>
</>
)}
</LayoutFormRow>
</FeatureWrapper>
<FeatureWrapper featureName="scoreboard" noauth={() => null}>
<LayoutFormRow header={t("bodyshop.labels.scoreboardsetup")} id="scoreboardsetup">
<Form.Item
label={t("bodyshop.fields.dailypainttarget")}
name={["scoreboard_target", "dailyPaintTarget"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={0} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.dailybodytarget")}
name={["scoreboard_target", "dailyBodyTarget"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={0} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.dailybodytarget")}
name={["scoreboard_target", "dailyBodyTarget"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={0} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.lastnumberworkingdays")}
name={["scoreboard_target", "lastNumberWorkingDays"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} max={12} precision={0} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.ignoreblockeddays")}
name={["scoreboard_target", "ignoreblockeddays"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.prodtargethrs")}
name={["prodtargethrs"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={1} precision={1} />
</Form.Item>
</LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.lastnumberworkingdays")}
name={["scoreboard_target", "lastNumberWorkingDays"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} max={12} precision={0} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.ignoreblockeddays")}
name={["scoreboard_target", "ignoreblockeddays"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.prodtargethrs")}
name={["prodtargethrs"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={1} precision={1} />
</Form.Item>
</LayoutFormRow>
</FeatureWrapper>
<LayoutFormRow header={t("bodyshop.labels.systemsettings")} id="systemsettings">
<Form.Item
name={["md_referral_sources"]}
@@ -567,27 +572,32 @@ export function ShopInfoGeneral({ form, bodyshop }) {
>
<Select mode="tags" />
</Form.Item>
<Form.Item
name={["tt_allow_post_to_invoiced"]}
label={t("bodyshop.fields.tt_allow_post_to_invoiced")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["tt_enforce_hours_for_tech_console"]}
label={t("bodyshop.fields.tt_enforce_hours_for_tech_console")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["bill_allow_post_to_closed"]}
label={t("bodyshop.fields.bill_allow_post_to_closed")}
valuePropName="checked"
>
<Switch />
</Form.Item>
{HasFeatureAccess({ featureName: "timetickets", bodyshop }) && (
<>
<Form.Item
name={["tt_allow_post_to_invoiced"]}
label={t("bodyshop.fields.tt_allow_post_to_invoiced")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["tt_enforce_hours_for_tech_console"]}
label={t("bodyshop.fields.tt_enforce_hours_for_tech_console")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["bill_allow_post_to_closed"]}
label={t("bodyshop.fields.bill_allow_post_to_closed")}
valuePropName="checked"
>
<Switch />
</Form.Item>
</>
)}
<Form.Item
name={["md_ded_notes"]}
label={t("bodyshop.fields.md_ded_notes")}
@@ -1042,111 +1052,118 @@ export function ShopInfoGeneral({ form, bodyshop }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.fields.md_ccc_rates")} id="md_ccc_rates">
<Form.List name={["md_ccc_rates"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
<Input />
</Form.Item>
<Form.Item label={t("contracts.fields.actax")} key={`${index}actax`} name={[field.name, "actax"]}>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.dailyfreekm")}
key={`${index}dailyfreekm`}
name={[field.name, "dailyfreekm"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.refuelcharge")}
key={`${index}refuelcharge`}
name={[field.name, "refuelcharge"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.excesskmrate")}
key={`${index}excesskmrate`}
name={[field.name, "excesskmrate"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.cleanupcharge")}
key={`${index}cleanupcharge`}
name={[field.name, "cleanupcharge"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.damagewaiver")}
key={`${index}damagewaiver`}
name={[field.name, "damagewaiver"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.federaltax")}
key={`${index}federaltax`}
name={[field.name, "federaltax"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.statetax")}
key={`${index}statetax`}
name={[field.name, "statetax"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.localtax")}
key={`${index}localtax`}
name={[field.name, "localtax"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.coverage")}
key={`${index}coverage`}
name={[field.name, "coverage"]}
>
<InputNumber precision={2} />
</Form.Item>
<FeatureWrapper featureName="courtesycars" noauth={() => null}>
<LayoutFormRow grow header={t("bodyshop.fields.md_ccc_rates")} id="md_ccc_rates">
<Form.List name={["md_ccc_rates"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
<Input />
</Form.Item>
<Form.Item
label={t("contracts.fields.actax")}
key={`${index}actax`}
name={[field.name, "actax"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.dailyfreekm")}
key={`${index}dailyfreekm`}
name={[field.name, "dailyfreekm"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.refuelcharge")}
key={`${index}refuelcharge`}
name={[field.name, "refuelcharge"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.excesskmrate")}
key={`${index}excesskmrate`}
name={[field.name, "excesskmrate"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.cleanupcharge")}
key={`${index}cleanupcharge`}
name={[field.name, "cleanupcharge"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.damagewaiver")}
key={`${index}damagewaiver`}
name={[field.name, "damagewaiver"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.federaltax")}
key={`${index}federaltax`}
name={[field.name, "federaltax"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.statetax")}
key={`${index}statetax`}
name={[field.name, "statetax"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.localtax")}
key={`${index}localtax`}
name={[field.name, "localtax"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.coverage")}
key={`${index}coverage`}
name={[field.name, "coverage"]}
>
<InputNumber precision={2} />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("general.actions.add")}
</Button>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("general.actions.add")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</LayoutFormRow>
</div>
);
}}
</Form.List>
</LayoutFormRow>
</FeatureWrapper>
<LayoutFormRow grow header={t("bodyshop.fields.md_jobline_presets")} id="md_jobline_presets">
<Form.List name={["md_jobline_presets"]}>
{(fields, { add, remove, move }) => {

View File

@@ -1,12 +1,13 @@
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Form, InputNumber } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -29,210 +30,219 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
return (
<RbacWrapper action="shop:rbac">
<LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.rbac.accounting.exportlog")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:exportlog"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payables")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:payables"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payments")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:payments"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.accounting.receivables")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:receivables"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.delete")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:delete"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.enter")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:enter"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:list"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.reexport")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:reexport"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.contracts.create")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:create"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.contracts.detail")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:detail"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.contracts.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:list"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.create")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:create"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.detail")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:detail"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:list"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.csi.export")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "csi:export"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.csi.page")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "csi:page"]}
>
<InputNumber />
</Form.Item>
{...HasFeatureAccess({ featureName: "export", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.accounting.exportlog")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:exportlog"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payables")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:payables"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payments")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:payments"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.accounting.receivables")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:receivables"]}
>
<InputNumber />
</Form.Item>
]:[]}
{...HasFeatureAccess({ featureName: "bills", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.bills.delete")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:delete"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.enter")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:enter"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:list"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.reexport")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:reexport"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:view"]}
>
<InputNumber />
</Form.Item>
]:[]}
{...HasFeatureAccess({ featureName: "courtesycars", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.contracts.create")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:create"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.contracts.detail")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:detail"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.contracts.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:list"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.create")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:create"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.detail")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:detail"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:list"]}
>
<InputNumber />
</Form.Item>
]:[]}
{...HasFeatureAccess({ featureName: "csi", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.csi.export")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "csi:export"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.csi.page")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "csi:page"]}
>
<InputNumber />
</Form.Item>
]:[]}
<Form.Item
label={t("bodyshop.fields.rbac.employees.page")}
rules={[
@@ -425,18 +435,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:list"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.employees.page")}
rules={[
@@ -510,18 +508,21 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.production.board")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "production:board"]}
>
<InputNumber />
</Form.Item>
{HasFeatureAccess({ featureName: "visualboard", bodyshop }) && (
<Form.Item
label={t("bodyshop.fields.rbac.production.board")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "production:board"]}
>
<InputNumber />
</Form.Item>
)}
<Form.Item
label={t("bodyshop.fields.rbac.production.list")}
rules={[
@@ -546,102 +547,142 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.scoreboard.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "scoreboard:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shiftclock.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "shiftclock:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shop.config")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "shop:config"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.edit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:edit"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:shiftedit"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.editcommitted")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:editcommitted"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.ttapprovals.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "ttapprovals:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.ttapprovals.approve")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "ttapprovals:approve"]}
>
<InputNumber />
</Form.Item>
{HasFeatureAccess({ featureName: "scoreboard", bodyshop }) && (
<Form.Item
label={t("bodyshop.fields.rbac.scoreboard.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "scoreboard:view"]}
>
<InputNumber />
</Form.Item>
)}
{...HasFeatureAccess({ featureName: "timetickets", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.shiftclock.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "shiftclock:view"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.shop.config")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "shop:config"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.edit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:edit"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:shiftedit"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.editcommitted")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:editcommitted"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.ttapprovals.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "ttapprovals:view"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.ttapprovals.approve")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "ttapprovals:approve"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.enter")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:enter"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:list"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:shiftedit"]}
>
<InputNumber />
</Form.Item>
]:[]}
<Form.Item
label={t("bodyshop.fields.rbac.shop.vendors")}
rules={[
@@ -690,7 +731,7 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
{/* <Form.Item
label={t("bodyshop.fields.rbac.shop.templates")}
rules={[
{
@@ -701,79 +742,22 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
name={["md_rbac", "shop:templates"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shop.vendors")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "shop:vendors"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.temporarydocs.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "temporarydocs:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.edit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:edit"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.enter")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:enter"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:list"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:shiftedit"]}
>
<InputNumber />
</Form.Item>
</Form.Item> */}
{HasFeatureAccess({ featureName: "media", bodyshop }) && (
<Form.Item
label={t("bodyshop.fields.rbac.temporarydocs.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "temporarydocs:view"]}
>
<InputNumber />
</Form.Item>
)}
<Form.Item
label={t("bodyshop.fields.rbac.users.editaccess")}
rules={[

View File

@@ -339,7 +339,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
{(fields, { add, remove, move }) => {
return (
<div>
<LayoutFormRow>
<Space size='large' wrap>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<Space direction="vertical">
@@ -386,7 +386,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
</Space>
</Form.Item>
))}
</LayoutFormRow>
</Space>
<Form.Item>
<Button
type="dashed"

View File

@@ -1,4 +1,4 @@
import { Button, Result } from "antd";
import { Result } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -16,19 +16,5 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShopSubStatus);
export function ShopSubStatus({ bodyshop }) {
const { t } = useTranslation();
const { sub_status } = bodyshop;
return (
<Result
status="403"
title={t(`general.labels.sub_status.${sub_status}`)}
extra={
<Button
onClick={() => {
alert("Not implemented yet.");
}}
>
{t("general.actions.submitticket")}
</Button>
}
/>
);
return <Result status="403" title={t(`general.labels.sub_status.${sub_status}`)} />;
}

View File

@@ -6,7 +6,8 @@ import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, statusSort } from "../../utils/sorters";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
@@ -67,9 +68,20 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
text: status,
value: status
})),
onFilter: (value, record) => value.includes(record.status)
onFilter: (value, record) => value.includes(record.status),
},
{
title: t("jobs.fields.actual_completion"),
dataIndex: "actual_completion",
key: "actual_completion",
render: (text, record) => (
<DateTimeFormatter>{record.actual_completion}</DateTimeFormatter>
),
sorter: (a, b) => dateSort(a.actual_completion, b.actual_completion),
sortOrder:
state.sortedInfo.columnKey === "actual_completion" &&
state.sortedInfo.order,
},
{
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",

View File

@@ -66,6 +66,7 @@ export const QUERY_BILLS_BY_JOBID = gql`
order_date
deliver_by
return
returnfrombill
orderedby
parts_order_lines {
id

View File

@@ -67,6 +67,7 @@ export const QUERY_OWNER_BY_ID = gql`
tax_number
jobs(order_by: { date_open: desc }) {
id
actual_completion
ro_number
clm_no
status

View File

@@ -30,6 +30,7 @@ export const QUERY_VEHICLE_BY_ID = gql`
notes
jobs(order_by: { date_open: desc }) {
id
actual_completion
ro_number
ownr_co_nm
ownr_fn

View File

@@ -51,6 +51,17 @@ export function JobsAvailablePageContainer({ partnerVersion, setBreadcrumbs, set
{!partnerVersion && (
<AlertComponent
type="warning"
action={
<a
href={InstanceRenderManager({
imex: "https://partner.imex.online/Setup.exe",
rome: "https://partner.romeonline.io/Setup.exe",
promanager: "https://dzaenazwrgg60.cloudfront.net/Setup.exe"
})}
>
<Button size="small">{t("general.actions.download")}</Button>
</a>
}
message={t("general.messages.partnernotrunning", {
app: InstanceRenderManager({
imex: "$t(titles.imexonline)",

View File

@@ -291,6 +291,7 @@ export function JobsDetailPage({
{
key: "general",
icon: <Icon component={FaShieldAlt} />,
id: "job-details-general",
label: t("menus.jobsdetail.general"),
forceRender: true,
children: <JobsDetailGeneral job={job} form={form} />
@@ -298,6 +299,7 @@ export function JobsDetailPage({
{
key: "repairdata",
icon: <BarsOutlined />,
id: "job-details-repairdata",
label: t("menus.jobsdetail.repairdata"),
forceRender: true,
children: <JobsLinesContainer job={job} joblines={job.joblines} refetch={refetch} form={form} />
@@ -305,18 +307,21 @@ export function JobsDetailPage({
{
key: "rates",
icon: <DollarCircleOutlined />,
id: "job-details-rates",
label: t("menus.jobsdetail.rates"),
forceRender: true,
children: <JobsDetailRates job={job} form={form} />
},
{
key: "totals",
id: "job-details-totals",
icon: <DollarCircleOutlined />,
label: t("menus.jobsdetail.totals"),
children: <JobsDetailTotals job={job} refetch={refetch} />
},
{
key: "partssublet",
id: "job-details-partssublet",
icon: <ToolFilled />,
label: HasFeatureAccess({ featureName: "bills", bodyshop })
? t("menus.jobsdetail.partssublet")
@@ -331,6 +336,7 @@ export function JobsDetailPage({
? [
{
key: "labor",
id: "job-details-labor",
icon: <Icon component={FaHardHat} />,
label: t("menus.jobsdetail.labor"),
children: <JobsDetailLaborContainer job={job} jobId={job.id} />
@@ -340,11 +346,13 @@ export function JobsDetailPage({
{
key: "lifecycle",
icon: <BarsOutlined />,
id: "job-details-lifecycle",
label: t("menus.jobsdetail.lifecycle"),
children: <JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses} />
},
{
key: "dates",
id: "job-details-dates",
icon: <CalendarFilled />,
label: t("menus.jobsdetail.dates"),
forceRender: true,
@@ -358,6 +366,7 @@ export function JobsDetailPage({
? [
{
key: "documents",
id: "job-details-documents",
icon: <FileImageFilled />,
label: t("jobs.labels.documents"),
children: bodyshop.uselocalmediaserver ? (
@@ -370,6 +379,7 @@ export function JobsDetailPage({
: []),
{
key: "notes",
id: "job-details-notes",
icon: <Icon component={FaRegStickyNote} />,
label: t("jobs.labels.notes"),
children: <JobNotesContainer jobId={job.id} />
@@ -377,12 +387,14 @@ export function JobsDetailPage({
{
key: "audit",
icon: <HistoryOutlined />,
id: "job-details-audit",
label: t("jobs.labels.audit"),
children: <JobAuditTrail jobId={job.id} />
},
{
key: "tasks",
icon: <FaTasks />,
id: "job-details-tasks",
label: (
<Space direction="horizontal">
{t("jobs.labels.tasks")}

View File

@@ -1,4 +1,4 @@
import { Button, Collapse, FloatButton, Layout, Space, Spin, Tag } from "antd";
import { FloatButton, Layout, Spin } from "antd";
// import preval from "preval.macro";
import React, { lazy, Suspense, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -117,7 +117,6 @@ const mapDispatchToProps = (dispatch) => ({
export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished }) {
const { t } = useTranslation();
const [chatVisible] = useState(false);
const [tours, setTours] = useState([]);
useEffect(() => {
const widgetId = InstanceRenderManager({
@@ -630,28 +629,6 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
Disclaimer & Notices
</Link>
</div>
{InstanceRenderManager({
promanager: (
<Collapse>
<Collapse.Panel header="DEVELOPMENT ONLY - ProductFruits Tours">
<Space>
<Button
onClick={async () => {
setTours(await window.productFruits.api.tours.getTours());
}}
>
Get Tours
</Button>
{tours.map((tour) => (
<Tag key={tour.id} onClick={() => window.productFruits.api.tours.tryStartTour(tour.id)}>
{tour.name}
</Tag>
))}
</Space>
</Collapse.Panel>
</Collapse>
)
})}
</Footer>
</Layout>
</>

View File

@@ -72,10 +72,17 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
});
}
items.push({
key: "licensing",
label: t("bodyshop.labels.licensing"),
children: <ShopInfoUsersComponent />
InstanceRenderManager({
executeFunction: true,
args: [],
imex: () => {
items.push({
key: "licensing",
label: t("bodyshop.labels.licensing"),
children: <ShopInfoUsersComponent />
});
},
rome: "USE_IMEX"
});
if (HasFeatureAccess({ featureName: "csi", bodyshop })) {

View File

@@ -8,7 +8,8 @@ const INITIAL_STATE = {
name: "ShopName",
address: InstanceRenderManager({
imex: "noreply@iemx.online",
rome: "noreply@romeonline.io"
rome: "noreply@romeonline.io",
promanager: "noreply@promanager.web-est.com"
})
},
to: null,

View File

@@ -15,6 +15,7 @@ import { getToken } from "firebase/messaging";
import i18next from "i18next";
import LogRocket from "logrocket";
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
import { Userpilot } from "userpilot";
import { factory } from "../../App/App.container";
import {
analytics,
@@ -25,6 +26,10 @@ import {
messaging,
updateCurrentUser
} from "../../firebase/firebase.utils";
import { QUERY_EULA } from "../../graphql/bodyshop.queries";
import client from "../../utils/GraphQLClient";
import day from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import {
checkInstanceId,
sendPasswordResetFailure,
@@ -43,11 +48,6 @@ import {
validatePasswordResetSuccess
} from "./user.actions";
import UserActionTypes from "./user.types";
import client from "../../utils/GraphQLClient";
import { QUERY_EULA } from "../../graphql/bodyshop.queries";
import day from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { Userpilot } from "userpilot";
const fpPromise = FingerprintJS.load();
@@ -310,10 +310,41 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
updateUserDetailsSuccess(authRecord[0] ? { validemail: authRecord[0].user.validemail } : { validemail: false })
);
const user = yield select((state) => state.user.currentUser);
if (payload.features.singleDeviceOnly) {
const user = yield select((state) => state.user.currentUser);
if (
!(
user.email.includes("@imex.") ||
user.email.includes("@rome.") ||
user.email.includes("@rometech.") ||
user.email.includes("@promanager.")
)
)
yield put(setInstanceId(user.uid));
}
if (!(user.email.includes("@imex.") || user.email.includes("@rome."))) yield put(setInstanceId(user.uid));
//For Rome, check to make sure it's not a PM shop.
try {
InstanceRenderManager({
executeFunction: true,
args: [],
rome: () => {
if (
payload.imexshopid.toLowerCase().startsWith("pm_") &&
!(
user.email.includes("@imex.") ||
user.email.includes("@rome.") ||
user.email.includes("@rometech.") ||
user.email.includes("@promanager.")
)
) {
throw new Error("You are not authorized to use this application.");
}
},
promanager: () => {}
});
} catch (error) {
yield put(setInstanceConflict());
}
try {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
import axios from "axios";
import { auth } from "../firebase/firebase.utils";
import InstanceRenderManager from "./instanceRenderMgr";
axios.defaults.baseURL =
import.meta.env.VITE_APP_AXIOS_BASE_API_URL ||
@@ -12,6 +13,15 @@ export const axiosAuthInterceptorId = axios.interceptors.request.use(
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
InstanceRenderManager({
executeFunction: true,
args: [],
promanager: () => {
if (!config.url.startsWith("http://localhost:1337")) {
config.headers["Convenient-Company"] = "promanager";
}
}
});
}
return config;

View File

@@ -10,6 +10,7 @@ import client from "../utils/GraphQLClient";
import cleanAxios from "./CleanAxios";
import { TemplateList } from "./TemplateConstants";
import { generateTemplate } from "./graphQLmodifier";
import InstanceRenderManager from "./instanceRenderMgr";
const server = import.meta.env.VITE_APP_REPORTS_SERVER_URL;
jsreport.serverUrl = server;
@@ -71,8 +72,8 @@ export default async function RenderTemplate(
...contextData,
...templateObject.variables,
...templateObject.context,
headerpath: `/${bodyshop.imexshopid}/header.html`,
footerpath: `/${bodyshop.imexshopid}/footer.html`,
headerpath: `/${InstanceRenderManager({ imex: bodyshop.imexshopid, rome: bodyshop.imexshopid, promanager: "GENERIC" })}/header.html`,
footerpath: `/${InstanceRenderManager({ imex: bodyshop.imexshopid, rome: bodyshop.imexshopid, promanager: "GENERIC" })}/footer.html`,
bodyshop: bodyshop,
filters: templateObject?.filters,
sorters: templateObject?.sorters,

View File

@@ -1,4 +1,5 @@
export default async function FcmHandler({ client, payload }) {
console.log("FCM", payload);
switch (payload.type) {
case "messaging-inbound":
client.cache.modify({

View File

@@ -920,6 +920,7 @@
- cdk_dealerid
- city
- claimscorpid
- convenient_company
- country
- created_at
- default_adjustment_rate

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 "convenient_company" text
-- null;

View File

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

View File

@@ -1,9 +1,13 @@
const DineroQbFormat = require("./accounting-constants").DineroQbFormat;
const Dinero = require("dinero.js");
const { DiscountNotAlreadyCounted } = require("../job/job-totals");
const logger = require("../utils/logger");
const InstanceManager = require("../utils/instanceMgr").default;
const { DiscountNotAlreadyCounted } = InstanceManager({
imex: require("../job/job-totals"),
rome: require("../job/job-totals-USA"),
promanager: "USE_ROME"
});
const logger = require("../utils/logger");
exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes, classes }) {
const InvoiceLineAdd = [];

View File

@@ -9,8 +9,12 @@ const CdkBase = require("../web-sockets/web-socket");
const Dinero = require("dinero.js");
const _ = require("lodash");
const { DiscountNotAlreadyCounted } = require("../job/job-totals");
const InstanceManager = require("../utils/instanceMgr").default;
const { DiscountNotAlreadyCounted } = InstanceManager({
imex: require("../job/job-totals"),
rome: require("../job/job-totals-USA"),
promanager: "USE_ROME"
});
exports.default = async function (socket, jobid) {
try {
@@ -232,10 +236,36 @@ exports.default = async function (socket, jobid) {
const mapaAccountName = selectedDmsAllocationConfig.costs.MAPA;
const mapaAccount = bodyshop.md_responsibility_centers.costs.find((c) => c.name === mapaAccountName);
if (mapaAccount) {
if (!costCenterHash[mapaAccountName]) costCenterHash[mapaAccountName] = Dinero();
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
Dinero(job.job_totals.rates.mapa.total).percentage(bodyshop?.cdk_configuration?.sendmaterialscosting)
);
if (!costCenterHash[mapaAccountName])
costCenterHash[mapaAccountName] = Dinero();
if (job.bodyshop.use_paint_scale_data === true) {
if (job.mixdata.length > 0) {
costCenterHash[mapaAccountName] = costCenterHash[
mapaAccountName
].add(
Dinero({
amount: Math.round(
((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) *
100
),
})
);
} else {
costCenterHash[mapaAccountName] = costCenterHash[
mapaAccountName
].add(
Dinero(job.job_totals.rates.mapa.total).percentage(
bodyshop?.cdk_configuration?.sendmaterialscosting
)
);
}
} else {
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
Dinero(job.job_totals.rates.mapa.total).percentage(
bodyshop?.cdk_configuration?.sendmaterialscosting
)
);
}
} else {
//console.log("NO MAPA ACCOUNT FOUND!!");
}

View File

@@ -24,7 +24,7 @@ const ses = new aws.SES({
const transporter = nodemailer.createTransport({
SES: { ses, aws },
sendingRate: 40 // 40 emails per second.
sendingRate: InstanceManager({ imex: 40, rome: 10 })
});
// Initialize the Tasks Email Queue
@@ -41,38 +41,28 @@ const tasksEmailQueueCleanup = async () => {
}
};
// Handling SIGINT (e.g., Ctrl+C)
process.on("SIGINT", async () => {
await tasksEmailQueueCleanup();
process.exit(0);
});
// Handling SIGTERM (e.g., sent by system shutdown)
process.on("SIGTERM", async () => {
await tasksEmailQueueCleanup();
process.exit(0);
});
// Handling uncaught exceptions
process.on("uncaughtException", async (err) => {
await tasksEmailQueueCleanup();
process.exit(1); // Exit with an 'error' code
});
// Handling unhandled promise rejections
process.on("unhandledRejection", async (reason, promise) => {
await tasksEmailQueueCleanup();
process.exit(1); // Exit with an 'error' code
});
const fromEmails = InstanceManager({
imex: "ImEX Online <noreply@imex.online>",
rome: "Rome Online <noreply@romeonline.io>",
promanager: "ProManager <noreply@promanager.web-est.com>"
});
const endPoints = InstanceManager({
imex: process.env?.NODE_ENV === "test" ? "https://test.imex.online" : "https://imex.online",
rome: process.env?.NODE_ENV === "test" ? "https//test.romeonline.io" : "https://romeonline.io",
promanager: process.env?.NODE_ENV === "test" ? "https//test.promanager.web-est.com" : "https://promanager.web-est.com"
});
if (process.env.NODE_ENV !== "development") {
// Handling SIGINT (e.g., Ctrl+C)
process.on("SIGINT", async () => {
await tasksEmailQueueCleanup();
process.exit(0);
});
// Handling SIGTERM (e.g., sent by system shutdown)
process.on("SIGTERM", async () => {
await tasksEmailQueueCleanup();
process.exit(0);
});
// Handling uncaught exceptions
process.on("uncaughtException", async (err) => {
await tasksEmailQueueCleanup();
process.exit(1); // Exit with an 'error' code
});
// Handling unhandled promise rejections
process.on("unhandledRejection", async (reason, promise) => {
await tasksEmailQueueCleanup();
process.exit(1); // Exit with an 'error' code
});
}
/**
* Format the date for the email.
@@ -105,6 +95,17 @@ const formatPriority = (priority) => {
* @returns {{header, body: string, subHeader: string}}
*/
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId) => {
const endPoints = InstanceManager({
imex: process.env?.NODE_ENV === "test" ? "https://test.imex.online" : "https://imex.online",
rome:
bodyshop.convenient_company === "promanager"
? process.env?.NODE_ENV === "test"
? "https//test.promanager.web-est.com"
: "https://promanager.web-est.com"
: process.env?.NODE_ENV === "test"
? "https//test.romeonline.io"
: "https://romeonline.io"
});
return {
header: title,
subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)}`,
@@ -121,12 +122,15 @@ const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, j
* @param taskIds
* @param successCallback
*/
const sendMail = (type, to, subject, html, taskIds, successCallback) => {
// Push next messages to Nodemailer
//transporter.once("idle", () => {
// Note: This is commented out because despite being in the documentation, it does not work
// and stackoverflow suggests it is not needed
// if (transporter.isIdle()) {
const sendMail = (type, to, subject, html, taskIds, successCallback, requestInstance) => {
const fromEmails = InstanceManager({
imex: "ImEX Online <noreply@imex.online>",
rome:
requestInstance === "promanager"
? "ProManager <noreply@promanager.web-est.com>"
: "Rome Online <noreply@romeonline.io>"
});
transporter.sendMail(
{
from: fromEmails,
@@ -184,7 +188,10 @@ const taskAssignedEmail = async (req, res) => {
tasks_by_pk.job,
newTask.id
)
)
),
null,
null,
tasks_by_pk.bodyshop.convenient_company
);
// We return success regardless because we don't want to block the event trigger.
@@ -233,6 +240,14 @@ const tasksRemindEmail = async (req, res) => {
// Iterate over all recipients and send the email.
recipientCounts.forEach((recipient) => {
const fromEmails = InstanceManager({
imex: "ImEX Online <noreply@imex.online>",
rome:
onlyTask.bodyshop.convenient_company === "promanager"
? "ProManager <noreply@promanager.web-est.com>"
: "Rome Online <noreply@romeonline.io>"
});
const emailData = {
from: fromEmails,
to: recipient.email
@@ -261,6 +276,18 @@ const tasksRemindEmail = async (req, res) => {
}
// There are multiple emails to send to this author.
else {
const endPoints = InstanceManager({
imex: process.env?.NODE_ENV === "test" ? "https://test.imex.online" : "https://imex.online",
rome:
allTasks[0].bodyshop.convenient_company === "promanager"
? process.env?.NODE_ENV === "test"
? "https//test.promanager.web-est.com"
: "https://promanager.web-est.com"
: process.env?.NODE_ENV === "test"
? "https//test.romeonline.io"
: "https://romeonline.io"
});
const allTasks = groupedTasks[recipient.email];
emailData.subject = `New Tasks Reminder - ${allTasks.length} Tasks require your attention`;
emailData.html = generateEmailTemplate({
@@ -278,11 +305,19 @@ const tasksRemindEmail = async (req, res) => {
if (emailData?.subject && emailData?.html) {
// Send Email
sendMail("remind", emailData.to, emailData.subject, emailData.html, taskIds, (taskIds) => {
for (const taskId of taskIds) {
tasksEmailQueue.push(taskId);
}
});
sendMail(
"remind",
emailData.to,
emailData.subject,
emailData.html,
taskIds,
(taskIds) => {
for (const taskId of taskIds) {
tasksEmailQueue.push(taskId);
}
},
allTasks[0].bodyshop.convenient_company
);
}
});

View File

@@ -1790,6 +1790,7 @@ exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
md_responsibility_centers
cdk_configuration
pbs_configuration
use_paint_scale_data
}
ro_number
dms_allocation
@@ -1897,6 +1898,10 @@ exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
line_ref
unq_seq
}
mixdata(limit: 1, order_by: {updated_at: desc}) {
jobid
totalliquidcost
}
}
}`;
@@ -2431,6 +2436,7 @@ exports.QUERY_REMIND_TASKS = `
jobid
bodyshop {
shopname
convenient_company
}
bodyshopid
}
@@ -2453,6 +2459,7 @@ query QUERY_TASK_BY_ID($id: uuid!) {
}
bodyshop{
shopname
convenient_company
}
job{
ro_number
@@ -2467,3 +2474,12 @@ query QUERY_TASK_BY_ID($id: uuid!) {
}
`;
exports.GET_JOBS_BY_PKS = `query GET_JOBS_BY_PKS($ids: [uuid!]!) {
jobs(where: {id: {_in: $ids}}) {
id
shopid
}
}
`;

View File

@@ -6,6 +6,7 @@ const qs = require("query-string");
const axios = require("axios");
const moment = require("moment");
const logger = require("../utils/logger");
const InstanceManager = require("../utils/instanceMgr").default;
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
@@ -16,7 +17,10 @@ const domain = process.env.NODE_ENV ? "secure" : "test";
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
const client = new SecretsManagerClient({
region: "ca-central-1"
region: InstanceManager({
imex: "ca-central-1",
rome: "us-east-2"
})
});
const gqlClient = require("../graphql-client/graphql-client").client;
@@ -145,45 +149,89 @@ exports.generate_payment_url = async (req, res) => {
};
exports.postback = async (req, res) => {
logger.log("intellipay-postback", "ERROR", req.user?.email, null, req.body);
const { body: values } = req;
if (!values.invoice) {
res.sendStatus(200);
return;
}
// TODO query job by account name
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
id: values.invoice
});
// TODO add mutation to database
try {
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: {
amount: values.total,
transactionid: `C00 ${values.authcode}`,
payer: "Customer",
type: values.cardtype,
jobid: values.invoice,
date: moment(Date.now())
}
});
logger.log("intellipay-postback", "DEBUG", req.user?.email, null, req.body);
const { body: values } = req;
await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
paymentResponse: {
amount: values.total,
bodyshopid: job.jobs_by_pk.shopid,
paymentid: paymentResult.id,
jobid: values.invoice,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
});
const comment = Buffer.from(values?.comment, "base64").toString();
res.send({ message: "Postback Successful" });
if ((!values.invoice || values.invoice === "") && !comment) {
//invoice is specified through the pay link. Comment by IO.
logger.log("intellipay-postback-ignored", "DEBUG", req.user?.email, null, req.body);
res.sendStatus(200);
return;
}
if (values.invoice) {
//This is a link email that's been sent out.
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
id: values.invoice
});
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: {
amount: values.total,
transactionid: values.authcode,
payer: "Customer",
type: values.cardtype,
jobid: values.invoice,
date: moment(Date.now())
}
});
const responseResults = await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
paymentResponse: {
amount: values.total,
bodyshopid: job.jobs_by_pk.shopid,
paymentid: paymentResult.id,
jobid: values.invoice,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
});
logger.log("intellipay-postback-link-success", "DEBUG", req.user?.email, null, {
iprequest: values,
responseResults,
paymentResult
});
res.sendStatus(200);
} else if (comment) {
//This has been triggered by IO and may have multiple jobs.
const partialPayments = JSON.parse(comment);
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
ids: partialPayments.map((p) => p.jobid)
});
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: partialPayments.map((p) => ({
amount: p.amount,
transactionid: values.authcode,
payer: "Customer",
type: values.cardtype,
jobid: p.jobid,
date: moment(Date.now()),
payment_responses: {
data: {
amount: values.total,
bodyshopid: jobs.jobs[0].shopid,
jobid: p.jobid,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
}
}))
});
logger.log("intellipay-postback-app-success", "DEBUG", req.user?.email, null, {
iprequest: values,
paymentResult
});
res.sendStatus(200);
}
} catch (error) {
logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, {
error: JSON.stringify(error),

View File

@@ -2,7 +2,12 @@ const _ = require("lodash");
const Dinero = require("dinero.js");
const queries = require("../graphql-client/queries");
const logger = require("../utils/logger");
const { DiscountNotAlreadyCounted } = require("./job-totals");
const InstanceManager = require("../utils/instanceMgr").default;
const { DiscountNotAlreadyCounted } = InstanceManager({
imex: require("../job/job-totals"),
rome: require("../job/job-totals-USA"),
promanager: "USE_ROME"
});
// Dinero.defaultCurrency = "USD";
// Dinero.globalLocale = "en-CA";

View File

@@ -718,6 +718,7 @@ function CalculateTaxesTotals(job, otherTotals) {
const taxableAmounts = {
PAA: Dinero(),
PAE: Dinero(),
PAN: Dinero(),
PAL: Dinero(),
PAR: Dinero(),