Compare commits

...

56 Commits

Author SHA1 Message Date
Allan Carr
bdb2951330 IO-2500 Courtesy Car Readiness 2023-12-27 13:55:31 -08:00
Allan Carr
e12edd977e Merged in release/2023-12-15 (pull request #1120)
Release/2023 12 15

Approved-by: Dave Richer
2023-12-22 18:16:40 +00:00
Allan Carr
b925c991eb Merged in feature/IO-2501-Add-Jobs-Completed-Delivered-Not-Invoiced-Section (pull request #1118)
IO-2501 Correct for missing query variables

Approved-by: Dave Richer
2023-12-22 02:33:30 +00:00
Allan Carr
1e7f43fe3d IO-2501 Correct for missing query variables 2023-12-21 18:32:37 -08:00
Allan Carr
6f21de1901 Merged in feature/IO-2505-Conversation-List-Print (pull request #1116)
IO-2505 Conversation List Print

Approved-by: Dave Richer
2023-12-21 17:37:47 +00:00
Allan Carr
25e8eaa1d4 IO-2505 Conversation List Print 2023-12-21 09:24:25 -08:00
Allan Carr
6fe736ce06 Merged in feature/IO-1366-Audit-Logging (pull request #1114)
IO-1366 Invoice Job Audit Trail

Approved-by: Dave Richer
2023-12-20 20:44:11 +00:00
Allan Carr
482b03c2d1 IO-1366 Invoice Job Audit Trail 2023-12-20 11:52:04 -08:00
Allan Carr
ce3c72fc47 Merged in feature/IO-1366-Audit-Logging (pull request #1113)
IO-1366 Audit Logging for Production Alert

Approved-by: Dave Richer
2023-12-20 19:38:46 +00:00
Allan Carr
1ff5ed4141 IO-1366 Audit Logging for Production Alert 2023-12-20 11:36:42 -08:00
Allan Carr
10e3421572 Merged in feature/IO-2506-Federal-Tax-Exempt-on-Bill-Entry (pull request #1111)
IO-2506 Correct for variable immutibility and nested ifs
2023-12-19 17:16:48 +00:00
Allan Carr
0117237988 IO-2506 Correct for variable immutibility and nested ifs 2023-12-19 09:18:00 -08:00
Allan Carr
373fd817d0 Merged in feature/IO-2506-Federal-Tax-Exempt-on-Bill-Entry (pull request #1108)
IO-2506 Federal Tax Exempt on Bill Entry

Approved-by: Dave Richer
2023-12-19 16:34:38 +00:00
Allan Carr
0ef2d9646d Merged in feature/IO-2509-Report-Center-RBAC (pull request #1109)
IO-2509 Report Center RBAC

Approved-by: Dave Richer
2023-12-19 16:32:29 +00:00
Allan Carr
c8ac417200 IO-2509 Report Center RBAC 2023-12-18 14:12:21 -08:00
Allan Carr
661bedbe5b IO-2506 Federal Tax Exempt on Bill Entry
Will Toggle Federal Tax off on any new line or retroactively toggle it off on all lines when switch is enabled. Limited to PBS or CDK setups.
2023-12-18 12:36:46 -08:00
Allan Carr
2dd56590d3 Admin panel to force email addresses to be lowercase to conform with firebase 2023-12-14 08:57:36 -08:00
Allan Carr
98b760251c Merged in feature/IO-2501-Add-Jobs-Completed-Delivered-Not-Invoiced-Section (pull request #1107)
IO-2501 Add Jobs Complete Not Invoiced Section to Stats

Approved-by: Dave Richer
2023-12-13 18:47:04 +00:00
Allan Carr
b97de32a44 IO-2501 Add Jobs Complete Not Invoiced Section to Stats 2023-12-12 15:41:36 -08:00
Dave Richer
92c8b54f85 Merged in release/2023-12-01 (pull request #1103)
Reversion

Approved-by: Allan Carr
2023-12-04 16:50:13 +00:00
Dave Richer
d8420f472c Merged in feature/reversion-to-active-jobs-pagination (pull request #1102)
Reversion
2023-12-04 16:40:50 +00:00
Dave Richer
34d93c4de0 Reversion 2023-12-04 11:39:20 -05:00
Dave Richer
1c400cd456 Merged in release/2023-12-01 (pull request #1099)
Release/2023 12 01

Approved-by: Allan Carr
2023-12-01 18:18:23 +00:00
Allan Carr
a3cf97fcab Merged in feature/IO-2485-Fix-onlyFuture-Prop (pull request #1095)
IO-2485 Correct onlyFuture on typed values

Approved-by: Dave Richer
2023-11-30 16:56:57 +00:00
Allan Carr
1a9dc7a377 Merged in feature/IO-2484-Next-Service-KMs (pull request #1094)
IO-2484 Next Service KMs

Approved-by: Dave Richer
2023-11-30 16:55:55 +00:00
Allan Carr
806daebd3f IO-2485 Correct onlyFuture on typed values 2023-11-29 19:51:03 -08:00
Allan Carr
dfd8845864 IO-2484 Next Service KMs
Allow null and only display warning if not null and current milage is greater than service KMs
2023-11-29 17:32:09 -08:00
Allan Carr
170108b339 Merged in feature/IO-2465-Add-Vehicle-to-Override-Headers (pull request #1092)
IO-2465 Adjust Headerfile override and comment out line
2023-11-30 01:15:53 +00:00
Allan Carr
9f1f58a9c7 IO-2465 Adjust Headerfile override and comment out line 2023-11-29 17:14:39 -08:00
Dave Richer
4288e2d986 Merged in feature/IO-2403-Paginated-Active-Jobs (pull request #1088)
Fix issues with limits.
2023-11-29 22:27:49 +00:00
Dave Richer
4b289388bf Fix issues with limits. 2023-11-29 17:27:08 -05:00
Dave Richer
ed0136090c Merged in feature/IO-2403-Paginated-Active-Jobs (pull request #1086)
Fix order issue on all jobs
2023-11-29 21:10:56 +00:00
Dave Richer
0d70545b98 Fix order issue on all jobs 2023-11-29 16:10:30 -05:00
Dave Richer
2252091b53 Fix order issue on all jobs 2023-11-29 16:09:20 -05:00
Dave Richer
386531884b Merged in feature/IO-2403-Paginated-Active-Jobs (pull request #1068)
Paginated Active Jobs / Pagination refactor
2023-11-29 18:43:00 +00:00
Dave Richer
afbe328aa7 Merged release/2023-12-01 into feature/IO-2403-Paginated-Active-Jobs 2023-11-29 18:40:47 +00:00
Allan Carr
b7c0fba48b Merged in feature/IO-2483-Translation-Update (pull request #1084)
IO-2483 Update Transations

Approved-by: Dave Richer
2023-11-29 18:40:01 +00:00
Allan Carr
68635b1629 Merged in feature/IO-2465-Add-Vehicle-to-Override-Headers (pull request #1083)
IO-2465 Restrict Update of Vehicle on Supplement to Override Header

Approved-by: Dave Richer
2023-11-29 18:39:42 +00:00
Allan Carr
3bd0058dc8 Merged in feature/IO-2481-Parts-Queue-Query (pull request #1082)
Feature/IO-2481 Parts Queue Query

Approved-by: Dave Richer
2023-11-29 18:39:24 +00:00
Allan Carr
270a512585 Merged in feature/IO-2480-Unqueue-Parts-Queue-Label (pull request #1081)
IO-2480 Unqueue Parts Label instead of Remove

Approved-by: Dave Richer
2023-11-29 18:38:49 +00:00
Dave Richer
ec2519eae4 Move PageSize (PageLimit) to an external configuration file. 2023-11-29 13:37:02 -05:00
Dave Richer
d7f52d864a Add Global search to Active Jobs. 2023-11-28 16:05:23 -05:00
Allan Carr
5cb2b3940e IO-2483 Update Transations 2023-11-28 10:08:33 -08:00
Dave Richer
b5efaa944d Merge branch 'master' into feature/IO-2403-Paginated-Active-Jobs 2023-11-28 12:55:00 -05:00
Allan Carr
bbcfc420d2 IO-2465 Restrict Update of Vehicle on Supplement to Override Header 2023-11-27 14:34:36 -08:00
Allan Carr
8ebf7baa71 IO-2481 Parts Queue Query
prettyier
2023-11-27 10:16:10 -08:00
Allan Carr
742d2b5ff2 IO-2481 Parts Queue Query
Adjust query to only show converted Jobs
2023-11-27 10:13:43 -08:00
Allan Carr
f96fefbfdc IO-2480 Unqueue Parts Label instead of Remove
Remove is the uncorrect work as it doesn't actually remove just unqueue
2023-11-27 10:10:02 -08:00
Allan Carr
6570d38719 Merged in release/2023-11-24 (pull request #1080)
Release/2023 11 24
2023-11-24 21:12:02 +00:00
Dave Richer
547b58f05a Merged in release/2023-11-24 (pull request #1079)
Update translations, move configuration toggle for autopartsqueue

Approved-by: Allan Carr
2023-11-24 17:52:32 +00:00
Dave Richer
f299c685e2 Merged in feature/IO-2304-Auto-Parts-Toggle (pull request #1078)
Update translations, move configuration toggle for autopartsqueue
2023-11-24 17:46:30 +00:00
Dave Richer
59075ee610 Update translations, move configuration toggle for autopartsqueue 2023-11-24 12:43:55 -05:00
Dave Richer
432aa9f1e1 Progress 2023-11-24 12:34:17 -05:00
Dave Richer
c5bed4f36d Progress 2023-11-24 12:32:59 -05:00
Dave Richer
65f960db00 Add Margin around the Messaging Icon 2023-11-23 16:58:56 -05:00
Dave Richer
8670f386dc refactors 2023-11-22 17:14:52 -05:00
71 changed files with 1229 additions and 840 deletions

View File

@@ -15,6 +15,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -210,7 +211,7 @@ export function AccountingPayablesTableComponent({
<Table
loading={loading}
dataSource={dataSource}
pagination={{ position: "top", pageSize: 50 }}
pagination={{ position: "top", pageSize: pageLimit }}
columns={columns}
rowKey="id"
onChange={handleTableChange}

View File

@@ -15,6 +15,7 @@ import PaymentExportButton from "../payment-export-button/payment-export-button.
import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component";
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -209,7 +210,7 @@ export function AccountingPayablesTableComponent({
<Table
loading={loading}
dataSource={dataSource}
pagination={{ position: "top", pageSize: 50 }}
pagination={{ position: "top", pageSize: pageLimit }}
columns={columns}
rowKey="id"
onChange={handleTableChange}

View File

@@ -4,6 +4,7 @@ import { alphaSort } from "../../utils/sorters";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { useTranslation } from "react-i18next";
import AuditTrailValuesComponent from "../audit-trail-values/audit-trail-values.component";
import {pageLimit} from "../../utils/config";
export default function AuditTrailListComponent({ loading, data }) {
const [state, setState] = useState({
@@ -74,7 +75,7 @@ export default function AuditTrailListComponent({ loading, data }) {
<Table
{...formItemLayout}
loading={loading}
pagination={{ position: "top", defaultPageSize: 25 }}
pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns}
rowKey="id"
dataSource={data}

View File

@@ -3,6 +3,7 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import {pageLimit} from "../../utils/config";
export default function EmailAuditTrailListComponent({ loading, data }) {
const [state, setState] = useState({
@@ -53,7 +54,7 @@ export default function EmailAuditTrailListComponent({ loading, data }) {
<Table
{...formItemLayout}
loading={loading}
pagination={{ position: "top", defaultPageSize: 25 }}
pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns}
rowKey="id"
dataSource={data}

View File

@@ -79,6 +79,20 @@ export function BillFormComponent({
});
};
const handleFederalTaxExemptSwitchToggle = (checked) => {
// Early gate
if (!checked) return;
const values = form.getFieldsValue("billlines");
// Gate bill lines
if (!values?.billlines?.length) return;
const billlines = values.billlines.map((b) => {
b.applicable_taxes.federal = false;
return b;
});
form.setFieldsValue({ billlines });
};
useEffect(() => {
if (job) form.validateFields(["is_credit_memo"]);
}, [job, form]);
@@ -387,7 +401,16 @@ export function BillFormComponent({
>
<CurrencyInput min={0} />
</Form.Item>
<Form.Item shouldUpdate span={15}>
{bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
<Form.Item
span={2}
label={t("bills.labels.federal_tax_exempt")}
name="federal_tax_exempt"
>
<Switch onChange={handleFederalTaxExemptSwitchToggle} />
</Form.Item>
) : null}
<Form.Item shouldUpdate span={13}>
{() => {
const values = form.getFieldsValue([
"billlines",
@@ -405,7 +428,7 @@ export function BillFormComponent({
totals = CalculateBillTotal(values);
if (!!totals)
return (
<div>
<div align="right">
<Space wrap>
<Statistic
title={t("bills.labels.subtotal")}

View File

@@ -1,14 +1,15 @@
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Button, Form,
Button,
Form,
Input,
InputNumber,
Select,
Space,
Switch,
Table,
Tooltip
Tooltip,
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -466,7 +467,8 @@ export function BillEnterModalLinesComponent({
return {
key: `${field.index}fedtax`,
valuePropName: "checked",
initialValue: true,
initialValue:
form.getFieldValue("federal_tax_exempt") === true ? false : true,
name: [field.name, "applicable_taxes", "federal"],
};
},

View File

@@ -4,6 +4,7 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component";
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component";
import ChatLabelComponent from "../chat-label/chat-label.component";
import ChatPrintButton from "../chat-print-button/chat-print-button.component";
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
export default function ChatConversationTitle({ conversation }) {
@@ -13,6 +14,7 @@ export default function ChatConversationTitle({ conversation }) {
{conversation && conversation.phone_num}
</PhoneNumberFormatter>
<ChatLabelComponent conversation={conversation} />
<ChatPrintButton conversation={conversation} />
<ChatConversationTitleTags
jobConversations={
(conversation && conversation.job_conversations) || []

View File

@@ -132,7 +132,7 @@ export function ChatPopupComponent({
onClick={() => toggleChatVisible()}
style={{ cursor: "pointer" }}
>
<MessageOutlined />
<MessageOutlined className="chat-popup-info-icon" />
<strong>{t("messaging.labels.messaging")}</strong>
</div>
)}

View File

@@ -13,6 +13,9 @@
height: 100%;
}
}
.chat-popup-info-icon {
margin-right: 5px;
}
@media only screen and (min-width: 992px) {
.chat-popup {

View File

@@ -0,0 +1,59 @@
import { MailOutlined, PrinterOutlined } from "@ant-design/icons";
import { Space, Spin } from "antd";
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setEmailOptions } from "../../redux/email/email.actions";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
});
export function ChatPrintButton({ conversation }) {
const [loading, setLoading] = useState(false);
return (
<Space wrap>
<PrinterOutlined
onClick={() => {
setLoading(true);
GenerateDocument(
{
name: TemplateList("messaging").conversation_list.key,
variables: { id: conversation.id },
},
{
subject: TemplateList("messaging").conversation_list.subject,
},
"p",
conversation.id
);
setLoading(false);
}}
/>
<MailOutlined
onClick={() => {
setLoading(true);
GenerateDocument(
{
name: TemplateList("messaging").conversation_list.key,
variables: { id: conversation.id },
},
{
subject: TemplateList("messaging").conversation_list.subject,
},
"e",
conversation.id
);
setLoading(false);
}}
/>
{loading && <Spin />}
</Space>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatPrintButton);

View File

@@ -4,6 +4,7 @@ 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";
import {pageLimit} from "../../utils/config";
export default function ContractsJobsComponent({
loading,
@@ -175,7 +176,7 @@ export default function ContractsJobsComponent({
loading={loading}
pagination={{
position: "top",
defaultPageSize: 10,
defaultPageSize: pageLimit,
defaultCurrent: defaultCurrent,
}}
columns={columns}

View File

@@ -13,6 +13,7 @@ import moment from "moment";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -209,7 +210,7 @@ export function ContractsList({
}}
pagination={{
position: "top",
pageSize: 25,
pageSize: pageLimit,
current: parseInt(page || 1),
total: total,
}}

View File

@@ -5,6 +5,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 {pageLimit} from "../../utils/config";
export default function CourtesyCarContractListComponent({
contracts,
@@ -89,7 +90,7 @@ export default function CourtesyCarContractListComponent({
scroll={{ x: true }}
pagination={{
position: "top",
pageSize: 25,
pageSize: pageLimit,
current: parseInt(page || 1),
total: totalContracts,
}}

View File

@@ -228,8 +228,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
{() => {
const nextservicekm = form.getFieldValue("nextservicekm");
const mileageOver =
nextservicekm <= form.getFieldValue("mileage");
nextservicekm && nextservicekm <= form.getFieldValue("mileage");
if (mileageOver)
return (
<Space direction="vertical" style={{ color: "tomato" }}>

View File

@@ -34,6 +34,32 @@ const CourtesyCarFuelComponent = (props, ref) => {
step={null}
style={{ marginLeft: "2rem", marginRight: "2rem" }}
{...props}
tooltip={{
formatter: (value) => {
switch (value) {
case 0:
return t("courtesycars.labels.fuel.empty");
case 13:
return t("courtesycars.labels.fuel.18");
case 25:
return t("courtesycars.labels.fuel.14");
case 38:
return t("courtesycars.labels.fuel.38");
case 50:
return t("courtesycars.labels.fuel.12");
case 63:
return t("courtesycars.labels.fuel.58");
case 75:
return t("courtesycars.labels.fuel.34");
case 88:
return t("courtesycars.labels.fuel.78");
case 100:
return t("courtesycars.labels.fuel.full");
default:
return value;
}
},
}}
/>
);
};

View File

@@ -7,6 +7,7 @@ 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";
import {pageLimit} from "../../utils/config";
export default function CsiResponseListPaginated({
refetch,
@@ -106,7 +107,7 @@ export default function CsiResponseListPaginated({
loading={loading}
pagination={{
position: "top",
pageSize: 25,
pageSize: pageLimit,
current: parseInt(page || 1),
total: total,
}}

View File

@@ -6,6 +6,7 @@ import { alphaSort } from "../../../utils/sorters";
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
import Dinero from "dinero.js";
import DashboardRefreshRequired from "../refresh-required.component";
import {pageLimit} from "../../../utils/config";
export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
const { t } = useTranslation();
@@ -118,7 +119,7 @@ export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
<div style={{ height: "100%" }}>
<Table
onChange={handleTableChange}
pagination={{ position: "top", defaultPageSize: 50 }}
pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns}
scroll={{ x: true, y: "calc(100% - 4em)" }}
rowKey="id"

View File

@@ -11,6 +11,7 @@ import { Link } from "react-router-dom";
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
import DashboardRefreshRequired from "../refresh-required.component";
import {pageLimit} from "../../../utils/config";
export default function DashboardScheduledInToday({ data, ...cardProps }) {
const { t } = useTranslation();
@@ -195,7 +196,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
<div style={{ height: "100%" }}>
<Table
onChange={handleTableChange}
pagination={{ position: "top", defaultPageSize: 50 }}
pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns}
scroll={{ x: true, y: "calc(100% - 2em)" }}
rowKey="id"

View File

@@ -11,6 +11,7 @@ import { Link } from "react-router-dom";
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
import DashboardRefreshRequired from "../refresh-required.component";
import {pageLimit} from "../../../utils/config";
export default function DashboardScheduledOutToday({ data, ...cardProps }) {
const { t } = useTranslation();
@@ -165,7 +166,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
<div style={{ height: "100%" }}>
<Table
onChange={handleTableChange}
pagination={{ position: "top", defaultPageSize: 50 }}
pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns}
scroll={{ x: true, y: "calc(100% - 2em)" }}
rowKey="id"

View File

@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -117,7 +118,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
}
>
<Table
pagination={{ position: "top", defaultPageSize: 50 }}
pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns}
rowKey={(record) => `${record.InvoiceNumber}${record.Account}`}
dataSource={allocationsSummary}

View File

@@ -6,6 +6,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import Dinero from "dinero.js";
import { SyncOutlined } from "@ant-design/icons";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -94,7 +95,7 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
<Alert type="warning" message={t("jobs.labels.dms.disablebillwip")} />
)}
<Table
pagination={{ position: "top", defaultPageSize: 50 }}
pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns}
rowKey="center"
dataSource={allocationsSummary}

View File

@@ -65,8 +65,17 @@ export function FormDatePicker({
});
}
if (_a.isValid() && onChange)
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
if (_a.isValid() && onChange) {
if (onlyFuture) {
if (moment().subtract(1, "day").isBefore(_a)) {
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
} else {
onChange(isDateOnly ? moment().format("YYYY-MM-DD") : moment());
}
} else {
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
}
}
};
return (

View File

@@ -1,9 +1,9 @@
import React, { forwardRef } from "react";
//import DatePicker from "react-datepicker";
//import "react-datepicker/src/stylesheets/datepicker.scss";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import { TimePicker } from "antd";
import moment from "moment";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
//To be used as a form element only.
const DateTimePicker = (
@@ -26,20 +26,21 @@ const DateTimePicker = (
value={value}
onBlur={onBlur}
onChange={onChange}
onlyFuture={onlyFuture}
isDateOnly={false}
/>
<TimePicker
value={value ? moment(value) : null}
{...(onlyFuture && {
disabledDate: (d) => moment().isAfter(d),
})}
onChange={onChange}
showSecond={false}
minuteStep={15}
onBlur={onBlur}
format="hh:mm a"
{...restProps}
value={value ? moment(value) : null}
{...(onlyFuture && {
disabledDate: (d) => moment().isAfter(d),
})}
onChange={onChange}
showSecond={false}
minuteStep={15}
onBlur={onBlur}
format="hh:mm a"
{...restProps}
/>
</div>
);

View File

@@ -11,6 +11,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import InventoryBillRo from "../inventory-bill-ro/inventory-bill-ro.component";
import InventoryLineDelete from "../inventory-line-delete/inventory-line-delete.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
@@ -213,7 +214,7 @@ export function JobsList({
loading={loading}
pagination={{
position: "top",
pageSize: 25,
pageSize: pageLimit,
current: parseInt(page || 1),
total: total,
}}

View File

@@ -11,6 +11,7 @@ import {
} from "../../redux/application/application.actions";
import AlertComponent from "../alert/alert.component";
import InventoryListPaginated from "./inventory-list.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
//bodyshop: selectBodyshop,
@@ -32,8 +33,8 @@ export function InventoryList({ setBreadcrumbs, setSelectedHeader }) {
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * 25 : 0,
limit: 25,
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
consumedIsNull: showall === "true" ? null : true,
order: [
{

View File

@@ -3,6 +3,7 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { alphaSort } from "../../utils/sorters";
import Dinero from "dinero.js";
import {pageLimit} from "../../utils/config";
export default function JobCostingPartsTable({ data, summaryData }) {
const [searchText, setSearchText] = useState("");
const [state, setState] = useState({
@@ -98,7 +99,7 @@ export default function JobCostingPartsTable({ data, summaryData }) {
x: "50%", //y: "40rem"
}}
onChange={handleTableChange}
pagination={{ position: "top", defaultPageSize: 50 }}
pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns}
rowKey="id"
dataSource={filteredData}

View File

@@ -1,6 +1,6 @@
import { GET_ALL_JOBLINES_BY_PK } from "../../graphql/jobs-lines.queries";
import { gql } from "@apollo/client";
import _ from "lodash";
import { GET_ALL_JOBLINES_BY_PK } from "../../graphql/jobs-lines.queries";
export const GetSupplementDelta = async (client, jobId, newLines) => {
const {
@@ -50,7 +50,7 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
.reduce((acc, value, idx) => {
return acc + generateRemoveQuery(value, idx);
}, "");
console.log(insertQueries, updateQueries, removeQueries);
//console.log(insertQueries, updateQueries, removeQueries);
if ((insertQueries + updateQueries + removeQueries).trim() === "") {
return new Promise((resolve, reject) => {

View File

@@ -114,7 +114,7 @@ const headerFields = [
"ins_ct_ph",
"ins_ct_phx",
"loss_cat",
//ad2
//AD2
"clmt_ln",
"clmt_fn",
"clmt_title",
@@ -219,7 +219,16 @@ const headerFields = [
"loc_title",
"loc_ph",
"loc_phx",
"loc_ea"
"loc_ea",
//VEH
"plate_no",
"plate_st",
"v_vin",
"v_model_yr",
"v_make_desc",
"v_model_desc",
"v_options",
"v_color",
];
export default headerFields;

View File

@@ -108,6 +108,14 @@ export function JobsDetailHeaderActions({
},
},
});
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.alertToggle(
!!job.production_vars && !!job.production_vars.alert
? !job.production_vars.alert
: true
),
});
};
const handleSuspend = (e) => {

View File

@@ -12,6 +12,7 @@ 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";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
@@ -33,17 +34,14 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder: sortcolumn === "ro_number" && sortorder,
render: (text, record) => (
<Link to={"/manage/jobs/" + record.id}>
{record.ro_number || t("general.labels.na")}
</Link>
),
},
{
title: t("jobs.fields.owner"),
dataIndex: "ownr_ln",
@@ -125,7 +123,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no",
key: "plate_no",
ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder: sortcolumn === "plate_no" && sortorder,
@@ -137,7 +134,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder: sortcolumn === "clm_no" && sortorder,
@@ -259,11 +255,11 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
pagination={
search?.search
? {
pageSize: 25,
pageSize: pageLimit,
showSizeChanger: false,
}
: {
pageSize: 25,
pageSize: pageLimit,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,

View File

@@ -1,8 +1,8 @@
import {
SyncOutlined,
ExclamationCircleFilled,
PauseCircleOutlined,
BranchesOutlined,
SyncOutlined,
ExclamationCircleFilled,
PauseCircleOutlined,
BranchesOutlined,
} from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
@@ -22,374 +22,374 @@ 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,
bodyshop: selectBodyshop,
});
export function JobsList({ bodyshop }) {
const searchParams = queryString.parse(useLocation().search);
const { selected } = searchParams;
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: {
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const searchParams = queryString.parse(useLocation().search);
const { selected } = searchParams;
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: {
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
});
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
});
const { t } = useTranslation();
const history = useHistory();
const [searchText, setSearchText] = useState("");
const { t } = useTranslation();
const history = useHistory();
const [searchText, setSearchText] = useState("");
if (error) return <AlertComponent message={error.message} type="error" />;
if (error) return <AlertComponent message={error.message} type="error" />;
const jobs = data
? searchText === ""
? data.jobs
: data.jobs.filter(
(j) =>
(j.ro_number || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.comments || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.plate_no || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.est_ct_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.est_ct_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
)
: [];
const jobs = data
? searchText === ""
? data.jobs
: data.jobs.filter(
(j) =>
(j.ro_number || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.comments || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.plate_no || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.est_ct_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.est_ct_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
)
: [];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const handleOnRowClick = (record) => {
if (record) {
if (record.id) {
history.push({
search: queryString.stringify({
...searchParams,
selected: record.id,
}),
});
}
}
};
const handleOnRowClick = (record) => {
if (record) {
if (record.id) {
history.push({
search: queryString.stringify({
...searchParams,
selected: record.id,
}),
});
}
}
};
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "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,
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "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,
render: (text, record) => (
<Link
to={"/manage/jobs/" + record.id}
onClick={(e) => e.stopPropagation()}
>
<Space>
{record.ro_number || t("general.labels.na")}
{record.production_vars && record.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" />
) : null}
{record.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{record.iouparent && (
<Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} />
</Tooltip>
)}
</Space>
</Link>
),
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner",
key: "owner",
ellipsis: true,
render: (text, record) => (
<Link
to={"/manage/jobs/" + record.id}
onClick={(e) => e.stopPropagation()}
>
<Space>
{record.ro_number || t("general.labels.na")}
{record.production_vars && record.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" />
) : null}
{record.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{record.iouparent && (
<Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} />
</Tooltip>
)}
</Space>
</Link>
),
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner",
key: "owner",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => {
return record.ownerid ? (
<Link
to={"/manage/owners/" + record.ownerid}
onClick={(e) => e.stopPropagation()}
>
<OwnerNameDisplay ownerObject={record} />
</Link>
) : (
<span>
responsive: ["md"],
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => {
return record.ownerid ? (
<Link
to={"/manage/owners/" + record.ownerid}
onClick={(e) => e.stopPropagation()}
>
<OwnerNameDisplay ownerObject={record} />
</Link>
) : (
<span>
<OwnerNameDisplay ownerObject={record} />
</span>
);
},
},
{
title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
),
},
{
title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
ellipsis: true,
sorter: (a, b) => alphaSort(a.status, b.status),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
filters:
(jobs &&
jobs
.map((j) => j.status)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Status*",
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.status),
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => {
return record.vehicleid ? (
<Link
to={"/manage/vehicles/" + record.vehicleid}
onClick={(e) => e.stopPropagation()}
>
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}
</Link>
) : (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}</span>
);
},
},
{
title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no",
key: "plate_no",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder:
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
},
{
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
render: (text, record) =>
`${record.clm_no || ""}${
record.po_number ? ` (PO: ${record.po_number})` : ""
}`,
},
{
title: t("jobs.fields.ins_co_nm"),
dataIndex: "ins_co_nm",
key: "ins_co_nm",
ellipsis: true,
filters:
(jobs &&
jobs
.map((j) => j.ins_co_nm)
.filter(onlyUnique)
.map((s) => {
return {
text: s,
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.ins_co_nm),
responsive: ["md"],
},
{
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
responsive: ["md"],
ellipsis: true,
sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
),
},
{
title: t("jobs.labels.estimator"),
dataIndex: "jobs.labels.estimator",
key: "jobs.labels.estimator",
ellipsis: true,
responsive: ["xl"],
filterSearch: true,
filters:
(jobs &&
jobs
.map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim())
.filter(onlyUnique)
.map((s) => {
return {
text: s || "N/A",
value: [s],
};
})) ||
[],
onFilter: (value, record) =>
value.includes(
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
),
render: (text, record) =>
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
responsive: ["md"],
},
// {
// title: t("jobs.fields.owner_owing"),
// dataIndex: "owner_owing",
// key: "owner_owing",
// responsive: ["md"],
// render: (text, record) => (
// <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
// ),
// },
];
const scrollMapper = {
xs: true,
sm: true,
md: true,
lg: "100%",
xl: "100%",
xxl: "100%",
};
return (
<Card
title={t("titles.bc.jobs-active")}
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Input.Search
placeholder={t("general.labels.search")}
onChange={(e) => {
setSearchText(e.target.value);
}}
value={searchText}
enterButton
/>
</Space>
}
>
<Table
loading={loading}
pagination={{ defaultPageSize: 50 }}
columns={columns}
rowKey="id"
dataSource={jobs}
scroll={{
x: selectedBreakpoint ? scrollMapper[selectedBreakpoint[0]] : "100%",
}}
rowSelection={{
onSelect: (record) => {
handleOnRowClick(record);
},
selectedRowKeys: [selected],
type: "radio",
}}
onChange={handleTableChange}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleOnRowClick(record);
);
},
};
}}
/>
</Card>
);
},
{
title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
),
},
{
title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
ellipsis: true,
sorter: (a, b) => alphaSort(a.status, b.status),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
filters:
(jobs &&
jobs
.map((j) => j.status)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Status*",
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.status),
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => {
return record.vehicleid ? (
<Link
to={"/manage/vehicles/" + record.vehicleid}
onClick={(e) => e.stopPropagation()}
>
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}
</Link>
) : (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}</span>
);
},
},
{
title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no",
key: "plate_no",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder:
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
},
{
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
render: (text, record) =>
`${record.clm_no || ""}${
record.po_number ? ` (PO: ${record.po_number})` : ""
}`,
},
{
title: t("jobs.fields.ins_co_nm"),
dataIndex: "ins_co_nm",
key: "ins_co_nm",
ellipsis: true,
filters:
(jobs &&
jobs
.map((j) => j.ins_co_nm)
.filter(onlyUnique)
.map((s) => {
return {
text: s,
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.ins_co_nm),
responsive: ["md"],
},
{
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
responsive: ["md"],
ellipsis: true,
sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
),
},
{
title: t("jobs.labels.estimator"),
dataIndex: "jobs.labels.estimator",
key: "jobs.labels.estimator",
ellipsis: true,
responsive: ["xl"],
filterSearch: true,
filters:
(jobs &&
jobs
.map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim())
.filter(onlyUnique)
.map((s) => {
return {
text: s || "N/A",
value: [s],
};
})) ||
[],
onFilter: (value, record) =>
value.includes(
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
),
render: (text, record) =>
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
responsive: ["md"],
},
// {
// title: t("jobs.fields.owner_owing"),
// dataIndex: "owner_owing",
// key: "owner_owing",
// responsive: ["md"],
// render: (text, record) => (
// <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
// ),
// },
];
const scrollMapper = {
xs: true,
sm: true,
md: true,
lg: "100%",
xl: "100%",
xxl: "100%",
};
return (
<Card
title={t("titles.bc.jobs-active")}
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Input.Search
placeholder={t("general.labels.search")}
onChange={(e) => {
setSearchText(e.target.value);
}}
value={searchText}
enterButton
/>
</Space>
}
>
<Table
loading={loading}
pagination={{ defaultPageSize: 50 }}
columns={columns}
rowKey="id"
dataSource={jobs}
scroll={{
x: selectedBreakpoint ? scrollMapper[selectedBreakpoint[0]] : "100%",
}}
rowSelection={{
onSelect: (record) => {
handleOnRowClick(record);
},
selectedRowKeys: [selected],
type: "radio",
}}
onChange={handleTableChange}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleOnRowClick(record);
},
};
}}
/>
</Card>
);
}
export default connect(mapStateToProps, null)(JobsList);

View File

@@ -20,6 +20,7 @@ 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";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -377,7 +378,7 @@ export function JobsReadyList({ bodyshop }) {
>
<Table
loading={loading}
pagination={{ defaultPageSize: 50 }}
pagination={{ defaultPageSize: pageLimit }}
columns={columns}
rowKey="id"
dataSource={jobs}

View File

@@ -6,6 +6,7 @@ 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";
import {pageLimit} from "../../utils/config";
export default function OwnersListComponent({
loading,
@@ -122,7 +123,7 @@ export default function OwnersListComponent({
loading={loading}
pagination={{
position: "top",
pageSize: 25,
pageSize: pageLimit,
current: parseInt(page || 1),
total: total,
}}

View File

@@ -5,6 +5,7 @@ import AlertComponent from "../alert/alert.component";
import OwnersListComponent from "./owners-list.component";
import queryString from "query-string";
import { useLocation } from "react-router-dom";
import {pageLimit} from "../../utils/config";
export default function OwnersListContainer() {
const searchParams = queryString.parse(useLocation().search);
@@ -16,8 +17,8 @@ export default function OwnersListContainer() {
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * 25 : 0,
limit: 25,
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "created_at"]: sortorder

View File

@@ -18,6 +18,7 @@ import { alphaSort } from "../../utils/sorters";
import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -282,11 +283,11 @@ export function PaymentsListPaginated({
pagination={
search?.search
? {
pageSize: 25,
pageSize: pageLimit,
showSizeChanger: false,
}
: {
pageSize: 25,
pageSize: pageLimit,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,

View File

@@ -1,12 +1,23 @@
import { ExclamationCircleFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Dropdown, Menu } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { useMutation } from "@apollo/client";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
export default function ProductionListColumnAlert({ record }) {
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function ProductionListColumnAlert({ record, insertAuditTrail }) {
const { t } = useTranslation();
const [updateAlert] = useMutation(UPDATE_JOB);
@@ -27,6 +38,14 @@ export default function ProductionListColumnAlert({ record }) {
},
},
},
});
insertAuditTrail({
jobid: record.id,
operation: AuditTrailMapping.alertToggle(
!!record.production_vars && !!record.production_vars.alert
? !record.production_vars.alert
: true
),
}).then(() => {
if (record.refetch) record.refetch();
});
@@ -58,3 +77,8 @@ export default function ProductionListColumnAlert({ record }) {
</Dropdown>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListColumnAlert);

View File

@@ -55,10 +55,11 @@ const ret = {
"shiftclock:view": 2,
"shop:config": 4,
"shop:rbac": 5,
"shop:vendors": 2,
"shop:dashboard": 3,
"shop:rbac": 5,
"shop:reportcenter": 2,
"shop:templates": 4,
"shop:vendors": 2,
"temporarydocs:view": 2,

View File

@@ -5,6 +5,7 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectReportCenter } from "../../redux/modals/modals.selectors";
import RbacWrapperComponent from "../rbac-wrapper/rbac-wrapper.component";
import ReportCenterModalComponent from "./report-center-modal.component";
const mapStateToProps = createStructuredSelector({
@@ -33,7 +34,9 @@ export function ReportCenterModalContainer({
destroyOnClose
width="80%"
>
<ReportCenterModalComponent />
<RbacWrapperComponent action="shop:reportcenter">
<ReportCenterModalComponent />
</RbacWrapperComponent>
</Modal>
);
}

View File

@@ -10,13 +10,14 @@ import OwnerNameDisplay from "../owner-name-display/owner-name-display.component
import ScoreboardEntryEdit from "../scoreboard-entry-edit/scoreboard-entry-edit.component";
import ScoreboardRemoveButton from "../scoreboard-remove-button/scorebard-remove-button.component";
import { SyncOutlined } from "@ant-design/icons";
import {pageLimit} from "../../utils/config";
export default function ScoreboardJobsList({ scoreBoardlist }) {
const { t } = useTranslation();
const [state, setState] = useState({
visible: false,
search: "",
current: 1,
pageSize: 10,
pageSize: pageLimit,
});
const { loading, error, data, refetch } = useQuery(
@@ -148,7 +149,7 @@ export default function ScoreboardJobsList({ scoreBoardlist }) {
}
pagination={{
position: "top",
pageSize: state.pageSize || 10,
pageSize: state.pageSize || pageLimit,
current: state.current || 1,
total: data ? data.scoreboard_aggregate.aggregate.count : 0,
}}

View File

@@ -29,7 +29,7 @@ export default connect(
export function ScoreboardTimeTicketsStats({ bodyshop }) {
const { t } = useTranslation();
const startDate = moment().startOf("month")
const startDate = moment().startOf("month");
const endDate = moment().endOf("month");
const fixedPeriods = useMemo(() => {
@@ -84,6 +84,8 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
end: endDate.format("YYYY-MM-DD"),
fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
jobStart: startDate,
jobEnd: endDate,
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
@@ -340,11 +342,21 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
larData.push({ ...r, ...lar });
});
const jobData = {};
data.jobs.forEach((job) => {
job.tthrs = job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
});
jobData.tthrs = data.jobs
.reduce((acc, val) => acc + val.tthrs, 0)
.toFixed(1);
jobData.count = data.jobs.length.toFixed(0);
return {
fixed: ret,
combinedData: combinedData,
labData: labData,
larData: larData,
jobData: jobData,
};
}, [fixedPeriods, data, bodyshop]);
@@ -356,7 +368,10 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
<ScoreboardTimeticketsTargetsTable />
</Col>
<Col span={24}>
<ScoreboardTicketsStats data={calculatedData.fixed} />
<ScoreboardTicketsStats
data={calculatedData.fixed}
jobData={calculatedData.jobData}
/>
</Col>
<Col span={24}>
<ScoreboardTimeTicketsChart

View File

@@ -41,7 +41,7 @@ function useLocalStorage(key, initialValue) {
return [storedValue, setStoredValue];
}
export function ScoreboardTicketsStats({ data, bodyshop }) {
export function ScoreboardTicketsStats({ data, jobData, bodyshop }) {
const { t } = useTranslation();
const [isLarge, setIsLarge] = useLocalStorage("isLargeStatistic", false);
@@ -408,7 +408,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
{/* Monthly Stats */}
<Row gutter={[16, 16]}>
{/* This Month */}
<Col span={8} align="center">
<Col span={7} align="center">
<Card size="small" title={t("scoreboard.labels.thismonth")}>
<Row gutter={[16, 16]}>
<Col span={24}>
@@ -482,7 +482,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
</Card>
</Col>
{/* Last Month */}
<Col span={8} align="center">
<Col span={7} align="center">
<Card size="small" title={t("scoreboard.labels.lastmonth")}>
<Row gutter={[16, 16]}>
<Col span={24}>
@@ -556,7 +556,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
</Card>
</Col>
{/* Efficiency Over Period */}
<Col span={8} align="center">
<Col span={7} align="center">
<Card
size="small"
title={t("scoreboard.labels.efficiencyoverperiod")}
@@ -604,6 +604,40 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
</Row>
</Card>
</Col>
<Col span={3} align="center">
<Card
size="small"
title={t("scoreboard.labels.jobscompletednotinvoiced")}
>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={jobData.count}
valueStyle={{
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.totalhrs")}
</Typography.Text>
}
value={jobData.tthrs}
valueStyle={{
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
</Row>
</Space>
{/* Disclaimer */}

View File

@@ -65,6 +65,8 @@ export default function ScoreboardTimeTickets() {
end: endDate.format("YYYY-MM-DD"),
fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
jobStart: startDate,
jobEnd: endDate,
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",

View File

@@ -49,13 +49,6 @@ export function ShopInfoGeneral({ form, bodyshop }) {
header={t("bodyshop.labels.businessinformation")}
id="businessinformation"
>
<Form.Item
label={t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
name={["md_functionality_toggles","parts_queue_toggle"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.shopname")}
name="shopname"
@@ -688,6 +681,13 @@ export function ShopInfoGeneral({ form, bodyshop }) {
>
<Select mode="tags" />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
name={["md_functionality_toggles","parts_queue_toggle"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["last_name_first"]}
label={t("bodyshop.fields.last_name_first")}

View File

@@ -28,18 +28,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
return (
<RbacWrapper action="shop:rbac">
<LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payables")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "accounting:payables"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.accounting.exportlog")}
rules={[
@@ -52,6 +40,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payables")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "accounting:payables"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payments")}
rules={[
@@ -77,26 +77,62 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.csi.page")}
label={t("bodyshop.fields.rbac.bills.delete")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "csi:page"]}
name={["md_rbac", "bills:delete"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.csi.export")}
label={t("bodyshop.fields.rbac.bills.enter")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "csi:export"]}
name={["md_rbac", "bills:enter"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.list")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:list"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.reexport")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:reexport"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.view")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:view"]}
>
<InputNumber />
</Form.Item>
@@ -173,26 +209,38 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.list-active")}
label={t("bodyshop.fields.rbac.csi.export")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:list-active"]}
name={["md_rbac", "csi:export"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.list-ready")}
label={t("bodyshop.fields.rbac.csi.page")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:list-ready"]}
name={["md_rbac", "csi:page"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.employees.page")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "employees:page"]}
>
<InputNumber />
</Form.Item>
@@ -208,30 +256,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.partsqueue")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:partsqueue"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.list-all")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:list-all"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.available-list")}
rules={[
@@ -245,26 +269,14 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.create")}
label={t("bodyshop.fields.rbac.jobs.checklist-view")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:create"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.intake")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:intake"]}
name={["md_rbac", "jobs:checklist-view"]}
>
<InputNumber />
</Form.Item>
@@ -280,6 +292,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.create")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:create"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.deliver")}
rules={[
@@ -305,14 +329,62 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.checklist-view")}
label={t("bodyshop.fields.rbac.jobs.intake")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:checklist-view"]}
name={["md_rbac", "jobs:intake"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.list-active")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:list-active"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.list-all")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:list-all"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.list-ready")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:list-ready"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.partsqueue")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:partsqueue"]}
>
<InputNumber />
</Form.Item>
@@ -329,74 +401,14 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.enter")}
label={t("bodyshop.fields.rbac.owners.detail")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:enter"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.delete")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:delete"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.reexport")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:reexport"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.view")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.list")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:list"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.employees.page")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "employees:page"]}
name={["md_rbac", "owners:detail"]}
>
<InputNumber />
</Form.Item>
@@ -412,18 +424,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.owners.detail")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "owners:detail"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.payments.enter")}
rules={[
@@ -448,6 +448,30 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.phonebook.edit")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "phonebook:edit"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.phonebook.view")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "phonebook:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.production.board")}
rules={[
@@ -509,38 +533,14 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.edit")}
label={t("bodyshop.fields.rbac.shop.config")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "timetickets:edit"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "timetickets:shiftedit"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shop.vendors")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "shop:vendors"]}
name={["md_rbac", "shop:config"]}
>
<InputNumber />
</Form.Item>
@@ -556,18 +556,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shop.config")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "shop:config"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shop.rbac")}
rules={[
@@ -580,6 +568,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shop.reportcenter")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "shop:reportcenter"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shop.templates")}
rules={[
@@ -592,6 +592,42 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shop.vendors")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "shop:vendors"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.temporarydocs.view")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "temporarydocs:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.edit")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "timetickets:edit"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.enter")}
rules={[
@@ -616,6 +652,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "timetickets:shiftedit"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.users.editaccess")}
rules={[
@@ -628,42 +676,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.temporarydocs.view")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "temporarydocs:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.phonebook.view")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "phonebook:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.phonebook.edit")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "phonebook:edit"]}
>
<InputNumber />
</Form.Item>
{Simple_Inventory.treatment === "on" && (
<>
<Form.Item

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 VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
import {pageLimit} from "../../utils/config";
export default function VehiclesListComponent({
loading,
vehicles,
@@ -106,7 +107,7 @@ export default function VehiclesListComponent({
loading={loading}
pagination={{
position: "top",
pageSize: 25,
pageSize: pageLimit,
current: parseInt(page || 1),
total: total,
}}

View File

@@ -5,6 +5,7 @@ import AlertComponent from "../alert/alert.component";
import { QUERY_ALL_VEHICLES_PAGINATED } from "../../graphql/vehicles.queries";
import queryString from "query-string";
import { useLocation } from "react-router-dom";
import {pageLimit} from "../../utils/config";
export default function VehiclesListContainer() {
const searchParams = queryString.parse(useLocation().search);
@@ -15,8 +16,8 @@ export default function VehiclesListContainer() {
{
variables: {
search: search || "",
offset: page ? (page - 1) * 25 : 0,
limit: 25,
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "created_at"]: sortorder

View File

@@ -1,5 +1,65 @@
import { gql } from "@apollo/client";
export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql`
query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED(
$offset: Int
$limit: Int
$order: [jobs_order_by!]
$statuses: [String!]!,
$isConverted: Boolean
) {
jobs(
offset: $offset
limit: $limit
where: { status: { _in: $statuses }, converted: { _eq: $isConverted } }
order_by: $order
) {
iouparent
ownr_fn
ownr_ln
ownr_co_nm
ownr_ph1
ownr_ph2
ownr_ea
ownerid
comment
plate_no
plate_st
v_vin
v_model_yr
v_model_desc
v_make_desc
v_color
vehicleid
actual_completion
actual_delivery
actual_in
production_vars
id
ins_co_nm
clm_no
po_number
clm_total
owner_owing
ro_number
scheduled_completion
scheduled_in
scheduled_delivery
status
updated_at
ded_amt
suspended
est_ct_fn
est_ct_ln
}
jobs_aggregate(where: { status: { _in: $statuses } }) {
aggregate {
count(distinct: true)
}
}
}
`;
export const QUERY_ALL_ACTIVE_JOBS = gql`
query QUERY_ALL_ACTIVE_JOBS($statuses: [String!]!, $isConverted: Boolean) {
jobs(
@@ -60,7 +120,7 @@ export const QUERY_PARTS_QUEUE = gql`
}
}
jobs(
where: { _and: [{ status: { _in: $statuses } }] }
where: { _and: [{ status: { _in: $statuses }, converted: { _eq: true } }] }
offset: $offset
limit: $limit
order_by: $order

View File

@@ -143,9 +143,14 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
$end: date!
$fixedStart: date!
$fixedEnd: date!
$jobStart: timestamptz!
$jobEnd: timestamptz!
) {
timetickets(
where: { date: { _gte: $start, _lte: $end }, cost_center: {_neq: "timetickets.labels.shift"} }
where: {
date: { _gte: $start, _lte: $end }
cost_center: { _neq: "timetickets.labels.shift" }
}
order_by: { date: desc_nulls_first }
) {
actualhrs
@@ -176,7 +181,10 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
}
}
fixedperiod: timetickets(
where: { date: { _gte: $fixedStart, _lte: $fixedEnd }, cost_center: {_neq: "timetickets.labels.shift"} }
where: {
date: { _gte: $fixedStart, _lte: $fixedEnd }
cost_center: { _neq: "timetickets.labels.shift" }
}
order_by: { date: desc_nulls_first }
) {
actualhrs
@@ -205,6 +213,25 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
last_name
}
}
jobs(
where: {
date_invoiced: { _is_null: true }
ro_number: { _is_null: false }
voided: { _eq: false }
_or: [
{ actual_completion: { _gte: $jobStart, _lte: $jobEnd } }
{ actual_delivery: { _gte: $jobStart, _lte: $jobEnd } }
]
}
) {
id
joblines(order_by: { line_no: asc }, where: { removed: { _eq: false } }) {
convertedtolbr
convertedtolbr_data
mod_lb_hrs
mod_lbr_ty
}
}
}
`;

View File

@@ -14,6 +14,7 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort, dateSort } from "../../utils/sorters";
import {pageLimit} from "../../utils/config";
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
@@ -295,11 +296,11 @@ export function BillsListPage({
pagination={
search?.search
? {
pageSize: 25,
pageSize: pageLimit,
showSizeChanger: false,
}
: {
pageSize: 25,
pageSize: pageLimit,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,

View File

@@ -13,6 +13,7 @@ import {
setSelectedHeader,
} from "../../redux/application/application.actions";
import BillsPageComponent from "./bills.page.component";
import {pageLimit} from "../../utils/config";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -38,8 +39,8 @@ export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
offset: page ? (page - 1) * 25 : 0,
limit: 25,
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
searchObj
? JSON.parse(searchObj)

View File

@@ -12,6 +12,7 @@ import {
setSelectedHeader,
} from "../../redux/application/application.actions";
import ContractsPageComponent from "./contracts.page.component";
import {pageLimit} from "../../utils/config";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -29,8 +30,8 @@ export function ContractsPageContainer({ setBreadcrumbs, setSelectedHeader }) {
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * 25 : 0,
limit: 25,
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "start"]: sortorder

View File

@@ -19,6 +19,7 @@ import NotFound from "../../components/not-found/not-found.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import queryString from "query-string";
import { useLocation } from "react-router-dom";
import {pageLimit} from "../../utils/config";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -41,8 +42,8 @@ export function CourtesyCarDetailPageContainer({
const { loading, error, data } = useQuery(QUERY_CC_BY_PK, {
variables: {
id: ccId,
offset: page ? (page - 1) * 25 : 0,
limit: 25,
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "start"]: sortorder

View File

@@ -12,6 +12,7 @@ import AlertComponent from "../../components/alert/alert.component";
import { QUERY_EXPORT_LOG_PAGINATED } from "../../graphql/accounting.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -29,8 +30,8 @@ export function ExportLogsPageComponent({ bodyshop }) {
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * 25 : 0,
limit: 25,
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "created_at"]: sortorder
@@ -178,7 +179,7 @@ export function ExportLogsPageComponent({ bodyshop }) {
loading={loading}
pagination={{
position: "top",
pageSize: 25,
pageSize: pageLimit,
current: parseInt(page || 1),
total: data && data.search_exportlog_aggregate.aggregate.count,
}}

View File

@@ -13,6 +13,7 @@ import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
//bodyshop: selectBodyshop,
@@ -33,16 +34,16 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
offset: page ? (page - 1) * 25 : 0,
limit: 25,
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}),
order: [
{
[sortcolumn || "ro_number"]:
sortorder && sortorder !== "false"
? sortorder === "descend"
? (sortorder === "descend"
? "desc"
: "asc"
: "asc")
: "desc",
},
],

View File

@@ -36,14 +36,22 @@ import JobsCloseLines from "../../components/jobs-close-lines/jobs-close-lines.c
import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component";
import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
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";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
});
export function JobsCloseComponent({ job, bodyshop, jobRO }) {
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, }) {
const { t } = useTranslation();
const [form] = Form.useForm();
const client = useApolloClient();
@@ -110,6 +118,10 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
notification["success"]({
message: t("jobs.successes.closed"),
});
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobinvoiced(),
});
// history.push(`/manage/jobs/${job.id}`);
} else {
setLoading(false);
@@ -527,4 +539,4 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
</div>
);
}
export default connect(mapStateToProps, null)(JobsCloseComponent);
export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseComponent);

View File

@@ -18,6 +18,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateTimeFormatter, TimeAgoFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -296,7 +297,7 @@ export function PartsQueuePageComponent({ bodyshop }) {
loading={loading}
pagination={{
position: "top",
pageSize: 50,
pageSize: pageLimit,
// current: parseInt(page || 1),
// total: data && data.jobs_aggregate.aggregate.count,
}}

View File

@@ -14,6 +14,7 @@ import {
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -34,8 +35,8 @@ export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
offset: page ? (page - 1) * 25 : 0,
limit: 25,
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
searchObj
? JSON.parse(searchObj)

View File

@@ -17,6 +17,7 @@ import {
import ChatOpenButton from "../../components/chat-open-button/chat-open-button.component";
import { alphaSort } from "../../utils/sorters";
import { HasRbacAccess } from "../../components/rbac-wrapper/rbac-wrapper.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -35,8 +36,8 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) {
nextFetchPolicy: "network-only",
variables: {
search: search || "",
offset: page ? (page - 1) * 25 : 0,
limit: 25,
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "lastname"]: sortorder
@@ -189,7 +190,7 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) {
loading={loading}
pagination={{
position: "top",
pageSize: 25,
pageSize: pageLimit,
current: parseInt(page || 1),
total: data && data.search_phonebook_aggregate.aggregate.count,
}}

View File

@@ -16,6 +16,7 @@ import {
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -42,8 +43,8 @@ export function ShopCsiContainer({
nextFetchPolicy: "network-only",
variables: {
//search: search || "",
offset: page ? (page - 1) * 25 : 0,
limit: 25,
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "completedon"]: sortorder

View File

@@ -56,13 +56,13 @@
"history": "History",
"inproduction": "Jobs In Production",
"manualevent": "Add Manual Appointment",
"noarrivingjobs": "No jobs are arriving.",
"nocompletingjobs": "No jobs scheduled for completion.",
"noarrivingjobs": "No Jobs are arriving.",
"nocompletingjobs": "No Jobs scheduled for completion.",
"nodateselected": "No date has been selected.",
"priorappointments": "Previous Appointments",
"reminder": "This is {{shopname}} reminding you about an appointment on {{date}} at {{time}}. Please let us know if you are not able to make the appointment. We look forward to seeing you soon. ",
"scheduledfor": "Scheduled appointment for: ",
"severalerrorsfound": "Several jobs have issues which may prevent accurate smart scheduling. Click to expand.",
"severalerrorsfound": "Several Jobs have issues which may prevent accurate smart scheduling. Click to expand.",
"smartscheduling": "Smart Scheduling",
"suggesteddates": "Suggested Dates"
},
@@ -103,6 +103,7 @@
"admin_jobmarkforreexport": "ADMIN: Job marked for re-export.",
"admin_jobuninvoice": "ADMIN: Job has been uninvoiced.",
"admin_jobunvoid": "ADMIN: Job has been unvoided.",
"alerttoggle": "Alert Toggle set to {{status}}",
"appointmentcancel": "Appointment canceled. Lost Reason: {{lost_sale_reason}}.",
"appointmentinsert": "Appointment created. Appointment Date: {{start}}.",
"billposted": "Bill with invoice number {{invoice_number}} posted.",
@@ -111,17 +112,18 @@
"jobassignmentchange": "Employee {{name}} assigned to {{operation}}",
"jobassignmentremoved": "Employee assignment removed for {{operation}}",
"jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.",
"jobinvoiced": "Job has been invoiced.",
"jobconverted": "Job converted and assigned number {{ro_number}}.",
"jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.",
"jobimported": "Job imported.",
"jobinproductionchange": "Job production status set to {{inproduction}}",
"jobioucreated": "IOU Created.",
"jobmodifylbradj": "Labor adjustments modified {{mod_lbr_ty}} / {{hours}}.",
"jobnoteadded": "Note added to job.",
"jobnotedeleted": "Note deleted from job.",
"jobnoteupdated": "Note updated on job.",
"jobspartsorder": "Parts order {{order_number}} added to job.",
"jobspartsreturn": "Parts return {{order_number}} added to job.",
"jobnoteadded": "Note added to Job.",
"jobnotedeleted": "Note deleted from Job.",
"jobnoteupdated": "Note updated on Job.",
"jobspartsorder": "Parts order {{order_number}} added to Job.",
"jobspartsreturn": "Parts return {{order_number}} added to Job.",
"jobstatuschange": "Job status changed to {{status}}.",
"jobsupplement": "Job supplement imported."
}
@@ -203,6 +205,7 @@
"entered_total": "Total of Entered Lines",
"enteringcreditmemo": "You are entering a credit memo. Please ensure you are also entering positive values.",
"federal_tax": "Federal Tax",
"federal_tax_exempt": "Federal Tax Exempt?",
"generatepartslabel": "Generate Parts Labels after Saving?",
"iouexists": "An IOU exists that is associated to this RO.",
"local_tax": "Local Tax",
@@ -210,7 +213,7 @@
"markforreexport": "Mark for Re-export",
"new": "New Bill",
"noneselected": "No bill selected.",
"onlycmforinvoiced": "Only credit memos can be entered for any job that has been invoiced, exported, or voided.",
"onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.",
"retailtotal": "Bills Retail Total",
"savewithdiscrepancy": "You are about to save this bill with a discrepancy. The system will continue to use the calculated amount using the bill lines. Press cancel to return to the bill.",
"state_tax": "Provincial/State Tax",
@@ -350,7 +353,7 @@
"md_payment_types": "Payment Types",
"md_referral_sources": "Referral Sources",
"md_functionality_toggles": {
"parts_queue_toggle": "Automatically Add Imported/Supplemented Jobs to Parts Queue"
"parts_queue_toggle": "Auto Add Imported/Supplemented Jobs to Parts Queue"
},
"md_tasks_presets": {
"hourstype": "",
@@ -447,6 +450,7 @@
"config": "Shop -> Config",
"dashboard": "Shop -> Dashboard",
"rbac": "Shop -> RBAC",
"reportcenter": "Shop -> Report Center",
"templates": "Shop -> Templates",
"vendors": "Shop -> Vendors"
},
@@ -658,7 +662,7 @@
"printall": "Print All Documents"
},
"errors": {
"complete": "Error during job checklist completion. {{error}}",
"complete": "Error during Job checklist completion. {{error}}",
"nochecklist": "No checklist has been configured for your shop. "
},
"labels": {
@@ -666,7 +670,7 @@
"allow_text_message": "Permission to Text?",
"checklist": "Checklist",
"printpack": "Job Intake Print Pack",
"removefromproduction": "Remove job from production?"
"removefromproduction": "Remove Job from Production?"
},
"successes": {
"completed": "Job checklist completed."
@@ -682,9 +686,9 @@
"senddltoform": "Insert Driver's License Information"
},
"errors": {
"fetchingjobinfo": "Error fetching job info. {{error}}.",
"returning": "Error returning courtesy car. {{error}}",
"saving": "Error saving contract. {{error}}",
"fetchingjobinfo": "Error fetching Job Info. {{error}}.",
"returning": "Error returning Courtesy Car. {{error}}",
"saving": "Error saving Contract. {{error}}",
"selectjobandcar": "Please ensure both a car and job are selected."
},
"fields": {
@@ -741,7 +745,7 @@
"driverinformation": "Driver's Information",
"findcontract": "Find Contract",
"findermodal": "Contract Finder",
"noteconvertedfrom": "R.O. created from converted Courtesy Car Contract {{agreementnumber}}.",
"noteconvertedfrom": "R.O. created from converted Courtesy Car Contract {{agreementnumber}}.",
"populatefromjob": "Populate from Job",
"rates": "Contract Rates",
"time": "Time",
@@ -763,7 +767,7 @@
"return": "Return Car"
},
"errors": {
"saving": "Error saving courtesy card. {{error}}"
"saving": "Error saving Courtesy Car. {{error}}"
},
"fields": {
"color": "Color",
@@ -914,7 +918,7 @@
"upload_limitexceeded": "Uploading all selected documents will exceed the job storage limit for your shop. ",
"upload_limitexceeded_title": "Unable to upload document(s)",
"uploading": "Uploading...",
"usage": "of job storage used. ({{used}} / {{total}})"
"usage": "of Job storage used. ({{used}} / {{total}})"
},
"successes": {
"delete": "Document(s) deleted successfully.",
@@ -1375,28 +1379,28 @@
},
"errors": {
"addingtoproduction": "Error adding to production. {{error}}",
"cannotintake": "Intake cannot be completed for this job. It has either already been completed or the job is already here.",
"closing": "Error closing job. {{error}}",
"creating": "Error encountered while creating job. {{error}}",
"deleted": "Error deleting job. {{error}}",
"exporting": "Error exporting job. {{error}}",
"cannotintake": "Intake cannot be completed for this Job. It has either already been completed or the Job is already here.",
"closing": "Error closing Job. {{error}}",
"creating": "Error encountered while creating Job. {{error}}",
"deleted": "Error deleting Job. {{error}}",
"exporting": "Error exporting Job. {{error}}",
"exporting-partner": "Unable to connect to ImEX Partner. Please ensure it is running and logged in.",
"invoicing": "Error invoicing job. {{error}}",
"noaccess": "This job does not exist or you do not have access to it.",
"invoicing": "Error invoicing Job. {{error}}",
"noaccess": "This Job does not exist or you do not have access to it.",
"nodamage": "No damage points on estimate.",
"nodates": "No dates specified for this job.",
"nodates": "No dates specified for this Job.",
"nofinancial": "No financial data has been calculated yet for this job. Please save it again.",
"nojobselected": "No job is selected.",
"nojobselected": "No Job is selected.",
"noowner": "No owner associated.",
"novehicle": "No vehicle associated.",
"partspricechange": "",
"saving": "Error encountered while saving record.",
"scanimport": "Error importing job. {{message}}",
"totalscalc": "Error while calculating new job totals.",
"updating": "Error while updating job(s). {{error}}",
"scanimport": "Error importing Job. {{message}}",
"totalscalc": "Error while calculating new Job totals.",
"updating": "Error while updating Job(s). {{error}}",
"validation": "Please ensure all fields are entered correctly.",
"validationtitle": "Validation Error",
"voiding": "Error voiding job. {{error}}"
"voiding": "Error voiding Job. {{error}}"
},
"fields": {
"actual_completion": "Actual Completion",
@@ -1673,9 +1677,9 @@
"adminwarning": "Use the functionality on this page at your own risk. You are responsible for any and all changes to your data.",
"allocations": "Allocations",
"alreadyaddedtoscoreboard": "Job has already been added to scoreboard. Saving will update the previous entry.",
"alreadyclosed": "This job has already been closed.",
"alreadyclosed": "This Job has already been closed.",
"appointmentconfirmation": "Send confirmation to customer?",
"associationwarning": "Any changes to associations will require updating the data from the new parent record to the job.",
"associationwarning": "Any changes to associations will require updating the data from the new parent record to the Job.",
"audit": "Audit Trail",
"available": "Available",
"availablejobs": "Available Jobs",
@@ -1683,7 +1687,7 @@
"days": "Days",
"rate": "PVRT Rate"
},
"ca_gst_all_if_null": "If the job is marked as a \"GST Registrant\" and this value is set to $0, the customer will be responsible for paying all of the GST by default. ",
"ca_gst_all_if_null": "If the Job is marked as a \"GST Registrant\" and this value is set to $0, the customer will be responsible for paying all of the GST by default. ",
"calc_repair_days": "Calculated Repair Days",
"calc_repair_days_tt": "This is the approximate number of days required to complete the repair according to the target touch time in your shop configuration (current set to {{target_touchtime}}).",
"cards": {
@@ -1699,7 +1703,7 @@
"totals": "Totals",
"vehicle": "Vehicle"
},
"changeclass": "Changing the job's class can have fundamental impacts to already exported accounting items. Are you sure you want to do this?",
"changeclass": "Changing the Job's class can have fundamental impacts to already exported accounting items. Are you sure you want to do this?",
"checklistcompletedby": "Checklist completed by {{by}} at {{at}}",
"checklistdocuments": "Checklist Documents",
"checklists": "Checklists",
@@ -1723,12 +1727,12 @@
"vehicleinfo": "Vehicle Info"
},
"createiouwarning": "Are you sure you want to create an IOU for these lines? A new RO will be created based on those lines for this customer.",
"creating_new_job": "Creating new job...",
"creating_new_job": "Creating new Job...",
"deductible": {
"stands": "Stands",
"waived": "Waived"
},
"deleteconfirm": "Are you sure you want to delete this job? This cannot be undone. ",
"deleteconfirm": "Are you sure you want to delete this Job? This cannot be undone. ",
"deletedelivery": "Delete Delivery Checklist",
"deleteintake": "Delete Intake Checklist",
"deliverchecklist": "Deliver Checklist",
@@ -1749,7 +1753,7 @@
"documents": "Documents",
"documents-images": "Images",
"documents-other": "Other Documents",
"duplicateconfirm": "Are you sure you want to duplicate this job? Some elements of this job will not be duplicated.",
"duplicateconfirm": "Are you sure you want to duplicate this Job? Some elements of this Job will not be duplicated.",
"emailaudit": "Email Audit Trail",
"employeeassignments": "Employee Assignments",
"estimatelines": "Estimate Lines",
@@ -1760,7 +1764,7 @@
"gppercent": "% G.P.",
"hrs_claimed": "Hours Claimed",
"hrs_total": "Hours Total",
"importnote": "The job was initially imported.",
"importnote": "The Job was initially imported.",
"inproduction": "In Production",
"intakechecklist": "Intake Checklist",
"iou": "IOU",
@@ -1793,9 +1797,9 @@
"calculatedcreditsnotreceived": "The calculated credits not received is derived by subtracting the amount of credit memos entered from the <b>retail</b> total of returns created. This does not take into account whether the credit was marked as received. You can find more information <a href=\"https://help.imex.online/en/article/credits-not-received-changes-1jy9snw\" target=\"_blank\">here</a>.",
"creditmemos": "The total <b>retail</b> amount of all returns created. This amount does not reflect credit memos that have been posted.",
"creditsnotreceived": "This total reflects the total <b>retail</b> of parts returns lines that have not been explicitly marked as returned when posting a credit memo. You can learn more about this here <a href=\"https://help.imex.online/en/article/credits-not-received-changes-1jy9snw\" target=\"_blank\">here</a>. ",
"discrep1": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>Too many bills/bill lines that have been posted against this RO. Check to make sure every bill posted on this RO is correctly posted and assigned.</li>\n<li>You do not have the latest supplement imported, or, a supplement must be submitted and then imported.</li>\n<li>You have posted a bill line to labor.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this job from reconciling.</i>",
"discrep2": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>Used an incorrect rate when deducting from labor.</li>\n<li>An outstanding imbalance higher in the reconciliation process.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this job from reconciling.</i>",
"discrep3": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>A parts order return has not been created.</li>\n<li>An outstanding imbalance higher in the reconciliation process.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this job from reconciling.</i>",
"discrep1": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>Too many bills/bill lines that have been posted against this RO. Check to make sure every bill posted on this RO is correctly posted and assigned.</li>\n<li>You do not have the latest supplement imported, or, a supplement must be submitted and then imported.</li>\n<li>You have posted a bill line to labor.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this Job from reconciling.</i>",
"discrep2": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>Used an incorrect rate when deducting from labor.</li>\n<li>An outstanding imbalance higher in the reconciliation process.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this Job from reconciling.</i>",
"discrep3": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>A parts order return has not been created.</li>\n<li>An outstanding imbalance higher in the reconciliation process.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this Job from reconciling.</i>",
"laboradj": "The sum of all bill lines that deducted from labor hours, rather than part prices.",
"partstotal": "This is the total of all parts and sublet amounts on the vehicle (some of these may require an in-house invoice).<br/>\nItems such as shop and paint materials, labor online lines, etc. are not included in this total.",
"totalreturns": "The total <b>retail</b> amount of returns created for this job."
@@ -1826,13 +1830,13 @@
"sale_parts": "Sales - Parts",
"sale_sublet": "Sales - Sublet",
"sales": "Sales",
"savebeforeconversion": "You have unsaved changes on the job. Please save them before converting it. ",
"scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the job. ",
"savebeforeconversion": "You have unsaved changes on the Job. Please save them before converting it. ",
"scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the Job. ",
"specialcoveragepolicy": "Special Coverage Policy Applies",
"state_tax_amt": "Provincial/State Taxes",
"subletstotal": "Sublets Total",
"subtotal": "Subtotal",
"supplementnote": "The job had a supplement imported.",
"supplementnote": "The Job had a supplement imported.",
"suspended": "SUSPENDED",
"suspense": "Suspense",
"threshhold": "Max Threshold: ${{amount}}",
@@ -1841,16 +1845,16 @@
"total_repairs": "Total Repairs",
"total_sales": "Total Sales",
"totals": "Totals",
"unvoidnote": "This job was unvoided.",
"unvoidnote": "This Job was unvoided.",
"vehicle_info": "Vehicle",
"vehicleassociation": "Vehicle Association",
"viewallocations": "View Allocations",
"voidjob": "Are you sure you want to void this job? This cannot be easily undone. ",
"voidnote": "This job was voided."
"voidjob": "Are you sure you want to void this Job? This cannot be easily undone. ",
"voidnote": "This Job was voided."
},
"successes": {
"addedtoproduction": "Job added to production board.",
"all_deleted": "{{count}} jobs deleted successfully.",
"all_deleted": "{{count}} Jobs deleted successfully.",
"closed": "Job closed successfully.",
"converted": "Job converted successfully.",
"created": "Job created successfully. Click to view.",
@@ -2026,7 +2030,7 @@
},
"errors": {
"invalidphone": "The phone number is invalid. Unable to open conversation. ",
"noattachedjobs": "No jobs have been associated to this conversation. ",
"noattachedjobs": "No Jobs have been associated to this conversation. ",
"updatinglabel": "Error updating label. {{error}}"
},
"labels": {
@@ -2035,7 +2039,7 @@
"maxtenimages": "You can only select up to a maximum of 10 images at a time.",
"messaging": "Messaging",
"noallowtxt": "This customer has not indicated their permission to be messaged.",
"nojobs": "Not associated to any job.",
"nojobs": "Not associated to any Job.",
"nopush": "Polling Mode Enabled",
"phonenumber": "Phone #",
"presets": "Presets",
@@ -2044,6 +2048,9 @@
"sentby": "Sent by {{by}} at {{time}}",
"typeamessage": "Send a message...",
"unarchive": "Unarchive"
},
"render": {
"conversation_list": "Conversation List"
}
},
"notes": {
@@ -2202,7 +2209,7 @@
"parts_orders": "Parts Orders",
"print": "Show Printed Form",
"receive": "Receive Parts Order",
"removefrompartsqueue": "Remove from Parts Queue?",
"removefrompartsqueue": "Unqueue from Parts Queue?",
"returnpartsorder": "Return Parts Order",
"sublet_order": "Sublet Order"
},
@@ -2455,15 +2462,15 @@
"unsuspend": "Unsuspend"
},
"errors": {
"boardupdate": "Error encountered updating job. {{message}}",
"boardupdate": "Error encountered updating Job. {{message}}",
"removing": "Error removing from production board. {{error}}",
"settings": "Error saving board settings: {{error}}"
},
"labels": {
"actual_in": "Actual In",
"alert": "Alert",
"alertoff": "Remove alert from job",
"alerton": "Add alert to job",
"alertoff": "Remove alert from Job",
"alerton": "Add alert to Job",
"ats": "Alternative Transportation",
"bodyhours": "B",
"bodypriority": "B/P",
@@ -2674,9 +2681,9 @@
"edit": "Edit"
},
"errors": {
"adding": "Error adding job to scoreboard. {{message}}",
"removing": "Error removing job from scoreboard. {{message}}",
"updating": "Error updating scoreboard. {{message}}"
"adding": "Error adding Job to Scoreboard. {{message}}",
"removing": "Error removing Job from Scoreboard. {{message}}",
"updating": "Error updating Scoreboard. {{message}}"
},
"fields": {
"bodyhrs": "Body Hours",
@@ -2695,6 +2702,7 @@
"efficiencyoverperiod": "Efficiency over Selected Dates",
"entries": "Scoreboard Entries",
"jobs": "Jobs",
"jobscompletednotinvoiced": "Completed Not Invoiced",
"lastmonth": "Last Month",
"lastweek": "Last Week",
"monthlytarget": "Monthly",
@@ -2709,6 +2717,7 @@
"timetickets": "Time Tickets",
"timeticketsemployee": "Time Tickets by Employee",
"todateactual": "Actual (MTD)",
"totalhrs": "Total Hours",
"totaloverperiod": "Total over Selected Dates",
"weeklyactual": "Actual (W)",
"weeklytarget": "Weekly",
@@ -2774,7 +2783,7 @@
"ro_number": "Job to Post Against"
},
"labels": {
"alreadyclockedon": "You are already clocked in to the following job(s):",
"alreadyclockedon": "You are already clocked in to the following Job(s):",
"ambreak": "AM Break",
"amshift": "AM Shift",
"clockhours": "Shift Clock Hours Summary",

View File

@@ -103,6 +103,7 @@
"admin_jobmarkforreexport": "",
"admin_jobuninvoice": "",
"admin_jobunvoid": "",
"alerttoggle": "",
"appointmentcancel": "",
"appointmentinsert": "",
"billposted": "",
@@ -111,6 +112,7 @@
"jobassignmentchange": "",
"jobassignmentremoved": "",
"jobchecklist": "",
"jobinvoiced": "",
"jobconverted": "",
"jobfieldchanged": "",
"jobimported": "",
@@ -203,6 +205,7 @@
"entered_total": "",
"enteringcreditmemo": "",
"federal_tax": "",
"federal_tax_exempt": "",
"generatepartslabel": "",
"iouexists": "",
"local_tax": "",
@@ -255,7 +258,7 @@
"address2": "",
"appt_alt_transport": "",
"md_functionality_toggles": {
"parts_queue_toggle": "Parts Queue Toggle"
"parts_queue_toggle": ""
},
"appt_colors": {
"color": "",
@@ -447,6 +450,7 @@
"config": "",
"dashboard": "",
"rbac": "",
"reportcenter": "",
"templates": "",
"vendors": ""
},
@@ -2044,6 +2048,9 @@
"sentby": "",
"typeamessage": "Enviar un mensaje...",
"unarchive": ""
},
"render": {
"conversation_list": ""
}
},
"notes": {
@@ -2695,6 +2702,7 @@
"efficiencyoverperiod": "",
"entries": "",
"jobs": "",
"jobscompletednotinvoiced": "",
"lastmonth": "",
"lastweek": "",
"monthlytarget": "",
@@ -2709,6 +2717,7 @@
"timetickets": "",
"timeticketsemployee": "",
"todateactual": "",
"totalhrs": "",
"totaloverperiod": "",
"weeklyactual": "",
"weeklytarget": "",

View File

@@ -103,6 +103,7 @@
"admin_jobmarkforreexport": "",
"admin_jobuninvoice": "",
"admin_jobunvoid": "",
"alerttoggle": "",
"appointmentcancel": "",
"appointmentinsert": "",
"billposted": "",
@@ -111,6 +112,7 @@
"jobassignmentchange": "",
"jobassignmentremoved": "",
"jobchecklist": "",
"jobinvoiced": "",
"jobconverted": "",
"jobfieldchanged": "",
"jobimported": "",
@@ -203,6 +205,7 @@
"entered_total": "",
"enteringcreditmemo": "",
"federal_tax": "",
"federal_tax_exempt": "",
"generatepartslabel": "",
"iouexists": "",
"local_tax": "",
@@ -332,7 +335,7 @@
"prep": ""
},
"md_functionality_toggles": {
"parts_queue_toggle": "Parts Queue Toggle"
"parts_queue_toggle": ""
},
"md_ins_co": {
"city": "",
@@ -447,6 +450,7 @@
"config": "",
"dashboard": "",
"rbac": "",
"reportcenter": "",
"templates": "",
"vendors": ""
},
@@ -2044,6 +2048,9 @@
"sentby": "",
"typeamessage": "Envoyer un message...",
"unarchive": ""
},
"render": {
"conversation_list": ""
}
},
"notes": {
@@ -2695,6 +2702,7 @@
"efficiencyoverperiod": "",
"entries": "",
"jobs": "",
"jobscompletednotinvoiced": "",
"lastmonth": "",
"lastweek": "",
"monthlytarget": "",
@@ -2709,6 +2717,7 @@
"timetickets": "",
"timeticketsemployee": "",
"todateactual": "",
"totalhrs": "",
"totaloverperiod": "",
"weeklyactual": "",
"weeklytarget": "",

View File

@@ -1,6 +1,7 @@
import i18n from "i18next";
const AuditTrailMapping = {
alertToggle: (status) => i18n.t("audit_trail.messages.alerttoggle", { status }),
appointmentcancel: (lost_sale_reason) =>
i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }),
appointmentinsert: (start) =>
@@ -11,6 +12,8 @@ const AuditTrailMapping = {
"ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", { status }),
jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"),
jobimported: () => i18n.t("audit_trail.messages.jobimported"),
jobinvoiced: () =>
i18n.t("audit_trail.messages.jobinvoiced"),
jobconverted: (ro_number) =>
i18n.t("audit_trail.messages.jobconverted", { ro_number }),
jobfieldchange: (field, value) =>

View File

@@ -2102,6 +2102,17 @@ export const TemplateList = (type, context) => {
// },
}
: {}),
...(!type || type === "messaging"
? {
conversation_list: {
title: i18n.t("messaging.render.conversation_list"),
description: "",
subject: i18n.t("messaging.render.conversation_list"),
key: "conversation_list",
disabled: false,
},
}
: {}),
...(!type || type === "vendor"
? {
purchases_by_vendor_detailed: {

View File

@@ -0,0 +1,4 @@
// Sometimes referred to as PageSize, this variable controls the amount of records
// to show on one page during pagination.
export const pageLimit = 50;

View File

@@ -0,0 +1,2 @@
alter table "public"."courtesycars" alter column "nextservicekm" set not null;
alter table "public"."courtesycars" alter column "nextservicekm" set default '0';

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."courtesycars" ALTER COLUMN "nextservicekm" drop default;
alter table "public"."courtesycars" alter column "nextservicekm" drop not null;

56
libs/awsUtils.js Normal file
View File

@@ -0,0 +1,56 @@
require("dotenv").config({
path: require("path").resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const {isNil} = require('lodash');
const aws4 = require("aws4");
const {Connection, Client} = require("@opensearch-project/opensearch");
const {defaultProvider} = require("@aws-sdk/credential-provider-node");
const createAwsConnector = (credentials, region) => {
class AmazonConnection extends Connection {
buildRequestObject(params) {
const request = super.buildRequestObject(params);
request.service = "es";
request.region = region;
request.headers = request.headers || {};
request.headers["host"] = request.hostname;
return aws4.sign(request, credentials);
}
}
return {
Connection: AmazonConnection,
};
};
const getClient = async () => {
// We have manual configuration for OpenSearch,
// Return a client using these custom credentials
if (
!isNil(process.env.OPEN_SEARCH_PASSWORD) &&
!isNil(process.env.OPEN_SEARCH_USER) &&
!isNil(process.env.OPEN_SEARCH_HOST) &&
!isNil(process.env.OPEN_SEARCH_PROTOCOL)
) {
// The URI is currently being stored in its entirety, so strip protocol prior to rebuilding it.
const hostUrl = process.env.OPEN_SEARCH_HOST.replace(/^https?:\/\//i, '');
const node = `${process.env.OPEN_SEARCH_PROTOCOL}://${process.env.OPEN_SEARCH_USER}:${process.env.OPEN_SEARCH_PASSWORD}@${hostUrl}`;
return new Client({
node,
});
}
// Default to the AWS Credentials Provider.
const credentials = await defaultProvider()();
return new Client({
...createAwsConnector(credentials, "ca-central-1"),
node: process.env.OPEN_SEARCH_HOST,
});
};
module.exports = { getClient };

View File

@@ -1,59 +1,17 @@
const Dinero = require("dinero.js");
//const client = require("../graphql-client/graphql-client").client;
const _ = require("lodash");
const GraphQLClient = require("graphql-request").GraphQLClient;
const logger = require("./server/utils/logger");
const path = require("path");
const client = require("./server/graphql-client/graphql-client").client;
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
path: require("path").resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const { Client, Connection } = require("@opensearch-project/opensearch");
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
const aws4 = require("aws4");
const { gql } = require("graphql-request");
const gqlclient = require("./server/graphql-client/graphql-client").client;
// const osClient = new Client({
// node: `https://imex:Wl0d8k@!@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com/`,
// });
var host = process.env.OPEN_SEARCH_HOST; // e.g. https://my-domain.region.es.amazonaws.com
const createAwsConnector = (credentials, region) => {
class AmazonConnection extends Connection {
buildRequestObject(params) {
const request = super.buildRequestObject(params);
request.service = "es";
request.region = region;
request.headers = request.headers || {};
request.headers["host"] = request.hostname;
return aws4.sign(request, credentials);
}
}
return {
Connection: AmazonConnection,
};
};
const getClient = async () => {
const credentials = await defaultProvider()();
return new Client({
...createAwsConnector(credentials, "ca-central-1"),
node: host,
});
};
const {omit} = require("lodash");
const gqlClient = require("./server/graphql-client/graphql-client").client;
const getClient = require('./libs/awsUtils');
async function OpenSearchUpdateHandler(req, res) {
try {
var osClient = await getClient();
// const osClient = new Client({
// node: `https://imex:password@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com`,
// });
const osClient = await getClient();
//Clear out all current documents
// const deleteResult = await osClient.deleteByQuery({
@@ -67,11 +25,11 @@ async function OpenSearchUpdateHandler(req, res) {
// return;
var batchSize = 1000;
var promiseQueue = [];
const batchSize = 1000;
const promiseQueue = [];
//Jobs Load.
const jobsData = await gqlclient.request(`query{jobs{
const jobsData = await gqlClient.request(`query{jobs{
id
bodyshopid:shopid
clm_no
@@ -105,7 +63,7 @@ async function OpenSearchUpdateHandler(req, res) {
}
//Owner Load
const ownersData = await gqlclient.request(`{
const ownersData = await gqlClient.request(`{
owners {
id
bodyshopid: shopid
@@ -131,7 +89,7 @@ async function OpenSearchUpdateHandler(req, res) {
}
//Vehicles
const vehiclesData = await gqlclient.request(`{
const vehiclesData = await gqlClient.request(`{
vehicles {
id
bodyshopid: shopid
@@ -158,7 +116,7 @@ async function OpenSearchUpdateHandler(req, res) {
}
//payments
const paymentsData = await gqlclient.request(`{
const paymentsData = await gqlClient.request(`{
payments {
id
amount
@@ -198,7 +156,7 @@ async function OpenSearchUpdateHandler(req, res) {
slicedArray.forEach((payment) => {
bulkOperation.push({ index: { _index: "payments", _id: payment.id } });
bulkOperation.push({
..._.omit(payment, ["job"]),
...omit(payment, ["job"]),
bodyshopid: payment.job.bodyshopid,
});
});
@@ -206,7 +164,7 @@ async function OpenSearchUpdateHandler(req, res) {
}
//bills
const billsData = await gqlclient.request(`{
const billsData = await gqlClient.request(`{
bills {
id
date
@@ -235,7 +193,7 @@ async function OpenSearchUpdateHandler(req, res) {
slicedArray.forEach((bill) => {
bulkOperation.push({ index: { _index: "bills", _id: bill.id } });
bulkOperation.push({
..._.omit(bill, ["job"]),
...omit(bill, ["job"]),
bodyshopid: bill.job.bodyshopid,
});
});

View File

@@ -50,7 +50,7 @@ exports.createUser = async (req, res) => {
`,
{
user: {
email,
email: email.toLowerCase(),
authid: userRecord.uid,
associations: {
data: [{ shopid, authlevel, active: true }],

View File

@@ -1,49 +1,18 @@
const queries = require("../graphql-client/queries");
const {pick} = require("lodash");
const GraphQLClient = require("graphql-request").GraphQLClient;
const logger = require("../utils/logger");
//const client = require("../graphql-client/graphql-client").client;
const path = require("path");
const client = require("../graphql-client/graphql-client").client;
require("dotenv").config({
path: path.resolve(
path: require("path").resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const {Client, Connection} = require("@opensearch-project/opensearch");
const {defaultProvider} = require("@aws-sdk/credential-provider-node");
const aws4 = require("aws4");
const {gql} = require("graphql-request");
var host = process.env.OPEN_SEARCH_HOST;
const GraphQLClient = require("graphql-request").GraphQLClient;
//const client = require("../graphql-client/graphql-client").client;
const logger = require("../utils/logger");
const queries = require("../graphql-client/queries");
const client = require("../graphql-client/graphql-client").client;
const {pick, isNil} = require("lodash");
const {getClient} = require('../../libs/awsUtils');
const createAwsConnector = (credentials, region) => {
class AmazonConnection extends Connection {
buildRequestObject(params) {
const request = super.buildRequestObject(params);
request.service = "es";
request.region = region;
request.headers = request.headers || {};
request.headers["host"] = request.hostname;
return aws4.sign(request, credentials);
}
}
return {
Connection: AmazonConnection,
};
};
const getClient = async () => {
const credentials = await defaultProvider()();
return new Client({
...createAwsConnector(credentials, "ca-central-1"),
node: host,
});
};
async function OpenSearchUpdateHandler(req, res) {
if (req.headers["event-secret"] !== process.env.EVENT_SECRET) {
@@ -51,10 +20,8 @@ async function OpenSearchUpdateHandler(req, res) {
return;
}
try {
var osClient = await getClient();
// const osClient = new Client({
// node: `https://imex:<password>@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com/`,
// });
const osClient = await getClient();
if (req.body.event.op === "DELETE") {
let response;
@@ -197,14 +164,12 @@ async function OpenSearchUpdateHandler(req, res) {
body: document,
};
let response;
response = await osClient.index(payload);
const response = await osClient.index(payload);
console.log(response.body);
res.status(200).json(response.body);
}
} catch (error) {
res.status(400).json(JSON.stringify(error));
} finally {
}
}
@@ -240,6 +205,8 @@ async function OpenSearchSearchHandler(req, res) {
const osClient = await getClient();
const bodyShopIdMatchOverride = isNil(process.env.BODY_SHOP_ID_MATCH_OVERRIDE) ? assocs.associations[0].shopid : process.env.BODY_SHOP_ID_MATCH_OVERRIDE
const {body} = await osClient.search({
...(index ? {index} : {}),
body: {
@@ -249,7 +216,7 @@ async function OpenSearchSearchHandler(req, res) {
must: [
{
match: {
bodyshopid: assocs.associations[0].shopid,
bodyshopid: bodyShopIdMatchOverride,
},
},
{
@@ -318,7 +285,6 @@ async function OpenSearchSearchHandler(req, res) {
error: JSON.stringify(error),
});
res.status(400).json(error);
} finally {
}
}