Merged in development (pull request #28)
Reorder totals table IO-734 Resolve null parts status reference. IO-737 Allow time ticket entry for RO jobs IO-741 Technician clock issues IO-731 Updates for audatex claims & mapa/mash calculations IO-718 Partial fixes to jobline upsert & totals calculation. IO-730 Clear Errors & Update CI
This commit is contained in:
@@ -22603,6 +22603,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>shiftclockin</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>
|
||||||
</children>
|
</children>
|
||||||
@@ -25619,7 +25640,7 @@
|
|||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>filing_coverhseet_portrait</name>
|
<name>filing_coversheet_portrait</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
<description></description>
|
<description></description>
|
||||||
<comment></comment>
|
<comment></comment>
|
||||||
|
|||||||
@@ -47,13 +47,14 @@ export function JobLinesComponent({
|
|||||||
form,
|
form,
|
||||||
}) {
|
}) {
|
||||||
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
|
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
loading: billLinesLoading,
|
loading: billLinesLoading,
|
||||||
error: billLinesError,
|
error: billLinesError,
|
||||||
data: billLinesData,
|
data: billLinesData,
|
||||||
} = useQuery(QUERY_BILLS_BY_JOB_REF, {
|
} = useQuery(QUERY_BILLS_BY_JOB_REF, {
|
||||||
variables: { jobId: job.id },
|
variables: { jobId: job && job.id },
|
||||||
skip: loading,
|
skip: loading || !job,
|
||||||
});
|
});
|
||||||
|
|
||||||
const billLinesDataObj = useMemo(() => {
|
const billLinesDataObj = useMemo(() => {
|
||||||
@@ -299,40 +300,43 @@ export function JobLinesComponent({
|
|||||||
dataIndex: "actions",
|
dataIndex: "actions",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Space>
|
<div>
|
||||||
<Button
|
|
||||||
disabled={jobRO}
|
|
||||||
onClick={() => {
|
|
||||||
setJobLineEditContext({
|
|
||||||
actions: { refetch: refetch, submit: form && form.submit },
|
|
||||||
context: record,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("general.actions.edit")}
|
|
||||||
</Button>
|
|
||||||
{record.manual_line && (
|
{record.manual_line && (
|
||||||
<Button
|
<Space >
|
||||||
onClick={() =>
|
<Button
|
||||||
deleteJobLine({
|
disabled={jobRO}
|
||||||
variables: { joblineId: record.id },
|
onClick={() => {
|
||||||
update(cache) {
|
setJobLineEditContext({
|
||||||
cache.modify({
|
actions: { refetch: refetch, submit: form && form.submit },
|
||||||
id: cache.identify(job),
|
context: record,
|
||||||
fields: {
|
});
|
||||||
joblines(existingJobLines, { readField }) {
|
}}
|
||||||
return existingJobLines.filter(
|
>
|
||||||
(jlRef) => record.id !== readField("id", jlRef)
|
{t("general.actions.edit")}
|
||||||
);
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={jobRO}
|
||||||
|
onClick={() =>
|
||||||
|
deleteJobLine({
|
||||||
|
variables: { joblineId: record.id },
|
||||||
|
update(cache) {
|
||||||
|
cache.modify({
|
||||||
|
id: cache.identify(job),
|
||||||
|
fields: {
|
||||||
|
joblines(existingJobLines, { readField }) {
|
||||||
|
return existingJobLines.filter(
|
||||||
|
(jlRef) => record.id !== readField("id", jlRef)
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
},
|
||||||
},
|
})
|
||||||
})
|
}
|
||||||
}
|
>
|
||||||
>
|
<DeleteFilled />
|
||||||
<DeleteFilled />
|
</Button>
|
||||||
</Button>
|
</Space>
|
||||||
)}
|
)}
|
||||||
{
|
{
|
||||||
// <AllocationsAssignmentContainer
|
// <AllocationsAssignmentContainer
|
||||||
@@ -342,7 +346,7 @@ export function JobLinesComponent({
|
|||||||
// hours={record.mod_lb_hrs}
|
// hours={record.mod_lb_hrs}
|
||||||
// />
|
// />
|
||||||
}
|
}
|
||||||
</Space>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export function JobLineStatusPopup({ bodyshop, jobline, disabled }) {
|
|||||||
onSelect={handleChange}
|
onSelect={handleChange}
|
||||||
onBlur={handleSave}
|
onBlur={handleSave}
|
||||||
>
|
>
|
||||||
{bodyshop.md_order_statuses.statuses.map((s, idx) => (
|
{Object.values(bodyshop.md_order_statuses).map((s, idx) => (
|
||||||
<Select.Option key={idx} value={s}>
|
<Select.Option key={idx} value={s}>
|
||||||
{s}
|
{s}
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "../../graphql/jobs-lines.queries";
|
} from "../../graphql/jobs-lines.queries";
|
||||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
import { selectJobLineEditModal } from "../../redux/modals/modals.selectors";
|
import { selectJobLineEditModal } from "../../redux/modals/modals.selectors";
|
||||||
|
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||||
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
|
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -39,7 +40,7 @@ function JobLinesUpsertModalContainer({
|
|||||||
manual_line: !(
|
manual_line: !(
|
||||||
jobLineEditModal.context && jobLineEditModal.context.id
|
jobLineEditModal.context && jobLineEditModal.context.id
|
||||||
),
|
),
|
||||||
...values,
|
...UndefinedToNull(values),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -267,6 +267,14 @@ export function JobsTotalsTableComponent({ bodyshop, jobRO, job }) {
|
|||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{t("jobs.labels.subtotal")}</td>
|
||||||
|
<td className="currency">
|
||||||
|
<strong>
|
||||||
|
{Dinero(job.job_totals.totals.subtotal).toFormat()}
|
||||||
|
</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{t("jobs.labels.local_tax_amt")}</td>
|
<td>{t("jobs.labels.local_tax_amt")}</td>
|
||||||
<td className="currency">
|
<td className="currency">
|
||||||
@@ -317,26 +325,18 @@ export function JobsTotalsTableComponent({ bodyshop, jobRO, job }) {
|
|||||||
).toFormat()}
|
).toFormat()}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>{t("jobs.labels.total_cust_payable")}</td>
|
|
||||||
<td className="currency">
|
|
||||||
{Dinero(job.job_totals.totals.custPayable.total).toFormat()}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{t("jobs.labels.subtotal")}</td>
|
|
||||||
<td className="currency">
|
|
||||||
<strong>
|
|
||||||
{Dinero(job.job_totals.totals.subtotal).toFormat()}
|
|
||||||
</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>{t("jobs.labels.total_repairs")}</td>
|
<td>{t("jobs.labels.total_repairs")}</td>
|
||||||
<td className="currency">
|
<td className="currency">
|
||||||
{Dinero(job.job_totals.totals.total_repairs).toFormat()}
|
{Dinero(job.job_totals.totals.total_repairs).toFormat()}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{t("jobs.labels.total_cust_payable")}</td>
|
||||||
|
<td className="currency">
|
||||||
|
{Dinero(job.job_totals.totals.custPayable.total).toFormat()}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{t("jobs.labels.net_repairs")}</td>
|
<td>{t("jobs.labels.net_repairs")}</td>
|
||||||
<td className="currency">
|
<td className="currency">
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
label={t("jobs.fields.referralsource")}
|
label={t("jobs.fields.referralsource")}
|
||||||
name="referral_source"
|
name="referral_source"
|
||||||
>
|
>
|
||||||
<Select disabled={jobRO}>
|
<Select disabled={jobRO} allowClear>
|
||||||
{bodyshop.md_referral_sources.map((s) => (
|
{bodyshop.md_referral_sources.map((s) => (
|
||||||
<Select.Option key={s} value={s}>
|
<Select.Option key={s} value={s}>
|
||||||
{s}
|
{s}
|
||||||
@@ -106,7 +106,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.alt_transport")} name="alt_transport">
|
<Form.Item label={t("jobs.fields.alt_transport")} name="alt_transport">
|
||||||
<Select disabled={jobRO}>
|
<Select disabled={jobRO} allowClear>
|
||||||
{bodyshop.appt_alt_transport.map((s) => (
|
{bodyshop.appt_alt_transport.map((s) => (
|
||||||
<Select.Option key={s} value={s}>
|
<Select.Option key={s} value={s}>
|
||||||
{s}
|
{s}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
dispatch(setModalContext({ context: context, modal: "payment" })),
|
dispatch(setModalContext({ context: context, modal: "payment" })),
|
||||||
setJobCostingContext: (context) =>
|
setJobCostingContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "jobCosting" })),
|
dispatch(setModalContext({ context: context, modal: "jobCosting" })),
|
||||||
|
setTimeTicketContext: (context) =>
|
||||||
|
dispatch(setModalContext({ context: context, modal: "timeTicket" })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsDetailHeaderActions({
|
export function JobsDetailHeaderActions({
|
||||||
@@ -39,6 +41,7 @@ export function JobsDetailHeaderActions({
|
|||||||
setPaymentContext,
|
setPaymentContext,
|
||||||
setJobCostingContext,
|
setJobCostingContext,
|
||||||
jobRO,
|
jobRO,
|
||||||
|
setTimeTicketContext,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
@@ -110,6 +113,19 @@ export function JobsDetailHeaderActions({
|
|||||||
{t("jobs.actions.viewchecklist")}
|
{t("jobs.actions.viewchecklist")}
|
||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
<Menu.Item
|
||||||
|
key="entertimetickets"
|
||||||
|
onClick={() => {
|
||||||
|
logImEXEvent("job_header_enter_time_ticekts");
|
||||||
|
|
||||||
|
setTimeTicketContext({
|
||||||
|
actions: {},
|
||||||
|
context: { jobId: job.id },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("timetickets.actions.enter")}
|
||||||
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key="enterpayments"
|
key="enterpayments"
|
||||||
disabled={jobRO}
|
disabled={jobRO}
|
||||||
|
|||||||
@@ -27,11 +27,7 @@ export function JobsDetailLaborContainer({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{techConsole ? null : (
|
{techConsole ? null : (
|
||||||
<TimeTicketEnterButton
|
<TimeTicketEnterButton actions={{ refetch }} context={{ jobId: jobId }}>
|
||||||
disabled={jobRO}
|
|
||||||
actions={{ refetch }}
|
|
||||||
context={{ jobId: jobId }}
|
|
||||||
>
|
|
||||||
{t("timetickets.actions.enter")}
|
{t("timetickets.actions.enter")}
|
||||||
</TimeTicketEnterButton>
|
</TimeTicketEnterButton>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -86,17 +86,13 @@ export default function JobsFindModalComponent({
|
|||||||
key: "vehicle",
|
key: "vehicle",
|
||||||
width: "15%",
|
width: "15%",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (text, record) => {
|
render: (text, record) => (
|
||||||
return record.vehicle ? (
|
<Link to={"/manage/vehicles/" + record.vehicleid}>
|
||||||
<Link to={"/manage/vehicles/" + record.vehicleid}>
|
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
record.v_model_desc || ""
|
||||||
record.v_model_desc || ""
|
}`}
|
||||||
}`}
|
</Link>
|
||||||
</Link>
|
),
|
||||||
) : (
|
|
||||||
t("jobs.errors.novehicle")
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("vehicles.fields.plate_no"),
|
title: t("vehicles.fields.plate_no"),
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
import { Form } from "antd";
|
import { Form, Select } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||||
import JobsDetailLaborContainer from "../jobs-detail-labor/jobs-detail-labor.container";
|
import JobsDetailLaborContainer from "../jobs-detail-labor/jobs-detail-labor.container";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
|
technician: selectTechnician,
|
||||||
});
|
});
|
||||||
|
|
||||||
export function TechClockInComponent({ form, bodyshop }) {
|
export function TechClockInComponent({ form, bodyshop, technician }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const emps = bodyshop.employees.filter((e) => e.id === technician.id)[0];
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -29,6 +31,26 @@ export function TechClockInComponent({ form, bodyshop }) {
|
|||||||
<JobSearchSelect />
|
<JobSearchSelect />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="cost_center"
|
||||||
|
label={t("timetickets.fields.cost_center")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
{emps &&
|
||||||
|
emps.rates.map((item) => (
|
||||||
|
<Select.Option key={item.cost_center}>
|
||||||
|
{item.cost_center}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
shouldUpdate={(prevValues, curValues) =>
|
shouldUpdate={(prevValues, curValues) =>
|
||||||
prevValues.jobid !== curValues.jobid
|
prevValues.jobid !== curValues.jobid
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export function TechClockInContainer({ technician, bodyshop }) {
|
|||||||
date: theTime,
|
date: theTime,
|
||||||
clockon: theTime,
|
clockon: theTime,
|
||||||
jobid: values.jobid,
|
jobid: values.jobid,
|
||||||
cost_center: technician.cost_center,
|
cost_center: values.cost_center,
|
||||||
ciecacode: Object.keys(
|
ciecacode: Object.keys(
|
||||||
bodyshop.md_responsibility_centers.defaults.costs
|
bodyshop.md_responsibility_centers.defaults.costs
|
||||||
).find((key) => {
|
).find((key) => {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export function TechClockOffButton({
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const emps = bodyshop.employees.filter((e) => e.id === technician.id)[0];
|
||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
logImEXEvent("tech_clock_out_job");
|
logImEXEvent("tech_clock_out_job");
|
||||||
@@ -120,9 +121,10 @@ export function TechClockOffButton({
|
|||||||
{t("timetickets.labels.shift")}
|
{t("timetickets.labels.shift")}
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
) : (
|
) : (
|
||||||
bodyshop.md_responsibility_centers.costs.map((i, idx) => (
|
emps &&
|
||||||
<Select.Option key={idx} value={i.name}>
|
emps.rates.map((item) => (
|
||||||
{i.name}
|
<Select.Option key={item.cost_center}>
|
||||||
|
{item.cost_center}
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -81,6 +81,9 @@ export function TechClockedInList({ technician }) {
|
|||||||
<DataLabel label={t("timetickets.fields.clockon")}>
|
<DataLabel label={t("timetickets.fields.clockon")}>
|
||||||
<DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
|
<DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
|
<DataLabel label={t("timetickets.fields.cost_center")}>
|
||||||
|
{ticket.cost_center}{" "}
|
||||||
|
</DataLabel>
|
||||||
</Card>
|
</Card>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PrinterFilled } from "@ant-design/icons";
|
import { PrinterFilled } from "@ant-design/icons";
|
||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { Button, Col, Drawer, Grid, PageHeader, Row, Tag, Tabs } from "antd";
|
import { Button, Drawer, Grid, PageHeader, Tabs, Tag } from "antd";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -9,26 +9,26 @@ import { Link, useHistory, useLocation } from "react-router-dom";
|
|||||||
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
|
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
|
||||||
import OwnerTagPopoverComponent from "../owner-tag-popover/owner-tag-popover.component";
|
|
||||||
import VehicleTagPopoverComponent from "../vehicle-tag-popover/vehicle-tag-popover.component";
|
|
||||||
import JobLinesContainer from "../job-detail-lines/job-lines.container";
|
import JobLinesContainer from "../job-detail-lines/job-lines.container";
|
||||||
import JobsDocumentsGalleryContainer from "../jobs-documents-gallery/jobs-documents-gallery.container";
|
import JobsDocumentsGalleryContainer from "../jobs-documents-gallery/jobs-documents-gallery.container";
|
||||||
import JobNotesContainer from "../jobs-notes/jobs-notes.container";
|
import JobNotesContainer from "../jobs-notes/jobs-notes.container";
|
||||||
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
|
import OwnerTagPopoverComponent from "../owner-tag-popover/owner-tag-popover.component";
|
||||||
|
import VehicleTagPopoverComponent from "../vehicle-tag-popover/vehicle-tag-popover.component";
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPrintCenterContext: (context) =>
|
setPrintCenterContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
||||||
});
|
});
|
||||||
|
|
||||||
const colBreakPoints = {
|
// const colBreakPoints = {
|
||||||
xs: {
|
// xs: {
|
||||||
span: 24,
|
// span: 24,
|
||||||
},
|
// },
|
||||||
sm: {
|
// sm: {
|
||||||
span: 8,
|
// span: 8,
|
||||||
},
|
// },
|
||||||
};
|
// };
|
||||||
|
|
||||||
export function JobDetailCards({ setPrintCenterContext }) {
|
export function JobDetailCards({ setPrintCenterContext }) {
|
||||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||||
@@ -116,16 +116,12 @@ export function JobDetailCards({ setPrintCenterContext }) {
|
|||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Row gutter={[16, 16]}>
|
|
||||||
<Col {...colBreakPoints}> What would be good to have here?</Col>
|
|
||||||
<Col {...colBreakPoints}> What would be good to have here?</Col>
|
|
||||||
<Col {...colBreakPoints}> What would be good to have here?</Col>
|
|
||||||
</Row>
|
|
||||||
<Tabs size="large">
|
<Tabs size="large">
|
||||||
<Tabs.TabPane key="lines" tab={t("jobs.labels.lines")}>
|
<Tabs.TabPane key="lines" tab={t("jobs.labels.lines")}>
|
||||||
<JobLinesContainer
|
<JobLinesContainer
|
||||||
jobId={selected}
|
jobId={selected}
|
||||||
joblines={data.jobs_by_pk.joblines}
|
joblines={data.jobs_by_pk.joblines}
|
||||||
|
job={data.jobs_by_pk}
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
|||||||
@@ -13,10 +13,8 @@ import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
|
|||||||
|
|
||||||
export default function TimeTicketModalComponent({
|
export default function TimeTicketModalComponent({
|
||||||
form,
|
form,
|
||||||
roAutoCompleteOptions,
|
|
||||||
employeeAutoCompleteOptions,
|
employeeAutoCompleteOptions,
|
||||||
loadLineTicketData,
|
|
||||||
lineTicketData,
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -33,7 +31,7 @@ export default function TimeTicketModalComponent({
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<JobSearchSelect options={roAutoCompleteOptions} />
|
<JobSearchSelect convertedOnly notExported={false} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("timetickets.fields.date")}
|
label={t("timetickets.fields.date")}
|
||||||
|
|||||||
@@ -346,6 +346,7 @@ export const GET_JOB_BY_PK = gql`
|
|||||||
inproduction
|
inproduction
|
||||||
vehicleid
|
vehicleid
|
||||||
plate_no
|
plate_no
|
||||||
|
plate_st
|
||||||
v_vin
|
v_vin
|
||||||
v_model_yr
|
v_model_yr
|
||||||
v_model_desc
|
v_model_desc
|
||||||
@@ -357,6 +358,7 @@ export const GET_JOB_BY_PK = gql`
|
|||||||
vehicle {
|
vehicle {
|
||||||
id
|
id
|
||||||
plate_no
|
plate_no
|
||||||
|
plate_st
|
||||||
v_vin
|
v_vin
|
||||||
v_model_yr
|
v_model_yr
|
||||||
v_model_desc
|
v_model_desc
|
||||||
@@ -828,7 +830,6 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
|
|||||||
search_jobs(
|
search_jobs(
|
||||||
args: { search: $search }
|
args: { search: $search }
|
||||||
limit: 50
|
limit: 50
|
||||||
order_by: { ro_number: desc_nulls_last }
|
|
||||||
where: {
|
where: {
|
||||||
_and: {
|
_and: {
|
||||||
converted: { _eq: $isConverted }
|
converted: { _eq: $isConverted }
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ export const QUERY_ACTIVE_TIME_TICKETS = gql`
|
|||||||
id
|
id
|
||||||
clockon
|
clockon
|
||||||
memo
|
memo
|
||||||
|
cost_center
|
||||||
job {
|
job {
|
||||||
id
|
id
|
||||||
|
|
||||||
|
|||||||
@@ -1344,7 +1344,8 @@
|
|||||||
"login": "Login",
|
"login": "Login",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"productionboard": "Production Board - Visual",
|
"productionboard": "Production Board - Visual",
|
||||||
"productionlist": "Production List"
|
"productionlist": "Production List",
|
||||||
|
"shiftclockin": "Shift Clock"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"messaging": {
|
"messaging": {
|
||||||
@@ -1552,7 +1553,7 @@
|
|||||||
"diagnostic_authorization": "Diagnostic Authorization",
|
"diagnostic_authorization": "Diagnostic Authorization",
|
||||||
"estimate": "Estimate Only",
|
"estimate": "Estimate Only",
|
||||||
"estimate_detail": "Estimate Details",
|
"estimate_detail": "Estimate Details",
|
||||||
"filing_coverhseet_portrait": "Filing Coversheet (Portrait)",
|
"filing_coversheet_portrait": "Filing Coversheet (Portrait)",
|
||||||
"final_invoice": "Final Invoice",
|
"final_invoice": "Final Invoice",
|
||||||
"fippa_authorization": "FIPPA Authorization",
|
"fippa_authorization": "FIPPA Authorization",
|
||||||
"glass_express_checklist": "Glass Express Checklist",
|
"glass_express_checklist": "Glass Express Checklist",
|
||||||
|
|||||||
@@ -1344,7 +1344,8 @@
|
|||||||
"login": "",
|
"login": "",
|
||||||
"logout": "",
|
"logout": "",
|
||||||
"productionboard": "",
|
"productionboard": "",
|
||||||
"productionlist": ""
|
"productionlist": "",
|
||||||
|
"shiftclockin": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"messaging": {
|
"messaging": {
|
||||||
@@ -1552,7 +1553,7 @@
|
|||||||
"diagnostic_authorization": "",
|
"diagnostic_authorization": "",
|
||||||
"estimate": "",
|
"estimate": "",
|
||||||
"estimate_detail": "",
|
"estimate_detail": "",
|
||||||
"filing_coverhseet_portrait": "",
|
"filing_coversheet_portrait": "",
|
||||||
"final_invoice": "",
|
"final_invoice": "",
|
||||||
"fippa_authorization": "",
|
"fippa_authorization": "",
|
||||||
"glass_express_checklist": "",
|
"glass_express_checklist": "",
|
||||||
|
|||||||
@@ -1344,7 +1344,8 @@
|
|||||||
"login": "",
|
"login": "",
|
||||||
"logout": "",
|
"logout": "",
|
||||||
"productionboard": "",
|
"productionboard": "",
|
||||||
"productionlist": ""
|
"productionlist": "",
|
||||||
|
"shiftclockin": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"messaging": {
|
"messaging": {
|
||||||
@@ -1552,7 +1553,7 @@
|
|||||||
"diagnostic_authorization": "",
|
"diagnostic_authorization": "",
|
||||||
"estimate": "",
|
"estimate": "",
|
||||||
"estimate_detail": "",
|
"estimate_detail": "",
|
||||||
"filing_coverhseet_portrait": "",
|
"filing_coversheet_portrait": "",
|
||||||
"final_invoice": "",
|
"final_invoice": "",
|
||||||
"fippa_authorization": "",
|
"fippa_authorization": "",
|
||||||
"glass_express_checklist": "",
|
"glass_express_checklist": "",
|
||||||
|
|||||||
@@ -42,7 +42,11 @@ export default async function RenderTemplate(
|
|||||||
try {
|
try {
|
||||||
const render = await jsreport.renderAsync(reportRequest);
|
const render = await jsreport.renderAsync(reportRequest);
|
||||||
if (!renderAsHtml) {
|
if (!renderAsHtml) {
|
||||||
render.download(Templates[templateObject.name].title || "");
|
render.download(
|
||||||
|
(Templates[templateObject.name] &&
|
||||||
|
Templates[templateObject.name].title) ||
|
||||||
|
""
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
resolve(render.toString());
|
resolve(render.toString());
|
||||||
|
|||||||
@@ -167,11 +167,11 @@ export const TemplateList = (type, context) => {
|
|||||||
key: "coversheet_portrait",
|
key: "coversheet_portrait",
|
||||||
disabled: false,
|
disabled: false,
|
||||||
},
|
},
|
||||||
filing_coverhseet_portrait: {
|
filing_coversheet_portrait: {
|
||||||
title: i18n.t("printcenter.jobs.filing_coverhseet_portrait"),
|
title: i18n.t("printcenter.jobs.filing_coversheet_portrait"),
|
||||||
description: "All Jobs Notes",
|
description: "All Jobs Notes",
|
||||||
subject: i18n.t("printcenter.jobs.filing_coverhseet_portrait"),
|
subject: i18n.t("printcenter.jobs.filing_coversheet_portrait"),
|
||||||
key: "filing_coverhseet_portrait",
|
key: "filing_coversheet_portrait",
|
||||||
disabled: false,
|
disabled: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
6
client/src/utils/undefinedtonull.js
Normal file
6
client/src/utils/undefinedtonull.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default function UndefinedToNull(obj) {
|
||||||
|
Object.keys(obj).forEach((key) => {
|
||||||
|
if (obj[key] === undefined) obj[key] = null;
|
||||||
|
});
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
@@ -41,10 +41,10 @@ exports.totalsSsu = async function (req, res) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(200);
|
res.status(200).send();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
res.status(503);
|
res.status(503).send();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -269,7 +269,13 @@ function IsAdditionalCost(jobLine) {
|
|||||||
//936008 is Paint/Materials
|
//936008 is Paint/Materials
|
||||||
//936007 is Shop/Materials
|
//936007 is Shop/Materials
|
||||||
|
|
||||||
return !jobLine.db_ref || jobLine.db_ref.startsWith("9360");
|
//Remove paint and shop mat lines. They're calculated under rates.
|
||||||
|
const isPaintOrShopMat =
|
||||||
|
jobLine.db_ref === "936008" || jobLine.db_ref === "936007";
|
||||||
|
|
||||||
|
return (
|
||||||
|
!jobLine.db_ref || (jobLine.db_ref.startsWith("9360") && !isPaintOrShopMat)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CalculateAdditional(job) {
|
function CalculateAdditional(job) {
|
||||||
@@ -305,7 +311,7 @@ function CalculateAdditional(job) {
|
|||||||
function CalculateTaxesTotals(job, otherTotals) {
|
function CalculateTaxesTotals(job, otherTotals) {
|
||||||
const subtotal = otherTotals.parts.parts.subtotal
|
const subtotal = otherTotals.parts.parts.subtotal
|
||||||
.add(otherTotals.parts.sublets.subtotal)
|
.add(otherTotals.parts.sublets.subtotal)
|
||||||
.add(otherTotals.rates.rates_subtotal)
|
.add(otherTotals.rates.subtotal) //No longer using just rates subtotal to include mapa/mash.
|
||||||
.add(otherTotals.additional);
|
.add(otherTotals.additional);
|
||||||
// .add(Dinero({ amount: (job.towing_payable || 0) * 100 }))
|
// .add(Dinero({ amount: (job.towing_payable || 0) * 100 }))
|
||||||
// .add(Dinero({ amount: (job.storage_payable || 0) * 100 }));
|
// .add(Dinero({ amount: (job.storage_payable || 0) * 100 }));
|
||||||
@@ -319,7 +325,7 @@ function CalculateTaxesTotals(job, otherTotals) {
|
|||||||
job.joblines
|
job.joblines
|
||||||
.filter((jl) => !jl.removed)
|
.filter((jl) => !jl.removed)
|
||||||
.forEach((val) => {
|
.forEach((val) => {
|
||||||
if (!val.tax_part || !val.part_type || IsAdditionalCost(val)) {
|
if (!val.tax_part || (!val.part_type && IsAdditionalCost(val))) {
|
||||||
additionalItemsTax = additionalItemsTax.add(
|
additionalItemsTax = additionalItemsTax.add(
|
||||||
Dinero({ amount: Math.round((val.act_price || 0) * 100) })
|
Dinero({ amount: Math.round((val.act_price || 0) * 100) })
|
||||||
.multiply(val.part_qty || 1)
|
.multiply(val.part_qty || 1)
|
||||||
@@ -350,7 +356,7 @@ function CalculateTaxesTotals(job, otherTotals) {
|
|||||||
statePartsTax,
|
statePartsTax,
|
||||||
state_tax: statePartsTax
|
state_tax: statePartsTax
|
||||||
.add(
|
.add(
|
||||||
otherTotals.rates.rates_subtotal.percentage((job.tax_lbr_rt || 0) * 100)
|
otherTotals.rates.subtotal.percentage((job.tax_lbr_rt || 0) * 100) // THis is currently using the lbr tax rate from PFH not PFL.
|
||||||
)
|
)
|
||||||
.add(
|
.add(
|
||||||
Dinero({
|
Dinero({
|
||||||
|
|||||||
Reference in New Issue
Block a user