Compare commits

...

89 Commits

Author SHA1 Message Date
Allan Carr
5d2bdc7ee1 IO-2338 Selection Color
Overrides nth-child color
2023-06-15 15:24:24 -07:00
Patrick Fic
91279c27fe Merged in release/2023-06-09 (pull request #858)
Release/2023 06 09
2023-06-09 22:35:38 +00:00
Patrick Fic
3812a0650e Global search improvements. 2023-06-09 15:11:14 -07:00
Patrick Fic
b1c5bbb01f Change FK relaitonship. 2023-06-09 11:28:22 -07:00
Patrick Fic
f440a2b022 Improve update alert on mobile devices. 2023-06-08 16:18:12 -07:00
Patrick Fic
6262b3ff83 Revert changes for IO-2311 IO-2317. 2023-06-08 16:00:47 -07:00
Patrick Fic
307c77b30c Remove failing CI components. 2023-06-08 15:43:21 -07:00
Patrick Fic
f1d7a98fe8 Merge branch 'feature/IO-2208-chat-affix' into release/2023-06-09 2023-06-08 11:20:10 -07:00
Patrick Fic
be259317f9 Update payables posting label. 2023-06-08 11:13:35 -07:00
swtmply
046d104bfa IO-2208 removed subscriptions to the message 2023-06-09 01:04:59 +08:00
Patrick Fic
9c693a2b74 Merged in feature/IO-2329-update-alert (pull request #851)
IO-2329 Remove testing statement.
2023-06-07 19:06:22 +00:00
Patrick Fic
2be0f3de09 IO-2329 Remove testing statement. 2023-06-07 12:05:55 -07:00
Patrick Fic
de8c2cd5a2 Merged in feature/IO-2329-update-alert (pull request #849)
IO-2329 Change update alert to be permanent.
2023-06-07 19:03:48 +00:00
Patrick Fic
b791f9846f IO-2329 Change update alert to be permanent. 2023-06-07 12:03:17 -07:00
Patrick Fic
daa7631056 Integration IO-2278 schema changes made on wrong branch. 2023-06-07 11:23:00 -07:00
John Allen Delos Reyes
14b8a2daef Merged in feature/IO-2322-insurance-dropdown (pull request #844)
IO-2322 added search function to insurance dropdown

Approved-by: Patrick Fic
2023-06-07 18:14:31 +00:00
John Allen Delos Reyes
90b38d817d Merged in feature/IO-2311-messaging-tile-height (pull request #845)
IO-2311 fixed tile height

Approved-by: Patrick Fic
2023-06-07 18:14:25 +00:00
John Allen Delos Reyes
ace48e2890 Merged in feature/IO-2280-config-query-params (pull request #843)
IO-2280 fixed query params in shop config

Approved-by: Patrick Fic
2023-06-07 18:14:18 +00:00
John Allen Delos Reyes
fb810be5d5 Merged in feature/IO-2317-messaging-screen (pull request #846)
IO-2317 Messaging screen on mobile

Approved-by: Patrick Fic
2023-06-07 18:13:45 +00:00
Patrick Fic
50230e9f50 IO-2278 adjust schema for parts dispatch. 2023-06-06 13:41:38 -07:00
Patrick Fic
86e14967ca IO-2278 Additional permission for dispatch lines. 2023-06-06 13:38:51 -07:00
Patrick Fic
c45c3b4037 IO-2278 Add permissions for parts dispatch. 2023-06-06 13:33:03 -07:00
Patrick Fic
c4c11528b9 IO-2278 Add relationship tracking to parts dispatch. 2023-06-06 13:22:51 -07:00
Patrick Fic
1f9c4e92f1 IO-2278 Add parts dispatch relationship. 2023-06-06 13:21:40 -07:00
Patrick Fic
371e148e09 IO-2278 Track parts dispatch relationships. 2023-06-06 13:18:44 -07:00
swtmply
33af544ded IO-2322 added search function to insurance dropdown 2023-06-06 22:47:20 +08:00
swtmply
6b8d0ec91c IO-2311 fixed tile height 2023-06-06 22:39:39 +08:00
swtmply
5a3ddfad0f IO-2280 fixed query params in shop config 2023-06-06 22:20:22 +08:00
Allan Carr
043c44ed51 Merged in feature/IO-2281-table-colors (pull request #842)
IO-2281 Hover Row Color

Approved-by: Patrick Fic
2023-06-06 00:17:39 +00:00
Allan Carr
6d5dbf3145 IO-2281 Hover Row Color 2023-06-05 17:13:31 -07:00
swtmply
6d8463265c IO-2317 Messaging screen on mobile 2023-06-06 02:07:30 +08:00
Patrick Fic
0669282432 Merged in release/2023-06-02 (pull request #840)
Release/2023 06 02
2023-06-02 20:08:34 +00:00
John Allen Delos Reyes
bf4dc7e158 Merged in feature/IO-2314-problem-job-hyperlink (pull request #835)
IO-2314 Added hyperlink to ro number

Approved-by: Patrick Fic
2023-06-02 15:55:13 +00:00
Allan Carr
5de2036fdb Merged in feature/IO-2316-Predefined-Email-Address-for-CC (pull request #833)
IO-2316 Predefined Email address for CC field

Approved-by: Patrick Fic
2023-06-02 14:37:28 +00:00
Allan Carr
1629663e15 Merged in feature/IO-2315-DMS-Posting-Sheet-Restriction (pull request #832)
IO-2315 DMS Posting Sheet

Approved-by: Patrick Fic
2023-06-02 14:36:32 +00:00
swtmply
e25f2db2b1 IO-2314 Added hyperlink to ro number 2023-06-02 22:27:53 +08:00
Allan Carr
cbf5d268ea IO-2315 DMS Posting Sheet
Restrict to DMS setups only
2023-06-01 20:32:30 -07:00
Patrick Fic
a92a95a9fa Merge branch 'release/2023-06-02' of bitbucket.org:snaptsoft/bodyshop into release/2023-06-02 2023-06-01 18:31:12 -07:00
Patrick Fic
0be7bf2c8e Merge branch 'feature/IO-2310-active-jobs-ro-sort' into release/2023-06-02 2023-06-01 18:29:53 -07:00
Patrick Fic
56b810dd40 IO-2310 Change RO sort. 2023-06-01 18:29:41 -07:00
Allan Carr
33cfa531b8 IO-2316 Predefined Email address for CC field 2023-06-01 17:34:55 -07:00
John Allen Delos Reyes
000ded6649 Merged in feature/IO-2281-table-colors (pull request #829)
IO-2281 Added striped table colors

Approved-by: Patrick Fic
2023-06-01 22:28:30 +00:00
Patrick Fic
72181e1ff7 Add in missing filter for job search select on job clock on. 2023-05-31 14:33:59 -07:00
swtmply
d73b1d2220 IO-2281 Added striped table colors 2023-06-01 02:34:18 +08:00
Allan Carr
8645b434c8 Merged in feature/IO-2299-RBAC-for-Voiding-File (pull request #827)
IO-2299 RBAC for Voiding File

Approved-by: Patrick Fic
2023-05-31 15:30:15 +00:00
Allan Carr
38a13bd082 IO-2299 RBAC for Voiding File 2023-05-30 20:14:33 -07:00
Allan Carr
3bc5f5d626 Merged in feature/IO-2233-CDK-VEHICLE-LICENSE-PLATE (pull request #826)
IO-2233 Check for null and if stripped string is size 0 send null instead

Approved-by: Patrick Fic
2023-05-31 00:33:20 +00:00
Allan Carr
86a2351316 IO-2233 Check for null and if stripped string is size 0 send null instead 2023-05-30 17:23:22 -07:00
Patrick Fic
de62e994bd Merged in release/2023-05-26 (pull request #823)
Resolve error thrown when entering payment from accounting menu.
2023-05-26 22:11:09 +00:00
Patrick Fic
d0d8354395 Merged in feature/IO-2298-payment-export (pull request #821)
Resolve error thrown when entering payment from accounting menu.
2023-05-26 21:57:39 +00:00
Patrick Fic
6d2c3c81c7 Resolve error thrown when entering payment from accounting menu. 2023-05-26 14:57:04 -07:00
Patrick Fic
e2e02945cc Merged in release/2023-05-26 (pull request #820)
Brought in shop estimator & made list of estimators unique.
2023-05-26 19:04:20 +00:00
Patrick Fic
404efcaf05 Merged in feature/IO-2264-schedule-filter (pull request #818)
Brought in shop estimator & made list of estimators unique.
2023-05-26 19:03:54 +00:00
Patrick Fic
9b96460e4f Brought in shop estimator & made list of estimators unique. 2023-05-26 12:03:33 -07:00
Patrick Fic
618eff4973 Merged in release/2023-05-26 (pull request #817)
Add manual appointment handling.
2023-05-26 18:53:21 +00:00
Patrick Fic
109c34bebd Merged in feature/IO-2264-schedule-filter (pull request #816)
Add manual appointment handling.
2023-05-26 18:53:09 +00:00
Patrick Fic
1aab5aa740 Add manual appointment handling. 2023-05-26 11:52:43 -07:00
Patrick Fic
484cb8e39e Merged in release/2023-05-26 (pull request #815)
Release/2023 05 26
2023-05-26 18:27:21 +00:00
Patrick Fic
1ca483d4b0 Merged in feature/IO-2298-payment-export (pull request #814)
Update labels.
2023-05-26 18:22:23 +00:00
Patrick Fic
094160ebf3 Update labels. 2023-05-26 11:18:59 -07:00
John Allen Delos Reyes
e7be4c6e61 Merged in feature/IO-2298-payment-export (pull request #809)
IO-2298 added mark for export button on payment

Approved-by: Patrick Fic
2023-05-26 18:08:52 +00:00
John Allen Delos Reyes
708fe63852 Merged in feature/IO-2208-chat-affix (pull request #810)
IO-2208 added more conversations on initial load

Approved-by: Patrick Fic
2023-05-26 18:01:00 +00:00
John Allen Delos Reyes
3f579f49b9 Merged in feature/IO-2264-schedule-filter (pull request #811)
IO-2264 removed filter function for estimators

Approved-by: Patrick Fic
2023-05-26 18:00:52 +00:00
swtmply
c15e69f079 IO-2298 added mark for export button on payment 2023-05-27 00:17:44 +08:00
swtmply
ddb919e2cc IO-2264 removed filter function for estimators 2023-05-26 22:56:36 +08:00
swtmply
27e0f497bb IO-2208 added more conversations on initial load 2023-05-26 20:12:58 +08:00
John Allen Delos Reyes
3a06e813a8 Merged in feature/IO-2280-config-anchors (pull request #808)
IO-2280 added config anchors

Approved-by: Patrick Fic
2023-05-25 23:41:50 +00:00
swtmply
c2aaf8844f IO-2280 added config anchors 2023-05-26 02:24:34 +08:00
John Allen Delos Reyes
488f79ddc8 Merged in feature/IO-2264-schedule-filter (pull request #806)
Feature/IO-2264 schedule filter

Approved-by: Patrick Fic
2023-05-25 17:44:11 +00:00
John Allen Delos Reyes
5704e54e48 Merged in feature/IO-2298-payment-export (pull request #802)
IO-2298 added export buttons on payments

Approved-by: Patrick Fic
2023-05-25 17:41:54 +00:00
swtmply
3c4902f71f IO-2298 removed import destructure and added context reload 2023-05-26 00:48:28 +08:00
swtmply
5fb62aa16b IO-2264 added filter by estimators for the schedule 2023-05-26 00:15:35 +08:00
Allan Carr
dd469bad12 Merged in feature/IO-2233-CDK-VEHICLE-LICENSE-PLATE (pull request #803)
IO-2233 CDK Vehicle License Plate Standardization

Approved-by: Patrick Fic
2023-05-24 21:05:21 +00:00
Allan Carr
a716535795 Merged in feature/IO-2259-Company-Name-with-only-Space (pull request #804)
IO-2259 Trim Company Name before seeing if ""

Approved-by: Patrick Fic
2023-05-24 21:05:00 +00:00
Allan Carr
c2ec476324 IO-2259 Trim Company Name before seeing if "" 2023-05-24 11:15:55 -07:00
Patrick Fic
3ecd29c640 Add missing filter on job search select. 2023-05-24 11:13:55 -07:00
Allan Carr
01443c478d IO-2233 CDK Vehicle License Plate Standardization
Remove All special character and spaces and covert to Upper Case
2023-05-24 10:34:05 -07:00
swtmply
209245187f IO-2298 added export buttons on payments 2023-05-25 01:28:22 +08:00
John Allen Delos Reyes
3a9e989d70 Merged in feature/IO-2208-chat-affix (pull request #801)
IO-2208 replaced cache first to network only

Approved-by: Patrick Fic
2023-05-24 15:24:57 +00:00
swtmply
8839fc0293 IO-2208 replaced cache first to network only 2023-05-24 23:07:49 +08:00
Allan Carr
028bf3c7a0 Merged in feature/IO-2292-MPI-Signature-Sheets (pull request #800)
IO-2292 MPI Signature Sheets

Approved-by: Patrick Fic
2023-05-24 14:58:04 +00:00
Allan Carr
ed05754368 IO-2292 MPI Signature Sheets 2023-05-23 17:56:48 -07:00
Allan Carr
7c3043988b Merged in feature/IO-2295-Individual-Note-Print (pull request #799)
IO-2295 Individual Note Print File Name contained {{ro_number}}

Approved-by: Patrick Fic
2023-05-23 23:32:33 +00:00
John Allen Delos Reyes
a16f0df7de Merged in feature/IO-2297-datetime-improvement (pull request #798)
IO-2297 added optimistic response and click outside function

Approved-by: Patrick Fic
2023-05-23 23:32:11 +00:00
John Allen Delos Reyes
2562151f6e Merged in feature/IO-2208-chat-affix (pull request #797)
Feature/IO-2208 chat affix

Approved-by: Patrick Fic
2023-05-23 23:28:31 +00:00
Allan Carr
44f2287b07 IO-2295 Individual Note Print File Name contained {{ro_number}} 2023-05-23 13:38:55 -07:00
swtmply
c5c47e9bfc IO-2208 remove unused variables and imports 2023-05-19 00:14:28 +08:00
swtmply
d66fdfb2e0 IO-2208 added virtualization to conversation list 2023-05-18 23:04:10 +08:00
swtmply
4bc8ff26d2 IO-2208 Added pagination and subscription to chat 2023-05-13 00:32:51 +08:00
61 changed files with 1141 additions and 243 deletions

View File

@@ -51,6 +51,7 @@
"react-i18next": "^12.2.0",
"react-icons": "^4.7.1",
"react-image-lightbox": "^5.1.4",
"react-intersection-observer": "^9.4.3",
"react-number-format": "^5.1.3",
"react-redux": "^8.0.5",
"react-resizable": "^3.0.4",

View File

@@ -145,7 +145,11 @@
//Update row highlighting on production board.
.ant-table-tbody > tr.ant-table-row:hover > td {
background: #eaeaea !important;
background: #e7f3ff !important;
}
.ant-table-tbody > tr.ant-table-row-selected > td {
background: #e6f7ff !important;
}
.job-line-manual {
@@ -156,3 +160,11 @@
td.ant-table-column-sort {
background-color: transparent;
}
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
background-color: #f4f4f4;
}
.rowWithColor > td {
background-color: var(--bgColor) !important;
}

View File

@@ -1,12 +1,19 @@
import { Badge, List, Tag } from "antd";
import React from "react";
import { connect } from "react-redux";
import {
AutoSizer,
CellMeasurer,
CellMeasurerCache,
List as VirtualizedList,
} from "react-virtualized";
import { createStructuredSelector } from "reselect";
import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import "./chat-conversation-list.styles.scss";
const mapStateToProps = createStructuredSelector({
@@ -18,59 +25,95 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setSelectedConversation(conversationId)),
});
export function ChatConversationListComponent({
function ChatConversationListComponent({
conversationList,
selectedConversation,
setSelectedConversation,
loadMoreConversations,
}) {
const cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 60,
});
const rowRenderer = ({ index, key, style, parent }) => {
const item = conversationList[index];
return (
<CellMeasurer
key={key}
cache={cache}
parent={parent}
columnIndex={0}
rowIndex={index}
>
<List.Item
onClick={() => setSelectedConversation(item.id)}
className={`chat-list-item ${
item.id === selectedConversation
? "chat-list-selected-conversation"
: null
}`}
style={style}
>
<div
style={{
display: "inline-block",
}}
>
{item.label && <div className="chat-name">{item.label}</div>}
{item.job_conversations.length > 0 ? (
<div className="chat-name">
{item.job_conversations.map((j, idx) => (
<div key={idx}>
<OwnerNameDisplay ownerObject={j.job} />
</div>
))}
</div>
) : (
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
)}
</div>
<div style={{ display: "inline-block" }}>
<div>
{item.job_conversations.length > 0
? item.job_conversations.map((j, idx) => (
<Tag key={idx} className="ro-number-tag">
{j.job.ro_number}
</Tag>
))
: null}
</div>
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
</div>
<Badge count={item.messages_aggregate.aggregate.count || 0} />
</List.Item>
</CellMeasurer>
);
};
return (
<div className="chat-list-container">
<List
bordered
dataSource={conversationList}
renderItem={(item) => (
<List.Item
key={item.id}
onClick={() => setSelectedConversation(item.id)}
className={`chat-list-item ${
item.id === selectedConversation
? "chat-list-selected-conversation"
: null
}`}
>
<div sryle={{ display: "inline-block" }}>
{item.label && <div className="chat-name">{item.label}</div>}
{item.job_conversations.length > 0 ? (
<div className="chat-name">
{item.job_conversations.map((j, idx) => (
<div key={idx}>
<OwnerNameDisplay ownerObject={j.job} />
</div>
))}
</div>
) : (
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
)}
</div>
<div sryle={{ display: "inline-block" }}>
<div>
{item.job_conversations.length > 0
? item.job_conversations.map((j, idx) => (
<Tag key={idx} className="ro-number-tag">
{j.job.ro_number}
</Tag>
))
: null}
</div>
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
</div>
<Badge count={item.messages_aggregate.aggregate.count || 0} />
</List.Item>
<AutoSizer>
{({ height, width }) => (
<VirtualizedList
height={height}
width={width}
rowCount={conversationList.length}
rowHeight={cache.rowHeight}
rowRenderer={rowRenderer}
onScroll={({ scrollTop, scrollHeight, clientHeight }) => {
if (scrollTop + clientHeight === scrollHeight) {
loadMoreConversations();
}
}}
/>
)}
/>
</AutoSizer>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps

View File

@@ -3,8 +3,9 @@
}
.chat-list-container {
flex: 1;
overflow: auto;
overflow: hidden;
height: 100%;
border: 1px solid gainsboro;
}
.chat-list-item {
@@ -21,4 +22,6 @@
.ro-number-tag {
align-self: baseline;
}
padding: 12px 24px;
border-bottom: 1px solid gainsboro;
}

View File

@@ -4,9 +4,9 @@ import {
ShrinkOutlined,
SyncOutlined,
} from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { useLazyQuery, useQuery } from "@apollo/client";
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
import React, { useEffect, useState } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -47,12 +47,13 @@ export function ChatPopupComponent({
...(pollInterval > 0 ? { pollInterval } : {}),
});
const { loading, data, refetch, called } = useQuery(CONVERSATION_LIST_QUERY, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
skip: !chatVisible,
...(pollInterval > 0 ? { pollInterval } : {}),
});
const [getConversations, { loading, data, refetch, fetchMore }] =
useLazyQuery(CONVERSATION_LIST_QUERY, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
skip: !chatVisible,
...(pollInterval > 0 ? { pollInterval } : {}),
});
const fcmToken = sessionStorage.getItem("fcmtoken");
@@ -65,15 +66,22 @@ export function ChatPopupComponent({
}, [fcmToken]);
useEffect(() => {
if (called && chatVisible) refetch();
}, [chatVisible, called, refetch]);
if (chatVisible)
getConversations({
variables: {
offset: 0,
},
});
}, [chatVisible, getConversations]);
// const unreadCount = data
// ? data.conversations.reduce(
// (acc, val) => val.messages_aggregate.aggregate.count + acc,
// 0
// )
// : 0;
const loadMoreConversations = useCallback(() => {
if (data)
fetchMore({
variables: {
offset: data.conversations.length,
},
});
}, [data, fetchMore]);
const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0;
@@ -110,6 +118,7 @@ export function ChatPopupComponent({
) : (
<ChatConversationListComponent
conversationList={data ? data.conversations : []}
loadMoreConversations={loadMoreConversations}
/>
)}
</Col>

View File

@@ -66,7 +66,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
key: "status",
},
{
title: t("jobs.fields.ro_number"),
title: t("bills.fields.invoice_number"),
dataIndex: ["Posting", "Reference"],
key: "reference",
},

View File

@@ -1,28 +1,28 @@
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons";
import {
Button,
Divider,
Dropdown,
Form,
Input,
Menu,
Select,
Space,
Tabs,
Upload,
Space,
Menu,
Dropdown,
Button,
} from "antd";
import _ from "lodash";
import React from "react";
import { useTranslation } from "react-i18next";
import EmailDocumentsComponent from "../email-documents/email-documents.component";
import _ from "lodash";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectEmailConfig } from "../../redux/email/email.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { CreateExplorerLinkForJob } from "../../utils/localmedia";
import { selectEmailConfig } from "../../redux/email/email.selectors";
import EmailDocumentsComponent from "../email-documents/email-documents.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -54,6 +54,15 @@ export function EmailOverlayComponent({
]),
});
};
const handle_CC_Click = ({ item, key, keyPath }) => {
const email = item.props.value;
form.setFieldsValue({
cc: _.uniq([
...(form.getFieldValue("cc") || ""),
...(typeof email === "string" ? [email] : email),
]),
});
};
const menu = (
<div>
@@ -74,6 +83,25 @@ export function EmailOverlayComponent({
</div>
);
const menuCC = (
<div>
<Menu onClick={handle_CC_Click}>
{bodyshop.employees
.filter((e) => e.user_email)
.map((e, idx) => (
<Menu.Item value={e.user_email} key={idx}>
{`${e.first_name} ${e.last_name}`}
</Menu.Item>
))}
{bodyshop.md_to_emails.map((e, idx) => (
<Menu.Item value={e.emails} key={idx + "group"}>
{e.label}
</Menu.Item>
))}
</Menu>
</div>
);
return (
<div>
<Form.Item
@@ -122,7 +150,23 @@ export function EmailOverlayComponent({
>
<Select mode="tags" tokenSeparators={[",", ";"]} />
</Form.Item>
<Form.Item label={t("emails.fields.cc")} name="cc">
<Form.Item
label={
<Space>
{t("emails.fields.cc")}
<Dropdown overlay={menuCC}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<UserAddOutlined />
</a>
</Dropdown>
</Space>
}
name="cc"
>
<Select mode="tags" tokenSeparators={[",", ";"]} />
</Form.Item>
<Form.Item

View File

@@ -311,7 +311,9 @@ function Header({
icon={<SettingOutlined />}
>
<Menu.Item key="shop" icon={<Icon component={GiSettingsKnobs} />}>
<Link to="/manage/shop">{t("menus.header.shop_config")}</Link>
<Link to="/manage/shop?tab=info">
{t("menus.header.shop_config")}
</Link>
</Menu.Item>
<Menu.Item key="dashboard" icon={<DashboardFilled />}>
<Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>

View File

@@ -99,7 +99,7 @@ export function JobPayments({
render: (text, record) => (
<Space wrap>
<Button
disabled={record.exportedat}
// disabled={record.exportedat}
onClick={() => {
setPaymentContext({
actions: { refetch: refetch },

View File

@@ -29,6 +29,7 @@ import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component";
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
import JobsDetailHeaderActionsExportcustdataComponent from "./jobs-detail-header-actions.exportcustdata.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -461,54 +462,56 @@ export function JobsDetailHeaderActions({
)}
<JobsDetailHeaderActionsAddevent jobid={job.id} />
{!jobRO && job.converted && (
<Menu.Item>
<Popconfirm
title={t("jobs.labels.voidjob")}
okText="Yes"
cancelText="No"
onClick={(e) => e.stopPropagation()}
onConfirm={async () => {
//delete the job.
const result = await voidJob({
variables: {
jobId: job.id,
job: {
status: bodyshop.md_ro_statuses.default_void,
voided: true,
scheduled_in: null,
scheduled_completion: null,
inproduction: false,
},
note: [
{
jobid: job.id,
created_by: currentUser.email,
audit: true,
text: t("jobs.labels.voidnote"),
<RbacWrapper action="jobs:void" noauth>
<Menu.Item>
<Popconfirm
title={t("jobs.labels.voidjob")}
okText="Yes"
cancelText="No"
onClick={(e) => e.stopPropagation()}
onConfirm={async () => {
//delete the job.
const result = await voidJob({
variables: {
jobId: job.id,
job: {
status: bodyshop.md_ro_statuses.default_void,
voided: true,
scheduled_in: null,
scheduled_completion: null,
inproduction: false,
},
],
},
});
note: [
{
jobid: job.id,
created_by: currentUser.email,
audit: true,
text: t("jobs.labels.voidnote"),
},
],
},
});
if (!!!result.errors) {
notification["success"]({
message: t("jobs.successes.voided"),
});
//go back to jobs list.
history.push(`/manage/`);
} else {
notification["error"]({
message: t("jobs.errors.voiding", {
error: JSON.stringify(result.errors),
}),
});
}
}}
getPopupContainer={(trigger) => trigger.parentNode}
>
{t("menus.jobsactions.void")}
</Popconfirm>
</Menu.Item>
if (!!!result.errors) {
notification["success"]({
message: t("jobs.successes.voided"),
});
//go back to jobs list.
history.push(`/manage/`);
} else {
notification["error"]({
message: t("jobs.errors.voiding", {
error: JSON.stringify(result.errors),
}),
});
}
}}
getPopupContainer={(trigger) => trigger.parentNode}
>
{t("menus.jobsactions.void")}
</Popconfirm>
</Menu.Item>
</RbacWrapper>
)}
</Menu>
);

View File

@@ -112,7 +112,9 @@ export function JobsList({ bodyshop }) {
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sorter: (a, b) =>
parseInt((a.ro_number || "0").replace(/\D/g, "")) -
parseInt((b.ro_number || "0").replace(/\D/g, "")),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,

View File

@@ -0,0 +1,100 @@
import React from "react";
import { Button, notification } from "antd";
import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { setModalContext } from "../../redux/modals/modals.actions";
import { connect } from "react-redux";
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import { createStructuredSelector } from "reselect";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
setPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "payment" })),
});
const PaymentMarkForExportButton = ({
bodyshop,
payment,
refetch,
setPaymentContext,
currentUser,
}) => {
const { t } = useTranslation();
const [insertExportLog, { loading: exportLogLoading }] =
useMutation(INSERT_EXPORT_LOG);
const [updatePayment, { loading: updatePaymentLoading }] =
useMutation(UPDATE_PAYMENT);
const handleClick = async () => {
const today = new Date();
await insertExportLog({
variables: {
logs: [
{
bodyshopid: bodyshop.id,
paymentid: payment.id,
successful: true,
useremail: currentUser.email,
},
],
},
});
const paymentUpdateResponse = await updatePayment({
variables: {
paymentId: payment.id,
payment: {
exportedat: today,
},
},
});
if (!!!paymentUpdateResponse.errors) {
notification.open({
type: "success",
key: "paymentsuccessmarkforexport",
message: t("payments.successes.markexported"),
});
if (refetch) refetch();
setPaymentContext({
actions: {
refetch,
},
context: {
...payment,
exportedat: today,
},
});
} else {
notification["error"]({
message: t("payments.errors.exporting", {
error: JSON.stringify(paymentUpdateResponse.error),
}),
});
}
};
return (
<Button
onClick={handleClick}
loading={exportLogLoading || updatePaymentLoading}
disabled={!!payment.exportedat}
>
{t("payments.labels.markexported")}
</Button>
);
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(PaymentMarkForExportButton);

View File

@@ -1,6 +1,6 @@
import { useMutation } from "@apollo/client";
import { Button, Form, Modal, notification } from "antd";
import { Button, Form, Modal, notification, Space } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -19,6 +19,8 @@ import {
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import PaymentForm from "../payment-form/payment-form.component";
import PaymentReexportButton from "../payment-reexport-button/payment-reexport-button.component";
import PaymentMarkForExportButton from "../payment-mark-export-button/payment-mark-export-button-component";
const mapStateToProps = createStructuredSelector({
paymentModal: selectPayment,
@@ -176,12 +178,24 @@ function PaymentModalContainer({
</span>
}
>
{!context || (context && !context.id) ? null : (
<Space>
<PaymentReexportButton payment={context} refetch={actions.refetch} />
<PaymentMarkForExportButton
bodyshop={bodyshop}
payment={context}
refetch={actions.refetch}
/>
</Space>
)}
<Form
onFinish={handleFinish}
autoComplete={"off"}
form={form}
layout="vertical"
initialValues={context || {}}
disabled={context?.exportedat}
>
<PaymentForm form={form} />
</Form>

View File

@@ -0,0 +1,66 @@
import React from "react";
import { Button, notification } from "antd";
import { useTranslation } from "react-i18next";
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
import { useMutation } from "@apollo/client";
import { setModalContext } from "../../redux/modals/modals.actions";
import { connect } from "react-redux";
const mapDispatchToProps = (dispatch) => ({
setPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "payment" })),
});
const PaymentReexportButton = ({ payment, refetch, setPaymentContext }) => {
const { t } = useTranslation();
const [updatePayment, { loading }] = useMutation(UPDATE_PAYMENT);
const handleClick = async () => {
const paymentUpdateResponse = await updatePayment({
variables: {
paymentId: payment.id,
payment: {
exportedat: null,
},
},
});
if (!!!paymentUpdateResponse.errors) {
notification.open({
type: "success",
key: "paymentsuccessexport",
message: t("payments.successes.markreexported"),
});
if (refetch) refetch();
setPaymentContext({
actions: {
refetch,
},
context: {
...payment,
exportedat: null,
},
});
} else {
notification["error"]({
message: t("payments.errors.exporting", {
error: JSON.stringify(paymentUpdateResponse.error),
}),
});
}
};
return (
<Button
onClick={handleClick}
loading={loading}
disabled={!payment.exportedat}
>
{t("payments.labels.markforreexport")}
</Button>
);
};
export default connect(null, mapDispatchToProps)(PaymentReexportButton);

View File

@@ -156,7 +156,7 @@ export function PaymentsListPaginated({
render: (text, record) => (
<Space>
<Button
disabled={record.exportedat}
// disabled={record.exportedat}
onClick={async () => {
let apolloResults;
if (search.search) {

View File

@@ -23,17 +23,34 @@ export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
const { id: jobId, job } = printCenterModal.context;
const tempList = TemplateList("job", {});
const { t } = useTranslation();
const JobsReportsList = Object.keys(tempList)
.map((key) => {
return tempList[key];
})
.filter(
(temp) =>
!temp.regions ||
(temp.regions && temp.regions[bodyshop.region_config]) ||
(temp.regions &&
bodyshop.region_config.includes(Object.keys(temp.regions)) === true)
);
const JobsReportsList =
bodyshop.cdk_dealerid === null && bodyshop.pbs_serialnumber === null
? Object.keys(tempList)
.map((key) => {
return tempList[key];
})
.filter(
(temp) =>
(!temp.regions ||
(temp.regions && temp.regions[bodyshop.region_config]) ||
(temp.regions &&
bodyshop.region_config.includes(Object.keys(temp.regions)) ===
true)) &&
(!temp.dms || temp.dms === false)
)
: Object.keys(tempList)
.map((key) => {
return tempList[key];
})
.filter(
(temp) =>
!temp.regions ||
(temp.regions && temp.regions[bodyshop.region_config]) ||
(temp.regions &&
bodyshop.region_config.includes(Object.keys(temp.regions)) ===
true)
);
const filteredJobsReportsList =
search !== ""

View File

@@ -246,11 +246,21 @@ export function ProductionListTable({
(x) => x.status === record.status
);
if (!color) return null;
if (!color) {
if (index % 2 === 0)
return {
style: {
backgroundColor: `rgb(236, 236, 236)`,
},
};
return null;
}
return {
className: "rowWithColor",
style: {
backgroundColor: `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
"--bgColor": `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
},
};
},

View File

@@ -26,6 +26,8 @@ const ret = {
"jobs:partsqueue": 4,
"jobs:checklist-view": 2,
"jobs:list-ready": 1,
"jobs:void": 5,
"bills:enter": 2,
"bills:view": 2,
"bills:list": 2,

View File

@@ -12,7 +12,8 @@ import "./schedule-calendar.styles.scss";
import JobDetailCards from "../job-detail-cards/job-detail-cards.component";
import { selectProblemJobs } from "../../redux/application/application.selectors";
import { Alert, Collapse } from "antd";
import { useTranslation } from "react-i18next";
import { useTranslation, Trans } from "react-i18next";
import { Link } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -66,10 +67,21 @@ export function ScheduleCalendarWrapperComponent({
<Alert
key={problem.id}
type="error"
message={t("appointments.labels.dataconsistency", {
ro_number: problem.ro_number,
code: problem.code,
})}
message={
<Trans
i18nKey="appointments.labels.dataconsistency"
components={[
<Link
to={`/manage/jobs/${problem.id}`}
target="_blank"
/>,
]}
values={{
ro_number: problem.ro_number,
code: problem.code,
}}
/>
}
/>
))}
</Collapse.Panel>
@@ -79,10 +91,18 @@ export function ScheduleCalendarWrapperComponent({
<Alert
key={problem.id}
type="error"
message={t("appointments.labels.dataconsistency", {
ro_number: problem.ro_number,
code: problem.code,
})}
message={
<Trans
i18nKey="appointments.labels.dataconsistency"
components={[
<Link to={`/manage/jobs/${problem.id}`} target="_blank" />,
]}
values={{
ro_number: problem.ro_number,
code: problem.code,
}}
/>
}
/>
))
)}

View File

@@ -21,6 +21,7 @@ import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verif
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import _ from "lodash";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -39,9 +40,39 @@ export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
employeevacation: true,
ins_co_nm: null,
});
const [estimatorsFilter, setEstimatiorsFilter] = useLocalStorage(
"estimators",
[]
);
const estimators = useMemo(() => {
return _.uniq([
...data
.filter((d) => d.__typename === "appointments")
.map((app) =>
`${app.job?.est_ct_fn || ""} ${app.job?.est_ct_ln || ""}`.trim()
)
.filter((e) => e.length > 0),
...bodyshop.md_estimators.map((e) =>
`${e.est_ct_fn || ""} ${e.est_ct_ln || ""}`.trim()
),
]);
}, [data, bodyshop.md_estimators]);
const filteredData = useMemo(() => {
return data.filter(
(d) =>
return data.filter((d) => {
const estFilter =
d.__typename === "appointments"
? estimatorsFilter.length === 0
? true
: !!estimatorsFilter.find(
(e) =>
e ===
`${d.job?.est_ct_fn || ""} ${d.job?.est_ct_ln || ""}`.trim()
)
: true;
return (
(d.block ||
(filter.intake && d.isintake) ||
(filter.manual && !d.isintake && d.block === false) ||
@@ -50,9 +81,11 @@ export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
!!d.employee)) &&
(filter.ins_co_nm && filter.ins_co_nm.length > 0
? filter.ins_co_nm.includes(d.job?.ins_co_nm)
: true)
);
}, [data, filter]);
: true) &&
estFilter
);
});
}, [data, filter, estimatorsFilter]);
return (
<Row gutter={[16, 16]}>
@@ -63,6 +96,21 @@ export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
extra={
<Space wrap>
<ScheduleAtsSummary appointments={filteredData} />
<Select
style={{ minWidth: "15rem" }}
mode="multiple"
placeholder={t("schedule.labels.estimators")}
allowClear
onClear={() => setEstimatiorsFilter([])}
value={[...estimatorsFilter]}
onChange={(e) => {
setEstimatiorsFilter(e);
}}
options={estimators.map((e) => ({
label: e,
value: e,
}))}
/>
<Select
style={{ minWidth: "15rem" }}
mode="multiple"

View File

@@ -15,6 +15,8 @@ import ShopInfoResponsibilityCenterComponent from "./shop-info.responsibilitycen
import ShopInfoROStatusComponent from "./shop-info.rostatus.component";
import ShopInfoSchedulingComponent from "./shop-info.scheduling.component";
import ShopInfoSpeedPrint from "./shop-info.speedprint.component";
import { useHistory, useLocation } from "react-router-dom";
import queryString from "query-string";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -31,6 +33,10 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
bodyshop.imexshopid
);
const { t } = useTranslation();
const history = useHistory();
const location = useLocation();
const search = queryString.parse(location.search);
return (
<Card
extra={
@@ -43,7 +49,14 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
</Button>
}
>
<Tabs>
<Tabs
defaultActiveKey={search.subtab}
onChange={(key) =>
history.push({
search: `?tab=${search.tab}&subtab=${key}`,
})
}
>
<Tabs.TabPane key="general" tab={t("bodyshop.labels.shopinfo")}>
<ShopInfoGeneral form={form} />
</Tabs.TabPane>

View File

@@ -24,9 +24,13 @@ const timeZonesList = momentTZ.tz.names();
export default function ShopInfoGeneral({ form }) {
const { t } = useTranslation();
return (
<div>
<LayoutFormRow header={t("bodyshop.labels.businessinformation")}>
<LayoutFormRow
header={t("bodyshop.labels.businessinformation")}
id="businessinformation"
>
<Form.Item
label={t("bodyshop.fields.shopname")}
name="shopname"
@@ -155,7 +159,10 @@ export default function ShopInfoGeneral({ form }) {
<InputNumber min={0} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.accountingsetup")}>
<LayoutFormRow
header={t("bodyshop.labels.accountingsetup")}
id="accountingsetup"
>
<Form.Item
label={t("bodyshop.labels.qbo")}
valuePropName="checked"
@@ -386,7 +393,10 @@ export default function ShopInfoGeneral({ form }) {
<Select mode="tags" />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.scoreboardsetup")}>
<LayoutFormRow
header={t("bodyshop.labels.scoreboardsetup")}
id="scoreboardsetup"
>
<Form.Item
label={t("bodyshop.fields.dailypainttarget")}
name={["scoreboard_target", "dailyPaintTarget"]}
@@ -445,7 +455,10 @@ export default function ShopInfoGeneral({ form }) {
<InputNumber min={1} precision={1} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.systemsettings")}>
<LayoutFormRow
header={t("bodyshop.labels.systemsettings")}
id="systemsettings"
>
<Form.Item
name={["md_referral_sources"]}
label={t("bodyshop.fields.md_referral_sources")}
@@ -655,7 +668,11 @@ export default function ShopInfoGeneral({ form }) {
<Input />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")}>
<LayoutFormRow
grow
header={t("bodyshop.labels.messagingpresets")}
id="messagingpresets"
>
<Form.List name={["md_messaging_presets"]}>
{(fields, { add, remove, move }) => {
return (
@@ -720,7 +737,11 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.labels.notespresets")}>
<LayoutFormRow
grow
header={t("bodyshop.labels.notespresets")}
id="notespresets"
>
<Form.List name={["md_notes_presets"]}>
{(fields, { add, remove, move }) => {
return (
@@ -785,7 +806,11 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.labels.partslocations")}>
<LayoutFormRow
grow
header={t("bodyshop.labels.partslocations")}
id="partslocations"
>
<Form.List name={["md_parts_locations"]}>
{(fields, { add, remove, move }) => {
return (
@@ -839,7 +864,11 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.labels.insurancecos")}>
<LayoutFormRow
grow
header={t("bodyshop.labels.insurancecos")}
id="insurancecos"
>
<Form.List name={["md_ins_cos"]}>
{(fields, { add, remove, move }) => {
return (
@@ -935,7 +964,11 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.labels.estimators")}>
<LayoutFormRow
grow
header={t("bodyshop.labels.estimators")}
id="estimators"
>
<Form.List name={["md_estimators"]}>
{(fields, { add, remove, move }) => {
return (
@@ -1024,7 +1057,11 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.labels.filehandlers")}>
<LayoutFormRow
grow
header={t("bodyshop.labels.filehandlers")}
id="filehandlers"
>
<Form.List name={["md_filehandlers"]}>
{(fields, { add, remove, move }) => {
return (
@@ -1106,7 +1143,11 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.fields.md_ccc_rates")}>
<LayoutFormRow
grow
header={t("bodyshop.fields.md_ccc_rates")}
id="md_ccc_rates"
>
<Form.List name={["md_ccc_rates"]}>
{(fields, { add, remove, move }) => {
return (
@@ -1223,7 +1264,11 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.fields.md_jobline_presets")}>
<LayoutFormRow
grow
header={t("bodyshop.fields.md_jobline_presets")}
id="md_jobline_presets"
>
<Form.List name={["md_jobline_presets"]}>
{(fields, { add, remove, move }) => {
return (
@@ -1404,7 +1449,11 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.fields.md_parts_order_comment")}>
<LayoutFormRow
grow
header={t("bodyshop.fields.md_parts_order_comment")}
id="md_parts_order_comment"
>
<Form.List name={["md_parts_order_comment"]}>
{(fields, { add, remove, move }) => {
return (
@@ -1470,7 +1519,11 @@ export default function ShopInfoGeneral({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.labels.md_to_emails")}>
<LayoutFormRow
grow
header={t("bodyshop.labels.md_to_emails")}
id="md_to_emails"
>
<Form.List name={["md_to_emails"]}>
{(fields, { add, remove, move }) => {
return (

View File

@@ -20,7 +20,10 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
const TemplateListGenerated = TemplateList();
return (
<div>
<LayoutFormRow header={t("bodyshop.labels.intakechecklist")}>
<LayoutFormRow
header={t("bodyshop.labels.intakechecklist")}
id="intakechecklist"
>
<Form.List name={["intakechecklist", "form"]}>
{(fields, { add, remove, move }) => {
return (
@@ -188,7 +191,10 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
</Form.Item>
</SelectorDiv>
<LayoutFormRow header={t("bodyshop.labels.deliverchecklist")}>
<LayoutFormRow
header={t("bodyshop.labels.deliverchecklist")}
id="deliverchecklist"
>
<Form.List name={["deliverchecklist", "form"]}>
{(fields, { add, remove, move }) => {
return (

View File

@@ -1,12 +1,12 @@
import { useTreatments } from "@splitsoftware/splitio-react";
import { Form, InputNumber } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
import { useTreatments } from "@splitsoftware/splitio-react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -316,6 +316,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.void")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:void"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.enter")}
rules={[

View File

@@ -95,7 +95,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
{form.getFieldValue("pbs_serialnumber")}
</DataLabel>
)}
<LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.dms.default_journal")}
@@ -315,7 +314,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</DataLabel>
</>
)}
<LayoutFormRow header={t("bodyshop.labels.responsibilitycenters.costs")}>
<LayoutFormRow
header={t("bodyshop.labels.responsibilitycenters.costs")}
id="costs"
>
<Form.List name={["md_responsibility_centers", "costs"]}>
{(fields, { add, remove }) => {
return (
@@ -462,6 +464,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<LayoutFormRow
header={t("bodyshop.labels.responsibilitycenters.profits")}
id="profits"
>
<Form.List name={["md_responsibility_centers", "profits"]}>
{(fields, { add, remove }) => {
@@ -601,7 +604,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
{fields.map((field, index) => (
<Form.Item key={field.key}>
<div>
<LayoutFormRow>
<LayoutFormRow id="mappingname">
<Form.Item
label={t("bodyshop.fields.dms.mappingname")}
key={`${index}name`}
@@ -631,6 +634,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</LayoutFormRow>
<LayoutFormRow
header={t("bodyshop.labels.defaultcostsmapping")}
id="defaultcostsmapping"
>
<Form.Item
label={t(
@@ -4088,6 +4092,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<LayoutFormRow
header={t("bodyshop.labels.responsibilitycenters.tax_accounts")}
id="tax_accounts"
>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.federal_tax")}
@@ -4202,7 +4207,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Form.Item>
</LayoutFormRow>
{DmsAp.treatment === "on" && (
<LayoutFormRow>
<LayoutFormRow id="federal_tax_itc">
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.federal_tax_itc")}
rules={[
@@ -4316,7 +4321,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Form.Item>
</LayoutFormRow>
)}
<LayoutFormRow>
<LayoutFormRow id="state_tax">
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.state_tax")}
rules={[
@@ -4414,7 +4419,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<InputNumber precision={2} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow>
<LayoutFormRow id="local_tax">
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.local_tax")}
rules={[
@@ -4512,7 +4517,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<InputNumber precision={2} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={<div>AR</div>}>
<LayoutFormRow header={<div>AR</div>} id="AR">
{/* <Form.Item
label={t("bodyshop.fields.responsibilitycenters.ar")}
rules={[
@@ -4576,7 +4581,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</LayoutFormRow>
{DmsAp.treatment === "on" && (
<LayoutFormRow header={t("bodyshop.fields.responsibilitycenters.ap")}>
<LayoutFormRow
header={t("bodyshop.fields.responsibilitycenters.ap")}
id="ap"
>
{/* <Form.Item
label={t("bodyshop.fields.responsibilitycenters.ap")}
rules={[
@@ -4639,7 +4647,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Form.Item>
</LayoutFormRow>
)}
<LayoutFormRow header={<div>Refund</div>}>
<LayoutFormRow header={<div>Refund</div>} id="refund">
{/* <Form.Item
label={t("bodyshop.fields.responsibilitycenters.refund")}
rules={[
@@ -4702,7 +4710,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Form.Item>
</LayoutFormRow>
{Qb_Multi_Ar.treatment === "on" && (
<LayoutFormRow header={<div>Multiple Payers Item</div>}>
<LayoutFormRow
header={<div>Multiple Payers Item</div>}
id="accountitem"
>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountitem")}
rules={[
@@ -4730,7 +4741,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow>
<LayoutFormRow id="sales_tax_codes">
<Form.Item
label={t(
"bodyshop.fields.responsibilitycenters.sales_tax_codes.description"

View File

@@ -44,7 +44,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
};
return (
<SelectorDiv>
<SelectorDiv id="jobstatus">
<Form.Item
name={["md_ro_statuses", "statuses"]}
label={t("bodyshop.labels.alljobstatuses")}
@@ -322,6 +322,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
<LayoutFormRow
grow
header={t("bodyshop.fields.statuses.production_colors")}
id="production_colors"
>
<Form.List name={["md_ro_statuses", "production_colors"]}>
{(fields, { add, remove, move }) => {

View File

@@ -92,7 +92,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
</Form.Item>
</LayoutFormRow>
<Divider orientation="left">{t("bodyshop.labels.workingdays")}</Divider>
<Space wrap size="large">
<Space wrap size="large" id="workingdays">
<Form.Item
label={t("general.labels.sunday")}
name={["workingdays", "sunday"]}
@@ -143,7 +143,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
<Switch />
</Form.Item>
</Space>
<LayoutFormRow header={t("bodyshop.labels.apptcolors")}>
<LayoutFormRow header={t("bodyshop.labels.apptcolors")} id="apptcolors">
<Form.List name={["appt_colors"]}>
{(fields, { add, remove, move }) => {
return (
@@ -208,7 +208,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.ssbuckets")}>
<LayoutFormRow header={t("bodyshop.labels.ssbuckets")} id="ssbuckets">
<Form.List name={["ssbuckets"]}>
{(fields, { add, remove, move }) => {
return (

View File

@@ -33,6 +33,7 @@ export function TechClockInComponent({ form, bodyshop, technician }) {
<JobSearchSelect
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
notExported={!bodyshop.tt_allow_post_to_invoiced}
notInvoiced={!bodyshop.tt_allow_post_to_invoiced}
/>
</Form.Item>

View File

@@ -96,8 +96,9 @@ export function TimeTicketModalComponent({
]}
>
<JobSearchSelect
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
convertedOnly={true}
notExported={!bodyshop.tt_allow_post_to_invoiced}
notInvoiced={!bodyshop.tt_allow_post_to_invoiced}
/>
</Form.Item>
)}

View File

@@ -0,0 +1,78 @@
import { Alert, Button, Col, Row, Space } from "antd";
import i18n from "i18next";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectUpdateAvailable } from "../../redux/application/application.selectors";
import { AlertOutlined } from "@ant-design/icons";
import { useTranslation } from "react-i18next";
import { setUpdateAvailable } from "../../redux/application/application.actions";
import { store } from "../../redux/store";
import * as serviceWorkerRegistration from "../../serviceWorkerRegistration";
let globalRegistration;
const mapStateToProps = createStructuredSelector({
updateAvailable: selectUpdateAvailable,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(UpdateAlert);
export function UpdateAlert({ updateAvailable }) {
const { t } = useTranslation();
if (!updateAvailable) return null;
return (
<Alert
message={t("general.messages.newversiontitle")}
showIcon
icon={<AlertOutlined />}
description={
<Row gutter={[16, 16]}>
<Col sm={24} md={16} lg={18}>
{t("general.messages.newversionmessage")}
</Col>
<Col sm={24} md={8} lg={6}>
<Space wrap>
<Button
onClick={async () => {
window.open("https://imex-online.noticeable.news/", "_blank");
}}
>
{i18n.t("general.actions.viewreleasenotes")}
</Button>
<Button
type="primary"
onClick={async () => {
if (globalRegistration && globalRegistration.waiting) {
await globalRegistration.unregister();
// Makes Workbox call skipWaiting()
globalRegistration.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>
</Col>
</Row>
}
closable={false}
type="warning"
/>
);
}
const onServiceWorkerUpdate = (registration) => {
console.log("onServiceWorkerUpdate", registration);
globalRegistration = registration;
store.dispatch(setUpdateAvailable(true));
};
serviceWorkerRegistration.register({ onUpdate: onServiceWorkerUpdate });

View File

@@ -57,6 +57,8 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
v_model_yr
v_make_desc
v_model_desc
est_ct_fn
est_ct_ln
labhrs: joblines_aggregate(
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
) {

View File

@@ -1,36 +1,5 @@
import { gql } from "@apollo/client";
// export const CONVERSATION_LIST_SUBSCRIPTION = gql`
// subscription CONVERSATION_LIST_SUBSCRIPTION {
// conversations(
// order_by: { updated_at: desc }
// limit: 50
// where: { archived: { _eq: false } }
// ) {
// phone_num
// id
// updated_at
// unreadcnt
// messages_aggregate(
// where: { read: { _eq: false }, isoutbound: { _eq: false } }
// ) {
// aggregate {
// count
// }
// }
// job_conversations {
// job {
// id
// ro_number
// ownr_fn
// ownr_ln
// ownr_co_nm
// }
// }
// }
// }
// `;
export const UNREAD_CONVERSATION_COUNT = gql`
query UNREAD_CONVERSATION_COUNT {
messages_aggregate(
@@ -44,10 +13,11 @@ export const UNREAD_CONVERSATION_COUNT = gql`
`;
export const CONVERSATION_LIST_QUERY = gql`
query CONVERSATION_LIST_QUERY {
query CONVERSATION_LIST_QUERY($offset: Int!) {
conversations(
order_by: { updated_at: desc }
limit: 50
offset: $offset
where: { archived: { _eq: false } }
) {
phone_num

View File

@@ -25,6 +25,7 @@ import {
import * as Sentry from "@sentry/react";
import "./manage.page.styles.scss";
import UpdateAlert from "../../components/update-alert/update-alert.component";
const ManageRootPage = lazy(() =>
import("../manage-root/manage-root.page.container")
@@ -394,6 +395,7 @@ export function Manage({ match, conflict, bodyshop }) {
<>
<ChatAffixContainer />
<Layout className="layout-container">
<UpdateAlert />
<HeaderContainer />
<Content className="content-container">

View File

@@ -6,7 +6,7 @@ import AlertComponent from "../../components/alert/alert.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
import { setBodyshop } from "../../redux/user/user.actions";
import "../../utils/RegisterSw";
//import "../../utils/RegisterSw";
import ManagePage from "./manage.page.component";
const mapDispatchToProps = (dispatch) => ({

View File

@@ -1,5 +1,7 @@
import { Tabs } from "antd";
import React, { useEffect } from "react";
import { useHistory, useLocation } from "react-router-dom";
import queryString from "query-string";
import { useTranslation } from "react-i18next";
import ShopEmployeesContainer from "../../components/shop-employees/shop-employees.container";
import ShopInfoContainer from "../../components/shop-info/shop-info.container";
@@ -24,6 +26,9 @@ const mapDispatchToProps = (dispatch) => ({
export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
const { t } = useTranslation();
const history = useHistory();
const search = queryString.parse(useLocation().search);
useEffect(() => {
document.title = t("titles.shop");
setSelectedHeader("shop");
@@ -35,9 +40,16 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
]);
}, [t, setSelectedHeader, setBreadcrumbs, bodyshop.shopname]);
useEffect(() => {
if (!search.tab) history.push({ search: "?tab=info" });
}, [history, search]);
return (
<RbacWrapper action="shop:config">
<Tabs>
<Tabs
defaultActiveKey={search.tab}
onChange={(key) => history.push({ search: `?tab=${key}` })}
>
<Tabs.TabPane tab={t("bodyshop.labels.shopinfo")} key="info">
<ShopInfoContainer />
</Tabs.TabPane>

View File

@@ -12,6 +12,7 @@ import TechSider from "../../components/tech-sider/tech-sider.component";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
import "./tech.page.styles.scss";
import UpdateAlert from "../../components/update-alert/update-alert.component";
const TimeTicketModalContainer = lazy(() =>
import("../../components/time-ticket-modal/time-ticket-modal.container")
);
@@ -56,7 +57,9 @@ export function TechPage({ technician, match }) {
<TechSider />
<Layout>
{technician ? null : <Redirect to={`${match.path}/login`} />}
<UpdateAlert />
<TechHeader />
<Content className="tech-content-container">
<ErrorBoundary>
<Suspense

View File

@@ -9,7 +9,7 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
import { useTranslation } from "react-i18next";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { createStructuredSelector } from "reselect";
import "../../utils/RegisterSw";
//import "../../utils/RegisterSw";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,

View File

@@ -62,3 +62,8 @@ export const setProblemJobs = (problemJobs) => ({
type: ApplicationActionTypes.SET_PROBLEM_JOBS,
payload: problemJobs,
});
export const setUpdateAvailable = (isUpdateAvailable) => ({
type: ApplicationActionTypes.SET_UPDATE_AVAILABLE,
payload: isUpdateAvailable,
});

View File

@@ -3,6 +3,7 @@ import ApplicationActionTypes from "./application.types";
const INITIAL_STATE = {
loading: false,
online: true,
updateAvailable: false,
breadcrumbs: [],
recentItems: [],
selectedHeader: "home",
@@ -18,6 +19,11 @@ const INITIAL_STATE = {
const applicationReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ApplicationActionTypes.SET_UPDATE_AVAILABLE:
return {
...state,
updateAvailable: action.payload,
};
case ApplicationActionTypes.SET_SELECTED_HEADER:
return {
...state,

View File

@@ -48,3 +48,7 @@ export const selectProblemJobs = createSelector(
[selectApplication],
(application) => application.problemJobs
);
export const selectUpdateAvailable = createSelector(
[selectApplication],
(application) => application.updateAvailable
);

View File

@@ -12,5 +12,6 @@ const ApplicationActionTypes = {
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE"
};
export default ApplicationActionTypes;

View File

@@ -49,7 +49,7 @@
"blocked": "Blocked",
"cancelledappointment": "Canceled appointment for: ",
"completingjobs": "Completing Jobs",
"dataconsistency": "{{ro_number}} has a data consistency issue. It may have been excluded for scheduling purposes. CODE: {{code}}.",
"dataconsistency": "<0>{{ro_number}}</0> has a data consistency issue. It may have been excluded for scheduling purposes. CODE: {{code}}.",
"expectedjobs": "Expected Jobs in Production: ",
"expectedprodhrs": "Expected Production Hours:",
"history": "History",
@@ -405,7 +405,8 @@
"list-active": "Jobs -> List Active",
"list-all": "Jobs -> List All",
"list-ready": "Jobs -> List Ready",
"partsqueue": "Jobs -> Parts Queue"
"partsqueue": "Jobs -> Parts Queue",
"void": "Jobs -> Void"
},
"owners": {
"detail": "Owners -> Detail",
@@ -1117,7 +1118,7 @@
},
"messages": {
"exception": "$t(titles.app) has encountered an error. Please try again. If the problem persists, please submit a support ticket or contact us.",
"newversionmessage": "Click refresh below to update to the latest available version of ImEX Online. Please make sure all other tabs and windows are closed.",
"newversionmessage": "Click refresh to update to the latest available version of ImEX Online. Please make sure all other tabs and windows are closed.",
"newversiontitle": "New version of ImEX Online Available",
"noacctfilepath": "There is no accounting file path set. You will not be able to export any items.",
"nofeatureaccess": "You do not have access to this feature of ImEX Online. Please contact support to request a license for this feature.",
@@ -2217,10 +2218,13 @@
"new": "New Payment",
"signup": "Please contact support to sign up for electronic payments.",
"title": "Payments",
"totalpayments": "Total Payments"
"totalpayments": "Total Payments",
"markexported": "Mark Exported",
"markforreexport": "Mark for Re-export"
},
"successes": {
"exported": "Payment(s) exported successfully.",
"markreexported": "Payment marked for re-export successfully",
"markexported": "Payment(s) marked exported.",
"payment": "Payment created successfully. ",
"stripe": "Credit card transaction charged successfully."
@@ -2316,7 +2320,7 @@
"folder_label_multiple": "Folder Label - Multi",
"glass_express_checklist": "Glass Express Checklist",
"guarantee": "Repair Guarantee",
"individual_job_note": "Job Note RO # {{ro_number}}",
"individual_job_note": "RO Job Note",
"invoice_customer_payable": "Invoice (Customer Payable)",
"invoice_total_payable": "Invoice (Total Payable)",
"iou_form": "IOU Form",
@@ -2332,7 +2336,8 @@
"mechanical_authorization": "Mechanical Authorization",
"mpi_animal_checklist": "MPI - Animal Checklist",
"mpi_eglass_auth": "MPI - eGlass Auth",
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet",
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet (Direct Repair)",
"mpi_final_repair_acct_sheet": "MPI - Final Accounting Sheet",
"paint_grid": "Paint Grid",
"parts_invoice_label_single": "Parts Label Single",
"parts_label_multiple": "Parts Label - Multi",
@@ -2394,6 +2399,7 @@
},
"subjects": {
"jobs": {
"individual_job_note": "Job Note RO: {{ro_number}}",
"parts_order": "Parts Order PO: {{ro_number}} - {{name}}",
"sublet_order": "Sublet Order PO: {{ro_number}} - {{name}}"
}
@@ -2619,6 +2625,7 @@
"atssummary": "ATS Summary",
"employeevacation": "Employee Vacations",
"ins_co_nm_filter": "Filter by Insurance Company",
"estimators": "Filter by Writer/Customer Rep.",
"intake": "Intake Events",
"manual": "Manual Events",
"manualevent": "Add Manual Event"

View File

@@ -405,7 +405,8 @@
"list-active": "",
"list-all": "",
"list-ready": "",
"partsqueue": ""
"partsqueue": "",
"void": ""
},
"owners": {
"detail": "",
@@ -2333,6 +2334,7 @@
"mpi_animal_checklist": "",
"mpi_eglass_auth": "",
"mpi_final_acct_sheet": "",
"mpi_final_repair_acct_sheet": "",
"paint_grid": "",
"parts_invoice_label_single": "",
"parts_label_multiple": "",
@@ -2394,6 +2396,7 @@
},
"subjects": {
"jobs": {
"individual_job_note": "",
"parts_order": "",
"sublet_order": ""
}

View File

@@ -405,7 +405,8 @@
"list-active": "",
"list-all": "",
"list-ready": "",
"partsqueue": ""
"partsqueue": "",
"void": ""
},
"owners": {
"detail": "",
@@ -2333,6 +2334,7 @@
"mpi_animal_checklist": "",
"mpi_eglass_auth": "",
"mpi_final_acct_sheet": "",
"mpi_final_repair_acct_sheet": "",
"paint_grid": "",
"parts_invoice_label_single": "",
"parts_label_multiple": "",
@@ -2394,6 +2396,7 @@
},
"subjects": {
"jobs": {
"individual_job_note": "",
"parts_order": "",
"sublet_order": ""
}

View File

@@ -3,7 +3,10 @@ import { setContext } from "@apollo/client/link/context";
import { HttpLink } from "@apollo/client/link/http"; //"apollo-link-http";
import { RetryLink } from "@apollo/client/link/retry";
import { WebSocketLink } from "@apollo/client/link/ws";
import { getMainDefinition } from "@apollo/client/utilities";
import {
getMainDefinition,
offsetLimitPagination,
} from "@apollo/client/utilities";
//import { split } from "apollo-link";
import apolloLogger from "apollo-link-logger";
//import axios from "axios";
@@ -140,7 +143,15 @@ middlewares.push(
)
);
const cache = new InMemoryCache({});
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
conversations: offsetLimitPagination(),
},
},
},
});
const client = new ApolloClient({
link: ApolloLink.from(middlewares),

View File

@@ -3,6 +3,7 @@ import { Button, notification, Space } from "antd";
import i18n from "i18next";
import React from "react";
import * as serviceWorkerRegistration from "../serviceWorkerRegistration";
import { store } from "../redux/store";
const onServiceWorkerUpdate = (registration) => {
console.log("onServiceWorkerUpdate", registration);
@@ -33,6 +34,9 @@ const onServiceWorkerUpdate = (registration) => {
</Button>
</Space>
);
store.dispatch()
notification.open({
icon: <AlertOutlined />,
message: i18n.t("general.messages.newversiontitle"),

View File

@@ -421,6 +421,17 @@ export const TemplateList = (type, context) => {
CA_MB: true,
},
},
mpi_final_repair_acct_sheet: {
title: i18n.t("printcenter.jobs.mpi_final_repair_acct_sheet"),
description: "",
key: "mpi_final_repair_acct_sheet",
subject: i18n.t("printcenter.jobs.mpi_final_repair_acct_sheet"),
disabled: false,
group: "post",
regions: {
CA_MB: true,
},
},
mpi_eglass_auth: {
title: i18n.t("printcenter.jobs.mpi_eglass_auth"),
description: "",
@@ -501,6 +512,7 @@ export const TemplateList = (type, context) => {
key: "dms_posting_sheet",
disabled: false,
group: "financial",
dms: true,
},
}
: {}),
@@ -542,7 +554,7 @@ export const TemplateList = (type, context) => {
title: i18n.t("printcenter.jobs.individual_job_note"),
description: "",
key: "individual_job_note",
subject: i18n.t("printcenter.jobs.individual_job_note", {
subject: i18n.t("printcenter.subjects.jobs.individual_job_note", {
ro_number: (context && context.ro_number) || "",
}),
disabled: false,

View File

@@ -11168,6 +11168,11 @@ react-image-lightbox@^5.1.4:
prop-types "^15.7.2"
react-modal "^3.11.1"
react-intersection-observer@^9.4.3:
version "9.4.3"
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.4.3.tgz#ec84ce0c25cad548075130ea045ac5c7adf908f3"
integrity sha512-WNRqMQvKpupr6MzecAQI0Pj0+JQong307knLP4g/nBex7kYfIaZsPpXaIhKHR+oV8z+goUbH9e10j6lGRnTzlQ==
react-is@^16.10.2, react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"

View File

@@ -2690,6 +2690,13 @@
table:
name: inventory
schema: public
- name: parts_dispatch_lines
using:
foreign_key_constraint_on:
column: joblineid
table:
name: parts_dispatch_lines
schema: public
- name: parts_order_lines
using:
foreign_key_constraint_on:
@@ -3131,6 +3138,13 @@
table:
name: notes
schema: public
- name: parts_dispatches
using:
foreign_key_constraint_on:
column: jobid
table:
name: parts_dispatch
schema: public
- name: parts_orders
using:
foreign_key_constraint_on:
@@ -4556,6 +4570,165 @@
template_engine: Kriti
url: '{{$base_url}}/opensearch'
version: 2
- table:
name: parts_dispatch
schema: public
object_relationships:
- name: job
using:
foreign_key_constraint_on: jobid
array_relationships:
- name: parts_dispatch_lines
using:
foreign_key_constraint_on:
column: partsdispatchid
table:
name: parts_dispatch_lines
schema: public
insert_permissions:
- role: user
permission:
check:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- id
- created_at
- updated_at
- jobid
- number
- employeeid
- dispatched_at
- dispatched_by
select_permissions:
- role: user
permission:
columns:
- number
- dispatched_by
- created_at
- dispatched_at
- updated_at
- employeeid
- id
- jobid
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
update_permissions:
- role: user
permission:
columns:
- number
- dispatched_by
- created_at
- dispatched_at
- updated_at
- employeeid
- id
- jobid
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
check: null
- table:
name: parts_dispatch_lines
schema: public
object_relationships:
- name: jobline
using:
foreign_key_constraint_on: joblineid
- name: parts_dispatch
using:
foreign_key_constraint_on: partsdispatchid
insert_permissions:
- role: user
permission:
check:
parts_dispatch:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- id
- created_at
- updated_at
- partsdispatchid
- joblineid
- quantity
- accepted_at
select_permissions:
- role: user
permission:
columns:
- quantity
- accepted_at
- created_at
- updated_at
- id
- joblineid
- partsdispatchid
filter:
parts_dispatch:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
update_permissions:
- role: user
permission:
columns:
- id
- created_at
- updated_at
- partsdispatchid
- joblineid
- quantity
- accepted_at
filter:
parts_dispatch:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
check: null
- table:
name: parts_order_lines
schema: public

View File

@@ -0,0 +1 @@
DROP TABLE "public"."parts_dispatch";

View File

@@ -0,0 +1,18 @@
CREATE TABLE "public"."parts_dispatch" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "jobid" uuid NOT NULL, "number" serial NOT NULL, "employeeid" uuid NOT NULL, "dispatched_at" timestamptz NOT NULL, "dispatched_by" text NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("jobid") REFERENCES "public"."jobs"("id") ON UPDATE cascade ON DELETE cascade);
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
RETURNS TRIGGER AS $$
DECLARE
_new record;
BEGIN
_new := NEW;
_new."updated_at" = NOW();
RETURN _new;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER "set_public_parts_dispatch_updated_at"
BEFORE UPDATE ON "public"."parts_dispatch"
FOR EACH ROW
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
COMMENT ON TRIGGER "set_public_parts_dispatch_updated_at" ON "public"."parts_dispatch"
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
CREATE EXTENSION IF NOT EXISTS pgcrypto;

View File

@@ -0,0 +1 @@
DROP TABLE "public"."parts_dispatch_lines";

View File

@@ -0,0 +1,18 @@
CREATE TABLE "public"."parts_dispatch_lines" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "partsdispatchid" UUID NOT NULL, "joblineid" uuid NOT NULL, "quantity" numeric NOT NULL DEFAULT 1, "accepted_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("joblineid") REFERENCES "public"."joblines"("id") ON UPDATE cascade ON DELETE cascade);
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
RETURNS TRIGGER AS $$
DECLARE
_new record;
BEGIN
_new := NEW;
_new."updated_at" = NOW();
RETURN _new;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER "set_public_parts_dispatch_lines_updated_at"
BEFORE UPDATE ON "public"."parts_dispatch_lines"
FOR EACH ROW
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
COMMENT ON TRIGGER "set_public_parts_dispatch_lines_updated_at" ON "public"."parts_dispatch_lines"
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
CREATE EXTENSION IF NOT EXISTS pgcrypto;

View File

@@ -0,0 +1 @@
alter table "public"."parts_dispatch_lines" drop constraint "parts_dispatch_lines_partsdispatchid_fkey";

View File

@@ -0,0 +1,5 @@
alter table "public"."parts_dispatch_lines"
add constraint "parts_dispatch_lines_partsdispatchid_fkey"
foreign key ("partsdispatchid")
references "public"."parts_dispatch"
("id") on update cascade on delete cascade;

View File

@@ -0,0 +1 @@
alter table "public"."parts_dispatch_lines" alter column "accepted_at" set not null;

View File

@@ -0,0 +1 @@
alter table "public"."parts_dispatch_lines" alter column "accepted_at" drop not null;

View File

@@ -0,0 +1,5 @@
alter table "public"."job_conversations" drop constraint "job_conversations_conversationid_fkey",
add constraint "job_conversations_conversationid_fkey"
foreign key ("conversationid")
references "public"."conversations"
("id") on update restrict on delete restrict;

View File

@@ -0,0 +1,5 @@
alter table "public"."job_conversations" drop constraint "job_conversations_conversationid_fkey",
add constraint "job_conversations_conversationid_fkey"
foreign key ("conversationid")
references "public"."conversations"
("id") on update cascade on delete cascade;

View File

@@ -224,7 +224,7 @@ async function CdkSelectedCustomer(socket, selectedCustomerId) {
} finally {
//Ensure we always insert logEvents
//GQL to insert logevents.
CdkBase.createLogEvent(
socket,
"DEBUG",
@@ -432,7 +432,7 @@ async function QueryDmsCustomerById(socket, JobData, CustomerId) {
async function QueryDmsCustomerByName(socket, JobData) {
const ownerName = (
JobData.ownr_co_nm && JobData.ownr_co_nm !== ""
JobData.ownr_co_nm && JobData.ownr_co_nm.trim() !== ""
? JobData.ownr_co_nm
: `${JobData.ownr_ln},${JobData.ownr_fn}`
).replace(replaceSpecialRegex, "");
@@ -642,7 +642,8 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
.toUpperCase(),
middleName: null,
nameType:
socket.JobData.ownr_co_nm && socket.JobData.ownr_co_nm
socket.JobData.ownr_co_nm &&
String(socket.JobData.ownr_co_nm).trim() !== ""
? "Business"
: "Person",
suffix: null,
@@ -728,7 +729,15 @@ async function InsertDmsVehicle(socket) {
deliveryDate: moment()
// .tz(socket.JobData.bodyshop.timezone)
.format("YYYYMMDD"),
licensePlateNo: socket.JobData.plate_no,
licensePlateNo:
socket.JobData.plate_no === null
? null
: String(socket.JobData.plate_no).replace(/([^\w]|_)/g, "")
.length === 0
? null
: String(socket.JobData.plate_no)
.replace(/([^\w]|_)/g, "")
.toUpperCase(),
make: socket.txEnvelope.dms_make,
modelAbrev: socket.txEnvelope.dms_model,
modelYear: socket.JobData.v_model_yr,

View File

@@ -86,6 +86,7 @@ async function OpenSearchUpdateHandler(req, res) {
"v_model_yr",
"v_make_desc",
"v_model_desc",
"v_vin",
]);
document.bodyshopid = req.body.event.data.new.shopid;
break;
@@ -139,7 +140,7 @@ async function OpenSearchUpdateHandler(req, res) {
"exported_at",
"invoice_number",
"is_credit_memo",
"total"
"total",
]),
...bill.bills_by_pk,
bodyshopid: bill.bills_by_pk.job.shopid,
@@ -244,17 +245,54 @@ async function OpensearchSearchHandler(req, res) {
bool: {
must: [
{
multi_match: {
query: search,
type: "phrase_prefix",
//fields: ["*"],
// fuzziness: "5",
//prefix_length: 2,
match: {
bodyshopid: assocs.associations[0].shopid,
},
},
{
match: {
bodyshopid: assocs.associations[0].shopid,
bool: {
should: [
{
multi_match: {
query: search,
type: "cross_fields",
fields: ["*ownr_fn", "*ownr_ln"],
},
},
{
multi_match: {
query: search,
type: "most_fields",
fields: [
"*v_model_yr",
"*v_make_desc^2",
"*v_model_desc^3",
],
},
},
{
query_string: {
query: `*${search}*`,
fields: [
"*ro_number^20",
"*clm_no^14",
"*v_vin^12",
"*plate_no^12",
"*ownr_ln^10",
"transactionid^10",
"paymentnum^10",
"invoice_number^10",
"*ownr_fn^8",
"*ownr_co_nm^8",
"*ownr_ph1^8",
"*ownr_ph2^8",
"*",
],
},
},
],
minimum_should_match: 1,
},
},
],