Merged in release/2022-08-26 (pull request #564)
release/2022-08-26 Approved-by: Patrick Fic
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
<babeledit_project be_version="2.7.1" version="1.2">
|
<babeledit_project version="1.2" be_version="2.7.1">
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
BabelEdit project file
|
BabelEdit project file
|
||||||
@@ -1149,6 +1149,48 @@
|
|||||||
<folder_node>
|
<folder_node>
|
||||||
<name>fields</name>
|
<name>fields</name>
|
||||||
<children>
|
<children>
|
||||||
|
<concept_node>
|
||||||
|
<name>cc</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>contents</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>created</name>
|
<name>created</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -1191,6 +1233,48 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>subject</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>to</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>useremail</name>
|
<name>useremail</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -27364,6 +27448,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>emailaudit</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>employeeassignments</name>
|
<name>employeeassignments</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -41258,6 +41363,69 @@
|
|||||||
<folder_node>
|
<folder_node>
|
||||||
<name>labels</name>
|
<name>labels</name>
|
||||||
<children>
|
<children>
|
||||||
|
<concept_node>
|
||||||
|
<name>employeevacation</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>intake</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>manual</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>manualevent</name>
|
<name>manualevent</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { useQuery } from "@apollo/client";
|
|||||||
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
|
import EmailAuditTrailListComponent from "./email-audit-trail-list.component";
|
||||||
|
import { Card, Row } from "antd";
|
||||||
|
|
||||||
export default function AuditTrailListContainer({ recordId }) {
|
export default function AuditTrailListContainer({ recordId }) {
|
||||||
const { loading, error, data } = useQuery(QUERY_AUDIT_TRAIL, {
|
const { loading, error, data } = useQuery(QUERY_AUDIT_TRAIL, {
|
||||||
@@ -18,10 +20,20 @@ export default function AuditTrailListContainer({ recordId }) {
|
|||||||
{error ? (
|
{error ? (
|
||||||
<AlertComponent type="error" message={error.message} />
|
<AlertComponent type="error" message={error.message} />
|
||||||
) : (
|
) : (
|
||||||
<AuditTrailListComponent
|
<Row gutter={[16, 16]}>
|
||||||
loading={loading}
|
<Card>
|
||||||
data={data ? data.audit_trail : null}
|
<AuditTrailListComponent
|
||||||
/>
|
loading={loading}
|
||||||
|
data={data ? data.audit_trail : []}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<EmailAuditTrailListComponent
|
||||||
|
loading={loading}
|
||||||
|
data={data ? data.audit_trail : []}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Row>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Table } from "antd";
|
||||||
|
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";
|
||||||
|
|
||||||
|
export default function EmailAuditTrailListComponent({ loading, data }) {
|
||||||
|
const [state, setState] = useState({
|
||||||
|
sortedInfo: {},
|
||||||
|
filteredInfo: {},
|
||||||
|
});
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("audit.fields.created"),
|
||||||
|
dataIndex: " created",
|
||||||
|
key: " created",
|
||||||
|
width: "10%",
|
||||||
|
render: (text, record) => (
|
||||||
|
<DateTimeFormatter>{record.created}</DateTimeFormatter>
|
||||||
|
),
|
||||||
|
sorter: (a, b) => a.created - b.created,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "created" && state.sortedInfo.order,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("audit.fields.useremail"),
|
||||||
|
dataIndex: "useremail",
|
||||||
|
key: "useremail",
|
||||||
|
width: "10%",
|
||||||
|
sorter: (a, b) => alphaSort(a.useremail, b.useremail),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "useremail" && state.sortedInfo.order,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const formItemLayout = {
|
||||||
|
labelCol: {
|
||||||
|
xs: { span: 12 },
|
||||||
|
sm: { span: 5 },
|
||||||
|
},
|
||||||
|
wrapperCol: {
|
||||||
|
xs: { span: 24 },
|
||||||
|
sm: { span: 12 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
{...formItemLayout}
|
||||||
|
loading={loading}
|
||||||
|
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||||
|
columns={columns}
|
||||||
|
rowKey="id"
|
||||||
|
dataSource={data}
|
||||||
|
onChange={handleTableChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -77,6 +77,9 @@ export function EmailOverlayContainer({
|
|||||||
setSending(true);
|
setSending(true);
|
||||||
try {
|
try {
|
||||||
await axios.post("/sendemail", {
|
await axios.post("/sendemail", {
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
jobid: emailConfig.jobid,
|
||||||
|
|
||||||
...defaultEmailFrom,
|
...defaultEmailFrom,
|
||||||
ReplyTo: {
|
ReplyTo: {
|
||||||
Email: from,
|
Email: from,
|
||||||
@@ -181,10 +184,11 @@ export function EmailOverlayContainer({
|
|||||||
loading: sending,
|
loading: sending,
|
||||||
disabled:
|
disabled:
|
||||||
selectedMedia &&
|
selectedMedia &&
|
||||||
( (selectedMedia
|
(selectedMedia
|
||||||
.filter((s) => s.isSelected)
|
.filter((s) => s.isSelected)
|
||||||
.reduce((acc, val) => (acc = acc + val.size), 0) >=
|
.reduce((acc, val) => (acc = acc + val.size), 0) >=
|
||||||
10485760 - new Blob([form.getFieldValue("html")]).size) || selectedMedia.filter((s) => s.isSelected).length > 10),
|
10485760 - new Blob([form.getFieldValue("html")]).size ||
|
||||||
|
selectedMedia.filter((s) => s.isSelected).length > 10),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form layout="vertical" form={form} onFinish={handleFinish}>
|
<Form layout="vertical" form={form} onFinish={handleFinish}>
|
||||||
|
|||||||
@@ -1,13 +1,24 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { Card, Table } from "antd";
|
import { Button, Card, Col, Row, Table, Tag } from "antd";
|
||||||
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
currentUser: selectCurrentUser,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(JobAuditTrail);
|
||||||
|
|
||||||
export default function JobAuditTrail({ jobId }) {
|
export function JobAuditTrail({ currentUser, jobId }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { loading, data } = useQuery(QUERY_AUDIT_TRAIL, {
|
const { loading, data, refetch } = useQuery(QUERY_AUDIT_TRAIL, {
|
||||||
variables: { jobid: jobId },
|
variables: { jobid: jobId },
|
||||||
skip: !jobId,
|
skip: !jobId,
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
@@ -34,15 +45,102 @@ export default function JobAuditTrail({ jobId }) {
|
|||||||
key: "operation",
|
key: "operation",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
const emailColumns = [
|
||||||
|
{
|
||||||
|
title: t("audit.fields.created"),
|
||||||
|
dataIndex: " created_at",
|
||||||
|
key: " created_at",
|
||||||
|
|
||||||
|
render: (text, record) => (
|
||||||
|
<DateTimeFormatter>{record.created_at}</DateTimeFormatter>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("audit.fields.useremail"),
|
||||||
|
dataIndex: "useremail",
|
||||||
|
key: "useremail",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("audit.fields.to"),
|
||||||
|
dataIndex: "to",
|
||||||
|
key: "to",
|
||||||
|
|
||||||
|
render: (text, record) =>
|
||||||
|
record.to &&
|
||||||
|
record.to.map((email, idx) => <Tag key={idx}>{email}</Tag>),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("audit.fields.cc"),
|
||||||
|
dataIndex: "cc",
|
||||||
|
key: "cc",
|
||||||
|
|
||||||
|
render: (text, record) =>
|
||||||
|
record.cc &&
|
||||||
|
record.cc.map((email, idx) => <Tag key={idx}>{email}</Tag>),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("audit.fields.subject"),
|
||||||
|
dataIndex: "subject",
|
||||||
|
key: "subject",
|
||||||
|
},
|
||||||
|
...(currentUser?.email.includes("@imex.") && [
|
||||||
|
{
|
||||||
|
title: t("audit.fields.contents"),
|
||||||
|
dataIndex: "contents",
|
||||||
|
key: "contents",
|
||||||
|
width: "10%",
|
||||||
|
render: (text, record) => (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
var win = window.open(
|
||||||
|
"",
|
||||||
|
"Title",
|
||||||
|
"toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=780,height=400,"
|
||||||
|
);
|
||||||
|
win.document.body.innerHTML = record.contents;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Preview
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<Card title={t("jobs.labels.audit")}>
|
<Row gutter={[16, 16]}>
|
||||||
<Table
|
<Col span={24}>
|
||||||
loading={loading}
|
<Card
|
||||||
columns={columns}
|
title={t("jobs.labels.audit")}
|
||||||
rowKey="id"
|
extra={
|
||||||
dataSource={data ? data.audit_trail : []}
|
<Button
|
||||||
/>
|
onClick={() => {
|
||||||
</Card>
|
refetch();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SyncOutlined />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
loading={loading}
|
||||||
|
columns={columns}
|
||||||
|
rowKey="id"
|
||||||
|
dataSource={data ? data.audit_trail : []}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Card title={t("jobs.labels.emailaudit")}>
|
||||||
|
<Table
|
||||||
|
loading={loading}
|
||||||
|
columns={emailColumns}
|
||||||
|
rowKey="id"
|
||||||
|
dataSource={data ? data.email_audit_trail : []}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
import { useMutation } from "@apollo/client";
|
||||||
|
import { Button, Card, Form, Input, Menu, Popover, Select, Space } from "antd";
|
||||||
|
import moment from "moment";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
|
import { INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(JobsDetailHeaderAddEvent);
|
||||||
|
|
||||||
|
export function JobsDetailHeaderAddEvent({ bodyshop, jobid, ...props }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [insertAppointment] = useMutation(INSERT_MANUAL_APPT);
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [visibility, setVisibility] = useState(false);
|
||||||
|
|
||||||
|
const handleFinish = async (values) => {
|
||||||
|
logImEXEvent("schedule_manual_event");
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
insertAppointment({
|
||||||
|
variables: {
|
||||||
|
apt: { ...values, isintake: false, jobid, bodyshopid: bodyshop.id },
|
||||||
|
},
|
||||||
|
refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"],
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
setVisibility(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const overlay = (
|
||||||
|
<Card>
|
||||||
|
<div>
|
||||||
|
<Form form={form} layout="vertical" onFinish={handleFinish}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("appointments.fields.title")}
|
||||||
|
name="title"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("appointments.fields.note")} name="note">
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("appointments.fields.start")}
|
||||||
|
name="start"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<FormDateTimePickerComponent
|
||||||
|
onBlur={() => {
|
||||||
|
const start = form.getFieldValue("start");
|
||||||
|
form.setFieldsValue({ end: start.add(30, "minutes") });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("appointments.fields.end")}
|
||||||
|
name="end"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
({ getFieldValue }) => ({
|
||||||
|
async validator(rule, value) {
|
||||||
|
if (value) {
|
||||||
|
const { start } = form.getFieldsValue();
|
||||||
|
if (moment(start).isAfter(moment(value))) {
|
||||||
|
return Promise.reject(
|
||||||
|
t("employees.labels.endmustbeafterstart")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<FormDateTimePickerComponent />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("appointments.fields.color")} name="color">
|
||||||
|
<Select>
|
||||||
|
{bodyshop.appt_colors.map((col, idx) => (
|
||||||
|
<Select.Option key={idx} value={col.color.hex}>
|
||||||
|
{col.label}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Space wrap>
|
||||||
|
<Button type="primary" htmlType="submit" loading={loading}>
|
||||||
|
{t("general.actions.save")}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => setVisibility(false)}>
|
||||||
|
{t("general.actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClick = (e) => {
|
||||||
|
setVisibility(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover content={overlay} visible={visibility}>
|
||||||
|
<Menu.Item {...props} onClick={handleClick}>
|
||||||
|
{t("appointments.labels.manualevent")}
|
||||||
|
</Menu.Item>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
|
import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent";
|
||||||
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
||||||
import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component";
|
import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component";
|
||||||
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
|
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
|
||||||
@@ -421,6 +422,7 @@ export function JobsDetailHeaderActions({
|
|||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
)}
|
)}
|
||||||
|
<JobsDetailHeaderActionsAddevent jobid={job.id} />
|
||||||
{!jobRO && job.converted && (
|
{!jobRO && job.converted && (
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { EditFilled } from "@ant-design/icons";
|
import { EditFilled } from "@ant-design/icons";
|
||||||
import { Card, Col, Row, Space, Table } from "antd";
|
import { Card, Col, Row, Space, Table, Typography } from "antd";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -188,6 +188,19 @@ export function LaborAllocationsTable({
|
|||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const summary =
|
||||||
|
totals &&
|
||||||
|
totals.reduce(
|
||||||
|
(acc, val) => {
|
||||||
|
acc.hrs_total += val.total;
|
||||||
|
acc.hrs_claimed += val.claimed;
|
||||||
|
acc.adjustments += val.adjustments;
|
||||||
|
acc.difference += val.difference;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
@@ -201,6 +214,27 @@ export function LaborAllocationsTable({
|
|||||||
scroll={{
|
scroll={{
|
||||||
x: true,
|
x: true,
|
||||||
}}
|
}}
|
||||||
|
summary={() => (
|
||||||
|
<Table.Summary.Row>
|
||||||
|
<Table.Summary.Cell>
|
||||||
|
<Typography.Title level={4}>
|
||||||
|
{t("general.labels.totals")}
|
||||||
|
</Typography.Title>
|
||||||
|
</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell>
|
||||||
|
{summary.hrs_total.toFixed(1)}
|
||||||
|
</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell>
|
||||||
|
{summary.hrs_claimed.toFixed(1)}
|
||||||
|
</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell>
|
||||||
|
{summary.adjustments.toFixed(1)}
|
||||||
|
</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell>
|
||||||
|
{summary.difference.toFixed(1)}
|
||||||
|
</Table.Summary.Cell>
|
||||||
|
</Table.Summary.Row>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const CalculateAllocationsTotals = (
|
|||||||
}, 0),
|
}, 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
r.difference = (r.total + r.adjustments - r.claimed).toFixed(2);
|
r.difference = r.total + r.adjustments - r.claimed;
|
||||||
acc.push(r);
|
acc.push(r);
|
||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Card, Col, PageHeader, Row, Space } from "antd";
|
import { Button, Card, Checkbox, Col, PageHeader, Row, Space } from "antd";
|
||||||
import React from "react";
|
import { t } from "i18next";
|
||||||
|
import React, { useMemo } from "react";
|
||||||
|
import useLocalStorage from "../../utils/useLocalStorage";
|
||||||
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
|
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
|
||||||
import ScheduleModal from "../schedule-job-modal/schedule-job-modal.container";
|
import ScheduleModal from "../schedule-job-modal/schedule-job-modal.container";
|
||||||
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
||||||
@@ -8,6 +10,23 @@ import ScheduleProductionList from "../schedule-production-list/schedule-product
|
|||||||
import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verify-integrity.component";
|
import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verify-integrity.component";
|
||||||
|
|
||||||
export default function ScheduleCalendarComponent({ data, refetch }) {
|
export default function ScheduleCalendarComponent({ data, refetch }) {
|
||||||
|
const [filter, setFilter] = useLocalStorage("filter_events", {
|
||||||
|
intake: true,
|
||||||
|
manual: true,
|
||||||
|
employeevacation: true,
|
||||||
|
});
|
||||||
|
const filteredData = useMemo(() => {
|
||||||
|
return data.filter(
|
||||||
|
(d) =>
|
||||||
|
d.block ||
|
||||||
|
(filter.intake && d.isintake) ||
|
||||||
|
(filter.manual && !d.isintake && d.block === false) ||
|
||||||
|
(d.__typename === "employee_vacation" &&
|
||||||
|
filter.employeevacation &&
|
||||||
|
!!d.employee)
|
||||||
|
);
|
||||||
|
}, [data, filter]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
<ScheduleModal />
|
<ScheduleModal />
|
||||||
@@ -16,6 +35,30 @@ export default function ScheduleCalendarComponent({ data, refetch }) {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
<Checkbox
|
||||||
|
checked={filter?.intake}
|
||||||
|
onChange={(e) => {
|
||||||
|
setFilter({ ...filter, intake: e.target.checked });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("schedule.labels.intake")}
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
checked={filter?.manual}
|
||||||
|
onChange={(e) => {
|
||||||
|
setFilter({ ...filter, manual: e.target.checked });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("schedule.labels.manual")}
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
checked={filter?.employeevacation}
|
||||||
|
onChange={(e) => {
|
||||||
|
setFilter({ ...filter, employeevacation: e.target.checked });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("schedule.labels.employeevacation")}
|
||||||
|
</Checkbox>
|
||||||
<ScheduleVerifyIntegrity />
|
<ScheduleVerifyIntegrity />
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -35,7 +78,7 @@ export default function ScheduleCalendarComponent({ data, refetch }) {
|
|||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Card>
|
<Card>
|
||||||
<ScheduleCalendarWrapperComponent
|
<ScheduleCalendarWrapperComponent
|
||||||
data={data}
|
data={filteredData}
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
style={{ height: "100rem" }}
|
style={{ height: "100rem" }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -13,6 +13,20 @@ export const QUERY_AUDIT_TRAIL = gql`
|
|||||||
created
|
created
|
||||||
bodyshopid
|
bodyshopid
|
||||||
}
|
}
|
||||||
|
email_audit_trail(
|
||||||
|
where: { jobid: { _eq: $jobid } }
|
||||||
|
order_by: { created_at: desc }
|
||||||
|
) {
|
||||||
|
cc
|
||||||
|
contents
|
||||||
|
created_at
|
||||||
|
id
|
||||||
|
jobid
|
||||||
|
noteid
|
||||||
|
subject
|
||||||
|
to
|
||||||
|
useremail
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -82,8 +82,12 @@
|
|||||||
},
|
},
|
||||||
"audit": {
|
"audit": {
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"cc": "CC",
|
||||||
|
"contents": "Contents",
|
||||||
"created": "Time",
|
"created": "Time",
|
||||||
"operation": "Operation",
|
"operation": "Operation",
|
||||||
|
"subject": "Subject",
|
||||||
|
"to": "To",
|
||||||
"useremail": "User",
|
"useremail": "User",
|
||||||
"values": "Values"
|
"values": "Values"
|
||||||
}
|
}
|
||||||
@@ -1614,6 +1618,7 @@
|
|||||||
"documents-images": "Images",
|
"documents-images": "Images",
|
||||||
"documents-other": "Other Documents",
|
"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",
|
"employeeassignments": "Employee Assignments",
|
||||||
"estimatelines": "Estimate Lines",
|
"estimatelines": "Estimate Lines",
|
||||||
"estimator": "Estimator",
|
"estimator": "Estimator",
|
||||||
@@ -2443,6 +2448,9 @@
|
|||||||
},
|
},
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"employeevacation": "Employee Vacations",
|
||||||
|
"intake": "Intake Events",
|
||||||
|
"manual": "Manual Events",
|
||||||
"manualevent": "Add Manual Event"
|
"manualevent": "Add Manual Event"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -82,8 +82,12 @@
|
|||||||
},
|
},
|
||||||
"audit": {
|
"audit": {
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"cc": "",
|
||||||
|
"contents": "",
|
||||||
"created": "",
|
"created": "",
|
||||||
"operation": "",
|
"operation": "",
|
||||||
|
"subject": "",
|
||||||
|
"to": "",
|
||||||
"useremail": "",
|
"useremail": "",
|
||||||
"values": ""
|
"values": ""
|
||||||
}
|
}
|
||||||
@@ -1614,6 +1618,7 @@
|
|||||||
"documents-images": "",
|
"documents-images": "",
|
||||||
"documents-other": "",
|
"documents-other": "",
|
||||||
"duplicateconfirm": "",
|
"duplicateconfirm": "",
|
||||||
|
"emailaudit": "",
|
||||||
"employeeassignments": "",
|
"employeeassignments": "",
|
||||||
"estimatelines": "",
|
"estimatelines": "",
|
||||||
"estimator": "",
|
"estimator": "",
|
||||||
@@ -2443,6 +2448,9 @@
|
|||||||
},
|
},
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"employeevacation": "",
|
||||||
|
"intake": "",
|
||||||
|
"manual": "",
|
||||||
"manualevent": ""
|
"manualevent": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -82,8 +82,12 @@
|
|||||||
},
|
},
|
||||||
"audit": {
|
"audit": {
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"cc": "",
|
||||||
|
"contents": "",
|
||||||
"created": "",
|
"created": "",
|
||||||
"operation": "",
|
"operation": "",
|
||||||
|
"subject": "",
|
||||||
|
"to": "",
|
||||||
"useremail": "",
|
"useremail": "",
|
||||||
"values": ""
|
"values": ""
|
||||||
}
|
}
|
||||||
@@ -1614,6 +1618,7 @@
|
|||||||
"documents-images": "",
|
"documents-images": "",
|
||||||
"documents-other": "",
|
"documents-other": "",
|
||||||
"duplicateconfirm": "",
|
"duplicateconfirm": "",
|
||||||
|
"emailaudit": "",
|
||||||
"employeeassignments": "",
|
"employeeassignments": "",
|
||||||
"estimatelines": "",
|
"estimatelines": "",
|
||||||
"estimator": "",
|
"estimator": "",
|
||||||
@@ -2443,6 +2448,9 @@
|
|||||||
},
|
},
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"employeevacation": "",
|
||||||
|
"intake": "",
|
||||||
|
"manual": "",
|
||||||
"manualevent": ""
|
"manualevent": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -752,6 +752,13 @@
|
|||||||
table:
|
table:
|
||||||
schema: public
|
schema: public
|
||||||
name: documents
|
name: documents
|
||||||
|
- name: email_audit_trails
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: bodyshopid
|
||||||
|
table:
|
||||||
|
schema: public
|
||||||
|
name: email_audit_trail
|
||||||
- name: employees
|
- name: employees
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
@@ -1827,6 +1834,93 @@
|
|||||||
_eq: X-Hasura-User-Id
|
_eq: X-Hasura-User-Id
|
||||||
- active:
|
- active:
|
||||||
_eq: true
|
_eq: true
|
||||||
|
- table:
|
||||||
|
schema: public
|
||||||
|
name: email_audit_trail
|
||||||
|
object_relationships:
|
||||||
|
- name: bodyshop
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: bodyshopid
|
||||||
|
- name: job
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: jobid
|
||||||
|
- name: user
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: useremail
|
||||||
|
insert_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
check:
|
||||||
|
bodyshop:
|
||||||
|
associations:
|
||||||
|
_and:
|
||||||
|
- user:
|
||||||
|
authid:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
- active:
|
||||||
|
_eq: true
|
||||||
|
columns:
|
||||||
|
- cc
|
||||||
|
- to
|
||||||
|
- contents
|
||||||
|
- subject
|
||||||
|
- useremail
|
||||||
|
- created_at
|
||||||
|
- updated_at
|
||||||
|
- bodyshopid
|
||||||
|
- id
|
||||||
|
- jobid
|
||||||
|
- noteid
|
||||||
|
backend_only: false
|
||||||
|
select_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- cc
|
||||||
|
- to
|
||||||
|
- contents
|
||||||
|
- subject
|
||||||
|
- useremail
|
||||||
|
- created_at
|
||||||
|
- updated_at
|
||||||
|
- bodyshopid
|
||||||
|
- id
|
||||||
|
- jobid
|
||||||
|
- noteid
|
||||||
|
filter:
|
||||||
|
bodyshop:
|
||||||
|
associations:
|
||||||
|
_and:
|
||||||
|
- user:
|
||||||
|
authid:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
- active:
|
||||||
|
_eq: true
|
||||||
|
update_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- cc
|
||||||
|
- to
|
||||||
|
- contents
|
||||||
|
- subject
|
||||||
|
- useremail
|
||||||
|
- created_at
|
||||||
|
- updated_at
|
||||||
|
- bodyshopid
|
||||||
|
- id
|
||||||
|
- jobid
|
||||||
|
- noteid
|
||||||
|
filter:
|
||||||
|
bodyshop:
|
||||||
|
associations:
|
||||||
|
_and:
|
||||||
|
- user:
|
||||||
|
authid:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
- active:
|
||||||
|
_eq: true
|
||||||
|
check: null
|
||||||
- table:
|
- table:
|
||||||
schema: public
|
schema: public
|
||||||
name: employee_vacation
|
name: employee_vacation
|
||||||
@@ -2726,6 +2820,13 @@
|
|||||||
table:
|
table:
|
||||||
schema: public
|
schema: public
|
||||||
name: documents
|
name: documents
|
||||||
|
- name: email_audit_trails
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: jobid
|
||||||
|
table:
|
||||||
|
schema: public
|
||||||
|
name: email_audit_trail
|
||||||
- name: exportlogs
|
- name: exportlogs
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
@@ -4976,6 +5077,13 @@
|
|||||||
table:
|
table:
|
||||||
schema: public
|
schema: public
|
||||||
name: audit_trail
|
name: audit_trail
|
||||||
|
- name: email_audit_trails
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: useremail
|
||||||
|
table:
|
||||||
|
schema: public
|
||||||
|
name: email_audit_trail
|
||||||
- name: exportlogs
|
- name: exportlogs
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE "public"."email_audit_trail";
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
CREATE TABLE "public"."email_audit_trail" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "bodyshopid" uuid NOT NULL, "jobid" uuid, "noteid" uuid, "to" jsonb NOT NULL DEFAULT jsonb_build_array(), "cc" jsonb NOT NULL DEFAULT jsonb_build_array(), "subject" text, "contents" text, "useremail" text NOT NULL, PRIMARY KEY ("id") );
|
||||||
|
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
DECLARE
|
||||||
|
_new record;
|
||||||
|
BEGIN
|
||||||
|
_new := NEW;
|
||||||
|
_new."updated_at" = NOW();
|
||||||
|
RETURN _new;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
CREATE TRIGGER "set_public_email_audit_trail_updated_at"
|
||||||
|
BEFORE UPDATE ON "public"."email_audit_trail"
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
|
||||||
|
COMMENT ON TRIGGER "set_public_email_audit_trail_updated_at" ON "public"."email_audit_trail"
|
||||||
|
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
|
||||||
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE "public"."email_audit_trail";
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
CREATE TABLE "public"."email_audit_trail" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "bodyshopid" uuid NOT NULL, "jobid" uuid, "noteid" uuid, "to" jsonb NOT NULL DEFAULT jsonb_build_array(), "cc" jsonb NOT NULL DEFAULT jsonb_build_array(), "subject" text, "contents" text, "useremail" text NOT NULL, PRIMARY KEY ("id") );
|
||||||
|
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
DECLARE
|
||||||
|
_new record;
|
||||||
|
BEGIN
|
||||||
|
_new := NEW;
|
||||||
|
_new."updated_at" = NOW();
|
||||||
|
RETURN _new;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
CREATE TRIGGER "set_public_email_audit_trail_updated_at"
|
||||||
|
BEFORE UPDATE ON "public"."email_audit_trail"
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
|
||||||
|
COMMENT ON TRIGGER "set_public_email_audit_trail_updated_at" ON "public"."email_audit_trail"
|
||||||
|
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
|
||||||
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table "public"."email_audit_trail" drop constraint "email_audit_trail_bodyshopid_fkey";
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
alter table "public"."email_audit_trail"
|
||||||
|
add constraint "email_audit_trail_bodyshopid_fkey"
|
||||||
|
foreign key ("bodyshopid")
|
||||||
|
references "public"."bodyshops"
|
||||||
|
("id") on update cascade on delete cascade;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table "public"."email_audit_trail" drop constraint "email_audit_trail_jobid_fkey";
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
alter table "public"."email_audit_trail"
|
||||||
|
add constraint "email_audit_trail_jobid_fkey"
|
||||||
|
foreign key ("jobid")
|
||||||
|
references "public"."jobs"
|
||||||
|
("id") on update cascade on delete cascade;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table "public"."email_audit_trail" drop constraint "email_audit_trail_useremail_fkey";
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
alter table "public"."email_audit_trail"
|
||||||
|
add constraint "email_audit_trail_useremail_fkey"
|
||||||
|
foreign key ("useremail")
|
||||||
|
references "public"."users"
|
||||||
|
("email") on update cascade on delete cascade;
|
||||||
@@ -414,7 +414,11 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) {
|
|||||||
});
|
});
|
||||||
setNewRefreshToken(req.user.email, items);
|
setNewRefreshToken(req.user.email, items);
|
||||||
const taxCodes = await oauthClient.makeApiCall({
|
const taxCodes = await oauthClient.makeApiCall({
|
||||||
url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`),
|
url: urlBuilder(
|
||||||
|
qbo_realmId,
|
||||||
|
"query",
|
||||||
|
`select * From TaxCode where active=true`
|
||||||
|
),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|||||||
@@ -57,10 +57,10 @@ exports.default = async (req, res) => {
|
|||||||
TransmitDateTime: moment().format(momentFormat), // Omitted from ARMS docs
|
TransmitDateTime: moment().format(momentFormat), // Omitted from ARMS docs
|
||||||
},
|
},
|
||||||
EventInfo: {
|
EventInfo: {
|
||||||
// AssignmentEvent: {
|
AssignmentEvent: {
|
||||||
// CreateDateTime:
|
CreateDateTime:
|
||||||
// job.asgn_date && moment(job.asgn_date).format(momentFormat),
|
job.asgn_date && moment(job.asgn_date).format(momentFormat),
|
||||||
// },
|
},
|
||||||
// EstimateEvent: {
|
// EstimateEvent: {
|
||||||
// UploadDateTime: moment().format(momentFormat),
|
// UploadDateTime: moment().format(momentFormat),
|
||||||
// },
|
// },
|
||||||
@@ -258,9 +258,9 @@ exports.default = async (req, res) => {
|
|||||||
VINNum: job.v_vin,
|
VINNum: job.v_vin,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// License: {
|
License: {
|
||||||
// LicensePlateNum: job.plate_no,
|
LicensePlateNum: job.plate_no,
|
||||||
// },
|
},
|
||||||
VehicleDesc: {
|
VehicleDesc: {
|
||||||
//ProductionDate: "2009-10",
|
//ProductionDate: "2009-10",
|
||||||
ModelYear:
|
ModelYear:
|
||||||
@@ -845,7 +845,7 @@ exports.default = async (req, res) => {
|
|||||||
|
|
||||||
const entegralResponse =
|
const entegralResponse =
|
||||||
await entegralSoapClient.RepairOrderFolderAddRqAsync(
|
await entegralSoapClient.RepairOrderFolderAddRqAsync(
|
||||||
jobsToPush,
|
[jobsToPush[0]],
|
||||||
function (err, result, rawResponse, soapHeader, rawRequest) {
|
function (err, result, rawResponse, soapHeader, rawRequest) {
|
||||||
fs.writeFileSync(`./logs/arms-request.xml`, rawRequest);
|
fs.writeFileSync(`./logs/arms-request.xml`, rawRequest);
|
||||||
fs.writeFileSync(`./logs/arms-response.xml`, rawResponse);
|
fs.writeFileSync(`./logs/arms-response.xml`, rawResponse);
|
||||||
@@ -910,10 +910,13 @@ function GetDocumentstatus(job, bodyshop) {
|
|||||||
function GetRepairStatusCode(job) {
|
function GetRepairStatusCode(job) {
|
||||||
return "25";
|
return "25";
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetProductionStageCode(job, bodyshop) {
|
function GetProductionStageCode(job, bodyshop) {
|
||||||
if (bodyshop.md_ro_statuses.post_production_statuses.includes(job.status))
|
const result = (bodyshop.features?.entegral).find(
|
||||||
return "8D";
|
(k) => k.status === job.status
|
||||||
return "33";
|
);
|
||||||
|
|
||||||
|
return result?.code || "33";
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEmpty(obj) {
|
function isEmpty(obj) {
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ const axios = require("axios");
|
|||||||
let nodemailer = require("nodemailer");
|
let nodemailer = require("nodemailer");
|
||||||
let aws = require("aws-sdk");
|
let aws = require("aws-sdk");
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
|
const queries = require("../graphql-client/queries");
|
||||||
|
|
||||||
const ses = new aws.SES({
|
const ses = new aws.SES({
|
||||||
apiVersion: "latest",
|
apiVersion: "latest",
|
||||||
|
|
||||||
@@ -141,7 +144,11 @@ exports.sendEmail = async (req, res) => {
|
|||||||
subject: req.body.subject,
|
subject: req.body.subject,
|
||||||
// info,
|
// info,
|
||||||
});
|
});
|
||||||
|
logEmail(req, {
|
||||||
|
to: req.body.to,
|
||||||
|
cc: req.body.cc,
|
||||||
|
subject: req.body.subject,
|
||||||
|
});
|
||||||
res.json({
|
res.json({
|
||||||
success: true, //response: info
|
success: true, //response: info
|
||||||
});
|
});
|
||||||
@@ -154,7 +161,12 @@ exports.sendEmail = async (req, res) => {
|
|||||||
subject: req.body.subject,
|
subject: req.body.subject,
|
||||||
error: err,
|
error: err,
|
||||||
});
|
});
|
||||||
|
logEmail(req, {
|
||||||
|
to: req.body.to,
|
||||||
|
cc: req.body.cc,
|
||||||
|
subject: req.body.subject,
|
||||||
|
bodyshopid: req.body.bodyshopid,
|
||||||
|
});
|
||||||
res.status(500).json({ success: false, error: err });
|
res.status(500).json({ success: false, error: err });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,3 +178,17 @@ async function getImage(imageUrl) {
|
|||||||
let raw = Buffer.from(image.data).toString("base64");
|
let raw = Buffer.from(image.data).toString("base64");
|
||||||
return "data:" + image.headers["content-type"] + ";base64," + raw;
|
return "data:" + image.headers["content-type"] + ";base64," + raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function logEmail(req, email) {
|
||||||
|
await client.request(queries.INSERT_EMAIL_AUDIT, {
|
||||||
|
email: {
|
||||||
|
to: email.to,
|
||||||
|
cc: email.cc,
|
||||||
|
subject: email.subject,
|
||||||
|
bodyshopid: req.body.bodyshopid,
|
||||||
|
useremail: req.user.email,
|
||||||
|
contents: req.body.html,
|
||||||
|
jobid: req.body.jobid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1322,6 +1322,8 @@ exports.GET_ENTEGRAL_SHOPS = `query GET_AUTOHOUSE_SHOPS {
|
|||||||
entegral_id
|
entegral_id
|
||||||
md_responsibility_centers
|
md_responsibility_centers
|
||||||
imexshopid
|
imexshopid
|
||||||
|
timezone
|
||||||
|
features
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -1602,3 +1604,10 @@ exports.INSERT_EXPORT_LOG = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports.INSERT_EMAIL_AUDIT = `mutation INSERT_EMAIL_AUDIT($email: email_audit_trail_insert_input!) {
|
||||||
|
insert_email_audit_trail_one(object: $email) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user