merge test (release pending tomorrow) into client-update and make required changes.

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2023-12-21 13:12:24 -05:00
19 changed files with 1075 additions and 827 deletions

View File

@@ -53,9 +53,23 @@ export function BillFormComponent({bodyshop, disabled, form, vendorAutoCompleteO
}); });
}; };
useEffect(() => { const handleFederalTaxExemptSwitchToggle = (checked) => {
if (job) form.validateFields(["is_credit_memo"]); // Early gate
}, [job, form]); if (!checked) return;
const values = form.getFieldsValue("billlines");
// Gate bill lines
if (!values?.billlines?.length) return;
const billlines = values.billlines.map((b) => {
b.applicable_taxes.federal = false;
return b;
});
form.setFieldsValue({ billlines });
};
useEffect(() => {
if (job) form.validateFields(["is_credit_memo"]);
}, [job, form]);
useEffect(() => { useEffect(() => {
const vendorId = form.getFieldValue("vendorid"); const vendorId = form.getFieldValue("vendorid");
@@ -308,134 +322,143 @@ export function BillFormComponent({bodyshop, disabled, form, vendorAutoCompleteO
return Promise.reject(t("bills.labels.onlycmforinvoiced")); return Promise.reject(t("bills.labels.onlycmforinvoiced"));
} }
return Promise.resolve(); return Promise.resolve();
}, },
}), }),
]} ]}
> >
<Switch/> <Switch />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bills.fields.total")} label={t("bills.fields.total")}
name="total" name="total"
rules={[ rules={[
{ {
required: true, required: true,
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
]} ]}
> >
<CurrencyInput min={0} disabled={disabled}/> <CurrencyInput min={0} disabled={disabled} />
</Form.Item> </Form.Item>
{!billEdit && ( {!billEdit && (
<Form.Item label={t("bills.fields.allpartslocation")} name="location"> <Form.Item label={t("bills.fields.allpartslocation")} name="location">
<Select style={{width: "10rem"}} disabled={disabled} allowClear> <Select style={{ width: "10rem" }} disabled={disabled} allowClear>
{bodyshop.md_parts_locations.map((loc, idx) => ( {bodyshop.md_parts_locations.map((loc, idx) => (
<Select.Option key={idx} value={loc}> <Select.Option key={idx} value={loc}>
{loc} {loc}
</Select.Option> </Select.Option>
))} ))}
</Select> </Select>
</Form.Item> </Form.Item>
)} )}
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow> <LayoutFormRow>
<Form.Item <Form.Item
span={3} span={3}
label={t("bills.fields.federal_tax_rate")} label={t("bills.fields.federal_tax_rate")}
name="federal_tax_rate" name="federal_tax_rate"
> >
<CurrencyInput min={0} disabled={disabled}/> <CurrencyInput min={0} disabled={disabled} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
span={3} span={3}
label={t("bills.fields.state_tax_rate")} label={t("bills.fields.state_tax_rate")}
name="state_tax_rate" name="state_tax_rate"
> >
<CurrencyInput min={0} disabled={disabled}/> <CurrencyInput min={0} disabled={disabled} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
span={3} span={3}
label={t("bills.fields.local_tax_rate")} label={t("bills.fields.local_tax_rate")}
name="local_tax_rate" name="local_tax_rate"
> >
<CurrencyInput min={0}/> <CurrencyInput min={0} />
</Form.Item> </Form.Item>
<Form.Item shouldUpdate span={15}> {bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
{() => { <Form.Item
const values = form.getFieldsValue([ span={2}
"billlines", label={t("bills.labels.federal_tax_exempt")}
"total", name="federal_tax_exempt"
"federal_tax_rate", >
"state_tax_rate", <Switch onChange={handleFederalTaxExemptSwitchToggle} />
"local_tax_rate", </Form.Item>
]); ) : null}
let totals; <Form.Item shouldUpdate span={13}>
if ( {() => {
!!values.total && const values = form.getFieldsValue([
!!values.billlines && "billlines",
values.billlines.length > 0 "total",
) "federal_tax_rate",
totals = CalculateBillTotal(values); "state_tax_rate",
if (!!totals) "local_tax_rate",
return ( ]);
<div> let totals;
<Space wrap> if (
<Statistic !!values.total &&
title={t("bills.labels.subtotal")} !!values.billlines &&
value={totals.subtotal.toFormat()} values.billlines.length > 0
precision={2} )
/> totals = CalculateBillTotal(values);
<Statistic if (!!totals)
title={t("bills.labels.federal_tax")} return (
value={totals.federalTax.toFormat()} <div align="right">
precision={2} <Space wrap>
/> <Statistic
<Statistic title={t("bills.labels.subtotal")}
title={t("bills.labels.state_tax")} value={totals.subtotal.toFormat()}
value={totals.stateTax.toFormat()} precision={2}
precision={2} />
/> <Statistic
<Statistic title={t("bills.labels.federal_tax")}
title={t("bills.labels.local_tax")} value={totals.federalTax.toFormat()}
value={totals.localTax.toFormat()} precision={2}
precision={2} />
/> <Statistic
<Statistic title={t("bills.labels.state_tax")}
title={t("bills.labels.entered_total")} value={totals.stateTax.toFormat()}
value={totals.enteredTotal.toFormat()} precision={2}
precision={2} />
/> <Statistic
<Statistic title={t("bills.labels.local_tax")}
title={t("bills.labels.bill_total")} value={totals.localTax.toFormat()}
value={totals.invoiceTotal.toFormat()} precision={2}
precision={2} />
/> <Statistic
<Statistic title={t("bills.labels.entered_total")}
title={t("bills.labels.discrepancy")} value={totals.enteredTotal.toFormat()}
valueStyle={{ precision={2}
color: />
totals.discrepancy.getAmount() === 0 <Statistic
? "green" title={t("bills.labels.bill_total")}
: "red", value={totals.invoiceTotal.toFormat()}
}} precision={2}
value={totals.discrepancy.toFormat()} />
precision={2} <Statistic
/> title={t("bills.labels.discrepancy")}
</Space> valueStyle={{
{form.getFieldValue("is_credit_memo") ? ( color:
<AlertComponent totals.discrepancy.getAmount() === 0
type="warning" ? "green"
message={t("bills.labels.enteringcreditmemo")} : "red",
/> }}
) : null} value={totals.discrepancy.toFormat()}
</div> precision={2}
); />
return null; </Space>
}} {form.getFieldValue("is_credit_memo") ? (
</Form.Item> <AlertComponent
</LayoutFormRow> type="warning"
<Divider orientation="left">{t("bills.labels.bill_lines")}</Divider> message={t("bills.labels.enteringcreditmemo")}
/>
) : null}
</div>
);
return null;
}}
</Form.Item>
</LayoutFormRow>
<Divider orientation="left">{t("bills.labels.bill_lines")}</Divider>
{Extended_Bill_Posting.treatment === "on" ? ( {Extended_Bill_Posting.treatment === "on" ? (
<BillFormLinesExtended <BillFormLinesExtended

View File

@@ -1,14 +1,15 @@
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons"; import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
import {useSplitTreatments} from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
import { import {
Button, Form, Button,
Form,
Input, Input,
InputNumber, InputNumber,
Select, Select,
Space, Space,
Switch, Switch,
Table, Table,
Tooltip Tooltip,
} from "antd"; } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -469,7 +470,8 @@ export function BillEnterModalLinesComponent({
return { return {
key: `${field.index}fedtax`, key: `${field.index}fedtax`,
valuePropName: "checked", valuePropName: "checked",
initialValue: true, initialValue:
form.getFieldValue("federal_tax_exempt") === true ? false : true,
name: [field.name, "applicable_taxes", "federal"], name: [field.name, "applicable_taxes", "federal"],
}; };
}, },

View File

@@ -4,6 +4,7 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component"; import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component";
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component"; import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component";
import ChatLabelComponent from "../chat-label/chat-label.component"; import ChatLabelComponent from "../chat-label/chat-label.component";
import ChatPrintButton from "../chat-print-button/chat-print-button.component";
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container"; import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
export default function ChatConversationTitle({ conversation }) { export default function ChatConversationTitle({ conversation }) {
@@ -13,6 +14,7 @@ export default function ChatConversationTitle({ conversation }) {
{conversation && conversation.phone_num} {conversation && conversation.phone_num}
</PhoneNumberFormatter> </PhoneNumberFormatter>
<ChatLabelComponent conversation={conversation} /> <ChatLabelComponent conversation={conversation} />
<ChatPrintButton conversation={conversation} />
<ChatConversationTitleTags <ChatConversationTitleTags
jobConversations={ jobConversations={
(conversation && conversation.job_conversations) || [] (conversation && conversation.job_conversations) || []

View File

@@ -0,0 +1,45 @@
import { MailOutlined, PrinterOutlined } from "@ant-design/icons";
import { Space, Spin } from "antd";
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setEmailOptions } from "../../redux/email/email.actions";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
});
export function ChatPrintButton({ conversation }) {
const [loading, setLoading] = useState(false);
const generateDocument = (type) => {
setLoading(true);
GenerateDocument(
{
name: TemplateList("messaging").conversation_list.key,
variables: { id: conversation.id },
},
{
subject: TemplateList("messaging").conversation_list.subject,
},
type,
conversation.id
).catch(e => {
console.warn('Something went wrong generating a document.');
});
setLoading(false);
}
return (
<Space wrap>
<PrinterOutlined onClick={() => generateDocument('p')}/>
<MailOutlined onClick={() => generateDocument('e')}/>
{loading && <Spin />}
</Space>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatPrintButton);

View File

@@ -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) => { const handleSuspend = (e) => {

View File

@@ -1,12 +1,23 @@
import { ExclamationCircleFilled } from "@ant-design/icons"; import { ExclamationCircleFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Dropdown, Menu } from "antd"; import { Dropdown, Menu } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useMutation } from "@apollo/client"; import { connect } from "react-redux";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; 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 { t } = useTranslation();
const [updateAlert] = useMutation(UPDATE_JOB); 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(() => { }).then(() => {
if (record.refetch) record.refetch(); if (record.refetch) record.refetch();
}); });
@@ -58,3 +77,8 @@ export default function ProductionListColumnAlert({ record }) {
</Dropdown> </Dropdown>
); );
} }
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListColumnAlert);

View File

@@ -55,10 +55,11 @@ const ret = {
"shiftclock:view": 2, "shiftclock:view": 2,
"shop:config": 4, "shop:config": 4,
"shop:rbac": 5,
"shop:vendors": 2,
"shop:dashboard": 3, "shop:dashboard": 3,
"shop:rbac": 5,
"shop:reportcenter": 2,
"shop:templates": 4, "shop:templates": 4,
"shop:vendors": 2,
"temporarydocs:view": 2, "temporarydocs:view": 2,

View File

@@ -5,6 +5,7 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectReportCenter } from "../../redux/modals/modals.selectors"; import { selectReportCenter } from "../../redux/modals/modals.selectors";
import RbacWrapperComponent from "../rbac-wrapper/rbac-wrapper.component";
import ReportCenterModalComponent from "./report-center-modal.component"; import ReportCenterModalComponent from "./report-center-modal.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -33,7 +34,9 @@ export function ReportCenterModalContainer({
destroyOnClose destroyOnClose
width="80%" width="80%"
> >
<ReportCenterModalComponent /> <RbacWrapperComponent action="shop:reportcenter">
<ReportCenterModalComponent />
</RbacWrapperComponent>
</Modal> </Modal>
); );
} }

View File

@@ -84,6 +84,8 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
end: endDate.format("YYYY-MM-DD"), end: endDate.format("YYYY-MM-DD"),
fixedStart: fixedPeriods.start.format("YYYY-MM-DD"), fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"), fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
jobStart: startDate,
jobEnd: endDate,
}, },
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
@@ -340,11 +342,24 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
larData.push({ ...r, ...lar }); 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 { return {
fixed: ret, fixed: ret,
combinedData: combinedData, combinedData: combinedData,
labData: labData, labData: labData,
larData: larData, larData: larData,
jobData: jobData,
}; };
}, [fixedPeriods, data, bodyshop]); }, [fixedPeriods, data, bodyshop]);
@@ -356,7 +371,10 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
<ScoreboardTimeticketsTargetsTable /> <ScoreboardTimeticketsTargetsTable />
</Col> </Col>
<Col span={24}> <Col span={24}>
<ScoreboardTicketsStats data={calculatedData.fixed} /> <ScoreboardTicketsStats
data={calculatedData.fixed}
jobData={calculatedData.jobData}
/>
</Col> </Col>
<Col span={24}> <Col span={24}>
<ScoreboardTimeTicketsChart <ScoreboardTimeTicketsChart

View File

@@ -41,7 +41,7 @@ function useLocalStorage(key, initialValue) {
return [storedValue, setStoredValue]; return [storedValue, setStoredValue];
} }
export function ScoreboardTicketsStats({ data, bodyshop }) { export function ScoreboardTicketsStats({ data, jobData, bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [isLarge, setIsLarge] = useLocalStorage("isLargeStatistic", false); const [isLarge, setIsLarge] = useLocalStorage("isLargeStatistic", false);
@@ -408,7 +408,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
{/* Monthly Stats */} {/* Monthly Stats */}
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
{/* This Month */} {/* This Month */}
<Col span={8} align="center"> <Col span={7} align="center">
<Card size="small" title={t("scoreboard.labels.thismonth")}> <Card size="small" title={t("scoreboard.labels.thismonth")}>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={24}> <Col span={24}>
@@ -482,7 +482,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
</Card> </Card>
</Col> </Col>
{/* Last Month */} {/* Last Month */}
<Col span={8} align="center"> <Col span={7} align="center">
<Card size="small" title={t("scoreboard.labels.lastmonth")}> <Card size="small" title={t("scoreboard.labels.lastmonth")}>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={24}> <Col span={24}>
@@ -556,7 +556,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
</Card> </Card>
</Col> </Col>
{/* Efficiency Over Period */} {/* Efficiency Over Period */}
<Col span={8} align="center"> <Col span={7} align="center">
<Card <Card
size="small" size="small"
title={t("scoreboard.labels.efficiencyoverperiod")} title={t("scoreboard.labels.efficiencyoverperiod")}
@@ -604,6 +604,40 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
</Row> </Row>
</Card> </Card>
</Col> </Col>
<Col span={3} align="center">
<Card
size="small"
title={t("scoreboard.labels.jobscompletednotinvoiced")}
>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={jobData.count}
valueStyle={{
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.totalhrs")}
</Typography.Text>
}
value={jobData.tthrs}
valueStyle={{
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
</Row> </Row>
</Space> </Space>
{/* Disclaimer */} {/* Disclaimer */}

File diff suppressed because it is too large Load Diff

View File

@@ -143,9 +143,14 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
$end: date! $end: date!
$fixedStart: date! $fixedStart: date!
$fixedEnd: date! $fixedEnd: date!
$jobStart: timestamptz!
$jobEnd: timestamptz!
) { ) {
timetickets( timetickets(
where: { date: { _gte: $start, _lte: $end }, cost_center: {_neq: "timetickets.labels.shift"} } where: {
date: { _gte: $start, _lte: $end }
cost_center: { _neq: "timetickets.labels.shift" }
}
order_by: { date: desc_nulls_first } order_by: { date: desc_nulls_first }
) { ) {
actualhrs actualhrs
@@ -176,7 +181,10 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
} }
} }
fixedperiod: timetickets( fixedperiod: timetickets(
where: { date: { _gte: $fixedStart, _lte: $fixedEnd }, cost_center: {_neq: "timetickets.labels.shift"} } where: {
date: { _gte: $fixedStart, _lte: $fixedEnd }
cost_center: { _neq: "timetickets.labels.shift" }
}
order_by: { date: desc_nulls_first } order_by: { date: desc_nulls_first }
) { ) {
actualhrs actualhrs
@@ -205,6 +213,25 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
last_name last_name
} }
} }
jobs(
where: {
date_invoiced: { _is_null: true }
ro_number: { _is_null: false }
voided: { _eq: false }
_or: [
{ actual_completion: { _gte: $jobStart, _lte: $jobEnd } }
{ actual_delivery: { _gte: $jobStart, _lte: $jobEnd } }
]
}
) {
id
joblines(order_by: { line_no: asc }, where: { removed: { _eq: false } }) {
convertedtolbr
convertedtolbr_data
mod_lb_hrs
mod_lbr_ty
}
}
} }
`; `;

View File

@@ -37,14 +37,22 @@ import JobsCloseLines from "../../components/jobs-close-lines/jobs-close-lines.c
import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component"; import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component";
import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries"; import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
}); });
export function JobsCloseComponent({ job, bodyshop, jobRO }) { const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const client = useApolloClient(); const client = useApolloClient();
@@ -107,9 +115,11 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
notification["success"]({ notification["success"]({
message: t("jobs.successes.closed"), message: t("jobs.successes.closed"),
}); });
insertAuditTrail({
// const navigate = useNavigate(); jobid: job.id,
// navigate(`/manage/jobs/${job.id}`); operation: AuditTrailMapping.jobinvoiced(),
});
// history.push(`/manage/jobs/${job.id}`);
} else { } else {
setLoading(false); setLoading(false);
notification["error"]({ notification["error"]({
@@ -526,4 +536,4 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
</div> </div>
); );
} }
export default connect(mapStateToProps, null)(JobsCloseComponent); export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseComponent);

View File

@@ -103,6 +103,7 @@
"admin_jobmarkforreexport": "ADMIN: Job marked for re-export.", "admin_jobmarkforreexport": "ADMIN: Job marked for re-export.",
"admin_jobuninvoice": "ADMIN: Job has been uninvoiced.", "admin_jobuninvoice": "ADMIN: Job has been uninvoiced.",
"admin_jobunvoid": "ADMIN: Job has been unvoided.", "admin_jobunvoid": "ADMIN: Job has been unvoided.",
"alerttoggle": "Alert Toggle set to {{status}}",
"appointmentcancel": "Appointment canceled. Lost Reason: {{lost_sale_reason}}.", "appointmentcancel": "Appointment canceled. Lost Reason: {{lost_sale_reason}}.",
"appointmentinsert": "Appointment created. Appointment Date: {{start}}.", "appointmentinsert": "Appointment created. Appointment Date: {{start}}.",
"billposted": "Bill with invoice number {{invoice_number}} posted.", "billposted": "Bill with invoice number {{invoice_number}} posted.",
@@ -111,6 +112,7 @@
"jobassignmentchange": "Employee {{name}} assigned to {{operation}}", "jobassignmentchange": "Employee {{name}} assigned to {{operation}}",
"jobassignmentremoved": "Employee assignment removed for {{operation}}", "jobassignmentremoved": "Employee assignment removed for {{operation}}",
"jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.", "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}}.", "jobconverted": "Job converted and assigned number {{ro_number}}.",
"jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.", "jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.",
"jobimported": "Job imported.", "jobimported": "Job imported.",
@@ -203,6 +205,7 @@
"entered_total": "Total of Entered Lines", "entered_total": "Total of Entered Lines",
"enteringcreditmemo": "You are entering a credit memo. Please ensure you are also entering positive values.", "enteringcreditmemo": "You are entering a credit memo. Please ensure you are also entering positive values.",
"federal_tax": "Federal Tax", "federal_tax": "Federal Tax",
"federal_tax_exempt": "Federal Tax Exempt?",
"generatepartslabel": "Generate Parts Labels after Saving?", "generatepartslabel": "Generate Parts Labels after Saving?",
"iouexists": "An IOU exists that is associated to this RO.", "iouexists": "An IOU exists that is associated to this RO.",
"local_tax": "Local Tax", "local_tax": "Local Tax",
@@ -447,6 +450,7 @@
"config": "Shop -> Config", "config": "Shop -> Config",
"dashboard": "Shop -> Dashboard", "dashboard": "Shop -> Dashboard",
"rbac": "Shop -> RBAC", "rbac": "Shop -> RBAC",
"reportcenter": "Shop -> Report Center",
"templates": "Shop -> Templates", "templates": "Shop -> Templates",
"vendors": "Shop -> Vendors" "vendors": "Shop -> Vendors"
}, },
@@ -2044,6 +2048,9 @@
"sentby": "Sent by {{by}} at {{time}}", "sentby": "Sent by {{by}} at {{time}}",
"typeamessage": "Send a message...", "typeamessage": "Send a message...",
"unarchive": "Unarchive" "unarchive": "Unarchive"
},
"render": {
"conversation_list": "Conversation List"
} }
}, },
"notes": { "notes": {
@@ -2695,6 +2702,7 @@
"efficiencyoverperiod": "Efficiency over Selected Dates", "efficiencyoverperiod": "Efficiency over Selected Dates",
"entries": "Scoreboard Entries", "entries": "Scoreboard Entries",
"jobs": "Jobs", "jobs": "Jobs",
"jobscompletednotinvoiced": "Completed Not Invoiced",
"lastmonth": "Last Month", "lastmonth": "Last Month",
"lastweek": "Last Week", "lastweek": "Last Week",
"monthlytarget": "Monthly", "monthlytarget": "Monthly",
@@ -2709,6 +2717,7 @@
"timetickets": "Time Tickets", "timetickets": "Time Tickets",
"timeticketsemployee": "Time Tickets by Employee", "timeticketsemployee": "Time Tickets by Employee",
"todateactual": "Actual (MTD)", "todateactual": "Actual (MTD)",
"totalhrs": "Total Hours",
"totaloverperiod": "Total over Selected Dates", "totaloverperiod": "Total over Selected Dates",
"weeklyactual": "Actual (W)", "weeklyactual": "Actual (W)",
"weeklytarget": "Weekly", "weeklytarget": "Weekly",

View File

@@ -103,6 +103,7 @@
"admin_jobmarkforreexport": "", "admin_jobmarkforreexport": "",
"admin_jobuninvoice": "", "admin_jobuninvoice": "",
"admin_jobunvoid": "", "admin_jobunvoid": "",
"alerttoggle": "",
"appointmentcancel": "", "appointmentcancel": "",
"appointmentinsert": "", "appointmentinsert": "",
"billposted": "", "billposted": "",
@@ -111,6 +112,7 @@
"jobassignmentchange": "", "jobassignmentchange": "",
"jobassignmentremoved": "", "jobassignmentremoved": "",
"jobchecklist": "", "jobchecklist": "",
"jobinvoiced": "",
"jobconverted": "", "jobconverted": "",
"jobfieldchanged": "", "jobfieldchanged": "",
"jobimported": "", "jobimported": "",
@@ -203,6 +205,7 @@
"entered_total": "", "entered_total": "",
"enteringcreditmemo": "", "enteringcreditmemo": "",
"federal_tax": "", "federal_tax": "",
"federal_tax_exempt": "",
"generatepartslabel": "", "generatepartslabel": "",
"iouexists": "", "iouexists": "",
"local_tax": "", "local_tax": "",
@@ -447,6 +450,7 @@
"config": "", "config": "",
"dashboard": "", "dashboard": "",
"rbac": "", "rbac": "",
"reportcenter": "",
"templates": "", "templates": "",
"vendors": "" "vendors": ""
}, },
@@ -2044,6 +2048,9 @@
"sentby": "", "sentby": "",
"typeamessage": "Enviar un mensaje...", "typeamessage": "Enviar un mensaje...",
"unarchive": "" "unarchive": ""
},
"render": {
"conversation_list": ""
} }
}, },
"notes": { "notes": {
@@ -2695,6 +2702,7 @@
"efficiencyoverperiod": "", "efficiencyoverperiod": "",
"entries": "", "entries": "",
"jobs": "", "jobs": "",
"jobscompletednotinvoiced": "",
"lastmonth": "", "lastmonth": "",
"lastweek": "", "lastweek": "",
"monthlytarget": "", "monthlytarget": "",
@@ -2709,6 +2717,7 @@
"timetickets": "", "timetickets": "",
"timeticketsemployee": "", "timeticketsemployee": "",
"todateactual": "", "todateactual": "",
"totalhrs": "",
"totaloverperiod": "", "totaloverperiod": "",
"weeklyactual": "", "weeklyactual": "",
"weeklytarget": "", "weeklytarget": "",

View File

@@ -103,6 +103,7 @@
"admin_jobmarkforreexport": "", "admin_jobmarkforreexport": "",
"admin_jobuninvoice": "", "admin_jobuninvoice": "",
"admin_jobunvoid": "", "admin_jobunvoid": "",
"alerttoggle": "",
"appointmentcancel": "", "appointmentcancel": "",
"appointmentinsert": "", "appointmentinsert": "",
"billposted": "", "billposted": "",
@@ -111,6 +112,7 @@
"jobassignmentchange": "", "jobassignmentchange": "",
"jobassignmentremoved": "", "jobassignmentremoved": "",
"jobchecklist": "", "jobchecklist": "",
"jobinvoiced": "",
"jobconverted": "", "jobconverted": "",
"jobfieldchanged": "", "jobfieldchanged": "",
"jobimported": "", "jobimported": "",
@@ -203,6 +205,7 @@
"entered_total": "", "entered_total": "",
"enteringcreditmemo": "", "enteringcreditmemo": "",
"federal_tax": "", "federal_tax": "",
"federal_tax_exempt": "",
"generatepartslabel": "", "generatepartslabel": "",
"iouexists": "", "iouexists": "",
"local_tax": "", "local_tax": "",
@@ -447,6 +450,7 @@
"config": "", "config": "",
"dashboard": "", "dashboard": "",
"rbac": "", "rbac": "",
"reportcenter": "",
"templates": "", "templates": "",
"vendors": "" "vendors": ""
}, },
@@ -2044,6 +2048,9 @@
"sentby": "", "sentby": "",
"typeamessage": "Envoyer un message...", "typeamessage": "Envoyer un message...",
"unarchive": "" "unarchive": ""
},
"render": {
"conversation_list": ""
} }
}, },
"notes": { "notes": {
@@ -2695,6 +2702,7 @@
"efficiencyoverperiod": "", "efficiencyoverperiod": "",
"entries": "", "entries": "",
"jobs": "", "jobs": "",
"jobscompletednotinvoiced": "",
"lastmonth": "", "lastmonth": "",
"lastweek": "", "lastweek": "",
"monthlytarget": "", "monthlytarget": "",
@@ -2709,6 +2717,7 @@
"timetickets": "", "timetickets": "",
"timeticketsemployee": "", "timeticketsemployee": "",
"todateactual": "", "todateactual": "",
"totalhrs": "",
"totaloverperiod": "", "totaloverperiod": "",
"weeklyactual": "", "weeklyactual": "",
"weeklytarget": "", "weeklytarget": "",

View File

@@ -1,6 +1,7 @@
import i18n from "i18next"; import i18n from "i18next";
const AuditTrailMapping = { const AuditTrailMapping = {
alertToggle: (status) => i18n.t("audit_trail.messages.alerttoggle", { status }),
appointmentcancel: (lost_sale_reason) => appointmentcancel: (lost_sale_reason) =>
i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }), i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }),
appointmentinsert: (start) => appointmentinsert: (start) =>
@@ -11,6 +12,8 @@ const AuditTrailMapping = {
"ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", { status }), "ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", { status }),
jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"), jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"),
jobimported: () => i18n.t("audit_trail.messages.jobimported"), jobimported: () => i18n.t("audit_trail.messages.jobimported"),
jobinvoiced: () =>
i18n.t("audit_trail.messages.jobinvoiced"),
jobconverted: (ro_number) => jobconverted: (ro_number) =>
i18n.t("audit_trail.messages.jobconverted", { ro_number }), i18n.t("audit_trail.messages.jobconverted", { ro_number }),
jobfieldchange: (field, value) => jobfieldchange: (field, value) =>

View File

@@ -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" ...(!type || type === "vendor"
? { ? {
purchases_by_vendor_detailed: { purchases_by_vendor_detailed: {

View File

@@ -50,7 +50,7 @@ exports.createUser = async (req, res) => {
`, `,
{ {
user: { user: {
email, email: email.toLowerCase(),
authid: userRecord.uid, authid: userRecord.uid,
associations: { associations: {
data: [{ shopid, authlevel, active: true }], data: [{ shopid, authlevel, active: true }],