Compare commits

...

29 Commits

Author SHA1 Message Date
Patrick Fic
b5a2be9392 Package updates. 2022-04-18 10:27:06 -07:00
Patrick Fic
df1e15be60 All package upgrades. 2022-04-13 17:46:46 -07:00
Patrick Fic
274b572915 Resolve vendor delete error message. 2022-04-13 17:04:52 -07:00
Patrick Fic
fa4aa5ca8f IO-1689 Resolve partner popup checks. 2022-04-13 16:53:31 -07:00
Patrick Fic
2b537b65dd Add migrations that were not created for previous tickets. 2022-04-13 16:32:51 -07:00
Patrick Fic
75b2cb18ca IO-1826 Refetch on convert if GST registrant. 2022-04-13 15:07:47 -07:00
Patrick Fic
5167668958 IO-1824 Allow posting to closed ROs. 2022-04-13 15:02:07 -07:00
Patrick Fic
1eacf43669 IO-1817 Delete existing bill line. 2022-04-13 14:49:33 -07:00
Patrick Fic
77ed64969e IO-1822 Add manual ATS calculation. 2022-04-13 12:34:31 -07:00
Patrick Fic
44ff032acc IO-1819 Allow private estimate lines to be modified. 2022-04-13 10:26:17 -07:00
Patrick Fic
542637edb2 Global search improvement. 2022-04-12 13:17:56 -07:00
Patrick Fic
f7971ed60c Add further days out for smart scheduling. 2022-04-12 08:56:34 -07:00
Patrick Fic
ca71b0479a IO-1394 Disable delete on vendor create. 2022-04-12 07:47:21 -07:00
Patrick Fic
515de38fe6 IO-1689 Remove partner and acct path popups. 2022-04-11 17:12:27 -07:00
Patrick Fic
113c62b36f Minor UI Changes for UB 2022-04-11 16:39:04 -07:00
Patrick Fic
c0ea5a9818 IO-1464 Edit assigned vendor on bill. 2022-04-11 13:34:43 -07:00
Patrick Fic
6b13dffe68 IO-1442 Update login error codes. 2022-04-11 13:28:51 -07:00
Patrick Fic
51d49be16e IO-1661 Add parts order comments presets. 2022-04-11 13:17:14 -07:00
Patrick Fic
04c3abd65f Merged in release/2022-04-08 (pull request #443)
Release/2022 04 08
2022-04-08 20:42:13 +00:00
Patrick Fic
29dab7312b IO-1810 Resolve time ticke tmodal issue. 2022-04-07 17:36:58 -07:00
Patrick Fic
01eb68fda1 IO-1589 2022-04-07 17:28:57 -07:00
Patrick Fic
a4c4329253 IO-1711 Cancel appointments from job 2022-04-07 15:22:27 -07:00
Patrick Fic
b738fc9007 IO-1751 Add parts order data to repair data screen. 2022-04-07 10:56:02 -07:00
Patrick Fic
5484358b32 Merged in release/2022-04-08 (pull request #441)
Autohouse update.
2022-04-07 15:00:32 +00:00
Patrick Fic
b48d7512a2 Autohouse update. 2022-04-07 07:59:54 -07:00
Patrick Fic
ae3226efb8 Merged in release/2022-04-08 (pull request #440)
Release/2022 04 08
2022-04-06 22:13:56 +00:00
Patrick Fic
da9ddfc419 Autohouse update. 2022-04-06 15:13:23 -07:00
Patrick Fic
901902c1d1 Missing translations for QBO in shop setup. 2022-04-04 14:07:16 -07:00
Patrick Fic
79bfb12063 Merged in release/2022-04-02 (pull request #439)
Test
2022-04-01 21:05:31 +00:00
86 changed files with 8632 additions and 12075 deletions

View File

@@ -1,21 +0,0 @@
{
"env": {
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"no-console": "off"
},
"settings": {},
"plugins": ["cypress"]
}

View File

@@ -3654,6 +3654,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>bill_allow_post_to_closed</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>bill_federal_tax_rate</name>
<definition_loaded>false</definition_loaded>
@@ -4388,6 +4409,27 @@
</concept_node>
</children>
</folder_node>
<concept_node>
<name>last_name_first</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>lastnumberworkingdays</name>
<definition_loaded>false</definition_loaded>
@@ -4732,6 +4774,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>private</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>state</name>
<definition_loaded>false</definition_loaded>
@@ -4839,6 +4902,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>md_parts_order_comment</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>md_payment_types</name>
<definition_loaded>false</definition_loaded>
@@ -8843,6 +8927,48 @@
</translation>
</translations>
</concept_node>
<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>
<concept_node>
<name>rbac</name>
<definition_loaded>false</definition_loaded>
@@ -20250,6 +20376,27 @@
</concept_node>
</children>
</folder_node>
<concept_node>
<name>auto_add_ats</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>ca_bc_pvrt</name>
<definition_loaded>false</definition_loaded>
@@ -23152,6 +23299,27 @@
</concept_node>
</children>
</folder_node>
<concept_node>
<name>rate_ats</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>rate_la1</name>
<definition_loaded>false</definition_loaded>
@@ -29923,6 +30091,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>cancelallappointments</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>closejob</name>
<definition_loaded>false</definition_loaded>
@@ -32630,6 +32819,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>notyetordered</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>oec</name>
<definition_loaded>false</definition_loaded>
@@ -43105,6 +43315,63 @@
</folder_node>
</children>
</folder_node>
<folder_node>
<name>users</name>
<children>
<folder_node>
<name>errors</name>
<children>
<folder_node>
<name>signinerror</name>
<children>
<concept_node>
<name>auth/user-not-found</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>auth/wrong-password</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>
</children>
</folder_node>
<folder_node>
<name>vehicles</name>
<children>

View File

@@ -3,21 +3,27 @@
"version": "0.2.1",
"private": true,
"proxy": "http://localhost:4000",
"browser": {
"fs": false,
"path": false,
"os": false
},
"dependencies": {
"@apollo/client": "^3.5.10",
"@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^6.4.3",
"@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.26.0",
"@tanem/react-nprogress": "^4.0.12",
"antd": "^4.19.3",
"@jsreport/browser-client": "^3.1.0",
"@sentry/react": "^6.19.6",
"@sentry/tracing": "^6.19.6",
"@splitsoftware/splitio-react": "^1.4.0",
"@stripe/react-stripe-js": "^1.7.1",
"@stripe/stripe-js": "^1.27.0",
"@tanem/react-nprogress": "^5.0.0",
"antd": "^4.19.5",
"apollo-link-logger": "^2.0.0",
"axios": "^0.26.1",
"craco-less": "^1.20.0",
"craco-less": "^2.0.0",
"dinero.js": "^1.9.1",
"dotenv": "^16.0.0",
"enquire-js": "^0.2.1",
@@ -25,33 +31,32 @@
"exifr": "^7.1.3",
"firebase": "^9.6.10",
"graphql": "^16.3.0",
"i18next": "^21.6.14",
"i18next": "^21.6.16",
"i18next-browser-languagedetector": "^6.1.4",
"jsoneditor": "^9.7.4",
"jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.9.50",
"libphonenumber-js": "^1.9.51",
"logrocket": "^2.2.1",
"markerjs2": "^2.20.0",
"markerjs2": "^2.21.0",
"moment-business-days": "^1.2.0",
"moment-timezone": "^0.5.34",
"phone": "^3.1.14",
"phone": "^3.1.15",
"preval.macro": "^5.0.0",
"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",
"react-big-calendar": "^0.38.2",
"react": "^18.0.0",
"react-big-calendar": "^0.40.0",
"react-color": "^2.19.3",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-dom": "^18.0.0",
"react-drag-listview": "^0.1.9",
"react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.3.4",
"react-i18next": "^11.16.2",
"react-i18next": "^11.16.6",
"react-icons": "^4.3.1",
"react-number-format": "^4.9.1",
"react-redux": "^7.2.7",
"react-redux": "^7.2.8",
"react-resizable": "^3.0.4",
"react-router-dom": "^5.3.0",
"react-scripts": "^4.0.3",
@@ -64,23 +69,23 @@
"redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.2",
"reselect": "^4.1.5",
"sass": "^1.49.10",
"sass": "^1.50.0",
"socket.io-client": "^4.4.1",
"styled-components": "^5.3.5",
"subscriptions-transport-ws": "^0.11.0",
"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",
"workbox-background-sync": "^6.5.3",
"workbox-broadcast-update": "^6.5.3",
"workbox-cacheable-response": "^6.5.3",
"workbox-core": "^6.5.3",
"workbox-expiration": "^6.5.3",
"workbox-google-analytics": "^6.5.3",
"workbox-navigation-preload": "^6.5.3",
"workbox-precaching": "^6.5.3",
"workbox-range-requests": "^6.5.3",
"workbox-routing": "^6.5.3",
"workbox-strategies": "^6.5.3",
"workbox-streams": "^6.5.3",
"yauzl": "^2.10.0"
},
"scripts": {
@@ -119,9 +124,9 @@
"devDependencies": {
"@sentry/webpack-plugin": "^1.18.8",
"@testing-library/cypress": "^8.0.2",
"cypress": "^9.5.3",
"cypress": "^9.5.4",
"eslint-plugin-cypress": "^2.12.1",
"react-error-overlay": "6.0.10",
"react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.2"
}

View File

@@ -12,6 +12,7 @@ import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -76,14 +77,12 @@ export function AccountingPayablesTableComponent({
render: (text, record) => {
return record.job.owner ? (
<Link to={"/manage/owners/" + record.job.owner.id}>
{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${
record.job.ownr_co_nm || ""
}`}
<OwnerNameDisplay ownerObject={record.job} />
</Link>
) : (
<span>{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${
record.job.ownr_co_nm || ""
}`}</span>
<span>
<OwnerNameDisplay ownerObject={record.job} />
</span>
);
},
},

View File

@@ -13,6 +13,8 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { DateFormatter } from "../../utils/DateFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -85,14 +87,12 @@ export function AccountingReceivablesTableComponent({
render: (text, record) => {
return record.owner ? (
<Link to={"/manage/owners/" + record.owner.id}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}
<OwnerNameDisplay ownerObject={record} />
</Link>
) : (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}</span>
<span>
<OwnerNameDisplay ownerObject={record} />
</span>
);
},
},

View File

@@ -14,6 +14,7 @@ import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useLocation, useHistory } from "react-router-dom";
import {
DELETE_BILL_LINE,
INSERT_NEW_BILL_LINES,
UPDATE_BILL_LINE,
} from "../../graphql/bill-lines.queries";
@@ -58,6 +59,7 @@ export function BillDetailEditcontainer({
const [update_bill] = useMutation(UPDATE_BILL);
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
@@ -107,6 +109,20 @@ export function BillDetailEditcontainer({
})
);
//Find bill lines that were deleted.
const deletedJobLines = [];
data.bills_by_pk.billlines.forEach((a) => {
const matchingRecord = billlines.find((b) => b.id === a.id);
if (!matchingRecord) {
deletedJobLines.push(a);
}
});
deletedJobLines.forEach((d) => {
updates.push(deleteBillLine({ variables: { id: d.id } }));
});
billlines.forEach((billline) => {
const { deductedfromlbr, jobline, ...il } = billline;
delete il.__typename;
@@ -142,6 +158,7 @@ export function BillDetailEditcontainer({
);
}
});
await Promise.all(updates);
insertAuditTrail({

View File

@@ -114,7 +114,7 @@ export function BillFormComponent({
<Form.Item
label={t("bills.fields.vendor")}
name="vendorid"
style={{ display: billEdit ? "none" : null }}
// style={{ display: billEdit ? "none" : null }}
rules={[
{
required: true,
@@ -229,6 +229,7 @@ export function BillFormComponent({
({ getFieldValue }) => ({
validator(rule, value) {
if (
!bodyshop.bill_allow_post_to_closed &&
(job.status === bodyshop.md_ro_statuses.default_invoiced ||
job.status === bodyshop.md_ro_statuses.default_exported ||
job.status === bodyshop.md_ro_statuses.default_void) &&

View File

@@ -7,6 +7,7 @@ import { selectSelectedConversation } from "../../redux/messaging/messaging.sele
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import "./chat-conversation-list.styles.scss";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
selectedConversation: selectSelectedConversation,
@@ -40,9 +41,9 @@ export function ChatConversationListComponent({
{item.job_conversations.length > 0 ? (
<div className="chat-name">
{item.job_conversations.map((j, idx) => (
<div key={idx}>{`${j.job.ownr_fn || ""} ${
j.job.ownr_ln || ""
} ${j.job.ownr_co_nm || ""} `}</div>
<div key={idx}>
<OwnerNameDisplay ownerObject={j.job} />
</div>
))}
</div>
) : (

View File

@@ -4,6 +4,7 @@ import React from "react";
import { Link } from "react-router-dom";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function ChatConversationTitleTags({ jobConversations }) {
const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG);
@@ -45,9 +46,8 @@ export default function ChatConversationTitleTags({ jobConversations }) {
onClose={() => handleRemoveTag(item.job.id)}
>
<Link to={`/manage/jobs/${item.job.id}`}>
{`${item.job.ro_number || "?"} | ${item.job.ownr_fn || ""} ${
item.job.ownr_ln || ""
} ${item.job.ownr_co_nm || ""}`}
{`${item.job.ro_number || "?"} | `}
<OwnerNameDisplay ownerObject={item.job} />
</Link>
</Tag>
))}

View File

@@ -1,7 +1,8 @@
import { CloseCircleOutlined, LoadingOutlined } from "@ant-design/icons";
import { Select, Empty, Space } from "antd";
import { Empty, Select, Space } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
export default function ChatTagRoComponent({
roOptions,
@@ -27,9 +28,7 @@ export default function ChatTagRoComponent({
>
{roOptions.map((item, idx) => (
<Select.Option key={item.id || idx}>
{` ${item.ro_number || ""} | ${item.ownr_fn || ""} ${
item.ownr_ln || ""
} ${item.ownr_co_nm || ""}`}
{` ${item.ro_number || ""} | ${OwnerNameDisplayFunction(item)}`}
</Select.Option>
))}
</Select>

View File

@@ -3,7 +3,7 @@ import React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import DataLabel from "../data-label/data-label.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function ContractJobBlock({ job }) {
const { t } = useTranslation();
return (
@@ -23,9 +23,7 @@ export default function ContractJobBlock({ job }) {
} ${(job && job.v_model_desc) || ""}`}
</DataLabel>
<DataLabel label={t("jobs.fields.owner")}>
{`${(job && job.ownr_fn) || ""} ${(job && job.ownr_ln) || ""} ${
(job && job.ownr_co_nm) || ""
}`}
<OwnerNameDisplay ownerObject={job} />
</DataLabel>
</div>
</Card>

View File

@@ -3,6 +3,7 @@ import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { alphaSort } from "../../utils/sorters";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function ContractsJobsComponent({
loading,
@@ -43,17 +44,7 @@ export default function ContractsJobsComponent({
width: "25%",
sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => {
return record.owner ? (
<span>
{record.ownr_fn} {record.ownr_ln} {record.ownr_co_nm || ""}
</span>
) : (
<span>{`${record.ownr_fn} ${record.ownr_ln} ${
record.ownr_co_nm || ""
}`}</span>
);
},
render: (text, record) => <OwnerNameDisplay ownerObject={record} />,
},
{
title: t("jobs.fields.status"),

View File

@@ -12,17 +12,22 @@ import { setModalContext } from "../../redux/modals/modals.actions";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import moment from "moment";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
setContractFinderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "contractFinder" })),
});
export default connect(mapStateToProps, mapDispatchToProps)(ContractsList);
export function ContractsList({
bodyshop,
loading,
contracts,
refetch,
@@ -72,7 +77,9 @@ export function ContractsList({
sortOrder:
state.sortedInfo.columnKey === "driver_ln" && state.sortedInfo.order,
render: (text, record) =>
`${record.driver_fn || ""} ${record.driver_ln || ""}`,
bodyshop.last_name_first
? `${record.driver_ln || ""}, ${record.driver_fn || ""}`
: `${record.driver_fn || ""} ${record.driver_ln || ""}`,
},
{
title: t("contracts.labels.vehicle"),

View File

@@ -1,9 +1,11 @@
import { Table, Button, Input, Card, Space } from "antd";
import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Input, Space, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { alphaSort } from "../../utils/sorters";
import { SyncOutlined } from "@ant-design/icons";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
const [state, setState] = useState({
sortedInfo: {},
@@ -97,9 +99,9 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
render: (text, record) =>
record.cccontracts.length === 1 ? (
<Link to={`/manage/jobs/${record.cccontracts[0].job.id}`}>
{`${record.cccontracts[0].job.ro_number} - ${
record.cccontracts[0].job.ownr_fn || ""
} ${record.cccontracts[0].job.ownr_ln || ""} ${record.cccontracts[0].job.ownr_co_nm || ""}`}
{`${
record.cccontracts[0].job.ro_number
} - ${OwnerNameDisplayFunction(record.cccontracts[0].job)}`}
</Link>
) : null,
},

View File

@@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next";
import { Link, useHistory, useLocation } from "react-router-dom";
import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function CsiResponseListPaginated({
refetch,
@@ -48,14 +49,12 @@ export default function CsiResponseListPaginated({
render: (text, record) => {
return record.job.owner ? (
<Link to={"/manage/owners/" + record.job.owner.id}>
{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${
record.job.ownr_co_nm || ""
}`}
<OwnerNameDisplay ownerObject={record.job} />
</Link>
) : (
<span>{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${
record.job.ownr_co_nm || ""
}`}</span>
<span>
<OwnerNameDisplay ownerObject={record.job} />
</span>
);
},
},

View File

@@ -1,17 +1,21 @@
import { useLazyQuery } from "@apollo/client";
import { LoadingOutlined } from "@ant-design/icons";
import { AutoComplete, Divider, Space } from "antd";
import _ from "lodash";
import React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { Link, useHistory } from "react-router-dom";
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import AlertComponent from "../alert/alert.component";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
export default function GlobalSearch() {
const { t } = useTranslation();
const [callSearch, { error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
const history = useHistory();
const [callSearch, { loading, error, data }] =
useLazyQuery(GLOBAL_SEARCH_QUERY);
const executeSearch = (v) => {
if (v && v.variables.search && v.variables.search !== "") callSearch(v);
@@ -39,9 +43,9 @@ export default function GlobalSearch() {
<Space size="small" split={<Divider type="vertical" />}>
<strong>{job.ro_number || t("general.labels.na")}</strong>
<span>{`${job.status || ""}`}</span>
<span>{`${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
job.ownr_co_nm || ""
}`}</span>
<span>
<OwnerNameDisplay ownerObject={job} />
</span>
<span>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
job.v_model_desc || ""
}`}</span>
@@ -57,15 +61,13 @@ export default function GlobalSearch() {
options: data.search_owners.map((owner) => {
return {
key: owner.id,
value: `${owner.ownr_fn || ""} ${owner.ownr_ln || ""} ${
owner.ownr_co_nm || ""
}`,
value: OwnerNameDisplayFunction(owner),
label: (
<Link to={`/manage/owners/${owner.id}`}>
<Space size="small" split={<Divider type="vertical" />} wrap>
<span>{`${owner.ownr_fn || ""} ${owner.ownr_ln || ""} ${
owner.ownr_co_nm || ""
}`}</span>
<span>
<OwnerNameDisplay ownerObject={owner} />
</span>
<PhoneNumberFormatter>
{owner.ownr_ph1}
</PhoneNumberFormatter>
@@ -171,8 +173,13 @@ export default function GlobalSearch() {
<AutoComplete
options={options}
onSearch={handleSearch}
suffixIcon={loading && <LoadingOutlined spin />}
defaultActiveFirstOption
placeholder={t("general.labels.globalsearch")}
allowClear
onSelect={(val, opt) => {
history.push(opt.label.props.to);
}}
></AutoComplete>
);
}

View File

@@ -31,6 +31,7 @@ import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.
import ScheduleAtChange from "./job-at-change.component";
import ScheduleEventColor from "./schedule-event.color.component";
import ScheduleEventNote from "./schedule-event.note.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -73,9 +74,9 @@ export function ScheduleEventComponent({
</Space>
) : (
<Space>
<strong>{`${(event.job && event.job.ownr_fn) || ""} ${
(event.job && event.job.ownr_ln) || ""
}`}</strong>
<strong>
<OwnerNameDisplay ownerObject={event.job} />
</strong>
<span style={{ margin: 4 }}>
{`${(event.job && event.job.v_model_yr) || ""} ${
(event.job && event.job.v_make_desc) || ""
@@ -256,9 +257,9 @@ export function ScheduleEventComponent({
<Space>
{event.note && <AlertFilled className="production-alert" />}
<strong>{`${event.job.ro_number || t("general.labels.na")}`}</strong>
<span>{`${(event.job && event.job.ownr_fn) || ""} ${
(event.job && event.job.ownr_ln) || ""
} ${(event.job && event.job.ownr_co_nm) || ""}`}</span>
<span>
<OwnerNameDisplay ownerObject={event.job} />
</span>
</Space>
<Space>
<span>

View File

@@ -0,0 +1,54 @@
import { useQuery } from "@apollo/client";
import { Row, Col, Timeline, Typography, Space, Divider, Skeleton } from "antd";
import React from "react";
import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries";
import { useTranslation } from "react-i18next";
import AlertComponent from "../alert/alert.component";
import { DateFormatter } from "../../utils/DateFormatter";
import { Link } from "react-router-dom";
export default function JobLinesExpander({ jobline, jobid }) {
const { t } = useTranslation();
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
joblineid: jobline.id,
},
});
if (loading) return <Skeleton />;
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<Row>
<Col md={24} lg={12}>
<Typography.Title level={4}>
{t("parts_orders.labels.parts_orders")}
</Typography.Title>
<Timeline>
{data.parts_order_lines.length > 0 ? (
data.parts_order_lines.map((line) => (
<Timeline.Item key={line.id}>
<Space split={<Divider type="vertical" />} wrap>
<Link
to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}
>
{line.parts_order.order_number}
</Link>
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
{line.parts_order.vendor.name}
</Space>
</Timeline.Item>
))
) : (
<Timeline.Item>
{t("parts_orders.labels.notyetordered")}
</Timeline.Item>
)}
</Timeline>
</Col>
<Col md={24} lg={12}></Col>
</Row>
);
}

View File

@@ -4,6 +4,8 @@ import {
SyncOutlined,
WarningFilled,
EditFilled,
PlusCircleTwoTone,
MinusCircleTwoTone,
} from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import {
@@ -38,9 +40,11 @@ import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-re
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import _ from "lodash";
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobLinesExpander from "./job-lines-expander.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
technician: selectTechnician,
});
@@ -53,6 +57,7 @@ const mapDispatchToProps = (dispatch) => ({
});
export function JobLinesComponent({
bodyshop,
jobRO,
technician,
setPartsOrderContext,
@@ -72,6 +77,9 @@ export function JobLinesComponent({
filteredInfo: {},
});
const { t } = useTranslation();
const jobIsPrivate = bodyshop.md_ins_cos.find(
(c) => c.name === job.ins_co_nm
)?.private;
const columns = [
{
@@ -283,7 +291,7 @@ export function JobLinesComponent({
key: "actions",
render: (text, record) => (
<div>
{record.manual_line && (
{(record.manual_line || jobIsPrivate) && (
<Space>
<Button
disabled={jobRO}
@@ -449,6 +457,19 @@ export function JobLinesComponent({
scroll={{
x: true,
}}
expandable={{
expandedRowRender: (record) => (
<JobLinesExpander jobline={record} jobid={job.id} />
),
rowExpandable: (record) => true,
//expandRowByClick: true,
expandIcon: ({ expanded, onExpand, record }) =>
expanded ? (
<MinusCircleTwoTone onClick={(e) => onExpand(record, e)} />
) : (
<PlusCircleTwoTone onClick={(e) => onExpand(record, e)} />
),
}}
onRow={(record, rowIndex) => {
return {
onDoubleClick: (event) => {

View File

@@ -216,6 +216,7 @@ export function JobLinesUpsertModalComponent({
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
console.log(value);
if (!value || getFieldValue("part_type") !== "PAE") {
return Promise.resolve();
}
@@ -226,7 +227,10 @@ export function JobLinesUpsertModalComponent({
}),
({ getFieldValue }) => ({
validator(rule, value) {
if (!!getFieldValue("part_type") === !!value) {
console.log(value, !!value);
if (
!!getFieldValue("part_type") === (!!value || value === 0)
) {
return Promise.resolve();
}
return Promise.reject(

View File

@@ -9,6 +9,7 @@ import {
SEARCH_JOBS_FOR_AUTOCOMPLETE,
} from "../../graphql/jobs.queries";
import AlertComponent from "../alert/alert.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
const { Option } = Select;
const JobSearchSelect = (
@@ -86,11 +87,9 @@ const JobSearchSelect = (
<span>
{`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${
o.ro_number || t("general.labels.na")
} | ${o.ownr_ln || ""} ${o.ownr_fn || ""} ${
o.ownr_co_nm ? ` ${o.ownr_co_num}` : ""
}| ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${
o.v_model_desc || ""
}`}
} | ${OwnerNameDisplayFunction(o)} | ${
o.v_model_yr || ""
} ${o.v_make_desc || ""} ${o.v_model_desc || ""}`}
</span>
<Tag>
<strong>{o.status}</strong>

View File

@@ -18,7 +18,7 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import axios from "axios";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
@@ -53,6 +53,12 @@ export function JobsConvertButton({
variables: { jobId: job.id, ...values },
});
if (values.ca_gst_registrant) {
await axios.post("/job/totalsssu", {
id: job.id,
});
}
if (!res.errors) {
refetch();
notification["success"]({

View File

@@ -7,6 +7,7 @@ import { connect } from "react-redux";
import { Link, useHistory } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { CANCEL_APPOINTMENTS_BY_JOB_ID } from "../../graphql/appointments.queries";
import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
@@ -56,6 +57,7 @@ export function JobsDetailHeaderActions({
const [deleteJob] = useMutation(DELETE_JOB);
const [updateJob] = useMutation(UPDATE_JOB);
const [voidJob] = useMutation(VOID_JOB);
const [cancelAllAppointments] = useMutation(CANCEL_APPOINTMENTS_BY_JOB_ID);
const jobInProduction = useMemo(() => {
return bodyshop.md_ro_statuses.production_statuses.includes(job.status);
}, [job, bodyshop.md_ro_statuses.production_statuses]);
@@ -121,6 +123,39 @@ export function JobsDetailHeaderActions({
>
{t("jobs.actions.schedule")}
</Menu.Item>
<Menu.Item
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
>
<Popconfirm
title={t("general.labels.areyousure")}
okText="Yes"
cancelText="No"
onClick={(e) => e.stopPropagation()}
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
onConfirm={async () => {
const jobUpdate = await cancelAllAppointments({
variables: {
jobid: job.id,
job: {
date_scheduled: null,
scheduled_in: null,
scheduled_completion: null,
status: bodyshop.md_ro_statuses.default_imported,
},
},
});
if (!jobUpdate.errors) {
notification["success"]({
message: t("appointments.successes.canceled"),
});
return;
}
}}
getPopupContainer={(trigger) => trigger.parentNode}
>
{t("menus.jobsactions.cancelallappointments")}
</Popconfirm>
</Menu.Item>
<Menu.Item
disabled={
!!job.intakechecklist ||

View File

@@ -22,6 +22,7 @@ import "./jobs-detail-header.styles.scss";
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -64,9 +65,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
${job.v_make_desc || ""}
${job.v_model_desc || ""}`.trim();
const ownerTitle = `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
job.ownr_co_nm || ""
}`.trim();
const ownerTitle = OwnerNameDisplayFunction(job).trim();
return (
<Row gutter={[16, 16]} style={{ alignItems: "stretch" }}>

View File

@@ -88,6 +88,33 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
</Form.Item>
<CABCpvrtCalculator form={form} disabled={jobRO} />
</Space>
<Form.Item
label={t("jobs.fields.auto_add_ats")}
name="auto_add_ats"
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
nostyle
shouldUpdate={(prev, cur) => prev.auto_add_ats !== cur.auto_add_ats}
>
{() => {
if (form.getFieldValue("auto_add_ats"))
return (
<Form.Item
label={t("jobs.fields.rate_ats")}
name="rate_ats"
initialValue={bodyshop.shoprates.rate_atp}
>
<CurrencyInput disabled={jobRO} />
</Form.Item>
);
return null;
}}
</Form.Item>
</FormRow>
<FormRow>
<Form.Item
@@ -100,7 +127,13 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
label={t("jobs.fields.state_tax_rate")}
name="state_tax_rate"
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} autoComplete="new-password"/>
<InputNumber
min={0}
max={1}
precision={2}
disabled={jobRO}
autoComplete="new-password"
/>
</Form.Item>
<Form.Item
label={t("jobs.fields.local_tax_rate")}

View File

@@ -4,6 +4,7 @@ import React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import PhoneFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function JobsFindModalComponent({
selectedJob,
@@ -43,15 +44,12 @@ export default function JobsFindModalComponent({
render: (text, record) => {
return record.owner ? (
<Link to={"/manage/owners/" + record.owner.id}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}
<OwnerNameDisplay ownerObject={record} />
</Link>
) : (
// t("jobs.errors.noowner")
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}</span>
<span>
<OwnerNameDisplay ownerObject={record} />
</span>
);
},
},

View File

@@ -10,7 +10,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import StartChatButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
@@ -52,14 +52,12 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
render: (text, record) => {
return record.ownerid ? (
<Link to={"/manage/owners/" + record.ownerid}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}
<OwnerNameDisplay ownerObject={record} />
</Link>
) : (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}</span>
<span>
<OwnerNameDisplay ownerObject={record} />
</span>
);
},
},

View File

@@ -18,6 +18,7 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
import AlertComponent from "../alert/alert.component";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -141,14 +142,12 @@ export function JobsList({ bodyshop }) {
to={"/manage/owners/" + record.owner.id}
onClick={(e) => e.stopPropagation()}
>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}
<OwnerNameDisplay ownerObject={record} />
</Link>
) : (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}</span>
<span>
<OwnerNameDisplay ownerObject={record} />
</span>
);
},
},

View File

@@ -6,6 +6,7 @@ import { QUERY_SEARCH_OWNER_BY_IDX } from "../../graphql/owners.queries";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import OwnerFindModalComponent from "./owner-find-modal.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
export default function OwnerFindModalContainer({
loading,
@@ -30,9 +31,7 @@ export default function OwnerFindModalContainer({
useEffect(() => {
if (modalProps.visible && owner) {
const s = `${owner.ownr_fn || ""} ${owner.ownr_ln || ""} ${
owner.ownr_co_nm || ""
}`;
const s = OwnerNameDisplayFunction(owner);
setSearchText(s.trim());
callSearchowners({ variables: { search: s.trim() } });

View File

@@ -0,0 +1,47 @@
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { store } from "../../redux/store";
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)(OwnerNameDisplay);
export function OwnerNameDisplay({ bodyshop, ownerObject }) {
const emptyTest =
ownerObject.ownr_fn + ownerObject.ownr_ln + ownerObject.ownr_co_nm;
if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "")
return "N/A";
if (bodyshop.last_name_first)
return `${ownerObject.ownr_ln || ""}, ${ownerObject.ownr_fn || ""} ${
ownerObject.ownr_co_nm || ""
}`.trim();
return `${ownerObject.ownr_fn || ""} ${ownerObject.ownr_ln || ""} ${
ownerObject.ownr_co_nm || ""
}`.trim();
}
export function OwnerNameDisplayFunction(ownerObject) {
const emptyTest =
ownerObject.ownr_fn + ownerObject.ownr_ln + ownerObject.ownr_co_nm;
if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "")
return "N/A";
const rdxStore = store.getState();
if (rdxStore.user.bodyshop.last_name_first)
return `${ownerObject.ownr_ln || ""}, ${ownerObject.ownr_fn || ""} ${
ownerObject.ownr_co_nm || ""
}`.trim();
return `${ownerObject.ownr_fn || ""} ${ownerObject.ownr_ln || ""} ${
ownerObject.ownr_co_nm || ""
}`.trim();
}

View File

@@ -8,6 +8,7 @@ import {
SEARCH_OWNERS_FOR_AUTOCOMPLETE,
} from "../../graphql/owners.queries";
import AlertComponent from "../alert/alert.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
const { Option } = Select;
@@ -16,10 +17,8 @@ const OwnerSearchSelect = ({ value, onChange, onBlur, disabled }, ref) => {
SEARCH_OWNERS_FOR_AUTOCOMPLETE
);
const [
callIdSearch,
{ loading: idLoading, error: idError, data: idData },
] = useLazyQuery(SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE);
const [callIdSearch, { loading: idLoading, error: idError, data: idData }] =
useLazyQuery(SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE);
const executeSearch = (v) => {
callSearch(v);
@@ -78,9 +77,7 @@ const OwnerSearchSelect = ({ value, onChange, onBlur, disabled }, ref) => {
{theOptions
? theOptions.map((o) => (
<Option key={o.id} value={o.id}>
{`${o.ownr_ln || ""} ${o.ownr_fn || ""} ${
o.ownr_co_nm ? ` ${o.ownr_co_num}` : ""
}| ${o.ownr_addr1 || ""} `}
{`${OwnerNameDisplayFunction(o)} | ${o.ownr_addr1 || ""} `}
</Option>
))
: null}

View File

@@ -3,6 +3,10 @@ import React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import PhoneFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
export default function OwnerTagPopoverComponent({ job }) {
const { t } = useTranslation();
const content = (
@@ -10,9 +14,9 @@ export default function OwnerTagPopoverComponent({ job }) {
<Row>
<Col span={12}>
<Descriptions title={t("owners.labels.fromclaim")} column={1}>
<Descriptions.Item key="1" label={t("jobs.fields.owner")}>{`${
job.ownr_fn || ""
} ${job.ownr_ln || ""} ${job.ownr_co_nm || ""}`}</Descriptions.Item>
<Descriptions.Item key="1" label={t("jobs.fields.owner")}>
<OwnerNameDisplay ownerObject={job} />
</Descriptions.Item>
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph1")}>
<PhoneFormatter>{job.ownr_ph1 || ""}</PhoneFormatter>
</Descriptions.Item>
@@ -31,11 +35,9 @@ export default function OwnerTagPopoverComponent({ job }) {
</Col>
<Col span={12}>
<Descriptions title={t("owners.labels.fromowner")} column={1}>
<Descriptions.Item key="1" label={t("jobs.fields.owner")}>{`${
job.owner.ownr_fn || ""
} ${job.owner.ownr_ln || ""} ${
job.owner.ownr_co_nm || ""
}`}</Descriptions.Item>
<Descriptions.Item key="1" label={t("jobs.fields.owner")}>
<OwnerNameDisplay ownerObject={job.owner} />
</Descriptions.Item>
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph1")}>
<PhoneFormatter>{job.owner.ownr_ph1 || ""}</PhoneFormatter>
</Descriptions.Item>
@@ -68,9 +70,7 @@ export default function OwnerTagPopoverComponent({ job }) {
<Popover placement="bottom" content={content}>
<Tag color="cyan">
<Link to={`/manage/owners/${job.owner.id}`}>
{job.owner
? `${job.ownr_co_nm || ""}${job.ownr_fn || ""} ${job.ownr_ln || ""}`
: t("jobs.errors.noowner")}
{job.owner ? OwnerNameDisplayFunction(job) : t("jobs.errors.noowner")}
</Link>
</Tag>
</Popover>

View File

@@ -5,6 +5,7 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useHistory, useLocation } from "react-router-dom";
import PhoneFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function OwnersListComponent({
loading,
@@ -33,9 +34,7 @@ export default function OwnersListComponent({
key: "name",
render: (text, record) => (
<Link to={"/manage/owners/" + record.id}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}
<OwnerNameDisplay ownerObject={record} />
</Link>
),
},

View File

@@ -1,13 +1,10 @@
import { notification } from "antd";
import axios from "axios";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setPartnerVersion } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {store} from '../../redux/store'
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
@@ -21,45 +18,48 @@ export default connect(
mapDispatchToProps
)(PartnerPingComponent);
export function PartnerPingComponent({ bodyshop, setPartnerVersion }) {
const { t } = useTranslation();
export function PartnerPingComponent({ bodyshop, }) {
useEffect(() => {
// Create an scoped async function in the hook
async function checkPartnerStatus() {
if (!bodyshop) return;
try {
//if (process.env.NODE_ENV === "development") return;
const PartnerResponse = await axios.post("http://localhost:1337/ping/");
const { appver, qbpath } = PartnerResponse.data;
setPartnerVersion(appver);
console.log({ appver, qbpath });
if (
!qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
)
) {
notification["error"]({
title: "",
message: t("general.messages.noacctfilepath"),
});
}
} catch (error) {
console.log(error);
notification["error"]({
title: "",
message: t("general.messages.partnernotrunning"),
});
}
}
// Execute the created function directly
checkPartnerStatus();
checkPartnerStatus(bodyshop);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bodyshop]);
return <></>;
}
export async function checkPartnerStatus(bodyshop) {
if (!bodyshop) return;
try {
//if (process.env.NODE_ENV === "development") return;
const PartnerResponse = await axios.post("http://localhost:1337/ping/");
// const {
// appver, //qbpath
// } = PartnerResponse.data;
console.log(PartnerResponse.data)
store.dispatch(setPartnerVersion(PartnerResponse.data));
// if (
// checkAcctPath &&
// !qbpath &&
// !(
// bodyshop &&
// (bodyshop.cdk_dealerid ||
// bodyshop.pbs_serialnumber ||
// bodyshop.accountingconfig.qbo)
// )
// ) {
// notification["error"]({
// title: "",
// message: i18n.t("general.messages.noacctfilepath"),
// });
// }
} catch (error) {
console.log("ImEX Online Partner is not running.", error);
// notification["error"]({
// title: "",
// message: i18n.t("general.messages.partnernotrunning"),
// });
}
}

View File

@@ -1,4 +1,4 @@
import { DeleteFilled, WarningFilled } from "@ant-design/icons";
import { DeleteFilled, WarningFilled, DownOutlined } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Divider,
@@ -9,6 +9,8 @@ import {
Space,
Tag,
Select,
Menu,
Dropdown,
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -39,6 +41,7 @@ export function PartsOrderModalComponent({
isReturn,
preferredMake,
job,
form,
}) {
const [sendType, setSendType] = sendTypeState;
const { OEConnection } = useTreatments(
@@ -52,6 +55,21 @@ export function PartsOrderModalComponent({
bodyshop.imexshopid
);
const { t } = useTranslation();
const handleClick = ({ item, key, keyPath }) => {
form.setFieldsValue({ comments: item.props.value });
};
const menu = (
<div>
<Menu onClick={handleClick}>
{bodyshop.md_parts_order_comment.map((comment, idx) => (
<Menu.Item value={comment.comment} key={idx}>
{comment.label}
</Menu.Item>
))}
</Menu>
</div>
);
return (
<div>
@@ -243,7 +261,23 @@ export function PartsOrderModalComponent({
);
}}
</Form.List>
<Form.Item name="comments" label={t("parts_orders.fields.comments")}>
<Form.Item
name="comments"
label={
<Space>
{t("parts_orders.fields.comments")}
<Dropdown overlay={menu}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<DownOutlined />
</a>
</Dropdown>
</Space>
}
>
<Input.TextArea rows={3} />
</Form.Item>
<Radio.Group

View File

@@ -346,6 +346,7 @@ export function PartsOrderModalContainer({
<LoadingSpinner />
) : (
<PartsOrderModalComponent
form={form}
vendorList={(data && data.vendors) || []}
sendTypeState={sendTypeState}
isReturn={isReturn}

View File

@@ -14,6 +14,7 @@ import { alphaSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const stripeTestEnv = process.env.REACT_APP_STRIPE_PUBLIC_KEY; //.includes("test");
@@ -78,14 +79,12 @@ export function PaymentsListPaginated({
render: (text, record) => {
return record.job.owner ? (
<Link to={"/manage/owners/" + record.job.owner.id}>
{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${
record.job.ownr_co_nm || ""
}`}
<OwnerNameDisplay ownerObject={record} />
</Link>
) : (
<span>{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""} ${
record.job.ownr_co_nm || ""
}`}</span>
<span>
<OwnerNameDisplay ownerObject={record} />
</span>
);
},
},

View File

@@ -13,6 +13,7 @@ import ProductionListColumnProductionNote from "../production-list-columns/produ
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
import "./production-board-card.styles.scss";
import moment from "moment";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function ProductionBoardCard(
technician,
@@ -87,9 +88,9 @@ export default function ProductionBoardCard(
card.ownr_co_nm || ""
}`}</div>
) : (
<div className="ellipses">{`${card.ownr_ln || ""}, ${
card.ownr_fn || ""
} ${card.ownr_co_nm || ""}`}</div>
<div className="ellipses">
<OwnerNameDisplay ownerObject={card} />
</div>
)}
</Col>
)}

View File

@@ -22,6 +22,7 @@ import ProductionListColumnCategory from "./production-list-columns.status.categ
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
import ProductionListColumnComment from "./production-list-columns.comment.component";
import ProductionListColumnPartsReceived from "./production-list-columns.partsreceived.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const r = ({ technician, state, activeStatuses, bodyshop }) => {
return [
@@ -68,11 +69,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
dataIndex: "ownr",
key: "ownr",
ellipsis: true,
render: (text, record) => (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}</span>
),
render: (text, record) => <OwnerNameDisplay ownerObject={record} />,
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
sortOrder:
state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order,
@@ -82,6 +79,13 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
sorter: (a, b) =>
alphaSort(
a.v_make_desc + a.v_model_desc,
b.v_make_desc + b.v_model_desc
),
sortOrder:
state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order,
render: (text, record) => (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""

View File

@@ -17,7 +17,7 @@ import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
import JobAtChange from "../job-at-change/job-at-change.component";
import { PrinterFilled } from "@ant-design/icons";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
@@ -107,9 +107,7 @@ export function ProductionListDetail({ jobs, setPrintCenterContext }) {
{theJob.ins_co_nm || ""}
</Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.owner")}>
{`${theJob.ownr_fn || ""} ${theJob.ownr_ln || ""} ${
theJob.ownr_co_nm || ""
}`}
<OwnerNameDisplay ownerObject={theJob} />
<StartChatButton
phone={data.jobs_by_pk.ownr_ph1}
jobid={data.jobs_by_pk.id}

View File

@@ -9,7 +9,7 @@ import { DateTimeFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import "./schedule-production-list.styles.scss";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function ScheduleProductionList() {
const { t } = useTranslation();
const [callQuery, { loading, error, data }] = useLazyQuery(
@@ -36,9 +36,7 @@ export default function ScheduleProductionList() {
<td>
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
</td>
<td>{`${j.ownr_fn || ""} ${j.ownr_ln || ""} ${
j.ownr_co_nm || ""
}`}</td>
<td><OwnerNameDisplay ownerObject={j} /></td>
<td>{`${j.v_model_yr || ""} ${j.v_make_desc || ""} ${
j.v_model_desc || ""
}`}</td>

View File

@@ -520,7 +520,6 @@ export default function ShopInfoGeneral({ form }) {
>
<CurrencyInput />
</Form.Item>
<Form.Item
name={["attach_pdf_to_email"]}
label={t("bodyshop.fields.attach_pdf_to_email")}
@@ -559,6 +558,13 @@ export default function ShopInfoGeneral({ form }) {
>
<Switch />
</Form.Item>
<Form.Item
name={["bill_allow_post_to_closed"]}
label={t("bodyshop.fields.bill_allow_post_to_closed")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["md_ded_notes"]}
label={t("bodyshop.fields.md_ded_notes")}
@@ -571,6 +577,13 @@ export default function ShopInfoGeneral({ form }) {
>
<Select mode="tags" />
</Form.Item>
<Form.Item
name={["last_name_first"]}
label={t("bodyshop.fields.last_name_first")}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")}>
<Form.List name={["md_messaging_presets"]}>
@@ -805,14 +818,23 @@ export default function ShopInfoGeneral({ form }) {
>
<Input />
</Form.Item>
<Space>
<Form.Item
label={t("bodyshop.fields.md_ins_co.zip")}
key={`${index}zip`}
name={[field.name, "zip"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.md_ins_co.zip")}
key={`${index}zip`}
name={[field.name, "zip"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.md_ins_co.private")}
key={`${index}private`}
name={[field.name, "private"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
@@ -1305,6 +1327,72 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.fields.md_parts_order_comment")}>
<Form.List name={["md_parts_order_comment"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<Form.Item
label={t("general.labels.label")}
key={`${index}label`}
name={[field.name, "label"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("parts_orders.fields.comments")}
key={`${index}comment`}
name={[field.name, "comment"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
</LayoutFormRow>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("general.actions.add")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</LayoutFormRow>
</div>
);
}

View File

@@ -81,7 +81,10 @@ export function SignInComponent({
/>
</Form.Item>
{signInError ? (
<AlertComponent type="error" message={signInError.message} />
<AlertComponent
type="error"
message={t(`users.errors.signinerror.${signInError.code}`)}
/>
) : null}
<Button
className="login-btn"

View File

@@ -12,6 +12,7 @@ import AlertComponent from "../alert/alert.component";
import DataLabel from "../data-label/data-label.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
technician: selectTechnician,
@@ -60,11 +61,9 @@ export function TechClockedInList({ technician }) {
<Card
title={
<Link to={`/tech/joblookup?selected=${ticket.job.id}`}>
{`${ticket.job.ro_number || t("general.labels.na")} ${
ticket.job.ownr_fn || ""
} ${ticket.job.ownr_ln || ""} ${
ticket.job.ownr_co_nm || ""
}`}
{`${
ticket.job.ro_number || t("general.labels.na")
} ${OwnerNameDisplayFunction(ticket.job)}`}
</Link>
}
actions={[

View File

@@ -12,6 +12,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { onlyUnique } from "../../utils/arrayHelper";
import { alphaSort } from "../../utils/sorters";
import AlertComponent from "../alert/alert.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -108,9 +109,9 @@ export function TechLookupJobsList({ bodyshop }) {
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
ellipsis: true,
render: (text, record) => (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}</span>
<span>
<OwnerNameDisplay ownerObject={record} />
</span>
),
},
{

View File

@@ -237,7 +237,7 @@ export function TimeTicketModalComponent({
return Promise.reject(
t("timetickets.validation.clockoffwithoutclockon")
);
if (!value.isSameOrAfter(clockon))
if (value && !value.isSameOrAfter(clockon))
return Promise.reject(
t("timetickets.validation.clockoffmustbeafterclockon")
);

View File

@@ -7,6 +7,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -34,9 +35,7 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
key: "owner",
render: (text, record) => (
<Link to={`/manage/owners/${record.owner.id}`}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}
<OwnerNameDisplay ownerObject={record} />
</Link>
),
},

View File

@@ -25,6 +25,7 @@ export default function VendorsFormComponent({
formLoading,
handleDelete,
responsibilityCenters,
selectedvendor,
}) {
const { t } = useTranslation();
const client = useApolloClient();
@@ -52,7 +53,12 @@ export default function VendorsFormComponent({
>
{t("general.actions.save")}
</Button>
<Button type="danger" onClick={handleDelete} loading={formLoading}>
<Button
type="danger"
disabled={selectedvendor === "new"}
onClick={handleDelete}
loading={formLoading}
>
{t("general.actions.delete")}
</Button>

View File

@@ -39,30 +39,30 @@ function VendorsFormContainer({ refetch, bodyshop }) {
const [insertvendor] = useMutation(INSERT_NEW_VENDOR);
const [deleteVendor] = useMutation(DELETE_VENDOR);
const handleDelete = () => {
const handleDelete = async () => {
setFormLoading(true);
deleteVendor({
const result = await deleteVendor({
variables: { id: selectedvendor },
refetchQueries: ["QUERY_ALL_VENDORS"],
})
.then((r) => {
notification["success"]({
message: t("vendors.successes.deleted"),
});
delete search.selectedvendor;
history.push({ search: queryString.stringify(search) });
if (refetch)
refetch().then((r) => {
form.resetFields();
});
setFormLoading(false);
})
.catch((error) => {
notification["error"]({
message: t("vendors.errors.deleting"),
});
setFormLoading(false);
});
console.log(result);
if (result.errors) {
notification["error"]({
message: t("vendors.errors.deleting"),
});
} else {
notification["success"]({
message: t("vendors.successes.deleted"),
});
delete search.selectedvendor;
history.push({ search: queryString.stringify(search) });
if (refetch)
refetch().then((r) => {
form.resetFields();
});
}
setFormLoading(false);
};
const handleFinish = async (values) => {
@@ -139,6 +139,7 @@ function VendorsFormContainer({ refetch, bodyshop }) {
formLoading={formLoading}
handleDelete={handleDelete}
responsibilityCenters={bodyshop.md_responsibility_centers || null}
selectedvendor={selectedvendor}
/>
) : (
t("vendors.labels.noneselected")

View File

@@ -245,6 +245,27 @@ export const CANCEL_APPOINTMENT_BY_ID = gql`
}
`;
export const CANCEL_APPOINTMENTS_BY_JOB_ID = gql`
mutation CANCEL_APPOINTMENTS_BY_JOB_ID($jobid: uuid!, $job: jobs_set_input) {
update_appointments(
where: { _and: { jobid: { _eq: $jobid }, arrived: { _eq: false } } }
_set: { canceled: true }
) {
returning {
id
canceled
}
}
update_jobs_by_pk(pk_columns: { id: $jobid }, _set: $job) {
date_scheduled
id
scheduled_in
scheduled_completion
status
}
}
`;
export const QUERY_APPOINTMENTS_BY_JOBID = gql`
query QUERY_APPOINTMENTS_BY_JOBID($jobid: uuid!) {
appointments(where: { jobid: { _eq: $jobid } }, order_by: { start: desc }) {

View File

@@ -12,6 +12,13 @@ export const UPDATE_BILL_LINE = gql`
}
}
`;
export const DELETE_BILL_LINE = gql`
mutation DELETE_BILL_LINE($id: uuid!) {
delete_billlines_by_pk(id: $id) {
id
}
}
`;
export const INSERT_NEW_BILL_LINES = gql`
mutation INSERT_NEW_BILL_LINES($billLines: [billlines_insert_input!]!) {

View File

@@ -103,6 +103,9 @@ export const QUERY_BODYSHOP = gql`
timezone
ss_configuration
md_from_emails
last_name_first
md_parts_order_comment
bill_allow_post_to_closed
employees {
user_email
id
@@ -203,6 +206,9 @@ export const UPDATE_SHOP = gql`
timezone
ss_configuration
md_from_emails
last_name_first
md_parts_order_comment
bill_allow_post_to_closed
employees {
id
first_name

View File

@@ -146,7 +146,7 @@ export const QUERY_EXACT_JOB_IN_PRODUCTION = gql`
employee_refinish
employee_prep
employee_csr
joblines_status{
joblines_status {
part_type
status
count
@@ -224,7 +224,7 @@ export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
employee_refinish
employee_prep
employee_csr
joblines_status{
joblines_status {
part_type
status
count
@@ -304,7 +304,7 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
employee_prep
employee_csr
suspended
joblines_status{
joblines_status {
part_type
status
count
@@ -611,6 +611,8 @@ export const GET_JOB_BY_PK = gql`
ownerid
ded_note
materials
auto_add_ats
rate_ats
owner {
id
ownr_fn
@@ -721,20 +723,6 @@ export const GET_JOB_BY_PK = gql`
}
}
}
parts_order_lines {
id
parts_order {
id
order_number
comments
order_date
user_email
vendor {
id
name
}
}
}
}
payments {
id
@@ -2114,3 +2102,23 @@ export const DELETE_RELATED_RO = gql`
}
}
`;
export const GET_JOB_LINE_ORDERS = gql`
query GET_JOB_LINE_ORDERS($joblineid: uuid!) {
parts_order_lines(where: { job_line_id: { _eq: $joblineid } }) {
id
act_price
parts_order {
id
order_date
order_number
orderedby
return
comments
vendor {
id
name
}
}
}
}
`;

View File

@@ -3,7 +3,7 @@ import * as Sentry from "@sentry/react";
import "antd/dist/antd.less";
import Dinero from "dinero.js";
import React from "react";
import ReactDOM from "react-dom";
import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom";
import { PersistGate } from "redux-persist/integration/react";
@@ -39,7 +39,9 @@ if (process.env.NODE_ENV !== "development") {
});
}
ReactDOM.render(
const container = document.getElementById("root");
const root = createRoot(container); // createRoot(container!) if you use TypeScript
root.render(
<Provider store={store}>
<BrowserRouter>
<PersistGate
@@ -49,48 +51,7 @@ ReactDOM.render(
<AppContainer />
</PersistGate>
</BrowserRouter>
</Provider>,
document.getElementById("root")
</Provider>
);
// const onServiceWorkerUpdate = (registration) => {
// console.log("onServiceWorkerUpdate", registration);
// const btn = (
// <Space flex>
// <Button
// onClick={async () => {
// window.open("https://imex-online.noticeable.news/", "_blank");
// }}
// >
// {i18n.t("general.actions.viewreleasenotes")}
// </Button>
// <Button
// type="primary"
// onClick={async () => {
// if (registration && registration.waiting) {
// await registration.unregister();
// // Makes Workbox call skipWaiting()
// registration.waiting.postMessage({ type: "SKIP_WAITING" });
// // Once the service worker is unregistered, we can reload the page to let
// // the browser download a fresh copy of our app (invalidating the cache)
// window.location.reload();
// }
// }}
// >
// {i18n.t("general.actions.refresh")}
// </Button>
// </Space>
// );
// notification.open({
// icon: <AlertOutlined />,
// message: i18n.t("general.messages.newversiontitle"),
// description: i18n.t("general.messages.newversionmessage"),
// duration: 0,
// btn,
// key: "updateavailable",
// });
// };
// serviceWorkerRegistration.register({ onUpdate: onServiceWorkerUpdate });
reportWebVitals();

View File

@@ -5,16 +5,19 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import AccountingPayablesTable from "../../components/accounting-payables-table/accounting-payables-table.component";
import AlertComponent from "../../components/alert/alert.component";
import { checkPartnerStatus } from "../../components/partner-ping/partner-ping.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_BILLS_FOR_EXPORT } from "../../graphql/accounting.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectPartnerVersion } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
partnerVersion: selectPartnerVersion,
});
const mapDispatchToProps = (dispatch) => ({
@@ -26,6 +29,7 @@ export function AccountingPayablesContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
partnerVersion,
}) {
const { t } = useTranslation();
@@ -38,7 +42,8 @@ export function AccountingPayablesContainer({
label: t("titles.bc.accounting-payables"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
checkPartnerStatus(bodyshop, true);
}, [t, setBreadcrumbs, setSelectedHeader, bodyshop]);
const { loading, error, data } = useQuery(QUERY_BILLS_FOR_EXPORT, {
fetchPolicy: "network-only",
@@ -47,9 +52,24 @@ export function AccountingPayablesContainer({
if (error) return <AlertComponent message={error.message} type="error" />;
const noPath =
!partnerVersion?.qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
);
return (
<div>
<RbacWrapper action="accounting:payables">
{noPath && (
<AlertComponent
type="error"
message={t("general.messages.noacctfilepath")}
/>
)}
<AccountingPayablesTable
loadaing={loading}
bills={data ? data.bills : []}

View File

@@ -12,9 +12,12 @@ import {
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { checkPartnerStatus } from "../../components/partner-ping/partner-ping.component";
import { selectPartnerVersion } from "../../redux/application/application.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
partnerVersion: selectPartnerVersion,
});
const mapDispatchToProps = (dispatch) => ({
@@ -25,6 +28,7 @@ export function AccountingPaymentsContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
partnerVersion,
}) {
const { t } = useTranslation();
@@ -37,7 +41,8 @@ export function AccountingPaymentsContainer({
label: t("titles.bc.accounting-payments"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
checkPartnerStatus(bodyshop, true);
}, [t, setBreadcrumbs, setSelectedHeader, bodyshop]);
const { loading, error, data } = useQuery(QUERY_PAYMENTS_FOR_EXPORT, {
fetchPolicy: "network-only",
@@ -45,10 +50,23 @@ export function AccountingPaymentsContainer({
});
if (error) return <AlertComponent message={error.message} type="error" />;
const noPath =
!partnerVersion?.qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
);
return (
<div>
<RbacWrapper action="accounting:payments">
{noPath && (
<AlertComponent
type="error"
message={t("general.messages.noacctfilepath")}
/>
)}
<AccountingPaymentsTable
loadaing={loading}
payments={data ? data.payments : []}

View File

@@ -12,9 +12,12 @@ import {
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { checkPartnerStatus } from "../../components/partner-ping/partner-ping.component";
import { selectPartnerVersion } from "../../redux/application/application.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
partnerVersion: selectPartnerVersion,
});
const mapDispatchToProps = (dispatch) => ({
@@ -25,6 +28,7 @@ export function AccountingReceivablesContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
partnerVersion,
}) {
const { t } = useTranslation();
@@ -37,7 +41,8 @@ export function AccountingReceivablesContainer({
label: t("titles.bc.accounting-receivables"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
checkPartnerStatus(bodyshop, true);
}, [t, setBreadcrumbs, setSelectedHeader, bodyshop]);
const { loading, error, data } = useQuery(QUERY_JOBS_FOR_EXPORT, {
variables: {
@@ -48,9 +53,25 @@ export function AccountingReceivablesContainer({
});
if (error) return <AlertComponent message={error.message} type="error" />;
const noPath =
!partnerVersion?.qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
);
return (
<div>
<RbacWrapper action="accounting:receivables">
{noPath && (
<AlertComponent
type="error"
message={t("general.messages.noacctfilepath")}
/>
)}
<AccountingReceivablesTable
loadaing={loading}
jobs={data ? data.jobs : []}

View File

@@ -1,4 +1,4 @@
import { SyncOutlined } from "@ant-design/icons";
import { SyncOutlined, EditFilled } from "@ant-design/icons";
import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd";
import queryString from "query-string";
import React, { useState } from "react";
@@ -141,7 +141,9 @@ export function BillsListPage({
render: (text, record) => (
<Space wrap>
<Link to={`/manage/bills?billid=${record.id}`}>
<Button>{t("bills.actions.edit")}</Button>
<Button>
<EditFilled />
</Button>
</Link>
{
// <Button

View File

@@ -29,6 +29,7 @@ import {
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -143,9 +144,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
<Link to={`/manage/jobs/${data && data.jobs_by_pk.id}`}>{`${
data && data.jobs_by_pk && data.jobs_by_pk.ro_number
}`}</Link>
{` | ${data.jobs_by_pk.ownr_fn || ""} ${
data.jobs_by_pk.ownr_ln || ""
} ${data.jobs_by_pk.ownr_co_nm || ""} | ${
{` | ${OwnerNameDisplayFunction(data.jobs_by_pk)} | ${
data.jobs_by_pk.v_model_yr || ""
} ${data.jobs_by_pk.v_make_desc || ""} ${
data.jobs_by_pk.v_model_desc || ""

View File

@@ -3,12 +3,19 @@ import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import JobsAvailableTableContainer from "../../components/jobs-available-table/jobs-available-table.container";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectPartnerVersion } from "../../redux/application/application.selectors";
const mapStateToProps = createStructuredSelector({
partnerVersion: selectPartnerVersion,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -16,6 +23,7 @@ const mapDispatchToProps = (dispatch) => ({
});
export function JobsAvailablePageContainer({
partnerVersion,
setBreadcrumbs,
setSelectedHeader,
}) {
@@ -40,9 +48,18 @@ export function JobsAvailablePageContainer({
</Link>
}
/>
{!partnerVersion && (
<AlertComponent
type="warning"
message={t("general.messages.partnernotrunning")}
/>
)}
<JobsAvailableTableContainer />
</div>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(JobsAvailablePageContainer);
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsAvailablePageContainer);

View File

@@ -6,6 +6,7 @@ import { createStructuredSelector } from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import SpinComponent from "../../components/loading-spinner/loading-spinner.component";
import NotFound from "../../components/not-found/not-found.component";
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { GET_JOB_BY_PK, UPDATE_JOB } from "../../graphql/jobs.queries";
import {
@@ -78,11 +79,10 @@ function JobsDetailPageContainer({
CreateRecentItem(
jobId,
"job",
`${data.jobs_by_pk.ro_number || t("general.labels.na")} | ${
data.jobs_by_pk.ownr_fn || ""
} ${data.jobs_by_pk.ownr_ln || ""} ${
data.jobs_by_pk.ownr_co_nm || ""
}`,
`${
data.jobs_by_pk.ro_number || t("general.labels.na")
} | ${OwnerNameDisplayFunction(data.jobs_by_pk)}`,
`/manage/jobs/${jobId}`
)
);

View File

@@ -14,6 +14,7 @@ import {
import { CreateRecentItem } from "../../utils/create-recent-item";
import OwnersDetailComponent from "./owners-detail.page.component";
import NotFound from "../../components/not-found/not-found.component";
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -38,11 +39,7 @@ export function OwnersDetailContainer({
useEffect(() => {
document.title = t("titles.owners-detail", {
name: data
? `${(data.owners_by_pk && data.owners_by_pk.ownr_fn) || ""} ${
(data.owners_by_pk && data.owners_by_pk.ownr_ln) || ""
} ${(data.owners_by_pk && data.owners_by_pk.ownr_co_nm) || ""}`
: "",
name: data ? OwnerNameDisplayFunction(data.owners_by_pk) : "",
});
setSelectedHeader("owners");
setBreadcrumbs([
@@ -50,11 +47,7 @@ export function OwnersDetailContainer({
{
link: `/manage/owners/${ownerId}`,
label: t("titles.bc.owner-detail", {
name: data
? `${(data.owners_by_pk && data.owners_by_pk.ownr_fn) || ""} ${
(data.owners_by_pk && data.owners_by_pk.ownr_ln) || ""
} ${(data.owners_by_pk && data.owners_by_pk.ownr_co_nm) || ""}`
: "",
name: data ? OwnerNameDisplayFunction(data.owners_by_pk) : "",
}),
},
]);
@@ -64,9 +57,7 @@ export function OwnersDetailContainer({
CreateRecentItem(
ownerId,
"owner",
`${data.owners_by_pk.ownr_fn || ""} ${
data.owners_by_pk.ownr_ln || ""
} ${data.owners_by_pk.ownr_co_nm || ""}`,
OwnerNameDisplayFunction(data.owners_by_pk),
`/manage/owners/${ownerId}`
)
);

View File

@@ -17,6 +17,7 @@ import { alphaSort } from "../../utils/sorters";
import { useLocation } from "react-router-dom";
import queryString from "query-string";
import _ from "lodash";
import OwnerNameDisplay from "../../components/owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -120,14 +121,12 @@ export function PartsQueuePageComponent({ bodyshop }) {
render: (text, record) => {
return record.ownerid ? (
<Link to={"/manage/owners/" + record.ownerid}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}
<OwnerNameDisplay ownerObject={record} />
</Link>
) : (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
}`}</span>
<span>
<OwnerNameDisplay ownerObject={record} />
</span>
);
},
},

View File

@@ -233,6 +233,7 @@
},
"appt_length": "Default Appointment Length",
"attach_pdf_to_email": "Attach PDF copy to sent emails?",
"bill_allow_post_to_closed": "Allow Bills to be posted to Closed Jobs",
"bill_federal_tax_rate": "Bills - Federal Tax Rate %",
"bill_local_tax_rate": "Bill - Provincial/State Tax Rate %",
"bill_state_tax_rate": "Bill - Provincial/State Tax Rate %",
@@ -275,6 +276,7 @@
"mapa": "Job Costing - Paint Materials Hourly Cost Rate",
"mash": "Job Costing - Shop Materials Hourly Cost Rate"
},
"last_name_first": "Display Owner Info as <Last>, <First>",
"lastnumberworkingdays": "Scoreboard - Last Number of Working Days",
"logo_img_footer_margin": "Footer Margin (px)",
"logo_img_header_margin": "Header Margin (px)",
@@ -294,12 +296,14 @@
"md_ins_co": {
"city": "City",
"name": "Insurance Company Name",
"private": "Private",
"state": "Province/State",
"street1": "Street 1",
"street2": "Street 2",
"zip": "Zip/Postal Code"
},
"md_jobline_presets": "Jobline Presets",
"md_parts_order_comment": "Parts Orders Comments",
"md_payment_types": "Payment Types",
"md_referral_sources": "Referral Sources",
"messaginglabel": "Messaging Preset Label",
@@ -540,6 +544,8 @@
"partslocations": "Parts Locations",
"printlater": "Print Later",
"qbo": "Use QuickBooks Online?",
"qbo_departmentid": "QBO Department ID",
"qbo_usa": "QBO USA Compatibility",
"rbac": "Role Based Access Control",
"responsibilitycenters": {
"costs": "Cost Centers",
@@ -1080,7 +1086,7 @@
"mod_lbr_ty": "Labor Type",
"notes": "Notes",
"oem_partno": "OEM Part #",
"op_code_desc": "Operation Code Description",
"op_code_desc": "Op Code Description",
"part_qty": "Qty.",
"part_type": "Part Type",
"part_types": {
@@ -1240,6 +1246,7 @@
"08": "Left Rear Side",
"09": "Left Side"
},
"auto_add_ats": "Automatically Add/Update ATS",
"ca_bc_pvrt": "PVRT",
"ca_customer_gst": "Customer Portion of GST",
"ca_gst_registrant": "GST Registrant",
@@ -1387,6 +1394,7 @@
"production_vars": {
"note": "Production Note"
},
"rate_ats": "ATS Rate",
"rate_la1": "LA1",
"rate_la2": "LA2",
"rate_la3": "LA3",
@@ -1757,6 +1765,7 @@
},
"jobsactions": {
"admin": "Admin",
"cancelallappointments": "Cancel all appointments",
"closejob": "Close Job",
"deletejob": "Delete Job",
"duplicate": "Duplicate this Job",
@@ -1937,6 +1946,7 @@
"email": "Send by Email",
"inthisorder": "Parts in this Order",
"newpartsorder": "New Parts Order",
"notyetordered": "This part has not yet been ordered.",
"oec": "Order via OEC",
"orderhistory": "Order History",
"parts_orders": "Parts Orders",
@@ -2568,6 +2578,14 @@
"passwordchanged": "Password changed successfully. "
}
},
"users": {
"errors": {
"signinerror": {
"auth/user-not-found": "A user with this email does not exist.",
"auth/wrong-password": "The email and password combination you provided is incorrect."
}
}
},
"vehicles": {
"errors": {
"noaccess": "The vehicle does not exist or you do not have access to it.",

View File

@@ -233,6 +233,7 @@
},
"appt_length": "",
"attach_pdf_to_email": "",
"bill_allow_post_to_closed": "",
"bill_federal_tax_rate": "",
"bill_local_tax_rate": "",
"bill_state_tax_rate": "",
@@ -275,6 +276,7 @@
"mapa": "",
"mash": ""
},
"last_name_first": "",
"lastnumberworkingdays": "",
"logo_img_footer_margin": "",
"logo_img_header_margin": "",
@@ -294,12 +296,14 @@
"md_ins_co": {
"city": "",
"name": "",
"private": "",
"state": "",
"street1": "",
"street2": "",
"zip": ""
},
"md_jobline_presets": "",
"md_parts_order_comment": "",
"md_payment_types": "",
"md_referral_sources": "",
"messaginglabel": "",
@@ -540,6 +544,8 @@
"partslocations": "",
"printlater": "",
"qbo": "",
"qbo_departmentid": "",
"qbo_usa": "",
"rbac": "",
"responsibilitycenters": {
"costs": "",
@@ -1240,6 +1246,7 @@
"08": "",
"09": ""
},
"auto_add_ats": "",
"ca_bc_pvrt": "",
"ca_customer_gst": "",
"ca_gst_registrant": "",
@@ -1387,6 +1394,7 @@
"production_vars": {
"note": ""
},
"rate_ats": "",
"rate_la1": "Tarifa LA1",
"rate_la2": "Tarifa LA2",
"rate_la3": "Tarifa LA3",
@@ -1757,6 +1765,7 @@
},
"jobsactions": {
"admin": "",
"cancelallappointments": "",
"closejob": "",
"deletejob": "",
"duplicate": "",
@@ -1937,6 +1946,7 @@
"email": "Enviar por correo electrónico",
"inthisorder": "Partes en este pedido",
"newpartsorder": "",
"notyetordered": "",
"oec": "",
"orderhistory": "Historial de pedidos",
"parts_orders": "",
@@ -2568,6 +2578,14 @@
"passwordchanged": ""
}
},
"users": {
"errors": {
"signinerror": {
"auth/user-not-found": "",
"auth/wrong-password": ""
}
}
},
"vehicles": {
"errors": {
"noaccess": "El vehículo no existe o usted no tiene acceso a él.",

View File

@@ -233,6 +233,7 @@
},
"appt_length": "",
"attach_pdf_to_email": "",
"bill_allow_post_to_closed": "",
"bill_federal_tax_rate": "",
"bill_local_tax_rate": "",
"bill_state_tax_rate": "",
@@ -275,6 +276,7 @@
"mapa": "",
"mash": ""
},
"last_name_first": "",
"lastnumberworkingdays": "",
"logo_img_footer_margin": "",
"logo_img_header_margin": "",
@@ -294,12 +296,14 @@
"md_ins_co": {
"city": "",
"name": "",
"private": "",
"state": "",
"street1": "",
"street2": "",
"zip": ""
},
"md_jobline_presets": "",
"md_parts_order_comment": "",
"md_payment_types": "",
"md_referral_sources": "",
"messaginglabel": "",
@@ -540,6 +544,8 @@
"partslocations": "",
"printlater": "",
"qbo": "",
"qbo_departmentid": "",
"qbo_usa": "",
"rbac": "",
"responsibilitycenters": {
"costs": "",
@@ -1240,6 +1246,7 @@
"08": "",
"09": ""
},
"auto_add_ats": "",
"ca_bc_pvrt": "",
"ca_customer_gst": "",
"ca_gst_registrant": "",
@@ -1387,6 +1394,7 @@
"production_vars": {
"note": ""
},
"rate_ats": "",
"rate_la1": "Taux LA1",
"rate_la2": "Taux LA2",
"rate_la3": "Taux LA3",
@@ -1757,6 +1765,7 @@
},
"jobsactions": {
"admin": "",
"cancelallappointments": "",
"closejob": "",
"deletejob": "",
"duplicate": "",
@@ -1937,6 +1946,7 @@
"email": "Envoyé par email",
"inthisorder": "Pièces dans cette commande",
"newpartsorder": "",
"notyetordered": "",
"oec": "",
"orderhistory": "Historique des commandes",
"parts_orders": "",
@@ -2568,6 +2578,14 @@
"passwordchanged": ""
}
},
"users": {
"errors": {
"signinerror": {
"auth/user-not-found": "",
"auth/wrong-password": ""
}
}
},
"vehicles": {
"errors": {
"noaccess": "Le véhicule n'existe pas ou vous n'y avez pas accès.",

View File

@@ -1,7 +1,7 @@
import { gql } from "@apollo/client";
import { notification } from "antd";
import axios from "axios";
import jsreport from "jsreport-browser-client-dist";
import jsreport from "@jsreport/browser-client";
import _ from "lodash";
import moment from "moment";
import { auth } from "../firebase/firebase.utils";

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
{}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."bodyshops" add column "last_name_first" boolean
-- not null default 'false';

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "last_name_first" boolean
not null default 'false';

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."bodyshops" add column "md_parts_order_comment" jsonb
-- not null default jsonb_build_array();

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "md_parts_order_comment" jsonb
not null default jsonb_build_array();

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."bodyshops" add column "bill_allow_post_to_closed" boolean
-- not null default 'false';

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "bill_allow_post_to_closed" boolean
not null default 'false';

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."jobs" add column "auto_add_ats" boolean
-- null default 'false';

View File

@@ -0,0 +1,2 @@
alter table "public"."jobs" add column "auto_add_ats" boolean
null default 'false';

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."jobs" add column "rate_ats" numeric
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."jobs" add column "rate_ats" numeric
null;

5325
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,8 @@
"version": "0.0.1",
"license": "UNLICENSED",
"engines": {
"node": "12.22.6",
"npm": "7.17.0"
"node": "16.14.2",
"npm": "8.6.0"
},
"scripts": {
"setup": "yarn && cd client && yarn",

View File

@@ -168,7 +168,6 @@ exports.default = async (req, res) => {
sendServerEmail({
subject: `Autohouse Report ${moment().format("MM-DD-YY")}`,
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
Uploaded: ${JSON.stringify(
allxmlsToUpload.map((x) => x.filename),
null,
@@ -755,9 +754,14 @@ const CreateCosts = (job) => {
return {
PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => {
if (key !== defaultCosts.PAS && key !== defaultCosts.PASL)
if (
key !== defaultCosts.PAS &&
key !== defaultCosts.PASL &&
key !== defaultCosts.MAPA &&
key !== defaultCosts.MASH &&
key !== defaultCosts.TOW
)
return acc.add(billTotalsByCostCenters[key]);
return acc;
}, Dinero()),
PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add(

View File

@@ -20,6 +20,38 @@ mutation UNARCHIVE_CONVERSATION($id: uuid!) {
}
`;
exports.INSERT_NEW_JOB_LINE = `
mutation INSERT_NEW_JOB_LINE($lineInput: [joblines_insert_input!]!) {
insert_joblines(objects: $lineInput) {
returning {
id
}
}
}
`;
exports.UPDATE_JOB_LINE = `
mutation UPDATE_JOB_LINE($lineId: uuid!, $line: joblines_set_input!) {
update_joblines(where: { id: { _eq: $lineId } }, _set: $line) {
returning {
id
notes
mod_lbr_ty
part_qty
db_price
act_price
line_desc
line_no
oem_partno
notes
location
status
removed
}
}
}
`;
exports.RECEIVE_MESSAGE = `
mutation RECEIVE_MESSAGE($msg: [messages_insert_input!]!) {
insert_messages(objects: $msg) {
@@ -970,6 +1002,8 @@ exports.GET_JOB_BY_PK = ` query GET_JOB_BY_PK($id: uuid!) {
ca_bc_pvrt
ca_customer_gst
materials
auto_add_ats
rate_ats
joblines(where: { removed: { _eq: false } }){
id
line_no

View File

@@ -24,7 +24,7 @@ exports.totalsSsu = async function (req, res) {
});
const newTotals = await TotalsServerSide(
{ body: { job: job.jobs_by_pk } },
{ body: { job: job.jobs_by_pk, client: client } },
res,
true
);
@@ -53,7 +53,9 @@ exports.totalsSsu = async function (req, res) {
//IMPORTANT*** These two functions MUST be mirrrored.
async function TotalsServerSide(req, res) {
const { job } = req.body;
const { job, client } = req.body;
await AutoAddAtsIfRequired({ job: job, client: client });
try {
let ret = {
parts: CalculatePartsTotals(job.joblines),
@@ -78,6 +80,16 @@ async function Totals(req, res) {
jobid: job.id,
});
const BearerToken = req.headers.authorization;
const { id } = req.body;
logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
Authorization: BearerToken,
},
});
await AutoAddAtsIfRequired({ job, client });
try {
let ret = {
parts: CalculatePartsTotals(job.joblines),
@@ -96,6 +108,83 @@ async function Totals(req, res) {
}
}
async function AutoAddAtsIfRequired({ job, client }) {
//Check if ATS should be automatically added.
if (job.auto_add_ats) {
//Get the total sum of hours that should be the ATS amount.
//Check to see if an ATS line exists.
let atsLineIndex = null;
const atsHours = job.joblines.reduce((acc, val, index) => {
if (val.line_desc && val.line_desc.toLowerCase() === "ats amount") {
atsLineIndex = index;
}
if (
val.mod_lbr_ty !== "LA1" &&
val.mod_lbr_ty !== "LA2" &&
val.mod_lbr_ty !== "LA3" &&
val.mod_lbr_ty !== "LA4" &&
val.mod_lbr_ty !== "LAU" &&
val.mod_lbr_ty !== "LAG" &&
val.mod_lbr_ty !== "LAS" &&
val.mod_lbr_ty !== "LAA"
) {
acc = acc + val.mod_lb_hrs;
}
return acc;
}, 0);
const atsAmount = atsHours * (job.rate_ats || 0);
//If it does, update it in place, and make sure it is updated for local calculations.
if (atsLineIndex === null) {
const newAtsLine = {
jobid: job.id,
alt_partm: null,
line_no: 35,
unq_seq: 0,
line_ind: "E",
line_desc: "ATS Amount",
line_ref: 0.0,
part_type: null,
oem_partno: null,
db_price: 0.0,
act_price: atsAmount,
part_qty: 1,
mod_lbr_ty: null,
db_hrs: 0.0,
mod_lb_hrs: 0.0,
lbr_op: "OP13",
lbr_amt: 0.0,
op_code_desc: "ADDITIONAL COSTS",
status: null,
location: null,
tax_part: true,
db_ref: null,
manual_line: true,
prt_dsmk_p: 0.0,
prt_dsmk_m: 0.0,
};
const result = await client.request(queries.INSERT_NEW_JOB_LINE, {
lineInput: [newAtsLine],
});
job.joblines.push(newAtsLine);
}
//If it does not, create one for local calculations and insert it.
else {
const result = await client.request(queries.UPDATE_JOB_LINE, {
line: { act_price: atsAmount },
lineId: job.joblines[atsLineIndex].id,
});
job.joblines[atsLineIndex].act_price = atsAmount;
}
console.log(job.jobLines);
}
}
function CalculateRatesTotals(ratesList) {
const jobLines = ratesList.joblines.filter((jl) => !jl.removed);

View File

@@ -201,7 +201,9 @@ exports.job = async (req, res) => {
.filter((p) => p.isValid() && p.isAfter(yesterday)),
moment().tz(timezone).add(15, "days"),
]);
const range = Math.round(moment.duration(end.diff(today)).asDays());
const range = Math.round(
moment.duration(end.add(20, "days").diff(today)).asDays()
);
for (var day = 0; day < range; day++) {
const current = moment(today)
.tz(timezone)