diff --git a/client/src/components/eula/eula.component.jsx b/client/src/components/eula/eula.component.jsx
index 4ffc4be97..e4b0eb326 100644
--- a/client/src/components/eula/eula.component.jsx
+++ b/client/src/components/eula/eula.component.jsx
@@ -51,12 +51,23 @@ const Eula = ({currentEula, currentUser, acceptEula}) => {
try {
const {accepted_terms, ...otherFormValues} = formValues;
+
+ // Trim the values of the fields before submitting
+ const trimmedFormValues = {
+ first_name: otherFormValues.first_name.trim(),
+ last_name: otherFormValues.last_name.trim(),
+ business_name: otherFormValues.business_name.trim(),
+ address: otherFormValues.address.trim(),
+ phone_number: otherFormValues.phone_number.trim(),
+ };
+
await insertEulaAcceptance({
variables: {
eulaAcceptance: {
eulaid: eulaId,
useremail,
...otherFormValues,
+ ...trimmedFormValues,
date_accepted: new Date(),
}
}
@@ -101,7 +112,7 @@ const Eula = ({currentEula, currentUser, acceptEula}) => {
>
-
+
diff --git a/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx b/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx
index 11de5546e..39853d506 100644
--- a/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx
+++ b/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx
@@ -51,12 +51,14 @@ export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) {
}
return (
-
-
+
+ >
);
}
diff --git a/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx b/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx
index 190778437..ec1bd976b 100644
--- a/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx
+++ b/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx
@@ -1,34 +1,18 @@
import { useMutation } from "@apollo/client";
-import { Button, notification } from "antd";
-import { gql } from "@apollo/client";
+import { Button, Space, notification } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
+import {
+ DELETE_DELIVERY_CHECKLIST,
+ DELETE_INTAKE_CHECKLIST,
+} from "../../graphql/jobs.queries";
+
export default function JobAdminDeleteIntake({ job }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
- const [deleteIntake] = useMutation(gql`
- mutation DELETE_INTAKE($jobId: uuid!) {
- update_jobs_by_pk(
- pk_columns: { id: $jobId }
- _set: { intakechecklist: null }
- ) {
- id
- intakechecklist
- }
- }
- `);
- const [DELETE_DELIVERY] = useMutation(gql`
- mutation DELETE_DELIVERY($jobId: uuid!) {
- update_jobs_by_pk(
- pk_columns: { id: $jobId }
- _set: { deliverchecklist: null }
- ) {
- id
- deliverchecklist
- }
- }
- `);
+ const [deleteIntake] = useMutation(DELETE_INTAKE_CHECKLIST);
+ const [deleteDelivery] = useMutation(DELETE_DELIVERY_CHECKLIST);
const handleDelete = async (values) => {
setLoading(true);
@@ -50,7 +34,7 @@ export default function JobAdminDeleteIntake({ job }) {
const handleDeleteDelivery = async (values) => {
setLoading(true);
- const result = await DELETE_DELIVERY({
+ const result = await deleteDelivery({
variables: { jobId: job.id },
});
@@ -68,12 +52,22 @@ export default function JobAdminDeleteIntake({ job }) {
return (
<>
-
- {t("jobs.labels.deleteintake")}
-
-
- {t("jobs.labels.deletedelivery")}
-
+
+
+ {t("jobs.labels.deleteintake")}
+
+
+ {t("jobs.labels.deletedelivery")}
+
+
>
);
}
diff --git a/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx b/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx
index 1f52b6df5..a71b8da49 100644
--- a/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx
+++ b/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx
@@ -1,5 +1,5 @@
-import { gql, useMutation } from "@apollo/client";
-import { Button, notification } from "antd";
+import { useMutation } from "@apollo/client";
+import { Button, Space, notification } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -7,6 +7,11 @@ import dayjs from "../../utils/day";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
+import {
+ MARK_JOB_AS_EXPORTED,
+ MARK_JOB_AS_UNINVOICED,
+ MARK_JOB_FOR_REEXPORT,
+} from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import {
selectBodyshop,
@@ -35,58 +40,18 @@ export function JobAdminMarkReexport({
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
- const [markJobForReexport] = useMutation(gql`
- mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!) {
- update_jobs_by_pk(
- pk_columns: { id: $jobId }
- _set: { date_exported: null
- status: "${bodyshop.md_ro_statuses.default_invoiced}"
- }
- ) {
- id
- date_exported
- status
- date_invoiced
- }
- }
- `);
- const [markJobExported] = useMutation(gql`
- mutation MARK_JOB_AS_EXPORTED($jobId: uuid!, $date_exported: timestamptz!) {
- update_jobs_by_pk(
- pk_columns: { id: $jobId }
- _set: { date_exported: $date_exported
- status: "${bodyshop.md_ro_statuses.default_exported}"
- }
- ) {
- id
- date_exported
- date_invoiced
- status
- }
- }
- `);
- const [markJobUninvoiced] = useMutation(gql`
- mutation MARK_JOB_AS_UNINVOICED($jobId: uuid!, ) {
- update_jobs_by_pk(
- pk_columns: { id: $jobId }
- _set: { date_exported: null
- date_invoiced: null
- status: "${bodyshop.md_ro_statuses.default_delivered}"
- }
- ) {
- id
- date_exported
- date_invoiced
- status
- }
- }
- `);
+ const [markJobForReexport] = useMutation(MARK_JOB_FOR_REEXPORT);
+ const [markJobExported] = useMutation(MARK_JOB_AS_EXPORTED);
+ const [markJobUninvoiced] = useMutation(MARK_JOB_AS_UNINVOICED);
const handleMarkForExport = async () => {
setLoading(true);
const result = await markJobForReexport({
- variables: { jobId: job.id },
+ variables: {
+ jobId: job.id,
+ default_invoiced: bodyshop.md_ro_statuses.default_invoiced,
+ },
});
if (!result.errors) {
@@ -108,7 +73,11 @@ export function JobAdminMarkReexport({
const handleMarkExported = async () => {
setLoading(true);
const result = await markJobExported({
- variables: { jobId: job.id, date_exported: dayjs() },
+ variables: {
+ jobId: job.id,
+ date_exported: dayjs(),
+ default_exported: bodyshop.md_ro_statuses.default_exported,
+ },
});
await insertExportLog({
@@ -144,7 +113,10 @@ export function JobAdminMarkReexport({
const handleUninvoice = async () => {
setLoading(true);
const result = await markJobUninvoiced({
- variables: { jobId: job.id },
+ variables: {
+ jobId: job.id,
+ default_delivered: bodyshop.md_ro_statuses.default_delivered,
+ },
});
if (!result.errors) {
@@ -165,27 +137,29 @@ export function JobAdminMarkReexport({
return (
<>
-
- {t("jobs.labels.markforreexport")}
-
-
- {t("jobs.actions.markasexported")}
-
-
- {t("jobs.actions.uninvoice")}
-
+
+
+ {t("jobs.labels.markforreexport")}
+
+
+ {t("jobs.actions.markasexported")}
+
+
+ {t("jobs.actions.uninvoice")}
+
+
>
);
}
diff --git a/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx b/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx
new file mode 100644
index 000000000..f1bda15ce
--- /dev/null
+++ b/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx
@@ -0,0 +1,65 @@
+import { useMutation } from "@apollo/client";
+import { Switch, notification } from "antd";
+import React, { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { UPDATE_REMOVE_FROM_AR } from "../../graphql/jobs.queries";
+import { insertAuditTrail } from "../../redux/application/application.actions";
+import AuditTrailMapping from "../../utils/AuditTrailMappings";
+
+const mapStateToProps = createStructuredSelector({});
+const mapDispatchToProps = (dispatch) => ({
+ insertAuditTrail: ({ jobid, operation }) =>
+ dispatch(insertAuditTrail({ jobid, operation })),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminRemoveAR);
+
+export function JobsAdminRemoveAR({ insertAuditTrail, job }) {
+ const { t } = useTranslation();
+ const [loading, setLoading] = useState(false);
+ const [switchValue, setSwitchValue] = useState(job.remove_from_ar);
+
+ const [mutationUpdateRemoveFromAR] = useMutation(UPDATE_REMOVE_FROM_AR);
+
+ const handleChange = async (value) => {
+ setLoading(true);
+ const result = await mutationUpdateRemoveFromAR({
+ variables: { jobId: job.id, remove_from_ar: value },
+ });
+
+ if (!result.errors) {
+ notification["success"]({ message: t("jobs.successes.save") });
+ insertAuditTrail({
+ jobid: job.id,
+ operation: AuditTrailMapping.admin_job_remove_from_ar(value),
+ });
+ setSwitchValue(value);
+ } else {
+ notification["error"]({
+ message: t("jobs.errors.saving", {
+ error: JSON.stringify(result.errors),
+ }),
+ });
+ }
+ setLoading(false);
+ };
+
+ return (
+ <>
+
+
+ {t("jobs.labels.remove_from_ar")}:
+
+
+
+
+
+ >
+ );
+}
diff --git a/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx b/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx
index b147c07d8..2094178c4 100644
--- a/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx
+++ b/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx
@@ -1,9 +1,10 @@
-import { gql, useMutation } from "@apollo/client";
+import { useMutation } from "@apollo/client";
import { Button, notification } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
+import { UNVOID_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import {
selectBodyshop,
@@ -29,66 +30,17 @@ export function JobsAdminUnvoid({
}) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
- const [updateJob] = useMutation(gql`
-mutation UNVOID_JOB($jobId: uuid!) {
- update_jobs_by_pk(pk_columns: {id: $jobId}, _set: {voided: false, status: "${
- bodyshop.md_ro_statuses.default_imported
- }", date_void: null}) {
- id
- date_void
- voided
- status
- }
- insert_notes(objects: {jobid: $jobId, audit: true, created_by: "${
- currentUser.email
- }", text: "${t("jobs.labels.unvoidnote")}"}) {
- returning {
- id
- }
- }
-}
-
- `);
-
- // const result = await voidJob({
- // variables: {
- // jobId: job.id,
- // job: {
- // status: bodyshop.md_ro_statuses.default_void,
- // voided: true,
- // },
- // note: [
- // {
- // jobid: job.id,
- // created_by: currentUser.email,
- // audit: true,
- // text: t("jobs.labels.voidnote", {
- // date: dayjs().format("MM/DD/yyy"),
- // time: dayjs().format("hh:mm a"),
- // }),
- // },
- // ],
- // },
- // });
-
- // if (!!!result.errors) {
- // notification["success"]({
- // message: t("jobs.successes.voided"),
- // });
- // //go back to jobs list.
- // history.push(`/manage/`);
- // } else {
- // notification["error"]({
- // message: t("jobs.errors.voiding", {
- // error: JSON.stringify(result.errors),
- // }),
- // });
- // }
+ const [mutationUnvoidJob] = useMutation(UNVOID_JOB);
const handleUpdate = async (values) => {
setLoading(true);
- const result = await updateJob({
- variables: { jobId: job.id },
+ const result = await mutationUnvoidJob({
+ variables: {
+ jobId: job.id,
+ default_imported: bodyshop.md_ro_statuses.default_imported,
+ currentUserEmail: currentUser.email,
+ text: t("jobs.labels.unvoidnote"),
+ },
});
if (!result.errors) {
@@ -110,8 +62,10 @@ mutation UNVOID_JOB($jobId: uuid!) {
};
return (
-
- {t("jobs.actions.unvoid")}
-
+ <>
+
+ {t("jobs.actions.unvoid")}
+
+ >
);
}
diff --git a/client/src/components/report-center-modal/report-center-modal.component.jsx b/client/src/components/report-center-modal/report-center-modal.component.jsx
index cd4dd74e4..641d2724c 100644
--- a/client/src/components/report-center-modal/report-center-modal.component.jsx
+++ b/client/src/components/report-center-modal/report-center-modal.component.jsx
@@ -68,8 +68,8 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
const handleFinish = async (values) => {
setLoading(true);
- const start = values.dates[0];
- const end = values.dates[1];
+ const start = values.dates ? values.dates[0] : null;
+ const end = values.dates ? values.dates[1] : null;
const { id } = values;
await GenerateDocument(
@@ -239,20 +239,30 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
else return null;
}}
-
-
+
+ {() => {
+ const key = form.getFieldValue("key");
+ const datedisable = Templates[key] && Templates[key].datedisable;
+ if (datedisable !== true) {
+ return (
+
+
+
+ );
+ } else return null;
+ }}
{() => {
diff --git a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx
index 8d3171fde..72f22da28 100644
--- a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx
+++ b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx
@@ -1,13 +1,13 @@
-import { useQuery } from "@apollo/client";
-import { Col, Row } from "antd";
+import {useQuery} from "@apollo/client";
+import {Col, Row} from "antd";
import _ from "lodash";
import dayjs from "../../utils/day";
-import React, { useMemo } from "react";
-import { useTranslation } from "react-i18next";
-import { connect } from "react-redux";
-import { createStructuredSelector } from "reselect";
-import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries";
-import { selectBodyshop } from "../../redux/user/user.selectors";
+import React, {useMemo} from "react";
+import {useTranslation} from "react-i18next";
+import {connect} from "react-redux";
+import {createStructuredSelector} from "reselect";
+import {QUERY_TIME_TICKETS_IN_RANGE_SB} from "../../graphql/timetickets.queries";
+import {selectBodyshop} from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
@@ -16,396 +16,397 @@ import ScoreboardTicketsStats from "./scoreboard-timetickets.stats.component";
import ScoreboardTimeticketsTargetsTable from "./scoreboard-timetickets.targets-table.component";
const mapStateToProps = createStructuredSelector({
- bodyshop: selectBodyshop,
+ bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
- //setUserLanguage: language => dispatch(setUserLanguage(language))
+ //setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
- mapStateToProps,
- mapDispatchToProps
+ mapStateToProps,
+ mapDispatchToProps
)(ScoreboardTimeTicketsStats);
-export function ScoreboardTimeTicketsStats({ bodyshop }) {
- const { t } = useTranslation();
- const startDate = dayjs().startOf("month")
- const endDate = dayjs().endOf("month");
+export function ScoreboardTimeTicketsStats({bodyshop}) {
+ const {t} = useTranslation();
+ const startDate = dayjs().startOf("month")
+ const endDate = dayjs().endOf("month");
- const fixedPeriods = useMemo(() => {
- const endOfThisMonth = dayjs().endOf("month");
- const startofthisMonth = dayjs().startOf("month");
+ const fixedPeriods = useMemo(() => {
+ const endOfThisMonth = dayjs().endOf("month");
+ const startofthisMonth = dayjs().startOf("month");
- const endOfLastmonth = dayjs().subtract(1, "month").endOf("month");
- const startOfLastmonth = dayjs().subtract(1, "month").startOf("month");
+ const endOfLastmonth = dayjs().subtract(1, "month").endOf("month");
+ const startOfLastmonth = dayjs().subtract(1, "month").startOf("month");
- const endOfThisWeek = dayjs().endOf("week");
- const startOfThisWeek = dayjs().startOf("week");
+ const endOfThisWeek = dayjs().endOf("week");
+ const startOfThisWeek = dayjs().startOf("week");
- const endOfLastWeek = dayjs().subtract(1, "week").endOf("week");
- const startOfLastWeek = dayjs().subtract(1, "week").startOf("week");
+ const endOfLastWeek = dayjs().subtract(1, "week").endOf("week");
+ const startOfLastWeek = dayjs().subtract(1, "week").startOf("week");
- const endOfPriorWeek = dayjs().subtract(2, "week").endOf("week");
- const startOfPriorWeek = dayjs().subtract(2, "week").startOf("week");
+ const endOfPriorWeek = dayjs().subtract(2, "week").endOf("week");
+ const startOfPriorWeek = dayjs().subtract(2, "week").startOf("week");
- const allDates = [
- endOfThisMonth,
- startofthisMonth,
- endOfLastmonth,
- startOfLastmonth,
- endOfThisWeek,
- startOfThisWeek,
- endOfLastWeek,
- startOfLastWeek,
- endOfPriorWeek,
- startOfPriorWeek,
- ];
- const start = dayjs.min(allDates);
- const end = dayjs.max(allDates);
- return {
- start,
- end,
- endOfThisMonth,
- startofthisMonth,
- endOfLastmonth,
- startOfLastmonth,
- endOfThisWeek,
- startOfThisWeek,
- endOfLastWeek,
- startOfLastWeek,
- endOfPriorWeek,
- startOfPriorWeek,
- };
- }, []);
+ const allDates = [
+ endOfThisMonth,
+ startofthisMonth,
+ endOfLastmonth,
+ startOfLastmonth,
+ endOfThisWeek,
+ startOfThisWeek,
+ endOfLastWeek,
+ startOfLastWeek,
+ endOfPriorWeek,
+ startOfPriorWeek,
+ ];
+ const start = dayjs.min(allDates);
+ const end = dayjs.max(allDates);
+ return {
+ start,
+ end,
+ endOfThisMonth,
+ startofthisMonth,
+ endOfLastmonth,
+ startOfLastmonth,
+ endOfThisWeek,
+ startOfThisWeek,
+ endOfLastWeek,
+ startOfLastWeek,
+ endOfPriorWeek,
+ startOfPriorWeek,
+ };
+ }, []);
- const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE_SB, {
- variables: {
- start: startDate.format("YYYY-MM-DD"),
- end: endDate.format("YYYY-MM-DD"),
- fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
- fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
- jobStart: startDate,
- jobEnd: endDate,
- },
- fetchPolicy: "network-only",
- nextFetchPolicy: "network-only",
- pollInterval: 60000,
- skip: !fixedPeriods,
- });
-
- const calculatedData = useMemo(() => {
- if (!data) return [];
- const ret = {
- totalThisWeek: 0,
- totalThisWeekLAB: 0,
- totalThisWeekLAR: 0,
- totalLastWeek: 0,
- totalLastWeekLAB: 0,
- totalLastWeekLAR: 0,
- totalPriorWeek: 0,
- totalPriorWeekLAB: 0,
- totalPriorWeekLAR: 0,
- totalThisMonth: 0,
- totalThisMonthLAB: 0,
- totalThisMonthLAR: 0,
- totalLastMonth: 0,
- totalLastMonthLAB: 0,
- totalLastMonthLAR: 0,
- actualTotalOverPeriod: 0,
- actualTotalOverPeriodLAB: 0,
- actualTotalOverPeriodLAR: 0,
- totalEffieciencyOverPeriod: 0,
- totalEffieciencyOverPeriodLAB: 0,
- totalEffieciencyOverPeriodLAR: 0,
- seperatedThisWeek: {
- sunday: {
- total: 0,
- lab: 0,
- lar: 0,
+ const {loading, error, data} = useQuery(QUERY_TIME_TICKETS_IN_RANGE_SB, {
+ variables: {
+ start: startDate.format("YYYY-MM-DD"),
+ end: endDate.format("YYYY-MM-DD"),
+ fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
+ fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
+ jobStart: startDate,
+ jobEnd: endDate,
},
- monday: {
- total: 0,
- lab: 0,
- lar: 0,
- },
- tuesday: {
- total: 0,
- lab: 0,
- lar: 0,
- },
- wednesday: {
- total: 0,
- lab: 0,
- lar: 0,
- },
- thursday: {
- total: 0,
- lab: 0,
- lar: 0,
- },
- friday: {
- total: 0,
- lab: 0,
- lar: 0,
- },
- saturday: {
- total: 0,
- lab: 0,
- lar: 0,
- },
- },
- };
-
- data.fixedperiod.forEach((ticket) => {
- const ticketDate = dayjs(ticket.date);
- if (
- ticketDate.isBetween(
- fixedPeriods.startOfThisWeek,
- fixedPeriods.endOfThisWeek,
- undefined,
- "[]"
- )
- ) {
- ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs;
- if (ticket.ciecacode !== "LAR")
- ret.totalThisWeekLAB = ret.totalThisWeekLAB + ticket.productivehrs;
- if (ticket.ciecacode === "LAR")
- ret.totalThisWeekLAR = ret.totalThisWeekLAR + ticket.productivehrs;
-
- //Seperate out to Day of Week
- ret.seperatedThisWeek[
- dayjs(ticket.date).format("dddd").toLowerCase()
- ].total =
- ret.seperatedThisWeek[
- dayjs(ticket.date).format("dddd").toLowerCase()
- ].total + ticket.productivehrs;
- if (ticket.ciecacode !== "LAR")
- ret.seperatedThisWeek[
- dayjs(ticket.date).format("dddd").toLowerCase()
- ].lab =
- ret.seperatedThisWeek[
- dayjs(ticket.date).format("dddd").toLowerCase()
- ].lab + ticket.productivehrs;
- if (ticket.ciecacode === "LAR")
- ret.seperatedThisWeek[
- dayjs(ticket.date).format("dddd").toLowerCase()
- ].lar =
- ret.seperatedThisWeek[
- dayjs(ticket.date).format("dddd").toLowerCase()
- ].lar + ticket.productivehrs;
- } else if (
- ticketDate.isBetween(
- fixedPeriods.startOfLastWeek,
- fixedPeriods.endOfLastWeek,
- undefined,
- "[]"
- )
- ) {
- ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs;
- if (ticket.ciecacode !== "LAR")
- ret.totalLastWeekLAB = ret.totalLastWeekLAB + ticket.productivehrs;
- if (ticket.ciecacode === "LAR")
- ret.totalLastWeekLAR = ret.totalLastWeekLAR + ticket.productivehrs;
- } else if (
- ticketDate.isBetween(
- fixedPeriods.startOfPriorWeek,
- fixedPeriods.endOfPriorWeek,
- undefined,
- "[]"
- )
- ) {
- ret.totalPriorWeek = ret.totalPriorWeek + ticket.productivehrs;
- if (ticket.ciecacode !== "LAR")
- ret.totalPriorWeekLAB = ret.totalPriorWeekLAB + ticket.productivehrs;
- if (ticket.ciecacode === "LAR")
- ret.totalPriorWeekLAR = ret.totalPriorWeekLAR + ticket.productivehrs;
- }
- if (
- ticketDate.isBetween(
- fixedPeriods.startofthisMonth,
- fixedPeriods.endOfThisMonth,
- undefined,
- "[]"
- )
- ) {
- ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs;
- ret.actualTotalOverPeriod =
- ret.actualTotalOverPeriod + (ticket.actualhrs || 0);
- if (ticket.ciecacode !== "LAR") {
- ret.totalThisMonthLAB = ret.totalThisMonthLAB + ticket.productivehrs;
- ret.actualTotalOverPeriodLAB =
- ret.actualTotalOverPeriodLAB + (ticket.actualhrs || 0);
- }
- if (ticket.ciecacode === "LAR") {
- ret.totalThisMonthLAR = ret.totalThisMonthLAR + ticket.productivehrs;
- ret.actualTotalOverPeriodLAR =
- ret.actualTotalOverPeriodLAR + (ticket.actualhrs || 0);
- }
- } else if (
- ticketDate.isBetween(
- fixedPeriods.startOfLastmonth,
- fixedPeriods.endOfLastmonth,
- undefined,
- "[]"
- )
- ) {
- ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs;
- if (ticket.ciecacode !== "LAR")
- ret.totalLastMonthLAB = ret.totalLastMonthLAB + ticket.productivehrs;
- if (ticket.ciecacode === "LAR")
- ret.totalLastMonthLAR = ret.totalLastMonthLAR + ticket.productivehrs;
- }
+ fetchPolicy: "network-only",
+ nextFetchPolicy: "network-only",
+ pollInterval: 60000,
+ skip: !fixedPeriods,
});
- ret.totalEffieciencyOverPeriod = ret.actualTotalOverPeriod
- ? (ret.totalThisMonth / ret.actualTotalOverPeriod) * 100
- : 0;
- ret.totalEffieciencyOverPeriodLAB = ret.actualTotalOverPeriodLAB
- ? (ret.totalThisMonthLAB / ret.actualTotalOverPeriodLAB) * 100
- : 0;
- ret.totalEffieciencyOverPeriodLAR = ret.actualTotalOverPeriodLAR
- ? (ret.totalThisMonthLAR / ret.actualTotalOverPeriodLAR) * 100
- : 0;
+ const calculatedData = useMemo(() => {
+ if (!data) return [];
+ const ret = {
+ totalThisWeek: 0,
+ totalThisWeekLAB: 0,
+ totalThisWeekLAR: 0,
+ totalLastWeek: 0,
+ totalLastWeekLAB: 0,
+ totalLastWeekLAR: 0,
+ totalPriorWeek: 0,
+ totalPriorWeekLAB: 0,
+ totalPriorWeekLAR: 0,
+ totalThisMonth: 0,
+ totalThisMonthLAB: 0,
+ totalThisMonthLAR: 0,
+ totalLastMonth: 0,
+ totalLastMonthLAB: 0,
+ totalLastMonthLAR: 0,
+ actualTotalOverPeriod: 0,
+ actualTotalOverPeriodLAB: 0,
+ actualTotalOverPeriodLAR: 0,
+ totalEffieciencyOverPeriod: 0,
+ totalEffieciencyOverPeriodLAB: 0,
+ totalEffieciencyOverPeriodLAR: 0,
+ seperatedThisWeek: {
+ sunday: {
+ total: 0,
+ lab: 0,
+ lar: 0,
+ },
+ monday: {
+ total: 0,
+ lab: 0,
+ lar: 0,
+ },
+ tuesday: {
+ total: 0,
+ lab: 0,
+ lar: 0,
+ },
+ wednesday: {
+ total: 0,
+ lab: 0,
+ lar: 0,
+ },
+ thursday: {
+ total: 0,
+ lab: 0,
+ lar: 0,
+ },
+ friday: {
+ total: 0,
+ lab: 0,
+ lar: 0,
+ },
+ saturday: {
+ total: 0,
+ lab: 0,
+ lar: 0,
+ },
+ },
+ };
- roundObject(ret);
+ data.fixedperiod.forEach((ticket) => {
+ const ticketDate = dayjs(ticket.date);
+ if (
+ ticketDate.isBetween(
+ fixedPeriods.startOfThisWeek,
+ fixedPeriods.endOfThisWeek,
+ undefined,
+ "[]"
+ )
+ ) {
+ ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs;
+ if (ticket.ciecacode !== "LAR")
+ ret.totalThisWeekLAB = ret.totalThisWeekLAB + ticket.productivehrs;
+ if (ticket.ciecacode === "LAR")
+ ret.totalThisWeekLAR = ret.totalThisWeekLAR + ticket.productivehrs;
- const ticketsGroupedByDate = _.groupBy(data.timetickets, "date");
-
- const listOfDays = Utils.ListOfDaysInCurrentMonth();
-
- const combinedData = [],
- labData = [],
- larData = [];
- var acc_comb = 0;
- var acc_lab = 0;
- var acc_lar = 0;
-
- listOfDays.forEach((day) => {
- const r = {
- date: dayjs(day).format("MM/DD"),
- actualhrs: 0,
- productivehrs: 0,
- };
-
- const combined = {
- accTargetHrs: _.round(
- Utils.AsOfDateTargetHours(
- bodyshop.scoreboard_target.dailyBodyTarget +
- bodyshop.scoreboard_target.dailyPaintTarget,
- day
- ) +
- (bodyshop.scoreboard_target.dailyBodyTarget +
- bodyshop.scoreboard_target.dailyPaintTarget),
- 1
- ),
- accHrs: 0,
- };
- const lab = {
- accTargetHrs: _.round(
- Utils.AsOfDateTargetHours(
- bodyshop.scoreboard_target.dailyBodyTarget,
- day
- ) + bodyshop.scoreboard_target.dailyBodyTarget,
- 1
- ),
- accHrs: 0,
- };
- const lar = {
- accTargetHrs: _.round(
- Utils.AsOfDateTargetHours(
- bodyshop.scoreboard_target.dailyPaintTarget,
- day
- ) + bodyshop.scoreboard_target.dailyPaintTarget,
- 1
- ),
- accHrs: 0,
- };
-
- if (ticketsGroupedByDate[day]) {
- ticketsGroupedByDate[day].forEach((ticket) => {
- r.actualhrs = r.actualhrs + ticket.actualhrs;
- r.productivehrs = r.productivehrs + ticket.productivehrs;
- acc_comb = acc_comb + ticket.productivehrs;
-
- if (ticket.ciecacode !== "LAR")
- acc_lab = acc_lab + ticket.productivehrs;
- if (ticket.ciecacode === "LAR")
- acc_lar = acc_lar + ticket.productivehrs;
+ //Seperate out to Day of Week
+ ret.seperatedThisWeek[
+ dayjs(ticket.date).format("dddd").toLowerCase()
+ ].total =
+ ret.seperatedThisWeek[
+ dayjs(ticket.date).format("dddd").toLowerCase()
+ ].total + ticket.productivehrs;
+ if (ticket.ciecacode !== "LAR")
+ ret.seperatedThisWeek[
+ dayjs(ticket.date).format("dddd").toLowerCase()
+ ].lab =
+ ret.seperatedThisWeek[
+ dayjs(ticket.date).format("dddd").toLowerCase()
+ ].lab + ticket.productivehrs;
+ if (ticket.ciecacode === "LAR")
+ ret.seperatedThisWeek[
+ dayjs(ticket.date).format("dddd").toLowerCase()
+ ].lar =
+ ret.seperatedThisWeek[
+ dayjs(ticket.date).format("dddd").toLowerCase()
+ ].lar + ticket.productivehrs;
+ } else if (
+ ticketDate.isBetween(
+ fixedPeriods.startOfLastWeek,
+ fixedPeriods.endOfLastWeek,
+ undefined,
+ "[]"
+ )
+ ) {
+ ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs;
+ if (ticket.ciecacode !== "LAR")
+ ret.totalLastWeekLAB = ret.totalLastWeekLAB + ticket.productivehrs;
+ if (ticket.ciecacode === "LAR")
+ ret.totalLastWeekLAR = ret.totalLastWeekLAR + ticket.productivehrs;
+ } else if (
+ ticketDate.isBetween(
+ fixedPeriods.startOfPriorWeek,
+ fixedPeriods.endOfPriorWeek,
+ undefined,
+ "[]"
+ )
+ ) {
+ ret.totalPriorWeek = ret.totalPriorWeek + ticket.productivehrs;
+ if (ticket.ciecacode !== "LAR")
+ ret.totalPriorWeekLAB = ret.totalPriorWeekLAB + ticket.productivehrs;
+ if (ticket.ciecacode === "LAR")
+ ret.totalPriorWeekLAR = ret.totalPriorWeekLAR + ticket.productivehrs;
+ }
+ if (
+ ticketDate.isBetween(
+ fixedPeriods.startofthisMonth,
+ fixedPeriods.endOfThisMonth,
+ undefined,
+ "[]"
+ )
+ ) {
+ ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs;
+ ret.actualTotalOverPeriod =
+ ret.actualTotalOverPeriod + (ticket.actualhrs || 0);
+ if (ticket.ciecacode !== "LAR") {
+ ret.totalThisMonthLAB = ret.totalThisMonthLAB + ticket.productivehrs;
+ ret.actualTotalOverPeriodLAB =
+ ret.actualTotalOverPeriodLAB + (ticket.actualhrs || 0);
+ }
+ if (ticket.ciecacode === "LAR") {
+ ret.totalThisMonthLAR = ret.totalThisMonthLAR + ticket.productivehrs;
+ ret.actualTotalOverPeriodLAR =
+ ret.actualTotalOverPeriodLAR + (ticket.actualhrs || 0);
+ }
+ } else if (
+ ticketDate.isBetween(
+ fixedPeriods.startOfLastmonth,
+ fixedPeriods.endOfLastmonth,
+ undefined,
+ "[]"
+ )
+ ) {
+ ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs;
+ if (ticket.ciecacode !== "LAR")
+ ret.totalLastMonthLAB = ret.totalLastMonthLAB + ticket.productivehrs;
+ if (ticket.ciecacode === "LAR")
+ ret.totalLastMonthLAR = ret.totalLastMonthLAR + ticket.productivehrs;
+ }
});
- }
- combined.accHrs = acc_comb;
- lab.accHrs = acc_lab;
- lar.accHrs = acc_lar;
- combinedData.push({ ...r, ...combined });
- labData.push({ ...r, ...lab });
- larData.push({ ...r, ...lar });
- });
+ ret.totalEffieciencyOverPeriod = ret.actualTotalOverPeriod
+ ? (ret.totalThisMonth / ret.actualTotalOverPeriod) * 100
+ : 0;
+ ret.totalEffieciencyOverPeriodLAB = ret.actualTotalOverPeriodLAB
+ ? (ret.totalThisMonthLAB / ret.actualTotalOverPeriodLAB) * 100
+ : 0;
+ ret.totalEffieciencyOverPeriodLAR = ret.actualTotalOverPeriodLAR
+ ? (ret.totalThisMonthLAR / ret.actualTotalOverPeriodLAR) * 100
+ : 0;
- const jobData = {};
+ roundObject(ret);
- data.jobs.forEach((job) => {
- job.tthrs = job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
- });
+ const ticketsGroupedByDate = _.groupBy(data.timetickets, "date");
- jobData.tthrs = data.jobs
- .reduce((acc, val) => acc + val.tthrs, 0)
- .toFixed(1);
+ const listOfDays = Utils.ListOfDaysInCurrentMonth();
- jobData.count = data.jobs.length.toFixed(0);
+ const combinedData = [],
+ labData = [],
+ larData = [];
+ var acc_comb = 0;
+ var acc_lab = 0;
+ var acc_lar = 0;
- return {
- fixed: ret,
- combinedData: combinedData,
- labData: labData,
- larData: larData,
- jobData: jobData,
- };
- }, [fixedPeriods, data, bodyshop]);
+ listOfDays.forEach((day) => {
+ const r = {
+ date: dayjs(day).format("MM/DD"),
+ actualhrs: 0,
+ productivehrs: 0,
+ };
- if (error) return ;
- if (loading) return ;
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
+ const combined = {
+ accTargetHrs: _.round(
+ Utils.AsOfDateTargetHours(
+ bodyshop.scoreboard_target.dailyBodyTarget +
+ bodyshop.scoreboard_target.dailyPaintTarget,
+ day
+ ) +
+ (bodyshop.scoreboard_target.dailyBodyTarget +
+ bodyshop.scoreboard_target.dailyPaintTarget),
+ 1
+ ),
+ accHrs: 0,
+ };
+ const lab = {
+ accTargetHrs: _.round(
+ Utils.AsOfDateTargetHours(
+ bodyshop.scoreboard_target.dailyBodyTarget,
+ day
+ ) + bodyshop.scoreboard_target.dailyBodyTarget,
+ 1
+ ),
+ accHrs: 0,
+ };
+ const lar = {
+ accTargetHrs: _.round(
+ Utils.AsOfDateTargetHours(
+ bodyshop.scoreboard_target.dailyPaintTarget,
+ day
+ ) + bodyshop.scoreboard_target.dailyPaintTarget,
+ 1
+ ),
+ accHrs: 0,
+ };
+
+ if (ticketsGroupedByDate[day]) {
+ ticketsGroupedByDate[day].forEach((ticket) => {
+ r.actualhrs = r.actualhrs + ticket.actualhrs;
+ r.productivehrs = r.productivehrs + ticket.productivehrs;
+ acc_comb = acc_comb + ticket.productivehrs;
+
+ if (ticket.ciecacode !== "LAR")
+ acc_lab = acc_lab + ticket.productivehrs;
+ if (ticket.ciecacode === "LAR")
+ acc_lar = acc_lar + ticket.productivehrs;
+ });
+ }
+ combined.accHrs = acc_comb;
+ lab.accHrs = acc_lab;
+ lar.accHrs = acc_lar;
+
+ combinedData.push({...r, ...combined});
+ labData.push({...r, ...lab});
+ larData.push({...r, ...lar});
+ });
+
+ const jobData = {};
+
+ const dataJobs = data.jobs.map((job) => ({
+ ...job,
+ tthrs: job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
+ }));
+
+ jobData.tthrs = dataJobs
+ .reduce((acc, val) => acc + val.tthrs, 0)
+ .toFixed(1);
+
+ jobData.count = dataJobs.length.toFixed(0);
+
+ return {
+ fixed: ret,
+ combinedData: combinedData,
+ labData: labData,
+ larData: larData,
+ jobData: jobData,
+ };
+ }, [fixedPeriods, data, bodyshop]);
+
+ if (error) return ;
+ if (loading) return ;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
}
function roundObject(inputObj) {
- for (var key of Object.keys(inputObj)) {
- if (typeof inputObj[key] === "number") {
- inputObj[key] = inputObj[key].toFixed(1);
- } else if (Array.isArray(inputObj[key])) {
- inputObj[key].forEach((item) => roundObject(item));
- } else if (typeof inputObj[key] === "object") {
- roundObject(inputObj[key]);
+ for (var key of Object.keys(inputObj)) {
+ if (typeof inputObj[key] === "number") {
+ inputObj[key] = inputObj[key].toFixed(1);
+ } else if (Array.isArray(inputObj[key])) {
+ inputObj[key].forEach((item) => roundObject(item));
+ } else if (typeof inputObj[key] === "object") {
+ roundObject(inputObj[key]);
+ }
}
- }
}
diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js
index 4a1c85036..57bb935f8 100644
--- a/client/src/graphql/jobs.queries.js
+++ b/client/src/graphql/jobs.queries.js
@@ -545,147 +545,166 @@ export const QUERY_JOB_COSTING_DETAILS = gql`
export const GET_JOB_BY_PK = gql`
query GET_JOB_BY_PK($id: uuid!) {
jobs_by_pk(id: $id) {
- updated_at
+ actual_completion
+ actual_delivery
+ actual_in
+ adjustment_bottom_line
+ area_of_damage
+ auto_add_ats
+ available_jobs {
+ id
+ }
+ alt_transport
+ ca_bc_pvrt
+ ca_customer_gst
+ ca_gst_registrant
+ category
+ cccontracts {
+ agreementnumber
+ courtesycar {
+ fleetnumber
+ id
+ make
+ model
+ plate
+ year
+ }
+ id
+ scheduledreturn
+ start
+ status
+ }
+ cieca_ttl
+ class
+ clm_no
+ clm_total
+ comment
+ converted
+ csiinvites {
+ completedon
+ id
+ }
+ date_estimated
+ date_exported
+ date_invoiced
+ date_last_contacted
+ date_lost_sale
+ date_next_contact
+ date_open
+ date_rentalresp
+ date_repairstarted
+ date_scheduled
+ date_towin
+ date_void
+ ded_amt
+ ded_note
+ ded_status
+ deliverchecklist
+ depreciation_taxes
+ driveable
+ employee_body
employee_body_rel {
id
first_name
last_name
}
- employee_refinish_rel {
- id
- first_name
- last_name
- }
- employee_prep_rel {
- id
- first_name
- last_name
- }
+ employee_csr
employee_csr_rel {
id
first_name
last_name
}
- employee_csr
employee_prep
+ employee_prep_rel {
+ id
+ first_name
+ last_name
+ }
employee_refinish
- employee_body
- alt_transport
- intakechecklist
- invoice_final_note
- comment
- loss_desc
- kmin
- kmout
- referral_source
- referral_source_extra
- unit_number
- po_number
- special_coverage_policy
- scheduled_delivery
- converted
- lbr_adjustments
- ro_number
- po_number
- clm_total
+ employee_refinish_rel {
+ id
+ first_name
+ last_name
+ }
+ est_co_nm
+ est_ct_fn
+ est_ct_ln
+ est_ea
+ est_ph1
+ federal_tax_rate
+ id
inproduction
- vehicleid
- plate_no
- plate_st
- v_vin
- v_model_yr
- v_model_desc
- v_make_desc
- v_color
- vehicleid
- driveable
- towin
- loss_of_use
- lost_sale_reason
- vehicle {
- id
- plate_no
- plate_st
- v_vin
- v_model_yr
- v_model_desc
- v_make_desc
- v_color
- notes
- v_paint_codes
- jobs {
- id
- ro_number
- status
- clm_no
- }
- }
- available_jobs {
- id
- }
- ins_co_id
- policy_no
- loss_date
- clm_no
- area_of_damage
- ins_co_nm
ins_addr1
ins_city
+ ins_co_id
+ ins_co_nm
ins_ct_ln
ins_ct_fn
ins_ea
ins_ph1
- est_co_nm
- est_ct_fn
- est_ct_ln
- est_ph1
- est_ea
- selling_dealer
- servicing_dealer
- selling_dealer_contact
- servicing_dealer_contact
- regie_number
- scheduled_completion
- id
- ded_amt
- ded_status
- depreciation_taxes
- other_amount_payable
- towing_payable
- storage_payable
- adjustment_bottom_line
- federal_tax_rate
- state_tax_rate
- local_tax_rate
- tax_tow_rt
- tax_str_rt
- tax_paint_mat_rt
- tax_shop_mat_rt
- tax_sub_rt
- tax_lbr_rt
- tax_levies_rt
- parts_tax_rates
- job_totals
- ownr_fn
- ownr_ln
- ownr_co_nm
- ownr_ea
- ownr_addr1
- ownr_addr2
- ownr_city
- ownr_st
- ownr_zip
- ownr_ctry
- ownr_ph1
- ownr_ph2
- production_vars
- ca_gst_registrant
- ownerid
- ded_note
- materials
- auto_add_ats
- rate_ats
+ intakechecklist
+ invoice_final_note
iouparent
+ job_totals
+ joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
+ act_price
+ ah_detail_line
+ alt_partm
+ alt_partno
+ billlines(limit: 1, order_by: { bill: { date: desc } }) {
+ actual_cost
+ actual_price
+ bill {
+ id
+ invoice_number
+ vendor {
+ id
+ name
+ }
+ }
+ joblineid
+ id
+ quantity
+ }
+ convertedtolbr
+ critical
+ db_hrs
+ db_price
+ db_ref
+ id
+ ioucreated
+ lbr_amt
+ lbr_op
+ line_desc
+ line_ind
+ line_no
+ line_ref
+ location
+ manual_line
+ mod_lb_hrs
+ mod_lbr_ty
+ notes
+ oem_partno
+ op_code_desc
+ part_qty
+ part_type
+ prt_dsmk_m
+ prt_dsmk_p
+ status
+ tax_part
+ unq_seq
+ }
+ kmin
+ kmout
+ labor_rate_desc
+ lbr_adjustments
+ local_tax_rate
+ loss_date
+ loss_desc
+ loss_of_use
+ lost_sale_reason
+ materials
+ other_amount_payable
owner {
id
ownr_fn
@@ -702,7 +721,40 @@ export const GET_JOB_BY_PK = gql`
ownr_ph2
tax_number
}
- labor_rate_desc
+ owner_owing
+ ownerid
+ ownr_addr1
+ ownr_addr2
+ ownr_ctry
+ ownr_city
+ ownr_co_nm
+ ownr_ea
+ ownr_fn
+ ownr_ln
+ ownr_ph1
+ ownr_ph2
+ ownr_st
+ ownr_zip
+ parts_tax_rates
+ payments {
+ amount
+ created_at
+ date
+ exportedat
+ id
+ jobid
+ memo
+ payer
+ paymentnum
+ transactionid
+ type
+ }
+ plate_no
+ plate_st
+ po_number
+ policy_no
+ production_vars
+ rate_ats
rate_la1
rate_la2
rate_la3
@@ -726,121 +778,64 @@ export const GET_JOB_BY_PK = gql`
rate_mapa
rate_mash
rate_matd
- actual_in
- federal_tax_rate
- local_tax_rate
- state_tax_rate
+ regie_number
+ referral_source
+ referral_source_extra
+ remove_from_ar
+ ro_number
scheduled_completion
- scheduled_in
- actual_completion
scheduled_delivery
- actual_delivery
- date_estimated
- date_open
- date_scheduled
- date_invoiced
- date_last_contacted
- date_lost_sale
- date_next_contact
- date_towin
- date_rentalresp
- date_exported
- date_repairstarted
- date_void
+ scheduled_in
+ selling_dealer
+ servicing_dealer
+ selling_dealer_contact
+ servicing_dealer_contact
+ special_coverage_policy
+ state_tax_rate
status
- owner_owing
- tax_registration_number
- class
- category
- deliverchecklist
- voided
- ca_bc_pvrt
- ca_customer_gst
+ storage_payable
suspended
- joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
+ tax_lbr_rt
+ tax_levies_rt
+ tax_paint_mat_rt
+ tax_registration_number
+ tax_shop_mat_rt
+ tax_str_rt
+ tax_sub_rt
+ tax_tow_rt
+ towin
+ towing_payable
+ unit_number
+ updated_at
+ v_vin
+ v_model_yr
+ v_model_desc
+ v_make_desc
+ v_color
+ vehicleid
+ vehicle {
id
- alt_partm
- line_no
- unq_seq
- line_ind
- line_desc
- line_ref
- part_type
- oem_partno
- alt_partno
- db_price
- act_price
- part_qty
- mod_lbr_ty
- db_hrs
- mod_lb_hrs
- lbr_op
- lbr_amt
- op_code_desc
- status
+ jobs {
+ clm_no
+ id
+ ro_number
+ status
+ }
notes
- location
- tax_part
- db_ref
- manual_line
- prt_dsmk_p
- prt_dsmk_m
- ioucreated
- convertedtolbr
- ah_detail_line
- critical
- billlines(limit: 1, order_by: { bill: { date: desc } }) {
- id
- quantity
- actual_cost
- actual_price
- joblineid
- bill {
- id
- invoice_number
- vendor {
- id
- name
- }
- }
- }
- }
- payments {
- id
- jobid
- amount
- payer
- paymentnum
- created_at
- transactionid
- memo
- date
- type
- exportedat
- }
- cccontracts {
- id
- status
- start
- scheduledreturn
- agreementnumber
- courtesycar {
- id
- make
- model
- year
- plate
- fleetnumber
- }
- }
- cieca_ttl
- csiinvites {
- id
- completedon
+ plate_no
+ plate_st
+ v_color
+ v_make_desc
+ v_model_desc
+ v_model_yr
+ v_paint_codes
+ v_vin
}
+ voided
}
}
`;
+
export const GET_JOB_RECONCILIATION_BY_PK = gql`
query GET_JOB_RECONCILIATION_BY_PK($id: uuid!) {
bills(where: { jobid: { _eq: $id } }) {
@@ -905,6 +900,7 @@ export const GET_JOB_RECONCILIATION_BY_PK = gql`
}
}
`;
+
export const QUERY_JOB_CARD_DETAILS = gql`
query QUERY_JOB_CARD_DETAILS($id: uuid!) {
jobs_by_pk(id: $id) {
@@ -2225,3 +2221,120 @@ export const GET_JOB_LINE_ORDERS = gql`
}
}
`;
+
+export const UPDATE_REMOVE_FROM_AR = gql`
+ mutation UPDATE_REMOVE_FROM_AR($jobId: uuid!, $remove_from_ar: Boolean!) {
+ update_jobs_by_pk(
+ pk_columns: { id: $jobId }
+ _set: { remove_from_ar: $remove_from_ar }
+ ) {
+ id
+ remove_from_ar
+ }
+ }
+`;
+
+export const UNVOID_JOB = gql`
+ mutation UNVOID_JOB(
+ $jobId: uuid!
+ $default_imported: String!
+ $currentUserEmail: String!
+ $text: String!
+ ) {
+ update_jobs_by_pk(
+ pk_columns: { id: $jobId }
+ _set: { voided: false, status: $default_imported, date_void: null }
+ ) {
+ id
+ date_void
+ voided
+ status
+ }
+ insert_notes(
+ objects: {
+ jobid: $jobId
+ audit: true
+ created_by: $currentUserEmail
+ text: $text
+ }
+ ) {
+ returning {
+ id
+ }
+ }
+ }
+`;
+
+export const DELETE_INTAKE_CHECKLIST = gql`
+ mutation DELETE_INTAKE($jobId: uuid!) {
+ update_jobs_by_pk(
+ pk_columns: { id: $jobId }
+ _set: { intakechecklist: null }
+ ) {
+ id
+ intakechecklist
+ }
+ }
+`;
+
+export const DELETE_DELIVERY_CHECKLIST = gql`
+ mutation DELETE_DELIVERY($jobId: uuid!) {
+ update_jobs_by_pk(
+ pk_columns: { id: $jobId }
+ _set: { deliverchecklist: null }
+ ) {
+ id
+ deliverchecklist
+ }
+ }
+`;
+
+export const MARK_JOB_FOR_REEXPORT = gql`
+ mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!, $default_invoiced: String!) {
+ update_jobs_by_pk(
+ pk_columns: { id: $jobId }
+ _set: { date_exported: null, status: $default_invoiced }
+ ) {
+ id
+ date_exported
+ status
+ date_invoiced
+ }
+ }
+`;
+
+export const MARK_JOB_AS_EXPORTED = gql`
+ mutation MARK_JOB_AS_EXPORTED(
+ $jobId: uuid!
+ $date_exported: timestamptz!
+ $default_exported: String!
+ ) {
+ update_jobs_by_pk(
+ pk_columns: { id: $jobId }
+ _set: { date_exported: $date_exported, status: $default_exported }
+ ) {
+ id
+ date_exported
+ date_invoiced
+ status
+ }
+ }
+`;
+
+export const MARK_JOB_AS_UNINVOICED = gql`
+ mutation MARK_JOB_AS_UNINVOICED($jobId: uuid!, $default_delivered: String!) {
+ update_jobs_by_pk(
+ pk_columns: { id: $jobId }
+ _set: {
+ date_exported: null
+ date_invoiced: null
+ status: $default_delivered
+ }
+ ) {
+ id
+ date_exported
+ date_invoiced
+ status
+ }
+ }
+`;
diff --git a/client/src/pages/jobs-admin/jobs-admin.page.jsx b/client/src/pages/jobs-admin/jobs-admin.page.jsx
index 0d08e5114..0c7eb9a33 100644
--- a/client/src/pages/jobs-admin/jobs-admin.page.jsx
+++ b/client/src/pages/jobs-admin/jobs-admin.page.jsx
@@ -7,16 +7,16 @@ import { useParams } from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component";
import JobCalculateTotals from "../../components/job-calculate-totals/job-calculate-totals.component";
import ScoreboardAddButton from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component";
+import JobsAdminStatus from "../../components/jobs-admin-change-status/jobs-admin-change.status.component";
import JobsAdminClass from "../../components/jobs-admin-class/jobs-admin-class.component";
import JobsAdminDatesChange from "../../components/jobs-admin-dates/jobs-admin-dates.component";
import JobsAdminDeleteIntake from "../../components/jobs-admin-delete-intake/jobs-admin-delete-intake.component";
import JobsAdminMarkReexport from "../../components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component";
import JobAdminOwnerReassociate from "../../components/jobs-admin-owner-reassociate/jobs-admin-owner-reassociate.component";
+import JobsAdminRemoveAR from "../../components/jobs-admin-remove-ar/jobs-admin-remove-ar.component";
import JobsAdminUnvoid from "../../components/jobs-admin-unvoid/jobs-admin-unvoid.component";
import JobAdminVehicleReassociate from "../../components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
-import JobsAdminStatus from "../../components/jobs-admin-change-status/jobs-admin-change.status.component";
-
import NotFound from "../../components/not-found/not-found.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
@@ -104,6 +104,7 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
+
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index dcddeb7cd..eed845da3 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -99,6 +99,7 @@
},
"audit_trail": {
"messages": {
+ "admin_job_remove_from_ar": "ADMIN: Remove from AR updated to: {{status}}",
"admin_jobmarkexported": "ADMIN: Job marked as exported.",
"admin_jobmarkforreexport": "ADMIN: Job marked for re-export.",
"admin_jobuninvoice": "ADMIN: Job has been uninvoiced.",
@@ -1868,6 +1869,7 @@
},
"reconciliationheader": "Parts & Sublet Reconciliation",
"relatedros": "Related ROs",
+ "remove_from_ar": "Remove from AR",
"returntotals": "Return Totals",
"rosaletotal": "RO Parts Total",
"sale_additional": "Sales - Additional",
@@ -2598,6 +2600,7 @@
},
"templates": {
"anticipated_revenue": "Anticipated Revenue",
+ "ar_aging": "AR Aging",
"attendance_detail": "Attendance (All Employees)",
"attendance_employee": "Employee Attendance",
"attendance_summary": "Attendance Summary (All Employees)",
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index 4fd6a54fa..302780f72 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -99,6 +99,7 @@
},
"audit_trail": {
"messages": {
+ "admin_job_remove_from_ar": "",
"admin_jobmarkexported": "",
"admin_jobmarkforreexport": "",
"admin_jobuninvoice": "",
@@ -1868,6 +1869,7 @@
},
"reconciliationheader": "",
"relatedros": "",
+ "remove_from_ar": "",
"returntotals": "",
"rosaletotal": "",
"sale_additional": "",
@@ -2598,6 +2600,7 @@
},
"templates": {
"anticipated_revenue": "",
+ "ar_aging": "",
"attendance_detail": "",
"attendance_employee": "",
"attendance_summary": "",
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index 70b09f588..161a2d15d 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -99,6 +99,7 @@
},
"audit_trail": {
"messages": {
+ "admin_job_remove_from_ar": "",
"admin_jobmarkexported": "",
"admin_jobmarkforreexport": "",
"admin_jobuninvoice": "",
@@ -1868,6 +1869,7 @@
},
"reconciliationheader": "",
"relatedros": "",
+ "remove_from_ar": "",
"returntotals": "",
"rosaletotal": "",
"sale_additional": "",
@@ -2598,6 +2600,7 @@
},
"templates": {
"anticipated_revenue": "",
+ "ar_aging": "",
"attendance_detail": "",
"attendance_employee": "",
"attendance_summary": "",
diff --git a/client/src/utils/AuditTrailMappings.js b/client/src/utils/AuditTrailMappings.js
index d7098fa2d..eefbb3a11 100644
--- a/client/src/utils/AuditTrailMappings.js
+++ b/client/src/utils/AuditTrailMappings.js
@@ -1,54 +1,56 @@
import i18n from "i18next";
const AuditTrailMapping = {
- alertToggle: (status) => i18n.t("audit_trail.messages.alerttoggle", { status }),
+ admin_job_remove_from_ar: (status) =>
+ i18n.t("audit_trail.messages.admin_job_remove_from_ar", { status }),
+ admin_jobfieldchange: (field, value) =>
+ "ADMIN: " +
+ i18n.t("audit_trail.messages.jobfieldchanged", { field, value }),
+ admin_jobmarkexported: () =>
+ i18n.t("audit_trail.messages.admin_jobmarkexported"),
+ admin_jobmarkforreexport: () =>
+ i18n.t("audit_trail.messages.admin_jobmarkforreexport"),
+ admin_jobstatuschange: (status) =>
+ "ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", { status }),
+ admin_jobuninvoice: () => i18n.t("audit_trail.messages.admin_jobuninvoice"),
+ admin_jobunvoid: () => i18n.t("audit_trail.messages.admin_jobunvoid"),
+ alertToggle: (status) =>
+ i18n.t("audit_trail.messages.alerttoggle", { status }),
appointmentcancel: (lost_sale_reason) =>
i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }),
appointmentinsert: (start) =>
i18n.t("audit_trail.messages.appointmentinsert", { start }),
- jobstatuschange: (status) =>
- i18n.t("audit_trail.messages.jobstatuschange", { status }),
- admin_jobstatuschange: (status) =>
- "ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", { status }),
- jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"),
- jobimported: () => i18n.t("audit_trail.messages.jobimported"),
- jobinvoiced: () =>
- i18n.t("audit_trail.messages.jobinvoiced"),
- jobconverted: (ro_number) =>
- i18n.t("audit_trail.messages.jobconverted", { ro_number }),
- jobfieldchange: (field, value) =>
- i18n.t("audit_trail.messages.jobfieldchanged", { field, value }),
- admin_jobfieldchange: (field, value) =>
- "ADMIN: " +
- i18n.t("audit_trail.messages.jobfieldchanged", { field, value }),
- jobspartsorder: (order_number) =>
- i18n.t("audit_trail.messages.jobspartsorder", { order_number }),
- jobspartsreturn: (order_number) =>
- i18n.t("audit_trail.messages.jobspartsreturn", { order_number }),
- jobmodifylbradj: ({ mod_lbr_ty, hours }) =>
- i18n.t("audit_trail.messages.jobmodifylbradj", { mod_lbr_ty, hours }),
billposted: (invoice_number) =>
i18n.t("audit_trail.messages.billposted", { invoice_number }),
billupdated: (invoice_number) =>
i18n.t("audit_trail.messages.billupdated", { invoice_number }),
+ failedpayment: () => i18n.t("audit_trail.messages.failedpayment"),
jobassignmentchange: (operation, name) =>
i18n.t("audit_trail.messages.jobassignmentchange", { operation, name }),
jobassignmentremoved: (operation) =>
i18n.t("audit_trail.messages.jobassignmentremoved", { operation }),
- jobinproductionchange: (inproduction) =>
- i18n.t("audit_trail.messages.jobinproductionchange", { inproduction }),
jobchecklist: (type, inproduction, status) =>
i18n.t("audit_trail.messages.jobchecklist", { type, inproduction, status }),
+ jobconverted: (ro_number) =>
+ i18n.t("audit_trail.messages.jobconverted", { ro_number }),
+ jobfieldchange: (field, value) =>
+ i18n.t("audit_trail.messages.jobfieldchanged", { field, value }),
+ jobimported: () => i18n.t("audit_trail.messages.jobimported"),
+ jobinproductionchange: (inproduction) =>
+ i18n.t("audit_trail.messages.jobinproductionchange", { inproduction }),
+ jobinvoiced: () => i18n.t("audit_trail.messages.jobinvoiced"),
+ jobmodifylbradj: ({ mod_lbr_ty, hours }) =>
+ i18n.t("audit_trail.messages.jobmodifylbradj", { mod_lbr_ty, hours }),
jobnoteadded: () => i18n.t("audit_trail.messages.jobnoteadded"),
- jobnoteupdated: () => i18n.t("audit_trail.messages.jobnoteupdated"),
jobnotedeleted: () => i18n.t("audit_trail.messages.jobnotedeleted"),
- admin_jobunvoid: () => i18n.t("audit_trail.messages.admin_jobunvoid"),
- admin_jobuninvoice: () => i18n.t("audit_trail.messages.admin_jobuninvoice"),
- admin_jobmarkforreexport: () =>
- i18n.t("audit_trail.messages.admin_jobmarkforreexport"),
- admin_jobmarkexported: () =>
- i18n.t("audit_trail.messages.admin_jobmarkexported"),
- failedpayment: () => i18n.t("audit_trail.messages.failedpayment"),
+ jobnoteupdated: () => i18n.t("audit_trail.messages.jobnoteupdated"),
+ jobspartsorder: (order_number) =>
+ i18n.t("audit_trail.messages.jobspartsorder", { order_number }),
+ jobspartsreturn: (order_number) =>
+ i18n.t("audit_trail.messages.jobspartsreturn", { order_number }),
+ jobstatuschange: (status) =>
+ i18n.t("audit_trail.messages.jobstatuschange", { status }),
+ jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"),
};
export default AuditTrailMapping;
diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js
index eeb937c3a..ad614af3b 100644
--- a/client/src/utils/TemplateConstants.js
+++ b/client/src/utils/TemplateConstants.js
@@ -2020,6 +2020,7 @@ export const TemplateList = (type, context) => {
key: "lost_sales",
//idtype: "vendor",
disabled: false,
+ datedisable: true,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_lost_sale"),
@@ -2039,6 +2040,15 @@ export const TemplateList = (type, context) => {
},
group: "jobs",
},
+ ar_aging: {
+ title: i18n.t("reportcenter.templates.ar_aging"),
+ subject: i18n.t("reportcenter.templates.ar_aging"),
+ key: "ar_aging",
+ //idtype: "vendor",
+ disabled: false,
+ datedisable: true,
+ group: "customers",
+ },
}
: {}),
...(!type || type === "courtesycarcontract"
diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml
index a8e271f93..b9efbb437 100644
--- a/hasura/metadata/tables.yaml
+++ b/hasura/metadata/tables.yaml
@@ -2676,6 +2676,31 @@
- table:
name: job_ar_schema
schema: public
+ object_relationships:
+ - name: job
+ using:
+ foreign_key_constraint_on: id
+ select_permissions:
+ - role: user
+ permission:
+ columns:
+ - id
+ - ro_number
+ - clm_total
+ - total_payments
+ - balance
+ - date_invoiced
+ - shopid
+ filter:
+ job:
+ bodyshop:
+ associations:
+ _and:
+ - active:
+ _eq: true
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
- table:
name: job_conversations
schema: public
@@ -4164,11 +4189,16 @@
- name: job_status_transition
definition:
enable_manual: true
+ insert:
+ columns: '*'
+ update:
+ columns:
+ - status
retry_conf:
interval_sec: 10
num_retries: 0
timeout_sec: 60
- webhook_from_env: HASURA_API_URL
+ webhook: https://worktest.home.irony.online
headers:
- name: event-secret
value_from_env: EVENT_SECRET
diff --git a/hasura/migrations/1705952780563_alter_table_public_job_ar_schema_add_column_shopid/down.sql b/hasura/migrations/1705952780563_alter_table_public_job_ar_schema_add_column_shopid/down.sql
new file mode 100644
index 000000000..fe15ee344
--- /dev/null
+++ b/hasura/migrations/1705952780563_alter_table_public_job_ar_schema_add_column_shopid/down.sql
@@ -0,0 +1,4 @@
+-- Could not auto-generate a down migration.
+-- Please write an appropriate down migration for the SQL below:
+-- alter table "public"."job_ar_schema" add column "shopid" uuid
+-- null;
diff --git a/hasura/migrations/1705952780563_alter_table_public_job_ar_schema_add_column_shopid/up.sql b/hasura/migrations/1705952780563_alter_table_public_job_ar_schema_add_column_shopid/up.sql
new file mode 100644
index 000000000..349ad38de
--- /dev/null
+++ b/hasura/migrations/1705952780563_alter_table_public_job_ar_schema_add_column_shopid/up.sql
@@ -0,0 +1,2 @@
+alter table "public"."job_ar_schema" add column "shopid" uuid
+ null;
diff --git a/hasura/migrations/1705952821099_set_fk_public_job_ar_schema_id/down.sql b/hasura/migrations/1705952821099_set_fk_public_job_ar_schema_id/down.sql
new file mode 100644
index 000000000..61b827d98
--- /dev/null
+++ b/hasura/migrations/1705952821099_set_fk_public_job_ar_schema_id/down.sql
@@ -0,0 +1 @@
+alter table "public"."job_ar_schema" drop constraint "job_ar_schema_id_fkey";
diff --git a/hasura/migrations/1705952821099_set_fk_public_job_ar_schema_id/up.sql b/hasura/migrations/1705952821099_set_fk_public_job_ar_schema_id/up.sql
new file mode 100644
index 000000000..238c13e33
--- /dev/null
+++ b/hasura/migrations/1705952821099_set_fk_public_job_ar_schema_id/up.sql
@@ -0,0 +1,5 @@
+alter table "public"."job_ar_schema"
+ add constraint "job_ar_schema_id_fkey"
+ foreign key ("id")
+ references "public"."jobs"
+ ("id") on update restrict on delete restrict;
diff --git a/hasura/migrations/1705952926623_run_sql_migration/down.sql b/hasura/migrations/1705952926623_run_sql_migration/down.sql
new file mode 100644
index 000000000..ff68f3148
--- /dev/null
+++ b/hasura/migrations/1705952926623_run_sql_migration/down.sql
@@ -0,0 +1,35 @@
+-- Could not auto-generate a down migration.
+-- Please write an appropriate down migration for the SQL below:
+-- CREATE OR REPLACE FUNCTION public.jobs_ar_summary ()
+-- RETURNS SETOF job_ar_schema
+-- LANGUAGE plpgsql
+-- STABLE
+-- AS $function$
+-- BEGIN
+--
+-- RETURN query
+-- select
+-- j.id,
+-- j.ro_number,
+-- j.clm_total,
+-- coalesce (p.total_payments,0) as total_payments,
+-- j.clm_total - coalesce (p.total_payments,0) as balance,
+-- j.date_invoiced,
+-- j.shopid
+-- from
+-- jobs j
+-- left join (
+-- select
+-- p.jobid,
+-- coalesce (sum(p.amount),0) as total_payments
+-- from
+-- payments p
+-- group by
+-- p.jobid
+-- ) p on
+-- j.id = p.jobid
+-- where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) > 0;
+--
+--
+-- END
+-- $function$;
diff --git a/hasura/migrations/1705952926623_run_sql_migration/up.sql b/hasura/migrations/1705952926623_run_sql_migration/up.sql
new file mode 100644
index 000000000..2ac974bd5
--- /dev/null
+++ b/hasura/migrations/1705952926623_run_sql_migration/up.sql
@@ -0,0 +1,33 @@
+CREATE OR REPLACE FUNCTION public.jobs_ar_summary ()
+ RETURNS SETOF job_ar_schema
+ LANGUAGE plpgsql
+ STABLE
+ AS $function$
+BEGIN
+
+ RETURN query
+select
+ j.id,
+ j.ro_number,
+ j.clm_total,
+ coalesce (p.total_payments,0) as total_payments,
+ j.clm_total - coalesce (p.total_payments,0) as balance,
+ j.date_invoiced,
+ j.shopid
+from
+ jobs j
+left join (
+ select
+ p.jobid,
+ coalesce (sum(p.amount),0) as total_payments
+ from
+ payments p
+ group by
+ p.jobid
+ ) p on
+ j.id = p.jobid
+where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) > 0;
+
+
+END
+$function$;
diff --git a/hasura/migrations/1706207204357_run_sql_migration/down.sql b/hasura/migrations/1706207204357_run_sql_migration/down.sql
new file mode 100644
index 000000000..1deabac64
--- /dev/null
+++ b/hasura/migrations/1706207204357_run_sql_migration/down.sql
@@ -0,0 +1,35 @@
+-- Could not auto-generate a down migration.
+-- Please write an appropriate down migration for the SQL below:
+-- CREATE OR REPLACE FUNCTION public.jobs_ar_summary ()
+-- RETURNS SETOF job_ar_schema
+-- LANGUAGE plpgsql
+-- STABLE
+-- AS $function$
+-- BEGIN
+--
+-- RETURN query
+-- select
+-- j.id,
+-- j.ro_number,
+-- j.clm_total,
+-- coalesce (p.total_payments,0) as total_payments,
+-- j.clm_total - coalesce (p.total_payments,0) as balance,
+-- j.date_invoiced,
+-- j.shopid
+-- from
+-- jobs j
+-- left join (
+-- select
+-- p.jobid,
+-- coalesce (sum(p.amount),0) as total_payments
+-- from
+-- payments p
+-- group by
+-- p.jobid
+-- ) p on
+-- j.id = p.jobid
+-- where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0;
+--
+--
+-- END
+-- $function$;
diff --git a/hasura/migrations/1706207204357_run_sql_migration/up.sql b/hasura/migrations/1706207204357_run_sql_migration/up.sql
new file mode 100644
index 000000000..6a42b8d7a
--- /dev/null
+++ b/hasura/migrations/1706207204357_run_sql_migration/up.sql
@@ -0,0 +1,33 @@
+CREATE OR REPLACE FUNCTION public.jobs_ar_summary ()
+ RETURNS SETOF job_ar_schema
+ LANGUAGE plpgsql
+ STABLE
+ AS $function$
+BEGIN
+
+ RETURN query
+select
+ j.id,
+ j.ro_number,
+ j.clm_total,
+ coalesce (p.total_payments,0) as total_payments,
+ j.clm_total - coalesce (p.total_payments,0) as balance,
+ j.date_invoiced,
+ j.shopid
+from
+ jobs j
+left join (
+ select
+ p.jobid,
+ coalesce (sum(p.amount),0) as total_payments
+ from
+ payments p
+ group by
+ p.jobid
+ ) p on
+ j.id = p.jobid
+where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0;
+
+
+END
+$function$;
diff --git a/hasura/migrations/1706207267558_run_sql_migration/down.sql b/hasura/migrations/1706207267558_run_sql_migration/down.sql
new file mode 100644
index 000000000..1deabac64
--- /dev/null
+++ b/hasura/migrations/1706207267558_run_sql_migration/down.sql
@@ -0,0 +1,35 @@
+-- Could not auto-generate a down migration.
+-- Please write an appropriate down migration for the SQL below:
+-- CREATE OR REPLACE FUNCTION public.jobs_ar_summary ()
+-- RETURNS SETOF job_ar_schema
+-- LANGUAGE plpgsql
+-- STABLE
+-- AS $function$
+-- BEGIN
+--
+-- RETURN query
+-- select
+-- j.id,
+-- j.ro_number,
+-- j.clm_total,
+-- coalesce (p.total_payments,0) as total_payments,
+-- j.clm_total - coalesce (p.total_payments,0) as balance,
+-- j.date_invoiced,
+-- j.shopid
+-- from
+-- jobs j
+-- left join (
+-- select
+-- p.jobid,
+-- coalesce (sum(p.amount),0) as total_payments
+-- from
+-- payments p
+-- group by
+-- p.jobid
+-- ) p on
+-- j.id = p.jobid
+-- where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0;
+--
+--
+-- END
+-- $function$;
diff --git a/hasura/migrations/1706207267558_run_sql_migration/up.sql b/hasura/migrations/1706207267558_run_sql_migration/up.sql
new file mode 100644
index 000000000..6a42b8d7a
--- /dev/null
+++ b/hasura/migrations/1706207267558_run_sql_migration/up.sql
@@ -0,0 +1,33 @@
+CREATE OR REPLACE FUNCTION public.jobs_ar_summary ()
+ RETURNS SETOF job_ar_schema
+ LANGUAGE plpgsql
+ STABLE
+ AS $function$
+BEGIN
+
+ RETURN query
+select
+ j.id,
+ j.ro_number,
+ j.clm_total,
+ coalesce (p.total_payments,0) as total_payments,
+ j.clm_total - coalesce (p.total_payments,0) as balance,
+ j.date_invoiced,
+ j.shopid
+from
+ jobs j
+left join (
+ select
+ p.jobid,
+ coalesce (sum(p.amount),0) as total_payments
+ from
+ payments p
+ group by
+ p.jobid
+ ) p on
+ j.id = p.jobid
+where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0;
+
+
+END
+$function$;