+
+
({
+ setEmailOptions: (e) => dispatch(setEmailOptions(e)),
+});
+
+export function ChatPrintButton({ conversation }) {
+ const [loading, setLoading] = useState(false);
+
+ return (
+
+ {
+ setLoading(true);
+ GenerateDocument(
+ {
+ name: TemplateList("messaging").conversation_list.key,
+ variables: { id: conversation.id },
+ },
+ {
+ subject: TemplateList("messaging").conversation_list.subject,
+ },
+ "p",
+ conversation.id
+ );
+ setLoading(false);
+ }}
+ />
+ {
+ setLoading(true);
+ GenerateDocument(
+ {
+ name: TemplateList("messaging").conversation_list.key,
+ variables: { id: conversation.id },
+ },
+ {
+ subject: TemplateList("messaging").conversation_list.subject,
+ },
+ "e",
+ conversation.id
+ );
+ setLoading(false);
+ }}
+ />
+ {loading && }
+
+ );
+}
+export default connect(mapStateToProps, mapDispatchToProps)(ChatPrintButton);
diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx
index ae3ba9a7b..bf0b049fb 100644
--- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx
+++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx
@@ -108,6 +108,14 @@ export function JobsDetailHeaderActions({
},
},
});
+ insertAuditTrail({
+ jobid: job.id,
+ operation: AuditTrailMapping.alertToggle(
+ !!job.production_vars && !!job.production_vars.alert
+ ? !job.production_vars.alert
+ : true
+ ),
+ });
};
const handleSuspend = (e) => {
diff --git a/client/src/components/production-list-columns/production-list-columns.alert.component.jsx b/client/src/components/production-list-columns/production-list-columns.alert.component.jsx
index 8823afb46..8bad135ca 100644
--- a/client/src/components/production-list-columns/production-list-columns.alert.component.jsx
+++ b/client/src/components/production-list-columns/production-list-columns.alert.component.jsx
@@ -1,12 +1,23 @@
import { ExclamationCircleFilled } from "@ant-design/icons";
+import { useMutation } from "@apollo/client";
import { Dropdown, Menu } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
-import { useMutation } from "@apollo/client";
-import { UPDATE_JOB } from "../../graphql/jobs.queries";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
+import { UPDATE_JOB } from "../../graphql/jobs.queries";
+import { insertAuditTrail } from "../../redux/application/application.actions";
+import AuditTrailMapping from "../../utils/AuditTrailMappings";
-export default function ProductionListColumnAlert({ record }) {
+const mapStateToProps = createStructuredSelector({});
+
+const mapDispatchToProps = (dispatch) => ({
+ insertAuditTrail: ({ jobid, operation }) =>
+ dispatch(insertAuditTrail({ jobid, operation })),
+});
+
+export function ProductionListColumnAlert({ record, insertAuditTrail }) {
const { t } = useTranslation();
const [updateAlert] = useMutation(UPDATE_JOB);
@@ -27,6 +38,14 @@ export default function ProductionListColumnAlert({ record }) {
},
},
},
+ });
+ insertAuditTrail({
+ jobid: record.id,
+ operation: AuditTrailMapping.alertToggle(
+ !!record.production_vars && !!record.production_vars.alert
+ ? !record.production_vars.alert
+ : true
+ ),
}).then(() => {
if (record.refetch) record.refetch();
});
@@ -58,3 +77,8 @@ export default function ProductionListColumnAlert({ record }) {
);
}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(ProductionListColumnAlert);
diff --git a/client/src/components/rbac-wrapper/rbac-defaults.js b/client/src/components/rbac-wrapper/rbac-defaults.js
index 24a564872..a01dd54c2 100644
--- a/client/src/components/rbac-wrapper/rbac-defaults.js
+++ b/client/src/components/rbac-wrapper/rbac-defaults.js
@@ -55,10 +55,11 @@ const ret = {
"shiftclock:view": 2,
"shop:config": 4,
- "shop:rbac": 5,
- "shop:vendors": 2,
"shop:dashboard": 3,
+ "shop:rbac": 5,
+ "shop:reportcenter": 2,
"shop:templates": 4,
+ "shop:vendors": 2,
"temporarydocs:view": 2,
diff --git a/client/src/components/report-center-modal/report-center-modal.container.jsx b/client/src/components/report-center-modal/report-center-modal.container.jsx
index f0d361785..84fe65560 100644
--- a/client/src/components/report-center-modal/report-center-modal.container.jsx
+++ b/client/src/components/report-center-modal/report-center-modal.container.jsx
@@ -5,6 +5,7 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectReportCenter } from "../../redux/modals/modals.selectors";
+import RbacWrapperComponent from "../rbac-wrapper/rbac-wrapper.component";
import ReportCenterModalComponent from "./report-center-modal.component";
const mapStateToProps = createStructuredSelector({
@@ -33,7 +34,9 @@ export function ReportCenterModalContainer({
destroyOnClose
width="80%"
>
-
+
+
+
);
}
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 0117279d5..af4f28695 100644
--- a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx
+++ b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx
@@ -29,7 +29,7 @@ export default connect(
export function ScoreboardTimeTicketsStats({ bodyshop }) {
const { t } = useTranslation();
- const startDate = moment().startOf("month")
+ const startDate = moment().startOf("month");
const endDate = moment().endOf("month");
const fixedPeriods = useMemo(() => {
@@ -84,6 +84,8 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
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",
@@ -340,11 +342,21 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
larData.push({ ...r, ...lar });
});
+ const jobData = {};
+ data.jobs.forEach((job) => {
+ job.tthrs = job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
+ });
+ jobData.tthrs = data.jobs
+ .reduce((acc, val) => acc + val.tthrs, 0)
+ .toFixed(1);
+ jobData.count = data.jobs.length.toFixed(0);
+
return {
fixed: ret,
combinedData: combinedData,
labData: labData,
larData: larData,
+ jobData: jobData,
};
}, [fixedPeriods, data, bodyshop]);
@@ -356,7 +368,10 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
-
+
{/* This Month */}
-
+
@@ -482,7 +482,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
{/* Last Month */}
-
+
@@ -556,7 +556,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
{/* Efficiency Over Period */}
-
+
+
+
+
+
+
+
+
+
+
+
+ {t("scoreboard.labels.totalhrs")}
+
+ }
+ value={jobData.tthrs}
+ valueStyle={{
+ fontSize: statisticSize,
+ fontWeight: statisticWeight,
+ }}
+ />
+
+
+
+
{/* Disclaimer */}
diff --git a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx
index 0fc84af6d..9fdb384d1 100644
--- a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx
+++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx
@@ -65,6 +65,8 @@ export default function ScoreboardTimeTickets() {
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",
diff --git a/client/src/components/shop-info/shop-info.rbac.component.jsx b/client/src/components/shop-info/shop-info.rbac.component.jsx
index fe4f80f31..e4152fb88 100644
--- a/client/src/components/shop-info/shop-info.rbac.component.jsx
+++ b/client/src/components/shop-info/shop-info.rbac.component.jsx
@@ -28,18 +28,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
return (
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -173,26 +209,38 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
+
+
+
@@ -208,30 +256,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
-
-
-
-
-
-
-
-
-
@@ -280,6 +292,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -329,74 +401,14 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -412,18 +424,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
@@ -556,18 +556,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
{Simple_Inventory.treatment === "on" && (
<>
({
+ insertAuditTrail: ({ jobid, operation }) =>
+ dispatch(insertAuditTrail({ jobid, operation })),
+});
+
+export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, }) {
const { t } = useTranslation();
const [form] = Form.useForm();
const client = useApolloClient();
@@ -110,6 +118,10 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
notification["success"]({
message: t("jobs.successes.closed"),
});
+ insertAuditTrail({
+ jobid: job.id,
+ operation: AuditTrailMapping.jobinvoiced(),
+ });
// history.push(`/manage/jobs/${job.id}`);
} else {
setLoading(false);
@@ -527,4 +539,4 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
);
}
-export default connect(mapStateToProps, null)(JobsCloseComponent);
+export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseComponent);
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index bbac4cc7b..b96b555db 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -103,6 +103,7 @@
"admin_jobmarkforreexport": "ADMIN: Job marked for re-export.",
"admin_jobuninvoice": "ADMIN: Job has been uninvoiced.",
"admin_jobunvoid": "ADMIN: Job has been unvoided.",
+ "alerttoggle": "Alert Toggle set to {{status}}",
"appointmentcancel": "Appointment canceled. Lost Reason: {{lost_sale_reason}}.",
"appointmentinsert": "Appointment created. Appointment Date: {{start}}.",
"billposted": "Bill with invoice number {{invoice_number}} posted.",
@@ -111,6 +112,7 @@
"jobassignmentchange": "Employee {{name}} assigned to {{operation}}",
"jobassignmentremoved": "Employee assignment removed for {{operation}}",
"jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.",
+ "jobinvoiced": "Job has been invoiced.",
"jobconverted": "Job converted and assigned number {{ro_number}}.",
"jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.",
"jobimported": "Job imported.",
@@ -203,6 +205,7 @@
"entered_total": "Total of Entered Lines",
"enteringcreditmemo": "You are entering a credit memo. Please ensure you are also entering positive values.",
"federal_tax": "Federal Tax",
+ "federal_tax_exempt": "Federal Tax Exempt?",
"generatepartslabel": "Generate Parts Labels after Saving?",
"iouexists": "An IOU exists that is associated to this RO.",
"local_tax": "Local Tax",
@@ -447,6 +450,7 @@
"config": "Shop -> Config",
"dashboard": "Shop -> Dashboard",
"rbac": "Shop -> RBAC",
+ "reportcenter": "Shop -> Report Center",
"templates": "Shop -> Templates",
"vendors": "Shop -> Vendors"
},
@@ -2044,6 +2048,9 @@
"sentby": "Sent by {{by}} at {{time}}",
"typeamessage": "Send a message...",
"unarchive": "Unarchive"
+ },
+ "render": {
+ "conversation_list": "Conversation List"
}
},
"notes": {
@@ -2695,6 +2702,7 @@
"efficiencyoverperiod": "Efficiency over Selected Dates",
"entries": "Scoreboard Entries",
"jobs": "Jobs",
+ "jobscompletednotinvoiced": "Completed Not Invoiced",
"lastmonth": "Last Month",
"lastweek": "Last Week",
"monthlytarget": "Monthly",
@@ -2709,6 +2717,7 @@
"timetickets": "Time Tickets",
"timeticketsemployee": "Time Tickets by Employee",
"todateactual": "Actual (MTD)",
+ "totalhrs": "Total Hours",
"totaloverperiod": "Total over Selected Dates",
"weeklyactual": "Actual (W)",
"weeklytarget": "Weekly",
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index 33cf621da..f68cf4a41 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -103,6 +103,7 @@
"admin_jobmarkforreexport": "",
"admin_jobuninvoice": "",
"admin_jobunvoid": "",
+ "alerttoggle": "",
"appointmentcancel": "",
"appointmentinsert": "",
"billposted": "",
@@ -111,6 +112,7 @@
"jobassignmentchange": "",
"jobassignmentremoved": "",
"jobchecklist": "",
+ "jobinvoiced": "",
"jobconverted": "",
"jobfieldchanged": "",
"jobimported": "",
@@ -203,6 +205,7 @@
"entered_total": "",
"enteringcreditmemo": "",
"federal_tax": "",
+ "federal_tax_exempt": "",
"generatepartslabel": "",
"iouexists": "",
"local_tax": "",
@@ -447,6 +450,7 @@
"config": "",
"dashboard": "",
"rbac": "",
+ "reportcenter": "",
"templates": "",
"vendors": ""
},
@@ -2044,6 +2048,9 @@
"sentby": "",
"typeamessage": "Enviar un mensaje...",
"unarchive": ""
+ },
+ "render": {
+ "conversation_list": ""
}
},
"notes": {
@@ -2695,6 +2702,7 @@
"efficiencyoverperiod": "",
"entries": "",
"jobs": "",
+ "jobscompletednotinvoiced": "",
"lastmonth": "",
"lastweek": "",
"monthlytarget": "",
@@ -2709,6 +2717,7 @@
"timetickets": "",
"timeticketsemployee": "",
"todateactual": "",
+ "totalhrs": "",
"totaloverperiod": "",
"weeklyactual": "",
"weeklytarget": "",
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index 3b5251fa7..d0e44b704 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -103,6 +103,7 @@
"admin_jobmarkforreexport": "",
"admin_jobuninvoice": "",
"admin_jobunvoid": "",
+ "alerttoggle": "",
"appointmentcancel": "",
"appointmentinsert": "",
"billposted": "",
@@ -111,6 +112,7 @@
"jobassignmentchange": "",
"jobassignmentremoved": "",
"jobchecklist": "",
+ "jobinvoiced": "",
"jobconverted": "",
"jobfieldchanged": "",
"jobimported": "",
@@ -203,6 +205,7 @@
"entered_total": "",
"enteringcreditmemo": "",
"federal_tax": "",
+ "federal_tax_exempt": "",
"generatepartslabel": "",
"iouexists": "",
"local_tax": "",
@@ -447,6 +450,7 @@
"config": "",
"dashboard": "",
"rbac": "",
+ "reportcenter": "",
"templates": "",
"vendors": ""
},
@@ -2044,6 +2048,9 @@
"sentby": "",
"typeamessage": "Envoyer un message...",
"unarchive": ""
+ },
+ "render": {
+ "conversation_list": ""
}
},
"notes": {
@@ -2695,6 +2702,7 @@
"efficiencyoverperiod": "",
"entries": "",
"jobs": "",
+ "jobscompletednotinvoiced": "",
"lastmonth": "",
"lastweek": "",
"monthlytarget": "",
@@ -2709,6 +2717,7 @@
"timetickets": "",
"timeticketsemployee": "",
"todateactual": "",
+ "totalhrs": "",
"totaloverperiod": "",
"weeklyactual": "",
"weeklytarget": "",
diff --git a/client/src/utils/AuditTrailMappings.js b/client/src/utils/AuditTrailMappings.js
index afa0de1e5..d7098fa2d 100644
--- a/client/src/utils/AuditTrailMappings.js
+++ b/client/src/utils/AuditTrailMappings.js
@@ -1,6 +1,7 @@
import i18n from "i18next";
const AuditTrailMapping = {
+ alertToggle: (status) => i18n.t("audit_trail.messages.alerttoggle", { status }),
appointmentcancel: (lost_sale_reason) =>
i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }),
appointmentinsert: (start) =>
@@ -11,6 +12,8 @@ const AuditTrailMapping = {
"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) =>
diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js
index c5e10d712..365b2431e 100644
--- a/client/src/utils/TemplateConstants.js
+++ b/client/src/utils/TemplateConstants.js
@@ -2102,6 +2102,17 @@ export const TemplateList = (type, context) => {
// },
}
: {}),
+ ...(!type || type === "messaging"
+ ? {
+ conversation_list: {
+ title: i18n.t("messaging.render.conversation_list"),
+ description: "",
+ subject: i18n.t("messaging.render.conversation_list"),
+ key: "conversation_list",
+ disabled: false,
+ },
+ }
+ : {}),
...(!type || type === "vendor"
? {
purchases_by_vendor_detailed: {
diff --git a/server/firebase/firebase-handler.js b/server/firebase/firebase-handler.js
index 7221649ec..e203bcbf3 100644
--- a/server/firebase/firebase-handler.js
+++ b/server/firebase/firebase-handler.js
@@ -50,7 +50,7 @@ exports.createUser = async (req, res) => {
`,
{
user: {
- email,
+ email: email.toLowerCase(),
authid: userRecord.uid,
associations: {
data: [{ shopid, authlevel, active: true }],