Compare commits
29 Commits
release/20
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1bf112aac | ||
|
|
ecfb3e91cd | ||
|
|
e25606070b | ||
|
|
28f0d9a4b2 | ||
|
|
c125cd8ca2 | ||
|
|
123f94a0f5 | ||
|
|
d601617819 | ||
|
|
80bd2dc6d8 | ||
|
|
45bd2d3281 | ||
|
|
b4304c743e | ||
|
|
faf9fbb9d8 | ||
|
|
6d1581a4e1 | ||
|
|
28f6c72de1 | ||
|
|
54577ac680 | ||
|
|
c912681793 | ||
|
|
09f909142b | ||
|
|
89b515fff4 | ||
|
|
fb380a5b31 | ||
|
|
3896a0b03d | ||
|
|
832674662d | ||
|
|
f2b2011900 | ||
|
|
15c305317a | ||
|
|
d6924c2292 | ||
|
|
7ccd356f0a | ||
|
|
592b47b5d4 | ||
|
|
d9beee7f2f | ||
|
|
3d68a7099b | ||
|
|
9b1f24926f | ||
|
|
bcc153caa5 |
@@ -2354,6 +2354,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>is_credit_memo_short</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>local_tax_rate</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -4598,6 +4619,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>md_from_emails</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>
|
||||
<folder_node>
|
||||
<name>md_hour_split</name>
|
||||
<children>
|
||||
@@ -7280,6 +7322,32 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>ss_configuration</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>dailyhrslimit</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>ssbuckets</name>
|
||||
<children>
|
||||
@@ -13205,6 +13273,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>from</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>subject</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -26521,6 +26610,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>parts_received</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>parts_tax_rates</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -31121,6 +31231,37 @@
|
||||
</folder_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>owner</name>
|
||||
<children>
|
||||
<folder_node>
|
||||
<name>labels</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>noownerinfo</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>owners</name>
|
||||
<children>
|
||||
@@ -34857,6 +34998,27 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<concept_node>
|
||||
<name>lag_time_ro</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>mechanical_authorization</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -36419,6 +36581,58 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<folder_node>
|
||||
<name>bodyshop</name>
|
||||
<children>
|
||||
<folder_node>
|
||||
<name>labels</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>qbo_departmentid</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>qbo_usa</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<concept_node>
|
||||
<name>cardsettings</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -36776,6 +36990,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>stickyheader</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>sublets</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
@@ -4,40 +4,40 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.5.6",
|
||||
"@apollo/client": "^3.5.10",
|
||||
"@asseinfo/react-kanban": "^2.2.0",
|
||||
"@craco/craco": "^6.4.3",
|
||||
"@fingerprintjs/fingerprintjs": "^3.3.1",
|
||||
"@sentry/react": "^6.16.1",
|
||||
"@sentry/tracing": "^6.16.1",
|
||||
"@splitsoftware/splitio-react": "^1.3.0",
|
||||
"@fingerprintjs/fingerprintjs": "^3.3.3",
|
||||
"@sentry/react": "^6.19.3",
|
||||
"@sentry/tracing": "^6.19.3",
|
||||
"@splitsoftware/splitio-react": "^1.3.1-rc.1",
|
||||
"@stripe/react-stripe-js": "^1.7.0",
|
||||
"@stripe/stripe-js": "^1.22.0",
|
||||
"@tanem/react-nprogress": "^3.0.82",
|
||||
"antd": "^4.17.4",
|
||||
"@stripe/stripe-js": "^1.26.0",
|
||||
"@tanem/react-nprogress": "^4.0.12",
|
||||
"antd": "^4.19.3",
|
||||
"apollo-link-logger": "^2.0.0",
|
||||
"axios": "^0.24.0",
|
||||
"axios": "^0.26.1",
|
||||
"craco-less": "^1.20.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"dotenv": "^16.0.0",
|
||||
"enquire-js": "^0.2.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
"firebase": "^9.6.1",
|
||||
"graphql": "^16.2.0",
|
||||
"i18next": "^21.6.3",
|
||||
"i18next-browser-languagedetector": "^6.1.2",
|
||||
"jsoneditor": "^9.5.8",
|
||||
"firebase": "^9.6.10",
|
||||
"graphql": "^16.3.0",
|
||||
"i18next": "^21.6.14",
|
||||
"i18next-browser-languagedetector": "^6.1.4",
|
||||
"jsoneditor": "^9.7.4",
|
||||
"jsreport-browser-client-dist": "^1.3.0",
|
||||
"libphonenumber-js": "^1.9.44",
|
||||
"logrocket": "^2.1.2",
|
||||
"markerjs2": "^2.17.2",
|
||||
"libphonenumber-js": "^1.9.50",
|
||||
"logrocket": "^2.2.1",
|
||||
"markerjs2": "^2.20.0",
|
||||
"moment-business-days": "^1.2.0",
|
||||
"moment-timezone": "^0.5.34",
|
||||
"phone": "^3.1.10",
|
||||
"phone": "^3.1.14",
|
||||
"preval.macro": "^5.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"query-string": "^7.0.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^7.1.1",
|
||||
"rc-queue-anim": "^2.0.0",
|
||||
"rc-scroll-anim": "^2.7.6",
|
||||
"react": "^17.0.2",
|
||||
@@ -45,41 +45,42 @@
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^4.1.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-drag-listview": "^0.1.8",
|
||||
"react-drag-listview": "^0.1.9",
|
||||
"react-grid-gallery": "^0.5.5",
|
||||
"react-grid-layout": "^1.3.0",
|
||||
"react-i18next": "^11.15.1",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-i18next": "^11.16.2",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-number-format": "^4.9.0",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-number-format": "^4.9.1",
|
||||
"react-redux": "^7.2.7",
|
||||
"react-resizable": "^3.0.4",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-sticky": "^6.0.3",
|
||||
"react-sublime-video": "^0.2.5",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"recharts": "^2.1.8",
|
||||
"recharts": "^2.1.9",
|
||||
"redux": "^4.1.2",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.1.3",
|
||||
"redux-state-sync": "^3.1.2",
|
||||
"reselect": "^4.1.5",
|
||||
"sass": "^1.45.0",
|
||||
"socket.io-client": "^4.4.0",
|
||||
"styled-components": "^5.3.3",
|
||||
"sass": "^1.49.10",
|
||||
"socket.io-client": "^4.4.1",
|
||||
"styled-components": "^5.3.5",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"web-vitals": "^2.1.2",
|
||||
"workbox-background-sync": "^6.4.2",
|
||||
"workbox-broadcast-update": "^6.4.2",
|
||||
"workbox-cacheable-response": "^6.4.2",
|
||||
"workbox-core": "^6.4.2",
|
||||
"workbox-expiration": "^6.4.2",
|
||||
"workbox-google-analytics": "^6.4.2",
|
||||
"workbox-navigation-preload": "^6.4.2",
|
||||
"workbox-precaching": "^6.4.2",
|
||||
"workbox-range-requests": "^6.4.2",
|
||||
"workbox-routing": "^6.4.2",
|
||||
"workbox-strategies": "^6.4.2",
|
||||
"workbox-streams": "^6.4.2",
|
||||
"web-vitals": "^2.1.4",
|
||||
"workbox-background-sync": "^6.5.2",
|
||||
"workbox-broadcast-update": "^6.5.2",
|
||||
"workbox-cacheable-response": "^6.5.2",
|
||||
"workbox-core": "^6.5.2",
|
||||
"workbox-expiration": "^6.5.2",
|
||||
"workbox-google-analytics": "^6.5.2",
|
||||
"workbox-navigation-preload": "^6.5.2",
|
||||
"workbox-precaching": "^6.5.2",
|
||||
"workbox-range-requests": "^6.5.2",
|
||||
"workbox-routing": "^6.5.2",
|
||||
"workbox-strategies": "^6.5.2",
|
||||
"workbox-streams": "^6.5.2",
|
||||
"yauzl": "^2.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -116,11 +117,11 @@
|
||||
"react-error-overlay": "6.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/webpack-plugin": "^1.18.3",
|
||||
"@sentry/webpack-plugin": "^1.18.8",
|
||||
"@testing-library/cypress": "^8.0.2",
|
||||
"cypress": "^9.1.1",
|
||||
"cypress": "^9.5.3",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"react-error-overlay": "6.0.9",
|
||||
"react-error-overlay": "6.0.10",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.2"
|
||||
}
|
||||
|
||||
@@ -162,10 +162,19 @@ export function AccountingPayablesTableComponent({
|
||||
const dataSource = state.search
|
||||
? payments.filter(
|
||||
(v) =>
|
||||
(v.vendor.name || "")
|
||||
(v.paymentnum || "")
|
||||
.toLowerCase()
|
||||
.includes(state.search.toLowerCase()) ||
|
||||
(v.invoice_number || "")
|
||||
((v.job && v.job.ro_number) || "")
|
||||
.toLowerCase()
|
||||
.includes(state.search.toLowerCase()) ||
|
||||
((v.job && v.job.ownr_fn) || "")
|
||||
.toLowerCase()
|
||||
.includes(state.search.toLowerCase()) ||
|
||||
((v.job && v.job.ownr_ln) || "")
|
||||
.toLowerCase()
|
||||
.includes(state.search.toLowerCase()) ||
|
||||
((v.job && v.job.ownr_co_nm) || "")
|
||||
.toLowerCase()
|
||||
.includes(state.search.toLowerCase())
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
|
||||
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
|
||||
|
||||
@@ -12,6 +12,7 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
@@ -62,6 +63,18 @@ export function AccountingReceivablesTableComponent({
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.date_invoiced"),
|
||||
dataIndex: "date_invoiced",
|
||||
key: "date_invoiced",
|
||||
sorter: (a, b) => dateSort(a.date_invoiced, b.date_invoiced),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "date_invoiced" &&
|
||||
state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<DateFormatter>{record.date_invoiced}</DateFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
|
||||
@@ -16,9 +16,14 @@ import EmailDocumentsComponent from "../email-documents/email-documents.componen
|
||||
import _ from "lodash";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
@@ -28,7 +33,12 @@ export default connect(
|
||||
mapDispatchToProps
|
||||
)(EmailOverlayComponent);
|
||||
|
||||
export function EmailOverlayComponent({ form, selectedMediaState, bodyshop }) {
|
||||
export function EmailOverlayComponent({
|
||||
form,
|
||||
selectedMediaState,
|
||||
bodyshop,
|
||||
currentUser,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const handleClick = ({ item, key, keyPath }) => {
|
||||
const email = item.props.value;
|
||||
@@ -51,6 +61,27 @@ export function EmailOverlayComponent({ form, selectedMediaState, bodyshop }) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t("emails.fields.from")}
|
||||
name="from"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select>
|
||||
<Select.Option key={currentUser.email}>
|
||||
{currentUser.email}
|
||||
</Select.Option>
|
||||
<Select.Option key={bodyshop.email}>{bodyshop.email}</Select.Option>
|
||||
{bodyshop.md_from_emails &&
|
||||
bodyshop.md_from_emails.map((e) => (
|
||||
<Select.Option key={e}>{e}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<Space>
|
||||
|
||||
@@ -56,13 +56,9 @@ export function EmailOverlayContainer({
|
||||
: bodyshop.shopname,
|
||||
address: EmailSettings.fromAddress,
|
||||
},
|
||||
ReplyTo: {
|
||||
Email: currentUser.validemail ? currentUser.email : bodyshop.email,
|
||||
Name: currentUser.displayName,
|
||||
},
|
||||
};
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
const handleFinish = async (allValues) => {
|
||||
logImEXEvent("email_send_from_modal");
|
||||
|
||||
//const attachments = [];
|
||||
@@ -77,10 +73,15 @@ export function EmailOverlayContainer({
|
||||
// attachments.push(t);
|
||||
// });
|
||||
|
||||
const { from, ...values } = allValues;
|
||||
setSending(true);
|
||||
try {
|
||||
await axios.post("/sendemail", {
|
||||
...defaultEmailFrom,
|
||||
ReplyTo: {
|
||||
Email: from,
|
||||
Name: currentUser.displayName,
|
||||
},
|
||||
...values,
|
||||
html: rawHtml,
|
||||
attachments: [
|
||||
@@ -138,6 +139,7 @@ export function EmailOverlayContainer({
|
||||
}
|
||||
|
||||
form.setFieldsValue({
|
||||
from: currentUser.validemail ? currentUser.email : bodyshop.email,
|
||||
...emailConfig.messageOptions,
|
||||
cc:
|
||||
emailConfig.messageOptions.cc &&
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function JobReconciliationBillsTable({
|
||||
dataIndex: "line_desc",
|
||||
key: "line_desc",
|
||||
ellipsis: true,
|
||||
minWidth: "65rem",
|
||||
width: "10rem",
|
||||
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
||||
@@ -72,11 +72,11 @@ export default function JobReconciliationBillsTable({
|
||||
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("bills.fields.is_credit_memo"),
|
||||
title: t("bills.fields.is_credit_memo_short"),
|
||||
dataIndex: "is_credit_memo",
|
||||
key: "is_credit_memo",
|
||||
sorter: (a, b) => a.bill.is_credit_memo - b.bill.is_credit_memo,
|
||||
width: "8rem",
|
||||
width: "3rem",
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "is_credit_memo" &&
|
||||
state.sortedInfo.order,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
Col,
|
||||
Divider,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
@@ -23,8 +22,8 @@ import FormItemPhone, {
|
||||
} from "../form-items-formatted/phone-form-item.component";
|
||||
import Car from "../job-damage-visual/job-damage-visual.component";
|
||||
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
|
||||
import FormRow from "../layout-form-row/layout-form-row.component";
|
||||
import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
|
||||
import FormRow from "../layout-form-row/layout-form-row.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -220,15 +219,8 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider
|
||||
orientation="left"
|
||||
type="horizontal"
|
||||
style={{ marginTop: ".8rem", float: "right" }}
|
||||
>
|
||||
{t("jobs.forms.appraiserinfo")}
|
||||
</Divider>
|
||||
|
||||
<FormRow noDivider>
|
||||
<FormRow header={t("jobs.forms.appraiserinfo")}>
|
||||
<Form.Item label={t("jobs.fields.est_co_nm")} name="est_co_nm">
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
|
||||
@@ -63,10 +63,11 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""}
|
||||
${job.v_make_desc || ""}
|
||||
${job.v_model_desc || ""}`.trim();
|
||||
console.log(
|
||||
"🚀 ~ file: jobs-detail-header.component.jsx ~ line 64 ~ vehicleTitle",
|
||||
vehicleTitle.length
|
||||
);
|
||||
|
||||
const ownerTitle = `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
|
||||
job.ownr_co_nm || ""
|
||||
}`.trim();
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]} style={{ alignItems: "stretch" }}>
|
||||
<Col {...colSpan}>
|
||||
@@ -159,9 +160,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
style={{ height: "100%" }}
|
||||
title={
|
||||
<Link to={disabled ? "#" : `/manage/owners/${job.owner.id}`}>
|
||||
{`${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
|
||||
job.ownr_co_nm || ""
|
||||
}`}
|
||||
{ownerTitle.length > 0
|
||||
? ownerTitle
|
||||
: t("owner.labels.noownerinfo")}
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -425,8 +425,6 @@ export function PartsOrderListTableComponent({
|
||||
placement="right"
|
||||
onClose={() => handleOnRowClick(null)}
|
||||
visible={selectedpartsorder}
|
||||
//getContainer={false}
|
||||
style={{ position: "absolute" }}
|
||||
closable
|
||||
width={drawerPercentage}
|
||||
>
|
||||
|
||||
@@ -61,20 +61,22 @@ export default function ProductionBoardCard(
|
||||
<PauseCircleOutlined style={{ color: "orangered" }} />
|
||||
)}
|
||||
<span style={{ fontWeight: "bolder" }}>
|
||||
{card.ro_number || t("general.labels.na")}
|
||||
<Link
|
||||
to={
|
||||
technician
|
||||
? `/tech/joblookup?selected=${card.id}`
|
||||
: `/manage/jobs/${card.id}`
|
||||
}
|
||||
>
|
||||
{card.ro_number || t("general.labels.na")}
|
||||
</Link>
|
||||
</span>
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
technician ? (
|
||||
<Link to={`/tech/joblookup?selected=${card.id}`}>
|
||||
<EyeFilled />
|
||||
</Link>
|
||||
) : (
|
||||
<Link to={`/manage/jobs/${card.id}`}>
|
||||
<EyeFilled />
|
||||
</Link>
|
||||
)
|
||||
<Link to={{ search: `?selected=${card.id}` }}>
|
||||
<EyeFilled />
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
<Row>
|
||||
|
||||
@@ -131,6 +131,13 @@ export default function ProductionBoardKanbanCardSettings({
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
valuePropName="checked"
|
||||
label={t("production.labels.stickyheader")}
|
||||
name="stickyheader"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import Board, { moveCard } from "@asseinfo/react-kanban";
|
||||
//import "@asseinfo/react-kanban/dist/styles.css";
|
||||
import "./production-board-kanban.styles.scss";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { Grid, notification, Button, PageHeader, Space, Statistic } from "antd";
|
||||
import { Button, Grid, notification, PageHeader, Space, Statistic } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Sticky, StickyContainer } from "react-sticky";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { generate_UPDATE_JOB_KANBAN } from "../../graphql/jobs.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ProductionBoardCard from "../production-board-kanban-card/production-board-kanban-card.component";
|
||||
import { createBoardData } from "./production-board-kanban.utils.js";
|
||||
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { generate_UPDATE_JOB_KANBAN } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
|
||||
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
|
||||
import ProductionBoardCard from "../production-board-kanban-card/production-board-kanban-card.component";
|
||||
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
|
||||
import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component";
|
||||
//import "@asseinfo/react-kanban/dist/styles.css";
|
||||
import "./production-board-kanban.styles.scss";
|
||||
import { createBoardData } from "./production-board-kanban.utils.js";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
technician: selectTechnician,
|
||||
@@ -183,9 +184,52 @@ export function ProductionBoardKanbanComponent({
|
||||
: standardSizes[selectedBreakpoint[0]]
|
||||
: "250";
|
||||
|
||||
const stickyHeader = {
|
||||
renderColumnHeader: ({ title }) => (
|
||||
<Sticky>
|
||||
{({
|
||||
style,
|
||||
|
||||
// the following are also available but unused in this example
|
||||
isSticky,
|
||||
wasSticky,
|
||||
distanceFromTop,
|
||||
distanceFromBottom,
|
||||
calculatedHeight,
|
||||
}) => (
|
||||
<div
|
||||
className="react-kanban-column-header"
|
||||
style={{ ...style, zIndex: "99", backgroundColor: "#ddd" }}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
</Sticky>
|
||||
),
|
||||
};
|
||||
|
||||
const cardSettings =
|
||||
associationSettings &&
|
||||
associationSettings.kanban_settings &&
|
||||
Object.keys(associationSettings.kanban_settings).length > 0
|
||||
? associationSettings.kanban_settings
|
||||
: {
|
||||
ats: true,
|
||||
clm_no: true,
|
||||
compact: false,
|
||||
ownr_nm: true,
|
||||
sublets: true,
|
||||
ins_co_nm: true,
|
||||
production_note: true,
|
||||
employeeassignments: true,
|
||||
scheduled_completion: true,
|
||||
stickyheader: false,
|
||||
};
|
||||
|
||||
return (
|
||||
<Container width={width}>
|
||||
<IndefiniteLoading loading={isMoving} />
|
||||
|
||||
<PageHeader
|
||||
title={
|
||||
<Space>
|
||||
@@ -215,34 +259,19 @@ export function ProductionBoardKanbanComponent({
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
|
||||
<Board
|
||||
children={boardLanes}
|
||||
disableCardDrag={isMoving}
|
||||
renderCard={(card) =>
|
||||
ProductionBoardCard(
|
||||
technician,
|
||||
card,
|
||||
bodyshop,
|
||||
associationSettings &&
|
||||
associationSettings.kanban_settings &&
|
||||
Object.keys(associationSettings.kanban_settings).length > 0
|
||||
? associationSettings.kanban_settings
|
||||
: {
|
||||
ats: true,
|
||||
clm_no: true,
|
||||
compact: false,
|
||||
ownr_nm: true,
|
||||
sublets: true,
|
||||
ins_co_nm: true,
|
||||
production_note: true,
|
||||
employeeassignments: true,
|
||||
scheduled_completion: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
onCardDragEnd={handleDragEnd}
|
||||
/>
|
||||
<ProductionListDetailComponent jobs={data} />
|
||||
<StickyContainer>
|
||||
<Board
|
||||
style={{ height: "100%" }}
|
||||
children={boardLanes}
|
||||
disableCardDrag={isMoving}
|
||||
{...(cardSettings.stickyheader && stickyHeader)}
|
||||
renderCard={(card) =>
|
||||
ProductionBoardCard(technician, card, bodyshop, cardSettings)
|
||||
}
|
||||
onCardDragEnd={handleDragEnd}
|
||||
/>
|
||||
</StickyContainer>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import ProductionListColumnStatus from "./production-list-columns.status.compone
|
||||
import ProductionListColumnCategory from "./production-list-columns.status.category";
|
||||
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
||||
import ProductionListColumnComment from "./production-list-columns.comment.component";
|
||||
import ProductionListColumnPartsReceived from "./production-list-columns.partsreceived.component";
|
||||
|
||||
const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
||||
return [
|
||||
@@ -96,7 +97,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<ProductionListDate record={record} field="actual_in" time/>
|
||||
<ProductionListDate record={record} field="actual_in" time />
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -477,6 +478,14 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.t("jobs.labels.parts_received"),
|
||||
dataIndex: "parts_received",
|
||||
key: "parts_received",
|
||||
render: (text, record) => (
|
||||
<ProductionListColumnPartsReceived record={record} />
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
export default r;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useMemo } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ProductionListColumnPartsReceived);
|
||||
|
||||
export function ProductionListColumnPartsReceived({ bodyshop, record }) {
|
||||
const amount = useMemo(() => {
|
||||
const amount = record.joblines_status.reduce(
|
||||
(acc, val) => {
|
||||
acc.total += val.count;
|
||||
acc.received =
|
||||
val.status === bodyshop.md_order_statuses.default_received
|
||||
? acc.received + val.count
|
||||
: acc.received;
|
||||
return acc;
|
||||
},
|
||||
{ total: 0, received: 0 }
|
||||
);
|
||||
|
||||
return {
|
||||
...amount,
|
||||
percent:
|
||||
amount.total !== 0
|
||||
? ((amount.received / amount.total) * 100).toFixed(0) + "%"
|
||||
: "N/A",
|
||||
};
|
||||
}, [record, bodyshop.md_order_statuses]);
|
||||
|
||||
return `${amount.percent} (${amount.received}/${amount.total})`;
|
||||
}
|
||||
@@ -88,12 +88,6 @@ export function ProductionListTable({
|
||||
);
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
console.log(
|
||||
"🚀 ~ file: production-list-table.component.jsx ~ line 91 ~ pagination, filters, sorter",
|
||||
pagination,
|
||||
filters,
|
||||
sorter
|
||||
);
|
||||
setState({
|
||||
...state,
|
||||
filteredInfo: filters,
|
||||
|
||||
@@ -163,6 +163,27 @@ export default function ShopInfoGeneral({ form }) {
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => (
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.qbo_usa")}
|
||||
shouldUpdate
|
||||
valuePropName="checked"
|
||||
name={["accountingconfig", "qbo_usa"]}
|
||||
>
|
||||
<Switch
|
||||
disabled={!form.getFieldValue(["accountingconfig", "qbo"])}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.qbo_departmentid")}
|
||||
name={["accountingconfig", "qbo_departmentid"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.accountingtiers")}
|
||||
rules={[
|
||||
@@ -507,6 +528,18 @@ export default function ShopInfoGeneral({ form }) {
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["md_from_emails"]}
|
||||
label={t("bodyshop.fields.md_from_emails")}
|
||||
// rules={[
|
||||
// {
|
||||
// //message: t("general.validation.required"),
|
||||
// type: "array",
|
||||
// },
|
||||
// ]}
|
||||
>
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["md_email_cc", "parts_order"]}
|
||||
label={t("bodyshop.fields.md_email_cc", { template: "parts_order" })}
|
||||
|
||||
@@ -70,6 +70,12 @@ export default function ShopInfoSchedulingComponent({ form }) {
|
||||
>
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["ss_configuration", "dailyhrslimit"]}
|
||||
label={t("bodyshop.fields.ss_configuration.dailyhrslimit")}
|
||||
>
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Divider orientation="left">{t("bodyshop.labels.workingdays")}</Divider>
|
||||
<Space wrap size="large">
|
||||
|
||||
@@ -101,6 +101,8 @@ export const QUERY_BODYSHOP = gql`
|
||||
md_filehandlers
|
||||
md_email_cc
|
||||
timezone
|
||||
ss_configuration
|
||||
md_from_emails
|
||||
employees {
|
||||
user_email
|
||||
id
|
||||
@@ -199,6 +201,8 @@ export const UPDATE_SHOP = gql`
|
||||
md_filehandlers
|
||||
md_email_cc
|
||||
timezone
|
||||
ss_configuration
|
||||
md_from_emails
|
||||
employees {
|
||||
id
|
||||
first_name
|
||||
@@ -266,7 +270,6 @@ export const QUERY_DELIVER_CHECKLIST = gql`
|
||||
ro_number
|
||||
actual_completion
|
||||
actual_delivery
|
||||
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -146,6 +146,11 @@ export const QUERY_EXACT_JOB_IN_PRODUCTION = gql`
|
||||
employee_refinish
|
||||
employee_prep
|
||||
employee_csr
|
||||
joblines_status{
|
||||
part_type
|
||||
status
|
||||
count
|
||||
}
|
||||
labhrs: joblines_aggregate(
|
||||
where: {
|
||||
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
|
||||
@@ -219,6 +224,11 @@ export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
|
||||
employee_refinish
|
||||
employee_prep
|
||||
employee_csr
|
||||
joblines_status{
|
||||
part_type
|
||||
status
|
||||
count
|
||||
}
|
||||
labhrs: joblines_aggregate(
|
||||
where: {
|
||||
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
|
||||
@@ -294,6 +304,11 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
|
||||
employee_prep
|
||||
employee_csr
|
||||
suspended
|
||||
joblines_status{
|
||||
part_type
|
||||
status
|
||||
count
|
||||
}
|
||||
labhrs: joblines_aggregate(
|
||||
where: {
|
||||
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
confirmPasswordReset,
|
||||
signInWithEmailAndPassword,
|
||||
signOut,
|
||||
sendPasswordResetEmail,
|
||||
} from "firebase/auth";
|
||||
import { doc } from "firebase/firestore";
|
||||
import i18next from "i18next";
|
||||
@@ -223,16 +224,16 @@ export function* signInSuccessSaga({ payload }) {
|
||||
export function* onSendPasswordResetStart() {
|
||||
yield takeLatest(
|
||||
UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START,
|
||||
sendPasswordResetEmail
|
||||
sendPasswordResetEmailSaga
|
||||
);
|
||||
yield takeLatest(
|
||||
UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN,
|
||||
sendPasswordResetEmail
|
||||
sendPasswordResetEmailSaga
|
||||
);
|
||||
}
|
||||
export function* sendPasswordResetEmail({ payload }) {
|
||||
export function* sendPasswordResetEmailSaga({ payload }) {
|
||||
try {
|
||||
yield sendPasswordResetEmail(payload, {
|
||||
yield sendPasswordResetEmail(auth, payload, {
|
||||
url: "https://imex.online/passwordreset",
|
||||
});
|
||||
|
||||
@@ -269,7 +270,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
const userEmail = yield select((state) => state.user.currentUser.email);
|
||||
try {
|
||||
console.log("Setting shop timezone.");
|
||||
// moment.tz.setDefault(payload.timezone);
|
||||
// moment.tz.setDefault(payload.timezone);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -157,6 +157,7 @@
|
||||
"federal_tax_rate": "Federal Tax Rate",
|
||||
"invoice_number": "Invoice Number",
|
||||
"is_credit_memo": "Credit Memo?",
|
||||
"is_credit_memo_short": "CM",
|
||||
"local_tax_rate": "Local Tax Rate",
|
||||
"ro_number": "RO Number",
|
||||
"state_tax_rate": "Provincial/State Tax Rate",
|
||||
@@ -285,6 +286,7 @@
|
||||
"md_classes": "Classes",
|
||||
"md_ded_notes": "Deductible Notes",
|
||||
"md_email_cc": "Auto Email CC: $t(printcenter.subjects.jobs.{{template}})",
|
||||
"md_from_emails": "Additional From Emails",
|
||||
"md_hour_split": {
|
||||
"paint": "Paint Hour Split",
|
||||
"prep": "Prep Hour Split"
|
||||
@@ -455,6 +457,9 @@
|
||||
"label": "Label",
|
||||
"templates": "Templates"
|
||||
},
|
||||
"ss_configuration": {
|
||||
"dailyhrslimit": "Daily Incoming Hours Limit"
|
||||
},
|
||||
"ssbuckets": {
|
||||
"gte": "Greater Than/Equal to (hrs)",
|
||||
"id": "ID",
|
||||
@@ -827,6 +832,7 @@
|
||||
},
|
||||
"fields": {
|
||||
"cc": "CC",
|
||||
"from": "From",
|
||||
"subject": "Subject",
|
||||
"to": "To"
|
||||
},
|
||||
@@ -1558,6 +1564,7 @@
|
||||
"override_header": "Override estimate header on import?",
|
||||
"ownerassociation": "Owner Association",
|
||||
"parts": "Parts",
|
||||
"parts_received": "Parts Rec.",
|
||||
"parts_tax_rates": "Parts Tax rates",
|
||||
"partsfilter": "Parts Only",
|
||||
"partssubletstotal": "Parts & Sublets Total",
|
||||
@@ -1837,6 +1844,11 @@
|
||||
"updated": "Note updated successfully."
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
"labels": {
|
||||
"noownerinfo": "No owner information."
|
||||
}
|
||||
},
|
||||
"owners": {
|
||||
"actions": {
|
||||
"update": "Update Selected Records"
|
||||
@@ -2073,6 +2085,7 @@
|
||||
"labels": "Labels",
|
||||
"position": "Starting Position"
|
||||
},
|
||||
"lag_time_ro": "Lag Time",
|
||||
"mechanical_authorization": "Mechanical Authorization",
|
||||
"mpi_animal_checklist": "MPI - Animal Checklist",
|
||||
"mpi_eglass_auth": "MPI - eGlass Auth",
|
||||
@@ -2165,6 +2178,12 @@
|
||||
"ats": "Alternative Transportation",
|
||||
"bodyhours": "B",
|
||||
"bodypriority": "B/P",
|
||||
"bodyshop": {
|
||||
"labels": {
|
||||
"qbo_departmentid": "QBO Department ID",
|
||||
"qbo_usa": "QBO USA"
|
||||
}
|
||||
},
|
||||
"cardsettings": "Card Settings",
|
||||
"clm_no": "Claim Number",
|
||||
"comment": "Comment",
|
||||
@@ -2182,6 +2201,7 @@
|
||||
"refinishhours": "R",
|
||||
"scheduled_completion": "Scheduled Completion",
|
||||
"selectview": "Select a View",
|
||||
"stickyheader": "Sticky Header (BETA)",
|
||||
"sublets": "Sublets",
|
||||
"totalhours": "Total Hrs ",
|
||||
"touchtime": "T/T",
|
||||
|
||||
@@ -157,6 +157,7 @@
|
||||
"federal_tax_rate": "",
|
||||
"invoice_number": "",
|
||||
"is_credit_memo": "",
|
||||
"is_credit_memo_short": "",
|
||||
"local_tax_rate": "",
|
||||
"ro_number": "",
|
||||
"state_tax_rate": "",
|
||||
@@ -285,6 +286,7 @@
|
||||
"md_classes": "",
|
||||
"md_ded_notes": "",
|
||||
"md_email_cc": "",
|
||||
"md_from_emails": "",
|
||||
"md_hour_split": {
|
||||
"paint": "",
|
||||
"prep": ""
|
||||
@@ -455,6 +457,9 @@
|
||||
"label": "",
|
||||
"templates": ""
|
||||
},
|
||||
"ss_configuration": {
|
||||
"dailyhrslimit": ""
|
||||
},
|
||||
"ssbuckets": {
|
||||
"gte": "",
|
||||
"id": "",
|
||||
@@ -827,6 +832,7 @@
|
||||
},
|
||||
"fields": {
|
||||
"cc": "",
|
||||
"from": "",
|
||||
"subject": "",
|
||||
"to": ""
|
||||
},
|
||||
@@ -1558,6 +1564,7 @@
|
||||
"override_header": "¿Anular encabezado estimado al importar?",
|
||||
"ownerassociation": "",
|
||||
"parts": "Partes",
|
||||
"parts_received": "",
|
||||
"parts_tax_rates": "",
|
||||
"partsfilter": "",
|
||||
"partssubletstotal": "",
|
||||
@@ -1837,6 +1844,11 @@
|
||||
"updated": "Nota actualizada con éxito."
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
"labels": {
|
||||
"noownerinfo": ""
|
||||
}
|
||||
},
|
||||
"owners": {
|
||||
"actions": {
|
||||
"update": ""
|
||||
@@ -2073,6 +2085,7 @@
|
||||
"labels": "",
|
||||
"position": ""
|
||||
},
|
||||
"lag_time_ro": "",
|
||||
"mechanical_authorization": "",
|
||||
"mpi_animal_checklist": "",
|
||||
"mpi_eglass_auth": "",
|
||||
@@ -2165,6 +2178,12 @@
|
||||
"ats": "",
|
||||
"bodyhours": "",
|
||||
"bodypriority": "",
|
||||
"bodyshop": {
|
||||
"labels": {
|
||||
"qbo_departmentid": "",
|
||||
"qbo_usa": ""
|
||||
}
|
||||
},
|
||||
"cardsettings": "",
|
||||
"clm_no": "",
|
||||
"comment": "",
|
||||
@@ -2182,6 +2201,7 @@
|
||||
"refinishhours": "",
|
||||
"scheduled_completion": "",
|
||||
"selectview": "",
|
||||
"stickyheader": "",
|
||||
"sublets": "",
|
||||
"totalhours": "",
|
||||
"touchtime": "",
|
||||
|
||||
@@ -157,6 +157,7 @@
|
||||
"federal_tax_rate": "",
|
||||
"invoice_number": "",
|
||||
"is_credit_memo": "",
|
||||
"is_credit_memo_short": "",
|
||||
"local_tax_rate": "",
|
||||
"ro_number": "",
|
||||
"state_tax_rate": "",
|
||||
@@ -285,6 +286,7 @@
|
||||
"md_classes": "",
|
||||
"md_ded_notes": "",
|
||||
"md_email_cc": "",
|
||||
"md_from_emails": "",
|
||||
"md_hour_split": {
|
||||
"paint": "",
|
||||
"prep": ""
|
||||
@@ -455,6 +457,9 @@
|
||||
"label": "",
|
||||
"templates": ""
|
||||
},
|
||||
"ss_configuration": {
|
||||
"dailyhrslimit": ""
|
||||
},
|
||||
"ssbuckets": {
|
||||
"gte": "",
|
||||
"id": "",
|
||||
@@ -827,6 +832,7 @@
|
||||
},
|
||||
"fields": {
|
||||
"cc": "",
|
||||
"from": "",
|
||||
"subject": "",
|
||||
"to": ""
|
||||
},
|
||||
@@ -1558,6 +1564,7 @@
|
||||
"override_header": "Remplacer l'en-tête d'estimation à l'importation?",
|
||||
"ownerassociation": "",
|
||||
"parts": "les pièces",
|
||||
"parts_received": "",
|
||||
"parts_tax_rates": "",
|
||||
"partsfilter": "",
|
||||
"partssubletstotal": "",
|
||||
@@ -1837,6 +1844,11 @@
|
||||
"updated": "Remarque mise à jour avec succès."
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
"labels": {
|
||||
"noownerinfo": ""
|
||||
}
|
||||
},
|
||||
"owners": {
|
||||
"actions": {
|
||||
"update": ""
|
||||
@@ -2073,6 +2085,7 @@
|
||||
"labels": "",
|
||||
"position": ""
|
||||
},
|
||||
"lag_time_ro": "",
|
||||
"mechanical_authorization": "",
|
||||
"mpi_animal_checklist": "",
|
||||
"mpi_eglass_auth": "",
|
||||
@@ -2165,6 +2178,12 @@
|
||||
"ats": "",
|
||||
"bodyhours": "",
|
||||
"bodypriority": "",
|
||||
"bodyshop": {
|
||||
"labels": {
|
||||
"qbo_departmentid": "",
|
||||
"qbo_usa": ""
|
||||
}
|
||||
},
|
||||
"cardsettings": "",
|
||||
"clm_no": "",
|
||||
"comment": "",
|
||||
@@ -2182,6 +2201,7 @@
|
||||
"refinishhours": "",
|
||||
"scheduled_completion": "",
|
||||
"selectview": "",
|
||||
"stickyheader": "",
|
||||
"sublets": "",
|
||||
"totalhours": "",
|
||||
"touchtime": "",
|
||||
|
||||
@@ -24,13 +24,34 @@ export default async function RenderTemplate(
|
||||
let { contextData, useShopSpecificTemplate } = await fetchContextData(
|
||||
templateObject
|
||||
);
|
||||
const { ignoreCustomMargins } = Templates[templateObject.name];
|
||||
|
||||
let reportRequest = {
|
||||
template: {
|
||||
name: useShopSpecificTemplate
|
||||
? `/${bodyshop.imexshopid}/${templateObject.name}`
|
||||
: `/${templateObject.name}`,
|
||||
...(renderAsHtml ? {} : { recipe: "chrome-pdf" }),
|
||||
...(renderAsHtml
|
||||
? {}
|
||||
: {
|
||||
recipe: "chrome-pdf",
|
||||
...(!ignoreCustomMargins && {
|
||||
chrome: {
|
||||
marginTop:
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.headerMargin &&
|
||||
bodyshop.logo_img_path.headerMargin > 36
|
||||
? bodyshop.logo_img_path.headerMargin
|
||||
: "36px",
|
||||
marginBottom:
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.footerMargin &&
|
||||
bodyshop.logo_img_path.footerMargin > 36
|
||||
? bodyshop.logo_img_path.footerMargin
|
||||
: "36px",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
...(renderAsExcel ? { recipe: "html-to-xlsx" } : {}),
|
||||
},
|
||||
data: {
|
||||
@@ -121,7 +142,25 @@ export async function RenderTemplates(
|
||||
name: rootTemplate.useShopSpecificTemplate
|
||||
? `/${bodyshop.imexshopid}/${rootTemplate.templateObject.name}`
|
||||
: `/${rootTemplate.templateObject.name}`,
|
||||
...(renderAsHtml ? {} : { recipe: "chrome-pdf" }),
|
||||
...(renderAsHtml
|
||||
? {}
|
||||
: {
|
||||
recipe: "chrome-pdf",
|
||||
chrome: {
|
||||
marginTop:
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.headerMargin &&
|
||||
bodyshop.logo_img_path.headerMargin > 36
|
||||
? bodyshop.logo_img_path.headerMargin
|
||||
: "36px",
|
||||
marginBottom:
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.footerMargin &&
|
||||
bodyshop.logo_img_path.footerMargin > 36
|
||||
? bodyshop.logo_img_path.footerMargin
|
||||
: "36px",
|
||||
},
|
||||
}),
|
||||
pdfOperations: templateAndData.map((template) => {
|
||||
return {
|
||||
template: {
|
||||
|
||||
@@ -371,6 +371,7 @@ export const TemplateList = (type, context) => {
|
||||
subject: i18n.t("printcenter.jobs.parts_label_single"),
|
||||
disabled: false,
|
||||
group: "ro",
|
||||
ignoreCustomMargins: true,
|
||||
},
|
||||
envelope_return_address: {
|
||||
title: i18n.t("printcenter.jobs.envelope_return_address"),
|
||||
@@ -379,6 +380,7 @@ export const TemplateList = (type, context) => {
|
||||
key: "envelope_return_address",
|
||||
disabled: false,
|
||||
group: "ro",
|
||||
ignoreCustomMargins: true,
|
||||
},
|
||||
sgi_certificate_of_repairs: {
|
||||
title: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"),
|
||||
@@ -462,6 +464,14 @@ export const TemplateList = (type, context) => {
|
||||
disabled: false,
|
||||
group: "post",
|
||||
},
|
||||
lag_time_ro: {
|
||||
title: i18n.t("printcenter.jobs.lag_time_ro"),
|
||||
description: "CASL Authorization",
|
||||
subject: i18n.t("printcenter.jobs.lag_time_ro"),
|
||||
key: "lag_time_ro",
|
||||
disabled: false,
|
||||
group: "ro",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(!type || type === "job_special"
|
||||
|
||||
1399
client/yarn.lock
1399
client/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -834,6 +834,7 @@
|
||||
- md_email_cc
|
||||
- md_estimators
|
||||
- md_filehandlers
|
||||
- md_from_emails
|
||||
- md_hour_split
|
||||
- md_ins_cos
|
||||
- md_jobline_presets
|
||||
@@ -860,6 +861,7 @@
|
||||
- shopname
|
||||
- shoprates
|
||||
- speedprint
|
||||
- ss_configuration
|
||||
- ssbuckets
|
||||
- state
|
||||
- state_tax_id
|
||||
@@ -915,6 +917,7 @@
|
||||
- md_email_cc
|
||||
- md_estimators
|
||||
- md_filehandlers
|
||||
- md_from_emails
|
||||
- md_hour_split
|
||||
- md_ins_cos
|
||||
- md_jobline_presets
|
||||
@@ -938,6 +941,7 @@
|
||||
- shopname
|
||||
- shoprates
|
||||
- speedprint
|
||||
- ss_configuration
|
||||
- ssbuckets
|
||||
- state
|
||||
- state_tax_id
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."bodyshops" add column "ss_configuration" jsonb
|
||||
-- null default jsonb_build_object();
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "ss_configuration" jsonb
|
||||
null default jsonb_build_object();
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."bodyshops" add column "md_from_emails" jsonb
|
||||
-- null default jsonb_build_array();
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "md_from_emails" jsonb
|
||||
null default jsonb_build_array();
|
||||
2
logs/IM_1234_30032022_080308.xml
Normal file
2
logs/IM_1234_30032022_080308.xml
Normal file
File diff suppressed because one or more lines are too long
2
logs/IM_1234_30032022_080358.xml
Normal file
2
logs/IM_1234_30032022_080358.xml
Normal file
File diff suppressed because one or more lines are too long
30873
logs/oAuthClient-log.log
30873
logs/oAuthClient-log.log
File diff suppressed because one or more lines are too long
@@ -611,6 +611,32 @@ exports.default = function ({
|
||||
}
|
||||
}
|
||||
|
||||
//QB USA with GST
|
||||
//This was required for the No. 1 Collision Group.
|
||||
if (
|
||||
bodyshop.accountingconfig &&
|
||||
bodyshop.accountingconfig.qbo &&
|
||||
bodyshop.accountingconfig.qbo_usa &&
|
||||
bodyshop.region_config.includes("CA_")
|
||||
) {
|
||||
InvoiceLineAdd.push({
|
||||
DetailType: "SalesItemLineDetail",
|
||||
Amount: Dinero(jobs_by_pk.job_totals.totals.federal_tax).toFormat(
|
||||
DineroQbFormat
|
||||
),
|
||||
SalesItemLineDetail: {
|
||||
...(jobs_by_pk.class
|
||||
? { ClassRef: { value: classes[jobs_by_pk.class] } }
|
||||
: {}),
|
||||
ItemRef: {
|
||||
value:
|
||||
items[bodyshop.md_responsibility_centers.taxes.federal.accountitem],
|
||||
},
|
||||
Qty: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return InvoiceLineAdd;
|
||||
};
|
||||
|
||||
@@ -626,7 +652,7 @@ const findTaxCode = ({ local, state, federal }, taxcode) => {
|
||||
} else if (t.length > 1) {
|
||||
return "Multiple Tax Codes Match";
|
||||
} else {
|
||||
return "No Tax Code Matches";
|
||||
return "";
|
||||
}
|
||||
};
|
||||
exports.findTaxCode = findTaxCode;
|
||||
|
||||
@@ -174,6 +174,55 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor) {
|
||||
req
|
||||
);
|
||||
|
||||
const lines = bill.billlines.map((il) =>
|
||||
generateBillLine(
|
||||
il,
|
||||
accounts,
|
||||
bill.job.class,
|
||||
bill.job.bodyshop.md_responsibility_centers.sales_tax_codes,
|
||||
classes,
|
||||
taxCodes,
|
||||
bill.job.bodyshop.md_responsibility_centers.costs
|
||||
)
|
||||
);
|
||||
|
||||
//QB USA with GST
|
||||
//This was required for the No. 1 Collision Group.
|
||||
if (
|
||||
bill.job.bodyshop.accountingconfig &&
|
||||
bill.job.bodyshop.accountingconfig.qbo &&
|
||||
bill.job.bodyshop.accountingconfig.qbo_usa &&
|
||||
bill.job.bodyshop.region_config.includes("CA_")
|
||||
) {
|
||||
lines.push({
|
||||
DetailType: "AccountBasedExpenseLineDetail",
|
||||
|
||||
AccountBasedExpenseLineDetail: {
|
||||
...(bill.job.class
|
||||
? { ClassRef: { value: classes[bill.job.class] } }
|
||||
: {}),
|
||||
AccountRef: {
|
||||
value:
|
||||
accounts[
|
||||
bill.job.bodyshop.md_responsibility_centers.taxes.federal
|
||||
.accountdesc
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
Amount: Dinero({
|
||||
amount: Math.round(
|
||||
bill.billlines.reduce((acc, val) => {
|
||||
return acc + val.actual_cost * val.quantity;
|
||||
}, 0) * 100
|
||||
),
|
||||
})
|
||||
.percentage(bill.federal_tax_rate)
|
||||
|
||||
.toFormat(DineroQbFormat),
|
||||
});
|
||||
}
|
||||
|
||||
const billQbo = {
|
||||
VendorRef: {
|
||||
value: vendor.Id,
|
||||
@@ -192,17 +241,7 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor) {
|
||||
//...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),
|
||||
|
||||
PrivateNote: `RO ${bill.job.ro_number || ""}`,
|
||||
Line: bill.billlines.map((il) =>
|
||||
generateBillLine(
|
||||
il,
|
||||
accounts,
|
||||
bill.job.class,
|
||||
bill.job.bodyshop.md_responsibility_centers.sales_tax_codes,
|
||||
classes,
|
||||
taxCodes,
|
||||
bill.job.bodyshop.md_responsibility_centers.costs
|
||||
)
|
||||
),
|
||||
Line: lines,
|
||||
};
|
||||
logger.log("qbo-payable-objectlog", "DEBUG", req.user.email, bill.id, {
|
||||
billQbo,
|
||||
@@ -282,7 +321,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) {
|
||||
url: urlBuilder(
|
||||
qbo_realmId,
|
||||
"query",
|
||||
`select * From Account where AccountType = 'Cost of Goods Sold'`
|
||||
`select * From Account where AccountType in ('Cost of Goods Sold', 'Other Current Liability')`
|
||||
),
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
||||
@@ -455,6 +455,25 @@ async function InsertInvoice(
|
||||
CustomerRef: {
|
||||
value: parentTierRef.Id,
|
||||
},
|
||||
...(bodyshop.accountingconfig.qbo_departmentid &&
|
||||
bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && {
|
||||
DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid },
|
||||
}),
|
||||
|
||||
...(bodyshop.accountingconfig &&
|
||||
bodyshop.accountingconfig.qbo &&
|
||||
bodyshop.accountingconfig.qbo_usa &&
|
||||
bodyshop.region_config.includes("CA_") && {
|
||||
TxnTaxDetail: {
|
||||
TxnTaxCodeRef: {
|
||||
value:
|
||||
taxCodes[
|
||||
bodyshop.md_responsibility_centers.taxes.state.accountitem
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
...(bodyshop.accountingconfig.printlater
|
||||
? { PrintStatus: "NeedToPrint" }
|
||||
: {}),
|
||||
|
||||
@@ -39,7 +39,7 @@ exports.default = async (req, res) => {
|
||||
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
|
||||
|
||||
const specificShopIds = req.body.bodyshopIds; // ['uuid]
|
||||
const start = req.body.start; //YYYY-MM-DD
|
||||
const { start, end } = req.body; //YYYY-MM-DD
|
||||
const allxmlsToUpload = [];
|
||||
const allErrors = [];
|
||||
try {
|
||||
@@ -58,6 +58,7 @@ exports.default = async (req, res) => {
|
||||
start: start
|
||||
? moment(start).startOf("day")
|
||||
: moment().subtract(3, "days").startOf("day"),
|
||||
...(end && { end: moment(end).startOf("day") }),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -184,6 +185,11 @@ exports.default = async (req, res) => {
|
||||
const CreateRepairOrderTag = (job, errorCallback) => {
|
||||
//Level 2
|
||||
|
||||
if (!job.job_totals) {
|
||||
errorCallback({ job, error: { toString: () => "No job totals for RO." } });
|
||||
return {};
|
||||
}
|
||||
|
||||
const repairCosts = CreateCosts(job);
|
||||
|
||||
try {
|
||||
@@ -576,6 +582,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
|
||||
SalesTaxTotal: Dinero(job.job_totals.totals.local_tax)
|
||||
.add(Dinero(job.job_totals.totals.state_tax))
|
||||
.add(Dinero(job.job_totals.totals.federal_tax))
|
||||
.add(Dinero(job.job_totals.additional.pvrt))
|
||||
.toFormat(AHDineroFormat),
|
||||
SalesTaxTotalCost: 0,
|
||||
GrossTotal: Dinero(job.job_totals.totals.total_repairs).toFormat(
|
||||
|
||||
@@ -111,6 +111,11 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
|
||||
ro_number
|
||||
clm_total
|
||||
clm_no
|
||||
v_model_yr
|
||||
v_model_desc
|
||||
v_make_desc
|
||||
v_vin
|
||||
plate_no
|
||||
ownerid
|
||||
ownr_ln
|
||||
ownr_fn
|
||||
@@ -176,6 +181,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
|
||||
bodyshops(where: {associations: {active: {_eq: true}}}) {
|
||||
id
|
||||
md_responsibility_centers
|
||||
region_config
|
||||
accountingconfig
|
||||
md_ins_cos
|
||||
timezone
|
||||
@@ -389,6 +395,8 @@ query QUERY_BILLS_FOR_PAYABLES_EXPORT($bills: [uuid!]!) {
|
||||
bodyshop{
|
||||
md_responsibility_centers
|
||||
timezone
|
||||
region_config
|
||||
accountingconfig
|
||||
}
|
||||
}
|
||||
billlines{
|
||||
@@ -464,6 +472,7 @@ exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: t
|
||||
jobs_by_pk(id: $jobId) {
|
||||
bodyshop {
|
||||
ssbuckets
|
||||
ss_configuration
|
||||
target_touchtime
|
||||
workingdays
|
||||
timezone
|
||||
@@ -549,7 +558,7 @@ exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employee
|
||||
}
|
||||
}`;
|
||||
|
||||
exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshopid: uuid!) {
|
||||
exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshopid: uuid!, $end: timestamptz) {
|
||||
bodyshops_by_pk(id: $bodyshopid){
|
||||
|
||||
id
|
||||
@@ -567,7 +576,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
|
||||
jc_hourly_rates
|
||||
timezone
|
||||
}
|
||||
jobs(where: {_and: [{converted: {_eq: true}}, {updated_at: {_gt: $start}}, {shopid: {_eq: $bodyshopid}}]}) {
|
||||
jobs(where: {_and: [{converted: {_eq: true}}, {updated_at: {_gt: $start}}, {updated_at: {_lte: $end}}, {shopid: {_eq: $bodyshopid}}]}) {
|
||||
id
|
||||
ro_number
|
||||
status
|
||||
|
||||
@@ -5,6 +5,7 @@ const Dinero = require("dinero.js");
|
||||
const moment = require("moment-timezone");
|
||||
const logger = require("../utils/logger");
|
||||
const _ = require("lodash");
|
||||
const { filter } = require("lodash");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
@@ -32,7 +33,8 @@ exports.job = async (req, res) => {
|
||||
});
|
||||
|
||||
const { jobs_by_pk, blockedDays, prodJobs, arrJobs, compJobs } = result;
|
||||
const { ssbuckets, workingdays, timezone } = result.jobs_by_pk.bodyshop;
|
||||
const { ssbuckets, workingdays, timezone, ss_configuration } =
|
||||
result.jobs_by_pk.bodyshop;
|
||||
const jobHrs = result.jobs_by_pk.jobhrs.aggregate.sum.mod_lb_hrs;
|
||||
|
||||
const JobBucket = ssbuckets.filter(
|
||||
@@ -63,29 +65,68 @@ exports.job = async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
const filteredArrJobs = arrJobs.filter(
|
||||
(j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
|
||||
);
|
||||
// const filteredArrJobs = arrJobs.filter(
|
||||
// (j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
|
||||
// );
|
||||
const filteredArrJobs = [];
|
||||
|
||||
// filteredArrJobs.forEach((item) => {
|
||||
// const itemDate = moment(item.scheduled_in)
|
||||
// .tz(timezone)
|
||||
// .format("yyyy-MM-DD");
|
||||
// if (!!load[itemDate]) {
|
||||
// load[itemDate].hoursIn =
|
||||
// (load[itemDate].hoursIn || 0) +
|
||||
// item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
// item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
// load[itemDate].jobsIn.push(item);
|
||||
// } else {
|
||||
// load[itemDate] = {
|
||||
// jobsIn: [item],
|
||||
// jobsOut: [],
|
||||
// hoursIn:
|
||||
// item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
// item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
|
||||
arrJobs.forEach((item) => {
|
||||
let isSameBucket = false;
|
||||
if (JobBucket.id === CheckJobBucket(ssbuckets, item)) {
|
||||
filteredArrJobs.push(item);
|
||||
isSameBucket = true;
|
||||
}
|
||||
|
||||
let jobHours =
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
|
||||
filteredArrJobs.forEach((item) => {
|
||||
const itemDate = moment(item.scheduled_in)
|
||||
.tz(timezone)
|
||||
.format("yyyy-MM-DD");
|
||||
if (!!load[itemDate]) {
|
||||
load[itemDate].hoursIn =
|
||||
(load[itemDate].hoursIn || 0) +
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
load[itemDate].jobsIn.push(item);
|
||||
} else {
|
||||
if (isSameBucket) {
|
||||
if (!!load[itemDate]) {
|
||||
load[itemDate].hoursIn = (load[itemDate].hoursIn || 0) + jobHours;
|
||||
load[itemDate].jobsIn.push(item);
|
||||
} else {
|
||||
load[itemDate] = {
|
||||
jobsIn: [item],
|
||||
jobsOut: [],
|
||||
hoursIn: jobHours,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!load[itemDate]) {
|
||||
load[itemDate] = {
|
||||
jobsIn: [item],
|
||||
jobsIn: [],
|
||||
jobsOut: [],
|
||||
hoursIn:
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
hoursIn: 0,
|
||||
hoursInTotal: 0,
|
||||
};
|
||||
}
|
||||
load[itemDate].hoursInTotal =
|
||||
(load[itemDate].hoursInTotal || 0) + jobHours;
|
||||
});
|
||||
|
||||
//Get the completing jobs.
|
||||
@@ -214,11 +255,25 @@ exports.job = async (req, res) => {
|
||||
(workingdays[dayOfWeekMapper(moment(loadKey).day())] || false) &&
|
||||
!load[loadKey].blocked;
|
||||
|
||||
let isUnderDailyTotalLimit = true;
|
||||
|
||||
if (
|
||||
ss_configuration &&
|
||||
ss_configuration.dailyhrslimit &&
|
||||
ss_configuration.dailyhrslimit > 0 &&
|
||||
load[loadKey] &&
|
||||
load[loadKey].hoursInTotal &&
|
||||
load[loadKey].hoursInTotal > ss_configuration.dailyhrslimit
|
||||
) {
|
||||
isUnderDailyTotalLimit = false;
|
||||
}
|
||||
|
||||
if (
|
||||
load[loadKey].expectedLoad &&
|
||||
load[loadKey].expectedLoad[JobBucket.id] &&
|
||||
JobBucket.target > load[loadKey].expectedLoad[JobBucket.id].count &&
|
||||
isShopOpen
|
||||
isShopOpen &&
|
||||
isUnderDailyTotalLimit
|
||||
)
|
||||
possibleDates.push(new Date(loadKey).toISOString().substr(0, 10));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user