@@ -8,13 +8,13 @@ orbs:
|
|||||||
jobs:
|
jobs:
|
||||||
api-deploy:
|
api-deploy:
|
||||||
docker:
|
docker:
|
||||||
- image: "cimg/base:stable"
|
- image: cimg/node:18.18.2
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- eb/setup
|
- eb/setup
|
||||||
- run:
|
- run:
|
||||||
command: |
|
command: |
|
||||||
eb init imex-online-production-api -r ca-central-1 -p "Node.js 16 running on 64bit Amazon Linux 2"
|
eb init imex-online-production-api -r ca-central-1 -p "Node.js 18 running on 64bit Amazon Linux 2"
|
||||||
eb status --verbose
|
eb status --verbose
|
||||||
eb deploy
|
eb deploy
|
||||||
eb status
|
eb status
|
||||||
|
|||||||
@@ -89,8 +89,8 @@
|
|||||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||||
"start": "craco start",
|
"start": "craco start",
|
||||||
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
||||||
"build:test": "env-cmd -f .env.test yarn run build",
|
"build:test": "env-cmd -f .env.test npm run build",
|
||||||
"build-deploy:test": "yarn run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
|
"build-deploy:test": "npm run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
|
||||||
"buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
"buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
||||||
"test": "cypress open",
|
"test": "cypress open",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
|
|
||||||
{/* <FormFieldsChanged form={form} /> */}
|
{/* <FormFieldsChanged form={form} /> */}
|
||||||
<LayoutFormRow header={t("courtesycars.labels.vehicle")}>
|
<LayoutFormRow header={t("courtesycars.labels.vehicle")}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("courtesycars.fields.year")}
|
label={t("courtesycars.fields.year")}
|
||||||
name="year"
|
name="year"
|
||||||
rules={[
|
rules={[
|
||||||
@@ -118,7 +118,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<InputNumber precision={0} />
|
<InputNumber min={0} precision={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("courtesycars.fields.fleetnumber")}
|
label={t("courtesycars.fields.fleetnumber")}
|
||||||
@@ -213,12 +213,38 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
>
|
>
|
||||||
<CourtesyCarStatus />
|
<CourtesyCarStatus />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<div>
|
||||||
label={t("courtesycars.fields.nextservicekm")}
|
<Form.Item
|
||||||
name="nextservicekm"
|
label={t("courtesycars.fields.nextservicekm")}
|
||||||
>
|
name="nextservicekm"
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>
|
<InputNumber min={0} precision={0} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
shouldUpdate={(p, c) =>
|
||||||
|
p.mileage !== c.mileage || p.nextservicekm !== c.nextservicekm
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{() => {
|
||||||
|
const nextservicekm = form.getFieldValue("nextservicekm");
|
||||||
|
const mileageOver =
|
||||||
|
nextservicekm <= form.getFieldValue("mileage");
|
||||||
|
|
||||||
|
if (mileageOver)
|
||||||
|
return (
|
||||||
|
<Space direction="vertical" style={{ color: "tomato" }}>
|
||||||
|
<span>
|
||||||
|
<WarningFilled style={{ marginRight: ".3rem" }} />
|
||||||
|
{t("contracts.labels.cardueforservice")}
|
||||||
|
</span>
|
||||||
|
<span>{`${nextservicekm} km`}</span>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("courtesycars.fields.nextservicedate")}
|
label={t("courtesycars.fields.nextservicedate")}
|
||||||
@@ -227,30 +253,21 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
<FormDatePicker />
|
<FormDatePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
shouldUpdate={(p, c) =>
|
shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}
|
||||||
p.mileage !== c.mileage ||
|
|
||||||
p.nextservicedate !== c.nextservicedate ||
|
|
||||||
p.nextservicekm !== c.nextservicekm
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{() => {
|
{() => {
|
||||||
const nextservicedate = form.getFieldValue("nextservicedate");
|
const nextservicedate = form.getFieldValue("nextservicedate");
|
||||||
const nextservicekm = form.getFieldValue("nextservicekm");
|
|
||||||
|
|
||||||
const mileageOver =
|
|
||||||
nextservicekm <= form.getFieldValue("mileage");
|
|
||||||
|
|
||||||
const dueForService =
|
const dueForService =
|
||||||
nextservicedate && moment(nextservicedate).isBefore(moment());
|
nextservicedate &&
|
||||||
|
moment(nextservicedate).endOf("day").isSameOrBefore(moment());
|
||||||
|
|
||||||
if (mileageOver || dueForService)
|
if (dueForService)
|
||||||
return (
|
return (
|
||||||
<Space direction="vertical" style={{ color: "tomato" }}>
|
<Space direction="vertical" style={{ color: "tomato" }}>
|
||||||
<span>
|
<span>
|
||||||
<WarningFilled style={{ marginRight: ".3rem" }} />
|
<WarningFilled style={{ marginRight: ".3rem" }} />
|
||||||
{t("contracts.labels.cardueforservice")}
|
{t("contracts.labels.cardueforservice")}
|
||||||
</span>
|
</span>
|
||||||
<span>{`${nextservicekm} km`}</span>
|
|
||||||
<span>
|
<span>
|
||||||
<DateFormatter>{nextservicedate}</DateFormatter>
|
<DateFormatter>{nextservicedate}</DateFormatter>
|
||||||
</span>
|
</span>
|
||||||
@@ -282,7 +299,8 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
{() => {
|
{() => {
|
||||||
const expires = form.getFieldValue("registrationexpires");
|
const expires = form.getFieldValue("registrationexpires");
|
||||||
|
|
||||||
const dateover = expires && moment(expires).isBefore(moment());
|
const dateover =
|
||||||
|
expires && moment(expires).endOf("day").isBefore(moment());
|
||||||
|
|
||||||
if (dateover)
|
if (dateover)
|
||||||
return (
|
return (
|
||||||
@@ -317,7 +335,8 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
{() => {
|
{() => {
|
||||||
const expires = form.getFieldValue("insuranceexpires");
|
const expires = form.getFieldValue("insuranceexpires");
|
||||||
|
|
||||||
const dateover = expires && moment(expires).isBefore(moment());
|
const dateover =
|
||||||
|
expires && moment(expires).endOf("day").isBefore(moment());
|
||||||
|
|
||||||
if (dateover)
|
if (dateover)
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ import { useMutation } from "@apollo/client";
|
|||||||
import { notification } from "antd";
|
import { notification } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.queries";
|
import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.queries";
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import ScheduleEventComponent from "./schedule-event.component";
|
import ScheduleEventComponent from "./schedule-event.component";
|
||||||
|
|
||||||
export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
|
export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
|
||||||
|
const dispatch = useDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
|
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
|
||||||
const [updateJob] = useMutation(UPDATE_JOB);
|
const [updateJob] = useMutation(UPDATE_JOB);
|
||||||
@@ -34,16 +38,24 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
|
|||||||
const jobUpdate = await updateJob({
|
const jobUpdate = await updateJob({
|
||||||
variables: {
|
variables: {
|
||||||
jobId: event.job.id,
|
jobId: event.job.id,
|
||||||
|
|
||||||
job: {
|
job: {
|
||||||
date_scheduled: null,
|
date_scheduled: null,
|
||||||
scheduled_in: null,
|
scheduled_in: null,
|
||||||
scheduled_completion: null,
|
scheduled_completion: null,
|
||||||
lost_sale_reason,
|
lost_sale_reason,
|
||||||
|
date_lost_sale: new Date(),
|
||||||
status: bodyshop.md_ro_statuses.default_imported,
|
status: bodyshop.md_ro_statuses.default_imported,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (!jobUpdate.errors) {
|
||||||
|
dispatch(
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: event.job.id,
|
||||||
|
operation: AuditTrailMapping.appointmentcancel(lost_sale_reason),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
if (!!jobUpdate.errors) {
|
if (!!jobUpdate.errors) {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("jobs.errors.updating", {
|
message: t("jobs.errors.updating", {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
|
import { DateTimeFormat } from "./../../utils/DateFormatter";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -53,7 +54,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
|||||||
operation: AuditTrailMapping.admin_jobfieldchange(
|
operation: AuditTrailMapping.admin_jobfieldchange(
|
||||||
key,
|
key,
|
||||||
changedAuditFields[key] instanceof moment
|
changedAuditFields[key] instanceof moment
|
||||||
? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a")
|
? DateTimeFormat(changedAuditFields[key])
|
||||||
: changedAuditFields[key]
|
: changedAuditFields[key]
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@@ -179,6 +180,12 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
|||||||
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
|
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
|
||||||
<DateTimePicker />
|
<DateTimePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.fields.date_lost_sale")}
|
||||||
|
name="date_lost_sale"
|
||||||
|
>
|
||||||
|
<DateTimePicker />
|
||||||
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ export function JobsAvailableContainer({
|
|||||||
|
|
||||||
const [selectedJob, setSelectedJob] = useState(null);
|
const [selectedJob, setSelectedJob] = useState(null);
|
||||||
const [selectedOwner, setSelectedOwner] = useState(null);
|
const [selectedOwner, setSelectedOwner] = useState(null);
|
||||||
|
const [partsQueueToggle, setPartsQueueToggle] = useState(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||||
|
|
||||||
const [insertLoading, setInsertLoading] = useState(false);
|
const [insertLoading, setInsertLoading] = useState(false);
|
||||||
|
|
||||||
const [insertNote] = useMutation(INSERT_NEW_NOTE);
|
const [insertNote] = useMutation(INSERT_NEW_NOTE);
|
||||||
@@ -94,6 +96,7 @@ export function JobsAvailableContainer({
|
|||||||
logImEXEvent("job_import_new");
|
logImEXEvent("job_import_new");
|
||||||
|
|
||||||
setOwnerModalVisible(false);
|
setOwnerModalVisible(false);
|
||||||
|
|
||||||
setInsertLoading(true);
|
setInsertLoading(true);
|
||||||
|
|
||||||
const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk);
|
const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk);
|
||||||
@@ -120,7 +123,7 @@ export function JobsAvailableContainer({
|
|||||||
|
|
||||||
let existingVehicles;
|
let existingVehicles;
|
||||||
if (estData.est_data.v_vin) {
|
if (estData.est_data.v_vin) {
|
||||||
//There's vehicle data, need to double check the VIN.
|
//There's vehicle data, need to double-check the VIN.
|
||||||
existingVehicles = await client.query({
|
existingVehicles = await client.query({
|
||||||
query: SEARCH_VEHICLE_BY_VIN,
|
query: SEARCH_VEHICLE_BY_VIN,
|
||||||
variables: {
|
variables: {
|
||||||
@@ -143,7 +146,7 @@ export function JobsAvailableContainer({
|
|||||||
text: t("jobs.labels.importnote"),
|
text: t("jobs.labels.importnote"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
queued_for_parts: true,
|
queued_for_parts: partsQueueToggle,
|
||||||
...(existingVehicles && existingVehicles.data.vehicles.length > 0
|
...(existingVehicles && existingVehicles.data.vehicles.length > 0
|
||||||
? { vehicleid: existingVehicles.data.vehicles[0].id, vehicle: null }
|
? { vehicleid: existingVehicles.data.vehicles[0].id, vehicle: null }
|
||||||
: {}),
|
: {}),
|
||||||
@@ -157,46 +160,51 @@ export function JobsAvailableContainer({
|
|||||||
delete newJob.vehicle;
|
delete newJob.vehicle;
|
||||||
}
|
}
|
||||||
|
|
||||||
insertNewJob({
|
try {
|
||||||
variables: {
|
const r = await insertNewJob({
|
||||||
job: newJob,
|
variables: {
|
||||||
},
|
job: newJob,
|
||||||
})
|
},
|
||||||
.then((r) => {
|
});
|
||||||
if (CriticalPartsScanning.treatment === "on") {
|
|
||||||
CriticalPartsScan(r.data.insert_jobs.returning[0].id);
|
|
||||||
}
|
|
||||||
notification["success"]({
|
|
||||||
message: t("jobs.successes.created"),
|
|
||||||
onClick: () => {
|
|
||||||
history.push(`/manage/jobs/${r.data.insert_jobs.returning[0].id}`);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
//Job has been inserted. Clean up the available jobs record.
|
|
||||||
|
|
||||||
insertAuditTrail({
|
if (CriticalPartsScanning.treatment === "on") {
|
||||||
jobid: r.data.insert_jobs.returning[0].id,
|
CriticalPartsScan(r.data.insert_jobs.returning[0].id);
|
||||||
operation: AuditTrailMapping.jobimported(),
|
}
|
||||||
});
|
|
||||||
|
|
||||||
deleteJob({
|
notification["success"]({
|
||||||
variables: { id: estData.id },
|
message: t("jobs.successes.created"),
|
||||||
}).then((r) => {
|
onClick: () => {
|
||||||
refetch();
|
history.push(`/manage/jobs/${r.data.insert_jobs.returning[0].id}`);
|
||||||
setInsertLoading(false);
|
},
|
||||||
});
|
});
|
||||||
})
|
//Job has been inserted. Clean up the available jobs record.
|
||||||
.catch((r) => {
|
|
||||||
//error while inserting
|
insertAuditTrail({
|
||||||
notification["error"]({
|
jobid: r.data.insert_jobs.returning[0].id,
|
||||||
message: t("jobs.errors.creating", { error: r.message }),
|
operation: AuditTrailMapping.jobimported(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
deleteJob({
|
||||||
|
variables: { id: estData.id },
|
||||||
|
}).then((r) => {
|
||||||
refetch();
|
refetch();
|
||||||
setInsertLoading(false);
|
setInsertLoading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||||
|
} catch (err) {
|
||||||
|
//error while inserting
|
||||||
|
notification["error"]({
|
||||||
|
message: t("jobs.errors.creating", { error: err.message }),
|
||||||
|
});
|
||||||
|
refetch().catch(e => {console.error(`Something went wrong in jobs available table container - ${err.message || ''}`)});
|
||||||
|
setInsertLoading(false);
|
||||||
|
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Suplement scenario
|
//Supplement scenario
|
||||||
const onJobFindModalOk = async () => {
|
const onJobFindModalOk = async () => {
|
||||||
logImEXEvent("job_import_supplement");
|
logImEXEvent("job_import_supplement");
|
||||||
|
|
||||||
@@ -248,11 +256,14 @@ export function JobsAvailableContainer({
|
|||||||
// "0.00"
|
// "0.00"
|
||||||
// ),
|
// ),
|
||||||
// job_totals: newTotals,
|
// job_totals: newTotals,
|
||||||
// queued_for_parts: true,
|
queued_for_parts: partsQueueToggle,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (CriticalPartsScanning.treatment === "on") {
|
|
||||||
|
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||||
|
|
||||||
|
if (CriticalPartsScanning.treatment === "on") {
|
||||||
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id);
|
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id);
|
||||||
}
|
}
|
||||||
if (updateResult.errors) {
|
if (updateResult.errors) {
|
||||||
@@ -327,12 +338,14 @@ export function JobsAvailableContainer({
|
|||||||
const onOwnerModalCancel = () => {
|
const onOwnerModalCancel = () => {
|
||||||
setOwnerModalVisible(false);
|
setOwnerModalVisible(false);
|
||||||
setSelectedOwner(null);
|
setSelectedOwner(null);
|
||||||
|
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onJobModalCancel = () => {
|
const onJobModalCancel = () => {
|
||||||
setJobModalVisible(false);
|
setJobModalVisible(false);
|
||||||
modalSearchState[1]("");
|
modalSearchState[1]("");
|
||||||
setSelectedJob(null);
|
setSelectedJob(null);
|
||||||
|
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addJobAsNew = (record) => {
|
const addJobAsNew = (record) => {
|
||||||
@@ -353,6 +366,8 @@ export function JobsAvailableContainer({
|
|||||||
}, [addJobAsSupp, availableJobId, clm_no]);
|
}, [addJobAsSupp, availableJobId, clm_no]);
|
||||||
|
|
||||||
if (error) return <AlertComponent type="error" message={error.message} />;
|
if (error) return <AlertComponent type="error" message={error.message} />;
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoadingSpinner
|
<LoadingSpinner
|
||||||
loading={insertLoading}
|
loading={insertLoading}
|
||||||
@@ -362,11 +377,14 @@ export function JobsAvailableContainer({
|
|||||||
loading={estDataRaw.loading}
|
loading={estDataRaw.loading}
|
||||||
error={estDataRaw.error}
|
error={estDataRaw.error}
|
||||||
owner={owner}
|
owner={owner}
|
||||||
|
partsQueueToggle={partsQueueToggle}
|
||||||
|
setPartsQueueToggle={setPartsQueueToggle}
|
||||||
selectedOwner={selectedOwner}
|
selectedOwner={selectedOwner}
|
||||||
setSelectedOwner={setSelectedOwner}
|
setSelectedOwner={setSelectedOwner}
|
||||||
visible={ownerModalVisible}
|
visible={ownerModalVisible}
|
||||||
onOk={onOwnerFindModalOk}
|
onOk={onOwnerFindModalOk}
|
||||||
onCancel={onOwnerModalCancel}
|
onCancel={onOwnerModalCancel}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
<JobsFindModalContainer
|
<JobsFindModalContainer
|
||||||
loading={estDataRaw.loading}
|
loading={estDataRaw.loading}
|
||||||
@@ -378,6 +396,8 @@ export function JobsAvailableContainer({
|
|||||||
onOk={onJobFindModalOk}
|
onOk={onJobFindModalOk}
|
||||||
onCancel={onJobModalCancel}
|
onCancel={onJobModalCancel}
|
||||||
modalSearchState={modalSearchState}
|
modalSearchState={modalSearchState}
|
||||||
|
partsQueueToggle={partsQueueToggle}
|
||||||
|
setPartsQueueToggle={setPartsQueueToggle}
|
||||||
/>
|
/>
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
|
|||||||
@@ -145,6 +145,13 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
|||||||
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
|
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
|
||||||
<DateTimePicker disabled={true || jobRO} />
|
<DateTimePicker disabled={true || jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.fields.date_lost_sale")}
|
||||||
|
name="date_lost_sale"
|
||||||
|
>
|
||||||
|
<DateTimePicker disabled={true || jobRO} />
|
||||||
|
</Form.Item>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,12 +18,14 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { CANCEL_APPOINTMENTS_BY_JOB_ID } from "../../graphql/appointments.queries";
|
import { CANCEL_APPOINTMENTS_BY_JOB_ID } from "../../graphql/appointments.queries";
|
||||||
import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries";
|
import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent";
|
import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent";
|
||||||
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
||||||
@@ -50,6 +52,8 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
dispatch(setModalContext({ context: context, modal: "timeTicket" })),
|
dispatch(setModalContext({ context: context, modal: "timeTicket" })),
|
||||||
setCardPaymentContext: (context) =>
|
setCardPaymentContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
||||||
|
insertAuditTrail: ({ jobid, operation }) =>
|
||||||
|
dispatch(insertAuditTrail({ jobid, operation })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsDetailHeaderActions({
|
export function JobsDetailHeaderActions({
|
||||||
@@ -64,6 +68,7 @@ export function JobsDetailHeaderActions({
|
|||||||
jobRO,
|
jobRO,
|
||||||
setTimeTicketContext,
|
setTimeTicketContext,
|
||||||
setCardPaymentContext,
|
setCardPaymentContext,
|
||||||
|
insertAuditTrail,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
@@ -158,6 +163,7 @@ export function JobsDetailHeaderActions({
|
|||||||
scheduled_in: null,
|
scheduled_in: null,
|
||||||
scheduled_completion: null,
|
scheduled_completion: null,
|
||||||
lost_sale_reason,
|
lost_sale_reason,
|
||||||
|
date_lost_sale: new Date(),
|
||||||
status: bodyshop.md_ro_statuses.default_imported,
|
status: bodyshop.md_ro_statuses.default_imported,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -166,6 +172,11 @@ export function JobsDetailHeaderActions({
|
|||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("appointments.successes.canceled"),
|
message: t("appointments.successes.canceled"),
|
||||||
});
|
});
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: job.id,
|
||||||
|
operation:
|
||||||
|
AuditTrailMapping.appointmentcancel(lost_sale_reason),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ export default function JobsFindModalComponent({
|
|||||||
importOptionsState,
|
importOptionsState,
|
||||||
modalSearchState,
|
modalSearchState,
|
||||||
jobsListRefetch,
|
jobsListRefetch,
|
||||||
|
partsQueueToggle,
|
||||||
|
setPartsQueueToggle,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [modalSearch, setModalSearch] = modalSearchState;
|
const [modalSearch, setModalSearch] = modalSearchState;
|
||||||
@@ -199,6 +201,12 @@ export default function JobsFindModalComponent({
|
|||||||
>
|
>
|
||||||
{t("jobs.labels.override_header")}
|
{t("jobs.labels.override_header")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
checked={partsQueueToggle}
|
||||||
|
onChange={(e) => setPartsQueueToggle(e.target.checked)}
|
||||||
|
>
|
||||||
|
{t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
|
||||||
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export default connect(
|
|||||||
setSelectedJob,
|
setSelectedJob,
|
||||||
importOptionsState,
|
importOptionsState,
|
||||||
modalSearchState,
|
modalSearchState,
|
||||||
|
partsQueueToggle,
|
||||||
|
setPartsQueueToggle,
|
||||||
...modalProps
|
...modalProps
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -91,6 +93,8 @@ export default connect(
|
|||||||
jobsListRefetch={jobsList.refetch}
|
jobsListRefetch={jobsList.refetch}
|
||||||
jobsList={jobsData}
|
jobsList={jobsData}
|
||||||
modalSearchState={modalSearchState}
|
modalSearchState={modalSearchState}
|
||||||
|
partsQueueToggle={partsQueueToggle}
|
||||||
|
setPartsQueueToggle={setPartsQueueToggle}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ export default function OwnerFindModalComponent({
|
|||||||
setSelectedOwner,
|
setSelectedOwner,
|
||||||
ownersListLoading,
|
ownersListLoading,
|
||||||
ownersList,
|
ownersList,
|
||||||
|
partsQueueToggle,
|
||||||
|
setPartsQueueToggle,
|
||||||
}) {
|
}) {
|
||||||
//setSelectedOwner is used to set the record id of the owner to use for adding the job.
|
//setSelectedOwner is used to set the record id of the owner to use for adding the job.
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: t("owners.fields.ownr_ln"),
|
title: t("owners.fields.ownr_ln"),
|
||||||
@@ -109,6 +110,12 @@ export default function OwnerFindModalComponent({
|
|||||||
>
|
>
|
||||||
{t("owners.labels.create_new")}
|
{t("owners.labels.create_new")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
checked={partsQueueToggle}
|
||||||
|
onChange={(e) => setPartsQueueToggle(e.target.checked)}
|
||||||
|
>
|
||||||
|
{t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
|
||||||
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ export default function OwnerFindModalContainer({
|
|||||||
owner,
|
owner,
|
||||||
selectedOwner,
|
selectedOwner,
|
||||||
setSelectedOwner,
|
setSelectedOwner,
|
||||||
|
partsQueueToggle,
|
||||||
|
setPartsQueueToggle,
|
||||||
...modalProps
|
...modalProps
|
||||||
}) {
|
}) {
|
||||||
//use owner object to run query and find what possible owners there are.
|
//use owner object to run query and find what possible owners there are.
|
||||||
@@ -59,6 +61,8 @@ export default function OwnerFindModalContainer({
|
|||||||
selectedOwner={selectedOwner}
|
selectedOwner={selectedOwner}
|
||||||
setSelectedOwner={setSelectedOwner}
|
setSelectedOwner={setSelectedOwner}
|
||||||
ownersListLoading={ownersList.loading}
|
ownersListLoading={ownersList.loading}
|
||||||
|
partsQueueToggle={partsQueueToggle}
|
||||||
|
setPartsQueueToggle={setPartsQueueToggle}
|
||||||
ownersList={
|
ownersList={
|
||||||
ownersList.data && ownersList.data.search_owners
|
ownersList.data && ownersList.data.search_owners
|
||||||
? ownersList.data.search_owners
|
? ownersList.data.search_owners
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ import ProductionBoardCard from "../production-board-kanban-card/production-boar
|
|||||||
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
|
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
|
||||||
import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component";
|
import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component";
|
||||||
//import "@asseinfo/react-kanban/dist/styles.css";
|
//import "@asseinfo/react-kanban/dist/styles.css";
|
||||||
|
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
|
||||||
import "./production-board-kanban.styles.scss";
|
import "./production-board-kanban.styles.scss";
|
||||||
import { createBoardData } from "./production-board-kanban.utils.js";
|
import { createBoardData } from "./production-board-kanban.utils.js";
|
||||||
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
technician: selectTechnician,
|
technician: selectTechnician,
|
||||||
@@ -153,6 +153,18 @@ export function ProductionBoardKanbanComponent({
|
|||||||
0
|
0
|
||||||
)
|
)
|
||||||
.toFixed(1);
|
.toFixed(1);
|
||||||
|
const totalLAB = data
|
||||||
|
.reduce(
|
||||||
|
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
.toFixed(1);
|
||||||
|
const totalLAR = data
|
||||||
|
.reduce(
|
||||||
|
(acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
.toFixed(1);
|
||||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||||
.filter((screen) => !!screen[1])
|
.filter((screen) => !!screen[1])
|
||||||
.slice(-1)[0];
|
.slice(-1)[0];
|
||||||
@@ -236,6 +248,14 @@ export function ProductionBoardKanbanComponent({
|
|||||||
title={t("dashboard.titles.productionhours")}
|
title={t("dashboard.titles.productionhours")}
|
||||||
value={totalHrs}
|
value={totalHrs}
|
||||||
/>
|
/>
|
||||||
|
<Statistic
|
||||||
|
title={t("dashboard.titles.labhours")}
|
||||||
|
value={totalLAB}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title={t("dashboard.titles.larhours")}
|
||||||
|
value={totalLAR}
|
||||||
|
/>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("appointments.labels.inproduction")}
|
title={t("appointments.labels.inproduction")}
|
||||||
value={data && data.length}
|
value={data && data.length}
|
||||||
|
|||||||
@@ -184,6 +184,18 @@ export function ProductionListTable({
|
|||||||
0
|
0
|
||||||
)
|
)
|
||||||
.toFixed(1);
|
.toFixed(1);
|
||||||
|
const totalLAB = data
|
||||||
|
.reduce(
|
||||||
|
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
.toFixed(1);
|
||||||
|
const totalLAR = data
|
||||||
|
.reduce(
|
||||||
|
(acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
.toFixed(1);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
@@ -193,6 +205,14 @@ export function ProductionListTable({
|
|||||||
title={t("dashboard.titles.productionhours")}
|
title={t("dashboard.titles.productionhours")}
|
||||||
value={totalHrs}
|
value={totalHrs}
|
||||||
/>
|
/>
|
||||||
|
<Statistic
|
||||||
|
title={t("dashboard.titles.labhours")}
|
||||||
|
value={totalLAB}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title={t("dashboard.titles.larhours")}
|
||||||
|
value={totalLAR}
|
||||||
|
/>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("appointments.labels.inproduction")}
|
title={t("appointments.labels.inproduction")}
|
||||||
value={dataSource && dataSource.length}
|
value={dataSource && dataSource.length}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
QUERY_APPOINTMENTS_BY_JOBID,
|
QUERY_APPOINTMENTS_BY_JOBID,
|
||||||
} from "../../graphql/appointments.queries";
|
} from "../../graphql/appointments.queries";
|
||||||
import { QUERY_LBR_HRS_BY_PK, UPDATE_JOBS } from "../../graphql/jobs.queries";
|
import { QUERY_LBR_HRS_BY_PK, UPDATE_JOBS } from "../../graphql/jobs.queries";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
import { selectSchedule } from "../../redux/modals/modals.selectors";
|
import { selectSchedule } from "../../redux/modals/modals.selectors";
|
||||||
@@ -20,6 +21,8 @@ import {
|
|||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
|
import { DateTimeFormat } from "../../utils/DateFormatter";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import ScheduleJobModalComponent from "./schedule-job-modal.component";
|
import ScheduleJobModalComponent from "./schedule-job-modal.component";
|
||||||
|
|
||||||
@@ -31,6 +34,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
toggleModalVisible: () => dispatch(toggleModalVisible("schedule")),
|
toggleModalVisible: () => dispatch(toggleModalVisible("schedule")),
|
||||||
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
||||||
|
insertAuditTrail: ({ jobid, operation }) =>
|
||||||
|
dispatch(insertAuditTrail({ jobid, operation })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ScheduleJobModalContainer({
|
export function ScheduleJobModalContainer({
|
||||||
@@ -39,6 +44,7 @@ export function ScheduleJobModalContainer({
|
|||||||
toggleModalVisible,
|
toggleModalVisible,
|
||||||
setEmailOptions,
|
setEmailOptions,
|
||||||
currentUser,
|
currentUser,
|
||||||
|
insertAuditTrail,
|
||||||
}) {
|
}) {
|
||||||
const { visible, context, actions } = scheduleModal;
|
const { visible, context, actions } = scheduleModal;
|
||||||
const { jobId, job, previousEvent } = context;
|
const { jobId, job, previousEvent } = context;
|
||||||
@@ -134,6 +140,15 @@ export function ScheduleJobModalContainer({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!appt.errors) {
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: job.id,
|
||||||
|
operation: AuditTrailMapping.appointmentinsert(
|
||||||
|
DateTimeFormat(values.start)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!!appt.errors) {
|
if (!!appt.errors) {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("appointments.errors.saving", {
|
message: t("appointments.errors.saving", {
|
||||||
@@ -155,6 +170,7 @@ export function ScheduleJobModalContainer({
|
|||||||
scheduled_in: values.start,
|
scheduled_in: values.start,
|
||||||
scheduled_completion: values.scheduled_completion,
|
scheduled_completion: values.scheduled_completion,
|
||||||
lost_sale_reason: null,
|
lost_sale_reason: null,
|
||||||
|
date_lost_sale: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,12 +42,20 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
|||||||
bodyshop && bodyshop.imexshopid
|
bodyshop && bodyshop.imexshopid
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<LayoutFormRow
|
<LayoutFormRow
|
||||||
header={t("bodyshop.labels.businessinformation")}
|
header={t("bodyshop.labels.businessinformation")}
|
||||||
id="businessinformation"
|
id="businessinformation"
|
||||||
>
|
>
|
||||||
|
<Form.Item
|
||||||
|
label={t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
|
||||||
|
name={["md_functionality_toggles","parts_queue_toggle"]}
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.shopname")}
|
label={t("bodyshop.fields.shopname")}
|
||||||
name="shopname"
|
name="shopname"
|
||||||
|
|||||||
@@ -271,6 +271,7 @@ export const CANCEL_APPOINTMENTS_BY_JOB_ID = gql`
|
|||||||
scheduled_completion
|
scheduled_completion
|
||||||
status
|
status
|
||||||
lost_sale_reason
|
lost_sale_reason
|
||||||
|
date_lost_sale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export const QUERY_BODYSHOP = gql`
|
|||||||
logo_img_path
|
logo_img_path
|
||||||
md_ro_statuses
|
md_ro_statuses
|
||||||
md_order_statuses
|
md_order_statuses
|
||||||
|
md_functionality_toggles
|
||||||
shopname
|
shopname
|
||||||
state
|
state
|
||||||
state_tax_id
|
state_tax_id
|
||||||
@@ -158,6 +159,7 @@ export const UPDATE_SHOP = gql`
|
|||||||
logo_img_path
|
logo_img_path
|
||||||
md_ro_statuses
|
md_ro_statuses
|
||||||
md_order_statuses
|
md_order_statuses
|
||||||
|
md_functionality_toggles
|
||||||
shopname
|
shopname
|
||||||
state
|
state
|
||||||
state_tax_id
|
state_tax_id
|
||||||
|
|||||||
@@ -675,6 +675,7 @@ export const GET_JOB_BY_PK = gql`
|
|||||||
date_scheduled
|
date_scheduled
|
||||||
date_invoiced
|
date_invoiced
|
||||||
date_last_contacted
|
date_last_contacted
|
||||||
|
date_lost_sale
|
||||||
date_next_contact
|
date_next_contact
|
||||||
date_towin
|
date_towin
|
||||||
date_rentalresp
|
date_rentalresp
|
||||||
@@ -1077,6 +1078,7 @@ export const UPDATE_JOB = gql`
|
|||||||
actual_in
|
actual_in
|
||||||
date_repairstarted
|
date_repairstarted
|
||||||
date_void
|
date_void
|
||||||
|
date_lost_sale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,19 +3,19 @@ import Icon, {
|
|||||||
CalendarFilled,
|
CalendarFilled,
|
||||||
DollarCircleOutlined,
|
DollarCircleOutlined,
|
||||||
FileImageFilled,
|
FileImageFilled,
|
||||||
PrinterFilled,
|
|
||||||
ToolFilled,
|
|
||||||
HistoryOutlined,
|
HistoryOutlined,
|
||||||
|
PrinterFilled,
|
||||||
SyncOutlined,
|
SyncOutlined,
|
||||||
|
ToolFilled,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Divider,
|
Divider,
|
||||||
Form,
|
Form,
|
||||||
notification,
|
|
||||||
PageHeader,
|
PageHeader,
|
||||||
Space,
|
Space,
|
||||||
Tabs,
|
Tabs,
|
||||||
|
notification,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
@@ -27,6 +27,7 @@ import { connect } from "react-redux";
|
|||||||
import { useHistory, useLocation } from "react-router-dom";
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
|
import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
|
||||||
|
import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component";
|
||||||
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
|
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
|
||||||
import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container";
|
import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container";
|
||||||
import JobReconciliationModal from "../../components/job-reconciliation-modal/job-reconciliation.modal.container";
|
import JobReconciliationModal from "../../components/job-reconciliation-modal/job-reconciliation.modal.container";
|
||||||
@@ -42,17 +43,17 @@ import JobsDetailPliContainer from "../../components/jobs-detail-pli/jobs-detail
|
|||||||
import JobsDetailRates from "../../components/jobs-detail-rates/jobs-detail-rates.component";
|
import JobsDetailRates from "../../components/jobs-detail-rates/jobs-detail-rates.component";
|
||||||
import JobsDetailTotals from "../../components/jobs-detail-totals/jobs-detail-totals.component";
|
import JobsDetailTotals from "../../components/jobs-detail-totals/jobs-detail-totals.component";
|
||||||
import JobsDocumentsGalleryContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container";
|
import JobsDocumentsGalleryContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container";
|
||||||
|
import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||||
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
|
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
|
||||||
|
import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||||
import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
|
import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component";
|
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
|
||||||
import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
|
||||||
import UndefinedToNull from "../../utils/undefinedtonull";
|
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||||
import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container";
|
import { DateTimeFormat } from "./../../utils/DateFormatter";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -172,7 +173,7 @@ export function JobsDetailPage({
|
|||||||
operation: AuditTrailMapping.jobfieldchange(
|
operation: AuditTrailMapping.jobfieldchange(
|
||||||
key,
|
key,
|
||||||
changedAuditFields[key] instanceof moment
|
changedAuditFields[key] instanceof moment
|
||||||
? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a")
|
? DateTimeFormat(changedAuditFields[key])
|
||||||
: changedAuditFields[key]
|
: changedAuditFields[key]
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -103,6 +103,8 @@
|
|||||||
"admin_jobmarkforreexport": "ADMIN: Job marked for re-export.",
|
"admin_jobmarkforreexport": "ADMIN: Job marked for re-export.",
|
||||||
"admin_jobuninvoice": "ADMIN: Job has been uninvoiced.",
|
"admin_jobuninvoice": "ADMIN: Job has been uninvoiced.",
|
||||||
"admin_jobunvoid": "ADMIN: Job has been unvoided.",
|
"admin_jobunvoid": "ADMIN: Job has been unvoided.",
|
||||||
|
"appointmentcancel": "Appointment canceled. Lost Reason: {{lost_sale_reason}}.",
|
||||||
|
"appointmentinsert": "Appointment created. Appointment Date: {{start}}.",
|
||||||
"billposted": "Bill with invoice number {{invoice_number}} posted.",
|
"billposted": "Bill with invoice number {{invoice_number}} posted.",
|
||||||
"billupdated": "Bill with invoice number {{invoice_number}} updated.",
|
"billupdated": "Bill with invoice number {{invoice_number}} updated.",
|
||||||
"failedpayment": "Failed payment",
|
"failedpayment": "Failed payment",
|
||||||
@@ -347,6 +349,9 @@
|
|||||||
},
|
},
|
||||||
"md_payment_types": "Payment Types",
|
"md_payment_types": "Payment Types",
|
||||||
"md_referral_sources": "Referral Sources",
|
"md_referral_sources": "Referral Sources",
|
||||||
|
"md_functionality_toggles": {
|
||||||
|
"parts_queue_toggle": "Automatically Add Imported/Supplemented Jobs to Parts Queue"
|
||||||
|
},
|
||||||
"md_tasks_presets": {
|
"md_tasks_presets": {
|
||||||
"hourstype": "",
|
"hourstype": "",
|
||||||
"memo": "",
|
"memo": "",
|
||||||
@@ -855,14 +860,16 @@
|
|||||||
"refhrs": "Refinish Hrs"
|
"refhrs": "Refinish Hrs"
|
||||||
},
|
},
|
||||||
"titles": {
|
"titles": {
|
||||||
|
"labhours": "Total Body Hours",
|
||||||
|
"larhours": "Total Refinish Hours",
|
||||||
"monthlyemployeeefficiency": "Monthly Employee Efficiency",
|
"monthlyemployeeefficiency": "Monthly Employee Efficiency",
|
||||||
"monthlyjobcosting": "Monthly Job Costing ",
|
"monthlyjobcosting": "Monthly Job Costing ",
|
||||||
"monthlylaborsales": "Monthly Labor Sales",
|
"monthlylaborsales": "Monthly Labor Sales",
|
||||||
"monthlypartssales": "Monthly Parts Sales",
|
"monthlypartssales": "Monthly Parts Sales",
|
||||||
"monthlyrevenuegraph": "Monthly Revenue Graph",
|
"monthlyrevenuegraph": "Monthly Revenue Graph",
|
||||||
"prodhrssummary": "Production Hours Summary",
|
"prodhrssummary": "Production Hours Summary",
|
||||||
"productiondollars": "Total dollars in Production",
|
"productiondollars": "Total Dollars in Production",
|
||||||
"productionhours": "Total hours in Production",
|
"productionhours": "Total Hours in Production",
|
||||||
"projectedmonthlysales": "Projected Monthly Sales",
|
"projectedmonthlysales": "Projected Monthly Sales",
|
||||||
"scheduledintoday": "Sheduled In Today: {{date}}",
|
"scheduledintoday": "Sheduled In Today: {{date}}",
|
||||||
"scheduledouttoday": "Sheduled Out Today: {{date}}"
|
"scheduledouttoday": "Sheduled Out Today: {{date}}"
|
||||||
@@ -1442,6 +1449,7 @@
|
|||||||
"date_exported": "Exported",
|
"date_exported": "Exported",
|
||||||
"date_invoiced": "Invoiced",
|
"date_invoiced": "Invoiced",
|
||||||
"date_last_contacted": "Last Contacted Date",
|
"date_last_contacted": "Last Contacted Date",
|
||||||
|
"date_lost_sale": "Lost Sale",
|
||||||
"date_next_contact": "Next Contact Date",
|
"date_next_contact": "Next Contact Date",
|
||||||
"date_open": "Open",
|
"date_open": "Open",
|
||||||
"date_rentalresp": "Shop Rental Responsibility Start",
|
"date_rentalresp": "Shop Rental Responsibility Start",
|
||||||
@@ -2596,6 +2604,7 @@
|
|||||||
"jobs_reconcile": "Parts/Sublet/Labor Reconciliation",
|
"jobs_reconcile": "Parts/Sublet/Labor Reconciliation",
|
||||||
"jobs_scheduled_completion": "Jobs Scheduled Completion",
|
"jobs_scheduled_completion": "Jobs Scheduled Completion",
|
||||||
"lag_time": "Lag Time",
|
"lag_time": "Lag Time",
|
||||||
|
"lost_sales": "Lost Sales",
|
||||||
"open_orders": "Open Orders by Date",
|
"open_orders": "Open Orders by Date",
|
||||||
"open_orders_csr": "Open Orders by CSR",
|
"open_orders_csr": "Open Orders by CSR",
|
||||||
"open_orders_estimator": "Open Orders by Estimator",
|
"open_orders_estimator": "Open Orders by Estimator",
|
||||||
|
|||||||
@@ -103,6 +103,8 @@
|
|||||||
"admin_jobmarkforreexport": "",
|
"admin_jobmarkforreexport": "",
|
||||||
"admin_jobuninvoice": "",
|
"admin_jobuninvoice": "",
|
||||||
"admin_jobunvoid": "",
|
"admin_jobunvoid": "",
|
||||||
|
"appointmentcancel": "",
|
||||||
|
"appointmentinsert": "",
|
||||||
"billposted": "",
|
"billposted": "",
|
||||||
"billupdated": "",
|
"billupdated": "",
|
||||||
"failedpayment": "",
|
"failedpayment": "",
|
||||||
@@ -252,6 +254,9 @@
|
|||||||
"address1": "",
|
"address1": "",
|
||||||
"address2": "",
|
"address2": "",
|
||||||
"appt_alt_transport": "",
|
"appt_alt_transport": "",
|
||||||
|
"md_functionality_toggles": {
|
||||||
|
"parts_queue_toggle": "Parts Queue Toggle"
|
||||||
|
},
|
||||||
"appt_colors": {
|
"appt_colors": {
|
||||||
"color": "",
|
"color": "",
|
||||||
"label": ""
|
"label": ""
|
||||||
@@ -855,6 +860,8 @@
|
|||||||
"refhrs": ""
|
"refhrs": ""
|
||||||
},
|
},
|
||||||
"titles": {
|
"titles": {
|
||||||
|
"labhours": "",
|
||||||
|
"larhours": "",
|
||||||
"monthlyemployeeefficiency": "",
|
"monthlyemployeeefficiency": "",
|
||||||
"monthlyjobcosting": "",
|
"monthlyjobcosting": "",
|
||||||
"monthlylaborsales": "",
|
"monthlylaborsales": "",
|
||||||
@@ -1442,6 +1449,7 @@
|
|||||||
"date_exported": "Exportado",
|
"date_exported": "Exportado",
|
||||||
"date_invoiced": "Facturado",
|
"date_invoiced": "Facturado",
|
||||||
"date_last_contacted": "",
|
"date_last_contacted": "",
|
||||||
|
"date_lost_sale": "",
|
||||||
"date_next_contact": "",
|
"date_next_contact": "",
|
||||||
"date_open": "Abierto",
|
"date_open": "Abierto",
|
||||||
"date_rentalresp": "",
|
"date_rentalresp": "",
|
||||||
@@ -2596,6 +2604,7 @@
|
|||||||
"jobs_reconcile": "",
|
"jobs_reconcile": "",
|
||||||
"jobs_scheduled_completion": "",
|
"jobs_scheduled_completion": "",
|
||||||
"lag_time": "",
|
"lag_time": "",
|
||||||
|
"lost_sales": "",
|
||||||
"open_orders": "",
|
"open_orders": "",
|
||||||
"open_orders_csr": "",
|
"open_orders_csr": "",
|
||||||
"open_orders_estimator": "",
|
"open_orders_estimator": "",
|
||||||
|
|||||||
@@ -103,6 +103,8 @@
|
|||||||
"admin_jobmarkforreexport": "",
|
"admin_jobmarkforreexport": "",
|
||||||
"admin_jobuninvoice": "",
|
"admin_jobuninvoice": "",
|
||||||
"admin_jobunvoid": "",
|
"admin_jobunvoid": "",
|
||||||
|
"appointmentcancel": "",
|
||||||
|
"appointmentinsert": "",
|
||||||
"billposted": "",
|
"billposted": "",
|
||||||
"billupdated": "",
|
"billupdated": "",
|
||||||
"failedpayment": "",
|
"failedpayment": "",
|
||||||
@@ -329,6 +331,9 @@
|
|||||||
"paint": "",
|
"paint": "",
|
||||||
"prep": ""
|
"prep": ""
|
||||||
},
|
},
|
||||||
|
"md_functionality_toggles": {
|
||||||
|
"parts_queue_toggle": "Parts Queue Toggle"
|
||||||
|
},
|
||||||
"md_ins_co": {
|
"md_ins_co": {
|
||||||
"city": "",
|
"city": "",
|
||||||
"name": "",
|
"name": "",
|
||||||
@@ -855,6 +860,8 @@
|
|||||||
"refhrs": ""
|
"refhrs": ""
|
||||||
},
|
},
|
||||||
"titles": {
|
"titles": {
|
||||||
|
"labhours": "",
|
||||||
|
"larhours": "",
|
||||||
"monthlyemployeeefficiency": "",
|
"monthlyemployeeefficiency": "",
|
||||||
"monthlyjobcosting": "",
|
"monthlyjobcosting": "",
|
||||||
"monthlylaborsales": "",
|
"monthlylaborsales": "",
|
||||||
@@ -1442,6 +1449,7 @@
|
|||||||
"date_exported": "Exportés",
|
"date_exported": "Exportés",
|
||||||
"date_invoiced": "Facturé",
|
"date_invoiced": "Facturé",
|
||||||
"date_last_contacted": "",
|
"date_last_contacted": "",
|
||||||
|
"date_lost_sale": "",
|
||||||
"date_next_contact": "",
|
"date_next_contact": "",
|
||||||
"date_open": "Ouvrir",
|
"date_open": "Ouvrir",
|
||||||
"date_rentalresp": "",
|
"date_rentalresp": "",
|
||||||
@@ -2596,6 +2604,7 @@
|
|||||||
"jobs_reconcile": "",
|
"jobs_reconcile": "",
|
||||||
"jobs_scheduled_completion": "",
|
"jobs_scheduled_completion": "",
|
||||||
"lag_time": "",
|
"lag_time": "",
|
||||||
|
"lost_sales": "",
|
||||||
"open_orders": "",
|
"open_orders": "",
|
||||||
"open_orders_csr": "",
|
"open_orders_csr": "",
|
||||||
"open_orders_estimator": "",
|
"open_orders_estimator": "",
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
|
|
||||||
const AuditTrailMapping = {
|
const AuditTrailMapping = {
|
||||||
|
appointmentcancel: (lost_sale_reason) =>
|
||||||
|
i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }),
|
||||||
|
appointmentinsert: (start) =>
|
||||||
|
i18n.t("audit_trail.messages.appointmentinsert", { start }),
|
||||||
jobstatuschange: (status) =>
|
jobstatuschange: (status) =>
|
||||||
i18n.t("audit_trail.messages.jobstatuschange", { status }),
|
i18n.t("audit_trail.messages.jobstatuschange", { status }),
|
||||||
admin_jobstatuschange: (status) =>
|
admin_jobstatuschange: (status) =>
|
||||||
|
|||||||
@@ -31,3 +31,7 @@ export function TimeAgoFormatter(props) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function DateTimeFormat(value) {
|
||||||
|
return moment(value).format("MM/DD/YYYY hh:mm A");
|
||||||
|
}
|
||||||
|
|||||||
@@ -2014,6 +2014,18 @@ export const TemplateList = (type, context) => {
|
|||||||
},
|
},
|
||||||
group: "jobs",
|
group: "jobs",
|
||||||
},
|
},
|
||||||
|
lost_sales: {
|
||||||
|
title: i18n.t("reportcenter.templates.lost_sales"),
|
||||||
|
subject: i18n.t("reportcenter.templates.lost_sales"),
|
||||||
|
key: "lost_sales",
|
||||||
|
//idtype: "vendor",
|
||||||
|
disabled: false,
|
||||||
|
rangeFilter: {
|
||||||
|
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||||
|
field: i18n.t("jobs.fields.date_lost_sale"),
|
||||||
|
},
|
||||||
|
group: "customers",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(!type || type === "courtesycarcontract"
|
...(!type || type === "courtesycarcontract"
|
||||||
|
|||||||
@@ -890,11 +890,13 @@
|
|||||||
- appt_colors
|
- appt_colors
|
||||||
- appt_length
|
- appt_length
|
||||||
- attach_pdf_to_email
|
- attach_pdf_to_email
|
||||||
|
- autohouseid
|
||||||
- bill_allow_post_to_closed
|
- bill_allow_post_to_closed
|
||||||
- bill_tax_rates
|
- bill_tax_rates
|
||||||
- cdk_configuration
|
- cdk_configuration
|
||||||
- cdk_dealerid
|
- cdk_dealerid
|
||||||
- city
|
- city
|
||||||
|
- claimscorpid
|
||||||
- country
|
- country
|
||||||
- created_at
|
- created_at
|
||||||
- default_adjustment_rate
|
- default_adjustment_rate
|
||||||
@@ -928,6 +930,7 @@
|
|||||||
- md_estimators
|
- md_estimators
|
||||||
- md_filehandlers
|
- md_filehandlers
|
||||||
- md_from_emails
|
- md_from_emails
|
||||||
|
- md_functionality_toggles
|
||||||
- md_hour_split
|
- md_hour_split
|
||||||
- md_ins_cos
|
- md_ins_cos
|
||||||
- md_jobline_presets
|
- md_jobline_presets
|
||||||
@@ -1026,6 +1029,7 @@
|
|||||||
- md_estimators
|
- md_estimators
|
||||||
- md_filehandlers
|
- md_filehandlers
|
||||||
- md_from_emails
|
- md_from_emails
|
||||||
|
- md_functionality_toggles
|
||||||
- md_hour_split
|
- md_hour_split
|
||||||
- md_ins_cos
|
- md_ins_cos
|
||||||
- md_jobline_presets
|
- md_jobline_presets
|
||||||
@@ -3583,6 +3587,7 @@
|
|||||||
- date_exported
|
- date_exported
|
||||||
- date_invoiced
|
- date_invoiced
|
||||||
- date_last_contacted
|
- date_last_contacted
|
||||||
|
- date_lost_sale
|
||||||
- date_next_contact
|
- date_next_contact
|
||||||
- date_open
|
- date_open
|
||||||
- date_rentalresp
|
- date_rentalresp
|
||||||
@@ -3863,6 +3868,7 @@
|
|||||||
- date_exported
|
- date_exported
|
||||||
- date_invoiced
|
- date_invoiced
|
||||||
- date_last_contacted
|
- date_last_contacted
|
||||||
|
- date_lost_sale
|
||||||
- date_next_contact
|
- date_next_contact
|
||||||
- date_open
|
- date_open
|
||||||
- date_rentalresp
|
- date_rentalresp
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."bodyshops" add column "md_functionality_toggles" jsonb
|
||||||
|
-- null default jsonb_build_object();
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."bodyshops" add column "md_functionality_toggles" jsonb
|
||||||
|
null default jsonb_build_object();
|
||||||
@@ -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 "date_lost_sale" timestamp with time zone
|
||||||
|
-- null;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."jobs" add column "date_lost_sale" timestamp with time zone
|
||||||
|
null;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "public"."jobs" ALTER COLUMN "date_lost_sale" TYPE timestamp with time zone;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "public"."jobs" ALTER COLUMN "date_lost_sale" TYPE timestamp with time zone;
|
||||||
7156
package-lock.json
generated
Normal file
7156
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
61
package.json
61
package.json
@@ -3,64 +3,63 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0",
|
"node": ">=18.0.0",
|
||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"setup": "rm -rf node_modules && yarn && cd client && rm -rf node_modules && yarn",
|
"setup": "rm -rf node_modules && npm i && cd client && rm -rf node_modules && npm i",
|
||||||
"admin": "cd admin && yarn start",
|
"admin": "cd admin && npm start",
|
||||||
"client": "cd client && yarn start",
|
"client": "cd client && npm start",
|
||||||
"server": "nodemon server.js",
|
"server": "nodemon server.js",
|
||||||
"build": "cd client && yarn run build",
|
"build": "cd client && npm run build",
|
||||||
"dev": "concurrently --kill-others-on-fail \"yarn run server\" \"yarn run client\"",
|
"dev": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\"",
|
||||||
"deva": "concurrently --kill-others-on-fail \"yarn run server\" \"yarn run client\" \"yarn run admin\"",
|
"deva": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\" \"npm run admin\"",
|
||||||
"start": "node server.js"
|
"start": "node server.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-secrets-manager": "^3.388.0",
|
"@aws-sdk/client-secrets-manager": "^3.454.0",
|
||||||
"@aws-sdk/credential-provider-node": "^3.319.0",
|
"@aws-sdk/client-ses": "^3.454.0",
|
||||||
"@opensearch-project/opensearch": "^2.2.1",
|
"@aws-sdk/credential-provider-node": "^3.451.0",
|
||||||
"aws-sdk": "^2.1326.0",
|
"@opensearch-project/opensearch": "^2.4.0",
|
||||||
"aws4": "^1.12.0",
|
"aws4": "^1.12.0",
|
||||||
"axios": "^0.27.2",
|
"axios": "^1.6.2",
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"cloudinary": "^1.34.0",
|
"cloudinary": "^1.41.0",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"csrf": "^3.1.0",
|
"csrf": "^3.1.0",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "16.0.3",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"firebase-admin": "^11.5.0",
|
"firebase-admin": "^11.11.0",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.8.1",
|
||||||
"graphql-request": "^4.2.0",
|
"graphql-request": "^6.1.0",
|
||||||
"graylog2": "^0.2.1",
|
"graylog2": "^0.2.1",
|
||||||
"inline-css": "^4.0.2",
|
"inline-css": "^4.0.2",
|
||||||
"intuit-oauth": "^4.0.0",
|
"intuit-oauth": "^4.0.0",
|
||||||
"json-2-csv": "^3.19.0",
|
"json-2-csv": "^5.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"moment-timezone": "^0.5.41",
|
"moment-timezone": "^0.5.41",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"node-mailjet": "^6.0.2",
|
"node-mailjet": "^6.0.4",
|
||||||
"node-persist": "^3.1.3",
|
"node-persist": "^3.1.3",
|
||||||
"node-quickbooks": "^2.0.41",
|
"node-quickbooks": "^2.0.43",
|
||||||
"nodemailer": "^6.9.1",
|
"nodemailer": "^6.9.7",
|
||||||
"phone": "^3.1.35",
|
"phone": "^3.1.41",
|
||||||
"query-string": "^7.1.1",
|
|
||||||
"soap": "^1.0.0",
|
"soap": "^1.0.0",
|
||||||
"socket.io": "^4.6.1",
|
"socket.io": "^4.7.2",
|
||||||
"ssh2-sftp-client": "^9.0.4",
|
"ssh2-sftp-client": "^9.1.0",
|
||||||
"stripe": "^9.15.0",
|
"stripe": "^14.5.0",
|
||||||
"twilio": "^4.8.0",
|
"twilio": "^4.19.0",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.1",
|
||||||
"xml2js": "^0.4.23",
|
"xml2js": "^0.6.2",
|
||||||
"xmlbuilder2": "^3.0.2"
|
"xmlbuilder2": "^3.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^7.3.0",
|
"concurrently": "^8.2.2",
|
||||||
"source-map-explorer": "^2.5.2"
|
"source-map-explorer": "^2.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ const path = require("path");
|
|||||||
const compression = require("compression");
|
const compression = require("compression");
|
||||||
const twilio = require("twilio");
|
const twilio = require("twilio");
|
||||||
const logger = require("./server/utils/logger");
|
const logger = require("./server/utils/logger");
|
||||||
var fb = require("./server/firebase/firebase-handler");
|
const fb = require("./server/firebase/firebase-handler");
|
||||||
var cookieParser = require("cookie-parser");
|
const cookieParser = require("cookie-parser");
|
||||||
const multer = require("multer");
|
const multer = require("multer");
|
||||||
const upload = multer();
|
const upload = multer();
|
||||||
//var enforce = require("express-sslify");
|
//var enforce = require("express-sslify");
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const logger = require("../../utils/logger");
|
|||||||
const OAuthClient = require("intuit-oauth");
|
const OAuthClient = require("intuit-oauth");
|
||||||
const client = require("../../graphql-client/graphql-client").client;
|
const client = require("../../graphql-client/graphql-client").client;
|
||||||
const queries = require("../../graphql-client/queries");
|
const queries = require("../../graphql-client/queries");
|
||||||
const queryString = require("query-string");
|
const {parse, stringify} = require("querystring");
|
||||||
|
|
||||||
const oauthClient = new OAuthClient({
|
const oauthClient = new OAuthClient({
|
||||||
clientId: process.env.QBO_CLIENT_ID,
|
clientId: process.env.QBO_CLIENT_ID,
|
||||||
@@ -30,7 +30,8 @@ if (process.env.NODE_ENV === "production") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
const params = queryString.parse(req.url.split("?").reverse()[0]);
|
const queryString = req.url.split("?").reverse()[0];
|
||||||
|
const params = parse(queryString);
|
||||||
try {
|
try {
|
||||||
logger.log("qbo-callback-create-token", "DEBUG", params.state, null, null);
|
logger.log("qbo-callback-create-token", "DEBUG", params.state, null, null);
|
||||||
const authResponse = await oauthClient.createToken(req.url);
|
const authResponse = await oauthClient.createToken(req.url);
|
||||||
@@ -58,7 +59,7 @@ exports.default = async (req, res) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
res.redirect(
|
res.redirect(
|
||||||
`${url}/manage/accounting/qbo?${queryString.stringify(params)}`
|
`${url}/manage/accounting/qbo?${stringify(params)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -7,15 +7,19 @@ require("dotenv").config({
|
|||||||
});
|
});
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
let nodemailer = require("nodemailer");
|
let nodemailer = require("nodemailer");
|
||||||
let aws = require("aws-sdk");
|
let aws = require("@aws-sdk/client-ses");
|
||||||
|
let { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
||||||
|
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
const client = require("../graphql-client/graphql-client").client;
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
const queries = require("../graphql-client/queries");
|
const queries = require("../graphql-client/queries");
|
||||||
|
|
||||||
const ses = new aws.SES({
|
const ses = new aws.SES({
|
||||||
|
// The key apiVersion is no longer supported in v3, and can be removed.
|
||||||
|
// @deprecated The client uses the "latest" apiVersion.
|
||||||
apiVersion: "latest",
|
apiVersion: "latest",
|
||||||
|
|
||||||
region: "ca-central-1",
|
region: "ca-central-1",
|
||||||
|
defaultProvider
|
||||||
});
|
});
|
||||||
|
|
||||||
let transporter = nodemailer.createTransport({
|
let transporter = nodemailer.createTransport({
|
||||||
@@ -48,7 +52,6 @@ exports.sendServerEmail = async function ({ subject, text }) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
logger.log("server-email-failure", "error", null, null, error);
|
logger.log("server-email-failure", "error", null, null, error);
|
||||||
res.status(500).json(error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
exports.sendTaskEmail = async function ({ to, subject, text, attachments }) {
|
exports.sendTaskEmail = async function ({ to, subject, text, attachments }) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const AWS = require("aws-sdk");
|
const awsSecretManager = require("@aws-sdk/client-secrets-manager");
|
||||||
|
|
||||||
class SecretsManager {
|
class SecretsManager {
|
||||||
/**
|
/**
|
||||||
@@ -8,11 +8,10 @@ class SecretsManager {
|
|||||||
*/
|
*/
|
||||||
static async getSecret(secretName, region) {
|
static async getSecret(secretName, region) {
|
||||||
const config = { region: region };
|
const config = { region: region };
|
||||||
let secretsManager = new AWS.SecretsManager(config);
|
let secretsManager = new awsSecretManager.SecretsManager(config);
|
||||||
try {
|
try {
|
||||||
let secretValue = await secretsManager
|
let secretValue = await secretsManager
|
||||||
.getSecretValue({ SecretId: secretName })
|
.getSecretValue({ SecretId: secretName });
|
||||||
.promise();
|
|
||||||
if ("SecretString" in secretValue) {
|
if ("SecretString" in secretValue) {
|
||||||
return secretValue.SecretString;
|
return secretValue.SecretString;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,122 +1,122 @@
|
|||||||
const Dinero = require("dinero.js");
|
|
||||||
const queries = require("../graphql-client/queries");
|
const queries = require("../graphql-client/queries");
|
||||||
//const client = require("../graphql-client/graphql-client").client;
|
const {pick} = require("lodash");
|
||||||
const _ = require("lodash");
|
|
||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
|
//const client = require("../graphql-client/graphql-client").client;
|
||||||
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const client = require("../graphql-client/graphql-client").client;
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
`.env.${process.env.NODE_ENV || "development"}`
|
`.env.${process.env.NODE_ENV || "development"}`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
const { Client, Connection } = require("@opensearch-project/opensearch");
|
const {Client, Connection} = require("@opensearch-project/opensearch");
|
||||||
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
const {defaultProvider} = require("@aws-sdk/credential-provider-node");
|
||||||
const aws4 = require("aws4");
|
const aws4 = require("aws4");
|
||||||
const { gql } = require("graphql-request");
|
const {gql} = require("graphql-request");
|
||||||
|
|
||||||
var host = process.env.OPEN_SEARCH_HOST;
|
var host = process.env.OPEN_SEARCH_HOST;
|
||||||
|
|
||||||
const createAwsConnector = (credentials, region) => {
|
const createAwsConnector = (credentials, region) => {
|
||||||
class AmazonConnection extends Connection {
|
class AmazonConnection extends Connection {
|
||||||
buildRequestObject(params) {
|
buildRequestObject(params) {
|
||||||
const request = super.buildRequestObject(params);
|
const request = super.buildRequestObject(params);
|
||||||
request.service = "es";
|
request.service = "es";
|
||||||
request.region = region;
|
request.region = region;
|
||||||
request.headers = request.headers || {};
|
request.headers = request.headers || {};
|
||||||
request.headers["host"] = request.hostname;
|
request.headers["host"] = request.hostname;
|
||||||
|
|
||||||
return aws4.sign(request, credentials);
|
return aws4.sign(request, credentials);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
Connection: AmazonConnection,
|
Connection: AmazonConnection,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getClient = async () => {
|
const getClient = async () => {
|
||||||
const credentials = await defaultProvider()();
|
const credentials = await defaultProvider()();
|
||||||
return new Client({
|
return new Client({
|
||||||
...createAwsConnector(credentials, "ca-central-1"),
|
...createAwsConnector(credentials, "ca-central-1"),
|
||||||
node: host,
|
node: host,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
async function OpenSearchUpdateHandler(req, res) {
|
async function OpenSearchUpdateHandler(req, res) {
|
||||||
if (req.headers["event-secret"] !== process.env.EVENT_SECRET) {
|
if (req.headers["event-secret"] !== process.env.EVENT_SECRET) {
|
||||||
res.status(401).send("Unauthorized");
|
res.status(401).send("Unauthorized");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
var osClient = await getClient();
|
var osClient = await getClient();
|
||||||
// const osClient = new Client({
|
// const osClient = new Client({
|
||||||
// node: `https://imex:<password>@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com/`,
|
// node: `https://imex:<password>@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com/`,
|
||||||
// });
|
// });
|
||||||
|
|
||||||
if (req.body.event.op === "DELETE") {
|
if (req.body.event.op === "DELETE") {
|
||||||
let response;
|
let response;
|
||||||
response = await osClient.delete({
|
response = await osClient.delete({
|
||||||
id: req.body.event.data.old.id,
|
id: req.body.event.data.old.id,
|
||||||
index: req.body.table.name,
|
index: req.body.table.name,
|
||||||
});
|
});
|
||||||
res.status(200).json(response.body);
|
res.status(200).json(response.body);
|
||||||
} else {
|
} else {
|
||||||
let document;
|
let document;
|
||||||
|
|
||||||
switch (req.body.table.name) {
|
switch (req.body.table.name) {
|
||||||
case "jobs":
|
case "jobs":
|
||||||
document = _.pick(req.body.event.data.new, [
|
document = pick(req.body.event.data.new, [
|
||||||
"id",
|
"id",
|
||||||
"bodyshopid",
|
"bodyshopid",
|
||||||
"clm_no",
|
"clm_no",
|
||||||
"clm_total",
|
"clm_total",
|
||||||
"comment",
|
"comment",
|
||||||
"ins_co_nm",
|
"ins_co_nm",
|
||||||
"owner_owing",
|
"owner_owing",
|
||||||
"ownr_co_nm",
|
"ownr_co_nm",
|
||||||
"ownr_fn",
|
"ownr_fn",
|
||||||
"ownr_ln",
|
"ownr_ln",
|
||||||
"ownr_ph1",
|
"ownr_ph1",
|
||||||
"ownr_ph2",
|
"ownr_ph2",
|
||||||
"plate_no",
|
"plate_no",
|
||||||
"ro_number",
|
"ro_number",
|
||||||
"status",
|
"status",
|
||||||
"v_model_yr",
|
"v_model_yr",
|
||||||
"v_make_desc",
|
"v_make_desc",
|
||||||
"v_model_desc",
|
"v_model_desc",
|
||||||
"v_vin",
|
"v_vin",
|
||||||
]);
|
]);
|
||||||
document.bodyshopid = req.body.event.data.new.shopid;
|
document.bodyshopid = req.body.event.data.new.shopid;
|
||||||
break;
|
break;
|
||||||
case "vehicles":
|
case "vehicles":
|
||||||
document = _.pick(req.body.event.data.new, [
|
document = pick(req.body.event.data.new, [
|
||||||
"id",
|
"id",
|
||||||
"v_model_yr",
|
"v_model_yr",
|
||||||
"v_model_desc",
|
"v_model_desc",
|
||||||
"v_make_desc",
|
"v_make_desc",
|
||||||
"v_color",
|
"v_color",
|
||||||
"v_vin",
|
"v_vin",
|
||||||
"plate_no",
|
"plate_no",
|
||||||
]);
|
]);
|
||||||
document.bodyshopid = req.body.event.data.new.shopid;
|
document.bodyshopid = req.body.event.data.new.shopid;
|
||||||
break;
|
break;
|
||||||
case "owners":
|
case "owners":
|
||||||
document = _.pick(req.body.event.data.new, [
|
document = pick(req.body.event.data.new, [
|
||||||
"id",
|
"id",
|
||||||
"ownr_fn",
|
"ownr_fn",
|
||||||
"ownr_ln",
|
"ownr_ln",
|
||||||
"ownr_co_nm",
|
"ownr_co_nm",
|
||||||
"ownr_ph1",
|
"ownr_ph1",
|
||||||
"ownr_ph2",
|
"ownr_ph2",
|
||||||
]);
|
]);
|
||||||
document.bodyshopid = req.body.event.data.new.shopid;
|
document.bodyshopid = req.body.event.data.new.shopid;
|
||||||
break;
|
break;
|
||||||
case "bills":
|
case "bills":
|
||||||
const bill = await client.request(
|
const bill = await client.request(
|
||||||
`query ADMIN_GET_BILL_BY_ID($billId: uuid!) {
|
`query ADMIN_GET_BILL_BY_ID($billId: uuid!) {
|
||||||
bills_by_pk(id: $billId) {
|
bills_by_pk(id: $billId) {
|
||||||
id
|
id
|
||||||
job {
|
job {
|
||||||
@@ -131,26 +131,26 @@ async function OpenSearchUpdateHandler(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
{ billId: req.body.event.data.new.id }
|
{billId: req.body.event.data.new.id}
|
||||||
);
|
);
|
||||||
document = {
|
document = {
|
||||||
..._.pick(req.body.event.data.new, [
|
...pick(req.body.event.data.new, [
|
||||||
"id",
|
"id",
|
||||||
"date",
|
"date",
|
||||||
"exported",
|
"exported",
|
||||||
"exported_at",
|
"exported_at",
|
||||||
"invoice_number",
|
"invoice_number",
|
||||||
"is_credit_memo",
|
"is_credit_memo",
|
||||||
"total",
|
"total",
|
||||||
]),
|
]),
|
||||||
...bill.bills_by_pk,
|
...bill.bills_by_pk,
|
||||||
bodyshopid: bill.bills_by_pk.job.shopid,
|
bodyshopid: bill.bills_by_pk.job.shopid,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "payments":
|
case "payments":
|
||||||
//Query to get the job and RO number
|
//Query to get the job and RO number
|
||||||
const payment = await client.request(
|
const payment = await client.request(
|
||||||
`query ADMIN_GET_PAYMENT_BY_ID($paymentId: uuid!) {
|
`query ADMIN_GET_PAYMENT_BY_ID($paymentId: uuid!) {
|
||||||
payments_by_pk(id: $paymentId) {
|
payments_by_pk(id: $paymentId) {
|
||||||
id
|
id
|
||||||
job {
|
job {
|
||||||
@@ -171,155 +171,155 @@ async function OpenSearchUpdateHandler(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
{ paymentId: req.body.event.data.new.id }
|
{paymentId: req.body.event.data.new.id}
|
||||||
);
|
);
|
||||||
document = {
|
document = {
|
||||||
..._.pick(req.body.event.data.new, [
|
...pick(req.body.event.data.new, [
|
||||||
"id",
|
"id",
|
||||||
"amount",
|
"amount",
|
||||||
"created_at",
|
"created_at",
|
||||||
"date",
|
"date",
|
||||||
"exportedat",
|
"exportedat",
|
||||||
"memo",
|
"memo",
|
||||||
"payer",
|
"payer",
|
||||||
"paymentnum",
|
"paymentnum",
|
||||||
"transactionid",
|
"transactionid",
|
||||||
"type",
|
"type",
|
||||||
]),
|
]),
|
||||||
...payment.payments_by_pk,
|
...payment.payments_by_pk,
|
||||||
bodyshopid: payment.payments_by_pk.job.shopid,
|
bodyshopid: payment.payments_by_pk.job.shopid,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const payload = {
|
const payload = {
|
||||||
id: req.body.event.data.new.id,
|
id: req.body.event.data.new.id,
|
||||||
index: req.body.table.name,
|
index: req.body.table.name,
|
||||||
body: document,
|
body: document,
|
||||||
};
|
};
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
response = await osClient.index(payload);
|
response = await osClient.index(payload);
|
||||||
console.log(response.body);
|
console.log(response.body);
|
||||||
res.status(200).json(response.body);
|
res.status(200).json(response.body);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json(JSON.stringify(error));
|
||||||
|
} finally {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
res.status(400).json(JSON.stringify(error));
|
|
||||||
} finally {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function OpenSearchSearchHandler(req, res) {
|
async function OpenSearchSearchHandler(req, res) {
|
||||||
try {
|
try {
|
||||||
const { search, bodyshopid, index } = req.body;
|
const {search, bodyshopid, index} = req.body;
|
||||||
|
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
res.sendStatus(401);
|
res.sendStatus(401);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log("os-search", "DEBUG", req.user.email, null, {
|
logger.log("os-search", "DEBUG", req.user.email, null, {
|
||||||
search,
|
search,
|
||||||
});
|
});
|
||||||
|
|
||||||
const BearerToken = req.headers.authorization;
|
const BearerToken = req.headers.authorization;
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: BearerToken,
|
Authorization: BearerToken,
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const assocs = await client
|
|
||||||
.setHeaders({ Authorization: BearerToken })
|
|
||||||
.request(queries.ACTIVE_SHOP_BY_USER, {
|
|
||||||
user: req.user.email,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (assocs.length === 0) {
|
|
||||||
res.sendStatus(401);
|
|
||||||
}
|
|
||||||
|
|
||||||
var osClient = await getClient();
|
|
||||||
|
|
||||||
const { body } = await osClient.search({
|
|
||||||
...(index ? { index } : {}),
|
|
||||||
body: {
|
|
||||||
size: 100,
|
|
||||||
query: {
|
|
||||||
bool: {
|
|
||||||
must: [
|
|
||||||
{
|
|
||||||
match: {
|
|
||||||
bodyshopid: assocs.associations[0].shopid,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bool: {
|
|
||||||
should: [
|
|
||||||
{
|
|
||||||
multi_match: {
|
|
||||||
query: search,
|
|
||||||
type: "cross_fields",
|
|
||||||
fields: ["*ownr_fn", "*ownr_ln"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
multi_match: {
|
|
||||||
query: search,
|
|
||||||
type: "most_fields",
|
|
||||||
fields: [
|
|
||||||
"*v_model_yr",
|
|
||||||
"*v_make_desc^2",
|
|
||||||
"*v_model_desc^3",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query_string: {
|
|
||||||
query: `*${search}*`,
|
|
||||||
// Weighted Fields
|
|
||||||
fields: [
|
|
||||||
"*ro_number^20",
|
|
||||||
"*clm_no^14",
|
|
||||||
"*v_vin^12",
|
|
||||||
"*plate_no^12",
|
|
||||||
"*ownr_ln^10",
|
|
||||||
"transactionid^10",
|
|
||||||
"paymentnum^10",
|
|
||||||
"invoice_number^10",
|
|
||||||
"*ownr_fn^8",
|
|
||||||
"*ownr_co_nm^8",
|
|
||||||
"*ownr_ph1^8",
|
|
||||||
"*ownr_ph2^8",
|
|
||||||
"*",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
minimum_should_match: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sort: [
|
|
||||||
{
|
|
||||||
_score: {
|
|
||||||
order: "desc",
|
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
res.json(body);
|
const assocs = await client
|
||||||
} catch (error) {
|
.setHeaders({Authorization: BearerToken})
|
||||||
console.log(error);
|
.request(queries.ACTIVE_SHOP_BY_USER, {
|
||||||
logger.log("os-search-error", "ERROR", req.user.email, null, {
|
user: req.user.email,
|
||||||
error: JSON.stringify(error),
|
});
|
||||||
});
|
|
||||||
res.status(400).json(error);
|
if (assocs.length === 0) {
|
||||||
} finally {
|
res.sendStatus(401);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const osClient = await getClient();
|
||||||
|
|
||||||
|
const {body} = await osClient.search({
|
||||||
|
...(index ? {index} : {}),
|
||||||
|
body: {
|
||||||
|
size: 100,
|
||||||
|
query: {
|
||||||
|
bool: {
|
||||||
|
must: [
|
||||||
|
{
|
||||||
|
match: {
|
||||||
|
bodyshopid: assocs.associations[0].shopid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bool: {
|
||||||
|
should: [
|
||||||
|
{
|
||||||
|
multi_match: {
|
||||||
|
query: search,
|
||||||
|
type: "cross_fields",
|
||||||
|
fields: ["*ownr_fn", "*ownr_ln"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
multi_match: {
|
||||||
|
query: search,
|
||||||
|
type: "most_fields",
|
||||||
|
fields: [
|
||||||
|
"*v_model_yr",
|
||||||
|
"*v_make_desc^2",
|
||||||
|
"*v_model_desc^3",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query_string: {
|
||||||
|
query: `*${search}*`,
|
||||||
|
// Weighted Fields
|
||||||
|
fields: [
|
||||||
|
"*ro_number^20",
|
||||||
|
"*clm_no^14",
|
||||||
|
"*v_vin^12",
|
||||||
|
"*plate_no^12",
|
||||||
|
"*ownr_ln^10",
|
||||||
|
"transactionid^10",
|
||||||
|
"paymentnum^10",
|
||||||
|
"invoice_number^10",
|
||||||
|
"*ownr_fn^8",
|
||||||
|
"*ownr_co_nm^8",
|
||||||
|
"*ownr_ph1^8",
|
||||||
|
"*ownr_ph2^8",
|
||||||
|
"*",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
minimum_should_match: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
_score: {
|
||||||
|
order: "desc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(body);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
logger.log("os-search-error", "ERROR", req.user.email, null, {
|
||||||
|
error: JSON.stringify(error),
|
||||||
|
});
|
||||||
|
res.status(400).json(error);
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.handler = OpenSearchUpdateHandler;
|
exports.handler = OpenSearchUpdateHandler;
|
||||||
|
|||||||
@@ -1,79 +1,46 @@
|
|||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const queries = require("../graphql-client/queries");
|
|
||||||
const Dinero = require("dinero.js");
|
|
||||||
|
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
`.env.${process.env.NODE_ENV || "development"}`
|
`.env.${process.env.NODE_ENV || "development"}`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
|
|
||||||
|
|
||||||
exports.payment = async (req, res) => {
|
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
|
||||||
const { amount, stripe_acct_id } = req.body;
|
|
||||||
|
|
||||||
try {
|
const processor = async (req, res) => {
|
||||||
await stripe.paymentIntents
|
const {amount, stripe_acct_id} = req.body;
|
||||||
.create(
|
|
||||||
{
|
try {
|
||||||
payment_method_types: ["card"],
|
await stripe.paymentIntents
|
||||||
amount: amount,
|
.create(
|
||||||
currency: "cad",
|
{
|
||||||
application_fee_amount: 50,
|
payment_method_types: ["card"],
|
||||||
},
|
amount: amount,
|
||||||
{
|
currency: "cad",
|
||||||
stripeAccount: stripe_acct_id,
|
application_fee_amount: 50,
|
||||||
}
|
},
|
||||||
)
|
{
|
||||||
.then(function (paymentIntent) {
|
stripeAccount: stripe_acct_id,
|
||||||
try {
|
}
|
||||||
return res.send({
|
)
|
||||||
clientSecret: paymentIntent.client_secret,
|
.then(function (paymentIntent) {
|
||||||
});
|
try {
|
||||||
} catch (err) {
|
return res.send({
|
||||||
return res.status(500).send({
|
clientSecret: paymentIntent.client_secret,
|
||||||
error: err.message,
|
});
|
||||||
});
|
} catch (err) {
|
||||||
}
|
return res.status(500).send({
|
||||||
});
|
error: err.message,
|
||||||
} catch (error) {
|
});
|
||||||
console.log("error", error);
|
}
|
||||||
res.status(400).send(error);
|
});
|
||||||
}
|
} catch (error) {
|
||||||
|
console.log("error", error);
|
||||||
|
res.status(400).send(error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.mobile_payment = async (req, res) => {
|
exports.payment = processor;
|
||||||
const { amount, stripe_acct_id } = req.body;
|
exports.mobile_payment = processor;
|
||||||
|
|
||||||
try {
|
|
||||||
await stripe.paymentIntents
|
|
||||||
.create(
|
|
||||||
{
|
|
||||||
//Pull the amounts from the payment request.
|
|
||||||
payment_method_types: ["card"],
|
|
||||||
amount: amount,
|
|
||||||
currency: "cad",
|
|
||||||
application_fee_amount: 50,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
stripeAccount: stripe_acct_id,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(function (paymentIntent) {
|
|
||||||
try {
|
|
||||||
return res.send({
|
|
||||||
clientSecret: paymentIntent.client_secret,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
return res.status(500).send({
|
|
||||||
error: err.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log("error", error);
|
|
||||||
res.status(400).send(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,92 +1,84 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
`.env.${process.env.NODE_ENV || "development"}`
|
`.env.${process.env.NODE_ENV || "development"}`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
const axios = require("axios");
|
|
||||||
const client = require("../graphql-client/graphql-client").client;
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
const emailer = require("../email/sendemail");
|
const emailer = require("../email/sendemail");
|
||||||
const logger = require("../utils/logger");
|
|
||||||
const moment = require("moment-timezone");
|
const moment = require("moment-timezone");
|
||||||
|
const converter = require("json-2-csv");
|
||||||
|
|
||||||
exports.taskHandler = async (req, res) => {
|
exports.taskHandler = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { bodyshopid, query, variables, text, to, subject, timezone } =
|
const {bodyshopid, query, variables, text, to, subject, timezone} = req.body;
|
||||||
req.body;
|
|
||||||
//Run the query
|
|
||||||
|
|
||||||
//Check the variables to see if they are an object.
|
//Check the variables to see if they are an object.
|
||||||
Object.keys(variables).forEach((key) => {
|
Object.keys(variables).forEach((key) => {
|
||||||
if (typeof variables[key] === "object") {
|
if (typeof variables[key] === "object") {
|
||||||
if (variables[key].function) {
|
if (variables[key].function) {
|
||||||
variables[key] = functionMapper(variables[key].function, timezone);
|
variables[key] = functionMapper(variables[key].function, timezone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await client.request(query, variables);
|
const response = await client.request(query, variables);
|
||||||
//Massage the data
|
const rootElement = response[Object.keys(response)[0]]; //This element should always be an array.
|
||||||
//Send the email
|
|
||||||
const rootElement = response[Object.keys(response)[0]]; //This element shoudl always be an array.
|
const csv = converter.json2csv(rootElement, {emptyFieldValue: ""});
|
||||||
let converter = require("json-2-csv");
|
|
||||||
converter.json2csv(
|
|
||||||
rootElement,
|
|
||||||
(err, csv) => {
|
|
||||||
if (err) {
|
|
||||||
res.status(500).json(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
emailer.sendTaskEmail({
|
emailer.sendTaskEmail({
|
||||||
to,
|
to,
|
||||||
subject,
|
subject,
|
||||||
text,
|
text,
|
||||||
attachments: [{ filename: "query.csv", content: csv }],
|
attachments: [{filename: "query.csv", content: csv}],
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('Errors sending CSV Email.')
|
||||||
});
|
});
|
||||||
res.status(200).send(csv);
|
|
||||||
},
|
return res.status(200).send(csv);
|
||||||
{ emptyFieldValue: "" }
|
} catch (error) {
|
||||||
);
|
res.status(500).json({error: error.message, stack: error.stackTrace});
|
||||||
} catch (error) {
|
}
|
||||||
res.status(500).json({ error: error.message, stack: error.stackTrace });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const isoformat = "YYYY-MM-DD";
|
const isoFormat = "YYYY-MM-DD";
|
||||||
function functionMapper(f, timezone) {
|
|
||||||
switch (f) {
|
|
||||||
case "date.today":
|
|
||||||
return moment().tz(timezone).format(isoformat);
|
|
||||||
case "date.now":
|
|
||||||
return moment().tz(timezone);
|
|
||||||
case "date.yesterday":
|
|
||||||
return moment().tz(timezone).subtract(1, "day").format(isoformat);
|
|
||||||
case "date.3daysago":
|
|
||||||
return moment().tz(timezone).subtract(3, "days").format(isoformat);
|
|
||||||
case "date.7daysago":
|
|
||||||
return moment().tz(timezone).subtract(7, "days").format(isoformat);
|
|
||||||
case "date.tomorrow":
|
|
||||||
return moment().tz(timezone).add(1, "day").format(isoformat);
|
|
||||||
case "date.3daysfromnow":
|
|
||||||
return moment().tz(timezone).add(3, "days").format(isoformat);
|
|
||||||
case "date.7daysfromnow":
|
|
||||||
return moment().tz(timezone).add(7, "days").format(isoformat);
|
|
||||||
case "date.yesterdaytz":
|
|
||||||
return moment().tz(timezone).subtract(1, "day");
|
|
||||||
case "date.3daysagotz":
|
|
||||||
return moment().tz(timezone).subtract(3, "days");
|
|
||||||
case "date.7daysagotz":
|
|
||||||
return moment().tz(timezone).subtract(7, "days");
|
|
||||||
case "date.tomorrowtz":
|
|
||||||
return moment().tz(timezone).add(1, "day");
|
|
||||||
case "date.3daysfromnowtz":
|
|
||||||
return moment().tz(timezone).add(3, "days");
|
|
||||||
case "date.7daysfromnowtz":
|
|
||||||
return moment().tz(timezone).add(7, "days");
|
|
||||||
|
|
||||||
case "date.now":
|
function functionMapper(f, timezone) {
|
||||||
return moment().tz(timezone);
|
switch (f) {
|
||||||
default:
|
case "date.today":
|
||||||
return f;
|
return moment().tz(timezone).format(isoFormat);
|
||||||
}
|
case "date.now":
|
||||||
|
return moment().tz(timezone);
|
||||||
|
case "date.yesterday":
|
||||||
|
return moment().tz(timezone).subtract(1, "day").format(isoFormat);
|
||||||
|
case "date.3daysago":
|
||||||
|
return moment().tz(timezone).subtract(3, "days").format(isoFormat);
|
||||||
|
case "date.7daysago":
|
||||||
|
return moment().tz(timezone).subtract(7, "days").format(isoFormat);
|
||||||
|
case "date.tomorrow":
|
||||||
|
return moment().tz(timezone).add(1, "day").format(isoFormat);
|
||||||
|
case "date.3daysfromnow":
|
||||||
|
return moment().tz(timezone).add(3, "days").format(isoFormat);
|
||||||
|
case "date.7daysfromnow":
|
||||||
|
return moment().tz(timezone).add(7, "days").format(isoFormat);
|
||||||
|
case "date.yesterdaytz":
|
||||||
|
return moment().tz(timezone).subtract(1, "day");
|
||||||
|
case "date.3daysagotz":
|
||||||
|
return moment().tz(timezone).subtract(3, "days");
|
||||||
|
case "date.7daysagotz":
|
||||||
|
return moment().tz(timezone).subtract(7, "days");
|
||||||
|
case "date.tomorrowtz":
|
||||||
|
return moment().tz(timezone).add(1, "day");
|
||||||
|
case "date.3daysfromnowtz":
|
||||||
|
return moment().tz(timezone).add(3, "days");
|
||||||
|
case "date.7daysfromnowtz":
|
||||||
|
return moment().tz(timezone).add(7, "days");
|
||||||
|
|
||||||
|
case "date.now":
|
||||||
|
return moment().tz(timezone);
|
||||||
|
default:
|
||||||
|
return f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user