Merged in feature/2020-06-04 (pull request #95)

Feature/2020 06 04
This commit is contained in:
Patrick Fic
2021-06-02 23:19:55 +00:00
56 changed files with 1494 additions and 197 deletions

View File

@@ -24030,6 +24030,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>home</name> <name>home</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -26853,6 +26874,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>quantity</name> <name>quantity</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -29,6 +29,7 @@
"jsreport-browser-client-dist": "^1.3.0", "jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.9.17", "libphonenumber-js": "^1.9.17",
"logrocket": "^1.2.0", "logrocket": "^1.2.0",
"markerjs2": "^2.8.1",
"moment-business-days": "^1.2.0", "moment-business-days": "^1.2.0",
"phone": "^2.4.21", "phone": "^2.4.21",
"preval.macro": "^5.0.0", "preval.macro": "^5.0.0",

View File

@@ -3,11 +3,12 @@ import { ConfigProvider } from "antd";
import enLocale from "antd/es/locale/en_US"; import enLocale from "antd/es/locale/en_US";
import LogRocket from "logrocket"; import LogRocket from "logrocket";
import moment from "moment"; 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 GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
import client from "../utils/GraphQLClient"; import client from "../utils/GraphQLClient";
import App from "./App"; import App from "./App";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import JiraSupportComponent from "../components/jira-support-widget/jira-support-widget.component";
moment.locale("en-US"); moment.locale("en-US");
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp"); 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() { export default function AppContainer() {
const { t } = useTranslation(); 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 ( return (
<ApolloProvider client={client}> <ApolloProvider client={client}>
<ConfigProvider <ConfigProvider
@@ -30,6 +48,7 @@ export default function AppContainer() {
> >
<GlobalLoadingBar /> <GlobalLoadingBar />
<App /> <App />
<JiraSupportComponent />
</ConfigProvider> </ConfigProvider>
</ApolloProvider> </ApolloProvider>
); );

View File

@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Route, Switch } from "react-router-dom"; import { Route, Switch } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
import ErrorBoundary from "../components/error-boundary/error-boundary.component"; import ErrorBoundary from "../components/error-boundary/error-boundary.component";
//Component Imports //Component Imports
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
@@ -39,8 +40,12 @@ const mapDispatchToProps = (dispatch) => ({
export function App({ checkUserSession, currentUser, online, setOnline }) { export function App({ checkUserSession, currentUser, online, setOnline }) {
useEffect(() => { useEffect(() => {
if (!navigator.onLine) {
setOnline(false);
}
checkUserSession(); checkUserSession();
}, [checkUserSession]); }, [checkUserSession, setOnline]);
//const b = Grid.useBreakpoint(); //const b = Grid.useBreakpoint();
// console.log("Breakpoints:", b); // console.log("Breakpoints:", b);
@@ -118,6 +123,13 @@ export function App({ checkUserSession, currentUser, online, setOnline }) {
component={TechPageContainer} component={TechPageContainer}
/> />
</ErrorBoundary> </ErrorBoundary>
<ErrorBoundary>
<PrivateRoute
isAuthorized={currentUser.authorized}
path="/edit"
component={DocumentEditorContainer}
/>
</ErrorBoundary>
</Suspense> </Suspense>
</Switch> </Switch>
); );

View File

@@ -5,7 +5,7 @@ import { Link } from "react-router-dom";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter"; 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 PaymentExportButton from "../payment-export-button/payment-export-button.component";
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-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"), title: t("payments.fields.date"),
dataIndex: "date", dataIndex: "date",
key: "date", key: "date",
sorter: (a, b) => alphaSort(a.date, b.date), sorter: (a, b) => dateSort(a.date, b.date),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "date" && state.sortedInfo.order, state.sortedInfo.columnKey === "date" && state.sortedInfo.order,
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>, 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"), title: t("jobs.fields.owner"),
dataIndex: "owner", dataIndex: "owner",
@@ -61,7 +54,7 @@ export default function AccountingPayablesTableComponent({
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln), sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "ownr_ln" && state.sortedInfo.order, state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
return record.job.owner ? ( return record.job.owner ? (
<Link to={"/manage/owners/" + record.job.owner.id}> <Link to={"/manage/owners/" + record.job.owner.id}>

View File

@@ -1,3 +1,4 @@
import { Space } from "antd";
import React from "react"; import React from "react";
import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component"; 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 }) { export default function ChatConversationTitle({ conversation }) {
return ( return (
<div> <Space flex>
<div className="imex-flex-row"> <PhoneNumberFormatter>
<ChatConversationTitleTags {conversation && conversation.phone_num}
jobConversations={ </PhoneNumberFormatter>
(conversation && conversation.job_conversations) || [] <ChatConversationTitleTags
} jobConversations={
/> (conversation && conversation.job_conversations) || []
<ChatTagRoContainer conversation={conversation || []} /> }
</div> />
<div className="imex-flex-row"> <ChatTagRoContainer conversation={conversation || []} />
<PhoneNumberFormatter> </Space>
{conversation && conversation.phone_num}
</PhoneNumberFormatter>
</div>
</div>
); );
} }

View File

@@ -1,5 +1,5 @@
import { CloseCircleOutlined, LoadingOutlined } from "@ant-design/icons"; import { CloseCircleOutlined, LoadingOutlined } from "@ant-design/icons";
import { Select, Empty } from "antd"; import { Select, Empty, Space } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -13,27 +13,27 @@ export default function ChatTagRoComponent({
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div> <Space flex>
<Select <div style={{ width: "15rem" }}>
showSearch <Select
autoFocus showSearch
style={{ autoFocus
width: 300, dropdownMatchSelectWidth
}} placeholder={t("general.labels.search")}
placeholder={t("general.labels.search")} filterOption={false}
filterOption={false} onSearch={handleSearch}
onSearch={handleSearch} onSelect={handleInsertTag}
onSelect={handleInsertTag} notFoundContent={loading ? <LoadingOutlined /> : <Empty />}
notFoundContent={loading ? <LoadingOutlined /> : <Empty />} >
> {roOptions.map((item, idx) => (
{roOptions.map((item, idx) => ( <Select.Option key={item.id || idx}>
<Select.Option key={item.id || idx}> {` ${item.ro_number || ""} | ${item.ownr_fn || ""} ${
{` ${item.ro_number || ""} | ${item.ownr_fn || ""} ${ item.ownr_ln || ""
item.ownr_ln || "" } ${item.ownr_co_nm || ""}`}
} ${item.ownr_co_nm || ""}`} </Select.Option>
</Select.Option> ))}
))} </Select>
</Select> </div>
{loading ? <LoadingOutlined /> : null} {loading ? <LoadingOutlined /> : null}
{loading ? ( {loading ? (
@@ -41,6 +41,6 @@ export default function ChatTagRoComponent({
) : ( ) : (
<CloseCircleOutlined onClick={() => setVisible(false)} /> <CloseCircleOutlined onClick={() => setVisible(false)} />
)} )}
</div> </Space>
); );
} }

View File

@@ -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);

View File

@@ -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>
);
}

View File

@@ -21,8 +21,13 @@ export const handleUpload = (ev, context) => {
const { onError, onSuccess, onProgress } = ev; const { onError, onSuccess, onProgress } = ev;
const { bodyshop, jobId } = context; const { bodyshop, jobId } = context;
let key = `${bodyshop.id}/${jobId}/${ev.file.name.replace(/\.[^/.]+$/, "")}`; const fileName = ev.file.name || ev.filename;
let extension = ev.file.name.split(".").pop();
let key = `${bodyshop.id}/${jobId}/${fileName.replace(
/\.[^/.]+$/,
""
)}-${new Date().getTime()}`;
let extension = fileName.split(".").pop();
uploadToCloudinary( uploadToCloudinary(
key, key,
extension, extension,

View File

@@ -57,6 +57,23 @@ ${this.state.error.stack}
if (this.state.hasErrored === true) { if (this.state.hasErrored === true) {
logImEXEvent("error_boundary_rendered", { error, info }); 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 ( return (
<div> <div>
<Result <Result

View File

@@ -20,6 +20,7 @@ import Icon, {
ToolFilled, ToolFilled,
UnorderedListOutlined, UnorderedListOutlined,
UserOutlined, UserOutlined,
QuestionCircleFilled,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { Layout, Menu } from "antd"; import { Layout, Menu } from "antd";
import React from "react"; import React from "react";
@@ -284,6 +285,15 @@ function Header({
<Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link> <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </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 <Menu.SubMenu
style={{ float: "right" }} style={{ float: "right" }}
title={ title={

View File

@@ -1,28 +1,28 @@
import React, { useEffect } from "react"; import React from "react";
export default function JiraSupportComponent() { export default function JiraSupportComponent() {
useScript(); //useScript();
return <div></div>; return <div></div>;
} }
const useScript = () => { // const useScript = () => {
useEffect(() => { // useEffect(() => {
const script = document.createElement("script"); // const script = document.createElement("script");
script.src = "https://jsd-widget.atlassian.com/assets/embed.js"; // script.src = "https://jsd-widget.atlassian.com/assets/embed.js";
script.setAttribute("data-jsd-embedded", true); // script.setAttribute("data-jsd-embedded", true);
script.setAttribute("data-key", "d69bb65c-1dd3-483f-b109-66a970d03f44"); // script.setAttribute("data-key", "d69bb65c-1dd3-483f-b109-66a970d03f44");
script.setAttribute("data-base-url", "https://jsd-widget.atlassian.com"); // script.setAttribute("data-base-url", "https://jsd-widget.atlassian.com");
//script.async = true; // //script.async = true;
script.onload = () => { // script.onload = () => {
var DOMContentLoaded_event = document.createEvent("Event"); // var DOMContentLoaded_event = document.createEvent("Event");
DOMContentLoaded_event.initEvent("DOMContentLoaded", true, true); // DOMContentLoaded_event.initEvent("DOMContentLoaded", true, true);
window.document.dispatchEvent(DOMContentLoaded_event); // window.document.dispatchEvent(DOMContentLoaded_event);
}; // };
document.head.appendChild(script); // document.head.appendChild(script);
return () => { // return () => {
document.head.removeChild(script); // document.head.removeChild(script);
}; // };
}, []); // }, []);
}; // };

View File

@@ -29,7 +29,7 @@ export default function JobDetailCardsNotesComponent({ loading, data }) {
bordered bordered
dataSource={data.notes} dataSource={data.notes}
renderItem={(item) => ( renderItem={(item) => (
<List.Item> <List.Item style={{ whiteSpace: "pre-line" }}>
{item.critical ? ( {item.critical ? (
<EyeInvisibleFilled style={{ margin: 4, color: "red" }} /> <EyeInvisibleFilled style={{ margin: 4, color: "red" }} />
) : null} ) : null}

View File

@@ -22,6 +22,7 @@ import { createStructuredSelector } from "reselect";
import { DELETE_JOB_LINE_BY_PK } from "../../graphql/jobs-lines.queries"; import { DELETE_JOB_LINE_BY_PK } from "../../graphql/jobs-lines.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { onlyUnique } from "../../utils/arrayHelper"; import { onlyUnique } from "../../utils/arrayHelper";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
@@ -37,6 +38,7 @@ import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.con
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
technician: selectTechnician,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -48,6 +50,7 @@ const mapDispatchToProps = (dispatch) => ({
export function JobLinesComponent({ export function JobLinesComponent({
jobRO, jobRO,
technician,
setPartsOrderContext, setPartsOrderContext,
loading, loading,
refetch, refetch,
@@ -366,7 +369,8 @@ export function JobLinesComponent({
disabled={ disabled={
(job && !job.converted) || (job && !job.converted) ||
(selectedLines.length > 0 ? false : true) || (selectedLines.length > 0 ? false : true) ||
jobRO jobRO ||
technician
} }
onClick={() => { onClick={() => {
setPartsOrderContext({ setPartsOrderContext({
@@ -401,7 +405,7 @@ export function JobLinesComponent({
<Button>{t("jobs.actions.mark")}</Button> <Button>{t("jobs.actions.mark")}</Button>
</Dropdown> </Dropdown>
<Button <Button
disabled={jobRO} disabled={jobRO || technician}
onClick={() => { onClick={() => {
setJobLineEditContext({ setJobLineEditContext({
actions: { refetch: refetch }, actions: { refetch: refetch },

View File

@@ -8,8 +8,8 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort, dateSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
@@ -40,14 +40,14 @@ export function JobPayments({
}); });
const columns = [ const columns = [
{ {
title: t("payments.fields.created_at"), title: t("payments.fields.date"),
dataIndex: "created_at", dataIndex: "date",
key: "created_at", key: "date",
sorter: (a, b) => dateSort(a.date, b.date),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "created_at" && state.sortedInfo.order, state.sortedInfo.columnKey === "date" && state.sortedInfo.order,
render: (text, record) => ( render: (text, record) => <DateFormatter>{record.date}</DateFormatter>,
<DateTimeFormatter>{record.created_at}</DateTimeFormatter>
),
}, },
{ {
title: t("payments.fields.payer"), title: t("payments.fields.payer"),

View File

@@ -104,7 +104,10 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
).data; ).data;
let existingVehicles; 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. //There's vehicle data, need to double check the VIN.
existingVehicles = await client.query({ existingVehicles = await client.query({
query: SEARCH_VEHICLE_BY_VIN, query: SEARCH_VEHICLE_BY_VIN,

View File

@@ -1,10 +1,7 @@
import Axios from "axios"; import Axios from "axios";
import _ from "lodash"; import _ from "lodash";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { import { INSERT_NEW_JOB, QUERY_JOB_FOR_DUPE } from "../../graphql/jobs.queries";
INSERT_NEW_JOB,
QUERY_ALL_JOB_FIELDS,
} from "../../graphql/jobs.queries";
export default async function DuplicateJob( export default async function DuplicateJob(
apolloClient, apolloClient,
@@ -18,33 +15,21 @@ export default async function DuplicateJob(
const { defaultOpenStatus } = config; const { defaultOpenStatus } = config;
//get a list of all fields on the job //get a list of all fields on the job
const res = await apolloClient.query({ const res = await apolloClient.query({
query: QUERY_ALL_JOB_FIELDS, query: QUERY_JOB_FOR_DUPE,
variables: { id: jobId }, variables: { id: jobId },
}); });
console.log("res", res); 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 = { const newJob = {
date_estimated: new Date(), ...existingJob,
shopid: existingJob.shopid,
status: defaultOpenStatus, 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); const _tempLines = _.cloneDeep(existingJob.joblines);
@@ -54,24 +39,20 @@ export default async function DuplicateJob(
line.manual_line = true; line.manual_line = true;
}); });
newJob.joblines = keepJobLines ? _tempLines : []; newJob.joblines = keepJobLines ? _tempLines : [];
newJob.job_totals = (
await Axios.post("/job/totals", {
job: newJob,
})
).data;
delete newJob.joblines; delete newJob.joblines;
newJob.joblines = keepJobLines ? { data: _tempLines } : null; newJob.joblines = keepJobLines ? { data: _tempLines } : null;
apolloClient const res2 = await apolloClient.mutate({
.mutate({ mutation: INSERT_NEW_JOB,
mutation: INSERT_NEW_JOB, variables: { job: [newJob] },
variables: { job: [newJob] }, });
}) await Axios.post("/job/totalsssu", {
.then((res2) => { id: res2.data.insert_jobs.returning[0].id,
if (completionCallback) });
completionCallback(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. //insert the new job. call the callback with the returned ID when done.

View File

@@ -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
}`;

View File

@@ -14,11 +14,11 @@ export default function JobsDocumentsDownloadButton({
const [download, setDownload] = useState(null); const [download, setDownload] = useState(null);
const imagesToDownload = [ const imagesToDownload = [
...galleryImages.images.filter((image) => image.isSelected), ...galleryImages.images.filter((image) => image.isSelected),
...galleryImages.other.filter((image) => image.isSelected), // ...galleryImages.other.filter((image) => image.isSelected),
]; ];
const handleDownload = () => { const handleDownload = () => {
logImEXEvent("jobs_documents_download"); logImEXEvent("jobs_documents_download");
axios axios
.post("/media/download", { .post("/media/download", {
ids: imagesToDownload.map((_) => _.key), ids: imagesToDownload.map((_) => _.key),
@@ -27,12 +27,8 @@ export default function JobsDocumentsDownloadButton({
// window.open(r.data); // window.open(r.data);
downloadAs( downloadAs(
r.data, r.data,
`${identifier || "images"}.zip`, `${identifier || "documents"}.zip`,
(progressEvent) => { (progressEvent) => {
const percentage = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(progressEvent, percentage);
setDownload((currentDownloadState) => { setDownload((currentDownloadState) => {
return { return {
downloaded: progressEvent.loaded || 0, downloaded: progressEvent.loaded || 0,

View File

@@ -1,10 +1,11 @@
import { FileExcelFilled } from "@ant-design/icons"; import { FileExcelFilled, EditFilled, SyncOutlined } from "@ant-design/icons";
import { Card, Col, Row, Space } from "antd"; import { Card, Col, Row, Space, Button } from "antd";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Gallery from "react-grid-gallery"; import Gallery from "react-grid-gallery";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import DocumentsUploadComponent from "../documents-upload/documents-upload.component"; import DocumentsUploadComponent from "../documents-upload/documents-upload.component";
import { DetermineFileType } from "../documents-upload/documents-upload.utility"; import { DetermineFileType } from "../documents-upload/documents-upload.utility";
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility";
import JobsDocumentsDownloadButton from "./jobs-document-gallery.download.component"; import JobsDocumentsDownloadButton from "./jobs-document-gallery.download.component";
import JobsDocumentsGalleryReassign from "./jobs-document-gallery.reassign.component"; import JobsDocumentsGalleryReassign from "./jobs-document-gallery.reassign.component";
import JobsDocumentsDeleteButton from "./jobs-documents-gallery.delete.component"; import JobsDocumentsDeleteButton from "./jobs-documents-gallery.delete.component";
@@ -22,6 +23,25 @@ function JobsDocumentsComponent({
}) { }) {
const [galleryImages, setgalleryImages] = useState({ images: [], other: [] }); const [galleryImages, setgalleryImages] = useState({ images: [], other: [] });
const { t } = useTranslation(); 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(() => { useEffect(() => {
let documents = data.reduce( let documents = data.reduce(
@@ -29,21 +49,14 @@ function JobsDocumentsComponent({
const fileType = DetermineFileType(value.type); const fileType = DetermineFileType(value.type);
if (value.type.startsWith("image")) { if (value.type.startsWith("image")) {
acc.images.push({ acc.images.push({
src: `${ src: GenerateSrcUrl(value),
process.env.REACT_APP_CLOUDINARY_ENDPOINT thumbnail: GenerateThumbUrl(value),
}/${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}`,
thumbnailHeight: 225, thumbnailHeight: 225,
thumbnailWidth: 225, thumbnailWidth: 225,
isSelected: false, isSelected: false,
key: value.key, key: value.key,
extension: value.extension, extension: value.extension,
id: value.id, id: value.id,
type: value.type, type: value.type,
size: value.size, size: value.size,
@@ -52,28 +65,18 @@ function JobsDocumentsComponent({
} else { } else {
let thumb; let thumb;
switch (fileType) { 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": case "raw":
thumb = `${window.location.origin}/file.png`; thumb = `${window.location.origin}/file.png`;
break; break;
default: default:
thumb = `${ thumb = GenerateThumbUrl(value);
process.env.REACT_APP_CLOUDINARY_ENDPOINT
}/${fileType}/upload/${
process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS
}/${value.key}${value.extension ? `.${value.extension}` : ""}`;
break; break;
} }
const fileName = value.key.split("/").pop(); const fileName = value.key.split("/").pop();
acc.other.push({ acc.other.push({
src: `${ source: GenerateSrcUrl(value),
process.env.REACT_APP_CLOUDINARY_ENDPOINT src: "",
}/${fileType}/upload/${value.key}${
value.extension ? `.${value.extension}` : ""
}`,
thumbnail: thumb, thumbnail: thumb,
tags: [ tags: [
{ {
@@ -99,6 +102,7 @@ function JobsDocumentsComponent({
thumbnailHeight: 225, thumbnailHeight: 225,
thumbnailWidth: 225, thumbnailWidth: 225,
isSelected: false, isSelected: false,
extension: value.extension, extension: value.extension,
key: value.key, key: value.key,
id: value.id, id: value.id,
@@ -119,6 +123,9 @@ function JobsDocumentsComponent({
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={24}> <Col span={24}>
<Space wrap> <Space wrap>
<Button onClick={() => refetch && refetch()}>
<SyncOutlined />
</Button>
<JobsDocumentsGallerySelectAllComponent <JobsDocumentsGallerySelectAllComponent
galleryImages={galleryImages} galleryImages={galleryImages}
setGalleryImages={setgalleryImages} setGalleryImages={setgalleryImages}
@@ -153,6 +160,26 @@ function JobsDocumentsComponent({
<Gallery <Gallery
images={galleryImages.images} images={galleryImages.images}
backdropClosesModal={true} 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) => { onClickImage={(props) => {
window.open( window.open(
props.target.src, props.target.src,
@@ -187,7 +214,7 @@ function JobsDocumentsComponent({
}} }}
onClickThumbnail={(index) => { onClickThumbnail={(index) => {
window.open( window.open(
galleryImages.other[index].src, galleryImages.other[index].source,
"_blank", "_blank",
"toolbar=0,location=0,menubar=0" "toolbar=0,location=0,menubar=0"
); );

View File

@@ -57,6 +57,9 @@ export function JobNotesComponent({
dataIndex: "text", dataIndex: "text",
key: "text", key: "text",
ellipsis: true, ellipsis: true,
render: (text, record) => (
<span style={{ whiteSpace: "pre-line" }}>{text}</span>
),
}, },
{ {

View File

@@ -237,6 +237,11 @@ export function PartsOrderListTableComponent({
<DateFormatter>{record.deliver_by}</DateFormatter> <DateFormatter>{record.deliver_by}</DateFormatter>
), ),
}, },
{
title: t("parts_orders.fields.orderedby"),
dataIndex: "orderedby",
key: "orderedby",
},
{ {
title: t("general.labels.actions"), title: t("general.labels.actions"),
dataIndex: "actions", dataIndex: "actions",
@@ -336,6 +341,7 @@ export function PartsOrderListTableComponent({
/> />
), ),
}, },
{ {
title: t("general.labels.actions"), title: t("general.labels.actions"),
dataIndex: "actions", dataIndex: "actions",

View File

@@ -73,6 +73,7 @@ export default function PartsOrderModalComponent({
<Form.Item required={false} key={field.key}> <Form.Item required={false} key={field.key}>
<LayoutFormRow grow noDivider> <LayoutFormRow grow noDivider>
<Form.Item <Form.Item
span={8}
label={t("parts_orders.fields.line_desc")} label={t("parts_orders.fields.line_desc")}
key={`${index}line_desc`} key={`${index}line_desc`}
name={[field.name, "line_desc"]} name={[field.name, "line_desc"]}

View File

@@ -81,6 +81,7 @@ export function PartsOrderModalContainer({
po: [ po: [
{ {
...values, ...values,
orderedby: currentUser.email,
jobid: jobId, jobid: jobId,
user_email: currentUser.email, user_email: currentUser.email,
return: isReturn, return: isReturn,

View File

@@ -73,7 +73,7 @@ export const QUERY_BILLS_BY_JOBID = gql`
order_date order_date
deliver_by deliver_by
return return
orderedby
parts_order_lines { parts_order_lines {
id id
act_price act_price

View File

@@ -1,5 +1,20 @@
import { gql } from "@apollo/client"; 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` export const GET_DOCUMENTS_BY_JOB = gql`
query GET_DOCUMENTS_BY_JOB($jobId: uuid!) { query GET_DOCUMENTS_BY_JOB($jobId: uuid!) {
jobs_by_pk(id: $jobId) { jobs_by_pk(id: $jobId) {

View File

@@ -565,6 +565,7 @@ export const GET_JOB_BY_PK = gql`
stripeid stripeid
transactionid transactionid
memo memo
date
} }
cccontracts { cccontracts {
id 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` export const QUERY_ALL_JOB_FIELDS = gql`
query QUERY_ALL_JOB_FIELDS($id: uuid!) { query QUERY_ALL_JOB_FIELDS($id: uuid!) {
jobs_by_pk(id: $id) { jobs_by_pk(id: $id) {

View File

@@ -164,6 +164,16 @@ export function* onSignInSuccess() {
export function* signInSuccessSaga({ payload }) { export function* signInSuccessSaga({ payload }) {
LogRocket.identify(payload.email); 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)); // if (!payload.email.includes("@imex.")) yield put(setInstanceId(payload.uid));
analytics.setUserId(payload.email); analytics.setUserId(payload.email);
@@ -220,6 +230,8 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
try { try {
const userEmail = yield select((state) => state.user.currentUser.email); const userEmail = yield select((state) => state.user.currentUser.email);
window.$crisp.push(["set", "user:company", [payload.shopname]]);
const authRecord = payload.associations.filter( const authRecord = payload.associations.filter(
(a) => a.useremail === userEmail (a) => a.useremail === userEmail
); );

View File

@@ -677,7 +677,7 @@
"documents": { "documents": {
"actions": { "actions": {
"delete": "Delete Selected Documents", "delete": "Delete Selected Documents",
"download": "Download Selected Documents", "download": "Download Selected Images",
"reassign": "Reassign to another Job", "reassign": "Reassign to another Job",
"selectallimages": "Select All Images", "selectallimages": "Select All Images",
"selectallotherdocuments": "Select All Other Documents" "selectallotherdocuments": "Select All Other Documents"
@@ -1420,6 +1420,7 @@
"entertimeticket": "Enter Time Tickets", "entertimeticket": "Enter Time Tickets",
"export": "Export", "export": "Export",
"export-logs": "Export Logs", "export-logs": "Export Logs",
"help": "Help",
"home": "Home", "home": "Home",
"jobs": "Jobs", "jobs": "Jobs",
"owners": "Owners", "owners": "Owners",
@@ -1602,6 +1603,7 @@
"oem_partno": "Part #", "oem_partno": "Part #",
"order_date": "Order Date", "order_date": "Order Date",
"order_number": "Order Number", "order_number": "Order Number",
"orderedby": "Ordered By",
"quantity": "Qty.", "quantity": "Qty.",
"return": "Return", "return": "Return",
"status": "Status" "status": "Status"

View File

@@ -1420,6 +1420,7 @@
"entertimeticket": "", "entertimeticket": "",
"export": "", "export": "",
"export-logs": "", "export-logs": "",
"help": "",
"home": "Casa", "home": "Casa",
"jobs": "Trabajos", "jobs": "Trabajos",
"owners": "propietarios", "owners": "propietarios",
@@ -1602,6 +1603,7 @@
"oem_partno": "", "oem_partno": "",
"order_date": "", "order_date": "",
"order_number": "", "order_number": "",
"orderedby": "",
"quantity": "", "quantity": "",
"return": "", "return": "",
"status": "" "status": ""

View File

@@ -1420,6 +1420,7 @@
"entertimeticket": "", "entertimeticket": "",
"export": "", "export": "",
"export-logs": "", "export-logs": "",
"help": "",
"home": "Accueil", "home": "Accueil",
"jobs": "Emplois", "jobs": "Emplois",
"owners": "Propriétaires", "owners": "Propriétaires",
@@ -1602,6 +1603,7 @@
"oem_partno": "", "oem_partno": "",
"order_date": "", "order_date": "",
"order_number": "", "order_number": "",
"orderedby": "",
"quantity": "", "quantity": "",
"return": "", "return": "",
"status": "" "status": ""

View File

@@ -230,6 +230,14 @@ export const TemplateList = (type, context) => {
disabled: false, disabled: false,
group: "financial", 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: { job_costing_ro: {
title: i18n.t("printcenter.jobs.job_costing_ro"), title: i18n.t("printcenter.jobs.job_costing_ro"),
description: "All Jobs Notes", description: "All Jobs Notes",
@@ -238,6 +246,22 @@ export const TemplateList = (type, context) => {
disabled: false, disabled: false,
group: "financial", 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: { filing_coversheet_portrait: {
title: i18n.t("printcenter.jobs.filing_coversheet_portrait"), title: i18n.t("printcenter.jobs.filing_coversheet_portrait"),
description: "All Jobs Notes", description: "All Jobs Notes",
@@ -361,6 +385,16 @@ export const TemplateList = (type, context) => {
...(!type || type === "csi" ? {} : {}), ...(!type || type === "csi" ? {} : {}),
...(!type || type === "report_center" ...(!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: { payments_by_date_type: {
title: i18n.t("reportcenter.templates.payments_by_date_type"), title: i18n.t("reportcenter.templates.payments_by_date_type"),
subject: 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", idtype: "employee",
disabled: false, 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: { attendance_employee: {
title: i18n.t("reportcenter.templates.attendance_employee"), title: i18n.t("reportcenter.templates.attendance_employee"),
subject: 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( title: i18n.t(
"reportcenter.templates.hours_sold_detail_closed_source" "reportcenter.templates.hours_sold_detail_closed_ins_co"
), ),
description: "", description: "",
subject: i18n.t( 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", //idtype: "vendor",
disabled: false, disabled: false,
rangeFilter: { rangeFilter: {
@@ -589,15 +644,15 @@ export const TemplateList = (type, context) => {
}, },
}, },
hours_sold_summary_closed_source: { hours_sold_summary_closed_ins_co: {
title: i18n.t( title: i18n.t(
"reportcenter.templates.hours_sold_summary_closed_source" "reportcenter.templates.hours_sold_summary_closed_ins_co"
), ),
description: "", description: "",
subject: i18n.t( 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", //idtype: "vendor",
disabled: false, disabled: false,
rangeFilter: { rangeFilter: {
@@ -619,15 +674,15 @@ export const TemplateList = (type, context) => {
}, },
}, },
hours_sold_detail_open_source: { hours_sold_detail_open_ins_co: {
title: i18n.t( title: i18n.t(
"reportcenter.templates.hours_sold_detail_open_source" "reportcenter.templates.hours_sold_detail_open_ins_co"
), ),
description: "", description: "",
subject: i18n.t( 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", //idtype: "vendor",
disabled: false, disabled: false,
rangeFilter: { rangeFilter: {
@@ -649,15 +704,15 @@ export const TemplateList = (type, context) => {
}, },
}, },
hours_sold_summary_open_source: { hours_sold_summary_open_ins_co: {
title: i18n.t( title: i18n.t(
"reportcenter.templates.hours_sold_summary_open_source" "reportcenter.templates.hours_sold_summary_open_ins_co"
), ),
description: "", description: "",
subject: i18n.t( 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", //idtype: "vendor",
disabled: false, disabled: false,
rangeFilter: { rangeFilter: {
@@ -689,11 +744,11 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_invoiced"), field: i18n.t("jobs.fields.date_invoiced"),
}, },
}, },
supplement_ratio_source: { supplement_ratio_ins_co: {
title: i18n.t("reportcenter.templates.supplement_ratio_source"), title: i18n.t("reportcenter.templates.supplement_ratio_ins_co"),
description: "", description: "",
subject: i18n.t("reportcenter.templates.supplement_ratio_source"), subject: i18n.t("reportcenter.templates.supplement_ratio_ins_co"),
key: "supplement_ratio_source", key: "supplement_ratio_ins_co",
//idtype: "vendor", //idtype: "vendor",
disabled: false, disabled: false,
rangeFilter: { rangeFilter: {
@@ -727,11 +782,11 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_invoiced"), field: i18n.t("jobs.fields.date_invoiced"),
}, },
}, },
job_costing_ro_source: { job_costing_ro_ins_co: {
title: i18n.t("reportcenter.templates.job_costing_ro_source"), title: i18n.t("reportcenter.templates.job_costing_ro_ins_co"),
description: "", description: "",
subject: i18n.t("reportcenter.templates.job_costing_ro_source"), subject: i18n.t("reportcenter.templates.job_costing_ro_ins_co"),
key: "job_costing_ro_source", key: "job_costing_ro_ins_co",
//idtype: "vendor", //idtype: "vendor",
disabled: false, disabled: false,
rangeFilter: { rangeFilter: {
@@ -789,6 +844,67 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_invoiced"), 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: { gsr_by_estimator: {
title: i18n.t("reportcenter.templates.gsr_by_estimator"), title: i18n.t("reportcenter.templates.gsr_by_estimator"),
description: "", description: "",
@@ -825,6 +941,126 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_open"), 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" ...(!type || type === "courtesycarcontract"

View File

@@ -8302,6 +8302,11 @@ map-visit@^1.0.0:
dependencies: dependencies:
object-visit "^1.0.0" 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: material-colors@^1.2.1:
version "1.2.6" version "1.2.6"
resolved "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz" resolved "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz"

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."conversations" DROP COLUMN "archived";
type: run_sql

View File

@@ -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

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."parts_orders" DROP COLUMN "orderedby";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."parts_orders" ADD COLUMN "orderedby" text NULL;
type: run_sql

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1114,11 +1114,12 @@ tables:
- role: user - role: user
permission: permission:
columns: columns:
- phone_num - archived
- created_at
- updated_at
- bodyshopid - bodyshopid
- created_at
- id - id
- phone_num
- updated_at
filter: filter:
bodyshop: bodyshop:
associations: associations:
@@ -1133,11 +1134,12 @@ tables:
- role: user - role: user
permission: permission:
columns: columns:
- phone_num - archived
- created_at
- updated_at
- bodyshopid - bodyshopid
- created_at
- id - id
- phone_num
- updated_at
filter: filter:
bodyshop: bodyshop:
associations: associations:
@@ -3622,6 +3624,7 @@ tables:
- jobid - jobid
- order_date - order_date
- order_number - order_number
- orderedby
- return - return
- returnfrombill - returnfrombill
- status - status
@@ -3638,6 +3641,7 @@ tables:
- jobid - jobid
- order_date - order_date
- order_number - order_number
- orderedby
- return - return
- returnfrombill - returnfrombill
- status - status
@@ -3664,6 +3668,7 @@ tables:
- jobid - jobid
- order_date - order_date
- order_number - order_number
- orderedby
- returnfrombill - returnfrombill
- status - status
- updated_at - updated_at

View File

@@ -67,6 +67,12 @@ async function JobCostingMulti(req, res) {
gpdollars: Dinero({ amount: 0 }), gpdollars: Dinero({ amount: 0 }),
gppercent: null, gppercent: null,
gppercentFormatted: 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( multiSummary.summaryData.gpdollars.add(
costingData.summaryData.gpdollars 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}`); console.timeEnd(`SummaryOfCostingData-${job.id}`);
//Take the summary data & add it to total summary data. //Take the summary data & add it to total summary data.
}); });
//For each center, recalculate and toFormat() the values. //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.gppercent = (
(multiSummary.summaryData.gpdollars.getAmount() / (multiSummary.summaryData.gpdollars.getAmount() /
@@ -232,11 +265,11 @@ function GenerateCostingData(job) {
acc.labor[laborProfitCenter].add(laborAmount); acc.labor[laborProfitCenter].add(laborAmount);
if (val.mod_lbr_ty === "LAR") { if (val.mod_lbr_ty === "LAR") {
if (!acc.labor[defaultProfits["MAPA"]]) if (!acc.parts[defaultProfits["MAPA"]])
acc.labor[defaultProfits["MAPA"]] = Dinero(); acc.parts[defaultProfits["MAPA"]] = Dinero();
materialsHours.mapaHrs += val.mod_lb_hrs || 0; materialsHours.mapaHrs += val.mod_lb_hrs || 0;
acc.labor[defaultProfits["MAPA"]] = acc.labor[ acc.parts[defaultProfits["MAPA"]] = acc.parts[
defaultProfits["MAPA"] defaultProfits["MAPA"]
].add( ].add(
Dinero({ Dinero({
@@ -244,11 +277,11 @@ function GenerateCostingData(job) {
}).multiply(val.mod_lb_hrs || 0) }).multiply(val.mod_lb_hrs || 0)
); );
} }
if (!acc.labor[defaultProfits["MASH"]]) if (!acc.parts[defaultProfits["MASH"]])
acc.labor[defaultProfits["MASH"]] = Dinero(); acc.parts[defaultProfits["MASH"]] = Dinero();
if (val.mod_lbr_ty !== "LAR") { if (val.mod_lbr_ty !== "LAR") {
acc.labor[defaultProfits["MASH"]] = acc.labor[ acc.parts[defaultProfits["MASH"]] = acc.parts[
defaultProfits["MASH"] defaultProfits["MASH"]
].add( ].add(
Dinero({ Dinero({
@@ -369,6 +402,7 @@ function GenerateCostingData(job) {
}).multiply(materialsHours.mapaHrs) }).multiply(materialsHours.mapaHrs)
); );
} }
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) { if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) {
if ( if (
!billTotalsByCostCenters[ !billTotalsByCostCenters[
@@ -418,7 +452,14 @@ function GenerateCostingData(job) {
totalLaborCost: Dinero({ amount: 0 }), totalLaborCost: Dinero({ amount: 0 }),
totalPartsCost: Dinero({ amount: 0 }), totalPartsCost: Dinero({ amount: 0 }),
totalCost: Dinero({ amount: 0 }), totalCost: Dinero({ amount: 0 }),
totalLaborGp: Dinero({ amount: 0 }),
totalPartsGp: Dinero({ amount: 0 }),
gpdollars: Dinero({ amount: 0 }), gpdollars: Dinero({ amount: 0 }),
totalLaborGppercent: null,
totalLaborGppercentFormatted: null,
totalPartsGppercent: null,
totalPartsGppercentFormatted: null,
gppercent: null, gppercent: null,
gppercentFormatted: null, gppercentFormatted: null,
}; };
@@ -503,6 +544,31 @@ function GenerateCostingData(job) {
} }
//Final summary data massaging. //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.gpdollars = summaryData.totalSales.subtract(
summaryData.totalCost summaryData.totalCost
); );
@@ -510,6 +576,7 @@ function GenerateCostingData(job) {
(summaryData.gpdollars.getAmount() / summaryData.totalSales.getAmount()) * (summaryData.gpdollars.getAmount() / summaryData.totalSales.getAmount()) *
100 100
).toFixed(2); ).toFixed(2);
if (isNaN(summaryData.gppercent)) summaryData.gppercentFormatted = 0; if (isNaN(summaryData.gppercent)) summaryData.gppercentFormatted = 0;
else if (!isFinite(summaryData.gppercent)) else if (!isFinite(summaryData.gppercent))
summaryData.gppercentFormatted = "- ∞"; summaryData.gppercentFormatted = "- ∞";