Merge branch 'release/2023-05-19' into feature/payroll

This commit is contained in:
Patrick Fic
2023-05-17 07:51:23 -07:00
115 changed files with 3826 additions and 1193 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project be_version="2.7.1" version="1.2"> <babeledit_project version="1.2" be_version="2.7.1">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -9152,6 +9152,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>use_paint_scale_data</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>uselocalmediaserver</name> <name>uselocalmediaserver</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -34001,6 +34022,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>savetojobnotes</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>

View File

@@ -1,3 +1,4 @@
GENERATE_SOURCEMAP=false
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
REACT_APP_GA_CODE=231103507 REACT_APP_GA_CODE=231103507

View File

@@ -143,13 +143,16 @@
} }
} }
//Update row highlighting on production board.
//Update row highlighting on production board.
.ant-table-tbody > tr.ant-table-row:hover > td { .ant-table-tbody > tr.ant-table-row:hover > td {
background: #eaeaea !important; background: #eaeaea !important;
} }
.job-line-manual{ .job-line-manual {
color: tomato; color: tomato;
font-style: italic; font-style: italic;
} }
td.ant-table-column-sort {
background-color: transparent;
}

View File

@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
import { DELETE_BILL } from "../../graphql/bills.queries"; import { DELETE_BILL } from "../../graphql/bills.queries";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
export default function BillDeleteButton({ bill }) { export default function BillDeleteButton({ bill, callback }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const [deleteBill] = useMutation(DELETE_BILL); const [deleteBill] = useMutation(DELETE_BILL);
@@ -36,6 +36,8 @@ export default function BillDeleteButton({ bill }) {
if (!!!result.errors) { if (!!!result.errors) {
notification["success"]({ message: t("bills.successes.deleted") }); notification["success"]({ message: t("bills.successes.deleted") });
if (callback && typeof callback === "function") callback(bill.id);
} else { } else {
//Check if it's an fkey violation. //Check if it's an fkey violation.
const error = JSON.stringify(result.errors); const error = JSON.stringify(result.errors);

View File

@@ -7,7 +7,9 @@ import { createStructuredSelector } from "reselect";
import { selectBreadcrumbs } from "../../redux/application/application.selectors"; import { selectBreadcrumbs } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import GlobalSearch from "../global-search/global-search.component"; import GlobalSearch from "../global-search/global-search.component";
import GlobalSearchOs from "../global-search/global-search-os.component";
import "./breadcrumbs.styles.scss"; import "./breadcrumbs.styles.scss";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
breadcrumbs: selectBreadcrumbs, breadcrumbs: selectBreadcrumbs,
@@ -15,6 +17,12 @@ const mapStateToProps = createStructuredSelector({
}); });
export function BreadCrumbs({ breadcrumbs, bodyshop }) { export function BreadCrumbs({ breadcrumbs, bodyshop }) {
const { OpenSearch } = useTreatments(
["OpenSearch"],
{},
bodyshop && bodyshop.imexshopid
);
return ( return (
<Row className="breadcrumb-container"> <Row className="breadcrumb-container">
<Col xs={24} sm={24} md={16}> <Col xs={24} sm={24} md={16}>
@@ -38,7 +46,7 @@ export function BreadCrumbs({ breadcrumbs, bodyshop }) {
</Breadcrumb> </Breadcrumb>
</Col> </Col>
<Col xs={24} sm={24} md={8}> <Col xs={24} sm={24} md={8}>
<GlobalSearch /> {OpenSearch.treatment === "on" ? <GlobalSearchOs /> : <GlobalSearch />}
</Col> </Col>
</Row> </Row>
); );

View File

@@ -10,7 +10,10 @@ export default function CABCpvrtCalculator({ disabled, form }) {
const handleFinish = async (values) => { const handleFinish = async (values) => {
logImEXEvent("job_ca_bc_pvrt_calculate"); logImEXEvent("job_ca_bc_pvrt_calculate");
form.setFieldsValue({ ca_bc_pvrt: ((values.rate||0) * (values.days||0)).toFixed(2) }); form.setFieldsValue({
ca_bc_pvrt: ((values.rate || 0) * (values.days || 0)).toFixed(2),
});
form.setFields([{ name: "ca_bc_pvrt", touched: true }]);
setVisibility(false); setVisibility(false);
}; };

View File

@@ -0,0 +1,216 @@
import { AutoComplete, Divider, Input, Space } from "antd";
import axios from "axios";
import _ from "lodash";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useHistory } from "react-router-dom";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearchOs() {
const { t } = useTranslation();
const history = useHistory();
const [loading, setLoading] = useState(false);
const [data, setData] = useState(false);
const executeSearch = async (v) => {
if (v && v && v !== "" && v.length >= 3) {
try {
setLoading(true);
const searchData = await axios.post("/search", {
search: v,
});
const resultsByType = {
payments: [],
jobs: [],
bills: [],
owners: [],
vehicles: [],
};
searchData.data.hits.hits.forEach((hit) => {
resultsByType[hit._index].push(hit._source);
});
setData([
{
label: renderTitle(t("menus.header.search.jobs")),
options: resultsByType.jobs.map((job) => {
return {
key: job.id,
value: job.ro_number || "N/A",
label: (
<Link to={`/manage/jobs/${job.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<strong>{job.ro_number || t("general.labels.na")}</strong>
<span>{`${job.status || ""}`}</span>
<span>
<OwnerNameDisplay ownerObject={job} />
</span>
<span>{`${job.v_model_yr || ""} ${
job.v_make_desc || ""
} ${job.v_model_desc || ""}`}</span>
<span>{`${job.clm_no || ""}`}</span>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.owners")),
options: resultsByType.owners.map((owner) => {
return {
key: owner.id,
value: OwnerNameDisplayFunction(owner),
label: (
<Link to={`/manage/owners/${owner.id}`}>
<Space
size="small"
split={<Divider type="vertical" />}
wrap
>
<span>
<OwnerNameDisplay ownerObject={owner} />
</span>
<PhoneNumberFormatter>
{owner.ownr_ph1}
</PhoneNumberFormatter>
<PhoneNumberFormatter>
{owner.ownr_ph2}
</PhoneNumberFormatter>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.vehicles")),
options: resultsByType.vehicles.map((vehicle) => {
return {
key: vehicle.id,
value: `${vehicle.v_model_yr || ""} ${
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`,
label: (
<Link to={`/manage/vehicles/${vehicle.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<span>
{`${vehicle.v_model_yr || ""} ${
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`}
</span>
<span>{vehicle.plate_no || ""}</span>
<span>
<VehicleVinDisplay>
{vehicle.v_vin || ""}
</VehicleVinDisplay>
</span>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.payments")),
options: resultsByType.payments.map((payment) => {
return {
key: payment.id,
value: `${payment.job?.ro_number} ${payment.amount}`,
label: (
<Link to={`/manage/jobs/${payment.job?.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<span>{payment.paymentnum}</span>
<span>{payment.job?.ro_number}</span>
<span>{payment.memo || ""}</span>
<span>{payment.amount || ""}</span>
<span>{payment.transactionid || ""}</span>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.bills")),
options: resultsByType.bills.map((bill) => {
return {
key: bill.id,
value: `${bill.invoice_number} - ${bill.vendor.name}`,
label: (
<Link to={`/manage/bills?billid=${bill.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<span>{bill.invoice_number}</span>
<span>{bill.vendor.name}</span>
<span>{bill.date}</span>
</Space>
</Link>
),
};
}),
},
// {
// label: renderTitle(t("menus.header.search.phonebook")),
// options: resultsByType.search_phonebook.map((pb) => {
// return {
// key: pb.id,
// value: `${pb.firstname || ""} ${pb.lastname || ""} ${
// pb.company || ""
// }`,
// label: (
// <Link to={`/manage/phonebook?phonebookentry=${pb.id}`}>
// <Space size="small" split={<Divider type="vertical" />}>
// <span>{`${pb.firstname || ""} ${pb.lastname || ""} ${
// pb.company || ""
// }`}</span>
// <PhoneNumberFormatter>{pb.phone1}</PhoneNumberFormatter>
// <span>{pb.email}</span>
// </Space>
// </Link>
// ),
// };
// }),
// },
]);
} catch (error) {
console.log("Error while fetching search results", error);
} finally {
setLoading(false);
}
}
};
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
const handleSearch = (value) => {
debouncedExecuteSearch(value);
};
const renderTitle = (title) => {
return <span>{title}</span>;
};
return (
<AutoComplete
options={data}
onSearch={handleSearch}
defaultActiveFirstOption
onSelect={(val, opt) => {
history.push(opt.label.props.to);
}}
onClear={() => setData([])}
>
<Input.Search
size="large"
placeholder={t("general.labels.globalsearch")}
enterButton
allowClear
loading={loading}
/>
</AutoComplete>
);
}

View File

@@ -8,7 +8,7 @@ import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import OwnerNameDisplay, { import OwnerNameDisplay, {
OwnerNameDisplayFunction OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component"; } from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearch() { export default function GlobalSearch() {
@@ -18,11 +18,18 @@ export default function GlobalSearch() {
useLazyQuery(GLOBAL_SEARCH_QUERY); useLazyQuery(GLOBAL_SEARCH_QUERY);
const executeSearch = (v) => { const executeSearch = (v) => {
if (v && v.variables.search && v.variables.search !== "") callSearch(v); if (
v &&
v.variables.search &&
v.variables.search !== "" &&
v.variables.search.length >= 3
)
callSearch(v);
}; };
const debouncedExecuteSearch = _.debounce(executeSearch, 750); const debouncedExecuteSearch = _.debounce(executeSearch, 750);
const handleSearch = (value) => { const handleSearch = (value) => {
console.log("Handle Search");
debouncedExecuteSearch({ variables: { search: value } }); debouncedExecuteSearch({ variables: { search: value } });
}; };
@@ -37,7 +44,7 @@ export default function GlobalSearch() {
options: data.search_jobs.map((job) => { options: data.search_jobs.map((job) => {
return { return {
key: job.id, key: job.id,
value: job.ro_number, value: job.ro_number || "N/A",
label: ( label: (
<Link to={`/manage/jobs/${job.id}`}> <Link to={`/manage/jobs/${job.id}`}>
<Space size="small" split={<Divider type="vertical" />}> <Space size="small" split={<Divider type="vertical" />}>

View File

@@ -109,8 +109,8 @@ export function JobsConvertButton({
]} ]}
> >
<Select> <Select>
{bodyshop.md_ins_cos.map((s) => ( {bodyshop.md_ins_cos.map((s, i) => (
<Select.Option key={s.name} value={s.name}> <Select.Option key={i} value={s.name}>
{s.name} {s.name}
</Select.Option> </Select.Option>
))} ))}

View File

@@ -82,7 +82,7 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
> >
<CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} /> <CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} />
</Form.Item> </Form.Item>
<Space align="end"> <Space align="center">
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt"> <Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
<CurrencyInput disabled={jobRO} min={0} /> <CurrencyInput disabled={jobRO} min={0} />
</Form.Item> </Form.Item>

View File

@@ -52,11 +52,15 @@ function JobDocumentsLocalGalleryExternal({
val.type.mime && val.type.mime &&
val.type.mime.startsWith("image") val.type.mime.startsWith("image")
) { ) {
acc.push(val); acc.push({ ...val, src: val.thumbnail });
} }
return acc; return acc;
}, []) }, [])
: []; : [];
console.log(
"🚀 ~ file: jobs-documents-local-gallery.external.component.jsx:48 ~ useEffect ~ documents:",
documents
);
setgalleryImages(documents); setgalleryImages(documents);
}, [allMedia, jobId, setgalleryImages, t]); }, [allMedia, jobId, setgalleryImages, t]);

View File

@@ -1,8 +1,9 @@
import { SyncOutlined } from "@ant-design/icons"; import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Input, Space, Table, Typography } from "antd"; import { Button, Card, Input, Space, Table, Typography } from "antd";
import axios from "axios";
import _ from "lodash"; import _ from "lodash";
import queryString from "query-string"; import queryString from "query-string";
import React from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, useHistory, useLocation } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
@@ -21,6 +22,8 @@ const mapDispatchToProps = (dispatch) => ({
export function JobsList({ bodyshop, refetch, loading, jobs, total }) { export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const [openSearchResults, setOpenSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false);
const { page, sortcolumn, sortorder } = search; const { page, sortcolumn, sortorder } = search;
const history = useHistory(); const history = useHistory();
@@ -193,6 +196,28 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
history.push({ search: queryString.stringify(search) }); history.push({ search: queryString.stringify(search) });
}; };
useEffect(() => {
if (search.search && search.search.trim() !== "") {
searchJobs();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function searchJobs(value) {
try {
setSearchLoading(true);
const searchData = await axios.post("/search", {
search: value || search.search,
index: "jobs",
});
setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source));
} catch (error) {
console.log("Error while fetching search results", error);
} finally {
setSearchLoading(false);
}
}
return ( return (
<Card <Card
extra={ extra={
@@ -205,6 +230,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
<Button <Button
onClick={() => { onClick={() => {
delete search.search; delete search.search;
delete search.page;
history.push({ search: queryString.stringify(search) }); history.push({ search: queryString.stringify(search) });
}} }}
> >
@@ -220,24 +246,32 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
onSearch={(value) => { onSearch={(value) => {
search.search = value; search.search = value;
history.push({ search: queryString.stringify(search) }); history.push({ search: queryString.stringify(search) });
searchJobs(value);
}} }}
loading={loading || searchLoading}
enterButton enterButton
/> />
</Space> </Space>
} }
> >
<Table <Table
loading={loading} loading={loading || searchLoading}
pagination={{ pagination={
position: "top", search?.search
pageSize: 25, ? {
current: parseInt(page || 1), pageSize: 25,
total: total, showSizeChanger: false,
showSizeChanger: false, }
}} : {
pageSize: 25,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,
}
}
columns={columns} columns={columns}
rowKey="id" rowKey="id"
dataSource={jobs} dataSource={search?.search ? openSearchResults : jobs}
onChange={handleTableChange} onChange={handleTableChange}
/> />
</Card> </Card>

View File

@@ -34,7 +34,7 @@ export function NoteUpsertModalContainer({
const [updateNote] = useMutation(UPDATE_NOTE); const [updateNote] = useMutation(UPDATE_NOTE);
const { visible, context, actions } = noteUpsertModal; const { visible, context, actions } = noteUpsertModal;
const { jobId, existingNote } = context; const { jobId, existingNote, text } = context;
const { refetch } = actions; const { refetch } = actions;
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -45,8 +45,12 @@ export function NoteUpsertModalContainer({
form.setFieldsValue(existingNote); form.setFieldsValue(existingNote);
} else if (!existingNote && visible) { } else if (!existingNote && visible) {
form.resetFields(); form.resetFields();
if (text) {
form.setFieldValue("text", text);
}
} }
}, [existingNote, form, visible]); }, [existingNote, form, visible, text]);
const handleFinish = async (formValues) => { const handleFinish = async (formValues) => {
const { relatedros, ...values } = formValues; const { relatedros, ...values } = formValues;
@@ -82,6 +86,7 @@ export function NoteUpsertModalContainer({
{ ...values, jobid: jobId, created_by: currentUser.email }, { ...values, jobid: jobId, created_by: currentUser.email },
], ],
}, },
refetchQueries: ["QUERY_NOTES_BY_JOB_PK"],
}); });
if (AdditionalNoteInserts.length > 0) { if (AdditionalNoteInserts.length > 0) {

View File

@@ -201,6 +201,7 @@ export function PartsOrderListTableComponent({
subject: record.return subject: record.return
? Templates.parts_return_slip.subject ? Templates.parts_return_slip.subject
: Templates.parts_order.subject, : Templates.parts_order.subject,
to: record.vendor.email,
}} }}
id={job.id} id={job.id}
/> />
@@ -296,7 +297,6 @@ export function PartsOrderListTableComponent({
sortOrder: sortOrder:
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order, state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
}, },
{ {
title: t("parts_orders.fields.act_price"), title: t("parts_orders.fields.act_price"),
dataIndex: "act_price", dataIndex: "act_price",

View File

@@ -52,7 +52,7 @@ function PaymentModalContainer({
const { useStripe, sendby, ...paymentObj } = values; const { useStripe, sendby, ...paymentObj } = values;
setLoading(true); setLoading(true);
let updatedPayment; //Moved up from if statement for greater scope.
try { try {
if (!context || (context && !context.id)) { if (!context || (context && !context.id)) {
const newPayment = await insertPayment({ const newPayment = await insertPayment({
@@ -87,7 +87,7 @@ function PaymentModalContainer({
); );
} }
} else { } else {
const updatedPayment = await updatePayment({ updatedPayment = await updatePayment({
variables: { variables: {
paymentId: context.id, paymentId: context.id,
payment: paymentObj, payment: paymentObj,
@@ -101,7 +101,11 @@ function PaymentModalContainer({
} }
} }
if (actions.refetch) actions.refetch(); if (actions.refetch)
actions.refetch(
updatedPayment && updatedPayment.data.update_payments.returning[0]
);
if (enterAgain) { if (enterAgain) {
const prev = form.getFieldsValue(["date"]); const prev = form.getFieldsValue(["date"]);

View File

@@ -1,20 +1,23 @@
import { EditFilled, SyncOutlined } from "@ant-design/icons"; import { EditFilled, SyncOutlined } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client";
import { Button, Card, Input, Space, Table, Typography } from "antd"; import { Button, Card, Input, Space, Table, Typography } from "antd";
import axios from "axios";
import queryString from "query-string"; import queryString from "query-string";
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, useHistory, useLocation } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { QUERY_PAYMENT_BY_ID } from "../../graphql/payments.queries";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter"; import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters";
import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container"; import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -39,7 +42,10 @@ export function PaymentsListPaginated({
bodyshop, bodyshop,
}) { }) {
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const [openSearchResults, setOpenSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false);
const { page, sortcolumn, sortorder } = search; const { page, sortcolumn, sortorder } = search;
const client = useApolloClient();
const history = useHistory(); const history = useHistory();
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
@@ -52,13 +58,17 @@ export function PaymentsListPaginated({
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
dataIndex: "ro_number", dataIndex: "ro_number",
key: "ro_number", key: "ro_number",
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), // sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
sortOrder: sortcolumn === "ro_number" && sortorder, // sortOrder: sortcolumn === "ro_number" && sortorder,
render: (text, record) => ( render: (text, record) => {
<Link to={"/manage/jobs/" + record.job.id}> return record.job ? (
{record.job.ro_number || t("general.labels.na")} <Link to={"/manage/jobs/" + record.job.id}>
</Link> {record.job.ro_number || t("general.labels.na")}
), </Link>
) : (
<span>{t("general.labels.na")}</span>
);
},
}, },
{ {
title: t("payments.fields.paymentnum"), title: t("payments.fields.paymentnum"),
@@ -72,16 +82,16 @@ export function PaymentsListPaginated({
dataIndex: "owner", dataIndex: "owner",
key: "owner", key: "owner",
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln), // sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln),
sortOrder: sortcolumn === "owner" && sortorder, // sortOrder: sortcolumn === "owner" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.job.owner ? ( return record.job?.owner ? (
<Link to={"/manage/owners/" + record.job.owner.id}> <Link to={"/manage/owners/" + record.job?.owner?.id}>
<OwnerNameDisplay ownerObject={record} /> <OwnerNameDisplay ownerObject={record.job} />
</Link> </Link>
) : ( ) : (
<span> <span>
<OwnerNameDisplay ownerObject={record} /> <OwnerNameDisplay ownerObject={record.job} />
</span> </span>
); );
}, },
@@ -147,10 +157,33 @@ export function PaymentsListPaginated({
<Space> <Space>
<Button <Button
disabled={record.exportedat} disabled={record.exportedat}
onClick={() => { onClick={async () => {
let apolloResults;
if (search.search) {
const { data } = await client.query({
query: QUERY_PAYMENT_BY_ID,
variables: {
paymentId: record.id,
},
});
apolloResults = data.payments_by_pk;
}
setPaymentContext({ setPaymentContext({
actions: { refetch: refetch }, actions: {
context: record, refetch: apolloResults
? (updatedRecord) => {
setOpenSearchResults((results) =>
results.map((result) => {
if (result.id !== record.id) {
return result;
}
return updatedRecord;
})
);
}
: refetch,
},
context: apolloResults ? apolloResults : record,
}); });
}} }}
> >
@@ -177,6 +210,28 @@ export function PaymentsListPaginated({
history.push({ search: queryString.stringify(search) }); history.push({ search: queryString.stringify(search) });
}; };
useEffect(() => {
if (search.search && search.search.trim() !== "") {
searchPayments();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function searchPayments(value) {
try {
setSearchLoading(true);
const searchData = await axios.post("/search", {
search: value || search.search,
index: "payments",
});
setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source));
} catch (error) {
console.log("Error while fetching search results", error);
} finally {
setSearchLoading(false);
}
}
return ( return (
<Card <Card
extra={ extra={
@@ -189,6 +244,7 @@ export function PaymentsListPaginated({
<Button <Button
onClick={() => { onClick={() => {
delete search.search; delete search.search;
delete search.page;
history.push({ search: queryString.stringify(search) }); history.push({ search: queryString.stringify(search) });
}} }}
> >
@@ -212,24 +268,33 @@ export function PaymentsListPaginated({
onSearch={(value) => { onSearch={(value) => {
search.search = value; search.search = value;
history.push({ search: queryString.stringify(search) }); history.push({ search: queryString.stringify(search) });
searchPayments(value);
}} }}
loading={loading || searchLoading}
enterButton enterButton
/> />
</Space> </Space>
} }
> >
<Table <Table
loading={loading} loading={loading || searchLoading}
scroll={{ x: true }} scroll={{ x: true }}
pagination={{ pagination={
position: "top", search?.search
pageSize: 25, ? {
current: parseInt(page || 1), pageSize: 25,
total: total, showSizeChanger: false,
}} }
: {
pageSize: 25,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,
}
}
columns={columns} columns={columns}
rowKey="id" rowKey="id"
dataSource={payments} dataSource={search?.search ? openSearchResults : payments}
onChange={handleTableChange} onChange={handleTableChange}
/> />
</Card> </Card>

View File

@@ -29,7 +29,10 @@ export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
}) })
.filter( .filter(
(temp) => (temp) =>
!temp.regions || (temp.regions && temp.regions[bodyshop.region_config]) !temp.regions ||
(temp.regions && temp.regions[bodyshop.region_config]) ||
(temp.regions &&
bodyshop.region_config.includes(Object.keys(temp.regions)) === true)
); );
const filteredJobsReportsList = const filteredJobsReportsList =

View File

@@ -0,0 +1,51 @@
import { Col, List, Space, Typography } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
const CardColorLegend = ({ bodyshop }) => {
const { t } = useTranslation();
const data = bodyshop.ssbuckets.map((bucket) => {
let color = { r: 255, g: 255, b: 255 };
if (bucket.color) {
color = bucket.color;
if (bucket.color.rgb) {
color = bucket.color.rgb;
}
}
return {
label: bucket.label,
color,
};
});
return (
<Col>
<Typography>{t("production.labels.legend")}</Typography>
<List
grid={{
gutter: 16,
}}
dataSource={data}
renderItem={(item) => (
<List.Item>
<Space>
<div
style={{
width: "1.5rem",
aspectRatio: "1/1",
backgroundColor: `rgba(${item.color.r},${item.color.g},${item.color.b},${item.color.a})`,
}}
></div>
<div>{item.label}</div>
</Space>
</List.Item>
)}
/>
</Col>
);
};
export default CardColorLegend;

View File

@@ -18,6 +18,31 @@ import moment from "moment";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component"; import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
const cardColor = (ssbuckets, totalHrs) => {
const bucket = ssbuckets.filter(
(bucket) =>
bucket.gte <= totalHrs && (!!bucket.lt ? bucket.lt > totalHrs : true)
)[0];
let color = { r: 255, g: 255, b: 255 };
if (bucket && bucket.color) {
color = bucket.color;
if (bucket.color.rgb) {
color = bucket.color.rgb;
}
}
return color;
};
function getContrastYIQ(bgColor) {
const yiq = (bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000;
return yiq >= 128 ? "black" : "white";
}
export default function ProductionBoardCard( export default function ProductionBoardCard(
technician, technician,
card, card,
@@ -54,10 +79,22 @@ export default function ProductionBoardCard(
.isSame(moment(card.scheduled_completion), "day") && .isSame(moment(card.scheduled_completion), "day") &&
"production-completion-soon")); "production-completion-soon"));
const totalHrs =
card.labhrs.aggregate.sum.mod_lb_hrs + card.larhrs.aggregate.sum.mod_lb_hrs;
const bgColor = cardColor(bodyshop.ssbuckets, totalHrs);
return ( return (
<Card <Card
className="react-kanban-card imex-kanban-card" className="react-kanban-card imex-kanban-card"
size="small" size="small"
style={{
backgroundColor:
cardSettings &&
cardSettings.cardcolor &&
`rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`,
color:
cardSettings && cardSettings.cardcolor && getContrastYIQ(bgColor),
}}
title={ title={
<Space> <Space>
<ProductionAlert record={card} key="alert" /> <ProductionAlert record={card} key="alert" />

View File

@@ -104,6 +104,13 @@ export default function ProductionBoardKanbanCardSettings({
> >
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item
valuePropName="checked"
label={t("production.labels.cardcolor")}
name="cardcolor"
>
<Switch />
</Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item <Form.Item
@@ -166,7 +173,7 @@ export default function ProductionBoardKanbanCardSettings({
</div> </div>
); );
return ( return (
<Popover content={overlay} visible={visible}> <Popover content={overlay} visible={visible} placement="topRight">
<Button loading={loading} onClick={() => setVisible(true)}> <Button loading={loading} onClick={() => setVisible(true)}>
{t("production.labels.cardsettings")} {t("production.labels.cardsettings")}
</Button> </Button>

View File

@@ -22,6 +22,7 @@ import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-se
//import "@asseinfo/react-kanban/dist/styles.css"; //import "@asseinfo/react-kanban/dist/styles.css";
import "./production-board-kanban.styles.scss"; import "./production-board-kanban.styles.scss";
import { createBoardData } from "./production-board-kanban.utils.js"; import { createBoardData } from "./production-board-kanban.utils.js";
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician, technician: selectTechnician,
@@ -221,6 +222,7 @@ export function ProductionBoardKanbanComponent({
employeeassignments: true, employeeassignments: true,
scheduled_completion: true, scheduled_completion: true,
stickyheader: false, stickyheader: false,
cardcolor: false,
}; };
return ( return (
@@ -256,6 +258,11 @@ export function ProductionBoardKanbanComponent({
</Space> </Space>
} }
/> />
{cardSettings.cardcolor && (
<CardColorLegend cardSettings={cardSettings} bodyshop={bodyshop} />
)}
<ProductionListDetailComponent jobs={data} /> <ProductionListDetailComponent jobs={data} />
<StickyContainer> <StickyContainer>
<Board <Board

View File

@@ -1,12 +1,23 @@
import Icon from "@ant-design/icons"; import Icon from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Input, Popover } from "antd"; import { Button, Input, Popover, Space } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FaRegStickyNote } from "react-icons/fa"; import { FaRegStickyNote } from "react-icons/fa";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
export default function ProductionListColumnProductionNote({ record }) { import { setModalContext } from "../../redux/modals/modals.actions";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setNoteUpsertContext: (context) =>
dispatch(setModalContext({ context: context, modal: "noteUpsert" })),
});
function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [note, setNote] = useState( const [note, setNote] = useState(
@@ -60,12 +71,26 @@ export default function ProductionListColumnProductionNote({ record }) {
// onPressEnter={handleSaveNote} // onPressEnter={handleSaveNote}
autoFocus autoFocus
allowClear allowClear
style={{ marginBottom: "1em" }}
/> />
<div> <Space>
<Button onClick={handleSaveNote}> <Button onClick={handleSaveNote} type="primary">
{t("general.actions.save")} {t("general.actions.save")}
</Button> </Button>
</div> <Button
onClick={() => {
setVisible(false);
setNoteUpsertContext({
context: {
jobId: record.id,
text: note,
},
});
}}
>
{t("notes.actions.savetojobnotes")}
</Button>
</Space>
</div> </div>
} }
trigger={["click"]} trigger={["click"]}
@@ -85,3 +110,8 @@ export default function ProductionListColumnProductionNote({ record }) {
</Popover> </Popover>
); );
} }
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListColumnProductionNote);

View File

@@ -0,0 +1,39 @@
import Dinero from "dinero.js";
const CustomTooltip = ({ active, payload, label }) => {
if (active && payload && payload.length) {
return (
<div
style={{
backgroundColor: "white",
border: "1px solid gray",
padding: "0.5rem",
}}
>
<p style={{ margin: "0" }}>{label}</p>
{payload.map((data, index) => {
if (data.dataKey === "sales" || data.dataKey === "accSales")
return (
<p
style={{ margin: "10px 0", color: data.color }}
key={index}
>{`${data.name} : ${Dinero({
amount: Math.round(data.value * 100),
}).toFormat()}`}</p>
);
return (
<p
style={{ margin: "10px 0", color: data.color }}
key={index}
>{`${data.name} : ${data.value}`}</p>
);
})}
</div>
);
}
return null;
};
export default CustomTooltip;

View File

@@ -1,4 +1,6 @@
import { Card } from "antd"; import { Card } from "antd";
import Dinero from "dinero.js";
import _ from "lodash";
import moment from "moment"; import moment from "moment";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -17,7 +19,7 @@ import {
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util"; import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
import _ from "lodash"; import CustomTooltip from "./chart-custom-tooltip";
const graphProps = { const graphProps = {
strokeWidth: 3, strokeWidth: 3,
@@ -44,14 +46,19 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
return { return {
bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs, bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs,
painthrs: dayAcc.painthrs + dayVal.painthrs, painthrs: dayAcc.painthrs + dayVal.painthrs,
sales:
dayAcc.painthrs +
dayVal.job.job_totals.totals.subtotal.amount / 100 +
2500,
}; };
}, },
{ bodyhrs: 0, painthrs: 0 } { bodyhrs: 0, painthrs: 0, sales: 0 }
); );
} else { } else {
dayhrs = { dayhrs = {
bodyhrs: 0, bodyhrs: 0,
painthrs: 0, painthrs: 0,
sales: 0,
}; };
} }
@@ -64,7 +71,9 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
bodyshop.scoreboard_target.dailyBodyTarget + bodyshop.scoreboard_target.dailyBodyTarget +
bodyshop.scoreboard_target.dailyPaintTarget, bodyshop.scoreboard_target.dailyPaintTarget,
val val
), ) +
bodyshop.scoreboard_target.dailyBodyTarget +
bodyshop.scoreboard_target.dailyPaintTarget,
1 1
), ),
accHrs: _.round( accHrs: _.round(
@@ -73,6 +82,13 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
: dayhrs.painthrs + dayhrs.bodyhrs, : dayhrs.painthrs + dayhrs.bodyhrs,
1 1
), ),
sales: _.round(dayhrs.sales, 2),
accSales: _.round(
acc.length > 0
? acc[acc.length - 1].accSales + dayhrs.sales
: dayhrs.sales,
2
),
}; };
return [...acc, theValue]; return [...acc, theValue];
@@ -87,22 +103,27 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
> >
<CartesianGrid stroke="#f5f5f5" /> <CartesianGrid stroke="#f5f5f5" />
<XAxis dataKey="date" strokeWidth={graphProps.strokeWidth} /> <XAxis dataKey="date" strokeWidth={graphProps.strokeWidth} />
<YAxis strokeWidth={graphProps.strokeWidth} /> <YAxis
<Tooltip /> strokeWidth={graphProps.strokeWidth}
<Legend /> // allowDataOverflow
<Area dataKey="sales"
type="monotone" yAxisId="right"
name="Accumulated Hours" tickFormatter={(value) =>
dataKey="accHrs" Dinero({ amount: Math.round(value * 100) }).toFormat()
fill="lightgreen" }
stroke="green" orientation="right"
/> />
<YAxis yAxisId="left" strokeWidth={graphProps.strokeWidth} />
<Tooltip content={<CustomTooltip />} />
<Legend />
<Bar <Bar
name="Body Hours" name="Body Hours"
dataKey="bodyHrs" dataKey="bodyHrs"
stackId="day" stackId="day"
barSize={20} barSize={20}
fill="darkblue" fill="darkblue"
yAxisId="left"
/> />
<Bar <Bar
name="Paint Hours" name="Paint Hours"
@@ -110,12 +131,42 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
stackId="day" stackId="day"
barSize={20} barSize={20}
fill="darkred" fill="darkred"
yAxisId="left"
/> />
<Line <Line
name="Target Hours" name="Target Hours"
type="monotone" type="monotone"
dataKey="accTargetHrs" dataKey="accTargetHrs"
stroke="#ff7300" stroke="#ff7300"
yAxisId="left"
strokeWidth={graphProps.strokeWidth}
/>
<Area
type="monotone"
name="MTD Hours"
dataKey="accHrs"
fill="lightblue"
stroke="blue"
yAxisId="left"
/>
{
// <Area
// type="monotone"
// name="MTD Sales"
// dataKey="accSales"
// fill="lightgreen"
// stroke="green"
// yAxisId="right"
// />
}
<Bar
name="Sales"
dataKey="sales"
stackId="day"
barSize={20}
fill="darkgreen"
yAxisId="right"
strokeWidth={graphProps.strokeWidth} strokeWidth={graphProps.strokeWidth}
/> />
</ComposedChart> </ComposedChart>

View File

@@ -241,9 +241,11 @@ export default function ScoreboardTimeTickets() {
); );
ret.totalEffieciencyOverPeriod = ret.totalEffieciencyOverPeriod =
(totalActualAndProductive.totalOverPeriod / totalActualAndProductive.actualTotalOverPeriod
totalActualAndProductive.actualTotalOverPeriod) * ? (totalActualAndProductive.totalOverPeriod /
100; totalActualAndProductive.actualTotalOverPeriod) *
100
: 0;
roundObject(ret); roundObject(ret);
roundObject(totals); roundObject(totals);

View File

@@ -116,7 +116,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
<Col span={12}> <Col span={12}>
<Statistic <Statistic
title={t("scoreboard.labels.efficiencyoverperiod")} title={t("scoreboard.labels.efficiencyoverperiod")}
value={`${data.totalEffieciencyOverPeriod}%`} value={`${data.totalEffieciencyOverPeriod || 0}%`}
/> />
</Col> </Col>
</Row> </Row>

View File

@@ -11,13 +11,13 @@ import {
} from "antd"; } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import PhoneFormItem, { import PhoneFormItem, {
PhoneItemFormatterValidation, PhoneItemFormatterValidation,
} from "../form-items-formatted/phone-form-item.component"; } from "../form-items-formatted/phone-form-item.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import momentTZ from "moment-timezone"; import momentTZ from "moment-timezone";
const timeZonesList = momentTZ.tz.names(); const timeZonesList = momentTZ.tz.names();
@@ -551,6 +551,13 @@ export default function ShopInfoGeneral({ form }) {
> >
<CurrencyInput /> <CurrencyInput />
</Form.Item> </Form.Item>
<Form.Item
name={["use_paint_scale_data"]}
label={t("bodyshop.fields.use_paint_scale_data")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item <Form.Item
name={["attach_pdf_to_email"]} name={["attach_pdf_to_email"]}
label={t("bodyshop.fields.attach_pdf_to_email")} label={t("bodyshop.fields.attach_pdf_to_email")}

View File

@@ -396,7 +396,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
); );
} }
const ColorPicker = ({ value, onChange, style, ...restProps }) => { export const ColorPicker = ({ value, onChange, style, ...restProps }) => {
const handleChange = (color) => { const handleChange = (color) => {
if (onChange) onChange(color.rgb); if (onChange) onChange(color.rgb);
}; };

View File

@@ -15,6 +15,7 @@ import { useTranslation } from "react-i18next";
import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-form-item.component"; import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-form-item.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { ColorPicker } from "./shop-info.rostatus.component";
export default function ShopInfoSchedulingComponent({ form }) { export default function ShopInfoSchedulingComponent({ form }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -277,17 +278,50 @@ export default function ShopInfoSchedulingComponent({ form }) {
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Space wrap>
<DeleteFilled <Space direction="horizontal">
onClick={() => { <Form.Item
remove(field.name); label={
}} <Space>
/> {t("bodyshop.fields.ssbuckets.color")}
<FormListMoveArrows <Button
move={move} size="small"
index={index} onClick={() => {
total={fields.length} form.setFieldValue([
/> "ssbuckets",
field.name,
"color",
]);
form.setFields([
{
name: ["ssbuckets", field.name, "color"],
touched: true,
},
]);
}}
>
Reset
</Button>
</Space>
}
key={`${index}color`}
name={[field.name, "color"]}
>
<ColorPicker />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
</Space> </Space>
</LayoutFormRow> </LayoutFormRow>
</Form.Item> </Form.Item>

View File

@@ -0,0 +1,131 @@
import { useQuery } from "@apollo/client";
import { Card, Col, Space, Statistic, Typography } from "antd";
import moment from "moment";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE } from "../../graphql/timetickets.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
const { Title } = Typography;
const mapStateToProps = createStructuredSelector({
technician: selectTechnician,
});
const mapDispatchToProps = (dispatch) => ({});
const TechJobStatistics = ({ technician }) => {
const { t } = useTranslation();
const startDate = moment().startOf("week");
const endDate = moment().endOf("week");
const { loading, error, data } = useQuery(
QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE,
{
variables: {
start: startDate.format("YYYY-MM-DD"),
end: endDate.format("YYYY-MM-DD"),
fixedStart: moment().startOf("month").format("YYYY-MM-DD"),
fixedEnd: moment().endOf("month").format("YYYY-MM-DD"),
employeeid: technician.id,
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
}
);
const totals = useMemo(() => {
if (data && data.timetickets && data.fixedperiod) {
const week = data.timetickets.reduce(
(acc, val) => {
acc.productivehrs = acc.productivehrs + val.productivehrs;
acc.actualhrs = acc.actualhrs + val.actualhrs;
return acc;
},
{ productivehrs: 0, actualhrs: 0 }
);
const month = data.fixedperiod.reduce(
(acc, val) => {
acc.productivehrs = acc.productivehrs + val.productivehrs;
acc.actualhrs = acc.actualhrs + val.actualhrs;
return acc;
},
{ productivehrs: 0, actualhrs: 0 }
);
return {
week,
month,
};
}
return {
week: { productivehrs: 0, actualhrs: 0 },
month: { productivehrs: 0, actualhrs: 0 },
};
}, [data]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<Card title={t("scoreboard.labels.productivestatistics")}>
<Space size={100}>
<Col>
<Title level={5}>{t("scoreboard.labels.thisweek")}</Title>
<Space size={20}>
<Statistic
title={t("timetickets.fields.productivehrs")}
value={totals.week.productivehrs.toFixed(2)}
/>
<Statistic
title={t("timetickets.fields.actualhrs")}
value={totals.week.actualhrs.toFixed(2)}
/>
<Statistic
title={t("timetickets.labels.efficiency")}
value={
totals.week.actualhrs
? `${(
(totals.week.productivehrs / totals.week.actualhrs) *
100
).toFixed(2)}%`
: "0%"
}
/>
</Space>
</Col>
<Col>
<Title level={5}>{t("scoreboard.labels.thismonth")}</Title>
<Space size={20}>
<Statistic
title={t("timetickets.fields.productivehrs")}
value={totals.month.productivehrs.toFixed(2)}
/>
<Statistic
title={t("timetickets.fields.actualhrs")}
value={totals.month.actualhrs.toFixed(2)}
/>
<Statistic
title={t("timetickets.labels.efficiency")}
value={
totals.month.actualhrs
? `${(
(totals.month.productivehrs / totals.month.actualhrs) *
100
).toFixed(2)}%`
: "0%"
}
/>
</Space>
</Col>
</Space>
</Card>
);
};
export default connect(mapStateToProps, mapDispatchToProps)(TechJobStatistics);

View File

@@ -29,7 +29,17 @@ export function TechSider({ technician, techLogout }) {
}; };
return ( return (
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse}> <Sider
style={{
height: "100vh",
position: "sticky",
top: 0,
left: 0,
}}
collapsible
collapsed={collapsed}
onCollapse={onCollapse}
>
<Menu theme="dark" defaultSelectedKeys={["1"]} mode="inline"> <Menu theme="dark" defaultSelectedKeys={["1"]} mode="inline">
<Menu.Item <Menu.Item
key="1" key="1"

View File

@@ -20,13 +20,11 @@ export const DELETE_BILL = gql`
export const QUERY_ALL_BILLS_PAGINATED = gql` export const QUERY_ALL_BILLS_PAGINATED = gql`
query QUERY_ALL_BILLS_PAGINATED( query QUERY_ALL_BILLS_PAGINATED(
$search: String
$offset: Int $offset: Int
$limit: Int $limit: Int
$order: [bills_order_by!]! $order: [bills_order_by!]!
) { ) {
search_bills( bills(
args: { search: $search }
offset: $offset offset: $offset
limit: $limit limit: $limit
order_by: $order order_by: $order
@@ -51,7 +49,7 @@ export const QUERY_ALL_BILLS_PAGINATED = gql`
ro_number ro_number
} }
} }
search_bills_aggregate(args: { search: $search }) { bills_aggregate {
aggregate { aggregate {
count(distinct: true) count(distinct: true)
} }
@@ -69,6 +67,7 @@ export const QUERY_BILLS_BY_JOBID = gql`
vendor { vendor {
id id
name name
email
} }
order_date order_date
deliver_by deliver_by
@@ -104,6 +103,7 @@ export const QUERY_BILLS_BY_JOBID = gql`
vendor { vendor {
id id
name name
email
} }
total total
invoice_number invoice_number

View File

@@ -118,6 +118,7 @@ export const QUERY_BODYSHOP = gql`
enforce_conversion_category enforce_conversion_category
tt_enforce_hours_for_tech_console tt_enforce_hours_for_tech_console
md_tasks_presets md_tasks_presets
use_paint_scale_data
employees { employees {
user_email user_email
id id

View File

@@ -1781,14 +1781,12 @@ export const QUERY_ALL_JOB_FIELDS = gql`
export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql` export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED( query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED(
$search: String
$offset: Int $offset: Int
$limit: Int $limit: Int
$order: [jobs_order_by!] $order: [jobs_order_by!]
$statusList: [String!] $statusList: [String!]
) { ) {
search_jobs( jobs(
args: { search: $search }
offset: $offset offset: $offset
limit: $limit limit: $limit
order_by: $order order_by: $order
@@ -1819,10 +1817,7 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
updated_at updated_at
ded_amt ded_amt
} }
search_jobs_aggregate( jobs_aggregate(where: { status: { _in: $statusList } }) {
args: { search: $search }
where: { status: { _in: $statusList } }
) {
aggregate { aggregate {
count(distinct: true) count(distinct: true)
} }

View File

@@ -4,7 +4,15 @@ export const INSERT_NEW_NOTE = gql`
mutation INSERT_NEW_NOTE($noteInput: [notes_insert_input!]!) { mutation INSERT_NEW_NOTE($noteInput: [notes_insert_input!]!) {
insert_notes(objects: $noteInput) { insert_notes(objects: $noteInput) {
returning { returning {
created_at
created_by
critical
id id
jobid
private
text
updated_at
audit
} }
} }
} }
@@ -15,8 +23,8 @@ export const QUERY_NOTES_BY_JOB_PK = gql`
jobs_by_pk(id: $id) { jobs_by_pk(id: $id) {
id id
ro_number ro_number
vehicle{ vehicle {
jobs{ jobs {
id id
ro_number ro_number
status status

View File

@@ -12,39 +12,39 @@ export const INSERT_NEW_PAYMENT = gql`
export const QUERY_ALL_PAYMENTS_PAGINATED = gql` export const QUERY_ALL_PAYMENTS_PAGINATED = gql`
query QUERY_ALL_PAYMENTS_PAGINATED( query QUERY_ALL_PAYMENTS_PAGINATED(
$search: String
$offset: Int $offset: Int
$limit: Int $limit: Int
$order: [payments_order_by!]! $order: [payments_order_by!]!
) { ) {
search_payments( payments(offset: $offset, limit: $limit, order_by: $order) {
args: { search: $search }
offset: $offset
limit: $limit
order_by: $order
) {
id id
amount
created_at created_at
jobid
paymentnum
date date
exportedat
jobid
job { job {
id id
ro_number ownerid
ownr_co_nm
ownr_fn ownr_fn
ownr_ln ownr_ln
ownr_co_nm owner {
id
ownr_co_nm
ownr_fn
ownr_ln
}
ro_number
} }
transactionid
memo memo
type
amount
stripeid
exportedat
stripeid
payer payer
paymentnum
stripeid
transactionid
type
} }
search_payments_aggregate(args: { search: $search }) { payments_aggregate {
aggregate { aggregate {
count(distinct: true) count(distinct: true)
} }
@@ -57,16 +57,31 @@ export const UPDATE_PAYMENT = gql`
update_payments(where: { id: { _eq: $paymentId } }, _set: $payment) { update_payments(where: { id: { _eq: $paymentId } }, _set: $payment) {
returning { returning {
id id
transactionid
memo
type
amount amount
stripeid created_at
date
exportedat exportedat
stripeid jobid
job {
id
ownerid
ownr_co_nm
ownr_fn
ownr_ln
owner {
id
ownr_co_nm
ownr_fn
ownr_ln
}
ro_number
}
memo
payer payer
paymentnum paymentnum
date stripeid
transactionid
type
} }
} }
} }
@@ -80,17 +95,31 @@ export const UPDATE_PAYMENTS = gql`
update_payments(where: { id: { _in: $paymentIdList } }, _set: $payment) { update_payments(where: { id: { _in: $paymentIdList } }, _set: $payment) {
returning { returning {
id id
exportedat
transactionid
memo
type
amount amount
stripeid created_at
date
exportedat exportedat
stripeid jobid
job {
id
ownerid
ownr_co_nm
ownr_fn
ownr_ln
owner {
id
ownr_co_nm
ownr_fn
ownr_ln
}
ro_number
}
memo
payer payer
paymentnum paymentnum
date stripeid
transactionid
type
} }
} }
} }
@@ -109,3 +138,36 @@ export const QUERY_JOB_PAYMENT_TOTALS = gql`
} }
} }
`; `;
export const QUERY_PAYMENT_BY_ID = gql`
query QUERY_PAYMENT_BY_ID($paymentId: uuid!) {
payments_by_pk(id: $paymentId) {
id
amount
created_at
exportedat
date
jobid
job {
id
ownerid
ownr_co_nm
ownr_fn
ownr_ln
owner {
id
ownr_co_nm
ownr_fn
ownr_ln
}
ro_number
}
memo
payer
paymentnum
stripeid
transactionid
type
}
}
`;

View File

@@ -19,6 +19,7 @@ export const QUERY_SCOREBOARD = gql`
v_make_desc v_make_desc
v_model_desc v_model_desc
v_model_yr v_model_yr
job_totals
} }
} }
} }

View File

@@ -59,6 +59,81 @@ export const QUERY_TIME_TICKETS_IN_RANGE = gql`
} }
`; `;
export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
query QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE(
$employeeid: uuid!
$start: date!
$end: date!
$fixedStart: date!
$fixedEnd: date!
) {
timetickets(
where: {
date: { _gte: $start, _lte: $end }
employeeid: { _eq: $employeeid }
}
order_by: { date: desc_nulls_first }
) {
actualhrs
ciecacode
clockoff
clockon
cost_center
created_at
date
id
rate
productivehrs
memo
jobid
flat_rate
job {
id
ro_number
}
employeeid
employee {
id
employee_number
first_name
last_name
}
}
fixedperiod: timetickets(
where: {
date: { _gte: $fixedStart, _lte: $fixedEnd }
employeeid: { _eq: $employeeid }
}
order_by: { date: desc_nulls_first }
) {
actualhrs
ciecacode
clockoff
clockon
cost_center
created_at
date
id
rate
productivehrs
memo
jobid
flat_rate
job {
id
ro_number
}
employeeid
employee {
id
employee_number
first_name
last_name
}
}
}
`;
export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql` export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
query QUERY_TIME_TICKETS_IN_RANGE_SB( query QUERY_TIME_TICKETS_IN_RANGE_SB(
$start: date! $start: date!

View File

@@ -1,7 +1,8 @@
import { SyncOutlined, EditFilled } from "@ant-design/icons"; import { EditFilled, SyncOutlined } from "@ant-design/icons";
import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd"; import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd";
import axios from "axios";
import queryString from "query-string"; import queryString from "query-string";
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, useHistory, useLocation } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
@@ -11,8 +12,8 @@ import PrintWrapperComponent from "../../components/print-wrapper/print-wrapper.
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort, dateSort } from "../../utils/sorters";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) => setPartsOrderContext: (context) =>
@@ -29,34 +30,36 @@ export function BillsListPage({
setPartsOrderContext, setPartsOrderContext,
setBillEnterContext, setBillEnterContext,
}) { }) {
const { t } = useTranslation(); const search = queryString.parse(useLocation().search);
const [openSearchResults, setOpenSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false);
const { page } = search;
const history = useHistory();
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
filteredInfo: { text: "" },
}); });
const history = useHistory();
const search = queryString.parse(useLocation().search);
const { page } = search;
const Templates = TemplateList("bill"); const Templates = TemplateList("bill");
const { t } = useTranslation();
const columns = [ const columns = [
{ {
title: t("bills.fields.vendorname"), title: t("bills.fields.vendorname"),
dataIndex: "vendorname", dataIndex: "vendorname",
key: "vendorname", key: "vendorname",
sortObject: (direction) => { // sortObject: (direction) => {
return { // return {
vendor: { // vendor: {
name: direction // name: direction
? direction === "descend" // ? direction === "descend"
? "desc" // ? "desc"
: "asc" // : "asc"
: "desc", // : "desc",
}, // },
}; // };
}, // },
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name), // sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
sortOrder: // sortOrder:
state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order, // state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
render: (text, record) => <span>{record.vendor.name}</span>, render: (text, record) => <span>{record.vendor.name}</span>,
}, },
{ {
@@ -72,20 +75,20 @@ export function BillsListPage({
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
dataIndex: "ro_number", dataIndex: "ro_number",
key: "ro_number", key: "ro_number",
sortObject: (direction) => { // sortObject: (direction) => {
return { // return {
job: { // job: {
ro_number: direction // ro_number: direction
? direction === "descend" // ? direction === "descend"
? "desc" // ? "desc"
: "asc" // : "asc"
: "desc", // : "desc",
}, // },
}; // };
}, // },
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), // sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
sortOrder: // sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, // state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => render: (text, record) =>
record.job && ( record.job && (
<Link to={`/manage/jobs/${record.job.id}`}> <Link to={`/manage/jobs/${record.job.id}`}>
@@ -174,7 +177,15 @@ export function BillsListPage({
// {t("bills.actions.return")} // {t("bills.actions.return")}
// </Button> // </Button>
} }
<BillDeleteButton bill={record} /> <BillDeleteButton
bill={record}
callback={(deletedBillid) => {
//Filter out the state and set it again.
setOpenSearchResults((currentResults) =>
currentResults.filter((bill) => bill.id !== deletedBillid)
);
}}
/>
{record.isinhouse && ( {record.isinhouse && (
<PrintWrapperComponent <PrintWrapperComponent
templateObject={{ templateObject={{
@@ -199,11 +210,32 @@ export function BillsListPage({
search.sortcolumn = sorter.order ? sorter.columnKey : null; search.sortcolumn = sorter.order ? sorter.columnKey : null;
search.sortorder = sorter.order; search.sortorder = sorter.order;
} }
search.sort = JSON.stringify({ [sorter.columnKey]: sorter.order }); search.sort = JSON.stringify({ [sorter.columnKey]: sorter.order });
history.push({ search: queryString.stringify(search) }); history.push({ search: queryString.stringify(search) });
}; };
useEffect(() => {
if (search.search && search.search.trim() !== "") {
searchBills();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function searchBills(value) {
try {
setSearchLoading(true);
const searchData = await axios.post("/search", {
search: value || search.search,
index: "bills",
});
setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source));
} catch (error) {
console.log("Error while fetching search results", error);
} finally {
setSearchLoading(false);
}
}
return ( return (
<Card <Card
title={t("bills.labels.bills")} title={t("bills.labels.bills")}
@@ -217,6 +249,7 @@ export function BillsListPage({
<Button <Button
onClick={() => { onClick={() => {
delete search.search; delete search.search;
delete search.page;
history.push({ search: queryString.stringify(search) }); history.push({ search: queryString.stringify(search) });
}} }}
> >
@@ -243,7 +276,10 @@ export function BillsListPage({
onSearch={(value) => { onSearch={(value) => {
search.search = value; search.search = value;
history.push({ search: queryString.stringify(search) }); history.push({ search: queryString.stringify(search) });
searchBills(value);
}} }}
loading={loading || searchLoading}
enterButton
/> />
</Space> </Space>
} }
@@ -251,19 +287,27 @@ export function BillsListPage({
<PartsOrderModalContainer /> <PartsOrderModalContainer />
<Table <Table
loading={loading} loading={loading || searchLoading}
scroll={{ // scroll={{
x: "50%", // y: "40rem" // x: "50%", // y: "40rem"
}} // }}
pagination={{ scroll={{ x: true }}
position: "top", pagination={
pageSize: 25, search?.search
current: parseInt(page || 1), ? {
total: total, pageSize: 25,
}} showSizeChanger: false,
}
: {
pageSize: 25,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,
}
}
columns={columns} columns={columns}
rowKey="id" rowKey="id"
dataSource={data} dataSource={search?.search ? openSearchResults : data}
onChange={handleTableChange} onChange={handleTableChange}
/> />
</Card> </Card>

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@apollo/client";
import queryString from "query-string"; import queryString from "query-string";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useQuery } from "@apollo/client";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
@@ -22,7 +22,7 @@ const mapDispatchToProps = (dispatch) => ({
export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) { export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation(); const { t } = useTranslation();
const searchParams = queryString.parse(useLocation().search); const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, search, searchObj } = searchParams; const { page, sortcolumn, sortorder, searchObj } = searchParams;
useEffect(() => { useEffect(() => {
document.title = t("titles.bills-list"); document.title = t("titles.bills-list");
@@ -38,7 +38,6 @@ export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
search: search || "",
offset: page ? (page - 1) * 25 : 0, offset: page ? (page - 1) * 25 : 0,
limit: 25, limit: 25,
order: [ order: [
@@ -61,10 +60,10 @@ export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) {
<RbacWrapper action="bills:list"> <RbacWrapper action="bills:list">
<div> <div>
<BillsPageComponent <BillsPageComponent
data={data ? data.search_bills : []} data={data ? data.bills : []}
loading={loading} loading={loading}
refetch={refetch} refetch={refetch}
total={data ? data.search_bills_aggregate.aggregate.count : 0} total={data ? data.bills_aggregate.aggregate.count : 0}
/> />
<BillDetailEditContainer /> <BillDetailEditContainer />

View File

@@ -25,7 +25,7 @@ const mapDispatchToProps = (dispatch) => ({
export function AllJobs({ setBreadcrumbs, setSelectedHeader }) { export function AllJobs({ setBreadcrumbs, setSelectedHeader }) {
const searchParams = queryString.parse(useLocation().search); const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, search, statusFilters } = searchParams; const { page, sortcolumn, sortorder, statusFilters } = searchParams;
const { loading, error, data, refetch } = useQuery( const { loading, error, data, refetch } = useQuery(
QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED, QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED,
@@ -33,13 +33,12 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
search: search || "",
offset: page ? (page - 1) * 25 : 0, offset: page ? (page - 1) * 25 : 0,
limit: 25, limit: 25,
...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}), ...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}),
order: [ order: [
{ {
[sortcolumn || "created_at"]: [sortcolumn || "ro_number"]:
sortorder && sortorder !== "false" sortorder && sortorder !== "false"
? sortorder === "descend" ? sortorder === "descend"
? "desc" ? "desc"
@@ -67,8 +66,8 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) {
refetch={refetch} refetch={refetch}
loading={loading} loading={loading}
searchParams={searchParams} searchParams={searchParams}
total={data ? data.search_jobs_aggregate.aggregate.count : 0} total={data ? data.jobs_aggregate.aggregate.count : 0}
jobs={data ? data.search_jobs : []} jobs={data ? data.jobs : []}
/> />
</RbacWrapper> </RbacWrapper>
); );

View File

@@ -52,6 +52,7 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container"; import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import UndefinedToNull from "../../utils/undefinedtonull"; import UndefinedToNull from "../../utils/undefinedtonull";
import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -97,7 +98,11 @@ export function JobsDetailPage({
variables: { variables: {
jobId: job.id, jobId: job.id,
job: { job: {
...UndefinedToNull(values, ["alt_transport", "category", "referral_source"]), ...UndefinedToNull(values, [
"alt_transport",
"category",
"referral_source",
]),
parts_tax_rates: { parts_tax_rates: {
...job.parts_tax_rates, ...job.parts_tax_rates,
...values.parts_tax_rates, ...values.parts_tax_rates,
@@ -231,6 +236,7 @@ export function JobsDetailPage({
<ScheduleJobModalContainer /> <ScheduleJobModalContainer />
<JobReconciliationModal /> <JobReconciliationModal />
<JobLineUpsertModalContainer /> <JobLineUpsertModalContainer />
<NoteUpsertModalComponent />
<Form <Form
form={form} form={form}
name="JobDetailForm" name="JobDetailForm"

View File

@@ -26,7 +26,7 @@ const mapDispatchToProps = (dispatch) => ({
export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) { export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
const searchParams = queryString.parse(useLocation().search); const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, search } = searchParams; const { page, sortcolumn, sortorder, searchObj } = searchParams;
const { loading, error, data, refetch } = useQuery( const { loading, error, data, refetch } = useQuery(
QUERY_ALL_PAYMENTS_PAGINATED, QUERY_ALL_PAYMENTS_PAGINATED,
@@ -34,11 +34,12 @@ export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
search: search || "",
offset: page ? (page - 1) * 25 : 0, offset: page ? (page - 1) * 25 : 0,
limit: 25, limit: 25,
order: [ order: [
{ searchObj
? JSON.parse(searchObj)
: {
[sortcolumn || "date"]: sortorder [sortcolumn || "date"]: sortorder
? sortorder === "descend" ? sortorder === "descend"
? "desc" ? "desc"
@@ -66,8 +67,8 @@ export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
refetch={refetch} refetch={refetch}
loading={loading} loading={loading}
searchParams={searchParams} searchParams={searchParams}
total={data ? data.search_payments_aggregate.aggregate.count : 0} total={data ? data.payments_aggregate.aggregate.count : 0}
payments={data ? data.search_payments : []} payments={data ? data.payments : []}
/> />
</RbacWrapper> </RbacWrapper>
); );

View File

@@ -2,10 +2,12 @@ import { Divider } from "antd";
import React from "react"; import React from "react";
import TechClockInFormContainer from "../../components/tech-job-clock-in-form/tech-job-clock-in-form.container"; import TechClockInFormContainer from "../../components/tech-job-clock-in-form/tech-job-clock-in-form.container";
import TechClockedInList from "../../components/tech-job-clocked-in-list/tech-job-clocked-in-list.component"; import TechClockedInList from "../../components/tech-job-clocked-in-list/tech-job-clocked-in-list.component";
import TechJobStatistics from "../../components/tech-job-statistics/tech-job-statistics.component";
export default function TechClockComponent() { export default function TechClockComponent() {
return ( return (
<div> <div>
<TechJobStatistics />
<TechClockInFormContainer /> <TechClockInFormContainer />
<Divider /> <Divider />
<TechClockedInList /> <TechClockedInList />

View File

@@ -1,9 +1,10 @@
.tech-content-container { .tech-content-container {
overflow-y: auto; overflow-y: visible;
padding: 1rem; padding: 1rem;
background: #fff; background: #fff;
} }
.tech-layout-container { .tech-layout-container {
height: 100vh; position: relative;
min-height: 100vh;
} }

View File

@@ -525,7 +525,8 @@
"id": "ID", "id": "ID",
"label": "Label", "label": "Label",
"lt": "Less than (hrs)", "lt": "Less than (hrs)",
"target": "Target (count)" "target": "Target (count)",
"color": "Job Color"
}, },
"state": "Province/State", "state": "Province/State",
"state_tax_id": "Provincial/State Tax ID (PST, QST)", "state_tax_id": "Provincial/State Tax ID (PST, QST)",
@@ -560,6 +561,7 @@
"tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs", "tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs",
"tt_enforce_hours_for_tech_console": "Restrict Claimable hours from Tech Console", "tt_enforce_hours_for_tech_console": "Restrict Claimable hours from Tech Console",
"use_fippa": "Use FIPPA for Names on Generated Documents?", "use_fippa": "Use FIPPA for Names on Generated Documents?",
"use_paint_scale_data": "Use Paint Scale Data for Job Costing?",
"uselocalmediaserver": "Use Local Media Server?", "uselocalmediaserver": "Use Local Media Server?",
"website": "Website", "website": "Website",
"zip_post": "Zip/Postal Code" "zip_post": "Zip/Postal Code"
@@ -1996,7 +1998,8 @@
"actions": "Actions", "actions": "Actions",
"deletenote": "Delete Note", "deletenote": "Delete Note",
"edit": "Edit Note", "edit": "Edit Note",
"new": "New Note" "new": "New Note",
"savetojobnotes": "Save to Job Notes"
}, },
"errors": { "errors": {
"inserting": "Error inserting note. {{error}}" "inserting": "Error inserting note. {{error}}"
@@ -2418,7 +2421,9 @@
"sublets": "Sublets", "sublets": "Sublets",
"totalhours": "Total Hrs ", "totalhours": "Total Hrs ",
"touchtime": "T/T", "touchtime": "T/T",
"viewname": "View Name" "viewname": "View Name",
"legend": "Legend:",
"cardcolor": "Card Colors"
}, },
"successes": { "successes": {
"removed": "Job removed from production." "removed": "Job removed from production."

View File

@@ -560,6 +560,7 @@
"tt_allow_post_to_invoiced": "", "tt_allow_post_to_invoiced": "",
"tt_enforce_hours_for_tech_console": "", "tt_enforce_hours_for_tech_console": "",
"use_fippa": "", "use_fippa": "",
"use_paint_scale_data": "",
"uselocalmediaserver": "", "uselocalmediaserver": "",
"website": "", "website": "",
"zip_post": "" "zip_post": ""
@@ -1996,7 +1997,8 @@
"actions": "Comportamiento", "actions": "Comportamiento",
"deletenote": "Borrar nota", "deletenote": "Borrar nota",
"edit": "Editar nota", "edit": "Editar nota",
"new": "Nueva nota" "new": "Nueva nota",
"savetojobnotes": ""
}, },
"errors": { "errors": {
"inserting": "" "inserting": ""

View File

@@ -560,6 +560,7 @@
"tt_allow_post_to_invoiced": "", "tt_allow_post_to_invoiced": "",
"tt_enforce_hours_for_tech_console": "", "tt_enforce_hours_for_tech_console": "",
"use_fippa": "", "use_fippa": "",
"use_paint_scale_data": "",
"uselocalmediaserver": "", "uselocalmediaserver": "",
"website": "", "website": "",
"zip_post": "" "zip_post": ""
@@ -1996,7 +1997,8 @@
"actions": "actes", "actions": "actes",
"deletenote": "Supprimer la note", "deletenote": "Supprimer la note",
"edit": "Note éditée", "edit": "Note éditée",
"new": "Nouvelle note" "new": "Nouvelle note",
"savetojobnotes": ""
}, },
"errors": { "errors": {
"inserting": "" "inserting": ""

View File

@@ -1,6 +1,5 @@
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
import { notification } from "antd"; import { notification } from "antd";
import axios from "axios";
import jsreport from "@jsreport/browser-client"; import jsreport from "@jsreport/browser-client";
import _ from "lodash"; import _ from "lodash";
import moment from "moment"; import moment from "moment";
@@ -9,7 +8,8 @@ import { setEmailOptions } from "../redux/email/email.actions";
import { store } from "../redux/store"; import { store } from "../redux/store";
import client from "../utils/GraphQLClient"; import client from "../utils/GraphQLClient";
import { TemplateList } from "./TemplateConstants"; import { TemplateList } from "./TemplateConstants";
import cleanAxios from "./CleanAxios";
import axios from "axios";
const server = process.env.REACT_APP_REPORTS_SERVER_URL; const server = process.env.REACT_APP_REPORTS_SERVER_URL;
jsreport.serverUrl = server; jsreport.serverUrl = server;
@@ -26,10 +26,14 @@ export default async function RenderTemplate(
if (window.jsr3) { if (window.jsr3) {
jsreport.serverUrl = "https://reports3.test.imex.online/"; jsreport.serverUrl = "https://reports3.test.imex.online/";
} }
const jsrAuth = (await axios.post("/utils/jsr")).data;
jsreport.headers["Authorization"] = jsrAuth;
//Query assets that match the template name. Must be in format <<templateName>>.query //Query assets that match the template name. Must be in format <<templateName>>.query
let { contextData, useShopSpecificTemplate } = await fetchContextData( let { contextData, useShopSpecificTemplate } = await fetchContextData(
templateObject templateObject,
jsrAuth
); );
const { ignoreCustomMargins } = Templates[templateObject.name]; const { ignoreCustomMargins } = Templates[templateObject.name];
@@ -137,11 +141,15 @@ export async function RenderTemplates(
//Query assets that match the template name. Must be in format <<templateName>>.query //Query assets that match the template name. Must be in format <<templateName>>.query
let unsortedTemplatesAndData = []; let unsortedTemplatesAndData = [];
let proms = []; let proms = [];
const jsrAuth = (await axios.post("/utils/jsr")).data;
jsreport.headers["Authorization"] = jsrAuth;
templateObjects.forEach((template) => { templateObjects.forEach((template) => {
proms.push( proms.push(
(async () => { (async () => {
let { contextData, useShopSpecificTemplate } = await fetchContextData( let { contextData, useShopSpecificTemplate } = await fetchContextData(
template template,
jsrAuth
); );
unsortedTemplatesAndData.push({ unsortedTemplatesAndData.push({
templateObject: template, templateObject: template,
@@ -298,19 +306,22 @@ export const GenerateDocuments = async (templates) => {
await RenderTemplates(templates, bodyshop); await RenderTemplates(templates, bodyshop);
}; };
const fetchContextData = async (templateObject) => { const fetchContextData = async (templateObject, jsrAuth) => {
const bodyshop = store.getState().user.bodyshop; const bodyshop = store.getState().user.bodyshop;
jsreport.headers["Authorization"] = jsreport.headers["FirebaseAuthorization"] =
"Bearer " + (await auth.currentUser.getIdToken()); "Bearer " + (await auth.currentUser.getIdToken());
const folders = await axios.get(`${server}/odata/folders`); const folders = await cleanAxios.get(`${server}/odata/folders`, {
headers: { Authorization: jsrAuth },
});
const shopSpecificFolder = folders.data.value.find( const shopSpecificFolder = folders.data.value.find(
(f) => f.name === bodyshop.imexshopid (f) => f.name === bodyshop.imexshopid
); );
const jsReportQueries = await axios.get( const jsReportQueries = await cleanAxios.get(
`${server}/odata/assets?$filter=name eq '${templateObject.name}.query'` `${server}/odata/assets?$filter=name eq '${templateObject.name}.query'`,
{ headers: { Authorization: jsrAuth } }
); );
let templateQueryToExecute; let templateQueryToExecute;

View File

@@ -7,22 +7,24 @@ export const EmailSettings = {
export const TemplateList = (type, context) => { export const TemplateList = (type, context) => {
//const { bodyshop } = store.getState().user; //const { bodyshop } = store.getState().user;
return { return {
//If there's no type or the type is job, send it back. //If there's no type or the type is job, send it back.
...(!type || type === "job" ...(!type || type === "job"
? { ? {
casl_authorization: { casl_authorization: {
title: i18n.t("printcenter.jobs.casl_authorization"), title: i18n.t("printcenter.jobs.casl_authorization"),
description: "CASL Authorization", description: "",
subject: i18n.t("printcenter.jobs.casl_authorization"), subject: i18n.t("printcenter.jobs.casl_authorization"),
key: "casl_authorization", key: "casl_authorization",
disabled: false, disabled: false,
group: "authorization", group: "authorization",
regions: {
CA: true,
},
}, },
fippa_authorization: { fippa_authorization: {
title: i18n.t("printcenter.jobs.fippa_authorization"), title: i18n.t("printcenter.jobs.fippa_authorization"),
description: "CASL Authorization", description: "",
subject: i18n.t("printcenter.jobs.fippa_authorization"), subject: i18n.t("printcenter.jobs.fippa_authorization"),
key: "fippa_authorization", key: "fippa_authorization",
disabled: false, disabled: false,
@@ -30,7 +32,7 @@ export const TemplateList = (type, context) => {
}, },
diagnostic_authorization: { diagnostic_authorization: {
title: i18n.t("printcenter.jobs.diagnostic_authorization"), title: i18n.t("printcenter.jobs.diagnostic_authorization"),
description: "Diagnostic Authorization", description: "",
subject: i18n.t("printcenter.jobs.diagnostic_authorization"), subject: i18n.t("printcenter.jobs.diagnostic_authorization"),
key: "diagnostic_authorization", key: "diagnostic_authorization",
disabled: false, disabled: false,
@@ -38,7 +40,7 @@ export const TemplateList = (type, context) => {
}, },
mechanical_authorization: { mechanical_authorization: {
title: i18n.t("printcenter.jobs.mechanical_authorization"), title: i18n.t("printcenter.jobs.mechanical_authorization"),
description: "Diagnostic Authorization", description: "",
subject: i18n.t("printcenter.jobs.mechanical_authorization"), subject: i18n.t("printcenter.jobs.mechanical_authorization"),
key: "mechanical_authorization", key: "mechanical_authorization",
disabled: false, disabled: false,
@@ -46,7 +48,7 @@ export const TemplateList = (type, context) => {
}, },
appointment_reminder: { appointment_reminder: {
title: i18n.t("printcenter.jobs.appointment_reminder"), title: i18n.t("printcenter.jobs.appointment_reminder"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.appointment_reminder"), subject: i18n.t("printcenter.jobs.appointment_reminder"),
key: "appointment_reminder", key: "appointment_reminder",
disabled: false, disabled: false,
@@ -54,7 +56,7 @@ export const TemplateList = (type, context) => {
}, },
estimate_followup: { estimate_followup: {
title: i18n.t("printcenter.jobs.estimate_followup"), title: i18n.t("printcenter.jobs.estimate_followup"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.estimate_followup"), subject: i18n.t("printcenter.jobs.estimate_followup"),
key: "estimate_followup", key: "estimate_followup",
disabled: false, disabled: false,
@@ -62,7 +64,7 @@ export const TemplateList = (type, context) => {
}, },
express_repair_checklist: { express_repair_checklist: {
title: i18n.t("printcenter.jobs.express_repair_checklist"), title: i18n.t("printcenter.jobs.express_repair_checklist"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.express_repair_checklist"), subject: i18n.t("printcenter.jobs.express_repair_checklist"),
key: "express_repair_checklist", key: "express_repair_checklist",
disabled: false, disabled: false,
@@ -70,7 +72,7 @@ export const TemplateList = (type, context) => {
}, },
glass_express_checklist: { glass_express_checklist: {
title: i18n.t("printcenter.jobs.glass_express_checklist"), title: i18n.t("printcenter.jobs.glass_express_checklist"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.glass_express_checklist"), subject: i18n.t("printcenter.jobs.glass_express_checklist"),
key: "glass_express_checklist", key: "glass_express_checklist",
disabled: false, disabled: false,
@@ -78,7 +80,7 @@ export const TemplateList = (type, context) => {
}, },
stolen_recovery_checklist: { stolen_recovery_checklist: {
title: i18n.t("printcenter.jobs.stolen_recovery_checklist"), title: i18n.t("printcenter.jobs.stolen_recovery_checklist"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.stolen_recovery_checklist"), subject: i18n.t("printcenter.jobs.stolen_recovery_checklist"),
key: "stolen_recovery_checklist", key: "stolen_recovery_checklist",
disabled: false, disabled: false,
@@ -86,7 +88,7 @@ export const TemplateList = (type, context) => {
}, },
vehicle_check_in: { vehicle_check_in: {
title: i18n.t("printcenter.jobs.vehicle_check_in"), title: i18n.t("printcenter.jobs.vehicle_check_in"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.vehicle_check_in"), subject: i18n.t("printcenter.jobs.vehicle_check_in"),
key: "vehicle_check_in", key: "vehicle_check_in",
disabled: false, disabled: false,
@@ -94,7 +96,7 @@ export const TemplateList = (type, context) => {
}, },
parts_order_history: { parts_order_history: {
title: i18n.t("printcenter.jobs.parts_order_history"), title: i18n.t("printcenter.jobs.parts_order_history"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.parts_order_history"), subject: i18n.t("printcenter.jobs.parts_order_history"),
key: "parts_order_history", key: "parts_order_history",
disabled: false, disabled: false,
@@ -102,7 +104,7 @@ export const TemplateList = (type, context) => {
}, },
job_notes: { job_notes: {
title: i18n.t("printcenter.jobs.job_notes"), title: i18n.t("printcenter.jobs.job_notes"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.job_notes"), subject: i18n.t("printcenter.jobs.job_notes"),
key: "job_notes", key: "job_notes",
disabled: false, disabled: false,
@@ -110,7 +112,7 @@ export const TemplateList = (type, context) => {
}, },
ro_with_description: { ro_with_description: {
title: i18n.t("printcenter.jobs.ro_with_description"), title: i18n.t("printcenter.jobs.ro_with_description"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.ro_with_description"), subject: i18n.t("printcenter.jobs.ro_with_description"),
key: "ro_with_description", key: "ro_with_description",
disabled: false, disabled: false,
@@ -118,7 +120,7 @@ export const TemplateList = (type, context) => {
}, },
window_tag: { window_tag: {
title: i18n.t("printcenter.jobs.window_tag"), title: i18n.t("printcenter.jobs.window_tag"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.window_tag"), subject: i18n.t("printcenter.jobs.window_tag"),
key: "window_tag", key: "window_tag",
disabled: false, disabled: false,
@@ -126,7 +128,7 @@ export const TemplateList = (type, context) => {
}, },
supplement_request: { supplement_request: {
title: i18n.t("printcenter.jobs.supplement_request"), title: i18n.t("printcenter.jobs.supplement_request"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.supplement_request"), subject: i18n.t("printcenter.jobs.supplement_request"),
key: "supplement_request", key: "supplement_request",
disabled: false, disabled: false,
@@ -134,7 +136,7 @@ export const TemplateList = (type, context) => {
}, },
estimate: { estimate: {
title: i18n.t("printcenter.jobs.estimate"), title: i18n.t("printcenter.jobs.estimate"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.estimate"), subject: i18n.t("printcenter.jobs.estimate"),
key: "estimate", key: "estimate",
disabled: false, disabled: false,
@@ -142,7 +144,7 @@ export const TemplateList = (type, context) => {
}, },
parts_list: { parts_list: {
title: i18n.t("printcenter.jobs.parts_list"), title: i18n.t("printcenter.jobs.parts_list"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.parts_list"), subject: i18n.t("printcenter.jobs.parts_list"),
key: "parts_list", key: "parts_list",
disabled: false, disabled: false,
@@ -150,7 +152,7 @@ export const TemplateList = (type, context) => {
}, },
coversheet_portrait: { coversheet_portrait: {
title: i18n.t("printcenter.jobs.coversheet_portrait"), title: i18n.t("printcenter.jobs.coversheet_portrait"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.coversheet_portrait"), subject: i18n.t("printcenter.jobs.coversheet_portrait"),
key: "coversheet_portrait", key: "coversheet_portrait",
disabled: false, disabled: false,
@@ -158,7 +160,7 @@ export const TemplateList = (type, context) => {
}, },
coversheet_landscape: { coversheet_landscape: {
title: i18n.t("printcenter.jobs.coversheet_landscape"), title: i18n.t("printcenter.jobs.coversheet_landscape"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.coversheet_landscape"), subject: i18n.t("printcenter.jobs.coversheet_landscape"),
key: "coversheet_landscape", key: "coversheet_landscape",
disabled: false, disabled: false,
@@ -166,7 +168,7 @@ export const TemplateList = (type, context) => {
}, },
key_tag: { key_tag: {
title: i18n.t("printcenter.jobs.key_tag"), title: i18n.t("printcenter.jobs.key_tag"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.key_tag"), subject: i18n.t("printcenter.jobs.key_tag"),
key: "key_tag", key: "key_tag",
disabled: false, disabled: false,
@@ -174,7 +176,7 @@ export const TemplateList = (type, context) => {
}, },
paint_grid: { paint_grid: {
title: i18n.t("printcenter.jobs.paint_grid"), title: i18n.t("printcenter.jobs.paint_grid"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.paint_grid"), subject: i18n.t("printcenter.jobs.paint_grid"),
key: "paint_grid", key: "paint_grid",
disabled: false, disabled: false,
@@ -182,7 +184,7 @@ export const TemplateList = (type, context) => {
}, },
worksheet_by_line_number: { worksheet_by_line_number: {
title: i18n.t("printcenter.jobs.worksheet_by_line_number"), title: i18n.t("printcenter.jobs.worksheet_by_line_number"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.worksheet_by_line_number"), subject: i18n.t("printcenter.jobs.worksheet_by_line_number"),
key: "worksheet_by_line_number", key: "worksheet_by_line_number",
disabled: false, disabled: false,
@@ -192,7 +194,7 @@ export const TemplateList = (type, context) => {
title: i18n.t( title: i18n.t(
"printcenter.jobs.worksheet_sorted_by_operation_type" "printcenter.jobs.worksheet_sorted_by_operation_type"
), ),
description: "All Jobs Notes", description: "",
subject: i18n.t( subject: i18n.t(
"printcenter.jobs.worksheet_sorted_by_operation_type" "printcenter.jobs.worksheet_sorted_by_operation_type"
), ),
@@ -202,7 +204,7 @@ export const TemplateList = (type, context) => {
}, },
worksheet_sorted_by_operation: { worksheet_sorted_by_operation: {
title: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"), title: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"), subject: i18n.t("printcenter.jobs.worksheet_sorted_by_operation"),
key: "worksheet_sorted_by_operation", key: "worksheet_sorted_by_operation",
disabled: false, disabled: false,
@@ -212,7 +214,7 @@ export const TemplateList = (type, context) => {
title: i18n.t( title: i18n.t(
"printcenter.jobs.worksheet_sorted_by_operation_no_hours" "printcenter.jobs.worksheet_sorted_by_operation_no_hours"
), ),
description: "All Jobs Notes", description: "",
subject: i18n.t( subject: i18n.t(
"printcenter.jobs.worksheet_sorted_by_operation_no_hours" "printcenter.jobs.worksheet_sorted_by_operation_no_hours"
), ),
@@ -224,7 +226,7 @@ export const TemplateList = (type, context) => {
title: i18n.t( title: i18n.t(
"printcenter.jobs.worksheet_sorted_by_operation_part_type" "printcenter.jobs.worksheet_sorted_by_operation_part_type"
), ),
description: "All Jobs Notes", description: "",
subject: i18n.t( subject: i18n.t(
"printcenter.jobs.worksheet_sorted_by_operation_part_type" "printcenter.jobs.worksheet_sorted_by_operation_part_type"
), ),
@@ -234,7 +236,7 @@ export const TemplateList = (type, context) => {
}, },
payments_by_job: { payments_by_job: {
title: i18n.t("printcenter.jobs.payments_by_job"), title: i18n.t("printcenter.jobs.payments_by_job"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.payments_by_job"), subject: i18n.t("printcenter.jobs.payments_by_job"),
key: "payments_by_job", key: "payments_by_job",
disabled: false, disabled: false,
@@ -242,7 +244,7 @@ export const TemplateList = (type, context) => {
}, },
final_invoice: { final_invoice: {
title: i18n.t("printcenter.jobs.final_invoice"), title: i18n.t("printcenter.jobs.final_invoice"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.final_invoice"), subject: i18n.t("printcenter.jobs.final_invoice"),
key: "final_invoice", key: "final_invoice",
disabled: false, disabled: false,
@@ -250,7 +252,7 @@ export const TemplateList = (type, context) => {
}, },
payment_request: { payment_request: {
title: i18n.t("printcenter.jobs.payment_request"), title: i18n.t("printcenter.jobs.payment_request"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.payment_request"), subject: i18n.t("printcenter.jobs.payment_request"),
key: "payment_request", key: "payment_request",
disabled: false, disabled: false,
@@ -258,7 +260,7 @@ export const TemplateList = (type, context) => {
}, },
invoice_total_payable: { invoice_total_payable: {
title: i18n.t("printcenter.jobs.invoice_total_payable"), title: i18n.t("printcenter.jobs.invoice_total_payable"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.invoice_total_payable"), subject: i18n.t("printcenter.jobs.invoice_total_payable"),
key: "invoice_total_payable", key: "invoice_total_payable",
disabled: false, disabled: false,
@@ -266,7 +268,7 @@ export const TemplateList = (type, context) => {
}, },
invoice_customer_payable: { invoice_customer_payable: {
title: i18n.t("printcenter.jobs.invoice_customer_payable"), title: i18n.t("printcenter.jobs.invoice_customer_payable"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.invoice_customer_payable"), subject: i18n.t("printcenter.jobs.invoice_customer_payable"),
key: "invoice_customer_payable", key: "invoice_customer_payable",
disabled: false, disabled: false,
@@ -274,7 +276,7 @@ export const TemplateList = (type, context) => {
}, },
ro_totals: { ro_totals: {
title: i18n.t("printcenter.jobs.ro_totals"), title: i18n.t("printcenter.jobs.ro_totals"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.ro_totals"), subject: i18n.t("printcenter.jobs.ro_totals"),
key: "ro_totals", key: "ro_totals",
disabled: false, disabled: false,
@@ -282,7 +284,7 @@ export const TemplateList = (type, context) => {
}, },
job_costing_ro: { job_costing_ro: {
title: i18n.t("printcenter.jobs.job_costing_ro"), title: i18n.t("printcenter.jobs.job_costing_ro"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.job_costing_ro"), subject: i18n.t("printcenter.jobs.job_costing_ro"),
key: "job_costing_ro", key: "job_costing_ro",
disabled: false, disabled: false,
@@ -290,7 +292,7 @@ export const TemplateList = (type, context) => {
}, },
purchases_by_ro_detail: { purchases_by_ro_detail: {
title: i18n.t("printcenter.jobs.purchases_by_ro_detail"), title: i18n.t("printcenter.jobs.purchases_by_ro_detail"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.purchases_by_ro_detail"), subject: i18n.t("printcenter.jobs.purchases_by_ro_detail"),
key: "purchases_by_ro_detail", key: "purchases_by_ro_detail",
disabled: false, disabled: false,
@@ -298,7 +300,7 @@ export const TemplateList = (type, context) => {
}, },
purchases_by_ro_summary: { purchases_by_ro_summary: {
title: i18n.t("printcenter.jobs.purchases_by_ro_summary"), title: i18n.t("printcenter.jobs.purchases_by_ro_summary"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.purchases_by_ro_summary"), subject: i18n.t("printcenter.jobs.purchases_by_ro_summary"),
key: "purchases_by_ro_summary", key: "purchases_by_ro_summary",
disabled: false, disabled: false,
@@ -306,7 +308,7 @@ export const TemplateList = (type, context) => {
}, },
filing_coversheet_portrait: { filing_coversheet_portrait: {
title: i18n.t("printcenter.jobs.filing_coversheet_portrait"), title: i18n.t("printcenter.jobs.filing_coversheet_portrait"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.filing_coversheet_portrait"), subject: i18n.t("printcenter.jobs.filing_coversheet_portrait"),
key: "filing_coversheet_portrait", key: "filing_coversheet_portrait",
disabled: false, disabled: false,
@@ -314,7 +316,7 @@ export const TemplateList = (type, context) => {
}, },
filing_coversheet_landscape: { filing_coversheet_landscape: {
title: i18n.t("printcenter.jobs.filing_coversheet_landscape"), title: i18n.t("printcenter.jobs.filing_coversheet_landscape"),
description: "CASL Authorization", description: "",
subject: i18n.t("printcenter.jobs.filing_coversheet_landscape"), subject: i18n.t("printcenter.jobs.filing_coversheet_landscape"),
key: "filing_coversheet_landscape", key: "filing_coversheet_landscape",
disabled: false, disabled: false,
@@ -322,7 +324,7 @@ export const TemplateList = (type, context) => {
}, },
qc_sheet: { qc_sheet: {
title: i18n.t("printcenter.jobs.qc_sheet"), title: i18n.t("printcenter.jobs.qc_sheet"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.qc_sheet"), subject: i18n.t("printcenter.jobs.qc_sheet"),
key: "qc_sheet", key: "qc_sheet",
disabled: false, disabled: false,
@@ -330,7 +332,7 @@ export const TemplateList = (type, context) => {
}, },
vehicle_delivery_check: { vehicle_delivery_check: {
title: i18n.t("printcenter.jobs.vehicle_delivery_check"), title: i18n.t("printcenter.jobs.vehicle_delivery_check"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.vehicle_delivery_check"), subject: i18n.t("printcenter.jobs.vehicle_delivery_check"),
key: "vehicle_delivery_check", key: "vehicle_delivery_check",
disabled: false, disabled: false,
@@ -338,7 +340,7 @@ export const TemplateList = (type, context) => {
}, },
guarantee: { guarantee: {
title: i18n.t("printcenter.jobs.guarantee"), title: i18n.t("printcenter.jobs.guarantee"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.guarantee"), subject: i18n.t("printcenter.jobs.guarantee"),
key: "guarantee", key: "guarantee",
disabled: false, disabled: false,
@@ -346,7 +348,7 @@ export const TemplateList = (type, context) => {
}, },
csi_invitation: { csi_invitation: {
title: i18n.t("printcenter.jobs.csi_invitation"), title: i18n.t("printcenter.jobs.csi_invitation"),
description: "CSI invite", description: "",
key: "csi_invitation", key: "csi_invitation",
subject: i18n.t("printcenter.jobs.csi_invitation"), subject: i18n.t("printcenter.jobs.csi_invitation"),
disabled: false, disabled: false,
@@ -354,7 +356,7 @@ export const TemplateList = (type, context) => {
}, },
window_tag_sublet: { window_tag_sublet: {
title: i18n.t("printcenter.jobs.window_tag_sublet"), title: i18n.t("printcenter.jobs.window_tag_sublet"),
description: "Window Tag Sublet", description: "",
key: "window_tag_sublet", key: "window_tag_sublet",
subject: i18n.t("printcenter.jobs.window_tag_sublet"), subject: i18n.t("printcenter.jobs.window_tag_sublet"),
disabled: false, disabled: false,
@@ -362,7 +364,7 @@ export const TemplateList = (type, context) => {
}, },
thank_you_ro: { thank_you_ro: {
title: i18n.t("printcenter.jobs.thank_you_ro"), title: i18n.t("printcenter.jobs.thank_you_ro"),
description: "Thank You Letter by RO", description: "",
key: "thank_you_ro", key: "thank_you_ro",
subject: i18n.t("printcenter.jobs.thank_you_ro"), subject: i18n.t("printcenter.jobs.thank_you_ro"),
disabled: false, disabled: false,
@@ -370,7 +372,7 @@ export const TemplateList = (type, context) => {
}, },
parts_label_single: { parts_label_single: {
title: i18n.t("printcenter.jobs.parts_label_single"), title: i18n.t("printcenter.jobs.parts_label_single"),
description: "Thank You Letter by RO", description: "",
key: "parts_label_single", key: "parts_label_single",
subject: i18n.t("printcenter.jobs.parts_label_single"), subject: i18n.t("printcenter.jobs.parts_label_single"),
disabled: false, disabled: false,
@@ -379,7 +381,7 @@ export const TemplateList = (type, context) => {
}, },
envelope_return_address: { envelope_return_address: {
title: i18n.t("printcenter.jobs.envelope_return_address"), title: i18n.t("printcenter.jobs.envelope_return_address"),
description: "All Jobs Notes", description: "",
subject: i18n.t("printcenter.jobs.envelope_return_address"), subject: i18n.t("printcenter.jobs.envelope_return_address"),
key: "envelope_return_address", key: "envelope_return_address",
disabled: false, disabled: false,
@@ -388,7 +390,7 @@ export const TemplateList = (type, context) => {
}, },
sgi_certificate_of_repairs: { sgi_certificate_of_repairs: {
title: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"), title: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"),
description: "Thank You Letter by RO", description: "",
key: "sgi_certificate_of_repairs", key: "sgi_certificate_of_repairs",
subject: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"), subject: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"),
disabled: false, disabled: false,
@@ -399,7 +401,7 @@ export const TemplateList = (type, context) => {
}, },
sgi_windshield_auth: { sgi_windshield_auth: {
title: i18n.t("printcenter.jobs.sgi_windshield_auth"), title: i18n.t("printcenter.jobs.sgi_windshield_auth"),
description: "Thank You Letter by RO", description: "",
key: "sgi_windshield_auth", key: "sgi_windshield_auth",
subject: i18n.t("printcenter.jobs.sgi_windshield_auth"), subject: i18n.t("printcenter.jobs.sgi_windshield_auth"),
disabled: false, disabled: false,
@@ -410,7 +412,7 @@ export const TemplateList = (type, context) => {
}, },
mpi_final_acct_sheet: { mpi_final_acct_sheet: {
title: i18n.t("printcenter.jobs.mpi_final_acct_sheet"), title: i18n.t("printcenter.jobs.mpi_final_acct_sheet"),
description: "Thank You Letter by RO", description: "",
key: "mpi_final_acct_sheet", key: "mpi_final_acct_sheet",
subject: i18n.t("printcenter.jobs.mpi_final_acct_sheet"), subject: i18n.t("printcenter.jobs.mpi_final_acct_sheet"),
disabled: false, disabled: false,
@@ -421,7 +423,7 @@ export const TemplateList = (type, context) => {
}, },
mpi_eglass_auth: { mpi_eglass_auth: {
title: i18n.t("printcenter.jobs.mpi_eglass_auth"), title: i18n.t("printcenter.jobs.mpi_eglass_auth"),
description: "Thank You Letter by RO", description: "",
key: "mpi_eglass_auth", key: "mpi_eglass_auth",
subject: i18n.t("printcenter.jobs.mpi_eglass_auth"), subject: i18n.t("printcenter.jobs.mpi_eglass_auth"),
disabled: false, disabled: false,
@@ -432,7 +434,7 @@ export const TemplateList = (type, context) => {
}, },
mpi_animal_checklist: { mpi_animal_checklist: {
title: i18n.t("printcenter.jobs.mpi_animal_checklist"), title: i18n.t("printcenter.jobs.mpi_animal_checklist"),
description: "Thank You Letter by RO", description: "",
key: "mpi_animal_checklist", key: "mpi_animal_checklist",
subject: i18n.t("printcenter.jobs.mpi_animal_checklist"), subject: i18n.t("printcenter.jobs.mpi_animal_checklist"),
disabled: false, disabled: false,
@@ -443,7 +445,7 @@ export const TemplateList = (type, context) => {
}, },
ab_proof_of_loss: { ab_proof_of_loss: {
title: i18n.t("printcenter.jobs.ab_proof_of_loss"), title: i18n.t("printcenter.jobs.ab_proof_of_loss"),
description: "Thank You Letter by RO", description: "",
key: "ab_proof_of_loss", key: "ab_proof_of_loss",
subject: i18n.t("printcenter.jobs.ab_proof_of_loss"), subject: i18n.t("printcenter.jobs.ab_proof_of_loss"),
disabled: false, disabled: false,
@@ -454,7 +456,7 @@ export const TemplateList = (type, context) => {
}, },
// parts_label_multi: { // parts_label_multi: {
// title: i18n.t("printcenter.jobs.parts_label_multi"), // title: i18n.t("printcenter.jobs.parts_label_multi"),
// description: "Thank You Letter by RO", // description: "",
// key: "parts_label_multi", // key: "parts_label_multi",
// subject: i18n.t("printcenter.jobs.parts_label_multi"), // subject: i18n.t("printcenter.jobs.parts_label_multi"),
// disabled: false, // disabled: false,
@@ -462,7 +464,7 @@ export const TemplateList = (type, context) => {
// }, // },
iou_form: { iou_form: {
title: i18n.t("printcenter.jobs.iou_form"), title: i18n.t("printcenter.jobs.iou_form"),
description: "CASL Authorization", description: "",
subject: i18n.t("printcenter.jobs.iou_form"), subject: i18n.t("printcenter.jobs.iou_form"),
key: "iou_form", key: "iou_form",
disabled: false, disabled: false,
@@ -470,7 +472,7 @@ export const TemplateList = (type, context) => {
}, },
lag_time_ro: { lag_time_ro: {
title: i18n.t("printcenter.jobs.lag_time_ro"), title: i18n.t("printcenter.jobs.lag_time_ro"),
description: "CASL Authorization", description: "",
subject: i18n.t("printcenter.jobs.lag_time_ro"), subject: i18n.t("printcenter.jobs.lag_time_ro"),
key: "lag_time_ro", key: "lag_time_ro",
disabled: false, disabled: false,
@@ -478,7 +480,7 @@ export const TemplateList = (type, context) => {
}, },
rental_reservation: { rental_reservation: {
title: i18n.t("printcenter.jobs.rental_reservation"), title: i18n.t("printcenter.jobs.rental_reservation"),
description: "CASL Authorization", description: "",
subject: i18n.t("printcenter.jobs.rental_reservation"), subject: i18n.t("printcenter.jobs.rental_reservation"),
key: "rental_reservation", key: "rental_reservation",
disabled: false, disabled: false,
@@ -486,7 +488,7 @@ export const TemplateList = (type, context) => {
}, },
timetickets_ro: { timetickets_ro: {
title: i18n.t("printcenter.jobs.timetickets_ro"), title: i18n.t("printcenter.jobs.timetickets_ro"),
description: "CASL Authorization", description: "",
subject: i18n.t("printcenter.jobs.timetickets_ro"), subject: i18n.t("printcenter.jobs.timetickets_ro"),
key: "timetickets_ro", key: "timetickets_ro",
disabled: false, disabled: false,
@@ -494,7 +496,7 @@ export const TemplateList = (type, context) => {
}, },
dms_posting_sheet: { dms_posting_sheet: {
title: i18n.t("printcenter.jobs.dms_posting_sheet"), title: i18n.t("printcenter.jobs.dms_posting_sheet"),
description: "DMS Posting Sheet", description: "",
subject: i18n.t("printcenter.jobs.dms_posting_sheet"), subject: i18n.t("printcenter.jobs.dms_posting_sheet"),
key: "dms_posting_sheet", key: "dms_posting_sheet",
disabled: false, disabled: false,
@@ -506,39 +508,39 @@ export const TemplateList = (type, context) => {
? { ? {
special_thirdpartypayer: { special_thirdpartypayer: {
title: i18n.t("printcenter.jobs.thirdpartypayer"), title: i18n.t("printcenter.jobs.thirdpartypayer"),
description: "CSI invite", description: "",
key: "special_thirdpartypayer", key: "special_thirdpartypayer",
disabled: false, disabled: false,
}, },
folder_label_multiple: { folder_label_multiple: {
title: i18n.t("printcenter.jobs.folder_label_multiple"), title: i18n.t("printcenter.jobs.folder_label_multiple"),
description: "Folder Label Multiple", description: "",
key: "folder_label_multiple", key: "folder_label_multiple",
disabled: false, disabled: false,
}, },
parts_label_multiple: { parts_label_multiple: {
title: i18n.t("printcenter.jobs.parts_label_multiple"), title: i18n.t("printcenter.jobs.parts_label_multiple"),
description: "Parts Label Multiple", description: "",
key: "parts_label_multiple", key: "parts_label_multiple",
disabled: false, disabled: false,
}, },
parts_invoice_label_single: { parts_invoice_label_single: {
title: i18n.t("printcenter.jobs.parts_invoice_label_single"), title: i18n.t("printcenter.jobs.parts_invoice_label_single"),
description: "Parts Label Multiple", description: "",
key: "parts_invoice_label_single", key: "parts_invoice_label_single",
disabled: false, disabled: false,
ignoreCustomMargins: true, ignoreCustomMargins: true,
}, },
csi_invitation_action: { csi_invitation_action: {
title: i18n.t("printcenter.jobs.csi_invitation_action"), title: i18n.t("printcenter.jobs.csi_invitation_action"),
description: "CSI invite", description: "",
key: "csi_invitation_action", key: "csi_invitation_action",
subject: i18n.t("printcenter.jobs.csi_invitation_action"), subject: i18n.t("printcenter.jobs.csi_invitation_action"),
disabled: false, disabled: false,
}, },
individual_job_note: { individual_job_note: {
title: i18n.t("printcenter.jobs.individual_job_note"), title: i18n.t("printcenter.jobs.individual_job_note"),
description: "CSI invite", description: "",
key: "individual_job_note", key: "individual_job_note",
subject: i18n.t("printcenter.jobs.individual_job_note", { subject: i18n.t("printcenter.jobs.individual_job_note", {
ro_number: (context && context.ro_number) || "", ro_number: (context && context.ro_number) || "",
@@ -551,7 +553,7 @@ export const TemplateList = (type, context) => {
? { ? {
appointment_confirmation: { appointment_confirmation: {
title: i18n.t("printcenter.appointments.appointment_confirmation"), title: i18n.t("printcenter.appointments.appointment_confirmation"),
description: "Appointment Confirmation", description: "",
subject: i18n.t( subject: i18n.t(
"printcenter.appointments.appointment_confirmation" "printcenter.appointments.appointment_confirmation"
), ),
@@ -564,7 +566,7 @@ export const TemplateList = (type, context) => {
? { ? {
parts_order: { parts_order: {
title: i18n.t("printcenter.jobs.parts_order"), title: i18n.t("printcenter.jobs.parts_order"),
description: "Parts Order", description: "",
key: "parts_order", key: "parts_order",
subject: i18n.t("printcenter.subjects.jobs.parts_order", { subject: i18n.t("printcenter.subjects.jobs.parts_order", {
ro_number: context && context.job && context.job.ro_number, ro_number: context && context.job && context.job.ro_number,
@@ -578,7 +580,7 @@ export const TemplateList = (type, context) => {
}, },
sublet_order: { sublet_order: {
title: i18n.t("printcenter.jobs.sublet_order"), title: i18n.t("printcenter.jobs.sublet_order"),
description: "Parts Order", description: "",
key: "sublet_order", key: "sublet_order",
subject: i18n.t("printcenter.subjects.jobs.sublet_order", { subject: i18n.t("printcenter.subjects.jobs.sublet_order", {
ro_number: context && context.job && context.job.ro_number, ro_number: context && context.job && context.job.ro_number,
@@ -593,7 +595,7 @@ export const TemplateList = (type, context) => {
parts_return_slip: { parts_return_slip: {
title: i18n.t("printcenter.jobs.parts_return_slip"), title: i18n.t("printcenter.jobs.parts_return_slip"),
subject: i18n.t("printcenter.jobs.parts_return_slip"), subject: i18n.t("printcenter.jobs.parts_return_slip"),
description: "Parts Return", description: "",
key: "parts_return_slip", key: "parts_return_slip",
disabled: false, disabled: false,
}, },
@@ -603,7 +605,7 @@ export const TemplateList = (type, context) => {
? { ? {
payment_receipt: { payment_receipt: {
title: i18n.t("printcenter.jobs.payment_receipt"), title: i18n.t("printcenter.jobs.payment_receipt"),
description: "Payment Receipt", description: "",
subject: i18n.t("printcenter.jobs.payment_receipt"), subject: i18n.t("printcenter.jobs.payment_receipt"),
key: "payment_receipt", key: "payment_receipt",
disabled: false, disabled: false,
@@ -1891,7 +1893,7 @@ export const TemplateList = (type, context) => {
title: i18n.t( title: i18n.t(
"printcenter.courtesycarcontract.courtesy_car_contract" "printcenter.courtesycarcontract.courtesy_car_contract"
), ),
description: "Est Detail", description: "",
subject: i18n.t( subject: i18n.t(
"printcenter.courtesycarcontract.courtesy_car_contract" "printcenter.courtesycarcontract.courtesy_car_contract"
), ),
@@ -1900,7 +1902,7 @@ export const TemplateList = (type, context) => {
}, },
courtesy_car_terms: { courtesy_car_terms: {
title: i18n.t("printcenter.courtesycarcontract.courtesy_car_terms"), title: i18n.t("printcenter.courtesycarcontract.courtesy_car_terms"),
description: "Est Detail", description: "",
subject: i18n.t( subject: i18n.t(
"printcenter.courtesycarcontract.courtesy_car_terms" "printcenter.courtesycarcontract.courtesy_car_terms"
), ),
@@ -1911,7 +1913,7 @@ export const TemplateList = (type, context) => {
title: i18n.t( title: i18n.t(
"printcenter.courtesycarcontract.courtesy_car_impound" "printcenter.courtesycarcontract.courtesy_car_impound"
), ),
description: "Est Detail", description: "",
subject: i18n.t( subject: i18n.t(
"printcenter.courtesycarcontract.courtesy_car_impound" "printcenter.courtesycarcontract.courtesy_car_impound"
), ),
@@ -1926,7 +1928,7 @@ export const TemplateList = (type, context) => {
title: i18n.t( title: i18n.t(
"printcenter.courtesycarcontract.courtesy_car_inventory" "printcenter.courtesycarcontract.courtesy_car_inventory"
), ),
description: "Est Detail", description: "",
subject: i18n.t( subject: i18n.t(
"printcenter.courtesycarcontract.courtesy_car_inventory" "printcenter.courtesycarcontract.courtesy_car_inventory"
), ),
@@ -1939,7 +1941,7 @@ export const TemplateList = (type, context) => {
? { ? {
inhouse_invoice: { inhouse_invoice: {
title: i18n.t("printcenter.bills.inhouse_invoice"), title: i18n.t("printcenter.bills.inhouse_invoice"),
description: "Est Detail", description: "",
subject: i18n.t("printcenter.bills.inhouse_invoice"), subject: i18n.t("printcenter.bills.inhouse_invoice"),
key: "inhouse_invoice", key: "inhouse_invoice",
disabled: false, disabled: false,
@@ -1950,7 +1952,7 @@ export const TemplateList = (type, context) => {
? { ? {
// timetickets: { // timetickets: {
// title: i18n.t("printcenter.timetickets.timetickets"), // title: i18n.t("printcenter.timetickets.timetickets"),
// description: "Est Detail", // description: "",
// subject: `${i18n.t("printcenter.timetickets.timetickets")} - ${ // subject: `${i18n.t("printcenter.timetickets.timetickets")} - ${
// context && context.job && context.job.ro_number // context && context.job && context.job.ro_number
// }`, // }`,
@@ -1963,14 +1965,14 @@ export const TemplateList = (type, context) => {
? { ? {
purchases_by_vendor_detailed: { purchases_by_vendor_detailed: {
title: i18n.t("printcenter.vendors.purchases_by_vendor_detailed"), title: i18n.t("printcenter.vendors.purchases_by_vendor_detailed"),
description: "Est Detail", description: "",
subject: i18n.t("printcenter.vendors.purchases_by_vendor_detailed"), subject: i18n.t("printcenter.vendors.purchases_by_vendor_detailed"),
key: "purchases_by_vendor_detailed", key: "purchases_by_vendor_detailed",
disabled: false, disabled: false,
}, },
purchases_by_vendor_summary: { purchases_by_vendor_summary: {
title: i18n.t("printcenter.vendors.purchases_by_vendor_summary"), title: i18n.t("printcenter.vendors.purchases_by_vendor_summary"),
description: "Est Detail", description: "",
subject: i18n.t("printcenter.vendors.purchases_by_vendor_summary"), subject: i18n.t("printcenter.vendors.purchases_by_vendor_summary"),
key: "purchases_by_vendor_summary", key: "purchases_by_vendor_summary",
disabled: false, disabled: false,
@@ -2043,21 +2045,21 @@ export const TemplateList = (type, context) => {
? { ? {
ca_bc_etf_table: { ca_bc_etf_table: {
title: i18n.t("printcenter.payments.ca_bc_etf_table"), title: i18n.t("printcenter.payments.ca_bc_etf_table"),
description: "Est Detail", description: "",
subject: i18n.t("printcenter.payments.ca_bc_etf_table"), subject: i18n.t("printcenter.payments.ca_bc_etf_table"),
key: "ca_bc_etf_table", key: "ca_bc_etf_table",
disabled: false, disabled: false,
}, },
exported_payroll: { exported_payroll: {
title: i18n.t("printcenter.payments.exported_payroll"), title: i18n.t("printcenter.payments.exported_payroll"),
description: "Est Detail", description: "",
subject: i18n.t("printcenter.payments.exported_payroll"), subject: i18n.t("printcenter.payments.exported_payroll"),
key: "exported_payroll", key: "exported_payroll",
disabled: false, disabled: false,
}, },
attendance_detail_csv: { attendance_detail_csv: {
title: i18n.t("printcenter.special.attendance_detail_csv"), title: i18n.t("printcenter.special.attendance_detail_csv"),
description: "Est Detail", description: "",
subject: i18n.t("printcenter.special.attendance_detail_csv"), subject: i18n.t("printcenter.special.attendance_detail_csv"),
key: "attendance_detail_csv", key: "attendance_detail_csv",
disabled: false, disabled: false,

View File

@@ -673,6 +673,30 @@
_eq: true _eq: true
- exported: - exported:
_eq: false _eq: false
event_triggers:
- name: os_bills
definition:
delete:
columns: '*'
enable_manual: false
insert:
columns: '*'
update:
columns: '*'
retry_conf:
interval_sec: 10
num_retries: 3
timeout_sec: 60
webhook_from_env: HASURA_API_URL
headers:
- name: event-secret
value_from_env: EVENT_SECRET
request_transform:
method: POST
query_params: {}
template_engine: Kriti
url: '{{$base_url}}/opensearch'
version: 2
- table: - table:
name: bodyshops name: bodyshops
schema: public schema: public
@@ -917,6 +941,7 @@
- md_referral_sources - md_referral_sources
- md_responsibility_centers - md_responsibility_centers
- md_ro_statuses - md_ro_statuses
- md_tasks_presets
- md_to_emails - md_to_emails
- messagingservicesid - messagingservicesid
- pbs_configuration - pbs_configuration
@@ -945,6 +970,7 @@
- tt_enforce_hours_for_tech_console - tt_enforce_hours_for_tech_console
- updated_at - updated_at
- use_fippa - use_fippa
- use_paint_scale_data
- uselocalmediaserver - uselocalmediaserver
- website - website
- workingdays - workingdays
@@ -1013,6 +1039,7 @@
- md_referral_sources - md_referral_sources
- md_responsibility_centers - md_responsibility_centers
- md_ro_statuses - md_ro_statuses
- md_tasks_presets
- md_to_emails - md_to_emails
- pbs_configuration - pbs_configuration
- phone - phone
@@ -1034,6 +1061,7 @@
- tt_enforce_hours_for_tech_console - tt_enforce_hours_for_tech_console
- updated_at - updated_at
- use_fippa - use_fippa
- use_paint_scale_data
- uselocalmediaserver - uselocalmediaserver
- website - website
- workingdays - workingdays
@@ -4054,6 +4082,29 @@
template_engine: Kriti template_engine: Kriti
url: '{{$base_url}}/record-handler/arms' url: '{{$base_url}}/record-handler/arms'
version: 2 version: 2
- name: os_jobs
definition:
delete:
columns: '*'
enable_manual: false
insert:
columns: '*'
update:
columns: '*'
retry_conf:
interval_sec: 10
num_retries: 3
timeout_sec: 60
webhook_from_env: HASURA_API_URL
headers:
- name: event-secret
value_from_env: EVENT_SECRET
request_transform:
method: POST
query_params: {}
template_engine: Kriti
url: '{{$base_url}}/opensearch'
version: 2
- table: - table:
name: masterdata name: masterdata
schema: public schema: public
@@ -4478,6 +4529,30 @@
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
- active: - active:
_eq: true _eq: true
event_triggers:
- name: os_owners
definition:
delete:
columns: '*'
enable_manual: false
insert:
columns: '*'
update:
columns: '*'
retry_conf:
interval_sec: 10
num_retries: 3
timeout_sec: 60
webhook_from_env: HASURA_API_URL
headers:
- name: event-secret
value_from_env: EVENT_SECRET
request_transform:
method: POST
query_params: {}
template_engine: Kriti
url: '{{$base_url}}/opensearch'
version: 2
- table: - table:
name: parts_order_lines name: parts_order_lines
schema: public schema: public
@@ -4901,6 +4976,30 @@
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
- active: - active:
_eq: true _eq: true
event_triggers:
- name: os_payments
definition:
delete:
columns: '*'
enable_manual: false
insert:
columns: '*'
update:
columns: '*'
retry_conf:
interval_sec: 10
num_retries: 3
timeout_sec: 60
webhook_from_env: HASURA_API_URL
headers:
- name: event-secret
value_from_env: EVENT_SECRET
request_transform:
method: POST
query_params: {}
template_engine: Kriti
url: '{{$base_url}}/opensearch'
version: 2
- table: - table:
name: phonebook name: phonebook
schema: public schema: public
@@ -5231,14 +5330,12 @@
- name: job - name: job
using: using:
foreign_key_constraint_on: jobid foreign_key_constraint_on: jobid
array_relationships: - name: tt_approval_queue
- name: tt_approval_queues
using: using:
foreign_key_constraint_on: foreign_key_constraint_on: ttapprovalqueueid
column: timeticketid - name: user
table: using:
name: tt_approval_queue foreign_key_constraint_on: commited_by
schema: public
insert_permissions: insert_permissions:
- role: user - role: user
permission: permission:
@@ -5257,6 +5354,7 @@
- ciecacode - ciecacode
- clockoff - clockoff
- clockon - clockon
- commited_by
- committed_at - committed_at
- cost_center - cost_center
- created_at - created_at
@@ -5268,6 +5366,7 @@
- memo - memo
- productivehrs - productivehrs
- rate - rate
- ttapprovalqueueid
- updated_at - updated_at
select_permissions: select_permissions:
- role: user - role: user
@@ -5278,6 +5377,7 @@
- ciecacode - ciecacode
- clockoff - clockoff
- clockon - clockon
- commited_by
- committed_at - committed_at
- cost_center - cost_center
- created_at - created_at
@@ -5289,6 +5389,7 @@
- memo - memo
- productivehrs - productivehrs
- rate - rate
- ttapprovalqueueid
- updated_at - updated_at
filter: filter:
bodyshop: bodyshop:
@@ -5308,6 +5409,7 @@
- ciecacode - ciecacode
- clockoff - clockoff
- clockon - clockon
- commited_by
- committed_at - committed_at
- cost_center - cost_center
- created_at - created_at
@@ -5319,6 +5421,7 @@
- memo - memo
- productivehrs - productivehrs
- rate - rate
- ttapprovalqueueid
- updated_at - updated_at
filter: filter:
bodyshop: bodyshop:
@@ -5442,12 +5545,17 @@
- name: job - name: job
using: using:
foreign_key_constraint_on: jobid foreign_key_constraint_on: jobid
- name: timeticket
using:
foreign_key_constraint_on: timeticketid
- name: user - name: user
using: using:
foreign_key_constraint_on: approved_by foreign_key_constraint_on: approved_by
array_relationships:
- name: timetickets
using:
foreign_key_constraint_on:
column: ttapprovalqueueid
table:
name: timetickets
schema: public
insert_permissions: insert_permissions:
- role: user - role: user
permission: permission:
@@ -5461,44 +5569,42 @@
authid: authid:
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
columns: columns:
- id
- created_at
- updated_at
- bodyshopid
- jobid
- employeeid
- timeticketid
- approved_by
- approved_at
- actualhrs - actualhrs
- productivehrs - approved_at
- rate - approved_by
- flat_rate - bodyshopid
- ciecacode - ciecacode
- cost_center - cost_center
- created_at
- date - date
- employeeid
- flat_rate
- id
- jobid
- memo - memo
- productivehrs
- rate
- updated_at
select_permissions: select_permissions:
- role: user - role: user
permission: permission:
columns: columns:
- flat_rate
- date
- actualhrs - actualhrs
- productivehrs - approved_at
- rate
- approved_by - approved_by
- bodyshopid
- ciecacode - ciecacode
- cost_center - cost_center
- memo
- approved_at
- created_at - created_at
- updated_at - date
- bodyshopid
- employeeid - employeeid
- flat_rate
- id - id
- jobid - jobid
- timeticketid - memo
- productivehrs
- rate
- updated_at
filter: filter:
bodyshop: bodyshop:
associations: associations:
@@ -5508,27 +5614,27 @@
- user: - user:
authid: authid:
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
allow_aggregations: true
update_permissions: update_permissions:
- role: user - role: user
permission: permission:
columns: columns:
- flat_rate
- date
- actualhrs - actualhrs
- productivehrs - approved_at
- rate
- approved_by - approved_by
- bodyshopid
- ciecacode - ciecacode
- cost_center - cost_center
- memo
- approved_at
- created_at - created_at
- updated_at - date
- bodyshopid
- employeeid - employeeid
- flat_rate
- id - id
- jobid - jobid
- timeticketid - memo
- productivehrs
- rate
- updated_at
filter: filter:
bodyshop: bodyshop:
associations: associations:
@@ -5609,6 +5715,13 @@
table: table:
name: parts_orders name: parts_orders
schema: public schema: public
- name: timetickets
using:
foreign_key_constraint_on:
column: commited_by
table:
name: timetickets
schema: public
- name: tt_approval_queues - name: tt_approval_queues
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
@@ -5801,6 +5914,30 @@
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
- active: - active:
_eq: true _eq: true
event_triggers:
- name: os_vehicles
definition:
delete:
columns: '*'
enable_manual: false
insert:
columns: '*'
update:
columns: '*'
retry_conf:
interval_sec: 10
num_retries: 3
timeout_sec: 60
webhook_from_env: HASURA_API_URL
headers:
- name: event-secret
value_from_env: EVENT_SECRET
request_transform:
method: POST
query_params: {}
template_engine: Kriti
url: '{{$base_url}}/opensearch'
version: 2
- table: - table:
name: vendors name: vendors
schema: public schema: public

View File

@@ -0,0 +1 @@
alter table "public"."tt_approval_queue" alter column "approved_at" set not null;

View File

@@ -0,0 +1 @@
alter table "public"."tt_approval_queue" alter column "approved_at" drop not null;

View File

@@ -0,0 +1 @@
alter table "public"."tt_approval_queue" alter column "memo" set not null;

View File

@@ -0,0 +1 @@
alter table "public"."tt_approval_queue" alter column "memo" drop not null;

View File

@@ -0,0 +1,5 @@
alter table "public"."tt_approval_queue"
add constraint "tt_approval_queue_timeticketid_fkey"
foreign key ("timeticketid")
references "public"."timetickets"
("id") on update cascade on delete cascade;

View File

@@ -0,0 +1 @@
alter table "public"."tt_approval_queue" drop constraint "tt_approval_queue_timeticketid_fkey";

View File

@@ -0,0 +1,2 @@
alter table "public"."tt_approval_queue" alter column "timeticketid" drop not null;
alter table "public"."tt_approval_queue" add column "timeticketid" uuid;

View File

@@ -0,0 +1 @@
alter table "public"."tt_approval_queue" drop column "timeticketid" cascade;

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."timetickets" add column "ttapprovalqueueid" uuid
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."timetickets" add column "ttapprovalqueueid" uuid
null;

View File

@@ -0,0 +1 @@
alter table "public"."timetickets" drop constraint "timetickets_ttapprovalqueueid_fkey";

View File

@@ -0,0 +1,5 @@
alter table "public"."timetickets"
add constraint "timetickets_ttapprovalqueueid_fkey"
foreign key ("ttapprovalqueueid")
references "public"."tt_approval_queue"
("id") on update cascade on delete set null;

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."timetickets" add column "commited_by" text
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."timetickets" add column "commited_by" text
null;

View File

@@ -0,0 +1 @@
alter table "public"."timetickets" drop constraint "timetickets_commited_by_fkey";

View File

@@ -0,0 +1,5 @@
alter table "public"."timetickets"
add constraint "timetickets_commited_by_fkey"
foreign key ("commited_by")
references "public"."users"
("email") on update restrict on delete restrict;

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."bodyshops" add column "use_paint_scale_data" boolean
-- not null default 'false';

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "use_paint_scale_data" boolean
not null default 'false';

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."jobs_idx_status_hash";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "jobs_idx_status_hash" on
"public"."jobs" using hash ("status");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."idx_jobs_ronumber_btree";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "idx_jobs_ronumber_btree" on
"public"."jobs" using btree ("ro_number");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."job_conversations_jobid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "job_conversations_jobid" on
"public"."job_conversations" using btree ("jobid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."job_conversations_conversationid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "job_conversations_conversationid" on
"public"."job_conversations" using btree ("conversationid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."job_conversations_job_and_conversation_id";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "job_conversations_job_and_conversation_id" on
"public"."job_conversations" using btree ("conversationid", "jobid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."idx_payments_jobid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "idx_payments_jobid" on
"public"."payments" using btree ("jobid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."idx_mixdata_jobid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "idx_mixdata_jobid" on
"public"."mixdata" using btree ("jobid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."idx_notes_jobid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "idx_notes_jobid" on
"public"."notes" using btree ("jobid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."idx_users_authid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "idx_users_authid" on
"public"."users" using btree ("authid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."idx_employees_shopid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "idx_employees_shopid" on
"public"."employees" using btree ("shopid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."idx_employee_vacation_employeeid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "idx_employee_vacation_employeeid" on
"public"."employee_vacation" using btree ("employeeid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."idx_counters_shopid_type";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "idx_counters_shopid_type" on
"public"."counters" using btree ("shopid", "countertype");

View File

@@ -0,0 +1,3 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE INDEX idx_jobs_inproduction_true ON jobs(inproduction) WHERE inproduction = true;

View File

@@ -0,0 +1 @@
CREATE INDEX idx_jobs_inproduction_true ON jobs(inproduction) WHERE inproduction = true;

View File

@@ -0,0 +1,3 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE INDEX idx_associations_active_true ON associations(active) WHERE active = true;

View File

@@ -0,0 +1 @@
CREATE INDEX idx_associations_active_true ON associations(active) WHERE active = true;

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."idx_associations_shopid_user";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "idx_associations_shopid_user" on
"public"."associations" using btree ("shopid", "useremail", "active");

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."bodyshops" add column "md_tasks_presets" jsonb
-- not null default jsonb_build_object();

Some files were not shown because too many files have changed in this diff Show More