Merged in release/2022-06-30 (pull request #528)

release/2022-06-30

Approved-by: Patrick Fic
This commit is contained in:
Patrick Fic
2022-06-29 22:05:24 +00:00
18 changed files with 400 additions and 31 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
@@ -17826,6 +17826,27 @@
<folder_node> <folder_node>
<name>actions</name> <name>actions</name>
<children> <children>
<concept_node>
<name>converttolabor</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>new</name> <name>new</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -19006,6 +19027,27 @@
<folder_node> <folder_node>
<name>labels</name> <name>labels</name>
<children> <children>
<concept_node>
<name>adjustmenttobeadded</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>billref</name> <name>billref</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -19027,6 +19069,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>convertedtolabor</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>edit</name> <name>edit</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -38,12 +38,6 @@ export function EmailDocumentsComponent({
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
}); });
console.log(
selectedMedia &&
selectedMedia
.filter((s) => s.isSelected)
.reduce((acc, val) => (acc = acc + val.size), 0)
);
return ( return (
<div> <div>
{loading && <LoadingSpinner />} {loading && <LoadingSpinner />}

View File

@@ -44,6 +44,7 @@ import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobLinesExpander from "./job-lines-expander.component"; import JobLinesExpander from "./job-lines-expander.component";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import moment from "moment"; import moment from "moment";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -295,9 +296,9 @@ export function JobLinesComponent({
dataIndex: "actions", dataIndex: "actions",
key: "actions", key: "actions",
render: (text, record) => ( render: (text, record) => (
<div> <Space>
{(record.manual_line || jobIsPrivate) && ( {(record.manual_line || jobIsPrivate) && (
<Space> <>
<Button <Button
disabled={jobRO} disabled={jobRO}
onClick={() => { onClick={() => {
@@ -334,9 +335,10 @@ export function JobLinesComponent({
> >
<DeleteFilled /> <DeleteFilled />
</Button> </Button>
</Space> </>
)} )}
</div> <JobLineConvertToLabor jobline={record} job={job} />
</Space>
), ),
}, },
]; ];

View File

@@ -0,0 +1,244 @@
import { ClockCircleOutlined } from "@ant-design/icons";
import { useApolloClient, useMutation } from "@apollo/client";
import {
Button,
Card,
Form,
InputNumber,
notification,
Popover,
Select,
Space,
Tooltip,
} from "antd";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import {
QUERY_JOB_LBR_ADJUSTMENTS,
UPDATE_JOB,
} from "../../graphql/jobs.queries";
import {
INSERT_SCOREBOARD_ENTRY,
UPDATE_SCOREBOARD_ENTRY,
} from "../../graphql/scoreboard.queries";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import LaborTypeFormItemComponent from "../form-items-formatted/labor-type-form-item.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { insertAuditTrail } from "../../redux/application/application.actions";
import _ from "lodash";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobLineConvertToLabor);
export function JobLineConvertToLabor({ jobline, job, ...otherBtnProps }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const [visibility, setVisibility] = useState(false);
const client = useApolloClient();
const handleFinish = async (values) => {
const { mod_lbr_ty } = values;
logImEXEvent("job_convert_dollar_to_labor");
setLoading(true);
const existingAdjustments = await client.query({
query: QUERY_JOB_LBR_ADJUSTMENTS,
variables: {
id: job.id,
},
});
const newAdjustments = _.cloneDeep(
existingAdjustments.data.jobs_by_pk.lbr_adjustments
);
newAdjustments[mod_lbr_ty] =
(newAdjustments[mod_lbr_ty] || 0) +
calculateAdjustment({ mod_lbr_ty, job, jobline });
const jobUpdate = client.mutate({
mutation: UPDATE_JOB,
variables: {
jobId: job.id,
job: { lbr_adjustments: newAdjustments },
},
});
const lineUpdate = client.mutate({
mutation: UPDATE_JOB_LINE,
variables: {
lineId: jobline.id,
line: { convertedtolbr: true },
},
});
if (!!jobUpdate.errors) {
notification["error"]({
message: t("jobs.errors.saving", {
message: JSON.stringify(jobUpdate.errors),
}),
});
return;
}
if (!!lineUpdate.errors) {
notification["error"]({
message: t("joblines.errors.saving", {
message: JSON.stringify(lineUpdate.errors),
}),
});
return;
}
insertAuditTrail({
jobid: values.jobid,
operation: AuditTrailMapping.jobmodifylbradj(),
});
setLoading(false);
setVisibility(false);
};
const overlay = (
<Card>
<Form form={form} layout="vertical" onFinish={handleFinish}>
<Form.Item
label={t("joblines.fields.mod_lbr_ty")}
name="mod_lbr_ty"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select allowClear optionFilterProp="children" showSearch>
<Select.Option value="LAA">
{t("joblines.fields.lbr_types.LAA")}
</Select.Option>
<Select.Option value="LAB">
{t("joblines.fields.lbr_types.LAB")}
</Select.Option>
<Select.Option value="LAD">
{t("joblines.fields.lbr_types.LAD")}
</Select.Option>
<Select.Option value="LAE">
{t("joblines.fields.lbr_types.LAE")}
</Select.Option>
<Select.Option value="LAF">
{t("joblines.fields.lbr_types.LAF")}
</Select.Option>
<Select.Option value="LAG">
{t("joblines.fields.lbr_types.LAG")}
</Select.Option>
<Select.Option value="LAM">
{t("joblines.fields.lbr_types.LAM")}
</Select.Option>
<Select.Option value="LAR">
{t("joblines.fields.lbr_types.LAR")}
</Select.Option>
<Select.Option value="LAS">
{t("joblines.fields.lbr_types.LAS")}
</Select.Option>
<Select.Option value="LAU">
{t("joblines.fields.lbr_types.LAU")}
</Select.Option>
<Select.Option value="LA1">
{t("joblines.fields.lbr_types.LA1")}
</Select.Option>
<Select.Option value="LA2">
{t("joblines.fields.lbr_types.LA2")}
</Select.Option>
<Select.Option value="LA3">
{t("joblines.fields.lbr_types.LA3")}
</Select.Option>
<Select.Option value="LA4">
{t("joblines.fields.lbr_types.LA4")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item shouldUpdate>
{() => {
const { mod_lbr_ty } = form.getFieldsValue();
return t("joblines.labels.adjustmenttobeadded", {
adjustment: calculateAdjustment({
mod_lbr_ty,
job,
jobline,
}).toFixed(1),
});
}}
</Form.Item>
<Space wrap>
<Button
type="primary"
disabled={jobline.convertedtolbr}
htmlType="submit"
>
{t("general.actions.save")}
</Button>
<Button onClick={() => setVisibility(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
</Card>
);
const handleClick = (e) => {
setLoading(true);
form.setFieldsValue({
// date: new moment(),
// bodyhrs: Math.round(v.bodyhrs * 10) / 10,
// painthrs: Math.round(v.painthrs * 10) / 10,
});
setVisibility(true);
setLoading(false);
};
return (
<Popover
disabled={jobline.convertedtolbr}
content={overlay}
visible={visibility}
placement="bottom"
>
<Tooltip title={t("joblines.actions.converttolabor")}>
<Button
disabled={jobline.convertedtolbr}
loading={loading}
onClick={handleClick}
{...otherBtnProps}
>
<ClockCircleOutlined />
</Button>
</Tooltip>
</Popover>
);
}
function calculateAdjustment({ mod_lbr_ty, job, jobline }) {
if (!mod_lbr_ty) return 0;
const rate = job[`rate_${mod_lbr_ty.toLowerCase()}`];
if (rate === 0 || rate === null || rate === undefined) return 0;
const adj = jobline.act_price / job[`rate_${mod_lbr_ty.toLowerCase()}`];
return adj;
}

View File

@@ -1,4 +1,4 @@
import { Form, Select } from "antd"; import { Form, Select, Space, Tooltip } 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";
@@ -8,6 +8,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import LaborTypeFormItem from "../form-items-formatted/labor-type-form-item.component"; import LaborTypeFormItem from "../form-items-formatted/labor-type-form-item.component";
import PartTypeFormItem from "../form-items-formatted/part-type-form-item.component"; import PartTypeFormItem from "../form-items-formatted/part-type-form-item.component";
import ReadOnlyFormItem from "../form-items-formatted/read-only-form-item.component"; import ReadOnlyFormItem from "../form-items-formatted/read-only-form-item.component";
import { WarningOutlined } from "@ant-design/icons";
import "./jobs-close-lines.styles.scss"; import "./jobs-close-lines.styles.scss";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -62,14 +63,23 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
</Form.Item> </Form.Item>
</td> </td>
<td> <td>
<Form.Item <Space>
span={2} <Form.Item
// label={t("joblines.fields.act_price")} span={2}
key={`${index}act_price`} // label={t("joblines.fields.act_price")}
name={[field.name, "act_price"]} key={`${index}act_price`}
> name={[field.name, "act_price"]}
<ReadOnlyFormItem type="currency" /> >
</Form.Item> <ReadOnlyFormItem type="currency" />
</Form.Item>
<Form.Item
noStyle
key={`${index}convertedtolbr`}
name={[field.name, "convertedtolbr"]}
>
<HasBeenConvertedTolabor />
</Form.Item>
</Space>
</td> </td>
<td> <td>
<Form.Item <Form.Item
@@ -192,3 +202,14 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseLines); export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseLines);
const HasBeenConvertedTolabor = ({ value }) => {
const { t } = useTranslation();
console.log(value);
if (!value) return null;
return (
<Tooltip title={t("joblines.labels.convertedtolabor")}>
<WarningOutlined style={{ color: "tomato" }} />
</Tooltip>
);
};

View File

@@ -2,7 +2,6 @@ import { PauseCircleOutlined } from "@ant-design/icons";
import { Space } from "antd"; import { Space } from "antd";
import i18n from "i18next"; import i18n from "i18next";
import moment from "moment"; import moment from "moment";
import React from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { TimeFormatter } from "../../utils/DateFormatter"; import { TimeFormatter } from "../../utils/DateFormatter";
@@ -530,6 +529,27 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
<JobPartsQueueCount parts={record.joblines_status} record={record} /> <JobPartsQueueCount parts={record.joblines_status} record={record} />
), ),
}, },
//Added as a place holder for St Claude. Not implemented as it requires another join for a field used by only 1 client.
// {
// title: i18n.t("vehicles.fields.v_paint_codes", { number: "" }),
// dataIndex: "v_paint_codes",
// key: "v_paint_codes",
// render: (text, record) =>
// record.vehicle?.v_paint_codes ? (
// <span style={{ whiteSpace: "pre" }}>
// {Object.keys(record.vehicle.v_paint_codes)
// .filter(
// (key) =>
// record.vehicle.v_paint_codes[key] !== "" &&
// record.vehicle.v_paint_codes[key] !== null &&
// record.vehicle.v_paint_codes[key] !== undefined
// )
// .map((key, idx) => (
// <Tag key={idx}>{record.vehicle.v_paint_codes[key]}</Tag>
// ))}
// </span>
// ) : null,
// },
]; ];
}; };
export default r; export default r;

View File

@@ -177,6 +177,7 @@ export const UPDATE_JOB_LINE = gql`
location location
status status
removed removed
convertedtolbr
} }
} }
} }

View File

@@ -702,6 +702,7 @@ export const GET_JOB_BY_PK = gql`
prt_dsmk_p prt_dsmk_p
prt_dsmk_m prt_dsmk_m
ioucreated ioucreated
convertedtolbr
billlines(limit: 1, order_by: { bill: { date: desc } }) { billlines(limit: 1, order_by: { bill: { date: desc } }) {
id id
quantity quantity
@@ -1905,6 +1906,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
profitcenter_labor profitcenter_labor
profitcenter_part profitcenter_part
prt_dsmk_p prt_dsmk_p
convertedtolbr
} }
} }
} }

View File

@@ -1114,6 +1114,7 @@
}, },
"joblines": { "joblines": {
"actions": { "actions": {
"converttolabor": "Convert amount to Labor.",
"new": "New Line" "new": "New Line"
}, },
"errors": { "errors": {
@@ -1179,7 +1180,9 @@
"unq_seq": "Seq #" "unq_seq": "Seq #"
}, },
"labels": { "labels": {
"adjustmenttobeadded": "Adjustment to be added: {{adjustment}}",
"billref": "Latest Bill", "billref": "Latest Bill",
"convertedtolabor": "This line has been converted to labor. Ensure you adjust the profit center for the amount accordingly.",
"edit": "Edit Line", "edit": "Edit Line",
"ioucreated": "IOU", "ioucreated": "IOU",
"new": "New Line", "new": "New Line",

View File

@@ -1114,6 +1114,7 @@
}, },
"joblines": { "joblines": {
"actions": { "actions": {
"converttolabor": "",
"new": "" "new": ""
}, },
"errors": { "errors": {
@@ -1179,7 +1180,9 @@
"unq_seq": "Seq #" "unq_seq": "Seq #"
}, },
"labels": { "labels": {
"adjustmenttobeadded": "",
"billref": "", "billref": "",
"convertedtolabor": "",
"edit": "Línea de edición", "edit": "Línea de edición",
"ioucreated": "", "ioucreated": "",
"new": "Nueva línea", "new": "Nueva línea",

View File

@@ -1114,6 +1114,7 @@
}, },
"joblines": { "joblines": {
"actions": { "actions": {
"converttolabor": "",
"new": "" "new": ""
}, },
"errors": { "errors": {
@@ -1179,7 +1180,9 @@
"unq_seq": "Seq #" "unq_seq": "Seq #"
}, },
"labels": { "labels": {
"adjustmenttobeadded": "",
"billref": "", "billref": "",
"convertedtolabor": "",
"edit": "Ligne d'édition", "edit": "Ligne d'édition",
"ioucreated": "", "ioucreated": "",
"new": "Nouvelle ligne", "new": "Nouvelle ligne",

View File

@@ -111,15 +111,16 @@ export default async function RenderTemplate(
}; };
const pdfRender = await jsreport.render(pdfRequest); const pdfRender = await jsreport.render(pdfRequest);
pdf = pdfRender.toDataURI(); pdf = await pdfRender.toDataURI();
} }
const html = await render.toString();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
resolve({ resolve({
pdf, pdf,
filename: filename:
Templates[templateObject.name] && Templates[templateObject.name] &&
Templates[templateObject.name].title, Templates[templateObject.name].title,
html: render.toString(), html,
}); });
}); });
} }
@@ -255,9 +256,7 @@ export async function RenderTemplates(
if (!renderAsHtml) { if (!renderAsHtml) {
render.download("Speed Print"); render.download("Speed Print");
} else { } else {
return new Promise((resolve, reject) => { return render.toString();
resolve(render.toString());
});
} }
} catch (error) { } catch (error) {
notification["error"]({ message: JSON.stringify(error) }); notification["error"]({ message: JSON.stringify(error) });

View File

@@ -2384,6 +2384,7 @@
- bett_tax - bett_tax
- bett_type - bett_type
- cert_part - cert_part
- convertedtolbr
- created_at - created_at
- db_hrs - db_hrs
- db_price - db_price
@@ -2447,6 +2448,7 @@
- bett_tax - bett_tax
- bett_type - bett_type
- cert_part - cert_part
- convertedtolbr
- created_at - created_at
- db_hrs - db_hrs
- db_price - db_price
@@ -2521,6 +2523,7 @@
- bett_tax - bett_tax
- bett_type - bett_type
- cert_part - cert_part
- convertedtolbr
- created_at - created_at
- db_hrs - db_hrs
- db_price - db_price

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"."joblines" add column "convertedtolbr" boolean
-- not null default 'false';

View File

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

View File

@@ -198,7 +198,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
if (!job.job_totals) { if (!job.job_totals) {
errorCallback({ errorCallback({
jobid: jobid, jobid: job.id,
ro_number: job.ro_number, ro_number: job.ro_number,
error: { toString: () => "No job totals for RO." }, error: { toString: () => "No job totals for RO." },
}); });
@@ -674,7 +674,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
error, error,
}); });
errorCallback({ jobid: jobid, ro_number: job.ro_number, error }); errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
} }
}; };

View File

@@ -257,12 +257,14 @@ exports.validateFirebaseIdToken = async (req, res, next) => {
return; return;
} catch (error) { } catch (error) {
logger.log("api-unauthorized-call", "WARN", null, null, { logger.log("api-unauthorized-call", "WARN", null, null, {
req, path: req.path,
body: req.body,
type: "unauthroized", type: "unauthroized",
error, ...error,
}); });
res.status(403).send("Unauthorized"); res.status(401).send("Unauthorized");
return; return;
} }
}; };

View File

@@ -32,6 +32,8 @@ exports.mixdataUpload = async (req, res) => {
explicitArray: false, explicitArray: false,
}); });
logger.log("job-mixdata-parse", "DEBUG", req.user.email, inboundRequest);
const ScaleType = DetermineScaleType(inboundRequest); const ScaleType = DetermineScaleType(inboundRequest);
const RoNumbersFromInboundRequest = GetListOfRos( const RoNumbersFromInboundRequest = GetListOfRos(
inboundRequest, inboundRequest,
@@ -76,6 +78,7 @@ exports.mixdataUpload = async (req, res) => {
res.status(500).JSON(error); res.status(500).JSON(error);
logger.log("job-mixdata-upload-error", "ERROR", null, null, { logger.log("job-mixdata-upload-error", "ERROR", null, null, {
error: error.message, error: error.message,
...error,
}); });
} }
}; };