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 ( <> - - + + + + ); } 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 ( <> - - - + + + + + ); } 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 ( - + <> + + ); } 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$;