diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 8d960bdfe..96ddb91b6 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -24030,6 +24030,27 @@ + + help + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + home false @@ -26853,6 +26874,27 @@ + + orderedby + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + quantity false diff --git a/client/package.json b/client/package.json index 57f1b5afd..d7d61881b 100644 --- a/client/package.json +++ b/client/package.json @@ -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", diff --git a/client/src/App/App.container.jsx b/client/src/App/App.container.jsx index 1f5298da2..590e31678 100644 --- a/client/src/App/App.container.jsx +++ b/client/src/App/App.container.jsx @@ -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 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 ( + ); diff --git a/client/src/App/App.jsx b/client/src/App/App.jsx index 89d96e27c..fbaedfd0a 100644 --- a/client/src/App/App.jsx +++ b/client/src/App/App.jsx @@ -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} /> + + + ); diff --git a/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx b/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx index 35308356e..94ca9e50b 100644 --- a/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx +++ b/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx @@ -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) => {record.date}, }, - { - 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 ? ( diff --git a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx index 61cc3147c..7fa5bf585 100644 --- a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx +++ b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx @@ -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 ( -
-
- - -
-
- - {conversation && conversation.phone_num} - -
-
+ + + {conversation && conversation.phone_num} + + + + ); } diff --git a/client/src/components/chat-tag-ro/chat-tag-ro.component.jsx b/client/src/components/chat-tag-ro/chat-tag-ro.component.jsx index 106e91393..fef423b69 100644 --- a/client/src/components/chat-tag-ro/chat-tag-ro.component.jsx +++ b/client/src/components/chat-tag-ro/chat-tag-ro.component.jsx @@ -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 ( -
- + +
+ +
{loading ? : null} {loading ? ( @@ -41,6 +41,6 @@ export default function ChatTagRoComponent({ ) : ( setVisible(false)} /> )} -
+ ); } diff --git a/client/src/components/document-editor/document-editor.component.jsx b/client/src/components/document-editor/document-editor.component.jsx new file mode 100644 index 000000000..cb12c1aec --- /dev/null +++ b/client/src/components/document-editor/document-editor.component.jsx @@ -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 ( +
+ {!loading && !uploaded && ( + sample + )} + {loading && } + {uploaded && ( + + )} +
+ ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(DocumentEditorComponent); diff --git a/client/src/components/document-editor/document-editor.container.jsx b/client/src/components/document-editor/document-editor.container.jsx new file mode 100644 index 000000000..3819f27f8 --- /dev/null +++ b/client/src/components/document-editor/document-editor.container.jsx @@ -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 ; + if (error || errorShop) + return ( + + ); + + if (!data || !data.documents_by_pk) + return ; + return ( +
+ +
+ ); +} diff --git a/client/src/components/documents-upload/documents-upload.utility.js b/client/src/components/documents-upload/documents-upload.utility.js index 5856d985e..abc0315f1 100644 --- a/client/src/components/documents-upload/documents-upload.utility.js +++ b/client/src/components/documents-upload/documents-upload.utility.js @@ -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, diff --git a/client/src/components/error-boundary/error-boundary.component.jsx b/client/src/components/error-boundary/error-boundary.component.jsx index cef01e299..0eca73dea 100644 --- a/client/src/components/error-boundary/error-boundary.component.jsx +++ b/client/src/components/error-boundary/error-boundary.component.jsx @@ -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 (
{t("menus.header.shop_csi")} + { + window.open("https://help.imex.online/", "_blank"); + }} + icon={} + > + {t("menus.header.help")} +
; } -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); +// }; +// }, []); +// }; diff --git a/client/src/components/job-detail-cards/job-detail-cards.notes.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.notes.component.jsx index 39b27c5f6..57fdec9ba 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.notes.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.notes.component.jsx @@ -29,7 +29,7 @@ export default function JobDetailCardsNotesComponent({ loading, data }) { bordered dataSource={data.notes} renderItem={(item) => ( - + {item.critical ? ( ) : null} diff --git a/client/src/components/job-detail-lines/job-lines.component.jsx b/client/src/components/job-detail-lines/job-lines.component.jsx index 9616b1571..bb85343a8 100644 --- a/client/src/components/job-detail-lines/job-lines.component.jsx +++ b/client/src/components/job-detail-lines/job-lines.component.jsx @@ -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({ { + const newWindow = window.open( + `${window.location.protocol}//${window.location.host}/edit?documentId=${galleryImages.images[index].id}`, + "_blank", + "noopener,noreferrer" + ); + if (newWindow) newWindow.opener = null; + }} + > + + , + ]} 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" ); diff --git a/client/src/components/jobs-notes/jobs.notes.component.jsx b/client/src/components/jobs-notes/jobs.notes.component.jsx index e8354b026..2f3459211 100644 --- a/client/src/components/jobs-notes/jobs.notes.component.jsx +++ b/client/src/components/jobs-notes/jobs.notes.component.jsx @@ -57,6 +57,9 @@ export function JobNotesComponent({ dataIndex: "text", key: "text", ellipsis: true, + render: (text, record) => ( + {text} + ), }, { diff --git a/client/src/components/parts-order-list-table/parts-order-list-table.component.jsx b/client/src/components/parts-order-list-table/parts-order-list-table.component.jsx index 9ae6695e5..436229c0c 100644 --- a/client/src/components/parts-order-list-table/parts-order-list-table.component.jsx +++ b/client/src/components/parts-order-list-table/parts-order-list-table.component.jsx @@ -237,6 +237,11 @@ export function PartsOrderListTableComponent({ {record.deliver_by} ), }, + { + 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", diff --git a/client/src/components/parts-order-modal/parts-order-modal.component.jsx b/client/src/components/parts-order-modal/parts-order-modal.component.jsx index 81c289d95..03f6d4edd 100644 --- a/client/src/components/parts-order-modal/parts-order-modal.component.jsx +++ b/client/src/components/parts-order-modal/parts-order-modal.component.jsx @@ -73,6 +73,7 @@ export default function PartsOrderModalComponent({ state.user.currentUser.email); + window.$crisp.push(["set", "user:company", [payload.shopname]]); + const authRecord = payload.associations.filter( (a) => a.useremail === userEmail ); diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index c2f783fb3..9a9bc0fb6 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -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" diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 19b3c3226..fcfddaa12 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -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": "" diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 885581ca3..8dd18311a 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -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": "" diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js index 0b063e6e1..bffe16a95 100644 --- a/client/src/utils/TemplateConstants.js +++ b/client/src/utils/TemplateConstants.js @@ -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" diff --git a/client/yarn.lock b/client/yarn.lock index e6fdbac30..2283b5afb 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -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" diff --git a/hasura/migrations/1622495491154_alter_table_public_vehicles_alter_column_v_vin/down.yaml b/hasura/migrations/1622495491154_alter_table_public_vehicles_alter_column_v_vin/down.yaml new file mode 100644 index 000000000..4302f4466 --- /dev/null +++ b/hasura/migrations/1622495491154_alter_table_public_vehicles_alter_column_v_vin/down.yaml @@ -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 diff --git a/hasura/migrations/1622495491154_alter_table_public_vehicles_alter_column_v_vin/up.yaml b/hasura/migrations/1622495491154_alter_table_public_vehicles_alter_column_v_vin/up.yaml new file mode 100644 index 000000000..5740a2ce7 --- /dev/null +++ b/hasura/migrations/1622495491154_alter_table_public_vehicles_alter_column_v_vin/up.yaml @@ -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 diff --git a/hasura/migrations/1622647828371_alter_table_public_conversations_add_column_archived/down.yaml b/hasura/migrations/1622647828371_alter_table_public_conversations_add_column_archived/down.yaml new file mode 100644 index 000000000..e3564fbac --- /dev/null +++ b/hasura/migrations/1622647828371_alter_table_public_conversations_add_column_archived/down.yaml @@ -0,0 +1,5 @@ +- args: + cascade: false + read_only: false + sql: ALTER TABLE "public"."conversations" DROP COLUMN "archived"; + type: run_sql diff --git a/hasura/migrations/1622647828371_alter_table_public_conversations_add_column_archived/up.yaml b/hasura/migrations/1622647828371_alter_table_public_conversations_add_column_archived/up.yaml new file mode 100644 index 000000000..728c78a86 --- /dev/null +++ b/hasura/migrations/1622647828371_alter_table_public_conversations_add_column_archived/up.yaml @@ -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 diff --git a/hasura/migrations/1622648039058_alter_table_public_parts_orders_add_column_orderedby/down.yaml b/hasura/migrations/1622648039058_alter_table_public_parts_orders_add_column_orderedby/down.yaml new file mode 100644 index 000000000..3ce027bbb --- /dev/null +++ b/hasura/migrations/1622648039058_alter_table_public_parts_orders_add_column_orderedby/down.yaml @@ -0,0 +1,5 @@ +- args: + cascade: false + read_only: false + sql: ALTER TABLE "public"."parts_orders" DROP COLUMN "orderedby"; + type: run_sql diff --git a/hasura/migrations/1622648039058_alter_table_public_parts_orders_add_column_orderedby/up.yaml b/hasura/migrations/1622648039058_alter_table_public_parts_orders_add_column_orderedby/up.yaml new file mode 100644 index 000000000..da4d4e462 --- /dev/null +++ b/hasura/migrations/1622648039058_alter_table_public_parts_orders_add_column_orderedby/up.yaml @@ -0,0 +1,5 @@ +- args: + cascade: false + read_only: false + sql: ALTER TABLE "public"."parts_orders" ADD COLUMN "orderedby" text NULL; + type: run_sql diff --git a/hasura/migrations/1622648063952_set_fk_public_parts_orders_orderedby/down.yaml b/hasura/migrations/1622648063952_set_fk_public_parts_orders_orderedby/down.yaml new file mode 100644 index 000000000..4b1ccfb84 --- /dev/null +++ b/hasura/migrations/1622648063952_set_fk_public_parts_orders_orderedby/down.yaml @@ -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 diff --git a/hasura/migrations/1622648063952_set_fk_public_parts_orders_orderedby/up.yaml b/hasura/migrations/1622648063952_set_fk_public_parts_orders_orderedby/up.yaml new file mode 100644 index 000000000..34c9c53f3 --- /dev/null +++ b/hasura/migrations/1622648063952_set_fk_public_parts_orders_orderedby/up.yaml @@ -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 diff --git a/hasura/migrations/1622648072730_update_permission_user_public_table_parts_orders/down.yaml b/hasura/migrations/1622648072730_update_permission_user_public_table_parts_orders/down.yaml new file mode 100644 index 000000000..23d6f2c78 --- /dev/null +++ b/hasura/migrations/1622648072730_update_permission_user_public_table_parts_orders/down.yaml @@ -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 diff --git a/hasura/migrations/1622648072730_update_permission_user_public_table_parts_orders/up.yaml b/hasura/migrations/1622648072730_update_permission_user_public_table_parts_orders/up.yaml new file mode 100644 index 000000000..f65ebc8d9 --- /dev/null +++ b/hasura/migrations/1622648072730_update_permission_user_public_table_parts_orders/up.yaml @@ -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 diff --git a/hasura/migrations/1622648079209_update_permission_user_public_table_parts_orders/down.yaml b/hasura/migrations/1622648079209_update_permission_user_public_table_parts_orders/down.yaml new file mode 100644 index 000000000..2df440493 --- /dev/null +++ b/hasura/migrations/1622648079209_update_permission_user_public_table_parts_orders/down.yaml @@ -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 diff --git a/hasura/migrations/1622648079209_update_permission_user_public_table_parts_orders/up.yaml b/hasura/migrations/1622648079209_update_permission_user_public_table_parts_orders/up.yaml new file mode 100644 index 000000000..4fc57b4f7 --- /dev/null +++ b/hasura/migrations/1622648079209_update_permission_user_public_table_parts_orders/up.yaml @@ -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 diff --git a/hasura/migrations/1622648087365_update_permission_user_public_table_parts_orders/down.yaml b/hasura/migrations/1622648087365_update_permission_user_public_table_parts_orders/down.yaml new file mode 100644 index 000000000..3a374a190 --- /dev/null +++ b/hasura/migrations/1622648087365_update_permission_user_public_table_parts_orders/down.yaml @@ -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 diff --git a/hasura/migrations/1622648087365_update_permission_user_public_table_parts_orders/up.yaml b/hasura/migrations/1622648087365_update_permission_user_public_table_parts_orders/up.yaml new file mode 100644 index 000000000..7fb9b294f --- /dev/null +++ b/hasura/migrations/1622648087365_update_permission_user_public_table_parts_orders/up.yaml @@ -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 diff --git a/hasura/migrations/1622648112396_update_permission_user_public_table_conversations/down.yaml b/hasura/migrations/1622648112396_update_permission_user_public_table_conversations/down.yaml new file mode 100644 index 000000000..9c9dd4ce0 --- /dev/null +++ b/hasura/migrations/1622648112396_update_permission_user_public_table_conversations/down.yaml @@ -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 diff --git a/hasura/migrations/1622648112396_update_permission_user_public_table_conversations/up.yaml b/hasura/migrations/1622648112396_update_permission_user_public_table_conversations/up.yaml new file mode 100644 index 000000000..6b394a2fd --- /dev/null +++ b/hasura/migrations/1622648112396_update_permission_user_public_table_conversations/up.yaml @@ -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 diff --git a/hasura/migrations/1622648118751_update_permission_user_public_table_conversations/down.yaml b/hasura/migrations/1622648118751_update_permission_user_public_table_conversations/down.yaml new file mode 100644 index 000000000..143c20b05 --- /dev/null +++ b/hasura/migrations/1622648118751_update_permission_user_public_table_conversations/down.yaml @@ -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 diff --git a/hasura/migrations/1622648118751_update_permission_user_public_table_conversations/up.yaml b/hasura/migrations/1622648118751_update_permission_user_public_table_conversations/up.yaml new file mode 100644 index 000000000..0c3147203 --- /dev/null +++ b/hasura/migrations/1622648118751_update_permission_user_public_table_conversations/up.yaml @@ -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 diff --git a/hasura/migrations/1622650359491_set_fk_public_exportlog_billid/down.yaml b/hasura/migrations/1622650359491_set_fk_public_exportlog_billid/down.yaml new file mode 100644 index 000000000..4cbdba134 --- /dev/null +++ b/hasura/migrations/1622650359491_set_fk_public_exportlog_billid/down.yaml @@ -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 diff --git a/hasura/migrations/1622650359491_set_fk_public_exportlog_billid/up.yaml b/hasura/migrations/1622650359491_set_fk_public_exportlog_billid/up.yaml new file mode 100644 index 000000000..34ec41ec8 --- /dev/null +++ b/hasura/migrations/1622650359491_set_fk_public_exportlog_billid/up.yaml @@ -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 diff --git a/hasura/migrations/metadata.yaml b/hasura/migrations/metadata.yaml index 02291ea5a..2dcbc2190 100644 --- a/hasura/migrations/metadata.yaml +++ b/hasura/migrations/metadata.yaml @@ -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 diff --git a/server/job/job-costing.js b/server/job/job-costing.js index 7ee629959..e9de9b5ff 100644 --- a/server/job/job-costing.js +++ b/server/job/job-costing.js @@ -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 = "- ∞";