@@ -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
|
||||||
@@ -1233,6 +1233,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>status</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>subject</name>
|
<name>subject</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -13447,6 +13468,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>deleting</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>deleting_cloudinary</name>
|
<name>deleting_cloudinary</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -24536,6 +24578,53 @@
|
|||||||
</concept_node>
|
</concept_node>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
|
<folder_node>
|
||||||
|
<name>qb_multiple_payers</name>
|
||||||
|
<children>
|
||||||
|
<concept_node>
|
||||||
|
<name>amount</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>name</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
</children>
|
||||||
|
</folder_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>queued_for_parts</name>
|
<name>queued_for_parts</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import { Table } from "antd";
|
import { Table } from "antd";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import React, { useState } from "react";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import AuditTrailValuesComponent from "../audit-trail-values/audit-trail-values.component";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
|
||||||
export default function EmailAuditTrailListComponent({ loading, data }) {
|
export default function EmailAuditTrailListComponent({ loading, data }) {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
|
|||||||
@@ -85,6 +85,11 @@ export function JobAuditTrail({ currentUser, jobId }) {
|
|||||||
dataIndex: "subject",
|
dataIndex: "subject",
|
||||||
key: "subject",
|
key: "subject",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t("audit.fields.status"),
|
||||||
|
dataIndex: "status",
|
||||||
|
key: "status",
|
||||||
|
},
|
||||||
...(currentUser?.email.includes("@imex.")
|
...(currentUser?.email.includes("@imex.")
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
import { useApolloClient, useMutation } from "@apollo/client";
|
import { useApolloClient } from "@apollo/client";
|
||||||
import { Button, Form, notification, Popover, Space } from "antd";
|
import { Button, Form, notification, Popover, Space } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import {
|
import { GET_DOC_SIZE_BY_JOB } from "../../graphql/documents.queries";
|
||||||
GET_DOC_SIZE_BY_JOB,
|
|
||||||
UPDATE_DOCUMENT,
|
|
||||||
} from "../../graphql/documents.queries";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||||
|
|
||||||
@@ -23,7 +20,11 @@ export default connect(
|
|||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(JobsDocumentsGalleryReassign);
|
)(JobsDocumentsGalleryReassign);
|
||||||
|
|
||||||
export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages }) {
|
export function JobsDocumentsGalleryReassign({
|
||||||
|
bodyshop,
|
||||||
|
galleryImages,
|
||||||
|
callback,
|
||||||
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
@@ -36,34 +37,33 @@ export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages }) {
|
|||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [updateDocument] = useMutation(UPDATE_DOCUMENT);
|
|
||||||
|
|
||||||
const updateImage = async (i, jobid) => {
|
// const updateImage = async (i, jobid) => {
|
||||||
//Move the cloudinary image
|
// //Move the cloudinary image
|
||||||
|
|
||||||
//Update it in the database.
|
// //Update it in the database.
|
||||||
const result = await updateDocument({
|
// const result = await updateDocument({
|
||||||
variables: {
|
// variables: {
|
||||||
id: i.id,
|
// id: i.id,
|
||||||
document: {
|
// document: {
|
||||||
key: i.public_id,
|
// key: i.public_id,
|
||||||
jobid: jobid,
|
// jobid: jobid,
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (!!result.errors) {
|
// if (!!result.errors) {
|
||||||
notification["error"]({
|
// notification["error"]({
|
||||||
message: t("documents.errors.updating", {
|
// message: t("documents.errors.updating", {
|
||||||
message: JSON.stringify(result.errors),
|
// message: JSON.stringify(result.errors),
|
||||||
}),
|
// }),
|
||||||
});
|
// });
|
||||||
} else {
|
// } else {
|
||||||
notification["success"]({
|
// notification["success"]({
|
||||||
message: t("documents.successes.updated"),
|
// message: t("documents.successes.updated"),
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handleFinish = async ({ jobid }) => {
|
const handleFinish = async ({ jobid }) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -96,6 +96,7 @@ export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const res = await axios.post("/media/rename", {
|
const res = await axios.post("/media/rename", {
|
||||||
|
tojobid: jobid,
|
||||||
documents: selectedImages.map((i) => {
|
documents: selectedImages.map((i) => {
|
||||||
//Need to check if the current key folder is null, or another job.
|
//Need to check if the current key folder is null, or another job.
|
||||||
const currentKeys = i.key.split("/");
|
const currentKeys = i.key.split("/");
|
||||||
@@ -110,24 +111,21 @@ export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages }) {
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
//Add in confirmation & errors.
|
||||||
|
if (callback) callback();
|
||||||
|
|
||||||
res.data
|
if (res.errors) {
|
||||||
.filter((d) => d.error)
|
notification["error"]({
|
||||||
.forEach((d) => {
|
message: t("documents.errors.updating", {
|
||||||
notification["error"]({ message: t("documents.errors.updating") });
|
message: JSON.stringify(res.errors),
|
||||||
console.error("Error updating job document", d);
|
}),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
const proms = [];
|
if (!res.mutationResult?.errors) {
|
||||||
|
notification["success"]({
|
||||||
res.data
|
message: t("documents.successes.updated"),
|
||||||
.filter((d) => !d.error)
|
|
||||||
.forEach((d) => {
|
|
||||||
proms.push(updateImage(d, jobid));
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
await Promise.all(proms);
|
|
||||||
|
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -125,7 +125,10 @@ function JobsDocumentsComponent({
|
|||||||
deletionCallback={billsCallback || refetch}
|
deletionCallback={billsCallback || refetch}
|
||||||
/>
|
/>
|
||||||
{!billId && (
|
{!billId && (
|
||||||
<JobsDocumentsGalleryReassign galleryImages={galleryImages} />
|
<JobsDocumentsGalleryReassign
|
||||||
|
galleryImages={galleryImages}
|
||||||
|
callback={refetch}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { QuestionCircleOutlined } from "@ant-design/icons";
|
import { QuestionCircleOutlined } from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
|
||||||
import { Button, notification, Popconfirm } from "antd";
|
import { Button, notification, Popconfirm } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { DELETE_DOCUMENTS } from "../../graphql/documents.queries";
|
|
||||||
//Context: currentUserEmail, bodyshop, jobid, invoiceid
|
//Context: currentUserEmail, bodyshop, jobid, invoiceid
|
||||||
|
|
||||||
export default function JobsDocumentsDeleteButton({
|
export default function JobsDocumentsDeleteButton({
|
||||||
@@ -13,7 +11,7 @@ export default function JobsDocumentsDeleteButton({
|
|||||||
deletionCallback,
|
deletionCallback,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [deleteDocument] = useMutation(DELETE_DOCUMENTS);
|
|
||||||
const imagesToDelete = [
|
const imagesToDelete = [
|
||||||
...galleryImages.images.filter((image) => image.isSelected),
|
...galleryImages.images.filter((image) => image.isSelected),
|
||||||
...galleryImages.other.filter((image) => image.isSelected),
|
...galleryImages.other.filter((image) => image.isSelected),
|
||||||
@@ -27,31 +25,10 @@ export default function JobsDocumentsDeleteButton({
|
|||||||
ids: imagesToDelete,
|
ids: imagesToDelete,
|
||||||
});
|
});
|
||||||
|
|
||||||
const successfulDeletes = [];
|
if (res.data.error) {
|
||||||
res.data.forEach((resType) => {
|
|
||||||
Object.keys(resType.deleted).forEach((key) => {
|
|
||||||
if (resType.deleted[key] !== "deleted") {
|
|
||||||
notification["error"]({
|
|
||||||
message: t("documents.errors.deleting_cloudinary", {
|
|
||||||
message: JSON.stringify(resType.deleted[key]),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
successfulDeletes.push(key.replace(/\.[^/.]+$/, ""));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const delres = await deleteDocument({
|
|
||||||
variables: {
|
|
||||||
ids: imagesToDelete
|
|
||||||
.filter((i) => successfulDeletes.includes(i.key))
|
|
||||||
.map((i) => i.id),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (delres.errors) {
|
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("documents.errors.deleting", {
|
message: t("documents.errors.deleting", {
|
||||||
message: JSON.stringify(delres.errors),
|
error: JSON.stringify(res.data.error.response.errors),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { gql, useMutation } from "@apollo/client";
|
import { gql, useMutation } from "@apollo/client";
|
||||||
import { Button, notification } from "antd";
|
import { Button, notification, Popconfirm } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -34,6 +34,7 @@ export function BillMarkSelectedExported({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||||
const [updateBill] = useMutation(gql`
|
const [updateBill] = useMutation(gql`
|
||||||
mutation UPDATE_BILL($billIds: [uuid!]!) {
|
mutation UPDATE_BILL($billIds: [uuid!]!) {
|
||||||
@@ -84,11 +85,24 @@ export function BillMarkSelectedExported({
|
|||||||
completedCallback && completedCallback([]);
|
completedCallback && completedCallback([]);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
refetch && refetch();
|
refetch && refetch();
|
||||||
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button loading={loading} disabled={disabled} onClick={handleUpdate}>
|
<Popconfirm
|
||||||
{t("bills.labels.markexported")}
|
visible={visible}
|
||||||
</Button>
|
title={t("general.labels.areyousure")}
|
||||||
|
onCancel={() => setVisible(false)}
|
||||||
|
onConfirm={handleUpdate}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
loading={loading}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={() => setVisible(true)}
|
||||||
|
>
|
||||||
|
{t("bills.labels.markexported")}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { gql, useMutation } from "@apollo/client";
|
import { gql, useMutation } from "@apollo/client";
|
||||||
import { Button, notification } from "antd";
|
import { Button, notification, Popconfirm } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -34,6 +34,8 @@ export function PaymentMarkSelectedExported({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||||
const [updatePayments] = useMutation(gql`
|
const [updatePayments] = useMutation(gql`
|
||||||
mutation UPDATE_PAYMENTS($paymentIds: [uuid!]!, $exportedat: timestamptz!) {
|
mutation UPDATE_PAYMENTS($paymentIds: [uuid!]!, $exportedat: timestamptz!) {
|
||||||
@@ -86,11 +88,24 @@ export function PaymentMarkSelectedExported({
|
|||||||
completedCallback && completedCallback([]);
|
completedCallback && completedCallback([]);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
refetch && refetch();
|
refetch && refetch();
|
||||||
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button loading={loading} disabled={disabled} onClick={handleUpdate}>
|
<Popconfirm
|
||||||
{t("bills.labels.markexported")}
|
visible={visible}
|
||||||
</Button>
|
title={t("general.labels.areyousure")}
|
||||||
|
onCancel={() => setVisible(false)}
|
||||||
|
onConfirm={handleUpdate}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
loading={loading}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={() => setVisible(true)}
|
||||||
|
>
|
||||||
|
{t("bills.labels.markexported")}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4535,6 +4535,24 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
<LayoutFormRow header={<div>Multiple Payers Item</div>}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("bodyshop.fields.responsibilitycenter_accountitem")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
name={[
|
||||||
|
"md_responsibility_centers",
|
||||||
|
"qb_multiple_payers",
|
||||||
|
"accountitem",
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</LayoutFormRow>
|
||||||
<Typography.Title level={4}>
|
<Typography.Title level={4}>
|
||||||
{t("bodyshop.labels.responsibilitycenters.sales_tax_codes")}
|
{t("bodyshop.labels.responsibilitycenters.sales_tax_codes")}
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
|
|||||||
@@ -1890,6 +1890,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
|||||||
actual_in
|
actual_in
|
||||||
kmin
|
kmin
|
||||||
kmout
|
kmout
|
||||||
|
qb_multiple_payers
|
||||||
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
|
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
|
||||||
id
|
id
|
||||||
removed
|
removed
|
||||||
|
|||||||
@@ -1,34 +1,38 @@
|
|||||||
|
import { DeleteFilled } from "@ant-design/icons";
|
||||||
import { useApolloClient, useMutation } from "@apollo/client";
|
import { useApolloClient, useMutation } from "@apollo/client";
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
Form,
|
|
||||||
notification,
|
|
||||||
Popconfirm,
|
|
||||||
Space,
|
|
||||||
Alert,
|
Alert,
|
||||||
|
Button,
|
||||||
Divider,
|
Divider,
|
||||||
PageHeader,
|
Form,
|
||||||
InputNumber,
|
|
||||||
Input,
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
notification,
|
||||||
|
PageHeader,
|
||||||
|
Popconfirm,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
Switch,
|
Switch,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
//import { useHistory } from "react-router-dom";
|
//import { useHistory } from "react-router-dom";
|
||||||
|
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import moment from "moment";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import DateTimePicker from "../../components/form-date-time-picker/form-date-time-picker.component";
|
||||||
import FormsFieldChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
|
import FormsFieldChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
|
||||||
|
import CurrencyInput from "../../components/form-items-formatted/currency-form-item.component";
|
||||||
import JobsScoreboardAdd from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component";
|
import JobsScoreboardAdd from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component";
|
||||||
import JobsCloseAutoAllocate from "../../components/jobs-close-auto-allocate/jobs-close-auto-allocate.component";
|
import JobsCloseAutoAllocate from "../../components/jobs-close-auto-allocate/jobs-close-auto-allocate.component";
|
||||||
import JobsCloseLines from "../../components/jobs-close-lines/jobs-close-lines.component";
|
import JobsCloseLines from "../../components/jobs-close-lines/jobs-close-lines.component";
|
||||||
|
import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component";
|
||||||
import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries";
|
import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries";
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component";
|
|
||||||
import DateTimePicker from "../../components/form-date-time-picker/form-date-time-picker.component";
|
|
||||||
import moment from "moment";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -42,6 +46,11 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
|
|||||||
// const history = useHistory();
|
// const history = useHistory();
|
||||||
const [closeJob] = useMutation(UPDATE_JOB);
|
const [closeJob] = useMutation(UPDATE_JOB);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { Qb_Multi_Ar } = useTreatments(
|
||||||
|
["Qb_Multi_Ar"],
|
||||||
|
{},
|
||||||
|
bodyshop && bodyshop.imexshopid
|
||||||
|
);
|
||||||
|
|
||||||
const handleFinish = async ({ removefromproduction, ...values }) => {
|
const handleFinish = async ({ removefromproduction, ...values }) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -65,6 +74,9 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
|
|||||||
kmout: values.kmout,
|
kmout: values.kmout,
|
||||||
dms_allocation: values.dms_allocation,
|
dms_allocation: values.dms_allocation,
|
||||||
...(removefromproduction ? { inproduction: false } : {}),
|
...(removefromproduction ? { inproduction: false } : {}),
|
||||||
|
...(values.qb_multiple_payers
|
||||||
|
? { qb_multiple_payers: values.qb_multiple_payers }
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
refetchQueries: ["QUERY_JOB_CLOSE_DETAILS"],
|
refetchQueries: ["QUERY_JOB_CLOSE_DETAILS"],
|
||||||
@@ -127,6 +139,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
|
|||||||
kmin: job.kmin,
|
kmin: job.kmin,
|
||||||
kmout: job.kmout,
|
kmout: job.kmout,
|
||||||
dms_allocation: job.dms_allocation,
|
dms_allocation: job.dms_allocation,
|
||||||
|
qb_multiple_payers: job.qb_multiple_payers,
|
||||||
}}
|
}}
|
||||||
scrollToFirstError
|
scrollToFirstError
|
||||||
>
|
>
|
||||||
@@ -312,6 +325,76 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
{Qb_Multi_Ar.treatment === "on" && (
|
||||||
|
<>
|
||||||
|
<Form.List name={["qb_multiple_payers"]}>
|
||||||
|
{(fields, { add, remove }) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{fields.map((field, index) => (
|
||||||
|
<Form.Item key={field.key}>
|
||||||
|
<Space>
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.fields.qb_multiple_payers.name")}
|
||||||
|
key={`${index}name`}
|
||||||
|
name={[field.name, "name"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
style={{ minWidth: "12rem" }}
|
||||||
|
disabled={jobRO}
|
||||||
|
>
|
||||||
|
{bodyshop.md_ins_cos.map((s) => (
|
||||||
|
<Select.Option key={s.name} value={s.name}>
|
||||||
|
{s.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.fields.qb_multiple_payers.amount")}
|
||||||
|
key={`${index}amount`}
|
||||||
|
name={[field.name, "amount"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<CurrencyInput min={0} disabled={jobRO} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<DeleteFilled
|
||||||
|
disabled={jobRO}
|
||||||
|
onClick={() => {
|
||||||
|
remove(field.name);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
))}
|
||||||
|
<Form.Item>
|
||||||
|
<Button
|
||||||
|
disabled={jobRO}
|
||||||
|
onClick={() => {
|
||||||
|
if (fields.length < 3) add();
|
||||||
|
}}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
>
|
||||||
|
{t("jobs.actions.dms.addpayer")}
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form.List>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Divider />
|
<Divider />
|
||||||
<JobsCloseLines job={job} />
|
<JobsCloseLines job={job} />
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"contents": "Contents",
|
"contents": "Contents",
|
||||||
"created": "Time",
|
"created": "Time",
|
||||||
"operation": "Operation",
|
"operation": "Operation",
|
||||||
|
"status": "Status",
|
||||||
"subject": "Subject",
|
"subject": "Subject",
|
||||||
"to": "To",
|
"to": "To",
|
||||||
"useremail": "User",
|
"useremail": "User",
|
||||||
@@ -835,6 +836,7 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"deletes3": "Error deleting document from storage. ",
|
"deletes3": "Error deleting document from storage. ",
|
||||||
|
"deleting": "Error deleting documents {{error}}",
|
||||||
"deleting_cloudinary": "Error deleting document from storage. {{message}}",
|
"deleting_cloudinary": "Error deleting document from storage. {{message}}",
|
||||||
"getpresignurl": "Error obtaining presigned URL for document. {{message}}",
|
"getpresignurl": "Error obtaining presigned URL for document. {{message}}",
|
||||||
"insert": "Unable to upload file. {{message}}",
|
"insert": "Unable to upload file. {{message}}",
|
||||||
@@ -1467,6 +1469,10 @@
|
|||||||
"production_vars": {
|
"production_vars": {
|
||||||
"note": "Production Note"
|
"note": "Production Note"
|
||||||
},
|
},
|
||||||
|
"qb_multiple_payers": {
|
||||||
|
"amount": "Amount",
|
||||||
|
"name": "Name"
|
||||||
|
},
|
||||||
"queued_for_parts": "Queued for Parts",
|
"queued_for_parts": "Queued for Parts",
|
||||||
"rate_ats": "ATS Rate",
|
"rate_ats": "ATS Rate",
|
||||||
"rate_la1": "LA1",
|
"rate_la1": "LA1",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"contents": "",
|
"contents": "",
|
||||||
"created": "",
|
"created": "",
|
||||||
"operation": "",
|
"operation": "",
|
||||||
|
"status": "",
|
||||||
"subject": "",
|
"subject": "",
|
||||||
"to": "",
|
"to": "",
|
||||||
"useremail": "",
|
"useremail": "",
|
||||||
@@ -835,6 +836,7 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"deletes3": "Error al eliminar el documento del almacenamiento.",
|
"deletes3": "Error al eliminar el documento del almacenamiento.",
|
||||||
|
"deleting": "",
|
||||||
"deleting_cloudinary": "",
|
"deleting_cloudinary": "",
|
||||||
"getpresignurl": "Error al obtener la URL prescrita para el documento. {{message}}",
|
"getpresignurl": "Error al obtener la URL prescrita para el documento. {{message}}",
|
||||||
"insert": "Incapaz de cargar el archivo. {{message}}",
|
"insert": "Incapaz de cargar el archivo. {{message}}",
|
||||||
@@ -1467,6 +1469,10 @@
|
|||||||
"production_vars": {
|
"production_vars": {
|
||||||
"note": ""
|
"note": ""
|
||||||
},
|
},
|
||||||
|
"qb_multiple_payers": {
|
||||||
|
"amount": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
"queued_for_parts": "",
|
"queued_for_parts": "",
|
||||||
"rate_ats": "",
|
"rate_ats": "",
|
||||||
"rate_la1": "Tarifa LA1",
|
"rate_la1": "Tarifa LA1",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"contents": "",
|
"contents": "",
|
||||||
"created": "",
|
"created": "",
|
||||||
"operation": "",
|
"operation": "",
|
||||||
|
"status": "",
|
||||||
"subject": "",
|
"subject": "",
|
||||||
"to": "",
|
"to": "",
|
||||||
"useremail": "",
|
"useremail": "",
|
||||||
@@ -835,6 +836,7 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"deletes3": "Erreur lors de la suppression du document du stockage.",
|
"deletes3": "Erreur lors de la suppression du document du stockage.",
|
||||||
|
"deleting": "",
|
||||||
"deleting_cloudinary": "",
|
"deleting_cloudinary": "",
|
||||||
"getpresignurl": "Erreur lors de l'obtention de l'URL présignée pour le document. {{message}}",
|
"getpresignurl": "Erreur lors de l'obtention de l'URL présignée pour le document. {{message}}",
|
||||||
"insert": "Incapable de télécharger le fichier. {{message}}",
|
"insert": "Incapable de télécharger le fichier. {{message}}",
|
||||||
@@ -1467,6 +1469,10 @@
|
|||||||
"production_vars": {
|
"production_vars": {
|
||||||
"note": ""
|
"note": ""
|
||||||
},
|
},
|
||||||
|
"qb_multiple_payers": {
|
||||||
|
"amount": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
"queued_for_parts": "",
|
"queued_for_parts": "",
|
||||||
"rate_ats": "",
|
"rate_ats": "",
|
||||||
"rate_la1": "Taux LA1",
|
"rate_la1": "Taux LA1",
|
||||||
|
|||||||
24
hasura/MigrationResolution.md
Normal file
24
hasura/MigrationResolution.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
Issue when migrating events resolved by running SQL below. User needed updating when running in prod.
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS hdb_catalog.hdb_source_catalog_version
|
||||||
|
(
|
||||||
|
version text COLLATE pg_catalog."default" NOT NULL,
|
||||||
|
upgraded_on timestamp with time zone NOT NULL
|
||||||
|
)
|
||||||
|
|
||||||
|
TABLESPACE pg_default;
|
||||||
|
|
||||||
|
ALTER TABLE hdb_catalog.hdb_source_catalog_version
|
||||||
|
OWNER to postgres;
|
||||||
|
-- Index: hdb_source_catalog_version_one_row
|
||||||
|
|
||||||
|
-- DROP INDEX hdb_catalog.hdb_source_catalog_version_one_row;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX hdb_source_catalog_version_one_row
|
||||||
|
ON hdb_catalog.hdb_source_catalog_version USING btree
|
||||||
|
((version IS NOT NULL) ASC NULLS LAST)
|
||||||
|
TABLESPACE pg_default;
|
||||||
|
|
||||||
|
INSERT INTO hdb_catalog.hdb_source_catalog_version (version, upgraded_on) VALUES ('2', NOW());
|
||||||
|
|
||||||
|
https://devscope.io/code/hasura/graphql-engine/issues/8694
|
||||||
@@ -1882,17 +1882,19 @@
|
|||||||
- role: user
|
- role: user
|
||||||
permission:
|
permission:
|
||||||
columns:
|
columns:
|
||||||
- cc
|
|
||||||
- to
|
|
||||||
- contents
|
|
||||||
- subject
|
|
||||||
- useremail
|
|
||||||
- created_at
|
|
||||||
- updated_at
|
|
||||||
- bodyshopid
|
- bodyshopid
|
||||||
|
- cc
|
||||||
|
- contents
|
||||||
|
- created_at
|
||||||
- id
|
- id
|
||||||
- jobid
|
- jobid
|
||||||
- noteid
|
- noteid
|
||||||
|
- status
|
||||||
|
- status_context
|
||||||
|
- subject
|
||||||
|
- to
|
||||||
|
- updated_at
|
||||||
|
- useremail
|
||||||
filter:
|
filter:
|
||||||
bodyshop:
|
bodyshop:
|
||||||
associations:
|
associations:
|
||||||
@@ -3139,6 +3141,7 @@
|
|||||||
- po_number
|
- po_number
|
||||||
- policy_no
|
- policy_no
|
||||||
- production_vars
|
- production_vars
|
||||||
|
- qb_multiple_payers
|
||||||
- queued_for_parts
|
- queued_for_parts
|
||||||
- rate_ats
|
- rate_ats
|
||||||
- rate_la1
|
- rate_la1
|
||||||
@@ -3401,6 +3404,7 @@
|
|||||||
- po_number
|
- po_number
|
||||||
- policy_no
|
- policy_no
|
||||||
- production_vars
|
- production_vars
|
||||||
|
- qb_multiple_payers
|
||||||
- queued_for_parts
|
- queued_for_parts
|
||||||
- rate_ats
|
- rate_ats
|
||||||
- rate_la1
|
- rate_la1
|
||||||
@@ -3673,6 +3677,7 @@
|
|||||||
- po_number
|
- po_number
|
||||||
- policy_no
|
- policy_no
|
||||||
- production_vars
|
- production_vars
|
||||||
|
- qb_multiple_payers
|
||||||
- queued_for_parts
|
- queued_for_parts
|
||||||
- rate_ats
|
- rate_ats
|
||||||
- rate_la1
|
- rate_la1
|
||||||
@@ -3767,8 +3772,6 @@
|
|||||||
- name: jobs_arms
|
- name: jobs_arms
|
||||||
definition:
|
definition:
|
||||||
enable_manual: false
|
enable_manual: false
|
||||||
insert:
|
|
||||||
columns: '*'
|
|
||||||
update:
|
update:
|
||||||
columns:
|
columns:
|
||||||
- actual_delivery
|
- actual_delivery
|
||||||
@@ -3776,9 +3779,9 @@
|
|||||||
- scheduled_completion
|
- scheduled_completion
|
||||||
- actual_completion
|
- actual_completion
|
||||||
- date_scheduled
|
- date_scheduled
|
||||||
|
- inproduction
|
||||||
- clm_total
|
- clm_total
|
||||||
- suspended
|
- suspended
|
||||||
- date_open
|
|
||||||
- job_totals
|
- job_totals
|
||||||
- converted
|
- converted
|
||||||
- employee_body
|
- employee_body
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."jobs" add column "qb_multiple_payers" jsonb
|
||||||
|
-- null;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."jobs" add column "qb_multiple_payers" jsonb
|
||||||
|
null;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."email_audit_trail" add column "sesmessageid" text
|
||||||
|
-- null;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."email_audit_trail" add column "sesmessageid" text
|
||||||
|
null;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP INDEX IF EXISTS "public"."email_audit_trail_sesmessageid";
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
CREATE INDEX "email_audit_trail_sesmessageid" on
|
||||||
|
"public"."email_audit_trail" using btree ("sesmessageid");
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."email_audit_trail" add column "status" text
|
||||||
|
-- null;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."email_audit_trail" add column "status" text
|
||||||
|
null;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."email_audit_trail" add column "status_context" jsonb
|
||||||
|
-- null default jsonb_build_array();
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."email_audit_trail" add column "status_context" jsonb
|
||||||
|
null default jsonb_build_array();
|
||||||
43065
logs/oAuthClient-log.log
43065
logs/oAuthClient-log.log
File diff suppressed because one or more lines are too long
@@ -64,6 +64,11 @@ app.use(
|
|||||||
//Email Based Paths.
|
//Email Based Paths.
|
||||||
var sendEmail = require("./server/email/sendemail.js");
|
var sendEmail = require("./server/email/sendemail.js");
|
||||||
app.post("/sendemail", fb.validateFirebaseIdToken, sendEmail.sendEmail);
|
app.post("/sendemail", fb.validateFirebaseIdToken, sendEmail.sendEmail);
|
||||||
|
app.post(
|
||||||
|
"/emailbounce",
|
||||||
|
bodyParser.text(),
|
||||||
|
sendEmail.emailBounce
|
||||||
|
);
|
||||||
|
|
||||||
//Test route to ensure Express is responding.
|
//Test route to ensure Express is responding.
|
||||||
app.get("/test", async function (req, res) {
|
app.get("/test", async function (req, res) {
|
||||||
@@ -238,7 +243,6 @@ app.get("/", async function (req, res) {
|
|||||||
res.status(200).send("Access Forbidden.");
|
res.status(200).send("Access Forbidden.");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
server.listen(port, (error) => {
|
server.listen(port, (error) => {
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
logger.log(
|
logger.log(
|
||||||
|
|||||||
@@ -648,6 +648,56 @@ exports.default = function ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Check if there are multiple payers. If there are, add a deduction line and make sure we create new invoices.
|
||||||
|
|
||||||
|
if (
|
||||||
|
jobs_by_pk.qb_multiple_payers &&
|
||||||
|
jobs_by_pk.qb_multiple_payers.length > 0
|
||||||
|
) {
|
||||||
|
jobs_by_pk.qb_multiple_payers.forEach((payer) => {
|
||||||
|
if (qbo) {
|
||||||
|
InvoiceLineAdd.push({
|
||||||
|
DetailType: "SalesItemLineDetail",
|
||||||
|
Amount: Dinero({ amount: (payer.amount || 0) * 100 * -1 }).toFormat(
|
||||||
|
DineroQbFormat
|
||||||
|
),
|
||||||
|
SalesItemLineDetail: {
|
||||||
|
...(jobs_by_pk.class
|
||||||
|
? { ClassRef: { value: classes[jobs_by_pk.class] } }
|
||||||
|
: {}),
|
||||||
|
ItemRef: {
|
||||||
|
value:
|
||||||
|
items[responsibilityCenters.qb_multiple_payers?.accountitem],
|
||||||
|
},
|
||||||
|
Qty: 1,
|
||||||
|
TaxCodeRef: {
|
||||||
|
value:
|
||||||
|
taxCodes[
|
||||||
|
findTaxCode(
|
||||||
|
{
|
||||||
|
local: false,
|
||||||
|
federal: false,
|
||||||
|
state: false,
|
||||||
|
},
|
||||||
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
||||||
|
)
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
InvoiceLineAdd.push({
|
||||||
|
ItemRef: {
|
||||||
|
FullName: responsibilityCenters.qb_multiple_payers?.accountitem,
|
||||||
|
},
|
||||||
|
Desc: `${payer.name} Liability`,
|
||||||
|
Amount: Dinero({ amount: (payer.amount || 0) * 100 * -1 }).toFormat(
|
||||||
|
DineroQbFormat
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
return InvoiceLineAdd;
|
return InvoiceLineAdd;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -667,3 +717,65 @@ const findTaxCode = ({ local, state, federal }, taxcode) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
exports.findTaxCode = findTaxCode;
|
exports.findTaxCode = findTaxCode;
|
||||||
|
|
||||||
|
exports.createMultiQbPayerLines = function ({
|
||||||
|
bodyshop,
|
||||||
|
jobs_by_pk,
|
||||||
|
qbo = false,
|
||||||
|
items,
|
||||||
|
taxCodes,
|
||||||
|
classes,
|
||||||
|
payer,
|
||||||
|
}) {
|
||||||
|
const InvoiceLineAdd = [];
|
||||||
|
const responsibilityCenters = bodyshop.md_responsibility_centers;
|
||||||
|
|
||||||
|
const invoiceLineHash = {}; //The hash of cost and profit centers based on the center name.
|
||||||
|
|
||||||
|
if (qbo) {
|
||||||
|
//Going to always assume that we need to apply GST and PST for labor.
|
||||||
|
const taxAccountCode = findTaxCode(
|
||||||
|
{
|
||||||
|
local: false,
|
||||||
|
federal: false,
|
||||||
|
state: false,
|
||||||
|
},
|
||||||
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
||||||
|
);
|
||||||
|
const QboTaxId = taxCodes[taxAccountCode];
|
||||||
|
InvoiceLineAdd.push({
|
||||||
|
DetailType: "SalesItemLineDetail",
|
||||||
|
Amount: Dinero({
|
||||||
|
amount: Math.round((payer.amount || 0) * 100),
|
||||||
|
}).toFormat(DineroQbFormat),
|
||||||
|
SalesItemLineDetail: {
|
||||||
|
...(jobs_by_pk.class
|
||||||
|
? { ClassRef: { value: classes[jobs_by_pk.class] } }
|
||||||
|
: {}),
|
||||||
|
ItemRef: {
|
||||||
|
value: items[responsibilityCenters.qb_multiple_payers?.accountitem],
|
||||||
|
},
|
||||||
|
TaxCodeRef: {
|
||||||
|
value: QboTaxId,
|
||||||
|
},
|
||||||
|
Qty: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
InvoiceLineAdd.push({
|
||||||
|
ItemRef: {
|
||||||
|
FullName: responsibilityCenters.qb_multiple_payers?.accountitem,
|
||||||
|
},
|
||||||
|
Desc: `${payer.name} Liability`,
|
||||||
|
Quantity: 1,
|
||||||
|
Amount: Dinero({
|
||||||
|
amount: Math.round((payer.amount || 0) * 100),
|
||||||
|
}).toFormat(DineroQbFormat),
|
||||||
|
SalesTaxCodeRef: {
|
||||||
|
FullName: "E",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return InvoiceLineAdd;
|
||||||
|
};
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const moment = require("moment-timezone");
|
|||||||
|
|
||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||||
const { generateOwnerTier } = require("../qbxml/qbxml-utils");
|
const { generateOwnerTier } = require("../qbxml/qbxml-utils");
|
||||||
|
const { createMultiQbPayerLines } = require("../qb-receivables-lines");
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
const oauthClient = new OAuthClient({
|
const oauthClient = new OAuthClient({
|
||||||
@@ -115,7 +116,13 @@ exports.default = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Query for the Job or Create it.
|
//Query for the Job or Create it.
|
||||||
jobTier = await QueryJob(oauthClient, qbo_realmId, req, job);
|
jobTier = await QueryJob(
|
||||||
|
oauthClient,
|
||||||
|
qbo_realmId,
|
||||||
|
req,
|
||||||
|
job,
|
||||||
|
isThreeTier ? ownerCustomerTier : null // ownerCustomerTier || insCoCustomerTier
|
||||||
|
);
|
||||||
|
|
||||||
// Need to validate that the job tier is associated to the right individual?
|
// Need to validate that the job tier is associated to the right individual?
|
||||||
|
|
||||||
@@ -140,6 +147,65 @@ exports.default = async (req, res) => {
|
|||||||
jobTier
|
jobTier
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (job.qb_multiple_payers && job.qb_multiple_payers.length > 0) {
|
||||||
|
for (const [index, payer] of job.qb_multiple_payers.entries()) {
|
||||||
|
//do the thing.
|
||||||
|
|
||||||
|
//Create the source level.
|
||||||
|
let insCoCustomerTier, ownerCustomerTier, jobTier;
|
||||||
|
|
||||||
|
//Insert the insurance company tier.
|
||||||
|
//Query for top level customer, the insurance company name.
|
||||||
|
insCoCustomerTier = await QueryInsuranceCo(
|
||||||
|
oauthClient,
|
||||||
|
qbo_realmId,
|
||||||
|
req,
|
||||||
|
{ ...job, ins_co_nm: payer.name }
|
||||||
|
);
|
||||||
|
if (!insCoCustomerTier) {
|
||||||
|
//Creating the Insurance Customer.
|
||||||
|
insCoCustomerTier = await InsertInsuranceCo(
|
||||||
|
oauthClient,
|
||||||
|
qbo_realmId,
|
||||||
|
req,
|
||||||
|
{ ...job, ins_co_nm: payer.name },
|
||||||
|
bodyshop
|
||||||
|
);
|
||||||
|
}
|
||||||
|
//Query for the Job or Create it.
|
||||||
|
jobTier = await QueryJob(
|
||||||
|
oauthClient,
|
||||||
|
qbo_realmId,
|
||||||
|
req,
|
||||||
|
job,
|
||||||
|
insCoCustomerTier
|
||||||
|
);
|
||||||
|
// Need to validate that the job tier is associated to the right individual?
|
||||||
|
if (!jobTier) {
|
||||||
|
jobTier = await InsertJob(
|
||||||
|
oauthClient,
|
||||||
|
qbo_realmId,
|
||||||
|
req,
|
||||||
|
job,
|
||||||
|
insCoCustomerTier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create the RO level
|
||||||
|
|
||||||
|
await InsertInvoiceMultiPayerInvoice(
|
||||||
|
oauthClient,
|
||||||
|
qbo_realmId,
|
||||||
|
req,
|
||||||
|
job,
|
||||||
|
bodyshop,
|
||||||
|
jobTier,
|
||||||
|
payer,
|
||||||
|
`-${index + 1}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// //No error. Mark the job exported & insert export log.
|
// //No error. Mark the job exported & insert export log.
|
||||||
if (elgen) {
|
if (elgen) {
|
||||||
const result = await client
|
const result = await client
|
||||||
@@ -212,7 +278,7 @@ async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) {
|
|||||||
"query",
|
"query",
|
||||||
`select * From Customer where DisplayName = '${StandardizeName(
|
`select * From Customer where DisplayName = '${StandardizeName(
|
||||||
job.ins_co_nm.trim()
|
job.ins_co_nm.trim()
|
||||||
)}'`
|
)}' and Active = true`
|
||||||
),
|
),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -284,7 +350,7 @@ async function QueryOwner(oauthClient, qbo_realmId, req, job) {
|
|||||||
"query",
|
"query",
|
||||||
`select * From Customer where DisplayName = '${StandardizeName(
|
`select * From Customer where DisplayName = '${StandardizeName(
|
||||||
ownerName
|
ownerName
|
||||||
)}'`
|
)}' and Active = true`
|
||||||
),
|
),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -348,12 +414,12 @@ async function InsertOwner(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.InsertOwner = InsertOwner;
|
exports.InsertOwner = InsertOwner;
|
||||||
async function QueryJob(oauthClient, qbo_realmId, req, job) {
|
async function QueryJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
|
||||||
const result = await oauthClient.makeApiCall({
|
const result = await oauthClient.makeApiCall({
|
||||||
url: urlBuilder(
|
url: urlBuilder(
|
||||||
qbo_realmId,
|
qbo_realmId,
|
||||||
"query",
|
"query",
|
||||||
`select * From Customer where DisplayName = '${job.ro_number}'`
|
`select * From Customer where DisplayName = '${job.ro_number}' and Active = true`
|
||||||
),
|
),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -365,9 +431,14 @@ async function QueryJob(oauthClient, qbo_realmId, req, job) {
|
|||||||
result.json &&
|
result.json &&
|
||||||
result.json.QueryResponse &&
|
result.json.QueryResponse &&
|
||||||
result.json.QueryResponse.Customer &&
|
result.json.QueryResponse.Customer &&
|
||||||
result.json.QueryResponse.Customer[0]
|
(parentTierRef
|
||||||
|
? result.json.QueryResponse.Customer.find(
|
||||||
|
(x) => x.ParentRef.value === parentTierRef.Id
|
||||||
|
)
|
||||||
|
: result.json.QueryResponse.Customer[0])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.QueryJob = QueryJob;
|
exports.QueryJob = QueryJob;
|
||||||
async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
|
async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
|
||||||
const Customer = {
|
const Customer = {
|
||||||
@@ -602,3 +673,137 @@ async function InsertInvoice(
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function InsertInvoiceMultiPayerInvoice(
|
||||||
|
oauthClient,
|
||||||
|
qbo_realmId,
|
||||||
|
req,
|
||||||
|
job,
|
||||||
|
bodyshop,
|
||||||
|
parentTierRef,
|
||||||
|
payer,
|
||||||
|
suffix
|
||||||
|
) {
|
||||||
|
const { items, taxCodes, classes } = await QueryMetaData(
|
||||||
|
oauthClient,
|
||||||
|
qbo_realmId,
|
||||||
|
req
|
||||||
|
);
|
||||||
|
const InvoiceLineAdd = createMultiQbPayerLines({
|
||||||
|
bodyshop,
|
||||||
|
jobs_by_pk: job,
|
||||||
|
qbo: true,
|
||||||
|
items,
|
||||||
|
taxCodes,
|
||||||
|
classes,
|
||||||
|
payer,
|
||||||
|
suffix,
|
||||||
|
});
|
||||||
|
|
||||||
|
const invoiceObj = {
|
||||||
|
Line: InvoiceLineAdd,
|
||||||
|
TxnDate: moment(job.date_invoiced)
|
||||||
|
.tz(bodyshop.timezone)
|
||||||
|
.format("YYYY-MM-DD"),
|
||||||
|
DocNumber: job.ro_number + suffix,
|
||||||
|
...(job.class ? { ClassRef: { value: classes[job.class] } } : {}),
|
||||||
|
CustomerMemo: {
|
||||||
|
value: `${job.clm_no ? `Claim No: ${job.clm_no}` : ``}${
|
||||||
|
job.po_number ? `PO No: ${job.po_number}` : ``
|
||||||
|
} Vehicle:${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
|
||||||
|
job.v_model_desc || ""
|
||||||
|
} ${job.v_vin || ""} ${job.plate_no || ""} `.trim(),
|
||||||
|
},
|
||||||
|
CustomerRef: {
|
||||||
|
value: parentTierRef.Id,
|
||||||
|
},
|
||||||
|
...(bodyshop.accountingconfig.qbo_departmentid &&
|
||||||
|
bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && {
|
||||||
|
DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid },
|
||||||
|
}),
|
||||||
|
CustomField: [
|
||||||
|
...(bodyshop.accountingconfig.ReceivableCustomField1
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
DefinitionId: "1",
|
||||||
|
StringValue:
|
||||||
|
job[bodyshop.accountingconfig.ReceivableCustomField1],
|
||||||
|
Type: "StringType",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(bodyshop.accountingconfig.ReceivableCustomField2
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
DefinitionId: "2",
|
||||||
|
StringValue:
|
||||||
|
job[bodyshop.accountingconfig.ReceivableCustomField2],
|
||||||
|
Type: "StringType",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(bodyshop.accountingconfig.ReceivableCustomField3
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
DefinitionId: "3",
|
||||||
|
StringValue:
|
||||||
|
job[bodyshop.accountingconfig.ReceivableCustomField3],
|
||||||
|
Type: "StringType",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
...(bodyshop.accountingconfig &&
|
||||||
|
bodyshop.accountingconfig.qbo &&
|
||||||
|
bodyshop.accountingconfig.qbo_usa &&
|
||||||
|
bodyshop.region_config.includes("CA_") && {
|
||||||
|
TxnTaxDetail: {
|
||||||
|
TxnTaxCodeRef: {
|
||||||
|
value:
|
||||||
|
taxCodes[
|
||||||
|
bodyshop.md_responsibility_centers.taxes.state.accountitem
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
...(bodyshop.accountingconfig.printlater
|
||||||
|
? { PrintStatus: "NeedToPrint" }
|
||||||
|
: {}),
|
||||||
|
...(bodyshop.accountingconfig.emaillater && job.ownr_ea
|
||||||
|
? { EmailStatus: "NeedToSend" }
|
||||||
|
: {}),
|
||||||
|
BillAddr: {
|
||||||
|
Line3: `${job.ownr_city || ""}, ${job.ownr_st || ""} ${
|
||||||
|
job.ownr_zip || ""
|
||||||
|
}`.trim(),
|
||||||
|
Line2: job.ownr_addr1 || "",
|
||||||
|
Line1: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
|
||||||
|
job.ownr_co_nm || ""
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, {
|
||||||
|
invoiceObj,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await oauthClient.makeApiCall({
|
||||||
|
url: urlBuilder(qbo_realmId, "invoice"),
|
||||||
|
method: "POST",
|
||||||
|
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(invoiceObj),
|
||||||
|
});
|
||||||
|
setNewRefreshToken(req.user.email, result);
|
||||||
|
return result && result.json && result.json.Invoice;
|
||||||
|
} catch (error) {
|
||||||
|
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
|
||||||
|
error,
|
||||||
|
method: "InsertOwner",
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ function pollFunc(fn, timeout, interval) {
|
|||||||
pollFunc(getEntegralShopData, 0, 5 * 60 * 1000); //Set the metadata to refresh every 5 minutes.
|
pollFunc(getEntegralShopData, 0, 5 * 60 * 1000); //Set the metadata to refresh every 5 minutes.
|
||||||
|
|
||||||
async function getEntegralShopData() {
|
async function getEntegralShopData() {
|
||||||
const { bodyshops } = await client.request(queries.GET_ENTEGRAL_SHOPS);
|
|
||||||
await storage.init({ logging: true });
|
await storage.init({ logging: true });
|
||||||
|
const { bodyshops } = await client.request(queries.GET_ENTEGRAL_SHOPS);
|
||||||
logger.log("set-entegral-shops-local-storage", "DEBUG", "API", null, null);
|
logger.log("set-entegral-shops-local-storage", "DEBUG", "API", null, null);
|
||||||
await storage.setItem("entegralShops", bodyshops);
|
await storage.setItem("entegralShops", bodyshops);
|
||||||
return true; //Continue execution.
|
return true; //Continue execution.
|
||||||
@@ -866,12 +866,16 @@ exports.default = async (req, res) => {
|
|||||||
|
|
||||||
const [result, rawResponse, , rawRequest] = entegralResponse;
|
const [result, rawResponse, , rawRequest] = entegralResponse;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
|
logger.log("arms-failed-job-upload", "ERROR", "api", job.shopid, {
|
||||||
|
job: JSON.stringify({ id: job.id, ro_number: job.ro_number }),
|
||||||
|
error: error.message || JSON.stringify(error),
|
||||||
|
});
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("arms-failed-job", "ERROR", "api", job.shopid, {
|
logger.log("arms-failed-job", "ERROR", "api", job.shopid, {
|
||||||
job: JSON.stringify({ id: job.id, ro_number: job.ro_number }),
|
job: JSON.stringify({ id: job.id, ro_number: job.ro_number }),
|
||||||
|
error: error.message || JSON.stringify(error),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ exports.sendEmail = async (req, res) => {
|
|||||||
to: req.body.to,
|
to: req.body.to,
|
||||||
cc: req.body.cc,
|
cc: req.body.cc,
|
||||||
subject: req.body.subject,
|
subject: req.body.subject,
|
||||||
|
messageId: info.messageId,
|
||||||
});
|
});
|
||||||
res.json({
|
res.json({
|
||||||
success: true, //response: info
|
success: true, //response: info
|
||||||
@@ -190,6 +191,8 @@ async function logEmail(req, email) {
|
|||||||
useremail: req.user.email,
|
useremail: req.user.email,
|
||||||
contents: req.body.html,
|
contents: req.body.html,
|
||||||
jobid: req.body.jobid,
|
jobid: req.body.jobid,
|
||||||
|
sesmessageid: email.messageId,
|
||||||
|
status: "Sent",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -202,3 +205,59 @@ async function logEmail(req, email) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.emailBounce = async function (req, res, next) {
|
||||||
|
try {
|
||||||
|
const body = JSON.parse(req.body);
|
||||||
|
if (body.type === "SubscriptionConfirmation") {
|
||||||
|
logger.log("SNS-confirmation", "DEBUG", "api", null, {
|
||||||
|
message: body.message,
|
||||||
|
url: body.SubscribeUrl,
|
||||||
|
body: body,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (body.Type === "Notification") {
|
||||||
|
const message = JSON.parse(body.Message);
|
||||||
|
let replyTo, subject, messageId;
|
||||||
|
message.mail.headers.forEach((header) => {
|
||||||
|
if (header.name === "Reply-To") {
|
||||||
|
replyTo = header.value;
|
||||||
|
} else if (header.name === "Subject") {
|
||||||
|
subject = header.value;
|
||||||
|
} else if (header.name === "Message-ID") {
|
||||||
|
messageId = header.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//If it's bounced, log it as bounced in audit log. Send an email to the user.
|
||||||
|
const result = await client.request(queries.UPDATE_EMAIL_AUDIT, {
|
||||||
|
sesid: messageId,
|
||||||
|
status: "Bounced",
|
||||||
|
context: message.bounce?.bouncedRecipients,
|
||||||
|
});
|
||||||
|
transporter.sendMail(
|
||||||
|
{
|
||||||
|
from: `ImEX Online <noreply@imex.online>`,
|
||||||
|
to: "patrick@snapt.ca", // replyTo,
|
||||||
|
bcc: "patrick@snapt.ca",
|
||||||
|
subject: `ImEX Online Bounced Email - RE: ${subject}`,
|
||||||
|
text: `ImEX Online has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error.
|
||||||
|
|
||||||
|
${message.bounce?.bouncedRecipients.map(
|
||||||
|
(r) =>
|
||||||
|
`Recipient: ${r.emailAddress} | Status: ${r.action} | Code: ${r.diagnosticCode}
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
(err, info) => {
|
||||||
|
console.log("***", err || info);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.log("sns-error", "ERROR", "api", null, {
|
||||||
|
error: JSON.stringify(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
res.sendStatus(200);
|
||||||
|
};
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
|
|||||||
storage_payable
|
storage_payable
|
||||||
adjustment_bottom_line
|
adjustment_bottom_line
|
||||||
state_tax_rate
|
state_tax_rate
|
||||||
|
qb_multiple_payers
|
||||||
owner {
|
owner {
|
||||||
accountingid
|
accountingid
|
||||||
}
|
}
|
||||||
@@ -1611,3 +1612,22 @@ exports.INSERT_EMAIL_AUDIT = `mutation INSERT_EMAIL_AUDIT($email: email_audit_tr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports.DELETE_MEDIA_DOCUMENTS = `
|
||||||
|
mutation DELETE_DOCUMENTS($ids: [uuid!]!) {
|
||||||
|
delete_documents(where: { id: { _in: $ids } }) {
|
||||||
|
returning {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports.UPDATE_EMAIL_AUDIT = `
|
||||||
|
mutation ($sesid: String!, $status: String, $context: jsonb) {
|
||||||
|
update_email_audit_trail(where: {sesmessageid: {_eq: $sesid}}, _set: {status: $status, status_context: $context}) {
|
||||||
|
returning {
|
||||||
|
contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const _ = require("lodash");
|
const _ = require("lodash");
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
|
const queries = require("../graphql-client/queries");
|
||||||
|
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(
|
||||||
@@ -69,11 +71,38 @@ exports.deleteFiles = async (req, res) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.send(returns);
|
// Delete it on apollo.
|
||||||
|
const successfulDeletes = [];
|
||||||
|
returns.forEach((resType) => {
|
||||||
|
Object.keys(resType.deleted).forEach((key) => {
|
||||||
|
if (
|
||||||
|
resType.deleted[key] === "deleted" ||
|
||||||
|
resType.deleted[key] === "not_found"
|
||||||
|
) {
|
||||||
|
successfulDeletes.push(key.replace(/\.[^/.]+$/, ""));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await client.request(queries.DELETE_MEDIA_DOCUMENTS, {
|
||||||
|
ids: ids
|
||||||
|
.filter((i) => successfulDeletes.includes(i.key))
|
||||||
|
.map((i) => i.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.send({ returns, result });
|
||||||
|
} catch (error) {
|
||||||
|
logger.log("media-delete-error", "ERROR", req.user.email, null, [
|
||||||
|
{ ids, error: error.message || JSON.stringify(error) },
|
||||||
|
]);
|
||||||
|
|
||||||
|
res.json({ error });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.renameKeys = async (req, res) => {
|
exports.renameKeys = async (req, res) => {
|
||||||
const { documents } = req.body;
|
const { documents, tojobid } = req.body;
|
||||||
logger.log("media-bulk-rename", "DEBUG", req.user.email, null, documents);
|
logger.log("media-bulk-rename", "DEBUG", req.user.email, null, documents);
|
||||||
|
|
||||||
const proms = [];
|
const proms = [];
|
||||||
@@ -98,8 +127,37 @@ exports.renameKeys = async (req, res) => {
|
|||||||
let result;
|
let result;
|
||||||
|
|
||||||
result = await Promise.all(proms);
|
result = await Promise.all(proms);
|
||||||
|
const errors = [];
|
||||||
|
result
|
||||||
|
.filter((d) => d.error)
|
||||||
|
.forEach((d) => {
|
||||||
|
errors.push(d);
|
||||||
|
});
|
||||||
|
|
||||||
res.send(result);
|
let mutations = "";
|
||||||
|
|
||||||
|
result
|
||||||
|
.filter((d) => !d.error)
|
||||||
|
.forEach((d, idx) => {
|
||||||
|
//Create mutation text
|
||||||
|
|
||||||
|
mutations =
|
||||||
|
mutations +
|
||||||
|
`
|
||||||
|
update_doc${idx}:update_documents_by_pk(pk_columns: { id: "${d.id}" }, _set: {key: "${d.public_id}", jobid: "${tojobid}"}){
|
||||||
|
id
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mutations !== "") {
|
||||||
|
const mutationResult = await client.request(`mutation {
|
||||||
|
${mutations}
|
||||||
|
}`);
|
||||||
|
res.json({ errors, mutationResult });
|
||||||
|
} else {
|
||||||
|
res.json({ errors: "No images were succesfully moved on remote server. " });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//Also needs to be updated in upload utility and mobile app.
|
//Also needs to be updated in upload utility and mobile app.
|
||||||
|
|||||||
@@ -24,9 +24,8 @@ exports.mixdataUpload = async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
req.files.forEach(async (element) => {
|
for (const element of req.files) {
|
||||||
const b = Buffer.from(element.buffer);
|
const b = Buffer.from(element.buffer);
|
||||||
console.log(b.toString());
|
|
||||||
|
|
||||||
const inboundRequest = await xml2js.parseStringPromise(b.toString(), {
|
const inboundRequest = await xml2js.parseStringPromise(b.toString(), {
|
||||||
explicitArray: false,
|
explicitArray: false,
|
||||||
@@ -59,23 +58,24 @@ exports.mixdataUpload = async (req, res) => {
|
|||||||
ScaleType,
|
ScaleType,
|
||||||
jobHash
|
jobHash
|
||||||
);
|
);
|
||||||
|
const foundJobs = MixDataArray.filter((m) => m.jobid);
|
||||||
const MixDataQuery = `
|
const MixDataQuery = `
|
||||||
mutation UPSERT_MIXDATA{
|
mutation UPSERT_MIXDATA{
|
||||||
${MixDataArray.map((md, idx) =>
|
${foundJobs
|
||||||
GenerateGqlForMixData(md, idx)
|
.map((md, idx) => GenerateGqlForMixData(md, idx))
|
||||||
).join(" ")}
|
.join(" ")}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
if (foundJobs.length > 1) {
|
||||||
const resp = await client.request(MixDataQuery);
|
const resp = await client.request(MixDataQuery);
|
||||||
|
}
|
||||||
|
|
||||||
//Process the list of ROs and return an object to generate the queries.
|
//Process the list of ROs and return an object to generate the queries.
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).JSON(error);
|
res.status(500).json(error);
|
||||||
logger.log("job-mixdata-upload-error", "ERROR", null, null, {
|
logger.log("job-mixdata-upload-error", "ERROR", null, null, {
|
||||||
error: error.message,
|
error: error.message,
|
||||||
...error,
|
...error,
|
||||||
@@ -98,7 +98,7 @@ function DetermineScaleType(inboundRequest) {
|
|||||||
|
|
||||||
function GetListOfRos(inboundRequest, ScaleType) {
|
function GetListOfRos(inboundRequest, ScaleType) {
|
||||||
if (ScaleType.company === "PPG" && ScaleType.version === "1.3.0") {
|
if (ScaleType.company === "PPG" && ScaleType.version === "1.3.0") {
|
||||||
return inboundRequest.PPG.DataExportInterface.ROData.RepairOrders.RO.map(
|
return inboundRequest.PPG.MixDataInterface.ROData.RepairOrders.RO.map(
|
||||||
(r) => r.RONumber
|
(r) => r.RONumber
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -106,11 +106,11 @@ function GetListOfRos(inboundRequest, ScaleType) {
|
|||||||
|
|
||||||
function GenerateMixDataArray(inboundRequest, ScaleType, jobHash) {
|
function GenerateMixDataArray(inboundRequest, ScaleType, jobHash) {
|
||||||
if (ScaleType.company === "PPG" && ScaleType.version === "1.3.0") {
|
if (ScaleType.company === "PPG" && ScaleType.version === "1.3.0") {
|
||||||
return inboundRequest.PPG.DataExportInterface.ROData.RepairOrders.RO.map(
|
return inboundRequest.PPG.MixDataInterface.ROData.RepairOrders.RO.map(
|
||||||
(r) => {
|
(r) => {
|
||||||
return {
|
return {
|
||||||
jobid: jobHash[r.RONumber].jobid,
|
jobid: jobHash[r.RONumber]?.jobid,
|
||||||
id: jobHash[r.RONumber].mixdataid,
|
id: jobHash[r.RONumber]?.mixdataid,
|
||||||
mixdata: r,
|
mixdata: r,
|
||||||
totalliquidcost: r.TotalLiquidCost,
|
totalliquidcost: r.TotalLiquidCost,
|
||||||
totalsundrycost: r.TotalSundryCost,
|
totalsundrycost: r.TotalSundryCost,
|
||||||
|
|||||||
Reference in New Issue
Block a user