Compare commits

...

20 Commits

Author SHA1 Message Date
Patrick Fic
3d9a24de4f IO-782 Parts Label Multi. 2022-09-13 15:43:05 -07:00
Patrick Fic
9792773cf0 IO-2043 Add LMS bill upload notification. 2022-09-13 15:26:55 -07:00
Patrick Fic
7c4ba416f7 IO-2028 LMS Deletion. 2022-09-13 15:13:43 -07:00
Patrick Fic
9dbb4b586f IO-233 ARMS event based updates. 2022-09-13 14:00:54 -07:00
Patrick Fic
169fdf6ae8 Merged in release/2022-09-09 (pull request #576)
Release/2022 09 09
2022-09-09 20:13:00 +00:00
Patrick Fic
5f3c1fc95e IO-2039 LMS support for non media files. 2022-09-08 10:03:24 -07:00
Patrick Fic
ba9ea17805 Add QBO error for missing insurance company name. 2022-09-06 09:41:02 -07:00
Patrick Fic
d7c68441e8 Merged in releaase/2022-09-02 (pull request #574)
Releaase/2022 09 02
2022-09-02 15:03:09 +00:00
Patrick Fic
c0973b6098 Remove unnecessary console log. 2022-09-01 16:48:29 -07:00
Patrick Fic
e10196bb62 IO-1325 Populate 3rd party payer modal correctly. 2022-09-01 16:39:05 -07:00
Patrick Fic
637a33e670 IO-1981 Disable order type for OEC orders. 2022-08-31 14:48:47 -07:00
Patrick Fic
8bcc903f2b IO-1325 Automatically populate 3rd party modal. 2022-08-31 13:37:01 -07:00
Patrick Fic
5ec5be0852 IO-1981 Add Sublet Order. 2022-08-31 13:04:36 -07:00
Patrick Fic
cb0c4d55df IO-1950 Parts Order Discounts 2022-08-31 10:32:41 -07:00
Patrick Fic
dea4d50821 IO-1949 Add additional buttons to email overlay. 2022-08-30 16:16:07 -07:00
Patrick Fic
df1adc34a2 IO-2033 Mark payment as exported. 2022-08-30 14:11:34 -07:00
Patrick Fic
e44e2bd7dd Merged in release/2022-08-26 (pull request #570)
IO-2032 Add psr by make template.

Approved-by: Patrick Fic
2022-08-29 23:50:53 +00:00
Patrick Fic
b557100fc6 Merged in release/2022-08-26 (pull request #568)
IO-1984 Resolve error on email audit tab.

Approved-by: Patrick Fic
2022-08-29 15:13:57 +00:00
Patrick Fic
d9a8831eb3 Merged in release/2022-08-26 (pull request #566)
Minor release fixes.

Approved-by: Patrick Fic
2022-08-26 21:06:43 +00:00
Patrick Fic
137370812d Merged in hotfix/2022-08-23 (pull request #563)
Query only active QBO tax codes.
2022-08-23 17:01:03 +00:00
30 changed files with 1808 additions and 1014 deletions

3
.gitignore vendored
View File

@@ -113,3 +113,6 @@ firebase/.env
!.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml
logs/oAuthClient-log.log
.node-persist/**

View File

@@ -15416,6 +15416,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>send</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>senderrortosupport</name>
<definition_loaded>false</definition_loaded>
@@ -34329,6 +34350,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>custompercent</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>discount</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>email</name>
<definition_loaded>false</definition_loaded>
@@ -34476,6 +34539,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>order_type</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>orderhistory</name>
<definition_loaded>false</definition_loaded>
@@ -34497,6 +34581,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>parts_order</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>parts_orders</name>
<definition_loaded>false</definition_loaded>
@@ -34602,6 +34707,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>sublet_order</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>
<folder_node>
@@ -35220,6 +35346,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>markexported</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>payment</name>
<definition_loaded>false</definition_loaded>
@@ -37012,6 +37159,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>parts_label_multiple</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>parts_label_single</name>
<definition_loaded>false</definition_loaded>
@@ -37390,6 +37558,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>sublet_order</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>supplement_request</name>
<definition_loaded>false</definition_loaded>
@@ -38006,6 +38195,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>sublet_order</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>

View File

@@ -1,19 +1,20 @@
import { Card, Input, Space, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component";
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -184,6 +185,13 @@ export function AccountingPayablesTableComponent({
<Card
extra={
<Space wrap>
<PaymentMarkSelectedExported
paymentIds={selectedPayments}
disabled={transInProgress || selectedPayments.length === 0}
loadingCallback={setTransInProgress}
completedCallback={setSelectedPayments}
refetch={refetch}
/>
<PaymentsExportAllButton
paymentIds={selectedPayments}
disabled={transInProgress || selectedPayments.length === 0}

View File

@@ -23,6 +23,7 @@ export function DocumentsLocalUploadComponent({
vendorid,
invoice_number,
callbackAfterUpload,
allowAllTypes,
}) {
const [fileList, setFileList] = useState([]);
@@ -52,7 +53,9 @@ export function DocumentsLocalUploadComponent({
},
})
}
accept="audio/*, video/*, image/*, .pdf, .doc, .docx, .xls, .xlsx"
{...(!allowAllTypes && {
accept: "audio/*, video/*, image/*, .pdf, .doc, .docx, .xls, .xlsx",
})}
>
{children || (
<>

View File

@@ -2,6 +2,8 @@ import cleanAxios from "../../utils/CleanAxios";
import { store } from "../../redux/store";
import { addMediaForJob } from "../../redux/media/media.actions";
import normalizeUrl from "normalize-url";
import { notification } from "antd";
import i18n from "i18next";
export const handleUpload = async ({ ev, context }) => {
const { onError, onSuccess, onProgress, file } = ev;
@@ -45,6 +47,11 @@ export const handleUpload = async ({ ev, context }) => {
}
} else {
onSuccess && onSuccess(file);
notification.open({
type: "success",
key: "docuploadsuccess",
message: i18n.t("documents.successes.insert"),
});
store.dispatch(
addMediaForJob({
jobid,

View File

@@ -1,4 +1,4 @@
import { Divider, Form, Modal, notification } from "antd";
import { Button, Divider, Form, Modal, notification, Space } from "antd";
import axios from "axios";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -180,8 +180,10 @@ export function EmailOverlayContainer({
onCancel={() => {
toggleEmailOverlayVisible();
}}
okText={t("general.actions.send")}
okButtonProps={{
loading: sending,
disabled:
selectedMedia &&
(selectedMedia
@@ -191,21 +193,56 @@ export function EmailOverlayContainer({
selectedMedia.filter((s) => s.isSelected).length > 10),
}}
>
<Form layout="vertical" form={form} onFinish={handleFinish}>
{loading && (
<div>
<LoadingSkeleton />
<Divider>{t("emails.labels.preview")}</Divider>
<LoadingSpinner message={t("emails.labels.generatingemail")} />
</div>
)}
{!loading && (
<EmailOverlayComponent
form={form}
selectedMediaState={[selectedMedia, setSelectedMedia]}
/>
)}
</Form>
<div>
<div
style={{
marginTop: "1rem",
display: "flex",
justifyContent: "flex-end",
}}
>
<Space style={{ alignSelf: "flex-end" }} align="right">
<Button
onClick={() => {
toggleEmailOverlayVisible();
}}
>
{t("general.actions.cancel")}
</Button>
<Button
loading={sending}
onClick={() => form.submit()}
disabled={
selectedMedia &&
(selectedMedia
.filter((s) => s.isSelected)
.reduce((acc, val) => (acc = acc + val.size), 0) >=
10485760 - new Blob([form.getFieldValue("html")]).size ||
selectedMedia.filter((s) => s.isSelected).length > 10)
}
type="primary"
>
{t("general.actions.send")}
</Button>
</Space>
</div>
<Form layout="vertical" form={form} onFinish={handleFinish}>
{loading && (
<div>
<LoadingSkeleton />
<Divider>{t("emails.labels.preview")}</Divider>
<LoadingSpinner message={t("emails.labels.generatingemail")} />
</div>
)}
{!loading && (
<EmailOverlayComponent
form={form}
selectedMediaState={[selectedMedia, setSelectedMedia]}
/>
)}
</Form>
</div>
</Modal>
);
}

View File

@@ -19,7 +19,7 @@ const mapDispatchToProps = (dispatch) => ({
});
export default connect(mapStateToProps, mapDispatchToProps)(Jobd3RdPartyModal);
export function Jobd3RdPartyModal({ bodyshop, jobId }) {
export function Jobd3RdPartyModal({ bodyshop, jobId, job }) {
const [isModalVisible, setIsModalVisible] = useState(false);
const { t } = useTranslation();
const [form] = Form.useForm();
@@ -33,6 +33,11 @@ export function Jobd3RdPartyModal({ bodyshop, jobId }) {
);
const showModal = () => {
form.setFieldsValue({
ded_amt: job.ded_amt,
depreciation: job.depreciation_taxes,
custgst: job.ca_customer_gst,
});
setIsModalVisible(true);
};
@@ -42,6 +47,7 @@ export function Jobd3RdPartyModal({ bodyshop, jobId }) {
};
const handleCancel = () => {
form.resetFields();
setIsModalVisible(false);
};
const handleFinish = (values) => {

View File

@@ -14,6 +14,7 @@ import { selectAllMedia } from "../../redux/media/media.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { CreateExplorerLinkForJob } from "../../utils/localmedia";
import DocumentsLocalUploadComponent from "../documents-local-upload/documents-local-upload.component";
import JobsDocumentsLocalDeleteButton from "./jobs-documents-local-gallery.delete.component";
import JobsLocalGalleryDownloadButton from "./jobs-documents-local-gallery.download";
import JobsDocumentsLocalGalleryReassign from "./jobs-documents-local-gallery.reassign.component";
import JobsDocumentsLocalGallerySelectAllComponent from "./jobs-documents-local-gallery.selectall.component";
@@ -69,7 +70,10 @@ export function JobsDocumentsLocalGallery({
) {
acc.images.push(val);
} else {
acc.other.push(val);
acc.other.push({
...val,
tags: [{ value: val.filename, title: val.filename }],
});
}
return acc;
},
@@ -98,12 +102,14 @@ export function JobsDocumentsLocalGallery({
<JobsDocumentsLocalGalleryReassign jobid={job.id} />
<JobsDocumentsLocalGallerySelectAllComponent jobid={job.id} />
<JobsLocalGalleryDownloadButton job={job} />
<JobsDocumentsLocalDeleteButton jobid={job.id} />
</Space>
<Card>
<DocumentsLocalUploadComponent
job={job}
invoice_number={invoice_number}
vendorid={vendorid}
allowAllTypes
/>
</Card>
<Card title={t("jobs.labels.documents-images")}>

View File

@@ -0,0 +1,81 @@
import { QuestionCircleOutlined } from "@ant-design/icons";
import { Button, notification, Popconfirm } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import cleanAxios from "../../utils/CleanAxios";
//Context: currentUserEmail, bodyshop, jobid, invoiceid
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { getJobMedia } from "../../redux/media/media.actions";
import { selectAllMedia } from "../../redux/media/media.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
allMedia: selectAllMedia,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
getJobMedia: (id) => dispatch(getJobMedia(id)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsDocumentsLocalDeleteButton);
export function JobsDocumentsLocalDeleteButton({
bodyshop,
getJobMedia,
allMedia,
jobid,
}) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const handleDelete = async () => {
logImEXEvent("job_documents_delete");
setLoading(true);
const delres = await cleanAxios.post(
`${bodyshop.localmediaserverhttp}/jobs/delete`,
{
jobid: jobid,
files: ((allMedia && allMedia[jobid]) || [])
.filter((i) => i.isSelected)
.map((i) => i.filename),
},
{ headers: { ims_token: bodyshop.localmediatoken } }
);
if (delres.errors) {
notification["error"]({
message: t("documents.errors.deleting", {
message: JSON.stringify(delres.errors),
}),
});
} else {
notification.open({
key: "docdeletedsuccesfully",
type: "success",
message: t("documents.successes.delete"),
});
}
getJobMedia(jobid);
setLoading(false);
};
return (
<Popconfirm
icon={<QuestionCircleOutlined style={{ color: "red" }} />}
onConfirm={handleDelete}
title={t("documents.labels.confirmdelete")}
okText={t("general.actions.delete")}
okButtonProps={{ type: "danger" }}
cancelText={t("general.actions.cancel")}
>
<Button loading={loading}>{t("documents.actions.delete")}</Button>
</Popconfirm>
);
}

View File

@@ -0,0 +1,97 @@
import { DownOutlined } from "@ant-design/icons";
import { Dropdown, InputNumber, Menu, Space } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
export default function PartsOrderModalPriceChange({ form, field }) {
const { t } = useTranslation();
const menu = (
<Menu
onClick={({ key }) => {
if (key === "custom") return;
const values = form.getFieldsValue();
const { parts_order_lines } = values;
form.setFieldsValue({
parts_order_lines: {
data: parts_order_lines.data.map((p, idx) => {
if (idx !== field.name) return p;
return {
...p,
act_price: (p.act_price || 0) * ((100 - key) / 100),
};
}),
},
});
}}
items={[
{
key: "5",
label: t("parts_orders.labels.discount", { percent: "5%" }),
},
{
key: "10",
label: t("parts_orders.labels.discount", { percent: "10%" }),
},
{
key: "15",
label: t("parts_orders.labels.discount", { percent: "15%" }),
},
{
key: "20",
label: t("parts_orders.labels.discount", { percent: "20%" }),
},
{
key: "25",
label: t("parts_orders.labels.discount", { percent: "25%" }),
},
{
key: "custom",
label: (
<InputNumber
onClick={(e) => e.stopPropagation()}
addonAfter="%"
onKeyUp={(e) => {
if (e.key === "Enter") {
const values = form.getFieldsValue();
const { parts_order_lines } = values;
form.setFieldsValue({
parts_order_lines: {
data: parts_order_lines.data.map((p, idx) => {
if (idx !== field.name) return p;
console.log(
p,
e.target.value,
(p.act_price || 0) *
((100 - (e.target.value || 0)) / 100)
);
return {
...p,
act_price:
(p.act_price || 0) *
((100 - (e.target.value || 0)) / 100),
};
}),
},
});
e.target.value = 0;
}
}}
min={0}
max={100}
/>
),
},
]}
/>
);
return (
<Dropdown overlay={menu} trigger="click">
<Space>
%
<DownOutlined />
</Space>
</Dropdown>
);
}

View File

@@ -23,6 +23,7 @@ import CurrencyInput from "../form-items-formatted/currency-form-item.component"
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -133,6 +134,21 @@ export function PartsOrderModalComponent({
<Checkbox />
</Form.Item>
)}
<Form.Item
name="order_type"
initialValue="parts_order"
label={t("parts_orders.labels.order_type")}
>
<Radio.Group disabled={sendType === "oec"}>
<Radio value={"parts_order"}>
{t("parts_orders.labels.parts_order")}
</Radio>
<Radio value={"sublet"}>
{t("parts_orders.labels.sublet_order")}
</Radio>
</Radio.Group>
</Form.Item>
</LayoutFormRow>
<Divider orientation="left">
{t("parts_orders.labels.inthisorder")}
@@ -246,7 +262,14 @@ export function PartsOrderModalComponent({
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<CurrencyInput />
<CurrencyInput
addonBefore={
<PartsOrderModalPriceChange
form={form}
field={field}
/>
}
/>
</Form.Item>
{isReturn && (
<Form.Item

View File

@@ -94,6 +94,7 @@ export function PartsOrderModalContainer({
const [updateJob] = useMutation(UPDATE_JOB);
const handleFinish = async ({
order_type,
removefrompartsqueue,
is_quote,
...values
@@ -102,47 +103,46 @@ export function PartsOrderModalContainer({
setSaving(true);
let insertResult;
insertResult = await insertPartOrder({
variables: {
po: [
{
...values,
order_date: moment().format("YYYY-MM-DD"),
orderedby: currentUser.email,
jobid: jobId,
user_email: currentUser.email,
return: isReturn,
status: is_quote
? bodyshop.md_order_statuses.default_quote || "Quote"
: bodyshop.md_order_statuses.default_ordered || "Ordered*",
},
],
},
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
insertResult = await insertPartOrder({
variables: {
po: [
{
...values,
order_date: moment().format("YYYY-MM-DD"),
orderedby: currentUser.email,
jobid: jobId,
user_email: currentUser.email,
return: isReturn,
status: is_quote
? bodyshop.md_order_statuses.default_quote || "Quote"
: bodyshop.md_order_statuses.default_ordered || "Ordered*",
},
],
},
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
});
if (!!insertResult.errors) {
notification["error"]({
message: t("parts_orders.errors.creating"),
description: JSON.stringify(insertResult.errors),
});
if (!!insertResult.errors) {
notification["error"]({
message: t("parts_orders.errors.creating"),
description: JSON.stringify(insertResult.errors),
});
return;
}
notification["success"]({
message: values.isReturn
? t("parts_orders.successes.return_created")
: t("parts_orders.successes.created"),
});
insertAuditTrail({
jobid: jobId,
operation: isReturn
? AuditTrailMapping.jobspartsreturn(
insertResult.data.insert_parts_orders.returning[0].order_number
)
: AuditTrailMapping.jobspartsorder(
insertResult.data.insert_parts_orders.returning[0].order_number
),
});
return;
}
notification["success"]({
message: values.isReturn
? t("parts_orders.successes.return_created")
: t("parts_orders.successes.created"),
});
insertAuditTrail({
jobid: jobId,
operation: isReturn
? AuditTrailMapping.jobspartsreturn(
insertResult.data.insert_parts_orders.returning[0].order_number
)
: AuditTrailMapping.jobspartsorder(
insertResult.data.insert_parts_orders.returning[0].order_number
),
});
const jobLinesResult = await updateJobLines({
variables: {
@@ -228,7 +228,9 @@ export function PartsOrderModalContainer({
{
name: isReturn
? Templates.parts_return_slip.key
: Templates.parts_order.key,
: order_type === "parts_order"
? Templates.parts_order.key
: Templates.sublet_order.key,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
@@ -238,7 +240,9 @@ export function PartsOrderModalContainer({
replyTo: bodyshop.email,
subject: isReturn
? Templates.parts_return_slip.subject
: Templates.parts_order.subject,
: order_type === "parts_order"
? Templates.parts_order.subject
: Templates.sublet_order.subject,
},
"e",
jobId
@@ -248,7 +252,9 @@ export function PartsOrderModalContainer({
{
name: isReturn
? Templates.parts_return_slip.key
: Templates.parts_order.key,
: order_type === "parts_order"
? Templates.parts_order.key
: Templates.sublet_order.key,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},

View File

@@ -0,0 +1,96 @@
import { gql, useMutation } from "@apollo/client";
import { Button, notification } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(PaymentMarkSelectedExported);
export function PaymentMarkSelectedExported({
bodyshop,
currentUser,
paymentIds,
disabled,
loadingCallback,
completedCallback,
refetch,
}) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
const [updatePayments] = useMutation(gql`
mutation UPDATE_PAYMENTS($paymentIds: [uuid!]!, $exportedat: timestamptz!) {
update_payments(
where: { id: { _in: $paymentIds } }
_set: { exportedat: $exportedat }
) {
returning {
id
exportedat
}
}
}
`);
const handleUpdate = async () => {
setLoading(true);
loadingCallback(true);
const result = await updatePayments({
variables: { paymentIds: paymentIds, exportedat: new Date() },
update(cache) {},
});
await insertExportLog({
variables: {
logs: paymentIds.map((id) => {
return {
bodyshopid: bodyshop.id,
paymentid: id,
successful: true,
message: JSON.stringify([t("general.labels.markedexported")]),
useremail: currentUser.email,
};
}),
},
});
if (!result.errors) {
notification["success"]({
message: t("payments.successes.markexported"),
});
} else {
notification["error"]({
message: t("bills.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
}
loadingCallback(false);
completedCallback && completedCallback([]);
setLoading(false);
refetch && refetch();
};
return (
<Button loading={loading} disabled={disabled} onClick={handleUpdate}>
{t("bills.labels.markexported")}
</Button>
);
}

View File

@@ -1,4 +1,4 @@
import { Button, Card, Form, InputNumber, Popover } from "antd";
import { Button, Card, Form, InputNumber, Popover, Radio } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -24,7 +24,8 @@ export function PrintCenterJobsLabels({ bodyshop, jobId }) {
const { t } = useTranslation();
const [form] = Form.useForm();
const handleOk = () => {
const handleOk = (e) => {
e.stopPropagation();
form.submit();
setIsModalVisible(false);
};
@@ -33,12 +34,12 @@ export function PrintCenterJobsLabels({ bodyshop, jobId }) {
setIsModalVisible(false);
setLoading(false);
};
const handleFinish = async (values) => {
const handleFinish = async ({ template, ...values }) => {
const { sendtype, ...restVals } = values;
setLoading(true);
await GenerateDocument(
{
name: TemplateList("job_special").folder_label_multiple.key,
name: TemplateList("job_special")[template].key,
variables: { id: jobId },
context: restVals,
},
@@ -48,6 +49,7 @@ export function PrintCenterJobsLabels({ bodyshop, jobId }) {
);
setLoading(false);
setIsModalVisible(false);
form.resetFields();
};
const content = (
@@ -58,13 +60,28 @@ export function PrintCenterJobsLabels({ bodyshop, jobId }) {
layout="vertical"
form={form}
>
<Form.Item required name="template">
<Radio.Group>
<Radio.Button value="parts_label_multiple">
{t("printcenter.jobs.parts_label_multiple")}
</Radio.Button>
<Radio.Button value="folder_label_multiple">
{t("printcenter.jobs.folder_label_multiple")}
</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item
required
label={t("printcenter.jobs.labels.position")}
name="position"
>
<InputNumber min={1} precision={0} />
</Form.Item>
<Form.Item label={t("printcenter.jobs.labels.count")} name="count">
<Form.Item
required
label={t("printcenter.jobs.labels.count")}
name="count"
>
<InputNumber min={1} precision={0} max={99} />
</Form.Item>
<Button type="primary" loading={loading} onClick={handleOk}>

View File

@@ -20,7 +20,7 @@ const mapDispatchToProps = (dispatch) => ({});
export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
const [search, setSearch] = useState("");
const { id: jobId } = printCenterModal.context;
const { id: jobId, job } = printCenterModal.context;
const tempList = TemplateList("job", {});
const { t } = useTranslation();
const JobsReportsList = Object.keys(tempList)
@@ -54,7 +54,7 @@ export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
extra={
<Space wrap>
<PrintCenterJobsLabels jobId={jobId} />
<Jobd3RdPartyModal jobId={jobId} />
<Jobd3RdPartyModal jobId={jobId} job={job} />
<Input.Search
onChange={(e) => setSearch(e.target.value)}
value={search}

View File

@@ -53,7 +53,6 @@ export const requestForToken = () => {
})
.then((currentToken) => {
if (currentToken) {
console.log("current token for client: ", currentToken);
window.sessionStorage.setItem("fcmtoken", currentToken);
// Perform any other necessary action with the token
} else {

View File

@@ -964,6 +964,7 @@
"save": "Save",
"saveandnew": "Save and New",
"selectall": "Select All",
"send": "Send",
"senderrortosupport": "Send Error to Support",
"submit": "Submit",
"tryagain": "Try Again",
@@ -2032,6 +2033,8 @@
"labels": {
"allpartsto": "All Parts Location",
"confirmdelete": "Are you sure you want to delete this item? It cannot be recovered. Job line statuses will not be updated and may require manual review. ",
"custompercent": "Custom %",
"discount": "Discount {{percent}}",
"email": "Send by Email",
"inthisorder": "Parts in this Order",
"is_quote": "Parts Quote?",
@@ -2039,12 +2042,15 @@
"newpartsorder": "New Parts Order",
"notyetordered": "This part has not yet been ordered.",
"oec": "Order via OEC",
"order_type": "Order Type",
"orderhistory": "Order History",
"parts_order": "Parts Order",
"parts_orders": "Parts Orders",
"print": "Show Printed Form",
"receive": "Receive Parts Order",
"removefrompartsqueue": "Remove from Parts Queue?",
"returnpartsorder": "Return Parts Order"
"returnpartsorder": "Return Parts Order",
"sublet_order": "Sublet Order"
},
"successes": {
"created": "Parts order created successfully. ",
@@ -2085,6 +2091,7 @@
},
"successes": {
"exported": "Payment(s) exported successfully.",
"markexported": "Payment(s) marked exported.",
"payment": "Payment created successfully. ",
"stripe": "Credit card transaction charged successfully."
}
@@ -2174,7 +2181,7 @@
"filing_coversheet_portrait": "Filing Coversheet (Portrait)",
"final_invoice": "Final Invoice",
"fippa_authorization": "FIPPA Authorization",
"folder_label_multiple": "Folder Label Multiple",
"folder_label_multiple": "Folder Label - Multi",
"glass_express_checklist": "Glass Express Checklist",
"guarantee": "Repair Guarantee",
"individual_job_note": "Job Note RO # {{ro_number}}",
@@ -2195,6 +2202,7 @@
"mpi_eglass_auth": "MPI - eGlass Auth",
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet",
"paint_grid": "Paint Grid",
"parts_label_multiple": "Parts Label - Multi",
"parts_label_single": "Parts Label - Single",
"parts_list": "Parts List",
"parts_order": "Parts Order Confirmation",
@@ -2213,6 +2221,7 @@
"sgi_certificate_of_repairs": "SGI - Certificate of Repairs",
"sgi_windshield_auth": "SGI - Windshield Authorization",
"stolen_recovery_checklist": "Stolen Recovery Checklist",
"sublet_order": "Sublet Order",
"supplement_request": "Supplement Request",
"thank_you_ro": "Thank You Letter",
"thirdpartypayer": "Third Party Payer",
@@ -2251,7 +2260,8 @@
},
"subjects": {
"jobs": {
"parts_order": "Parts Order PO: {{ro_number}} - {{name}}"
"parts_order": "Parts Order PO: {{ro_number}} - {{name}}",
"sublet_order": "Sublet Order PO: {{ro_number}} - {{name}}"
}
},
"vendors": {

View File

@@ -964,6 +964,7 @@
"save": "Salvar",
"saveandnew": "",
"selectall": "",
"send": "",
"senderrortosupport": "",
"submit": "",
"tryagain": "",
@@ -2032,6 +2033,8 @@
"labels": {
"allpartsto": "",
"confirmdelete": "",
"custompercent": "",
"discount": "",
"email": "Enviar por correo electrónico",
"inthisorder": "Partes en este pedido",
"is_quote": "",
@@ -2039,12 +2042,15 @@
"newpartsorder": "",
"notyetordered": "",
"oec": "",
"order_type": "",
"orderhistory": "Historial de pedidos",
"parts_order": "",
"parts_orders": "",
"print": "Mostrar formulario impreso",
"receive": "",
"removefrompartsqueue": "",
"returnpartsorder": ""
"returnpartsorder": "",
"sublet_order": ""
},
"successes": {
"created": "Pedido de piezas creado con éxito.",
@@ -2085,6 +2091,7 @@
},
"successes": {
"exported": "",
"markexported": "",
"payment": "",
"stripe": ""
}
@@ -2195,6 +2202,7 @@
"mpi_eglass_auth": "",
"mpi_final_acct_sheet": "",
"paint_grid": "",
"parts_label_multiple": "",
"parts_label_single": "",
"parts_list": "",
"parts_order": "",
@@ -2213,6 +2221,7 @@
"sgi_certificate_of_repairs": "",
"sgi_windshield_auth": "",
"stolen_recovery_checklist": "",
"sublet_order": "",
"supplement_request": "",
"thank_you_ro": "",
"thirdpartypayer": "",
@@ -2251,7 +2260,8 @@
},
"subjects": {
"jobs": {
"parts_order": ""
"parts_order": "",
"sublet_order": ""
}
},
"vendors": {

View File

@@ -964,6 +964,7 @@
"save": "sauvegarder",
"saveandnew": "",
"selectall": "",
"send": "",
"senderrortosupport": "",
"submit": "",
"tryagain": "",
@@ -2032,6 +2033,8 @@
"labels": {
"allpartsto": "",
"confirmdelete": "",
"custompercent": "",
"discount": "",
"email": "Envoyé par email",
"inthisorder": "Pièces dans cette commande",
"is_quote": "",
@@ -2039,12 +2042,15 @@
"newpartsorder": "",
"notyetordered": "",
"oec": "",
"order_type": "",
"orderhistory": "Historique des commandes",
"parts_order": "",
"parts_orders": "",
"print": "Afficher le formulaire imprimé",
"receive": "",
"removefrompartsqueue": "",
"returnpartsorder": ""
"returnpartsorder": "",
"sublet_order": ""
},
"successes": {
"created": "Commande de pièces créée avec succès.",
@@ -2085,6 +2091,7 @@
},
"successes": {
"exported": "",
"markexported": "",
"payment": "",
"stripe": ""
}
@@ -2195,6 +2202,7 @@
"mpi_eglass_auth": "",
"mpi_final_acct_sheet": "",
"paint_grid": "",
"parts_label_multiple": "",
"parts_label_single": "",
"parts_list": "",
"parts_order": "",
@@ -2213,6 +2221,7 @@
"sgi_certificate_of_repairs": "",
"sgi_windshield_auth": "",
"stolen_recovery_checklist": "",
"sublet_order": "",
"supplement_request": "",
"thank_you_ro": "",
"thirdpartypayer": "",
@@ -2251,7 +2260,8 @@
},
"subjects": {
"jobs": {
"parts_order": ""
"parts_order": "",
"sublet_order": ""
}
},
"vendors": {

View File

@@ -504,6 +504,12 @@ export const TemplateList = (type, context) => {
key: "folder_label_multiple",
disabled: false,
},
parts_label_multiple: {
title: i18n.t("printcenter.jobs.parts_label_multiple"),
description: "Parts Label Multiple",
key: "parts_label_multiple",
disabled: false,
},
csi_invitation_action: {
title: i18n.t("printcenter.jobs.csi_invitation_action"),
description: "CSI invite",
@@ -551,6 +557,20 @@ export const TemplateList = (type, context) => {
}),
disabled: false,
},
sublet_order: {
title: i18n.t("printcenter.jobs.sublet_order"),
description: "Parts Order",
key: "sublet_order",
subject: i18n.t("printcenter.subjects.jobs.sublet_order", {
ro_number: context && context.job && context.job.ro_number,
name: (
(context && context.job && context.job.ownr_ln) ||
(context && context.job && context.job.ownr_co_nm) ||
""
).trim(),
}),
disabled: false,
},
parts_return_slip: {
title: i18n.t("printcenter.jobs.parts_return_slip"),
subject: i18n.t("printcenter.jobs.parts_return_slip"),

View File

@@ -1,3 +1,3 @@
Must set the environment variables using:
firebase functions:config:set auth.graphql_endpoint="https://db.development.bodyshop.app/v1/graphql" auth.hasura_secret_admin_key="Dev-BodyShopApp!"
firebase functions:config:set auth.graphql_endpoint="https://db.dev.bodyshop.app/v1/graphql" auth.hasura_secret_admin_key="Dev-BodyShopApp!"

View File

@@ -1,5 +1,5 @@
version: 2
endpoint: https://db.development.bodyshop.app
endpoint: https://db.dev.bodyshop.app
admin_secret: Dev-BodyShopApp!
metadata_directory: metadata
actions:

View File

@@ -1,30 +1,30 @@
- function:
schema: public
name: search_bills
- function:
schema: public
- function:
name: search_cccontracts
- function:
schema: public
- function:
name: search_dms_vehicles
- function:
schema: public
- function:
name: search_exportlog
- function:
schema: public
- function:
name: search_inventory
- function:
schema: public
- function:
name: search_jobs
- function:
schema: public
- function:
name: search_owners
- function:
schema: public
- function:
name: search_payments
- function:
schema: public
- function:
name: search_phonebook
- function:
schema: public
- function:
name: search_vehicles
schema: public

File diff suppressed because it is too large Load Diff

View File

@@ -41,6 +41,7 @@
"moment-timezone": "^0.5.34",
"multer": "^1.4.5-lts.1",
"node-mailjet": "^5.1.0",
"node-persist": "^3.1.0",
"node-quickbooks": "^2.0.39",
"nodemailer": "^6.7.7",
"phone": "^3.1.23",

View File

@@ -210,7 +210,7 @@ app.post("/qbo/payments", fb.validateFirebaseIdToken, qbo.payments);
var data = require("./server/data/data");
app.post("/data/ah", data.autohouse);
app.post("/data/arms", data.arms);
app.post("/record-handler/arms", data.arms);
var taskHandler = require("./server/tasks/tasks");
app.post("/taskHandler", taskHandler.taskHandler);
@@ -238,6 +238,7 @@ app.get("/", async function (req, res) {
res.status(200).send("Access Forbidden.");
});
server.listen(port, (error) => {
if (error) throw error;
logger.log(

View File

@@ -238,6 +238,12 @@ exports.QueryInsuranceCo = QueryInsuranceCo;
async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
const insCo = bodyshop.md_ins_cos.find((i) => i.name === job.ins_co_nm);
if (!insCo) {
throw new Error(
`Insurance Company '${job.ins_co_nm}' not found in shop configuration. Please make sure it exists or change the insurance company name on the job to one that exists.`
);
return;
}
const Customer = {
DisplayName: job.ins_co_nm.trim(),
BillWithParent: true,

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ const logger = new graylog2.graylog({
});
function log(message, type, user, record, object) {
if (type !== "ioevent" && type !== "DEBUG")
if (type !== "ioevent")
console.log(message, {
type,
env: process.env.NODE_ENV || "development",

View File

@@ -2715,6 +2715,11 @@ node-mailjet@^5.1.0:
json-bigint "^1.0.0"
url-join "^4.0.0"
node-persist@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/node-persist/-/node-persist-3.1.0.tgz#9d4b03950bba70d37d13d3d3551840e25fd17e09"
integrity sha512-/j+fd/u71wNgKf3V2bx4tnDm+3GvLnlCuvf2MXbJ3wern+67IAb6zN9Leu1tCWPlPNZ+v1hLSibVukkPK2HqJw==
node-quickbooks@^2.0.39:
version "2.0.39"
resolved "https://registry.yarnpkg.com/node-quickbooks/-/node-quickbooks-2.0.39.tgz#a2534d24063e8a0cea5bb80c66b0727c1c942081"