@@ -24030,6 +24030,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>help</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>home</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -26853,6 +26874,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>orderedby</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>quantity</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"jsreport-browser-client-dist": "^1.3.0",
|
||||
"libphonenumber-js": "^1.9.17",
|
||||
"logrocket": "^1.2.0",
|
||||
"markerjs2": "^2.8.1",
|
||||
"moment-business-days": "^1.2.0",
|
||||
"phone": "^2.4.21",
|
||||
"preval.macro": "^5.0.0",
|
||||
|
||||
@@ -3,11 +3,12 @@ import { ConfigProvider } from "antd";
|
||||
import enLocale from "antd/es/locale/en_US";
|
||||
import LogRocket from "logrocket";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||
import client from "../utils/GraphQLClient";
|
||||
import App from "./App";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import JiraSupportComponent from "../components/jira-support-widget/jira-support-widget.component";
|
||||
moment.locale("en-US");
|
||||
|
||||
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
|
||||
@@ -15,6 +16,23 @@ if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
|
||||
export default function AppContainer() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
// Include the Crisp code here, without the <script></script> tags
|
||||
window.$crisp = [];
|
||||
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
|
||||
|
||||
var d = document;
|
||||
var s = d.createElement("script");
|
||||
|
||||
s.src = "https://client.crisp.chat/l.js";
|
||||
s.async = 1;
|
||||
d.getElementsByTagName("head")[0].appendChild(s);
|
||||
|
||||
return () => {
|
||||
d.getElementsByTagName("head")[0].removeChild(s);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ApolloProvider client={client}>
|
||||
<ConfigProvider
|
||||
@@ -30,6 +48,7 @@ export default function AppContainer() {
|
||||
>
|
||||
<GlobalLoadingBar />
|
||||
<App />
|
||||
<JiraSupportComponent />
|
||||
</ConfigProvider>
|
||||
</ApolloProvider>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
||||
//Component Imports
|
||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||
@@ -39,8 +40,12 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
export function App({ checkUserSession, currentUser, online, setOnline }) {
|
||||
useEffect(() => {
|
||||
if (!navigator.onLine) {
|
||||
setOnline(false);
|
||||
}
|
||||
|
||||
checkUserSession();
|
||||
}, [checkUserSession]);
|
||||
}, [checkUserSession, setOnline]);
|
||||
|
||||
//const b = Grid.useBreakpoint();
|
||||
// console.log("Breakpoints:", b);
|
||||
@@ -118,6 +123,13 @@ export function App({ checkUserSession, currentUser, online, setOnline }) {
|
||||
component={TechPageContainer}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
<PrivateRoute
|
||||
isAuthorized={currentUser.authorized}
|
||||
path="/edit"
|
||||
component={DocumentEditorContainer}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</Suspense>
|
||||
</Switch>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Link } from "react-router-dom";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
|
||||
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
|
||||
|
||||
@@ -41,19 +41,12 @@ export default function AccountingPayablesTableComponent({
|
||||
title: t("payments.fields.date"),
|
||||
dataIndex: "date",
|
||||
key: "date",
|
||||
sorter: (a, b) => alphaSort(a.date, b.date),
|
||||
sorter: (a, b) => dateSort(a.date, b.date),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "date" && state.sortedInfo.order,
|
||||
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>,
|
||||
},
|
||||
{
|
||||
title: t("payments.fields.date"),
|
||||
dataIndex: "date",
|
||||
key: "date",
|
||||
sorter: (a, b) => alphaSort(a.date, b.date),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "date" && state.sortedInfo.order,
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
@@ -61,7 +54,7 @@ export default function AccountingPayablesTableComponent({
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ownr_ln" && state.sortedInfo.order,
|
||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.job.owner ? (
|
||||
<Link to={"/manage/owners/" + record.job.owner.id}>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Space } from "antd";
|
||||
import React from "react";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component";
|
||||
@@ -5,20 +6,16 @@ import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
|
||||
|
||||
export default function ChatConversationTitle({ conversation }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="imex-flex-row">
|
||||
<ChatConversationTitleTags
|
||||
jobConversations={
|
||||
(conversation && conversation.job_conversations) || []
|
||||
}
|
||||
/>
|
||||
<ChatTagRoContainer conversation={conversation || []} />
|
||||
</div>
|
||||
<div className="imex-flex-row">
|
||||
<PhoneNumberFormatter>
|
||||
{conversation && conversation.phone_num}
|
||||
</PhoneNumberFormatter>
|
||||
</div>
|
||||
</div>
|
||||
<Space flex>
|
||||
<PhoneNumberFormatter>
|
||||
{conversation && conversation.phone_num}
|
||||
</PhoneNumberFormatter>
|
||||
<ChatConversationTitleTags
|
||||
jobConversations={
|
||||
(conversation && conversation.job_conversations) || []
|
||||
}
|
||||
/>
|
||||
<ChatTagRoContainer conversation={conversation || []} />
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CloseCircleOutlined, LoadingOutlined } from "@ant-design/icons";
|
||||
import { Select, Empty } from "antd";
|
||||
import { Select, Empty, Space } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -13,27 +13,27 @@ export default function ChatTagRoComponent({
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Select
|
||||
showSearch
|
||||
autoFocus
|
||||
style={{
|
||||
width: 300,
|
||||
}}
|
||||
placeholder={t("general.labels.search")}
|
||||
filterOption={false}
|
||||
onSearch={handleSearch}
|
||||
onSelect={handleInsertTag}
|
||||
notFoundContent={loading ? <LoadingOutlined /> : <Empty />}
|
||||
>
|
||||
{roOptions.map((item, idx) => (
|
||||
<Select.Option key={item.id || idx}>
|
||||
{` ${item.ro_number || ""} | ${item.ownr_fn || ""} ${
|
||||
item.ownr_ln || ""
|
||||
} ${item.ownr_co_nm || ""}`}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Space flex>
|
||||
<div style={{ width: "15rem" }}>
|
||||
<Select
|
||||
showSearch
|
||||
autoFocus
|
||||
dropdownMatchSelectWidth
|
||||
placeholder={t("general.labels.search")}
|
||||
filterOption={false}
|
||||
onSearch={handleSearch}
|
||||
onSelect={handleInsertTag}
|
||||
notFoundContent={loading ? <LoadingOutlined /> : <Empty />}
|
||||
>
|
||||
{roOptions.map((item, idx) => (
|
||||
<Select.Option key={item.id || idx}>
|
||||
{` ${item.ro_number || ""} | ${item.ownr_fn || ""} ${
|
||||
item.ownr_ln || ""
|
||||
} ${item.ownr_co_nm || ""}`}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
{loading ? <LoadingOutlined /> : null}
|
||||
|
||||
{loading ? (
|
||||
@@ -41,6 +41,6 @@ export default function ChatTagRoComponent({
|
||||
) : (
|
||||
<CloseCircleOutlined onClick={() => setVisible(false)} />
|
||||
)}
|
||||
</div>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
//import "tui-image-editor/dist/tui-image-editor.css";
|
||||
import { Result } from "antd";
|
||||
import * as markerjs2 from "markerjs2";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||
import { GenerateSrcUrl } from "../jobs-documents-gallery/job-documents.utility";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export function DocumentEditorComponent({ currentUser, bodyshop, document }) {
|
||||
const imgRef = useRef(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [uploaded, setuploaded] = useState(false);
|
||||
const markerArea = useRef(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const triggerUpload = useCallback(
|
||||
async (dataUrl) => {
|
||||
setLoading(true);
|
||||
handleUpload(
|
||||
{
|
||||
filename: `${document.key.split("/").pop()}-${Date.now()}.jpg`,
|
||||
file: await b64toBlob(dataUrl),
|
||||
onSuccess: () => {
|
||||
setLoading(false);
|
||||
setuploaded(true);
|
||||
},
|
||||
onError: () => setLoading(false),
|
||||
},
|
||||
{
|
||||
bodyshop: bodyshop,
|
||||
uploaded_by: currentUser.email,
|
||||
jobId: document.jobid,
|
||||
//billId: billId,
|
||||
tagsArray: ["edited"],
|
||||
//callback: callbackAfterUpload,
|
||||
}
|
||||
);
|
||||
},
|
||||
[bodyshop, currentUser, document]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (imgRef.current !== null) {
|
||||
// create a marker.js MarkerArea
|
||||
markerArea.current = new markerjs2.MarkerArea(imgRef.current);
|
||||
console.log(`markerArea.current`, markerArea.current);
|
||||
// attach an event handler to assign annotated image back to our image element
|
||||
markerArea.current.addCloseEventListener((closeEvent) => {
|
||||
console.log("Close Event", closeEvent);
|
||||
});
|
||||
|
||||
markerArea.current.addRenderEventListener((dataUrl) => {
|
||||
imgRef.current.src = dataUrl;
|
||||
markerArea.current.close();
|
||||
triggerUpload(dataUrl);
|
||||
});
|
||||
// launch marker.js
|
||||
|
||||
markerArea.current.renderAtNaturalSize = true;
|
||||
markerArea.current.renderImageType = "image/jpeg";
|
||||
markerArea.current.renderImageQuality = 1;
|
||||
//markerArea.current.settings.displayMode = "inline";
|
||||
markerArea.current.show();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [triggerUpload]);
|
||||
|
||||
async function b64toBlob(url) {
|
||||
const res = await fetch(url);
|
||||
return await res.blob();
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!loading && !uploaded && (
|
||||
<img
|
||||
ref={imgRef}
|
||||
src={GenerateSrcUrl(document)}
|
||||
alt="sample"
|
||||
crossOrigin="anonymous"
|
||||
style={{ maxWidth: "90vw", maxHeight: "90vh" }}
|
||||
/>
|
||||
)}
|
||||
{loading && <LoadingSpinner message={t("documents.labels.uploading")} />}
|
||||
{uploaded && (
|
||||
<Result
|
||||
status="success"
|
||||
title={t("documents.successes.edituploaded")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(DocumentEditorComponent);
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Result } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation } from "react-router";
|
||||
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
|
||||
import { GET_DOCUMENT_BY_PK } from "../../graphql/documents.queries";
|
||||
import { setBodyshop } from "../../redux/user/user.actions";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import DocumentEditor from "./document-editor.component";
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBodyshop: (bs) => dispatch(setBodyshop(bs)),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(DocumentEditorContainer);
|
||||
|
||||
export function DocumentEditorContainer({ setBodyshop }) {
|
||||
//Get the image details for the image to be saved.
|
||||
//Get the document id from the search string.
|
||||
const { documentId } = queryString.parse(useLocation().search);
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
loading: loadingShop,
|
||||
error: errorShop,
|
||||
data: dataShop,
|
||||
} = useQuery(QUERY_BODYSHOP, {
|
||||
fetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (dataShop) setBodyshop(dataShop.bodyshops[0]);
|
||||
}, [dataShop, setBodyshop]);
|
||||
|
||||
const { loading, error, data } = useQuery(GET_DOCUMENT_BY_PK, {
|
||||
variables: { documentId },
|
||||
skip: !documentId,
|
||||
});
|
||||
|
||||
if (loading || loadingShop) return <LoadingSpinner />;
|
||||
if (error || errorShop)
|
||||
return (
|
||||
<AlertComponent
|
||||
message={error.message || errorShop.message}
|
||||
type="error"
|
||||
/>
|
||||
);
|
||||
|
||||
if (!data || !data.documents_by_pk)
|
||||
return <Result status="404" title={t("general.errors.notfound")} />;
|
||||
return (
|
||||
<div>
|
||||
<DocumentEditor document={data ? data.documents_by_pk : null} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -21,8 +21,13 @@ export const handleUpload = (ev, context) => {
|
||||
const { onError, onSuccess, onProgress } = ev;
|
||||
const { bodyshop, jobId } = context;
|
||||
|
||||
let key = `${bodyshop.id}/${jobId}/${ev.file.name.replace(/\.[^/.]+$/, "")}`;
|
||||
let extension = ev.file.name.split(".").pop();
|
||||
const fileName = ev.file.name || ev.filename;
|
||||
|
||||
let key = `${bodyshop.id}/${jobId}/${fileName.replace(
|
||||
/\.[^/.]+$/,
|
||||
""
|
||||
)}-${new Date().getTime()}`;
|
||||
let extension = fileName.split(".").pop();
|
||||
uploadToCloudinary(
|
||||
key,
|
||||
extension,
|
||||
|
||||
@@ -57,6 +57,23 @@ ${this.state.error.stack}
|
||||
if (this.state.hasErrored === true) {
|
||||
logImEXEvent("error_boundary_rendered", { error, info });
|
||||
|
||||
window.$crisp.push([
|
||||
"set",
|
||||
"session:event",
|
||||
[
|
||||
[
|
||||
[
|
||||
"error_boundary",
|
||||
{
|
||||
error: this.state.error.message,
|
||||
stack: this.state.error.stack,
|
||||
},
|
||||
"red",
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Result
|
||||
|
||||
@@ -20,6 +20,7 @@ import Icon, {
|
||||
ToolFilled,
|
||||
UnorderedListOutlined,
|
||||
UserOutlined,
|
||||
QuestionCircleFilled,
|
||||
} from "@ant-design/icons";
|
||||
import { Layout, Menu } from "antd";
|
||||
import React from "react";
|
||||
@@ -284,6 +285,15 @@ function Header({
|
||||
<Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<Menu.Item
|
||||
key="shop"
|
||||
onClick={() => {
|
||||
window.open("https://help.imex.online/", "_blank");
|
||||
}}
|
||||
icon={<Icon component={QuestionCircleFilled} />}
|
||||
>
|
||||
{t("menus.header.help")}
|
||||
</Menu.Item>
|
||||
<Menu.SubMenu
|
||||
style={{ float: "right" }}
|
||||
title={
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React from "react";
|
||||
|
||||
export default function JiraSupportComponent() {
|
||||
useScript();
|
||||
//useScript();
|
||||
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
const useScript = () => {
|
||||
useEffect(() => {
|
||||
const script = document.createElement("script");
|
||||
script.src = "https://jsd-widget.atlassian.com/assets/embed.js";
|
||||
script.setAttribute("data-jsd-embedded", true);
|
||||
script.setAttribute("data-key", "d69bb65c-1dd3-483f-b109-66a970d03f44");
|
||||
script.setAttribute("data-base-url", "https://jsd-widget.atlassian.com");
|
||||
//script.async = true;
|
||||
script.onload = () => {
|
||||
var DOMContentLoaded_event = document.createEvent("Event");
|
||||
DOMContentLoaded_event.initEvent("DOMContentLoaded", true, true);
|
||||
window.document.dispatchEvent(DOMContentLoaded_event);
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
// const useScript = () => {
|
||||
// useEffect(() => {
|
||||
// const script = document.createElement("script");
|
||||
// script.src = "https://jsd-widget.atlassian.com/assets/embed.js";
|
||||
// script.setAttribute("data-jsd-embedded", true);
|
||||
// script.setAttribute("data-key", "d69bb65c-1dd3-483f-b109-66a970d03f44");
|
||||
// script.setAttribute("data-base-url", "https://jsd-widget.atlassian.com");
|
||||
// //script.async = true;
|
||||
// script.onload = () => {
|
||||
// var DOMContentLoaded_event = document.createEvent("Event");
|
||||
// DOMContentLoaded_event.initEvent("DOMContentLoaded", true, true);
|
||||
// window.document.dispatchEvent(DOMContentLoaded_event);
|
||||
// };
|
||||
// document.head.appendChild(script);
|
||||
|
||||
return () => {
|
||||
document.head.removeChild(script);
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
// return () => {
|
||||
// document.head.removeChild(script);
|
||||
// };
|
||||
// }, []);
|
||||
// };
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function JobDetailCardsNotesComponent({ loading, data }) {
|
||||
bordered
|
||||
dataSource={data.notes}
|
||||
renderItem={(item) => (
|
||||
<List.Item>
|
||||
<List.Item style={{ whiteSpace: "pre-line" }}>
|
||||
{item.critical ? (
|
||||
<EyeInvisibleFilled style={{ margin: 4, color: "red" }} />
|
||||
) : null}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { DELETE_JOB_LINE_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { onlyUnique } from "../../utils/arrayHelper";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
@@ -37,6 +38,7 @@ import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.con
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
jobRO: selectJobReadOnly,
|
||||
technician: selectTechnician,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
@@ -48,6 +50,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
export function JobLinesComponent({
|
||||
jobRO,
|
||||
technician,
|
||||
setPartsOrderContext,
|
||||
loading,
|
||||
refetch,
|
||||
@@ -366,7 +369,8 @@ export function JobLinesComponent({
|
||||
disabled={
|
||||
(job && !job.converted) ||
|
||||
(selectedLines.length > 0 ? false : true) ||
|
||||
jobRO
|
||||
jobRO ||
|
||||
technician
|
||||
}
|
||||
onClick={() => {
|
||||
setPartsOrderContext({
|
||||
@@ -401,7 +405,7 @@ export function JobLinesComponent({
|
||||
<Button>{t("jobs.actions.mark")}</Button>
|
||||
</Dropdown>
|
||||
<Button
|
||||
disabled={jobRO}
|
||||
disabled={jobRO || technician}
|
||||
onClick={() => {
|
||||
setJobLineEditContext({
|
||||
actions: { refetch: refetch },
|
||||
|
||||
@@ -8,8 +8,8 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||
@@ -40,14 +40,14 @@ export function JobPayments({
|
||||
});
|
||||
const columns = [
|
||||
{
|
||||
title: t("payments.fields.created_at"),
|
||||
dataIndex: "created_at",
|
||||
key: "created_at",
|
||||
title: t("payments.fields.date"),
|
||||
dataIndex: "date",
|
||||
key: "date",
|
||||
sorter: (a, b) => dateSort(a.date, b.date),
|
||||
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "created_at" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<DateTimeFormatter>{record.created_at}</DateTimeFormatter>
|
||||
),
|
||||
state.sortedInfo.columnKey === "date" && state.sortedInfo.order,
|
||||
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>,
|
||||
},
|
||||
{
|
||||
title: t("payments.fields.payer"),
|
||||
|
||||
@@ -104,7 +104,10 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
|
||||
).data;
|
||||
|
||||
let existingVehicles;
|
||||
if (estData.data.available_jobs_by_pk.est_data.vehicle) {
|
||||
if (
|
||||
estData.data.available_jobs_by_pk.est_data.vehicle &&
|
||||
estData.data.available_jobs_by_pk.est_data.vin
|
||||
) {
|
||||
//There's vehicle data, need to double check the VIN.
|
||||
existingVehicles = await client.query({
|
||||
query: SEARCH_VEHICLE_BY_VIN,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import Axios from "axios";
|
||||
import _ from "lodash";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import {
|
||||
INSERT_NEW_JOB,
|
||||
QUERY_ALL_JOB_FIELDS,
|
||||
} from "../../graphql/jobs.queries";
|
||||
import { INSERT_NEW_JOB, QUERY_JOB_FOR_DUPE } from "../../graphql/jobs.queries";
|
||||
|
||||
export default async function DuplicateJob(
|
||||
apolloClient,
|
||||
@@ -18,33 +15,21 @@ export default async function DuplicateJob(
|
||||
const { defaultOpenStatus } = config;
|
||||
//get a list of all fields on the job
|
||||
const res = await apolloClient.query({
|
||||
query: QUERY_ALL_JOB_FIELDS,
|
||||
query: QUERY_JOB_FOR_DUPE,
|
||||
variables: { id: jobId },
|
||||
});
|
||||
|
||||
console.log("res", res);
|
||||
const { jobs_by_pk: existingJob } = res.data;
|
||||
const { jobs_by_pk } = res.data;
|
||||
const existingJob = _.cloneDeep(jobs_by_pk);
|
||||
delete existingJob.__typename;
|
||||
delete existingJob.id;
|
||||
delete existingJob.createdat;
|
||||
delete existingJob.updatedat;
|
||||
|
||||
const newJob = {
|
||||
date_estimated: new Date(),
|
||||
shopid: existingJob.shopid,
|
||||
...existingJob,
|
||||
status: defaultOpenStatus,
|
||||
ownerid: existingJob.ownerid,
|
||||
ownr_fn: existingJob.ownr_fn,
|
||||
ownr_ln: existingJob.ownr_ln,
|
||||
ownr_co_nm: existingJob.ownr_co_nm,
|
||||
ownr_addr1: existingJob.ownr_addr1,
|
||||
ownr_addr2: existingJob.ownr_addr2,
|
||||
ownr_st: existingJob.ownr_st,
|
||||
ownr_zip: existingJob.ownr_zip,
|
||||
ownr_ctry: existingJob.ownr_ctry,
|
||||
|
||||
ownr_ph1: existingJob.ownr_ph1,
|
||||
vehicleid: existingJob.vehicleid,
|
||||
v_vin: existingJob.v_vin,
|
||||
v_make_desc: existingJob.v_make_desc,
|
||||
v_model_desc: existingJob.v_model_desc,
|
||||
v_model_yr: existingJob.v_model_yr,
|
||||
};
|
||||
|
||||
const _tempLines = _.cloneDeep(existingJob.joblines);
|
||||
@@ -54,24 +39,20 @@ export default async function DuplicateJob(
|
||||
line.manual_line = true;
|
||||
});
|
||||
newJob.joblines = keepJobLines ? _tempLines : [];
|
||||
newJob.job_totals = (
|
||||
await Axios.post("/job/totals", {
|
||||
job: newJob,
|
||||
})
|
||||
).data;
|
||||
|
||||
delete newJob.joblines;
|
||||
newJob.joblines = keepJobLines ? { data: _tempLines } : null;
|
||||
|
||||
apolloClient
|
||||
.mutate({
|
||||
mutation: INSERT_NEW_JOB,
|
||||
variables: { job: [newJob] },
|
||||
})
|
||||
.then((res2) => {
|
||||
if (completionCallback)
|
||||
completionCallback(res2.data.insert_jobs.returning[0].id);
|
||||
});
|
||||
const res2 = await apolloClient.mutate({
|
||||
mutation: INSERT_NEW_JOB,
|
||||
variables: { job: [newJob] },
|
||||
});
|
||||
await Axios.post("/job/totalsssu", {
|
||||
id: res2.data.insert_jobs.returning[0].id,
|
||||
});
|
||||
|
||||
if (completionCallback)
|
||||
completionCallback(res2.data.insert_jobs.returning[0].id);
|
||||
|
||||
//insert the new job. call the callback with the returned ID when done.
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { DetermineFileType } from "../documents-upload/documents-upload.utility";
|
||||
|
||||
export const GenerateSrcUrl = (value) => {
|
||||
let extension = value.extension;
|
||||
if (extension && extension.toLowerCase().includes("heic")) extension = "jpg";
|
||||
|
||||
return `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType(
|
||||
value.type
|
||||
)}/upload/${value.key}${extension ? `.${extension}` : ""}`;
|
||||
};
|
||||
|
||||
export const GenerateThumbUrl = (value) =>
|
||||
`${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType(
|
||||
value.type
|
||||
)}/upload/${process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${
|
||||
value.key
|
||||
}`;
|
||||
@@ -14,11 +14,11 @@ export default function JobsDocumentsDownloadButton({
|
||||
const [download, setDownload] = useState(null);
|
||||
const imagesToDownload = [
|
||||
...galleryImages.images.filter((image) => image.isSelected),
|
||||
...galleryImages.other.filter((image) => image.isSelected),
|
||||
// ...galleryImages.other.filter((image) => image.isSelected),
|
||||
];
|
||||
|
||||
const handleDownload = () => {
|
||||
logImEXEvent("jobs_documents_download");
|
||||
|
||||
axios
|
||||
.post("/media/download", {
|
||||
ids: imagesToDownload.map((_) => _.key),
|
||||
@@ -27,12 +27,8 @@ export default function JobsDocumentsDownloadButton({
|
||||
// window.open(r.data);
|
||||
downloadAs(
|
||||
r.data,
|
||||
`${identifier || "images"}.zip`,
|
||||
`${identifier || "documents"}.zip`,
|
||||
(progressEvent) => {
|
||||
const percentage = Math.round(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
);
|
||||
console.log(progressEvent, percentage);
|
||||
setDownload((currentDownloadState) => {
|
||||
return {
|
||||
downloaded: progressEvent.loaded || 0,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { FileExcelFilled } from "@ant-design/icons";
|
||||
import { Card, Col, Row, Space } from "antd";
|
||||
import { FileExcelFilled, EditFilled, SyncOutlined } from "@ant-design/icons";
|
||||
import { Card, Col, Row, Space, Button } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Gallery from "react-grid-gallery";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DocumentsUploadComponent from "../documents-upload/documents-upload.component";
|
||||
import { DetermineFileType } from "../documents-upload/documents-upload.utility";
|
||||
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility";
|
||||
import JobsDocumentsDownloadButton from "./jobs-document-gallery.download.component";
|
||||
import JobsDocumentsGalleryReassign from "./jobs-document-gallery.reassign.component";
|
||||
import JobsDocumentsDeleteButton from "./jobs-documents-gallery.delete.component";
|
||||
@@ -22,6 +23,25 @@ function JobsDocumentsComponent({
|
||||
}) {
|
||||
const [galleryImages, setgalleryImages] = useState({ images: [], other: [] });
|
||||
const { t } = useTranslation();
|
||||
const [index, setIndex] = useState(0);
|
||||
|
||||
const onCurrentImageChange = (index) => {
|
||||
setIndex(index);
|
||||
};
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log("Added event listening for reteching.");
|
||||
// window.addEventListener("storage", (ev) => {
|
||||
// if (ev.key === "refetch" && ev.newValue === true) {
|
||||
// refetch && refetch();
|
||||
// localStorage.setItem("refetch", false);
|
||||
// }
|
||||
// });
|
||||
|
||||
// return () => {
|
||||
// window.removeEventListener("storage");
|
||||
// };
|
||||
// }, [refetch]);
|
||||
|
||||
useEffect(() => {
|
||||
let documents = data.reduce(
|
||||
@@ -29,21 +49,14 @@ function JobsDocumentsComponent({
|
||||
const fileType = DetermineFileType(value.type);
|
||||
if (value.type.startsWith("image")) {
|
||||
acc.images.push({
|
||||
src: `${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${DetermineFileType(value.type)}/upload/${value.key}${
|
||||
value.extension ? `.${value.extension}` : ""
|
||||
}`,
|
||||
thumbnail: `${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${DetermineFileType(value.type)}/upload/${
|
||||
process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS
|
||||
}/${value.key}`,
|
||||
src: GenerateSrcUrl(value),
|
||||
thumbnail: GenerateThumbUrl(value),
|
||||
thumbnailHeight: 225,
|
||||
thumbnailWidth: 225,
|
||||
isSelected: false,
|
||||
key: value.key,
|
||||
extension: value.extension,
|
||||
|
||||
id: value.id,
|
||||
type: value.type,
|
||||
size: value.size,
|
||||
@@ -52,28 +65,18 @@ function JobsDocumentsComponent({
|
||||
} else {
|
||||
let thumb;
|
||||
switch (fileType) {
|
||||
case "video":
|
||||
thumb = `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${fileType}/upload/${process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${value.key}`;
|
||||
break;
|
||||
case "raw":
|
||||
thumb = `${window.location.origin}/file.png`;
|
||||
break;
|
||||
default:
|
||||
thumb = `${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${fileType}/upload/${
|
||||
process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS
|
||||
}/${value.key}${value.extension ? `.${value.extension}` : ""}`;
|
||||
thumb = GenerateThumbUrl(value);
|
||||
break;
|
||||
}
|
||||
|
||||
const fileName = value.key.split("/").pop();
|
||||
acc.other.push({
|
||||
src: `${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${fileType}/upload/${value.key}${
|
||||
value.extension ? `.${value.extension}` : ""
|
||||
}`,
|
||||
source: GenerateSrcUrl(value),
|
||||
src: "",
|
||||
thumbnail: thumb,
|
||||
tags: [
|
||||
{
|
||||
@@ -99,6 +102,7 @@ function JobsDocumentsComponent({
|
||||
thumbnailHeight: 225,
|
||||
thumbnailWidth: 225,
|
||||
isSelected: false,
|
||||
|
||||
extension: value.extension,
|
||||
key: value.key,
|
||||
id: value.id,
|
||||
@@ -119,6 +123,9 @@ function JobsDocumentsComponent({
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Space wrap>
|
||||
<Button onClick={() => refetch && refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<JobsDocumentsGallerySelectAllComponent
|
||||
galleryImages={galleryImages}
|
||||
setGalleryImages={setgalleryImages}
|
||||
@@ -153,6 +160,26 @@ function JobsDocumentsComponent({
|
||||
<Gallery
|
||||
images={galleryImages.images}
|
||||
backdropClosesModal={true}
|
||||
currentImageWillChange={onCurrentImageChange}
|
||||
customControls={[
|
||||
<Button
|
||||
style={{
|
||||
float: "right",
|
||||
|
||||
zIndex: "5",
|
||||
}}
|
||||
onClick={() => {
|
||||
const newWindow = window.open(
|
||||
`${window.location.protocol}//${window.location.host}/edit?documentId=${galleryImages.images[index].id}`,
|
||||
"_blank",
|
||||
"noopener,noreferrer"
|
||||
);
|
||||
if (newWindow) newWindow.opener = null;
|
||||
}}
|
||||
>
|
||||
<EditFilled style={{}} />
|
||||
</Button>,
|
||||
]}
|
||||
onClickImage={(props) => {
|
||||
window.open(
|
||||
props.target.src,
|
||||
@@ -187,7 +214,7 @@ function JobsDocumentsComponent({
|
||||
}}
|
||||
onClickThumbnail={(index) => {
|
||||
window.open(
|
||||
galleryImages.other[index].src,
|
||||
galleryImages.other[index].source,
|
||||
"_blank",
|
||||
"toolbar=0,location=0,menubar=0"
|
||||
);
|
||||
|
||||
@@ -57,6 +57,9 @@ export function JobNotesComponent({
|
||||
dataIndex: "text",
|
||||
key: "text",
|
||||
ellipsis: true,
|
||||
render: (text, record) => (
|
||||
<span style={{ whiteSpace: "pre-line" }}>{text}</span>
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
@@ -237,6 +237,11 @@ export function PartsOrderListTableComponent({
|
||||
<DateFormatter>{record.deliver_by}</DateFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("parts_orders.fields.orderedby"),
|
||||
dataIndex: "orderedby",
|
||||
key: "orderedby",
|
||||
},
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
@@ -336,6 +341,7 @@ export function PartsOrderListTableComponent({
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
|
||||
@@ -73,6 +73,7 @@ export default function PartsOrderModalComponent({
|
||||
<Form.Item required={false} key={field.key}>
|
||||
<LayoutFormRow grow noDivider>
|
||||
<Form.Item
|
||||
span={8}
|
||||
label={t("parts_orders.fields.line_desc")}
|
||||
key={`${index}line_desc`}
|
||||
name={[field.name, "line_desc"]}
|
||||
|
||||
@@ -81,6 +81,7 @@ export function PartsOrderModalContainer({
|
||||
po: [
|
||||
{
|
||||
...values,
|
||||
orderedby: currentUser.email,
|
||||
jobid: jobId,
|
||||
user_email: currentUser.email,
|
||||
return: isReturn,
|
||||
|
||||
@@ -73,7 +73,7 @@ export const QUERY_BILLS_BY_JOBID = gql`
|
||||
order_date
|
||||
deliver_by
|
||||
return
|
||||
|
||||
orderedby
|
||||
parts_order_lines {
|
||||
id
|
||||
act_price
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
export const GET_DOCUMENT_BY_PK = gql`
|
||||
query GET_DOCUMENT_BY_PK($documentId: uuid!) {
|
||||
documents_by_pk(id: $documentId) {
|
||||
id
|
||||
name
|
||||
key
|
||||
type
|
||||
size
|
||||
takenat
|
||||
extension
|
||||
jobid
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const GET_DOCUMENTS_BY_JOB = gql`
|
||||
query GET_DOCUMENTS_BY_JOB($jobId: uuid!) {
|
||||
jobs_by_pk(id: $jobId) {
|
||||
|
||||
@@ -565,6 +565,7 @@ export const GET_JOB_BY_PK = gql`
|
||||
stripeid
|
||||
transactionid
|
||||
memo
|
||||
date
|
||||
}
|
||||
cccontracts {
|
||||
id
|
||||
@@ -1056,6 +1057,236 @@ export const SEARCH_FOR_JOBS = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_JOB_FOR_DUPE = gql`
|
||||
query QUERY_JOB_FOR_DUPE($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
id
|
||||
agt_addr1
|
||||
agt_addr2
|
||||
agt_city
|
||||
agt_co_id
|
||||
agt_co_nm
|
||||
agt_ct_fn
|
||||
agt_ct_ln
|
||||
agt_ct_ph
|
||||
agt_ct_phx
|
||||
agt_ctry
|
||||
agt_ea
|
||||
agt_faxx
|
||||
agt_fax
|
||||
agt_lic_no
|
||||
agt_ph1
|
||||
agt_ph1x
|
||||
agt_ph2
|
||||
agt_zip
|
||||
agt_st
|
||||
agt_ph2x
|
||||
area_of_damage
|
||||
cat_no
|
||||
cieca_stl
|
||||
cieca_ttl
|
||||
clm_addr1
|
||||
clm_addr2
|
||||
clm_city
|
||||
clm_ct_fn
|
||||
clm_ct_ln
|
||||
clm_ct_ph
|
||||
clm_ct_phx
|
||||
clm_ctry
|
||||
clm_ea
|
||||
clm_fax
|
||||
clm_faxx
|
||||
clm_ofc_id
|
||||
clm_ofc_nm
|
||||
clm_ph1
|
||||
clm_ph1x
|
||||
clm_ph2
|
||||
clm_ph2x
|
||||
clm_st
|
||||
clm_title
|
||||
clm_total
|
||||
clm_zip
|
||||
cust_pr
|
||||
est_addr1
|
||||
est_addr2
|
||||
est_city
|
||||
est_co_nm
|
||||
est_ct_fn
|
||||
est_ct_ln
|
||||
est_ctry
|
||||
est_ea
|
||||
est_ph1
|
||||
est_st
|
||||
est_zip
|
||||
federal_tax_rate
|
||||
ins_addr1
|
||||
ins_addr2
|
||||
ins_city
|
||||
ins_co_id
|
||||
ins_co_nm
|
||||
ins_ct_fn
|
||||
ins_ct_ln
|
||||
ins_ct_ph
|
||||
ins_ct_phx
|
||||
ins_ctry
|
||||
ins_ea
|
||||
ins_fax
|
||||
ins_faxx
|
||||
ins_memo
|
||||
ins_ph1
|
||||
ins_ph1x
|
||||
ins_ph2
|
||||
ins_ph2x
|
||||
ins_st
|
||||
ins_title
|
||||
ins_zip
|
||||
insd_addr1
|
||||
insd_addr2
|
||||
insd_city
|
||||
insd_co_nm
|
||||
insd_ctry
|
||||
insd_ea
|
||||
insd_fax
|
||||
insd_faxx
|
||||
insd_fn
|
||||
insd_ln
|
||||
insd_ph1
|
||||
insd_ph1x
|
||||
insd_ph2
|
||||
insd_ph2x
|
||||
insd_st
|
||||
insd_title
|
||||
insd_zip
|
||||
labor_rate_desc
|
||||
labor_rate_id
|
||||
local_tax_rate
|
||||
other_amount_payable
|
||||
owner_owing
|
||||
ownerid
|
||||
ownr_addr1
|
||||
ownr_addr2
|
||||
ownr_city
|
||||
ownr_co_nm
|
||||
ownr_ctry
|
||||
ownr_ea
|
||||
ownr_fax
|
||||
ownr_faxx
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph1x
|
||||
ownr_ph2
|
||||
ownr_ph2x
|
||||
ownr_st
|
||||
ownr_title
|
||||
ownr_zip
|
||||
parts_tax_rates
|
||||
plate_no
|
||||
plate_st
|
||||
po_number
|
||||
rate_la1
|
||||
rate_la2
|
||||
rate_la3
|
||||
rate_la4
|
||||
rate_laa
|
||||
rate_lab
|
||||
rate_lad
|
||||
rate_lae
|
||||
rate_lag
|
||||
rate_laf
|
||||
rate_lam
|
||||
rate_lar
|
||||
rate_las
|
||||
rate_lau
|
||||
rate_ma2s
|
||||
rate_ma2t
|
||||
rate_ma3s
|
||||
rate_mabl
|
||||
rate_macs
|
||||
rate_mahw
|
||||
rate_mapa
|
||||
rate_mash
|
||||
rate_matd
|
||||
regie_number
|
||||
selling_dealer
|
||||
selling_dealer_contact
|
||||
servicing_dealer
|
||||
servicing_dealer_contact
|
||||
shopid
|
||||
|
||||
state_tax_rate
|
||||
|
||||
tax_lbr_rt
|
||||
tax_levies_rt
|
||||
tax_paint_mat_rt
|
||||
tax_predis
|
||||
tax_prethr
|
||||
tax_pstthr
|
||||
tax_str_rt
|
||||
tax_sub_rt
|
||||
tax_thramt
|
||||
tax_tow_rt
|
||||
unit_number
|
||||
v_color
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
vehicleid
|
||||
joblines(where: { removed: { _eq: false } }) {
|
||||
act_price
|
||||
alt_co_id
|
||||
alt_overrd
|
||||
alt_part_i
|
||||
alt_partm
|
||||
bett_amt
|
||||
alt_partno
|
||||
bett_pctg
|
||||
bett_tax
|
||||
bett_type
|
||||
cert_part
|
||||
db_hrs
|
||||
db_price
|
||||
db_ref
|
||||
est_seq
|
||||
glass_flag
|
||||
id
|
||||
lbr_amt
|
||||
lbr_hrs_j
|
||||
lbr_inc
|
||||
lbr_op
|
||||
lbr_op_j
|
||||
lbr_tax
|
||||
lbr_typ_j
|
||||
line_desc
|
||||
line_ind
|
||||
line_ref
|
||||
misc_amt
|
||||
misc_sublt
|
||||
misc_tax
|
||||
mod_lb_hrs
|
||||
mod_lbr_ty
|
||||
oem_partno
|
||||
op_code_desc
|
||||
paint_stg
|
||||
paint_tone
|
||||
part_qty
|
||||
part_type
|
||||
price_inc
|
||||
price_j
|
||||
prt_dsmk_m
|
||||
prt_dsmk_p
|
||||
status
|
||||
tax_part
|
||||
unq_seq
|
||||
manual_line
|
||||
}
|
||||
driveable
|
||||
towin
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_ALL_JOB_FIELDS = gql`
|
||||
query QUERY_ALL_JOB_FIELDS($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
|
||||
@@ -164,6 +164,16 @@ export function* onSignInSuccess() {
|
||||
|
||||
export function* signInSuccessSaga({ payload }) {
|
||||
LogRocket.identify(payload.email);
|
||||
try {
|
||||
window.$crisp.push(["set", "user:email", [payload.email]]);
|
||||
window.$crisp.push([
|
||||
"set",
|
||||
"user:nickname",
|
||||
[payload.displayName || payload.email],
|
||||
]);
|
||||
} catch (error) {
|
||||
console.log("Error updating Crisp settings.", error);
|
||||
}
|
||||
|
||||
// if (!payload.email.includes("@imex.")) yield put(setInstanceId(payload.uid));
|
||||
analytics.setUserId(payload.email);
|
||||
@@ -220,6 +230,8 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
try {
|
||||
const userEmail = yield select((state) => state.user.currentUser.email);
|
||||
|
||||
window.$crisp.push(["set", "user:company", [payload.shopname]]);
|
||||
|
||||
const authRecord = payload.associations.filter(
|
||||
(a) => a.useremail === userEmail
|
||||
);
|
||||
|
||||
@@ -677,7 +677,7 @@
|
||||
"documents": {
|
||||
"actions": {
|
||||
"delete": "Delete Selected Documents",
|
||||
"download": "Download Selected Documents",
|
||||
"download": "Download Selected Images",
|
||||
"reassign": "Reassign to another Job",
|
||||
"selectallimages": "Select All Images",
|
||||
"selectallotherdocuments": "Select All Other Documents"
|
||||
@@ -1420,6 +1420,7 @@
|
||||
"entertimeticket": "Enter Time Tickets",
|
||||
"export": "Export",
|
||||
"export-logs": "Export Logs",
|
||||
"help": "Help",
|
||||
"home": "Home",
|
||||
"jobs": "Jobs",
|
||||
"owners": "Owners",
|
||||
@@ -1602,6 +1603,7 @@
|
||||
"oem_partno": "Part #",
|
||||
"order_date": "Order Date",
|
||||
"order_number": "Order Number",
|
||||
"orderedby": "Ordered By",
|
||||
"quantity": "Qty.",
|
||||
"return": "Return",
|
||||
"status": "Status"
|
||||
|
||||
@@ -1420,6 +1420,7 @@
|
||||
"entertimeticket": "",
|
||||
"export": "",
|
||||
"export-logs": "",
|
||||
"help": "",
|
||||
"home": "Casa",
|
||||
"jobs": "Trabajos",
|
||||
"owners": "propietarios",
|
||||
@@ -1602,6 +1603,7 @@
|
||||
"oem_partno": "",
|
||||
"order_date": "",
|
||||
"order_number": "",
|
||||
"orderedby": "",
|
||||
"quantity": "",
|
||||
"return": "",
|
||||
"status": ""
|
||||
|
||||
@@ -1420,6 +1420,7 @@
|
||||
"entertimeticket": "",
|
||||
"export": "",
|
||||
"export-logs": "",
|
||||
"help": "",
|
||||
"home": "Accueil",
|
||||
"jobs": "Emplois",
|
||||
"owners": "Propriétaires",
|
||||
@@ -1602,6 +1603,7 @@
|
||||
"oem_partno": "",
|
||||
"order_date": "",
|
||||
"order_number": "",
|
||||
"orderedby": "",
|
||||
"quantity": "",
|
||||
"return": "",
|
||||
"status": ""
|
||||
|
||||
@@ -230,6 +230,14 @@ export const TemplateList = (type, context) => {
|
||||
disabled: false,
|
||||
group: "financial",
|
||||
},
|
||||
ro_totals: {
|
||||
title: i18n.t("printcenter.jobs.ro_totals"),
|
||||
description: "All Jobs Notes",
|
||||
subject: i18n.t("printcenter.jobs.ro_totals"),
|
||||
key: "ro_totals",
|
||||
disabled: false,
|
||||
group: "financial",
|
||||
},
|
||||
job_costing_ro: {
|
||||
title: i18n.t("printcenter.jobs.job_costing_ro"),
|
||||
description: "All Jobs Notes",
|
||||
@@ -238,6 +246,22 @@ export const TemplateList = (type, context) => {
|
||||
disabled: false,
|
||||
group: "financial",
|
||||
},
|
||||
purchases_by_ro_detail: {
|
||||
title: i18n.t("printcenter.jobs.purchases_by_ro_detail"),
|
||||
description: "All Jobs Notes",
|
||||
subject: i18n.t("printcenter.jobs.purchases_by_ro_detail"),
|
||||
key: "purchases_by_ro_detail",
|
||||
disabled: false,
|
||||
group: "financial",
|
||||
},
|
||||
purchases_by_ro_summary: {
|
||||
title: i18n.t("printcenter.jobs.purchases_by_ro_summary"),
|
||||
description: "All Jobs Notes",
|
||||
subject: i18n.t("printcenter.jobs.purchases_by_ro_summary"),
|
||||
key: "purchases_by_ro_summary",
|
||||
disabled: false,
|
||||
group: "financial",
|
||||
},
|
||||
filing_coversheet_portrait: {
|
||||
title: i18n.t("printcenter.jobs.filing_coversheet_portrait"),
|
||||
description: "All Jobs Notes",
|
||||
@@ -361,6 +385,16 @@ export const TemplateList = (type, context) => {
|
||||
...(!type || type === "csi" ? {} : {}),
|
||||
...(!type || type === "report_center"
|
||||
? {
|
||||
payments_by_date: {
|
||||
title: i18n.t("reportcenter.templates.payments_by_date"),
|
||||
subject: i18n.t("reportcenter.templates.payments_by_date"),
|
||||
key: "payments_by_date",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.payments"),
|
||||
field: i18n.t("payments.fields.date"),
|
||||
},
|
||||
},
|
||||
payments_by_date_type: {
|
||||
title: i18n.t("reportcenter.templates.payments_by_date_type"),
|
||||
subject: i18n.t("reportcenter.templates.payments_by_date_type"),
|
||||
@@ -521,7 +555,28 @@ export const TemplateList = (type, context) => {
|
||||
idtype: "employee",
|
||||
disabled: false,
|
||||
},
|
||||
attendance: {
|
||||
title: i18n.t("reportcenter.templates.attendance"),
|
||||
subject: i18n.t("reportcenter.templates.attendance"),
|
||||
key: "attendance",
|
||||
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.timetickets"),
|
||||
field: i18n.t("timetickets.fields.date"),
|
||||
},
|
||||
},
|
||||
attendance_summary: {
|
||||
title: i18n.t("reportcenter.templates.attendance_summary"),
|
||||
subject: i18n.t("reportcenter.templates.attendance_summary"),
|
||||
key: "attendance_summary",
|
||||
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.timetickets"),
|
||||
field: i18n.t("timetickets.fields.date"),
|
||||
},
|
||||
},
|
||||
attendance_employee: {
|
||||
title: i18n.t("reportcenter.templates.attendance_employee"),
|
||||
subject: i18n.t("reportcenter.templates.attendance_employee"),
|
||||
@@ -559,15 +614,15 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
},
|
||||
|
||||
hours_sold_detail_closed_source: {
|
||||
hours_sold_detail_closed_ins_co: {
|
||||
title: i18n.t(
|
||||
"reportcenter.templates.hours_sold_detail_closed_source"
|
||||
"reportcenter.templates.hours_sold_detail_closed_ins_co"
|
||||
),
|
||||
description: "",
|
||||
subject: i18n.t(
|
||||
"reportcenter.templates.hours_sold_detail_closed_source"
|
||||
"reportcenter.templates.hours_sold_detail_closed_ins_co"
|
||||
),
|
||||
key: "hours_sold_detail_closed_source",
|
||||
key: "hours_sold_detail_closed_ins_co",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
@@ -589,15 +644,15 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
},
|
||||
|
||||
hours_sold_summary_closed_source: {
|
||||
hours_sold_summary_closed_ins_co: {
|
||||
title: i18n.t(
|
||||
"reportcenter.templates.hours_sold_summary_closed_source"
|
||||
"reportcenter.templates.hours_sold_summary_closed_ins_co"
|
||||
),
|
||||
description: "",
|
||||
subject: i18n.t(
|
||||
"reportcenter.templates.hours_sold_summary_closed_source"
|
||||
"reportcenter.templates.hours_sold_summary_closed_ins_co"
|
||||
),
|
||||
key: "hours_sold_summary_closed_source",
|
||||
key: "hours_sold_summary_closed_ins_co",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
@@ -619,15 +674,15 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
},
|
||||
|
||||
hours_sold_detail_open_source: {
|
||||
hours_sold_detail_open_ins_co: {
|
||||
title: i18n.t(
|
||||
"reportcenter.templates.hours_sold_detail_open_source"
|
||||
"reportcenter.templates.hours_sold_detail_open_ins_co"
|
||||
),
|
||||
description: "",
|
||||
subject: i18n.t(
|
||||
"reportcenter.templates.hours_sold_detail_open_source"
|
||||
"reportcenter.templates.hours_sold_detail_open_ins_co"
|
||||
),
|
||||
key: "hours_sold_detail_open_source",
|
||||
key: "hours_sold_detail_open_ins_co",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
@@ -649,15 +704,15 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
},
|
||||
|
||||
hours_sold_summary_open_source: {
|
||||
hours_sold_summary_open_ins_co: {
|
||||
title: i18n.t(
|
||||
"reportcenter.templates.hours_sold_summary_open_source"
|
||||
"reportcenter.templates.hours_sold_summary_open_ins_co"
|
||||
),
|
||||
description: "",
|
||||
subject: i18n.t(
|
||||
"reportcenter.templates.hours_sold_summary_open_source"
|
||||
"reportcenter.templates.hours_sold_summary_open_ins_cƒo"
|
||||
),
|
||||
key: "hours_sold_summary_open_source",
|
||||
key: "hours_sold_summary_open_ins_co",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
@@ -689,11 +744,11 @@ export const TemplateList = (type, context) => {
|
||||
field: i18n.t("jobs.fields.date_invoiced"),
|
||||
},
|
||||
},
|
||||
supplement_ratio_source: {
|
||||
title: i18n.t("reportcenter.templates.supplement_ratio_source"),
|
||||
supplement_ratio_ins_co: {
|
||||
title: i18n.t("reportcenter.templates.supplement_ratio_ins_co"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.supplement_ratio_source"),
|
||||
key: "supplement_ratio_source",
|
||||
subject: i18n.t("reportcenter.templates.supplement_ratio_ins_co"),
|
||||
key: "supplement_ratio_ins_co",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
@@ -727,11 +782,11 @@ export const TemplateList = (type, context) => {
|
||||
field: i18n.t("jobs.fields.date_invoiced"),
|
||||
},
|
||||
},
|
||||
job_costing_ro_source: {
|
||||
title: i18n.t("reportcenter.templates.job_costing_ro_source"),
|
||||
job_costing_ro_ins_co: {
|
||||
title: i18n.t("reportcenter.templates.job_costing_ro_ins_co"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.job_costing_ro_source"),
|
||||
key: "job_costing_ro_source",
|
||||
subject: i18n.t("reportcenter.templates.job_costing_ro_ins_co"),
|
||||
key: "job_costing_ro_ins_co",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
@@ -789,6 +844,67 @@ export const TemplateList = (type, context) => {
|
||||
field: i18n.t("jobs.fields.date_invoiced"),
|
||||
},
|
||||
},
|
||||
gsr_by_delivery_date: {
|
||||
title: i18n.t("reportcenter.templates.gsr_by_delivery_date"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.gsr_by_delivery_date"),
|
||||
key: "gsr_by_delivery_date",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.actual_delivery"),
|
||||
},
|
||||
},
|
||||
gsr_by_referral: {
|
||||
title: i18n.t("reportcenter.templates.gsr_by_referral"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.gsr_by_referral"),
|
||||
key: "gsr_by_referral",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_invoiced"),
|
||||
},
|
||||
},
|
||||
gsr_by_ro: {
|
||||
title: i18n.t("reportcenter.templates.gsr_by_ro"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.gsr_by_ro"),
|
||||
key: "gsr_by_ro",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_invoiced"),
|
||||
},
|
||||
},
|
||||
gsr_by_ins_co: {
|
||||
title: i18n.t("reportcenter.templates.gsr_by_ins_co"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.gsr_by_ins_co"),
|
||||
key: "gsr_by_ins_co",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_invoiced"),
|
||||
},
|
||||
},
|
||||
gsr_by_exported_date: {
|
||||
title: i18n.t("reportcenter.templates.gsr_by_exported_date"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.gsr_by_exported_date"),
|
||||
key: "gsr_by_exported_date",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_exported"),
|
||||
},
|
||||
},
|
||||
|
||||
gsr_by_estimator: {
|
||||
title: i18n.t("reportcenter.templates.gsr_by_estimator"),
|
||||
description: "",
|
||||
@@ -825,6 +941,126 @@ export const TemplateList = (type, context) => {
|
||||
field: i18n.t("jobs.fields.date_open"),
|
||||
},
|
||||
},
|
||||
open_orders_estimator: {
|
||||
title: i18n.t("reportcenter.templates.open_orders_estimator"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.open_orders_estimator"),
|
||||
key: "open_orders_estimator",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_open"),
|
||||
},
|
||||
},
|
||||
open_orders_ins_co: {
|
||||
title: i18n.t("reportcenter.templates.open_orders_ins_co"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.open_orders_ins_co"),
|
||||
key: "open_orders_ins_co",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_open"),
|
||||
},
|
||||
},
|
||||
export_payables: {
|
||||
title: i18n.t("reportcenter.templates.export_payables"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.export_payables"),
|
||||
key: "export_payables",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.exportlogs"),
|
||||
field: i18n.t("exportlogs.fields.createdat"),
|
||||
},
|
||||
},
|
||||
export_payments: {
|
||||
title: i18n.t("reportcenter.templates.export_payments"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.export_payments"),
|
||||
key: "export_payments",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.exportlogs"),
|
||||
field: i18n.t("exportlogs.fields.createdat"),
|
||||
},
|
||||
},
|
||||
export_receivables: {
|
||||
title: i18n.t("reportcenter.templates.export_receivables"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.export_receivables"),
|
||||
key: "export_receivables",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.exportlogs"),
|
||||
field: i18n.t("exportlogs.fields.createdat"),
|
||||
},
|
||||
},
|
||||
parts_backorder: {
|
||||
title: i18n.t("reportcenter.templates.parts_backorder"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.parts_backorder"),
|
||||
key: "parts_backorder",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.actual_in"),
|
||||
},
|
||||
},
|
||||
thank_you_date: {
|
||||
title: i18n.t("reportcenter.templates.thank_you_date"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.thank_you_date"),
|
||||
key: "thank_you_date",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_invoiced"),
|
||||
},
|
||||
},
|
||||
unclaimed_hrs: {
|
||||
title: i18n.t("reportcenter.templates.unclaimed_hrs"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.unclaimed_hrs"),
|
||||
key: "unclaimed_hrs",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_open"),
|
||||
},
|
||||
},
|
||||
work_in_progress_labour: {
|
||||
title: i18n.t("reportcenter.templates.work_in_progress_labour"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.work_in_progress_labour"),
|
||||
key: "work_in_progress_labour",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_open"),
|
||||
},
|
||||
},
|
||||
work_in_progress_payables: {
|
||||
title: i18n.t("reportcenter.templates.work_in_progress_payables"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.work_in_progress_payables"),
|
||||
key: "work_in_progress_payables",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_open"),
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(!type || type === "courtesycarcontract"
|
||||
|
||||
@@ -8302,6 +8302,11 @@ map-visit@^1.0.0:
|
||||
dependencies:
|
||||
object-visit "^1.0.0"
|
||||
|
||||
markerjs2@^2.8.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/markerjs2/-/markerjs2-2.8.1.tgz#33c455cc1edd8fa9a5e5b39ed782dcd1b923c917"
|
||||
integrity sha512-M9AflvjOD5aIcBM0HZWW6u1h/NRdzfq73B9ILv1YehF88PeF0tYT5HIsi9PaSJ6EUOR/vWysZN08f3EyDCJixw==
|
||||
|
||||
material-colors@^1.2.1:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."vehicles" ALTER COLUMN "v_vin" SET NOT NULL;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."vehicles" ALTER COLUMN "v_vin" DROP NOT NULL;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."conversations" DROP COLUMN "archived";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,6 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."conversations" ADD COLUMN "archived" boolean NOT NULL
|
||||
DEFAULT false;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."parts_orders" DROP COLUMN "orderedby";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."parts_orders" ADD COLUMN "orderedby" text NULL;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: alter table "public"."parts_orders" drop constraint "parts_orders_orderedby_fkey";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,10 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: |-
|
||||
alter table "public"."parts_orders"
|
||||
add constraint "parts_orders_orderedby_fkey"
|
||||
foreign key ("orderedby")
|
||||
references "public"."users"
|
||||
("email") on update set null on delete set null;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,37 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
type: drop_insert_permission
|
||||
- args:
|
||||
permission:
|
||||
check:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- created_at
|
||||
- deliver_by
|
||||
- id
|
||||
- jobid
|
||||
- order_date
|
||||
- order_number
|
||||
- return
|
||||
- returnfrombill
|
||||
- status
|
||||
- updated_at
|
||||
- user_email
|
||||
- vendorid
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
type: create_insert_permission
|
||||
@@ -0,0 +1,38 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
type: drop_insert_permission
|
||||
- args:
|
||||
permission:
|
||||
check:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- created_at
|
||||
- deliver_by
|
||||
- id
|
||||
- jobid
|
||||
- order_date
|
||||
- order_number
|
||||
- orderedby
|
||||
- return
|
||||
- returnfrombill
|
||||
- status
|
||||
- updated_at
|
||||
- user_email
|
||||
- vendorid
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
type: create_insert_permission
|
||||
@@ -0,0 +1,38 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- created_at
|
||||
- deliver_by
|
||||
- id
|
||||
- jobid
|
||||
- order_date
|
||||
- order_number
|
||||
- return
|
||||
- returnfrombill
|
||||
- status
|
||||
- updated_at
|
||||
- user_email
|
||||
- vendorid
|
||||
computed_fields: []
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
role: user
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,39 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- created_at
|
||||
- deliver_by
|
||||
- id
|
||||
- jobid
|
||||
- order_date
|
||||
- order_number
|
||||
- orderedby
|
||||
- return
|
||||
- returnfrombill
|
||||
- status
|
||||
- updated_at
|
||||
- user_email
|
||||
- vendorid
|
||||
computed_fields: []
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
role: user
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,36 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- created_at
|
||||
- deliver_by
|
||||
- id
|
||||
- jobid
|
||||
- order_date
|
||||
- order_number
|
||||
- returnfrombill
|
||||
- status
|
||||
- updated_at
|
||||
- user_email
|
||||
- vendorid
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -0,0 +1,37 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- created_at
|
||||
- deliver_by
|
||||
- id
|
||||
- jobid
|
||||
- order_date
|
||||
- order_number
|
||||
- orderedby
|
||||
- returnfrombill
|
||||
- status
|
||||
- updated_at
|
||||
- user_email
|
||||
- vendorid
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -0,0 +1,30 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: conversations
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: true
|
||||
columns:
|
||||
- phone_num
|
||||
- created_at
|
||||
- updated_at
|
||||
- bodyshopid
|
||||
- id
|
||||
computed_fields: []
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
role: user
|
||||
table:
|
||||
name: conversations
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,31 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: conversations
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: true
|
||||
columns:
|
||||
- archived
|
||||
- bodyshopid
|
||||
- created_at
|
||||
- id
|
||||
- phone_num
|
||||
- updated_at
|
||||
computed_fields: []
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
role: user
|
||||
table:
|
||||
name: conversations
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,29 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: conversations
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- phone_num
|
||||
- created_at
|
||||
- updated_at
|
||||
- bodyshopid
|
||||
- id
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: conversations
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -0,0 +1,30 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: conversations
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- archived
|
||||
- bodyshopid
|
||||
- created_at
|
||||
- id
|
||||
- phone_num
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: conversations
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -0,0 +1,12 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: |-
|
||||
alter table "public"."exportlog" drop constraint "exportlog_billid_fkey",
|
||||
add constraint "exportlog_billid_fkey"
|
||||
foreign key ("billid")
|
||||
references "public"."bills"
|
||||
("id")
|
||||
on update restrict
|
||||
on delete restrict;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,10 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: |-
|
||||
alter table "public"."exportlog" drop constraint "exportlog_billid_fkey",
|
||||
add constraint "exportlog_billid_fkey"
|
||||
foreign key ("billid")
|
||||
references "public"."bills"
|
||||
("id") on update cascade on delete cascade;
|
||||
type: run_sql
|
||||
@@ -1114,11 +1114,12 @@ tables:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- phone_num
|
||||
- created_at
|
||||
- updated_at
|
||||
- archived
|
||||
- bodyshopid
|
||||
- created_at
|
||||
- id
|
||||
- phone_num
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -1133,11 +1134,12 @@ tables:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- phone_num
|
||||
- created_at
|
||||
- updated_at
|
||||
- archived
|
||||
- bodyshopid
|
||||
- created_at
|
||||
- id
|
||||
- phone_num
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -3622,6 +3624,7 @@ tables:
|
||||
- jobid
|
||||
- order_date
|
||||
- order_number
|
||||
- orderedby
|
||||
- return
|
||||
- returnfrombill
|
||||
- status
|
||||
@@ -3638,6 +3641,7 @@ tables:
|
||||
- jobid
|
||||
- order_date
|
||||
- order_number
|
||||
- orderedby
|
||||
- return
|
||||
- returnfrombill
|
||||
- status
|
||||
@@ -3664,6 +3668,7 @@ tables:
|
||||
- jobid
|
||||
- order_date
|
||||
- order_number
|
||||
- orderedby
|
||||
- returnfrombill
|
||||
- status
|
||||
- updated_at
|
||||
|
||||
@@ -67,6 +67,12 @@ async function JobCostingMulti(req, res) {
|
||||
gpdollars: Dinero({ amount: 0 }),
|
||||
gppercent: null,
|
||||
gppercentFormatted: null,
|
||||
totalLaborGp: Dinero({ amount: 0 }),
|
||||
totalPartsGp: Dinero({ amount: 0 }),
|
||||
totalLaborGppercent: null,
|
||||
totalLaborGppercentFormatted: null,
|
||||
totalPartsGppercent: null,
|
||||
totalPartsGppercentFormatted: null,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -149,12 +155,39 @@ async function JobCostingMulti(req, res) {
|
||||
multiSummary.summaryData.gpdollars.add(
|
||||
costingData.summaryData.gpdollars
|
||||
);
|
||||
|
||||
multiSummary.summaryData.totalLaborGp =
|
||||
multiSummary.summaryData.totalLaborGp.add(
|
||||
costingData.summaryData.totalLaborGp
|
||||
);
|
||||
multiSummary.summaryData.totalPartsGp =
|
||||
multiSummary.summaryData.totalPartsGp.add(
|
||||
costingData.summaryData.totalPartsGp
|
||||
);
|
||||
|
||||
console.timeEnd(`SummaryOfCostingData-${job.id}`);
|
||||
//Take the summary data & add it to total summary data.
|
||||
});
|
||||
|
||||
//For each center, recalculate and toFormat() the values.
|
||||
multiSummary.summaryData.gpdollars;
|
||||
|
||||
multiSummary.summaryData.totalLaborGppercent = (
|
||||
(multiSummary.summaryData.totalLaborGp.getAmount() /
|
||||
multiSummary.summaryData.totalLaborSales.getAmount()) *
|
||||
100
|
||||
).toFixed(2);
|
||||
multiSummary.summaryData.totalLaborGppercentFormatted = formatGpPercent(
|
||||
multiSummary.summaryData.totalLaborGppercent
|
||||
);
|
||||
|
||||
multiSummary.summaryData.totalPartsGppercent = (
|
||||
(multiSummary.summaryData.totalPartsGp.getAmount() /
|
||||
multiSummary.summaryData.totalPartsSales.getAmount()) *
|
||||
100
|
||||
).toFixed(2);
|
||||
multiSummary.summaryData.totalPartsGppercentFormatted = formatGpPercent(
|
||||
multiSummary.summaryData.totalPartsGppercent
|
||||
);
|
||||
|
||||
multiSummary.summaryData.gppercent = (
|
||||
(multiSummary.summaryData.gpdollars.getAmount() /
|
||||
@@ -232,11 +265,11 @@ function GenerateCostingData(job) {
|
||||
acc.labor[laborProfitCenter].add(laborAmount);
|
||||
|
||||
if (val.mod_lbr_ty === "LAR") {
|
||||
if (!acc.labor[defaultProfits["MAPA"]])
|
||||
acc.labor[defaultProfits["MAPA"]] = Dinero();
|
||||
if (!acc.parts[defaultProfits["MAPA"]])
|
||||
acc.parts[defaultProfits["MAPA"]] = Dinero();
|
||||
|
||||
materialsHours.mapaHrs += val.mod_lb_hrs || 0;
|
||||
acc.labor[defaultProfits["MAPA"]] = acc.labor[
|
||||
acc.parts[defaultProfits["MAPA"]] = acc.parts[
|
||||
defaultProfits["MAPA"]
|
||||
].add(
|
||||
Dinero({
|
||||
@@ -244,11 +277,11 @@ function GenerateCostingData(job) {
|
||||
}).multiply(val.mod_lb_hrs || 0)
|
||||
);
|
||||
}
|
||||
if (!acc.labor[defaultProfits["MASH"]])
|
||||
acc.labor[defaultProfits["MASH"]] = Dinero();
|
||||
if (!acc.parts[defaultProfits["MASH"]])
|
||||
acc.parts[defaultProfits["MASH"]] = Dinero();
|
||||
|
||||
if (val.mod_lbr_ty !== "LAR") {
|
||||
acc.labor[defaultProfits["MASH"]] = acc.labor[
|
||||
acc.parts[defaultProfits["MASH"]] = acc.parts[
|
||||
defaultProfits["MASH"]
|
||||
].add(
|
||||
Dinero({
|
||||
@@ -369,6 +402,7 @@ function GenerateCostingData(job) {
|
||||
}).multiply(materialsHours.mapaHrs)
|
||||
);
|
||||
}
|
||||
|
||||
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) {
|
||||
if (
|
||||
!billTotalsByCostCenters[
|
||||
@@ -418,7 +452,14 @@ function GenerateCostingData(job) {
|
||||
totalLaborCost: Dinero({ amount: 0 }),
|
||||
totalPartsCost: Dinero({ amount: 0 }),
|
||||
totalCost: Dinero({ amount: 0 }),
|
||||
totalLaborGp: Dinero({ amount: 0 }),
|
||||
totalPartsGp: Dinero({ amount: 0 }),
|
||||
gpdollars: Dinero({ amount: 0 }),
|
||||
|
||||
totalLaborGppercent: null,
|
||||
totalLaborGppercentFormatted: null,
|
||||
totalPartsGppercent: null,
|
||||
totalPartsGppercentFormatted: null,
|
||||
gppercent: null,
|
||||
gppercentFormatted: null,
|
||||
};
|
||||
@@ -503,6 +544,31 @@ function GenerateCostingData(job) {
|
||||
}
|
||||
|
||||
//Final summary data massaging.
|
||||
|
||||
summaryData.totalLaborGp = summaryData.totalLaborSales.subtract(
|
||||
summaryData.totalLaborCost
|
||||
);
|
||||
summaryData.totalLaborGppercent = (
|
||||
(summaryData.totalLaborGp.getAmount() /
|
||||
summaryData.totalLaborSales.getAmount()) *
|
||||
100
|
||||
).toFixed(2);
|
||||
summaryData.totalLaborGppercentFormatted = formatGpPercent(
|
||||
summaryData.totalLaborGppercent
|
||||
);
|
||||
|
||||
summaryData.totalPartsGp = summaryData.totalPartsSales.subtract(
|
||||
summaryData.totalPartsCost
|
||||
);
|
||||
summaryData.totalPartsGppercent = (
|
||||
(summaryData.totalPartsGp.getAmount() /
|
||||
summaryData.totalPartsSales.getAmount()) *
|
||||
100
|
||||
).toFixed(2);
|
||||
summaryData.totalPartsGppercentFormatted = formatGpPercent(
|
||||
summaryData.totalPartsGppercent
|
||||
);
|
||||
|
||||
summaryData.gpdollars = summaryData.totalSales.subtract(
|
||||
summaryData.totalCost
|
||||
);
|
||||
@@ -510,6 +576,7 @@ function GenerateCostingData(job) {
|
||||
(summaryData.gpdollars.getAmount() / summaryData.totalSales.getAmount()) *
|
||||
100
|
||||
).toFixed(2);
|
||||
|
||||
if (isNaN(summaryData.gppercent)) summaryData.gppercentFormatted = 0;
|
||||
else if (!isFinite(summaryData.gppercent))
|
||||
summaryData.gppercentFormatted = "- ∞";
|
||||
|
||||
Reference in New Issue
Block a user